relq 1.0.4 → 1.0.6

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.
Files changed (84) hide show
  1. package/dist/cjs/cli/commands/add.cjs +252 -12
  2. package/dist/cjs/cli/commands/commit.cjs +12 -1
  3. package/dist/cjs/cli/commands/export.cjs +25 -19
  4. package/dist/cjs/cli/commands/import.cjs +219 -100
  5. package/dist/cjs/cli/commands/init.cjs +86 -14
  6. package/dist/cjs/cli/commands/pull.cjs +104 -23
  7. package/dist/cjs/cli/commands/push.cjs +38 -3
  8. package/dist/cjs/cli/index.cjs +9 -1
  9. package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
  10. package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
  11. package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
  12. package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
  13. package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
  14. package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
  15. package/dist/cjs/cli/utils/ast/index.cjs +19 -0
  16. package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
  17. package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
  18. package/dist/cjs/cli/utils/ast/types.cjs +2 -0
  19. package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
  20. package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
  21. package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
  22. package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
  23. package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
  24. package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
  25. package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
  26. package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
  27. package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
  28. package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
  29. package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
  30. package/dist/cjs/cli/utils/type-generator.cjs +114 -15
  31. package/dist/cjs/core/relq-client.cjs +22 -6
  32. package/dist/cjs/schema-definition/column-types.cjs +150 -13
  33. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  34. package/dist/cjs/schema-definition/index.cjs +15 -1
  35. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  36. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  37. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  38. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  39. package/dist/cjs/types/config-types.cjs +13 -4
  40. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  41. package/dist/config.d.ts +146 -1
  42. package/dist/esm/cli/commands/add.js +250 -13
  43. package/dist/esm/cli/commands/commit.js +12 -1
  44. package/dist/esm/cli/commands/export.js +25 -19
  45. package/dist/esm/cli/commands/import.js +221 -102
  46. package/dist/esm/cli/commands/init.js +86 -14
  47. package/dist/esm/cli/commands/pull.js +106 -25
  48. package/dist/esm/cli/commands/push.js +39 -4
  49. package/dist/esm/cli/index.js +9 -1
  50. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  51. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  52. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  53. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  54. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  55. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  56. package/dist/esm/cli/utils/ast/index.js +3 -0
  57. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  58. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  59. package/dist/esm/cli/utils/ast/types.js +1 -0
  60. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  61. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  62. package/dist/esm/cli/utils/change-tracker.js +50 -1
  63. package/dist/esm/cli/utils/cli-utils.js +147 -0
  64. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  65. package/dist/esm/cli/utils/pg-parser.js +1 -0
  66. package/dist/esm/cli/utils/repo-manager.js +114 -4
  67. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  68. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  69. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  70. package/dist/esm/cli/utils/sql-generator.js +353 -64
  71. package/dist/esm/cli/utils/type-generator.js +114 -15
  72. package/dist/esm/core/relq-client.js +23 -7
  73. package/dist/esm/schema-definition/column-types.js +147 -12
  74. package/dist/esm/schema-definition/defaults.js +69 -0
  75. package/dist/esm/schema-definition/index.js +3 -0
  76. package/dist/esm/schema-definition/introspection.js +7 -3
  77. package/dist/esm/schema-definition/pg-relations.js +161 -0
  78. package/dist/esm/schema-definition/pg-view.js +24 -0
  79. package/dist/esm/schema-definition/table-definition.js +110 -4
  80. package/dist/esm/types/config-types.js +12 -4
  81. package/dist/esm/utils/aws-dsql.js +139 -0
  82. package/dist/index.d.ts +159 -1
  83. package/dist/schema-builder.d.ts +1314 -32
  84. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  const RESERVED_WORDS = new Set([
2
- 'user', 'order', 'check', 'table', 'index', 'column', 'constraint',
2
+ 'order', 'check', 'table', 'index', 'column', 'constraint',
3
3
  'primary', 'foreign', 'key', 'references', 'unique', 'default',
4
4
  'null', 'not', 'and', 'or', 'in', 'like', 'between', 'case',
5
5
  'when', 'then', 'else', 'end', 'select', 'from', 'where', 'group',
6
- 'having', 'order', 'by', 'limit', 'offset', 'join', 'left', 'right',
6
+ 'having', 'by', 'limit', 'offset', 'join', 'left', 'right',
7
7
  'inner', 'outer', 'cross', 'on', 'as', 'into', 'values', 'insert',
8
8
  'update', 'delete', 'create', 'alter', 'drop', 'grant', 'revoke',
9
9
  'all', 'any', 'some', 'exists', 'true', 'false', 'boolean', 'int',
@@ -45,7 +45,7 @@ const RESERVED_WORDS = new Set([
45
45
  'off', 'oids', 'old', 'only', 'operator', 'option', 'options',
46
46
  'ordinality', 'others', 'out', 'over', 'overlaps', 'overlay',
47
47
  'overriding', 'owned', 'owner', 'parallel', 'parser', 'partial',
48
- 'partition', 'passing', 'password', 'placing', 'plans', 'policy',
48
+ 'partition', 'passing', 'placing', 'plans', 'policy',
49
49
  'position', 'preceding', 'prepare', 'prepared', 'preserve', 'prior',
50
50
  'privileges', 'procedural', 'program', 'publication', 'quote',
51
51
  'range', 'read', 'reassign', 'recheck', 'refresh', 'reindex',
@@ -81,6 +81,34 @@ export function quoteIdentifier(name) {
81
81
  }
82
82
  return name;
83
83
  }
84
+ function parseFKDefinition(definition) {
85
+ if (!definition)
86
+ return null;
87
+ const constraintNameMatch = definition.match(/^CONSTRAINT\s+"?([^"]+)"?\s+/i);
88
+ const constraintName = constraintNameMatch?.[1];
89
+ const fkPart = constraintName
90
+ ? definition.substring(constraintNameMatch[0].length)
91
+ : definition;
92
+ const fkMatch = fkPart.match(/FOREIGN\s+KEY\s*\(\s*([^)]+)\s*\)\s*REFERENCES\s+"?([^"\s(]+)"?\s*(?:\(\s*([^)]+)\s*\))?/i);
93
+ if (!fkMatch)
94
+ return null;
95
+ const parseColumnList = (str) => {
96
+ return str.split(',').map(c => c.trim().replace(/^"|"$/g, ''));
97
+ };
98
+ const columns = parseColumnList(fkMatch[1]);
99
+ const referencedTable = fkMatch[2].replace(/^"|"$/g, '');
100
+ const referencedColumns = fkMatch[3] ? parseColumnList(fkMatch[3]) : ['id'];
101
+ const onDeleteMatch = definition.match(/ON\s+DELETE\s+(CASCADE|SET\s+NULL|SET\s+DEFAULT|RESTRICT|NO\s+ACTION)/i);
102
+ const onUpdateMatch = definition.match(/ON\s+UPDATE\s+(CASCADE|SET\s+NULL|SET\s+DEFAULT|RESTRICT|NO\s+ACTION)/i);
103
+ return {
104
+ constraintName,
105
+ columns,
106
+ referencedTable,
107
+ referencedColumns,
108
+ onDelete: onDeleteMatch?.[1]?.toUpperCase().replace(/\s+/g, ' '),
109
+ onUpdate: onUpdateMatch?.[1]?.toUpperCase().replace(/\s+/g, ' '),
110
+ };
111
+ }
84
112
  function quoteColumnRef(name) {
85
113
  const lowerName = name.toLowerCase();
86
114
  if (RESERVED_WORDS.has(lowerName)) {
@@ -91,12 +119,25 @@ function quoteColumnRef(name) {
91
119
  }
92
120
  return name;
93
121
  }
122
+ const PG_TYPE_MAP = {
123
+ 'int2': 'smallint',
124
+ 'int4': 'integer',
125
+ 'int8': 'bigint',
126
+ 'float4': 'real',
127
+ 'float8': 'double precision',
128
+ 'bool': 'boolean',
129
+ 'timestamptz': 'timestamp with time zone',
130
+ 'timetz': 'time with time zone',
131
+ 'bpchar': 'character',
132
+ 'varbit': 'bit varying',
133
+ };
94
134
  function normalizePgType(pgType) {
95
135
  if (pgType.startsWith('_')) {
96
136
  const baseType = pgType.slice(1);
97
- return `${baseType}[]`;
137
+ const friendlyBase = PG_TYPE_MAP[baseType] || baseType;
138
+ return `${friendlyBase}[]`;
98
139
  }
99
- return pgType;
140
+ return PG_TYPE_MAP[pgType] || pgType;
100
141
  }
101
142
  export function generateExtensionSQL(extensionName) {
102
143
  const quotedName = extensionName.includes('-') ? `"${extensionName}"` : extensionName;
@@ -174,15 +215,116 @@ export function generateSequenceSQL(seq) {
174
215
  return parts.join(' ') + ';';
175
216
  }
176
217
  export function generateTableSQL(table, options = {}) {
177
- const { includeConstraints = true } = options;
218
+ const { includeConstraints = true, ifNotExists = false } = options;
178
219
  const lines = [];
179
- lines.push(`CREATE TABLE ${quoteIdentifier(table.name)} (`);
220
+ const ifNotExistsClause = ifNotExists ? 'IF NOT EXISTS ' : '';
221
+ lines.push(`-- DEBUG: generateTableSQL running for ${table.name}`);
222
+ lines.push(`CREATE TABLE ${ifNotExistsClause}${quoteIdentifier(table.name)} (`);
223
+ let singleColumnPkName = null;
224
+ let pkConstraintIndex = -1;
225
+ const compositeUniqueColumns = new Set();
226
+ const inlineFKs = new Map();
227
+ const skipFKIndices = new Set();
228
+ if (table.constraints) {
229
+ const pkIndex = table.constraints.findIndex(c => c.type === 'PRIMARY KEY');
230
+ if (pkIndex !== -1) {
231
+ const pkConstraint = table.constraints[pkIndex];
232
+ pkConstraintIndex = pkIndex;
233
+ if (pkConstraint.columns && pkConstraint.columns.length === 1) {
234
+ singleColumnPkName = pkConstraint.columns[0];
235
+ }
236
+ else if (pkConstraint.definition) {
237
+ const match = pkConstraint.definition.match(/PRIMARY\s+KEY\s*\(\s*"?([^",\s)]+)"?\s*\)/i);
238
+ if (match && !pkConstraint.definition.includes(',')) {
239
+ singleColumnPkName = match[1];
240
+ }
241
+ }
242
+ }
243
+ for (const constraint of table.constraints) {
244
+ if (constraint.type === 'UNIQUE') {
245
+ let cols = [];
246
+ if (constraint.columns && constraint.columns.length > 0) {
247
+ if (Array.isArray(constraint.columns)) {
248
+ cols = constraint.columns;
249
+ }
250
+ else if (typeof constraint.columns === 'string') {
251
+ const str = constraint.columns;
252
+ if (str.startsWith('{') && str.endsWith('}')) {
253
+ cols = str.slice(1, -1).split(',').map(c => c.trim().replace(/^"|"$/g, ''));
254
+ }
255
+ }
256
+ }
257
+ else if (constraint.definition) {
258
+ const match = constraint.definition.match(/UNIQUE\s*\(([^)]+)\)/i);
259
+ if (match) {
260
+ cols = match[1].split(',').map(c => c.trim().replace(/^"|"$/g, ''));
261
+ }
262
+ }
263
+ if (cols.length > 1) {
264
+ for (const col of cols) {
265
+ compositeUniqueColumns.add(col);
266
+ }
267
+ }
268
+ }
269
+ }
270
+ table.constraints.forEach((constraint, index) => {
271
+ if (constraint.type === 'FOREIGN KEY') {
272
+ let fkColumns = constraint.columns;
273
+ let refTable = constraint.referencedTable || constraint.references?.table;
274
+ let refColumns = constraint.referencedColumns || constraint.references?.columns;
275
+ let onDelete = constraint.onDelete || constraint.references?.onDelete;
276
+ let onUpdate = constraint.onUpdate || constraint.references?.onUpdate;
277
+ let constraintName = constraint.name;
278
+ if (!refTable && constraint.definition) {
279
+ const parsed = parseFKDefinition(constraint.definition);
280
+ if (parsed) {
281
+ fkColumns = parsed.columns;
282
+ refTable = parsed.referencedTable;
283
+ refColumns = parsed.referencedColumns;
284
+ onDelete = parsed.onDelete;
285
+ onUpdate = parsed.onUpdate;
286
+ if (!constraintName) {
287
+ constraintName = parsed.constraintName;
288
+ }
289
+ }
290
+ }
291
+ const isSingleColumn = fkColumns && fkColumns.length === 1;
292
+ let hasExplicitName = false;
293
+ if (constraintName && fkColumns && fkColumns.length > 0) {
294
+ const colName = fkColumns[0];
295
+ const expectedAutoName = `${table.name}_${colName}_fkey`;
296
+ hasExplicitName = constraintName.toLowerCase() !== expectedAutoName.toLowerCase();
297
+ }
298
+ if (isSingleColumn && refTable) {
299
+ const colName = fkColumns[0];
300
+ inlineFKs.set(colName, {
301
+ table: refTable,
302
+ column: refColumns?.[0],
303
+ onDelete: onDelete,
304
+ onUpdate: onUpdate,
305
+ constraintName: hasExplicitName ? constraintName : undefined,
306
+ });
307
+ skipFKIndices.add(index);
308
+ }
309
+ }
310
+ });
311
+ }
180
312
  const columnLines = [];
181
313
  for (const col of table.columns) {
182
- columnLines.push(generateColumnSQL(col));
314
+ const isInlinePk = singleColumnPkName === col.name;
315
+ const skipInlineUnique = compositeUniqueColumns.has(col.name);
316
+ const inlineFKInfo = inlineFKs.get(col.name);
317
+ columnLines.push(generateColumnSQL(col, { inlinePrimaryKey: isInlinePk, skipInlineUnique, inlineForeignKey: inlineFKInfo }));
183
318
  }
184
319
  if (includeConstraints && table.constraints) {
185
- for (const constraint of table.constraints) {
320
+ for (let i = 0; i < table.constraints.length; i++) {
321
+ const constraint = table.constraints[i];
322
+ if (constraint.type === 'PRIMARY KEY' && singleColumnPkName) {
323
+ continue;
324
+ }
325
+ if (constraint.type === 'FOREIGN KEY' && skipFKIndices.has(i)) {
326
+ continue;
327
+ }
186
328
  const constraintSQL = generateInlineConstraintSQL(constraint);
187
329
  if (constraintSQL) {
188
330
  columnLines.push(constraintSQL);
@@ -201,9 +343,21 @@ export function generateTableSQL(table, options = {}) {
201
343
  }
202
344
  return lines.join('\n');
203
345
  }
204
- function generateColumnSQL(col) {
346
+ function generateColumnSQL(col, options = {}) {
347
+ const { inlinePrimaryKey = false, skipInlineUnique = false, inlineForeignKey } = options;
205
348
  const parts = [];
206
- const normalizedType = normalizePgType(col.dataType);
349
+ let normalizedType = normalizePgType(col.dataType);
350
+ const baseType = normalizedType.replace('[]', '').toLowerCase();
351
+ if ((baseType === 'varchar' || baseType === 'character varying') && col.maxLength) {
352
+ normalizedType = `varchar(${col.maxLength})${normalizedType.endsWith('[]') ? '[]' : ''}`;
353
+ }
354
+ else if ((baseType === 'char' || baseType === 'character' || baseType === 'bpchar') && col.maxLength) {
355
+ normalizedType = `char(${col.maxLength})${normalizedType.endsWith('[]') ? '[]' : ''}`;
356
+ }
357
+ else if (baseType === 'numeric' && col.precision) {
358
+ const precisionStr = col.scale ? `${col.precision},${col.scale}` : `${col.precision}`;
359
+ normalizedType = `numeric(${precisionStr})${normalizedType.endsWith('[]') ? '[]' : ''}`;
360
+ }
207
361
  parts.push(`${quoteIdentifier(col.name).padEnd(28)}${normalizedType}`);
208
362
  if (col.dataType.endsWith('[]')) {
209
363
  }
@@ -214,44 +368,68 @@ function generateColumnSQL(col) {
214
368
  if (col.identityGeneration) {
215
369
  parts.push(`GENERATED ${col.identityGeneration} AS IDENTITY`);
216
370
  }
217
- if (!col.isNullable && !col.isPrimaryKey && !col.identityGeneration) {
371
+ if (!col.isNullable && !col.identityGeneration && !inlinePrimaryKey) {
218
372
  parts.push('NOT NULL');
219
373
  }
220
374
  if (col.defaultValue !== null && col.defaultValue !== undefined && !col.isGenerated) {
221
375
  parts.push(`DEFAULT ${col.defaultValue}`);
222
376
  }
223
- if (col.isUnique && !col.isPrimaryKey) {
377
+ if (inlinePrimaryKey) {
378
+ parts.push('PRIMARY KEY');
379
+ }
380
+ if (col.isUnique && !col.isPrimaryKey && !inlinePrimaryKey && !skipInlineUnique) {
224
381
  parts.push('UNIQUE');
225
382
  }
226
- if (col.references) {
227
- const ref = col.references;
228
- let refStr = `REFERENCES ${quoteIdentifier(ref.table)}(${quoteIdentifier(ref.column)})`;
229
- if (ref.onDelete && ref.onDelete !== 'NO ACTION') {
230
- refStr += ` ON DELETE ${ref.onDelete}`;
383
+ if (inlineForeignKey) {
384
+ let refPart = '';
385
+ if (inlineForeignKey.constraintName) {
386
+ refPart += `CONSTRAINT ${quoteIdentifier(inlineForeignKey.constraintName)} `;
387
+ }
388
+ refPart += `REFERENCES ${quoteIdentifier(inlineForeignKey.table)}`;
389
+ if (inlineForeignKey.column && inlineForeignKey.column.toLowerCase() !== 'id') {
390
+ refPart += `(${quoteIdentifier(inlineForeignKey.column)})`;
231
391
  }
232
- if (ref.onUpdate && ref.onUpdate !== 'NO ACTION') {
233
- refStr += ` ON UPDATE ${ref.onUpdate}`;
392
+ if (inlineForeignKey.onDelete && inlineForeignKey.onDelete !== 'NO ACTION') {
393
+ refPart += ` ON DELETE ${inlineForeignKey.onDelete}`;
234
394
  }
235
- parts.push(refStr);
395
+ if (inlineForeignKey.onUpdate && inlineForeignKey.onUpdate !== 'NO ACTION') {
396
+ refPart += ` ON UPDATE ${inlineForeignKey.onUpdate}`;
397
+ }
398
+ parts.push(refPart);
236
399
  }
237
400
  if (col.check) {
238
401
  parts.push(`CHECK (${col.check})`);
239
402
  }
240
403
  return parts.join(' ');
241
404
  }
405
+ function unquoteIdentifiers(sql) {
406
+ return sql.replace(/"([a-z_][a-z0-9_]*)"/g, (match, identifier) => {
407
+ if (RESERVED_WORDS.has(identifier.toLowerCase())) {
408
+ return match;
409
+ }
410
+ return identifier;
411
+ });
412
+ }
242
413
  function generateInlineConstraintSQL(constraint) {
243
414
  if (constraint.definition) {
244
- const def = constraint.definition.trim();
415
+ let def = constraint.definition.trim();
416
+ def = unquoteIdentifiers(def);
417
+ if (constraint.type === 'PRIMARY KEY') {
418
+ const pkMatch = def.match(/PRIMARY\s+KEY\s*\([^)]+\)/i);
419
+ if (pkMatch) {
420
+ return pkMatch[0];
421
+ }
422
+ }
245
423
  if (def.toUpperCase().startsWith('CONSTRAINT')) {
246
424
  return def;
247
425
  }
248
- if (constraint.name) {
426
+ if (constraint.name && constraint.type !== 'PRIMARY KEY') {
249
427
  return `CONSTRAINT ${quoteIdentifier(constraint.name)} ${def}`;
250
428
  }
251
429
  return def;
252
430
  }
253
431
  const parts = [];
254
- if (constraint.name) {
432
+ if (constraint.name && constraint.type !== 'PRIMARY KEY') {
255
433
  parts.push(`CONSTRAINT ${quoteIdentifier(constraint.name)}`);
256
434
  }
257
435
  switch (constraint.type) {
@@ -272,9 +450,20 @@ function generateInlineConstraintSQL(constraint) {
272
450
  }
273
451
  break;
274
452
  case 'FOREIGN KEY':
275
- if (constraint.columns && constraint.referencedTable && constraint.referencedColumns) {
453
+ const refTable = constraint.referencedTable || constraint.references?.table;
454
+ const refColumns = constraint.referencedColumns || constraint.references?.columns;
455
+ const onDelete = constraint.onDelete || constraint.references?.onDelete;
456
+ const onUpdate = constraint.onUpdate || constraint.references?.onUpdate;
457
+ if (constraint.columns && refTable && refColumns) {
276
458
  parts.push(`FOREIGN KEY (${constraint.columns.map(c => quoteIdentifier(c)).join(', ')})`);
277
- parts.push(`REFERENCES ${quoteIdentifier(constraint.referencedTable)}(${constraint.referencedColumns.map(c => quoteIdentifier(c)).join(', ')})`);
459
+ let refPart = `REFERENCES ${quoteIdentifier(refTable)}(${refColumns.map((c) => quoteIdentifier(c)).join(', ')})`;
460
+ if (onDelete && onDelete !== 'NO ACTION') {
461
+ refPart += ` ON DELETE ${onDelete}`;
462
+ }
463
+ if (onUpdate && onUpdate !== 'NO ACTION') {
464
+ refPart += ` ON UPDATE ${onUpdate}`;
465
+ }
466
+ parts.push(refPart);
278
467
  }
279
468
  else {
280
469
  return null;
@@ -309,32 +498,64 @@ export function generatePartitionSQL(partition) {
309
498
  }
310
499
  return parts.join(' ') + ';';
311
500
  }
312
- export function generateIndexSQL(index, tableName) {
313
- if (index.definition) {
314
- let def = index.definition.trim();
315
- if (!def.endsWith(';')) {
316
- def += ';';
317
- }
318
- return def;
319
- }
501
+ export function generateIndexSQL(index, tableName, options = {}) {
320
502
  const parts = [];
321
503
  parts.push('CREATE');
322
504
  if (index.isUnique) {
323
505
  parts.push('UNIQUE');
324
506
  }
325
507
  parts.push('INDEX');
508
+ if (options.ifNotExists) {
509
+ parts.push('IF NOT EXISTS');
510
+ }
326
511
  parts.push(quoteIdentifier(index.name));
327
512
  parts.push('ON');
328
- parts.push(quoteIdentifier(tableName));
513
+ if (options.tableOnly) {
514
+ parts.push('ONLY');
515
+ }
516
+ if (options.includeSchema && options.schema) {
517
+ parts.push(`${quoteIdentifier(options.schema)}.${quoteIdentifier(tableName)}`);
518
+ }
519
+ else {
520
+ parts.push(quoteIdentifier(tableName));
521
+ }
329
522
  if (index.type && index.type.toLowerCase() !== 'btree') {
330
- parts.push(`USING ${index.type.toUpperCase()}`);
523
+ parts.push(`USING ${index.type.toLowerCase()}`);
524
+ }
525
+ let columnSpec = null;
526
+ if (index.definition) {
527
+ const defMatch = index.definition.match(/\bON\s+(?:ONLY\s+)?[^\s(]+\s+(?:USING\s+\w+\s+)?\(([^)]+)\)/i);
528
+ if (defMatch) {
529
+ columnSpec = defMatch[1].trim();
530
+ }
531
+ }
532
+ if (columnSpec) {
533
+ parts.push(`(${columnSpec})`);
331
534
  }
332
- if (index.expression) {
535
+ else if (index.expression) {
333
536
  parts.push(`(${index.expression})`);
334
537
  }
335
- else if (index.columns && Array.isArray(index.columns) && index.columns.length > 0) {
336
- const cols = index.columns.map(c => quoteColumnRef(c)).join(', ');
337
- parts.push(`(${cols})`);
538
+ else if (index.columns) {
539
+ let columnsArray;
540
+ if (Array.isArray(index.columns)) {
541
+ columnsArray = index.columns;
542
+ }
543
+ else if (typeof index.columns === 'string') {
544
+ const str = index.columns;
545
+ if (str.startsWith('{') && str.endsWith('}')) {
546
+ columnsArray = str.slice(1, -1).split(',').map(c => c.trim().replace(/^"|"$/g, ''));
547
+ }
548
+ else {
549
+ columnsArray = [str];
550
+ }
551
+ }
552
+ else {
553
+ columnsArray = [];
554
+ }
555
+ if (columnsArray.length > 0) {
556
+ const cols = columnsArray.map(c => quoteColumnRef(c)).join(', ');
557
+ parts.push(`(${cols})`);
558
+ }
338
559
  }
339
560
  if (index.whereClause) {
340
561
  parts.push(`WHERE ${index.whereClause}`);
@@ -423,7 +644,7 @@ export function generateCommentSQL(comment) {
423
644
  }
424
645
  }
425
646
  export function generateFullSchemaSQL(schema, options = {}) {
426
- const { includeExtensions = true, includeEnums = true, includeDomains = true, includeCompositeTypes = true, includeSequences = true, includeTables = true, includePartitions = true, includeIndexes = true, includeConstraints = true, includeFunctions = true, includeTriggers = true, headerComment, } = options;
647
+ const { includeExtensions = true, includeEnums = true, includeDomains = true, includeCompositeTypes = true, includeSequences = true, includeTables = true, includePartitions = true, includeIndexes = true, includeConstraints = true, includeFunctions = true, includeTriggers = true, includeComments = true, headerComment, ifNotExists = false, } = options;
427
648
  const sections = [];
428
649
  if (headerComment) {
429
650
  sections.push(`-- ${headerComment}\n`);
@@ -464,45 +685,113 @@ export function generateFullSchemaSQL(schema, options = {}) {
464
685
  sections.push('');
465
686
  }
466
687
  if (includeTables && schema.tables && schema.tables.length > 0) {
467
- sections.push('-- Tables');
468
- for (const table of schema.tables) {
469
- sections.push(generateTableSQL(table, { includeConstraints }));
470
- sections.push('');
471
- }
472
- }
473
- if (includePartitions && schema.partitions && schema.partitions.length > 0) {
474
- sections.push('-- Partitions');
475
- for (const partition of schema.partitions) {
476
- sections.push(generatePartitionSQL(partition));
688
+ const partitionsByParent = new Map();
689
+ if (includePartitions && schema.partitions) {
690
+ for (const partition of schema.partitions) {
691
+ const parent = partition.parentTable;
692
+ if (!partitionsByParent.has(parent)) {
693
+ partitionsByParent.set(parent, []);
694
+ }
695
+ partitionsByParent.get(parent).push(partition);
696
+ }
477
697
  }
478
- sections.push('');
479
- }
480
- if (includeIndexes && schema.tables) {
481
- const indexLines = [];
482
698
  for (const table of schema.tables) {
483
- if (table.indexes && table.indexes.length > 0) {
699
+ const tableSection = [];
700
+ tableSection.push('-- ==================================================================');
701
+ tableSection.push(`-- TABLE: ${table.name}`);
702
+ tableSection.push('-- ==================================================================');
703
+ tableSection.push('');
704
+ tableSection.push('-- Table');
705
+ tableSection.push(generateTableSQL(table, { includeConstraints, ifNotExists }));
706
+ const tablePartitions = partitionsByParent.get(table.name);
707
+ if (tablePartitions && tablePartitions.length > 0) {
708
+ tableSection.push('');
709
+ tableSection.push(`-- Partitions for ${table.name}`);
710
+ for (const partition of tablePartitions) {
711
+ tableSection.push(generatePartitionSQL(partition));
712
+ }
713
+ }
714
+ if (includeIndexes && table.indexes && table.indexes.length > 0) {
715
+ const uniqueConstraintNames = new Set((table.constraints || [])
716
+ .filter(c => c.type === 'UNIQUE')
717
+ .map(c => c.name));
718
+ const indexLines = [];
484
719
  for (const index of table.indexes) {
485
- if (!index.isPrimary) {
486
- indexLines.push(generateIndexSQL(index, table.name));
720
+ if (index.isPrimary)
721
+ continue;
722
+ if (index.isUnique && uniqueConstraintNames.has(index.name))
723
+ continue;
724
+ if (index.isUnique && (index.name.endsWith('_key') ||
725
+ index.name.startsWith('unique_') ||
726
+ uniqueConstraintNames.has(index.name.replace(/_key$/, ''))))
727
+ continue;
728
+ indexLines.push(generateIndexSQL(index, table.name, { ifNotExists }));
729
+ }
730
+ if (indexLines.length > 0) {
731
+ tableSection.push('');
732
+ tableSection.push(`-- Indexes for ${table.name}`);
733
+ tableSection.push(indexLines.join('\n'));
734
+ }
735
+ }
736
+ if (includeComments) {
737
+ const commentLines = [];
738
+ if (table.comment) {
739
+ commentLines.push(generateCommentSQL({
740
+ objectType: 'table',
741
+ objectName: table.name,
742
+ comment: table.comment,
743
+ }));
744
+ }
745
+ if (table.columns) {
746
+ for (const col of table.columns) {
747
+ if (col.comment) {
748
+ commentLines.push(generateCommentSQL({
749
+ objectType: 'column',
750
+ objectName: table.name,
751
+ subObjectName: col.name,
752
+ comment: col.comment,
753
+ }));
754
+ }
487
755
  }
488
756
  }
757
+ if (table.indexes) {
758
+ for (const idx of table.indexes) {
759
+ if (idx.comment) {
760
+ commentLines.push(generateCommentSQL({
761
+ objectType: 'index',
762
+ objectName: idx.name,
763
+ comment: idx.comment,
764
+ }));
765
+ }
766
+ }
767
+ }
768
+ if (commentLines.length > 0) {
769
+ tableSection.push('');
770
+ tableSection.push(`-- Comments for ${table.name}`);
771
+ tableSection.push(commentLines.join('\n'));
772
+ }
489
773
  }
490
- }
491
- if (indexLines.length > 0) {
492
- sections.push('-- Indexes');
493
- sections.push(indexLines.join('\n'));
774
+ sections.push(tableSection.join('\n'));
775
+ sections.push('');
776
+ sections.push('');
494
777
  sections.push('');
495
778
  }
496
779
  }
497
780
  if (includeFunctions && schema.functions && schema.functions.length > 0) {
498
- sections.push('-- Functions');
781
+ sections.push('-- ==================================================================');
782
+ sections.push('-- FUNCTIONS');
783
+ sections.push('-- ==================================================================');
784
+ sections.push('');
499
785
  for (const func of schema.functions) {
500
786
  sections.push(generateFunctionSQL(func));
501
787
  sections.push('');
502
788
  }
503
789
  }
504
790
  if (includeTriggers && schema.triggers && schema.triggers.length > 0) {
505
- sections.push('-- Triggers');
791
+ sections.push('-- ==================================================================');
792
+ sections.push('-- TRIGGERS');
793
+ sections.push('-- ==================================================================');
794
+ sections.push('');
506
795
  for (const trigger of schema.triggers) {
507
796
  sections.push(generateTriggerSQL(trigger));
508
797
  }