rake-db 2.3.10 → 2.3.13
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/CHANGELOG.md +22 -0
- package/dist/index.d.ts +111 -7
- package/dist/index.js +221 -120
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +222 -121
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/ast.ts +9 -1
- package/src/commands/migrateOrRollback.ts +3 -3
- package/src/common.ts +2 -2
- package/src/migration/migration.ts +3 -3
- package/src/pull/astToMigration.test.ts +56 -0
- package/src/pull/astToMigration.ts +37 -0
- package/src/pull/structureToAst.test.ts +156 -22
- package/src/pull/structureToAst.ts +256 -150
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ForeignKeyOptions,
|
|
7
7
|
instantiateColumn,
|
|
8
8
|
singleQuote,
|
|
9
|
+
TableData,
|
|
9
10
|
} from 'pqb';
|
|
10
11
|
import { getForeignKeyName, getIndexName } from '../migration/migrationUtils';
|
|
11
12
|
|
|
@@ -23,28 +24,27 @@ const fkeyActionMap = {
|
|
|
23
24
|
d: 'SET DEFAULT',
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
type Data = {
|
|
28
|
+
schemas: string[];
|
|
29
|
+
tables: DbStructure.Table[];
|
|
30
|
+
columns: DbStructure.Column[];
|
|
31
|
+
primaryKeys: DbStructure.PrimaryKey[];
|
|
32
|
+
indexes: DbStructure.Index[];
|
|
33
|
+
foreignKeys: DbStructure.ForeignKey[];
|
|
34
|
+
extensions: DbStructure.Extension[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type PendingTables = Record<
|
|
38
|
+
string,
|
|
39
|
+
{ table: DbStructure.Table; dependsOn: Set<string> }
|
|
40
|
+
>;
|
|
41
|
+
|
|
26
42
|
export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
27
43
|
const ast: RakeDbAst[] = [];
|
|
28
44
|
|
|
29
|
-
const
|
|
30
|
-
schemas,
|
|
31
|
-
tables,
|
|
32
|
-
allColumns,
|
|
33
|
-
allPrimaryKeys,
|
|
34
|
-
allIndexes,
|
|
35
|
-
allForeignKeys,
|
|
36
|
-
extensions,
|
|
37
|
-
] = await Promise.all([
|
|
38
|
-
db.getSchemas(),
|
|
39
|
-
db.getTables(),
|
|
40
|
-
db.getColumns(),
|
|
41
|
-
db.getPrimaryKeys(),
|
|
42
|
-
db.getIndexes(),
|
|
43
|
-
db.getForeignKeys(),
|
|
44
|
-
db.getExtensions(),
|
|
45
|
-
]);
|
|
45
|
+
const data = await getData(db);
|
|
46
46
|
|
|
47
|
-
for (const name of schemas) {
|
|
47
|
+
for (const name of data.schemas) {
|
|
48
48
|
if (name === 'public') continue;
|
|
49
49
|
|
|
50
50
|
ast.push({
|
|
@@ -54,154 +54,63 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const pendingTables: PendingTables = {};
|
|
58
|
+
for (const table of data.tables) {
|
|
59
|
+
const key = `${table.schemaName}.${table.name}`;
|
|
60
|
+
const dependsOn = new Set<string>();
|
|
61
61
|
|
|
62
|
-
const
|
|
62
|
+
for (const fk of data.foreignKeys) {
|
|
63
|
+
if (fk.schemaName !== table.schemaName || fk.tableName !== table.name)
|
|
64
|
+
continue;
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const tableForeignKeys = allForeignKeys.filter(belongsToTable);
|
|
68
|
-
|
|
69
|
-
const shape: ColumnsShape = {};
|
|
70
|
-
for (let item of columns) {
|
|
71
|
-
const isSerial = getIsSerial(item);
|
|
72
|
-
if (isSerial) {
|
|
73
|
-
item = { ...item, default: undefined };
|
|
66
|
+
const otherKey = `${fk.foreignTableSchemaName}.${fk.foreignTableName}`;
|
|
67
|
+
if (otherKey !== key) {
|
|
68
|
+
dependsOn.add(otherKey);
|
|
74
69
|
}
|
|
70
|
+
}
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
throw new Error(`Column type \`${item.type}\` is not supported`);
|
|
79
|
-
}
|
|
72
|
+
pendingTables[key] = { table, dependsOn };
|
|
73
|
+
}
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
for (const key in pendingTables) {
|
|
76
|
+
const { table, dependsOn } = pendingTables[key];
|
|
77
|
+
if (!dependsOn.size) {
|
|
78
|
+
pushTableAst(ast, data, table, pendingTables);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
primaryKey?.columnNames.length === 1 &&
|
|
85
|
-
primaryKey?.columnNames[0] === item.name
|
|
86
|
-
) {
|
|
87
|
-
column = column.primaryKey();
|
|
88
|
-
}
|
|
82
|
+
const outerFKeys: [DbStructure.ForeignKey, DbStructure.Table][] = [];
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'column' in it.columns[0] &&
|
|
94
|
-
it.columns[0].column === item.name,
|
|
95
|
-
);
|
|
96
|
-
for (const index of indexes) {
|
|
97
|
-
const options = index.columns[0];
|
|
98
|
-
column = column.index({
|
|
99
|
-
collate: options.collate,
|
|
100
|
-
opclass: options.opclass,
|
|
101
|
-
order: options.order,
|
|
102
|
-
name:
|
|
103
|
-
index.name !== getIndexName(name, index.columns)
|
|
104
|
-
? index.name
|
|
105
|
-
: undefined,
|
|
106
|
-
using: index.using === 'btree' ? undefined : index.using,
|
|
107
|
-
unique: index.isUnique,
|
|
108
|
-
include: index.include,
|
|
109
|
-
with: index.with,
|
|
110
|
-
tablespace: index.tablespace,
|
|
111
|
-
where: index.where,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
84
|
+
for (const key in pendingTables) {
|
|
85
|
+
const innerFKeys: DbStructure.ForeignKey[] = [];
|
|
86
|
+
const { table } = pendingTables[key];
|
|
114
87
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const foreignKey of foreignKeys) {
|
|
119
|
-
column = column.foreignKey(
|
|
120
|
-
foreignKey.foreignTableName,
|
|
121
|
-
foreignKey.foreignColumnNames[0],
|
|
122
|
-
{
|
|
123
|
-
name:
|
|
124
|
-
foreignKey.name &&
|
|
125
|
-
foreignKey.name !==
|
|
126
|
-
getForeignKeyName(name, foreignKey.columnNames)
|
|
127
|
-
? foreignKey.name
|
|
128
|
-
: undefined,
|
|
129
|
-
match: matchMap[foreignKey.match],
|
|
130
|
-
onUpdate: fkeyActionMap[foreignKey.onUpdate],
|
|
131
|
-
onDelete: fkeyActionMap[foreignKey.onDelete],
|
|
132
|
-
} as ForeignKeyOptions,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
88
|
+
for (const fkey of data.foreignKeys) {
|
|
89
|
+
if (fkey.schemaName !== table.schemaName || fkey.tableName !== table.name)
|
|
90
|
+
continue;
|
|
135
91
|
|
|
136
|
-
|
|
92
|
+
const otherKey = `${fkey.foreignTableSchemaName}.${fkey.foreignTableName}`;
|
|
93
|
+
if (!pendingTables[otherKey] || otherKey === key) {
|
|
94
|
+
innerFKeys.push(fkey);
|
|
95
|
+
} else {
|
|
96
|
+
outerFKeys.push([fkey, table]);
|
|
97
|
+
}
|
|
137
98
|
}
|
|
138
99
|
|
|
100
|
+
pushTableAst(ast, data, table, pendingTables, innerFKeys);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [fkey, table] of outerFKeys) {
|
|
139
104
|
ast.push({
|
|
140
|
-
|
|
105
|
+
...foreignKeyToAst(fkey),
|
|
106
|
+
type: 'foreignKey',
|
|
141
107
|
action: 'create',
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
name: name,
|
|
145
|
-
shape,
|
|
146
|
-
noPrimaryKey: primaryKey ? 'error' : 'ignore',
|
|
147
|
-
primaryKey:
|
|
148
|
-
primaryKey && primaryKey.columnNames.length > 1
|
|
149
|
-
? {
|
|
150
|
-
columns: primaryKey.columnNames,
|
|
151
|
-
options:
|
|
152
|
-
primaryKey.name === `${name}_pkey`
|
|
153
|
-
? undefined
|
|
154
|
-
: { name: primaryKey.name },
|
|
155
|
-
}
|
|
156
|
-
: undefined,
|
|
157
|
-
indexes: tableIndexes
|
|
158
|
-
.filter(
|
|
159
|
-
(index) =>
|
|
160
|
-
index.columns.length > 1 ||
|
|
161
|
-
index.columns.some((it) => 'expression' in it),
|
|
162
|
-
)
|
|
163
|
-
.map((index) => ({
|
|
164
|
-
columns: index.columns.map((it) => ({
|
|
165
|
-
...('column' in it
|
|
166
|
-
? { column: it.column }
|
|
167
|
-
: { expression: it.expression }),
|
|
168
|
-
collate: it.collate,
|
|
169
|
-
opclass: it.opclass,
|
|
170
|
-
order: it.order,
|
|
171
|
-
})),
|
|
172
|
-
options: {
|
|
173
|
-
name:
|
|
174
|
-
index.name !== getIndexName(name, index.columns)
|
|
175
|
-
? index.name
|
|
176
|
-
: undefined,
|
|
177
|
-
using: index.using === 'btree' ? undefined : index.using,
|
|
178
|
-
unique: index.isUnique,
|
|
179
|
-
include: index.include,
|
|
180
|
-
with: index.with,
|
|
181
|
-
tablespace: index.tablespace,
|
|
182
|
-
where: index.where,
|
|
183
|
-
},
|
|
184
|
-
})),
|
|
185
|
-
foreignKeys: tableForeignKeys
|
|
186
|
-
.filter((it) => it.columnNames.length > 1)
|
|
187
|
-
.map((it) => ({
|
|
188
|
-
columns: it.columnNames,
|
|
189
|
-
fnOrTable: it.foreignTableName,
|
|
190
|
-
foreignColumns: it.foreignColumnNames,
|
|
191
|
-
options: {
|
|
192
|
-
name:
|
|
193
|
-
it.name && it.name !== getForeignKeyName(name, it.columnNames)
|
|
194
|
-
? it.name
|
|
195
|
-
: undefined,
|
|
196
|
-
match: matchMap[it.match],
|
|
197
|
-
onUpdate: fkeyActionMap[it.onUpdate],
|
|
198
|
-
onDelete: fkeyActionMap[it.onDelete],
|
|
199
|
-
} as ForeignKeyOptions,
|
|
200
|
-
})),
|
|
108
|
+
tableSchema: table.schemaName === 'public' ? undefined : table.schemaName,
|
|
109
|
+
tableName: fkey.tableName,
|
|
201
110
|
});
|
|
202
111
|
}
|
|
203
112
|
|
|
204
|
-
for (const it of extensions) {
|
|
113
|
+
for (const it of data.extensions) {
|
|
205
114
|
ast.push({
|
|
206
115
|
type: 'extension',
|
|
207
116
|
action: 'create',
|
|
@@ -214,6 +123,36 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
214
123
|
return ast;
|
|
215
124
|
};
|
|
216
125
|
|
|
126
|
+
const getData = async (db: DbStructure): Promise<Data> => {
|
|
127
|
+
const [
|
|
128
|
+
schemas,
|
|
129
|
+
tables,
|
|
130
|
+
columns,
|
|
131
|
+
primaryKeys,
|
|
132
|
+
indexes,
|
|
133
|
+
foreignKeys,
|
|
134
|
+
extensions,
|
|
135
|
+
] = await Promise.all([
|
|
136
|
+
db.getSchemas(),
|
|
137
|
+
db.getTables(),
|
|
138
|
+
db.getColumns(),
|
|
139
|
+
db.getPrimaryKeys(),
|
|
140
|
+
db.getIndexes(),
|
|
141
|
+
db.getForeignKeys(),
|
|
142
|
+
db.getExtensions(),
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
schemas,
|
|
147
|
+
tables,
|
|
148
|
+
columns,
|
|
149
|
+
primaryKeys,
|
|
150
|
+
indexes,
|
|
151
|
+
foreignKeys,
|
|
152
|
+
extensions,
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
|
|
217
156
|
const makeBelongsToTable =
|
|
218
157
|
(schema: string | undefined, table: string) =>
|
|
219
158
|
(item: { schemaName: string; tableName: string }) =>
|
|
@@ -250,3 +189,170 @@ const getColumnType = (item: DbStructure.Column, isSerial: boolean) => {
|
|
|
250
189
|
|
|
251
190
|
return item.type;
|
|
252
191
|
};
|
|
192
|
+
|
|
193
|
+
const pushTableAst = (
|
|
194
|
+
ast: RakeDbAst[],
|
|
195
|
+
data: Data,
|
|
196
|
+
table: DbStructure.Table,
|
|
197
|
+
pendingTables: PendingTables,
|
|
198
|
+
innerFKeys = data.foreignKeys,
|
|
199
|
+
) => {
|
|
200
|
+
const { schemaName, name } = table;
|
|
201
|
+
|
|
202
|
+
const key = `${schemaName}.${table.name}`;
|
|
203
|
+
delete pendingTables[key];
|
|
204
|
+
|
|
205
|
+
if (name === 'schemaMigrations') return;
|
|
206
|
+
|
|
207
|
+
const belongsToTable = makeBelongsToTable(schemaName, name);
|
|
208
|
+
|
|
209
|
+
const columns = data.columns.filter(belongsToTable);
|
|
210
|
+
const primaryKey = data.primaryKeys.find(belongsToTable);
|
|
211
|
+
const tableIndexes = data.indexes.filter(belongsToTable);
|
|
212
|
+
const tableForeignKeys = innerFKeys.filter(belongsToTable);
|
|
213
|
+
|
|
214
|
+
const shape: ColumnsShape = {};
|
|
215
|
+
for (let item of columns) {
|
|
216
|
+
const isSerial = getIsSerial(item);
|
|
217
|
+
if (isSerial) {
|
|
218
|
+
item = { ...item, default: undefined };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const klass = columnsByType[getColumnType(item, isSerial)];
|
|
222
|
+
if (!klass) {
|
|
223
|
+
throw new Error(`Column type \`${item.type}\` is not supported`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let column = instantiateColumn(klass, item);
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
primaryKey?.columnNames.length === 1 &&
|
|
230
|
+
primaryKey?.columnNames[0] === item.name
|
|
231
|
+
) {
|
|
232
|
+
column = column.primaryKey();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const indexes = tableIndexes.filter(
|
|
236
|
+
(it) =>
|
|
237
|
+
it.columns.length === 1 &&
|
|
238
|
+
'column' in it.columns[0] &&
|
|
239
|
+
it.columns[0].column === item.name,
|
|
240
|
+
);
|
|
241
|
+
for (const index of indexes) {
|
|
242
|
+
const options = index.columns[0];
|
|
243
|
+
column = column.index({
|
|
244
|
+
collate: options.collate,
|
|
245
|
+
opclass: options.opclass,
|
|
246
|
+
order: options.order,
|
|
247
|
+
name:
|
|
248
|
+
index.name !== getIndexName(name, index.columns)
|
|
249
|
+
? index.name
|
|
250
|
+
: undefined,
|
|
251
|
+
using: index.using === 'btree' ? undefined : index.using,
|
|
252
|
+
unique: index.isUnique,
|
|
253
|
+
include: index.include,
|
|
254
|
+
with: index.with,
|
|
255
|
+
tablespace: index.tablespace,
|
|
256
|
+
where: index.where,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const foreignKeys = tableForeignKeys.filter(
|
|
261
|
+
(it) => it.columnNames.length === 1 && it.columnNames[0] === item.name,
|
|
262
|
+
);
|
|
263
|
+
for (const foreignKey of foreignKeys) {
|
|
264
|
+
column = column.foreignKey(
|
|
265
|
+
foreignKey.foreignTableName,
|
|
266
|
+
foreignKey.foreignColumnNames[0],
|
|
267
|
+
{
|
|
268
|
+
name:
|
|
269
|
+
foreignKey.name &&
|
|
270
|
+
foreignKey.name !== getForeignKeyName(name, foreignKey.columnNames)
|
|
271
|
+
? foreignKey.name
|
|
272
|
+
: undefined,
|
|
273
|
+
match: matchMap[foreignKey.match],
|
|
274
|
+
onUpdate: fkeyActionMap[foreignKey.onUpdate],
|
|
275
|
+
onDelete: fkeyActionMap[foreignKey.onDelete],
|
|
276
|
+
} as ForeignKeyOptions,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
shape[item.name] = column;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
ast.push({
|
|
284
|
+
type: 'table',
|
|
285
|
+
action: 'create',
|
|
286
|
+
schema: schemaName === 'public' ? undefined : schemaName,
|
|
287
|
+
comment: table.comment,
|
|
288
|
+
name: name,
|
|
289
|
+
shape,
|
|
290
|
+
noPrimaryKey: primaryKey ? 'error' : 'ignore',
|
|
291
|
+
primaryKey:
|
|
292
|
+
primaryKey && primaryKey.columnNames.length > 1
|
|
293
|
+
? {
|
|
294
|
+
columns: primaryKey.columnNames,
|
|
295
|
+
options:
|
|
296
|
+
primaryKey.name === `${name}_pkey`
|
|
297
|
+
? undefined
|
|
298
|
+
: { name: primaryKey.name },
|
|
299
|
+
}
|
|
300
|
+
: undefined,
|
|
301
|
+
indexes: tableIndexes
|
|
302
|
+
.filter(
|
|
303
|
+
(index) =>
|
|
304
|
+
index.columns.length > 1 ||
|
|
305
|
+
index.columns.some((it) => 'expression' in it),
|
|
306
|
+
)
|
|
307
|
+
.map((index) => ({
|
|
308
|
+
columns: index.columns.map((it) => ({
|
|
309
|
+
...('column' in it
|
|
310
|
+
? { column: it.column }
|
|
311
|
+
: { expression: it.expression }),
|
|
312
|
+
collate: it.collate,
|
|
313
|
+
opclass: it.opclass,
|
|
314
|
+
order: it.order,
|
|
315
|
+
})),
|
|
316
|
+
options: {
|
|
317
|
+
name:
|
|
318
|
+
index.name !== getIndexName(name, index.columns)
|
|
319
|
+
? index.name
|
|
320
|
+
: undefined,
|
|
321
|
+
using: index.using === 'btree' ? undefined : index.using,
|
|
322
|
+
unique: index.isUnique,
|
|
323
|
+
include: index.include,
|
|
324
|
+
with: index.with,
|
|
325
|
+
tablespace: index.tablespace,
|
|
326
|
+
where: index.where,
|
|
327
|
+
},
|
|
328
|
+
})),
|
|
329
|
+
foreignKeys: tableForeignKeys
|
|
330
|
+
.filter((it) => it.columnNames.length > 1)
|
|
331
|
+
.map(foreignKeyToAst),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
for (const otherKey in pendingTables) {
|
|
335
|
+
const item = pendingTables[otherKey];
|
|
336
|
+
if (item.dependsOn.delete(key) && item.dependsOn.size === 0) {
|
|
337
|
+
pushTableAst(ast, data, item.table, pendingTables);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const foreignKeyToAst = (
|
|
343
|
+
fkey: DbStructure.ForeignKey,
|
|
344
|
+
): TableData.ForeignKey => ({
|
|
345
|
+
columns: fkey.columnNames,
|
|
346
|
+
fnOrTable: fkey.foreignTableName,
|
|
347
|
+
foreignColumns: fkey.foreignColumnNames,
|
|
348
|
+
options: {
|
|
349
|
+
name:
|
|
350
|
+
fkey.name &&
|
|
351
|
+
fkey.name !== getForeignKeyName(fkey.tableName, fkey.columnNames)
|
|
352
|
+
? fkey.name
|
|
353
|
+
: undefined,
|
|
354
|
+
match: matchMap[fkey.match],
|
|
355
|
+
onUpdate: fkeyActionMap[fkey.onUpdate],
|
|
356
|
+
onDelete: fkeyActionMap[fkey.onDelete],
|
|
357
|
+
} as ForeignKeyOptions,
|
|
358
|
+
});
|