Skip to content

Commit

Permalink
feature(orm): allow to configure migrate() + new skip field options
Browse files Browse the repository at this point in the history
it's now new default to not drop tables when database.migrate() is called (enable again with database.migrate({drop: true})).

Also allows to skip fields from being selected in SELECT or migration statements via `DatabaseField<{skip: true, skipMigration: true}>`.
  • Loading branch information
marcj committed Oct 2, 2023
1 parent a47111c commit 9eafe2d
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 75 deletions.
4 changes: 2 additions & 2 deletions packages/framework/src/orm-browser/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isArray, isObject } from '@deepkit/core';
import { Database, DatabaseAdapter } from '@deepkit/orm';
import { Database, DatabaseAdapter, MigrateOptions } from '@deepkit/orm';
import {
BrowserControllerInterface,
DatabaseCommit,
Expand Down Expand Up @@ -114,7 +114,7 @@ export class OrmBrowserController implements BrowserControllerInterface {
async getMigrations(name: string): Promise<{ [name: string]: { sql: string[], diff: string } }> {
const db = this.findDatabase(name);
if (db.adapter instanceof SQLDatabaseAdapter) {
return db.adapter.getMigrations(db.entityRegistry);
return db.adapter.getMigrations(new MigrateOptions(), db.entityRegistry);
}
return {};
}
Expand Down
20 changes: 15 additions & 5 deletions packages/mongo/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@
* You should have received a copy of the MIT License along with this program.
*/

import { DatabaseAdapter, DatabaseAdapterQueryFactory, DatabaseEntityRegistry, DatabaseSession, FindQuery, ItemNotFound, OrmEntity, RawFactory } from '@deepkit/orm';
import {
DatabaseAdapter,
DatabaseAdapterQueryFactory,
DatabaseEntityRegistry,
DatabaseSession,
FindQuery,
ItemNotFound,
MigrateOptions,
OrmEntity,
RawFactory
} from '@deepkit/orm';
import { AbstractClassType, ClassType, isArray } from '@deepkit/core';
import { MongoDatabaseQuery } from './query.js';
import { MongoPersistence } from './persistence.js';
Expand Down Expand Up @@ -143,22 +153,22 @@ export class MongoDatabaseAdapter extends DatabaseAdapter {
await this.client.execute(new DeleteCommand(this.ormSequences));
}

async migrate(entityRegistry: DatabaseEntityRegistry) {
async migrate(options: MigrateOptions, entityRegistry: DatabaseEntityRegistry) {
await this.client.connect(); //manually connect to catch connection errors
let withOrmSequences = true;
for (const schema of entityRegistry.forMigration()) {
await this.migrateClassSchema(schema);
await this.migrateClassSchema(options, schema);
for (const property of schema.getProperties()) {
if (property.isAutoIncrement()) withOrmSequences = true;
}
}

if (withOrmSequences) {
await this.migrateClassSchema(this.ormSequences);
await this.migrateClassSchema(options, this.ormSequences);
}
};

async migrateClassSchema(schema: ReflectionClass<any>) {
async migrateClassSchema(options: MigrateOptions, schema: ReflectionClass<any>) {
try {
await this.client.execute(new CreateCollectionCommand(schema));
} catch (error) {
Expand Down
46 changes: 45 additions & 1 deletion packages/orm/src/database-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,47 @@ export class RawFactory<A extends Array<any>> {
}
}

export class MigrateOptions {
/**
* Whether drop statements should be issued, like DROP TABLE, DROP INDEX, etc.
*
* Default false.
*/
drop: boolean = false;

/**
* Whether drop statements should be issued for indexes/uniques, like DROP INDEX.
*/
dropIndex: boolean = false;

/**
* Whether create/drop statements should be issued for indexes/uniques, like CREATE/ INDEX/DROP INDEX.
*/
skipIndex: boolean = false;

/**
* Whether foreign key constraints should be created/dropped.
*/
skipForeignKey: boolean = false;

isDropIndex() {
if (this.skipIndex) return false;
return this.skipIndex || this.dropIndex || this.drop;
}

isIndex() {
return !this.skipIndex;
}

isForeignKey() {
return !this.skipForeignKey;
}

isDropSchema() {
return this.drop;
}
}

/**
* A generic database adapter you can use if the API of `Query` is sufficient.
*
Expand All @@ -62,8 +103,11 @@ export abstract class DatabaseAdapter {

abstract disconnect(force?: boolean): void;

abstract migrate(entityRegistry: DatabaseEntityRegistry): Promise<void>;
abstract migrate(options: MigrateOptions, entityRegistry: DatabaseEntityRegistry): Promise<void>;

/**
* Unique adapter name to be used in DatabaseField to apply certain adapter specific behavior per field.
*/
abstract getName(): string;

abstract getSchemaName(): string;
Expand Down
4 changes: 3 additions & 1 deletion packages/orm/src/database-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,9 @@ export class DatabaseSession<ADAPTER extends DatabaseAdapter> {

//we cannot use arrow functions, since they can't have ReceiveType<T>
function query<T extends OrmEntity>(type?: ReceiveType<T> | ClassType<T> | AbstractClassType<T> | ReflectionClass<T>) {
return queryFactory.createQuery(type);
const result = queryFactory.createQuery(type);
result.model.adapterName = adapter.getName();
return result;
}

this.query = query as any;
Expand Down
8 changes: 5 additions & 3 deletions packages/orm/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
resolveReceiveType,
Type
} from '@deepkit/type';
import { DatabaseAdapter, DatabaseEntityRegistry } from './database-adapter.js';
import { DatabaseAdapter, DatabaseEntityRegistry, MigrateOptions } from './database-adapter.js';
import { DatabaseSession } from './database-session.js';
import { DatabaseLogger } from './logger.js';
import { Query } from './query.js';
Expand Down Expand Up @@ -332,8 +332,10 @@ export class Database<ADAPTER extends DatabaseAdapter = DatabaseAdapter> {
* WARNING: DON'T USE THIS IN PRODUCTION AS THIS CAN CAUSE EASILY DATA LOSS.
* SEE THE MIGRATION DOCUMENTATION TO UNDERSTAND ITS IMPLICATIONS.
*/
async migrate() {
await this.adapter.migrate(this.entityRegistry);
async migrate(options: Partial<MigrateOptions> = {}) {
const o = new MigrateOptions();
Object.assign(o, options);
await this.adapter.migrate(o, this.entityRegistry);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/orm/src/memory-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { DatabaseSession, DatabaseTransaction } from './database-session.js';
import { DatabaseQueryModel, GenericQueryResolver, Query } from './query.js';
import { Changes, getSerializeFunction, ReceiveType, ReflectionClass, resolvePath, serialize, Serializer } from '@deepkit/type';
import { AbstractClassType, deletePathValue, getPathValue, setPathValue } from '@deepkit/core';
import { DatabaseAdapter, DatabaseAdapterQueryFactory, DatabaseEntityRegistry, DatabasePersistence, DatabasePersistenceChangeSet } from './database-adapter.js';
import { DatabaseAdapter, DatabaseAdapterQueryFactory, DatabaseEntityRegistry, DatabasePersistence, DatabasePersistenceChangeSet, MigrateOptions } from './database-adapter.js';
import { DeleteResult, OrmEntity, PatchResult } from './type.js';
import { findQueryList } from './utils.js';
import { convertQueryFilter } from './query-filter.js';
Expand Down Expand Up @@ -246,7 +246,7 @@ export class MemoryDatabaseTransaction extends DatabaseTransaction {
export class MemoryDatabaseAdapter extends DatabaseAdapter {
protected store = new Map<ReflectionClass<any>, SimpleStore<any>>();

async migrate(entityRegistry: DatabaseEntityRegistry) {
async migrate(options: MigrateOptions, entityRegistry: DatabaseEntityRegistry) {
}

isNativeForeignKeyConstraintSupported(): boolean {
Expand Down
6 changes: 6 additions & 0 deletions packages/orm/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ export class DatabaseQueryModel<T extends OrmEntity, FILTER extends FilterQuery<
public returning: (keyof T & string)[] = [];
public batchSize?: number;

/**
* The adapter name is set by the database adapter when the query is created.
*/
public adapterName: string = '';

isLazyLoaded(field: string): boolean {
return this.lazyLoad.has(field);
}
Expand Down Expand Up @@ -141,6 +146,7 @@ export class DatabaseQueryModel<T extends OrmEntity, FILTER extends FilterQuery<
m.lazyLoad = new Set(this.lazyLoad);
m.for = this.for;
m.batchSize = this.batchSize;
m.adapterName = this.adapterName;
m.aggregate = new Map(this.aggregate);
m.parameters = { ...this.parameters };

Expand Down
5 changes: 4 additions & 1 deletion packages/postgres/src/postgres-platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ export class PostgresPlatform extends DefaultPlatform {
}

getDropIndexDDL(index: IndexModel): string {
return `ALTER TABLE ${this.getIdentifier(index.table)} DROP CONSTRAINT ${this.getIdentifier(index)}`;
if (index.isUnique) {
return `ALTER TABLE ${this.getIdentifier(index.table)} DROP CONSTRAINT ${this.getIdentifier(index)}`;
}
return super.getDropIndexDDL(index);
}

supportsInlineForeignKey(): boolean {
Expand Down
20 changes: 10 additions & 10 deletions packages/sql/src/cli/migration-create-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { DatabaseComparator, DatabaseModel } from '../schema/table.js';
import { MigrationProvider } from '../migration/migration-provider.js';
import { BaseCommand } from './base-command.js';
import { ReflectionClass } from '@deepkit/type';
import { MigrateOptions } from '@deepkit/orm';

function serializeSQLLine(sql: string): string {
return '`' + sql.replace(/`/g, '\\`') + '`';
Expand All @@ -40,9 +41,9 @@ export class MigrationCreateController extends BaseCommand implements Command {
*/
@flag database?: string,
/**
* @description Do not drop any table that is not available anymore as entity
* @description Do drop any table that is not available anymore as entity. CAUTION: this might wipe your whole database.
*/
@flag noDrop: boolean = false,
@flag drop: boolean = false,
/**
* @description Create an empty migration file
*/
Expand All @@ -55,6 +56,9 @@ export class MigrationCreateController extends BaseCommand implements Command {
this.logger.error('No databases detected. Use --path path/to/database.ts');
}

const options = new MigrateOptions();
options.drop = drop;

for (const db of this.provider.databases.getDatabases()) {
if (database && db.name !== database) continue;
if (db.name === 'debug') continue;
Expand All @@ -64,14 +68,14 @@ export class MigrationCreateController extends BaseCommand implements Command {
let downSql: string[] = [];

if (!empty) {
const databaseModel = new DatabaseModel();
const databaseModel = new DatabaseModel([], db.adapter.getName());
databaseModel.schemaName = db.adapter.getSchemaName();
db.adapter.platform.createTables(db.entityRegistry, databaseModel);

const connection = await db.adapter.connectionPool.getConnection();
const schemaParser = new db.adapter.platform.schemaParserType(connection, db.adapter.platform);

const parsedDatabaseModel = new DatabaseModel();
const parsedDatabaseModel = new DatabaseModel([], db.adapter.getName());
parsedDatabaseModel.schemaName = db.adapter.getSchemaName();
await schemaParser.parse(parsedDatabaseModel);
parsedDatabaseModel.removeUnknownTables(databaseModel);
Expand All @@ -86,19 +90,15 @@ export class MigrationCreateController extends BaseCommand implements Command {
continue;
}

if (noDrop) {
databaseDiff.removedTables = [];
}

if (databaseDiff) {
upSql = db.adapter.platform.getModifyDatabaseDDL(databaseDiff);
upSql = db.adapter.platform.getModifyDatabaseDDL(databaseDiff, options);
if (!empty && !upSql.length) {
this.logger.error(db.name, `No generated sql for ${db.name} found.`);
continue;
}

const reverseDatabaseDiff = DatabaseComparator.computeDiff(databaseModel, parsedDatabaseModel);
downSql = reverseDatabaseDiff ? db.adapter.platform.getModifyDatabaseDDL(reverseDatabaseDiff) : [];
downSql = reverseDatabaseDiff ? db.adapter.platform.getModifyDatabaseDDL(reverseDatabaseDiff, options) : [];
}
}

Expand Down
Loading

0 comments on commit 9eafe2d

Please sign in to comment.