relq 1.0.5 → 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 +149 -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 +146 -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
@@ -15,11 +15,11 @@ exports.generateTriggerSQL = generateTriggerSQL;
15
15
  exports.generateCommentSQL = generateCommentSQL;
16
16
  exports.generateFullSchemaSQL = generateFullSchemaSQL;
17
17
  const RESERVED_WORDS = new Set([
18
- 'user', 'order', 'check', 'table', 'index', 'column', 'constraint',
18
+ 'order', 'check', 'table', 'index', 'column', 'constraint',
19
19
  'primary', 'foreign', 'key', 'references', 'unique', 'default',
20
20
  'null', 'not', 'and', 'or', 'in', 'like', 'between', 'case',
21
21
  'when', 'then', 'else', 'end', 'select', 'from', 'where', 'group',
22
- 'having', 'order', 'by', 'limit', 'offset', 'join', 'left', 'right',
22
+ 'having', 'by', 'limit', 'offset', 'join', 'left', 'right',
23
23
  'inner', 'outer', 'cross', 'on', 'as', 'into', 'values', 'insert',
24
24
  'update', 'delete', 'create', 'alter', 'drop', 'grant', 'revoke',
25
25
  'all', 'any', 'some', 'exists', 'true', 'false', 'boolean', 'int',
@@ -61,7 +61,7 @@ const RESERVED_WORDS = new Set([
61
61
  'off', 'oids', 'old', 'only', 'operator', 'option', 'options',
62
62
  'ordinality', 'others', 'out', 'over', 'overlaps', 'overlay',
63
63
  'overriding', 'owned', 'owner', 'parallel', 'parser', 'partial',
64
- 'partition', 'passing', 'password', 'placing', 'plans', 'policy',
64
+ 'partition', 'passing', 'placing', 'plans', 'policy',
65
65
  'position', 'preceding', 'prepare', 'prepared', 'preserve', 'prior',
66
66
  'privileges', 'procedural', 'program', 'publication', 'quote',
67
67
  'range', 'read', 'reassign', 'recheck', 'refresh', 'reindex',
@@ -97,6 +97,34 @@ function quoteIdentifier(name) {
97
97
  }
98
98
  return name;
99
99
  }
100
+ function parseFKDefinition(definition) {
101
+ if (!definition)
102
+ return null;
103
+ const constraintNameMatch = definition.match(/^CONSTRAINT\s+"?([^"]+)"?\s+/i);
104
+ const constraintName = constraintNameMatch?.[1];
105
+ const fkPart = constraintName
106
+ ? definition.substring(constraintNameMatch[0].length)
107
+ : definition;
108
+ const fkMatch = fkPart.match(/FOREIGN\s+KEY\s*\(\s*([^)]+)\s*\)\s*REFERENCES\s+"?([^"\s(]+)"?\s*(?:\(\s*([^)]+)\s*\))?/i);
109
+ if (!fkMatch)
110
+ return null;
111
+ const parseColumnList = (str) => {
112
+ return str.split(',').map(c => c.trim().replace(/^"|"$/g, ''));
113
+ };
114
+ const columns = parseColumnList(fkMatch[1]);
115
+ const referencedTable = fkMatch[2].replace(/^"|"$/g, '');
116
+ const referencedColumns = fkMatch[3] ? parseColumnList(fkMatch[3]) : ['id'];
117
+ const onDeleteMatch = definition.match(/ON\s+DELETE\s+(CASCADE|SET\s+NULL|SET\s+DEFAULT|RESTRICT|NO\s+ACTION)/i);
118
+ const onUpdateMatch = definition.match(/ON\s+UPDATE\s+(CASCADE|SET\s+NULL|SET\s+DEFAULT|RESTRICT|NO\s+ACTION)/i);
119
+ return {
120
+ constraintName,
121
+ columns,
122
+ referencedTable,
123
+ referencedColumns,
124
+ onDelete: onDeleteMatch?.[1]?.toUpperCase().replace(/\s+/g, ' '),
125
+ onUpdate: onUpdateMatch?.[1]?.toUpperCase().replace(/\s+/g, ' '),
126
+ };
127
+ }
100
128
  function quoteColumnRef(name) {
101
129
  const lowerName = name.toLowerCase();
102
130
  if (RESERVED_WORDS.has(lowerName)) {
@@ -107,12 +135,25 @@ function quoteColumnRef(name) {
107
135
  }
108
136
  return name;
109
137
  }
138
+ const PG_TYPE_MAP = {
139
+ 'int2': 'smallint',
140
+ 'int4': 'integer',
141
+ 'int8': 'bigint',
142
+ 'float4': 'real',
143
+ 'float8': 'double precision',
144
+ 'bool': 'boolean',
145
+ 'timestamptz': 'timestamp with time zone',
146
+ 'timetz': 'time with time zone',
147
+ 'bpchar': 'character',
148
+ 'varbit': 'bit varying',
149
+ };
110
150
  function normalizePgType(pgType) {
111
151
  if (pgType.startsWith('_')) {
112
152
  const baseType = pgType.slice(1);
113
- return `${baseType}[]`;
153
+ const friendlyBase = PG_TYPE_MAP[baseType] || baseType;
154
+ return `${friendlyBase}[]`;
114
155
  }
115
- return pgType;
156
+ return PG_TYPE_MAP[pgType] || pgType;
116
157
  }
117
158
  function generateExtensionSQL(extensionName) {
118
159
  const quotedName = extensionName.includes('-') ? `"${extensionName}"` : extensionName;
@@ -190,15 +231,116 @@ function generateSequenceSQL(seq) {
190
231
  return parts.join(' ') + ';';
191
232
  }
192
233
  function generateTableSQL(table, options = {}) {
193
- const { includeConstraints = true } = options;
234
+ const { includeConstraints = true, ifNotExists = false } = options;
194
235
  const lines = [];
195
- lines.push(`CREATE TABLE ${quoteIdentifier(table.name)} (`);
236
+ const ifNotExistsClause = ifNotExists ? 'IF NOT EXISTS ' : '';
237
+ lines.push(`-- DEBUG: generateTableSQL running for ${table.name}`);
238
+ lines.push(`CREATE TABLE ${ifNotExistsClause}${quoteIdentifier(table.name)} (`);
239
+ let singleColumnPkName = null;
240
+ let pkConstraintIndex = -1;
241
+ const compositeUniqueColumns = new Set();
242
+ const inlineFKs = new Map();
243
+ const skipFKIndices = new Set();
244
+ if (table.constraints) {
245
+ const pkIndex = table.constraints.findIndex(c => c.type === 'PRIMARY KEY');
246
+ if (pkIndex !== -1) {
247
+ const pkConstraint = table.constraints[pkIndex];
248
+ pkConstraintIndex = pkIndex;
249
+ if (pkConstraint.columns && pkConstraint.columns.length === 1) {
250
+ singleColumnPkName = pkConstraint.columns[0];
251
+ }
252
+ else if (pkConstraint.definition) {
253
+ const match = pkConstraint.definition.match(/PRIMARY\s+KEY\s*\(\s*"?([^",\s)]+)"?\s*\)/i);
254
+ if (match && !pkConstraint.definition.includes(',')) {
255
+ singleColumnPkName = match[1];
256
+ }
257
+ }
258
+ }
259
+ for (const constraint of table.constraints) {
260
+ if (constraint.type === 'UNIQUE') {
261
+ let cols = [];
262
+ if (constraint.columns && constraint.columns.length > 0) {
263
+ if (Array.isArray(constraint.columns)) {
264
+ cols = constraint.columns;
265
+ }
266
+ else if (typeof constraint.columns === 'string') {
267
+ const str = constraint.columns;
268
+ if (str.startsWith('{') && str.endsWith('}')) {
269
+ cols = str.slice(1, -1).split(',').map(c => c.trim().replace(/^"|"$/g, ''));
270
+ }
271
+ }
272
+ }
273
+ else if (constraint.definition) {
274
+ const match = constraint.definition.match(/UNIQUE\s*\(([^)]+)\)/i);
275
+ if (match) {
276
+ cols = match[1].split(',').map(c => c.trim().replace(/^"|"$/g, ''));
277
+ }
278
+ }
279
+ if (cols.length > 1) {
280
+ for (const col of cols) {
281
+ compositeUniqueColumns.add(col);
282
+ }
283
+ }
284
+ }
285
+ }
286
+ table.constraints.forEach((constraint, index) => {
287
+ if (constraint.type === 'FOREIGN KEY') {
288
+ let fkColumns = constraint.columns;
289
+ let refTable = constraint.referencedTable || constraint.references?.table;
290
+ let refColumns = constraint.referencedColumns || constraint.references?.columns;
291
+ let onDelete = constraint.onDelete || constraint.references?.onDelete;
292
+ let onUpdate = constraint.onUpdate || constraint.references?.onUpdate;
293
+ let constraintName = constraint.name;
294
+ if (!refTable && constraint.definition) {
295
+ const parsed = parseFKDefinition(constraint.definition);
296
+ if (parsed) {
297
+ fkColumns = parsed.columns;
298
+ refTable = parsed.referencedTable;
299
+ refColumns = parsed.referencedColumns;
300
+ onDelete = parsed.onDelete;
301
+ onUpdate = parsed.onUpdate;
302
+ if (!constraintName) {
303
+ constraintName = parsed.constraintName;
304
+ }
305
+ }
306
+ }
307
+ const isSingleColumn = fkColumns && fkColumns.length === 1;
308
+ let hasExplicitName = false;
309
+ if (constraintName && fkColumns && fkColumns.length > 0) {
310
+ const colName = fkColumns[0];
311
+ const expectedAutoName = `${table.name}_${colName}_fkey`;
312
+ hasExplicitName = constraintName.toLowerCase() !== expectedAutoName.toLowerCase();
313
+ }
314
+ if (isSingleColumn && refTable) {
315
+ const colName = fkColumns[0];
316
+ inlineFKs.set(colName, {
317
+ table: refTable,
318
+ column: refColumns?.[0],
319
+ onDelete: onDelete,
320
+ onUpdate: onUpdate,
321
+ constraintName: hasExplicitName ? constraintName : undefined,
322
+ });
323
+ skipFKIndices.add(index);
324
+ }
325
+ }
326
+ });
327
+ }
196
328
  const columnLines = [];
197
329
  for (const col of table.columns) {
198
- columnLines.push(generateColumnSQL(col));
330
+ const isInlinePk = singleColumnPkName === col.name;
331
+ const skipInlineUnique = compositeUniqueColumns.has(col.name);
332
+ const inlineFKInfo = inlineFKs.get(col.name);
333
+ columnLines.push(generateColumnSQL(col, { inlinePrimaryKey: isInlinePk, skipInlineUnique, inlineForeignKey: inlineFKInfo }));
199
334
  }
200
335
  if (includeConstraints && table.constraints) {
201
- for (const constraint of table.constraints) {
336
+ for (let i = 0; i < table.constraints.length; i++) {
337
+ const constraint = table.constraints[i];
338
+ if (constraint.type === 'PRIMARY KEY' && singleColumnPkName) {
339
+ continue;
340
+ }
341
+ if (constraint.type === 'FOREIGN KEY' && skipFKIndices.has(i)) {
342
+ continue;
343
+ }
202
344
  const constraintSQL = generateInlineConstraintSQL(constraint);
203
345
  if (constraintSQL) {
204
346
  columnLines.push(constraintSQL);
@@ -217,9 +359,21 @@ function generateTableSQL(table, options = {}) {
217
359
  }
218
360
  return lines.join('\n');
219
361
  }
220
- function generateColumnSQL(col) {
362
+ function generateColumnSQL(col, options = {}) {
363
+ const { inlinePrimaryKey = false, skipInlineUnique = false, inlineForeignKey } = options;
221
364
  const parts = [];
222
- const normalizedType = normalizePgType(col.dataType);
365
+ let normalizedType = normalizePgType(col.dataType);
366
+ const baseType = normalizedType.replace('[]', '').toLowerCase();
367
+ if ((baseType === 'varchar' || baseType === 'character varying') && col.maxLength) {
368
+ normalizedType = `varchar(${col.maxLength})${normalizedType.endsWith('[]') ? '[]' : ''}`;
369
+ }
370
+ else if ((baseType === 'char' || baseType === 'character' || baseType === 'bpchar') && col.maxLength) {
371
+ normalizedType = `char(${col.maxLength})${normalizedType.endsWith('[]') ? '[]' : ''}`;
372
+ }
373
+ else if (baseType === 'numeric' && col.precision) {
374
+ const precisionStr = col.scale ? `${col.precision},${col.scale}` : `${col.precision}`;
375
+ normalizedType = `numeric(${precisionStr})${normalizedType.endsWith('[]') ? '[]' : ''}`;
376
+ }
223
377
  parts.push(`${quoteIdentifier(col.name).padEnd(28)}${normalizedType}`);
224
378
  if (col.dataType.endsWith('[]')) {
225
379
  }
@@ -230,44 +384,68 @@ function generateColumnSQL(col) {
230
384
  if (col.identityGeneration) {
231
385
  parts.push(`GENERATED ${col.identityGeneration} AS IDENTITY`);
232
386
  }
233
- if (!col.isNullable && !col.isPrimaryKey && !col.identityGeneration) {
387
+ if (!col.isNullable && !col.identityGeneration && !inlinePrimaryKey) {
234
388
  parts.push('NOT NULL');
235
389
  }
236
390
  if (col.defaultValue !== null && col.defaultValue !== undefined && !col.isGenerated) {
237
391
  parts.push(`DEFAULT ${col.defaultValue}`);
238
392
  }
239
- if (col.isUnique && !col.isPrimaryKey) {
393
+ if (inlinePrimaryKey) {
394
+ parts.push('PRIMARY KEY');
395
+ }
396
+ if (col.isUnique && !col.isPrimaryKey && !inlinePrimaryKey && !skipInlineUnique) {
240
397
  parts.push('UNIQUE');
241
398
  }
242
- if (col.references) {
243
- const ref = col.references;
244
- let refStr = `REFERENCES ${quoteIdentifier(ref.table)}(${quoteIdentifier(ref.column)})`;
245
- if (ref.onDelete && ref.onDelete !== 'NO ACTION') {
246
- refStr += ` ON DELETE ${ref.onDelete}`;
399
+ if (inlineForeignKey) {
400
+ let refPart = '';
401
+ if (inlineForeignKey.constraintName) {
402
+ refPart += `CONSTRAINT ${quoteIdentifier(inlineForeignKey.constraintName)} `;
403
+ }
404
+ refPart += `REFERENCES ${quoteIdentifier(inlineForeignKey.table)}`;
405
+ if (inlineForeignKey.column && inlineForeignKey.column.toLowerCase() !== 'id') {
406
+ refPart += `(${quoteIdentifier(inlineForeignKey.column)})`;
247
407
  }
248
- if (ref.onUpdate && ref.onUpdate !== 'NO ACTION') {
249
- refStr += ` ON UPDATE ${ref.onUpdate}`;
408
+ if (inlineForeignKey.onDelete && inlineForeignKey.onDelete !== 'NO ACTION') {
409
+ refPart += ` ON DELETE ${inlineForeignKey.onDelete}`;
250
410
  }
251
- parts.push(refStr);
411
+ if (inlineForeignKey.onUpdate && inlineForeignKey.onUpdate !== 'NO ACTION') {
412
+ refPart += ` ON UPDATE ${inlineForeignKey.onUpdate}`;
413
+ }
414
+ parts.push(refPart);
252
415
  }
253
416
  if (col.check) {
254
417
  parts.push(`CHECK (${col.check})`);
255
418
  }
256
419
  return parts.join(' ');
257
420
  }
421
+ function unquoteIdentifiers(sql) {
422
+ return sql.replace(/"([a-z_][a-z0-9_]*)"/g, (match, identifier) => {
423
+ if (RESERVED_WORDS.has(identifier.toLowerCase())) {
424
+ return match;
425
+ }
426
+ return identifier;
427
+ });
428
+ }
258
429
  function generateInlineConstraintSQL(constraint) {
259
430
  if (constraint.definition) {
260
- const def = constraint.definition.trim();
431
+ let def = constraint.definition.trim();
432
+ def = unquoteIdentifiers(def);
433
+ if (constraint.type === 'PRIMARY KEY') {
434
+ const pkMatch = def.match(/PRIMARY\s+KEY\s*\([^)]+\)/i);
435
+ if (pkMatch) {
436
+ return pkMatch[0];
437
+ }
438
+ }
261
439
  if (def.toUpperCase().startsWith('CONSTRAINT')) {
262
440
  return def;
263
441
  }
264
- if (constraint.name) {
442
+ if (constraint.name && constraint.type !== 'PRIMARY KEY') {
265
443
  return `CONSTRAINT ${quoteIdentifier(constraint.name)} ${def}`;
266
444
  }
267
445
  return def;
268
446
  }
269
447
  const parts = [];
270
- if (constraint.name) {
448
+ if (constraint.name && constraint.type !== 'PRIMARY KEY') {
271
449
  parts.push(`CONSTRAINT ${quoteIdentifier(constraint.name)}`);
272
450
  }
273
451
  switch (constraint.type) {
@@ -288,9 +466,20 @@ function generateInlineConstraintSQL(constraint) {
288
466
  }
289
467
  break;
290
468
  case 'FOREIGN KEY':
291
- if (constraint.columns && constraint.referencedTable && constraint.referencedColumns) {
469
+ const refTable = constraint.referencedTable || constraint.references?.table;
470
+ const refColumns = constraint.referencedColumns || constraint.references?.columns;
471
+ const onDelete = constraint.onDelete || constraint.references?.onDelete;
472
+ const onUpdate = constraint.onUpdate || constraint.references?.onUpdate;
473
+ if (constraint.columns && refTable && refColumns) {
292
474
  parts.push(`FOREIGN KEY (${constraint.columns.map(c => quoteIdentifier(c)).join(', ')})`);
293
- parts.push(`REFERENCES ${quoteIdentifier(constraint.referencedTable)}(${constraint.referencedColumns.map(c => quoteIdentifier(c)).join(', ')})`);
475
+ let refPart = `REFERENCES ${quoteIdentifier(refTable)}(${refColumns.map((c) => quoteIdentifier(c)).join(', ')})`;
476
+ if (onDelete && onDelete !== 'NO ACTION') {
477
+ refPart += ` ON DELETE ${onDelete}`;
478
+ }
479
+ if (onUpdate && onUpdate !== 'NO ACTION') {
480
+ refPart += ` ON UPDATE ${onUpdate}`;
481
+ }
482
+ parts.push(refPart);
294
483
  }
295
484
  else {
296
485
  return null;
@@ -325,32 +514,64 @@ function generatePartitionSQL(partition) {
325
514
  }
326
515
  return parts.join(' ') + ';';
327
516
  }
328
- function generateIndexSQL(index, tableName) {
329
- if (index.definition) {
330
- let def = index.definition.trim();
331
- if (!def.endsWith(';')) {
332
- def += ';';
333
- }
334
- return def;
335
- }
517
+ function generateIndexSQL(index, tableName, options = {}) {
336
518
  const parts = [];
337
519
  parts.push('CREATE');
338
520
  if (index.isUnique) {
339
521
  parts.push('UNIQUE');
340
522
  }
341
523
  parts.push('INDEX');
524
+ if (options.ifNotExists) {
525
+ parts.push('IF NOT EXISTS');
526
+ }
342
527
  parts.push(quoteIdentifier(index.name));
343
528
  parts.push('ON');
344
- parts.push(quoteIdentifier(tableName));
529
+ if (options.tableOnly) {
530
+ parts.push('ONLY');
531
+ }
532
+ if (options.includeSchema && options.schema) {
533
+ parts.push(`${quoteIdentifier(options.schema)}.${quoteIdentifier(tableName)}`);
534
+ }
535
+ else {
536
+ parts.push(quoteIdentifier(tableName));
537
+ }
345
538
  if (index.type && index.type.toLowerCase() !== 'btree') {
346
- parts.push(`USING ${index.type.toUpperCase()}`);
539
+ parts.push(`USING ${index.type.toLowerCase()}`);
540
+ }
541
+ let columnSpec = null;
542
+ if (index.definition) {
543
+ const defMatch = index.definition.match(/\bON\s+(?:ONLY\s+)?[^\s(]+\s+(?:USING\s+\w+\s+)?\(([^)]+)\)/i);
544
+ if (defMatch) {
545
+ columnSpec = defMatch[1].trim();
546
+ }
547
+ }
548
+ if (columnSpec) {
549
+ parts.push(`(${columnSpec})`);
347
550
  }
348
- if (index.expression) {
551
+ else if (index.expression) {
349
552
  parts.push(`(${index.expression})`);
350
553
  }
351
- else if (index.columns && Array.isArray(index.columns) && index.columns.length > 0) {
352
- const cols = index.columns.map(c => quoteColumnRef(c)).join(', ');
353
- parts.push(`(${cols})`);
554
+ else if (index.columns) {
555
+ let columnsArray;
556
+ if (Array.isArray(index.columns)) {
557
+ columnsArray = index.columns;
558
+ }
559
+ else if (typeof index.columns === 'string') {
560
+ const str = index.columns;
561
+ if (str.startsWith('{') && str.endsWith('}')) {
562
+ columnsArray = str.slice(1, -1).split(',').map(c => c.trim().replace(/^"|"$/g, ''));
563
+ }
564
+ else {
565
+ columnsArray = [str];
566
+ }
567
+ }
568
+ else {
569
+ columnsArray = [];
570
+ }
571
+ if (columnsArray.length > 0) {
572
+ const cols = columnsArray.map(c => quoteColumnRef(c)).join(', ');
573
+ parts.push(`(${cols})`);
574
+ }
354
575
  }
355
576
  if (index.whereClause) {
356
577
  parts.push(`WHERE ${index.whereClause}`);
@@ -439,7 +660,7 @@ function generateCommentSQL(comment) {
439
660
  }
440
661
  }
441
662
  function generateFullSchemaSQL(schema, options = {}) {
442
- 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;
663
+ 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;
443
664
  const sections = [];
444
665
  if (headerComment) {
445
666
  sections.push(`-- ${headerComment}\n`);
@@ -480,45 +701,113 @@ function generateFullSchemaSQL(schema, options = {}) {
480
701
  sections.push('');
481
702
  }
482
703
  if (includeTables && schema.tables && schema.tables.length > 0) {
483
- sections.push('-- Tables');
484
- for (const table of schema.tables) {
485
- sections.push(generateTableSQL(table, { includeConstraints }));
486
- sections.push('');
487
- }
488
- }
489
- if (includePartitions && schema.partitions && schema.partitions.length > 0) {
490
- sections.push('-- Partitions');
491
- for (const partition of schema.partitions) {
492
- sections.push(generatePartitionSQL(partition));
704
+ const partitionsByParent = new Map();
705
+ if (includePartitions && schema.partitions) {
706
+ for (const partition of schema.partitions) {
707
+ const parent = partition.parentTable;
708
+ if (!partitionsByParent.has(parent)) {
709
+ partitionsByParent.set(parent, []);
710
+ }
711
+ partitionsByParent.get(parent).push(partition);
712
+ }
493
713
  }
494
- sections.push('');
495
- }
496
- if (includeIndexes && schema.tables) {
497
- const indexLines = [];
498
714
  for (const table of schema.tables) {
499
- if (table.indexes && table.indexes.length > 0) {
715
+ const tableSection = [];
716
+ tableSection.push('-- ==================================================================');
717
+ tableSection.push(`-- TABLE: ${table.name}`);
718
+ tableSection.push('-- ==================================================================');
719
+ tableSection.push('');
720
+ tableSection.push('-- Table');
721
+ tableSection.push(generateTableSQL(table, { includeConstraints, ifNotExists }));
722
+ const tablePartitions = partitionsByParent.get(table.name);
723
+ if (tablePartitions && tablePartitions.length > 0) {
724
+ tableSection.push('');
725
+ tableSection.push(`-- Partitions for ${table.name}`);
726
+ for (const partition of tablePartitions) {
727
+ tableSection.push(generatePartitionSQL(partition));
728
+ }
729
+ }
730
+ if (includeIndexes && table.indexes && table.indexes.length > 0) {
731
+ const uniqueConstraintNames = new Set((table.constraints || [])
732
+ .filter(c => c.type === 'UNIQUE')
733
+ .map(c => c.name));
734
+ const indexLines = [];
500
735
  for (const index of table.indexes) {
501
- if (!index.isPrimary) {
502
- indexLines.push(generateIndexSQL(index, table.name));
736
+ if (index.isPrimary)
737
+ continue;
738
+ if (index.isUnique && uniqueConstraintNames.has(index.name))
739
+ continue;
740
+ if (index.isUnique && (index.name.endsWith('_key') ||
741
+ index.name.startsWith('unique_') ||
742
+ uniqueConstraintNames.has(index.name.replace(/_key$/, ''))))
743
+ continue;
744
+ indexLines.push(generateIndexSQL(index, table.name, { ifNotExists }));
745
+ }
746
+ if (indexLines.length > 0) {
747
+ tableSection.push('');
748
+ tableSection.push(`-- Indexes for ${table.name}`);
749
+ tableSection.push(indexLines.join('\n'));
750
+ }
751
+ }
752
+ if (includeComments) {
753
+ const commentLines = [];
754
+ if (table.comment) {
755
+ commentLines.push(generateCommentSQL({
756
+ objectType: 'table',
757
+ objectName: table.name,
758
+ comment: table.comment,
759
+ }));
760
+ }
761
+ if (table.columns) {
762
+ for (const col of table.columns) {
763
+ if (col.comment) {
764
+ commentLines.push(generateCommentSQL({
765
+ objectType: 'column',
766
+ objectName: table.name,
767
+ subObjectName: col.name,
768
+ comment: col.comment,
769
+ }));
770
+ }
503
771
  }
504
772
  }
773
+ if (table.indexes) {
774
+ for (const idx of table.indexes) {
775
+ if (idx.comment) {
776
+ commentLines.push(generateCommentSQL({
777
+ objectType: 'index',
778
+ objectName: idx.name,
779
+ comment: idx.comment,
780
+ }));
781
+ }
782
+ }
783
+ }
784
+ if (commentLines.length > 0) {
785
+ tableSection.push('');
786
+ tableSection.push(`-- Comments for ${table.name}`);
787
+ tableSection.push(commentLines.join('\n'));
788
+ }
505
789
  }
506
- }
507
- if (indexLines.length > 0) {
508
- sections.push('-- Indexes');
509
- sections.push(indexLines.join('\n'));
790
+ sections.push(tableSection.join('\n'));
791
+ sections.push('');
792
+ sections.push('');
510
793
  sections.push('');
511
794
  }
512
795
  }
513
796
  if (includeFunctions && schema.functions && schema.functions.length > 0) {
514
- sections.push('-- Functions');
797
+ sections.push('-- ==================================================================');
798
+ sections.push('-- FUNCTIONS');
799
+ sections.push('-- ==================================================================');
800
+ sections.push('');
515
801
  for (const func of schema.functions) {
516
802
  sections.push(generateFunctionSQL(func));
517
803
  sections.push('');
518
804
  }
519
805
  }
520
806
  if (includeTriggers && schema.triggers && schema.triggers.length > 0) {
521
- sections.push('-- Triggers');
807
+ sections.push('-- ==================================================================');
808
+ sections.push('-- TRIGGERS');
809
+ sections.push('-- ==================================================================');
810
+ sections.push('');
522
811
  for (const trigger of schema.triggers) {
523
812
  sections.push(generateTriggerSQL(trigger));
524
813
  }