Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mongo): add SchemaGenerator support for mongo #2658

Merged
merged 1 commit into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 6 additions & 4 deletions docs/docs/usage-with-mongo.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ are several things you need to respect:
- use `implicitTransactions: true` to enable them globally
- or use explicit transaction demarcation via `em.transactional()`
- you need to explicitly create all collections before working with them
- use `em.getDriver().createCollections()` method to do so
- use `orm.getSchemaGenerator().createSchema()` method to do so

```sh
# first create replica set
Expand All @@ -102,7 +102,7 @@ const orm = await MikroORM.init<MongoDriver>({
implicitTransactions: true, // defaults to false
});

await orm.em.getDriver().createCollections();
await orm.getSchemaGenerator().createSchema();
```

> The `createCollections` method is present on the `MongoDriver` class only. You need
Expand All @@ -124,10 +124,12 @@ const orm = await MikroORM.init<MongoDriver>({
});
```

Alternatively you can call `ensureIndexes()` method on the `MongoDriver`:
Alternatively you can call `ensureIndexes()` method on the `SchemaGenerator`:

> SchemaGenerator support for mongo was introduced in v5.

```typescript
await orm.em.getDriver().ensureIndexes();
await orm.getSchemaGenerator().ensureIndexes();
```

> You can pass additional index/unique options via `options` parameter:
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/MikroORM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Logger } from './logging';
import { Configuration, ConfigurationLoader, Utils } from './utils';
import { NullCacheAdapter } from './cache';
import type { EntityManager } from './EntityManager';
import type { AnyEntity, Constructor, IEntityGenerator, IMigrator, ISchemaGenerator, ISeedManager } from './typings';
import type { AnyEntity, Constructor, IEntityGenerator, IMigrator, ISeedManager } from './typings';
import { colors } from './logging';

/**
Expand Down Expand Up @@ -45,7 +45,7 @@ export class MikroORM<D extends IDatabaseDriver = IDatabaseDriver> {
await orm.connect();

if (orm.config.get('ensureIndexes')) {
await orm.driver.ensureIndexes();
await orm.getSchemaGenerator().ensureIndexes();
}
}

Expand Down Expand Up @@ -132,8 +132,8 @@ export class MikroORM<D extends IDatabaseDriver = IDatabaseDriver> {
/**
* Gets the SchemaGenerator.
*/
getSchemaGenerator<T extends ISchemaGenerator = ISchemaGenerator>(): T {
return this.driver.getPlatform().getSchemaGenerator(this.em) as T;
getSchemaGenerator(): ReturnType<ReturnType<D['getPlatform']>['getSchemaGenerator']> {
return this.driver.getPlatform().getSchemaGenerator(this.driver) as any;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/drivers/DatabaseDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export abstract class DatabaseDriver<C extends Connection> implements IDatabaseD
});
}

getMetadata(): MetadataStorage {
return this.metadata;
}

getDependencies(): string[] {
return this.dependencies;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/drivers/IDatabaseDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import type { MetadataStorage } from '../metadata';
import type { Collection } from '../entity';
import type { EntityManager } from '../EntityManager';
import type { DriverException } from '../exceptions';
import type { Configuration } from '../utils/Configuration';

export const EntityManagerType = Symbol('EntityManagerType');

export interface IDatabaseDriver<C extends Connection = Connection> {

[EntityManagerType]: EntityManager<this>;
readonly config: Configuration;

createEntityManager<D extends IDatabaseDriver = IDatabaseDriver>(useContext?: boolean): D[typeof EntityManagerType];

Expand Down Expand Up @@ -63,6 +65,8 @@ export interface IDatabaseDriver<C extends Connection = Connection> {

setMetadata(metadata: MetadataStorage): void;

getMetadata(): MetadataStorage;

ensureIndexes(): Promise<void>;

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/metadata/MetadataDiscovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,7 @@ export class MetadataDiscovery {
private async initEnumValues(prop: EntityProperty, path: string): Promise<void> {
path = Utils.normalizePath(this.config.get('baseDir'), path);
const exports = await Utils.dynamicImport(path);
/* istanbul ignore next */
const target = exports[prop.type] || exports.default;

if (target) {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/platforms/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { AnyEntity, Constructor, EntityProperty, IEntityGenerator, IMigrato
import { ExceptionConverter } from './ExceptionConverter';
import type { EntityManager } from '../EntityManager';
import type { Configuration } from '../utils/Configuration';
import type { IDatabaseDriver } from '../drivers/IDatabaseDriver';
import {
ArrayType, BigIntType, BlobType, BooleanType, DateType, DecimalType, DoubleType, JsonType, SmallIntType, TimeType,
TinyIntType, Type, UuidType, StringType, IntegerType, FloatType, DateTimeType, TextType, EnumType, UnknownType,
Expand Down Expand Up @@ -304,8 +305,8 @@ export abstract class Platform {
return this.exceptionConverter;
}

getSchemaGenerator(em: EntityManager): ISchemaGenerator {
throw new Error(`${this.constructor.name} does not support SchemaGenerator`);
getSchemaGenerator(driver: IDatabaseDriver): ISchemaGenerator {
throw new Error(`${driver.constructor.name} does not support SchemaGenerator`);
}

getEntityGenerator(em: EntityManager): IEntityGenerator {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ export interface ISchemaGenerator {
createDatabase(name: string): Promise<void>;
dropDatabase(name: string): Promise<void>;
execute(sql: string, options?: { wrap?: boolean }): Promise<void>;
ensureIndexes(): Promise<void>;
}

export interface IEntityGenerator {
Expand Down
103 changes: 103 additions & 0 deletions packages/core/src/utils/AbstractSchemaGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { EntityMetadata, ISchemaGenerator } from '../typings';
import { CommitOrderCalculator } from '../unit-of-work/CommitOrderCalculator';
import type { IDatabaseDriver } from '../drivers/IDatabaseDriver';

export abstract class AbstractSchemaGenerator<D extends IDatabaseDriver> implements ISchemaGenerator {

protected readonly config = this.driver.config;
protected readonly metadata = this.driver.getMetadata();
protected readonly platform: ReturnType<D['getPlatform']> = this.driver.getPlatform() as ReturnType<D['getPlatform']>;
protected readonly connection: ReturnType<D['getConnection']> = this.driver.getConnection() as ReturnType<D['getConnection']>;

constructor(protected readonly driver: D) { }

async generate(): Promise<string> {
this.notImplemented();
}

async createSchema(): Promise<void> {
this.notImplemented();
}

/**
* Returns true if the database was created.
*/
async ensureDatabase(): Promise<boolean> {
this.notImplemented();
}

async refreshDatabase(): Promise<void> {
await this.dropSchema();
await this.createSchema();
}

async getCreateSchemaSQL(): Promise<string> {
this.notImplemented();
}

async dropSchema(): Promise<void> {
this.notImplemented();
}

async getDropSchemaSQL(): Promise<string> {
this.notImplemented();
}

async updateSchema(): Promise<void> {
this.notImplemented();
}

async getUpdateSchemaSQL(): Promise<string> {
this.notImplemented();
}

async getUpdateSchemaMigrationSQL(): Promise<{ up: string; down: string }> {
this.notImplemented();
}

/**
* creates new database and connects to it
*/
async createDatabase(name: string): Promise<void> {
this.notImplemented();
}

async dropDatabase(name: string): Promise<void> {
this.notImplemented();
}

async execute(query: string) {
this.notImplemented();
}

async ensureIndexes() {
this.notImplemented();
}

protected getOrderedMetadata(schema?: string): EntityMetadata[] {
const metadata = Object.values(this.metadata.getAll()).filter(meta => {
const isRootEntity = meta.root.className === meta.className;
return isRootEntity && !meta.embeddable;
});
const calc = new CommitOrderCalculator();
metadata.forEach(meta => calc.addNode(meta.root.className));
let meta = metadata.pop();

while (meta) {
for (const prop of meta.props) {
calc.discoverProperty(prop, meta.root.className);
}

meta = metadata.pop();
}

return calc.sort()
.map(cls => this.metadata.find(cls)!)
.filter(meta => schema ? [schema, '*'].includes(meta.schema!) : meta.schema !== '*');
}

protected notImplemented(): never {
throw new Error(`This method is not supported by ${this.driver.constructor.name} driver`);
}

}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './TransactionContext';
export * from './QueryHelper';
export * from './NullHighlighter';
export * from './EntityComparator';
export * from './AbstractSchemaGenerator';
6 changes: 3 additions & 3 deletions packages/knex/src/AbstractSqlPlatform.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { escape } from 'sqlstring';
import type { Constructor, EntityManager, EntityRepository } from '@mikro-orm/core';
import type { Constructor, EntityManager, EntityRepository, IDatabaseDriver } from '@mikro-orm/core';
import { JsonProperty, Platform, Utils } from '@mikro-orm/core';
import { SqlEntityRepository } from './SqlEntityRepository';
import type { SchemaHelper } from './schema';
Expand All @@ -25,8 +25,8 @@ export abstract class AbstractSqlPlatform extends Platform {
return this.schemaHelper;
}

getSchemaGenerator(em: EntityManager): SchemaGenerator {
return new SchemaGenerator(em as any); // cast as `any` to get around circular dependencies
getSchemaGenerator(driver: IDatabaseDriver): SchemaGenerator {
return new SchemaGenerator(driver as any); // cast as `any` to get around circular dependencies
}

getEntityGenerator(em: EntityManager) {
Expand Down
54 changes: 18 additions & 36 deletions packages/knex/src/schema/SchemaGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import type { Knex } from 'knex';
import type { Dictionary, EntityMetadata } from '@mikro-orm/core';
import { CommitOrderCalculator } from '@mikro-orm/core';
import { AbstractSchemaGenerator } from '@mikro-orm/core';
import type { Column, ForeignKey, Index, SchemaDifference, TableDifference } from '../typings';
import { DatabaseSchema } from './DatabaseSchema';
import type { DatabaseTable } from './DatabaseTable';
import type { SqlEntityManager } from '../SqlEntityManager';
import type { AbstractSqlDriver } from '../AbstractSqlDriver';
import { SchemaComparator } from './SchemaComparator';
import { SqlEntityManager } from '../SqlEntityManager';

export class SchemaGenerator {
/**
* Should be renamed to `SqlSchemaGenerator` in v6
*/
export class SchemaGenerator extends AbstractSchemaGenerator<AbstractSqlDriver> {

private readonly config = this.em.config;
private readonly driver = this.em.getDriver();
private readonly metadata = this.em.getMetadata();
private readonly platform = this.driver.getPlatform();
private readonly helper = this.platform.getSchemaHelper()!;
private readonly connection = this.driver.getConnection();
private readonly knex = this.connection.getKnex();
private readonly options = this.config.get('schemaGenerator');

constructor(private readonly em: SqlEntityManager) { }
/**
* @deprecated For back compatibility, we also support EM as the parameter.
*/
constructor(em: SqlEntityManager);

/**
* `orm.getSchemaGenerator()` should be preferred way of getting the SchemaGenerator instance.
*/
constructor(driver: AbstractSqlDriver | SqlEntityManager) {
super(driver instanceof SqlEntityManager ? driver.getDriver() : driver);
}

async generate(): Promise<string> {
const [dropSchema, createSchema] = await Promise.all([
Expand Down Expand Up @@ -55,11 +64,6 @@ export class SchemaGenerator {
return false;
}

async refreshDatabase(): Promise<void> {
await this.dropSchema();
await this.createSchema();
}

getTargetSchema(schema?: string): DatabaseSchema {
const metadata = this.getOrderedMetadata(schema);
return DatabaseSchema.fromMetadata(metadata, this.platform, this.config, schema ?? this.platform.getDefaultSchemaName());
Expand Down Expand Up @@ -500,28 +504,6 @@ export class SchemaGenerator {
}
}

private getOrderedMetadata(schema?: string): EntityMetadata[] {
const metadata = Object.values(this.metadata.getAll()).filter(meta => {
const isRootEntity = meta.root.className === meta.className;
return isRootEntity && !meta.embeddable;
});
const calc = new CommitOrderCalculator();
metadata.forEach(meta => calc.addNode(meta.root.className));
let meta = metadata.pop();

while (meta) {
for (const prop of meta.props) {
calc.discoverProperty(prop, meta.root.className);
}

meta = metadata.pop();
}

return calc.sort()
.map(cls => this.metadata.find(cls)!)
.filter(meta => schema ? [schema, '*'].includes(meta.schema!) : meta.schema !== '*');
}

private async dump(builder: Knex.SchemaBuilder, append = '\n\n'): Promise<string> {
const sql = await builder.generateDdlCommands();
const queries = [...sql.pre, ...sql.sql, ...sql.post];
Expand Down