diff --git a/src/dal/dao/indexes/indexes.types.ts b/src/dal/dao/indexes/indexes.types.ts new file mode 100644 index 000000000..cae5ad35e --- /dev/null +++ b/src/dal/dao/indexes/indexes.types.ts @@ -0,0 +1,35 @@ +import { Update } from '../schemas/ast.types' +import { AbstractSyntaxTree, AbstractScalars } from '../schemas/ast.types' +import { CreateIndexesOptions, IndexDirection } from 'mongodb' + +type MongoDBIndex = { + name: string + specs: { [F in keyof (K extends string ? Update : never)]?: IndexDirection } + opts?: Omit +} + +type MongoEntities = { + [K in keyof AST]: AST[K]['driverSpecification']['type'] extends 'mongodb' ? K : never +}[keyof AST] + +export type MongoDBIndexes = { [K in MongoEntities]: MongoDBIndex[] } +export type Indexes = { + mongodb?: Partial> +} + +export type IndexesPlanResults = { + mongodb: { + toDelete: { collection: string; name: string }[] + toCreate: { collection: string; name: string; specs: { [K in string]: IndexDirection }; opts?: Omit }[] + } + //knexjs: never +} + +export type IndexesApplyResults = { + mongodb: { + deleted: { collection: string; name: string }[] + created: { collection: string; name: string; specs: { [K in string]: IndexDirection }; opts?: Omit }[] + failed: { collection: string; name: string; specs: { [K in string]: IndexDirection }; opts?: Omit; error: unknown }[] + } + //knexjs: never +} diff --git a/src/dal/dao/schemas/ast.types.ts b/src/dal/dao/schemas/ast.types.ts index 2353201f2..c001b6caf 100644 --- a/src/dal/dao/schemas/ast.types.ts +++ b/src/dal/dao/schemas/ast.types.ts @@ -27,6 +27,7 @@ export type AbstractSyntaxTree driverSpecification: { + type: 'mongodb' | 'knexjs' | 'memory' rawFilter: unknown rawUpdate: unknown rawSorts: unknown diff --git a/src/dal/dao/schemas/schemas.types.ts b/src/dal/dao/schemas/schemas.types.ts index 4905c9240..4b7b32bdd 100644 --- a/src/dal/dao/schemas/schemas.types.ts +++ b/src/dal/dao/schemas/schemas.types.ts @@ -46,4 +46,4 @@ export type SchemaField = Readonly< Decorators > -export type Schema = Readonly<{ [key: string]: SchemaField }> +export type Schema = Readonly<{ [key: string]: SchemaField }> diff --git a/src/dal/entity-manager.ts b/src/dal/entity-manager.ts index 90f47b068..0bf2482a1 100644 --- a/src/dal/entity-manager.ts +++ b/src/dal/entity-manager.ts @@ -1,4 +1,5 @@ -import { AbstractScalars } from '..' +import { AbstractScalars, equals, Indexes, IndexesApplyResults, IndexesPlanResults, Schema } from '..' +import { toFirstLower } from '../generation/utils' import { AbstractDAO } from './dao/dao' import { DAOGenerics, TransactionData } from './dao/dao.types' import { DriverDataTypeAdapterMap } from './drivers/drivers.adapters' @@ -7,7 +8,7 @@ import { inMemoryAdapters } from './drivers/in-memory/adapters.memory' import { mongoDbAdapters } from './drivers/no-sql/mongodb/adapters.mongodb' import { knexJsAdapters } from './drivers/sql/knexjs/adapters.knexjs' import { Knex } from 'knex' -import { ClientSession } from 'mongodb' +import { ClientSession, Collection, CreateIndexesOptions, IndexDirection } from 'mongodb' export abstract class AbstractEntityManager< MongoDBDatasources extends string, @@ -40,6 +41,103 @@ export abstract class AbstractEntityManager< return this.transactionData !== null } + public abstract execQuery(run: (dbs: Record, entities: Record) => Promise): Promise + + protected async planIndexes(args: { indexes: Indexes }, schemas?: Record Schema>): Promise { + function getDbName(field: string, schema: Schema): string { + const [head, ...tail] = field.split('.') + const schemaField = schema[head] + const name = schemaField.alias ?? head + return tail.length > 0 && schemaField.type === 'embedded' ? `${name}.${getDbName(tail.join('.'), schemaField.schema())}` : name + } + if (!schemas) { + throw new Error('schemas is required') + } + const result: IndexesPlanResults = { mongodb: { toCreate: [], toDelete: [] } } + await this.execQuery(async (dbs, collections) => { + for (const [name, indexes] of Object.entries(args.indexes.mongodb ?? {})) { + if (!indexes) continue + const key = toFirstLower(name) + const collection = collections[key] as Collection + if (collection) { + // tricky way to create collection + const inserted = await collection.insertOne({ empty: true }) + await collection.deleteOne({ _id: inserted.insertedId }) + const schema = schemas[name]() + const loadedIndexes = await collection.indexes() + const indexesThatAreEquals = loadedIndexes.filter((li) => { + const index = (indexes as Record[]).find((i) => li.name === i.name) + if (!index) { + return false + } + const li2 = { ...li } + delete li2.v + delete li2.background + delete li2.key + delete li2.name + const specs = Object.fromEntries( + Object.entries(index.specs as Record).map(([field, value]) => { + const dbName = getDbName(field, schema) + return [dbName, value] + }), + ) + if (equals(specs, li.key) && equals(index.opts ?? {}, li2)) { + return true + } + return false + }) + //same name but not equals + const indexesToDrop = loadedIndexes.filter((li) => li.name !== '_id_' && !indexesThatAreEquals.find((i) => li.name === i.name)) + const indexesToCreate = (indexes as Record[]).filter((i) => !indexesThatAreEquals.find((li) => li.name === i.name)) + for (const index of indexesToDrop) { + result.mongodb.toDelete.push({ collection: key, name: index.name }) + } + for (const index of indexesToCreate) { + const specs = Object.fromEntries( + Object.entries(index.specs as Record).map(([field, value]) => { + const dbName = getDbName(field, schema) + return [dbName, value] + }), + ) + result.mongodb.toCreate.push({ + collection: key, + name: index.name as string, + specs: specs as Record, + opts: index.opts as Omit | undefined, + }) + } + } + } + }) + return result + } + + protected async applyIndexes(args: ({ plan: IndexesPlanResults } | { indexes: Indexes }) & { logs?: boolean }, schemas?: Record Schema>): Promise { + const plan: IndexesPlanResults = 'plan' in args ? args.plan : await this.planIndexes(args, schemas) + const results: IndexesApplyResults = { mongodb: { created: [], failed: [], deleted: [] } } + await this.execQuery(async (dbs, collections) => { + for (const index of plan.mongodb.toDelete) { + const collection = collections[index.collection] as Collection + if (!collection) continue + if (args.logs) console.log(`Dropping ${index.name} index of collection ${index.collection}...`) + await collection.dropIndex(index.name) + results.mongodb.deleted.push({ ...index }) + } + for (const index of plan.mongodb.toCreate) { + try { + const collection = collections[index.collection] as Collection + if (!collection) continue + if (args.logs) console.log(`Creating ${index.name} index of collection ${index.collection}...`) + await collection.createIndex(index.specs, { ...(index.opts ?? {}), name: index.name }) + results.mongodb.created.push({ ...index }) + } catch (error) { + results.mongodb.failed.push({ ...index, error }) + } + } + }) + return results + } + public getMongoSession(datasource: string): ClientSession | undefined { return ((this.transactionData?.mongodb ?? {}) as Record)[datasource] } diff --git a/src/generation/generators/dao-generator.ts b/src/generation/generators/dao-generator.ts index 3cd19ebff..c865618ce 100644 --- a/src/generation/generators/dao-generator.ts +++ b/src/generation/generators/dao-generator.ts @@ -252,6 +252,7 @@ export class TsTypettaGenerator extends TypettaGenerator { return `${node.name}: { fields: { ${node.fields.map((f) => generateASTNodes(f)).join(',\n')} }, driverSpecification: { + type: '${node.entity?.type === 'mongo' ? 'mongodb' : node.entity?.type === 'sql' ? 'knexjs' : 'memory'}', rawFilter: ${ node.entity?.type === 'mongo' ? '() => M.Filter' : node.entity?.type === 'sql' ? '(builder: Knex.QueryBuilder) => Knex.QueryBuilder' : 'never' }, @@ -326,6 +327,14 @@ export class TsTypettaGenerator extends TypettaGenerator { .join(', ') const execQueryF = `public async execQuery(run: (dbs: { ${dbsInputParam} }, entities: { ${entitiesInputParam} }) => Promise): Promise {\n` + ` return run({ ${dbsParam} }, { ${entitiesParam} })\n` + `}` + const applyIndexesF = ` + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + }` const cloneF = `public clone(): this {\n return new EntityManager(this.params) as this\n}` const createTableBody = Array.from(typesMap.values()) .flatMap((node) => { @@ -412,6 +421,7 @@ export type EntityManagerParams + mongodbIndexes: T.MongoDBIndexes }` return [[entityManagerParamsExport, entityManagerMiddleware, entityManagerExport, utilsCode, utilsType].join('\n')].join('\n') diff --git a/src/index.ts b/src/index.ts index 38348e763..c91f5051f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,3 +41,4 @@ export * from './dal/drivers/in-memory/utils.memory' export * from './dal/drivers/in-memory/dao.memory' export * from './dal/drivers/in-memory/adapters.memory' export * from './dal/drivers/in-memory/dao.memory.types' +export * from './dal/dao/indexes/indexes.types' diff --git a/tests/id-generator/dao.mock.ts b/tests/id-generator/dao.mock.ts index 7fbe0ec7d..dfbf4c17c 100644 --- a/tests/id-generator/dao.mock.ts +++ b/tests/id-generator/dao.mock.ts @@ -22,6 +22,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -33,6 +34,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -44,6 +46,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -55,6 +58,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -66,6 +70,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -77,6 +82,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -827,6 +833,14 @@ export class EntityManager< ) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -920,4 +934,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes } diff --git a/tests/in-memory/dao.mock.ts b/tests/in-memory/dao.mock.ts index 8462f13cc..f88fbaa72 100644 --- a/tests/in-memory/dao.mock.ts +++ b/tests/in-memory/dao.mock.ts @@ -23,6 +23,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'generator' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -35,6 +36,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'db' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -61,6 +63,7 @@ export type AST = { } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -83,6 +86,7 @@ export type AST = { } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -97,6 +101,7 @@ export type AST = { name: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -112,6 +117,7 @@ export type AST = { opt2: { type: 'scalar'; isList: false; astName: 'Live'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'generator' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -125,6 +131,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -138,6 +145,7 @@ export type AST = { ownerId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -150,6 +158,7 @@ export type AST = { name: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -163,6 +172,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -177,6 +187,7 @@ export type AST = { vatNumber: { type: 'scalar'; isList: false; astName: 'String'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -195,6 +206,7 @@ export type AST = { views: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -206,6 +218,7 @@ export type AST = { visible: { type: 'scalar'; isList: false; astName: 'Boolean'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -217,6 +230,7 @@ export type AST = { name: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -229,6 +243,7 @@ export type AST = { total: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -241,6 +256,7 @@ export type AST = { quota: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -282,6 +298,7 @@ export type AST = { } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -293,6 +310,7 @@ export type AST = { username: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -2216,6 +2234,14 @@ export class EntityManager< return run({}, {}) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -2315,4 +2341,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes } diff --git a/tests/mongodb/dao.mock.ts b/tests/mongodb/dao.mock.ts index 1c5343d83..a2566904a 100644 --- a/tests/mongodb/dao.mock.ts +++ b/tests/mongodb/dao.mock.ts @@ -25,6 +25,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'generator' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -37,6 +38,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'db' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -63,6 +65,7 @@ export type AST = { } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -77,6 +80,7 @@ export type AST = { name: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -92,6 +96,7 @@ export type AST = { opt2: { type: 'scalar'; isList: false; astName: 'Live'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'generator' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -105,6 +110,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -118,6 +124,7 @@ export type AST = { ownerId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -133,6 +140,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -144,6 +152,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -155,6 +164,7 @@ export type AST = { value: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -166,6 +176,7 @@ export type AST = { user: { type: 'relation'; relation: 'inner'; isList: false; astName: 'User'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -174,6 +185,7 @@ export type AST = { EmbeddedUser5: { fields: { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -193,6 +205,7 @@ export type AST = { users: { type: 'embedded'; isList: false; astName: 'UserCollection'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -206,6 +219,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -220,6 +234,7 @@ export type AST = { vatNumber: { type: 'scalar'; isList: false; astName: 'String'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -237,6 +252,7 @@ export type AST = { views: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -248,6 +264,7 @@ export type AST = { visible: { type: 'scalar'; isList: false; astName: 'Boolean'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -260,6 +277,7 @@ export type AST = { name: { type: 'scalar'; isList: false; astName: 'String'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -303,6 +321,7 @@ export type AST = { } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -314,6 +333,7 @@ export type AST = { usersId: { type: 'scalar'; isList: true; astName: 'ID'; isRequired: true; isListElementRequired: true; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -326,6 +346,7 @@ export type AST = { username: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -2444,6 +2465,14 @@ export class EntityManager< ) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -2540,4 +2569,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes } diff --git a/tests/mongodb/mongodb.test.ts b/tests/mongodb/mongodb.test.ts index 51438290a..e4f58134a 100644 --- a/tests/mongodb/mongodb.test.ts +++ b/tests/mongodb/mongodb.test.ts @@ -1865,9 +1865,24 @@ test('Text filter test', async () => { const found7 = (await dao.organization.findAll({ filter: { name: { startsWith: 'Mic', endsWith: 'oft', mode: 'sensitive' } } })).map((o) => o.name) const found8 = (await dao.organization.findAll({ filter: { name: { startsWith: 'mic', endsWith: 'oft', mode: 'insensitive' } } })).map((o) => o.name) - await dao.execQuery(async (dbs, entities) => { - await entities.organization?.createIndex({ name: 'text' }, { name: 'nameIndex' }) + const plan = await dao.planIndexes({ + indexes: { + mongodb: { + Organization: [{ name: 'nameIndex', specs: { name: 'text' } }], + }, + }, + }) + const result = await dao.applyIndexes({ plan }) + expect(result.mongodb.created[0].name).toBe('nameIndex') + const result1 = await dao.applyIndexes({ + indexes: { + mongodb: { + Organization: [{ name: 'nameIndex2', specs: { name: 'text' } }], + }, + }, }) + expect(result1.mongodb.created[0].name).toBe('nameIndex2') + expect(result1.mongodb.deleted[0].name).toBe('nameIndex') const found10 = (await dao.organization.findAll({ filter: () => ({ $text: { $search: 'Microsoft' } }), sorts: () => [['score', { $meta: 'textScore' }]] })).map((o) => o.name) expect(found1.length).toBe(2) diff --git a/tests/multitenant/dao.mock.ts b/tests/multitenant/dao.mock.ts index 921b9a751..5c50644c7 100644 --- a/tests/multitenant/dao.mock.ts +++ b/tests/multitenant/dao.mock.ts @@ -25,6 +25,7 @@ export type AST = { tenantId: { type: 'scalar'; isList: false; astName: 'TenantId'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'middleware' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -40,6 +41,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -55,6 +57,7 @@ export type AST = { tenantId: { type: 'scalar'; isList: false; astName: 'TenantId'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'middleware' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -66,6 +69,7 @@ export type AST = { info: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -102,6 +106,7 @@ export type AST = { tenantId: { type: 'scalar'; isList: false; astName: 'TenantId'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'middleware' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -113,6 +118,7 @@ export type AST = { username: { type: 'scalar'; isList: false; astName: 'Username'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -930,6 +936,14 @@ export class EntityManager< ) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -1012,4 +1026,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes } diff --git a/tests/resolvers/dao.mock.ts b/tests/resolvers/dao.mock.ts index e371cfc88..3a95523b2 100644 --- a/tests/resolvers/dao.mock.ts +++ b/tests/resolvers/dao.mock.ts @@ -19,6 +19,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -30,6 +31,7 @@ export type AST = { views: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -55,6 +57,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -80,6 +83,7 @@ export type AST = { posts: { type: 'relation'; relation: 'foreign'; isList: true; astName: 'Post'; isRequired: false; isListElementRequired: true; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -541,6 +545,14 @@ export class EntityManager< return run({}, {}) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -618,4 +630,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes } diff --git a/tests/security/dao.mock.ts b/tests/security/dao.mock.ts index 6c0306c80..a5fd5ec89 100644 --- a/tests/security/dao.mock.ts +++ b/tests/security/dao.mock.ts @@ -26,6 +26,7 @@ export type AST = { totalCustomers: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -38,6 +39,7 @@ export type AST = { multiReservationId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -61,6 +63,7 @@ export type AST = { tenantId: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -76,6 +79,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -87,6 +91,7 @@ export type AST = { permissions: { type: 'scalar'; isList: true; astName: 'Permission'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -103,6 +108,7 @@ export type AST = { to: { type: 'scalar'; isList: false; astName: 'Date'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -117,6 +123,7 @@ export type AST = { tenantId2: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -131,6 +138,7 @@ export type AST = { tenantId2: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -167,6 +175,7 @@ export type AST = { totalPayments: { type: 'scalar'; isList: false; astName: 'Int'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -183,6 +192,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'mongodb' rawFilter: () => M.Filter rawUpdate: () => M.UpdateFilter rawSorts: () => M.Sort @@ -1728,6 +1738,14 @@ export class EntityManager< ) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -1820,4 +1838,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes } diff --git a/tests/sql/dao.mock.ts b/tests/sql/dao.mock.ts index 4540b0a45..68c59e69f 100644 --- a/tests/sql/dao.mock.ts +++ b/tests/sql/dao.mock.ts @@ -23,6 +23,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'generator' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -31,6 +32,7 @@ export type AST = { Another: { fields: { test: { type: 'scalar'; isList: false; astName: 'String'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -52,6 +54,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'generator' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -64,6 +67,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'generator' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -85,6 +89,7 @@ export type AST = { id: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: true; generationStrategy: 'generator' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -99,6 +104,7 @@ export type AST = { name: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -114,6 +120,7 @@ export type AST = { opt2: { type: 'scalar'; isList: false; astName: 'Live'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'generator' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -127,6 +134,7 @@ export type AST = { userId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -140,6 +148,7 @@ export type AST = { ownerId: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -152,6 +161,7 @@ export type AST = { to: { type: 'scalar'; isList: false; astName: 'ID'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -166,6 +176,7 @@ export type AST = { vatNumber: { type: 'scalar'; isList: false; astName: 'String'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -218,6 +229,7 @@ export type AST = { title: { type: 'scalar'; isList: false; astName: 'LocalizedString'; isRequired: false; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'knexjs' rawFilter: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawUpdate: (builder: Knex.QueryBuilder) => Knex.QueryBuilder rawSorts: (builder: Knex.QueryBuilder) => Knex.QueryBuilder @@ -230,6 +242,7 @@ export type AST = { username: { type: 'scalar'; isList: false; astName: 'String'; isRequired: true; isListElementRequired: false; isExcluded: false; isId: false; generationStrategy: 'undefined' } } driverSpecification: { + type: 'memory' rawFilter: never rawUpdate: never rawSorts: never @@ -1918,6 +1931,14 @@ export class EntityManager< ) } + public planIndexes(args: { indexes: Partial> }): Promise { + return super.planIndexes(args, schemas) + } + + public applyIndexes(args: ({ plan: T.IndexesPlanResults } | { indexes: Partial> }) & { logs?: boolean }): Promise { + return super.applyIndexes(args, schemas) + } + public clone(): this { return new EntityManager(this.params) as this } @@ -2028,4 +2049,5 @@ export type EntityManagerTypes + mongodbIndexes: T.MongoDBIndexes }