rake-db 2.3.2 → 2.3.3
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 +6 -0
- package/dist/index.esm.js +17 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +17 -10
- package/dist/index.js.map +1 -1
- package/migrations/20230124210600_pull.ts +86 -0
- package/package.json +1 -1
- package/src/migration/changeTable.test.ts +10 -10
- package/src/migration/createTable.test.ts +1 -1
- package/src/migration/migrationUtils.ts +19 -11
- package/src/pull/structureToAst.test.ts +100 -0
- package/src/pull/structureToAst.ts +19 -4
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { change } from '../src';
|
|
2
|
+
|
|
3
|
+
change(async (db) => {
|
|
4
|
+
await db.createSchema('geo');
|
|
5
|
+
|
|
6
|
+
await db.createTable('chat', (t) => ({
|
|
7
|
+
id: t.serial().primaryKey(),
|
|
8
|
+
title: t.text(),
|
|
9
|
+
...t.timestamps(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
await db.createTable('chatUser', (t) => ({
|
|
13
|
+
chatId: t.integer().foreignKey('chat', 'id'),
|
|
14
|
+
userId: t.integer().foreignKey('user', 'id'),
|
|
15
|
+
...t.timestamps(),
|
|
16
|
+
...t.primaryKey(['chatId', 'userId']),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
await db.createTable('geo.city', (t) => ({
|
|
20
|
+
id: t.serial().primaryKey(),
|
|
21
|
+
name: t.text(),
|
|
22
|
+
countryId: t.integer().foreignKey('country', 'id'),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
await db.createTable('geo.country', (t) => ({
|
|
26
|
+
id: t.serial().primaryKey(),
|
|
27
|
+
name: t.text(),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
await db.createTable('message', (t) => ({
|
|
31
|
+
id: t.serial().primaryKey(),
|
|
32
|
+
chatId: t.integer().foreignKey('chat', 'id').index({
|
|
33
|
+
name: 'messageChatIdIndex',
|
|
34
|
+
}),
|
|
35
|
+
authorId: t.integer().foreignKey('user', 'id').nullable().index({
|
|
36
|
+
name: 'messageAuthorIdIndex',
|
|
37
|
+
}),
|
|
38
|
+
text: t.text(),
|
|
39
|
+
meta: t.json((t) => t.unknown()).nullable(),
|
|
40
|
+
...t.timestamps(),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
await db.createTable('profile', (t) => ({
|
|
44
|
+
id: t.serial().primaryKey(),
|
|
45
|
+
userId: t.integer().foreignKey('user', 'id').nullable(),
|
|
46
|
+
bio: t.text().nullable(),
|
|
47
|
+
...t.timestamps(),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
await db.createTable('uniqueTable', (t) => ({
|
|
51
|
+
id: t.serial().primaryKey(),
|
|
52
|
+
one: t.text().unique({
|
|
53
|
+
name: 'uniqueTableOneIndex',
|
|
54
|
+
}),
|
|
55
|
+
two: t.integer().unique({
|
|
56
|
+
name: 'uniqueTableTwoIndex',
|
|
57
|
+
}),
|
|
58
|
+
thirdColumn: t.text(),
|
|
59
|
+
fourthColumn: t.integer(),
|
|
60
|
+
...t.index(
|
|
61
|
+
[
|
|
62
|
+
{
|
|
63
|
+
column: 'thirdColumn',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
column: 'fourthColumn',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
{
|
|
70
|
+
name: 'uniqueTableThirdColumnFourthColumnIndex',
|
|
71
|
+
unique: true,
|
|
72
|
+
},
|
|
73
|
+
),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
await db.createTable('user', (t) => ({
|
|
77
|
+
id: t.serial().primaryKey(),
|
|
78
|
+
name: t.text(),
|
|
79
|
+
password: t.text(),
|
|
80
|
+
picture: t.text().nullable(),
|
|
81
|
+
data: t.json((t) => t.unknown()).nullable(),
|
|
82
|
+
age: t.integer().nullable(),
|
|
83
|
+
active: t.boolean().nullable(),
|
|
84
|
+
...t.timestamps(),
|
|
85
|
+
}));
|
|
86
|
+
});
|
package/package.json
CHANGED
|
@@ -139,7 +139,7 @@ describe('changeTable', () => {
|
|
|
139
139
|
WHERE column = 123
|
|
140
140
|
`),
|
|
141
141
|
toLine(`
|
|
142
|
-
CREATE UNIQUE INDEX "
|
|
142
|
+
CREATE UNIQUE INDEX "table_uniqueColumn_idx"
|
|
143
143
|
ON "table"
|
|
144
144
|
("uniqueColumn")
|
|
145
145
|
`),
|
|
@@ -169,7 +169,7 @@ describe('changeTable', () => {
|
|
|
169
169
|
DROP COLUMN "updatedAt"
|
|
170
170
|
`,
|
|
171
171
|
toLine(`DROP INDEX "indexName"`),
|
|
172
|
-
toLine(`DROP INDEX "
|
|
172
|
+
toLine(`DROP INDEX "table_uniqueColumn_idx" CASCADE`),
|
|
173
173
|
]);
|
|
174
174
|
};
|
|
175
175
|
|
|
@@ -664,11 +664,11 @@ describe('changeTable', () => {
|
|
|
664
664
|
|
|
665
665
|
await fn();
|
|
666
666
|
expectSql([
|
|
667
|
-
`DROP INDEX "
|
|
668
|
-
`DROP INDEX "
|
|
667
|
+
`DROP INDEX "table_removeIndex_idx"`,
|
|
668
|
+
`DROP INDEX "table_removeIndexWithOptions_idx" CASCADE`,
|
|
669
669
|
`DROP INDEX "from" CASCADE`,
|
|
670
|
-
`CREATE INDEX "
|
|
671
|
-
`CREATE UNIQUE INDEX "
|
|
670
|
+
`CREATE INDEX "table_addIndex_idx" ON "table" ("addIndex")`,
|
|
671
|
+
`CREATE UNIQUE INDEX "table_addIndexWithOptions_idx" ON "table" USING using ("addIndexWithOptions" COLLATE 'collate' opclass order) INCLUDE ("a", "b") WITH (with) TABLESPACE tablespace WHERE where`,
|
|
672
672
|
`CREATE UNIQUE INDEX "to" ON "table" USING to ("changeIndex" COLLATE 'to' to to) INCLUDE ("c", "d") WITH (to) TABLESPACE to WHERE to`,
|
|
673
673
|
]);
|
|
674
674
|
|
|
@@ -676,11 +676,11 @@ describe('changeTable', () => {
|
|
|
676
676
|
db.up = false;
|
|
677
677
|
await fn();
|
|
678
678
|
expectSql([
|
|
679
|
-
`DROP INDEX "
|
|
680
|
-
`DROP INDEX "
|
|
679
|
+
`DROP INDEX "table_addIndex_idx"`,
|
|
680
|
+
`DROP INDEX "table_addIndexWithOptions_idx" CASCADE`,
|
|
681
681
|
`DROP INDEX "to" RESTRICT`,
|
|
682
|
-
`CREATE INDEX "
|
|
683
|
-
`CREATE UNIQUE INDEX "
|
|
682
|
+
`CREATE INDEX "table_removeIndex_idx" ON "table" ("removeIndex")`,
|
|
683
|
+
`CREATE UNIQUE INDEX "table_removeIndexWithOptions_idx" ON "table" USING using ("removeIndexWithOptions" COLLATE 'collate' opclass order) INCLUDE ("a", "b") WITH (with) TABLESPACE tablespace WHERE where`,
|
|
684
684
|
`CREATE INDEX "from" ON "table" USING from ("changeIndex" COLLATE 'from' from from) INCLUDE ("a", "b") WITH (from) TABLESPACE from WHERE from`,
|
|
685
685
|
]);
|
|
686
686
|
});
|
|
@@ -13,7 +13,6 @@ import { ColumnComment, Migration } from './migration';
|
|
|
13
13
|
import {
|
|
14
14
|
getSchemaAndTableFromName,
|
|
15
15
|
joinColumns,
|
|
16
|
-
joinWords,
|
|
17
16
|
quoteWithSchema,
|
|
18
17
|
} from '../common';
|
|
19
18
|
|
|
@@ -105,13 +104,17 @@ export const getForeignKeyTable = (
|
|
|
105
104
|
return [item.schema, item.table];
|
|
106
105
|
};
|
|
107
106
|
|
|
107
|
+
export const getForeignKeyName = (table: string, columns: string[]) => {
|
|
108
|
+
return `${table}_${columns.join('_')}_fkey`;
|
|
109
|
+
};
|
|
110
|
+
|
|
108
111
|
export const constraintToSql = (
|
|
109
112
|
{ name }: { schema?: string; name: string },
|
|
110
113
|
up: boolean,
|
|
111
114
|
foreignKey: TableData['foreignKeys'][number],
|
|
112
115
|
) => {
|
|
113
116
|
const constraintName =
|
|
114
|
-
foreignKey.options.name ||
|
|
117
|
+
foreignKey.options.name || getForeignKeyName(name, foreignKey.columns);
|
|
115
118
|
|
|
116
119
|
if (!up) {
|
|
117
120
|
const { dropMode } = foreignKey.options;
|
|
@@ -156,21 +159,26 @@ export const referencesToSql = (
|
|
|
156
159
|
return sql.join(' ');
|
|
157
160
|
};
|
|
158
161
|
|
|
162
|
+
export const getIndexName = (
|
|
163
|
+
table: string,
|
|
164
|
+
columns: TableData.Index['columns'],
|
|
165
|
+
) => {
|
|
166
|
+
return `${table}_${columns
|
|
167
|
+
.map((it) =>
|
|
168
|
+
'column' in it
|
|
169
|
+
? it.column
|
|
170
|
+
: it.expression.match(/\w+/g)?.join('_') || 'expression',
|
|
171
|
+
)
|
|
172
|
+
.join('_')}_idx`;
|
|
173
|
+
};
|
|
174
|
+
|
|
159
175
|
export const indexesToQuery = (
|
|
160
176
|
up: boolean,
|
|
161
177
|
{ schema, name }: { schema?: string; name: string },
|
|
162
178
|
indexes: TableData.Index[],
|
|
163
179
|
): Sql[] => {
|
|
164
180
|
return indexes.map(({ columns, options }) => {
|
|
165
|
-
const indexName =
|
|
166
|
-
options.name ||
|
|
167
|
-
joinWords(
|
|
168
|
-
name,
|
|
169
|
-
...columns
|
|
170
|
-
.filter((it): it is { column: string } => 'column' in it)
|
|
171
|
-
.map((it) => it.column),
|
|
172
|
-
'index',
|
|
173
|
-
);
|
|
181
|
+
const indexName = options.name || getIndexName(name, columns);
|
|
174
182
|
|
|
175
183
|
if (!up) {
|
|
176
184
|
return {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from 'pqb';
|
|
15
15
|
import { structureToAst } from './structureToAst';
|
|
16
16
|
import { RakeDbAst } from '../ast';
|
|
17
|
+
import { getIndexName } from '../migration/migrationUtils';
|
|
17
18
|
|
|
18
19
|
const adapter = new Adapter({ databaseURL: 'file:path' });
|
|
19
20
|
const query = jest.fn().mockImplementation(() => ({ rows: [] }));
|
|
@@ -361,6 +362,23 @@ describe('structureToAst', () => {
|
|
|
361
362
|
expect(ast.indexes).toHaveLength(0);
|
|
362
363
|
});
|
|
363
364
|
|
|
365
|
+
it('should ignore standard index name', async () => {
|
|
366
|
+
const db = new DbStructure(adapter);
|
|
367
|
+
db.getTables = async () => [table];
|
|
368
|
+
db.getColumns = async () => columns;
|
|
369
|
+
db.getIndexes = async () => [
|
|
370
|
+
{ ...index, name: getIndexName(table.name, index.columns) },
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
|
|
374
|
+
expect(ast.shape.name.data.indexes).toEqual([
|
|
375
|
+
{
|
|
376
|
+
unique: false,
|
|
377
|
+
},
|
|
378
|
+
]);
|
|
379
|
+
expect(ast.indexes).toHaveLength(0);
|
|
380
|
+
});
|
|
381
|
+
|
|
364
382
|
it('should set index options to column index', async () => {
|
|
365
383
|
const db = new DbStructure(adapter);
|
|
366
384
|
db.getTables = async () => [table];
|
|
@@ -430,6 +448,30 @@ describe('structureToAst', () => {
|
|
|
430
448
|
]);
|
|
431
449
|
});
|
|
432
450
|
|
|
451
|
+
it('should ignore standard index name in composite index', async () => {
|
|
452
|
+
const db = new DbStructure(adapter);
|
|
453
|
+
db.getTables = async () => [table];
|
|
454
|
+
db.getColumns = async () => columns;
|
|
455
|
+
|
|
456
|
+
const indexColumns = [{ column: 'id' }, { column: 'name' }];
|
|
457
|
+
db.getIndexes = async () => [
|
|
458
|
+
{
|
|
459
|
+
...index,
|
|
460
|
+
columns: indexColumns,
|
|
461
|
+
name: getIndexName(table.name, indexColumns),
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
|
|
466
|
+
expect(ast.shape.name.data.indexes).toBe(undefined);
|
|
467
|
+
expect(ast.indexes).toEqual([
|
|
468
|
+
{
|
|
469
|
+
columns: indexColumns,
|
|
470
|
+
options: { unique: false },
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
});
|
|
474
|
+
|
|
433
475
|
it('should add index with expression and options to the table', async () => {
|
|
434
476
|
const db = new DbStructure(adapter);
|
|
435
477
|
db.getTables = async () => [table];
|
|
@@ -503,6 +545,31 @@ describe('structureToAst', () => {
|
|
|
503
545
|
expect(ast.foreignKeys).toHaveLength(0);
|
|
504
546
|
});
|
|
505
547
|
|
|
548
|
+
it('should ignore standard foreign key name', async () => {
|
|
549
|
+
const db = new DbStructure(adapter);
|
|
550
|
+
db.getTables = async () => [table];
|
|
551
|
+
db.getColumns = async () => [
|
|
552
|
+
...columns,
|
|
553
|
+
{ ...intColumn, name: 'otherId' },
|
|
554
|
+
];
|
|
555
|
+
db.getForeignKeys = async () => [
|
|
556
|
+
{ ...foreignKey, name: `${table.name}_otherId_fkey` },
|
|
557
|
+
];
|
|
558
|
+
|
|
559
|
+
const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
|
|
560
|
+
|
|
561
|
+
expect(ast.shape.otherId.data.foreignKeys).toEqual([
|
|
562
|
+
{
|
|
563
|
+
columns: ['id'],
|
|
564
|
+
table: 'otherTable',
|
|
565
|
+
match: 'FULL',
|
|
566
|
+
onUpdate: 'CASCADE',
|
|
567
|
+
onDelete: 'CASCADE',
|
|
568
|
+
},
|
|
569
|
+
]);
|
|
570
|
+
expect(ast.foreignKeys).toHaveLength(0);
|
|
571
|
+
});
|
|
572
|
+
|
|
506
573
|
it('should add composite foreign key', async () => {
|
|
507
574
|
const db = new DbStructure(adapter);
|
|
508
575
|
db.getTables = async () => [table];
|
|
@@ -535,6 +602,39 @@ describe('structureToAst', () => {
|
|
|
535
602
|
},
|
|
536
603
|
]);
|
|
537
604
|
});
|
|
605
|
+
|
|
606
|
+
it('should ignore standard foreign key name in a composite foreign key', async () => {
|
|
607
|
+
const db = new DbStructure(adapter);
|
|
608
|
+
db.getTables = async () => [table];
|
|
609
|
+
db.getColumns = async () => [
|
|
610
|
+
...columns,
|
|
611
|
+
{ ...intColumn, name: 'otherId' },
|
|
612
|
+
];
|
|
613
|
+
db.getForeignKeys = async () => [
|
|
614
|
+
{
|
|
615
|
+
...foreignKey,
|
|
616
|
+
columnNames: ['name', 'otherId'],
|
|
617
|
+
foreignColumnNames: ['name', 'id'],
|
|
618
|
+
name: 'table_name_otherId_fkey',
|
|
619
|
+
},
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
|
|
623
|
+
|
|
624
|
+
expect(ast.shape.otherId.data.foreignKeys).toBe(undefined);
|
|
625
|
+
expect(ast.foreignKeys).toEqual([
|
|
626
|
+
{
|
|
627
|
+
columns: ['name', 'otherId'],
|
|
628
|
+
fnOrTable: 'otherTable',
|
|
629
|
+
foreignColumns: ['name', 'id'],
|
|
630
|
+
options: {
|
|
631
|
+
match: 'FULL',
|
|
632
|
+
onUpdate: 'CASCADE',
|
|
633
|
+
onDelete: 'CASCADE',
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
]);
|
|
637
|
+
});
|
|
538
638
|
});
|
|
539
639
|
|
|
540
640
|
describe('extension', () => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
instantiateColumn,
|
|
8
8
|
singleQuote,
|
|
9
9
|
} from 'pqb';
|
|
10
|
+
import { getForeignKeyName, getIndexName } from '../migration/migrationUtils';
|
|
10
11
|
|
|
11
12
|
const matchMap = {
|
|
12
13
|
s: undefined,
|
|
@@ -98,7 +99,10 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
98
99
|
collate: options.collate,
|
|
99
100
|
opclass: options.opclass,
|
|
100
101
|
order: options.order,
|
|
101
|
-
name:
|
|
102
|
+
name:
|
|
103
|
+
index.name !== getIndexName(name, index.columns)
|
|
104
|
+
? index.name
|
|
105
|
+
: undefined,
|
|
102
106
|
using: index.using === 'btree' ? undefined : index.using,
|
|
103
107
|
unique: index.isUnique,
|
|
104
108
|
include: index.include,
|
|
@@ -116,7 +120,12 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
116
120
|
foreignKey.foreignTableName,
|
|
117
121
|
foreignKey.foreignColumnNames[0],
|
|
118
122
|
{
|
|
119
|
-
name:
|
|
123
|
+
name:
|
|
124
|
+
foreignKey.name &&
|
|
125
|
+
foreignKey.name !==
|
|
126
|
+
getForeignKeyName(name, foreignKey.columnNames)
|
|
127
|
+
? foreignKey.name
|
|
128
|
+
: undefined,
|
|
120
129
|
match: matchMap[foreignKey.match],
|
|
121
130
|
onUpdate: fkeyActionMap[foreignKey.onUpdate],
|
|
122
131
|
onDelete: fkeyActionMap[foreignKey.onDelete],
|
|
@@ -161,7 +170,10 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
161
170
|
order: it.order,
|
|
162
171
|
})),
|
|
163
172
|
options: {
|
|
164
|
-
name:
|
|
173
|
+
name:
|
|
174
|
+
index.name !== getIndexName(name, index.columns)
|
|
175
|
+
? index.name
|
|
176
|
+
: undefined,
|
|
165
177
|
using: index.using === 'btree' ? undefined : index.using,
|
|
166
178
|
unique: index.isUnique,
|
|
167
179
|
include: index.include,
|
|
@@ -177,7 +189,10 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
177
189
|
fnOrTable: it.foreignTableName,
|
|
178
190
|
foreignColumns: it.foreignColumnNames,
|
|
179
191
|
options: {
|
|
180
|
-
name:
|
|
192
|
+
name:
|
|
193
|
+
it.name && it.name !== getForeignKeyName(name, it.columnNames)
|
|
194
|
+
? it.name
|
|
195
|
+
: undefined,
|
|
181
196
|
match: matchMap[it.match],
|
|
182
197
|
onUpdate: fkeyActionMap[it.onUpdate],
|
|
183
198
|
onDelete: fkeyActionMap[it.onDelete],
|