@smartive/graphql-magic 23.7.0-next.5 → 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 +152 -291
- package/dist/cjs/index.cjs +153 -294
- 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 -18
- package/dist/esm/migrations/generate.js +150 -286
- 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 +186 -351
- 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 -> { normalized, raw } */
|
|
3122
|
-
existingExcludeConstraints = {};
|
|
3123
|
-
/** table name -> constraint name -> { normalized, raw } */
|
|
3124
|
-
existingConstraintTriggers = {};
|
|
3125
3101
|
uuidUsed;
|
|
3126
3102
|
nowUsed;
|
|
3127
3103
|
needsMigration = false;
|
|
@@ -3149,56 +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, {
|
|
3165
|
-
normalized: this.normalizeExcludeDef(row.constraint_def),
|
|
3166
|
-
raw: row.constraint_def
|
|
3167
|
-
});
|
|
3168
|
-
}
|
|
3169
|
-
const triggerResult = await schema.knex.raw(
|
|
3170
|
-
`SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_triggerdef(t.oid) as trigger_def
|
|
3171
|
-
FROM pg_constraint c
|
|
3172
|
-
JOIN pg_trigger t ON t.tgconstraint = c.oid
|
|
3173
|
-
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
3174
|
-
WHERE n.nspname = 'public' AND c.contype = 't'`
|
|
3175
|
-
);
|
|
3176
|
-
const triggerRows = "rows" in triggerResult && Array.isArray(triggerResult.rows) ? triggerResult.rows : [];
|
|
3177
|
-
for (const row of triggerRows) {
|
|
3178
|
-
const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
|
|
3179
|
-
if (!this.existingConstraintTriggers[tableName]) {
|
|
3180
|
-
this.existingConstraintTriggers[tableName] = /* @__PURE__ */ new Map();
|
|
3181
|
-
}
|
|
3182
|
-
this.existingConstraintTriggers[tableName].set(row.constraint_name, {
|
|
3183
|
-
normalized: this.normalizeTriggerDef(row.trigger_def),
|
|
3184
|
-
raw: row.trigger_def
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
3128
|
const up = [];
|
|
3188
3129
|
const down = [];
|
|
3189
|
-
const wantsBtreeGist = models.entities.some(
|
|
3190
|
-
(model) => model.constraints?.some((c) => c.kind === "exclude" && c.elements.some((el) => "column" in el && el.operator === "="))
|
|
3191
|
-
);
|
|
3192
|
-
if (wantsBtreeGist) {
|
|
3193
|
-
const extResult = await schema.knex("pg_extension").where("extname", "btree_gist").select("oid").first();
|
|
3194
|
-
const btreeGistInstalled = !!extResult;
|
|
3195
|
-
if (!btreeGistInstalled) {
|
|
3196
|
-
up.unshift(() => {
|
|
3197
|
-
this.writer.writeLine(`await knex.raw('CREATE EXTENSION IF NOT EXISTS btree_gist');`);
|
|
3198
|
-
this.writer.blankLine();
|
|
3199
|
-
});
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
3130
|
this.createEnums(
|
|
3203
3131
|
this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
|
|
3204
3132
|
up,
|
|
@@ -3277,22 +3205,10 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3277
3205
|
if (entry.kind === "check") {
|
|
3278
3206
|
validateCheckConstraint(model, entry);
|
|
3279
3207
|
const table = model.name;
|
|
3280
|
-
const constraintName = this.
|
|
3208
|
+
const constraintName = this.getCheckConstraintName(model, entry, i);
|
|
3209
|
+
const expression = entry.expression;
|
|
3281
3210
|
up.push(() => {
|
|
3282
|
-
this.addCheckConstraint(table, constraintName,
|
|
3283
|
-
});
|
|
3284
|
-
} else if (entry.kind === "exclude") {
|
|
3285
|
-
validateExcludeConstraint(model, entry);
|
|
3286
|
-
const table = model.name;
|
|
3287
|
-
const constraintName = this.getConstraintName(model, entry, i);
|
|
3288
|
-
up.push(() => {
|
|
3289
|
-
this.addExcludeConstraint(table, constraintName, entry);
|
|
3290
|
-
});
|
|
3291
|
-
} else if (entry.kind === "constraint_trigger") {
|
|
3292
|
-
const table = model.name;
|
|
3293
|
-
const constraintName = this.getConstraintName(model, entry, i);
|
|
3294
|
-
up.push(() => {
|
|
3295
|
-
this.addConstraintTrigger(table, constraintName, entry);
|
|
3211
|
+
this.addCheckConstraint(table, constraintName, expression);
|
|
3296
3212
|
});
|
|
3297
3213
|
}
|
|
3298
3214
|
}
|
|
@@ -3332,81 +3248,36 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3332
3248
|
);
|
|
3333
3249
|
this.updateFields(model, existingFields, up, down);
|
|
3334
3250
|
if (model.constraints?.length) {
|
|
3335
|
-
const existingCheckMap = this.existingCheckConstraints[model.name];
|
|
3336
|
-
const existingExcludeMap = this.existingExcludeConstraints[model.name];
|
|
3337
|
-
const existingTriggerMap = this.existingConstraintTriggers[model.name];
|
|
3338
3251
|
for (let i = 0; i < model.constraints.length; i++) {
|
|
3339
3252
|
const entry = model.constraints[i];
|
|
3253
|
+
if (entry.kind !== "check") {
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
validateCheckConstraint(model, entry);
|
|
3340
3257
|
const table = model.name;
|
|
3341
|
-
const constraintName = this.
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
validateExcludeConstraint(model, entry);
|
|
3365
|
-
const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
|
|
3366
|
-
const existing = existingExcludeMap?.get(constraintName);
|
|
3367
|
-
if (existing === void 0) {
|
|
3368
|
-
up.push(() => {
|
|
3369
|
-
this.addExcludeConstraint(table, constraintName, entry);
|
|
3370
|
-
});
|
|
3371
|
-
down.push(() => {
|
|
3372
|
-
this.dropExcludeConstraint(table, constraintName);
|
|
3373
|
-
});
|
|
3374
|
-
} else if (existing.normalized !== newDef) {
|
|
3375
|
-
up.push(() => {
|
|
3376
|
-
this.dropExcludeConstraint(table, constraintName);
|
|
3377
|
-
this.addExcludeConstraint(table, constraintName, entry);
|
|
3378
|
-
});
|
|
3379
|
-
down.push(() => {
|
|
3380
|
-
this.dropExcludeConstraint(table, constraintName);
|
|
3381
|
-
const escaped = this.escapeExpressionForRaw(existing.raw);
|
|
3382
|
-
this.writer.writeLine(
|
|
3383
|
-
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
|
|
3384
|
-
);
|
|
3385
|
-
this.writer.blankLine();
|
|
3386
|
-
});
|
|
3387
|
-
}
|
|
3388
|
-
} else if (entry.kind === "constraint_trigger") {
|
|
3389
|
-
const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
|
|
3390
|
-
const existing = existingTriggerMap?.get(constraintName);
|
|
3391
|
-
if (existing === void 0) {
|
|
3392
|
-
up.push(() => {
|
|
3393
|
-
this.addConstraintTrigger(table, constraintName, entry);
|
|
3394
|
-
});
|
|
3395
|
-
down.push(() => {
|
|
3396
|
-
this.dropConstraintTrigger(table, constraintName);
|
|
3397
|
-
});
|
|
3398
|
-
} else if (existing.normalized !== newDef) {
|
|
3399
|
-
up.push(() => {
|
|
3400
|
-
this.dropConstraintTrigger(table, constraintName);
|
|
3401
|
-
this.addConstraintTrigger(table, constraintName, entry);
|
|
3402
|
-
});
|
|
3403
|
-
down.push(() => {
|
|
3404
|
-
this.dropConstraintTrigger(table, constraintName);
|
|
3405
|
-
const escaped = existing.raw.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
3406
|
-
this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
|
|
3407
|
-
this.writer.blankLine();
|
|
3408
|
-
});
|
|
3409
|
-
}
|
|
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
|
+
});
|
|
3410
3281
|
}
|
|
3411
3282
|
}
|
|
3412
3283
|
}
|
|
@@ -3781,122 +3652,148 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3781
3652
|
renameColumn(from, to) {
|
|
3782
3653
|
this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
|
|
3783
3654
|
}
|
|
3784
|
-
|
|
3655
|
+
getCheckConstraintName(model, entry, index) {
|
|
3785
3656
|
return `${model.name}_${entry.name}_${entry.kind}_${index}`;
|
|
3786
3657
|
}
|
|
3787
|
-
|
|
3788
|
-
"
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
"in",
|
|
3792
|
-
"is",
|
|
3793
|
-
"null",
|
|
3794
|
-
"true",
|
|
3795
|
-
"false",
|
|
3796
|
-
"between",
|
|
3797
|
-
"like",
|
|
3798
|
-
"exists",
|
|
3799
|
-
"all",
|
|
3800
|
-
"any",
|
|
3801
|
-
"asc",
|
|
3802
|
-
"desc",
|
|
3803
|
-
"with",
|
|
3804
|
-
"using",
|
|
3805
|
-
"as",
|
|
3806
|
-
"on",
|
|
3807
|
-
"infinity",
|
|
3808
|
-
"extract",
|
|
3809
|
-
"current_date",
|
|
3810
|
-
"current_timestamp"
|
|
3811
|
-
]);
|
|
3812
|
-
static LITERAL_PLACEHOLDER = "\uE000";
|
|
3813
|
-
normalizeSqlIdentifiers(s) {
|
|
3814
|
-
const literals = [];
|
|
3815
|
-
let result = s.replace(/'([^']|'')*'/g, (lit) => {
|
|
3816
|
-
literals.push(lit);
|
|
3817
|
-
return `${_MigrationGenerator.LITERAL_PLACEHOLDER}${literals.length - 1}${_MigrationGenerator.LITERAL_PLACEHOLDER}`;
|
|
3818
|
-
});
|
|
3819
|
-
result = result.replace(/"([^"]*)"/g, (_, ident) => `"${ident.toLowerCase()}"`);
|
|
3820
|
-
result = result.replace(
|
|
3821
|
-
/\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g,
|
|
3822
|
-
(match) => _MigrationGenerator.SQL_KEYWORDS.has(match.toLowerCase()) ? match : `"${match.toLowerCase()}"`
|
|
3823
|
-
);
|
|
3824
|
-
for (let i = 0; i < literals.length; i++) {
|
|
3825
|
-
result = result.replace(
|
|
3826
|
-
new RegExp(`${_MigrationGenerator.LITERAL_PLACEHOLDER}${i}${_MigrationGenerator.LITERAL_PLACEHOLDER}`, "g"),
|
|
3827
|
-
literals[i]
|
|
3828
|
-
);
|
|
3658
|
+
normalizeCheckExpression(expr) {
|
|
3659
|
+
let normalized = expr.replace(/\s+/g, " ").trim();
|
|
3660
|
+
while (this.isWrappedByOuterParentheses(normalized)) {
|
|
3661
|
+
normalized = normalized.slice(1, -1).trim();
|
|
3829
3662
|
}
|
|
3830
|
-
return
|
|
3831
|
-
}
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3663
|
+
return normalized;
|
|
3664
|
+
}
|
|
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;
|
|
3841
3678
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
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;
|
|
3845
3694
|
}
|
|
3846
3695
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
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;
|
|
3849
3716
|
}
|
|
3850
|
-
|
|
3717
|
+
if (this.normalizeCheckExpression(expression) !== normalizedNewExpression) {
|
|
3718
|
+
continue;
|
|
3719
|
+
}
|
|
3720
|
+
return { constraintName, expression };
|
|
3851
3721
|
}
|
|
3852
|
-
return
|
|
3722
|
+
return null;
|
|
3853
3723
|
}
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
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}`);
|
|
3864
3766
|
}
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3767
|
+
return this.normalizeCheckExpression(this.extractCheckExpressionFromDefinition(definition));
|
|
3768
|
+
} finally {
|
|
3769
|
+
try {
|
|
3770
|
+
await trx.rollback();
|
|
3771
|
+
} catch {
|
|
3869
3772
|
}
|
|
3870
3773
|
}
|
|
3871
|
-
parts.push(s.slice(start).trim());
|
|
3872
|
-
return parts.filter(Boolean);
|
|
3873
3774
|
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
const
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
return this.
|
|
3887
|
-
}
|
|
3888
|
-
normalizeTriggerDef(def) {
|
|
3889
|
-
return def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\bON\s+[a-zA-Z_][a-zA-Z0-9_]*\./gi, "ON ").trim();
|
|
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, '""')}"`;
|
|
3785
|
+
}
|
|
3786
|
+
quoteQualifiedIdentifier(identifier) {
|
|
3787
|
+
return identifier.split(".").map((part) => this.quoteIdentifier(part)).join(".");
|
|
3890
3788
|
}
|
|
3891
3789
|
/** Escape expression for embedding inside a template literal in generated code */
|
|
3892
3790
|
escapeExpressionForRaw(expr) {
|
|
3893
3791
|
return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
3894
3792
|
}
|
|
3895
|
-
addCheckConstraint(table, constraintName, expression
|
|
3793
|
+
addCheckConstraint(table, constraintName, expression) {
|
|
3896
3794
|
const escaped = this.escapeExpressionForRaw(expression);
|
|
3897
|
-
const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
|
|
3898
3795
|
this.writer.writeLine(
|
|
3899
|
-
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})
|
|
3796
|
+
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
|
|
3900
3797
|
);
|
|
3901
3798
|
this.writer.blankLine();
|
|
3902
3799
|
}
|
|
@@ -3904,43 +3801,6 @@ var MigrationGenerator = class _MigrationGenerator {
|
|
|
3904
3801
|
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3905
3802
|
this.writer.blankLine();
|
|
3906
3803
|
}
|
|
3907
|
-
buildExcludeDef(entry) {
|
|
3908
|
-
const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
|
|
3909
|
-
const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
|
|
3910
|
-
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
3911
|
-
return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
|
|
3912
|
-
}
|
|
3913
|
-
addExcludeConstraint(table, constraintName, entry) {
|
|
3914
|
-
const def = this.buildExcludeDef(entry);
|
|
3915
|
-
const escaped = this.escapeExpressionForRaw(def);
|
|
3916
|
-
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
|
|
3917
|
-
this.writer.blankLine();
|
|
3918
|
-
}
|
|
3919
|
-
dropExcludeConstraint(table, constraintName) {
|
|
3920
|
-
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3921
|
-
this.writer.blankLine();
|
|
3922
|
-
}
|
|
3923
|
-
buildConstraintTriggerDef(table, constraintName, entry) {
|
|
3924
|
-
const eventsStr = entry.events.join(" OR ");
|
|
3925
|
-
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
3926
|
-
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
3927
|
-
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
3928
|
-
return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
|
|
3929
|
-
}
|
|
3930
|
-
addConstraintTrigger(table, constraintName, entry) {
|
|
3931
|
-
const eventsStr = entry.events.join(" OR ");
|
|
3932
|
-
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
3933
|
-
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
3934
|
-
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
3935
|
-
this.writer.writeLine(
|
|
3936
|
-
`await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
|
|
3937
|
-
);
|
|
3938
|
-
this.writer.blankLine();
|
|
3939
|
-
}
|
|
3940
|
-
dropConstraintTrigger(table, constraintName) {
|
|
3941
|
-
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3942
|
-
this.writer.blankLine();
|
|
3943
|
-
}
|
|
3944
3804
|
value(value2) {
|
|
3945
3805
|
if (typeof value2 === "string") {
|
|
3946
3806
|
return `'${value2}'`;
|
|
@@ -4967,6 +4827,5 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
|
|
|
4967
4827
|
updateEntity,
|
|
4968
4828
|
updateFunctions,
|
|
4969
4829
|
validateCheckConstraint,
|
|
4970
|
-
validateExcludeConstraint,
|
|
4971
4830
|
value
|
|
4972
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 -> { normalized, raw } */
|
|
13
|
-
private existingExcludeConstraints;
|
|
14
|
-
/** table name -> constraint name -> { normalized, raw } */
|
|
15
|
-
private existingConstraintTriggers;
|
|
16
12
|
private uuidUsed?;
|
|
17
13
|
private nowUsed?;
|
|
18
14
|
needsMigration: boolean;
|
|
@@ -34,25 +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 static readonly LITERAL_PLACEHOLDER;
|
|
40
|
-
private normalizeSqlIdentifiers;
|
|
41
|
-
private stripOuterParens;
|
|
42
|
-
private splitAtTopLevel;
|
|
33
|
+
private getCheckConstraintName;
|
|
43
34
|
private normalizeCheckExpression;
|
|
44
|
-
private
|
|
45
|
-
private
|
|
35
|
+
private isWrappedByOuterParentheses;
|
|
36
|
+
private findExistingConstraint;
|
|
37
|
+
private equalExpressions;
|
|
38
|
+
private canonicalizeCheckExpressionWithPostgres;
|
|
39
|
+
private extractCheckExpressionFromDefinition;
|
|
40
|
+
private quoteIdentifier;
|
|
41
|
+
private quoteQualifiedIdentifier;
|
|
46
42
|
/** Escape expression for embedding inside a template literal in generated code */
|
|
47
43
|
private escapeExpressionForRaw;
|
|
48
44
|
private addCheckConstraint;
|
|
49
45
|
private dropCheckConstraint;
|
|
50
|
-
private buildExcludeDef;
|
|
51
|
-
private addExcludeConstraint;
|
|
52
|
-
private dropExcludeConstraint;
|
|
53
|
-
private buildConstraintTriggerDef;
|
|
54
|
-
private addConstraintTrigger;
|
|
55
|
-
private dropConstraintTrigger;
|
|
56
46
|
private value;
|
|
57
47
|
private columnRaw;
|
|
58
48
|
private column;
|