relq 1.0.2 → 1.0.4

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 (92) hide show
  1. package/dist/cjs/cli/commands/add.cjs +403 -27
  2. package/dist/cjs/cli/commands/branch.cjs +13 -23
  3. package/dist/cjs/cli/commands/checkout.cjs +16 -29
  4. package/dist/cjs/cli/commands/cherry-pick.cjs +3 -4
  5. package/dist/cjs/cli/commands/commit.cjs +21 -29
  6. package/dist/cjs/cli/commands/diff.cjs +28 -32
  7. package/dist/cjs/cli/commands/export.cjs +7 -7
  8. package/dist/cjs/cli/commands/fetch.cjs +15 -21
  9. package/dist/cjs/cli/commands/generate.cjs +28 -54
  10. package/dist/cjs/cli/commands/history.cjs +19 -40
  11. package/dist/cjs/cli/commands/import.cjs +34 -41
  12. package/dist/cjs/cli/commands/init.cjs +69 -59
  13. package/dist/cjs/cli/commands/introspect.cjs +4 -8
  14. package/dist/cjs/cli/commands/log.cjs +26 -32
  15. package/dist/cjs/cli/commands/merge.cjs +24 -41
  16. package/dist/cjs/cli/commands/migrate.cjs +12 -25
  17. package/dist/cjs/cli/commands/pull.cjs +216 -106
  18. package/dist/cjs/cli/commands/push.cjs +35 -75
  19. package/dist/cjs/cli/commands/remote.cjs +2 -1
  20. package/dist/cjs/cli/commands/reset.cjs +22 -43
  21. package/dist/cjs/cli/commands/resolve.cjs +12 -14
  22. package/dist/cjs/cli/commands/rollback.cjs +16 -38
  23. package/dist/cjs/cli/commands/stash.cjs +5 -7
  24. package/dist/cjs/cli/commands/status.cjs +5 -10
  25. package/dist/cjs/cli/commands/sync.cjs +30 -50
  26. package/dist/cjs/cli/commands/tag.cjs +3 -4
  27. package/dist/cjs/cli/index.cjs +72 -9
  28. package/dist/cjs/cli/utils/change-tracker.cjs +107 -3
  29. package/dist/cjs/cli/utils/cli-utils.cjs +217 -0
  30. package/dist/cjs/cli/utils/config-loader.cjs +34 -8
  31. package/dist/cjs/cli/utils/fast-introspect.cjs +109 -3
  32. package/dist/cjs/cli/utils/git-utils.cjs +42 -161
  33. package/dist/cjs/cli/utils/pool-manager.cjs +156 -0
  34. package/dist/cjs/cli/utils/project-root.cjs +56 -5
  35. package/dist/cjs/cli/utils/relqignore.cjs +1 -0
  36. package/dist/cjs/cli/utils/repo-manager.cjs +47 -0
  37. package/dist/cjs/cli/utils/schema-comparator.cjs +301 -11
  38. package/dist/cjs/cli/utils/schema-diff.cjs +202 -1
  39. package/dist/cjs/cli/utils/schema-hash.cjs +2 -1
  40. package/dist/cjs/cli/utils/schema-introspect.cjs +7 -3
  41. package/dist/cjs/cli/utils/snapshot-manager.cjs +1 -0
  42. package/dist/cjs/cli/utils/spinner.cjs +14 -106
  43. package/dist/cjs/cli/utils/sql-generator.cjs +10 -2
  44. package/dist/cjs/cli/utils/type-generator.cjs +28 -16
  45. package/dist/config.d.ts +16 -6
  46. package/dist/esm/cli/commands/add.js +372 -29
  47. package/dist/esm/cli/commands/branch.js +14 -24
  48. package/dist/esm/cli/commands/checkout.js +16 -29
  49. package/dist/esm/cli/commands/cherry-pick.js +3 -4
  50. package/dist/esm/cli/commands/commit.js +22 -30
  51. package/dist/esm/cli/commands/diff.js +6 -10
  52. package/dist/esm/cli/commands/export.js +8 -8
  53. package/dist/esm/cli/commands/fetch.js +14 -20
  54. package/dist/esm/cli/commands/generate.js +28 -54
  55. package/dist/esm/cli/commands/history.js +11 -32
  56. package/dist/esm/cli/commands/import.js +35 -42
  57. package/dist/esm/cli/commands/init.js +65 -55
  58. package/dist/esm/cli/commands/introspect.js +4 -8
  59. package/dist/esm/cli/commands/log.js +6 -12
  60. package/dist/esm/cli/commands/merge.js +20 -37
  61. package/dist/esm/cli/commands/migrate.js +12 -25
  62. package/dist/esm/cli/commands/pull.js +204 -94
  63. package/dist/esm/cli/commands/push.js +21 -61
  64. package/dist/esm/cli/commands/remote.js +2 -1
  65. package/dist/esm/cli/commands/reset.js +16 -37
  66. package/dist/esm/cli/commands/resolve.js +13 -15
  67. package/dist/esm/cli/commands/rollback.js +16 -38
  68. package/dist/esm/cli/commands/stash.js +6 -8
  69. package/dist/esm/cli/commands/status.js +6 -11
  70. package/dist/esm/cli/commands/sync.js +30 -50
  71. package/dist/esm/cli/commands/tag.js +3 -4
  72. package/dist/esm/cli/index.js +72 -9
  73. package/dist/esm/cli/utils/change-tracker.js +107 -3
  74. package/dist/esm/cli/utils/cli-utils.js +169 -0
  75. package/dist/esm/cli/utils/config-loader.js +34 -8
  76. package/dist/esm/cli/utils/fast-introspect.js +109 -3
  77. package/dist/esm/cli/utils/git-utils.js +2 -124
  78. package/dist/esm/cli/utils/pool-manager.js +114 -0
  79. package/dist/esm/cli/utils/project-root.js +55 -5
  80. package/dist/esm/cli/utils/relqignore.js +1 -0
  81. package/dist/esm/cli/utils/repo-manager.js +42 -0
  82. package/dist/esm/cli/utils/schema-comparator.js +301 -11
  83. package/dist/esm/cli/utils/schema-diff.js +202 -1
  84. package/dist/esm/cli/utils/schema-hash.js +2 -1
  85. package/dist/esm/cli/utils/schema-introspect.js +7 -3
  86. package/dist/esm/cli/utils/snapshot-manager.js +1 -0
  87. package/dist/esm/cli/utils/spinner.js +1 -101
  88. package/dist/esm/cli/utils/sql-generator.js +10 -2
  89. package/dist/esm/cli/utils/type-generator.js +28 -16
  90. package/dist/index.d.ts +25 -8
  91. package/dist/schema-builder.d.ts +18 -7
  92. package/package.json +1 -1
@@ -41,16 +41,11 @@ const spinner_1 = require("../utils/spinner.cjs");
41
41
  const relqignore_1 = require("../utils/relqignore.cjs");
42
42
  const repo_manager_1 = require("../utils/repo-manager.cjs");
43
43
  async function statusCommand(context) {
44
- const { config, flags } = context;
45
- const projectRoot = process.cwd();
44
+ const { config, flags, projectRoot } = context;
46
45
  const connection = config?.connection;
47
46
  console.log('');
48
47
  if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
49
- console.log(`${spinner_1.colors.red('fatal:')} not a relq repository`);
50
- console.log('');
51
- console.log(`${spinner_1.colors.muted('Run')} ${spinner_1.colors.cyan('relq init')} ${spinner_1.colors.muted('to initialize.')}`);
52
- console.log('');
53
- return;
48
+ (0, spinner_1.fatal)('not a relq repository (or any parent directories): .relq', `Run ${spinner_1.colors.cyan('relq init')} to initialize.`);
54
49
  }
55
50
  const head = (0, repo_manager_1.getHead)(projectRoot);
56
51
  const staged = (0, repo_manager_1.getStagedChanges)(projectRoot);
@@ -73,7 +68,7 @@ async function statusCommand(context) {
73
68
  console.log(`${spinner_1.colors.green('Changes to be committed:')}`);
74
69
  console.log(` ${spinner_1.colors.muted('(use "relq restore --staged <name>..." to unstage)')}`);
75
70
  console.log('');
76
- displayChanges(staged, ' ');
71
+ displayChanges(staged, ' ');
77
72
  console.log('');
78
73
  }
79
74
  if (unstaged.length > 0) {
@@ -81,7 +76,7 @@ async function statusCommand(context) {
81
76
  console.log(` ${spinner_1.colors.muted('(use "relq add <name>..." to stage)')}`);
82
77
  console.log(` ${spinner_1.colors.muted('(use "relq restore <name>..." to discard)')}`);
83
78
  console.log('');
84
- displayChanges(unstaged, ' ');
79
+ displayChanges(unstaged, ' ');
85
80
  console.log('');
86
81
  }
87
82
  const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
@@ -92,7 +87,7 @@ async function statusCommand(context) {
92
87
  !p.raw.startsWith('_temp_') &&
93
88
  !p.raw.startsWith('tmp_'));
94
89
  if (userPatterns.length > 0) {
95
- console.log(`${spinner_1.colors.muted(`ℹ ${userPatterns.length} pattern(s) active from .relqignore`)}`);
90
+ console.log(`${spinner_1.colors.muted(`${userPatterns.length} pattern(s) active from .relqignore`)}`);
96
91
  console.log('');
97
92
  }
98
93
  }
@@ -3,77 +3,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.syncCommand = syncCommand;
4
4
  const config_loader_1 = require("../utils/config-loader.cjs");
5
5
  const env_loader_1 = require("../utils/env-loader.cjs");
6
- const spinner_1 = require("../utils/spinner.cjs");
6
+ const cli_utils_1 = require("../utils/cli-utils.cjs");
7
7
  const repo_manager_1 = require("../utils/repo-manager.cjs");
8
8
  const pull_1 = require("./pull.cjs");
9
9
  async function syncCommand(context) {
10
10
  const { config, flags } = context;
11
11
  if (!config) {
12
- console.error('Error: No configuration found.');
13
- process.exit(1);
12
+ (0, cli_utils_1.fatal)('No configuration found', `Run ${cli_utils_1.colors.cyan('relq init')} to create one.`);
14
13
  }
15
- (0, config_loader_1.requireValidConfig)(config);
14
+ await (0, config_loader_1.requireValidConfig)(config, { calledFrom: 'sync' });
16
15
  const connection = config.connection;
17
- const projectRoot = process.cwd();
16
+ const { projectRoot } = context;
18
17
  console.log('');
19
18
  if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
20
- console.log(`${spinner_1.colors.red('fatal:')} not a relq repository`);
21
- console.log('');
22
- console.log(`${spinner_1.colors.muted('Run')} ${spinner_1.colors.cyan('relq init')} ${spinner_1.colors.muted('to initialize.')}`);
23
- return;
19
+ (0, cli_utils_1.fatal)('not a relq repository (or any parent directories): .relq', `Run ${cli_utils_1.colors.cyan('relq init')} to initialize.`);
24
20
  }
25
- const spinner = (0, spinner_1.createSpinner)();
21
+ const spinner = (0, cli_utils_1.createSpinner)();
26
22
  try {
27
- spinner.start('Checking sync status...');
28
- const status = await (0, repo_manager_1.getRepoStatus)(connection, projectRoot);
29
- spinner.stop();
30
- console.log(`On database: ${spinner_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}`);
23
+ console.log(`Syncing with ${cli_utils_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}...`);
31
24
  console.log('');
32
- if (status.behindBy > 0) {
33
- console.log(`${spinner_1.colors.yellow('↓')} ${status.behindBy} commit(s) to pull`);
34
- }
35
- if (status.aheadBy > 0) {
36
- console.log(`${spinner_1.colors.green('↑')} ${status.aheadBy} commit(s) to push`);
37
- }
38
- if (status.behindBy === 0 && status.aheadBy === 0) {
39
- console.log(`${spinner_1.colors.green('✓')} Already in sync`);
25
+ await (0, pull_1.pullCommand)(context);
26
+ await (0, repo_manager_1.ensureRemoteTable)(connection);
27
+ const remoteCommits = await (0, repo_manager_1.fetchRemoteCommits)(connection, 100);
28
+ const remoteHashes = new Set(remoteCommits.map(c => c.hash));
29
+ const localCommitsAfter = (0, repo_manager_1.getAllCommits)(projectRoot);
30
+ const toPush = localCommitsAfter.filter(c => !remoteHashes.has(c.hash));
31
+ if (toPush.length === 0) {
32
+ (0, cli_utils_1.success)('Sync complete - up to date');
40
33
  console.log('');
41
34
  return;
42
35
  }
36
+ console.log(`Pushing ${toPush.length} local commit(s)...`);
43
37
  console.log('');
44
- if (status.behindBy > 0) {
45
- console.log(`${spinner_1.colors.cyan('●')} Pulling changes...`);
46
- console.log('');
47
- const pullContext = {
48
- ...context,
49
- flags: { ...flags, force: true },
50
- };
51
- await (0, pull_1.pullCommand)(pullContext);
38
+ spinner.start(`Pushing ${toPush.length} commit(s)...`);
39
+ for (const commit of toPush.reverse()) {
40
+ await (0, repo_manager_1.pushCommit)(connection, commit);
52
41
  }
53
- if (status.aheadBy > 0) {
54
- console.log(`${spinner_1.colors.cyan('')} Pushing commits...`);
55
- console.log('');
56
- await (0, repo_manager_1.ensureRemoteTable)(connection);
57
- const localCommits = (0, repo_manager_1.getAllCommits)(projectRoot);
58
- const remoteCommits = await (0, repo_manager_1.fetchRemoteCommits)(connection, 100);
59
- const remoteHashes = new Set(remoteCommits.map(c => c.hash));
60
- const toPush = localCommits.filter(c => !remoteHashes.has(c.hash));
61
- spinner.start(`Pushing ${toPush.length} commit(s)...`);
62
- for (const commit of toPush.reverse()) {
63
- await (0, repo_manager_1.pushCommit)(connection, commit);
64
- }
65
- spinner.succeed(`Pushed ${toPush.length} commit(s)`);
42
+ spinner.succeed(`Pushed ${toPush.length} commit(s)`);
43
+ console.log('');
44
+ console.log('Pushed commits:');
45
+ for (const commit of toPush.slice(0, 5)) {
46
+ console.log(` ${cli_utils_1.colors.yellow((0, repo_manager_1.shortHash)(commit.hash))} ${commit.message}`);
47
+ }
48
+ if (toPush.length > 5) {
49
+ console.log(` ${cli_utils_1.colors.muted(`... and ${toPush.length - 5} more`)}`);
66
50
  }
67
51
  console.log('');
68
- const pulledText = status.behindBy > 0 ? `${spinner_1.colors.yellow(`↓ ${status.behindBy}`)} pulled` : '';
69
- const pushedText = status.aheadBy > 0 ? `${spinner_1.colors.green(`↑ ${status.aheadBy}`)} pushed` : '';
70
- const separator = pulledText && pushedText ? ' | ' : '';
71
- console.log(`${spinner_1.colors.green('✓')} Sync complete: ${pulledText}${separator}${pushedText}`);
52
+ (0, cli_utils_1.success)(`Sync complete - pushed ${toPush.length} commit(s)`);
72
53
  }
73
- catch (error) {
54
+ catch (err) {
74
55
  spinner.fail('Sync failed');
75
- console.error(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : error}`));
76
- process.exit(1);
56
+ (0, cli_utils_1.fatal)(err instanceof Error ? err.message : String(err));
77
57
  }
78
58
  console.log('');
79
59
  }
@@ -50,8 +50,7 @@ function saveTags(tags, projectRoot) {
50
50
  fs.writeFileSync(tagPath, JSON.stringify(tags, null, 2));
51
51
  }
52
52
  async function tagCommand(context) {
53
- const { args, flags } = context;
54
- const projectRoot = process.cwd();
53
+ const { args, flags, projectRoot } = context;
55
54
  console.log('');
56
55
  if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
57
56
  console.log(`${spinner_1.colors.red('fatal:')} not a relq repository`);
@@ -75,7 +74,7 @@ async function tagCommand(context) {
75
74
  }
76
75
  delete tags[tagName];
77
76
  saveTags(tags, projectRoot);
78
- console.log(`${spinner_1.colors.green('✓')} Deleted tag '${tagName}'`);
77
+ console.log(`Deleted tag '${tagName}'`);
79
78
  console.log('');
80
79
  return;
81
80
  }
@@ -123,7 +122,7 @@ async function tagCommand(context) {
123
122
  createdAt: new Date().toISOString(),
124
123
  };
125
124
  saveTags(tags, projectRoot);
126
- console.log(`${spinner_1.colors.green('✓')} Tagged ${spinner_1.colors.yellow((0, repo_manager_1.shortHash)(hash))} as '${spinner_1.colors.cyan(tagName)}'`);
125
+ console.log(`Tagged ${spinner_1.colors.yellow((0, repo_manager_1.shortHash)(hash))} as '${tagName}'`);
127
126
  console.log(` ${commit.message}`);
128
127
  console.log('');
129
128
  return;
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
3
  if (k2 === undefined) k2 = k;
@@ -34,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
33
  };
35
34
  })();
36
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.main = main;
37
37
  const init_1 = require("./commands/init.cjs");
38
38
  const pull_1 = require("./commands/pull.cjs");
39
39
  const push_1 = require("./commands/push.cjs");
@@ -57,6 +57,34 @@ const merge_1 = require("./commands/merge.cjs");
57
57
  const tag_1 = require("./commands/tag.cjs");
58
58
  const cherry_pick_1 = require("./commands/cherry-pick.cjs");
59
59
  const remote_1 = require("./commands/remote.cjs");
60
+ const fs = __importStar(require("fs"));
61
+ const path = __importStar(require("path"));
62
+ function loadEnvFile() {
63
+ let currentDir = process.cwd();
64
+ const root = path.parse(currentDir).root;
65
+ while (currentDir !== root) {
66
+ const envPath = path.join(currentDir, '.env');
67
+ if (fs.existsSync(envPath)) {
68
+ const envContent = fs.readFileSync(envPath, 'utf-8');
69
+ for (const line of envContent.split('\n')) {
70
+ const trimmed = line.trim();
71
+ if (!trimmed || trimmed.startsWith('#'))
72
+ continue;
73
+ const match = trimmed.match(/^([^=]+)=(.*)$/);
74
+ if (match) {
75
+ const key = match[1].trim();
76
+ const value = match[2].trim().replace(/^["']|["']$/g, '');
77
+ if (!process.env[key]) {
78
+ process.env[key] = value;
79
+ }
80
+ }
81
+ }
82
+ return;
83
+ }
84
+ currentDir = path.dirname(currentDir);
85
+ }
86
+ }
87
+ loadEnvFile();
60
88
  const VERSION = '1.1.0';
61
89
  function parseArgs(argv) {
62
90
  const args = [];
@@ -184,20 +212,56 @@ async function main() {
184
212
  process.exit(1);
185
213
  }
186
214
  let config = null;
215
+ let resolvedProjectRoot = process.cwd();
187
216
  if (requiresConfig(command)) {
188
217
  const configPath = flags.config;
189
218
  try {
190
219
  const { loadConfigWithEnv, findConfigFileRecursive } = await Promise.resolve().then(() => __importStar(require("./utils/config-loader.cjs")));
191
- const foundConfig = configPath ? configPath : findConfigFileRecursive()?.path;
220
+ const { findProjectRoot } = await Promise.resolve().then(() => __importStar(require("./utils/project-root.cjs")));
221
+ const configResult = configPath ? { path: configPath, directory: path.dirname(path.resolve(configPath)) } : findConfigFileRecursive();
222
+ if (!configResult) {
223
+ const projectRoot = findProjectRoot();
224
+ if (!projectRoot) {
225
+ const colors = {
226
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
227
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
228
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
229
+ };
230
+ console.error('');
231
+ console.error(colors.red('fatal:') + ' not a relq project (or any of the parent directories)');
232
+ console.error('');
233
+ console.error(colors.yellow('hint:') + ` Run ${colors.cyan('relq init')} in your project directory to initialize relq.`);
234
+ console.error('');
235
+ process.exit(128);
236
+ }
237
+ }
238
+ resolvedProjectRoot = configResult?.directory || findProjectRoot() || process.cwd();
239
+ const foundConfig = configResult?.path;
192
240
  if (!foundConfig) {
193
- console.error('Error: relq.config.ts not found.');
194
- console.error('Run "relq init" to create one or use --config to specify a path.');
241
+ const colors = {
242
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
243
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
244
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
245
+ };
246
+ console.error('');
247
+ console.error(colors.red('error:') + ' relq.config.ts not found');
248
+ console.error('');
249
+ console.error(colors.yellow('hint:') + ` Run ${colors.cyan('relq init')} to create one or use ${colors.cyan('--config')} to specify a path.`);
250
+ console.error('');
195
251
  process.exit(1);
196
252
  }
197
253
  config = await loadConfigWithEnv(configPath);
198
254
  if (requiresDbConnection(command, flags) && !config.connection?.host && !config.connection?.url) {
199
- console.error('Error: No database connection configured.');
200
- console.error('Run "relq init" or set DATABASE_* environment variables.');
255
+ const colors = {
256
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
257
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
258
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
259
+ };
260
+ console.error('');
261
+ console.error(colors.red('error:') + ' No database connection configured');
262
+ console.error('');
263
+ console.error(colors.yellow('hint:') + ` Run ${colors.cyan('relq init')} or set DATABASE_* environment variables.`);
264
+ console.error('');
201
265
  process.exit(1);
202
266
  }
203
267
  }
@@ -206,7 +270,7 @@ async function main() {
206
270
  process.exit(1);
207
271
  }
208
272
  }
209
- const context = { config, args, flags };
273
+ const context = { config, args, flags, projectRoot: resolvedProjectRoot };
210
274
  try {
211
275
  switch (command) {
212
276
  case 'init':
@@ -296,7 +360,7 @@ async function main() {
296
360
  theirs: Boolean(flags.theirs),
297
361
  ours: Boolean(flags.ours),
298
362
  abort: Boolean(flags.abort),
299
- });
363
+ }, resolvedProjectRoot);
300
364
  break;
301
365
  case 'export':
302
366
  await (0, export_1.exportCommand)(context);
@@ -316,4 +380,3 @@ async function main() {
316
380
  process.exit(1);
317
381
  }
318
382
  }
319
- main().catch(console.error);
@@ -72,6 +72,12 @@ function generateChangeSQL(change) {
72
72
  return generateConstraintSQL(change);
73
73
  case 'PARTITION':
74
74
  return generatePartitionSQL(change);
75
+ case 'PARTITION_CHILD':
76
+ return generatePartitionChildSQL(change);
77
+ case 'TABLE_COMMENT':
78
+ return generateTableCommentSQL(change);
79
+ case 'COLUMN_COMMENT':
80
+ return generateColumnCommentSQL(change);
75
81
  case 'VIEW':
76
82
  return generateViewSQL(change);
77
83
  case 'MATERIALIZED_VIEW':
@@ -85,6 +91,8 @@ function generateChangeSQL(change) {
85
91
  return generateForeignServerSQL(change);
86
92
  case 'FOREIGN_TABLE':
87
93
  return generateForeignTableSQL(change);
94
+ case 'COLLATION':
95
+ return generateCollationSQL(change);
88
96
  default:
89
97
  return `-- Unsupported object type: ${change.objectType}`;
90
98
  }
@@ -185,8 +193,11 @@ function generateTableSQL(change) {
185
193
  }
186
194
  const allDefs = [...colDefs, ...constraintDefs].join(',\n');
187
195
  let sql = `CREATE TABLE "${data.name}" (\n${allDefs}\n)`;
188
- if (data.isPartitioned && data.partitionType && data.partitionKey?.length) {
189
- sql += ` PARTITION BY ${data.partitionType} (${data.partitionKey.join(', ')})`;
196
+ if (data.isPartitioned && data.partitionType && data.partitionKey) {
197
+ const keyArr = Array.isArray(data.partitionKey) ? data.partitionKey : [data.partitionKey];
198
+ if (keyArr.length) {
199
+ sql += ` PARTITION BY ${data.partitionType} (${keyArr.join(', ')})`;
200
+ }
190
201
  }
191
202
  return sql + ';';
192
203
  }
@@ -267,6 +278,9 @@ function generateConstraintSQL(change) {
267
278
  return '';
268
279
  }
269
280
  function generatePartitionSQL(change) {
281
+ return '';
282
+ }
283
+ function generatePartitionChildSQL(change) {
270
284
  const data = change.after;
271
285
  if (change.type === 'CREATE' && data) {
272
286
  return `CREATE TABLE "${data.name}" PARTITION OF "${data.parentTable}" ${data.bound};`;
@@ -276,6 +290,33 @@ function generatePartitionSQL(change) {
276
290
  }
277
291
  return '';
278
292
  }
293
+ function generateTableCommentSQL(change) {
294
+ const data = change.after;
295
+ if ((change.type === 'CREATE' || change.type === 'ALTER') && data) {
296
+ const escaped = data.comment.replace(/'/g, "''");
297
+ return `COMMENT ON TABLE "${data.tableName}" IS '${escaped}';`;
298
+ }
299
+ else if (change.type === 'DROP') {
300
+ const beforeData = change.before;
301
+ const tableName = beforeData?.tableName || change.objectName;
302
+ return `COMMENT ON TABLE "${tableName}" IS NULL;`;
303
+ }
304
+ return '';
305
+ }
306
+ function generateColumnCommentSQL(change) {
307
+ const data = change.after;
308
+ if ((change.type === 'CREATE' || change.type === 'ALTER') && data) {
309
+ const escaped = data.comment.replace(/'/g, "''");
310
+ return `COMMENT ON COLUMN "${data.tableName}"."${data.columnName}" IS '${escaped}';`;
311
+ }
312
+ else if (change.type === 'DROP') {
313
+ const beforeData = change.before;
314
+ const tableName = beforeData?.tableName || change.parentName || '';
315
+ const columnName = beforeData?.columnName || change.objectName;
316
+ return `COMMENT ON COLUMN "${tableName}"."${columnName}" IS NULL;`;
317
+ }
318
+ return '';
319
+ }
279
320
  function generateViewSQL(change) {
280
321
  const data = change.after;
281
322
  if (change.type === 'CREATE' && data) {
@@ -361,6 +402,28 @@ function generateForeignTableSQL(change) {
361
402
  }
362
403
  return '';
363
404
  }
405
+ function generateCollationSQL(change) {
406
+ const data = change.after;
407
+ if (change.type === 'CREATE' && data) {
408
+ const options = [];
409
+ if (data.locale)
410
+ options.push(`LOCALE = '${data.locale}'`);
411
+ if (data.lcCollate)
412
+ options.push(`LC_COLLATE = '${data.lcCollate}'`);
413
+ if (data.lcCtype)
414
+ options.push(`LC_CTYPE = '${data.lcCtype}'`);
415
+ if (data.provider)
416
+ options.push(`PROVIDER = ${data.provider}`);
417
+ if (data.deterministic !== undefined) {
418
+ options.push(`DETERMINISTIC = ${data.deterministic ? 'TRUE' : 'FALSE'}`);
419
+ }
420
+ return `CREATE COLLATION IF NOT EXISTS "${data.name}" (${options.join(', ')});`;
421
+ }
422
+ else if (change.type === 'DROP') {
423
+ return `DROP COLLATION IF EXISTS "${change.objectName}";`;
424
+ }
425
+ return '';
426
+ }
364
427
  function createChange(type, objectType, objectName, before, after, parentName) {
365
428
  const id = generateChangeId(type, objectType, objectName, parentName);
366
429
  const change = {
@@ -382,6 +445,42 @@ function getChangeDisplayName(change) {
382
445
  change.type === 'DROP' ? '-' :
383
446
  change.type === 'ALTER' ? '~' :
384
447
  '>';
448
+ if (change.objectType === 'INDEX') {
449
+ const data = change.after;
450
+ const tableName = data?.tableName || change.parentName;
451
+ if (tableName) {
452
+ return `${prefix} ${change.objectType} ${change.objectName} on ${tableName}`;
453
+ }
454
+ }
455
+ if (change.objectType === 'PARTITION') {
456
+ const data = change.after;
457
+ if (data?.tableName && data?.type && data?.key) {
458
+ let keyStr = Array.isArray(data.key) ? data.key.join(', ') : data.key;
459
+ keyStr = keyStr.replace(/[{}]/g, '');
460
+ return `${prefix} PARTITIONED ${data.tableName} by ${data.type.toUpperCase()}(${keyStr})`;
461
+ }
462
+ }
463
+ if (change.objectType === 'PARTITION_CHILD') {
464
+ const data = change.after;
465
+ if (data?.name && data?.parentTable) {
466
+ const bound = data.bound || '';
467
+ return `${prefix} PARTITION ${data.name} of ${data.parentTable} ${bound}`;
468
+ }
469
+ }
470
+ if (change.objectType === 'CHECK') {
471
+ const actionWord = change.type === 'CREATE' ? 'ADD' : change.type === 'DROP' ? 'DROP' : 'ALTER';
472
+ if (change.parentName) {
473
+ return `${prefix} ${actionWord} CHECK ${change.objectName} on ${change.parentName}`;
474
+ }
475
+ return `${prefix} ${actionWord} CHECK ${change.objectName}`;
476
+ }
477
+ if (change.objectType === 'PRIMARY_KEY' || change.objectType === 'FOREIGN_KEY' || change.objectType === 'EXCLUSION') {
478
+ const actionWord = change.type === 'CREATE' ? 'ADD' : change.type === 'DROP' ? 'DROP' : 'ALTER';
479
+ if (change.parentName) {
480
+ return `${prefix} ${actionWord} ${change.objectType.replace('_', ' ')} ${change.objectName} on ${change.parentName}`;
481
+ }
482
+ return `${prefix} ${actionWord} ${change.objectType.replace('_', ' ')} ${change.objectName}`;
483
+ }
385
484
  if (change.parentName) {
386
485
  return `${prefix} ${change.objectType} ${change.parentName}.${change.objectName}`;
387
486
  }
@@ -391,14 +490,18 @@ function sortChangesByDependency(changes) {
391
490
  const order = {
392
491
  'EXTENSION': 1,
393
492
  'SCHEMA': 2,
493
+ 'SCHEMA_FILE': 0,
394
494
  'ENUM': 3,
395
495
  'DOMAIN': 4,
396
496
  'COMPOSITE_TYPE': 5,
397
497
  'SEQUENCE': 6,
398
498
  'FOREIGN_SERVER': 7,
399
499
  'TABLE': 10,
500
+ 'TABLE_COMMENT': 10,
400
501
  'PARTITION': 11,
401
- 'COLUMN': 12,
502
+ 'PARTITION_CHILD': 12,
503
+ 'COLUMN': 13,
504
+ 'COLUMN_COMMENT': 12,
402
505
  'INDEX': 13,
403
506
  'CONSTRAINT': 14,
404
507
  'PRIMARY_KEY': 14,
@@ -411,6 +514,7 @@ function sortChangesByDependency(changes) {
411
514
  'PROCEDURE': 31,
412
515
  'TRIGGER': 40,
413
516
  'ENUM_VALUE': 3,
517
+ 'COLLATION': 4,
414
518
  'FOREIGN_TABLE': 8,
415
519
  };
416
520
  return [...changes].sort((a, b) => {