@smartive/graphql-magic 23.6.1-next.2 → 23.7.0-next.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.
- package/.gqmrc.json +2 -1
- package/CHANGELOG.md +3 -3
- package/dist/bin/gqm.cjs +278 -115
- package/dist/cjs/index.cjs +288 -125
- package/dist/esm/db/generate.js +5 -5
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +13 -1
- package/dist/esm/migrations/generate.js +197 -41
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/migrations/index.d.ts +2 -1
- package/dist/esm/migrations/index.js +2 -1
- package/dist/esm/migrations/index.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +27 -2
- package/dist/esm/models/models.d.ts +1 -5
- package/dist/esm/models/models.js +4 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/utils.d.ts +16 -7
- package/dist/esm/models/utils.js +16 -6
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.js +2 -2
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +6 -6
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/resolvers.js +3 -9
- package/dist/esm/resolvers/resolvers.js.map +1 -1
- package/dist/esm/schema/generate.js +3 -9
- package/dist/esm/schema/generate.js.map +1 -1
- package/docker-compose.yml +2 -3
- package/docs/docs/2-models.md +18 -4
- package/docs/docs/5-migrations.md +11 -5
- package/package.json +3 -3
- package/src/bin/gqm/parse-knexfile.ts +1 -0
- package/src/bin/gqm/settings.ts +4 -0
- package/src/db/generate.ts +5 -15
- package/src/migrations/generate.ts +257 -42
- package/src/migrations/index.ts +2 -1
- package/src/models/model-definitions.ts +20 -1
- package/src/models/models.ts +4 -1
- package/src/models/utils.ts +27 -8
- package/src/permissions/check.ts +2 -2
- package/src/resolvers/mutations.ts +6 -6
- package/src/resolvers/resolvers.ts +7 -13
- package/src/schema/generate.ts +28 -26
- package/tests/unit/constraints.spec.ts +98 -2
- package/tests/unit/generate-as.spec.ts +6 -6
- package/tests/utils/functions.ts +1 -0
package/dist/esm/db/generate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import CodeBlockWriter from 'code-block-writer';
|
|
2
|
-
import { get, getColumnName,
|
|
2
|
+
import { and, get, getColumnName, isDynamicField, isInTable, isRootModel, isStoredInDatabase, not } from '..';
|
|
3
3
|
import { DATE_CLASS, DATE_CLASS_IMPORT } from '../utils/dates';
|
|
4
4
|
export const PRIMITIVE_TYPES = {
|
|
5
5
|
ID: 'string',
|
|
@@ -49,7 +49,7 @@ export const generateDBModels = (models, dateLibrary) => {
|
|
|
49
49
|
writer
|
|
50
50
|
.write(`export type ${model.name} = `)
|
|
51
51
|
.inlineBlock(() => {
|
|
52
|
-
for (const field of fields.filter(
|
|
52
|
+
for (const field of fields.filter(isStoredInDatabase)) {
|
|
53
53
|
writer
|
|
54
54
|
.write(`'${getColumnName(field)}': ${getFieldType(field, dateLibrary)}${field.nonNull ? '' : ' | null'};`)
|
|
55
55
|
.newLine();
|
|
@@ -59,7 +59,7 @@ export const generateDBModels = (models, dateLibrary) => {
|
|
|
59
59
|
writer
|
|
60
60
|
.write(`export type ${model.name}Initializer = `)
|
|
61
61
|
.inlineBlock(() => {
|
|
62
|
-
for (const field of fields.filter(
|
|
62
|
+
for (const field of fields.filter(and(isInTable, not(isDynamicField)))) {
|
|
63
63
|
writer
|
|
64
64
|
.write(`'${getColumnName(field)}'${field.nonNull && field.defaultValue === undefined ? '' : '?'}: ${getFieldType(field, dateLibrary, true)}${field.list ? ' | string' : ''}${field.nonNull ? '' : ' | null'};`)
|
|
65
65
|
.newLine();
|
|
@@ -69,7 +69,7 @@ export const generateDBModels = (models, dateLibrary) => {
|
|
|
69
69
|
writer
|
|
70
70
|
.write(`export type ${model.name}Mutator = `)
|
|
71
71
|
.inlineBlock(() => {
|
|
72
|
-
for (const field of fields.filter(
|
|
72
|
+
for (const field of fields.filter(and(isInTable, not(isDynamicField)))) {
|
|
73
73
|
writer
|
|
74
74
|
.write(`'${getColumnName(field)}'?: ${getFieldType(field, dateLibrary, true)}${field.list ? ' | string' : ''}${field.nonNull ? '' : ' | null'};`)
|
|
75
75
|
.newLine();
|
|
@@ -80,7 +80,7 @@ export const generateDBModels = (models, dateLibrary) => {
|
|
|
80
80
|
writer
|
|
81
81
|
.write(`export type ${model.name}Seed = `)
|
|
82
82
|
.inlineBlock(() => {
|
|
83
|
-
for (const field of fields.filter(not(
|
|
83
|
+
for (const field of fields.filter(not(isDynamicField))) {
|
|
84
84
|
if (model.parent && field.name === 'type') {
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/db/generate.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/db/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,10 @@ export declare class MigrationGenerator {
|
|
|
9
9
|
private columns;
|
|
10
10
|
/** table name -> constraint name -> check clause expression */
|
|
11
11
|
private existingCheckConstraints;
|
|
12
|
+
/** table name -> constraint name -> exclude definition (normalized) */
|
|
13
|
+
private existingExcludeConstraints;
|
|
14
|
+
/** table name -> constraint name -> trigger definition (normalized) */
|
|
15
|
+
private existingConstraintTriggers;
|
|
12
16
|
private uuidUsed?;
|
|
13
17
|
private nowUsed?;
|
|
14
18
|
needsMigration: boolean;
|
|
@@ -30,12 +34,20 @@ export declare class MigrationGenerator {
|
|
|
30
34
|
private dropTable;
|
|
31
35
|
private renameTable;
|
|
32
36
|
private renameColumn;
|
|
33
|
-
private
|
|
37
|
+
private getConstraintName;
|
|
34
38
|
private normalizeCheckExpression;
|
|
39
|
+
private normalizeExcludeDef;
|
|
40
|
+
private normalizeTriggerDef;
|
|
35
41
|
/** Escape expression for embedding inside a template literal in generated code */
|
|
36
42
|
private escapeExpressionForRaw;
|
|
37
43
|
private addCheckConstraint;
|
|
38
44
|
private dropCheckConstraint;
|
|
45
|
+
private buildExcludeDef;
|
|
46
|
+
private addExcludeConstraint;
|
|
47
|
+
private dropExcludeConstraint;
|
|
48
|
+
private buildConstraintTriggerDef;
|
|
49
|
+
private addConstraintTrigger;
|
|
50
|
+
private dropConstraintTrigger;
|
|
39
51
|
private value;
|
|
40
52
|
private columnRaw;
|
|
41
53
|
private column;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import CodeBlockWriter from 'code-block-writer';
|
|
2
2
|
import { SchemaInspector } from 'knex-schema-inspector';
|
|
3
3
|
import lowerFirst from 'lodash/lowerFirst';
|
|
4
|
-
import { and, get, isCreatableModel,
|
|
4
|
+
import { and, get, isCreatableModel, isInherited, isStoredInDatabase, isUpdatableField, isUpdatableModel, modelNeedsTable, not, summonByName, typeToField, validateCheckConstraint, validateExcludeConstraint, } from '../models/utils';
|
|
5
5
|
import { getColumnName } from '../resolvers';
|
|
6
6
|
import { getDatabaseFunctions, normalizeAggregateDefinition, normalizeFunctionBody, } from './update-functions';
|
|
7
7
|
export class MigrationGenerator {
|
|
@@ -16,6 +16,10 @@ export class MigrationGenerator {
|
|
|
16
16
|
columns = {};
|
|
17
17
|
/** table name -> constraint name -> check clause expression */
|
|
18
18
|
existingCheckConstraints = {};
|
|
19
|
+
/** table name -> constraint name -> exclude definition (normalized) */
|
|
20
|
+
existingExcludeConstraints = {};
|
|
21
|
+
/** table name -> constraint name -> trigger definition (normalized) */
|
|
22
|
+
existingConstraintTriggers = {};
|
|
19
23
|
uuidUsed;
|
|
20
24
|
nowUsed;
|
|
21
25
|
needsMigration = false;
|
|
@@ -48,8 +52,44 @@ export class MigrationGenerator {
|
|
|
48
52
|
}
|
|
49
53
|
this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
|
|
50
54
|
}
|
|
55
|
+
const excludeResult = await schema.knex.raw(`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_constraintdef(c.oid) as constraint_def
|
|
56
|
+
FROM pg_constraint c
|
|
57
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
58
|
+
WHERE n.nspname = 'public' AND c.contype = 'x'`);
|
|
59
|
+
const excludeRows = 'rows' in excludeResult && Array.isArray(excludeResult.rows)
|
|
60
|
+
? excludeResult.rows
|
|
61
|
+
: [];
|
|
62
|
+
for (const row of excludeRows) {
|
|
63
|
+
const tableName = row.table_name.split('.').pop()?.replace(/^"|"$/g, '') ?? row.table_name;
|
|
64
|
+
if (!this.existingExcludeConstraints[tableName]) {
|
|
65
|
+
this.existingExcludeConstraints[tableName] = new Map();
|
|
66
|
+
}
|
|
67
|
+
this.existingExcludeConstraints[tableName].set(row.constraint_name, this.normalizeExcludeDef(row.constraint_def));
|
|
68
|
+
}
|
|
69
|
+
const triggerResult = await schema.knex.raw(`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_triggerdef(t.oid) as trigger_def
|
|
70
|
+
FROM pg_constraint c
|
|
71
|
+
JOIN pg_trigger t ON t.tgconstraint = c.oid
|
|
72
|
+
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
73
|
+
WHERE n.nspname = 'public' AND c.contype = 't'`);
|
|
74
|
+
const triggerRows = 'rows' in triggerResult && Array.isArray(triggerResult.rows)
|
|
75
|
+
? triggerResult.rows
|
|
76
|
+
: [];
|
|
77
|
+
for (const row of triggerRows) {
|
|
78
|
+
const tableName = row.table_name.split('.').pop()?.replace(/^"|"$/g, '') ?? row.table_name;
|
|
79
|
+
if (!this.existingConstraintTriggers[tableName]) {
|
|
80
|
+
this.existingConstraintTriggers[tableName] = new Map();
|
|
81
|
+
}
|
|
82
|
+
this.existingConstraintTriggers[tableName].set(row.constraint_name, this.normalizeTriggerDef(row.trigger_def));
|
|
83
|
+
}
|
|
51
84
|
const up = [];
|
|
52
85
|
const down = [];
|
|
86
|
+
const needsBtreeGist = models.entities.some((model) => model.constraints?.some((c) => c.kind === 'exclude' && c.elements.some((el) => 'column' in el && el.operator === '=')));
|
|
87
|
+
if (needsBtreeGist) {
|
|
88
|
+
up.unshift(() => {
|
|
89
|
+
this.writer.writeLine(`await knex.raw('CREATE EXTENSION IF NOT EXISTS btree_gist');`);
|
|
90
|
+
this.writer.blankLine();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
53
93
|
this.createEnums(this.models.enums.filter((enm) => !enums.includes(lowerFirst(enm.name))), up, down);
|
|
54
94
|
await this.handleFunctions(up, down);
|
|
55
95
|
for (const model of models.entities) {
|
|
@@ -129,10 +169,24 @@ export class MigrationGenerator {
|
|
|
129
169
|
if (entry.kind === 'check') {
|
|
130
170
|
validateCheckConstraint(model, entry);
|
|
131
171
|
const table = model.name;
|
|
132
|
-
const constraintName = this.
|
|
133
|
-
const expression = entry.expression;
|
|
172
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
134
173
|
up.push(() => {
|
|
135
|
-
this.addCheckConstraint(table, constraintName, expression);
|
|
174
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (entry.kind === 'exclude') {
|
|
178
|
+
validateExcludeConstraint(model, entry);
|
|
179
|
+
const table = model.name;
|
|
180
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
181
|
+
up.push(() => {
|
|
182
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else if (entry.kind === 'constraint_trigger') {
|
|
186
|
+
const table = model.name;
|
|
187
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
188
|
+
up.push(() => {
|
|
189
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
136
190
|
});
|
|
137
191
|
}
|
|
138
192
|
}
|
|
@@ -171,34 +225,84 @@ export class MigrationGenerator {
|
|
|
171
225
|
const existingFields = model.fields.filter((field) => (!field.generateAs || field.generateAs.type === 'expression') && this.hasChanged(model, field));
|
|
172
226
|
this.updateFields(model, existingFields, up, down);
|
|
173
227
|
if (model.constraints?.length) {
|
|
174
|
-
const
|
|
228
|
+
const existingCheckMap = this.existingCheckConstraints[model.name];
|
|
229
|
+
const existingExcludeMap = this.existingExcludeConstraints[model.name];
|
|
230
|
+
const existingTriggerMap = this.existingConstraintTriggers[model.name];
|
|
175
231
|
for (let i = 0; i < model.constraints.length; i++) {
|
|
176
232
|
const entry = model.constraints[i];
|
|
177
|
-
if (entry.kind !== 'check') {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
validateCheckConstraint(model, entry);
|
|
181
233
|
const table = model.name;
|
|
182
|
-
const constraintName = this.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
235
|
+
if (entry.kind === 'check') {
|
|
236
|
+
validateCheckConstraint(model, entry);
|
|
237
|
+
const newExpression = entry.expression;
|
|
238
|
+
const existingExpression = existingCheckMap?.get(constraintName);
|
|
239
|
+
if (existingExpression === undefined) {
|
|
240
|
+
up.push(() => {
|
|
241
|
+
this.addCheckConstraint(table, constraintName, newExpression, entry.deferrable);
|
|
242
|
+
});
|
|
243
|
+
down.push(() => {
|
|
244
|
+
this.dropCheckConstraint(table, constraintName);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
|
|
248
|
+
up.push(() => {
|
|
249
|
+
this.dropCheckConstraint(table, constraintName);
|
|
250
|
+
this.addCheckConstraint(table, constraintName, newExpression, entry.deferrable);
|
|
251
|
+
});
|
|
252
|
+
down.push(() => {
|
|
253
|
+
this.dropCheckConstraint(table, constraintName);
|
|
254
|
+
this.addCheckConstraint(table, constraintName, existingExpression);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
192
257
|
}
|
|
193
|
-
else if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
258
|
+
else if (entry.kind === 'exclude') {
|
|
259
|
+
validateExcludeConstraint(model, entry);
|
|
260
|
+
const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
|
|
261
|
+
const existingDef = existingExcludeMap?.get(constraintName);
|
|
262
|
+
if (existingDef === undefined) {
|
|
263
|
+
up.push(() => {
|
|
264
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
265
|
+
});
|
|
266
|
+
down.push(() => {
|
|
267
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else if (existingDef !== newDef) {
|
|
271
|
+
up.push(() => {
|
|
272
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
273
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
274
|
+
});
|
|
275
|
+
down.push(() => {
|
|
276
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
277
|
+
const escaped = this.escapeExpressionForRaw(existingDef);
|
|
278
|
+
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
|
|
279
|
+
this.writer.blankLine();
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (entry.kind === 'constraint_trigger') {
|
|
284
|
+
const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
|
|
285
|
+
const existingDef = existingTriggerMap?.get(constraintName);
|
|
286
|
+
if (existingDef === undefined) {
|
|
287
|
+
up.push(() => {
|
|
288
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
289
|
+
});
|
|
290
|
+
down.push(() => {
|
|
291
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
else if (existingDef !== newDef) {
|
|
295
|
+
up.push(() => {
|
|
296
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
297
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
298
|
+
});
|
|
299
|
+
down.push(() => {
|
|
300
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
301
|
+
const escaped = existingDef.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
302
|
+
this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
|
|
303
|
+
this.writer.blankLine();
|
|
304
|
+
});
|
|
305
|
+
}
|
|
202
306
|
}
|
|
203
307
|
}
|
|
204
308
|
}
|
|
@@ -228,9 +332,7 @@ export class MigrationGenerator {
|
|
|
228
332
|
writer.writeLine(`deleteRootType: row.deleteRootType,`);
|
|
229
333
|
writer.writeLine(`deleteRootId: row.deleteRootId,`);
|
|
230
334
|
}
|
|
231
|
-
for (const { name, kind } of model.fields
|
|
232
|
-
.filter(isUpdatableField)
|
|
233
|
-
.filter(not(isGenerateAsField))) {
|
|
335
|
+
for (const { name, kind } of model.fields.filter(and(isUpdatableField, isStoredInDatabase))) {
|
|
234
336
|
const col = kind === 'relation' ? `${name}Id` : name;
|
|
235
337
|
writer.writeLine(`${col}: row.${col},`);
|
|
236
338
|
}
|
|
@@ -253,10 +355,8 @@ export class MigrationGenerator {
|
|
|
253
355
|
.filter(not(isInherited))
|
|
254
356
|
.filter(({ oldName }) => oldName), up, down);
|
|
255
357
|
const missingRevisionFields = model.fields
|
|
256
|
-
.filter(isUpdatableField)
|
|
257
|
-
.filter(
|
|
258
|
-
.filter(({ name, ...field }) => field.kind !== 'custom' &&
|
|
259
|
-
!this.getColumn(revisionTable, field.kind === 'relation' ? field.foreignKey || `${name}Id` : name));
|
|
358
|
+
.filter(and(isUpdatableField, isStoredInDatabase))
|
|
359
|
+
.filter(({ name, ...field }) => !this.getColumn(revisionTable, field.kind === 'relation' ? field.foreignKey || `${name}Id` : name));
|
|
260
360
|
this.createRevisionFields(model, missingRevisionFields, up, down);
|
|
261
361
|
const revisionFieldsToRemove = model.fields.filter(({ name, updatable, generated, ...field }) => !generated &&
|
|
262
362
|
field.kind !== 'custom' &&
|
|
@@ -407,7 +507,7 @@ export class MigrationGenerator {
|
|
|
407
507
|
});
|
|
408
508
|
});
|
|
409
509
|
if (isUpdatableModel(model)) {
|
|
410
|
-
const updatableFields = fields.filter(isUpdatableField)
|
|
510
|
+
const updatableFields = fields.filter(and(isUpdatableField, isStoredInDatabase));
|
|
411
511
|
if (!updatableFields.length) {
|
|
412
512
|
return;
|
|
413
513
|
}
|
|
@@ -446,7 +546,7 @@ export class MigrationGenerator {
|
|
|
446
546
|
});
|
|
447
547
|
});
|
|
448
548
|
if (isUpdatableModel(model)) {
|
|
449
|
-
const updatableFields = fields.filter(isUpdatableField)
|
|
549
|
+
const updatableFields = fields.filter(and(isUpdatableField, isStoredInDatabase));
|
|
450
550
|
if (!updatableFields.length) {
|
|
451
551
|
return;
|
|
452
552
|
}
|
|
@@ -481,7 +581,7 @@ export class MigrationGenerator {
|
|
|
481
581
|
writer.writeLine(`table.uuid('deleteRootId');`);
|
|
482
582
|
}
|
|
483
583
|
}
|
|
484
|
-
for (const field of model.fields.filter(and(isUpdatableField, not(isInherited)))
|
|
584
|
+
for (const field of model.fields.filter(and(isUpdatableField, not(isInherited), isStoredInDatabase))) {
|
|
485
585
|
this.column(field, { setUnique: false, setDefault: false });
|
|
486
586
|
}
|
|
487
587
|
});
|
|
@@ -585,25 +685,81 @@ export class MigrationGenerator {
|
|
|
585
685
|
renameColumn(from, to) {
|
|
586
686
|
this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
|
|
587
687
|
}
|
|
588
|
-
|
|
688
|
+
getConstraintName(model, entry, index) {
|
|
589
689
|
return `${model.name}_${entry.name}_${entry.kind}_${index}`;
|
|
590
690
|
}
|
|
591
691
|
normalizeCheckExpression(expr) {
|
|
592
692
|
return expr.replace(/\s+/g, ' ').trim();
|
|
593
693
|
}
|
|
694
|
+
normalizeExcludeDef(def) {
|
|
695
|
+
return def
|
|
696
|
+
.replace(/\s+/g, ' ')
|
|
697
|
+
.replace(/\s*\(\s*/g, '(')
|
|
698
|
+
.replace(/\s*\)\s*/g, ')')
|
|
699
|
+
.trim();
|
|
700
|
+
}
|
|
701
|
+
normalizeTriggerDef(def) {
|
|
702
|
+
return def
|
|
703
|
+
.replace(/\s+/g, ' ')
|
|
704
|
+
.replace(/\s*\(\s*/g, '(')
|
|
705
|
+
.replace(/\s*\)\s*/g, ')')
|
|
706
|
+
.trim();
|
|
707
|
+
}
|
|
594
708
|
/** Escape expression for embedding inside a template literal in generated code */
|
|
595
709
|
escapeExpressionForRaw(expr) {
|
|
596
710
|
return expr.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
597
711
|
}
|
|
598
|
-
addCheckConstraint(table, constraintName, expression) {
|
|
712
|
+
addCheckConstraint(table, constraintName, expression, deferrable) {
|
|
599
713
|
const escaped = this.escapeExpressionForRaw(expression);
|
|
600
|
-
|
|
714
|
+
const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : '';
|
|
715
|
+
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`);
|
|
601
716
|
this.writer.blankLine();
|
|
602
717
|
}
|
|
603
718
|
dropCheckConstraint(table, constraintName) {
|
|
604
719
|
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
605
720
|
this.writer.blankLine();
|
|
606
721
|
}
|
|
722
|
+
buildExcludeDef(entry) {
|
|
723
|
+
const elementsStr = entry.elements
|
|
724
|
+
.map((el) => ('column' in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`))
|
|
725
|
+
.join(', ');
|
|
726
|
+
const whereClause = entry.where ? ` WHERE (${entry.where})` : '';
|
|
727
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : '';
|
|
728
|
+
return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
|
|
729
|
+
}
|
|
730
|
+
addExcludeConstraint(table, constraintName, entry) {
|
|
731
|
+
const def = this.buildExcludeDef(entry);
|
|
732
|
+
const escaped = this.escapeExpressionForRaw(def);
|
|
733
|
+
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
|
|
734
|
+
this.writer.blankLine();
|
|
735
|
+
}
|
|
736
|
+
dropExcludeConstraint(table, constraintName) {
|
|
737
|
+
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
738
|
+
this.writer.blankLine();
|
|
739
|
+
}
|
|
740
|
+
buildConstraintTriggerDef(table, constraintName, entry) {
|
|
741
|
+
const eventsStr = entry.events.join(' OR ');
|
|
742
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : '';
|
|
743
|
+
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(', ') : '';
|
|
744
|
+
const executeClause = argsStr
|
|
745
|
+
? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})`
|
|
746
|
+
: `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
747
|
+
return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}" FOR EACH ${entry.forEach}${deferrableClause} ${executeClause}`;
|
|
748
|
+
}
|
|
749
|
+
addConstraintTrigger(table, constraintName, entry) {
|
|
750
|
+
const eventsStr = entry.events.join(' OR ');
|
|
751
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : '';
|
|
752
|
+
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(', ') : '';
|
|
753
|
+
const executeClause = argsStr
|
|
754
|
+
? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})`
|
|
755
|
+
: `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
756
|
+
this.writer.writeLine(`await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}" FOR EACH ${entry.forEach}${deferrableClause} ${executeClause}\`);`);
|
|
757
|
+
this.writer.blankLine();
|
|
758
|
+
}
|
|
759
|
+
dropConstraintTrigger(table, constraintName) {
|
|
760
|
+
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
761
|
+
this.writer.blankLine();
|
|
762
|
+
}
|
|
607
763
|
value(value) {
|
|
608
764
|
if (typeof value === 'string') {
|
|
609
765
|
return `'${value}'`;
|