rake-db 2.27.33 → 2.28.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/dist/index.js CHANGED
@@ -17,6 +17,177 @@ const clearChanges = () => {
17
17
  const getCurrentChanges = () => currentChanges;
18
18
  const pushChange = (change) => currentChanges.push(change);
19
19
 
20
+ const RAKE_DB_LOCK_KEY = "8582141715823621641";
21
+ const getFirstWordAndRest = (input) => {
22
+ const i = input.search(/(?=[A-Z])|[-_ ]/);
23
+ if (i !== -1) {
24
+ const restStart = input[i] === "-" || input[i] === "_" || input[i] === " " ? i + 1 : i;
25
+ const rest = input.slice(restStart);
26
+ return [input.slice(0, i), rest[0].toLowerCase() + rest.slice(1)];
27
+ } else {
28
+ return [input];
29
+ }
30
+ };
31
+ const getTextAfterRegExp = (input, regex, length) => {
32
+ let i = input.search(regex);
33
+ if (i === -1) return;
34
+ if (input[i] === "-" || input[i] === "_" || input[i] === " ") i++;
35
+ i += length;
36
+ const start = input[i] == "-" || input[i] === "_" || input[i] === " " ? i + 1 : i;
37
+ const text = input.slice(start);
38
+ return text[0].toLowerCase() + text.slice(1);
39
+ };
40
+ const getTextAfterTo = (input) => {
41
+ return getTextAfterRegExp(input, /(To|-to|_to| to)[A-Z-_ ]/, 2);
42
+ };
43
+ const getTextAfterFrom = (input) => {
44
+ return getTextAfterRegExp(input, /(From|-from|_from| from)[A-Z-_ ]/, 4);
45
+ };
46
+ const joinColumns = (columns) => {
47
+ return columns.map((column) => `"${column}"`).join(", ");
48
+ };
49
+ const quoteWithSchema = ({
50
+ schema,
51
+ name
52
+ }) => quoteTable(schema, name);
53
+ const quoteTable = (schema, table) => schema ? `"${schema}"."${table}"` : `"${table}"`;
54
+ const getSchemaAndTableFromName = (name) => {
55
+ const i = name.indexOf(".");
56
+ return i !== -1 ? [name.slice(0, i), name.slice(i + 1)] : [void 0, name];
57
+ };
58
+ const quoteNameFromString = (string) => {
59
+ return quoteTable(...getSchemaAndTableFromName(string));
60
+ };
61
+ const quoteCustomType = (s) => {
62
+ const [schema, type] = getSchemaAndTableFromName(s);
63
+ return schema ? '"' + schema + '".' + type : type;
64
+ };
65
+ const quoteSchemaTable = (arg, excludeCurrentSchema) => {
66
+ return pqb.singleQuote(concatSchemaAndName(arg, excludeCurrentSchema));
67
+ };
68
+ const concatSchemaAndName = ({
69
+ schema,
70
+ name
71
+ }, excludeCurrentSchema) => {
72
+ return schema && schema !== excludeCurrentSchema ? `${schema}.${name}` : name;
73
+ };
74
+ const makePopulateEnumQuery = (item) => {
75
+ const [schema, name] = getSchemaAndTableFromName(item.enumName);
76
+ return {
77
+ text: `SELECT unnest(enum_range(NULL::${quoteTable(schema, name)}))::text`,
78
+ then(result) {
79
+ item.options.push(...result.rows.map(([value]) => value));
80
+ }
81
+ };
82
+ };
83
+ const transaction = (adapter, fn) => {
84
+ return adapter.transaction(void 0, fn);
85
+ };
86
+ const queryLock = (trx) => trx.query(`SELECT pg_advisory_xact_lock('${RAKE_DB_LOCK_KEY}')`);
87
+ const getCliParam = (args, name) => {
88
+ if (args) {
89
+ const key = "--" + name;
90
+ for (let i = 0; i < args.length; i += 1) {
91
+ const arg = args[i];
92
+ if (arg === key) return args[i + 1];
93
+ else if (arg.startsWith(key)) return arg.slice(key.length + 1);
94
+ }
95
+ }
96
+ return;
97
+ };
98
+
99
+ const migrationConfigDefaults = {
100
+ schemaConfig: pqb.defaultSchemaConfig,
101
+ migrationsPath: path.join("src", "db", "migrations"),
102
+ migrationId: { serial: 4 },
103
+ migrationsTable: "schemaMigrations",
104
+ snakeCase: false,
105
+ commands: {},
106
+ log: true,
107
+ logger: console,
108
+ import() {
109
+ throw new Error(
110
+ "Add `import: (path) => import(path),` setting to `rakeDb` config"
111
+ );
112
+ }
113
+ };
114
+ const ensureMigrationsPath = (config) => {
115
+ if (!config.migrationsPath) {
116
+ config.migrationsPath = migrationConfigDefaults.migrationsPath;
117
+ }
118
+ if (!path.isAbsolute(config.migrationsPath)) {
119
+ config.migrationsPath = path.resolve(
120
+ config.basePath,
121
+ config.migrationsPath
122
+ );
123
+ }
124
+ return config;
125
+ };
126
+ const ensureBasePathAndDbScript = (config, intermediateCallers = 0) => {
127
+ if (config.basePath && config.dbScript) return config;
128
+ let filePath = pqb.getStackTrace()?.[3 + intermediateCallers]?.getFileName();
129
+ if (!filePath) {
130
+ throw new Error(
131
+ "Failed to determine path to db script. Please set basePath option of rakeDb"
132
+ );
133
+ }
134
+ if (filePath.startsWith("file://")) {
135
+ filePath = node_url.fileURLToPath(filePath);
136
+ }
137
+ const ext = path.extname(filePath);
138
+ if (ext !== ".ts" && ext !== ".js" && ext !== ".mjs") {
139
+ throw new Error(
140
+ `Add a .ts suffix to the "${path.basename(filePath)}" when calling it`
141
+ );
142
+ }
143
+ config.basePath = path.dirname(filePath);
144
+ config.dbScript = path.basename(filePath);
145
+ return config;
146
+ };
147
+ const processRakeDbConfig = (config, args) => {
148
+ const result = { ...migrationConfigDefaults, ...config };
149
+ if (!result.log) {
150
+ delete result.logger;
151
+ }
152
+ ensureBasePathAndDbScript(result, 1);
153
+ ensureMigrationsPath(result);
154
+ if (!result.recurrentPath) {
155
+ result.recurrentPath = path.join(
156
+ result.migrationsPath,
157
+ "recurrent"
158
+ );
159
+ }
160
+ if ("recurrentPath" in result && !path.isAbsolute(result.recurrentPath)) {
161
+ result.recurrentPath = path.resolve(result.basePath, result.recurrentPath);
162
+ }
163
+ if ("baseTable" in config && config.baseTable) {
164
+ const { types, snakeCase, language } = config.baseTable.prototype;
165
+ result.columnTypes = types || pqb.makeColumnTypes(pqb.defaultSchemaConfig);
166
+ if (snakeCase) result.snakeCase = true;
167
+ if (language) result.language = language;
168
+ } else {
169
+ const ct = "columnTypes" in config && config.columnTypes;
170
+ result.columnTypes = (typeof ct === "function" ? ct(
171
+ pqb.makeColumnTypes(pqb.defaultSchemaConfig)
172
+ ) : ct) || pqb.makeColumnTypes(pqb.defaultSchemaConfig);
173
+ }
174
+ if (config.migrationId === "serial") {
175
+ result.migrationId = { serial: 4 };
176
+ }
177
+ const transaction = getCliParam(args, "transaction");
178
+ if (transaction) {
179
+ if (transaction !== "single" && transaction !== "per-migration") {
180
+ throw new Error(
181
+ `Unsupported transaction param ${transaction}, expected single or per-migration`
182
+ );
183
+ }
184
+ result.transaction = transaction;
185
+ } else if (!result.transaction) {
186
+ result.transaction = "single";
187
+ }
188
+ return result;
189
+ };
190
+
20
191
  const versionToString = (config, version) => config.migrationId === "timestamp" ? `${version}` : `${version}`.padStart(config.migrationId.serial, "0");
21
192
  const columnTypeToSql = (item) => {
22
193
  return item.data.isOfCustomType ? item instanceof pqb.DomainColumn ? quoteNameFromString(item.dataType) : quoteCustomType(item.toSQL()) : item.toSQL();
@@ -388,6 +559,17 @@ const cmpRawSql = (a, b) => {
388
559
  const bValues = JSON.stringify(values);
389
560
  return aSql === bSql && aValues === bValues;
390
561
  };
562
+ const getMigrationsSchemaAndTable = (config) => {
563
+ const [tableSchema, table] = getSchemaAndTableFromName(
564
+ config.migrationsTable
565
+ );
566
+ const schema = tableSchema || (config.schema && config.schema !== "public" ? config.schema : void 0);
567
+ return { schema, table };
568
+ };
569
+ const migrationsSchemaTableSql = (config) => {
570
+ const { schema, table } = getMigrationsSchemaAndTable(config);
571
+ return `${schema ? `"${schema}".` : ""}"${table}"`;
572
+ };
391
573
 
392
574
  const tableMethods = {
393
575
  enum(name) {
@@ -1250,13 +1432,15 @@ const createView = async (migration, up, name, options, sql) => {
1250
1432
  const query = astToQuery(ast);
1251
1433
  await migration.adapter.arrays(interpolateSqlValues(query));
1252
1434
  };
1253
- const makeAst = (up, name, options, sql) => {
1435
+ const makeAst = (up, fullName, options, sql) => {
1254
1436
  if (typeof sql === "string") {
1255
1437
  sql = pqb.raw({ raw: sql });
1256
1438
  }
1439
+ const [schema, name] = getSchemaAndTableFromName(fullName);
1257
1440
  return {
1258
1441
  type: "view",
1259
1442
  action: up ? "create" : "drop",
1443
+ schema,
1260
1444
  name,
1261
1445
  shape: {},
1262
1446
  sql,
@@ -1268,12 +1452,13 @@ const astToQuery = (ast) => {
1268
1452
  const values = [];
1269
1453
  const sql = [];
1270
1454
  const { options } = ast;
1455
+ const sqlName = `${ast.schema ? `"${ast.schema}".` : ""}"${ast.name}"`;
1271
1456
  if (ast.action === "create") {
1272
1457
  sql.push("CREATE");
1273
1458
  if (options?.createOrReplace) sql.push("OR REPLACE");
1274
1459
  if (options?.temporary) sql.push("TEMPORARY");
1275
1460
  if (options?.recursive) sql.push("RECURSIVE");
1276
- sql.push(`VIEW "${ast.name}"`);
1461
+ sql.push(`VIEW ${sqlName}`);
1277
1462
  if (options?.columns) {
1278
1463
  sql.push(
1279
1464
  `(${options.columns.map((column) => `"${column}"`).join(", ")})`
@@ -1291,7 +1476,7 @@ const astToQuery = (ast) => {
1291
1476
  } else {
1292
1477
  sql.push("DROP VIEW");
1293
1478
  if (options?.dropIfExists) sql.push(`IF EXISTS`);
1294
- sql.push(`"${ast.name}"`);
1479
+ sql.push(sqlName);
1295
1480
  if (options?.dropMode) sql.push(options.dropMode);
1296
1481
  }
1297
1482
  return {
@@ -1302,7 +1487,6 @@ const astToQuery = (ast) => {
1302
1487
 
1303
1488
  const createMigrationInterface = (tx, up, config) => {
1304
1489
  const adapter = Object.create(tx);
1305
- adapter.schema = adapter.getSchema() ?? "public";
1306
1490
  const { query, arrays } = adapter;
1307
1491
  const log = pqb.logParamToLogObject(config.logger || console, config.log);
1308
1492
  adapter.query = (text, values) => {
@@ -2336,7 +2520,7 @@ const renameType = async (migration, from, to, kind) => {
2336
2520
  }
2337
2521
  if (ast.fromSchema !== ast.toSchema) {
2338
2522
  await migration.adapter.query(
2339
- `ALTER ${ast.kind} ${quoteTable(ast.fromSchema, ast.to)} SET SCHEMA "${ast.toSchema ?? migration.adapter.schema}"`
2523
+ `ALTER ${ast.kind} ${quoteTable(ast.fromSchema, ast.to)} SET SCHEMA "${ast.toSchema ?? migration.options.schema ?? "public"}"`
2340
2524
  );
2341
2525
  }
2342
2526
  };
@@ -2410,7 +2594,7 @@ const changeEnumValues = async (migration, enumName, fromValues, toValues) => {
2410
2594
  );
2411
2595
  };
2412
2596
  const recreateEnum = async (migration, { schema, name }, values, errorMessage) => {
2413
- const defaultSchema = migration.adapter.schema;
2597
+ const defaultSchema = migration.options.schema ?? "public";
2414
2598
  const quotedName = quoteTable(schema, name);
2415
2599
  const relKinds = ["r", "m"];
2416
2600
  const { rows: tables } = await migration.adapter.query(
@@ -2641,7 +2825,9 @@ ${arg === "timestamp" || digits !== 4 ? `Set \`migrationId\`: ${arg === "timesta
2641
2825
  };
2642
2826
  const renameMigrationVersionsInDb = async (config, adapter, values) => {
2643
2827
  await adapter.arrays(
2644
- `UPDATE "${config.migrationsTable}" AS t SET version = v.version FROM (VALUES ${values.map(
2828
+ `UPDATE ${migrationsSchemaTableSql(
2829
+ config
2830
+ )} AS t SET version = v.version FROM (VALUES ${values.map(
2645
2831
  ([oldVersion, , newVersion], i) => `('${oldVersion}', $${i + 1}, '${newVersion}')`
2646
2832
  ).join(
2647
2833
  ", "
@@ -2806,13 +2992,44 @@ function getDigitsPrefix(name) {
2806
2992
 
2807
2993
  const saveMigratedVersion = async (db, version, name, config) => {
2808
2994
  await db.silentArrays(
2809
- `INSERT INTO "${config.migrationsTable}"(version, name) VALUES ($1, $2)`,
2995
+ `INSERT INTO ${migrationsSchemaTableSql(
2996
+ config
2997
+ )}(version, name) VALUES ($1, $2)`,
2810
2998
  [version, name]
2811
2999
  );
2812
3000
  };
3001
+ const createMigrationsTable = async (db, config) => {
3002
+ const { schema } = getMigrationsSchemaAndTable(config);
3003
+ if (schema) {
3004
+ try {
3005
+ await db.query(`CREATE SCHEMA "${schema}"`);
3006
+ config.logger?.log(`Created schema ${schema}`);
3007
+ } catch (err) {
3008
+ if (err.code !== "42P06") {
3009
+ throw err;
3010
+ }
3011
+ }
3012
+ }
3013
+ try {
3014
+ await db.query(
3015
+ `CREATE TABLE ${migrationsSchemaTableSql(
3016
+ config
3017
+ )} ( version TEXT NOT NULL, name TEXT NOT NULL )`
3018
+ );
3019
+ config.logger?.log("Created versions table");
3020
+ } catch (err) {
3021
+ if (err.code === "42P07") {
3022
+ config.logger?.log("Versions table exists");
3023
+ } else {
3024
+ throw err;
3025
+ }
3026
+ }
3027
+ };
2813
3028
  const deleteMigratedVersion = async (db, version, name, config) => {
2814
3029
  const res = await db.silentArrays(
2815
- `DELETE FROM "${config.migrationsTable}" WHERE version = $1 AND name = $2`,
3030
+ `DELETE FROM ${migrationsSchemaTableSql(
3031
+ config
3032
+ )} WHERE version = $1 AND name = $2`,
2816
3033
  [version, name]
2817
3034
  );
2818
3035
  if (res.rowCount === 0) {
@@ -2823,7 +3040,7 @@ class NoMigrationsTableError extends Error {
2823
3040
  }
2824
3041
  const getMigratedVersionsMap = async (ctx, adapter, config, renameTo) => {
2825
3042
  try {
2826
- const table = `"${config.migrationsTable}"`;
3043
+ const table = migrationsSchemaTableSql(config);
2827
3044
  const result = await adapter.arrays(
2828
3045
  `SELECT * FROM ${table} ORDER BY version`
2829
3046
  );
@@ -2899,33 +3116,6 @@ async function renameMigrations(config, trx, versions, renameTo) {
2899
3116
  return updatedVersions;
2900
3117
  }
2901
3118
 
2902
- const createMigrationsTable = async (db, config) => {
2903
- const { schema } = db;
2904
- if (schema && schema !== "public") {
2905
- try {
2906
- await db.query(`CREATE SCHEMA "${schema}"`);
2907
- config.logger?.log(`Created schema ${schema}`);
2908
- } catch (err) {
2909
- if (err.code !== "42P06") {
2910
- throw err;
2911
- }
2912
- }
2913
- }
2914
- try {
2915
- await db.query(
2916
- `CREATE TABLE "${config.migrationsTable}" ( version TEXT NOT NULL, name TEXT NOT NULL )`
2917
- );
2918
- config.logger?.log("Created versions table");
2919
- } catch (err) {
2920
- if (err.code === "42P07") {
2921
- config.logger?.log("Versions table exists");
2922
- } else {
2923
- throw err;
2924
- }
2925
- }
2926
- };
2927
-
2928
- const RAKE_DB_LOCK_KEY = "8582141715823621641";
2929
3119
  const transactionIfSingle = (params, fn) => {
2930
3120
  return params.config.transaction === "single" ? transaction(params.adapter, fn) : fn(params.adapter);
2931
3121
  };
@@ -3184,199 +3374,29 @@ const changeMigratedVersion = async (adapter, up, file, config) => {
3184
3374
  );
3185
3375
  };
3186
3376
 
3187
- const getFirstWordAndRest = (input) => {
3188
- const i = input.search(/(?=[A-Z])|[-_ ]/);
3189
- if (i !== -1) {
3190
- const restStart = input[i] === "-" || input[i] === "_" || input[i] === " " ? i + 1 : i;
3191
- const rest = input.slice(restStart);
3192
- return [input.slice(0, i), rest[0].toLowerCase() + rest.slice(1)];
3193
- } else {
3194
- return [input];
3377
+ const ESC = "\x1B";
3378
+ const CSI = `${ESC}[`;
3379
+ const cursorShow = `${CSI}?25h`;
3380
+ const cursorHide = `${CSI}?25l`;
3381
+ const { stdin, stdout } = process;
3382
+ const visibleChars = (s) => s.replace(
3383
+ // eslint-disable-next-line no-control-regex
3384
+ /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PRZcf-ntqry=><~]))/g,
3385
+ ""
3386
+ ).length;
3387
+ const clear = (text) => {
3388
+ const rows = text.split(/\r?\n/).reduce(
3389
+ (rows2, line) => rows2 + 1 + Math.floor(Math.max(visibleChars(line) - 1, 0) / stdout.columns),
3390
+ 0
3391
+ );
3392
+ let clear2 = "";
3393
+ for (let i = 0; i < rows; i++) {
3394
+ clear2 += `${CSI}2K`;
3395
+ if (i < rows - 1) {
3396
+ clear2 += `${CSI}${i < rows - 1 ? "1A" : "G"}`;
3397
+ }
3195
3398
  }
3196
- };
3197
- const getTextAfterRegExp = (input, regex, length) => {
3198
- let i = input.search(regex);
3199
- if (i === -1) return;
3200
- if (input[i] === "-" || input[i] === "_" || input[i] === " ") i++;
3201
- i += length;
3202
- const start = input[i] == "-" || input[i] === "_" || input[i] === " " ? i + 1 : i;
3203
- const text = input.slice(start);
3204
- return text[0].toLowerCase() + text.slice(1);
3205
- };
3206
- const getTextAfterTo = (input) => {
3207
- return getTextAfterRegExp(input, /(To|-to|_to| to)[A-Z-_ ]/, 2);
3208
- };
3209
- const getTextAfterFrom = (input) => {
3210
- return getTextAfterRegExp(input, /(From|-from|_from| from)[A-Z-_ ]/, 4);
3211
- };
3212
- const joinColumns = (columns) => {
3213
- return columns.map((column) => `"${column}"`).join(", ");
3214
- };
3215
- const quoteWithSchema = ({
3216
- schema,
3217
- name
3218
- }) => quoteTable(schema, name);
3219
- const quoteTable = (schema, table) => schema ? `"${schema}"."${table}"` : `"${table}"`;
3220
- const getSchemaAndTableFromName = (name) => {
3221
- const i = name.indexOf(".");
3222
- return i !== -1 ? [name.slice(0, i), name.slice(i + 1)] : [void 0, name];
3223
- };
3224
- const quoteNameFromString = (string) => {
3225
- return quoteTable(...getSchemaAndTableFromName(string));
3226
- };
3227
- const quoteCustomType = (s) => {
3228
- const [schema, type] = getSchemaAndTableFromName(s);
3229
- return schema ? '"' + schema + '".' + type : type;
3230
- };
3231
- const quoteSchemaTable = (arg, excludeCurrentSchema) => {
3232
- return pqb.singleQuote(concatSchemaAndName(arg, excludeCurrentSchema));
3233
- };
3234
- const concatSchemaAndName = ({
3235
- schema,
3236
- name
3237
- }, excludeCurrentSchema) => {
3238
- return schema && schema !== excludeCurrentSchema ? `${schema}.${name}` : name;
3239
- };
3240
- const makePopulateEnumQuery = (item) => {
3241
- const [schema, name] = getSchemaAndTableFromName(item.enumName);
3242
- return {
3243
- text: `SELECT unnest(enum_range(NULL::${quoteTable(schema, name)}))::text`,
3244
- then(result) {
3245
- item.options.push(...result.rows.map(([value]) => value));
3246
- }
3247
- };
3248
- };
3249
- const transaction = (adapter, fn) => {
3250
- return adapter.transaction(void 0, fn);
3251
- };
3252
- const queryLock = (trx) => trx.query(`SELECT pg_advisory_xact_lock('${RAKE_DB_LOCK_KEY}')`);
3253
- const getCliParam = (args, name) => {
3254
- if (args) {
3255
- const key = "--" + name;
3256
- for (let i = 0; i < args.length; i += 1) {
3257
- const arg = args[i];
3258
- if (arg === key) return args[i + 1];
3259
- else if (arg.startsWith(key)) return arg.slice(key.length + 1);
3260
- }
3261
- }
3262
- return;
3263
- };
3264
-
3265
- const migrationConfigDefaults = {
3266
- schemaConfig: pqb.defaultSchemaConfig,
3267
- migrationsPath: path.join("src", "db", "migrations"),
3268
- migrationId: { serial: 4 },
3269
- migrationsTable: "schemaMigrations",
3270
- snakeCase: false,
3271
- commands: {},
3272
- log: true,
3273
- logger: console,
3274
- import() {
3275
- throw new Error(
3276
- "Add `import: (path) => import(path),` setting to `rakeDb` config"
3277
- );
3278
- }
3279
- };
3280
- const ensureMigrationsPath = (config) => {
3281
- if (!config.migrationsPath) {
3282
- config.migrationsPath = migrationConfigDefaults.migrationsPath;
3283
- }
3284
- if (!path.isAbsolute(config.migrationsPath)) {
3285
- config.migrationsPath = path.resolve(
3286
- config.basePath,
3287
- config.migrationsPath
3288
- );
3289
- }
3290
- return config;
3291
- };
3292
- const ensureBasePathAndDbScript = (config, intermediateCallers = 0) => {
3293
- if (config.basePath && config.dbScript) return config;
3294
- let filePath = pqb.getStackTrace()?.[3 + intermediateCallers]?.getFileName();
3295
- if (!filePath) {
3296
- throw new Error(
3297
- "Failed to determine path to db script. Please set basePath option of rakeDb"
3298
- );
3299
- }
3300
- if (filePath.startsWith("file://")) {
3301
- filePath = node_url.fileURLToPath(filePath);
3302
- }
3303
- const ext = path.extname(filePath);
3304
- if (ext !== ".ts" && ext !== ".js" && ext !== ".mjs") {
3305
- throw new Error(
3306
- `Add a .ts suffix to the "${path.basename(filePath)}" when calling it`
3307
- );
3308
- }
3309
- config.basePath = path.dirname(filePath);
3310
- config.dbScript = path.basename(filePath);
3311
- return config;
3312
- };
3313
- const processRakeDbConfig = (config, args) => {
3314
- const result = { ...migrationConfigDefaults, ...config };
3315
- if (!result.log) {
3316
- delete result.logger;
3317
- }
3318
- ensureBasePathAndDbScript(result, 1);
3319
- ensureMigrationsPath(result);
3320
- if (!result.recurrentPath) {
3321
- result.recurrentPath = path.join(
3322
- result.migrationsPath,
3323
- "recurrent"
3324
- );
3325
- }
3326
- if ("recurrentPath" in result && !path.isAbsolute(result.recurrentPath)) {
3327
- result.recurrentPath = path.resolve(result.basePath, result.recurrentPath);
3328
- }
3329
- if ("baseTable" in config && config.baseTable) {
3330
- const { types, snakeCase, language } = config.baseTable.prototype;
3331
- result.columnTypes = types || pqb.makeColumnTypes(pqb.defaultSchemaConfig);
3332
- if (snakeCase) result.snakeCase = true;
3333
- if (language) result.language = language;
3334
- } else {
3335
- const ct = "columnTypes" in config && config.columnTypes;
3336
- result.columnTypes = (typeof ct === "function" ? ct(
3337
- pqb.makeColumnTypes(pqb.defaultSchemaConfig)
3338
- ) : ct) || pqb.makeColumnTypes(pqb.defaultSchemaConfig);
3339
- }
3340
- if (config.migrationId === "serial") {
3341
- result.migrationId = { serial: 4 };
3342
- }
3343
- const transaction = getCliParam(args, "transaction");
3344
- if (transaction) {
3345
- if (transaction !== "single" && transaction !== "per-migration") {
3346
- throw new Error(
3347
- `Unsupported transaction param ${transaction}, expected single or per-migration`
3348
- );
3349
- }
3350
- result.transaction = transaction;
3351
- } else if (!result.transaction) {
3352
- result.transaction = "single";
3353
- }
3354
- return result;
3355
- };
3356
-
3357
- const ESC = "\x1B";
3358
- const CSI = `${ESC}[`;
3359
- const cursorShow = `${CSI}?25h`;
3360
- const cursorHide = `${CSI}?25l`;
3361
- const { stdin, stdout } = process;
3362
- const visibleChars = (s) => s.replace(
3363
- // eslint-disable-next-line no-control-regex
3364
- /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PRZcf-ntqry=><~]))/g,
3365
- ""
3366
- ).length;
3367
- const clear = (text) => {
3368
- const rows = text.split(/\r?\n/).reduce(
3369
- (rows2, line) => rows2 + 1 + Math.floor(Math.max(visibleChars(line) - 1, 0) / stdout.columns),
3370
- 0
3371
- );
3372
- let clear2 = "";
3373
- for (let i = 0; i < rows; i++) {
3374
- clear2 += `${CSI}2K`;
3375
- if (i < rows - 1) {
3376
- clear2 += `${CSI}${i < rows - 1 ? "1A" : "G"}`;
3377
- }
3378
- }
3379
- return clear2;
3399
+ return clear2;
3380
3400
  };
3381
3401
  const prompt = async ({
3382
3402
  render,
@@ -3686,962 +3706,6 @@ const readdirRecursive = async (dirPath, cb) => {
3686
3706
  );
3687
3707
  };
3688
3708
 
3689
- const filterSchema = (table) => `${table} !~ '^pg_' AND ${table} != 'information_schema'`;
3690
- const jsonAgg = (sql2, as) => `(SELECT coalesce(json_agg(t.*), '[]') FROM (${sql2}) t) AS "${as}"`;
3691
- const columnsSql = ({
3692
- schema,
3693
- table,
3694
- join = "",
3695
- where
3696
- }) => `SELECT
3697
- ${schema}.nspname "schemaName",
3698
- ${table}.relname "tableName",
3699
- a.attname "name",
3700
- t.typname "type",
3701
- tn.nspname "typeSchema",
3702
- a.attndims "arrayDims",
3703
- information_schema._pg_char_max_length(tt.id, tt.mod) "maxChars",
3704
- information_schema._pg_numeric_precision(tt.id, tt.mod) "numericPrecision",
3705
- information_schema._pg_numeric_scale(tt.id,tt.mod) "numericScale",
3706
- information_schema._pg_datetime_precision(tt.id,tt.mod) "dateTimePrecision",
3707
- CAST(
3708
- CASE WHEN a.attgenerated = ''
3709
- THEN pg_get_expr(ad.adbin, ad.adrelid)
3710
- END AS information_schema.character_data
3711
- ) AS "default",
3712
- NOT (a.attnotnull OR (t.typtype = 'd' AND t.typnotnull)) AS "isNullable",
3713
- co.collname AS "collate",
3714
- NULLIF(a.attcompression, '') AS compression,
3715
- pgd.description AS "comment",
3716
- (
3717
- CASE WHEN a.attidentity IN ('a', 'd') THEN (
3718
- json_build_object(
3719
- 'always',
3720
- a.attidentity = 'a',
3721
- 'start',
3722
- seq.seqstart,
3723
- 'increment',
3724
- seq.seqincrement,
3725
- 'min',
3726
- nullif(seq.seqmin, 1),
3727
- 'max',
3728
- nullif(seq.seqmax, (
3729
- CASE t.typname
3730
- WHEN 'int2' THEN 32767
3731
- WHEN 'int4' THEN 2147483647
3732
- WHEN 'int8' THEN 9223372036854775807
3733
- ELSE NULL
3734
- END
3735
- )),
3736
- 'cache',
3737
- seq.seqcache,
3738
- 'cycle',
3739
- seq.seqcycle
3740
- )
3741
- ) END
3742
- ) "identity",
3743
- ext.extname "extension",
3744
- a.atttypmod "typmod"
3745
- FROM pg_attribute a
3746
- ${join}
3747
- LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
3748
- JOIN pg_type t
3749
- ON t.oid = (
3750
- CASE WHEN a.attndims = 0
3751
- THEN a.atttypid
3752
- ELSE (SELECT t.typelem FROM pg_type t WHERE t.oid = a.atttypid)
3753
- END
3754
- )
3755
- JOIN LATERAL (
3756
- SELECT
3757
- CASE WHEN t.typtype = 'd' THEN t.typbasetype ELSE t.oid END id,
3758
- CASE WHEN t.typtype = 'd' THEN t.typtypmod ELSE a.atttypmod END mod
3759
- ) tt ON true
3760
- JOIN pg_namespace tn ON tn.oid = t.typnamespace
3761
- LEFT JOIN (pg_collation co JOIN pg_namespace nco ON (co.collnamespace = nco.oid))
3762
- ON a.attcollation = co.oid AND (nco.nspname, co.collname) <> ('pg_catalog', 'default')
3763
- LEFT JOIN pg_catalog.pg_description pgd
3764
- ON pgd.objoid = a.attrelid
3765
- AND pgd.objsubid = a.attnum
3766
- LEFT JOIN (pg_depend dep JOIN pg_sequence seq ON (dep.classid = 'pg_class'::regclass AND dep.objid = seq.seqrelid AND dep.deptype = 'i'))
3767
- ON (dep.refclassid = 'pg_class'::regclass AND dep.refobjid = ${table}.oid AND dep.refobjsubid = a.attnum)
3768
- LEFT JOIN pg_depend d ON d.objid = t.oid AND d.classid = 'pg_type'::regclass AND d.deptype = 'e'
3769
- LEFT JOIN pg_extension ext ON ext.oid = d.refobjid
3770
- WHERE a.attnum > 0
3771
- AND NOT a.attisdropped
3772
- AND ${where}
3773
- ORDER BY a.attnum`;
3774
- const schemasSql = `SELECT coalesce(json_agg(nspname ORDER BY nspname), '[]')
3775
- FROM pg_catalog.pg_namespace n
3776
- WHERE ${filterSchema("nspname")}`;
3777
- const tablesSql = `SELECT
3778
- nspname AS "schemaName",
3779
- relname AS "name",
3780
- obj_description(c.oid) AS comment,
3781
- (SELECT coalesce(json_agg(t), '[]') FROM (${columnsSql({
3782
- schema: "n",
3783
- table: "c",
3784
- where: "a.attrelid = c.oid"
3785
- })}) t) AS "columns"
3786
- FROM pg_class c
3787
- JOIN pg_catalog.pg_namespace n ON n.oid = relnamespace
3788
- WHERE (relkind = 'r' OR relkind = 'p')
3789
- AND ${filterSchema("nspname")}
3790
- ORDER BY relname`;
3791
- const viewsSql = `SELECT
3792
- nc.nspname AS "schemaName",
3793
- c.relname AS "name",
3794
- (
3795
- SELECT COALESCE(json_agg(t.*), '[]')
3796
- FROM (
3797
- SELECT
3798
- ns.nspname AS "schemaName",
3799
- obj.relname AS "name"
3800
- FROM pg_class obj
3801
- JOIN pg_depend dep ON dep.refobjid = obj.oid
3802
- JOIN pg_rewrite rew ON rew.oid = dep.objid
3803
- JOIN pg_namespace ns ON ns.oid = obj.relnamespace
3804
- WHERE rew.ev_class = c.oid AND obj.oid <> c.oid
3805
- ) t
3806
- ) "deps",
3807
- right(substring(r.ev_action from ':hasRecursive w'), 1)::bool AS "isRecursive",
3808
- array_to_json(c.reloptions) AS "with",
3809
- (SELECT coalesce(json_agg(t), '[]') FROM (${columnsSql({
3810
- schema: "nc",
3811
- table: "c",
3812
- where: "a.attrelid = c.oid"
3813
- })}) t) AS "columns",
3814
- pg_get_viewdef(c.oid) AS "sql"
3815
- FROM pg_namespace nc
3816
- JOIN pg_class c
3817
- ON nc.oid = c.relnamespace
3818
- AND c.relkind = 'v'
3819
- AND c.relpersistence != 't'
3820
- JOIN pg_rewrite r ON r.ev_class = c.oid
3821
- WHERE ${filterSchema("nc.nspname")}
3822
- ORDER BY c.relname`;
3823
- const indexesSql = `SELECT
3824
- n.nspname "schemaName",
3825
- t.relname "tableName",
3826
- ic.relname "name",
3827
- am.amname AS "using",
3828
- i.indisunique "unique",
3829
- (
3830
- SELECT json_agg(
3831
- (
3832
- CASE WHEN t.e = 0
3833
- THEN jsonb_build_object('expression', pg_get_indexdef(i.indexrelid, t.i::int4, false))
3834
- ELSE jsonb_build_object('column', (
3835
- (
3836
- SELECT attname
3837
- FROM pg_catalog.pg_attribute
3838
- WHERE attrelid = i.indrelid
3839
- AND attnum = t.e
3840
- )
3841
- ))
3842
- END
3843
- ) || (
3844
- CASE WHEN i.indcollation[t.i - 1] = 0
3845
- THEN '{}'::jsonb
3846
- ELSE (
3847
- SELECT (
3848
- CASE WHEN collname = 'default'
3849
- THEN '{}'::jsonb
3850
- ELSE jsonb_build_object('collate', collname)
3851
- END
3852
- )
3853
- FROM pg_catalog.pg_collation
3854
- WHERE oid = i.indcollation[t.i - 1]
3855
- )
3856
- END
3857
- ) || (
3858
- SELECT
3859
- CASE WHEN opcdefault AND attoptions IS NULL
3860
- THEN '{}'::jsonb
3861
- ELSE jsonb_build_object(
3862
- 'opclass', opcname || COALESCE('(' || array_to_string(attoptions, ', ') || ')', '')
3863
- )
3864
- END
3865
- FROM pg_opclass
3866
- LEFT JOIN pg_attribute
3867
- ON attrelid = i.indexrelid
3868
- AND attnum = t.i
3869
- WHERE oid = i.indclass[t.i - 1]
3870
- ) || (
3871
- CASE WHEN i.indoption[t.i - 1] = 0
3872
- THEN '{}'::jsonb
3873
- ELSE jsonb_build_object(
3874
- 'order',
3875
- CASE
3876
- WHEN i.indoption[t.i - 1] = 1 THEN 'DESC NULLS LAST'
3877
- WHEN i.indoption[t.i - 1] = 2 THEN 'ASC NULLS FIRST'
3878
- WHEN i.indoption[t.i - 1] = 3 THEN 'DESC'
3879
- ELSE NULL
3880
- END
3881
- )
3882
- END
3883
- )
3884
- )
3885
- FROM unnest(i.indkey[:indnkeyatts - 1]) WITH ORDINALITY AS t(e, i)
3886
- ) "columns",
3887
- (
3888
- SELECT json_agg(
3889
- (
3890
- SELECT attname
3891
- FROM pg_catalog.pg_attribute
3892
- WHERE attrelid = i.indrelid
3893
- AND attnum = j.e
3894
- )
3895
- )
3896
- FROM unnest(i.indkey[indnkeyatts:]) AS j(e)
3897
- ) AS "include",
3898
- (to_jsonb(i.*)->'indnullsnotdistinct')::bool AS "nullsNotDistinct",
3899
- NULLIF(pg_catalog.array_to_string(
3900
- ic.reloptions || array(SELECT 'toast.' || x FROM pg_catalog.unnest(tc.reloptions) x),
3901
- ', '
3902
- ), '') AS "with",
3903
- (
3904
- SELECT tablespace
3905
- FROM pg_indexes
3906
- WHERE schemaname = n.nspname
3907
- AND indexname = ic.relname
3908
- ) AS tablespace,
3909
- pg_get_expr(i.indpred, i.indrelid) AS "where",
3910
- (
3911
- CASE i.indisexclusion WHEN true
3912
- THEN (
3913
- SELECT json_agg(o.oprname)
3914
- FROM pg_catalog.pg_constraint c, LATERAL unnest(c.conexclop) op_oid
3915
- JOIN pg_operator o ON o.oid = op_oid
3916
- WHERE c.conindid = ic.oid
3917
- )
3918
- END
3919
- ) "exclude"
3920
- FROM pg_index i
3921
- JOIN pg_class t ON t.oid = i.indrelid
3922
- JOIN pg_namespace n ON n.oid = t.relnamespace
3923
- JOIN pg_class ic ON ic.oid = i.indexrelid
3924
- JOIN pg_am am ON am.oid = ic.relam
3925
- LEFT JOIN pg_catalog.pg_class tc ON (ic.reltoastrelid = tc.oid)
3926
- WHERE ${filterSchema("n.nspname")}
3927
- AND NOT i.indisprimary
3928
- ORDER BY ic.relname`;
3929
- const constraintsSql = `SELECT
3930
- s.nspname AS "schemaName",
3931
- t.relname AS "tableName",
3932
- c.conname AS "name",
3933
- (
3934
- SELECT json_agg(ccu.column_name)
3935
- FROM information_schema.constraint_column_usage ccu
3936
- WHERE contype = 'p'
3937
- AND ccu.constraint_name = c.conname
3938
- AND ccu.table_schema = s.nspname
3939
- ) AS "primaryKey",
3940
- (
3941
- SELECT
3942
- json_build_object(
3943
- 'foreignSchema',
3944
- fs.nspname,
3945
- 'foreignTable',
3946
- ft.relname,
3947
- 'columns',
3948
- (
3949
- SELECT json_agg(ccu.column_name)
3950
- FROM information_schema.key_column_usage ccu
3951
- WHERE ccu.constraint_name = c.conname
3952
- AND ccu.table_schema = cs.nspname
3953
- ),
3954
- 'foreignColumns',
3955
- (
3956
- SELECT json_agg(ccu.column_name)
3957
- FROM information_schema.constraint_column_usage ccu
3958
- WHERE ccu.constraint_name = c.conname
3959
- AND ccu.table_schema = cs.nspname
3960
- ),
3961
- 'match',
3962
- c.confmatchtype,
3963
- 'onUpdate',
3964
- c.confupdtype,
3965
- 'onDelete',
3966
- c.confdeltype
3967
- )
3968
- FROM pg_class ft
3969
- JOIN pg_catalog.pg_namespace fs ON fs.oid = ft.relnamespace
3970
- JOIN pg_catalog.pg_namespace cs ON cs.oid = c.connamespace
3971
- WHERE contype = 'f' AND ft.oid = confrelid
3972
- ) AS "references",
3973
- (
3974
- SELECT
3975
- CASE conbin IS NULL
3976
- WHEN false THEN
3977
- json_build_object(
3978
- 'columns',
3979
- json_agg(ccu.column_name),
3980
- 'expression',
3981
- pg_get_expr(conbin, conrelid)
3982
- )
3983
- END
3984
- FROM information_schema.constraint_column_usage ccu
3985
- WHERE conbin IS NOT NULL
3986
- AND ccu.constraint_name = c.conname
3987
- AND ccu.table_schema = s.nspname
3988
- ) AS "check"
3989
- FROM pg_catalog.pg_constraint c
3990
- JOIN pg_class t ON t.oid = conrelid
3991
- JOIN pg_catalog.pg_namespace s
3992
- ON s.oid = t.relnamespace
3993
- AND contype IN ('p', 'f', 'c')
3994
- AND ${filterSchema("s.nspname")}
3995
- ORDER BY c.conname`;
3996
- const triggersSql = `SELECT event_object_schema AS "schemaName",
3997
- event_object_table AS "tableName",
3998
- trigger_schema AS "triggerSchema",
3999
- trigger_name AS name,
4000
- json_agg(event_manipulation) AS events,
4001
- action_timing AS activation,
4002
- action_condition AS condition,
4003
- action_statement AS definition
4004
- FROM information_schema.triggers
4005
- WHERE ${filterSchema("event_object_schema")}
4006
- GROUP BY event_object_schema, event_object_table, trigger_schema, trigger_name, action_timing, action_condition, action_statement
4007
- ORDER BY trigger_name`;
4008
- const extensionsSql = `SELECT
4009
- nspname AS "schemaName",
4010
- extname AS "name",
4011
- extversion AS version
4012
- FROM pg_extension
4013
- JOIN pg_catalog.pg_namespace n ON n.oid = extnamespace
4014
- AND ${filterSchema("n.nspname")}`;
4015
- const enumsSql = `SELECT
4016
- n.nspname as "schemaName",
4017
- t.typname as name,
4018
- json_agg(e.enumlabel ORDER BY e.enumsortorder) as values
4019
- FROM pg_type t
4020
- JOIN pg_enum e ON t.oid = e.enumtypid
4021
- JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
4022
- WHERE ${filterSchema("n.nspname")}
4023
- GROUP BY n.nspname, t.typname`;
4024
- const domainsSql = `SELECT
4025
- n.nspname AS "schemaName",
4026
- d.typname AS "name",
4027
- t.typname AS "type",
4028
- s.nspname AS "typeSchema",
4029
- NOT d.typnotnull AS "isNullable",
4030
- d.typndims AS "arrayDims",
4031
- character_maximum_length AS "maxChars",
4032
- numeric_precision AS "numericPrecision",
4033
- numeric_scale AS "numericScale",
4034
- datetime_precision AS "dateTimePrecision",
4035
- collation_name AS "collate",
4036
- domain_default AS "default",
4037
- (
4038
- SELECT json_agg(pg_get_expr(conbin, conrelid))
4039
- FROM pg_catalog.pg_constraint c
4040
- WHERE c.contypid = d.oid
4041
- ) AS "checks"
4042
- FROM pg_catalog.pg_type d
4043
- JOIN pg_catalog.pg_namespace n ON n.oid = d.typnamespace
4044
- JOIN information_schema.domains i
4045
- ON i.domain_schema = nspname
4046
- AND i.domain_name = d.typname
4047
- JOIN pg_catalog.pg_type t
4048
- ON (
4049
- CASE WHEN d.typcategory = 'A'
4050
- THEN t.typarray
4051
- ELSE t.oid
4052
- END
4053
- ) = d.typbasetype
4054
- JOIN pg_catalog.pg_namespace s ON s.oid = t.typnamespace
4055
- WHERE d.typtype = 'd' AND ${filterSchema("n.nspname")}`;
4056
- const collationsSql = (version) => `SELECT
4057
- nspname "schemaName",
4058
- collname "name",
4059
- CASE WHEN collprovider = 'i' THEN 'icu' WHEN collprovider = 'c' THEN 'libc' ELSE collprovider::text END "provider",
4060
- collisdeterministic "deterministic",
4061
- collcollate "lcCollate",
4062
- collctype "lcCType",
4063
- ${version >= 17 ? "colllocale" : "colliculocale"} "locale",
4064
- collversion "version"
4065
- FROM pg_collation
4066
- JOIN pg_namespace n on pg_collation.collnamespace = n.oid
4067
- WHERE ${filterSchema("n.nspname")}`;
4068
- const sql = (version) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(
4069
- tablesSql,
4070
- "tables"
4071
- )}, ${jsonAgg(viewsSql, "views")}, ${jsonAgg(
4072
- indexesSql,
4073
- "indexes"
4074
- )}, ${jsonAgg(constraintsSql, "constraints")}, ${jsonAgg(
4075
- triggersSql,
4076
- "triggers"
4077
- )}, ${jsonAgg(extensionsSql, "extensions")}, ${jsonAgg(
4078
- enumsSql,
4079
- "enums"
4080
- )}, ${jsonAgg(domainsSql, "domains")}, ${jsonAgg(
4081
- collationsSql(version),
4082
- "collations"
4083
- )}`;
4084
- async function introspectDbSchema(db) {
4085
- const {
4086
- rows: [{ version: versionString }]
4087
- } = await db.query("SELECT version()");
4088
- const version = +versionString.match(/\d+/)[0];
4089
- const data = await db.query(sql(version));
4090
- const result = data.rows[0];
4091
- for (const domain of result.domains) {
4092
- domain.checks = domain.checks?.filter((check) => check);
4093
- nullsToUndefined(domain);
4094
- }
4095
- for (const table of result.tables) {
4096
- for (const column of table.columns) {
4097
- nullsToUndefined(column);
4098
- if (column.identity) nullsToUndefined(column.identity);
4099
- if (column.compression) {
4100
- column.compression = column.compression === "p" ? "pglz" : "lz4";
4101
- }
4102
- }
4103
- }
4104
- const indexes = [];
4105
- const excludes = [];
4106
- for (const index of result.indexes) {
4107
- nullsToUndefined(index);
4108
- for (const column of index.columns) {
4109
- if (!("expression" in column)) continue;
4110
- const s = column.expression;
4111
- const columnR = `"?\\w+"?`;
4112
- const langR = `(${columnR}|'\\w+'::regconfig)`;
4113
- const firstColumnR = `[(]*${columnR}`;
4114
- const concatR = `\\|\\|`;
4115
- const restColumnR = ` ${concatR} ' '::text\\) ${concatR} ${columnR}\\)`;
4116
- const coalesceColumn = `COALESCE\\(${columnR}, ''::text\\)`;
4117
- const tsVectorR = `to_tsvector\\(${langR}, (${firstColumnR}|${restColumnR}|${coalesceColumn})+\\)`;
4118
- const weightR = `'\\w'::"char"`;
4119
- const setWeightR = `setweight\\(${tsVectorR}, ${weightR}\\)`;
4120
- const setWeightOrTsVectorR = `(${setWeightR}|${tsVectorR})`;
4121
- const match = s.match(
4122
- new RegExp(`^([\\(]*${setWeightOrTsVectorR}[\\)]*( ${concatR} )?)+$`)
4123
- );
4124
- if (!match) continue;
4125
- let language;
4126
- let languageColumn;
4127
- const tokens = match[0].match(
4128
- new RegExp(
4129
- `setweight\\(|to_tsvector\\(${langR}|[:']?${columnR}\\(?`,
4130
- "g"
4131
- )
4132
- )?.reduce((acc, token) => {
4133
- if (token === "setweight(" || token === "COALESCE(" || token[0] === ":")
4134
- return acc;
4135
- if (token.startsWith("to_tsvector(")) {
4136
- if (token[12] === "'") {
4137
- language = token.slice(13, -12);
4138
- } else {
4139
- languageColumn = token.slice(12);
4140
- }
4141
- } else if (token[0] === "'") {
4142
- acc.push({ kind: "weight", value: token[1] });
4143
- } else {
4144
- if (token[0] === '"') token = token.slice(1, -1);
4145
- acc.push({ kind: "column", value: token });
4146
- }
4147
- return acc;
4148
- }, []);
4149
- if (!tokens) continue;
4150
- index.language = language;
4151
- index.languageColumn = languageColumn;
4152
- index.tsVector = true;
4153
- index.columns = [];
4154
- for (const token of tokens) {
4155
- if (token.kind === "column") {
4156
- index.columns.push({
4157
- column: token.value
4158
- });
4159
- } else if (token.kind === "weight") {
4160
- index.columns[index.columns.length - 1].weight = token.value;
4161
- }
4162
- }
4163
- }
4164
- (index.exclude ? excludes : indexes).push(index);
4165
- }
4166
- result.indexes = indexes;
4167
- result.excludes = excludes;
4168
- return result;
4169
- }
4170
- const nullsToUndefined = (obj) => {
4171
- for (const key in obj) {
4172
- if (obj[key] === null)
4173
- obj[key] = void 0;
4174
- }
4175
- };
4176
-
4177
- const matchMap = {
4178
- s: void 0,
4179
- f: "FULL",
4180
- p: "PARTIAL"
4181
- };
4182
- const fkeyActionMap = {
4183
- a: void 0,
4184
- // default
4185
- r: "RESTRICT",
4186
- c: "CASCADE",
4187
- n: "SET NULL",
4188
- d: "SET DEFAULT"
4189
- };
4190
- const makeStructureToAstCtx = (config, currentSchema) => ({
4191
- snakeCase: config.snakeCase,
4192
- unsupportedTypes: {},
4193
- currentSchema,
4194
- columnSchemaConfig: config.schemaConfig,
4195
- columnsByType: pqb.makeColumnsByType(config.schemaConfig)
4196
- });
4197
- const structureToAst = async (ctx, adapter, config) => {
4198
- const ast = [];
4199
- const data = await introspectDbSchema(adapter);
4200
- for (const name of data.schemas) {
4201
- if (name === "public") continue;
4202
- ast.push({
4203
- type: "schema",
4204
- action: "create",
4205
- name
4206
- });
4207
- }
4208
- for (const it of data.collations) {
4209
- ast.push({
4210
- type: "collation",
4211
- action: "create",
4212
- ...it,
4213
- schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName
4214
- });
4215
- }
4216
- const domains = makeDomainsMap(ctx, data);
4217
- for (const table of data.tables) {
4218
- if (table.name === config.migrationsTable) continue;
4219
- ast.push(tableToAst(ctx, data, table, "create", domains));
4220
- }
4221
- for (const it of data.extensions) {
4222
- ast.push({
4223
- type: "extension",
4224
- action: "create",
4225
- name: it.name,
4226
- schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName,
4227
- version: it.version
4228
- });
4229
- }
4230
- for (const it of data.enums) {
4231
- ast.push({
4232
- type: "enum",
4233
- action: "create",
4234
- name: it.name,
4235
- schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName,
4236
- values: it.values
4237
- });
4238
- }
4239
- for (const it of data.domains) {
4240
- ast.push({
4241
- type: "domain",
4242
- action: "create",
4243
- schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName,
4244
- name: it.name,
4245
- baseType: domains[`${it.schemaName}.${it.name}`]
4246
- });
4247
- }
4248
- for (const table of data.tables) {
4249
- for (const fkey of data.constraints) {
4250
- if (fkey.references && fkey.tableName === table.name && fkey.schemaName === table.schemaName && checkIfIsOuterRecursiveFkey(data, table, fkey.references)) {
4251
- ast.push({
4252
- ...constraintToAst(ctx, fkey),
4253
- type: "constraint",
4254
- action: "create",
4255
- tableSchema: table.schemaName === ctx.currentSchema ? void 0 : table.schemaName,
4256
- tableName: fkey.tableName
4257
- });
4258
- }
4259
- }
4260
- }
4261
- for (const view of data.views) {
4262
- ast.push(viewToAst(ctx, data, domains, view));
4263
- }
4264
- return ast;
4265
- };
4266
- const makeDomainsMap = (ctx, data) => {
4267
- const domains = {};
4268
- for (const it of data.domains) {
4269
- const column = instantiateDbColumn(ctx, data, domains, {
4270
- schemaName: it.schemaName,
4271
- name: it.name,
4272
- type: it.type,
4273
- typeSchema: it.typeSchema,
4274
- arrayDims: it.arrayDims,
4275
- tableName: "",
4276
- isNullable: it.isNullable,
4277
- collate: it.collate,
4278
- default: it.default,
4279
- typmod: -1
4280
- });
4281
- if (it.checks) {
4282
- column.data.checks = it.checks.map((check) => ({
4283
- sql: new pqb.RawSql([[check]])
4284
- }));
4285
- }
4286
- domains[`${it.schemaName}.${it.name}`] = column;
4287
- }
4288
- return domains;
4289
- };
4290
- const getDbColumnIsSerial = (item) => {
4291
- if (item.type === "int2" || item.type === "int4" || item.type === "int8") {
4292
- const { default: def, schemaName, tableName, name } = item;
4293
- const seq = `${tableName}_${name}_seq`;
4294
- if (def && (def === `nextval(${pqb.singleQuote(`${seq}`)}::regclass)` || def === `nextval(${pqb.singleQuote(`"${seq}"`)}::regclass)` || def === `nextval(${pqb.singleQuote(`${schemaName}.${seq}`)}::regclass)` || def === `nextval(${pqb.singleQuote(`"${schemaName}".${seq}`)}::regclass)` || def === `nextval(${pqb.singleQuote(`${schemaName}."${seq}"`)}::regclass)` || def === `nextval(${pqb.singleQuote(`"${schemaName}"."${seq}"`)}::regclass)`)) {
4295
- return true;
4296
- }
4297
- }
4298
- return false;
4299
- };
4300
- const instantiateDbColumn = (ctx, data, domains, dbColumn) => {
4301
- var _a, _b;
4302
- const isSerial = getDbColumnIsSerial(dbColumn);
4303
- if (isSerial) {
4304
- dbColumn = { ...dbColumn, default: void 0 };
4305
- }
4306
- let column;
4307
- const col = instantiateColumnByDbType(ctx, dbColumn.type, isSerial, dbColumn);
4308
- if (col) {
4309
- column = col;
4310
- } else {
4311
- const { typeSchema, type: typeName } = dbColumn;
4312
- const typeId = typeSchema === "pg_catalog" ? typeName : `${typeSchema}.${typeName}`;
4313
- const domainColumn = domains[typeId];
4314
- if (domainColumn) {
4315
- column = new pqb.DomainColumn(
4316
- ctx.columnSchemaConfig,
4317
- typeName,
4318
- typeSchema,
4319
- dbColumn.extension
4320
- ).as(domainColumn);
4321
- } else {
4322
- const enumType = data.enums.find(
4323
- (x) => x.name === typeName && x.schemaName === typeSchema
4324
- );
4325
- if (enumType) {
4326
- column = new pqb.EnumColumn(
4327
- ctx.columnSchemaConfig,
4328
- typeSchema === ctx.currentSchema ? typeName : typeId,
4329
- enumType.values,
4330
- ctx.columnSchemaConfig.type
4331
- );
4332
- } else {
4333
- column = new pqb.CustomTypeColumn(
4334
- ctx.columnSchemaConfig,
4335
- typeName,
4336
- typeSchema === "pg_catalog" ? void 0 : typeSchema,
4337
- dbColumn.extension
4338
- );
4339
- ((_a = ctx.unsupportedTypes)[_b = dbColumn.type] ?? (_a[_b] = [])).push(
4340
- `${dbColumn.schemaName}${dbColumn.tableName ? `.${dbColumn.tableName}` : ""}.${dbColumn.name}`
4341
- );
4342
- }
4343
- pqb.assignDbDataToColumn(column, dbColumn);
4344
- }
4345
- }
4346
- column.data.name = void 0;
4347
- if (!column.data.isNullable) column.data.isNullable = void 0;
4348
- if (dbColumn.arrayDims) {
4349
- const arr = new pqb.ArrayColumn(
4350
- ctx.columnSchemaConfig,
4351
- column,
4352
- ctx.columnSchemaConfig.type
4353
- );
4354
- arr.data.isNullable = dbColumn.isNullable;
4355
- arr.data.arrayDims = dbColumn.arrayDims;
4356
- column = arr;
4357
- }
4358
- return column;
4359
- };
4360
- const instantiateColumnByDbType = (ctx, type, isSerial, params) => {
4361
- let columnFn = ctx.columnsByType[!isSerial ? type : type === "int2" ? "smallserial" : type === "int4" ? "serial" : "bigserial"];
4362
- if (!columnFn && params.extension === "postgis" && type === "geography" && pqb.PostgisGeographyPointColumn.isDefaultPoint(params.typmod)) {
4363
- columnFn = ctx.columnsByType.geographyDefaultPoint;
4364
- }
4365
- return columnFn ? pqb.assignDbDataToColumn(columnFn(), params) : void 0;
4366
- };
4367
- const tableToAst = (ctx, data, table, action, domains) => {
4368
- const { schemaName, name: tableName } = table;
4369
- const tableData = getDbStructureTableData(data, table);
4370
- const { primaryKey, constraints } = tableData;
4371
- return {
4372
- type: "table",
4373
- action,
4374
- schema: schemaName === ctx.currentSchema ? void 0 : schemaName,
4375
- comment: table.comment,
4376
- name: tableName,
4377
- shape: makeDbStructureColumnsShape(ctx, data, domains, table, tableData),
4378
- noPrimaryKey: tableData.primaryKey ? "error" : "ignore",
4379
- primaryKey: primaryKey && primaryKey.columns.length > 1 ? { ...primaryKey, columns: primaryKey.columns.map(pqb.toCamelCase) } : void 0,
4380
- indexes: indexesOrExcludesToAst(
4381
- tableName,
4382
- tableData,
4383
- "indexes"
4384
- ),
4385
- excludes: indexesOrExcludesToAst(
4386
- tableName,
4387
- tableData,
4388
- "excludes"
4389
- ),
4390
- constraints: constraints.reduce((acc, it) => {
4391
- if (it.check && it.references || it.check && it.check.columns?.length !== 1 || it.references && it.references.columns.length !== 1 && !checkIfIsOuterRecursiveFkey(data, table, it.references)) {
4392
- acc.push(dbConstraintToTableConstraint(ctx, table, it));
4393
- }
4394
- return acc;
4395
- }, [])
4396
- };
4397
- };
4398
- const indexesOrExcludesToAst = (tableName, tableData, key) => {
4399
- return tableData[key].reduce((acc, item) => {
4400
- if (item.columns.length > 1 || item.columns.some((it) => "expression" in it)) {
4401
- const options = makeIndexOrExcludeOptions(tableName, item, key);
4402
- acc.push({
4403
- columns: item.columns.map((it, i) => ({
4404
- with: "exclude" in item && item.exclude ? item.exclude[i] : void 0,
4405
- ..."expression" in it ? { expression: it.expression } : { column: pqb.toCamelCase(it.column) },
4406
- collate: it.collate,
4407
- opclass: it.opclass,
4408
- order: it.order
4409
- })),
4410
- options: {
4411
- ...options,
4412
- include: item.include?.map(pqb.toCamelCase)
4413
- }
4414
- });
4415
- }
4416
- return acc;
4417
- }, []);
4418
- };
4419
- const getDbStructureTableData = (data, { name, schemaName }) => {
4420
- const filterFn = filterByTableSchema(name, schemaName);
4421
- const constraints = data.constraints.filter(filterFn);
4422
- const primaryKey = constraints.find((c) => c.primaryKey);
4423
- return {
4424
- primaryKey: primaryKey?.primaryKey ? {
4425
- columns: primaryKey.primaryKey,
4426
- name: primaryKey.name === `${name}_pkey` ? void 0 : primaryKey.name
4427
- } : void 0,
4428
- indexes: data.indexes.filter(filterFn),
4429
- excludes: data.excludes.filter(filterFn),
4430
- constraints
4431
- };
4432
- };
4433
- const filterByTableSchema = (tableName, schemaName) => (x) => x.tableName === tableName && x.schemaName === schemaName;
4434
- const constraintToAst = (ctx, item) => {
4435
- const result = {};
4436
- const { references, check } = item;
4437
- if (references) {
4438
- const options = {};
4439
- result.references = {
4440
- columns: references.columns,
4441
- fnOrTable: getReferencesTable(ctx, references),
4442
- foreignColumns: references.foreignColumns,
4443
- options
4444
- };
4445
- const match = matchMap[references.match];
4446
- if (match) options.match = match;
4447
- const onUpdate = fkeyActionMap[references.onUpdate];
4448
- if (onUpdate) options.onUpdate = onUpdate;
4449
- const onDelete = fkeyActionMap[references.onDelete];
4450
- if (onDelete) options.onDelete = onDelete;
4451
- }
4452
- if (check) {
4453
- result.check = pqb.raw({ raw: check.expression });
4454
- }
4455
- if (item.name && item.name !== getConstraintName(item.tableName, result, ctx.snakeCase)) {
4456
- result.name = item.name;
4457
- if (result.references?.options) {
4458
- result.references.options.name = item.name;
4459
- }
4460
- }
4461
- return result;
4462
- };
4463
- const getReferencesTable = (ctx, references) => {
4464
- return references.foreignSchema !== ctx.currentSchema ? `${references.foreignSchema}.${references.foreignTable}` : references.foreignTable;
4465
- };
4466
- const isColumnCheck = (it) => {
4467
- return !it.references && it.check?.columns?.length === 1;
4468
- };
4469
- const viewToAst = (ctx, data, domains, view) => {
4470
- const shape = makeDbStructureColumnsShape(ctx, data, domains, view);
4471
- const options = {};
4472
- if (view.isRecursive) options.recursive = true;
4473
- if (view.with) {
4474
- const withOptions = {};
4475
- options.with = withOptions;
4476
- for (const pair of view.with) {
4477
- const [key, value] = pair.split("=");
4478
- withOptions[pqb.toCamelCase(key)] = value === "true" ? true : value === "false" ? false : value;
4479
- }
4480
- }
4481
- return {
4482
- type: "view",
4483
- action: "create",
4484
- schema: view.schemaName === ctx.currentSchema ? void 0 : view.schemaName,
4485
- name: view.name,
4486
- shape,
4487
- sql: pqb.raw({ raw: view.sql }),
4488
- options,
4489
- deps: view.deps
4490
- };
4491
- };
4492
- const makeDbStructureColumnsShape = (ctx, data, domains, table, tableData) => {
4493
- const shape = {};
4494
- const checks = tableData ? getDbTableColumnsChecks(tableData) : void 0;
4495
- for (const item of table.columns) {
4496
- const [key, column] = dbColumnToAst(
4497
- ctx,
4498
- data,
4499
- domains,
4500
- table.name,
4501
- item,
4502
- table,
4503
- tableData,
4504
- checks
4505
- );
4506
- shape[key] = column;
4507
- }
4508
- return shape;
4509
- };
4510
- const getDbTableColumnsChecks = (tableData) => tableData.constraints.reduce((acc, item) => {
4511
- var _a;
4512
- if (isColumnCheck(item)) {
4513
- (acc[_a = item.check.columns[0]] ?? (acc[_a] = [])).push(item.check.expression);
4514
- }
4515
- return acc;
4516
- }, {});
4517
- const dbColumnToAst = (ctx, data, domains, tableName, item, table, tableData, checks) => {
4518
- let column = instantiateDbColumn(ctx, data, domains, item);
4519
- column.data.name = item.name;
4520
- if (item.identity) {
4521
- column.data.identity = item.identity;
4522
- if (!item.identity.always) delete column.data.identity?.always;
4523
- }
4524
- if (tableData?.primaryKey?.columns?.length === 1 && tableData?.primaryKey?.columns[0] === item.name) {
4525
- column = column.primaryKey();
4526
- }
4527
- collectColumnIndexesOrExcludes(item, column, tableName, tableData, "indexes");
4528
- collectColumnIndexesOrExcludes(
4529
- item,
4530
- column,
4531
- tableName,
4532
- tableData,
4533
- "excludes"
4534
- );
4535
- if (table) {
4536
- for (const it of data.constraints) {
4537
- if (it.tableName !== table.name || it.schemaName !== table.schemaName || it.check || it.references?.columns.length !== 1 || it.references.columns[0] !== item.name || checkIfIsOuterRecursiveFkey(data, table, it.references)) {
4538
- continue;
4539
- }
4540
- const c = dbConstraintToTableConstraint(ctx, table, it);
4541
- column = column.foreignKey(
4542
- c.references?.fnOrTable,
4543
- it.references.foreignColumns[0],
4544
- c.references?.options
4545
- );
4546
- }
4547
- }
4548
- const columnChecks = checks?.[item.name];
4549
- if (columnChecks) {
4550
- column.data.checks = columnChecks.map((check) => ({
4551
- sql: new pqb.RawSql([[check]])
4552
- }));
4553
- }
4554
- const camelCaseName = pqb.toCamelCase(item.name);
4555
- if (ctx.snakeCase) {
4556
- const snakeCaseName = pqb.toSnakeCase(camelCaseName);
4557
- if (snakeCaseName !== item.name) column.data.name = item.name;
4558
- } else if (camelCaseName !== item.name) {
4559
- column.data.name = item.name;
4560
- }
4561
- return [camelCaseName, column];
4562
- };
4563
- const collectColumnIndexesOrExcludes = (dbColumn, column, tableName, tableData, key) => {
4564
- var _a;
4565
- const items = tableData?.[key];
4566
- if (!items) return;
4567
- const columnItems = items.filter(
4568
- (it) => it.columns.length === 1 && "column" in it.columns[0] && it.columns[0].column === dbColumn.name
4569
- );
4570
- for (const item of columnItems) {
4571
- const columnOptions = item.columns[0];
4572
- const { name, ...itemOptions } = makeIndexOrExcludeOptions(
4573
- tableName,
4574
- item,
4575
- key
4576
- );
4577
- ((_a = column.data)[key] ?? (_a[key] = [])).push({
4578
- with: "exclude" in item && item.exclude ? item.exclude[0] : void 0,
4579
- options: {
4580
- name,
4581
- collate: columnOptions.collate,
4582
- opclass: columnOptions.opclass,
4583
- order: columnOptions.order,
4584
- ...itemOptions
4585
- }
4586
- });
4587
- }
4588
- };
4589
- const dbConstraintToTableConstraint = (ctx, table, item) => {
4590
- const { references, check } = item;
4591
- const constraint = {
4592
- references: references ? {
4593
- columns: references.columns,
4594
- fnOrTable: getReferencesTable(ctx, references),
4595
- foreignColumns: references.foreignColumns,
4596
- options: {
4597
- match: matchMap[references.match],
4598
- onUpdate: fkeyActionMap[references.onUpdate],
4599
- onDelete: fkeyActionMap[references.onDelete]
4600
- }
4601
- } : void 0,
4602
- check: check ? pqb.raw({ raw: check.expression }) : void 0
4603
- };
4604
- const name = item.name && item.name !== getConstraintName(table.name, constraint, ctx.snakeCase) ? item.name : void 0;
4605
- if (name) {
4606
- constraint.name = name;
4607
- if (constraint.references?.options) {
4608
- constraint.references.options.name = name;
4609
- }
4610
- }
4611
- return constraint;
4612
- };
4613
- const makeIndexOrExcludeOptions = (tableName, index, key) => {
4614
- return {
4615
- name: index.name !== (key === "indexes" ? getIndexName : getExcludeName)(
4616
- tableName,
4617
- index.columns
4618
- ) ? index.name : void 0,
4619
- using: index.using === "btree" ? void 0 : index.using,
4620
- unique: index.unique || void 0,
4621
- include: index.include,
4622
- nullsNotDistinct: index.nullsNotDistinct || void 0,
4623
- with: index.with,
4624
- tablespace: index.tablespace,
4625
- where: index.where
4626
- };
4627
- };
4628
- const checkIfIsOuterRecursiveFkey = (data, table, references) => {
4629
- const referencesId = `${references.foreignSchema}.${references.foreignTable}`;
4630
- const tableId = `${table.schemaName}.${table.name}`;
4631
- for (const other of data.tables) {
4632
- const id = `${other.schemaName}.${other.name}`;
4633
- if (referencesId === id) {
4634
- for (const c of data.constraints) {
4635
- if (c.tableName === other.name && c.schemaName === other.schemaName && c.references?.foreignTable === table.name && c.references.foreignSchema === table.schemaName && tableId < id) {
4636
- return true;
4637
- }
4638
- }
4639
- break;
4640
- }
4641
- }
4642
- return false;
4643
- };
4644
-
4645
3709
  const astToGenerateItems = (config, asts, currentSchema) => {
4646
3710
  return asts.map((ast) => astToGenerateItem(config, ast, currentSchema));
4647
3711
  };
@@ -5027,23 +4091,79 @@ const astEncoders = {
5027
4091
  const inner = [`${quoteSchemaTable(ast)},`];
5028
4092
  code.push(inner);
5029
4093
  code = inner;
5030
- if (hasOptions) {
5031
- const options = [];
5032
- if (ast.comment)
5033
- options.push(`comment: ${JSON.stringify(ast.comment)},`);
5034
- if (ast.noPrimaryKey === "ignore") options.push(`noPrimaryKey: true,`);
5035
- code.push("{", options, "},");
5036
- }
5037
- code.push("(t) => ({");
4094
+ if (hasOptions) {
4095
+ const options = [];
4096
+ if (ast.comment)
4097
+ options.push(`comment: ${JSON.stringify(ast.comment)},`);
4098
+ if (ast.noPrimaryKey === "ignore") options.push(`noPrimaryKey: true,`);
4099
+ code.push("{", options, "},");
4100
+ }
4101
+ code.push("(t) => ({");
4102
+ } else {
4103
+ pqb.addCode(
4104
+ code,
4105
+ `await db.${ast.action}Table(${quoteSchemaTable(ast)}, (t) => ({`
4106
+ );
4107
+ }
4108
+ const timestamps = getHasTimestamps(
4109
+ ast.shape.createdAt,
4110
+ ast.shape.updatedAt
4111
+ );
4112
+ const toCodeCtx = {
4113
+ t: "t",
4114
+ table: ast.name,
4115
+ currentSchema,
4116
+ migration: true,
4117
+ snakeCase: config.snakeCase
4118
+ };
4119
+ for (const key in ast.shape) {
4120
+ if (timestamps.hasAnyTimestamps && (key === "createdAt" || key === "updatedAt"))
4121
+ continue;
4122
+ const line = [`${pqb.quoteObjectKey(key, config.snakeCase)}: `];
4123
+ const columnCode = ast.shape[key].toCode(toCodeCtx, key);
4124
+ for (const part of columnCode) {
4125
+ pqb.addCode(line, part);
4126
+ }
4127
+ pqb.addCode(line, ",");
4128
+ code.push(line);
4129
+ }
4130
+ if (timestamps.hasAnyTimestamps) {
4131
+ code.push([`...${timestampsToCode(timestamps)},`]);
4132
+ }
4133
+ if (isShifted) {
4134
+ pqb.addCode(code, "}),");
4135
+ if (hasTableData) pqb.pushTableDataCode(code, ast);
4136
+ pqb.addCode(result, ");");
4137
+ } else {
4138
+ pqb.addCode(result, "}));");
4139
+ }
4140
+ return result;
4141
+ },
4142
+ changeTable(ast, config, currentSchema) {
4143
+ let code = [];
4144
+ const result = code;
4145
+ const schemaTable = quoteSchemaTable({
4146
+ schema: ast.schema === currentSchema ? void 0 : ast.schema,
4147
+ name: ast.name
4148
+ });
4149
+ const { comment } = ast;
4150
+ if (comment !== void 0) {
4151
+ pqb.addCode(code, `await db.changeTable(`);
4152
+ const inner = [
4153
+ `${schemaTable},`,
4154
+ `{ comment: ${JSON.stringify(ast.comment)} },`,
4155
+ "(t) => ({"
4156
+ ];
4157
+ code.push(inner);
4158
+ code = inner;
5038
4159
  } else {
5039
- pqb.addCode(
5040
- code,
5041
- `await db.${ast.action}Table(${quoteSchemaTable(ast)}, (t) => ({`
5042
- );
4160
+ pqb.addCode(code, `await db.changeTable(${schemaTable}, (t) => ({`);
5043
4161
  }
5044
- const timestamps = getHasTimestamps(
5045
- ast.shape.createdAt,
5046
- ast.shape.updatedAt
4162
+ const [addTimestamps, dropTimestamps] = ["add", "drop"].map(
4163
+ (type) => getHasTimestamps(
4164
+ ast.shape.createdAt && "type" in ast.shape.createdAt && ast.shape.createdAt?.type === type ? ast.shape.createdAt.item : void 0,
4165
+ ast.shape.updatedAt && "type" in ast.shape.updatedAt && ast.shape.updatedAt?.type === type ? ast.shape.updatedAt.item : void 0
4166
+ )
5047
4167
  );
5048
4168
  const toCodeCtx = {
5049
4169
  t: "t",
@@ -5053,349 +4173,1251 @@ const astEncoders = {
5053
4173
  snakeCase: config.snakeCase
5054
4174
  };
5055
4175
  for (const key in ast.shape) {
5056
- if (timestamps.hasAnyTimestamps && (key === "createdAt" || key === "updatedAt"))
5057
- continue;
5058
- const line = [`${pqb.quoteObjectKey(key, config.snakeCase)}: `];
5059
- const columnCode = ast.shape[key].toCode(toCodeCtx, key);
5060
- for (const part of columnCode) {
5061
- pqb.addCode(line, part);
4176
+ const changes = pqb.toArray(ast.shape[key]);
4177
+ for (const change of changes) {
4178
+ if (change.type === "add" || change.type === "drop") {
4179
+ if ((addTimestamps.hasAnyTimestamps || dropTimestamps.hasAnyTimestamps) && (key === "createdAt" || key === "updatedAt"))
4180
+ continue;
4181
+ const recreate = changes.length > 1;
4182
+ const line = [
4183
+ recreate ? `...t.${change.type}(t.name(${pqb.singleQuote(
4184
+ change.item.data.name ?? key
4185
+ )})` : `${pqb.quoteObjectKey(key, config.snakeCase)}: t.${change.type}(`
4186
+ ];
4187
+ const columnCode = change.item.toCode(toCodeCtx, key);
4188
+ for (let i = 0; i < columnCode.length; i++) {
4189
+ let part = columnCode[i];
4190
+ if (recreate && !i) part = part.slice(1);
4191
+ pqb.addCode(line, part);
4192
+ }
4193
+ pqb.addCode(line, "),");
4194
+ code.push(line);
4195
+ } else if (change.type === "change") {
4196
+ if (!change.from.column || !change.to.column) continue;
4197
+ const line = [
4198
+ `${pqb.quoteObjectKey(key, config.snakeCase)}: t${change.name ? `.name(${pqb.singleQuote(change.name)})` : ""}.change(`
4199
+ ];
4200
+ const fromCode = change.from.column.toCode(
4201
+ {
4202
+ t: "t",
4203
+ table: ast.name,
4204
+ currentSchema,
4205
+ migration: true,
4206
+ snakeCase: config.snakeCase
4207
+ },
4208
+ key
4209
+ );
4210
+ for (const part of fromCode) {
4211
+ pqb.addCode(line, part);
4212
+ }
4213
+ pqb.addCode(line, ", ");
4214
+ const toCode = change.to.column.toCode(toCodeCtx, key);
4215
+ for (const part of toCode) {
4216
+ pqb.addCode(line, part);
4217
+ }
4218
+ if (change.using) {
4219
+ pqb.addCode(line, ", {");
4220
+ const u = [];
4221
+ if (change.using.usingUp) {
4222
+ u.push(`usingUp: ${pqb.rawSqlToCode(change.using.usingUp, "t")},`);
4223
+ }
4224
+ if (change.using.usingDown) {
4225
+ u.push(
4226
+ `usingDown: ${pqb.rawSqlToCode(change.using.usingDown, "t")},`
4227
+ );
4228
+ }
4229
+ pqb.addCode(line, u);
4230
+ pqb.addCode(line, "}");
4231
+ }
4232
+ pqb.addCode(line, "),");
4233
+ code.push(line);
4234
+ } else if (change.type === "rename") {
4235
+ code.push([
4236
+ `${pqb.quoteObjectKey(key, config.snakeCase)}: t.rename(${pqb.singleQuote(
4237
+ change.name
4238
+ )}),`
4239
+ ]);
4240
+ } else {
4241
+ pqb.exhaustive(change.type);
4242
+ }
5062
4243
  }
5063
- pqb.addCode(line, ",");
5064
- code.push(line);
5065
4244
  }
5066
- if (timestamps.hasAnyTimestamps) {
5067
- code.push([`...${timestampsToCode(timestamps)},`]);
4245
+ for (const key of ["drop", "add"]) {
4246
+ const timestamps = key === "add" ? addTimestamps : dropTimestamps;
4247
+ if (timestamps.hasAnyTimestamps) {
4248
+ pqb.addCode(code, [`...t.${key}(${timestampsToCode(timestamps)}),`]);
4249
+ }
4250
+ const { primaryKey, indexes, excludes, constraints } = ast[key];
4251
+ if (primaryKey) {
4252
+ pqb.addCode(code, [
4253
+ `...t.${key}(${pqb.primaryKeyInnerToCode(primaryKey, "t")}),`
4254
+ ]);
4255
+ }
4256
+ if (indexes) {
4257
+ for (const item of indexes) {
4258
+ pqb.addCode(code, [`...t.${key}(`, pqb.indexInnerToCode(item, "t"), "),"]);
4259
+ }
4260
+ }
4261
+ if (excludes) {
4262
+ for (const item of excludes) {
4263
+ pqb.addCode(code, [`...t.${key}(`, pqb.excludeInnerToCode(item, "t"), "),"]);
4264
+ }
4265
+ }
4266
+ if (constraints) {
4267
+ for (const item of constraints) {
4268
+ pqb.addCode(code, [
4269
+ `...t.${key}(`,
4270
+ pqb.constraintInnerToCode(item, "t", true),
4271
+ "),"
4272
+ ]);
4273
+ }
4274
+ }
5068
4275
  }
5069
- if (isShifted) {
4276
+ if (ast.comment !== void 0) {
5070
4277
  pqb.addCode(code, "}),");
5071
- if (hasTableData) pqb.pushTableDataCode(code, ast);
5072
4278
  pqb.addCode(result, ");");
5073
4279
  } else {
5074
4280
  pqb.addCode(result, "}));");
5075
4281
  }
5076
- return result;
5077
- },
5078
- changeTable(ast, config, currentSchema) {
5079
- let code = [];
5080
- const result = code;
5081
- const schemaTable = quoteSchemaTable({
5082
- schema: ast.schema === currentSchema ? void 0 : ast.schema,
5083
- name: ast.name
5084
- });
5085
- const { comment } = ast;
5086
- if (comment !== void 0) {
5087
- pqb.addCode(code, `await db.changeTable(`);
5088
- const inner = [
5089
- `${schemaTable},`,
5090
- `{ comment: ${JSON.stringify(ast.comment)} },`,
5091
- "(t) => ({"
5092
- ];
5093
- code.push(inner);
5094
- code = inner;
4282
+ return result;
4283
+ },
4284
+ renameType(ast, _, currentSchema) {
4285
+ const code = [];
4286
+ const kind = ast.kind === "TABLE" ? "Table" : "Type";
4287
+ if (ast.from === ast.to) {
4288
+ pqb.addCode(
4289
+ code,
4290
+ `await db.change${kind}Schema(${pqb.singleQuote(ast.to)}, ${pqb.singleQuote(
4291
+ ast.fromSchema ?? currentSchema
4292
+ )}, ${pqb.singleQuote(ast.toSchema ?? currentSchema)});`
4293
+ );
4294
+ } else {
4295
+ pqb.addCode(
4296
+ code,
4297
+ `await db.rename${kind}(${quoteSchemaTable({
4298
+ schema: ast.fromSchema === currentSchema ? void 0 : ast.fromSchema,
4299
+ name: ast.from
4300
+ })}, ${quoteSchemaTable({
4301
+ schema: ast.toSchema === currentSchema ? void 0 : ast.toSchema,
4302
+ name: ast.to
4303
+ })});`
4304
+ );
4305
+ }
4306
+ return code;
4307
+ },
4308
+ schema(ast) {
4309
+ return `await db.${ast.action === "create" ? "createSchema" : "dropSchema"}(${pqb.singleQuote(ast.name)});`;
4310
+ },
4311
+ renameSchema(ast) {
4312
+ return `await db.renameSchema(${pqb.singleQuote(ast.from)}, ${pqb.singleQuote(
4313
+ ast.to
4314
+ )});`;
4315
+ },
4316
+ extension(ast) {
4317
+ const code = [
4318
+ `await db.${ast.action}Extension(${quoteSchemaTable(ast)}`
4319
+ ];
4320
+ if (ast.version) {
4321
+ pqb.addCode(code, ", {");
4322
+ code.push([`version: ${pqb.singleQuote(ast.version)},`], "}");
4323
+ }
4324
+ pqb.addCode(code, ");");
4325
+ return code;
4326
+ },
4327
+ enum(ast, _, currentSchema) {
4328
+ return `await db.${ast.action === "create" ? "createEnum" : "dropEnum"}(${quoteSchemaTable(ast, currentSchema)}, [${ast.values.map(pqb.singleQuote).join(", ")}]);`;
4329
+ },
4330
+ enumValues(ast, _, currentSchema) {
4331
+ return `await db.${ast.action}EnumValues(${quoteSchemaTable(
4332
+ ast,
4333
+ currentSchema
4334
+ )}, [${ast.values.map(pqb.singleQuote).join(", ")}]);`;
4335
+ },
4336
+ renameEnumValues(ast, config, currentSchema) {
4337
+ return `await db.renameEnumValues(${quoteSchemaTable(
4338
+ ast,
4339
+ currentSchema
4340
+ )}, { ${Object.entries(ast.values).map(
4341
+ ([from, to]) => `${pqb.quoteObjectKey(from, config.snakeCase)}: ${pqb.singleQuote(to)}`
4342
+ ).join(", ")} });`;
4343
+ },
4344
+ changeEnumValues(ast, _, currentSchema) {
4345
+ return `await db.changeEnumValues(${quoteSchemaTable(
4346
+ ast,
4347
+ currentSchema
4348
+ )}, [${ast.fromValues.map(pqb.singleQuote).join(", ")}], [${ast.toValues.map(pqb.singleQuote).join(", ")}]);`;
4349
+ },
4350
+ domain(ast, _, currentSchema) {
4351
+ return `await db.${ast.action}Domain(${quoteSchemaTable(
4352
+ ast
4353
+ )}, (t) => ${ast.baseType.toCode(
4354
+ { t: "t", table: ast.name, currentSchema },
4355
+ ast.baseType.data.name ?? ""
4356
+ )});`;
4357
+ },
4358
+ collation(ast) {
4359
+ const params = [];
4360
+ if (ast.locale) params.push(`locale: '${ast.locale}',`);
4361
+ if (ast.lcCollate) params.push(`lcCollate: '${ast.lcCollate}',`);
4362
+ if (ast.lcCType) params.push(`lcCType: '${ast.lcCType}',`);
4363
+ if (ast.provider) params.push(`provider: '${ast.provider}',`);
4364
+ if (ast.deterministic) params.push(`deterministic: ${ast.deterministic},`);
4365
+ if (ast.version) params.push(`version: '${ast.version}',`);
4366
+ return [
4367
+ `await db.createCollation(${quoteSchemaTable(ast)}, {`,
4368
+ params,
4369
+ "});"
4370
+ ];
4371
+ },
4372
+ constraint(ast) {
4373
+ const table = quoteSchemaTable({
4374
+ schema: ast.tableSchema,
4375
+ name: ast.tableName
4376
+ });
4377
+ if (ast.references) {
4378
+ return [
4379
+ `await db.addForeignKey(`,
4380
+ [`${table},`, ...pqb.referencesArgsToCode(ast.references, ast.name, true)],
4381
+ ");"
4382
+ ];
4383
+ }
4384
+ const check = ast.check;
4385
+ return [
4386
+ `await db.addCheck(${table}, ${pqb.rawSqlToCode(check, "t")}${ast.name ? `, ${pqb.singleQuote(ast.name)}` : ""});`
4387
+ ];
4388
+ },
4389
+ renameTableItem(ast) {
4390
+ return [
4391
+ `await db.rename${ast.kind === "INDEX" ? "Index" : "Constraint"}(${quoteSchemaTable({
4392
+ schema: ast.tableSchema,
4393
+ name: ast.tableName
4394
+ })}, ${pqb.singleQuote(ast.from)}, ${pqb.singleQuote(ast.to)});`
4395
+ ];
4396
+ },
4397
+ view(ast) {
4398
+ const code = [`await db.createView(${quoteSchemaTable(ast)}`];
4399
+ const options = [];
4400
+ if (ast.options.recursive) options.push("recursive: true,");
4401
+ const w = ast.options.with;
4402
+ if (w?.checkOption) options.push(`checkOption: '${w.checkOption}',`);
4403
+ if (w?.securityBarrier)
4404
+ options.push(`securityBarrier: ${w.securityBarrier},`);
4405
+ if (w?.securityInvoker)
4406
+ options.push(`securityInvoker: ${w.securityInvoker},`);
4407
+ if (options.length) {
4408
+ pqb.addCode(code, ", {");
4409
+ code.push(options, "}");
4410
+ }
4411
+ pqb.addCode(code, ", ");
4412
+ if (!ast.sql._values) {
4413
+ const raw = ast.sql._sql;
4414
+ let sql;
4415
+ if (typeof raw === "string") {
4416
+ sql = raw;
4417
+ } else {
4418
+ sql = "";
4419
+ const parts = raw[0];
4420
+ const last = parts.length - 1;
4421
+ for (let i = 0; i < last; i++) {
4422
+ sql += parts[i] + `\${${raw[i + 1]}}`;
4423
+ }
4424
+ sql += parts[last];
4425
+ }
4426
+ pqb.addCode(code, pqb.backtickQuote(sql));
5095
4427
  } else {
5096
- pqb.addCode(code, `await db.changeTable(${schemaTable}, (t) => ({`);
4428
+ pqb.addCode(code, pqb.rawSqlToCode(ast.sql, "db"));
5097
4429
  }
5098
- const [addTimestamps, dropTimestamps] = ["add", "drop"].map(
5099
- (type) => getHasTimestamps(
5100
- ast.shape.createdAt && "type" in ast.shape.createdAt && ast.shape.createdAt?.type === type ? ast.shape.createdAt.item : void 0,
5101
- ast.shape.updatedAt && "type" in ast.shape.updatedAt && ast.shape.updatedAt?.type === type ? ast.shape.updatedAt.item : void 0
4430
+ pqb.addCode(code, ");");
4431
+ return code;
4432
+ }
4433
+ };
4434
+ const isTimestamp = (column, type) => {
4435
+ if (!column) return false;
4436
+ const { default: def } = column.data;
4437
+ return Boolean(
4438
+ column instanceof type && !column.data.isNullable && def && typeof def === "object" && pqb.isRawSQL(def) && (typeof def._sql === "object" ? def._sql[0][0] : def._sql) === "now()"
4439
+ );
4440
+ };
4441
+ const getHasTimestamps = (createdAt, updatedAt) => {
4442
+ const timestamps = getTimestampsInfo(createdAt, updatedAt, pqb.TimestampTZColumn);
4443
+ const timestampsNoTZ = getTimestampsInfo(
4444
+ createdAt,
4445
+ updatedAt,
4446
+ pqb.TimestampColumn
4447
+ );
4448
+ return {
4449
+ hasTZTimestamps: timestamps,
4450
+ hasAnyTimestamps: timestamps || timestampsNoTZ
4451
+ };
4452
+ };
4453
+ const getTimestampsInfo = (createdAt, updatedAt, type) => {
4454
+ return isTimestamp(createdAt, type) && isTimestamp(updatedAt, type) && (!createdAt?.data.name || createdAt?.data.name === "created_at") && (!updatedAt?.data.name || updatedAt?.data.name === "updated_at");
4455
+ };
4456
+ const timestampsToCode = ({ hasTZTimestamps }) => {
4457
+ const key = hasTZTimestamps ? "timestamps" : "timestampsNoTZ";
4458
+ return `t.${key}()`;
4459
+ };
4460
+
4461
+ const filterSchema = (table) => `${table} !~ '^pg_' AND ${table} != 'information_schema'`;
4462
+ const jsonAgg = (sql2, as) => `(SELECT coalesce(json_agg(t.*), '[]') FROM (${sql2}) t) AS "${as}"`;
4463
+ const columnsSql = ({
4464
+ schema,
4465
+ table,
4466
+ join = "",
4467
+ where
4468
+ }) => `SELECT
4469
+ ${schema}.nspname "schemaName",
4470
+ ${table}.relname "tableName",
4471
+ a.attname "name",
4472
+ t.typname "type",
4473
+ tn.nspname "typeSchema",
4474
+ a.attndims "arrayDims",
4475
+ information_schema._pg_char_max_length(tt.id, tt.mod) "maxChars",
4476
+ information_schema._pg_numeric_precision(tt.id, tt.mod) "numericPrecision",
4477
+ information_schema._pg_numeric_scale(tt.id,tt.mod) "numericScale",
4478
+ information_schema._pg_datetime_precision(tt.id,tt.mod) "dateTimePrecision",
4479
+ CAST(
4480
+ CASE WHEN a.attgenerated = ''
4481
+ THEN pg_get_expr(ad.adbin, ad.adrelid)
4482
+ END AS information_schema.character_data
4483
+ ) AS "default",
4484
+ NOT (a.attnotnull OR (t.typtype = 'd' AND t.typnotnull)) AS "isNullable",
4485
+ co.collname AS "collate",
4486
+ NULLIF(a.attcompression, '') AS compression,
4487
+ pgd.description AS "comment",
4488
+ (
4489
+ CASE WHEN a.attidentity IN ('a', 'd') THEN (
4490
+ json_build_object(
4491
+ 'always',
4492
+ a.attidentity = 'a',
4493
+ 'start',
4494
+ seq.seqstart,
4495
+ 'increment',
4496
+ seq.seqincrement,
4497
+ 'min',
4498
+ nullif(seq.seqmin, 1),
4499
+ 'max',
4500
+ nullif(seq.seqmax, (
4501
+ CASE t.typname
4502
+ WHEN 'int2' THEN 32767
4503
+ WHEN 'int4' THEN 2147483647
4504
+ WHEN 'int8' THEN 9223372036854775807
4505
+ ELSE NULL
4506
+ END
4507
+ )),
4508
+ 'cache',
4509
+ seq.seqcache,
4510
+ 'cycle',
4511
+ seq.seqcycle
4512
+ )
4513
+ ) END
4514
+ ) "identity",
4515
+ ext.extname "extension",
4516
+ a.atttypmod "typmod"
4517
+ FROM pg_attribute a
4518
+ ${join}
4519
+ LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
4520
+ JOIN pg_type t
4521
+ ON t.oid = (
4522
+ CASE WHEN a.attndims = 0
4523
+ THEN a.atttypid
4524
+ ELSE (SELECT t.typelem FROM pg_type t WHERE t.oid = a.atttypid)
4525
+ END
4526
+ )
4527
+ JOIN LATERAL (
4528
+ SELECT
4529
+ CASE WHEN t.typtype = 'd' THEN t.typbasetype ELSE t.oid END id,
4530
+ CASE WHEN t.typtype = 'd' THEN t.typtypmod ELSE a.atttypmod END mod
4531
+ ) tt ON true
4532
+ JOIN pg_namespace tn ON tn.oid = t.typnamespace
4533
+ LEFT JOIN (pg_collation co JOIN pg_namespace nco ON (co.collnamespace = nco.oid))
4534
+ ON a.attcollation = co.oid AND (nco.nspname, co.collname) <> ('pg_catalog', 'default')
4535
+ LEFT JOIN pg_catalog.pg_description pgd
4536
+ ON pgd.objoid = a.attrelid
4537
+ AND pgd.objsubid = a.attnum
4538
+ LEFT JOIN (pg_depend dep JOIN pg_sequence seq ON (dep.classid = 'pg_class'::regclass AND dep.objid = seq.seqrelid AND dep.deptype = 'i'))
4539
+ ON (dep.refclassid = 'pg_class'::regclass AND dep.refobjid = ${table}.oid AND dep.refobjsubid = a.attnum)
4540
+ LEFT JOIN pg_depend d ON d.objid = t.oid AND d.classid = 'pg_type'::regclass AND d.deptype = 'e'
4541
+ LEFT JOIN pg_extension ext ON ext.oid = d.refobjid
4542
+ WHERE a.attnum > 0
4543
+ AND NOT a.attisdropped
4544
+ AND ${where}
4545
+ ORDER BY a.attnum`;
4546
+ const schemasSql = `SELECT coalesce(json_agg(nspname ORDER BY nspname), '[]')
4547
+ FROM pg_catalog.pg_namespace n
4548
+ WHERE ${filterSchema("nspname")}`;
4549
+ const tablesSql = `SELECT
4550
+ nspname AS "schemaName",
4551
+ relname AS "name",
4552
+ obj_description(c.oid) AS comment,
4553
+ (SELECT coalesce(json_agg(t), '[]') FROM (${columnsSql({
4554
+ schema: "n",
4555
+ table: "c",
4556
+ where: "a.attrelid = c.oid"
4557
+ })}) t) AS "columns"
4558
+ FROM pg_class c
4559
+ JOIN pg_catalog.pg_namespace n ON n.oid = relnamespace
4560
+ WHERE (relkind = 'r' OR relkind = 'p')
4561
+ AND ${filterSchema("nspname")}
4562
+ ORDER BY relname`;
4563
+ const viewsSql = `SELECT
4564
+ nc.nspname AS "schemaName",
4565
+ c.relname AS "name",
4566
+ (
4567
+ SELECT COALESCE(json_agg(t.*), '[]')
4568
+ FROM (
4569
+ SELECT
4570
+ ns.nspname AS "schemaName",
4571
+ obj.relname AS "name"
4572
+ FROM pg_class obj
4573
+ JOIN pg_depend dep ON dep.refobjid = obj.oid
4574
+ JOIN pg_rewrite rew ON rew.oid = dep.objid
4575
+ JOIN pg_namespace ns ON ns.oid = obj.relnamespace
4576
+ WHERE rew.ev_class = c.oid AND obj.oid <> c.oid
4577
+ ) t
4578
+ ) "deps",
4579
+ right(substring(r.ev_action from ':hasRecursive \\w'), 1)::bool AS "isRecursive",
4580
+ array_to_json(c.reloptions) AS "with",
4581
+ (SELECT coalesce(json_agg(t), '[]') FROM (${columnsSql({
4582
+ schema: "nc",
4583
+ table: "c",
4584
+ where: "a.attrelid = c.oid"
4585
+ })}) t) AS "columns",
4586
+ pg_get_viewdef(c.oid) AS "sql"
4587
+ FROM pg_namespace nc
4588
+ JOIN pg_class c
4589
+ ON nc.oid = c.relnamespace
4590
+ AND c.relkind = 'v'
4591
+ AND c.relpersistence != 't'
4592
+ JOIN pg_rewrite r ON r.ev_class = c.oid
4593
+ WHERE ${filterSchema("nc.nspname")}
4594
+ ORDER BY c.relname`;
4595
+ const indexesSql = `SELECT
4596
+ n.nspname "schemaName",
4597
+ t.relname "tableName",
4598
+ ic.relname "name",
4599
+ am.amname AS "using",
4600
+ i.indisunique "unique",
4601
+ (
4602
+ SELECT json_agg(
4603
+ (
4604
+ CASE WHEN t.e = 0
4605
+ THEN jsonb_build_object('expression', pg_get_indexdef(i.indexrelid, t.i::int4, false))
4606
+ ELSE jsonb_build_object('column', (
4607
+ (
4608
+ SELECT attname
4609
+ FROM pg_catalog.pg_attribute
4610
+ WHERE attrelid = i.indrelid
4611
+ AND attnum = t.e
4612
+ )
4613
+ ))
4614
+ END
4615
+ ) || (
4616
+ CASE WHEN i.indcollation[t.i - 1] = 0
4617
+ THEN '{}'::jsonb
4618
+ ELSE (
4619
+ SELECT (
4620
+ CASE WHEN collname = 'default'
4621
+ THEN '{}'::jsonb
4622
+ ELSE jsonb_build_object('collate', collname)
4623
+ END
4624
+ )
4625
+ FROM pg_catalog.pg_collation
4626
+ WHERE oid = i.indcollation[t.i - 1]
4627
+ )
4628
+ END
4629
+ ) || (
4630
+ SELECT
4631
+ CASE WHEN opcdefault AND attoptions IS NULL
4632
+ THEN '{}'::jsonb
4633
+ ELSE jsonb_build_object(
4634
+ 'opclass', opcname || COALESCE('(' || array_to_string(attoptions, ', ') || ')', '')
4635
+ )
4636
+ END
4637
+ FROM pg_opclass
4638
+ LEFT JOIN pg_attribute
4639
+ ON attrelid = i.indexrelid
4640
+ AND attnum = t.i
4641
+ WHERE oid = i.indclass[t.i - 1]
4642
+ ) || (
4643
+ CASE WHEN i.indoption[t.i - 1] = 0
4644
+ THEN '{}'::jsonb
4645
+ ELSE jsonb_build_object(
4646
+ 'order',
4647
+ CASE
4648
+ WHEN i.indoption[t.i - 1] = 1 THEN 'DESC NULLS LAST'
4649
+ WHEN i.indoption[t.i - 1] = 2 THEN 'ASC NULLS FIRST'
4650
+ WHEN i.indoption[t.i - 1] = 3 THEN 'DESC'
4651
+ ELSE NULL
4652
+ END
4653
+ )
4654
+ END
5102
4655
  )
5103
- );
5104
- const toCodeCtx = {
5105
- t: "t",
5106
- table: ast.name,
5107
- currentSchema,
5108
- migration: true,
5109
- snakeCase: config.snakeCase
5110
- };
5111
- for (const key in ast.shape) {
5112
- const changes = pqb.toArray(ast.shape[key]);
5113
- for (const change of changes) {
5114
- if (change.type === "add" || change.type === "drop") {
5115
- if ((addTimestamps.hasAnyTimestamps || dropTimestamps.hasAnyTimestamps) && (key === "createdAt" || key === "updatedAt"))
5116
- continue;
5117
- const recreate = changes.length > 1;
5118
- const line = [
5119
- recreate ? `...t.${change.type}(t.name(${pqb.singleQuote(
5120
- change.item.data.name ?? key
5121
- )})` : `${pqb.quoteObjectKey(key, config.snakeCase)}: t.${change.type}(`
5122
- ];
5123
- const columnCode = change.item.toCode(toCodeCtx, key);
5124
- for (let i = 0; i < columnCode.length; i++) {
5125
- let part = columnCode[i];
5126
- if (recreate && !i) part = part.slice(1);
5127
- pqb.addCode(line, part);
5128
- }
5129
- pqb.addCode(line, "),");
5130
- code.push(line);
5131
- } else if (change.type === "change") {
5132
- if (!change.from.column || !change.to.column) continue;
5133
- const line = [
5134
- `${pqb.quoteObjectKey(key, config.snakeCase)}: t${change.name ? `.name(${pqb.singleQuote(change.name)})` : ""}.change(`
5135
- ];
5136
- const fromCode = change.from.column.toCode(
5137
- {
5138
- t: "t",
5139
- table: ast.name,
5140
- currentSchema,
5141
- migration: true,
5142
- snakeCase: config.snakeCase
5143
- },
5144
- key
5145
- );
5146
- for (const part of fromCode) {
5147
- pqb.addCode(line, part);
5148
- }
5149
- pqb.addCode(line, ", ");
5150
- const toCode = change.to.column.toCode(toCodeCtx, key);
5151
- for (const part of toCode) {
5152
- pqb.addCode(line, part);
5153
- }
5154
- if (change.using) {
5155
- pqb.addCode(line, ", {");
5156
- const u = [];
5157
- if (change.using.usingUp) {
5158
- u.push(`usingUp: ${pqb.rawSqlToCode(change.using.usingUp, "t")},`);
5159
- }
5160
- if (change.using.usingDown) {
5161
- u.push(
5162
- `usingDown: ${pqb.rawSqlToCode(change.using.usingDown, "t")},`
5163
- );
5164
- }
5165
- pqb.addCode(line, u);
5166
- pqb.addCode(line, "}");
5167
- }
5168
- pqb.addCode(line, "),");
5169
- code.push(line);
5170
- } else if (change.type === "rename") {
5171
- code.push([
5172
- `${pqb.quoteObjectKey(key, config.snakeCase)}: t.rename(${pqb.singleQuote(
5173
- change.name
5174
- )}),`
5175
- ]);
5176
- } else {
5177
- pqb.exhaustive(change.type);
5178
- }
4656
+ )
4657
+ FROM unnest(i.indkey[:indnkeyatts - 1]) WITH ORDINALITY AS t(e, i)
4658
+ ) "columns",
4659
+ (
4660
+ SELECT json_agg(
4661
+ (
4662
+ SELECT attname
4663
+ FROM pg_catalog.pg_attribute
4664
+ WHERE attrelid = i.indrelid
4665
+ AND attnum = j.e
4666
+ )
4667
+ )
4668
+ FROM unnest(i.indkey[indnkeyatts:]) AS j(e)
4669
+ ) AS "include",
4670
+ (to_jsonb(i.*)->'indnullsnotdistinct')::bool AS "nullsNotDistinct",
4671
+ NULLIF(pg_catalog.array_to_string(
4672
+ ic.reloptions || array(SELECT 'toast.' || x FROM pg_catalog.unnest(tc.reloptions) x),
4673
+ ', '
4674
+ ), '') AS "with",
4675
+ (
4676
+ SELECT tablespace
4677
+ FROM pg_indexes
4678
+ WHERE schemaname = n.nspname
4679
+ AND indexname = ic.relname
4680
+ ) AS tablespace,
4681
+ pg_get_expr(i.indpred, i.indrelid) AS "where",
4682
+ (
4683
+ CASE i.indisexclusion WHEN true
4684
+ THEN (
4685
+ SELECT json_agg(o.oprname)
4686
+ FROM pg_catalog.pg_constraint c, LATERAL unnest(c.conexclop) op_oid
4687
+ JOIN pg_operator o ON o.oid = op_oid
4688
+ WHERE c.conindid = ic.oid
4689
+ )
4690
+ END
4691
+ ) "exclude"
4692
+ FROM pg_index i
4693
+ JOIN pg_class t ON t.oid = i.indrelid
4694
+ JOIN pg_namespace n ON n.oid = t.relnamespace
4695
+ JOIN pg_class ic ON ic.oid = i.indexrelid
4696
+ JOIN pg_am am ON am.oid = ic.relam
4697
+ LEFT JOIN pg_catalog.pg_class tc ON (ic.reltoastrelid = tc.oid)
4698
+ WHERE ${filterSchema("n.nspname")}
4699
+ AND NOT i.indisprimary
4700
+ ORDER BY ic.relname`;
4701
+ const constraintsSql = `SELECT
4702
+ s.nspname AS "schemaName",
4703
+ t.relname AS "tableName",
4704
+ c.conname AS "name",
4705
+ (
4706
+ SELECT json_agg(ccu.column_name)
4707
+ FROM information_schema.constraint_column_usage ccu
4708
+ WHERE contype = 'p'
4709
+ AND ccu.constraint_name = c.conname
4710
+ AND ccu.table_schema = s.nspname
4711
+ ) AS "primaryKey",
4712
+ (
4713
+ SELECT
4714
+ json_build_object(
4715
+ 'foreignSchema',
4716
+ fs.nspname,
4717
+ 'foreignTable',
4718
+ ft.relname,
4719
+ 'columns',
4720
+ (
4721
+ SELECT json_agg(ccu.column_name)
4722
+ FROM information_schema.key_column_usage ccu
4723
+ WHERE ccu.constraint_name = c.conname
4724
+ AND ccu.table_schema = cs.nspname
4725
+ ),
4726
+ 'foreignColumns',
4727
+ (
4728
+ SELECT json_agg(ccu.column_name)
4729
+ FROM information_schema.constraint_column_usage ccu
4730
+ WHERE ccu.constraint_name = c.conname
4731
+ AND ccu.table_schema = cs.nspname
4732
+ ),
4733
+ 'match',
4734
+ c.confmatchtype,
4735
+ 'onUpdate',
4736
+ c.confupdtype,
4737
+ 'onDelete',
4738
+ c.confdeltype
4739
+ )
4740
+ FROM pg_class ft
4741
+ JOIN pg_catalog.pg_namespace fs ON fs.oid = ft.relnamespace
4742
+ JOIN pg_catalog.pg_namespace cs ON cs.oid = c.connamespace
4743
+ WHERE contype = 'f' AND ft.oid = confrelid
4744
+ ) AS "references",
4745
+ (
4746
+ SELECT
4747
+ CASE conbin IS NULL
4748
+ WHEN false THEN
4749
+ json_build_object(
4750
+ 'columns',
4751
+ json_agg(ccu.column_name),
4752
+ 'expression',
4753
+ pg_get_expr(conbin, conrelid)
4754
+ )
4755
+ END
4756
+ FROM information_schema.constraint_column_usage ccu
4757
+ WHERE conbin IS NOT NULL
4758
+ AND ccu.constraint_name = c.conname
4759
+ AND ccu.table_schema = s.nspname
4760
+ ) AS "check"
4761
+ FROM pg_catalog.pg_constraint c
4762
+ JOIN pg_class t ON t.oid = conrelid
4763
+ JOIN pg_catalog.pg_namespace s
4764
+ ON s.oid = t.relnamespace
4765
+ AND contype IN ('p', 'f', 'c')
4766
+ AND ${filterSchema("s.nspname")}
4767
+ ORDER BY c.conname`;
4768
+ const triggersSql = `SELECT event_object_schema AS "schemaName",
4769
+ event_object_table AS "tableName",
4770
+ trigger_schema AS "triggerSchema",
4771
+ trigger_name AS name,
4772
+ json_agg(event_manipulation) AS events,
4773
+ action_timing AS activation,
4774
+ action_condition AS condition,
4775
+ action_statement AS definition
4776
+ FROM information_schema.triggers
4777
+ WHERE ${filterSchema("event_object_schema")}
4778
+ GROUP BY event_object_schema, event_object_table, trigger_schema, trigger_name, action_timing, action_condition, action_statement
4779
+ ORDER BY trigger_name`;
4780
+ const extensionsSql = `SELECT
4781
+ nspname AS "schemaName",
4782
+ extname AS "name",
4783
+ extversion AS version
4784
+ FROM pg_extension
4785
+ JOIN pg_catalog.pg_namespace n ON n.oid = extnamespace
4786
+ AND ${filterSchema("n.nspname")}`;
4787
+ const enumsSql = `SELECT
4788
+ n.nspname as "schemaName",
4789
+ t.typname as name,
4790
+ json_agg(e.enumlabel ORDER BY e.enumsortorder) as values
4791
+ FROM pg_type t
4792
+ JOIN pg_enum e ON t.oid = e.enumtypid
4793
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
4794
+ WHERE ${filterSchema("n.nspname")}
4795
+ GROUP BY n.nspname, t.typname`;
4796
+ const domainsSql = `SELECT
4797
+ n.nspname AS "schemaName",
4798
+ d.typname AS "name",
4799
+ t.typname AS "type",
4800
+ s.nspname AS "typeSchema",
4801
+ NOT d.typnotnull AS "isNullable",
4802
+ d.typndims AS "arrayDims",
4803
+ character_maximum_length AS "maxChars",
4804
+ numeric_precision AS "numericPrecision",
4805
+ numeric_scale AS "numericScale",
4806
+ datetime_precision AS "dateTimePrecision",
4807
+ collation_name AS "collate",
4808
+ domain_default AS "default",
4809
+ (
4810
+ SELECT json_agg(pg_get_expr(conbin, conrelid))
4811
+ FROM pg_catalog.pg_constraint c
4812
+ WHERE c.contypid = d.oid
4813
+ ) AS "checks"
4814
+ FROM pg_catalog.pg_type d
4815
+ JOIN pg_catalog.pg_namespace n ON n.oid = d.typnamespace
4816
+ JOIN information_schema.domains i
4817
+ ON i.domain_schema = nspname
4818
+ AND i.domain_name = d.typname
4819
+ JOIN pg_catalog.pg_type t
4820
+ ON (
4821
+ CASE WHEN d.typcategory = 'A'
4822
+ THEN t.typarray
4823
+ ELSE t.oid
4824
+ END
4825
+ ) = d.typbasetype
4826
+ JOIN pg_catalog.pg_namespace s ON s.oid = t.typnamespace
4827
+ WHERE d.typtype = 'd' AND ${filterSchema("n.nspname")}`;
4828
+ const collationsSql = (version) => `SELECT
4829
+ nspname "schemaName",
4830
+ collname "name",
4831
+ CASE WHEN collprovider = 'i' THEN 'icu' WHEN collprovider = 'c' THEN 'libc' ELSE collprovider::text END "provider",
4832
+ collisdeterministic "deterministic",
4833
+ collcollate "lcCollate",
4834
+ collctype "lcCType",
4835
+ ${version >= 17 ? "colllocale" : "colliculocale"} "locale",
4836
+ collversion "version"
4837
+ FROM pg_collation
4838
+ JOIN pg_namespace n on pg_collation.collnamespace = n.oid
4839
+ WHERE ${filterSchema("n.nspname")}`;
4840
+ const sql = (version) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(
4841
+ tablesSql,
4842
+ "tables"
4843
+ )}, ${jsonAgg(viewsSql, "views")}, ${jsonAgg(
4844
+ indexesSql,
4845
+ "indexes"
4846
+ )}, ${jsonAgg(constraintsSql, "constraints")}, ${jsonAgg(
4847
+ triggersSql,
4848
+ "triggers"
4849
+ )}, ${jsonAgg(extensionsSql, "extensions")}, ${jsonAgg(
4850
+ enumsSql,
4851
+ "enums"
4852
+ )}, ${jsonAgg(domainsSql, "domains")}, ${jsonAgg(
4853
+ collationsSql(version),
4854
+ "collations"
4855
+ )}`;
4856
+ async function introspectDbSchema(db) {
4857
+ const {
4858
+ rows: [{ version: versionString }]
4859
+ } = await db.query("SELECT version()");
4860
+ const version = +versionString.match(/\d+/)[0];
4861
+ const data = await db.query(sql(version));
4862
+ const result = data.rows[0];
4863
+ for (const domain of result.domains) {
4864
+ domain.checks = domain.checks?.filter((check) => check);
4865
+ nullsToUndefined(domain);
4866
+ }
4867
+ for (const table of result.tables) {
4868
+ for (const column of table.columns) {
4869
+ nullsToUndefined(column);
4870
+ if (column.identity) nullsToUndefined(column.identity);
4871
+ if (column.compression) {
4872
+ column.compression = column.compression === "p" ? "pglz" : "lz4";
5179
4873
  }
5180
4874
  }
5181
- for (const key of ["drop", "add"]) {
5182
- const timestamps = key === "add" ? addTimestamps : dropTimestamps;
5183
- if (timestamps.hasAnyTimestamps) {
5184
- pqb.addCode(code, [`...t.${key}(${timestampsToCode(timestamps)}),`]);
5185
- }
5186
- const { primaryKey, indexes, excludes, constraints } = ast[key];
5187
- if (primaryKey) {
5188
- pqb.addCode(code, [
5189
- `...t.${key}(${pqb.primaryKeyInnerToCode(primaryKey, "t")}),`
5190
- ]);
5191
- }
5192
- if (indexes) {
5193
- for (const item of indexes) {
5194
- pqb.addCode(code, [`...t.${key}(`, pqb.indexInnerToCode(item, "t"), "),"]);
4875
+ }
4876
+ const indexes = [];
4877
+ const excludes = [];
4878
+ for (const index of result.indexes) {
4879
+ nullsToUndefined(index);
4880
+ for (const column of index.columns) {
4881
+ if (!("expression" in column)) continue;
4882
+ const s = column.expression;
4883
+ const columnR = `"?\\w+"?`;
4884
+ const langR = `(${columnR}|'\\w+'::regconfig)`;
4885
+ const firstColumnR = `[(]*${columnR}`;
4886
+ const concatR = `\\|\\|`;
4887
+ const restColumnR = ` ${concatR} ' '::text\\) ${concatR} ${columnR}\\)`;
4888
+ const coalesceColumn = `COALESCE\\(${columnR}, ''::text\\)`;
4889
+ const tsVectorR = `to_tsvector\\(${langR}, (${firstColumnR}|${restColumnR}|${coalesceColumn})+\\)`;
4890
+ const weightR = `'\\w'::"char"`;
4891
+ const setWeightR = `setweight\\(${tsVectorR}, ${weightR}\\)`;
4892
+ const setWeightOrTsVectorR = `(${setWeightR}|${tsVectorR})`;
4893
+ const match = s.match(
4894
+ new RegExp(`^([\\(]*${setWeightOrTsVectorR}[\\)]*( ${concatR} )?)+$`)
4895
+ );
4896
+ if (!match) continue;
4897
+ let language;
4898
+ let languageColumn;
4899
+ const tokens = match[0].match(
4900
+ new RegExp(
4901
+ `setweight\\(|to_tsvector\\(${langR}|[:']?${columnR}\\(?`,
4902
+ "g"
4903
+ )
4904
+ )?.reduce((acc, token) => {
4905
+ if (token === "setweight(" || token === "COALESCE(" || token[0] === ":")
4906
+ return acc;
4907
+ if (token.startsWith("to_tsvector(")) {
4908
+ if (token[12] === "'") {
4909
+ language = token.slice(13, -12);
4910
+ } else {
4911
+ languageColumn = token.slice(12);
4912
+ }
4913
+ } else if (token[0] === "'") {
4914
+ acc.push({ kind: "weight", value: token[1] });
4915
+ } else {
4916
+ if (token[0] === '"') token = token.slice(1, -1);
4917
+ acc.push({ kind: "column", value: token });
5195
4918
  }
5196
- }
5197
- if (excludes) {
5198
- for (const item of excludes) {
5199
- pqb.addCode(code, [`...t.${key}(`, pqb.excludeInnerToCode(item, "t"), "),"]);
4919
+ return acc;
4920
+ }, []);
4921
+ if (!tokens) continue;
4922
+ index.language = language;
4923
+ index.languageColumn = languageColumn;
4924
+ index.tsVector = true;
4925
+ index.columns = [];
4926
+ for (const token of tokens) {
4927
+ if (token.kind === "column") {
4928
+ index.columns.push({
4929
+ column: token.value
4930
+ });
4931
+ } else if (token.kind === "weight") {
4932
+ index.columns[index.columns.length - 1].weight = token.value;
5200
4933
  }
5201
4934
  }
5202
- if (constraints) {
5203
- for (const item of constraints) {
5204
- pqb.addCode(code, [
5205
- `...t.${key}(`,
5206
- pqb.constraintInnerToCode(item, "t", true),
5207
- "),"
5208
- ]);
5209
- }
4935
+ }
4936
+ (index.exclude ? excludes : indexes).push(index);
4937
+ }
4938
+ result.indexes = indexes;
4939
+ result.excludes = excludes;
4940
+ return result;
4941
+ }
4942
+ const nullsToUndefined = (obj) => {
4943
+ for (const key in obj) {
4944
+ if (obj[key] === null)
4945
+ obj[key] = void 0;
4946
+ }
4947
+ };
4948
+
4949
+ const matchMap = {
4950
+ s: void 0,
4951
+ f: "FULL",
4952
+ p: "PARTIAL"
4953
+ };
4954
+ const fkeyActionMap = {
4955
+ a: void 0,
4956
+ // default
4957
+ r: "RESTRICT",
4958
+ c: "CASCADE",
4959
+ n: "SET NULL",
4960
+ d: "SET DEFAULT"
4961
+ };
4962
+ const makeStructureToAstCtx = (config, currentSchema) => ({
4963
+ snakeCase: config.snakeCase,
4964
+ unsupportedTypes: {},
4965
+ currentSchema,
4966
+ columnSchemaConfig: config.schemaConfig,
4967
+ columnsByType: pqb.makeColumnsByType(config.schemaConfig)
4968
+ });
4969
+ const structureToAst = async (ctx, adapter, config) => {
4970
+ const ast = [];
4971
+ const data = await introspectDbSchema(adapter);
4972
+ for (const name of data.schemas) {
4973
+ if (name === "public") continue;
4974
+ ast.push({
4975
+ type: "schema",
4976
+ action: "create",
4977
+ name
4978
+ });
4979
+ }
4980
+ for (const it of data.collations) {
4981
+ ast.push({
4982
+ type: "collation",
4983
+ action: "create",
4984
+ ...it,
4985
+ schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName
4986
+ });
4987
+ }
4988
+ const domains = makeDomainsMap(ctx, data);
4989
+ const { schema: migrationsSchema = "public", table: migrationsTable } = getMigrationsSchemaAndTable(config);
4990
+ for (const table of data.tables) {
4991
+ if (table.name === migrationsTable && table.schemaName === migrationsSchema)
4992
+ continue;
4993
+ ast.push(tableToAst(ctx, data, table, "create", domains));
4994
+ }
4995
+ for (const it of data.extensions) {
4996
+ ast.push({
4997
+ type: "extension",
4998
+ action: "create",
4999
+ name: it.name,
5000
+ schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName,
5001
+ version: it.version
5002
+ });
5003
+ }
5004
+ for (const it of data.enums) {
5005
+ ast.push({
5006
+ type: "enum",
5007
+ action: "create",
5008
+ name: it.name,
5009
+ schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName,
5010
+ values: it.values
5011
+ });
5012
+ }
5013
+ for (const it of data.domains) {
5014
+ ast.push({
5015
+ type: "domain",
5016
+ action: "create",
5017
+ schema: it.schemaName === ctx.currentSchema ? void 0 : it.schemaName,
5018
+ name: it.name,
5019
+ baseType: domains[`${it.schemaName}.${it.name}`]
5020
+ });
5021
+ }
5022
+ for (const table of data.tables) {
5023
+ for (const fkey of data.constraints) {
5024
+ if (fkey.references && fkey.tableName === table.name && fkey.schemaName === table.schemaName && checkIfIsOuterRecursiveFkey(data, table, fkey.references)) {
5025
+ ast.push({
5026
+ ...constraintToAst(ctx, fkey),
5027
+ type: "constraint",
5028
+ action: "create",
5029
+ tableSchema: table.schemaName === ctx.currentSchema ? void 0 : table.schemaName,
5030
+ tableName: fkey.tableName
5031
+ });
5210
5032
  }
5211
5033
  }
5212
- if (ast.comment !== void 0) {
5213
- pqb.addCode(code, "}),");
5214
- pqb.addCode(result, ");");
5215
- } else {
5216
- pqb.addCode(result, "}));");
5034
+ }
5035
+ for (const view of data.views) {
5036
+ ast.push(viewToAst(ctx, data, domains, view));
5037
+ }
5038
+ return ast;
5039
+ };
5040
+ const makeDomainsMap = (ctx, data) => {
5041
+ const domains = {};
5042
+ for (const it of data.domains) {
5043
+ const column = instantiateDbColumn(ctx, data, domains, {
5044
+ schemaName: it.schemaName,
5045
+ name: it.name,
5046
+ type: it.type,
5047
+ typeSchema: it.typeSchema,
5048
+ arrayDims: it.arrayDims,
5049
+ tableName: "",
5050
+ isNullable: it.isNullable,
5051
+ collate: it.collate,
5052
+ default: it.default,
5053
+ typmod: -1
5054
+ });
5055
+ if (it.checks) {
5056
+ column.data.checks = it.checks.map((check) => ({
5057
+ sql: new pqb.RawSql([[check]])
5058
+ }));
5059
+ }
5060
+ domains[`${it.schemaName}.${it.name}`] = column;
5061
+ }
5062
+ return domains;
5063
+ };
5064
+ const getDbColumnIsSerial = (item) => {
5065
+ if (item.type === "int2" || item.type === "int4" || item.type === "int8") {
5066
+ const { default: def, schemaName, tableName, name } = item;
5067
+ const seq = `${tableName}_${name}_seq`;
5068
+ if (def && (def === `nextval(${pqb.singleQuote(`${seq}`)}::regclass)` || def === `nextval(${pqb.singleQuote(`"${seq}"`)}::regclass)` || def === `nextval(${pqb.singleQuote(`${schemaName}.${seq}`)}::regclass)` || def === `nextval(${pqb.singleQuote(`"${schemaName}".${seq}`)}::regclass)` || def === `nextval(${pqb.singleQuote(`${schemaName}."${seq}"`)}::regclass)` || def === `nextval(${pqb.singleQuote(`"${schemaName}"."${seq}"`)}::regclass)`)) {
5069
+ return true;
5217
5070
  }
5218
- return result;
5219
- },
5220
- renameType(ast, _, currentSchema) {
5221
- const code = [];
5222
- const kind = ast.kind === "TABLE" ? "Table" : "Type";
5223
- if (ast.from === ast.to) {
5224
- pqb.addCode(
5225
- code,
5226
- `await db.change${kind}Schema(${pqb.singleQuote(ast.to)}, ${pqb.singleQuote(
5227
- ast.fromSchema ?? currentSchema
5228
- )}, ${pqb.singleQuote(ast.toSchema ?? currentSchema)});`
5229
- );
5071
+ }
5072
+ return false;
5073
+ };
5074
+ const instantiateDbColumn = (ctx, data, domains, dbColumn) => {
5075
+ var _a, _b;
5076
+ const isSerial = getDbColumnIsSerial(dbColumn);
5077
+ if (isSerial) {
5078
+ dbColumn = { ...dbColumn, default: void 0 };
5079
+ }
5080
+ let column;
5081
+ const col = instantiateColumnByDbType(ctx, dbColumn.type, isSerial, dbColumn);
5082
+ if (col) {
5083
+ column = col;
5084
+ } else {
5085
+ const { typeSchema, type: typeName } = dbColumn;
5086
+ const typeId = typeSchema === "pg_catalog" ? typeName : `${typeSchema}.${typeName}`;
5087
+ const domainColumn = domains[typeId];
5088
+ if (domainColumn) {
5089
+ column = new pqb.DomainColumn(
5090
+ ctx.columnSchemaConfig,
5091
+ typeName,
5092
+ typeSchema,
5093
+ dbColumn.extension
5094
+ ).as(domainColumn);
5230
5095
  } else {
5231
- pqb.addCode(
5232
- code,
5233
- `await db.rename${kind}(${quoteSchemaTable({
5234
- schema: ast.fromSchema === currentSchema ? void 0 : ast.fromSchema,
5235
- name: ast.from
5236
- })}, ${quoteSchemaTable({
5237
- schema: ast.toSchema === currentSchema ? void 0 : ast.toSchema,
5238
- name: ast.to
5239
- })});`
5096
+ const enumType = data.enums.find(
5097
+ (x) => x.name === typeName && x.schemaName === typeSchema
5240
5098
  );
5099
+ if (enumType) {
5100
+ column = new pqb.EnumColumn(
5101
+ ctx.columnSchemaConfig,
5102
+ typeSchema === ctx.currentSchema ? typeName : typeId,
5103
+ enumType.values,
5104
+ ctx.columnSchemaConfig.type
5105
+ );
5106
+ } else {
5107
+ column = new pqb.CustomTypeColumn(
5108
+ ctx.columnSchemaConfig,
5109
+ typeName,
5110
+ typeSchema === "pg_catalog" ? void 0 : typeSchema,
5111
+ dbColumn.extension
5112
+ );
5113
+ ((_a = ctx.unsupportedTypes)[_b = dbColumn.type] ?? (_a[_b] = [])).push(
5114
+ `${dbColumn.schemaName}${dbColumn.tableName ? `.${dbColumn.tableName}` : ""}.${dbColumn.name}`
5115
+ );
5116
+ }
5117
+ pqb.assignDbDataToColumn(column, dbColumn);
5241
5118
  }
5242
- return code;
5243
- },
5244
- schema(ast) {
5245
- return `await db.${ast.action === "create" ? "createSchema" : "dropSchema"}(${pqb.singleQuote(ast.name)});`;
5246
- },
5247
- renameSchema(ast) {
5248
- return `await db.renameSchema(${pqb.singleQuote(ast.from)}, ${pqb.singleQuote(
5249
- ast.to
5250
- )});`;
5251
- },
5252
- extension(ast) {
5253
- const code = [
5254
- `await db.${ast.action}Extension(${quoteSchemaTable(ast)}`
5255
- ];
5256
- if (ast.version) {
5257
- pqb.addCode(code, ", {");
5258
- code.push([`version: ${pqb.singleQuote(ast.version)},`], "}");
5119
+ }
5120
+ column.data.name = void 0;
5121
+ if (!column.data.isNullable) column.data.isNullable = void 0;
5122
+ if (dbColumn.arrayDims) {
5123
+ const arr = new pqb.ArrayColumn(
5124
+ ctx.columnSchemaConfig,
5125
+ column,
5126
+ ctx.columnSchemaConfig.type
5127
+ );
5128
+ arr.data.isNullable = dbColumn.isNullable;
5129
+ arr.data.arrayDims = dbColumn.arrayDims;
5130
+ column = arr;
5131
+ }
5132
+ return column;
5133
+ };
5134
+ const instantiateColumnByDbType = (ctx, type, isSerial, params) => {
5135
+ let columnFn = ctx.columnsByType[!isSerial ? type : type === "int2" ? "smallserial" : type === "int4" ? "serial" : "bigserial"];
5136
+ if (!columnFn && params.extension === "postgis" && type === "geography" && pqb.PostgisGeographyPointColumn.isDefaultPoint(params.typmod)) {
5137
+ columnFn = ctx.columnsByType.geographyDefaultPoint;
5138
+ }
5139
+ return columnFn ? pqb.assignDbDataToColumn(columnFn(), params) : void 0;
5140
+ };
5141
+ const tableToAst = (ctx, data, table, action, domains) => {
5142
+ const { schemaName, name: tableName } = table;
5143
+ const tableData = getDbStructureTableData(data, table);
5144
+ const { primaryKey, constraints } = tableData;
5145
+ return {
5146
+ type: "table",
5147
+ action,
5148
+ schema: schemaName === ctx.currentSchema ? void 0 : schemaName,
5149
+ comment: table.comment,
5150
+ name: tableName,
5151
+ shape: makeDbStructureColumnsShape(ctx, data, domains, table, tableData),
5152
+ noPrimaryKey: tableData.primaryKey ? "error" : "ignore",
5153
+ primaryKey: primaryKey && primaryKey.columns.length > 1 ? { ...primaryKey, columns: primaryKey.columns.map(pqb.toCamelCase) } : void 0,
5154
+ indexes: indexesOrExcludesToAst(
5155
+ tableName,
5156
+ tableData,
5157
+ "indexes"
5158
+ ),
5159
+ excludes: indexesOrExcludesToAst(
5160
+ tableName,
5161
+ tableData,
5162
+ "excludes"
5163
+ ),
5164
+ constraints: constraints.reduce((acc, it) => {
5165
+ if (it.check && it.references || it.check && it.check.columns?.length !== 1 || it.references && it.references.columns.length !== 1 && !checkIfIsOuterRecursiveFkey(data, table, it.references)) {
5166
+ acc.push(dbConstraintToTableConstraint(ctx, table, it));
5167
+ }
5168
+ return acc;
5169
+ }, [])
5170
+ };
5171
+ };
5172
+ const indexesOrExcludesToAst = (tableName, tableData, key) => {
5173
+ return tableData[key].reduce((acc, item) => {
5174
+ if (item.columns.length > 1 || item.columns.some((it) => "expression" in it)) {
5175
+ const options = makeIndexOrExcludeOptions(tableName, item, key);
5176
+ acc.push({
5177
+ columns: item.columns.map((it, i) => ({
5178
+ with: "exclude" in item && item.exclude ? item.exclude[i] : void 0,
5179
+ ..."expression" in it ? { expression: it.expression } : { column: pqb.toCamelCase(it.column) },
5180
+ collate: it.collate,
5181
+ opclass: it.opclass,
5182
+ order: it.order
5183
+ })),
5184
+ options: {
5185
+ ...options,
5186
+ include: item.include?.map(pqb.toCamelCase)
5187
+ }
5188
+ });
5259
5189
  }
5260
- pqb.addCode(code, ");");
5261
- return code;
5262
- },
5263
- enum(ast, _, currentSchema) {
5264
- return `await db.${ast.action === "create" ? "createEnum" : "dropEnum"}(${quoteSchemaTable(ast, currentSchema)}, [${ast.values.map(pqb.singleQuote).join(", ")}]);`;
5265
- },
5266
- enumValues(ast, _, currentSchema) {
5267
- return `await db.${ast.action}EnumValues(${quoteSchemaTable(
5268
- ast,
5269
- currentSchema
5270
- )}, [${ast.values.map(pqb.singleQuote).join(", ")}]);`;
5271
- },
5272
- renameEnumValues(ast, config, currentSchema) {
5273
- return `await db.renameEnumValues(${quoteSchemaTable(
5274
- ast,
5275
- currentSchema
5276
- )}, { ${Object.entries(ast.values).map(
5277
- ([from, to]) => `${pqb.quoteObjectKey(from, config.snakeCase)}: ${pqb.singleQuote(to)}`
5278
- ).join(", ")} });`;
5279
- },
5280
- changeEnumValues(ast, _, currentSchema) {
5281
- return `await db.changeEnumValues(${quoteSchemaTable(
5282
- ast,
5283
- currentSchema
5284
- )}, [${ast.fromValues.map(pqb.singleQuote).join(", ")}], [${ast.toValues.map(pqb.singleQuote).join(", ")}]);`;
5285
- },
5286
- domain(ast, _, currentSchema) {
5287
- return `await db.${ast.action}Domain(${quoteSchemaTable(
5288
- ast
5289
- )}, (t) => ${ast.baseType.toCode(
5290
- { t: "t", table: ast.name, currentSchema },
5291
- ast.baseType.data.name ?? ""
5292
- )});`;
5293
- },
5294
- collation(ast) {
5295
- const params = [];
5296
- if (ast.locale) params.push(`locale: '${ast.locale}',`);
5297
- if (ast.lcCollate) params.push(`lcCollate: '${ast.lcCollate}',`);
5298
- if (ast.lcCType) params.push(`lcCType: '${ast.lcCType}',`);
5299
- if (ast.provider) params.push(`provider: '${ast.provider}',`);
5300
- if (ast.deterministic) params.push(`deterministic: ${ast.deterministic},`);
5301
- if (ast.version) params.push(`version: '${ast.version}',`);
5302
- return [
5303
- `await db.createCollation(${quoteSchemaTable(ast)}, {`,
5304
- params,
5305
- "});"
5306
- ];
5307
- },
5308
- constraint(ast) {
5309
- const table = quoteSchemaTable({
5310
- schema: ast.tableSchema,
5311
- name: ast.tableName
5312
- });
5313
- if (ast.references) {
5314
- return [
5315
- `await db.addForeignKey(`,
5316
- [`${table},`, ...pqb.referencesArgsToCode(ast.references, ast.name, true)],
5317
- ");"
5318
- ];
5190
+ return acc;
5191
+ }, []);
5192
+ };
5193
+ const getDbStructureTableData = (data, { name, schemaName }) => {
5194
+ const filterFn = filterByTableSchema(name, schemaName);
5195
+ const constraints = data.constraints.filter(filterFn);
5196
+ const primaryKey = constraints.find((c) => c.primaryKey);
5197
+ return {
5198
+ primaryKey: primaryKey?.primaryKey ? {
5199
+ columns: primaryKey.primaryKey,
5200
+ name: primaryKey.name === `${name}_pkey` ? void 0 : primaryKey.name
5201
+ } : void 0,
5202
+ indexes: data.indexes.filter(filterFn),
5203
+ excludes: data.excludes.filter(filterFn),
5204
+ constraints
5205
+ };
5206
+ };
5207
+ const filterByTableSchema = (tableName, schemaName) => (x) => x.tableName === tableName && x.schemaName === schemaName;
5208
+ const constraintToAst = (ctx, item) => {
5209
+ const result = {};
5210
+ const { references, check } = item;
5211
+ if (references) {
5212
+ const options = {};
5213
+ result.references = {
5214
+ columns: references.columns,
5215
+ fnOrTable: getReferencesTable(ctx, references),
5216
+ foreignColumns: references.foreignColumns,
5217
+ options
5218
+ };
5219
+ const match = matchMap[references.match];
5220
+ if (match) options.match = match;
5221
+ const onUpdate = fkeyActionMap[references.onUpdate];
5222
+ if (onUpdate) options.onUpdate = onUpdate;
5223
+ const onDelete = fkeyActionMap[references.onDelete];
5224
+ if (onDelete) options.onDelete = onDelete;
5225
+ }
5226
+ if (check) {
5227
+ result.check = pqb.raw({ raw: check.expression });
5228
+ }
5229
+ if (item.name && item.name !== getConstraintName(item.tableName, result, ctx.snakeCase)) {
5230
+ result.name = item.name;
5231
+ if (result.references?.options) {
5232
+ result.references.options.name = item.name;
5319
5233
  }
5320
- const check = ast.check;
5321
- return [
5322
- `await db.addCheck(${table}, ${pqb.rawSqlToCode(check, "t")}${ast.name ? `, ${pqb.singleQuote(ast.name)}` : ""});`
5323
- ];
5324
- },
5325
- renameTableItem(ast) {
5326
- return [
5327
- `await db.rename${ast.kind === "INDEX" ? "Index" : "Constraint"}(${quoteSchemaTable({
5328
- schema: ast.tableSchema,
5329
- name: ast.tableName
5330
- })}, ${pqb.singleQuote(ast.from)}, ${pqb.singleQuote(ast.to)});`
5331
- ];
5332
- },
5333
- view(ast) {
5334
- const code = [`await db.createView(${quoteSchemaTable(ast)}`];
5335
- const options = [];
5336
- if (ast.options.recursive) options.push("recursive: true,");
5337
- const w = ast.options.with;
5338
- if (w?.checkOption) options.push(`checkOption: '${w.checkOption}',`);
5339
- if (w?.securityBarrier)
5340
- options.push(`securityBarrier: ${w.securityBarrier},`);
5341
- if (w?.securityInvoker)
5342
- options.push(`securityInvoker: ${w.securityInvoker},`);
5343
- if (options.length) {
5344
- pqb.addCode(code, ", {");
5345
- code.push(options, "}");
5234
+ }
5235
+ return result;
5236
+ };
5237
+ const getReferencesTable = (ctx, references) => {
5238
+ return references.foreignSchema !== ctx.currentSchema ? `${references.foreignSchema}.${references.foreignTable}` : references.foreignTable;
5239
+ };
5240
+ const isColumnCheck = (it) => {
5241
+ return !it.references && it.check?.columns?.length === 1;
5242
+ };
5243
+ const viewToAst = (ctx, data, domains, view) => {
5244
+ const shape = makeDbStructureColumnsShape(ctx, data, domains, view);
5245
+ const options = {};
5246
+ if (view.isRecursive) options.recursive = true;
5247
+ if (view.with) {
5248
+ const withOptions = {};
5249
+ options.with = withOptions;
5250
+ for (const pair of view.with) {
5251
+ const [key, value] = pair.split("=");
5252
+ withOptions[pqb.toCamelCase(key)] = value === "true" ? true : value === "false" ? false : value;
5346
5253
  }
5347
- pqb.addCode(code, ", ");
5348
- if (!ast.sql._values) {
5349
- const raw = ast.sql._sql;
5350
- let sql;
5351
- if (typeof raw === "string") {
5352
- sql = raw;
5353
- } else {
5354
- sql = "";
5355
- const parts = raw[0];
5356
- const last = parts.length - 1;
5357
- for (let i = 0; i < last; i++) {
5358
- sql += parts[i] + `\${${raw[i + 1]}}`;
5359
- }
5360
- sql += parts[last];
5254
+ }
5255
+ return {
5256
+ type: "view",
5257
+ action: "create",
5258
+ schema: view.schemaName === ctx.currentSchema ? void 0 : view.schemaName,
5259
+ name: view.name,
5260
+ shape,
5261
+ sql: pqb.raw({ raw: view.sql }),
5262
+ options,
5263
+ deps: view.deps
5264
+ };
5265
+ };
5266
+ const makeDbStructureColumnsShape = (ctx, data, domains, table, tableData) => {
5267
+ const shape = {};
5268
+ const checks = tableData ? getDbTableColumnsChecks(tableData) : void 0;
5269
+ for (const item of table.columns) {
5270
+ const [key, column] = dbColumnToAst(
5271
+ ctx,
5272
+ data,
5273
+ domains,
5274
+ table.name,
5275
+ item,
5276
+ table,
5277
+ tableData,
5278
+ checks
5279
+ );
5280
+ shape[key] = column;
5281
+ }
5282
+ return shape;
5283
+ };
5284
+ const getDbTableColumnsChecks = (tableData) => tableData.constraints.reduce((acc, item) => {
5285
+ var _a;
5286
+ if (isColumnCheck(item)) {
5287
+ (acc[_a = item.check.columns[0]] ?? (acc[_a] = [])).push(item.check.expression);
5288
+ }
5289
+ return acc;
5290
+ }, {});
5291
+ const dbColumnToAst = (ctx, data, domains, tableName, item, table, tableData, checks) => {
5292
+ let column = instantiateDbColumn(ctx, data, domains, item);
5293
+ column.data.name = item.name;
5294
+ if (item.identity) {
5295
+ column.data.identity = item.identity;
5296
+ if (!item.identity.always) delete column.data.identity?.always;
5297
+ }
5298
+ if (tableData?.primaryKey?.columns?.length === 1 && tableData?.primaryKey?.columns[0] === item.name) {
5299
+ column = column.primaryKey();
5300
+ }
5301
+ collectColumnIndexesOrExcludes(item, column, tableName, tableData, "indexes");
5302
+ collectColumnIndexesOrExcludes(
5303
+ item,
5304
+ column,
5305
+ tableName,
5306
+ tableData,
5307
+ "excludes"
5308
+ );
5309
+ if (table) {
5310
+ for (const it of data.constraints) {
5311
+ if (it.tableName !== table.name || it.schemaName !== table.schemaName || it.check || it.references?.columns.length !== 1 || it.references.columns[0] !== item.name || checkIfIsOuterRecursiveFkey(data, table, it.references)) {
5312
+ continue;
5361
5313
  }
5362
- pqb.addCode(code, pqb.backtickQuote(sql));
5363
- } else {
5364
- pqb.addCode(code, pqb.rawSqlToCode(ast.sql, "db"));
5314
+ const c = dbConstraintToTableConstraint(ctx, table, it);
5315
+ column = column.foreignKey(
5316
+ c.references?.fnOrTable,
5317
+ it.references.foreignColumns[0],
5318
+ c.references?.options
5319
+ );
5365
5320
  }
5366
- pqb.addCode(code, ");");
5367
- return code;
5368
5321
  }
5322
+ const columnChecks = checks?.[item.name];
5323
+ if (columnChecks) {
5324
+ column.data.checks = columnChecks.map((check) => ({
5325
+ sql: new pqb.RawSql([[check]])
5326
+ }));
5327
+ }
5328
+ const camelCaseName = pqb.toCamelCase(item.name);
5329
+ if (ctx.snakeCase) {
5330
+ const snakeCaseName = pqb.toSnakeCase(camelCaseName);
5331
+ if (snakeCaseName !== item.name) column.data.name = item.name;
5332
+ } else if (camelCaseName !== item.name) {
5333
+ column.data.name = item.name;
5334
+ }
5335
+ return [camelCaseName, column];
5369
5336
  };
5370
- const isTimestamp = (column, type) => {
5371
- if (!column) return false;
5372
- const { default: def } = column.data;
5373
- return Boolean(
5374
- column instanceof type && !column.data.isNullable && def && typeof def === "object" && pqb.isRawSQL(def) && (typeof def._sql === "object" ? def._sql[0][0] : def._sql) === "now()"
5337
+ const collectColumnIndexesOrExcludes = (dbColumn, column, tableName, tableData, key) => {
5338
+ var _a;
5339
+ const items = tableData?.[key];
5340
+ if (!items) return;
5341
+ const columnItems = items.filter(
5342
+ (it) => it.columns.length === 1 && "column" in it.columns[0] && it.columns[0].column === dbColumn.name
5375
5343
  );
5344
+ for (const item of columnItems) {
5345
+ const columnOptions = item.columns[0];
5346
+ const { name, ...itemOptions } = makeIndexOrExcludeOptions(
5347
+ tableName,
5348
+ item,
5349
+ key
5350
+ );
5351
+ ((_a = column.data)[key] ?? (_a[key] = [])).push({
5352
+ with: "exclude" in item && item.exclude ? item.exclude[0] : void 0,
5353
+ options: {
5354
+ name,
5355
+ collate: columnOptions.collate,
5356
+ opclass: columnOptions.opclass,
5357
+ order: columnOptions.order,
5358
+ ...itemOptions
5359
+ }
5360
+ });
5361
+ }
5376
5362
  };
5377
- const getHasTimestamps = (createdAt, updatedAt) => {
5378
- const timestamps = getTimestampsInfo(createdAt, updatedAt, pqb.TimestampTZColumn);
5379
- const timestampsNoTZ = getTimestampsInfo(
5380
- createdAt,
5381
- updatedAt,
5382
- pqb.TimestampColumn
5383
- );
5384
- return {
5385
- hasTZTimestamps: timestamps,
5386
- hasAnyTimestamps: timestamps || timestampsNoTZ
5363
+ const dbConstraintToTableConstraint = (ctx, table, item) => {
5364
+ const { references, check } = item;
5365
+ const constraint = {
5366
+ references: references ? {
5367
+ columns: references.columns,
5368
+ fnOrTable: getReferencesTable(ctx, references),
5369
+ foreignColumns: references.foreignColumns,
5370
+ options: {
5371
+ match: matchMap[references.match],
5372
+ onUpdate: fkeyActionMap[references.onUpdate],
5373
+ onDelete: fkeyActionMap[references.onDelete]
5374
+ }
5375
+ } : void 0,
5376
+ check: check ? pqb.raw({ raw: check.expression }) : void 0
5387
5377
  };
5378
+ const name = item.name && item.name !== getConstraintName(table.name, constraint, ctx.snakeCase) ? item.name : void 0;
5379
+ if (name) {
5380
+ constraint.name = name;
5381
+ if (constraint.references?.options) {
5382
+ constraint.references.options.name = name;
5383
+ }
5384
+ }
5385
+ return constraint;
5388
5386
  };
5389
- const getTimestampsInfo = (createdAt, updatedAt, type) => {
5390
- return isTimestamp(createdAt, type) && isTimestamp(updatedAt, type) && (!createdAt?.data.name || createdAt?.data.name === "created_at") && (!updatedAt?.data.name || updatedAt?.data.name === "updated_at");
5387
+ const makeIndexOrExcludeOptions = (tableName, index, key) => {
5388
+ return {
5389
+ name: index.name !== (key === "indexes" ? getIndexName : getExcludeName)(
5390
+ tableName,
5391
+ index.columns
5392
+ ) ? index.name : void 0,
5393
+ using: index.using === "btree" ? void 0 : index.using,
5394
+ unique: index.unique || void 0,
5395
+ include: index.include,
5396
+ nullsNotDistinct: index.nullsNotDistinct || void 0,
5397
+ with: index.with,
5398
+ tablespace: index.tablespace,
5399
+ where: index.where
5400
+ };
5391
5401
  };
5392
- const timestampsToCode = ({ hasTZTimestamps }) => {
5393
- const key = hasTZTimestamps ? "timestamps" : "timestampsNoTZ";
5394
- return `t.${key}()`;
5402
+ const checkIfIsOuterRecursiveFkey = (data, table, references) => {
5403
+ const referencesId = `${references.foreignSchema}.${references.foreignTable}`;
5404
+ const tableId = `${table.schemaName}.${table.name}`;
5405
+ for (const other of data.tables) {
5406
+ const id = `${other.schemaName}.${other.name}`;
5407
+ if (referencesId === id) {
5408
+ for (const c of data.constraints) {
5409
+ if (c.tableName === other.name && c.schemaName === other.schemaName && c.references?.foreignTable === table.name && c.references.foreignSchema === table.schemaName && tableId < id) {
5410
+ return true;
5411
+ }
5412
+ }
5413
+ break;
5414
+ }
5415
+ }
5416
+ return false;
5395
5417
  };
5396
5418
 
5397
5419
  const pullDbStructure = async (adapter, config) => {
5398
- const currentSchema = adapter.schema || "public";
5420
+ const currentSchema = adapter.searchPath || "public";
5399
5421
  const ctx = makeStructureToAstCtx(config, currentSchema);
5400
5422
  const ast = await structureToAst(ctx, adapter, config);
5401
5423
  const result = astToMigration(currentSchema, config, ast);
@@ -5898,6 +5920,7 @@ exports.getDbStructureTableData = getDbStructureTableData;
5898
5920
  exports.getDbTableColumnsChecks = getDbTableColumnsChecks;
5899
5921
  exports.getExcludeName = getExcludeName;
5900
5922
  exports.getIndexName = getIndexName;
5923
+ exports.getMigrationsSchemaAndTable = getMigrationsSchemaAndTable;
5901
5924
  exports.getSchemaAndTableFromName = getSchemaAndTableFromName;
5902
5925
  exports.instantiateDbColumn = instantiateDbColumn;
5903
5926
  exports.introspectDbSchema = introspectDbSchema;