From 82b51b03485c513c796f4f60219f57bc025a39d4 Mon Sep 17 00:00:00 2001 From: Arnaud PEZEL Date: Wed, 16 Nov 2016 17:33:18 +0100 Subject: [PATCH] added a new table option skipSchemaSync to prevent schema-builder to sync a table with the database. Added a test in functional/connection/connection.ts --- src/decorator/options/TableOptions.ts | 5 ++++ src/decorator/tables/ClassTableChild.ts | 3 ++- src/decorator/tables/ClosureTable.ts | 3 ++- src/decorator/tables/Table.ts | 1 + src/entity-schema/EntitySchema.ts | 1 + src/metadata-args/TableMetadataArgs.ts | 5 ++++ src/metadata-builder/EntityMetadataBuilder.ts | 4 ++-- src/metadata/TableMetadata.ts | 6 +++++ src/schema-builder/SchemaBuilder.ts | 23 +++++++++++-------- test/functional/connection/connection.ts | 16 +++++++++++++ test/functional/connection/entity/View.ts | 14 +++++++++++ 11 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 test/functional/connection/entity/View.ts diff --git a/src/decorator/options/TableOptions.ts b/src/decorator/options/TableOptions.ts index 108dd4c50..c25073584 100644 --- a/src/decorator/options/TableOptions.ts +++ b/src/decorator/options/TableOptions.ts @@ -16,5 +16,10 @@ export interface TableOptions { * If you update this value and table is already created, it will not change table's engine type. */ readonly engine?: string; + + /** + * Specifies if this table will be skipped during schema synchronization. + */ + readonly skipSchemaSync?: boolean; } diff --git a/src/decorator/tables/ClassTableChild.ts b/src/decorator/tables/ClassTableChild.ts index 16a7066a6..43f4dece0 100644 --- a/src/decorator/tables/ClassTableChild.ts +++ b/src/decorator/tables/ClassTableChild.ts @@ -11,7 +11,8 @@ export function ClassTableChild(tableName?: string, options?: TableOptions) { target: target, name: tableName, type: "class-table-child", - orderBy: options && options.orderBy ? options.orderBy : undefined + orderBy: options && options.orderBy ? options.orderBy : undefined, + skipSchemaSync: (options && options.skipSchemaSync === true) || false }; getMetadataArgsStorage().tables.add(args); }; diff --git a/src/decorator/tables/ClosureTable.ts b/src/decorator/tables/ClosureTable.ts index d25b08da8..d7c3b4b48 100644 --- a/src/decorator/tables/ClosureTable.ts +++ b/src/decorator/tables/ClosureTable.ts @@ -11,7 +11,8 @@ export function ClosureTable(name?: string, options?: TableOptions) { target: target, name: name, type: "closure", - orderBy: options && options.orderBy ? options.orderBy : undefined + orderBy: options && options.orderBy ? options.orderBy : undefined, + skipSchemaSync: (options && options.skipSchemaSync === true) || false }; getMetadataArgsStorage().tables.add(args); }; diff --git a/src/decorator/tables/Table.ts b/src/decorator/tables/Table.ts index dbfee3097..4d37e3fc2 100644 --- a/src/decorator/tables/Table.ts +++ b/src/decorator/tables/Table.ts @@ -14,6 +14,7 @@ export function Table(name?: string, options?: TableOptions) { type: "regular", orderBy: options && options.orderBy ? options.orderBy : undefined, engine: options && options.engine ? options.engine : undefined, + skipSchemaSync: (options && options.skipSchemaSync === true) || false }; getMetadataArgsStorage().tables.add(args); }; diff --git a/src/entity-schema/EntitySchema.ts b/src/entity-schema/EntitySchema.ts index e99341863..c9da263d4 100644 --- a/src/entity-schema/EntitySchema.ts +++ b/src/entity-schema/EntitySchema.ts @@ -41,6 +41,7 @@ export interface EntitySchema { * Specifies a property name by which queries will perform ordering by default when fetching rows. */ orderBy?: OrderByCondition; + }; /** diff --git a/src/metadata-args/TableMetadataArgs.ts b/src/metadata-args/TableMetadataArgs.ts index 30923a7e8..23eaff08c 100644 --- a/src/metadata-args/TableMetadataArgs.ts +++ b/src/metadata-args/TableMetadataArgs.ts @@ -33,5 +33,10 @@ export interface TableMetadataArgs { * Table's database engine type (like "InnoDB", "MyISAM", etc). */ readonly engine?: string; + + /** + * Whether table must be synced during schema build or not + */ + readonly skipSchemaSync?: boolean; } diff --git a/src/metadata-builder/EntityMetadataBuilder.ts b/src/metadata-builder/EntityMetadataBuilder.ts index 25348a3f4..3a216fc6a 100644 --- a/src/metadata-builder/EntityMetadataBuilder.ts +++ b/src/metadata-builder/EntityMetadataBuilder.ts @@ -191,6 +191,7 @@ export class EntityMetadataBuilder { const allMergedArgs = metadataArgsStorage.getMergedTableMetadatas(entityClasses); allMergedArgs.forEach(mergedArgs => { + const tables = [mergedArgs.table].concat(mergedArgs.children); tables.forEach(tableArgs => { @@ -207,6 +208,7 @@ export class EntityMetadataBuilder { // create metadatas from args const argsForTable = mergedArgs.inheritance && mergedArgs.inheritance.type === "single-table" ? mergedArgs.table : tableArgs; + const table = new TableMetadata(argsForTable); const columns = mergedArgs.columns.map(args => { @@ -224,7 +226,6 @@ export class EntityMetadataBuilder { const discriminatorValueArgs = mergedArgs.discriminatorValues.find(discriminatorValueArgs => { return discriminatorValueArgs.target === tableArgs.target; }); - // create a new entity metadata const entityMetadata = new EntityMetadata({ target: tableArgs.target, @@ -239,7 +240,6 @@ export class EntityMetadataBuilder { discriminatorValue: discriminatorValueArgs ? discriminatorValueArgs.value : (tableArgs.target as any).name // todo: pass this to naming strategy to generate a name }, lazyRelationsWrapper); entityMetadatas.push(entityMetadata); - // create entity's relations join tables entityMetadata.manyToManyRelations.forEach(relation => { const joinTableMetadata = mergedArgs.joinTables.findByProperty(relation.propertyName); diff --git a/src/metadata/TableMetadata.ts b/src/metadata/TableMetadata.ts index 887d0b1cb..e7ade706d 100644 --- a/src/metadata/TableMetadata.ts +++ b/src/metadata/TableMetadata.ts @@ -32,6 +32,11 @@ export class TableMetadata { */ readonly engine?: string; + /** + * Whether table must be synced during schema build or not + */ + readonly skipSchemaSync?: boolean; + // --------------------------------------------------------------------- // Private Properties // --------------------------------------------------------------------- @@ -64,6 +69,7 @@ export class TableMetadata { this._name = args.name; this._orderBy = args.orderBy; this.engine = args.engine; + this.skipSchemaSync = args.skipSchemaSync; } // --------------------------------------------------------------------- diff --git a/src/schema-builder/SchemaBuilder.ts b/src/schema-builder/SchemaBuilder.ts index a66cd057e..820137dd2 100644 --- a/src/schema-builder/SchemaBuilder.ts +++ b/src/schema-builder/SchemaBuilder.ts @@ -95,11 +95,15 @@ export class SchemaBuilder { // Private Methods // ------------------------------------------------------------------------- + protected get entityToSyncMetadatas(): EntityMetadataCollection { + return this.entityMetadatas.filter( metadata => !metadata.table.skipSchemaSync ); + } + /** * Loads all table schemas from the database. */ protected loadTableSchemas(): Promise { - const tableNames = this.entityMetadatas.map(metadata => metadata.table.name); + const tableNames = this.entityToSyncMetadatas.map(metadata => metadata.table.name); return this.queryRunner.loadSchemaTables(tableNames, this.namingStrategy); } @@ -107,7 +111,7 @@ export class SchemaBuilder { * Drops all (old) foreign keys that exist in the table schemas, but do not exist in the entity metadata. */ protected async dropOldForeignKeys(): Promise { - await Promise.all(this.entityMetadatas.map(async metadata => { + await Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (!tableSchema) @@ -136,8 +140,7 @@ export class SchemaBuilder { * Primary key only can be created in conclusion with auto generated column. */ protected async createNewTables(): Promise { - await Promise.all(this.entityMetadatas.map(async metadata => { - + await Promise.all(this.entityToSyncMetadatas.map(async metadata => { // check if table does not exist yet const existTableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (existTableSchema) @@ -157,7 +160,7 @@ export class SchemaBuilder { * We drop their keys too, since it should be safe. */ protected dropRemovedColumns() { - return Promise.all(this.entityMetadatas.map(async metadata => { + return Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (!tableSchema) return; @@ -194,7 +197,7 @@ export class SchemaBuilder { * Columns are created without keys. */ protected addNewColumns() { - return Promise.all(this.entityMetadatas.map(async metadata => { + return Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (!tableSchema) return; @@ -220,7 +223,7 @@ export class SchemaBuilder { * Still don't create keys. Also we don't touch foreign keys of the changed columns. */ protected updateExistColumns() { - return Promise.all(this.entityMetadatas.map(async metadata => { + return Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (!tableSchema) return; @@ -267,7 +270,7 @@ export class SchemaBuilder { * Creates primary keys which does not exist in the table yet. */ protected updatePrimaryKeys() { - return Promise.all(this.entityMetadatas.map(async metadata => { + return Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name && !table.justCreated); if (!tableSchema) return; @@ -297,7 +300,7 @@ export class SchemaBuilder { * Creates foreign keys which does not exist in the table yet. */ protected createForeignKeys() { - return Promise.all(this.entityMetadatas.map(async metadata => { + return Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (!tableSchema) return; @@ -321,7 +324,7 @@ export class SchemaBuilder { */ protected createIndices() { // return Promise.all(this.entityMetadatas.map(metadata => this.createIndices(metadata.table, metadata.indices))); - return Promise.all(this.entityMetadatas.map(async metadata => { + return Promise.all(this.entityToSyncMetadatas.map(async metadata => { const tableSchema = this.tableSchemas.find(table => table.name === metadata.table.name); if (!tableSchema) return; diff --git a/test/functional/connection/connection.ts b/test/functional/connection/connection.ts index 6b593cf07..beb8dd65c 100644 --- a/test/functional/connection/connection.ts +++ b/test/functional/connection/connection.ts @@ -1,6 +1,7 @@ import "reflect-metadata"; import {expect} from "chai"; import {Post} from "./entity/Post"; +import {View} from "./entity/View"; import {Category} from "./entity/Category"; import {setupTestingConnections, closeConnections, createTestingConnectionOptions} from "../../utils/test-utils"; import {Connection} from "../../../src/connection/Connection"; @@ -14,6 +15,7 @@ import {ConnectionOptions} from "../../../src/connection/ConnectionOptions"; import {CannotSyncNotConnectedError} from "../../../src/connection/error/CannotSyncNotConnectedError"; import {NoConnectionForRepositoryError} from "../../../src/connection/error/NoConnectionForRepositoryError"; import {RepositoryNotFoundError} from "../../../src/connection/error/RepositoryNotFoundError"; +import {DefaultNamingStrategy} from "../../../src/naming-strategy/DefaultNamingStrategy"; import {FirstCustomNamingStrategy} from "./naming-strategy/FirstCustomNamingStrategy"; import {SecondCustomNamingStrategy} from "./naming-strategy/SecondCustomNamingStrategy"; import {CannotUseNamingStrategyNotConnectedError} from "../../../src/connection/error/CannotUseNamingStrategyNotConnectedError"; @@ -351,5 +353,19 @@ describe("Connection", () => { }); }); + + describe("skip schema generation when skipSchemaSync option is used", function() { + + let connections: Connection[]; + beforeEach(() => setupTestingConnections({ entities: [View] }).then(all => connections = all)); + afterEach(() => closeConnections(connections)); + it("database should be empty after schema sync", () => Promise.all(connections.map(async connection => { + await connection.syncSchema(true); + const queryRunner = await connection.driver.createQueryRunner(); + let schema = await queryRunner.loadSchemaTables(["view"], new DefaultNamingStrategy()); + expect(!schema.some( table => table.name === "view" )).to.be.true; + }))); + + }); }); \ No newline at end of file diff --git a/test/functional/connection/entity/View.ts b/test/functional/connection/entity/View.ts new file mode 100644 index 000000000..3227b76bf --- /dev/null +++ b/test/functional/connection/entity/View.ts @@ -0,0 +1,14 @@ +import {Table} from "../../../../src/decorator/tables/Table"; +import {PrimaryColumn} from "../../../../src/decorator/columns/PrimaryColumn"; +import {Column} from "../../../../src/decorator/columns/Column"; + +@Table("view", {skipSchemaSync: true}) +export class View { + + @PrimaryColumn() + id: number; + + @Column() + title: string; + +} \ No newline at end of file