rake-db 2.1.0 → 2.1.2

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.
@@ -356,6 +356,7 @@ describe('changeTable', () => {
356
356
  changeDefault: t.change(t.default('from'), t.default(t.raw("'to'"))),
357
357
  changeNull: t.change(t.nonNullable(), t.nullable()),
358
358
  changeComment: t.change(t.comment('comment 1'), t.comment('comment 2')),
359
+ changeCompression: t.change(t.text(), t.text().compression('value')),
359
360
  }));
360
361
  };
361
362
 
@@ -367,7 +368,8 @@ describe('changeTable', () => {
367
368
  ALTER COLUMN "changeTypeUsing" TYPE text USING b::text,
368
369
  ALTER COLUMN "changeCollate" TYPE text COLLATE 'fr_FR',
369
370
  ALTER COLUMN "changeDefault" SET DEFAULT 'to',
370
- ALTER COLUMN "changeNull" DROP NOT NULL
371
+ ALTER COLUMN "changeNull" DROP NOT NULL,
372
+ ALTER COLUMN "changeCompression" SET COMPRESSION value
371
373
  `,
372
374
  `COMMENT ON COLUMN "table"."changeComment" IS 'comment 2'`,
373
375
  ]);
@@ -382,12 +384,177 @@ describe('changeTable', () => {
382
384
  ALTER COLUMN "changeTypeUsing" TYPE integer USING b::int,
383
385
  ALTER COLUMN "changeCollate" TYPE text COLLATE 'de_DE',
384
386
  ALTER COLUMN "changeDefault" SET DEFAULT 'from',
385
- ALTER COLUMN "changeNull" SET NOT NULL
387
+ ALTER COLUMN "changeNull" SET NOT NULL,
388
+ ALTER COLUMN "changeCompression" SET COMPRESSION DEFAULT
386
389
  `,
387
390
  `COMMENT ON COLUMN "table"."changeComment" IS 'comment 1'`,
388
391
  ]);
389
392
  });
390
393
 
394
+ it('should change column foreign key', async () => {
395
+ const fn = () => {
396
+ return db.changeTable('table', (t) => ({
397
+ addFkey: t.change(
398
+ t.integer(),
399
+ t.integer().foreignKey('otherTable', 'foreignId'),
400
+ ),
401
+ addFkeyWithOptions: t.change(
402
+ t.integer(),
403
+ t.integer().foreignKey('otherTable', 'foreignId', {
404
+ name: 'foreignKeyName',
405
+ match: 'FULL',
406
+ onUpdate: 'SET NULL',
407
+ onDelete: 'CASCADE',
408
+ }),
409
+ ),
410
+ removeFkey: t.change(
411
+ t.integer().foreignKey('otherTable', 'foreignId'),
412
+ t.integer(),
413
+ ),
414
+ removeFkeyWithOptions: t.change(
415
+ t.integer().foreignKey('otherTable', 'foreignId', {
416
+ name: 'foreignKeyName',
417
+ match: 'FULL',
418
+ onUpdate: 'SET NULL',
419
+ onDelete: 'CASCADE',
420
+ }),
421
+ t.integer(),
422
+ ),
423
+ changeForeignKey: t.change(
424
+ t.integer().foreignKey('a', 'aId', {
425
+ name: 'fromFkeyName',
426
+ match: 'PARTIAL',
427
+ onUpdate: 'RESTRICT',
428
+ onDelete: 'SET DEFAULT',
429
+ }),
430
+ t.integer().foreignKey('b', 'bId', {
431
+ name: 'toFkeyName',
432
+ match: 'FULL',
433
+ onUpdate: 'NO ACTION',
434
+ onDelete: 'CASCADE',
435
+ }),
436
+ ),
437
+ }));
438
+ };
439
+
440
+ await fn();
441
+ expectSql(`
442
+ ALTER TABLE "table"
443
+ DROP CONSTRAINT "table_removeFkey_fkey",
444
+ DROP CONSTRAINT "foreignKeyName",
445
+ DROP CONSTRAINT "fromFkeyName",
446
+ ADD CONSTRAINT "table_addFkey_fkey" FOREIGN KEY ("addFkey") REFERENCES "otherTable"("foreignId"),
447
+ ADD CONSTRAINT "foreignKeyName" FOREIGN KEY ("addFkeyWithOptions") REFERENCES "otherTable"("foreignId") MATCH FULL ON DELETE CASCADE ON UPDATE SET NULL,
448
+ ADD CONSTRAINT "toFkeyName" FOREIGN KEY ("changeForeignKey") REFERENCES "b"("bId") MATCH FULL ON DELETE CASCADE ON UPDATE NO ACTION
449
+ `);
450
+
451
+ queryMock.mockClear();
452
+ db.up = false;
453
+ await fn();
454
+ expectSql(`
455
+ ALTER TABLE "table"
456
+ DROP CONSTRAINT "table_addFkey_fkey",
457
+ DROP CONSTRAINT "foreignKeyName",
458
+ DROP CONSTRAINT "toFkeyName",
459
+ ADD CONSTRAINT "table_removeFkey_fkey" FOREIGN KEY ("removeFkey") REFERENCES "otherTable"("foreignId"),
460
+ ADD CONSTRAINT "foreignKeyName" FOREIGN KEY ("removeFkeyWithOptions") REFERENCES "otherTable"("foreignId") MATCH FULL ON DELETE CASCADE ON UPDATE SET NULL,
461
+ ADD CONSTRAINT "fromFkeyName" FOREIGN KEY ("changeForeignKey") REFERENCES "a"("aId") MATCH PARTIAL ON DELETE SET DEFAULT ON UPDATE RESTRICT
462
+ `);
463
+ });
464
+
465
+ it('should change index', async () => {
466
+ const fn = () => {
467
+ return db.changeTable('table', (t) => ({
468
+ addIndex: t.change(t.integer(), t.integer().index()),
469
+ addIndexWithOptions: t.change(
470
+ t.integer(),
471
+ t.integer().index({
472
+ expression: 'expression',
473
+ collate: 'collate',
474
+ operator: 'operator',
475
+ order: 'order',
476
+ unique: true,
477
+ using: 'using',
478
+ include: ['a', 'b'],
479
+ with: 'with',
480
+ tablespace: 'tablespace',
481
+ where: 'where',
482
+ dropMode: 'CASCADE',
483
+ }),
484
+ ),
485
+ removeIndex: t.change(t.integer().index(), t.integer()),
486
+ removeIndexWithOptions: t.change(
487
+ t.integer().index({
488
+ expression: 'expression',
489
+ collate: 'collate',
490
+ operator: 'operator',
491
+ order: 'order',
492
+ unique: true,
493
+ using: 'using',
494
+ include: ['a', 'b'],
495
+ with: 'with',
496
+ tablespace: 'tablespace',
497
+ where: 'where',
498
+ dropMode: 'CASCADE',
499
+ }),
500
+ t.integer(),
501
+ ),
502
+ changeIndex: t.change(
503
+ t.integer().index({
504
+ name: 'from',
505
+ expression: 'from',
506
+ collate: 'from',
507
+ operator: 'from',
508
+ order: 'from',
509
+ unique: false,
510
+ using: 'from',
511
+ include: ['a', 'b'],
512
+ with: 'from',
513
+ tablespace: 'from',
514
+ where: 'from',
515
+ dropMode: 'CASCADE',
516
+ }),
517
+ t.integer().index({
518
+ name: 'to',
519
+ expression: 'to',
520
+ collate: 'to',
521
+ operator: 'to',
522
+ order: 'to',
523
+ unique: true,
524
+ using: 'to',
525
+ include: ['c', 'd'],
526
+ with: 'to',
527
+ tablespace: 'to',
528
+ where: 'to',
529
+ dropMode: 'RESTRICT',
530
+ }),
531
+ ),
532
+ }));
533
+ };
534
+
535
+ await fn();
536
+ expectSql([
537
+ `DROP INDEX "tableRemoveIndexIndex"`,
538
+ `DROP INDEX "tableRemoveIndexWithOptionsIndex" CASCADE`,
539
+ `DROP INDEX "from" CASCADE`,
540
+ `CREATE INDEX "tableAddIndexIndex" ON "table" ("addIndex")`,
541
+ `CREATE UNIQUE INDEX "tableAddIndexWithOptionsIndex" ON "table" USING using ("addIndexWithOptions"(expression) COLLATE 'collate' operator order) INCLUDE ("a", "b") WITH (with) TABLESPACE tablespace WHERE where`,
542
+ `CREATE UNIQUE INDEX "to" ON "table" USING to ("changeIndex"(to) COLLATE 'to' to to) INCLUDE ("c", "d") WITH (to) TABLESPACE to WHERE to`,
543
+ ]);
544
+
545
+ queryMock.mockClear();
546
+ db.up = false;
547
+ await fn();
548
+ expectSql([
549
+ `DROP INDEX "tableAddIndexIndex"`,
550
+ `DROP INDEX "tableAddIndexWithOptionsIndex" CASCADE`,
551
+ `DROP INDEX "to" RESTRICT`,
552
+ `CREATE INDEX "tableRemoveIndexIndex" ON "table" ("removeIndex")`,
553
+ `CREATE UNIQUE INDEX "tableRemoveIndexWithOptionsIndex" ON "table" USING using ("removeIndexWithOptions"(expression) COLLATE 'collate' operator order) INCLUDE ("a", "b") WITH (with) TABLESPACE tablespace WHERE where`,
554
+ `CREATE INDEX "from" ON "table" USING from ("changeIndex"(from) COLLATE 'from' from from) INCLUDE ("a", "b") WITH (from) TABLESPACE from WHERE from`,
555
+ ]);
556
+ });
557
+
391
558
  it('should rename a column', async () => {
392
559
  const fn = () => {
393
560
  return db.changeTable('table', (t) => ({
@@ -12,6 +12,9 @@ import {
12
12
  getRaw,
13
13
  isRaw,
14
14
  raw,
15
+ ForeignKey,
16
+ newTableData,
17
+ SingleColumnIndexOptions,
15
18
  } from 'pqb';
16
19
  import {
17
20
  ChangeTableCallback,
@@ -98,7 +101,10 @@ type ChangeArg =
98
101
  | ColumnType
99
102
  | ['default', unknown | RawExpression]
100
103
  | ['nullable', boolean]
101
- | ['comment', string | null];
104
+ | ['comment', string | null]
105
+ | ['compression', string]
106
+ | ['foreignKey', ForeignKey<string, string[]>]
107
+ | ['index', Omit<SingleColumnIndexOptions, 'column'>];
102
108
 
103
109
  type TableChangeMethods = typeof tableChangeMethods;
104
110
  const tableChangeMethods = {
@@ -201,12 +207,12 @@ export const changeTable = async (
201
207
  }
202
208
  }
203
209
 
204
- changeTableData.add.forEach((tableData) => {
205
- handleTableData(state, up, tableName, tableData);
210
+ changeTableData[up ? 'drop' : 'add'].forEach((tableData) => {
211
+ handleTableData(state, false, tableName, tableData);
206
212
  });
207
213
 
208
- changeTableData.drop.forEach((tableData) => {
209
- handleTableData(state, !up, tableName, tableData);
214
+ changeTableData[up ? 'add' : 'drop'].forEach((tableData) => {
215
+ handleTableData(state, true, tableName, tableData);
210
216
  });
211
217
 
212
218
  if (state.alterTable.length) {
@@ -216,10 +222,8 @@ export const changeTable = async (
216
222
  );
217
223
  }
218
224
 
219
- const createIndexes = up ? state.indexes : state.dropIndexes;
220
- const dropIndexes = up ? state.dropIndexes : state.indexes;
221
- await migrateIndexes(state, createIndexes, up);
222
- await migrateIndexes(state, dropIndexes, !up);
225
+ await migrateIndexes(state, state.dropIndexes, false);
226
+ await migrateIndexes(state, state.indexes, true);
223
227
  await migrateComments(state, state.comments);
224
228
  };
225
229
 
@@ -309,6 +313,79 @@ const changeActions = {
309
313
  );
310
314
  }
311
315
 
316
+ if (from.compression !== to.compression) {
317
+ state.alterTable.push(
318
+ `ALTER COLUMN "${key}" SET COMPRESSION ${to.compression || 'DEFAULT'}`,
319
+ );
320
+ }
321
+
322
+ const fromFkey = from.foreignKey;
323
+ const toFkey = to.foreignKey;
324
+ if (fromFkey || toFkey) {
325
+ if ((fromFkey && 'fn' in fromFkey) || (toFkey && 'fn' in toFkey)) {
326
+ throw new Error('Callback in foreignKey is not allowed in migration');
327
+ }
328
+
329
+ if (checkIfForeignKeysAreDifferent(fromFkey, toFkey)) {
330
+ if (fromFkey) {
331
+ const data = newTableData();
332
+ data.foreignKeys.push({
333
+ columns: [key],
334
+ fnOrTable: fromFkey.table,
335
+ foreignColumns: fromFkey.columns,
336
+ options: fromFkey,
337
+ });
338
+ changeTableData[up ? 'drop' : 'add'].push(data);
339
+ }
340
+
341
+ if (toFkey) {
342
+ const data = newTableData();
343
+ data.foreignKeys.push({
344
+ columns: [key],
345
+ fnOrTable: toFkey.table,
346
+ foreignColumns: toFkey.columns,
347
+ options: toFkey,
348
+ });
349
+ changeTableData[up ? 'add' : 'drop'].push(data);
350
+ }
351
+ }
352
+ }
353
+
354
+ const fromIndex = from.index;
355
+ const toIndex = to.index;
356
+ if (
357
+ (fromIndex || toIndex) &&
358
+ checkIfIndexesAreDifferent(fromIndex, toIndex)
359
+ ) {
360
+ if (fromIndex) {
361
+ const data = newTableData();
362
+ data.indexes.push({
363
+ columns: [
364
+ {
365
+ column: key,
366
+ ...fromIndex,
367
+ },
368
+ ],
369
+ options: fromIndex,
370
+ });
371
+ changeTableData[up ? 'drop' : 'add'].push(data);
372
+ }
373
+
374
+ if (toIndex) {
375
+ const data = newTableData();
376
+ data.indexes.push({
377
+ columns: [
378
+ {
379
+ column: key,
380
+ ...toIndex,
381
+ },
382
+ ],
383
+ options: toIndex,
384
+ });
385
+ changeTableData[up ? 'add' : 'drop'].push(data);
386
+ }
387
+ }
388
+
312
389
  if (from.comment !== to.comment) {
313
390
  state.comments.push({ column: key, comment: to.comment || null });
314
391
  }
@@ -320,15 +397,60 @@ const changeActions = {
320
397
  },
321
398
  };
322
399
 
323
- const getChangeProperties = (
324
- item: ChangeArg,
325
- ): {
400
+ const checkIfForeignKeysAreDifferent = (
401
+ from?: ForeignKey<string, string[]> & { table: string },
402
+ to?: ForeignKey<string, string[]> & { table: string },
403
+ ) => {
404
+ return (
405
+ !from ||
406
+ !to ||
407
+ from.name !== to.name ||
408
+ from.match !== to.match ||
409
+ from.onUpdate !== to.onUpdate ||
410
+ from.onDelete !== to.onDelete ||
411
+ from.dropMode !== to.dropMode ||
412
+ from.table !== to.table ||
413
+ from.columns.join(',') !== to.columns.join(',')
414
+ );
415
+ };
416
+
417
+ const checkIfIndexesAreDifferent = (
418
+ from?: Omit<SingleColumnIndexOptions, 'column'>,
419
+ to?: Omit<SingleColumnIndexOptions, 'column'>,
420
+ ) => {
421
+ return (
422
+ !from ||
423
+ !to ||
424
+ from.expression !== to.expression ||
425
+ from.collate !== to.collate ||
426
+ from.operator !== to.operator ||
427
+ from.order !== to.order ||
428
+ from.name !== to.name ||
429
+ from.unique !== to.unique ||
430
+ from.using !== to.using ||
431
+ from.include !== to.include ||
432
+ (Array.isArray(from.include) &&
433
+ Array.isArray(to.include) &&
434
+ from.include.join(',') !== to.include.join(',')) ||
435
+ from.with !== to.with ||
436
+ from.tablespace !== to.tablespace ||
437
+ from.where !== to.where ||
438
+ from.dropMode !== to.dropMode
439
+ );
440
+ };
441
+
442
+ type ChangeProperties = {
326
443
  type?: string;
327
444
  collate?: string;
328
445
  default?: unknown | RawExpression;
329
446
  nullable?: boolean;
330
447
  comment?: string | null;
331
- } => {
448
+ compression?: string;
449
+ foreignKey?: ForeignKey<string, string[]>;
450
+ index?: Omit<SingleColumnIndexOptions, 'column'>;
451
+ };
452
+
453
+ const getChangeProperties = (item: ChangeArg): ChangeProperties => {
332
454
  if (item instanceof ColumnType) {
333
455
  return {
334
456
  type: item.toSQL(),
@@ -336,6 +458,9 @@ const getChangeProperties = (
336
458
  default: item.data.default,
337
459
  nullable: item.isNullable,
338
460
  comment: item.data.comment,
461
+ compression: item.data.compression,
462
+ foreignKey: item.data.foreignKey,
463
+ index: item.data.index,
339
464
  };
340
465
  } else {
341
466
  return {
@@ -344,6 +469,9 @@ const getChangeProperties = (
344
469
  default: item[0] === 'default' ? item[1] : undefined,
345
470
  nullable: item[0] === 'nullable' ? item[1] : undefined,
346
471
  comment: item[0] === 'comment' ? item[1] : undefined,
472
+ compression: item[0] === 'compression' ? item[1] : undefined,
473
+ foreignKey: item[0] === 'foreignKey' ? item[1] : undefined,
474
+ index: item[0] === 'index' ? item[1] : undefined,
347
475
  };
348
476
  }
349
477
  };
@@ -283,7 +283,7 @@ describe('migration', () => {
283
283
  "createdAt" timestamp NOT NULL DEFAULT now(),
284
284
  "updatedAt" timestamp NOT NULL DEFAULT now(),
285
285
  PRIMARY KEY ("postUuid", "commentId", "commentAuthorName"),
286
- CONSTRAINT "postsCommentsToComments" FOREIGN KEY ("commentId", "commentAuthorName") REFERENCES "comments"("id", "authorName")
286
+ CONSTRAINT "postsComments_commentId_commentAuthorName_fkey" FOREIGN KEY ("commentId", "commentAuthorName") REFERENCES "comments"("id", "authorName")
287
287
  )
288
288
  `);
289
289
  };
@@ -101,15 +101,16 @@ export const constraintToSql = (
101
101
  up: boolean,
102
102
  foreignKey: TableData['foreignKeys'][number],
103
103
  ) => {
104
- const table = getForeignKeyTable(foreignKey.fnOrTable);
105
104
  const constraintName =
106
- foreignKey.options.name || joinWords(tableName, 'to', table);
105
+ foreignKey.options.name ||
106
+ `${tableName}_${foreignKey.columns.join('_')}_fkey`;
107
107
 
108
108
  if (!up) {
109
109
  const { dropMode } = foreignKey.options;
110
110
  return `CONSTRAINT "${constraintName}"${dropMode ? ` ${dropMode}` : ''}`;
111
111
  }
112
112
 
113
+ const table = getForeignKeyTable(foreignKey.fnOrTable);
113
114
  return `CONSTRAINT "${constraintName}" FOREIGN KEY (${joinColumns(
114
115
  foreignKey.columns,
115
116
  )}) ${referencesToSql(table, foreignKey.foreignColumns, foreignKey.options)}`;