rake-db 2.3.0 → 2.3.1
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 +8 -0
- package/db.ts +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.js +680 -146
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +679 -144
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/generate.ts +14 -6
- package/src/common.ts +14 -3
- package/src/pull/astToMigration.test.ts +115 -0
- package/src/pull/astToMigration.ts +65 -0
- package/src/pull/pull.test.ts +92 -0
- package/src/pull/pull.ts +18 -0
- package/src/pull/structureToAst.test.ts +65 -0
- package/src/pull/structureToAst.ts +40 -2
- package/src/rakeDb.test.ts +100 -0
- package/src/rakeDb.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rake-db",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Migrations tool for Postgresql DB",
|
|
5
5
|
"homepage": "https://orchid-orm.netlify.app/guide/migration-setup-and-overview.html",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"enquirer": "^2.3.6",
|
|
44
44
|
"pluralize": "^8.0.0",
|
|
45
|
-
"pqb": "0.9.
|
|
45
|
+
"pqb": "0.9.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@swc/core": "^1.2.210",
|
package/src/commands/generate.ts
CHANGED
|
@@ -7,20 +7,28 @@ import {
|
|
|
7
7
|
import { mkdir, writeFile } from 'fs/promises';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
|
|
10
|
-
export const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export const writeMigrationFile = async (
|
|
11
|
+
config: RakeDbConfig,
|
|
12
|
+
name: string,
|
|
13
|
+
content: string,
|
|
14
|
+
) => {
|
|
15
|
+
await mkdir(path.resolve(config.migrationsPath), { recursive: true });
|
|
15
16
|
|
|
16
17
|
const filePath = path.resolve(
|
|
17
18
|
config.migrationsPath,
|
|
18
19
|
`${makeFileTimeStamp()}_${name}.ts`,
|
|
19
20
|
);
|
|
20
|
-
await writeFile(filePath,
|
|
21
|
+
await writeFile(filePath, content);
|
|
21
22
|
console.log(`Created ${filePath}`);
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
export const generate = async (config: RakeDbConfig, args: string[]) => {
|
|
26
|
+
const name = args[0];
|
|
27
|
+
if (!name) throw new Error('Migration name is missing');
|
|
28
|
+
|
|
29
|
+
await writeMigrationFile(config, name, makeContent(name, args.slice(1)));
|
|
30
|
+
};
|
|
31
|
+
|
|
24
32
|
const makeFileTimeStamp = () => {
|
|
25
33
|
const now = new Date();
|
|
26
34
|
return [
|
package/src/common.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
AdapterOptions,
|
|
4
4
|
NoPrimaryKeyOption,
|
|
5
5
|
QueryLogOptions,
|
|
6
|
+
singleQuote,
|
|
6
7
|
} from 'pqb';
|
|
7
8
|
import Enquirer from 'enquirer';
|
|
8
9
|
import path from 'path';
|
|
@@ -25,7 +26,7 @@ export type AppCodeUpdater = (params: {
|
|
|
25
26
|
}) => Promise<void>;
|
|
26
27
|
|
|
27
28
|
export const migrationConfigDefaults = {
|
|
28
|
-
migrationsPath: path.resolve(
|
|
29
|
+
migrationsPath: path.resolve('src', 'migrations'),
|
|
29
30
|
migrationsTable: 'schemaMigrations',
|
|
30
31
|
requireTs: require,
|
|
31
32
|
log: true,
|
|
@@ -191,7 +192,7 @@ export const getMigrationFiles = async (
|
|
|
191
192
|
|
|
192
193
|
let files: string[];
|
|
193
194
|
try {
|
|
194
|
-
files = await readdir(migrationsPath);
|
|
195
|
+
files = await readdir(path.resolve(migrationsPath));
|
|
195
196
|
} catch (_) {
|
|
196
197
|
return [];
|
|
197
198
|
}
|
|
@@ -212,7 +213,7 @@ export const getMigrationFiles = async (
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
return {
|
|
215
|
-
path: path.
|
|
216
|
+
path: path.resolve(migrationsPath, file),
|
|
216
217
|
version: timestampMatch[1],
|
|
217
218
|
};
|
|
218
219
|
});
|
|
@@ -253,3 +254,13 @@ export const getSchemaAndTableFromName = (
|
|
|
253
254
|
? [name.slice(0, index), name.slice(index + 1)]
|
|
254
255
|
: [undefined, name];
|
|
255
256
|
};
|
|
257
|
+
|
|
258
|
+
export const quoteSchemaTable = ({
|
|
259
|
+
schema,
|
|
260
|
+
name,
|
|
261
|
+
}: {
|
|
262
|
+
schema?: string;
|
|
263
|
+
name: string;
|
|
264
|
+
}) => {
|
|
265
|
+
return singleQuote(schema ? `${schema}.${name}` : name);
|
|
266
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { astToMigration } from './astToMigration';
|
|
2
|
+
import { columnTypes } from 'pqb';
|
|
3
|
+
import { RakeDbAst } from '../ast';
|
|
4
|
+
|
|
5
|
+
const template = (content: string) => `import { change } from 'rake-db';
|
|
6
|
+
|
|
7
|
+
change(async (db) => {
|
|
8
|
+
${content}
|
|
9
|
+
});
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
const tableAst: RakeDbAst.Table = {
|
|
13
|
+
type: 'table',
|
|
14
|
+
action: 'create',
|
|
15
|
+
schema: 'schema',
|
|
16
|
+
name: 'table',
|
|
17
|
+
shape: {},
|
|
18
|
+
noPrimaryKey: 'ignore',
|
|
19
|
+
indexes: [],
|
|
20
|
+
foreignKeys: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('astToMigration', () => {
|
|
24
|
+
beforeEach(jest.clearAllMocks);
|
|
25
|
+
|
|
26
|
+
it('should return undefined when ast is empty', () => {
|
|
27
|
+
const result = astToMigration([]);
|
|
28
|
+
|
|
29
|
+
expect(result).toBe(undefined);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should create schema', () => {
|
|
33
|
+
const result = astToMigration([
|
|
34
|
+
{
|
|
35
|
+
type: 'schema',
|
|
36
|
+
action: 'create',
|
|
37
|
+
name: 'schemaName',
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
expect(result).toBe(template(` await db.createSchema('schemaName');`));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('table', () => {
|
|
45
|
+
it('should create table', () => {
|
|
46
|
+
const result = astToMigration([
|
|
47
|
+
{
|
|
48
|
+
...tableAst,
|
|
49
|
+
shape: {
|
|
50
|
+
id: columnTypes.serial().primaryKey(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
expect(result).toBe(
|
|
56
|
+
template(` await db.createTable('schema.table', (t) => ({
|
|
57
|
+
id: t.serial().primaryKey(),
|
|
58
|
+
}));`),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should add composite primaryKeys, indexes, foreignKeys', () => {
|
|
63
|
+
const result = astToMigration([
|
|
64
|
+
{
|
|
65
|
+
...tableAst,
|
|
66
|
+
shape: {
|
|
67
|
+
id: columnTypes.serial().primaryKey(),
|
|
68
|
+
},
|
|
69
|
+
primaryKey: { columns: ['id', 'name'], options: { name: 'pkey' } },
|
|
70
|
+
indexes: [
|
|
71
|
+
{
|
|
72
|
+
columns: [{ column: 'id' }, { column: 'name' }],
|
|
73
|
+
options: { name: 'index', unique: true },
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
foreignKeys: [
|
|
77
|
+
{
|
|
78
|
+
columns: ['id', 'name'],
|
|
79
|
+
fnOrTable: 'otherTable',
|
|
80
|
+
foreignColumns: ['otherId', 'otherName'],
|
|
81
|
+
options: {
|
|
82
|
+
name: 'fkey',
|
|
83
|
+
match: 'FULL',
|
|
84
|
+
onUpdate: 'CASCADE',
|
|
85
|
+
onDelete: 'CASCADE',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
expect(result).toBe(
|
|
93
|
+
template(` await db.createTable('schema.table', (t) => ({
|
|
94
|
+
id: t.serial().primaryKey(),
|
|
95
|
+
...t.primaryKey(['id', 'name'], { name: 'pkey' }),
|
|
96
|
+
...t.index(['id', 'name'], {
|
|
97
|
+
name: 'index',
|
|
98
|
+
unique: true,
|
|
99
|
+
}),
|
|
100
|
+
...t.foreignKey(
|
|
101
|
+
['id', 'name'],
|
|
102
|
+
'otherTable',
|
|
103
|
+
['otherId', 'otherName'],
|
|
104
|
+
{
|
|
105
|
+
name: 'fkey',
|
|
106
|
+
match: 'FULL',
|
|
107
|
+
onUpdate: 'CASCADE',
|
|
108
|
+
onDelete: 'CASCADE',
|
|
109
|
+
},
|
|
110
|
+
),
|
|
111
|
+
}));`),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { RakeDbAst } from '../ast';
|
|
2
|
+
import {
|
|
3
|
+
addCode,
|
|
4
|
+
Code,
|
|
5
|
+
codeToString,
|
|
6
|
+
foreignKeyToCode,
|
|
7
|
+
indexToCode,
|
|
8
|
+
primaryKeyToCode,
|
|
9
|
+
quoteObjectKey,
|
|
10
|
+
singleQuote,
|
|
11
|
+
} from 'pqb';
|
|
12
|
+
import { quoteSchemaTable } from '../common';
|
|
13
|
+
|
|
14
|
+
export const astToMigration = (ast: RakeDbAst[]): string | undefined => {
|
|
15
|
+
const code: Code[] = [];
|
|
16
|
+
for (const item of ast) {
|
|
17
|
+
if (item.type === 'schema' && item.action === 'create') {
|
|
18
|
+
code.push(createSchema(item));
|
|
19
|
+
} else if (item.type === 'table' && item.action === 'create') {
|
|
20
|
+
if (code.length) code.push([]);
|
|
21
|
+
code.push(...createTable(item));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!code.length) return;
|
|
26
|
+
|
|
27
|
+
return `import { change } from 'rake-db';
|
|
28
|
+
|
|
29
|
+
change(async (db) => {
|
|
30
|
+
${codeToString(code, ' ', ' ')}
|
|
31
|
+
});
|
|
32
|
+
`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const createSchema = (ast: RakeDbAst.Schema) => {
|
|
36
|
+
return `await db.createSchema(${singleQuote(ast.name)});`;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const createTable = (ast: RakeDbAst.Table) => {
|
|
40
|
+
const code: Code[] = [];
|
|
41
|
+
addCode(code, `await db.createTable(${quoteSchemaTable(ast)}, (t) => ({`);
|
|
42
|
+
|
|
43
|
+
for (const key in ast.shape) {
|
|
44
|
+
const line: Code[] = [`${quoteObjectKey(key)}: `];
|
|
45
|
+
addCode(line, ast.shape[key].toCode('t'));
|
|
46
|
+
addCode(line, ',');
|
|
47
|
+
code.push(line);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (ast.primaryKey) {
|
|
51
|
+
code.push([primaryKeyToCode(ast.primaryKey, 't')]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const index of ast.indexes) {
|
|
55
|
+
code.push(indexToCode(index, 't'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const foreignKey of ast.foreignKeys) {
|
|
59
|
+
code.push(foreignKeyToCode(foreignKey, 't'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
addCode(code, '}));');
|
|
63
|
+
|
|
64
|
+
return code;
|
|
65
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DbStructure } from './dbStructure';
|
|
2
|
+
import { pullDbStructure } from './pull';
|
|
3
|
+
import { getMigrationConfigWithDefaults } from '../common';
|
|
4
|
+
import { writeMigrationFile } from '../commands/generate';
|
|
5
|
+
|
|
6
|
+
jest.mock('./dbStructure', () => {
|
|
7
|
+
const { DbStructure } = jest.requireActual('./dbStructure');
|
|
8
|
+
for (const key of Object.getOwnPropertyNames(DbStructure.prototype)) {
|
|
9
|
+
(DbStructure.prototype as unknown as Record<string, () => unknown[]>)[key] =
|
|
10
|
+
() => [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return { DbStructure };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
jest.mock('../commands/generate', () => ({
|
|
17
|
+
writeMigrationFile: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const db = DbStructure.prototype;
|
|
21
|
+
|
|
22
|
+
describe('pull', () => {
|
|
23
|
+
it('should get db structure, convert it to ast, generate migrations', async () => {
|
|
24
|
+
db.getSchemas = async () => ['schema1', 'schema2'];
|
|
25
|
+
db.getTables = async () => [
|
|
26
|
+
{
|
|
27
|
+
schemaName: 'schema',
|
|
28
|
+
name: 'table1',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
schemaName: 'public',
|
|
32
|
+
name: 'table2',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
db.getPrimaryKeys = async () => [
|
|
36
|
+
{
|
|
37
|
+
schemaName: 'schema',
|
|
38
|
+
tableName: 'table1',
|
|
39
|
+
name: 'table1_pkey',
|
|
40
|
+
columnNames: ['id'],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
db.getColumns = async () => [
|
|
44
|
+
{
|
|
45
|
+
schemaName: 'schema',
|
|
46
|
+
tableName: 'table1',
|
|
47
|
+
name: 'id',
|
|
48
|
+
type: 'int4',
|
|
49
|
+
default: `nextval('table1_id_seq'::regclass)`,
|
|
50
|
+
isNullable: false,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
schemaName: 'public',
|
|
54
|
+
tableName: 'table2',
|
|
55
|
+
name: 'text',
|
|
56
|
+
type: 'text',
|
|
57
|
+
isNullable: false,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const config = getMigrationConfigWithDefaults({
|
|
62
|
+
migrationsPath: 'migrations',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await pullDbStructure(
|
|
66
|
+
{
|
|
67
|
+
databaseURL: 'file:path',
|
|
68
|
+
},
|
|
69
|
+
config,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(writeMigrationFile).toBeCalledWith(
|
|
73
|
+
config,
|
|
74
|
+
'pull',
|
|
75
|
+
`import { change } from 'rake-db';
|
|
76
|
+
|
|
77
|
+
change(async (db) => {
|
|
78
|
+
await db.createSchema('schema1');
|
|
79
|
+
await db.createSchema('schema2');
|
|
80
|
+
|
|
81
|
+
await db.createTable('schema.table1', (t) => ({
|
|
82
|
+
id: t.serial().primaryKey(),
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
await db.createTable('table2', (t) => ({
|
|
86
|
+
text: t.text(),
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
`,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/pull/pull.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RakeDbConfig } from '../common';
|
|
2
|
+
import { Adapter, AdapterOptions } from 'pqb';
|
|
3
|
+
import { DbStructure } from './dbStructure';
|
|
4
|
+
import { structureToAst } from './structureToAst';
|
|
5
|
+
import { astToMigration } from './astToMigration';
|
|
6
|
+
import { writeMigrationFile } from '../commands/generate';
|
|
7
|
+
|
|
8
|
+
export const pullDbStructure = async (
|
|
9
|
+
options: AdapterOptions,
|
|
10
|
+
config: RakeDbConfig,
|
|
11
|
+
) => {
|
|
12
|
+
const db = new DbStructure(new Adapter(options));
|
|
13
|
+
const ast = await structureToAst(db);
|
|
14
|
+
const result = astToMigration(ast);
|
|
15
|
+
if (!result) return;
|
|
16
|
+
|
|
17
|
+
await writeMigrationFile(config, 'pull', result);
|
|
18
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { DbStructure } from './dbStructure';
|
|
2
2
|
import {
|
|
3
3
|
Adapter,
|
|
4
|
+
BigSerialColumn,
|
|
4
5
|
DecimalColumn,
|
|
5
6
|
IntegerColumn,
|
|
7
|
+
SerialColumn,
|
|
8
|
+
SmallSerialColumn,
|
|
6
9
|
TextColumn,
|
|
7
10
|
TimestampColumn,
|
|
8
11
|
VarCharColumn,
|
|
@@ -164,6 +167,68 @@ describe('structureToAst', () => {
|
|
|
164
167
|
expect(ast.shape.name).toBeInstanceOf(TextColumn);
|
|
165
168
|
});
|
|
166
169
|
|
|
170
|
+
describe('serial column', () => {
|
|
171
|
+
it('should add serial column based on various default values', async () => {
|
|
172
|
+
const db = new DbStructure(adapter);
|
|
173
|
+
db.getTables = async () => [{ schemaName: 'schema', name: 'table' }];
|
|
174
|
+
|
|
175
|
+
const defaults = [
|
|
176
|
+
`nextval('table_id_seq'::regclass)`,
|
|
177
|
+
`nextval('"table_id_seq"'::regclass)`,
|
|
178
|
+
`nextval('schema.table_id_seq'::regclass)`,
|
|
179
|
+
`nextval('schema."table_id_seq"'::regclass)`,
|
|
180
|
+
`nextval('"schema".table_id_seq'::regclass)`,
|
|
181
|
+
`nextval('"schema"."table_id_seq"'::regclass)`,
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
for (const def of defaults) {
|
|
185
|
+
db.getColumns = async () => [
|
|
186
|
+
{
|
|
187
|
+
...intColumn,
|
|
188
|
+
name: 'id',
|
|
189
|
+
schemaName: 'schema',
|
|
190
|
+
tableName: 'table',
|
|
191
|
+
default: def,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
|
|
196
|
+
|
|
197
|
+
expect(ast.shape.id).toBeInstanceOf(SerialColumn);
|
|
198
|
+
expect(ast.shape.id.data.default).toBe(undefined);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should support smallserial, serial, and bigserial', async () => {
|
|
203
|
+
const db = new DbStructure(adapter);
|
|
204
|
+
db.getTables = async () => [{ schemaName: 'schema', name: 'table' }];
|
|
205
|
+
|
|
206
|
+
const types = [
|
|
207
|
+
['int2', SmallSerialColumn],
|
|
208
|
+
['int4', SerialColumn],
|
|
209
|
+
['int8', BigSerialColumn],
|
|
210
|
+
] as const;
|
|
211
|
+
|
|
212
|
+
for (const [type, Column] of types) {
|
|
213
|
+
db.getColumns = async () => [
|
|
214
|
+
{
|
|
215
|
+
...intColumn,
|
|
216
|
+
type,
|
|
217
|
+
name: 'id',
|
|
218
|
+
schemaName: 'schema',
|
|
219
|
+
tableName: 'table',
|
|
220
|
+
default: `nextval('table_id_seq'::regclass)`,
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
|
|
225
|
+
|
|
226
|
+
expect(ast.shape.id).toBeInstanceOf(Column);
|
|
227
|
+
expect(ast.shape.id.data.default).toBe(undefined);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
167
232
|
it('should set maxChars to char column', async () => {
|
|
168
233
|
const db = new DbStructure(adapter);
|
|
169
234
|
db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ColumnsShape,
|
|
6
6
|
ForeignKeyOptions,
|
|
7
7
|
instantiateColumn,
|
|
8
|
+
singleQuote,
|
|
8
9
|
} from 'pqb';
|
|
9
10
|
|
|
10
11
|
const matchMap = {
|
|
@@ -63,11 +64,16 @@ export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
|
|
|
63
64
|
const tableForeignKeys = allForeignKeys.filter(belongsToTable);
|
|
64
65
|
|
|
65
66
|
const shape: ColumnsShape = {};
|
|
66
|
-
for (
|
|
67
|
-
const
|
|
67
|
+
for (let item of columns) {
|
|
68
|
+
const isSerial = getIsSerial(item);
|
|
69
|
+
const klass = columnsByType[getColumnType(item, isSerial)];
|
|
68
70
|
if (!klass) {
|
|
69
71
|
throw new Error(`Column type \`${item.type}\` is not supported`);
|
|
70
72
|
}
|
|
73
|
+
if (isSerial) {
|
|
74
|
+
item = { ...item, default: undefined };
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
let column = instantiateColumn(klass, item);
|
|
72
78
|
|
|
73
79
|
if (
|
|
@@ -194,3 +200,35 @@ const makeBelongsToTable =
|
|
|
194
200
|
(schema: string | undefined, table: string) =>
|
|
195
201
|
(item: { schemaName: string; tableName: string }) =>
|
|
196
202
|
item.schemaName === schema && item.tableName === table;
|
|
203
|
+
|
|
204
|
+
const getIsSerial = (item: DbStructure.Column) => {
|
|
205
|
+
if (item.type === 'int2' || item.type === 'int4' || item.type === 'int8') {
|
|
206
|
+
const { default: def, schemaName, tableName, name } = item;
|
|
207
|
+
const seq = `${tableName}_${name}_seq`;
|
|
208
|
+
if (
|
|
209
|
+
def &&
|
|
210
|
+
(def === `nextval(${singleQuote(`${seq}`)}::regclass)` ||
|
|
211
|
+
def === `nextval(${singleQuote(`"${seq}"`)}::regclass)` ||
|
|
212
|
+
def === `nextval(${singleQuote(`${schemaName}.${seq}`)}::regclass)` ||
|
|
213
|
+
def === `nextval(${singleQuote(`"${schemaName}".${seq}`)}::regclass)` ||
|
|
214
|
+
def === `nextval(${singleQuote(`${schemaName}."${seq}"`)}::regclass)` ||
|
|
215
|
+
def === `nextval(${singleQuote(`"${schemaName}"."${seq}"`)}::regclass)`)
|
|
216
|
+
) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return false;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const getColumnType = (item: DbStructure.Column, isSerial: boolean) => {
|
|
225
|
+
if (isSerial) {
|
|
226
|
+
return item.type === 'int2'
|
|
227
|
+
? 'smallserial'
|
|
228
|
+
: item.type === 'int4'
|
|
229
|
+
? 'serial'
|
|
230
|
+
: 'bigserial';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return item.type;
|
|
234
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { rakeDb } from './rakeDb';
|
|
2
|
+
import { createDb, dropDb, resetDb } from './commands/createOrDrop';
|
|
3
|
+
import { migrate, rollback } from './commands/migrateOrRollback';
|
|
4
|
+
import { generate } from './commands/generate';
|
|
5
|
+
import { pullDbStructure } from './pull/pull';
|
|
6
|
+
|
|
7
|
+
jest.mock('./common', () => ({
|
|
8
|
+
getMigrationConfigWithDefaults: (config: unknown) => config,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock('./commands/createOrDrop', () => ({
|
|
12
|
+
createDb: jest.fn(),
|
|
13
|
+
dropDb: jest.fn(),
|
|
14
|
+
resetDb: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock('./commands/migrateOrRollback', () => ({
|
|
18
|
+
migrate: jest.fn(),
|
|
19
|
+
rollback: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('./commands/generate', () => ({
|
|
23
|
+
generate: jest.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
jest.mock('./pull/pull', () => ({
|
|
27
|
+
pullDbStructure: jest.fn(),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const options = [
|
|
31
|
+
{
|
|
32
|
+
databaseURL: 'one',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
databaseURL: 'two',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const config = {
|
|
40
|
+
migrationsPath: 'migrations',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('rakeDb', () => {
|
|
44
|
+
test('create', async () => {
|
|
45
|
+
await rakeDb(options, config, ['create']);
|
|
46
|
+
|
|
47
|
+
expect(createDb).toBeCalledWith(options, config);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('drop', async () => {
|
|
51
|
+
await rakeDb(options, config, ['drop']);
|
|
52
|
+
|
|
53
|
+
expect(dropDb).toBeCalledWith(options);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('reset', async () => {
|
|
57
|
+
await rakeDb(options, config, ['reset']);
|
|
58
|
+
|
|
59
|
+
expect(resetDb).toBeCalledWith(options, config);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('migrate', async () => {
|
|
63
|
+
await rakeDb(options, config, ['migrate', 'arg']);
|
|
64
|
+
|
|
65
|
+
expect(migrate).toBeCalledWith(options, config, ['arg']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('rollback', async () => {
|
|
69
|
+
await rakeDb(options, config, ['rollback', 'arg']);
|
|
70
|
+
|
|
71
|
+
expect(rollback).toBeCalledWith(options, config, ['arg']);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('generate', async () => {
|
|
75
|
+
await rakeDb(options, config, ['g', 'arg']);
|
|
76
|
+
|
|
77
|
+
expect(generate).toBeCalledWith(config, ['arg']);
|
|
78
|
+
|
|
79
|
+
jest.clearAllMocks();
|
|
80
|
+
|
|
81
|
+
await rakeDb(options, config, ['generate', 'arg']);
|
|
82
|
+
|
|
83
|
+
expect(generate).toBeCalledWith(config, ['arg']);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('pull', async () => {
|
|
87
|
+
await rakeDb(options, config, ['pull']);
|
|
88
|
+
|
|
89
|
+
expect(pullDbStructure).toBeCalledWith(options[0], config);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('other', async () => {
|
|
93
|
+
const log = jest.fn();
|
|
94
|
+
console.log = log;
|
|
95
|
+
|
|
96
|
+
await rakeDb(options, config, ['other']);
|
|
97
|
+
|
|
98
|
+
expect(log).toBeCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
package/src/rakeDb.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { AdapterOptions, MaybeArray } from 'pqb';
|
|
1
|
+
import { AdapterOptions, MaybeArray, toArray } from 'pqb';
|
|
2
2
|
import { createDb, dropDb, resetDb } from './commands/createOrDrop';
|
|
3
3
|
import { migrate, rollback } from './commands/migrateOrRollback';
|
|
4
4
|
import { getMigrationConfigWithDefaults, RakeDbConfig } from './common';
|
|
5
5
|
import { generate } from './commands/generate';
|
|
6
|
+
import { pullDbStructure } from './pull/pull';
|
|
6
7
|
|
|
7
8
|
export const rakeDb = async (
|
|
8
9
|
options: MaybeArray<AdapterOptions>,
|
|
@@ -25,6 +26,8 @@ export const rakeDb = async (
|
|
|
25
26
|
await rollback(options, config, args.slice(1));
|
|
26
27
|
} else if (command === 'g' || command === 'generate') {
|
|
27
28
|
await generate(config, args.slice(1));
|
|
29
|
+
} else if (command === 'pull') {
|
|
30
|
+
await pullDbStructure(toArray(options)[0], config);
|
|
28
31
|
} else {
|
|
29
32
|
printHelp();
|
|
30
33
|
}
|