@smartive/graphql-magic 23.7.0-next.4 → 23.7.0
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/CHANGELOG.md +3 -3
- package/dist/bin/gqm.cjs +155 -256
- package/dist/cjs/index.cjs +155 -258
- package/dist/esm/migrations/generate-functions.js +0 -2
- package/dist/esm/migrations/generate-functions.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +8 -15
- package/dist/esm/migrations/generate.js +156 -253
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/migrations/update-functions.js +0 -2
- package/dist/esm/migrations/update-functions.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +2 -27
- package/dist/esm/models/models.d.ts +5 -1
- package/dist/esm/models/models.js +1 -4
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/utils.d.ts +0 -10
- package/dist/esm/models/utils.js +0 -11
- package/dist/esm/models/utils.js.map +1 -1
- package/docs/docs/2-models.md +4 -18
- package/docs/docs/5-migrations.md +7 -9
- package/package.json +1 -1
- package/src/bin/gqm/parse-knexfile.ts +0 -1
- package/src/bin/gqm/settings.ts +0 -4
- package/src/migrations/generate-functions.ts +0 -2
- package/src/migrations/generate.ts +191 -310
- package/src/migrations/update-functions.ts +0 -2
- package/src/models/model-definitions.ts +1 -20
- package/src/models/models.ts +1 -4
- package/src/models/utils.ts +0 -20
- package/tests/unit/constraints.spec.ts +2 -98
- package/tests/unit/migration-constraints.spec.ts +300 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -198,7 +198,6 @@ __export(index_exports, {
|
|
|
198
198
|
updateEntity: () => updateEntity,
|
|
199
199
|
updateFunctions: () => updateFunctions,
|
|
200
200
|
validateCheckConstraint: () => validateCheckConstraint,
|
|
201
|
-
validateExcludeConstraint: () => validateExcludeConstraint,
|
|
202
201
|
value: () => value
|
|
203
202
|
});
|
|
204
203
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -655,8 +654,6 @@ var EntityModel = class extends Model {
|
|
|
655
654
|
for (const constraint of this.constraints) {
|
|
656
655
|
if (constraint.kind === "check") {
|
|
657
656
|
validateCheckConstraint(this, constraint);
|
|
658
|
-
} else if (constraint.kind === "exclude") {
|
|
659
|
-
validateExcludeConstraint(this, constraint);
|
|
660
657
|
}
|
|
661
658
|
}
|
|
662
659
|
}
|
|
@@ -955,19 +952,6 @@ var validateCheckConstraint = (model, constraint) => {
|
|
|
955
952
|
}
|
|
956
953
|
}
|
|
957
954
|
};
|
|
958
|
-
var validateExcludeConstraint = (model, constraint) => {
|
|
959
|
-
const validColumnNames = new Set(model.fields.map((f) => getColumnName(f)));
|
|
960
|
-
for (const el of constraint.elements) {
|
|
961
|
-
if ("column" in el) {
|
|
962
|
-
if (!validColumnNames.has(el.column)) {
|
|
963
|
-
const validList = [...validColumnNames].sort().join(", ");
|
|
964
|
-
throw new Error(
|
|
965
|
-
`Exclude constraint "${constraint.name}" references column "${el.column}" which does not exist on model ${model.name}. Valid columns: ${validList}`
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
955
|
|
|
972
956
|
// src/client/queries.ts
|
|
973
957
|
var fieldIsSearchable = (model, fieldName) => {
|
|
@@ -1182,7 +1166,6 @@ var generateFunctionsFromDatabase = async (knex) => {
|
|
|
1182
1166
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1183
1167
|
WHERE n.nspname = 'public'
|
|
1184
1168
|
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
1185
|
-
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
1186
1169
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1187
1170
|
`);
|
|
1188
1171
|
const aggregateFunctions = await knex.raw(`
|
|
@@ -1197,7 +1180,6 @@ var generateFunctionsFromDatabase = async (knex) => {
|
|
|
1197
1180
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
1198
1181
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1199
1182
|
WHERE n.nspname = 'public'
|
|
1200
|
-
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
1201
1183
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1202
1184
|
`);
|
|
1203
1185
|
const functions = [];
|
|
@@ -2942,7 +2924,6 @@ var getDatabaseFunctions = async (knex) => {
|
|
|
2942
2924
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
2943
2925
|
WHERE n.nspname = 'public'
|
|
2944
2926
|
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
2945
|
-
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
2946
2927
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
2947
2928
|
`);
|
|
2948
2929
|
const aggregateFunctions = await knex.raw(`
|
|
@@ -2958,7 +2939,6 @@ var getDatabaseFunctions = async (knex) => {
|
|
|
2958
2939
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
2959
2940
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
2960
2941
|
WHERE n.nspname = 'public'
|
|
2961
|
-
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
2962
2942
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
2963
2943
|
`);
|
|
2964
2944
|
const result = [];
|
|
@@ -3102,7 +3082,7 @@ Summary: ${updatedCount} updated, ${skippedCount} skipped`);
|
|
|
3102
3082
|
};
|
|
3103
3083
|
|
|
3104
3084
|
// src/migrations/generate.ts
|
|
3105
|
-
var MigrationGenerator = class
|
|
3085
|
+
var MigrationGenerator = class {
|
|
3106
3086
|
constructor(knex, models, parsedFunctions) {
|
|
3107
3087
|
this.models = models;
|
|
3108
3088
|
this.parsedFunctions = parsedFunctions;
|
|
@@ -3118,10 +3098,6 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3118
3098
|
columns = {};
|
|
3119
3099
|
/** table name -> constraint name -> check clause expression */
|
|
3120
3100
|
existingCheckConstraints = {};
|
|
3121
|
-
/** table name -> constraint name -> exclude definition (normalized) */
|
|
3122
|
-
existingExcludeConstraints = {};
|
|
3123
|
-
/** table name -> constraint name -> trigger definition (normalized) */
|
|
3124
|
-
existingConstraintTriggers = {};
|
|
3125
3101
|
uuidUsed;
|
|
3126
3102
|
nowUsed;
|
|
3127
3103
|
needsMigration = false;
|
|
@@ -3149,50 +3125,8 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3149
3125
|
}
|
|
3150
3126
|
this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
|
|
3151
3127
|
}
|
|
3152
|
-
const excludeResult = await schema.knex.raw(
|
|
3153
|
-
`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_constraintdef(c.oid) as constraint_def
|
|
3154
|
-
FROM pg_constraint c
|
|
3155
|
-
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
3156
|
-
WHERE n.nspname = 'public' AND c.contype = 'x'`
|
|
3157
|
-
);
|
|
3158
|
-
const excludeRows = "rows" in excludeResult && Array.isArray(excludeResult.rows) ? excludeResult.rows : [];
|
|
3159
|
-
for (const row of excludeRows) {
|
|
3160
|
-
const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
|
|
3161
|
-
if (!this.existingExcludeConstraints[tableName]) {
|
|
3162
|
-
this.existingExcludeConstraints[tableName] = /* @__PURE__ */ new Map();
|
|
3163
|
-
}
|
|
3164
|
-
this.existingExcludeConstraints[tableName].set(row.constraint_name, this.normalizeExcludeDef(row.constraint_def));
|
|
3165
|
-
}
|
|
3166
|
-
const triggerResult = await schema.knex.raw(
|
|
3167
|
-
`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_triggerdef(t.oid) as trigger_def
|
|
3168
|
-
FROM pg_constraint c
|
|
3169
|
-
JOIN pg_trigger t ON t.tgconstraint = c.oid
|
|
3170
|
-
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
3171
|
-
WHERE n.nspname = 'public' AND c.contype = 't'`
|
|
3172
|
-
);
|
|
3173
|
-
const triggerRows = "rows" in triggerResult && Array.isArray(triggerResult.rows) ? triggerResult.rows : [];
|
|
3174
|
-
for (const row of triggerRows) {
|
|
3175
|
-
const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
|
|
3176
|
-
if (!this.existingConstraintTriggers[tableName]) {
|
|
3177
|
-
this.existingConstraintTriggers[tableName] = /* @__PURE__ */ new Map();
|
|
3178
|
-
}
|
|
3179
|
-
this.existingConstraintTriggers[tableName].set(row.constraint_name, this.normalizeTriggerDef(row.trigger_def));
|
|
3180
|
-
}
|
|
3181
3128
|
const up = [];
|
|
3182
3129
|
const down = [];
|
|
3183
|
-
const wantsBtreeGist = models.entities.some(
|
|
3184
|
-
(model) => model.constraints?.some((c) => c.kind === "exclude" && c.elements.some((el) => "column" in el && el.operator === "="))
|
|
3185
|
-
);
|
|
3186
|
-
if (wantsBtreeGist) {
|
|
3187
|
-
const extResult = await schema.knex("pg_extension").where("extname", "btree_gist").select("oid").first();
|
|
3188
|
-
const btreeGistInstalled = !!extResult;
|
|
3189
|
-
if (!btreeGistInstalled) {
|
|
3190
|
-
up.unshift(() => {
|
|
3191
|
-
this.writer.writeLine(`await knex.raw('CREATE EXTENSION IF NOT EXISTS btree_gist');`);
|
|
3192
|
-
this.writer.blankLine();
|
|
3193
|
-
});
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
3130
|
this.createEnums(
|
|
3197
3131
|
this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
|
|
3198
3132
|
up,
|
|
@@ -3271,22 +3205,10 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3271
3205
|
if (entry.kind === "check") {
|
|
3272
3206
|
validateCheckConstraint(model, entry);
|
|
3273
3207
|
const table = model.name;
|
|
3274
|
-
const constraintName = this.
|
|
3275
|
-
|
|
3276
|
-
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
3277
|
-
});
|
|
3278
|
-
} else if (entry.kind === "exclude") {
|
|
3279
|
-
validateExcludeConstraint(model, entry);
|
|
3280
|
-
const table = model.name;
|
|
3281
|
-
const constraintName = this.getConstraintName(model, entry, i);
|
|
3208
|
+
const constraintName = this.getCheckConstraintName(model, entry, i);
|
|
3209
|
+
const expression = entry.expression;
|
|
3282
3210
|
up.push(() => {
|
|
3283
|
-
this.
|
|
3284
|
-
});
|
|
3285
|
-
} else if (entry.kind === "constraint_trigger") {
|
|
3286
|
-
const table = model.name;
|
|
3287
|
-
const constraintName = this.getConstraintName(model, entry, i);
|
|
3288
|
-
up.push(() => {
|
|
3289
|
-
this.addConstraintTrigger(table, constraintName, entry);
|
|
3211
|
+
this.addCheckConstraint(table, constraintName, expression);
|
|
3290
3212
|
});
|
|
3291
3213
|
}
|
|
3292
3214
|
}
|
|
@@ -3326,81 +3248,36 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3326
3248
|
);
|
|
3327
3249
|
this.updateFields(model, existingFields, up, down);
|
|
3328
3250
|
if (model.constraints?.length) {
|
|
3329
|
-
const existingCheckMap = this.existingCheckConstraints[model.name];
|
|
3330
|
-
const existingExcludeMap = this.existingExcludeConstraints[model.name];
|
|
3331
|
-
const existingTriggerMap = this.existingConstraintTriggers[model.name];
|
|
3332
3251
|
for (let i = 0; i < model.constraints.length; i++) {
|
|
3333
3252
|
const entry = model.constraints[i];
|
|
3253
|
+
if (entry.kind !== "check") {
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
validateCheckConstraint(model, entry);
|
|
3334
3257
|
const table = model.name;
|
|
3335
|
-
const constraintName = this.
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
validateExcludeConstraint(model, entry);
|
|
3359
|
-
const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
|
|
3360
|
-
const existingDef = existingExcludeMap?.get(constraintName);
|
|
3361
|
-
if (existingDef === void 0) {
|
|
3362
|
-
up.push(() => {
|
|
3363
|
-
this.addExcludeConstraint(table, constraintName, entry);
|
|
3364
|
-
});
|
|
3365
|
-
down.push(() => {
|
|
3366
|
-
this.dropExcludeConstraint(table, constraintName);
|
|
3367
|
-
});
|
|
3368
|
-
} else if (existingDef !== newDef) {
|
|
3369
|
-
up.push(() => {
|
|
3370
|
-
this.dropExcludeConstraint(table, constraintName);
|
|
3371
|
-
this.addExcludeConstraint(table, constraintName, entry);
|
|
3372
|
-
});
|
|
3373
|
-
down.push(() => {
|
|
3374
|
-
this.dropExcludeConstraint(table, constraintName);
|
|
3375
|
-
const escaped = this.escapeExpressionForRaw(existingDef);
|
|
3376
|
-
this.writer.writeLine(
|
|
3377
|
-
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
|
|
3378
|
-
);
|
|
3379
|
-
this.writer.blankLine();
|
|
3380
|
-
});
|
|
3381
|
-
}
|
|
3382
|
-
} else if (entry.kind === "constraint_trigger") {
|
|
3383
|
-
const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
|
|
3384
|
-
const existingDef = existingTriggerMap?.get(constraintName);
|
|
3385
|
-
if (existingDef === void 0) {
|
|
3386
|
-
up.push(() => {
|
|
3387
|
-
this.addConstraintTrigger(table, constraintName, entry);
|
|
3388
|
-
});
|
|
3389
|
-
down.push(() => {
|
|
3390
|
-
this.dropConstraintTrigger(table, constraintName);
|
|
3391
|
-
});
|
|
3392
|
-
} else if (existingDef !== newDef) {
|
|
3393
|
-
up.push(() => {
|
|
3394
|
-
this.dropConstraintTrigger(table, constraintName);
|
|
3395
|
-
this.addConstraintTrigger(table, constraintName, entry);
|
|
3396
|
-
});
|
|
3397
|
-
down.push(() => {
|
|
3398
|
-
this.dropConstraintTrigger(table, constraintName);
|
|
3399
|
-
const escaped = existingDef.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
3400
|
-
this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
|
|
3401
|
-
this.writer.blankLine();
|
|
3402
|
-
});
|
|
3403
|
-
}
|
|
3258
|
+
const constraintName = this.getCheckConstraintName(model, entry, i);
|
|
3259
|
+
const existingConstraint = this.findExistingConstraint(table, entry, constraintName);
|
|
3260
|
+
if (!existingConstraint) {
|
|
3261
|
+
up.push(() => {
|
|
3262
|
+
this.addCheckConstraint(table, constraintName, entry.expression);
|
|
3263
|
+
});
|
|
3264
|
+
down.push(() => {
|
|
3265
|
+
this.dropCheckConstraint(table, constraintName);
|
|
3266
|
+
});
|
|
3267
|
+
} else if (!await this.equalExpressions(
|
|
3268
|
+
table,
|
|
3269
|
+
existingConstraint.constraintName,
|
|
3270
|
+
existingConstraint.expression,
|
|
3271
|
+
entry.expression
|
|
3272
|
+
)) {
|
|
3273
|
+
up.push(() => {
|
|
3274
|
+
this.dropCheckConstraint(table, existingConstraint.constraintName);
|
|
3275
|
+
this.addCheckConstraint(table, constraintName, entry.expression);
|
|
3276
|
+
});
|
|
3277
|
+
down.push(() => {
|
|
3278
|
+
this.dropCheckConstraint(table, constraintName);
|
|
3279
|
+
this.addCheckConstraint(table, existingConstraint.constraintName, existingConstraint.expression);
|
|
3280
|
+
});
|
|
3404
3281
|
}
|
|
3405
3282
|
}
|
|
3406
3283
|
}
|
|
@@ -3775,90 +3652,148 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3775
3652
|
renameColumn(from, to) {
|
|
3776
3653
|
this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
|
|
3777
3654
|
}
|
|
3778
|
-
|
|
3655
|
+
getCheckConstraintName(model, entry, index) {
|
|
3779
3656
|
return `${model.name}_${entry.name}_${entry.kind}_${index}`;
|
|
3780
3657
|
}
|
|
3781
|
-
|
|
3782
|
-
"
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
"in",
|
|
3786
|
-
"is",
|
|
3787
|
-
"null",
|
|
3788
|
-
"true",
|
|
3789
|
-
"false",
|
|
3790
|
-
"between",
|
|
3791
|
-
"like",
|
|
3792
|
-
"exists",
|
|
3793
|
-
"all",
|
|
3794
|
-
"any",
|
|
3795
|
-
"asc",
|
|
3796
|
-
"desc",
|
|
3797
|
-
"with",
|
|
3798
|
-
"using",
|
|
3799
|
-
"as",
|
|
3800
|
-
"on",
|
|
3801
|
-
"infinity",
|
|
3802
|
-
"extract",
|
|
3803
|
-
"current_date",
|
|
3804
|
-
"current_timestamp"
|
|
3805
|
-
]);
|
|
3806
|
-
normalizeSqlIdentifiers(s) {
|
|
3807
|
-
const literals = [];
|
|
3808
|
-
let result = s.replace(/'([^']|'')*'/g, (lit) => {
|
|
3809
|
-
literals.push(lit);
|
|
3810
|
-
return `\0L${literals.length - 1}\0`;
|
|
3811
|
-
});
|
|
3812
|
-
result = result.replace(/"([^"]*)"/g, (_, ident) => `"${ident.toLowerCase()}"`);
|
|
3813
|
-
result = result.replace(
|
|
3814
|
-
/\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g,
|
|
3815
|
-
(match) => _MigrationGenerator.SQL_KEYWORDS.has(match.toLowerCase()) ? match : `"${match.toLowerCase()}"`
|
|
3816
|
-
);
|
|
3817
|
-
for (let i = 0; i < literals.length; i++) {
|
|
3818
|
-
result = result.replace(new RegExp(`\0L${i}\0`, "g"), literals[i]);
|
|
3658
|
+
normalizeCheckExpression(expr) {
|
|
3659
|
+
let normalized = expr.replace(/\s+/g, " ").trim();
|
|
3660
|
+
while (this.isWrappedByOuterParentheses(normalized)) {
|
|
3661
|
+
normalized = normalized.slice(1, -1).trim();
|
|
3819
3662
|
}
|
|
3820
|
-
return
|
|
3663
|
+
return normalized;
|
|
3821
3664
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3665
|
+
isWrappedByOuterParentheses(expr) {
|
|
3666
|
+
if (!expr.startsWith("(") || !expr.endsWith(")")) {
|
|
3667
|
+
return false;
|
|
3668
|
+
}
|
|
3669
|
+
let depth = 0;
|
|
3670
|
+
let inSingleQuote = false;
|
|
3671
|
+
for (let i = 0; i < expr.length; i++) {
|
|
3672
|
+
const char = expr[i];
|
|
3673
|
+
const next = expr[i + 1];
|
|
3674
|
+
if (char === "'") {
|
|
3675
|
+
if (inSingleQuote && next === "'") {
|
|
3676
|
+
i++;
|
|
3677
|
+
continue;
|
|
3832
3678
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3679
|
+
inSingleQuote = !inSingleQuote;
|
|
3680
|
+
continue;
|
|
3681
|
+
}
|
|
3682
|
+
if (inSingleQuote) {
|
|
3683
|
+
continue;
|
|
3684
|
+
}
|
|
3685
|
+
if (char === "(") {
|
|
3686
|
+
depth++;
|
|
3687
|
+
} else if (char === ")") {
|
|
3688
|
+
depth--;
|
|
3689
|
+
if (depth === 0 && i !== expr.length - 1) {
|
|
3690
|
+
return false;
|
|
3691
|
+
}
|
|
3692
|
+
if (depth < 0) {
|
|
3693
|
+
return false;
|
|
3836
3694
|
}
|
|
3837
3695
|
}
|
|
3838
|
-
|
|
3839
|
-
|
|
3696
|
+
}
|
|
3697
|
+
return depth === 0;
|
|
3698
|
+
}
|
|
3699
|
+
findExistingConstraint(table, entry, preferredConstraintName) {
|
|
3700
|
+
const existingMap = this.existingCheckConstraints[table];
|
|
3701
|
+
if (!existingMap) {
|
|
3702
|
+
return null;
|
|
3703
|
+
}
|
|
3704
|
+
const preferredExpression = existingMap.get(preferredConstraintName);
|
|
3705
|
+
if (preferredExpression !== void 0) {
|
|
3706
|
+
return {
|
|
3707
|
+
constraintName: preferredConstraintName,
|
|
3708
|
+
expression: preferredExpression
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
const normalizedNewExpression = this.normalizeCheckExpression(entry.expression);
|
|
3712
|
+
const constraintPrefix = `${table}_${entry.name}_${entry.kind}_`;
|
|
3713
|
+
for (const [constraintName, expression] of existingMap.entries()) {
|
|
3714
|
+
if (!constraintName.startsWith(constraintPrefix)) {
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3717
|
+
if (this.normalizeCheckExpression(expression) !== normalizedNewExpression) {
|
|
3718
|
+
continue;
|
|
3840
3719
|
}
|
|
3841
|
-
|
|
3720
|
+
return { constraintName, expression };
|
|
3842
3721
|
}
|
|
3843
|
-
|
|
3844
|
-
return this.normalizeSqlIdentifiers(s);
|
|
3722
|
+
return null;
|
|
3845
3723
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3724
|
+
async equalExpressions(table, constraintName, existingExpression, newExpression) {
|
|
3725
|
+
try {
|
|
3726
|
+
const [canonicalExisting, canonicalNew] = await Promise.all([
|
|
3727
|
+
this.canonicalizeCheckExpressionWithPostgres(table, existingExpression),
|
|
3728
|
+
this.canonicalizeCheckExpressionWithPostgres(table, newExpression)
|
|
3729
|
+
]);
|
|
3730
|
+
return canonicalExisting === canonicalNew;
|
|
3731
|
+
} catch (error) {
|
|
3732
|
+
console.warn(
|
|
3733
|
+
`Failed to canonicalize check constraint "${constraintName}" on table "${table}". Treating it as changed.`,
|
|
3734
|
+
error
|
|
3735
|
+
);
|
|
3736
|
+
return false;
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
async canonicalizeCheckExpressionWithPostgres(table, expression) {
|
|
3740
|
+
const sourceTableIdentifier = table.split(".").map((part) => this.quoteIdentifier(part)).join(".");
|
|
3741
|
+
const uniqueSuffix = `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
3742
|
+
const tableSlug = table.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
3743
|
+
const tempTableName = `gqm_tmp_check_${tableSlug}_${uniqueSuffix}`;
|
|
3744
|
+
const tempTableIdentifier = this.quoteIdentifier(tempTableName);
|
|
3745
|
+
const constraintName = `gqm_tmp_check_${uniqueSuffix}`;
|
|
3746
|
+
const constraintIdentifier = this.quoteIdentifier(constraintName);
|
|
3747
|
+
const trx = await this.knex.transaction();
|
|
3748
|
+
try {
|
|
3749
|
+
await trx.raw(`CREATE TEMP TABLE ${tempTableIdentifier} (LIKE ${sourceTableIdentifier}) ON COMMIT DROP`);
|
|
3750
|
+
await trx.raw(`ALTER TABLE ${tempTableIdentifier} ADD CONSTRAINT ${constraintIdentifier} CHECK (${expression})`);
|
|
3751
|
+
const result = await trx.raw(
|
|
3752
|
+
`SELECT pg_get_constraintdef(c.oid, true) AS constraint_definition
|
|
3753
|
+
FROM pg_constraint c
|
|
3754
|
+
JOIN pg_class t
|
|
3755
|
+
ON t.oid = c.conrelid
|
|
3756
|
+
WHERE t.relname = ?
|
|
3757
|
+
AND c.conname = ?
|
|
3758
|
+
ORDER BY c.oid DESC
|
|
3759
|
+
LIMIT 1`,
|
|
3760
|
+
[tempTableName, constraintName]
|
|
3761
|
+
);
|
|
3762
|
+
const rows = "rows" in result && Array.isArray(result.rows) ? result.rows : [];
|
|
3763
|
+
const definition = rows[0]?.constraint_definition;
|
|
3764
|
+
if (!definition) {
|
|
3765
|
+
throw new Error(`Could not read canonical check definition for expression: ${expression}`);
|
|
3766
|
+
}
|
|
3767
|
+
return this.normalizeCheckExpression(this.extractCheckExpressionFromDefinition(definition));
|
|
3768
|
+
} finally {
|
|
3769
|
+
try {
|
|
3770
|
+
await trx.rollback();
|
|
3771
|
+
} catch {
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
extractCheckExpressionFromDefinition(definition) {
|
|
3776
|
+
const trimmed = definition.trim();
|
|
3777
|
+
const match = trimmed.match(/^CHECK\s*\(([\s\S]*)\)$/i);
|
|
3778
|
+
if (!match) {
|
|
3779
|
+
return trimmed;
|
|
3780
|
+
}
|
|
3781
|
+
return match[1];
|
|
3782
|
+
}
|
|
3783
|
+
quoteIdentifier(identifier) {
|
|
3784
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
3849
3785
|
}
|
|
3850
|
-
|
|
3851
|
-
return
|
|
3786
|
+
quoteQualifiedIdentifier(identifier) {
|
|
3787
|
+
return identifier.split(".").map((part) => this.quoteIdentifier(part)).join(".");
|
|
3852
3788
|
}
|
|
3853
3789
|
/** Escape expression for embedding inside a template literal in generated code */
|
|
3854
3790
|
escapeExpressionForRaw(expr) {
|
|
3855
3791
|
return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
3856
3792
|
}
|
|
3857
|
-
addCheckConstraint(table, constraintName, expression
|
|
3793
|
+
addCheckConstraint(table, constraintName, expression) {
|
|
3858
3794
|
const escaped = this.escapeExpressionForRaw(expression);
|
|
3859
|
-
const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
|
|
3860
3795
|
this.writer.writeLine(
|
|
3861
|
-
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})
|
|
3796
|
+
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
|
|
3862
3797
|
);
|
|
3863
3798
|
this.writer.blankLine();
|
|
3864
3799
|
}
|
|
@@ -3866,43 +3801,6 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3866
3801
|
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3867
3802
|
this.writer.blankLine();
|
|
3868
3803
|
}
|
|
3869
|
-
buildExcludeDef(entry) {
|
|
3870
|
-
const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
|
|
3871
|
-
const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
|
|
3872
|
-
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
3873
|
-
return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
|
|
3874
|
-
}
|
|
3875
|
-
addExcludeConstraint(table, constraintName, entry) {
|
|
3876
|
-
const def = this.buildExcludeDef(entry);
|
|
3877
|
-
const escaped = this.escapeExpressionForRaw(def);
|
|
3878
|
-
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
|
|
3879
|
-
this.writer.blankLine();
|
|
3880
|
-
}
|
|
3881
|
-
dropExcludeConstraint(table, constraintName) {
|
|
3882
|
-
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3883
|
-
this.writer.blankLine();
|
|
3884
|
-
}
|
|
3885
|
-
buildConstraintTriggerDef(table, constraintName, entry) {
|
|
3886
|
-
const eventsStr = entry.events.join(" OR ");
|
|
3887
|
-
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
3888
|
-
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
3889
|
-
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
3890
|
-
return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
|
|
3891
|
-
}
|
|
3892
|
-
addConstraintTrigger(table, constraintName, entry) {
|
|
3893
|
-
const eventsStr = entry.events.join(" OR ");
|
|
3894
|
-
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
3895
|
-
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
3896
|
-
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
3897
|
-
this.writer.writeLine(
|
|
3898
|
-
`await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
|
|
3899
|
-
);
|
|
3900
|
-
this.writer.blankLine();
|
|
3901
|
-
}
|
|
3902
|
-
dropConstraintTrigger(table, constraintName) {
|
|
3903
|
-
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3904
|
-
this.writer.blankLine();
|
|
3905
|
-
}
|
|
3906
3804
|
value(value2) {
|
|
3907
3805
|
if (typeof value2 === "string") {
|
|
3908
3806
|
return `'${value2}'`;
|
|
@@ -4929,6 +4827,5 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
|
|
|
4929
4827
|
updateEntity,
|
|
4930
4828
|
updateFunctions,
|
|
4931
4829
|
validateCheckConstraint,
|
|
4932
|
-
validateExcludeConstraint,
|
|
4933
4830
|
value
|
|
4934
4831
|
});
|
|
@@ -6,7 +6,6 @@ export const generateFunctionsFromDatabase = async (knex) => {
|
|
|
6
6
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
7
7
|
WHERE n.nspname = 'public'
|
|
8
8
|
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
9
|
-
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
10
9
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
11
10
|
`);
|
|
12
11
|
const aggregateFunctions = await knex.raw(`
|
|
@@ -21,7 +20,6 @@ export const generateFunctionsFromDatabase = async (knex) => {
|
|
|
21
20
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
22
21
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
23
22
|
WHERE n.nspname = 'public'
|
|
24
|
-
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
25
23
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
26
24
|
`);
|
|
27
25
|
const functions = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-functions.js","sourceRoot":"","sources":["../../../src/migrations/generate-functions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,EAAE,IAAU,EAAmB,EAAE;IACjF,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC
|
|
1
|
+
{"version":3,"file":"generate-functions.js","sourceRoot":"","sources":["../../../src/migrations/generate-functions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,EAAE,IAAU,EAAmB,EAAE;IACjF,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;GAQvC,CAAC,CAAC;IAEH,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;GAazC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,GAAG,IAAI,gBAAgB,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QAC9C,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,kBAAkB,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;QAEvC,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,SAAS;QACX,CAAC;QAED,IAAI,YAAY,GAAG,oBAAoB,IAAI,IAAI,YAAY,OAAO,CAAC;QACnE,YAAY,IAAI,aAAa,SAAS,KAAK,CAAC;QAC5C,YAAY,IAAI,aAAa,SAAS,EAAE,CAAC;QAEzC,IAAI,SAAS,EAAE,CAAC;YACd,YAAY,IAAI,oBAAoB,SAAS,EAAE,CAAC;QAClD,CAAC;QAED,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC9C,MAAM,UAAU,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAClF,YAAY,IAAI,mBAAmB,UAAU,EAAE,CAAC;QAClD,CAAC;QAED,YAAY,IAAI,MAAM,CAAC;QAEvB,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,0CAA0C,CAAC;IACpD,CAAC;IAED,MAAM,oBAAoB,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAE9F,OAAO,yCAAyC,oBAAoB,SAAS,CAAC;AAChF,CAAC,CAAC"}
|
|
@@ -9,10 +9,6 @@ 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;
|
|
16
12
|
private uuidUsed?;
|
|
17
13
|
private nowUsed?;
|
|
18
14
|
needsMigration: boolean;
|
|
@@ -34,22 +30,19 @@ export declare class MigrationGenerator {
|
|
|
34
30
|
private dropTable;
|
|
35
31
|
private renameTable;
|
|
36
32
|
private renameColumn;
|
|
37
|
-
private
|
|
38
|
-
private static readonly SQL_KEYWORDS;
|
|
39
|
-
private normalizeSqlIdentifiers;
|
|
33
|
+
private getCheckConstraintName;
|
|
40
34
|
private normalizeCheckExpression;
|
|
41
|
-
private
|
|
42
|
-
private
|
|
35
|
+
private isWrappedByOuterParentheses;
|
|
36
|
+
private findExistingConstraint;
|
|
37
|
+
private equalExpressions;
|
|
38
|
+
private canonicalizeCheckExpressionWithPostgres;
|
|
39
|
+
private extractCheckExpressionFromDefinition;
|
|
40
|
+
private quoteIdentifier;
|
|
41
|
+
private quoteQualifiedIdentifier;
|
|
43
42
|
/** Escape expression for embedding inside a template literal in generated code */
|
|
44
43
|
private escapeExpressionForRaw;
|
|
45
44
|
private addCheckConstraint;
|
|
46
45
|
private dropCheckConstraint;
|
|
47
|
-
private buildExcludeDef;
|
|
48
|
-
private addExcludeConstraint;
|
|
49
|
-
private dropExcludeConstraint;
|
|
50
|
-
private buildConstraintTriggerDef;
|
|
51
|
-
private addConstraintTrigger;
|
|
52
|
-
private dropConstraintTrigger;
|
|
53
46
|
private value;
|
|
54
47
|
private columnRaw;
|
|
55
48
|
private column;
|