@contember/schema-migrations 1.4.0-alpha.1 → 1.4.0-alpha.2
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.
- package/dist/src/SchemaDiffer.d.ts.map +1 -1
- package/dist/src/SchemaDiffer.js +2 -0
- package/dist/src/SchemaDiffer.js.map +1 -1
- package/dist/src/modifications/ModificationHandlerFactory.d.ts.map +1 -1
- package/dist/src/modifications/ModificationHandlerFactory.js +2 -0
- package/dist/src/modifications/ModificationHandlerFactory.js.map +1 -1
- package/dist/src/modifications/relations/ConvertOneHasManyToOneHasOneRelationModification.d.ts +28 -0
- package/dist/src/modifications/relations/ConvertOneHasManyToOneHasOneRelationModification.d.ts.map +1 -0
- package/dist/src/modifications/relations/ConvertOneHasManyToOneHasOneRelationModification.js +98 -0
- package/dist/src/modifications/relations/ConvertOneHasManyToOneHasOneRelationModification.js.map +1 -0
- package/dist/src/modifications/relations/ConvertOneToManyRelationModification.d.ts.map +1 -1
- package/dist/src/modifications/relations/ConvertOneToManyRelationModification.js +4 -2
- package/dist/src/modifications/relations/ConvertOneToManyRelationModification.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/integration/convertManyHasOneToOneHasOneRelation.test.d.ts +2 -0
- package/dist/tests/cases/integration/convertManyHasOneToOneHasOneRelation.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/convertManyHasOneToOneHasOneRelation.test.js +59 -0
- package/dist/tests/cases/integration/convertManyHasOneToOneHasOneRelation.test.js.map +1 -0
- package/dist/tests/cases/integration/convertOneToManyRelation.test.js +37 -0
- package/dist/tests/cases/integration/convertOneToManyRelation.test.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/SchemaDiffer.ts +2 -0
- package/src/modifications/ModificationHandlerFactory.ts +2 -0
- package/src/modifications/relations/ConvertOneHasManyToOneHasOneRelationModification.ts +145 -0
- package/src/modifications/relations/ConvertOneToManyRelationModification.ts +3 -2
- package/tests/cases/integration/convertManyHasOneToOneHasOneRelation.test.ts +48 -0
- package/tests/cases/integration/convertOneToManyRelation.test.ts +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/schema-migrations",
|
|
3
|
-
"version": "1.4.0-alpha.
|
|
3
|
+
"version": "1.4.0-alpha.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"typings": "dist/src/index.d.ts",
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"test": "vitest --dir ./tests/cases"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@contember/database": "1.4.0-alpha.
|
|
16
|
-
"@contember/database-migrations": "1.4.0-alpha.
|
|
17
|
-
"@contember/engine-common": "1.4.0-alpha.
|
|
18
|
-
"@contember/schema": "1.4.0-alpha.
|
|
19
|
-
"@contember/schema-utils": "1.4.0-alpha.
|
|
15
|
+
"@contember/database": "1.4.0-alpha.2",
|
|
16
|
+
"@contember/database-migrations": "1.4.0-alpha.2",
|
|
17
|
+
"@contember/engine-common": "1.4.0-alpha.2",
|
|
18
|
+
"@contember/schema": "1.4.0-alpha.2",
|
|
19
|
+
"@contember/schema-utils": "1.4.0-alpha.2",
|
|
20
20
|
"fast-deep-equal": "^3.1.3",
|
|
21
21
|
"rfc6902": "^5.0.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@contember/schema-definition": "1.4.0-alpha.
|
|
24
|
+
"@contember/schema-definition": "1.4.0-alpha.2"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"pg": "^8.9.0"
|
package/src/SchemaDiffer.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { CreateTargetDiffer } from './modifications/actions/CreateTargetModifica
|
|
|
43
43
|
import { RemoveTargetDiffer } from './modifications/actions/RemoveTargetModification'
|
|
44
44
|
import { UpdateSettingsDiffer } from './modifications/settings'
|
|
45
45
|
import { RemoveIndexNamesDiffer } from './modifications/upgrade/RemoveIndexNamesModification'
|
|
46
|
+
import { ConvertOneHasManyToOneHasOneRelationDiffer } from './modifications/relations/ConvertOneHasManyToOneHasOneRelationModification'
|
|
46
47
|
|
|
47
48
|
type DiffOptions = { skipRecreateValidation?: boolean; skipInitialSchemaValidation?: boolean }
|
|
48
49
|
|
|
@@ -69,6 +70,7 @@ export class SchemaDiffer {
|
|
|
69
70
|
new UpdateSettingsDiffer(),
|
|
70
71
|
new ConvertOneToManyRelationDiffer(),
|
|
71
72
|
new ConvertOneHasManyToManyHasManyRelationDiffer(),
|
|
73
|
+
new ConvertOneHasManyToOneHasOneRelationDiffer(),
|
|
72
74
|
new RemoveUniqueConstraintDiffer(),
|
|
73
75
|
new RemoveIndexDiffer(),
|
|
74
76
|
new RemoveViewDiffer(),
|
|
@@ -36,6 +36,7 @@ import { removeTargetModification } from './actions/RemoveTargetModification'
|
|
|
36
36
|
import { updateTargetModification } from './actions/UpdateTargetModification'
|
|
37
37
|
import { updateEntityOrderByModification } from './entities/UpdateEntityOrderByModification'
|
|
38
38
|
import { removeIndexNamesModification } from './upgrade/RemoveIndexNamesModification'
|
|
39
|
+
import { convertOneHasManyToOneHasOneRelationModification } from './relations/ConvertOneHasManyToOneHasOneRelationModification'
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class ModificationHandlerFactory {
|
|
@@ -86,6 +87,7 @@ namespace ModificationHandlerFactory {
|
|
|
86
87
|
updateValidationSchemaModification,
|
|
87
88
|
patchValidationSchemaModification,
|
|
88
89
|
convertOneToManyRelationModification,
|
|
90
|
+
convertOneHasManyToOneHasOneRelationModification,
|
|
89
91
|
toggleEventLogModification,
|
|
90
92
|
toggleJunctionEventLogModification,
|
|
91
93
|
convertOneHasManyToManyHasManyRelationModification,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { MigrationBuilder } from '@contember/database-migrations'
|
|
2
|
+
import { Model, Schema } from '@contember/schema'
|
|
3
|
+
import { SchemaUpdater, updateEntity, updateField, updateModel, updateSchema } from '../utils/schemaUpdateUtils'
|
|
4
|
+
import {
|
|
5
|
+
createModificationType,
|
|
6
|
+
Differ,
|
|
7
|
+
ModificationHandler,
|
|
8
|
+
ModificationHandlerCreateSqlOptions,
|
|
9
|
+
ModificationHandlerOptions,
|
|
10
|
+
} from '../ModificationHandler'
|
|
11
|
+
import { isOwningRelation } from '@contember/schema-utils'
|
|
12
|
+
import { updateRelations } from '../utils/diffUtils'
|
|
13
|
+
import { NoopModification } from '../NoopModification'
|
|
14
|
+
import { updateFieldNameModification } from '../fields'
|
|
15
|
+
import { wrapIdentifier } from '../../utils/dbHelpers'
|
|
16
|
+
|
|
17
|
+
export class ConvertOneHasManyToOneHasOneRelationModificationHandler implements ModificationHandler<ConvertOneHasManyToOneHasOneRelationModificationData> {
|
|
18
|
+
private subModification: ModificationHandler<any>
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly data: ConvertOneHasManyToOneHasOneRelationModificationData,
|
|
22
|
+
private readonly schema: Schema,
|
|
23
|
+
private readonly options: ModificationHandlerOptions,
|
|
24
|
+
) {
|
|
25
|
+
const { relation } = this.getRelation()
|
|
26
|
+
this.subModification = data.newInverseSideFieldName && relation.inversedBy ?
|
|
27
|
+
updateFieldNameModification.createHandler(
|
|
28
|
+
{
|
|
29
|
+
entityName: relation.target,
|
|
30
|
+
fieldName: relation.inversedBy,
|
|
31
|
+
newFieldName: data.newInverseSideFieldName,
|
|
32
|
+
},
|
|
33
|
+
schema,
|
|
34
|
+
this.options,
|
|
35
|
+
)
|
|
36
|
+
: new NoopModification()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public createSql(builder: MigrationBuilder, options: ModificationHandlerCreateSqlOptions): void {
|
|
40
|
+
const { entity, relation } = this.getRelation()
|
|
41
|
+
const columnName = relation.joiningColumn.columnName
|
|
42
|
+
|
|
43
|
+
const tableNameId = wrapIdentifier(entity.tableName)
|
|
44
|
+
const columnNameId = wrapIdentifier(columnName)
|
|
45
|
+
|
|
46
|
+
const uniqueConstraintNames = options.databaseMetadata.indexes.filter({
|
|
47
|
+
tableName: entity.tableName,
|
|
48
|
+
columnNames: [columnName],
|
|
49
|
+
}).getNames()
|
|
50
|
+
|
|
51
|
+
for (const name of uniqueConstraintNames) {
|
|
52
|
+
builder.sql(`DROP INDEX ${wrapIdentifier(name)}`)
|
|
53
|
+
}
|
|
54
|
+
builder.sql(`ALTER TABLE ${tableNameId} ADD UNIQUE (${columnNameId})`)
|
|
55
|
+
|
|
56
|
+
options.invalidateDatabaseMetadata()
|
|
57
|
+
|
|
58
|
+
this.subModification.createSql(builder, options)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public getSchemaUpdater(): SchemaUpdater {
|
|
62
|
+
const { relation } = this.getRelation()
|
|
63
|
+
const { entityName, fieldName } = this.data
|
|
64
|
+
const inverseFieldName = this.data.newInverseSideFieldName ?? relation.inversedBy
|
|
65
|
+
return updateSchema(
|
|
66
|
+
updateModel(
|
|
67
|
+
updateEntity(
|
|
68
|
+
entityName,
|
|
69
|
+
updateField<Model.ManyHasOneRelation, Model.OneHasOneOwningRelation>(
|
|
70
|
+
fieldName,
|
|
71
|
+
({ field: { type, ...field } }) => ({
|
|
72
|
+
type: Model.RelationType.OneHasOne,
|
|
73
|
+
...field,
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
this.subModification.getSchemaUpdater(),
|
|
79
|
+
inverseFieldName ?
|
|
80
|
+
updateModel(
|
|
81
|
+
updateEntity(
|
|
82
|
+
relation.target,
|
|
83
|
+
updateField<Model.OneHasManyRelation, Model.OneHasOneInverseRelation>(
|
|
84
|
+
inverseFieldName,
|
|
85
|
+
({ field: { type, ...field } }) => ({
|
|
86
|
+
type: Model.RelationType.OneHasOne,
|
|
87
|
+
nullable: true,
|
|
88
|
+
...field,
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
) : undefined,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
describe() {
|
|
97
|
+
return {
|
|
98
|
+
message: `Converts ManyHasOne relation to OneHasOne on ${this.data.entityName}.${this.data.fieldName}`,
|
|
99
|
+
failureWarning: 'Make sure no conflicting rows exists, otherwise this may fail in runtime.',
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private getRelation() {
|
|
104
|
+
const entity = this.schema.model.entities[this.data.entityName]
|
|
105
|
+
const relation = entity.fields[this.data.fieldName]
|
|
106
|
+
if (relation.type !== Model.RelationType.ManyHasOne) {
|
|
107
|
+
throw new Error()
|
|
108
|
+
}
|
|
109
|
+
return { entity, relation }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface ConvertOneHasManyToOneHasOneRelationModificationData {
|
|
114
|
+
entityName: string
|
|
115
|
+
fieldName: string
|
|
116
|
+
newInverseSideFieldName?: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const convertOneHasManyToOneHasOneRelationModification = createModificationType({
|
|
120
|
+
id: 'convertOneHasManyToOneHasOneRelation',
|
|
121
|
+
handler: ConvertOneHasManyToOneHasOneRelationModificationHandler,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
export class ConvertOneHasManyToOneHasOneRelationDiffer implements Differ {
|
|
125
|
+
createDiff(originalSchema: Schema, updatedSchema: Schema) {
|
|
126
|
+
return updateRelations(originalSchema, updatedSchema, ({ originalRelation, updatedRelation, updatedEntity }) => {
|
|
127
|
+
if (
|
|
128
|
+
isOwningRelation(originalRelation) &&
|
|
129
|
+
isOwningRelation(updatedRelation) &&
|
|
130
|
+
originalRelation.type === Model.RelationType.ManyHasOne &&
|
|
131
|
+
updatedRelation.type === Model.RelationType.OneHasOne
|
|
132
|
+
) {
|
|
133
|
+
const isInverseSideRenamed = originalRelation.inversedBy && updatedRelation.inversedBy && originalRelation.inversedBy !== updatedRelation.inversedBy
|
|
134
|
+
return convertOneHasManyToOneHasOneRelationModification.createModification({
|
|
135
|
+
entityName: updatedEntity.name,
|
|
136
|
+
fieldName: updatedRelation.name,
|
|
137
|
+
...(isInverseSideRenamed ? {
|
|
138
|
+
newInverseSideFieldName: updatedRelation.inversedBy,
|
|
139
|
+
} : {}),
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
return undefined
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -61,6 +61,7 @@ export class ConvertOneToManyRelationModificationHandler implements Modification
|
|
|
61
61
|
const { relation } = this.getRelation()
|
|
62
62
|
const { entityName, fieldName } = this.data
|
|
63
63
|
|
|
64
|
+
const inverseFieldName = this.data.newInverseSideFieldName ?? relation.inversedBy
|
|
64
65
|
return updateSchema(
|
|
65
66
|
updateModel(
|
|
66
67
|
updateEntity(
|
|
@@ -75,12 +76,12 @@ export class ConvertOneToManyRelationModificationHandler implements Modification
|
|
|
75
76
|
),
|
|
76
77
|
),
|
|
77
78
|
this.subModification.getSchemaUpdater(),
|
|
78
|
-
|
|
79
|
+
inverseFieldName ?
|
|
79
80
|
updateModel(
|
|
80
81
|
updateEntity(
|
|
81
82
|
relation.target,
|
|
82
83
|
updateField<Model.OneHasOneInverseRelation, Model.OneHasManyRelation>(
|
|
83
|
-
|
|
84
|
+
inverseFieldName,
|
|
84
85
|
({ field: { type, nullable, ...field } }) => ({
|
|
85
86
|
type: Model.RelationType.OneHasMany,
|
|
86
87
|
...field,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { testMigrations } from '../../src/tests'
|
|
2
|
+
import { SQL } from '../../src/tags'
|
|
3
|
+
import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
|
|
4
|
+
import { createDatabaseMetadata } from '@contember/database'
|
|
5
|
+
|
|
6
|
+
namespace ConvertMHOtoOHOSchemaOrig {
|
|
7
|
+
export class Article {
|
|
8
|
+
title = def.stringColumn()
|
|
9
|
+
reviews = def.oneHasMany(ArticleReview, 'article')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ArticleReview {
|
|
13
|
+
article = def.manyHasOne(Article, 'reviews')
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
namespace ConvertMHOtoOHOSchemaUpdated {
|
|
18
|
+
export class Article {
|
|
19
|
+
title = def.stringColumn()
|
|
20
|
+
review = def.oneHasOneInverse(ArticleReview, 'article')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class ArticleReview {
|
|
24
|
+
article = def.oneHasOne(Article, 'review')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
testMigrations('convert one has many to one has one', {
|
|
30
|
+
original: createSchema(ConvertMHOtoOHOSchemaOrig),
|
|
31
|
+
updated: createSchema(ConvertMHOtoOHOSchemaUpdated),
|
|
32
|
+
diff: [{
|
|
33
|
+
entityName: 'ArticleReview',
|
|
34
|
+
fieldName: 'article',
|
|
35
|
+
modification: 'convertOneHasManyToOneHasOneRelation',
|
|
36
|
+
newInverseSideFieldName: 'review',
|
|
37
|
+
}],
|
|
38
|
+
sql: SQL`DROP INDEX "article_review_article_id_idx"; ALTER TABLE "article_review" ADD UNIQUE ("article_id");`,
|
|
39
|
+
databaseMetadata: createDatabaseMetadata({
|
|
40
|
+
foreignKeys: [],
|
|
41
|
+
indexes: [{
|
|
42
|
+
indexName: 'article_review_article_id_idx',
|
|
43
|
+
columnNames: ['article_id'],
|
|
44
|
+
tableName: 'article_review',
|
|
45
|
+
}],
|
|
46
|
+
uniqueConstraints: [],
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
@@ -88,3 +88,45 @@ testMigrations('convert one has one to many has one relation with inverse side',
|
|
|
88
88
|
}),
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
+
testMigrations('convert one has one to many has one relation with inverse side, but not renamed', {
|
|
92
|
+
original: {
|
|
93
|
+
model: new SchemaBuilder()
|
|
94
|
+
.entity('Post', entity =>
|
|
95
|
+
entity.oneHasOne('image', rel => rel.target('Image').inversedBy('posts')),
|
|
96
|
+
)
|
|
97
|
+
.entity('Image', e => e.column('url', c => c.type(Model.ColumnType.String)))
|
|
98
|
+
.buildSchema(),
|
|
99
|
+
},
|
|
100
|
+
updated: {
|
|
101
|
+
model: new SchemaBuilder()
|
|
102
|
+
.entity('Post', entity =>
|
|
103
|
+
entity.manyHasOne('image', rel => rel.target('Image').inversedBy('posts')),
|
|
104
|
+
)
|
|
105
|
+
.entity('Image', e => e.column('url', c => c.type(Model.ColumnType.String)))
|
|
106
|
+
.buildSchema(),
|
|
107
|
+
},
|
|
108
|
+
diff: [
|
|
109
|
+
{
|
|
110
|
+
modification: 'convertOneToManyRelation',
|
|
111
|
+
entityName: 'Post',
|
|
112
|
+
fieldName: 'image',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
sql: SQL`
|
|
116
|
+
CREATE INDEX ON "post" ("image_id");
|
|
117
|
+
ALTER TABLE "post"
|
|
118
|
+
DROP CONSTRAINT "uniq_post_image_id";
|
|
119
|
+
`,
|
|
120
|
+
databaseMetadata: createDatabaseMetadata({
|
|
121
|
+
foreignKeys: [],
|
|
122
|
+
indexes: [],
|
|
123
|
+
uniqueConstraints: [{
|
|
124
|
+
constraintName: 'uniq_post_image_id',
|
|
125
|
+
columnNames: ['image_id'],
|
|
126
|
+
tableName: 'post',
|
|
127
|
+
deferred: false,
|
|
128
|
+
deferrable: false,
|
|
129
|
+
}],
|
|
130
|
+
}),
|
|
131
|
+
})
|
|
132
|
+
|