rake-db 1.3.2 → 2.0.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.
Files changed (72) hide show
  1. package/.env +3 -0
  2. package/.env.local +1 -0
  3. package/README.md +1 -545
  4. package/db.ts +16 -0
  5. package/dist/index.d.ts +94 -0
  6. package/dist/index.esm.js +190 -0
  7. package/dist/index.esm.js.map +1 -0
  8. package/dist/index.js +201 -0
  9. package/dist/index.js.map +1 -0
  10. package/jest-setup.ts +3 -0
  11. package/migrations/20221009210157_first.ts +8 -0
  12. package/migrations/20221009210200_second.ts +5 -0
  13. package/package.json +55 -41
  14. package/rollup.config.js +3 -0
  15. package/src/commands/createOrDrop.test.ts +145 -0
  16. package/src/commands/createOrDrop.ts +107 -0
  17. package/src/commands/generate.test.ts +133 -0
  18. package/src/commands/generate.ts +85 -0
  19. package/src/commands/migrateOrRollback.test.ts +118 -0
  20. package/src/commands/migrateOrRollback.ts +108 -0
  21. package/src/common.test.ts +281 -0
  22. package/src/common.ts +224 -0
  23. package/src/index.ts +2 -0
  24. package/src/migration/change.ts +20 -0
  25. package/src/migration/changeTable.test.ts +417 -0
  26. package/src/migration/changeTable.ts +375 -0
  27. package/src/migration/createTable.test.ts +269 -0
  28. package/src/migration/createTable.ts +169 -0
  29. package/src/migration/migration.test.ts +341 -0
  30. package/src/migration/migration.ts +296 -0
  31. package/src/migration/migrationUtils.ts +281 -0
  32. package/src/rakeDb.ts +29 -0
  33. package/src/test-utils.ts +45 -0
  34. package/tsconfig.json +12 -0
  35. package/dist/lib/createAndDrop.d.ts +0 -2
  36. package/dist/lib/createAndDrop.js +0 -63
  37. package/dist/lib/defaults.d.ts +0 -2
  38. package/dist/lib/defaults.js +0 -5
  39. package/dist/lib/errorCodes.d.ts +0 -4
  40. package/dist/lib/errorCodes.js +0 -7
  41. package/dist/lib/generate.d.ts +0 -1
  42. package/dist/lib/generate.js +0 -99
  43. package/dist/lib/help.d.ts +0 -2
  44. package/dist/lib/help.js +0 -24
  45. package/dist/lib/init.d.ts +0 -2
  46. package/dist/lib/init.js +0 -276
  47. package/dist/lib/migrate.d.ts +0 -4
  48. package/dist/lib/migrate.js +0 -189
  49. package/dist/lib/migration.d.ts +0 -37
  50. package/dist/lib/migration.js +0 -159
  51. package/dist/lib/schema/changeTable.d.ts +0 -23
  52. package/dist/lib/schema/changeTable.js +0 -109
  53. package/dist/lib/schema/column.d.ts +0 -31
  54. package/dist/lib/schema/column.js +0 -201
  55. package/dist/lib/schema/createTable.d.ts +0 -10
  56. package/dist/lib/schema/createTable.js +0 -53
  57. package/dist/lib/schema/foreignKey.d.ts +0 -11
  58. package/dist/lib/schema/foreignKey.js +0 -53
  59. package/dist/lib/schema/index.d.ts +0 -3
  60. package/dist/lib/schema/index.js +0 -54
  61. package/dist/lib/schema/primaryKey.d.ts +0 -9
  62. package/dist/lib/schema/primaryKey.js +0 -24
  63. package/dist/lib/schema/table.d.ts +0 -43
  64. package/dist/lib/schema/table.js +0 -110
  65. package/dist/lib/schema/timestamps.d.ts +0 -3
  66. package/dist/lib/schema/timestamps.js +0 -9
  67. package/dist/lib/utils.d.ts +0 -26
  68. package/dist/lib/utils.js +0 -114
  69. package/dist/rake-db.d.ts +0 -2
  70. package/dist/rake-db.js +0 -34
  71. package/dist/types.d.ts +0 -94
  72. package/dist/types.js +0 -40
@@ -0,0 +1,169 @@
1
+ import {
2
+ ColumnType,
3
+ columnTypes,
4
+ getColumnTypes,
5
+ getTableData,
6
+ Operators,
7
+ quote,
8
+ } from 'pqb';
9
+ import {
10
+ TableOptions,
11
+ ColumnsShapeCallback,
12
+ Migration,
13
+ ColumnIndex,
14
+ ColumnComment,
15
+ JoinTableOptions,
16
+ } from './migration';
17
+ import {
18
+ addColumnComment,
19
+ addColumnIndex,
20
+ columnToSql,
21
+ constraintToSql,
22
+ getPrimaryKeysOfTable,
23
+ migrateComments,
24
+ migrateIndexes,
25
+ primaryKeyToSql,
26
+ } from './migrationUtils';
27
+ import { joinWords } from '../common';
28
+ import { singular } from 'pluralize';
29
+
30
+ class UnknownColumn extends ColumnType {
31
+ operators = Operators.any;
32
+
33
+ constructor(public dataType: string) {
34
+ super();
35
+ }
36
+ }
37
+
38
+ export const createJoinTable = async (
39
+ migration: Migration,
40
+ up: boolean,
41
+ tables: string[],
42
+ options: JoinTableOptions,
43
+ fn?: ColumnsShapeCallback,
44
+ ) => {
45
+ const tableName = options.tableName || joinWords(...tables);
46
+
47
+ if (!up) {
48
+ return createTable(migration, up, tableName, options, () => ({}));
49
+ }
50
+
51
+ const tablesWithPrimaryKeys = await Promise.all(
52
+ tables.map(
53
+ async (table) =>
54
+ [
55
+ table,
56
+ await getPrimaryKeysOfTable(migration, table).then((items) =>
57
+ items.map((item) => ({
58
+ ...item,
59
+ joinedName: joinWords(singular(table), item.name),
60
+ })),
61
+ ),
62
+ ] as const,
63
+ ),
64
+ );
65
+
66
+ return createTable(migration, up, tableName, options, (t) => {
67
+ const result: Record<string, ColumnType> = {};
68
+
69
+ tablesWithPrimaryKeys.forEach(([table, primaryKeys]) => {
70
+ if (primaryKeys.length === 1) {
71
+ const [{ type, joinedName, name }] = primaryKeys;
72
+
73
+ const column = new UnknownColumn(type);
74
+
75
+ result[joinedName] = column.foreignKey(table, name);
76
+
77
+ return;
78
+ }
79
+
80
+ primaryKeys.forEach(({ joinedName, type }) => {
81
+ result[joinedName] = new UnknownColumn(type);
82
+ });
83
+
84
+ t.foreignKey(
85
+ primaryKeys.map((key) => key.joinedName) as [string, ...string[]],
86
+ table,
87
+ primaryKeys.map((key) => key.name) as [string, ...string[]],
88
+ );
89
+ });
90
+
91
+ if (fn) {
92
+ Object.assign(result, fn(t));
93
+ }
94
+
95
+ t.primaryKey(
96
+ tablesWithPrimaryKeys.flatMap(([, primaryKeys]) =>
97
+ primaryKeys.map((item) => item.joinedName),
98
+ ),
99
+ );
100
+
101
+ return result;
102
+ });
103
+ };
104
+
105
+ export const createTable = async (
106
+ migration: Migration,
107
+ up: boolean,
108
+ tableName: string,
109
+ options: TableOptions,
110
+ fn: ColumnsShapeCallback,
111
+ ) => {
112
+ const shape = getColumnTypes(columnTypes, fn);
113
+
114
+ if (!up) {
115
+ const { dropMode } = options;
116
+ await migration.query(
117
+ `DROP TABLE "${tableName}"${dropMode ? ` ${dropMode}` : ''}`,
118
+ );
119
+ return;
120
+ }
121
+
122
+ const lines: string[] = [];
123
+
124
+ const state: {
125
+ migration: Migration;
126
+ tableName: string;
127
+ values: unknown[];
128
+ indexes: ColumnIndex[];
129
+ comments: ColumnComment[];
130
+ } = {
131
+ migration,
132
+ tableName,
133
+ values: [],
134
+ indexes: [],
135
+ comments: [],
136
+ };
137
+
138
+ for (const key in shape) {
139
+ const item = shape[key];
140
+ addColumnIndex(state.indexes, key, item);
141
+ addColumnComment(state.comments, key, item);
142
+ lines.push(`\n ${columnToSql(key, item, state)}`);
143
+ }
144
+
145
+ const tableData = getTableData();
146
+ if (tableData.primaryKey) {
147
+ lines.push(`\n ${primaryKeyToSql(tableData.primaryKey)}`);
148
+ }
149
+
150
+ tableData.foreignKeys.forEach((foreignKey) => {
151
+ lines.push(`\n ${constraintToSql(state.tableName, up, foreignKey)}`);
152
+ });
153
+
154
+ await migration.query({
155
+ text: `CREATE TABLE "${tableName}" (${lines.join(',')}\n)`,
156
+ values: state.values,
157
+ });
158
+
159
+ state.indexes.push(...tableData.indexes);
160
+
161
+ await migrateIndexes(state, state.indexes, up);
162
+ await migrateComments(state, state.comments);
163
+
164
+ if (options.comment) {
165
+ await migration.query(
166
+ `COMMENT ON TABLE "${tableName}" IS ${quote(options.comment)}`,
167
+ );
168
+ }
169
+ };
@@ -0,0 +1,341 @@
1
+ import { expectSql, getDb, queryMock, resetDb, toLine } from '../test-utils';
2
+ import { getPrimaryKeysOfTable } from './migrationUtils';
3
+
4
+ const db = getDb();
5
+
6
+ jest.mock('./migrationUtils', () => ({
7
+ ...jest.requireActual('./migrationUtils'),
8
+ getPrimaryKeysOfTable: jest.fn(),
9
+ }));
10
+
11
+ describe('migration', () => {
12
+ beforeEach(resetDb);
13
+
14
+ describe('renameTable', () => {
15
+ it('should rename a table', async () => {
16
+ const fn = () => {
17
+ return db.renameTable('from', 'to');
18
+ };
19
+
20
+ await fn();
21
+ expectSql(`
22
+ ALTER TABLE "from" RENAME TO "to"
23
+ `);
24
+
25
+ db.up = false;
26
+ queryMock.mockClear();
27
+ await fn();
28
+ expectSql(`
29
+ ALTER TABLE "to" RENAME TO "from"
30
+ `);
31
+ });
32
+ });
33
+
34
+ (['addColumn', 'dropColumn'] as const).forEach((action) => {
35
+ describe(action, () => {
36
+ it(`should use changeTable to ${
37
+ action === 'addColumn' ? 'add' : 'drop'
38
+ } a column`, async () => {
39
+ const fn = () => {
40
+ return db[action]('table', 'column', (t) => t.text());
41
+ };
42
+
43
+ const expectAddColumn = () => {
44
+ expectSql(`
45
+ ALTER TABLE "table"
46
+ ADD COLUMN "column" text NOT NULL
47
+ `);
48
+ };
49
+
50
+ const expectDropColumn = () => {
51
+ expectSql(`
52
+ ALTER TABLE "table"
53
+ DROP COLUMN "column"
54
+ `);
55
+ };
56
+
57
+ await fn();
58
+ (action === 'addColumn' ? expectAddColumn : expectDropColumn)();
59
+
60
+ db.up = false;
61
+ queryMock.mockClear();
62
+ await fn();
63
+ (action === 'addColumn' ? expectDropColumn : expectAddColumn)();
64
+ });
65
+ });
66
+ });
67
+
68
+ (['addIndex', 'dropIndex'] as const).forEach((action) => {
69
+ describe(action, () => {
70
+ it(`should use changeTable to ${
71
+ action === 'addIndex' ? 'add' : 'drop'
72
+ } an index`, async () => {
73
+ const fn = () => {
74
+ return db[action](
75
+ 'table',
76
+ ['id', { column: 'name', order: 'DESC' }],
77
+ {
78
+ name: 'indexName',
79
+ },
80
+ );
81
+ };
82
+
83
+ const expectAddIndex = () => {
84
+ expectSql(`
85
+ CREATE INDEX "indexName" ON "table" ("id", "name" DESC)
86
+ `);
87
+ };
88
+
89
+ const expectDropIndex = () => {
90
+ expectSql(`
91
+ DROP INDEX "indexName"
92
+ `);
93
+ };
94
+
95
+ await fn();
96
+ (action === 'addIndex' ? expectAddIndex : expectDropIndex)();
97
+
98
+ db.up = false;
99
+ queryMock.mockClear();
100
+ await fn();
101
+ (action === 'addIndex' ? expectDropIndex : expectAddIndex)();
102
+ });
103
+ });
104
+ });
105
+
106
+ (['addForeignKey', 'dropForeignKey'] as const).forEach((action) => {
107
+ describe(action, () => {
108
+ it(`should use changeTable to ${
109
+ action === 'addForeignKey' ? 'add' : 'drop'
110
+ } a foreignKey`, async () => {
111
+ const fn = () => {
112
+ return db[action](
113
+ 'table',
114
+ ['id', 'name'],
115
+ 'otherTable',
116
+ ['foreignId', 'foreignName'],
117
+ {
118
+ name: 'constraintName',
119
+ match: 'FULL',
120
+ onUpdate: 'CASCADE',
121
+ onDelete: 'CASCADE',
122
+ dropMode: 'CASCADE',
123
+ },
124
+ );
125
+ };
126
+
127
+ const expectAddForeignKey = () => {
128
+ const expectedConstraint = toLine(`
129
+ ADD CONSTRAINT "constraintName"
130
+ FOREIGN KEY ("id", "name")
131
+ REFERENCES "otherTable"("foreignId", "foreignName")
132
+ MATCH FULL
133
+ ON DELETE CASCADE
134
+ ON UPDATE CASCADE
135
+ `);
136
+ expectSql(`
137
+ ALTER TABLE "table"
138
+ ${expectedConstraint}
139
+ `);
140
+ };
141
+
142
+ const expectDropForeignKey = () => {
143
+ expectSql(`
144
+ ALTER TABLE "table"
145
+ DROP CONSTRAINT "constraintName" CASCADE
146
+ `);
147
+ };
148
+
149
+ await fn();
150
+ (action === 'addForeignKey'
151
+ ? expectAddForeignKey
152
+ : expectDropForeignKey)();
153
+
154
+ db.up = false;
155
+ queryMock.mockClear();
156
+ await fn();
157
+ (action === 'addForeignKey'
158
+ ? expectDropForeignKey
159
+ : expectAddForeignKey)();
160
+ });
161
+ });
162
+ });
163
+
164
+ (['addPrimaryKey', 'dropPrimaryKey'] as const).forEach((action) => {
165
+ describe(action, () => {
166
+ it(`should use changeTable to ${
167
+ action === 'addPrimaryKey' ? 'add' : 'drop'
168
+ } primary key`, async () => {
169
+ const fn = () => {
170
+ return db[action]('table', ['id', 'name']);
171
+ };
172
+
173
+ const expectAddPrimaryKey = () => {
174
+ expectSql(`
175
+ ALTER TABLE "table"
176
+ ADD PRIMARY KEY ("id", "name")
177
+ `);
178
+ };
179
+
180
+ const expectDropPrimaryKey = () => {
181
+ expectSql(`
182
+ ALTER TABLE "table"
183
+ DROP CONSTRAINT "table_pkey"
184
+ `);
185
+ };
186
+
187
+ await fn();
188
+ (action === 'addPrimaryKey'
189
+ ? expectAddPrimaryKey
190
+ : expectDropPrimaryKey)();
191
+
192
+ db.up = false;
193
+ queryMock.mockClear();
194
+ await fn();
195
+ (action === 'addPrimaryKey'
196
+ ? expectDropPrimaryKey
197
+ : expectAddPrimaryKey)();
198
+ });
199
+
200
+ it('should use changeTable to add primary key with constraint name', async () => {
201
+ const fn = () => {
202
+ return db.addPrimaryKey('table', ['id', 'name'], {
203
+ name: 'primaryKeyName',
204
+ });
205
+ };
206
+
207
+ await fn();
208
+ expectSql(`
209
+ ALTER TABLE "table"
210
+ ADD CONSTRAINT "primaryKeyName" PRIMARY KEY ("id", "name")
211
+ `);
212
+
213
+ db.up = false;
214
+ queryMock.mockClear();
215
+ await fn();
216
+ expectSql(`
217
+ ALTER TABLE "table"
218
+ DROP CONSTRAINT "primaryKeyName"
219
+ `);
220
+ });
221
+ });
222
+ });
223
+
224
+ describe('renameColumn', () => {
225
+ it('should use changeTable to rename a column', async () => {
226
+ const fn = () => {
227
+ return db.renameColumn('table', 'from', 'to');
228
+ };
229
+
230
+ await fn();
231
+ expectSql(`
232
+ ALTER TABLE "table"
233
+ RENAME COLUMN "from" TO "to"
234
+ `);
235
+
236
+ db.up = false;
237
+ queryMock.mockClear();
238
+ await fn();
239
+ expectSql(`
240
+ ALTER TABLE "table"
241
+ RENAME COLUMN "to" TO "from"
242
+ `);
243
+ });
244
+ });
245
+
246
+ (['createJoinTable', 'dropJoinTable'] as const).forEach((action) => {
247
+ describe(action, () => {
248
+ it(`should ${
249
+ action === 'createJoinTable' ? 'create' : 'drop'
250
+ } a join table`, async () => {
251
+ const fn = () => {
252
+ return db[action](['posts', 'comments'], (t) => ({
253
+ ...t.timestamps(),
254
+ }));
255
+ };
256
+
257
+ const expectCreateTable = async () => {
258
+ (getPrimaryKeysOfTable as jest.Mock)
259
+ .mockResolvedValueOnce([
260
+ {
261
+ name: 'uuid',
262
+ type: 'uuid',
263
+ },
264
+ ])
265
+ .mockResolvedValueOnce([
266
+ {
267
+ name: 'id',
268
+ type: 'integer',
269
+ },
270
+ {
271
+ name: 'authorName',
272
+ type: 'text',
273
+ },
274
+ ]);
275
+
276
+ await fn();
277
+
278
+ expectSql(`
279
+ CREATE TABLE "postsComments" (
280
+ "postUuid" uuid NOT NULL REFERENCES "posts"("uuid"),
281
+ "commentId" integer NOT NULL,
282
+ "commentAuthorName" text NOT NULL,
283
+ "createdAt" timestamp NOT NULL DEFAULT now(),
284
+ "updatedAt" timestamp NOT NULL DEFAULT now(),
285
+ PRIMARY KEY ("postUuid", "commentId", "commentAuthorName"),
286
+ CONSTRAINT "postsCommentsToComments" FOREIGN KEY ("commentId", "commentAuthorName") REFERENCES "comments"("id", "authorName")
287
+ )
288
+ `);
289
+ };
290
+
291
+ const expectDropTable = async () => {
292
+ await fn();
293
+
294
+ expectSql(`
295
+ DROP TABLE "postsComments"
296
+ `);
297
+ };
298
+
299
+ await (action === 'createJoinTable'
300
+ ? expectCreateTable
301
+ : expectDropTable)();
302
+
303
+ db.up = false;
304
+ queryMock.mockClear();
305
+ await (action === 'createJoinTable'
306
+ ? expectDropTable
307
+ : expectCreateTable)();
308
+ });
309
+ });
310
+ });
311
+
312
+ describe('tableExists', () => {
313
+ it('should return boolean', async () => {
314
+ queryMock.mockResolvedValueOnce({ rowCount: 1 });
315
+ expect(await db.tableExists('table')).toBe(true);
316
+
317
+ queryMock.mockResolvedValueOnce({ rowCount: 0 });
318
+ expect(await db.tableExists('table')).toBe(false);
319
+ });
320
+ });
321
+
322
+ describe('columnExists', () => {
323
+ it('should return boolean', async () => {
324
+ queryMock.mockResolvedValueOnce({ rowCount: 1 });
325
+ expect(await db.columnExists('table', 'colum')).toBe(true);
326
+
327
+ queryMock.mockResolvedValueOnce({ rowCount: 0 });
328
+ expect(await db.columnExists('table', 'colum')).toBe(false);
329
+ });
330
+ });
331
+
332
+ describe('constraintExists', () => {
333
+ it('should return boolean', async () => {
334
+ queryMock.mockResolvedValueOnce({ rowCount: 1 });
335
+ expect(await db.constraintExists('constraintName')).toBe(true);
336
+
337
+ queryMock.mockResolvedValueOnce({ rowCount: 0 });
338
+ expect(await db.constraintExists('constraintName')).toBe(false);
339
+ });
340
+ });
341
+ });