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.
- package/.env +3 -0
- package/.env.local +1 -0
- package/README.md +1 -545
- package/db.ts +16 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.esm.js +190 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +201 -0
- package/dist/index.js.map +1 -0
- package/jest-setup.ts +3 -0
- package/migrations/20221009210157_first.ts +8 -0
- package/migrations/20221009210200_second.ts +5 -0
- package/package.json +55 -41
- package/rollup.config.js +3 -0
- package/src/commands/createOrDrop.test.ts +145 -0
- package/src/commands/createOrDrop.ts +107 -0
- package/src/commands/generate.test.ts +133 -0
- package/src/commands/generate.ts +85 -0
- package/src/commands/migrateOrRollback.test.ts +118 -0
- package/src/commands/migrateOrRollback.ts +108 -0
- package/src/common.test.ts +281 -0
- package/src/common.ts +224 -0
- package/src/index.ts +2 -0
- package/src/migration/change.ts +20 -0
- package/src/migration/changeTable.test.ts +417 -0
- package/src/migration/changeTable.ts +375 -0
- package/src/migration/createTable.test.ts +269 -0
- package/src/migration/createTable.ts +169 -0
- package/src/migration/migration.test.ts +341 -0
- package/src/migration/migration.ts +296 -0
- package/src/migration/migrationUtils.ts +281 -0
- package/src/rakeDb.ts +29 -0
- package/src/test-utils.ts +45 -0
- package/tsconfig.json +12 -0
- package/dist/lib/createAndDrop.d.ts +0 -2
- package/dist/lib/createAndDrop.js +0 -63
- package/dist/lib/defaults.d.ts +0 -2
- package/dist/lib/defaults.js +0 -5
- package/dist/lib/errorCodes.d.ts +0 -4
- package/dist/lib/errorCodes.js +0 -7
- package/dist/lib/generate.d.ts +0 -1
- package/dist/lib/generate.js +0 -99
- package/dist/lib/help.d.ts +0 -2
- package/dist/lib/help.js +0 -24
- package/dist/lib/init.d.ts +0 -2
- package/dist/lib/init.js +0 -276
- package/dist/lib/migrate.d.ts +0 -4
- package/dist/lib/migrate.js +0 -189
- package/dist/lib/migration.d.ts +0 -37
- package/dist/lib/migration.js +0 -159
- package/dist/lib/schema/changeTable.d.ts +0 -23
- package/dist/lib/schema/changeTable.js +0 -109
- package/dist/lib/schema/column.d.ts +0 -31
- package/dist/lib/schema/column.js +0 -201
- package/dist/lib/schema/createTable.d.ts +0 -10
- package/dist/lib/schema/createTable.js +0 -53
- package/dist/lib/schema/foreignKey.d.ts +0 -11
- package/dist/lib/schema/foreignKey.js +0 -53
- package/dist/lib/schema/index.d.ts +0 -3
- package/dist/lib/schema/index.js +0 -54
- package/dist/lib/schema/primaryKey.d.ts +0 -9
- package/dist/lib/schema/primaryKey.js +0 -24
- package/dist/lib/schema/table.d.ts +0 -43
- package/dist/lib/schema/table.js +0 -110
- package/dist/lib/schema/timestamps.d.ts +0 -3
- package/dist/lib/schema/timestamps.js +0 -9
- package/dist/lib/utils.d.ts +0 -26
- package/dist/lib/utils.js +0 -114
- package/dist/rake-db.d.ts +0 -2
- package/dist/rake-db.js +0 -34
- package/dist/types.d.ts +0 -94
- package/dist/types.js +0 -40
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ColumnsShape,
|
|
3
|
+
ColumnType,
|
|
4
|
+
ColumnTypes,
|
|
5
|
+
ForeignKeyOptions,
|
|
6
|
+
IndexColumnOptions,
|
|
7
|
+
IndexOptions,
|
|
8
|
+
MaybeArray,
|
|
9
|
+
TransactionAdapter,
|
|
10
|
+
} from 'pqb';
|
|
11
|
+
import { createJoinTable, createTable } from './createTable';
|
|
12
|
+
import { changeTable, TableChangeData, TableChanger } from './changeTable';
|
|
13
|
+
|
|
14
|
+
export type DropMode = 'CASCADE' | 'RESTRICT';
|
|
15
|
+
|
|
16
|
+
export type TableOptions = { dropMode?: DropMode; comment?: string };
|
|
17
|
+
export type ColumnsShapeCallback = (t: ColumnTypes) => ColumnsShape;
|
|
18
|
+
|
|
19
|
+
export type ChangeTableOptions = { comment?: string | [string, string] | null };
|
|
20
|
+
export type ChangeTableCallback = (t: TableChanger) => TableChangeData;
|
|
21
|
+
|
|
22
|
+
export type ColumnIndex = {
|
|
23
|
+
columns: IndexColumnOptions[];
|
|
24
|
+
options: IndexOptions;
|
|
25
|
+
};
|
|
26
|
+
export type ColumnComment = { column: string; comment: string | null };
|
|
27
|
+
|
|
28
|
+
export type JoinTableOptions = {
|
|
29
|
+
tableName?: string;
|
|
30
|
+
comment?: string;
|
|
31
|
+
dropMode?: DropMode;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class Migration extends TransactionAdapter {
|
|
35
|
+
constructor(tx: TransactionAdapter, public up: boolean) {
|
|
36
|
+
super(tx.pool, tx.client, tx.types);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createTable(
|
|
40
|
+
tableName: string,
|
|
41
|
+
options: TableOptions,
|
|
42
|
+
fn: ColumnsShapeCallback,
|
|
43
|
+
): Promise<void>;
|
|
44
|
+
createTable(tableName: string, fn: ColumnsShapeCallback): Promise<void>;
|
|
45
|
+
createTable(
|
|
46
|
+
tableName: string,
|
|
47
|
+
cbOrOptions: ColumnsShapeCallback | TableOptions,
|
|
48
|
+
cb?: ColumnsShapeCallback,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const options = typeof cbOrOptions === 'function' ? {} : cbOrOptions;
|
|
51
|
+
const fn = (cb || cbOrOptions) as ColumnsShapeCallback;
|
|
52
|
+
|
|
53
|
+
return createTable(this, this.up, tableName, options, fn);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
dropTable(
|
|
57
|
+
tableName: string,
|
|
58
|
+
options: TableOptions,
|
|
59
|
+
fn: ColumnsShapeCallback,
|
|
60
|
+
): Promise<void>;
|
|
61
|
+
dropTable(tableName: string, fn: ColumnsShapeCallback): Promise<void>;
|
|
62
|
+
dropTable(
|
|
63
|
+
tableName: string,
|
|
64
|
+
cbOrOptions: ColumnsShapeCallback | TableOptions,
|
|
65
|
+
cb?: ColumnsShapeCallback,
|
|
66
|
+
) {
|
|
67
|
+
const options = typeof cbOrOptions === 'function' ? {} : cbOrOptions;
|
|
68
|
+
const fn = (cb || cbOrOptions) as ColumnsShapeCallback;
|
|
69
|
+
|
|
70
|
+
return createTable(this, !this.up, tableName, options, fn);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createJoinTable(
|
|
74
|
+
tables: string[],
|
|
75
|
+
options?: JoinTableOptions,
|
|
76
|
+
fn?: ColumnsShapeCallback,
|
|
77
|
+
): Promise<void>;
|
|
78
|
+
createJoinTable(tables: string[], fn?: ColumnsShapeCallback): Promise<void>;
|
|
79
|
+
async createJoinTable(
|
|
80
|
+
tables: string[],
|
|
81
|
+
cbOrOptions?: ColumnsShapeCallback | JoinTableOptions,
|
|
82
|
+
cb?: ColumnsShapeCallback,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
const options = typeof cbOrOptions === 'function' ? {} : cbOrOptions || {};
|
|
85
|
+
const fn = (cb || cbOrOptions) as ColumnsShapeCallback | undefined;
|
|
86
|
+
|
|
87
|
+
return createJoinTable(this, this.up, tables, options, fn);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
dropJoinTable(
|
|
91
|
+
tables: string[],
|
|
92
|
+
options?: JoinTableOptions,
|
|
93
|
+
fn?: ColumnsShapeCallback,
|
|
94
|
+
): Promise<void>;
|
|
95
|
+
dropJoinTable(tables: string[], fn?: ColumnsShapeCallback): Promise<void>;
|
|
96
|
+
async dropJoinTable(
|
|
97
|
+
tables: string[],
|
|
98
|
+
cbOrOptions?: ColumnsShapeCallback | JoinTableOptions,
|
|
99
|
+
cb?: ColumnsShapeCallback,
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
const options = typeof cbOrOptions === 'function' ? {} : cbOrOptions || {};
|
|
102
|
+
const fn = (cb || cbOrOptions) as ColumnsShapeCallback | undefined;
|
|
103
|
+
|
|
104
|
+
return createJoinTable(this, !this.up, tables, options, fn);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
changeTable(
|
|
108
|
+
tableName: string,
|
|
109
|
+
options: ChangeTableOptions,
|
|
110
|
+
fn?: ChangeTableCallback,
|
|
111
|
+
): Promise<void>;
|
|
112
|
+
changeTable(tableName: string, fn: ChangeTableCallback): Promise<void>;
|
|
113
|
+
changeTable(
|
|
114
|
+
tableName: string,
|
|
115
|
+
cbOrOptions: ChangeTableCallback | ChangeTableOptions,
|
|
116
|
+
cb?: ChangeTableCallback,
|
|
117
|
+
) {
|
|
118
|
+
const [fn, options] =
|
|
119
|
+
typeof cbOrOptions === 'function' ? [cbOrOptions, {}] : [cb, cbOrOptions];
|
|
120
|
+
|
|
121
|
+
return changeTable(this, this.up, tableName, options, fn);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async renameTable(from: string, to: string): Promise<void> {
|
|
125
|
+
const [table, newName] = this.up ? [from, to] : [to, from];
|
|
126
|
+
await this.query(`ALTER TABLE "${table}" RENAME TO "${newName}"`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
addColumn(
|
|
130
|
+
tableName: string,
|
|
131
|
+
columnName: string,
|
|
132
|
+
fn: (t: ColumnTypes) => ColumnType,
|
|
133
|
+
) {
|
|
134
|
+
return addColumn(this, this.up, tableName, columnName, fn);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
dropColumn(
|
|
138
|
+
tableName: string,
|
|
139
|
+
columnName: string,
|
|
140
|
+
fn: (t: ColumnTypes) => ColumnType,
|
|
141
|
+
) {
|
|
142
|
+
return addColumn(this, !this.up, tableName, columnName, fn);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
addIndex(
|
|
146
|
+
tableName: string,
|
|
147
|
+
columns: MaybeArray<string | IndexColumnOptions>,
|
|
148
|
+
options?: IndexOptions,
|
|
149
|
+
) {
|
|
150
|
+
return addIndex(this, this.up, tableName, columns, options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
dropIndex(
|
|
154
|
+
tableName: string,
|
|
155
|
+
columns: MaybeArray<string | IndexColumnOptions>,
|
|
156
|
+
options?: IndexOptions,
|
|
157
|
+
) {
|
|
158
|
+
return addIndex(this, !this.up, tableName, columns, options);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
addForeignKey(
|
|
162
|
+
tableName: string,
|
|
163
|
+
columns: [string, ...string[]],
|
|
164
|
+
foreignTable: string,
|
|
165
|
+
foreignColumns: [string, ...string[]],
|
|
166
|
+
options?: ForeignKeyOptions,
|
|
167
|
+
) {
|
|
168
|
+
return addForeignKey(
|
|
169
|
+
this,
|
|
170
|
+
this.up,
|
|
171
|
+
tableName,
|
|
172
|
+
columns,
|
|
173
|
+
foreignTable,
|
|
174
|
+
foreignColumns,
|
|
175
|
+
options,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
dropForeignKey(
|
|
180
|
+
tableName: string,
|
|
181
|
+
columns: [string, ...string[]],
|
|
182
|
+
foreignTable: string,
|
|
183
|
+
foreignColumns: [string, ...string[]],
|
|
184
|
+
options?: ForeignKeyOptions,
|
|
185
|
+
) {
|
|
186
|
+
return addForeignKey(
|
|
187
|
+
this,
|
|
188
|
+
!this.up,
|
|
189
|
+
tableName,
|
|
190
|
+
columns,
|
|
191
|
+
foreignTable,
|
|
192
|
+
foreignColumns,
|
|
193
|
+
options,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
addPrimaryKey(
|
|
198
|
+
tableName: string,
|
|
199
|
+
columns: string[],
|
|
200
|
+
options?: { name?: string },
|
|
201
|
+
) {
|
|
202
|
+
return addPrimaryKey(this, this.up, tableName, columns, options);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
dropPrimaryKey(
|
|
206
|
+
tableName: string,
|
|
207
|
+
columns: string[],
|
|
208
|
+
options?: { name?: string },
|
|
209
|
+
) {
|
|
210
|
+
return addPrimaryKey(this, !this.up, tableName, columns, options);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
renameColumn(tableName: string, from: string, to: string) {
|
|
214
|
+
return this.changeTable(tableName, (t) => ({
|
|
215
|
+
[from]: t.rename(to),
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async tableExists(tableName: string) {
|
|
220
|
+
return queryExists(this, {
|
|
221
|
+
text: `SELECT 1 FROM "information_schema"."tables" WHERE "table_name" = $1`,
|
|
222
|
+
values: [tableName],
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async columnExists(tableName: string, columnName: string): Promise<boolean> {
|
|
227
|
+
return queryExists(this, {
|
|
228
|
+
text: `SELECT 1 FROM "information_schema"."columns" WHERE "table_name" = $1 AND "column_name" = $2`,
|
|
229
|
+
values: [tableName, columnName],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async constraintExists(constraintName: string): Promise<boolean> {
|
|
234
|
+
return queryExists(this, {
|
|
235
|
+
text: `SELECT 1 FROM "information_schema"."table_constraints" WHERE "constraint_name" = $1`,
|
|
236
|
+
values: [constraintName],
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const addColumn = (
|
|
242
|
+
migration: Migration,
|
|
243
|
+
up: boolean,
|
|
244
|
+
tableName: string,
|
|
245
|
+
columnName: string,
|
|
246
|
+
fn: (t: ColumnTypes) => ColumnType,
|
|
247
|
+
) => {
|
|
248
|
+
return changeTable(migration, up, tableName, {}, (t) => ({
|
|
249
|
+
[columnName]: t.add(fn(t)),
|
|
250
|
+
}));
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const addIndex = (
|
|
254
|
+
migration: Migration,
|
|
255
|
+
up: boolean,
|
|
256
|
+
tableName: string,
|
|
257
|
+
columns: MaybeArray<string | IndexColumnOptions>,
|
|
258
|
+
options?: IndexOptions,
|
|
259
|
+
) => {
|
|
260
|
+
return changeTable(migration, up, tableName, {}, (t) => ({
|
|
261
|
+
...t.add(t.index(columns, options)),
|
|
262
|
+
}));
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const addForeignKey = (
|
|
266
|
+
migration: Migration,
|
|
267
|
+
up: boolean,
|
|
268
|
+
tableName: string,
|
|
269
|
+
columns: [string, ...string[]],
|
|
270
|
+
foreignTable: string,
|
|
271
|
+
foreignColumns: [string, ...string[]],
|
|
272
|
+
options?: ForeignKeyOptions,
|
|
273
|
+
) => {
|
|
274
|
+
return changeTable(migration, up, tableName, {}, (t) => ({
|
|
275
|
+
...t.add(t.foreignKey(columns, foreignTable, foreignColumns, options)),
|
|
276
|
+
}));
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const addPrimaryKey = (
|
|
280
|
+
migration: Migration,
|
|
281
|
+
up: boolean,
|
|
282
|
+
tableName: string,
|
|
283
|
+
columns: string[],
|
|
284
|
+
options?: { name?: string },
|
|
285
|
+
) => {
|
|
286
|
+
return changeTable(migration, up, tableName, {}, (t) => ({
|
|
287
|
+
...t.add(t.primaryKey(columns, options)),
|
|
288
|
+
}));
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const queryExists = (
|
|
292
|
+
db: Migration,
|
|
293
|
+
sql: { text: string; values: unknown[] },
|
|
294
|
+
) => {
|
|
295
|
+
return db.query(sql).then(({ rowCount }) => rowCount > 0);
|
|
296
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Adapter,
|
|
3
|
+
ColumnType,
|
|
4
|
+
ForeignKeyModel,
|
|
5
|
+
ForeignKeyOptions,
|
|
6
|
+
getRaw,
|
|
7
|
+
isRaw,
|
|
8
|
+
quote,
|
|
9
|
+
TableData,
|
|
10
|
+
toArray,
|
|
11
|
+
} from 'pqb';
|
|
12
|
+
import { ColumnComment, ColumnIndex, Migration } from './migration';
|
|
13
|
+
import { joinColumns, joinWords } from '../common';
|
|
14
|
+
|
|
15
|
+
export const columnToSql = (
|
|
16
|
+
key: string,
|
|
17
|
+
item: ColumnType,
|
|
18
|
+
{ values }: { values: unknown[] },
|
|
19
|
+
) => {
|
|
20
|
+
const line = [`"${key}" ${item.toSQL()}`];
|
|
21
|
+
|
|
22
|
+
if (item.data.compression) {
|
|
23
|
+
line.push(`COMPRESSION ${item.data.compression}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (item.data.collate) {
|
|
27
|
+
line.push(`COLLATE ${quote(item.data.collate)}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (item.isPrimaryKey) {
|
|
31
|
+
line.push('PRIMARY KEY');
|
|
32
|
+
} else if (!item.isNullable) {
|
|
33
|
+
line.push('NOT NULL');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (item.data.default !== undefined) {
|
|
37
|
+
if (
|
|
38
|
+
typeof item.data.default === 'object' &&
|
|
39
|
+
item.data.default &&
|
|
40
|
+
isRaw(item.data.default)
|
|
41
|
+
) {
|
|
42
|
+
line.push(`DEFAULT ${getRaw(item.data.default, values)}`);
|
|
43
|
+
} else {
|
|
44
|
+
line.push(`DEFAULT ${quote(item.data.default)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { foreignKey } = item.data;
|
|
49
|
+
if (foreignKey) {
|
|
50
|
+
const table = getForeignKeyTable(
|
|
51
|
+
'fn' in foreignKey ? foreignKey.fn : foreignKey.table,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (foreignKey.name) {
|
|
55
|
+
line.push(`CONSTRAINT "${foreignKey.name}"`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
line.push(referencesToSql(table, foreignKey.columns, foreignKey));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return line.join(' ');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const addColumnIndex = (
|
|
65
|
+
indexes: ColumnIndex[],
|
|
66
|
+
key: string,
|
|
67
|
+
item: ColumnType,
|
|
68
|
+
) => {
|
|
69
|
+
if (item.data) {
|
|
70
|
+
if (item.data.index) {
|
|
71
|
+
indexes.push({
|
|
72
|
+
columns: [{ ...item.data.index, column: key }],
|
|
73
|
+
options: item.data.index,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const addColumnComment = (
|
|
80
|
+
comments: ColumnComment[],
|
|
81
|
+
key: string,
|
|
82
|
+
item: ColumnType,
|
|
83
|
+
) => {
|
|
84
|
+
if (item.data.comment) {
|
|
85
|
+
comments.push({ column: key, comment: item.data.comment });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const getForeignKeyTable = (
|
|
90
|
+
fnOrTable: (() => ForeignKeyModel) | string,
|
|
91
|
+
) => {
|
|
92
|
+
if (typeof fnOrTable === 'string') {
|
|
93
|
+
return fnOrTable;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const klass = fnOrTable();
|
|
97
|
+
return new klass().table;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const constraintToSql = (
|
|
101
|
+
tableName: string,
|
|
102
|
+
up: boolean,
|
|
103
|
+
foreignKey: TableData['foreignKeys'][number],
|
|
104
|
+
) => {
|
|
105
|
+
const table = getForeignKeyTable(foreignKey.fnOrTable);
|
|
106
|
+
const constraintName =
|
|
107
|
+
foreignKey.options.name || joinWords(tableName, 'to', table);
|
|
108
|
+
|
|
109
|
+
if (!up) {
|
|
110
|
+
const { dropMode } = foreignKey.options;
|
|
111
|
+
return `CONSTRAINT "${constraintName}"${dropMode ? ` ${dropMode}` : ''}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return `CONSTRAINT "${constraintName}" FOREIGN KEY (${joinColumns(
|
|
115
|
+
foreignKey.columns,
|
|
116
|
+
)}) ${referencesToSql(table, foreignKey.foreignColumns, foreignKey.options)}`;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const referencesToSql = (
|
|
120
|
+
table: string,
|
|
121
|
+
columns: string[],
|
|
122
|
+
foreignKey: Pick<ForeignKeyOptions, 'match' | 'onDelete' | 'onUpdate'>,
|
|
123
|
+
) => {
|
|
124
|
+
const sql: string[] = [`REFERENCES "${table}"(${joinColumns(columns)})`];
|
|
125
|
+
|
|
126
|
+
if (foreignKey.match) {
|
|
127
|
+
sql.push(`MATCH ${foreignKey.match.toUpperCase()}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (foreignKey.onDelete) {
|
|
131
|
+
sql.push(`ON DELETE ${foreignKey.onDelete.toUpperCase()}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (foreignKey.onUpdate) {
|
|
135
|
+
sql.push(`ON UPDATE ${foreignKey.onUpdate.toUpperCase()}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return sql.join(' ');
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const migrateIndexes = async (
|
|
142
|
+
state: {
|
|
143
|
+
migration: Migration;
|
|
144
|
+
tableName: string;
|
|
145
|
+
},
|
|
146
|
+
indexes: ColumnIndex[],
|
|
147
|
+
up: boolean,
|
|
148
|
+
) => {
|
|
149
|
+
for (const item of indexes) {
|
|
150
|
+
await migrateIndex(state, up, item);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const migrateIndex = (
|
|
155
|
+
state: { migration: Migration; tableName: string },
|
|
156
|
+
up: boolean,
|
|
157
|
+
{ columns, options }: ColumnIndex,
|
|
158
|
+
) => {
|
|
159
|
+
const indexName =
|
|
160
|
+
options.name ||
|
|
161
|
+
joinWords(state.tableName, ...columns.map(({ column }) => column), 'index');
|
|
162
|
+
|
|
163
|
+
if (!up) {
|
|
164
|
+
return state.migration.query(
|
|
165
|
+
`DROP INDEX "${indexName}"${
|
|
166
|
+
options.dropMode ? ` ${options.dropMode}` : ''
|
|
167
|
+
}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const values: unknown[] = [];
|
|
172
|
+
|
|
173
|
+
const sql: string[] = ['CREATE'];
|
|
174
|
+
|
|
175
|
+
if (options.unique) {
|
|
176
|
+
sql.push('UNIQUE');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
sql.push(`INDEX "${indexName}" ON "${state.tableName}"`);
|
|
180
|
+
|
|
181
|
+
if (options.using) {
|
|
182
|
+
sql.push(`USING ${options.using}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const columnsSql: string[] = [];
|
|
186
|
+
|
|
187
|
+
columns.forEach((column) => {
|
|
188
|
+
const columnSql: string[] = [
|
|
189
|
+
`"${column.column}"${column.expression ? `(${column.expression})` : ''}`,
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
if (column.collate) {
|
|
193
|
+
columnSql.push(`COLLATE '${column.collate}'`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (column.operator) {
|
|
197
|
+
columnSql.push(column.operator);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (column.order) {
|
|
201
|
+
columnSql.push(column.order);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
columnsSql.push(columnSql.join(' '));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
sql.push(`(${columnsSql.join(', ')})`);
|
|
208
|
+
|
|
209
|
+
if (options.include) {
|
|
210
|
+
sql.push(
|
|
211
|
+
`INCLUDE (${toArray(options.include)
|
|
212
|
+
.map((column) => `"${column}"`)
|
|
213
|
+
.join(', ')})`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (options.with) {
|
|
218
|
+
sql.push(`WITH (${options.with})`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (options.tablespace) {
|
|
222
|
+
sql.push(`TABLESPACE ${options.tablespace}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (options.where) {
|
|
226
|
+
sql.push(
|
|
227
|
+
`WHERE ${
|
|
228
|
+
typeof options.where === 'object' &&
|
|
229
|
+
options.where &&
|
|
230
|
+
isRaw(options.where)
|
|
231
|
+
? getRaw(options.where, values)
|
|
232
|
+
: options.where
|
|
233
|
+
}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return state.migration.query({ text: sql.join(' '), values });
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const migrateComments = async (
|
|
241
|
+
state: { migration: Migration; tableName: string },
|
|
242
|
+
comments: ColumnComment[],
|
|
243
|
+
) => {
|
|
244
|
+
for (const { column, comment } of comments) {
|
|
245
|
+
await state.migration.query(
|
|
246
|
+
`COMMENT ON COLUMN "${state.tableName}"."${column}" IS ${quote(comment)}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export const primaryKeyToSql = (
|
|
252
|
+
primaryKey: Exclude<TableData['primaryKey'], undefined>,
|
|
253
|
+
) => {
|
|
254
|
+
const name = primaryKey.options?.name;
|
|
255
|
+
return `${name ? `CONSTRAINT "${name}" ` : ''}PRIMARY KEY (${joinColumns(
|
|
256
|
+
primaryKey.columns,
|
|
257
|
+
)})`;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export const getPrimaryKeysOfTable = async (
|
|
261
|
+
db: Adapter,
|
|
262
|
+
tableName: string,
|
|
263
|
+
): Promise<{ name: string; type: string }[]> => {
|
|
264
|
+
const { rows } = await db.query<{ name: string; type: string }>({
|
|
265
|
+
text: `SELECT
|
|
266
|
+
pg_attribute.attname AS name,
|
|
267
|
+
format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type
|
|
268
|
+
FROM pg_index, pg_class, pg_attribute, pg_namespace
|
|
269
|
+
WHERE
|
|
270
|
+
pg_class.oid = $1::regclass AND
|
|
271
|
+
indrelid = pg_class.oid AND
|
|
272
|
+
nspname = 'public' AND
|
|
273
|
+
pg_class.relnamespace = pg_namespace.oid AND
|
|
274
|
+
pg_attribute.attrelid = pg_class.oid AND
|
|
275
|
+
pg_attribute.attnum = any(pg_index.indkey) AND
|
|
276
|
+
indisprimary`,
|
|
277
|
+
values: [tableName],
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return rows;
|
|
281
|
+
};
|
package/src/rakeDb.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AdapterOptions, MaybeArray } from 'pqb';
|
|
2
|
+
import { createDb, dropDb } from './commands/createOrDrop';
|
|
3
|
+
import { migrate, rollback } from './commands/migrateOrRollback';
|
|
4
|
+
import { getMigrationConfigWithDefaults, MigrationConfig } from './common';
|
|
5
|
+
import { generate } from './commands/generate';
|
|
6
|
+
|
|
7
|
+
export const rakeDb = async (
|
|
8
|
+
options: MaybeArray<AdapterOptions>,
|
|
9
|
+
partialConfig: Partial<MigrationConfig> = {},
|
|
10
|
+
args: string[] = process.argv.slice(2),
|
|
11
|
+
) => {
|
|
12
|
+
const config = getMigrationConfigWithDefaults(partialConfig);
|
|
13
|
+
|
|
14
|
+
const command = args[0].split(':')[0];
|
|
15
|
+
|
|
16
|
+
if (command === 'create') {
|
|
17
|
+
await createDb(options, config);
|
|
18
|
+
} else if (command === 'drop') {
|
|
19
|
+
await dropDb(options);
|
|
20
|
+
} else if (command === 'migrate') {
|
|
21
|
+
await migrate(options, config);
|
|
22
|
+
} else if (command === 'rollback') {
|
|
23
|
+
await rollback(options, config);
|
|
24
|
+
} else if (command === 'g' || command === 'generate') {
|
|
25
|
+
await generate(config, args.slice(1));
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`Usage: rake-db [command] [arguments]`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Migration } from './migration/migration';
|
|
2
|
+
import { MaybeArray, toArray, TransactionAdapter } from 'pqb';
|
|
3
|
+
|
|
4
|
+
let db: Migration | undefined;
|
|
5
|
+
|
|
6
|
+
export const getDb = () => {
|
|
7
|
+
if (db) return db;
|
|
8
|
+
|
|
9
|
+
db = new Migration({} as unknown as TransactionAdapter, true);
|
|
10
|
+
db.query = queryMock;
|
|
11
|
+
return db;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const queryMock = jest.fn();
|
|
15
|
+
|
|
16
|
+
export const resetDb = () => {
|
|
17
|
+
queryMock.mockClear();
|
|
18
|
+
queryMock.mockResolvedValue(undefined);
|
|
19
|
+
getDb().up = true;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const setDbDown = () => {
|
|
23
|
+
getDb().up = false;
|
|
24
|
+
queryMock.mockClear();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const trim = (s: string) => {
|
|
28
|
+
return s.trim().replace(/\n\s+/g, '\n');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const toLine = (s: string) => {
|
|
32
|
+
return s.trim().replace(/\n\s*/g, ' ');
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const expectSql = (sql: MaybeArray<string>) => {
|
|
36
|
+
expect(
|
|
37
|
+
queryMock.mock.calls.map((call) =>
|
|
38
|
+
trim(
|
|
39
|
+
typeof call[0] === 'string'
|
|
40
|
+
? call[0]
|
|
41
|
+
: (call[0] as { text: string }).text,
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
).toEqual(toArray(sql).map(trim));
|
|
45
|
+
};
|
package/tsconfig.json
ADDED