Skip to content

Commit

Permalink
feat(entity-generator): allow skipping some tables or columns
Browse files Browse the repository at this point in the history
Closes #4584
  • Loading branch information
B4nan committed Aug 27, 2023
1 parent 040896e commit e603108
Show file tree
Hide file tree
Showing 7 changed files with 907 additions and 7 deletions.
18 changes: 18 additions & 0 deletions docs/docs/entity-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ By default, the `EntityGenerator` generates only owning sides of relations (e.g.
- `identifiedReferences` to generate M:1 and 1:1 relations as wrapped references
- `entitySchema` to generate the entities using `EntitySchema` instead of decorators
- `esmImport` to use esm style import for imported entities e.x. when `esmImport=true`, generated entities include `import Author from './Author.js'`
- `skipTables` to ignore some database tables (accepts array of table names)
- `skipColumns` to ignore some database tables columns (accepts an object, keys are table names with schema prefix if available, values are arrays of column names)

```ts
const dump = await orm.entityGenerator.generate({
save: true,
entitySchema: true,
bidirectionalRelations: true,
identifiedReferences: true,
esmImport: true,
baseDir: process.cwd() + '/my-entities',
skipTables: ['book', 'author'],
skipColumns: {
'public.user': ['email', 'middle_name'],
},
});

```

## Current limitations

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
export {
Constructor, ConnectionType, Dictionary, PrimaryKeyType, PrimaryKeyProp, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, EntityName, EntityData, Highlighter,
AnyEntity, EntityClass, EntityProperty, EntityMetadata, QBFilterQuery, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator,
GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff,
GetRepository, EntityRepositoryType, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, MigrationDiff, GenerateOptions,
IEntityGenerator, ISeedManager, EntityClassGroup, OptionalProps, RequiredEntityData, CheckCallback, SimpleColumnMeta, Rel, Ref,
} from './typings';
export * from './enums';
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,8 +583,16 @@ export interface ISchemaGenerator {
clearDatabase(options?: { schema?: string }): Promise<void>;
}

export interface GenerateOptions {
baseDir?: string;
save?: boolean;
schema?: string;
skipTables?: string[];
skipColumns?: Record<string, string[]>;
}

export interface IEntityGenerator {
generate(options?: { baseDir?: string; save?: boolean; schema?: string }): Promise<string[]>;
generate(options?: GenerateOptions): Promise<string[]>;
}

type UmzugMigration = { name: string; path?: string };
Expand Down
37 changes: 32 additions & 5 deletions packages/entity-generator/src/EntityGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ensureDir, writeFile } from 'fs-extra';
import type { EntityProperty, EntityMetadata, MikroORM } from '@mikro-orm/core';
import type { EntityMetadata, EntityProperty, GenerateOptions, MikroORM } from '@mikro-orm/core';
import { ReferenceType, Utils } from '@mikro-orm/core';
import type { EntityManager } from '@mikro-orm/knex';
import { DatabaseSchema } from '@mikro-orm/knex';
Expand All @@ -22,14 +22,36 @@ export class EntityGenerator {
orm.config.registerExtension('@mikro-orm/entity-generator', new EntityGenerator(orm.em as EntityManager));
}

async generate(options: { baseDir?: string; save?: boolean; schema?: string } = {}): Promise<string[]> {
async generate(options: GenerateOptions = {}): Promise<string[]> {
const baseDir = Utils.normalizePath(options.baseDir ?? (this.config.get('baseDir') + '/generated-entities'));
const schema = await DatabaseSchema.create(this.connection, this.platform, this.config);

const metadata = schema.getTables()
let metadata = schema.getTables()
.filter(table => !options.schema || table.schema === options.schema)
.sort((a, b) => a.name!.localeCompare(b.name!))
.map(table => table.getEntityDeclaration(this.namingStrategy, this.helper));
.map(table => {
const skipColumns = options.skipColumns?.[table.getShortestName()];
if (skipColumns) {
table.getColumns().forEach(col => {
if (skipColumns.includes(col.name)) {
table.removeColumn(col.name);
}
});
}
return table.getEntityDeclaration(this.namingStrategy, this.helper);
});

for (const meta of metadata) {
for (const prop of meta.relations) {
if (options.skipTables?.includes(prop.referencedTableName)) {
prop.reference = ReferenceType.SCALAR;
const meta2 = metadata.find(m => m.className === prop.type)!;
prop.type = meta2.getPrimaryProps().map(pk => pk.type).join(' | ');
}
}
}

metadata = metadata.filter(table => !options.skipTables || !options.skipTables.includes(table.tableName));

this.detectManyToManyRelations(metadata);

Expand Down Expand Up @@ -71,7 +93,12 @@ export class EntityGenerator {
meta.relations.every(prop => prop.reference === ReferenceType.MANY_TO_ONE) // all relations are m:1
) {
meta.pivotTable = true;
const owner = metadata.find(m => m.className === meta.relations[0].type)!;
const owner = metadata.find(m => m.className === meta.relations[0].type);

if (!owner) {
continue;
}

const name = this.namingStrategy.columnNameToProperty(meta.tableName.replace(new RegExp('^' + owner.tableName + '_'), ''));
owner.addProperty({
name,
Expand Down
4 changes: 4 additions & 0 deletions packages/knex/src/schema/DatabaseTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class DatabaseTable {
return this.columns[name];
}

removeColumn(name: string): void {
delete this.columns[name];
}

getIndexes(): Index[] {
return this.indexes;
}
Expand Down
32 changes: 32 additions & 0 deletions tests/features/entity-generator/EntityGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ describe('EntityGenerator', () => {
await orm.close(true);
});

test('skipTables [mysql]', async () => {
const orm = await initORMMySql('mysql', {}, true);
const dump = await orm.entityGenerator.generate({
save: true,
baseDir: './temp/entities',
skipTables: ['test2', 'test2_bars'],
skipColumns: { book2: ['price'] },
});
expect(dump).toMatchSnapshot('mysql-entity-dump-skipTables');
await expect(pathExists('./temp/entities/Author2.ts')).resolves.toBe(true);
await remove('./temp/entities');

await orm.schema.dropDatabase();
await orm.close(true);
});

test('generate entities with bidirectional relations [mysql]', async () => {
const orm = await initORMMySql('mysql', { entityGenerator: { bidirectionalRelations: true } }, true);
const dump = await orm.entityGenerator.generate({ save: true, baseDir: './temp/entities' });
Expand Down Expand Up @@ -137,6 +153,22 @@ describe('EntityGenerator', () => {
await orm.close(true);
});

test('skipTables [postgres]', async () => {
const orm = await initORMPostgreSql();
const dump = await orm.entityGenerator.generate({
save: true,
baseDir: './temp/entities',
skipTables: ['test2', 'test2_bars'],
skipColumns: { 'public.book2': ['price'] },
});
expect(dump).toMatchSnapshot('postgres-entity-dump-skipTables');
await expect(pathExists('./temp/entities/Author2.ts')).resolves.toBe(true);
await remove('./temp/entities');

await orm.schema.dropDatabase();
await orm.close(true);
});

test('not supported [mongodb]', async () => {
const orm = await MikroORM.init({ driver: MongoDriver, dbName: 'mikro-orm-test', discovery: { warnWhenNoEntities: false } }, false);
expect(() => orm.entityGenerator).toThrowError('MongoPlatform does not support EntityGenerator');
Expand Down
Loading

0 comments on commit e603108

Please sign in to comment.