@smartive/graphql-magic 23.7.0 → 23.8.0-next.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/CHANGELOG.md +3 -3
- package/dist/bin/gqm.cjs +307 -34
- package/dist/cjs/index.cjs +309 -34
- package/dist/esm/migrations/generate-functions.js +2 -0
- package/dist/esm/migrations/generate-functions.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +18 -1
- package/dist/esm/migrations/generate.js +292 -29
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/migrations/update-functions.js +2 -0
- package/dist/esm/migrations/update-functions.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 +10 -0
- package/dist/esm/models/utils.js +11 -0
- package/dist/esm/models/utils.js.map +1 -1
- package/docs/docs/2-models.md +18 -4
- package/docs/docs/5-migrations.md +11 -5
- package/package.json +1 -1
- package/src/bin/gqm/parse-knexfile.ts +1 -0
- package/src/bin/gqm/settings.ts +4 -0
- package/src/migrations/generate-functions.ts +2 -0
- package/src/migrations/generate.ts +379 -35
- package/src/migrations/update-functions.ts +2 -0
- package/src/models/model-definitions.ts +20 -1
- package/src/models/models.ts +4 -1
- package/src/models/utils.ts +20 -0
- package/tests/unit/constraints.spec.ts +98 -2
- package/tests/unit/migration-constraints.spec.ts +47 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -198,6 +198,7 @@ __export(index_exports, {
|
|
|
198
198
|
updateEntity: () => updateEntity,
|
|
199
199
|
updateFunctions: () => updateFunctions,
|
|
200
200
|
validateCheckConstraint: () => validateCheckConstraint,
|
|
201
|
+
validateExcludeConstraint: () => validateExcludeConstraint,
|
|
201
202
|
value: () => value
|
|
202
203
|
});
|
|
203
204
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -654,6 +655,8 @@ var EntityModel = class extends Model {
|
|
|
654
655
|
for (const constraint of this.constraints) {
|
|
655
656
|
if (constraint.kind === "check") {
|
|
656
657
|
validateCheckConstraint(this, constraint);
|
|
658
|
+
} else if (constraint.kind === "exclude") {
|
|
659
|
+
validateExcludeConstraint(this, constraint);
|
|
657
660
|
}
|
|
658
661
|
}
|
|
659
662
|
}
|
|
@@ -952,6 +955,19 @@ var validateCheckConstraint = (model, constraint) => {
|
|
|
952
955
|
}
|
|
953
956
|
}
|
|
954
957
|
};
|
|
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
|
+
};
|
|
955
971
|
|
|
956
972
|
// src/client/queries.ts
|
|
957
973
|
var fieldIsSearchable = (model, fieldName) => {
|
|
@@ -1166,6 +1182,7 @@ var generateFunctionsFromDatabase = async (knex) => {
|
|
|
1166
1182
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1167
1183
|
WHERE n.nspname = 'public'
|
|
1168
1184
|
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')
|
|
1169
1186
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1170
1187
|
`);
|
|
1171
1188
|
const aggregateFunctions = await knex.raw(`
|
|
@@ -1180,6 +1197,7 @@ var generateFunctionsFromDatabase = async (knex) => {
|
|
|
1180
1197
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
1181
1198
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1182
1199
|
WHERE n.nspname = 'public'
|
|
1200
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
1183
1201
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1184
1202
|
`);
|
|
1185
1203
|
const functions = [];
|
|
@@ -2924,6 +2942,7 @@ var getDatabaseFunctions = async (knex) => {
|
|
|
2924
2942
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
2925
2943
|
WHERE n.nspname = 'public'
|
|
2926
2944
|
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')
|
|
2927
2946
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
2928
2947
|
`);
|
|
2929
2948
|
const aggregateFunctions = await knex.raw(`
|
|
@@ -2939,6 +2958,7 @@ var getDatabaseFunctions = async (knex) => {
|
|
|
2939
2958
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
2940
2959
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
2941
2960
|
WHERE n.nspname = 'public'
|
|
2961
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
2942
2962
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
2943
2963
|
`);
|
|
2944
2964
|
const result = [];
|
|
@@ -3082,7 +3102,7 @@ Summary: ${updatedCount} updated, ${skippedCount} skipped`);
|
|
|
3082
3102
|
};
|
|
3083
3103
|
|
|
3084
3104
|
// src/migrations/generate.ts
|
|
3085
|
-
var MigrationGenerator = class {
|
|
3105
|
+
var MigrationGenerator = class _MigrationGenerator {
|
|
3086
3106
|
constructor(knex, models, parsedFunctions) {
|
|
3087
3107
|
this.models = models;
|
|
3088
3108
|
this.parsedFunctions = parsedFunctions;
|
|
@@ -3098,6 +3118,10 @@ var MigrationGenerator = class {
|
|
|
3098
3118
|
columns = {};
|
|
3099
3119
|
/** table name -> constraint name -> check clause expression */
|
|
3100
3120
|
existingCheckConstraints = {};
|
|
3121
|
+
/** table name -> constraint name -> { normalized, raw } */
|
|
3122
|
+
existingExcludeConstraints = {};
|
|
3123
|
+
/** table name -> constraint name -> { normalized, raw } */
|
|
3124
|
+
existingConstraintTriggers = {};
|
|
3101
3125
|
uuidUsed;
|
|
3102
3126
|
nowUsed;
|
|
3103
3127
|
needsMigration = false;
|
|
@@ -3125,8 +3149,56 @@ var MigrationGenerator = class {
|
|
|
3125
3149
|
}
|
|
3126
3150
|
this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
|
|
3127
3151
|
}
|
|
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
|
+
}
|
|
3128
3187
|
const up = [];
|
|
3129
3188
|
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
|
+
}
|
|
3130
3202
|
this.createEnums(
|
|
3131
3203
|
this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
|
|
3132
3204
|
up,
|
|
@@ -3205,10 +3277,22 @@ var MigrationGenerator = class {
|
|
|
3205
3277
|
if (entry.kind === "check") {
|
|
3206
3278
|
validateCheckConstraint(model, entry);
|
|
3207
3279
|
const table = model.name;
|
|
3208
|
-
const constraintName = this.
|
|
3209
|
-
|
|
3280
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
3281
|
+
up.push(() => {
|
|
3282
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
3283
|
+
});
|
|
3284
|
+
} else if (entry.kind === "exclude") {
|
|
3285
|
+
validateExcludeConstraint(model, entry);
|
|
3286
|
+
const table = model.name;
|
|
3287
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
3210
3288
|
up.push(() => {
|
|
3211
|
-
this.
|
|
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);
|
|
3212
3296
|
});
|
|
3213
3297
|
}
|
|
3214
3298
|
}
|
|
@@ -3248,36 +3332,84 @@ var MigrationGenerator = class {
|
|
|
3248
3332
|
);
|
|
3249
3333
|
this.updateFields(model, existingFields, up, down);
|
|
3250
3334
|
if (model.constraints?.length) {
|
|
3335
|
+
const existingExcludeMap = this.existingExcludeConstraints[model.name];
|
|
3336
|
+
const existingTriggerMap = this.existingConstraintTriggers[model.name];
|
|
3251
3337
|
for (let i = 0; i < model.constraints.length; i++) {
|
|
3252
3338
|
const entry = model.constraints[i];
|
|
3253
|
-
if (entry.kind !== "check") {
|
|
3254
|
-
continue;
|
|
3255
|
-
}
|
|
3256
|
-
validateCheckConstraint(model, entry);
|
|
3257
3339
|
const table = model.name;
|
|
3258
|
-
const constraintName = this.
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3340
|
+
const constraintName = this.getConstraintName(model, entry, i);
|
|
3341
|
+
if (entry.kind === "check") {
|
|
3342
|
+
validateCheckConstraint(model, entry);
|
|
3343
|
+
const existingConstraint = this.findExistingConstraint(table, entry, constraintName);
|
|
3344
|
+
if (!existingConstraint) {
|
|
3345
|
+
up.push(() => {
|
|
3346
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
3347
|
+
});
|
|
3348
|
+
down.push(() => {
|
|
3349
|
+
this.dropCheckConstraint(table, constraintName);
|
|
3350
|
+
});
|
|
3351
|
+
} else if (!await this.equalExpressions(
|
|
3352
|
+
table,
|
|
3353
|
+
existingConstraint.constraintName,
|
|
3354
|
+
existingConstraint.expression,
|
|
3355
|
+
entry.expression
|
|
3356
|
+
)) {
|
|
3357
|
+
up.push(() => {
|
|
3358
|
+
this.dropCheckConstraint(table, existingConstraint.constraintName);
|
|
3359
|
+
this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
|
|
3360
|
+
});
|
|
3361
|
+
down.push(() => {
|
|
3362
|
+
this.dropCheckConstraint(table, constraintName);
|
|
3363
|
+
this.addCheckConstraint(table, existingConstraint.constraintName, existingConstraint.expression);
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
} else if (entry.kind === "exclude") {
|
|
3367
|
+
validateExcludeConstraint(model, entry);
|
|
3368
|
+
const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
|
|
3369
|
+
const existing = existingExcludeMap?.get(constraintName);
|
|
3370
|
+
if (existing === void 0) {
|
|
3371
|
+
up.push(() => {
|
|
3372
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
3373
|
+
});
|
|
3374
|
+
down.push(() => {
|
|
3375
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
3376
|
+
});
|
|
3377
|
+
} else if (existing.normalized !== newDef) {
|
|
3378
|
+
up.push(() => {
|
|
3379
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
3380
|
+
this.addExcludeConstraint(table, constraintName, entry);
|
|
3381
|
+
});
|
|
3382
|
+
down.push(() => {
|
|
3383
|
+
this.dropExcludeConstraint(table, constraintName);
|
|
3384
|
+
const escaped = this.escapeExpressionForRaw(existing.raw);
|
|
3385
|
+
this.writer.writeLine(
|
|
3386
|
+
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
|
|
3387
|
+
);
|
|
3388
|
+
this.writer.blankLine();
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
} else if (entry.kind === "constraint_trigger") {
|
|
3392
|
+
const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
|
|
3393
|
+
const existing = existingTriggerMap?.get(constraintName);
|
|
3394
|
+
if (existing === void 0) {
|
|
3395
|
+
up.push(() => {
|
|
3396
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
3397
|
+
});
|
|
3398
|
+
down.push(() => {
|
|
3399
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
3400
|
+
});
|
|
3401
|
+
} else if (existing.normalized !== newDef) {
|
|
3402
|
+
up.push(() => {
|
|
3403
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
3404
|
+
this.addConstraintTrigger(table, constraintName, entry);
|
|
3405
|
+
});
|
|
3406
|
+
down.push(() => {
|
|
3407
|
+
this.dropConstraintTrigger(table, constraintName);
|
|
3408
|
+
const escaped = existing.raw.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
3409
|
+
this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
|
|
3410
|
+
this.writer.blankLine();
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3281
3413
|
}
|
|
3282
3414
|
}
|
|
3283
3415
|
}
|
|
@@ -3652,9 +3784,113 @@ var MigrationGenerator = class {
|
|
|
3652
3784
|
renameColumn(from, to) {
|
|
3653
3785
|
this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
|
|
3654
3786
|
}
|
|
3655
|
-
|
|
3787
|
+
getConstraintName(model, entry, index) {
|
|
3656
3788
|
return `${model.name}_${entry.name}_${entry.kind}_${index}`;
|
|
3657
3789
|
}
|
|
3790
|
+
static SQL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
3791
|
+
"and",
|
|
3792
|
+
"or",
|
|
3793
|
+
"not",
|
|
3794
|
+
"in",
|
|
3795
|
+
"is",
|
|
3796
|
+
"null",
|
|
3797
|
+
"true",
|
|
3798
|
+
"false",
|
|
3799
|
+
"between",
|
|
3800
|
+
"like",
|
|
3801
|
+
"exists",
|
|
3802
|
+
"all",
|
|
3803
|
+
"any",
|
|
3804
|
+
"asc",
|
|
3805
|
+
"desc",
|
|
3806
|
+
"with",
|
|
3807
|
+
"using",
|
|
3808
|
+
"as",
|
|
3809
|
+
"on",
|
|
3810
|
+
"infinity",
|
|
3811
|
+
"extract",
|
|
3812
|
+
"current_date",
|
|
3813
|
+
"current_timestamp"
|
|
3814
|
+
]);
|
|
3815
|
+
static LITERAL_PLACEHOLDER = "\uE000";
|
|
3816
|
+
static IDENT_PLACEHOLDER = "\uE001";
|
|
3817
|
+
normalizeSqlIdentifiers(s) {
|
|
3818
|
+
const literals = [];
|
|
3819
|
+
let result = s.replace(/'([^']|'')*'/g, (lit) => {
|
|
3820
|
+
literals.push(lit);
|
|
3821
|
+
return `${_MigrationGenerator.LITERAL_PLACEHOLDER}${literals.length - 1}${_MigrationGenerator.LITERAL_PLACEHOLDER}`;
|
|
3822
|
+
});
|
|
3823
|
+
const quotedIdents = [];
|
|
3824
|
+
result = result.replace(/"([^"]*)"/g, (_, ident) => {
|
|
3825
|
+
quotedIdents.push(`"${ident.toLowerCase()}"`);
|
|
3826
|
+
return `${_MigrationGenerator.IDENT_PLACEHOLDER}${quotedIdents.length - 1}${_MigrationGenerator.IDENT_PLACEHOLDER}`;
|
|
3827
|
+
});
|
|
3828
|
+
result = result.replace(
|
|
3829
|
+
/\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\()/g,
|
|
3830
|
+
(match) => _MigrationGenerator.SQL_KEYWORDS.has(match.toLowerCase()) ? match : `"${match.toLowerCase()}"`
|
|
3831
|
+
);
|
|
3832
|
+
for (let i = 0; i < quotedIdents.length; i++) {
|
|
3833
|
+
result = result.replace(
|
|
3834
|
+
new RegExp(`${_MigrationGenerator.IDENT_PLACEHOLDER}${i}${_MigrationGenerator.IDENT_PLACEHOLDER}`, "g"),
|
|
3835
|
+
quotedIdents[i]
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3838
|
+
for (let i = 0; i < literals.length; i++) {
|
|
3839
|
+
result = result.replace(
|
|
3840
|
+
new RegExp(`${_MigrationGenerator.LITERAL_PLACEHOLDER}${i}${_MigrationGenerator.LITERAL_PLACEHOLDER}`, "g"),
|
|
3841
|
+
literals[i]
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
return result;
|
|
3845
|
+
}
|
|
3846
|
+
normalizeExcludeDef(def) {
|
|
3847
|
+
let s = def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/::\s*timestamp\s+with\s+time\s+zone\b/gi, "::timestamptz").replace(/::\s*timestamp\s+without\s+time\s+zone\b/gi, "::timestamp").trim();
|
|
3848
|
+
const whereMatch = s.match(/\bWHERE\s*\(/i);
|
|
3849
|
+
if (whereMatch) {
|
|
3850
|
+
const openParen = (whereMatch.index ?? 0) + whereMatch[0].length - 1;
|
|
3851
|
+
const closeParen = this.findMatchingParen(s, openParen);
|
|
3852
|
+
if (closeParen !== -1) {
|
|
3853
|
+
let cond = s.slice(openParen + 1, closeParen).trim();
|
|
3854
|
+
while (this.isWrappedByOuterParentheses(cond)) {
|
|
3855
|
+
cond = cond.slice(1, -1).trim();
|
|
3856
|
+
}
|
|
3857
|
+
s = s.slice(0, openParen + 1) + cond + s.slice(closeParen);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
return this.normalizeSqlIdentifiers(s);
|
|
3861
|
+
}
|
|
3862
|
+
findMatchingParen(s, openIndex) {
|
|
3863
|
+
let depth = 1;
|
|
3864
|
+
let inSingleQuote = false;
|
|
3865
|
+
for (let i = openIndex + 1; i < s.length; i++) {
|
|
3866
|
+
const char = s[i];
|
|
3867
|
+
const next = s[i + 1];
|
|
3868
|
+
if (char === "'") {
|
|
3869
|
+
if (inSingleQuote && next === "'") {
|
|
3870
|
+
i++;
|
|
3871
|
+
continue;
|
|
3872
|
+
}
|
|
3873
|
+
inSingleQuote = !inSingleQuote;
|
|
3874
|
+
continue;
|
|
3875
|
+
}
|
|
3876
|
+
if (inSingleQuote) {
|
|
3877
|
+
continue;
|
|
3878
|
+
}
|
|
3879
|
+
if (char === "(") {
|
|
3880
|
+
depth++;
|
|
3881
|
+
} else if (char === ")") {
|
|
3882
|
+
depth--;
|
|
3883
|
+
if (depth === 0) {
|
|
3884
|
+
return i;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
return -1;
|
|
3889
|
+
}
|
|
3890
|
+
normalizeTriggerDef(def) {
|
|
3891
|
+
const s = 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();
|
|
3892
|
+
return this.normalizeSqlIdentifiers(s);
|
|
3893
|
+
}
|
|
3658
3894
|
normalizeCheckExpression(expr) {
|
|
3659
3895
|
let normalized = expr.replace(/\s+/g, " ").trim();
|
|
3660
3896
|
while (this.isWrappedByOuterParentheses(normalized)) {
|
|
@@ -3790,10 +4026,11 @@ var MigrationGenerator = class {
|
|
|
3790
4026
|
escapeExpressionForRaw(expr) {
|
|
3791
4027
|
return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
3792
4028
|
}
|
|
3793
|
-
addCheckConstraint(table, constraintName, expression) {
|
|
4029
|
+
addCheckConstraint(table, constraintName, expression, deferrable) {
|
|
3794
4030
|
const escaped = this.escapeExpressionForRaw(expression);
|
|
4031
|
+
const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
|
|
3795
4032
|
this.writer.writeLine(
|
|
3796
|
-
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
|
|
4033
|
+
`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
|
|
3797
4034
|
);
|
|
3798
4035
|
this.writer.blankLine();
|
|
3799
4036
|
}
|
|
@@ -3801,6 +4038,43 @@ var MigrationGenerator = class {
|
|
|
3801
4038
|
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
3802
4039
|
this.writer.blankLine();
|
|
3803
4040
|
}
|
|
4041
|
+
buildExcludeDef(entry) {
|
|
4042
|
+
const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
|
|
4043
|
+
const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
|
|
4044
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
4045
|
+
return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
|
|
4046
|
+
}
|
|
4047
|
+
addExcludeConstraint(table, constraintName, entry) {
|
|
4048
|
+
const def = this.buildExcludeDef(entry);
|
|
4049
|
+
const escaped = this.escapeExpressionForRaw(def);
|
|
4050
|
+
this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
|
|
4051
|
+
this.writer.blankLine();
|
|
4052
|
+
}
|
|
4053
|
+
dropExcludeConstraint(table, constraintName) {
|
|
4054
|
+
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
4055
|
+
this.writer.blankLine();
|
|
4056
|
+
}
|
|
4057
|
+
buildConstraintTriggerDef(table, constraintName, entry) {
|
|
4058
|
+
const eventsStr = entry.events.join(" OR ");
|
|
4059
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
4060
|
+
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
4061
|
+
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
4062
|
+
return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
|
|
4063
|
+
}
|
|
4064
|
+
addConstraintTrigger(table, constraintName, entry) {
|
|
4065
|
+
const eventsStr = entry.events.join(" OR ");
|
|
4066
|
+
const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
|
|
4067
|
+
const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
|
|
4068
|
+
const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
|
|
4069
|
+
this.writer.writeLine(
|
|
4070
|
+
`await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
|
|
4071
|
+
);
|
|
4072
|
+
this.writer.blankLine();
|
|
4073
|
+
}
|
|
4074
|
+
dropConstraintTrigger(table, constraintName) {
|
|
4075
|
+
this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
|
|
4076
|
+
this.writer.blankLine();
|
|
4077
|
+
}
|
|
3804
4078
|
value(value2) {
|
|
3805
4079
|
if (typeof value2 === "string") {
|
|
3806
4080
|
return `'${value2}'`;
|
|
@@ -4827,5 +5101,6 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
|
|
|
4827
5101
|
updateEntity,
|
|
4828
5102
|
updateFunctions,
|
|
4829
5103
|
validateCheckConstraint,
|
|
5104
|
+
validateExcludeConstraint,
|
|
4830
5105
|
value
|
|
4831
5106
|
});
|
|
@@ -6,6 +6,7 @@ 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')
|
|
9
10
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
10
11
|
`);
|
|
11
12
|
const aggregateFunctions = await knex.raw(`
|
|
@@ -20,6 +21,7 @@ export const generateFunctionsFromDatabase = async (knex) => {
|
|
|
20
21
|
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
21
22
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
22
23
|
WHERE n.nspname = 'public'
|
|
24
|
+
AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
|
|
23
25
|
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
24
26
|
`);
|
|
25
27
|
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;;;;;;;;;GASvC,CAAC,CAAC;IAEH,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;GAczC,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,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 -> { normalized, raw } */
|
|
13
|
+
private existingExcludeConstraints;
|
|
14
|
+
/** table name -> constraint name -> { normalized, raw } */
|
|
15
|
+
private existingConstraintTriggers;
|
|
12
16
|
private uuidUsed?;
|
|
13
17
|
private nowUsed?;
|
|
14
18
|
needsMigration: boolean;
|
|
@@ -30,7 +34,14 @@ export declare class MigrationGenerator {
|
|
|
30
34
|
private dropTable;
|
|
31
35
|
private renameTable;
|
|
32
36
|
private renameColumn;
|
|
33
|
-
private
|
|
37
|
+
private getConstraintName;
|
|
38
|
+
private static readonly SQL_KEYWORDS;
|
|
39
|
+
private static readonly LITERAL_PLACEHOLDER;
|
|
40
|
+
private static readonly IDENT_PLACEHOLDER;
|
|
41
|
+
private normalizeSqlIdentifiers;
|
|
42
|
+
private normalizeExcludeDef;
|
|
43
|
+
private findMatchingParen;
|
|
44
|
+
private normalizeTriggerDef;
|
|
34
45
|
private normalizeCheckExpression;
|
|
35
46
|
private isWrappedByOuterParentheses;
|
|
36
47
|
private findExistingConstraint;
|
|
@@ -43,6 +54,12 @@ export declare class MigrationGenerator {
|
|
|
43
54
|
private escapeExpressionForRaw;
|
|
44
55
|
private addCheckConstraint;
|
|
45
56
|
private dropCheckConstraint;
|
|
57
|
+
private buildExcludeDef;
|
|
58
|
+
private addExcludeConstraint;
|
|
59
|
+
private dropExcludeConstraint;
|
|
60
|
+
private buildConstraintTriggerDef;
|
|
61
|
+
private addConstraintTrigger;
|
|
62
|
+
private dropConstraintTrigger;
|
|
46
63
|
private value;
|
|
47
64
|
private columnRaw;
|
|
48
65
|
private column;
|