rake-db 2.2.4 → 2.2.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.
@@ -4,7 +4,7 @@ import {
4
4
  getMigrationFiles,
5
5
  RakeDbConfig,
6
6
  MigrationFile,
7
- quoteTable,
7
+ quoteWithSchema,
8
8
  } from '../common';
9
9
  import {
10
10
  getCurrentPromise,
@@ -105,7 +105,9 @@ const saveMigratedVersion = async (
105
105
  config: RakeDbConfig,
106
106
  ) => {
107
107
  await db.query(
108
- `INSERT INTO ${quoteTable(config.migrationsTable)} VALUES ('${version}')`,
108
+ `INSERT INTO ${quoteWithSchema({
109
+ name: config.migrationsTable,
110
+ })} VALUES ('${version}')`,
109
111
  );
110
112
  };
111
113
 
@@ -115,9 +117,9 @@ const removeMigratedVersion = async (
115
117
  config: RakeDbConfig,
116
118
  ) => {
117
119
  await db.query(
118
- `DELETE FROM ${quoteTable(
119
- config.migrationsTable,
120
- )} WHERE version = '${version}'`,
120
+ `DELETE FROM ${quoteWithSchema({
121
+ name: config.migrationsTable,
122
+ })} WHERE version = '${version}'`,
121
123
  );
122
124
  };
123
125
 
@@ -127,7 +129,7 @@ const getMigratedVersionsMap = async (
127
129
  ): Promise<Record<string, boolean>> => {
128
130
  try {
129
131
  const result = await db.arrays<[string]>(
130
- `SELECT * FROM ${quoteTable(config.migrationsTable)}`,
132
+ `SELECT * FROM ${quoteWithSchema({ name: config.migrationsTable })}`,
131
133
  );
132
134
  return Object.fromEntries(result.rows.map((row) => [row[0], true]));
133
135
  } catch (err) {
@@ -8,7 +8,7 @@ import {
8
8
  joinColumns,
9
9
  joinWords,
10
10
  migrationConfigDefaults,
11
- quoteTable,
11
+ quoteWithSchema,
12
12
  setAdapterOptions,
13
13
  setAdminCredentialsToOptions,
14
14
  sortAsc,
@@ -48,6 +48,7 @@ describe('common', () => {
48
48
  requireTs: expect.any(Function),
49
49
  log: true,
50
50
  logger: console,
51
+ useCodeUpdater: true,
51
52
  });
52
53
  });
53
54
  });
@@ -284,13 +285,15 @@ describe('common', () => {
284
285
  });
285
286
  });
286
287
 
287
- describe('quoteTable', () => {
288
- it('should quote a table', () => {
289
- expect(quoteTable('table')).toBe('"table"');
288
+ describe('quoteWithSchema', () => {
289
+ it('should quote a name', () => {
290
+ expect(quoteWithSchema({ name: 'table' })).toBe('"table"');
290
291
  });
291
292
 
292
- it('should quote a table with schema', () => {
293
- expect(quoteTable('schema.table')).toBe('"schema"."table"');
293
+ it('should quote a name with schema', () => {
294
+ expect(quoteWithSchema({ schema: 'schema', name: 'table' })).toBe(
295
+ '"schema"."table"',
296
+ );
294
297
  });
295
298
  });
296
299
  });
package/src/common.ts CHANGED
@@ -30,7 +30,7 @@ export const migrationConfigDefaults = {
30
30
  requireTs: require,
31
31
  log: true,
32
32
  logger: console,
33
- useCodeUpdaterByDefault: true,
33
+ useCodeUpdater: true,
34
34
  };
35
35
 
36
36
  export const getMigrationConfigWithDefaults = (
@@ -126,9 +126,9 @@ export const createSchemaMigrations = async (
126
126
  ) => {
127
127
  try {
128
128
  await db.query(
129
- `CREATE TABLE ${quoteTable(
130
- config.migrationsTable,
131
- )} ( version TEXT NOT NULL )`,
129
+ `CREATE TABLE ${quoteWithSchema({
130
+ name: config.migrationsTable,
131
+ })} ( version TEXT NOT NULL )`,
132
132
  );
133
133
  console.log('Created versions table');
134
134
  } catch (err) {
@@ -235,11 +235,21 @@ export const joinColumns = (columns: string[]) => {
235
235
  return columns.map((column) => `"${column}"`).join(', ');
236
236
  };
237
237
 
238
- export const quoteTable = (table: string) => {
239
- const index = table.indexOf('.');
240
- if (index !== -1) {
241
- return `"${table.slice(0, index)}"."${table.slice(index + 1)}"`;
242
- } else {
243
- return `"${table}"`;
244
- }
238
+ export const quoteWithSchema = ({
239
+ schema,
240
+ name,
241
+ }: {
242
+ schema?: string;
243
+ name: string;
244
+ }) => {
245
+ return schema ? `"${schema}"."${name}"` : `"${name}"`;
246
+ };
247
+
248
+ export const getSchemaAndTableFromName = (
249
+ name: string,
250
+ ): [string | undefined, string] => {
251
+ const index = name.indexOf('.');
252
+ return index !== -1
253
+ ? [name.slice(0, index), name.slice(index + 1)]
254
+ : [undefined, name];
245
255
  };
@@ -18,6 +18,23 @@ describe('changeTable', () => {
18
18
  expect(db.options.appCodeUpdater).toHaveBeenCalled();
19
19
  });
20
20
 
21
+ it('should work for table with schema', async () => {
22
+ const fn = () => {
23
+ return db.changeTable('schema.table', (t) => ({
24
+ column: t.add(t.text()),
25
+ }));
26
+ };
27
+
28
+ await fn();
29
+ expectSql(
30
+ `ALTER TABLE "schema"."table"\nADD COLUMN "column" text NOT NULL`,
31
+ );
32
+
33
+ setDbDown();
34
+ await fn();
35
+ expectSql(`ALTER TABLE "schema"."table"\nDROP COLUMN "column"`);
36
+ });
37
+
21
38
  it('should set comment', async () => {
22
39
  const fn = () => {
23
40
  return db.changeTable('table', { comment: 'comment' });
@@ -23,7 +23,7 @@ import {
23
23
  runCodeUpdater,
24
24
  } from './migration';
25
25
  import { RakeDbAst } from '../ast';
26
- import { quoteTable } from '../common';
26
+ import { getSchemaAndTableFromName, quoteWithSchema } from '../common';
27
27
  import {
28
28
  addColumnComment,
29
29
  addColumnIndex,
@@ -135,7 +135,7 @@ const columnTypeToColumnChange = (
135
135
  return {
136
136
  column: item,
137
137
  type: item.toSQL(),
138
- nullable: item.isNullable,
138
+ nullable: item.data.isNullable,
139
139
  primaryKey: item.isPrimaryKey,
140
140
  ...item.data,
141
141
  foreignKey,
@@ -257,9 +257,12 @@ const makeAst = (
257
257
  }
258
258
  }
259
259
 
260
+ const [schema, table] = getSchemaAndTableFromName(name);
261
+
260
262
  return {
261
263
  type: 'changeTable',
262
- name,
264
+ schema,
265
+ name: table,
263
266
  comment: comment
264
267
  ? up
265
268
  ? Array.isArray(comment)
@@ -287,7 +290,7 @@ const astToQueries = (ast: RakeDbAst.ChangeTable): Sql[] => {
287
290
 
288
291
  if (ast.comment !== undefined) {
289
292
  result.push({
290
- text: `COMMENT ON TABLE ${quoteTable(ast.name)} IS ${quote(ast.comment)}`,
293
+ text: `COMMENT ON TABLE ${quoteWithSchema(ast)} IS ${quote(ast.comment)}`,
291
294
  values: [],
292
295
  });
293
296
  }
@@ -493,7 +496,7 @@ const astToQueries = (ast: RakeDbAst.ChangeTable): Sql[] => {
493
496
 
494
497
  prependAlterTable.push(
495
498
  ...dropForeignKeys.map(
496
- (foreignKey) => `\n DROP ${constraintToSql(ast.name, false, foreignKey)}`,
499
+ (foreignKey) => `\n DROP ${constraintToSql(ast, false, foreignKey)}`,
497
500
  ),
498
501
  );
499
502
 
@@ -509,22 +512,22 @@ const astToQueries = (ast: RakeDbAst.ChangeTable): Sql[] => {
509
512
 
510
513
  alterTable.push(
511
514
  ...addForeignKeys.map(
512
- (foreignKey) => `\n ADD ${constraintToSql(ast.name, true, foreignKey)}`,
515
+ (foreignKey) => `\n ADD ${constraintToSql(ast, true, foreignKey)}`,
513
516
  ),
514
517
  );
515
518
 
516
519
  if (alterTable.length) {
517
520
  result.push({
518
521
  text:
519
- `ALTER TABLE ${quoteTable(ast.name)}` +
522
+ `ALTER TABLE ${quoteWithSchema(ast)}` +
520
523
  `\n ${alterTable.join(',\n ')}`,
521
524
  values,
522
525
  });
523
526
  }
524
527
 
525
- result.push(...indexesToQuery(false, ast.name, dropIndexes));
526
- result.push(...indexesToQuery(true, ast.name, addIndexes));
527
- result.push(...commentsToQuery(ast.name, comments));
528
+ result.push(...indexesToQuery(false, ast, dropIndexes));
529
+ result.push(...indexesToQuery(true, ast, addIndexes));
530
+ result.push(...commentsToQuery(ast, comments));
528
531
 
529
532
  return result;
530
533
  };
@@ -0,0 +1,96 @@
1
+ import { getPrimaryKeysOfTable } from './migrationUtils';
2
+ import { expectSql, getDb, queryMock, resetDb } from '../test-utils';
3
+
4
+ const db = getDb();
5
+
6
+ jest.mock('./migrationUtils', () => ({
7
+ ...jest.requireActual('./migrationUtils'),
8
+ getPrimaryKeysOfTable: jest.fn(),
9
+ }));
10
+
11
+ describe('join table', () => {
12
+ beforeEach(resetDb);
13
+
14
+ (['createJoinTable', 'dropJoinTable'] as const).forEach((action) => {
15
+ describe(action, () => {
16
+ it(`should ${
17
+ action === 'createJoinTable' ? 'create' : 'drop'
18
+ } a join table`, async () => {
19
+ const fn = () => {
20
+ return db[action](['posts', 'comments'], (t) => ({
21
+ ...t.timestamps(),
22
+ }));
23
+ };
24
+
25
+ const expectCreateTable = async () => {
26
+ (getPrimaryKeysOfTable as jest.Mock)
27
+ .mockResolvedValueOnce([
28
+ {
29
+ name: 'uuid',
30
+ type: 'uuid',
31
+ },
32
+ ])
33
+ .mockResolvedValueOnce([
34
+ {
35
+ name: 'id',
36
+ type: 'integer',
37
+ },
38
+ {
39
+ name: 'authorName',
40
+ type: 'text',
41
+ },
42
+ ]);
43
+
44
+ await fn();
45
+
46
+ expectSql(`
47
+ CREATE TABLE "postsComments" (
48
+ "postUuid" uuid NOT NULL REFERENCES "posts"("uuid"),
49
+ "commentId" integer NOT NULL,
50
+ "commentAuthorName" text NOT NULL,
51
+ "createdAt" timestamp NOT NULL DEFAULT now(),
52
+ "updatedAt" timestamp NOT NULL DEFAULT now(),
53
+ PRIMARY KEY ("postUuid", "commentId", "commentAuthorName"),
54
+ CONSTRAINT "postsComments_commentId_commentAuthorName_fkey" FOREIGN KEY ("commentId", "commentAuthorName") REFERENCES "comments"("id", "authorName")
55
+ )
56
+ `);
57
+ };
58
+
59
+ const expectDropTable = async () => {
60
+ await fn();
61
+
62
+ expectSql(`
63
+ DROP TABLE "postsComments"
64
+ `);
65
+ };
66
+
67
+ await (action === 'createJoinTable'
68
+ ? expectCreateTable
69
+ : expectDropTable)();
70
+
71
+ db.up = false;
72
+ queryMock.mockClear();
73
+ await (action === 'createJoinTable'
74
+ ? expectDropTable
75
+ : expectCreateTable)();
76
+ });
77
+
78
+ it('should throw error if table has no primary key', async () => {
79
+ db.up = action !== 'dropJoinTable';
80
+
81
+ (getPrimaryKeysOfTable as jest.Mock)
82
+ .mockResolvedValueOnce([
83
+ {
84
+ name: 'id',
85
+ type: 'integer',
86
+ },
87
+ ])
88
+ .mockResolvedValueOnce([]);
89
+
90
+ await expect(db[action](['posts', 'comments'])).rejects.toThrow(
91
+ 'Primary key for table "comments" is not defined',
92
+ );
93
+ });
94
+ });
95
+ });
96
+ });
@@ -1,6 +1,10 @@
1
1
  import { ColumnType, Operators } from 'pqb';
2
2
  import { ColumnsShapeCallback, JoinTableOptions, Migration } from './migration';
3
- import { joinWords, quoteTable } from '../common';
3
+ import {
4
+ getSchemaAndTableFromName,
5
+ joinWords,
6
+ quoteWithSchema,
7
+ } from '../common';
4
8
  import { getPrimaryKeysOfTable } from './migrationUtils';
5
9
  import { singular } from 'pluralize';
6
10
  import { createTable } from './createTable';
@@ -46,26 +50,33 @@ export const createJoinTable = async (
46
50
  })),
47
51
  );
48
52
 
53
+ const [schema, name] = getSchemaAndTableFromName(table);
49
54
  if (!primaryKeys.length) {
50
55
  throw new Error(
51
- `Primary key for table ${quoteTable(table)} is not defined`,
56
+ `Primary key for table ${quoteWithSchema({
57
+ schema,
58
+ name,
59
+ })} is not defined`,
52
60
  );
53
61
  }
54
62
 
55
- return [table, primaryKeys] as const;
63
+ return [schema, table, primaryKeys] as const;
56
64
  }),
57
65
  );
58
66
 
59
67
  return createTable(migration, up, tableName, options, (t) => {
60
68
  const result: Record<string, ColumnType> = {};
61
69
 
62
- tablesWithPrimaryKeys.forEach(([table, primaryKeys]) => {
70
+ tablesWithPrimaryKeys.forEach(([schema, table, primaryKeys]) => {
63
71
  if (primaryKeys.length === 1) {
64
72
  const [{ type, joinedName, name }] = primaryKeys;
65
73
 
66
74
  const column = new UnknownColumn(type);
67
75
 
68
- result[joinedName] = column.foreignKey(table, name);
76
+ result[joinedName] = column.foreignKey(
77
+ schema ? `${schema}.${table}` : table,
78
+ name,
79
+ );
69
80
 
70
81
  return;
71
82
  }
@@ -86,7 +97,7 @@ export const createJoinTable = async (
86
97
  }
87
98
 
88
99
  t.primaryKey(
89
- tablesWithPrimaryKeys.flatMap(([, primaryKeys]) =>
100
+ tablesWithPrimaryKeys.flatMap(([, , primaryKeys]) =>
90
101
  primaryKeys.map((item) => item.joinedName),
91
102
  ),
92
103
  );
@@ -14,6 +14,22 @@ const db = getDb();
14
14
  expect(db.options.appCodeUpdater).toHaveBeenCalled();
15
15
  });
16
16
 
17
+ it(`should ${action} with schema`, async () => {
18
+ await db[action]('schema.name', (t) => ({ id: t.serial().primaryKey() }));
19
+
20
+ if (action === 'createTable') {
21
+ expectSql(`
22
+ CREATE TABLE "schema"."name" (
23
+ "id" serial PRIMARY KEY
24
+ )
25
+ `);
26
+ } else {
27
+ expectSql(`
28
+ DROP TABLE "schema"."name"
29
+ `);
30
+ }
31
+ });
32
+
17
33
  it(`should ${action} with comment`, async () => {
18
34
  await db[action]('name', { comment: 'this is a table comment' }, (t) => ({
19
35
  id: t.serial().primaryKey(),
@@ -25,7 +25,7 @@ import {
25
25
  indexesToQuery,
26
26
  primaryKeyToSql,
27
27
  } from './migrationUtils';
28
- import { quoteTable } from '../common';
28
+ import { getSchemaAndTableFromName, quoteWithSchema } from '../common';
29
29
  import { RakeDbAst } from '../ast';
30
30
 
31
31
  const types = Object.assign(Object.create(columnTypes), {
@@ -77,10 +77,13 @@ const makeAst = (
77
77
 
78
78
  const primaryKey = tableData.primaryKey;
79
79
 
80
+ const [schema, table] = getSchemaAndTableFromName(tableName);
81
+
80
82
  return {
81
83
  type: 'table',
82
84
  action: up ? 'create' : 'drop',
83
- name: tableName,
85
+ schema,
86
+ name: table,
84
87
  shape,
85
88
  ...tableData,
86
89
  primaryKey:
@@ -121,7 +124,7 @@ const astToQueries = (ast: RakeDbAst.Table): Sql[] => {
121
124
  if (ast.action === 'drop') {
122
125
  return [
123
126
  {
124
- text: `DROP TABLE ${quoteTable(ast.name)}${
127
+ text: `DROP TABLE ${quoteWithSchema(ast)}${
125
128
  ast.dropMode ? ` ${ast.dropMode}` : ''
126
129
  }`,
127
130
  values: [],
@@ -146,23 +149,23 @@ const astToQueries = (ast: RakeDbAst.Table): Sql[] => {
146
149
  }
147
150
 
148
151
  ast.foreignKeys.forEach((foreignKey) => {
149
- lines.push(`\n ${constraintToSql(ast.name, true, foreignKey)}`);
152
+ lines.push(`\n ${constraintToSql(ast, true, foreignKey)}`);
150
153
  });
151
154
 
152
155
  indexes.push(...ast.indexes);
153
156
 
154
157
  const result: Sql[] = [
155
158
  {
156
- text: `CREATE TABLE ${quoteTable(ast.name)} (${lines.join(',')}\n)`,
159
+ text: `CREATE TABLE ${quoteWithSchema(ast)} (${lines.join(',')}\n)`,
157
160
  values,
158
161
  },
159
- ...indexesToQuery(true, ast.name, indexes),
160
- ...commentsToQuery(ast.name, comments),
162
+ ...indexesToQuery(true, ast, indexes),
163
+ ...commentsToQuery(ast, comments),
161
164
  ];
162
165
 
163
166
  if (ast.comment) {
164
167
  result.push({
165
- text: `COMMENT ON TABLE ${quoteTable(ast.name)} IS ${quote(ast.comment)}`,
168
+ text: `COMMENT ON TABLE ${quoteWithSchema(ast)} IS ${quote(ast.comment)}`,
166
169
  values: [],
167
170
  });
168
171
  }
@@ -1,5 +1,4 @@
1
1
  import { expectSql, getDb, queryMock, resetDb, toLine } from '../test-utils';
2
- import { getPrimaryKeysOfTable } from './migrationUtils';
3
2
 
4
3
  const db = getDb();
5
4
 
@@ -37,6 +36,24 @@ describe('migration', () => {
37
36
  });
38
37
  });
39
38
 
39
+ it('should rename table with schema', async () => {
40
+ const fn = () => {
41
+ return db.renameTable('one.from', 'two.to');
42
+ };
43
+
44
+ await fn();
45
+ expectSql(`
46
+ ALTER TABLE "one"."from" RENAME TO "two"."to"
47
+ `);
48
+
49
+ db.up = false;
50
+ queryMock.mockClear();
51
+ await fn();
52
+ expectSql(`
53
+ ALTER TABLE "two"."to" RENAME TO "one"."from"
54
+ `);
55
+ });
56
+
40
57
  (['addColumn', 'dropColumn'] as const).forEach((action) => {
41
58
  describe(action, () => {
42
59
  it(`should use changeTable to ${
@@ -249,91 +266,6 @@ describe('migration', () => {
249
266
  });
250
267
  });
251
268
 
252
- (['createJoinTable', 'dropJoinTable'] as const).forEach((action) => {
253
- describe(action, () => {
254
- it(`should ${
255
- action === 'createJoinTable' ? 'create' : 'drop'
256
- } a join table`, async () => {
257
- const fn = () => {
258
- return db[action](['posts', 'comments'], (t) => ({
259
- ...t.timestamps(),
260
- }));
261
- };
262
-
263
- const expectCreateTable = async () => {
264
- (getPrimaryKeysOfTable as jest.Mock)
265
- .mockResolvedValueOnce([
266
- {
267
- name: 'uuid',
268
- type: 'uuid',
269
- },
270
- ])
271
- .mockResolvedValueOnce([
272
- {
273
- name: 'id',
274
- type: 'integer',
275
- },
276
- {
277
- name: 'authorName',
278
- type: 'text',
279
- },
280
- ]);
281
-
282
- await fn();
283
-
284
- expectSql(`
285
- CREATE TABLE "postsComments" (
286
- "postUuid" uuid NOT NULL REFERENCES "posts"("uuid"),
287
- "commentId" integer NOT NULL,
288
- "commentAuthorName" text NOT NULL,
289
- "createdAt" timestamp NOT NULL DEFAULT now(),
290
- "updatedAt" timestamp NOT NULL DEFAULT now(),
291
- PRIMARY KEY ("postUuid", "commentId", "commentAuthorName"),
292
- CONSTRAINT "postsComments_commentId_commentAuthorName_fkey" FOREIGN KEY ("commentId", "commentAuthorName") REFERENCES "comments"("id", "authorName")
293
- )
294
- `);
295
- };
296
-
297
- const expectDropTable = async () => {
298
- await fn();
299
-
300
- expectSql(`
301
- DROP TABLE "postsComments"
302
- `);
303
- };
304
-
305
- await (action === 'createJoinTable'
306
- ? expectCreateTable
307
- : expectDropTable)();
308
-
309
- db.up = false;
310
- queryMock.mockClear();
311
- await (action === 'createJoinTable'
312
- ? expectDropTable
313
- : expectCreateTable)();
314
- });
315
-
316
- it('should throw error if table has no primary key', async () => {
317
- if (action === 'dropJoinTable') {
318
- db.up = false;
319
- }
320
-
321
- (getPrimaryKeysOfTable as jest.Mock)
322
- .mockResolvedValueOnce([
323
- {
324
- name: 'id',
325
- type: 'integer',
326
- },
327
- ])
328
- .mockResolvedValueOnce([]);
329
-
330
- await expect(db[action](['posts', 'comments'])).rejects.toThrow(
331
- 'Primary key for table "comments" is not defined',
332
- );
333
- });
334
- });
335
- });
336
-
337
269
  (['createSchema', 'dropSchema'] as const).forEach((action) => {
338
270
  describe(action, () => {
339
271
  it('should call appCodeUpdater', async () => {
@@ -21,7 +21,11 @@ import {
21
21
  } from 'pqb';
22
22
  import { createTable } from './createTable';
23
23
  import { changeTable, TableChangeData, TableChanger } from './changeTable';
24
- import { RakeDbConfig, quoteTable } from '../common';
24
+ import {
25
+ RakeDbConfig,
26
+ quoteWithSchema,
27
+ getSchemaAndTableFromName,
28
+ } from '../common';
25
29
  import { createJoinTable } from './createJoinTable';
26
30
  import { RakeDbAst } from '../ast';
27
31
 
@@ -179,14 +183,24 @@ export class Migration extends TransactionAdapter {
179
183
  }
180
184
 
181
185
  async renameTable(from: string, to: string): Promise<void> {
186
+ const [fromSchema, f] = getSchemaAndTableFromName(this.up ? from : to);
187
+ const [toSchema, t] = getSchemaAndTableFromName(this.up ? to : from);
182
188
  const ast: RakeDbAst.RenameTable = {
183
189
  type: 'renameTable',
184
- from: this.up ? from : to,
185
- to: this.up ? to : from,
190
+ fromSchema,
191
+ from: f,
192
+ toSchema,
193
+ to: t,
186
194
  };
187
195
 
188
196
  await this.query(
189
- `ALTER TABLE ${quoteTable(ast.from)} RENAME TO "${ast.to}"`,
197
+ `ALTER TABLE ${quoteWithSchema({
198
+ schema: ast.fromSchema,
199
+ name: ast.from,
200
+ })} RENAME TO ${quoteWithSchema({
201
+ schema: ast.toSchema,
202
+ name: ast.to,
203
+ })}`,
190
204
  );
191
205
 
192
206
  await runCodeUpdater(this, ast);