Skip to content

Commit

Permalink
feat(mongo): add SchemaGenerator support for mongo
Browse files Browse the repository at this point in the history
- `em.getDriver().createCollections()` -> `orm.getSchemaGenerator().createSchema()`
- `em.getDriver().dropCollections()` -> `orm.getSchemaGenerator().dropSchema()`
- `em.getDriver().refreshCollections()` -> `orm.getSchemaGenerator().refreshCollections()`
- `em.getDriver().ensureIndexes()` -> `orm.getSchemaGenerator().ensureIndexes()`
  • Loading branch information
B4nan committed Jan 23, 2022
1 parent 1444957 commit 9319a62
Show file tree
Hide file tree
Showing 23 changed files with 454 additions and 218 deletions.
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
Loading

0 comments on commit 9319a62

Please sign in to comment.