rake-db 2.3.25 → 2.3.26

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rake-db",
3
- "version": "2.3.25",
3
+ "version": "2.3.26",
4
4
  "description": "Migrations tool for Postgresql DB",
5
5
  "homepage": "https://orchid-orm.netlify.app/guide/migration-setup-and-overview.html",
6
6
  "repository": {
@@ -41,7 +41,7 @@
41
41
  "license": "ISC",
42
42
  "dependencies": {
43
43
  "pluralize": "^8.0.0",
44
- "pqb": "0.9.14",
44
+ "pqb": "0.9.15",
45
45
  "prompts": "^2.4.2"
46
46
  },
47
47
  "devDependencies": {
package/src/ast.ts CHANGED
@@ -15,6 +15,7 @@ export type RakeDbAst =
15
15
  | RakeDbAst.RenameTable
16
16
  | RakeDbAst.Schema
17
17
  | RakeDbAst.Extension
18
+ | RakeDbAst.Enum
18
19
  | RakeDbAst.ForeignKey;
19
20
 
20
21
  export namespace RakeDbAst {
@@ -101,8 +102,23 @@ export namespace RakeDbAst {
101
102
  schema?: string;
102
103
  version?: string;
103
104
  cascade?: boolean;
104
- ifExists?: boolean;
105
- ifNotExists?: boolean;
105
+ createIfNotExists?: boolean;
106
+ dropIfExists?: boolean;
107
+ };
108
+
109
+ export type Enum = {
110
+ type: 'enum';
111
+ action: 'create' | 'drop';
112
+ schema?: string;
113
+ name: string;
114
+ values: string[];
115
+ cascade?: boolean;
116
+ dropIfExists?: boolean;
117
+ };
118
+
119
+ export type EnumOptions = {
120
+ createIfNotExists?: boolean;
121
+ dropIfExists?: boolean;
106
122
  };
107
123
 
108
124
  export type ForeignKey = {
@@ -71,6 +71,7 @@ describe('changeTable', () => {
71
71
  dropCascade: t[action](t.text(), { dropMode: 'CASCADE' }),
72
72
  nullable: t[action](t.text().nullable()),
73
73
  nonNullable: t[action](t.text()),
74
+ enum: t[action](t.enum('mood')),
74
75
  withDefault: t[action](t.boolean().default(false)),
75
76
  withDefaultRaw: t[action](t.date().default(t.raw(`now()`))),
76
77
  withIndex: t[action](
@@ -115,6 +116,7 @@ describe('changeTable', () => {
115
116
  ADD COLUMN "dropCascade" text NOT NULL,
116
117
  ADD COLUMN "nullable" text,
117
118
  ADD COLUMN "nonNullable" text NOT NULL,
119
+ ADD COLUMN "enum" "mood" NOT NULL,
118
120
  ADD COLUMN "withDefault" boolean NOT NULL DEFAULT false,
119
121
  ADD COLUMN "withDefaultRaw" date NOT NULL DEFAULT now(),
120
122
  ADD COLUMN "withIndex" text NOT NULL,
@@ -155,6 +157,7 @@ describe('changeTable', () => {
155
157
  DROP COLUMN "dropCascade" CASCADE,
156
158
  DROP COLUMN "nullable",
157
159
  DROP COLUMN "nonNullable",
160
+ DROP COLUMN "enum",
158
161
  DROP COLUMN "withDefault",
159
162
  DROP COLUMN "withDefaultRaw",
160
163
  DROP COLUMN "withIndex",
@@ -407,6 +410,7 @@ describe('changeTable', () => {
407
410
  const fn = () => {
408
411
  return db.changeTable('table', (t) => ({
409
412
  changeType: t.change(t.integer(), t.text()),
413
+ changeEnum: t.change(t.enum('one'), t.enum('two')),
410
414
  changeTypeUsing: t.change(t.integer(), t.text(), {
411
415
  usingUp: t.raw('b::text'),
412
416
  usingDown: t.raw('b::int'),
@@ -427,6 +431,7 @@ describe('changeTable', () => {
427
431
  `
428
432
  ALTER TABLE "table"
429
433
  ALTER COLUMN "changeType" TYPE text,
434
+ ALTER COLUMN "changeEnum" TYPE "two",
430
435
  ALTER COLUMN "changeTypeUsing" TYPE text USING b::text,
431
436
  ALTER COLUMN "changeCollate" TYPE text COLLATE 'fr_FR',
432
437
  ALTER COLUMN "changeDefault" SET DEFAULT 'to',
@@ -443,6 +448,7 @@ describe('changeTable', () => {
443
448
  `
444
449
  ALTER TABLE "table"
445
450
  ALTER COLUMN "changeType" TYPE integer,
451
+ ALTER COLUMN "changeEnum" TYPE "one",
446
452
  ALTER COLUMN "changeTypeUsing" TYPE integer USING b::int,
447
453
  ALTER COLUMN "changeCollate" TYPE text COLLATE 'de_DE',
448
454
  ALTER COLUMN "changeDefault" SET DEFAULT 'from',
@@ -57,6 +57,7 @@ const db = getDb();
57
57
  id: t.serial().primaryKey(),
58
58
  nullable: t.text().nullable(),
59
59
  nonNullable: t.text(),
60
+ enum: t.enum('mood'),
60
61
  withDefault: t.boolean().default(false),
61
62
  withDefaultRaw: t.date().default(t.raw(`now()`)),
62
63
  withIndex: t.text().index({
@@ -94,6 +95,7 @@ const db = getDb();
94
95
  "id" serial PRIMARY KEY,
95
96
  "nullable" text,
96
97
  "nonNullable" text NOT NULL,
98
+ "enum" "mood" NOT NULL,
97
99
  "withDefault" boolean NOT NULL DEFAULT false,
98
100
  "withDefaultRaw" date NOT NULL DEFAULT now(),
99
101
  "withIndex" text NOT NULL,
@@ -317,8 +317,8 @@ describe('migration', () => {
317
317
  } an extension`, async () => {
318
318
  const fn = () => {
319
319
  return db[action]('extensionName', {
320
- ifExists: true,
321
- ifNotExists: true,
320
+ dropIfExists: true,
321
+ createIfNotExists: true,
322
322
  schema: 'schemaName',
323
323
  version: '123',
324
324
  cascade: true,
@@ -352,6 +352,52 @@ describe('migration', () => {
352
352
  });
353
353
  });
354
354
 
355
+ (['createEnum', 'dropEnum'] as const).forEach((action) => {
356
+ describe(action, () => {
357
+ it('should call appCodeUpdater', async () => {
358
+ await db[action]('enumName', ['one']);
359
+
360
+ expect(db.options.appCodeUpdater).toHaveBeenCalled();
361
+ });
362
+
363
+ it(`should ${
364
+ action === 'createEnum' ? 'add' : 'drop'
365
+ } an enum`, async () => {
366
+ const fn = () => {
367
+ return db[action]('enumName', ['one', 'two'], {
368
+ dropIfExists: true,
369
+ schema: 'schemaName',
370
+ cascade: true,
371
+ });
372
+ };
373
+
374
+ const expectCreateExtension = () => {
375
+ expectSql(`
376
+ CREATE TYPE "schemaName"."enumName" AS ENUM ($1, $2)
377
+ `);
378
+ };
379
+
380
+ const expectDropExtension = () => {
381
+ expectSql(`
382
+ DROP TYPE IF EXISTS "schemaName"."enumName" CASCADE
383
+ `);
384
+ };
385
+
386
+ await fn();
387
+ (action === 'createEnum'
388
+ ? expectCreateExtension
389
+ : expectDropExtension)();
390
+
391
+ db.up = false;
392
+ queryMock.mockClear();
393
+ await fn();
394
+ (action === 'createEnum'
395
+ ? expectDropExtension
396
+ : expectCreateExtension)();
397
+ });
398
+ });
399
+ });
400
+
355
401
  describe('tableExists', () => {
356
402
  it('should return boolean', async () => {
357
403
  queryMock.mockResolvedValueOnce({ rowCount: 1 });
@@ -17,6 +17,7 @@ import {
17
17
  createDb,
18
18
  DbResult,
19
19
  DefaultColumnTypes,
20
+ EnumColumn,
20
21
  } from 'pqb';
21
22
  import { createTable } from './createTable';
22
23
  import { changeTable, TableChangeData, TableChanger } from './changeTable';
@@ -37,9 +38,13 @@ export type TableOptions = {
37
38
 
38
39
  type TextColumnCreator = () => TextColumn;
39
40
 
40
- export type MigrationColumnTypes = Omit<ColumnTypes, 'text' | 'string'> & {
41
+ export type MigrationColumnTypes = Omit<
42
+ ColumnTypes,
43
+ 'text' | 'string' | 'enum'
44
+ > & {
41
45
  text: TextColumnCreator;
42
46
  string: TextColumnCreator;
47
+ enum: (name: string) => EnumColumn;
43
48
  };
44
49
 
45
50
  export type ColumnsShapeCallback = (
@@ -57,12 +62,6 @@ export type JoinTableOptions = {
57
62
  dropMode?: DropMode;
58
63
  };
59
64
 
60
- export type ExtensionOptions = {
61
- schema?: string;
62
- version?: string;
63
- cascade?: boolean;
64
- };
65
-
66
65
  export type Migration = DbResult<DefaultColumnTypes> & MigrationBase;
67
66
 
68
67
  export const createMigrationInterface = (
@@ -285,18 +284,37 @@ export class MigrationBase {
285
284
 
286
285
  createExtension(
287
286
  name: string,
288
- options: ExtensionOptions & { ifNotExists?: boolean } = {},
287
+ options: Omit<RakeDbAst.Extension, 'type' | 'action' | 'name'> = {},
289
288
  ) {
290
289
  return createExtension(this, this.up, name, options);
291
290
  }
292
291
 
293
292
  dropExtension(
294
293
  name: string,
295
- options: { ifExists?: boolean; cascade?: boolean } = {},
294
+ options: Omit<
295
+ RakeDbAst.Extension,
296
+ 'type' | 'action' | 'name' | 'values'
297
+ > = {},
296
298
  ) {
297
299
  return createExtension(this, !this.up, name, options);
298
300
  }
299
301
 
302
+ createEnum(
303
+ name: string,
304
+ values: string[],
305
+ options?: Omit<RakeDbAst.Enum, 'type' | 'action' | 'name' | 'values'>,
306
+ ) {
307
+ return createEnum(this, this.up, name, values, options);
308
+ }
309
+
310
+ dropEnum(
311
+ name: string,
312
+ values: string[],
313
+ options?: Omit<RakeDbAst.Enum, 'type' | 'action' | 'name' | 'values'>,
314
+ ) {
315
+ return createEnum(this, !this.up, name, values, options);
316
+ }
317
+
300
318
  async tableExists(tableName: string) {
301
319
  return queryExists(this, {
302
320
  text: `SELECT 1 FROM "information_schema"."tables" WHERE "table_name" = $1`,
@@ -420,9 +438,7 @@ const createExtension = async (
420
438
  migration: MigrationBase,
421
439
  up: boolean,
422
440
  name: string,
423
- options: ExtensionOptions & {
424
- checkExists?: boolean;
425
- },
441
+ options: Omit<RakeDbAst.Extension, 'type' | 'action' | 'name'>,
426
442
  ) => {
427
443
  const ast: RakeDbAst.Extension = {
428
444
  type: 'extension',
@@ -433,13 +449,13 @@ const createExtension = async (
433
449
 
434
450
  let query;
435
451
  if (ast.action === 'drop') {
436
- query = `DROP EXTENSION${ast.ifNotExists ? ' IF EXISTS' : ''} "${
452
+ query = `DROP EXTENSION${ast.dropIfExists ? ' IF EXISTS' : ''} "${
437
453
  ast.name
438
454
  }"${ast.cascade ? ' CASCADE' : ''}`;
439
455
  } else {
440
- query = `CREATE EXTENSION${ast.ifExists ? ' IF NOT EXISTS' : ''} "${
441
- ast.name
442
- }"${ast.schema ? ` SCHEMA "${ast.schema}"` : ''}${
456
+ query = `CREATE EXTENSION${
457
+ ast.createIfNotExists ? ' IF NOT EXISTS' : ''
458
+ } "${ast.name}"${ast.schema ? ` SCHEMA "${ast.schema}"` : ''}${
443
459
  ast.version ? ` VERSION '${ast.version}'` : ''
444
460
  }${ast.cascade ? ' CASCADE' : ''}`;
445
461
  }
@@ -449,6 +465,44 @@ const createExtension = async (
449
465
  await runCodeUpdater(migration, ast);
450
466
  };
451
467
 
468
+ const createEnum = async (
469
+ migration: MigrationBase,
470
+ up: boolean,
471
+ name: string,
472
+ values: string[],
473
+ options: Omit<RakeDbAst.Enum, 'type' | 'action' | 'name' | 'values'> = {},
474
+ ) => {
475
+ const [schema, enumName] = getSchemaAndTableFromName(name);
476
+
477
+ const ast: RakeDbAst.Enum = {
478
+ type: 'enum',
479
+ action: up ? 'create' : 'drop',
480
+ schema,
481
+ name: enumName,
482
+ values,
483
+ ...options,
484
+ };
485
+
486
+ let text;
487
+ const quotedName = quoteWithSchema(ast);
488
+ if (ast.action === 'create') {
489
+ text = `CREATE TYPE ${quotedName} AS ENUM (${values
490
+ .map((_, i) => `$${i + 1}`)
491
+ .join(', ')})`;
492
+ } else {
493
+ text = `DROP TYPE${ast.dropIfExists ? ' IF EXISTS' : ''} ${quotedName}${
494
+ ast.cascade ? ' CASCADE' : ''
495
+ }`;
496
+ }
497
+
498
+ await migration.adapter.query({
499
+ text,
500
+ values,
501
+ });
502
+
503
+ await runCodeUpdater(migration, ast);
504
+ };
505
+
452
506
  const queryExists = (
453
507
  db: MigrationBase,
454
508
  sql: { text: string; values: unknown[] },