@smartive/graphql-magic 23.4.1 → 23.5.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -2
- package/dist/bin/gqm.cjs +656 -59
- package/dist/cjs/index.cjs +2700 -2133
- package/dist/esm/migrations/generate-functions.d.ts +2 -0
- package/dist/esm/migrations/generate-functions.js +60 -0
- package/dist/esm/migrations/generate-functions.js.map +1 -0
- package/dist/esm/migrations/generate.d.ts +9 -1
- package/dist/esm/migrations/generate.js +269 -33
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/migrations/index.d.ts +2 -0
- package/dist/esm/migrations/index.js +2 -0
- package/dist/esm/migrations/index.js.map +1 -1
- package/dist/esm/migrations/types.d.ts +7 -0
- package/dist/esm/migrations/types.js +2 -0
- package/dist/esm/migrations/types.js.map +1 -0
- package/dist/esm/migrations/update-functions.d.ts +3 -0
- package/dist/esm/migrations/update-functions.js +177 -0
- package/dist/esm/migrations/update-functions.js.map +1 -0
- package/dist/esm/models/model-definitions.d.ts +4 -1
- package/dist/esm/resolvers/filters.js +76 -14
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/selects.js +20 -2
- package/dist/esm/resolvers/selects.js.map +1 -1
- package/dist/esm/resolvers/utils.d.ts +1 -0
- package/dist/esm/resolvers/utils.js +29 -0
- package/dist/esm/resolvers/utils.js.map +1 -1
- package/docs/docs/3-fields.md +149 -0
- package/docs/docs/5-migrations.md +9 -1
- package/package.json +2 -2
- package/src/bin/gqm/gqm.ts +44 -5
- package/src/bin/gqm/parse-functions.ts +141 -0
- package/src/bin/gqm/settings.ts +7 -0
- package/src/bin/gqm/utils.ts +1 -0
- package/src/migrations/generate-functions.ts +74 -0
- package/src/migrations/generate.ts +334 -41
- package/src/migrations/index.ts +2 -0
- package/src/migrations/types.ts +7 -0
- package/src/migrations/update-functions.ts +221 -0
- package/src/models/model-definitions.ts +4 -1
- package/src/resolvers/filters.ts +88 -25
- package/src/resolvers/selects.ts +22 -5
- package/src/resolvers/utils.ts +44 -0
package/dist/bin/gqm.cjs
CHANGED
|
@@ -750,9 +750,25 @@ var generateKnexTables = (models) => {
|
|
|
750
750
|
var import_code_block_writer2 = __toESM(require("code-block-writer"), 1);
|
|
751
751
|
var import_knex_schema_inspector = require("knex-schema-inspector");
|
|
752
752
|
var import_lowerFirst = __toESM(require("lodash/lowerFirst"), 1);
|
|
753
|
+
|
|
754
|
+
// src/resolvers/arguments.ts
|
|
755
|
+
var import_graphql3 = require("graphql");
|
|
756
|
+
|
|
757
|
+
// src/resolvers/utils.ts
|
|
758
|
+
var import_graphql4 = require("graphql");
|
|
759
|
+
var import_isEqual = __toESM(require("lodash/isEqual"), 1);
|
|
760
|
+
var getColumnName = (field) => field.kind === "relation" ? field.foreignKey || `${field.name}Id` : field.name;
|
|
761
|
+
|
|
762
|
+
// src/resolvers/resolver.ts
|
|
763
|
+
var import_cloneDeep2 = __toESM(require("lodash/cloneDeep"), 1);
|
|
764
|
+
var import_flatMap = __toESM(require("lodash/flatMap"), 1);
|
|
765
|
+
|
|
766
|
+
// src/migrations/generate.ts
|
|
753
767
|
var MigrationGenerator = class {
|
|
754
|
-
constructor(knex2, models) {
|
|
768
|
+
constructor(knex2, models, parsedFunctions) {
|
|
755
769
|
this.models = models;
|
|
770
|
+
this.parsedFunctions = parsedFunctions;
|
|
771
|
+
this.knex = knex2;
|
|
756
772
|
this.schema = (0, import_knex_schema_inspector.SchemaInspector)(knex2);
|
|
757
773
|
}
|
|
758
774
|
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
@@ -765,6 +781,7 @@ var MigrationGenerator = class {
|
|
|
765
781
|
uuidUsed;
|
|
766
782
|
nowUsed;
|
|
767
783
|
needsMigration = false;
|
|
784
|
+
knex;
|
|
768
785
|
async generate() {
|
|
769
786
|
const { writer, schema, models } = this;
|
|
770
787
|
const enums = (await schema.knex("pg_type").where({ typtype: "e" }).select("typname")).map(({ typname }) => typname);
|
|
@@ -779,6 +796,7 @@ var MigrationGenerator = class {
|
|
|
779
796
|
up,
|
|
780
797
|
down
|
|
781
798
|
);
|
|
799
|
+
await this.handleFunctions(up, down);
|
|
782
800
|
for (const model of models.entities) {
|
|
783
801
|
if (model.deleted) {
|
|
784
802
|
up.push(() => {
|
|
@@ -840,7 +858,7 @@ var MigrationGenerator = class {
|
|
|
840
858
|
foreignKey: "id"
|
|
841
859
|
});
|
|
842
860
|
}
|
|
843
|
-
for (const field of model.fields.filter(not(isInherited))) {
|
|
861
|
+
for (const field of model.fields.filter(not(isInherited)).filter((f) => !(f.generateAs?.type === "expression"))) {
|
|
844
862
|
this.column(field);
|
|
845
863
|
}
|
|
846
864
|
});
|
|
@@ -849,29 +867,25 @@ var MigrationGenerator = class {
|
|
|
849
867
|
this.dropTable(model.name);
|
|
850
868
|
});
|
|
851
869
|
} else {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
model.fields.filter(not(isInherited)).filter(({ oldName }) => oldName),
|
|
855
|
-
up,
|
|
856
|
-
down
|
|
857
|
-
);
|
|
870
|
+
const fieldsToRename = model.fields.filter(not(isInherited)).filter(({ oldName }) => oldName);
|
|
871
|
+
this.renameFields(model.name, fieldsToRename, up, down);
|
|
858
872
|
this.createFields(
|
|
859
873
|
model,
|
|
860
874
|
model.fields.filter(not(isInherited)).filter(
|
|
861
|
-
({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(model.name, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
|
|
875
|
+
({ name: name2, ...field }) => field.kind !== "custom" && !(field.generateAs?.type === "expression") && !this.getColumn(model.name, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
|
|
862
876
|
),
|
|
863
877
|
up,
|
|
864
878
|
down
|
|
865
879
|
);
|
|
866
880
|
const rawExistingFields = model.fields.filter((field) => {
|
|
867
|
-
if (!field.generateAs) {
|
|
881
|
+
if (!field.generateAs || field.generateAs.type === "expression") {
|
|
868
882
|
return false;
|
|
869
883
|
}
|
|
870
884
|
const col = this.getColumn(model.name, field.kind === "relation" ? `${field.name}Id` : field.name);
|
|
871
885
|
if (!col) {
|
|
872
886
|
return false;
|
|
873
887
|
}
|
|
874
|
-
if (col.generation_expression !== field.generateAs) {
|
|
888
|
+
if (col.generation_expression !== field.generateAs.expression) {
|
|
875
889
|
return true;
|
|
876
890
|
}
|
|
877
891
|
return this.hasChanged(model, field);
|
|
@@ -879,7 +893,9 @@ var MigrationGenerator = class {
|
|
|
879
893
|
if (rawExistingFields.length) {
|
|
880
894
|
this.updateFieldsRaw(model, rawExistingFields, up, down);
|
|
881
895
|
}
|
|
882
|
-
const existingFields = model.fields.filter(
|
|
896
|
+
const existingFields = model.fields.filter(
|
|
897
|
+
(field) => (!field.generateAs || field.generateAs.type === "expression") && this.hasChanged(model, field)
|
|
898
|
+
);
|
|
883
899
|
this.updateFields(model, existingFields, up, down);
|
|
884
900
|
}
|
|
885
901
|
if (isUpdatableModel(model)) {
|
|
@@ -903,7 +919,7 @@ var MigrationGenerator = class {
|
|
|
903
919
|
writer.writeLine(`deleteRootType: row.deleteRootType,`);
|
|
904
920
|
writer.writeLine(`deleteRootId: row.deleteRootId,`);
|
|
905
921
|
}
|
|
906
|
-
for (const { name: name2, kind } of model.fields.filter(isUpdatableField)) {
|
|
922
|
+
for (const { name: name2, kind } of model.fields.filter(isUpdatableField).filter((f) => !(f.generateAs?.type === "expression"))) {
|
|
907
923
|
const col = kind === "relation" ? `${name2}Id` : name2;
|
|
908
924
|
writer.writeLine(`${col}: row.${col},`);
|
|
909
925
|
}
|
|
@@ -917,8 +933,14 @@ var MigrationGenerator = class {
|
|
|
917
933
|
});
|
|
918
934
|
} else {
|
|
919
935
|
const revisionTable = `${model.name}Revision`;
|
|
936
|
+
this.renameFields(
|
|
937
|
+
revisionTable,
|
|
938
|
+
model.fields.filter(isUpdatableField).filter(not(isInherited)).filter(({ oldName }) => oldName),
|
|
939
|
+
up,
|
|
940
|
+
down
|
|
941
|
+
);
|
|
920
942
|
const missingRevisionFields = model.fields.filter(isUpdatableField).filter(
|
|
921
|
-
({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
|
|
943
|
+
({ name: name2, ...field }) => field.kind !== "custom" && !(field.generateAs?.type === "expression") && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
|
|
922
944
|
);
|
|
923
945
|
this.createRevisionFields(model, missingRevisionFields, up, down);
|
|
924
946
|
const revisionFieldsToRemove = model.fields.filter(
|
|
@@ -984,13 +1006,13 @@ var MigrationGenerator = class {
|
|
|
984
1006
|
this.migration("down", down.reverse());
|
|
985
1007
|
return writer.toString();
|
|
986
1008
|
}
|
|
987
|
-
renameFields(
|
|
1009
|
+
renameFields(tableName, fields2, up, down) {
|
|
988
1010
|
if (!fields2.length) {
|
|
989
1011
|
return;
|
|
990
1012
|
}
|
|
991
1013
|
up.push(() => {
|
|
992
1014
|
for (const field of fields2) {
|
|
993
|
-
this.alterTable(
|
|
1015
|
+
this.alterTable(tableName, () => {
|
|
994
1016
|
this.renameColumn(
|
|
995
1017
|
field.kind === "relation" ? `${field.oldName}Id` : get(field, "oldName"),
|
|
996
1018
|
field.kind === "relation" ? `${field.name}Id` : field.name
|
|
@@ -1000,7 +1022,7 @@ var MigrationGenerator = class {
|
|
|
1000
1022
|
});
|
|
1001
1023
|
down.push(() => {
|
|
1002
1024
|
for (const field of fields2) {
|
|
1003
|
-
this.alterTable(
|
|
1025
|
+
this.alterTable(tableName, () => {
|
|
1004
1026
|
this.renameColumn(
|
|
1005
1027
|
field.kind === "relation" ? `${field.name}Id` : field.name,
|
|
1006
1028
|
field.kind === "relation" ? `${field.oldName}Id` : get(field, "oldName")
|
|
@@ -1009,7 +1031,7 @@ var MigrationGenerator = class {
|
|
|
1009
1031
|
}
|
|
1010
1032
|
});
|
|
1011
1033
|
for (const field of fields2) {
|
|
1012
|
-
summonByName(this.columns[
|
|
1034
|
+
summonByName(this.columns[tableName], field.kind === "relation" ? `${field.oldName}Id` : field.oldName).name = field.kind === "relation" ? `${field.name}Id` : field.name;
|
|
1013
1035
|
}
|
|
1014
1036
|
}
|
|
1015
1037
|
createFields(model, fields2, up, down) {
|
|
@@ -1021,6 +1043,9 @@ var MigrationGenerator = class {
|
|
|
1021
1043
|
const updates = [];
|
|
1022
1044
|
const postAlter = [];
|
|
1023
1045
|
for (const field of fields2) {
|
|
1046
|
+
if (field.generateAs?.type === "expression") {
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1024
1049
|
alter.push(() => this.column(field, { setNonNull: field.defaultValue !== void 0 }));
|
|
1025
1050
|
if (field.generateAs) {
|
|
1026
1051
|
continue;
|
|
@@ -1073,7 +1098,7 @@ var MigrationGenerator = class {
|
|
|
1073
1098
|
});
|
|
1074
1099
|
});
|
|
1075
1100
|
if (isUpdatableModel(model)) {
|
|
1076
|
-
const updatableFields = fields2.filter(isUpdatableField);
|
|
1101
|
+
const updatableFields = fields2.filter(isUpdatableField).filter((f) => !(f.generateAs?.type === "expression"));
|
|
1077
1102
|
if (!updatableFields.length) {
|
|
1078
1103
|
return;
|
|
1079
1104
|
}
|
|
@@ -1121,7 +1146,7 @@ var MigrationGenerator = class {
|
|
|
1121
1146
|
});
|
|
1122
1147
|
});
|
|
1123
1148
|
if (isUpdatableModel(model)) {
|
|
1124
|
-
const updatableFields = fields2.filter(isUpdatableField);
|
|
1149
|
+
const updatableFields = fields2.filter(isUpdatableField).filter((f) => !(f.generateAs?.type === "expression"));
|
|
1125
1150
|
if (!updatableFields.length) {
|
|
1126
1151
|
return;
|
|
1127
1152
|
}
|
|
@@ -1159,7 +1184,7 @@ var MigrationGenerator = class {
|
|
|
1159
1184
|
writer.writeLine(`table.uuid('deleteRootId');`);
|
|
1160
1185
|
}
|
|
1161
1186
|
}
|
|
1162
|
-
for (const field of model.fields.filter(and(isUpdatableField, not(isInherited)))) {
|
|
1187
|
+
for (const field of model.fields.filter(and(isUpdatableField, not(isInherited))).filter((f) => !(f.generateAs?.type === "expression"))) {
|
|
1163
1188
|
this.column(field, { setUnique: false, setDefault: false });
|
|
1164
1189
|
}
|
|
1165
1190
|
});
|
|
@@ -1173,14 +1198,19 @@ var MigrationGenerator = class {
|
|
|
1173
1198
|
this.column(field, { setUnique: false, setNonNull: false, setDefault: false });
|
|
1174
1199
|
}
|
|
1175
1200
|
});
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1201
|
+
const revisionFieldsWithDataToCopy = missingRevisionFields.filter(
|
|
1202
|
+
(field) => this.columns[model.name].find((col) => col.name === getColumnName(field)) || field.defaultValue !== void 0 || field.nonNull
|
|
1203
|
+
);
|
|
1204
|
+
if (revisionFieldsWithDataToCopy.length) {
|
|
1205
|
+
this.writer.write(`await knex('${model.name}Revision').update(`).inlineBlock(() => {
|
|
1206
|
+
for (const { name: name2, kind: type } of revisionFieldsWithDataToCopy) {
|
|
1207
|
+
const col = type === "relation" ? `${name2}Id` : name2;
|
|
1208
|
+
this.writer.write(
|
|
1209
|
+
`${col}: knex.raw('(select "${col}" from "${model.name}" where "${model.name}".id = "${model.name}Revision"."${typeToField(model.name)}Id")'),`
|
|
1210
|
+
).newLine();
|
|
1211
|
+
}
|
|
1212
|
+
}).write(");").newLine().blankLine();
|
|
1213
|
+
}
|
|
1184
1214
|
const nonNullableMissingRevisionFields = missingRevisionFields.filter(({ nonNull: nonNull2 }) => nonNull2);
|
|
1185
1215
|
if (nonNullableMissingRevisionFields.length) {
|
|
1186
1216
|
this.alterTable(revisionTable, () => {
|
|
@@ -1236,7 +1266,7 @@ var MigrationGenerator = class {
|
|
|
1236
1266
|
return this.writer.writeLine(`await knex.schema.renameTable('${from}', '${to}');`).blankLine();
|
|
1237
1267
|
}
|
|
1238
1268
|
renameColumn(from, to) {
|
|
1239
|
-
this.writer.writeLine(`table.renameColumn('${from}', '${to}')
|
|
1269
|
+
this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
|
|
1240
1270
|
}
|
|
1241
1271
|
value(value2) {
|
|
1242
1272
|
if (typeof value2 === "string") {
|
|
@@ -1261,6 +1291,9 @@ var MigrationGenerator = class {
|
|
|
1261
1291
|
};
|
|
1262
1292
|
const kind = field.kind;
|
|
1263
1293
|
if (field.generateAs) {
|
|
1294
|
+
if (field.generateAs.type === "expression") {
|
|
1295
|
+
throw new Error(`Expression fields cannot be created in SQL schema.`);
|
|
1296
|
+
}
|
|
1264
1297
|
let type = "";
|
|
1265
1298
|
switch (kind) {
|
|
1266
1299
|
case void 0:
|
|
@@ -1269,6 +1302,9 @@ var MigrationGenerator = class {
|
|
|
1269
1302
|
case "Float":
|
|
1270
1303
|
type = `decimal(${field.precision ?? "undefined"}, ${field.scale ?? "undefined"})`;
|
|
1271
1304
|
break;
|
|
1305
|
+
case "Boolean":
|
|
1306
|
+
type = "boolean";
|
|
1307
|
+
break;
|
|
1272
1308
|
default:
|
|
1273
1309
|
throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
|
|
1274
1310
|
}
|
|
@@ -1288,10 +1324,10 @@ var MigrationGenerator = class {
|
|
|
1288
1324
|
this.writer.write(`, ALTER COLUMN "${name2}" DROP NOT NULL`);
|
|
1289
1325
|
}
|
|
1290
1326
|
}
|
|
1291
|
-
this.writer.write(`, ALTER COLUMN "${name2}" SET EXPRESSION AS (${field.generateAs})`);
|
|
1327
|
+
this.writer.write(`, ALTER COLUMN "${name2}" SET EXPRESSION AS (${field.generateAs.expression})`);
|
|
1292
1328
|
} else {
|
|
1293
1329
|
this.writer.write(
|
|
1294
|
-
`${alter ? "ALTER" : "ADD"} COLUMN "${name2}" ${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs}) STORED`
|
|
1330
|
+
`${alter ? "ALTER" : "ADD"} COLUMN "${name2}" ${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs.expression}) STORED`
|
|
1295
1331
|
);
|
|
1296
1332
|
}
|
|
1297
1333
|
return;
|
|
@@ -1315,6 +1351,9 @@ var MigrationGenerator = class {
|
|
|
1315
1351
|
};
|
|
1316
1352
|
const kind = field.kind;
|
|
1317
1353
|
if (field.generateAs) {
|
|
1354
|
+
if (field.generateAs.type === "expression") {
|
|
1355
|
+
throw new Error(`Expression fields cannot be created in SQL schema.`);
|
|
1356
|
+
}
|
|
1318
1357
|
let type = "";
|
|
1319
1358
|
switch (kind) {
|
|
1320
1359
|
case void 0:
|
|
@@ -1323,6 +1362,9 @@ var MigrationGenerator = class {
|
|
|
1323
1362
|
case "Float":
|
|
1324
1363
|
type = `decimal(${field.precision ?? "undefined"}, ${field.scale ?? "undefined"})`;
|
|
1325
1364
|
break;
|
|
1365
|
+
case "Boolean":
|
|
1366
|
+
type = "boolean";
|
|
1367
|
+
break;
|
|
1326
1368
|
default:
|
|
1327
1369
|
throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
|
|
1328
1370
|
}
|
|
@@ -1331,7 +1373,7 @@ var MigrationGenerator = class {
|
|
|
1331
1373
|
throw new Error(`Generated columns of kind ${kind} are not supported yet.`);
|
|
1332
1374
|
}
|
|
1333
1375
|
this.writer.write(
|
|
1334
|
-
`table.specificType('${name2}', '${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs}) STORED')`
|
|
1376
|
+
`table.specificType('${name2}', '${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs.expression}) ${field.generateAs.type === "virtual" ? "VIRTUAL" : "STORED"}')`
|
|
1335
1377
|
);
|
|
1336
1378
|
if (alter) {
|
|
1337
1379
|
this.writer.write(".alter()");
|
|
@@ -1429,14 +1471,17 @@ var MigrationGenerator = class {
|
|
|
1429
1471
|
return this.columns[tableName].find((col) => col.name === columnName);
|
|
1430
1472
|
}
|
|
1431
1473
|
hasChanged(model, field) {
|
|
1474
|
+
if (field.generateAs?.type === "expression") {
|
|
1475
|
+
return false;
|
|
1476
|
+
}
|
|
1432
1477
|
const col = this.getColumn(model.name, field.kind === "relation" ? `${field.name}Id` : field.name);
|
|
1433
1478
|
if (!col) {
|
|
1434
1479
|
return false;
|
|
1435
1480
|
}
|
|
1436
1481
|
if (field.generateAs) {
|
|
1437
|
-
if (col.generation_expression !== field.generateAs) {
|
|
1482
|
+
if (col.generation_expression !== field.generateAs.expression) {
|
|
1438
1483
|
throw new Error(
|
|
1439
|
-
`Column ${col.name} has specific type ${col.generation_expression} but expected ${field.generateAs}`
|
|
1484
|
+
`Column ${col.name} has specific type ${col.generation_expression} but expected ${field.generateAs.expression}`
|
|
1440
1485
|
);
|
|
1441
1486
|
}
|
|
1442
1487
|
}
|
|
@@ -1478,6 +1523,182 @@ var MigrationGenerator = class {
|
|
|
1478
1523
|
}
|
|
1479
1524
|
return false;
|
|
1480
1525
|
}
|
|
1526
|
+
normalizeFunctionBody(body) {
|
|
1527
|
+
return body.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").trim();
|
|
1528
|
+
}
|
|
1529
|
+
normalizeAggregateDefinition(definition) {
|
|
1530
|
+
let normalized = definition.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").trim();
|
|
1531
|
+
const initCondMatch = normalized.match(/INITCOND\s*=\s*([^,)]+)/i);
|
|
1532
|
+
if (initCondMatch) {
|
|
1533
|
+
const initCondValue = initCondMatch[1].trim();
|
|
1534
|
+
const unquoted = initCondValue.replace(/^['"]|['"]$/g, "");
|
|
1535
|
+
if (/^\d+$/.test(unquoted)) {
|
|
1536
|
+
normalized = normalized.replace(/INITCOND\s*=\s*[^,)]+/i, `INITCOND = '${unquoted}'`);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return normalized;
|
|
1540
|
+
}
|
|
1541
|
+
extractFunctionBody(definition) {
|
|
1542
|
+
const dollarQuoteMatch = definition.match(/AS\s+\$([^$]*)\$([\s\S]*?)\$\1\$/i);
|
|
1543
|
+
if (dollarQuoteMatch) {
|
|
1544
|
+
return dollarQuoteMatch[2].trim();
|
|
1545
|
+
}
|
|
1546
|
+
const bodyMatch = definition.match(/AS\s+\$\$([\s\S]*?)\$\$/i) || definition.match(/AS\s+['"]([\s\S]*?)['"]/i);
|
|
1547
|
+
if (bodyMatch) {
|
|
1548
|
+
return bodyMatch[1].trim();
|
|
1549
|
+
}
|
|
1550
|
+
return definition;
|
|
1551
|
+
}
|
|
1552
|
+
async getDatabaseFunctions() {
|
|
1553
|
+
const regularFunctions = await this.knex.raw(`
|
|
1554
|
+
SELECT
|
|
1555
|
+
p.proname as name,
|
|
1556
|
+
pg_get_function_identity_arguments(p.oid) as arguments,
|
|
1557
|
+
pg_get_functiondef(p.oid) as definition,
|
|
1558
|
+
false as is_aggregate
|
|
1559
|
+
FROM pg_proc p
|
|
1560
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1561
|
+
WHERE n.nspname = 'public'
|
|
1562
|
+
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
1563
|
+
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1564
|
+
`);
|
|
1565
|
+
const aggregateFunctions = await this.knex.raw(`
|
|
1566
|
+
SELECT
|
|
1567
|
+
p.proname as name,
|
|
1568
|
+
pg_get_function_identity_arguments(p.oid) as arguments,
|
|
1569
|
+
a.aggtransfn::regproc::text as trans_func,
|
|
1570
|
+
a.aggfinalfn::regproc::text as final_func,
|
|
1571
|
+
a.agginitval as init_val,
|
|
1572
|
+
pg_catalog.format_type(a.aggtranstype, NULL) as state_type,
|
|
1573
|
+
true as is_aggregate
|
|
1574
|
+
FROM pg_proc p
|
|
1575
|
+
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
1576
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1577
|
+
WHERE n.nspname = 'public'
|
|
1578
|
+
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1579
|
+
`);
|
|
1580
|
+
const result = [];
|
|
1581
|
+
for (const row of regularFunctions.rows || []) {
|
|
1582
|
+
const definition = row.definition || "";
|
|
1583
|
+
const name2 = row.name || "";
|
|
1584
|
+
const argumentsStr = row.arguments || "";
|
|
1585
|
+
if (!definition) {
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
const signature = `${name2}(${argumentsStr})`;
|
|
1589
|
+
const body = this.normalizeFunctionBody(this.extractFunctionBody(definition));
|
|
1590
|
+
result.push({
|
|
1591
|
+
name: name2,
|
|
1592
|
+
signature,
|
|
1593
|
+
body,
|
|
1594
|
+
isAggregate: false,
|
|
1595
|
+
definition
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
for (const row of aggregateFunctions.rows || []) {
|
|
1599
|
+
const name2 = row.name || "";
|
|
1600
|
+
const argumentsStr = row.arguments || "";
|
|
1601
|
+
const transFunc = row.trans_func || "";
|
|
1602
|
+
const finalFunc = row.final_func || "";
|
|
1603
|
+
const initVal = row.init_val;
|
|
1604
|
+
const stateType = row.state_type || "";
|
|
1605
|
+
const signature = `${name2}(${argumentsStr})`;
|
|
1606
|
+
let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (`;
|
|
1607
|
+
aggregateDef += `SFUNC = ${transFunc}, STYPE = ${stateType}`;
|
|
1608
|
+
if (finalFunc) {
|
|
1609
|
+
aggregateDef += `, FINALFUNC = ${finalFunc}`;
|
|
1610
|
+
}
|
|
1611
|
+
if (initVal !== null && initVal !== void 0) {
|
|
1612
|
+
let initValStr;
|
|
1613
|
+
if (typeof initVal === "string") {
|
|
1614
|
+
initValStr = `'${initVal}'`;
|
|
1615
|
+
} else {
|
|
1616
|
+
const numStr = String(initVal);
|
|
1617
|
+
initValStr = /^\d+$/.test(numStr) ? `'${numStr}'` : numStr;
|
|
1618
|
+
}
|
|
1619
|
+
aggregateDef += `, INITCOND = ${initValStr}`;
|
|
1620
|
+
}
|
|
1621
|
+
aggregateDef += ");";
|
|
1622
|
+
result.push({
|
|
1623
|
+
name: name2,
|
|
1624
|
+
signature,
|
|
1625
|
+
body: this.normalizeAggregateDefinition(aggregateDef),
|
|
1626
|
+
isAggregate: true,
|
|
1627
|
+
definition: aggregateDef
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
return result;
|
|
1631
|
+
}
|
|
1632
|
+
async handleFunctions(up, down) {
|
|
1633
|
+
if (!this.parsedFunctions || this.parsedFunctions.length === 0) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
const definedFunctions = this.parsedFunctions;
|
|
1637
|
+
const dbFunctions = await this.getDatabaseFunctions();
|
|
1638
|
+
const dbFunctionsBySignature = /* @__PURE__ */ new Map();
|
|
1639
|
+
for (const func of dbFunctions) {
|
|
1640
|
+
dbFunctionsBySignature.set(func.signature, func);
|
|
1641
|
+
}
|
|
1642
|
+
const definedFunctionsBySignature = /* @__PURE__ */ new Map();
|
|
1643
|
+
for (const func of definedFunctions) {
|
|
1644
|
+
definedFunctionsBySignature.set(func.signature, func);
|
|
1645
|
+
}
|
|
1646
|
+
const functionsToRestore = [];
|
|
1647
|
+
for (const definedFunc of definedFunctions) {
|
|
1648
|
+
const dbFunc = dbFunctionsBySignature.get(definedFunc.signature);
|
|
1649
|
+
if (!dbFunc) {
|
|
1650
|
+
up.push(() => {
|
|
1651
|
+
this.writer.writeLine(`await knex.raw(\`${definedFunc.fullDefinition.replace(/`/g, "\\`")}\`);`).blankLine();
|
|
1652
|
+
});
|
|
1653
|
+
down.push(() => {
|
|
1654
|
+
const isAggregate = definedFunc.isAggregate;
|
|
1655
|
+
const dropMatch = definedFunc.fullDefinition.match(/CREATE\s+(OR\s+REPLACE\s+)?(FUNCTION|AGGREGATE)\s+([^(]+)\(/i);
|
|
1656
|
+
if (dropMatch) {
|
|
1657
|
+
const functionName = dropMatch[3].trim();
|
|
1658
|
+
const argsMatch = definedFunc.fullDefinition.match(
|
|
1659
|
+
/CREATE\s+(OR\s+REPLACE\s+)?(FUNCTION|AGGREGATE)\s+[^(]+\(([^)]*)\)/i
|
|
1660
|
+
);
|
|
1661
|
+
const args2 = argsMatch ? argsMatch[3].trim() : "";
|
|
1662
|
+
const dropType = isAggregate ? "AGGREGATE" : "FUNCTION";
|
|
1663
|
+
this.writer.writeLine(`await knex.raw(\`DROP ${dropType} IF EXISTS ${functionName}${args2 ? `(${args2})` : ""}\`);`).blankLine();
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
} else {
|
|
1667
|
+
const dbBody = dbFunc.isAggregate ? this.normalizeAggregateDefinition(dbFunc.body) : this.normalizeFunctionBody(dbFunc.body);
|
|
1668
|
+
const definedBody = definedFunc.isAggregate ? this.normalizeAggregateDefinition(definedFunc.body) : this.normalizeFunctionBody(definedFunc.body);
|
|
1669
|
+
if (dbBody !== definedBody) {
|
|
1670
|
+
const oldDefinition = dbFunc.definition || dbFunc.body;
|
|
1671
|
+
up.push(() => {
|
|
1672
|
+
this.writer.writeLine(`await knex.raw(\`${definedFunc.fullDefinition.replace(/`/g, "\\`")}\`);`).blankLine();
|
|
1673
|
+
});
|
|
1674
|
+
down.push(() => {
|
|
1675
|
+
if (oldDefinition) {
|
|
1676
|
+
this.writer.writeLine(`await knex.raw(\`${oldDefinition.replace(/`/g, "\\`")}\`);`).blankLine();
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
for (const dbFunc of dbFunctions) {
|
|
1683
|
+
if (!definedFunctionsBySignature.has(dbFunc.signature)) {
|
|
1684
|
+
const definition = dbFunc.definition || dbFunc.body;
|
|
1685
|
+
if (definition) {
|
|
1686
|
+
functionsToRestore.push({ func: dbFunc, definition });
|
|
1687
|
+
down.push(() => {
|
|
1688
|
+
const argsMatch = dbFunc.signature.match(/\(([^)]*)\)/);
|
|
1689
|
+
const args2 = argsMatch ? argsMatch[1] : "";
|
|
1690
|
+
const dropType = dbFunc.isAggregate ? "AGGREGATE" : "FUNCTION";
|
|
1691
|
+
this.writer.writeLine(`await knex.raw(\`DROP ${dropType} IF EXISTS ${dbFunc.name}${args2 ? `(${args2})` : ""}\`);`).blankLine();
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
for (const { definition } of functionsToRestore) {
|
|
1697
|
+
up.push(() => {
|
|
1698
|
+
this.writer.writeLine(`await knex.raw(\`${definition.replace(/`/g, "\\`")}\`);`).blankLine();
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1481
1702
|
};
|
|
1482
1703
|
var getMigrationDate = () => {
|
|
1483
1704
|
const date = /* @__PURE__ */ new Date();
|
|
@@ -1490,21 +1711,258 @@ var getMigrationDate = () => {
|
|
|
1490
1711
|
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
1491
1712
|
};
|
|
1492
1713
|
|
|
1493
|
-
// src/
|
|
1494
|
-
var
|
|
1495
|
-
|
|
1496
|
-
|
|
1714
|
+
// src/migrations/generate-functions.ts
|
|
1715
|
+
var generateFunctionsFromDatabase = async (knex2) => {
|
|
1716
|
+
const regularFunctions = await knex2.raw(`
|
|
1717
|
+
SELECT
|
|
1718
|
+
pg_get_functiondef(p.oid) as definition
|
|
1719
|
+
FROM pg_proc p
|
|
1720
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1721
|
+
WHERE n.nspname = 'public'
|
|
1722
|
+
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
1723
|
+
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1724
|
+
`);
|
|
1725
|
+
const aggregateFunctions = await knex2.raw(`
|
|
1726
|
+
SELECT
|
|
1727
|
+
p.proname as name,
|
|
1728
|
+
pg_get_function_identity_arguments(p.oid) as arguments,
|
|
1729
|
+
a.aggtransfn::regproc::text as trans_func,
|
|
1730
|
+
a.aggfinalfn::regproc::text as final_func,
|
|
1731
|
+
a.agginitval as init_val,
|
|
1732
|
+
pg_catalog.format_type(a.aggtranstype, NULL) as state_type
|
|
1733
|
+
FROM pg_proc p
|
|
1734
|
+
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
1735
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1736
|
+
WHERE n.nspname = 'public'
|
|
1737
|
+
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1738
|
+
`);
|
|
1739
|
+
const functions = [];
|
|
1740
|
+
for (const row of regularFunctions.rows || []) {
|
|
1741
|
+
if (row.definition) {
|
|
1742
|
+
functions.push(row.definition.trim());
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
for (const row of aggregateFunctions.rows || []) {
|
|
1746
|
+
const name2 = row.name || "";
|
|
1747
|
+
const argumentsStr = row.arguments || "";
|
|
1748
|
+
const transFunc = row.trans_func || "";
|
|
1749
|
+
const finalFunc = row.final_func || "";
|
|
1750
|
+
const initVal = row.init_val;
|
|
1751
|
+
const stateType = row.state_type || "";
|
|
1752
|
+
if (!name2 || !transFunc || !stateType) {
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (
|
|
1756
|
+
`;
|
|
1757
|
+
aggregateDef += ` SFUNC = ${transFunc},
|
|
1758
|
+
`;
|
|
1759
|
+
aggregateDef += ` STYPE = ${stateType}`;
|
|
1760
|
+
if (finalFunc) {
|
|
1761
|
+
aggregateDef += `,
|
|
1762
|
+
FINALFUNC = ${finalFunc}`;
|
|
1763
|
+
}
|
|
1764
|
+
if (initVal !== null && initVal !== void 0) {
|
|
1765
|
+
const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
|
|
1766
|
+
aggregateDef += `,
|
|
1767
|
+
INITCOND = ${initValStr}`;
|
|
1768
|
+
}
|
|
1769
|
+
aggregateDef += "\n);";
|
|
1770
|
+
functions.push(aggregateDef);
|
|
1771
|
+
}
|
|
1772
|
+
if (functions.length === 0) {
|
|
1773
|
+
return `export const functions: string[] = [];
|
|
1774
|
+
`;
|
|
1775
|
+
}
|
|
1776
|
+
const functionsArrayString = functions.map((func) => ` ${JSON.stringify(func)}`).join(",\n");
|
|
1777
|
+
return `export const functions: string[] = [
|
|
1778
|
+
${functionsArrayString},
|
|
1779
|
+
];
|
|
1780
|
+
`;
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
// src/migrations/update-functions.ts
|
|
1784
|
+
var normalizeWhitespace = (str) => {
|
|
1785
|
+
return str.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").replace(/\s*;\s*/g, ";").trim();
|
|
1786
|
+
};
|
|
1787
|
+
var normalizeFunctionBody = (body) => {
|
|
1788
|
+
return normalizeWhitespace(body);
|
|
1789
|
+
};
|
|
1790
|
+
var extractFunctionBody = (definition) => {
|
|
1791
|
+
const dollarQuoteMatch = definition.match(/AS\s+\$([^$]*)\$([\s\S]*?)\$\1\$/i);
|
|
1792
|
+
if (dollarQuoteMatch) {
|
|
1793
|
+
return dollarQuoteMatch[2].trim();
|
|
1794
|
+
}
|
|
1795
|
+
const bodyMatch = definition.match(/AS\s+\$\$([\s\S]*?)\$\$/i) || definition.match(/AS\s+['"]([\s\S]*?)['"]/i);
|
|
1796
|
+
if (bodyMatch) {
|
|
1797
|
+
return bodyMatch[1].trim();
|
|
1798
|
+
}
|
|
1799
|
+
return definition;
|
|
1800
|
+
};
|
|
1801
|
+
var getDatabaseFunctions = async (knex2) => {
|
|
1802
|
+
const regularFunctions = await knex2.raw(`
|
|
1803
|
+
SELECT
|
|
1804
|
+
p.proname as name,
|
|
1805
|
+
pg_get_function_identity_arguments(p.oid) as arguments,
|
|
1806
|
+
pg_get_functiondef(p.oid) as definition,
|
|
1807
|
+
false as is_aggregate
|
|
1808
|
+
FROM pg_proc p
|
|
1809
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1810
|
+
WHERE n.nspname = 'public'
|
|
1811
|
+
AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
|
|
1812
|
+
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1813
|
+
`);
|
|
1814
|
+
const aggregateFunctions = await knex2.raw(`
|
|
1815
|
+
SELECT
|
|
1816
|
+
p.proname as name,
|
|
1817
|
+
pg_get_function_identity_arguments(p.oid) as arguments,
|
|
1818
|
+
a.aggtransfn::regproc::text as trans_func,
|
|
1819
|
+
a.aggfinalfn::regproc::text as final_func,
|
|
1820
|
+
a.agginitval as init_val,
|
|
1821
|
+
pg_catalog.format_type(a.aggtranstype, NULL) as state_type,
|
|
1822
|
+
true as is_aggregate
|
|
1823
|
+
FROM pg_proc p
|
|
1824
|
+
JOIN pg_aggregate a ON p.oid = a.aggfnoid
|
|
1825
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
1826
|
+
WHERE n.nspname = 'public'
|
|
1827
|
+
ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
|
|
1828
|
+
`);
|
|
1829
|
+
const result = [];
|
|
1830
|
+
for (const row of regularFunctions.rows || []) {
|
|
1831
|
+
const definition = row.definition || "";
|
|
1832
|
+
const name2 = row.name || "";
|
|
1833
|
+
const argumentsStr = row.arguments || "";
|
|
1834
|
+
if (!definition) {
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
const signature = `${name2}(${argumentsStr})`;
|
|
1838
|
+
const body = normalizeFunctionBody(extractFunctionBody(definition));
|
|
1839
|
+
result.push({
|
|
1840
|
+
name: name2,
|
|
1841
|
+
signature,
|
|
1842
|
+
body,
|
|
1843
|
+
isAggregate: false,
|
|
1844
|
+
definition
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
for (const row of aggregateFunctions.rows || []) {
|
|
1848
|
+
const name2 = row.name || "";
|
|
1849
|
+
const argumentsStr = row.arguments || "";
|
|
1850
|
+
const transFunc = row.trans_func || "";
|
|
1851
|
+
const finalFunc = row.final_func || "";
|
|
1852
|
+
const initVal = row.init_val;
|
|
1853
|
+
const stateType = row.state_type || "";
|
|
1854
|
+
const signature = `${name2}(${argumentsStr})`;
|
|
1855
|
+
let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (`;
|
|
1856
|
+
aggregateDef += `SFUNC = ${transFunc}, STYPE = ${stateType}`;
|
|
1857
|
+
if (finalFunc) {
|
|
1858
|
+
aggregateDef += `, FINALFUNC = ${finalFunc}`;
|
|
1859
|
+
}
|
|
1860
|
+
if (initVal !== null && initVal !== void 0) {
|
|
1861
|
+
const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
|
|
1862
|
+
aggregateDef += `, INITCOND = ${initValStr}`;
|
|
1863
|
+
}
|
|
1864
|
+
aggregateDef += ");";
|
|
1865
|
+
result.push({
|
|
1866
|
+
name: name2,
|
|
1867
|
+
signature,
|
|
1868
|
+
body: normalizeFunctionBody(aggregateDef),
|
|
1869
|
+
isAggregate: true,
|
|
1870
|
+
definition: aggregateDef
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
return result;
|
|
1874
|
+
};
|
|
1875
|
+
var compareFunctions = (defined, db) => {
|
|
1876
|
+
const definedBody = normalizeFunctionBody(defined.body);
|
|
1877
|
+
const dbBody = normalizeFunctionBody(db.body);
|
|
1878
|
+
if (definedBody !== dbBody) {
|
|
1879
|
+
const definedPreview = definedBody.length > 200 ? `${definedBody.substring(0, 200)}...` : definedBody;
|
|
1880
|
+
const dbPreview = dbBody.length > 200 ? `${dbBody.substring(0, 200)}...` : dbBody;
|
|
1881
|
+
return {
|
|
1882
|
+
changed: true,
|
|
1883
|
+
diff: `Definition changed:
|
|
1884
|
+
File: ${definedPreview}
|
|
1885
|
+
DB: ${dbPreview}`
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
return { changed: false };
|
|
1889
|
+
};
|
|
1890
|
+
var updateFunctions = async (knex2, parsedFunctions) => {
|
|
1891
|
+
if (parsedFunctions.length === 0) {
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
const definedFunctions = parsedFunctions;
|
|
1895
|
+
const dbFunctions = await getDatabaseFunctions(knex2);
|
|
1896
|
+
const dbFunctionsBySignature = /* @__PURE__ */ new Map();
|
|
1897
|
+
for (const func of dbFunctions) {
|
|
1898
|
+
dbFunctionsBySignature.set(func.signature, func);
|
|
1899
|
+
}
|
|
1900
|
+
console.info(`Found ${definedFunctions.length} function(s) in file, ${dbFunctions.length} function(s) in database.`);
|
|
1901
|
+
let updatedCount = 0;
|
|
1902
|
+
let skippedCount = 0;
|
|
1903
|
+
for (const definedFunc of definedFunctions) {
|
|
1904
|
+
const dbFunc = dbFunctionsBySignature.get(definedFunc.signature);
|
|
1905
|
+
if (!dbFunc) {
|
|
1906
|
+
try {
|
|
1907
|
+
await knex2.raw(definedFunc.fullDefinition);
|
|
1908
|
+
console.info(`\u2713 Created ${definedFunc.isAggregate ? "aggregate" : "function"}: ${definedFunc.signature}`);
|
|
1909
|
+
updatedCount++;
|
|
1910
|
+
} catch (error) {
|
|
1911
|
+
console.error(
|
|
1912
|
+
`\u2717 Failed to create ${definedFunc.isAggregate ? "aggregate" : "function"} ${definedFunc.signature}:`,
|
|
1913
|
+
error.message
|
|
1914
|
+
);
|
|
1915
|
+
throw error;
|
|
1916
|
+
}
|
|
1917
|
+
} else {
|
|
1918
|
+
const comparison = compareFunctions(definedFunc, dbFunc);
|
|
1919
|
+
if (comparison.changed) {
|
|
1920
|
+
console.info(`
|
|
1921
|
+
\u26A0 ${definedFunc.isAggregate ? "Aggregate" : "Function"} ${definedFunc.signature} has changes:`);
|
|
1922
|
+
if (comparison.diff) {
|
|
1923
|
+
console.info(comparison.diff);
|
|
1924
|
+
}
|
|
1925
|
+
try {
|
|
1926
|
+
if (definedFunc.isAggregate) {
|
|
1927
|
+
const dropMatch = definedFunc.fullDefinition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+([^(]+)\(/i);
|
|
1928
|
+
if (dropMatch) {
|
|
1929
|
+
const functionName = dropMatch[2].trim();
|
|
1930
|
+
const argsMatch = definedFunc.fullDefinition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+[^(]+\(([^)]*)\)/i);
|
|
1931
|
+
const args2 = argsMatch ? argsMatch[2].trim() : "";
|
|
1932
|
+
await knex2.raw(`DROP AGGREGATE IF EXISTS ${functionName}${args2 ? `(${args2})` : ""}`);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
await knex2.raw(definedFunc.fullDefinition);
|
|
1936
|
+
console.info(`\u2713 Updated ${definedFunc.isAggregate ? "aggregate" : "function"}: ${definedFunc.signature}
|
|
1937
|
+
`);
|
|
1938
|
+
updatedCount++;
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
console.error(
|
|
1941
|
+
`\u2717 Failed to update ${definedFunc.isAggregate ? "aggregate" : "function"} ${definedFunc.signature}:`,
|
|
1942
|
+
error.message
|
|
1943
|
+
);
|
|
1944
|
+
throw error;
|
|
1945
|
+
}
|
|
1946
|
+
} else {
|
|
1947
|
+
console.info(
|
|
1948
|
+
`\u25CB Skipped ${definedFunc.isAggregate ? "aggregate" : "function"} (unchanged): ${definedFunc.signature}`
|
|
1949
|
+
);
|
|
1950
|
+
skippedCount++;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
console.info(`
|
|
1955
|
+
Summary: ${updatedCount} updated, ${skippedCount} skipped`);
|
|
1956
|
+
if (updatedCount > 0) {
|
|
1957
|
+
console.info("Functions updated successfully.");
|
|
1958
|
+
} else {
|
|
1959
|
+
console.info("All functions are up to date.");
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1497
1962
|
|
|
1498
1963
|
// src/permissions/generate.ts
|
|
1499
1964
|
var ACTIONS = ["READ", "CREATE", "UPDATE", "DELETE", "RESTORE", "LINK"];
|
|
1500
1965
|
|
|
1501
|
-
// src/resolvers/arguments.ts
|
|
1502
|
-
var import_graphql4 = require("graphql");
|
|
1503
|
-
|
|
1504
|
-
// src/resolvers/resolver.ts
|
|
1505
|
-
var import_cloneDeep2 = __toESM(require("lodash/cloneDeep"), 1);
|
|
1506
|
-
var import_flatMap = __toESM(require("lodash/flatMap"), 1);
|
|
1507
|
-
|
|
1508
1966
|
// src/schema/generate.ts
|
|
1509
1967
|
var import_graphql6 = require("graphql");
|
|
1510
1968
|
|
|
@@ -2082,6 +2540,14 @@ var DEFAULTS = {
|
|
|
2082
2540
|
ensureFileExists(path, EMPTY_MODELS);
|
|
2083
2541
|
}
|
|
2084
2542
|
},
|
|
2543
|
+
functionsPath: {
|
|
2544
|
+
question: "What is the PostgreSQL functions file path?",
|
|
2545
|
+
defaultValue: "src/config/functions.ts",
|
|
2546
|
+
init: (path) => {
|
|
2547
|
+
ensureFileExists(path, `export const functions: string[] = [];
|
|
2548
|
+
`);
|
|
2549
|
+
}
|
|
2550
|
+
},
|
|
2085
2551
|
generatedFolderPath: {
|
|
2086
2552
|
question: "What is the path for generated stuff?",
|
|
2087
2553
|
defaultValue: "src/generated",
|
|
@@ -2261,7 +2727,8 @@ var generateGraphqlClientTypes = async () => {
|
|
|
2261
2727
|
});
|
|
2262
2728
|
};
|
|
2263
2729
|
|
|
2264
|
-
// src/bin/gqm/parse-
|
|
2730
|
+
// src/bin/gqm/parse-functions.ts
|
|
2731
|
+
var import_fs2 = require("fs");
|
|
2265
2732
|
var import_ts_morph4 = require("ts-morph");
|
|
2266
2733
|
|
|
2267
2734
|
// src/bin/gqm/static-eval.ts
|
|
@@ -2646,13 +3113,116 @@ var findDeclaration = (syntaxList, name2) => {
|
|
|
2646
3113
|
}
|
|
2647
3114
|
};
|
|
2648
3115
|
|
|
2649
|
-
// src/bin/gqm/parse-
|
|
2650
|
-
var
|
|
3116
|
+
// src/bin/gqm/parse-functions.ts
|
|
3117
|
+
var normalizeWhitespace2 = (str) => {
|
|
3118
|
+
return str.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").replace(/\s*;\s*/g, ";").trim();
|
|
3119
|
+
};
|
|
3120
|
+
var normalizeFunctionBody2 = (body) => {
|
|
3121
|
+
return normalizeWhitespace2(body);
|
|
3122
|
+
};
|
|
3123
|
+
var normalizeAggregateDefinition = (definition) => {
|
|
3124
|
+
let normalized = normalizeWhitespace2(definition);
|
|
3125
|
+
const initCondMatch = normalized.match(/INITCOND\s*=\s*([^,)]+)/i);
|
|
3126
|
+
if (initCondMatch) {
|
|
3127
|
+
const initCondValue = initCondMatch[1].trim();
|
|
3128
|
+
const unquoted = initCondValue.replace(/^['"]|['"]$/g, "");
|
|
3129
|
+
if (/^\d+$/.test(unquoted)) {
|
|
3130
|
+
normalized = normalized.replace(/INITCOND\s*=\s*[^,)]+/i, `INITCOND = '${unquoted}'`);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return normalized;
|
|
3134
|
+
};
|
|
3135
|
+
var extractFunctionSignature = (definition, isAggregate) => {
|
|
3136
|
+
if (isAggregate) {
|
|
3137
|
+
const createMatch2 = definition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+([^(]+)\(/i);
|
|
3138
|
+
if (!createMatch2) {
|
|
3139
|
+
return null;
|
|
3140
|
+
}
|
|
3141
|
+
const functionNamePart2 = createMatch2[2].trim().replace(/^[^.]+\./, "");
|
|
3142
|
+
const argsMatch = definition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+[^(]+\(([^)]*)\)/i);
|
|
3143
|
+
const args3 = argsMatch ? argsMatch[2].trim() : "";
|
|
3144
|
+
return `${functionNamePart2}(${args3})`;
|
|
3145
|
+
}
|
|
3146
|
+
const createMatch = definition.match(/CREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+([^(]+)\(/i);
|
|
3147
|
+
if (!createMatch) {
|
|
3148
|
+
return null;
|
|
3149
|
+
}
|
|
3150
|
+
const functionNamePart = createMatch[2].trim().replace(/^[^.]+\./, "");
|
|
3151
|
+
const fullArgsMatch = definition.match(
|
|
3152
|
+
/CREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+[^(]+\(([\s\S]*?)\)\s*(RETURNS|LANGUAGE|AS|STRICT|IMMUTABLE|STABLE|VOLATILE|SECURITY)/i
|
|
3153
|
+
);
|
|
3154
|
+
if (!fullArgsMatch) {
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
const argsSection = fullArgsMatch[2].trim();
|
|
3158
|
+
const args2 = argsSection.split(/\s*,\s*/).map((arg) => {
|
|
3159
|
+
return arg.trim().replace(/\s+/g, " ");
|
|
3160
|
+
}).join(", ");
|
|
3161
|
+
return `${functionNamePart}(${args2})`;
|
|
3162
|
+
};
|
|
3163
|
+
var extractFunctionBody2 = (definition) => {
|
|
3164
|
+
const dollarQuoteMatch = definition.match(/AS\s+\$([^$]*)\$([\s\S]*?)\$\1\$/i);
|
|
3165
|
+
if (dollarQuoteMatch) {
|
|
3166
|
+
return dollarQuoteMatch[2].trim();
|
|
3167
|
+
}
|
|
3168
|
+
const bodyMatch = definition.match(/AS\s+\$\$([\s\S]*?)\$\$/i) || definition.match(/AS\s+['"]([\s\S]*?)['"]/i);
|
|
3169
|
+
if (bodyMatch) {
|
|
3170
|
+
return bodyMatch[1].trim();
|
|
3171
|
+
}
|
|
3172
|
+
return definition;
|
|
3173
|
+
};
|
|
3174
|
+
var parseFunctionsFile = (filePath) => {
|
|
3175
|
+
if (!(0, import_fs2.existsSync)(filePath)) {
|
|
3176
|
+
return [];
|
|
3177
|
+
}
|
|
2651
3178
|
const project = new import_ts_morph4.Project({
|
|
2652
3179
|
manipulationSettings: {
|
|
2653
3180
|
indentationText: import_ts_morph4.IndentationText.TwoSpaces
|
|
2654
3181
|
}
|
|
2655
3182
|
});
|
|
3183
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
3184
|
+
try {
|
|
3185
|
+
const functionsDeclaration = findDeclarationInFile(sourceFile, "functions");
|
|
3186
|
+
const functionsArray = staticEval(functionsDeclaration, {});
|
|
3187
|
+
if (!Array.isArray(functionsArray)) {
|
|
3188
|
+
return [];
|
|
3189
|
+
}
|
|
3190
|
+
const parsedFunctions = [];
|
|
3191
|
+
for (const definition of functionsArray) {
|
|
3192
|
+
if (!definition || typeof definition !== "string") {
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
const trimmedDefinition = definition.trim();
|
|
3196
|
+
const isAggregate = /CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE/i.test(trimmedDefinition);
|
|
3197
|
+
const signature = extractFunctionSignature(trimmedDefinition, isAggregate);
|
|
3198
|
+
if (!signature) {
|
|
3199
|
+
continue;
|
|
3200
|
+
}
|
|
3201
|
+
const nameMatch = signature.match(/^([^(]+)\(/);
|
|
3202
|
+
const name2 = nameMatch ? nameMatch[1].trim().split(".").pop() || "" : "";
|
|
3203
|
+
const body = isAggregate ? trimmedDefinition : extractFunctionBody2(trimmedDefinition);
|
|
3204
|
+
parsedFunctions.push({
|
|
3205
|
+
name: name2,
|
|
3206
|
+
signature,
|
|
3207
|
+
body: isAggregate ? normalizeAggregateDefinition(body) : normalizeFunctionBody2(body),
|
|
3208
|
+
fullDefinition: trimmedDefinition,
|
|
3209
|
+
isAggregate
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
return parsedFunctions;
|
|
3213
|
+
} catch (error) {
|
|
3214
|
+
return [];
|
|
3215
|
+
}
|
|
3216
|
+
};
|
|
3217
|
+
|
|
3218
|
+
// src/bin/gqm/parse-knexfile.ts
|
|
3219
|
+
var import_ts_morph5 = require("ts-morph");
|
|
3220
|
+
var parseKnexfile = async () => {
|
|
3221
|
+
const project = new import_ts_morph5.Project({
|
|
3222
|
+
manipulationSettings: {
|
|
3223
|
+
indentationText: import_ts_morph5.IndentationText.TwoSpaces
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
2656
3226
|
const knexfilePath = await getSetting("knexfilePath");
|
|
2657
3227
|
ensureFileExists(knexfilePath, KNEXFILE);
|
|
2658
3228
|
const sourceFile = project.addSourceFileAtPath(knexfilePath);
|
|
@@ -2662,11 +3232,11 @@ var parseKnexfile = async () => {
|
|
|
2662
3232
|
};
|
|
2663
3233
|
|
|
2664
3234
|
// src/bin/gqm/parse-models.ts
|
|
2665
|
-
var
|
|
3235
|
+
var import_ts_morph6 = require("ts-morph");
|
|
2666
3236
|
var parseModels = async () => {
|
|
2667
|
-
const project = new
|
|
3237
|
+
const project = new import_ts_morph6.Project({
|
|
2668
3238
|
manipulationSettings: {
|
|
2669
|
-
indentationText:
|
|
3239
|
+
indentationText: import_ts_morph6.IndentationText.TwoSpaces
|
|
2670
3240
|
}
|
|
2671
3241
|
});
|
|
2672
3242
|
const modelsPath = await getSetting("modelsPath");
|
|
@@ -2679,11 +3249,11 @@ var parseModels = async () => {
|
|
|
2679
3249
|
};
|
|
2680
3250
|
|
|
2681
3251
|
// src/bin/gqm/permissions.ts
|
|
2682
|
-
var
|
|
3252
|
+
var import_ts_morph7 = require("ts-morph");
|
|
2683
3253
|
var generatePermissionTypes = (models) => {
|
|
2684
|
-
const project = new
|
|
3254
|
+
const project = new import_ts_morph7.Project({
|
|
2685
3255
|
manipulationSettings: {
|
|
2686
|
-
indentationText:
|
|
3256
|
+
indentationText: import_ts_morph7.IndentationText.TwoSpaces
|
|
2687
3257
|
}
|
|
2688
3258
|
});
|
|
2689
3259
|
const sourceFile = project.createSourceFile("permissions.ts", "", {
|
|
@@ -2786,7 +3356,9 @@ import_commander.program.command("generate-migration [<name>] [<date>]").descrip
|
|
|
2786
3356
|
const db = (0, import_knex.default)(knexfile);
|
|
2787
3357
|
try {
|
|
2788
3358
|
const models = await parseModels();
|
|
2789
|
-
const
|
|
3359
|
+
const functionsPath = await getSetting("functionsPath");
|
|
3360
|
+
const parsedFunctions = parseFunctionsFile(functionsPath);
|
|
3361
|
+
const migrations = await new MigrationGenerator(db, models, parsedFunctions).generate();
|
|
2790
3362
|
writeToFile(`migrations/${date || getMigrationDate()}_${name2}.ts`, migrations);
|
|
2791
3363
|
} finally {
|
|
2792
3364
|
await db.destroy();
|
|
@@ -2797,7 +3369,9 @@ import_commander.program.command("check-needs-migration").description("Check if
|
|
|
2797
3369
|
const db = (0, import_knex.default)(knexfile);
|
|
2798
3370
|
try {
|
|
2799
3371
|
const models = await parseModels();
|
|
2800
|
-
const
|
|
3372
|
+
const functionsPath = await getSetting("functionsPath");
|
|
3373
|
+
const parsedFunctions = parseFunctionsFile(functionsPath);
|
|
3374
|
+
const mg = new MigrationGenerator(db, models, parsedFunctions);
|
|
2801
3375
|
await mg.generate();
|
|
2802
3376
|
if (mg.needsMigration) {
|
|
2803
3377
|
console.error("Migration is needed.");
|
|
@@ -2807,8 +3381,31 @@ import_commander.program.command("check-needs-migration").description("Check if
|
|
|
2807
3381
|
await db.destroy();
|
|
2808
3382
|
}
|
|
2809
3383
|
});
|
|
2810
|
-
import_commander.program.command("
|
|
2811
|
-
|
|
3384
|
+
import_commander.program.command("generate-functions").description("Generate functions.ts file from database").action(async () => {
|
|
3385
|
+
const knexfile = await parseKnexfile();
|
|
3386
|
+
const db = (0, import_knex.default)(knexfile);
|
|
3387
|
+
try {
|
|
3388
|
+
const functionsPath = await getSetting("functionsPath");
|
|
3389
|
+
const functions = await generateFunctionsFromDatabase(db);
|
|
3390
|
+
writeToFile(functionsPath, functions);
|
|
3391
|
+
} finally {
|
|
3392
|
+
await db.destroy();
|
|
3393
|
+
}
|
|
3394
|
+
});
|
|
3395
|
+
import_commander.program.command("update-functions").description("Update database functions from functions.ts file").action(async () => {
|
|
3396
|
+
const knexfile = await parseKnexfile();
|
|
3397
|
+
const db = (0, import_knex.default)(knexfile);
|
|
3398
|
+
try {
|
|
3399
|
+
const functionsPath = await getSetting("functionsPath");
|
|
3400
|
+
const parsedFunctions = parseFunctionsFile(functionsPath);
|
|
3401
|
+
await updateFunctions(db, parsedFunctions);
|
|
3402
|
+
} finally {
|
|
3403
|
+
await db.destroy();
|
|
3404
|
+
}
|
|
3405
|
+
});
|
|
3406
|
+
import_commander.program.command("*").description("Invalid command").action((command) => {
|
|
3407
|
+
console.error(`Invalid command: ${command}
|
|
3408
|
+
See --help for a list of available commands.`);
|
|
2812
3409
|
process.exit(1);
|
|
2813
3410
|
});
|
|
2814
3411
|
import_commander.program.parse(process.argv);
|