rake-db 2.1.18 → 2.2.0

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.
@@ -1,72 +1,95 @@
1
1
  import {
2
2
  ColumnType,
3
- columnTypes,
4
3
  resetTableData,
5
- quote,
6
4
  getTableData,
7
5
  EmptyObject,
8
6
  emptyObject,
9
7
  TableData,
10
8
  RawExpression,
9
+ raw,
10
+ columnTypes,
11
+ Sql,
12
+ quote,
11
13
  getRaw,
12
14
  isRaw,
13
- raw,
14
- ForeignKey,
15
- newTableData,
16
- SingleColumnIndexOptions,
17
15
  } from 'pqb';
18
16
  import {
19
17
  ChangeTableCallback,
20
18
  ChangeTableOptions,
21
19
  ColumnComment,
22
- ColumnIndex,
23
20
  DropMode,
24
21
  Migration,
25
22
  MigrationColumnTypes,
26
23
  } from './migration';
24
+ import { RakeDbAst } from '../ast';
25
+ import { quoteTable } from '../common';
27
26
  import {
28
27
  addColumnComment,
29
28
  addColumnIndex,
30
29
  columnToSql,
30
+ commentsToQuery,
31
31
  constraintToSql,
32
- migrateComments,
33
- migrateIndexes,
32
+ indexesToQuery,
34
33
  primaryKeyToSql,
35
34
  } from './migrationUtils';
36
- import { quoteTable } from '../common';
37
35
 
38
- const newChangeTableData = () => ({
39
- add: [],
40
- drop: [],
36
+ type ChangeTableData = { add: TableData; drop: TableData };
37
+ const newChangeTableData = (): ChangeTableData => ({
38
+ add: { indexes: [], foreignKeys: [] },
39
+ drop: { indexes: [], foreignKeys: [] },
41
40
  });
42
41
 
43
- let changeTableData: { add: TableData[]; drop: TableData[] } =
44
- newChangeTableData();
42
+ let changeTableData = newChangeTableData();
45
43
 
46
44
  const resetChangeTableData = () => {
47
45
  changeTableData = newChangeTableData();
48
46
  };
49
47
 
50
- function add(item: ColumnType, options?: { dropMode?: DropMode }): ChangeItem;
48
+ const mergeTableData = (a: TableData, b: TableData) => {
49
+ if (b.primaryKey) {
50
+ if (!a.primaryKey) {
51
+ a.primaryKey = b.primaryKey;
52
+ } else {
53
+ a.primaryKey = {
54
+ columns: [...a.primaryKey.columns, ...b.primaryKey.columns],
55
+ options: { ...a.primaryKey.options, ...b.primaryKey.options },
56
+ };
57
+ }
58
+ }
59
+ a.indexes = [...a.indexes, ...b.indexes];
60
+ a.foreignKeys = [...a.foreignKeys, ...b.foreignKeys];
61
+ };
62
+
63
+ function add(
64
+ item: ColumnType,
65
+ options?: { dropMode?: DropMode },
66
+ ): RakeDbAst.ChangeTableItem.Column;
51
67
  function add(emptyObject: EmptyObject): EmptyObject;
52
68
  function add(
53
69
  items: Record<string, ColumnType>,
54
70
  options?: { dropMode?: DropMode },
55
- ): Record<string, ChangeItem>;
71
+ ): Record<string, RakeDbAst.ChangeTableItem.Column>;
56
72
  function add(
57
73
  item: ColumnType | EmptyObject | Record<string, ColumnType>,
58
74
  options?: { dropMode?: DropMode },
59
- ): ChangeItem | EmptyObject | Record<string, ChangeItem> {
75
+ ):
76
+ | RakeDbAst.ChangeTableItem.Column
77
+ | EmptyObject
78
+ | Record<string, RakeDbAst.ChangeTableItem.Column> {
60
79
  if (item instanceof ColumnType) {
61
- return ['add', item, options];
80
+ return { type: 'add', item, dropMode: options?.dropMode };
62
81
  } else if (item === emptyObject) {
63
- changeTableData.add.push(getTableData());
82
+ mergeTableData(changeTableData.add, getTableData());
64
83
  resetTableData();
65
84
  return emptyObject;
66
85
  } else {
67
- const result: Record<string, ChangeItem> = {};
86
+ const result: Record<string, RakeDbAst.ChangeTableItem.Column> = {};
68
87
  for (const key in item) {
69
- result[key] = ['add', (item as Record<string, ColumnType>)[key], options];
88
+ result[key] = {
89
+ type: 'add',
90
+ item: (item as Record<string, ColumnType>)[key],
91
+ dropMode: options?.dropMode,
92
+ };
70
93
  }
71
94
  return result;
72
95
  }
@@ -74,95 +97,104 @@ function add(
74
97
 
75
98
  const drop = ((item, options) => {
76
99
  if (item instanceof ColumnType) {
77
- return ['drop', item, options];
100
+ return { type: 'drop', item, dropMode: options?.dropMode };
78
101
  } else if (item === emptyObject) {
79
- changeTableData.drop.push(getTableData());
102
+ mergeTableData(changeTableData.drop, getTableData());
80
103
  resetTableData();
81
104
  return emptyObject;
82
105
  } else {
83
- const result: Record<string, ChangeItem> = {};
106
+ const result: Record<string, RakeDbAst.ChangeTableItem.Column> = {};
84
107
  for (const key in item) {
85
- result[key] = [
86
- 'drop',
87
- (item as Record<string, ColumnType>)[key],
88
- options,
89
- ];
108
+ result[key] = {
109
+ type: 'drop',
110
+ item: (item as Record<string, ColumnType>)[key],
111
+ dropMode: options?.dropMode,
112
+ };
90
113
  }
91
114
  return result;
92
115
  }
93
116
  }) as typeof add;
94
117
 
118
+ type Change = RakeDbAst.ChangeTableItem.Change & ChangeOptions;
119
+
95
120
  type ChangeOptions = {
96
121
  usingUp?: RawExpression;
97
122
  usingDown?: RawExpression;
98
123
  };
99
124
 
100
- type ChangeArg =
101
- | ColumnType
102
- | ['default', unknown | RawExpression]
103
- | ['nullable', boolean]
104
- | ['comment', string | null]
105
- | ['compression', string]
106
- | ['primaryKey', boolean]
107
- | ['foreignKey', ForeignKey<string, string[]>]
108
- | ['index', Omit<SingleColumnIndexOptions, 'column'>];
125
+ const columnTypeToColumnChange = (
126
+ item: ColumnType | Change,
127
+ ): RakeDbAst.ColumnChange => {
128
+ if (item instanceof ColumnType) {
129
+ const foreignKey = item.data.foreignKey;
130
+ if (foreignKey && 'fn' in foreignKey) {
131
+ throw new Error('Callback in foreignKey is not allowed in migration');
132
+ }
133
+
134
+ return {
135
+ column: item,
136
+ type: item.toSQL(),
137
+ nullable: item.isNullable,
138
+ primaryKey: item.isPrimaryKey,
139
+ ...item.data,
140
+ foreignKey,
141
+ };
142
+ }
143
+
144
+ return item.to;
145
+ };
109
146
 
110
147
  type TableChangeMethods = typeof tableChangeMethods;
111
148
  const tableChangeMethods = {
112
149
  raw: raw,
113
150
  add,
114
151
  drop,
115
- change(from: ChangeArg, to: ChangeArg, options?: ChangeOptions): ChangeItem {
116
- return ['change', from, to, options];
152
+ change(
153
+ from: ColumnType | Change,
154
+ to: ColumnType | Change,
155
+ options?: ChangeOptions,
156
+ ): Change {
157
+ return {
158
+ type: 'change',
159
+ from: columnTypeToColumnChange(from),
160
+ to: columnTypeToColumnChange(to),
161
+ ...options,
162
+ };
117
163
  },
118
- default(value: unknown | RawExpression): ChangeArg {
119
- return ['default', value];
164
+ default(value: unknown | RawExpression): Change {
165
+ return { type: 'change', from: { default: null }, to: { default: value } };
120
166
  },
121
- nullable(): ChangeArg {
122
- return ['nullable', true];
167
+ nullable(): Change {
168
+ return {
169
+ type: 'change',
170
+ from: { nullable: false },
171
+ to: { nullable: true },
172
+ };
123
173
  },
124
- nonNullable(): ChangeArg {
125
- return ['nullable', false];
174
+ nonNullable(): Change {
175
+ return {
176
+ type: 'change',
177
+ from: { nullable: true },
178
+ to: { nullable: false },
179
+ };
126
180
  },
127
- comment(name: string | null): ChangeArg {
128
- return ['comment', name];
181
+ comment(comment: string | null): Change {
182
+ return { type: 'change', from: { comment: null }, to: { comment } };
129
183
  },
130
- rename(name: string): ChangeItem {
131
- return ['rename', name];
184
+ rename(name: string): RakeDbAst.ChangeTableItem.Rename {
185
+ return { type: 'rename', name };
132
186
  },
133
187
  };
134
188
 
135
- export type ChangeItem =
136
- | [
137
- action: 'add' | 'drop',
138
- item: ColumnType,
139
- options?: { dropMode?: DropMode },
140
- ]
141
- | [action: 'change', from: ChangeArg, to: ChangeArg, options?: ChangeOptions]
142
- | ['rename', string];
143
-
144
189
  export type TableChanger = MigrationColumnTypes & TableChangeMethods;
145
190
 
146
- export type TableChangeData = Record<string, ChangeItem | EmptyObject>;
147
-
148
- type PrimaryKeys = {
149
- columns: string[];
150
- change?: true;
151
- options?: { name?: string };
152
- };
153
-
154
- type ChangeTableState = {
155
- migration: Migration;
156
- up: boolean;
157
- tableName: string;
158
- alterTable: (string | ((state: ChangeTableState) => string))[];
159
- values: unknown[];
160
- indexes: ColumnIndex[];
161
- dropIndexes: ColumnIndex[];
162
- comments: ColumnComment[];
163
- addPrimaryKeys: PrimaryKeys;
164
- dropPrimaryKeys: PrimaryKeys;
165
- };
191
+ export type TableChangeData = Record<
192
+ string,
193
+ | RakeDbAst.ChangeTableItem.Column
194
+ | RakeDbAst.ChangeTableItem.Rename
195
+ | Change
196
+ | EmptyObject
197
+ >;
166
198
 
167
199
  export const changeTable = async (
168
200
  migration: Migration,
@@ -179,414 +211,319 @@ export const changeTable = async (
179
211
 
180
212
  const changeData = fn?.(tableChanger) || {};
181
213
 
182
- const addPrimaryKeys: PrimaryKeys = {
183
- columns: [],
184
- };
185
- const dropPrimaryKeys: PrimaryKeys = {
186
- columns: [],
187
- };
188
- for (const key in changeData) {
189
- const item = changeData[key];
190
- if (Array.isArray(item)) {
191
- const [action] = item;
192
- if (action === 'add') {
193
- if ((item[1] as ColumnType).isPrimaryKey) {
194
- addPrimaryKeys.columns.push(key);
195
- }
196
- } else if (action === 'drop') {
197
- if ((item[1] as ColumnType).isPrimaryKey) {
198
- dropPrimaryKeys.columns.push(key);
199
- }
200
- }
201
- }
214
+ const ast = makeAst(up, tableName, changeData, changeTableData, options);
215
+
216
+ const queries = astToQueries(ast);
217
+ for (const query of queries) {
218
+ await migration.query(query);
202
219
  }
203
220
 
204
- const state: ChangeTableState = {
205
- migration,
206
- up,
207
- tableName,
208
- alterTable: [],
209
- values: [],
210
- indexes: [],
211
- dropIndexes: [],
212
- comments: [],
213
- addPrimaryKeys,
214
- dropPrimaryKeys,
215
- };
221
+ await migration.options.appCodeUpdater?.(ast);
222
+ };
216
223
 
217
- if (options.comment !== undefined) {
218
- await changeActions.tableComment(state, tableName, options.comment);
219
- }
224
+ const makeAst = (
225
+ up: boolean,
226
+ name: string,
227
+ changeData: TableChangeData,
228
+ changeTableData: ChangeTableData,
229
+ options: ChangeTableOptions,
230
+ ): RakeDbAst.ChangeTable => {
231
+ const { comment } = options;
220
232
 
233
+ const shape: Record<string, RakeDbAst.ChangeTableItem> = {};
221
234
  for (const key in changeData) {
222
235
  const item = changeData[key];
223
- if (Array.isArray(item)) {
224
- const [action] = item;
225
- if (action === 'change') {
226
- const [, from, to, options] = item;
227
- changeActions.change(state, up, key, from, to, options);
228
- } else if (action === 'rename') {
229
- const [, name] = item;
230
- changeActions.rename(state, up, key, name);
231
- } else if (action) {
232
- const [action, columnType, options] = item;
233
- changeActions[
234
- action as Exclude<
235
- keyof typeof changeActions,
236
- 'change' | 'rename' | 'tableComment'
237
- >
238
- ](state, up, key, columnType, options);
236
+ if ('type' in item) {
237
+ if (up) {
238
+ shape[key] =
239
+ item.type === 'change' && item.usingUp
240
+ ? { ...item, using: item.usingUp }
241
+ : item;
242
+ } else {
243
+ if (item.type === 'rename') {
244
+ shape[item.name] = { ...item, name: key };
245
+ } else {
246
+ shape[key] =
247
+ item.type === 'add'
248
+ ? { ...item, type: 'drop' }
249
+ : item.type === 'drop'
250
+ ? { ...item, type: 'add' }
251
+ : item.type === 'change'
252
+ ? { ...item, from: item.to, to: item.from, using: item.usingDown }
253
+ : item;
254
+ }
239
255
  }
240
256
  }
241
257
  }
242
258
 
243
- const prependAlterTable: string[] = [];
244
-
245
- let addedDownKey = false;
246
- changeTableData[up ? 'drop' : 'add'].forEach((tableData) => {
247
- if (tableData.primaryKey) {
248
- addedDownKey = true;
249
- const keys = state[up ? 'dropPrimaryKeys' : 'addPrimaryKeys'];
250
- keys.columns.push(...tableData.primaryKey.columns);
251
- keys.options = tableData.primaryKey.options;
252
- }
253
-
254
- state.dropIndexes.push(...tableData.indexes);
255
-
256
- prependAlterTable.push(...getForeignKeysLines(state, false, tableData));
257
- });
258
-
259
- const dropKeys = state[up ? 'dropPrimaryKeys' : 'addPrimaryKeys'];
260
- if (addedDownKey || dropKeys.change || dropKeys.columns.length > 1) {
261
- const name = dropKeys.options?.name || `${tableName}_pkey`;
262
- prependAlterTable.push(`DROP CONSTRAINT "${name}"`);
263
- }
264
-
265
- let addedUpKey = false;
266
- changeTableData[up ? 'add' : 'drop'].forEach((tableData) => {
267
- if (tableData.primaryKey) {
268
- addedUpKey = true;
269
- const keys = state[up ? 'addPrimaryKeys' : 'dropPrimaryKeys'];
270
- keys.columns.push(...tableData.primaryKey.columns);
271
- keys.options = tableData.primaryKey.options;
272
- }
273
-
274
- state.indexes.push(...tableData.indexes);
259
+ return {
260
+ type: 'changeTable',
261
+ name,
262
+ comment: comment
263
+ ? up
264
+ ? Array.isArray(comment)
265
+ ? comment[1]
266
+ : comment
267
+ : Array.isArray(comment)
268
+ ? comment[0]
269
+ : null
270
+ : undefined,
271
+ shape,
272
+ ...(up
273
+ ? changeTableData
274
+ : { add: changeTableData.drop, drop: changeTableData.add }),
275
+ };
276
+ };
275
277
 
276
- state.alterTable.push(...getForeignKeysLines(state, true, tableData));
277
- });
278
+ type PrimaryKeys = {
279
+ columns: string[];
280
+ change?: true;
281
+ options?: { name?: string };
282
+ };
278
283
 
279
- const addKeys = state[up ? 'addPrimaryKeys' : 'dropPrimaryKeys'];
280
- if (addedUpKey || addKeys.change || addKeys.columns.length > 1) {
281
- state.alterTable.push(`ADD ${primaryKeyToSql(addKeys)}`);
282
- }
284
+ const astToQueries = (ast: RakeDbAst.ChangeTable): Sql[] => {
285
+ const result: Sql[] = [];
283
286
 
284
- if (prependAlterTable.length || state.alterTable.length) {
285
- await migration.query(
286
- `ALTER TABLE ${quoteTable(tableName)}` +
287
- `\n ${[
288
- ...prependAlterTable,
289
- ...state.alterTable.map((item) =>
290
- typeof item === 'string' ? item : item(state),
291
- ),
292
- ].join(',\n ')}`,
293
- );
287
+ if (ast.comment !== undefined) {
288
+ result.push({
289
+ text: `COMMENT ON TABLE ${quoteTable(ast.name)} IS ${quote(ast.comment)}`,
290
+ values: [],
291
+ });
294
292
  }
295
293
 
296
- await migrateIndexes(state, state.dropIndexes, false);
297
- await migrateIndexes(state, state.indexes, true);
298
- await migrateComments(state, state.comments);
299
- };
300
-
301
- const changeActions = {
302
- tableComment(
303
- { migration, up }: ChangeTableState,
304
- tableName: string,
305
- comment: Exclude<ChangeTableOptions['comment'], undefined>,
306
- ) {
307
- let value;
308
- if (up) {
309
- value = Array.isArray(comment) ? comment[1] : comment;
310
- } else {
311
- value = Array.isArray(comment) ? comment[0] : null;
312
- }
313
- return migration.query(
314
- `COMMENT ON TABLE ${quoteTable(tableName)} IS ${quote(value)}`,
315
- );
316
- },
317
-
318
- add(
319
- state: ChangeTableState,
320
- up: boolean,
321
- key: string,
322
- item: ColumnType,
323
- options?: { dropMode?: DropMode },
324
- ) {
325
- addColumnIndex(state[up ? 'indexes' : 'dropIndexes'], key, item);
294
+ const addPrimaryKeys: PrimaryKeys = ast.add.primaryKey
295
+ ? { ...ast.add.primaryKey }
296
+ : {
297
+ columns: [],
298
+ };
299
+ const dropPrimaryKeys: PrimaryKeys = ast.drop.primaryKey
300
+ ? { ...ast.drop.primaryKey }
301
+ : {
302
+ columns: [],
303
+ };
304
+
305
+ for (const key in ast.shape) {
306
+ const item = ast.shape[key];
307
+
308
+ if (item.type === 'add') {
309
+ if (item.item.isPrimaryKey) {
310
+ addPrimaryKeys.columns.push(key);
311
+ }
312
+ } else if (item.type === 'drop') {
313
+ if (item.item.isPrimaryKey) {
314
+ dropPrimaryKeys.columns.push(key);
315
+ }
316
+ } else if (item.type === 'change') {
317
+ if (item.from.primaryKey) {
318
+ dropPrimaryKeys.columns.push(key);
319
+ dropPrimaryKeys.change = true;
320
+ }
326
321
 
327
- if (up) {
328
- addColumnComment(state.comments, key, item);
322
+ if (item.to.primaryKey) {
323
+ addPrimaryKeys.columns.push(key);
324
+ addPrimaryKeys.change = true;
325
+ }
329
326
  }
327
+ }
330
328
 
331
- if (up) {
332
- state.alterTable.push(
333
- (state) =>
334
- `ADD COLUMN ${columnToSql(
335
- key,
336
- item,
337
- state.values,
338
- (state.up ? state.addPrimaryKeys : state.dropPrimaryKeys).columns
339
- .length > 1,
340
- )}`,
329
+ const alterTable: string[] = [];
330
+ const values: unknown[] = [];
331
+ const addIndexes: TableData.Index[] = [...ast.add.indexes];
332
+ const dropIndexes: TableData.Index[] = [...ast.drop.indexes];
333
+ const addForeignKeys: TableData.ForeignKey[] = [...ast.add.foreignKeys];
334
+ const dropForeignKeys: TableData.ForeignKey[] = [...ast.drop.foreignKeys];
335
+ const comments: ColumnComment[] = [];
336
+
337
+ for (const key in ast.shape) {
338
+ const item = ast.shape[key];
339
+
340
+ if (item.type === 'add') {
341
+ addColumnIndex(addIndexes, key, item.item);
342
+ addColumnComment(comments, key, item.item);
343
+
344
+ alterTable.push(
345
+ `ADD COLUMN ${columnToSql(
346
+ key,
347
+ item.item,
348
+ values,
349
+ addPrimaryKeys.columns.length > 1,
350
+ )}`,
341
351
  );
342
- } else {
343
- state.alterTable.push(
344
- `DROP COLUMN "${key}"${
345
- options?.dropMode ? ` ${options.dropMode}` : ''
346
- }`,
347
- );
348
- }
349
- },
352
+ } else if (item.type === 'drop') {
353
+ addColumnIndex(dropIndexes, key, item.item);
350
354
 
351
- drop(
352
- state: ChangeTableState,
353
- up: boolean,
354
- key: string,
355
- item: ColumnType,
356
- options?: { dropMode?: DropMode },
357
- ) {
358
- this.add(state, !up, key, item, options);
359
- },
360
-
361
- change(
362
- state: ChangeTableState,
363
- up: boolean,
364
- key: string,
365
- first: ChangeArg,
366
- second: ChangeArg,
367
- options?: ChangeOptions,
368
- ) {
369
- const [fromItem, toItem] = up ? [first, second] : [second, first];
370
-
371
- const from = getChangeProperties(fromItem);
372
- const to = getChangeProperties(toItem);
373
-
374
- if (from.type !== to.type || from.collate !== to.collate) {
375
- const using = up ? options?.usingUp : options?.usingDown;
376
- state.alterTable.push(
377
- `ALTER COLUMN "${key}" TYPE ${to.type}${
378
- to.collate ? ` COLLATE ${quote(to.collate)}` : ''
379
- }${using ? ` USING ${getRaw(using, state.values)}` : ''}`,
355
+ alterTable.push(
356
+ `DROP COLUMN "${key}"${item.dropMode ? ` ${item.dropMode}` : ''}`,
380
357
  );
381
- }
358
+ } else if (item.type === 'change') {
359
+ const { from, to } = item;
360
+ if (from.type !== to.type || from.collate !== to.collate) {
361
+ alterTable.push(
362
+ `ALTER COLUMN "${key}" TYPE ${to.type}${
363
+ to.collate ? ` COLLATE ${quote(to.collate)}` : ''
364
+ }${item.using ? ` USING ${getRaw(item.using, values)}` : ''}`,
365
+ );
366
+ }
382
367
 
383
- if (from.default !== to.default) {
384
- const value = getRawOrValue(to.default, state.values);
385
- const expr =
386
- value === undefined ? `DROP DEFAULT` : `SET DEFAULT ${value}`;
387
- state.alterTable.push(`ALTER COLUMN "${key}" ${expr}`);
388
- }
368
+ if (from.default !== to.default) {
369
+ const value =
370
+ typeof to.default === 'object' && to.default && isRaw(to.default)
371
+ ? getRaw(to.default, values)
372
+ : quote(to.default);
389
373
 
390
- if (from.nullable !== to.nullable) {
391
- state.alterTable.push(
392
- `ALTER COLUMN "${key}" ${to.nullable ? 'DROP' : 'SET'} NOT NULL`,
393
- );
394
- }
374
+ const expr =
375
+ value === undefined ? 'DROP DEFAULT' : `SET DEFAULT ${value}`;
395
376
 
396
- if (from.compression !== to.compression) {
397
- state.alterTable.push(
398
- `ALTER COLUMN "${key}" SET COMPRESSION ${to.compression || 'DEFAULT'}`,
399
- );
400
- }
377
+ alterTable.push(`ALTER COLUMN "${key}" ${expr}`);
378
+ }
401
379
 
402
- if (from.primaryKey || to.primaryKey) {
403
- const primaryKey =
404
- state[
405
- (up && to.primaryKey) || (!up && from.primaryKey)
406
- ? 'addPrimaryKeys'
407
- : 'dropPrimaryKeys'
408
- ];
409
- primaryKey.columns.push(key);
410
- primaryKey.change = true;
411
- }
380
+ if (from.nullable !== to.nullable) {
381
+ alterTable.push(
382
+ `ALTER COLUMN "${key}" ${to.nullable ? 'DROP' : 'SET'} NOT NULL`,
383
+ );
384
+ }
412
385
 
413
- const fromFkey = from.foreignKey;
414
- const toFkey = to.foreignKey;
415
- if (fromFkey || toFkey) {
416
- if ((fromFkey && 'fn' in fromFkey) || (toFkey && 'fn' in toFkey)) {
417
- throw new Error('Callback in foreignKey is not allowed in migration');
386
+ if (from.compression !== to.compression) {
387
+ alterTable.push(
388
+ `ALTER COLUMN "${key}" SET COMPRESSION ${
389
+ to.compression || 'DEFAULT'
390
+ }`,
391
+ );
418
392
  }
419
393
 
420
- if (checkIfForeignKeysAreDifferent(fromFkey, toFkey)) {
394
+ const fromFkey = from.foreignKey;
395
+ const toFkey = to.foreignKey;
396
+ if (
397
+ (fromFkey || toFkey) &&
398
+ (!fromFkey ||
399
+ !toFkey ||
400
+ fromFkey.name !== toFkey.name ||
401
+ fromFkey.match !== toFkey.match ||
402
+ fromFkey.onUpdate !== toFkey.onUpdate ||
403
+ fromFkey.onDelete !== toFkey.onDelete ||
404
+ fromFkey.dropMode !== toFkey.dropMode ||
405
+ fromFkey.table !== toFkey.table ||
406
+ fromFkey.columns.join(',') !== toFkey.columns.join(','))
407
+ ) {
421
408
  if (fromFkey) {
422
- const data = newTableData();
423
- data.foreignKeys.push({
409
+ dropForeignKeys.push({
424
410
  columns: [key],
425
411
  fnOrTable: fromFkey.table,
426
412
  foreignColumns: fromFkey.columns,
427
413
  options: fromFkey,
428
414
  });
429
- changeTableData[up ? 'drop' : 'add'].push(data);
430
415
  }
431
416
 
432
417
  if (toFkey) {
433
- const data = newTableData();
434
- data.foreignKeys.push({
418
+ addForeignKeys.push({
435
419
  columns: [key],
436
420
  fnOrTable: toFkey.table,
437
421
  foreignColumns: toFkey.columns,
438
422
  options: toFkey,
439
423
  });
440
- changeTableData[up ? 'add' : 'drop'].push(data);
441
424
  }
442
425
  }
443
- }
444
426
 
445
- const fromIndex = from.index;
446
- const toIndex = to.index;
447
- if (
448
- (fromIndex || toIndex) &&
449
- checkIfIndexesAreDifferent(fromIndex, toIndex)
450
- ) {
451
- if (fromIndex) {
452
- const data = newTableData();
453
- data.indexes.push({
454
- columns: [
455
- {
456
- column: key,
457
- ...fromIndex,
458
- },
459
- ],
460
- options: fromIndex,
461
- });
462
- changeTableData[up ? 'drop' : 'add'].push(data);
463
- }
427
+ const fromIndex = from.index;
428
+ const toIndex = to.index;
429
+ if (
430
+ (fromIndex || toIndex) &&
431
+ (!fromIndex ||
432
+ !toIndex ||
433
+ fromIndex.expression !== toIndex.expression ||
434
+ fromIndex.collate !== toIndex.collate ||
435
+ fromIndex.operator !== toIndex.operator ||
436
+ fromIndex.order !== toIndex.order ||
437
+ fromIndex.name !== toIndex.name ||
438
+ fromIndex.unique !== toIndex.unique ||
439
+ fromIndex.using !== toIndex.using ||
440
+ fromIndex.include !== toIndex.include ||
441
+ (Array.isArray(fromIndex.include) &&
442
+ Array.isArray(toIndex.include) &&
443
+ fromIndex.include.join(',') !== toIndex.include.join(',')) ||
444
+ fromIndex.with !== toIndex.with ||
445
+ fromIndex.tablespace !== toIndex.tablespace ||
446
+ fromIndex.where !== toIndex.where ||
447
+ fromIndex.dropMode !== toIndex.dropMode)
448
+ ) {
449
+ if (fromIndex) {
450
+ dropIndexes.push({
451
+ columns: [
452
+ {
453
+ column: key,
454
+ ...fromIndex,
455
+ },
456
+ ],
457
+ options: fromIndex,
458
+ });
459
+ }
464
460
 
465
- if (toIndex) {
466
- const data = newTableData();
467
- data.indexes.push({
468
- columns: [
469
- {
470
- column: key,
471
- ...toIndex,
472
- },
473
- ],
474
- options: toIndex,
475
- });
476
- changeTableData[up ? 'add' : 'drop'].push(data);
461
+ if (toIndex) {
462
+ addIndexes.push({
463
+ columns: [
464
+ {
465
+ column: key,
466
+ ...toIndex,
467
+ },
468
+ ],
469
+ options: toIndex,
470
+ });
471
+ }
477
472
  }
478
- }
479
473
 
480
- if (from.comment !== to.comment) {
481
- state.comments.push({ column: key, comment: to.comment || null });
474
+ if (from.comment !== to.comment) {
475
+ comments.push({ column: key, comment: to.comment || null });
476
+ }
477
+ } else if (item.type === 'rename') {
478
+ alterTable.push(`RENAME COLUMN "${key}" TO "${item.name}"`);
482
479
  }
483
- },
480
+ }
484
481
 
485
- rename(state: ChangeTableState, up: boolean, key: string, name: string) {
486
- const [from, to] = up ? [key, name] : [name, key];
487
- state.alterTable.push(`RENAME COLUMN "${from}" TO "${to}"`);
488
- },
489
- };
482
+ const prependAlterTable: string[] = [];
490
483
 
491
- const checkIfForeignKeysAreDifferent = (
492
- from?: ForeignKey<string, string[]> & { table: string },
493
- to?: ForeignKey<string, string[]> & { table: string },
494
- ) => {
495
- return (
496
- !from ||
497
- !to ||
498
- from.name !== to.name ||
499
- from.match !== to.match ||
500
- from.onUpdate !== to.onUpdate ||
501
- from.onDelete !== to.onDelete ||
502
- from.dropMode !== to.dropMode ||
503
- from.table !== to.table ||
504
- from.columns.join(',') !== to.columns.join(',')
505
- );
506
- };
484
+ if (
485
+ ast.drop.primaryKey ||
486
+ dropPrimaryKeys.change ||
487
+ dropPrimaryKeys.columns.length > 1
488
+ ) {
489
+ const name = dropPrimaryKeys.options?.name || `${ast.name}_pkey`;
490
+ prependAlterTable.push(`DROP CONSTRAINT "${name}"`);
491
+ }
507
492
 
508
- const checkIfIndexesAreDifferent = (
509
- from?: Omit<SingleColumnIndexOptions, 'column'>,
510
- to?: Omit<SingleColumnIndexOptions, 'column'>,
511
- ) => {
512
- return (
513
- !from ||
514
- !to ||
515
- from.expression !== to.expression ||
516
- from.collate !== to.collate ||
517
- from.operator !== to.operator ||
518
- from.order !== to.order ||
519
- from.name !== to.name ||
520
- from.unique !== to.unique ||
521
- from.using !== to.using ||
522
- from.include !== to.include ||
523
- (Array.isArray(from.include) &&
524
- Array.isArray(to.include) &&
525
- from.include.join(',') !== to.include.join(',')) ||
526
- from.with !== to.with ||
527
- from.tablespace !== to.tablespace ||
528
- from.where !== to.where ||
529
- from.dropMode !== to.dropMode
493
+ prependAlterTable.push(
494
+ ...dropForeignKeys.map(
495
+ (foreignKey) => `\n DROP ${constraintToSql(ast.name, false, foreignKey)}`,
496
+ ),
530
497
  );
531
- };
532
498
 
533
- type ChangeProperties = {
534
- type?: string;
535
- collate?: string;
536
- default?: unknown | RawExpression;
537
- nullable?: boolean;
538
- comment?: string | null;
539
- compression?: string;
540
- primaryKey?: boolean;
541
- foreignKey?: ForeignKey<string, string[]>;
542
- index?: Omit<SingleColumnIndexOptions, 'column'>;
543
- };
499
+ alterTable.unshift(...prependAlterTable);
544
500
 
545
- const getChangeProperties = (item: ChangeArg): ChangeProperties => {
546
- if (item instanceof ColumnType) {
547
- return {
548
- type: item.toSQL(),
549
- collate: item.data.collate,
550
- default: item.data.default,
551
- nullable: item.isNullable,
552
- comment: item.data.comment,
553
- compression: item.data.compression,
554
- primaryKey: item.isPrimaryKey,
555
- foreignKey: item.data.foreignKey,
556
- index: item.data.index,
557
- };
558
- } else {
559
- return {
560
- type: undefined,
561
- collate: undefined,
562
- default: item[0] === 'default' ? item[1] : undefined,
563
- nullable: item[0] === 'nullable' ? item[1] : undefined,
564
- comment: item[0] === 'comment' ? item[1] : undefined,
565
- compression: item[0] === 'compression' ? item[1] : undefined,
566
- primaryKey: item[0] === 'primaryKey' ? item[1] : undefined,
567
- foreignKey: item[0] === 'foreignKey' ? item[1] : undefined,
568
- index: item[0] === 'index' ? item[1] : undefined,
569
- };
501
+ if (
502
+ ast.add.primaryKey ||
503
+ addPrimaryKeys.change ||
504
+ addPrimaryKeys.columns.length > 1
505
+ ) {
506
+ alterTable.push(`ADD ${primaryKeyToSql(addPrimaryKeys)}`);
570
507
  }
571
- };
572
508
 
573
- const getForeignKeysLines = (
574
- state: ChangeTableState,
575
- up: boolean,
576
- tableData: TableData,
577
- ) => {
578
- return tableData.foreignKeys.map(
579
- (foreignKey) =>
580
- `\n ${up ? 'ADD' : 'DROP'} ${constraintToSql(
581
- state.tableName,
582
- up,
583
- foreignKey,
584
- )}`,
509
+ alterTable.push(
510
+ ...addForeignKeys.map(
511
+ (foreignKey) => `\n ADD ${constraintToSql(ast.name, true, foreignKey)}`,
512
+ ),
585
513
  );
586
- };
587
514
 
588
- const getRawOrValue = (item: unknown | RawExpression, values: unknown[]) => {
589
- return typeof item === 'object' && item && isRaw(item)
590
- ? getRaw(item, values)
591
- : quote(item);
515
+ if (alterTable.length) {
516
+ result.push({
517
+ text:
518
+ `ALTER TABLE ${quoteTable(ast.name)}` +
519
+ `\n ${alterTable.join(',\n ')}`,
520
+ values,
521
+ });
522
+ }
523
+
524
+ result.push(...indexesToQuery(false, ast.name, dropIndexes));
525
+ result.push(...indexesToQuery(true, ast.name, addIndexes));
526
+ result.push(...commentsToQuery(ast.name, comments));
527
+
528
+ return result;
592
529
  };