relq 1.0.25 → 1.0.26
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/cjs/cli/commands/commit.cjs +80 -0
- package/dist/cjs/cli/commands/import.cjs +1 -0
- package/dist/cjs/cli/commands/pull.cjs +8 -25
- package/dist/cjs/cli/commands/push.cjs +48 -8
- package/dist/cjs/cli/commands/rollback.cjs +205 -84
- package/dist/cjs/cli/commands/schema-ast.cjs +219 -0
- package/dist/cjs/cli/index.cjs +6 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +95 -3
- package/dist/cjs/cli/utils/ast-transformer.cjs +12 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +135 -0
- package/dist/cjs/cli/utils/commit-manager.cjs +54 -0
- package/dist/cjs/cli/utils/migration-generator.cjs +319 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +99 -3
- package/dist/cjs/cli/utils/schema-diff.cjs +390 -0
- package/dist/cjs/cli/utils/schema-hash.cjs +4 -0
- package/dist/cjs/cli/utils/schema-to-ast.cjs +477 -0
- package/dist/cjs/schema-definition/column-types.cjs +50 -4
- package/dist/cjs/schema-definition/pg-enum.cjs +10 -0
- package/dist/cjs/schema-definition/pg-function.cjs +19 -0
- package/dist/cjs/schema-definition/pg-sequence.cjs +22 -1
- package/dist/cjs/schema-definition/pg-trigger.cjs +39 -0
- package/dist/cjs/schema-definition/pg-view.cjs +17 -0
- package/dist/cjs/schema-definition/sql-expressions.cjs +3 -0
- package/dist/cjs/schema-definition/table-definition.cjs +4 -0
- package/dist/config.d.ts +98 -0
- package/dist/esm/cli/commands/commit.js +83 -3
- package/dist/esm/cli/commands/import.js +1 -0
- package/dist/esm/cli/commands/pull.js +9 -26
- package/dist/esm/cli/commands/push.js +49 -9
- package/dist/esm/cli/commands/rollback.js +206 -85
- package/dist/esm/cli/commands/schema-ast.js +183 -0
- package/dist/esm/cli/index.js +6 -0
- package/dist/esm/cli/utils/ast-codegen.js +93 -3
- package/dist/esm/cli/utils/ast-transformer.js +12 -0
- package/dist/esm/cli/utils/change-tracker.js +134 -0
- package/dist/esm/cli/utils/commit-manager.js +51 -0
- package/dist/esm/cli/utils/migration-generator.js +318 -0
- package/dist/esm/cli/utils/repo-manager.js +96 -3
- package/dist/esm/cli/utils/schema-diff.js +389 -0
- package/dist/esm/cli/utils/schema-hash.js +4 -0
- package/dist/esm/cli/utils/schema-to-ast.js +447 -0
- package/dist/esm/schema-definition/column-types.js +50 -4
- package/dist/esm/schema-definition/pg-enum.js +10 -0
- package/dist/esm/schema-definition/pg-function.js +19 -0
- package/dist/esm/schema-definition/pg-sequence.js +22 -1
- package/dist/esm/schema-definition/pg-trigger.js +39 -0
- package/dist/esm/schema-definition/pg-view.js +17 -0
- package/dist/esm/schema-definition/sql-expressions.js +3 -0
- package/dist/esm/schema-definition/table-definition.js +4 -0
- package/dist/index.d.ts +98 -0
- package/dist/schema-builder.d.ts +223 -24
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateMigration = generateMigration;
|
|
4
|
+
exports.generateMigrationFromComparison = generateMigrationFromComparison;
|
|
4
5
|
exports.generateMigrationFile = generateMigrationFile;
|
|
5
6
|
exports.getNextMigrationNumber = getNextMigrationNumber;
|
|
6
7
|
exports.generateTimestampedName = generateTimestampedName;
|
|
@@ -29,6 +30,324 @@ function generateMigration(diff, options = {}) {
|
|
|
29
30
|
}
|
|
30
31
|
return { up, down };
|
|
31
32
|
}
|
|
33
|
+
function generateMigrationFromComparison(comparison, options = {}) {
|
|
34
|
+
const { includeDown = true } = options;
|
|
35
|
+
const up = [];
|
|
36
|
+
const down = [];
|
|
37
|
+
for (const rename of comparison.renamed.enums) {
|
|
38
|
+
up.push(`ALTER TYPE "${rename.from}" RENAME TO "${rename.to}";`);
|
|
39
|
+
if (includeDown) {
|
|
40
|
+
down.unshift(`ALTER TYPE "${rename.to}" RENAME TO "${rename.from}";`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const rename of comparison.renamed.sequences) {
|
|
44
|
+
up.push(`ALTER SEQUENCE "${rename.from}" RENAME TO "${rename.to}";`);
|
|
45
|
+
if (includeDown) {
|
|
46
|
+
down.unshift(`ALTER SEQUENCE "${rename.to}" RENAME TO "${rename.from}";`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const rename of comparison.renamed.tables) {
|
|
50
|
+
up.push(`ALTER TABLE "${rename.from}" RENAME TO "${rename.to}";`);
|
|
51
|
+
if (includeDown) {
|
|
52
|
+
down.unshift(`ALTER TABLE "${rename.to}" RENAME TO "${rename.from}";`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const rename of comparison.renamed.columns) {
|
|
56
|
+
up.push(`ALTER TABLE "${rename.table}" RENAME COLUMN "${rename.from}" TO "${rename.to}";`);
|
|
57
|
+
if (includeDown) {
|
|
58
|
+
down.unshift(`ALTER TABLE "${rename.table}" RENAME COLUMN "${rename.to}" TO "${rename.from}";`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const rename of comparison.renamed.indexes) {
|
|
62
|
+
up.push(`ALTER INDEX "${rename.from}" RENAME TO "${rename.to}";`);
|
|
63
|
+
if (includeDown) {
|
|
64
|
+
down.unshift(`ALTER INDEX "${rename.to}" RENAME TO "${rename.from}";`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const rename of comparison.renamed.functions) {
|
|
68
|
+
up.push(`ALTER FUNCTION "${rename.from}" RENAME TO "${rename.to}";`);
|
|
69
|
+
if (includeDown) {
|
|
70
|
+
down.unshift(`ALTER FUNCTION "${rename.to}" RENAME TO "${rename.from}";`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const enumMod of comparison.modified.enums) {
|
|
74
|
+
for (const value of enumMod.changes.added) {
|
|
75
|
+
up.push(`ALTER TYPE "${enumMod.name}" ADD VALUE IF NOT EXISTS '${value}';`);
|
|
76
|
+
}
|
|
77
|
+
if (enumMod.changes.removed.length > 0 && includeDown) {
|
|
78
|
+
down.unshift(`-- Warning: Cannot remove enum values in PostgreSQL: ${enumMod.changes.removed.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const colMod of comparison.modified.columns) {
|
|
82
|
+
const { upSQL, downSQL } = generateColumnModification(colMod.table, colMod.column, colMod.changes);
|
|
83
|
+
up.push(...upSQL);
|
|
84
|
+
if (includeDown)
|
|
85
|
+
down.unshift(...downSQL);
|
|
86
|
+
}
|
|
87
|
+
for (const ext of comparison.added.extensions) {
|
|
88
|
+
up.push(`CREATE EXTENSION IF NOT EXISTS "${ext}";`);
|
|
89
|
+
if (includeDown) {
|
|
90
|
+
down.unshift(`DROP EXTENSION IF EXISTS "${ext}";`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const enumDef of comparison.added.enums) {
|
|
94
|
+
const values = enumDef.values.map(v => `'${v}'`).join(', ');
|
|
95
|
+
up.push(`CREATE TYPE "${enumDef.name}" AS ENUM (${values});`);
|
|
96
|
+
if (includeDown) {
|
|
97
|
+
down.unshift(`DROP TYPE IF EXISTS "${enumDef.name}";`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const domain of comparison.added.domains) {
|
|
101
|
+
let domainSQL = `CREATE DOMAIN "${domain.name}" AS ${domain.baseType}`;
|
|
102
|
+
if (domain.notNull)
|
|
103
|
+
domainSQL += ' NOT NULL';
|
|
104
|
+
if (domain.defaultValue)
|
|
105
|
+
domainSQL += ` DEFAULT ${domain.defaultValue}`;
|
|
106
|
+
if (domain.checkExpression) {
|
|
107
|
+
const checkName = domain.checkName ? `CONSTRAINT "${domain.checkName}" ` : '';
|
|
108
|
+
domainSQL += ` ${checkName}CHECK (${domain.checkExpression})`;
|
|
109
|
+
}
|
|
110
|
+
up.push(domainSQL + ';');
|
|
111
|
+
if (includeDown) {
|
|
112
|
+
down.unshift(`DROP DOMAIN IF EXISTS "${domain.name}";`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const seq of comparison.added.sequences) {
|
|
116
|
+
const parts = [`CREATE SEQUENCE "${seq.name}"`];
|
|
117
|
+
if (seq.startValue !== undefined)
|
|
118
|
+
parts.push(`START WITH ${seq.startValue}`);
|
|
119
|
+
if (seq.increment !== undefined)
|
|
120
|
+
parts.push(`INCREMENT BY ${seq.increment}`);
|
|
121
|
+
if (seq.minValue !== undefined)
|
|
122
|
+
parts.push(`MINVALUE ${seq.minValue}`);
|
|
123
|
+
if (seq.maxValue !== undefined)
|
|
124
|
+
parts.push(`MAXVALUE ${seq.maxValue}`);
|
|
125
|
+
if (seq.cache !== undefined)
|
|
126
|
+
parts.push(`CACHE ${seq.cache}`);
|
|
127
|
+
if (seq.cycle)
|
|
128
|
+
parts.push('CYCLE');
|
|
129
|
+
up.push(parts.join(' ') + ';');
|
|
130
|
+
if (includeDown) {
|
|
131
|
+
down.unshift(`DROP SEQUENCE IF EXISTS "${seq.name}";`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const table of comparison.added.tables) {
|
|
135
|
+
up.push(...generateCreateTableFromParsed(table));
|
|
136
|
+
if (includeDown) {
|
|
137
|
+
down.unshift(`DROP TABLE IF EXISTS "${table.name}" CASCADE;`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const { table, column } of comparison.added.columns) {
|
|
141
|
+
up.push(`ALTER TABLE "${table}" ADD COLUMN ${generateParsedColumnDef(column)};`);
|
|
142
|
+
if (includeDown) {
|
|
143
|
+
down.unshift(`ALTER TABLE "${table}" DROP COLUMN IF EXISTS "${column.name}";`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const { table, index } of comparison.added.indexes) {
|
|
147
|
+
up.push(generateCreateIndexFromParsed(table, index));
|
|
148
|
+
if (includeDown) {
|
|
149
|
+
down.unshift(`DROP INDEX IF EXISTS "${index.name}";`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const view of comparison.added.views) {
|
|
153
|
+
const materialized = view.isMaterialized ? 'MATERIALIZED ' : '';
|
|
154
|
+
up.push(`CREATE ${materialized}VIEW "${view.name}" AS ${view.definition};`);
|
|
155
|
+
if (includeDown) {
|
|
156
|
+
down.unshift(`DROP ${materialized}VIEW IF EXISTS "${view.name}";`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const func of comparison.added.functions) {
|
|
160
|
+
const argsStr = func.args.map(a => `${a.name || ''} ${a.type}`.trim()).join(', ');
|
|
161
|
+
const volatility = func.volatility || 'VOLATILE';
|
|
162
|
+
up.push(`CREATE OR REPLACE FUNCTION "${func.name}"(${argsStr}) RETURNS ${func.returnType} LANGUAGE ${func.language} ${volatility} AS $$ ${func.body} $$;`);
|
|
163
|
+
if (includeDown) {
|
|
164
|
+
down.unshift(`DROP FUNCTION IF EXISTS "${func.name}"(${func.args.map(a => a.type).join(', ')});`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const trigger of comparison.added.triggers) {
|
|
168
|
+
const events = trigger.events.join(' OR ');
|
|
169
|
+
const forEach = trigger.forEach || 'ROW';
|
|
170
|
+
up.push(`CREATE TRIGGER "${trigger.name}" ${trigger.timing} ${events} ON "${trigger.table}" FOR EACH ${forEach} EXECUTE FUNCTION ${trigger.functionName}();`);
|
|
171
|
+
if (includeDown) {
|
|
172
|
+
down.unshift(`DROP TRIGGER IF EXISTS "${trigger.name}" ON "${trigger.table}";`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const trigger of comparison.removed.triggers) {
|
|
176
|
+
up.push(`DROP TRIGGER IF EXISTS "${trigger.name}" ON "${trigger.table}";`);
|
|
177
|
+
if (includeDown) {
|
|
178
|
+
const events = trigger.events.join(' OR ');
|
|
179
|
+
const forEach = trigger.forEach || 'ROW';
|
|
180
|
+
down.unshift(`CREATE TRIGGER "${trigger.name}" ${trigger.timing} ${events} ON "${trigger.table}" FOR EACH ${forEach} EXECUTE FUNCTION ${trigger.functionName}();`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const func of comparison.removed.functions) {
|
|
184
|
+
const argTypes = func.args.map(a => a.type).join(', ');
|
|
185
|
+
up.push(`DROP FUNCTION IF EXISTS "${func.name}"(${argTypes});`);
|
|
186
|
+
if (includeDown) {
|
|
187
|
+
const argsStr = func.args.map(a => `${a.name || ''} ${a.type}`.trim()).join(', ');
|
|
188
|
+
const volatility = func.volatility || 'VOLATILE';
|
|
189
|
+
down.unshift(`CREATE OR REPLACE FUNCTION "${func.name}"(${argsStr}) RETURNS ${func.returnType} LANGUAGE ${func.language} ${volatility} AS $$ ${func.body} $$;`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const view of comparison.removed.views) {
|
|
193
|
+
const materialized = view.isMaterialized ? 'MATERIALIZED ' : '';
|
|
194
|
+
up.push(`DROP ${materialized}VIEW IF EXISTS "${view.name}";`);
|
|
195
|
+
if (includeDown) {
|
|
196
|
+
down.unshift(`CREATE ${materialized}VIEW "${view.name}" AS ${view.definition};`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const { index } of comparison.removed.indexes) {
|
|
200
|
+
up.push(`DROP INDEX IF EXISTS "${index.name}";`);
|
|
201
|
+
}
|
|
202
|
+
for (const { table, column } of comparison.removed.columns) {
|
|
203
|
+
up.push(`ALTER TABLE "${table}" DROP COLUMN IF EXISTS "${column.name}";`);
|
|
204
|
+
if (includeDown) {
|
|
205
|
+
down.unshift(`ALTER TABLE "${table}" ADD COLUMN ${generateParsedColumnDef(column)};`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const table of comparison.removed.tables) {
|
|
209
|
+
up.push(`DROP TABLE IF EXISTS "${table.name}" CASCADE;`);
|
|
210
|
+
if (includeDown) {
|
|
211
|
+
down.unshift(...generateCreateTableFromParsed(table));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const seq of comparison.removed.sequences) {
|
|
215
|
+
up.push(`DROP SEQUENCE IF EXISTS "${seq.name}";`);
|
|
216
|
+
if (includeDown) {
|
|
217
|
+
const parts = [`CREATE SEQUENCE "${seq.name}"`];
|
|
218
|
+
if (seq.startValue !== undefined)
|
|
219
|
+
parts.push(`START WITH ${seq.startValue}`);
|
|
220
|
+
if (seq.increment !== undefined)
|
|
221
|
+
parts.push(`INCREMENT BY ${seq.increment}`);
|
|
222
|
+
if (seq.minValue !== undefined)
|
|
223
|
+
parts.push(`MINVALUE ${seq.minValue}`);
|
|
224
|
+
if (seq.maxValue !== undefined)
|
|
225
|
+
parts.push(`MAXVALUE ${seq.maxValue}`);
|
|
226
|
+
if (seq.cache !== undefined)
|
|
227
|
+
parts.push(`CACHE ${seq.cache}`);
|
|
228
|
+
if (seq.cycle)
|
|
229
|
+
parts.push('CYCLE');
|
|
230
|
+
down.unshift(parts.join(' ') + ';');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const domain of comparison.removed.domains) {
|
|
234
|
+
up.push(`DROP DOMAIN IF EXISTS "${domain.name}";`);
|
|
235
|
+
if (includeDown) {
|
|
236
|
+
let domainSQL = `CREATE DOMAIN "${domain.name}" AS ${domain.baseType}`;
|
|
237
|
+
if (domain.notNull)
|
|
238
|
+
domainSQL += ' NOT NULL';
|
|
239
|
+
if (domain.defaultValue)
|
|
240
|
+
domainSQL += ` DEFAULT ${domain.defaultValue}`;
|
|
241
|
+
if (domain.checkExpression) {
|
|
242
|
+
const checkName = domain.checkName ? `CONSTRAINT "${domain.checkName}" ` : '';
|
|
243
|
+
domainSQL += ` ${checkName}CHECK (${domain.checkExpression})`;
|
|
244
|
+
}
|
|
245
|
+
down.unshift(domainSQL + ';');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const enumDef of comparison.removed.enums) {
|
|
249
|
+
up.push(`DROP TYPE IF EXISTS "${enumDef.name}";`);
|
|
250
|
+
if (includeDown) {
|
|
251
|
+
const values = enumDef.values.map(v => `'${v}'`).join(', ');
|
|
252
|
+
down.unshift(`CREATE TYPE "${enumDef.name}" AS ENUM (${values});`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const ext of comparison.removed.extensions) {
|
|
256
|
+
up.push(`DROP EXTENSION IF EXISTS "${ext}";`);
|
|
257
|
+
if (includeDown) {
|
|
258
|
+
down.unshift(`CREATE EXTENSION IF NOT EXISTS "${ext}";`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return { up, down };
|
|
262
|
+
}
|
|
263
|
+
function generateColumnModification(tableName, columnName, changes) {
|
|
264
|
+
const upSQL = [];
|
|
265
|
+
const downSQL = [];
|
|
266
|
+
for (const change of changes) {
|
|
267
|
+
switch (change.field) {
|
|
268
|
+
case 'type':
|
|
269
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${change.to} USING "${columnName}"::${change.to};`);
|
|
270
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${change.from} USING "${columnName}"::${change.from};`);
|
|
271
|
+
break;
|
|
272
|
+
case 'nullable':
|
|
273
|
+
if (change.to) {
|
|
274
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP NOT NULL;`);
|
|
275
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET NOT NULL;`);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET NOT NULL;`);
|
|
279
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP NOT NULL;`);
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
case 'default':
|
|
283
|
+
if (change.to !== undefined && change.to !== null) {
|
|
284
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DEFAULT ${change.to};`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT;`);
|
|
288
|
+
}
|
|
289
|
+
if (change.from !== undefined && change.from !== null) {
|
|
290
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DEFAULT ${change.from};`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT;`);
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { upSQL, downSQL };
|
|
299
|
+
}
|
|
300
|
+
function generateCreateTableFromParsed(table) {
|
|
301
|
+
const sql = [];
|
|
302
|
+
const columnDefs = [];
|
|
303
|
+
for (const col of table.columns) {
|
|
304
|
+
columnDefs.push(` ${generateParsedColumnDef(col)}`);
|
|
305
|
+
}
|
|
306
|
+
let createSQL = `CREATE TABLE "${table.name}" (\n${columnDefs.join(',\n')}\n)`;
|
|
307
|
+
if (table.isPartitioned && table.partitionType && table.partitionKey?.length) {
|
|
308
|
+
createSQL += ` PARTITION BY ${table.partitionType} (${table.partitionKey.join(', ')})`;
|
|
309
|
+
}
|
|
310
|
+
sql.push(createSQL + ';');
|
|
311
|
+
for (const idx of table.indexes) {
|
|
312
|
+
const isPKIndex = table.columns.some(c => c.isPrimaryKey && idx.columns.includes(c.name) && idx.columns.length === 1);
|
|
313
|
+
if (!isPKIndex) {
|
|
314
|
+
sql.push(generateCreateIndexFromParsed(table.name, idx));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return sql;
|
|
318
|
+
}
|
|
319
|
+
function generateParsedColumnDef(col) {
|
|
320
|
+
const parts = [`"${col.name}"`, col.type];
|
|
321
|
+
if (col.isPrimaryKey) {
|
|
322
|
+
parts.push('PRIMARY KEY');
|
|
323
|
+
}
|
|
324
|
+
if (!col.isNullable && !col.isPrimaryKey) {
|
|
325
|
+
parts.push('NOT NULL');
|
|
326
|
+
}
|
|
327
|
+
if (col.isUnique && !col.isPrimaryKey) {
|
|
328
|
+
parts.push('UNIQUE');
|
|
329
|
+
}
|
|
330
|
+
if (col.defaultValue !== undefined) {
|
|
331
|
+
parts.push(`DEFAULT ${col.defaultValue}`);
|
|
332
|
+
}
|
|
333
|
+
if (col.references) {
|
|
334
|
+
parts.push(`REFERENCES "${col.references.table}"("${col.references.column}")`);
|
|
335
|
+
if (col.references.onDelete) {
|
|
336
|
+
parts.push(`ON DELETE ${col.references.onDelete}`);
|
|
337
|
+
}
|
|
338
|
+
if (col.references.onUpdate) {
|
|
339
|
+
parts.push(`ON UPDATE ${col.references.onUpdate}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return parts.join(' ');
|
|
343
|
+
}
|
|
344
|
+
function generateCreateIndexFromParsed(tableName, idx) {
|
|
345
|
+
const unique = idx.isUnique ? 'UNIQUE ' : '';
|
|
346
|
+
const colList = idx.columns.map(c => `"${c}"`).join(', ');
|
|
347
|
+
const using = idx.method && idx.method !== 'btree' ? ` USING ${idx.method}` : '';
|
|
348
|
+
const where = idx.whereClause ? ` WHERE ${idx.whereClause}` : '';
|
|
349
|
+
return `CREATE ${unique}INDEX IF NOT EXISTS "${idx.name}" ON "${tableName}"${using} (${colList})${where};`;
|
|
350
|
+
}
|
|
32
351
|
function generateMigrationFile(diff, name, options = {}) {
|
|
33
352
|
const { message, includeComments = true } = options;
|
|
34
353
|
const { up, down } = generateMigration(diff, options);
|
|
@@ -51,6 +51,7 @@ exports.saveCommit = saveCommit;
|
|
|
51
51
|
exports.loadCommit = loadCommit;
|
|
52
52
|
exports.getAllCommits = getAllCommits;
|
|
53
53
|
exports.getCommitHistory = getCommitHistory;
|
|
54
|
+
exports.buildTrackingMap = buildTrackingMap;
|
|
54
55
|
exports.createCommit = createCommit;
|
|
55
56
|
exports.getStaged = getStaged;
|
|
56
57
|
exports.setStaged = setStaged;
|
|
@@ -75,6 +76,8 @@ exports.cleanupStagedChanges = cleanupStagedChanges;
|
|
|
75
76
|
exports.getUnstagedChanges = getUnstagedChanges;
|
|
76
77
|
exports.hasUncommittedChanges = hasUncommittedChanges;
|
|
77
78
|
exports.ensureRemoteTable = ensureRemoteTable;
|
|
79
|
+
exports.markCommitAsApplied = markCommitAsApplied;
|
|
80
|
+
exports.markCommitAsRolledBack = markCommitAsRolledBack;
|
|
78
81
|
exports.fetchRemoteCommits = fetchRemoteCommits;
|
|
79
82
|
exports.getRemoteHead = getRemoteHead;
|
|
80
83
|
exports.pushCommit = pushCommit;
|
|
@@ -270,7 +273,42 @@ function getCommitHistory(limit = 20, projectRoot = process.cwd()) {
|
|
|
270
273
|
}
|
|
271
274
|
return history;
|
|
272
275
|
}
|
|
273
|
-
function
|
|
276
|
+
function buildTrackingMap(ast) {
|
|
277
|
+
const map = {
|
|
278
|
+
tables: {},
|
|
279
|
+
columns: {},
|
|
280
|
+
indexes: {},
|
|
281
|
+
enums: {},
|
|
282
|
+
functions: {},
|
|
283
|
+
};
|
|
284
|
+
for (const table of ast.tables) {
|
|
285
|
+
if (table.trackingId) {
|
|
286
|
+
map.tables[table.trackingId] = table.name;
|
|
287
|
+
}
|
|
288
|
+
for (const col of table.columns) {
|
|
289
|
+
if (col.trackingId) {
|
|
290
|
+
map.columns[col.trackingId] = `${table.name}.${col.name}`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const idx of table.indexes) {
|
|
294
|
+
if (idx.trackingId) {
|
|
295
|
+
map.indexes[idx.trackingId] = idx.name;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const e of ast.enums) {
|
|
300
|
+
if (e.trackingId) {
|
|
301
|
+
map.enums[e.trackingId] = e.name;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const f of ast.functions) {
|
|
305
|
+
if (f.trackingId) {
|
|
306
|
+
map.functions[f.trackingId] = f.name;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return map;
|
|
310
|
+
}
|
|
311
|
+
function createCommit(schema, author, message, projectRoot = process.cwd(), options) {
|
|
274
312
|
const hash = generateHash(schema);
|
|
275
313
|
const parentHash = getHead(projectRoot);
|
|
276
314
|
const commit = {
|
|
@@ -292,9 +330,18 @@ function createCommit(schema, author, message, projectRoot = process.cwd()) {
|
|
|
292
330
|
triggers: schema.triggers?.length || 0,
|
|
293
331
|
},
|
|
294
332
|
};
|
|
333
|
+
if (options?.schemaAST) {
|
|
334
|
+
commit.schemaAST = options.schemaAST;
|
|
335
|
+
commit.trackingMap = buildTrackingMap(options.schemaAST);
|
|
336
|
+
}
|
|
337
|
+
if (options?.changes) {
|
|
338
|
+
commit.changes = options.changes;
|
|
339
|
+
}
|
|
295
340
|
saveCommit(commit, projectRoot);
|
|
296
341
|
setHead(hash, projectRoot);
|
|
297
|
-
|
|
342
|
+
if (!options?.skipClearStaged) {
|
|
343
|
+
clearStaged(projectRoot);
|
|
344
|
+
}
|
|
298
345
|
return commit;
|
|
299
346
|
}
|
|
300
347
|
function getStaged(projectRoot = process.cwd()) {
|
|
@@ -517,12 +564,61 @@ async function ensureRemoteTable(connection) {
|
|
|
517
564
|
message TEXT,
|
|
518
565
|
schema_snapshot JSONB NOT NULL,
|
|
519
566
|
stats JSONB,
|
|
520
|
-
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
567
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
568
|
+
applied_at TIMESTAMPTZ,
|
|
569
|
+
rolled_back_at TIMESTAMPTZ
|
|
521
570
|
);
|
|
522
571
|
|
|
523
572
|
CREATE INDEX IF NOT EXISTS idx_relq_commits_hash ON _relq_commits(hash);
|
|
524
573
|
CREATE INDEX IF NOT EXISTS idx_relq_commits_created ON _relq_commits(created_at DESC);
|
|
525
574
|
`);
|
|
575
|
+
await pool.query(`
|
|
576
|
+
DO $$
|
|
577
|
+
BEGIN
|
|
578
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = '_relq_commits' AND column_name = 'applied_at') THEN
|
|
579
|
+
ALTER TABLE _relq_commits ADD COLUMN applied_at TIMESTAMPTZ;
|
|
580
|
+
END IF;
|
|
581
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = '_relq_commits' AND column_name = 'rolled_back_at') THEN
|
|
582
|
+
ALTER TABLE _relq_commits ADD COLUMN rolled_back_at TIMESTAMPTZ;
|
|
583
|
+
END IF;
|
|
584
|
+
END $$;
|
|
585
|
+
`);
|
|
586
|
+
}
|
|
587
|
+
finally {
|
|
588
|
+
await pool.end();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function markCommitAsApplied(connection, commitHash) {
|
|
592
|
+
const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
|
|
593
|
+
const pool = new Pool({
|
|
594
|
+
host: connection.host,
|
|
595
|
+
port: connection.port || 5432,
|
|
596
|
+
database: connection.database,
|
|
597
|
+
user: connection.user,
|
|
598
|
+
password: connection.password,
|
|
599
|
+
connectionString: connection.url,
|
|
600
|
+
ssl: connection.ssl,
|
|
601
|
+
});
|
|
602
|
+
try {
|
|
603
|
+
await pool.query(`UPDATE _relq_commits SET applied_at = NOW(), rolled_back_at = NULL WHERE hash = $1`, [commitHash]);
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
await pool.end();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async function markCommitAsRolledBack(connection, commitHash) {
|
|
610
|
+
const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
|
|
611
|
+
const pool = new Pool({
|
|
612
|
+
host: connection.host,
|
|
613
|
+
port: connection.port || 5432,
|
|
614
|
+
database: connection.database,
|
|
615
|
+
user: connection.user,
|
|
616
|
+
password: connection.password,
|
|
617
|
+
connectionString: connection.url,
|
|
618
|
+
ssl: connection.ssl,
|
|
619
|
+
});
|
|
620
|
+
try {
|
|
621
|
+
await pool.query(`UPDATE _relq_commits SET rolled_back_at = NOW() WHERE hash = $1`, [commitHash]);
|
|
526
622
|
}
|
|
527
623
|
finally {
|
|
528
624
|
await pool.end();
|