@contember/engine-system-api 1.3.0-alpha.7 → 1.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/src/SystemContainer.d.ts +6 -2
  2. package/dist/src/SystemContainer.d.ts.map +1 -1
  3. package/dist/src/SystemContainer.js +3 -1
  4. package/dist/src/SystemContainer.js.map +1 -1
  5. package/dist/src/bin/runMigrations.js +4 -1
  6. package/dist/src/bin/runMigrations.js.map +1 -1
  7. package/dist/src/index.d.ts +1 -1
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js +2 -1
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/migrations/2022-10-03-110000-table-on-delete.d.ts.map +1 -1
  12. package/dist/src/migrations/2022-10-03-110000-table-on-delete.js +17 -12
  13. package/dist/src/migrations/2022-10-03-110000-table-on-delete.js.map +1 -1
  14. package/dist/src/migrations/runner.d.ts +3 -1
  15. package/dist/src/migrations/runner.d.ts.map +1 -1
  16. package/dist/src/migrations/runner.js +6 -1
  17. package/dist/src/migrations/runner.js.map +1 -1
  18. package/dist/src/migrations/types.d.ts +2 -0
  19. package/dist/src/migrations/types.d.ts.map +1 -1
  20. package/dist/src/model/authorization/AuthorizationActions.js +1 -1
  21. package/dist/src/model/authorization/AuthorizationActions.js.map +1 -1
  22. package/dist/src/model/dependencies/tenant/IdentityFetcher.d.ts +1 -1
  23. package/dist/src/model/dependencies/tenant/IdentityFetcher.d.ts.map +1 -1
  24. package/dist/src/model/events/types.d.ts +1 -1
  25. package/dist/src/model/events/types.d.ts.map +1 -1
  26. package/dist/src/model/events/types.js +1 -1
  27. package/dist/src/model/events/types.js.map +1 -1
  28. package/dist/src/model/index.d.ts +1 -0
  29. package/dist/src/model/index.d.ts.map +1 -1
  30. package/dist/src/model/index.js +1 -0
  31. package/dist/src/model/index.js.map +1 -1
  32. package/dist/src/model/metadata/ResolvedDatabaseMetadata.d.ts +13 -0
  33. package/dist/src/model/metadata/ResolvedDatabaseMetadata.d.ts.map +1 -0
  34. package/dist/src/model/metadata/ResolvedDatabaseMetadata.js +42 -0
  35. package/dist/src/model/metadata/ResolvedDatabaseMetadata.js.map +1 -0
  36. package/dist/src/model/metadata/SchemaDatabaseMetadataResolver.d.ts +8 -0
  37. package/dist/src/model/metadata/SchemaDatabaseMetadataResolver.d.ts.map +1 -0
  38. package/dist/src/model/metadata/SchemaDatabaseMetadataResolver.js +92 -0
  39. package/dist/src/model/metadata/SchemaDatabaseMetadataResolver.js.map +1 -0
  40. package/dist/src/model/metadata/SchemaDatabaseMetadataResolverStore.d.ts +17 -0
  41. package/dist/src/model/metadata/SchemaDatabaseMetadataResolverStore.d.ts.map +1 -0
  42. package/dist/src/model/metadata/SchemaDatabaseMetadataResolverStore.js +33 -0
  43. package/dist/src/model/metadata/SchemaDatabaseMetadataResolverStore.js.map +1 -0
  44. package/dist/src/model/metadata/index.d.ts +4 -0
  45. package/dist/src/model/metadata/index.d.ts.map +1 -0
  46. package/dist/src/model/metadata/index.js +22 -0
  47. package/dist/src/model/metadata/index.js.map +1 -0
  48. package/dist/src/model/migrations/MigrationAlterer.js +2 -2
  49. package/dist/src/model/migrations/MigrationAlterer.js.map +1 -1
  50. package/dist/src/model/migrations/ProjectMigrator.d.ts +3 -1
  51. package/dist/src/model/migrations/ProjectMigrator.d.ts.map +1 -1
  52. package/dist/src/model/migrations/ProjectMigrator.js +20 -6
  53. package/dist/src/model/migrations/ProjectMigrator.js.map +1 -1
  54. package/dist/src/model/migrations/SchemaVersionBuilder.d.ts +1 -1
  55. package/dist/src/model/migrations/SchemaVersionBuilder.d.ts.map +1 -1
  56. package/dist/src/model/queries/events/EventQueryHelpers.d.ts +1 -1
  57. package/dist/src/model/queries/events/EventQueryHelpers.d.ts.map +1 -1
  58. package/dist/src/model/queries/migrations/ExecutedMigrationsQueryHelper.d.ts +1 -1
  59. package/dist/src/model/queries/migrations/ExecutedMigrationsQueryHelper.d.ts.map +1 -1
  60. package/dist/src/resolvers/Resolver.d.ts +2 -2
  61. package/dist/src/resolvers/Resolver.d.ts.map +1 -1
  62. package/dist/src/resolvers/SystemResolverContext.d.ts +1 -1
  63. package/dist/src/resolvers/SystemResolverContext.d.ts.map +1 -1
  64. package/dist/src/schema/types.d.ts +67 -67
  65. package/dist/src/schema/types.d.ts.map +1 -1
  66. package/dist/src/schema/types.js +5 -5
  67. package/dist/src/schema/types.js.map +1 -1
  68. package/dist/src/tsconfig.tsbuildinfo +1 -1
  69. package/dist/src/utils/Response.d.ts +1 -1
  70. package/dist/src/utils/Response.d.ts.map +1 -1
  71. package/dist/src/utils/batchQuery.d.ts +1 -1
  72. package/dist/src/utils/batchQuery.d.ts.map +1 -1
  73. package/dist/src/utils/providers.d.ts +1 -1
  74. package/dist/src/utils/providers.d.ts.map +1 -1
  75. package/dist/src/utils/set.d.ts +1 -1
  76. package/dist/src/utils/set.d.ts.map +1 -1
  77. package/dist/tests/cases/migrations/2020-05-06-150000-composed-primary.test.js +2 -0
  78. package/dist/tests/cases/migrations/2020-05-06-150000-composed-primary.test.js.map +1 -1
  79. package/dist/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.js +2 -0
  80. package/dist/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.js.map +1 -1
  81. package/dist/tests/cases/migrations/2022-10-03-110000-table-on-delete.test.js +5 -2
  82. package/dist/tests/cases/migrations/2022-10-03-110000-table-on-delete.test.js.map +1 -1
  83. package/dist/tests/src/utils.d.ts +1 -1
  84. package/dist/tests/src/utils.d.ts.map +1 -1
  85. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +15 -15
  87. package/src/SystemContainer.ts +8 -3
  88. package/src/bin/runMigrations.ts +8 -1
  89. package/src/index.ts +1 -0
  90. package/src/migrations/2022-10-03-110000-table-on-delete.ts +18 -19
  91. package/src/migrations/runner.ts +7 -1
  92. package/src/migrations/types.ts +2 -0
  93. package/src/model/index.ts +1 -0
  94. package/src/model/metadata/ResolvedDatabaseMetadata.ts +54 -0
  95. package/src/model/metadata/SchemaDatabaseMetadataResolver.ts +136 -0
  96. package/src/model/metadata/SchemaDatabaseMetadataResolverStore.ts +41 -0
  97. package/src/model/metadata/index.ts +3 -0
  98. package/src/model/migrations/ProjectMigrator.ts +26 -7
  99. package/tests/cases/migrations/2020-05-06-150000-composed-primary.test.ts +2 -0
  100. package/tests/cases/migrations/2020-06-01-103000-event-trigger-perf.test.ts +2 -0
  101. package/tests/cases/migrations/2022-10-03-110000-table-on-delete.test.ts +6 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-system-api",
3
- "version": "1.3.0-alpha.7",
3
+ "version": "1.3.0-beta.1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
@@ -10,25 +10,25 @@
10
10
  },
11
11
  "scripts": {
12
12
  "generate": "gql-gen --config graphql.codegen.yml",
13
- "test": "vitest"
13
+ "test": "vitest --dir ./tests/cases"
14
14
  },
15
15
  "dependencies": {
16
- "@contember/authorization": "^1.3.0-alpha.7",
17
- "@contember/database": "^1.3.0-alpha.7",
18
- "@contember/database-migrations": "^1.3.0-alpha.7",
19
- "@contember/dic": "^1.3.0-alpha.7",
20
- "@contember/graphql-utils": "^1.3.0-alpha.7",
21
- "@contember/logger": "^1.3.0-alpha.7",
22
- "@contember/queryable": "^1.3.0-alpha.7",
23
- "@contember/schema": "^1.3.0-alpha.7",
24
- "@contember/schema-migrations": "^1.3.0-alpha.7",
25
- "@contember/schema-utils": "^1.3.0-alpha.7",
16
+ "@contember/authorization": "1.3.0-beta.1",
17
+ "@contember/database": "1.3.0-beta.1",
18
+ "@contember/database-migrations": "1.3.0-beta.1",
19
+ "@contember/dic": "1.3.0-beta.1",
20
+ "@contember/graphql-utils": "1.3.0-beta.1",
21
+ "@contember/logger": "1.3.0-beta.1",
22
+ "@contember/queryable": "1.3.0-beta.1",
23
+ "@contember/schema": "1.3.0-beta.1",
24
+ "@contember/schema-migrations": "1.3.0-beta.1",
25
+ "@contember/schema-utils": "1.3.0-beta.1",
26
26
  "@graphql-tools/utils": "^8.6.13",
27
27
  "graphql-tag": "^2.12.5"
28
28
  },
29
29
  "devDependencies": {
30
- "@contember/engine-api-tester": "^1.3.0-alpha.7",
31
- "@contember/schema-definition": "^1.3.0-alpha.7",
30
+ "@contember/engine-api-tester": "1.3.0-beta.1",
31
+ "@contember/schema-definition": "1.3.0-beta.1",
32
32
  "@graphql-codegen/cli": "^2.6.2",
33
33
  "@graphql-codegen/typescript": "^2.5.1",
34
34
  "@graphql-codegen/typescript-operations": "^2.4.2",
@@ -41,4 +41,4 @@
41
41
  "graphql": ">= 14.6.0",
42
42
  "pg": "^8.9.0"
43
43
  }
44
- }
44
+ }
@@ -11,9 +11,10 @@ import {
11
11
  ExecutedMigrationsResolver,
12
12
  IdentityFetcher,
13
13
  MigrationAlterer,
14
+ MigrationsDatabaseMetadataResolverStoreFactory,
14
15
  PermissionsFactory,
15
16
  ProjectMigrator,
16
- ProjectTruncateExecutor,
17
+ ProjectTruncateExecutor, SchemaDatabaseMetadataResolver,
17
18
  SchemaVersionBuilder,
18
19
  } from './model'
19
20
  import { UuidProvider } from './utils'
@@ -79,8 +80,12 @@ export class SystemContainerFactory {
79
80
  new SchemaDiffer(schemaMigrator))
80
81
  .addService('migrationDescriber', ({ modificationHandlerFactory }) =>
81
82
  new MigrationDescriber(modificationHandlerFactory))
82
- .addService('projectMigrator', ({ migrationDescriber, schemaVersionBuilder, executedMigrationsResolver }) =>
83
- new ProjectMigrator(migrationDescriber, schemaVersionBuilder, executedMigrationsResolver))
83
+ .addService('databaseMetadataResolver', () =>
84
+ new SchemaDatabaseMetadataResolver())
85
+ .addService('migrationsDatabaseMetadataResolverStoreFactory', ({ databaseMetadataResolver }) =>
86
+ new MigrationsDatabaseMetadataResolverStoreFactory(databaseMetadataResolver))
87
+ .addService('projectMigrator', ({ migrationDescriber, schemaVersionBuilder, executedMigrationsResolver, migrationsDatabaseMetadataResolverStoreFactory }) =>
88
+ new ProjectMigrator(migrationDescriber, schemaVersionBuilder, executedMigrationsResolver, migrationsDatabaseMetadataResolverStoreFactory))
84
89
  .addService('projectTruncateExecutor', () =>
85
90
  new ProjectTruncateExecutor())
86
91
  .addService('migrationAlterer', () =>
@@ -3,7 +3,11 @@ import { DatabaseContextFactory, emptyVersionedSchema, SchemaVersionBuilder } fr
3
3
  import { Connection } from '@contember/database'
4
4
  import { createLogger, PrettyPrintLoggerHandler } from '@contember/logger'
5
5
 
6
- ;(async () => {
6
+
7
+ import { SchemaDatabaseMetadataResolver } from '../model/metadata/SchemaDatabaseMetadataResolver'
8
+ import { dummySchemaDatabaseMetadata } from '@contember/schema-utils';
9
+
10
+ (async () => {
7
11
  const dbConfig = {
8
12
  database: process.env.PGDATABASE as string,
9
13
  host: process.env.PGHOST as string,
@@ -25,6 +29,9 @@ import { createLogger, PrettyPrintLoggerHandler } from '@contember/logger'
25
29
  buildSchema: () => Promise.resolve(emptyVersionedSchema),
26
30
  } as unknown as SchemaVersionBuilder,
27
31
  {},
32
+ {
33
+ resolveMetadata: () => Promise.resolve(dummySchemaDatabaseMetadata),
34
+ } as unknown as SchemaDatabaseMetadataResolver,
28
35
  )
29
36
  // eslint-disable-next-line no-console
30
37
  await migrationsRunner.run(createLogger(new PrettyPrintLoggerHandler(process.stderr)))
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export {
12
12
  LatestTransactionIdByStageQuery,
13
13
  ProjectInitializer,
14
14
  ProjectMigrator,
15
+ SchemaDatabaseMetadataResolver,
15
16
  SchemaVersionBuilder,
16
17
  Stage,
17
18
  StageBySlugQuery,
@@ -1,12 +1,14 @@
1
1
  import { MigrationArgs, MigrationBuilder } from '@contember/database-migrations'
2
2
  import { wrapIdentifier } from '@contember/database'
3
3
  import { SystemMigrationArgs } from './types'
4
- import { acceptFieldVisitor, NamingHelper } from '@contember/schema-utils'
4
+ import { acceptFieldVisitor, SchemaDatabaseMetadata } from '@contember/schema-utils'
5
5
  import { Model } from '@contember/schema'
6
6
 
7
7
  export default async function (builder: MigrationBuilder, args: MigrationArgs<SystemMigrationArgs>) {
8
8
  const schema = await args.schemaResolver(args.connection)
9
- const stages = (await args.connection.query('SELECT schema FROM stage')).rows
9
+ const stages = (await args.connection.query<{schema: string}>('SELECT schema FROM stage')).rows
10
+ const metadataByStage: Record<string, SchemaDatabaseMetadata> = {}
11
+
10
12
  for (const entity of Object.values(schema.model.entities)) {
11
13
  for (const field of Object.values(entity.fields)) {
12
14
  const result = acceptFieldVisitor<null | {
@@ -36,30 +38,27 @@ export default async function (builder: MigrationBuilder, args: MigrationArgs<Sy
36
38
  continue
37
39
  }
38
40
  for (const stage of stages) {
39
- const fkName = NamingHelper.createForeignKeyName(
40
- entity.tableName,
41
- relation.joiningColumn.columnName,
42
- targetEntity.tableName,
43
- targetEntity.primaryColumn,
44
- )
45
- const tableName = { name: entity.tableName, schema: stage.schema }
41
+ const databaseMetadata = metadataByStage[stage.schema] ??= await args.databaseMetadataResolver(args.connection, stage.schema)
46
42
 
47
- builder.dropConstraint(tableName, fkName)
43
+ const fkNames = databaseMetadata.getForeignKeyConstraintNames({
44
+ fromTable: entity.tableName,
45
+ fromColumn: relation.joiningColumn.columnName,
46
+ toTable: targetEntity.tableName,
47
+ toColumn: targetEntity.primaryColumn,
48
+ })
49
+ for (const name of fkNames) {
50
+ builder.sql(`ALTER TABLE ${wrapIdentifier(stage.schema)}.${wrapIdentifier(entity.tableName)} DROP CONSTRAINT ${wrapIdentifier(name)}`)
51
+ }
48
52
 
49
53
  const onDelete = ({
50
54
  [Model.OnDelete.setNull]: 'SET NULL',
51
55
  [Model.OnDelete.cascade]: 'CASCADE',
52
56
  } as const)[relation.joiningColumn.onDelete]
53
57
 
54
- builder.addConstraint(tableName, fkName, {
55
- foreignKeys: {
56
- columns: relation.joiningColumn.columnName,
57
- references: `${wrapIdentifier(stage.schema)}.${wrapIdentifier(targetEntity.tableName)}(${wrapIdentifier(targetEntity.primaryColumn)})`,
58
- onDelete: onDelete,
59
- },
60
- deferrable: true,
61
- deferred: false,
62
- })
58
+ builder.sql(`ALTER TABLE ${wrapIdentifier(stage.schema)}.${wrapIdentifier(entity.tableName)}
59
+ ADD FOREIGN KEY (${wrapIdentifier(relation.joiningColumn.columnName)})
60
+ REFERENCES ${wrapIdentifier(stage.schema)}.${wrapIdentifier(targetEntity.tableName)} (${wrapIdentifier(targetEntity.primaryColumn)}) ON DELETE ${onDelete} DEFERRABLE INITIALLY IMMEDIATE
61
+ `)
63
62
  }
64
63
  }
65
64
  }
@@ -25,6 +25,7 @@ import { ProjectConfig } from '../types'
25
25
  import { Logger } from '@contember/logger'
26
26
  import { GroupMigrationsResolver } from '@contember/database-migrations'
27
27
  import { SnapshotMigrationResolver } from '@contember/database-migrations'
28
+ import { SchemaDatabaseMetadataResolver } from '../model/metadata/SchemaDatabaseMetadataResolver'
28
29
 
29
30
  const migrations = {
30
31
  '2018-08-04-102200-init': _20180804102200init,
@@ -55,6 +56,7 @@ export class SystemMigrationsRunner {
55
56
  private readonly schema: string,
56
57
  private readonly schemaVersionBuilder: SchemaVersionBuilder,
57
58
  private readonly migrationGroups: Record<string, MigrationGroup<unknown>>,
59
+ private readonly databaseMetadataResolver: SchemaDatabaseMetadataResolver,
58
60
  ) {
59
61
  }
60
62
  async run(logger: Logger) {
@@ -70,10 +72,14 @@ export class SystemMigrationsRunner {
70
72
  Object.fromEntries(Object.entries(this.migrationGroups).map(([group, it]) => [group, new SnapshotMigrationResolver(it.snapshot, it.migrations, group.replace(/[^-_\w]+/g, '-'))])),
71
73
  )
72
74
  const migrationsRunner = new DbMigrationsRunner(connection, this.schema, migrationResolver)
73
-
74
75
  await migrationsRunner.migrate(message => logger.warn(message), {
75
76
  project: this.project,
76
77
  schemaResolver,
78
+ databaseMetadataResolver: (connection: Connection.ConnectionLike, schema: string) => {
79
+ const dbContextMigrations = this.databaseContextFactory.create(connection)
80
+ return this.databaseMetadataResolver.resolveMetadata(dbContextMigrations, schema)
81
+ },
82
+
77
83
  })
78
84
  })
79
85
  await singleConnection.end()
@@ -1,8 +1,10 @@
1
1
  import { Schema } from '@contember/schema'
2
2
  import { ProjectConfig } from '../types'
3
3
  import { Connection } from '@contember/database'
4
+ import { SchemaDatabaseMetadata } from '@contember/schema-utils'
4
5
 
5
6
  export interface SystemMigrationArgs {
6
7
  schemaResolver: (connection: Connection.ConnectionLike) => Promise<Schema>
8
+ databaseMetadataResolver: (connection: Connection.ConnectionLike, schema: string) => Promise<SchemaDatabaseMetadata>
7
9
  project: ProjectConfig
8
10
  }
@@ -5,6 +5,7 @@ export * from './dependencies'
5
5
  export * from './dtos'
6
6
  export * from './events'
7
7
  export * from './helpers'
8
+ export * from './metadata'
8
9
  export * from './migrations'
9
10
  export * from './queries'
10
11
  export * from './stages'
@@ -0,0 +1,54 @@
1
+ import {
2
+ ForeignKeyConstraintMetadata,
3
+ GetForeignKeyConstraintNameArgs,
4
+ GetIndexNameArgs,
5
+ GetUniqueConstraintNameArgs,
6
+ IndexMetadata,
7
+ SchemaDatabaseMetadata,
8
+ UniqueConstraintMetadata,
9
+ } from '@contember/schema-utils'
10
+
11
+ const stringArrayEquals = (colA: string[], colB: string[]) => {
12
+ return colA.length === colB.length
13
+ && colA.every((it, index) => it === colB[index])
14
+ }
15
+
16
+ export class ResolvedDatabaseMetadata implements SchemaDatabaseMetadata {
17
+ constructor(
18
+ private readonly foreignKeys: ForeignKeyConstraintMetadata[],
19
+ private readonly uniqueConstraints: UniqueConstraintMetadata[],
20
+ private readonly indexes: IndexMetadata[],
21
+ ) {
22
+ }
23
+
24
+ getUniqueConstraint(tableName: string, constraintName: string): UniqueConstraintMetadata | null {
25
+ return this.uniqueConstraints.find(it => it.tableName === tableName && it.constraintName === constraintName) ?? null
26
+ }
27
+
28
+ getForeignKeyConstraint(tableName: string, constraintName: string): ForeignKeyConstraintMetadata | null {
29
+ return this.foreignKeys.find(it => it.fromTable === tableName && it.constraintName === constraintName) ?? null
30
+ }
31
+
32
+ getForeignKeyConstraintNames({ fromTable, fromColumn, toTable, toColumn }: GetForeignKeyConstraintNameArgs): string[] {
33
+ return this.foreignKeys
34
+ .filter(it =>
35
+ it.fromTable === fromTable
36
+ && it.toTable === toTable
37
+ && it.fromColumn === fromColumn
38
+ && it.toColumn === toColumn,
39
+ )
40
+ .map(it => it.constraintName)
41
+ }
42
+
43
+ getUniqueConstraintNames({ tableName, columnNames }: GetUniqueConstraintNameArgs): string[] {
44
+ return this.uniqueConstraints
45
+ .filter(it => it.tableName === tableName && stringArrayEquals(columnNames, it.columnNames))
46
+ .map(it => it.constraintName)
47
+ }
48
+
49
+ getIndexNames({ tableName, columnNames }: GetIndexNameArgs): string[] {
50
+ return this.indexes
51
+ .filter(it => it.tableName === tableName && stringArrayEquals(columnNames, it.columnNames))
52
+ .map(it => it.indexName)
53
+ }
54
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ ForeignKeyConstraintMetadata,
3
+ IndexMetadata,
4
+ SchemaDatabaseMetadata,
5
+ UniqueConstraintMetadata,
6
+ } from '@contember/schema-utils'
7
+ import { DatabaseContext } from '../database'
8
+ import { Connection } from '@contember/database'
9
+ import { ResolvedDatabaseMetadata } from './ResolvedDatabaseMetadata'
10
+
11
+ export class SchemaDatabaseMetadataResolver {
12
+ async resolveMetadata(db: DatabaseContext, contentSchema: string): Promise<SchemaDatabaseMetadata> {
13
+ const constraintRows = await this.fetchConstraints(db.client, contentSchema)
14
+ const indexRows = await this.fetchIndexes(db.client, contentSchema)
15
+
16
+ const fkConstraints = constraintRows
17
+ .filter((it): it is ForeignKeyConstraintsRow => it.type === ConstraintTypes.foreignKey)
18
+ .map((it): ForeignKeyConstraintMetadata => ({
19
+ constraintName: it.constraint_name,
20
+ fromColumn: it.columns[0],
21
+ toColumn: it.target_columns[0],
22
+ fromTable: it.table_name,
23
+ toTable: it.target_table,
24
+ }))
25
+ const uniqueConstraints = constraintRows
26
+ .filter((it): it is UniqueConstraintsRow => it.type === ConstraintTypes.unique)
27
+ .map((it): UniqueConstraintMetadata => ({
28
+ constraintName: it.constraint_name,
29
+ tableName: it.table_name,
30
+ columnNames: it.columns,
31
+ }))
32
+ const indexes = indexRows
33
+ .map((it): IndexMetadata => ({
34
+ indexName: it.index_name,
35
+ tableName: it.table_name,
36
+ columnNames: it.columns,
37
+ }))
38
+
39
+ return new ResolvedDatabaseMetadata(
40
+ fkConstraints,
41
+ uniqueConstraints,
42
+ indexes,
43
+ )
44
+ }
45
+
46
+ private async fetchConstraints(connection: Connection.Queryable, schema: string): Promise<ConstraintsRow[]> {
47
+ return (await connection.query<ConstraintsRow>(`
48
+ SELECT
49
+ MAX(pg_constraint.conname) AS constraint_name,
50
+ MAX(pg_class.relname) AS table_name,
51
+ JSONB_AGG(DISTINCT pg_attribute.attname) AS columns,
52
+ MAX(pg_constraint.contype) AS type,
53
+ MAX(pg_constraint_table.relname) AS target_table,
54
+ JSONB_AGG(DISTINCT pg_attribute_target.attname)
55
+ FILTER ( WHERE pg_attribute_target.attname IS NOT NULL) AS target_columns,
56
+ BOOL_OR(condeferrable),
57
+ BOOL_OR(condeferred)
58
+ FROM pg_constraint
59
+ JOIN pg_class
60
+ ON pg_constraint.conrelid = pg_class.oid
61
+ JOIN pg_namespace
62
+ ON pg_class.relnamespace = pg_namespace.oid
63
+ JOIN pg_attribute
64
+ ON pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = ANY (pg_constraint.conkey)
65
+ LEFT JOIN pg_attribute pg_attribute_target
66
+ ON pg_constraint.confrelid = pg_attribute_target.attrelid AND
67
+ pg_attribute_target.attnum = ANY (pg_constraint.confkey)
68
+ LEFT JOIN pg_class pg_constraint_table
69
+ ON pg_constraint_table.oid = pg_constraint.confrelid
70
+ WHERE pg_namespace.nspname = ?
71
+ AND pg_constraint.contype = ANY (ARRAY ['f', 'u'])
72
+ GROUP BY pg_constraint.oid
73
+ `, [schema]))
74
+ .rows
75
+ }
76
+
77
+ private async fetchIndexes(connection: Connection.Queryable, schema: string): Promise<IndexRow[]> {
78
+ return (await connection.query<IndexRow>(`
79
+ SELECT
80
+ MAX(idx_class.relname) AS index_name,
81
+ MAX(table_class.relname) AS table_name,
82
+ JSONB_AGG(DISTINCT pg_attribute.attname) AS columns
83
+ FROM pg_index
84
+ JOIN pg_class AS table_class
85
+ ON pg_index.indrelid = table_class.oid
86
+ JOIN pg_class AS idx_class
87
+ ON pg_index.indexrelid = idx_class.oid
88
+ JOIN pg_namespace
89
+ ON table_class.relnamespace = pg_namespace.oid
90
+ JOIN pg_attribute
91
+ ON pg_attribute.attrelid = table_class.oid AND pg_attribute.attnum = ANY (pg_index.indkey)
92
+ WHERE pg_namespace.nspname = ?
93
+ AND indisprimary = FALSE
94
+ AND indisunique = FALSE
95
+ GROUP BY pg_index.indexrelid
96
+ `, [schema]))
97
+ .rows
98
+ }
99
+ }
100
+
101
+ enum ConstraintTypes {
102
+ foreignKey = 'f',
103
+ unique = 'u',
104
+ }
105
+
106
+ type ConstraintsRow =
107
+ | ForeignKeyConstraintsRow
108
+ | UniqueConstraintsRow
109
+
110
+ type ForeignKeyConstraintsRow = {
111
+ constraint_name: string
112
+ table_name: string
113
+ columns: string[]
114
+ target_table: string
115
+ target_columns: string[]
116
+ type: ConstraintTypes.foreignKey
117
+ deferrable: boolean
118
+ deferred: boolean
119
+ }
120
+ type UniqueConstraintsRow = {
121
+ constraint_name: string
122
+ table_name: string
123
+ columns: string[]
124
+ target_table: null
125
+ target_columns: null
126
+ type: ConstraintTypes.unique
127
+ deferrable: boolean
128
+ deferred: boolean
129
+ }
130
+
131
+ type IndexRow = {
132
+ index_name: string
133
+ table_name: string
134
+ columns: string[]
135
+ }
136
+
@@ -0,0 +1,41 @@
1
+ import { SchemaDatabaseMetadata } from '@contember/schema-utils'
2
+ import { SchemaDatabaseMetadataResolver } from './SchemaDatabaseMetadataResolver'
3
+ import { DatabaseContext } from '../database'
4
+
5
+ export class SchemaDatabaseMetadataResolverStore {
6
+
7
+ private cache: Map<string, SchemaDatabaseMetadata> = new Map()
8
+
9
+ constructor(
10
+ private resolver: SchemaDatabaseMetadataResolver,
11
+ private db: DatabaseContext,
12
+ ) {
13
+ }
14
+
15
+ public async getMetadata(schema: string): Promise<SchemaDatabaseMetadata> {
16
+ const cached = this.cache.get(schema)
17
+ if (cached) {
18
+ return cached
19
+ }
20
+
21
+ const newValue = await this.resolver.resolveMetadata(this.db, schema)
22
+ this.cache.set(schema, newValue)
23
+ return newValue
24
+ }
25
+
26
+ public invalidate(schema: string): void {
27
+ this.cache.delete(schema)
28
+ }
29
+ }
30
+
31
+
32
+ export class MigrationsDatabaseMetadataResolverStoreFactory {
33
+ constructor(
34
+ private resolver: SchemaDatabaseMetadataResolver,
35
+ ) {
36
+ }
37
+
38
+ public create(db: DatabaseContext) {
39
+ return new SchemaDatabaseMetadataResolverStore(this.resolver, db)
40
+ }
41
+ }
@@ -0,0 +1,3 @@
1
+ export * from './SchemaDatabaseMetadataResolver'
2
+ export * from './SchemaDatabaseMetadataResolverStore'
3
+ export { ResolvedDatabaseMetadata } from './ResolvedDatabaseMetadata'
@@ -1,4 +1,4 @@
1
- import { calculateMigrationChecksum, Migration, MigrationDescriber } from '@contember/schema-migrations'
1
+ import { calculateMigrationChecksum, Migration, MigrationDescriber, MigrationVersionHelper } from '@contember/schema-migrations'
2
2
  import { Client, QueryError, wrapIdentifier } from '@contember/database'
3
3
  import { Schema } from '@contember/schema'
4
4
  import { SaveMigrationCommand } from '../commands'
@@ -7,14 +7,19 @@ import { DatabaseContext } from '../database'
7
7
  import { ExecutedMigrationsResolver } from './ExecutedMigrationsResolver'
8
8
  import { MigrateErrorCode } from '../../schema'
9
9
  import { SchemaVersionBuilder } from './SchemaVersionBuilder'
10
- import { SchemaValidator } from '@contember/schema-utils'
10
+ import { SchemaValidator, SchemaValidatorSkippedErrors } from '@contember/schema-utils'
11
11
  import { logger } from '@contember/logger'
12
+ import {
13
+ SchemaDatabaseMetadataResolverStore,
14
+ MigrationsDatabaseMetadataResolverStoreFactory,
15
+ } from '../metadata/SchemaDatabaseMetadataResolverStore'
12
16
 
13
17
  export class ProjectMigrator {
14
18
  constructor(
15
19
  private readonly migrationDescriber: MigrationDescriber,
16
20
  private readonly schemaVersionBuilder: SchemaVersionBuilder,
17
21
  private readonly executedMigrationsResolver: ExecutedMigrationsResolver,
22
+ private readonly migrationsDatabaseMetadataResolverStoreFactory: MigrationsDatabaseMetadataResolverStoreFactory,
18
23
  ) {}
19
24
 
20
25
  public async migrate(
@@ -34,6 +39,8 @@ export class ProjectMigrator {
34
39
 
35
40
  const sorted = [...validated].sort((a, b) => a.version.localeCompare(b.version))
36
41
 
42
+ const metadataStore = this.migrationsDatabaseMetadataResolverStoreFactory.create(db)
43
+
37
44
  for (const migration of sorted) {
38
45
  const formatVersion = migration.formatVersion
39
46
 
@@ -46,6 +53,7 @@ export class ProjectMigrator {
46
53
  formatVersion,
47
54
  migration.version,
48
55
  db.client.schema,
56
+ metadataStore,
49
57
  )
50
58
  }
51
59
  await db.commandBus.execute(new SaveMigrationCommand(migration))
@@ -64,6 +72,7 @@ export class ProjectMigrator {
64
72
  ): Promise<readonly Migration[]> {
65
73
  const executedMigrations = await this.executedMigrationsResolver.getMigrations(db)
66
74
  const toExecute = []
75
+ let skippedErrors: SchemaValidatorSkippedErrors[] = []
67
76
  for (const migration of migrationsToExecute) {
68
77
  const executedMigration = executedMigrations.find(it => it.version === migration.version)
69
78
  if (executedMigration) {
@@ -76,13 +85,17 @@ export class ProjectMigrator {
76
85
  if (migration.version < version && !ignoreOrder) {
77
86
  throw new MustFollowLatestMigrationError(migration.version, `Must follow latest executed migration ${version}`)
78
87
  }
79
- const described = await this.migrationDescriber.describeModifications(schema, migration, 'system') // system schema name not important here
88
+ const described = this.migrationDescriber.describeModifications(schema, migration)
80
89
  if (described.length === 0) {
81
90
  continue
82
91
  }
83
92
  const latestModification = described[described.length - 1]
84
93
  schema = latestModification.schema
85
- const errors = SchemaValidator.validate(schema, migration.skippedErrors)
94
+ skippedErrors = [
95
+ ...skippedErrors.filter(it => it.skipUntil && MigrationVersionHelper.extractVersion(it.skipUntil) >= migration.version),
96
+ ...migration.skippedErrors ?? [],
97
+ ]
98
+ const errors = SchemaValidator.validate(schema, skippedErrors)
86
99
  if (errors.length > 0) {
87
100
  throw new InvalidSchemaError(
88
101
  migration.version,
@@ -112,13 +125,19 @@ export class ProjectMigrator {
112
125
  formatVersion: number,
113
126
  migrationVersion: string,
114
127
  systemSchema: string,
128
+ metadataStore: SchemaDatabaseMetadataResolverStore,
115
129
  ): Promise<[Schema]> {
116
130
  const {
117
- sql,
131
+ getSql,
118
132
  schema: newSchema,
119
- handler,
120
- } = await this.migrationDescriber.describeModification(schema, modification, { systemSchema, formatVersion })
133
+ } = this.migrationDescriber.describeModification(schema, modification, { formatVersion })
121
134
  for (const stage of stages) {
135
+ const databaseMetadata = await metadataStore.getMetadata(stage.schema)
136
+ const sql = getSql({
137
+ systemSchema,
138
+ databaseMetadata,
139
+ invalidateDatabaseMetadata: () => metadataStore.invalidate(stage.schema),
140
+ })
122
141
  await this.executeOnStage(db, stage, sql, migrationVersion)
123
142
  }
124
143
  return [newSchema]
@@ -2,12 +2,14 @@ import migration from '../../../src/migrations/2020-05-06-150000-composed-primar
2
2
  import { createMigrationBuilder } from '@contember/database-migrations'
3
3
  import { sampleProject } from '@contember/engine-api-tester'
4
4
  import { test, assert } from 'vitest'
5
+ import { dummySchemaDatabaseMetadata } from '@contember/schema-utils'
5
6
 
6
7
  test('many-has-many-primary migration sql', async () => {
7
8
  const builder = createMigrationBuilder()
8
9
  await migration(builder, {
9
10
  connection: undefined as any,
10
11
  schemaResolver: () => Promise.resolve(sampleProject),
12
+ databaseMetadataResolver: () => Promise.resolve(dummySchemaDatabaseMetadata),
11
13
  project: {
12
14
  slug: 'test',
13
15
  stages: [
@@ -2,6 +2,7 @@ import migration from '../../../src/migrations/2020-06-01-103000-event-trigger-p
2
2
  import { createMigrationBuilder } from '@contember/database-migrations'
3
3
  import { sampleProject } from '@contember/engine-api-tester'
4
4
  import { test, assert } from 'vitest'
5
+ import { dummySchemaDatabaseMetadata } from '@contember/schema-utils'
5
6
 
6
7
 
7
8
  test('event-trigger-performance sql', async () => {
@@ -9,6 +10,7 @@ test('event-trigger-performance sql', async () => {
9
10
  await migration(builder, {
10
11
  connection: undefined as any,
11
12
  schemaResolver: () => Promise.resolve(sampleProject),
13
+ databaseMetadataResolver: () => Promise.resolve(dummySchemaDatabaseMetadata),
12
14
  project: {
13
15
  slug: 'test',
14
16
  stages: [
@@ -2,7 +2,7 @@ import migration from '../../../src/migrations/2022-10-03-110000-table-on-delete
2
2
  import { createMigrationBuilder } from '@contember/database-migrations'
3
3
  import { assert, test } from 'vitest'
4
4
  import { SchemaBuilder } from '@contember/schema-definition'
5
- import { emptySchema } from '@contember/schema-utils'
5
+ import { dummySchemaDatabaseMetadata, emptySchema } from '@contember/schema-utils'
6
6
  import { Model } from '@contember/schema'
7
7
  import { createConnectionMock } from '@contember/database-tester'
8
8
 
@@ -15,6 +15,7 @@ test('table-on-delete test', async () => {
15
15
  }])
16
16
  await migration(builder, {
17
17
  connection: connection,
18
+ databaseMetadataResolver: () => Promise.resolve(dummySchemaDatabaseMetadata),
18
19
  schemaResolver: () => Promise.resolve(({ ...emptySchema, model: new SchemaBuilder()
19
20
  .entity('Post', entity =>
20
21
  entity.manyHasOne('author', relation => relation.target('Author').onDelete(Model.OnDelete.setNull)),
@@ -33,9 +34,11 @@ test('table-on-delete test', async () => {
33
34
  })
34
35
  assert.equal(
35
36
  builder.getSql(),
36
- `ALTER TABLE "stage_live"."post" DROP CONSTRAINT "fk_post_author_id_87ef9a";
37
+ `ALTER TABLE "stage_live"."post" DROP CONSTRAINT "fk_post_author_id_author_id";
37
38
  ALTER TABLE "stage_live"."post"
38
- ADD CONSTRAINT "fk_post_author_id_87ef9a" FOREIGN KEY ("author_id") REFERENCES "stage_live"."author"("id") ON DELETE SET NULL DEFERRABLE INITIALLY IMMEDIATE;
39
+ ADD FOREIGN KEY ("author_id")
40
+ REFERENCES "stage_live"."author" ("id") ON DELETE SET NULL DEFERRABLE INITIALLY IMMEDIATE
41
+ ;
39
42
  `,
40
43
  )
41
44
  })