@strapi/database 4.0.0-next.9 → 4.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/lib/dialects/dialect.js +45 -0
- package/lib/dialects/index.js +6 -112
- package/lib/dialects/mysql/index.js +51 -0
- package/lib/dialects/mysql/schema-inspector.js +199 -0
- package/lib/dialects/postgresql/index.js +49 -0
- package/lib/dialects/postgresql/schema-inspector.js +232 -0
- package/lib/dialects/sqlite/index.js +74 -0
- package/lib/dialects/sqlite/schema-inspector.js +151 -0
- package/lib/entity-manager.js +18 -14
- package/lib/entity-repository.js +2 -3
- package/lib/errors.js +44 -2
- package/lib/fields.d.ts +2 -3
- package/lib/fields.js +7 -16
- package/lib/index.d.ts +53 -8
- package/lib/index.js +44 -27
- package/lib/lifecycles/index.d.ts +50 -0
- package/lib/{lifecycles.js → lifecycles/index.js} +25 -14
- package/lib/lifecycles/subscribers/index.d.ts +9 -0
- package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
- package/lib/lifecycles/subscribers/timestamps.js +65 -0
- package/lib/metadata/index.js +84 -95
- package/lib/metadata/relations.js +16 -0
- package/lib/migrations/index.d.ts +9 -0
- package/lib/migrations/index.js +69 -0
- package/lib/migrations/storage.js +51 -0
- package/lib/query/helpers/join.js +3 -5
- package/lib/query/helpers/order-by.js +21 -11
- package/lib/query/helpers/populate.js +35 -10
- package/lib/query/helpers/search.js +26 -12
- package/lib/query/helpers/transform.js +42 -14
- package/lib/query/helpers/where.js +92 -57
- package/lib/query/query-builder.js +116 -34
- package/lib/schema/__tests__/schema-diff.test.js +14 -1
- package/lib/schema/builder.js +315 -284
- package/lib/schema/diff.js +376 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +47 -50
- package/lib/schema/schema.js +21 -18
- package/lib/schema/storage.js +79 -0
- package/lib/utils/content-types.js +0 -1
- package/package.json +27 -21
- package/lib/configuration.js +0 -49
- package/lib/schema/schema-diff.js +0 -337
- package/lib/schema/schema-storage.js +0 -44
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class Dialect {
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
configure() {}
|
|
9
|
+
initialize() {}
|
|
10
|
+
|
|
11
|
+
getSqlType(type) {
|
|
12
|
+
return type;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
canAlterConstraints() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
usesForeignKeys() {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
useReturning() {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
supportsUnsigned() {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async startSchemaUpdate() {}
|
|
32
|
+
async endSchemaUpdate() {}
|
|
33
|
+
|
|
34
|
+
transformErrors(error) {
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error(error.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
Dialect,
|
|
45
|
+
};
|
package/lib/dialects/index.js
CHANGED
|
@@ -1,120 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const fse = require('fs-extra');
|
|
5
|
-
|
|
6
|
-
const errors = require('../errors');
|
|
7
|
-
|
|
8
|
-
class Dialect {
|
|
9
|
-
constructor(db) {
|
|
10
|
-
this.db = db;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
usesForeignKeys() {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
useReturning() {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// TODO: pass query info to display some more metadata
|
|
22
|
-
transformErrors(error) {
|
|
23
|
-
if (error instanceof Error) {
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
throw new Error(error.message);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
class PostgresDialect extends Dialect {
|
|
31
|
-
useReturning() {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
initialize() {
|
|
36
|
-
// FIXME:
|
|
37
|
-
// this.db.connection.context.client.types.setTypeParser(1700, 'text', parseFloat);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
usesForeignKeys() {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
transformErrors(error) {
|
|
45
|
-
switch (error.code) {
|
|
46
|
-
case '23502': {
|
|
47
|
-
throw new errors.NotNullConstraint({ column: error.column });
|
|
48
|
-
}
|
|
49
|
-
default: {
|
|
50
|
-
super.transformErrors(error);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
class MysqlDialect extends Dialect {
|
|
57
|
-
initialize() {
|
|
58
|
-
this.db.config.connection.connection.supportBigNumbers = true;
|
|
59
|
-
this.db.config.connection.connection.bigNumberStrings = true;
|
|
60
|
-
this.db.config.connection.connection.typeCast = (field, next) => {
|
|
61
|
-
if (field.type == 'DECIMAL' || field.type === 'NEWDECIMAL') {
|
|
62
|
-
var value = field.string();
|
|
63
|
-
return value === null ? null : Number(value);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (field.type == 'TINY' && field.length == 1) {
|
|
67
|
-
let value = field.string();
|
|
68
|
-
return value ? value == '1' : null;
|
|
69
|
-
}
|
|
70
|
-
return next();
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
usesForeignKeys() {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
transformErrors(error) {
|
|
79
|
-
super.transformErrors(error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
class SqliteDialect extends Dialect {
|
|
84
|
-
async initialize() {
|
|
85
|
-
// Create the directory if it does not exist.
|
|
86
|
-
|
|
87
|
-
// TODO: get strapi.dir from somewhere else
|
|
88
|
-
|
|
89
|
-
this.db.config.connection.connection.filename = path.resolve(
|
|
90
|
-
this.db.config.connection.connection.filename
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const dbDir = path.dirname(this.db.config.connection.connection.filename);
|
|
94
|
-
|
|
95
|
-
await fse.ensureDir(dbDir);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
transformErrors(error) {
|
|
99
|
-
switch (error.errno) {
|
|
100
|
-
case 19: {
|
|
101
|
-
throw new errors.NotNullConstraint(); // TODO: extract column name
|
|
102
|
-
}
|
|
103
|
-
default: {
|
|
104
|
-
super.transformErrors(error);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const createDialect = (db, client) => {
|
|
3
|
+
const getDialectClass = client => {
|
|
111
4
|
switch (client) {
|
|
112
5
|
case 'postgres':
|
|
113
|
-
return
|
|
6
|
+
return require('./postgresql');
|
|
114
7
|
case 'mysql':
|
|
115
|
-
return
|
|
8
|
+
return require('./mysql');
|
|
116
9
|
case 'sqlite':
|
|
117
|
-
return
|
|
10
|
+
return require('./sqlite');
|
|
118
11
|
default:
|
|
119
12
|
throw new Error(`Unknow dialect ${client}`);
|
|
120
13
|
}
|
|
@@ -123,7 +16,8 @@ const createDialect = (db, client) => {
|
|
|
123
16
|
const getDialect = db => {
|
|
124
17
|
const { client } = db.config.connection;
|
|
125
18
|
|
|
126
|
-
const
|
|
19
|
+
const constructor = getDialectClass(client);
|
|
20
|
+
const dialect = new constructor(db);
|
|
127
21
|
dialect.client = client;
|
|
128
22
|
|
|
129
23
|
return dialect;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Dialect } = require('../dialect');
|
|
4
|
+
const MysqlSchemaInspector = require('./schema-inspector');
|
|
5
|
+
|
|
6
|
+
class MysqlDialect extends Dialect {
|
|
7
|
+
constructor(db) {
|
|
8
|
+
super(db);
|
|
9
|
+
|
|
10
|
+
this.schemaInspector = new MysqlSchemaInspector(db);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
configure() {
|
|
14
|
+
this.db.config.connection.connection.supportBigNumbers = true;
|
|
15
|
+
this.db.config.connection.connection.bigNumberStrings = true;
|
|
16
|
+
this.db.config.connection.connection.typeCast = (field, next) => {
|
|
17
|
+
if (field.type == 'DECIMAL' || field.type === 'NEWDECIMAL') {
|
|
18
|
+
var value = field.string();
|
|
19
|
+
return value === null ? null : Number(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (field.type == 'TINY' && field.length == 1) {
|
|
23
|
+
let value = field.string();
|
|
24
|
+
return value ? value == '1' : null;
|
|
25
|
+
}
|
|
26
|
+
return next();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async startSchemaUpdate() {
|
|
31
|
+
await this.db.connection.raw(`set foreign_key_checks = 0;`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async endSchemaUpdate() {
|
|
35
|
+
await this.db.connection.raw(`set foreign_key_checks = 1;`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
supportsUnsigned() {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
usesForeignKeys() {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
transformErrors(error) {
|
|
47
|
+
super.transformErrors(error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = MysqlDialect;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SQL_QUERIES = {
|
|
4
|
+
TABLE_LIST: /* sql */ `
|
|
5
|
+
SELECT
|
|
6
|
+
t.table_name as table_name
|
|
7
|
+
FROM information_schema.tables t
|
|
8
|
+
WHERE table_type = 'BASE TABLE'
|
|
9
|
+
AND table_schema = schema();
|
|
10
|
+
`,
|
|
11
|
+
LIST_COLUMNS: /* sql */ `
|
|
12
|
+
SELECT
|
|
13
|
+
c.data_type as data_type,
|
|
14
|
+
c.column_name as column_name,
|
|
15
|
+
c.character_maximum_length as character_maximum_length,
|
|
16
|
+
c.column_default as column_default,
|
|
17
|
+
c.is_nullable as is_nullable,
|
|
18
|
+
c.column_type as column_type,
|
|
19
|
+
c.column_key as column_key
|
|
20
|
+
FROM information_schema.columns c
|
|
21
|
+
WHERE table_schema = database()
|
|
22
|
+
AND table_name = ?;
|
|
23
|
+
`,
|
|
24
|
+
INDEX_LIST: /* sql */ `
|
|
25
|
+
show index from ??;
|
|
26
|
+
`,
|
|
27
|
+
FOREIGN_KEY_LIST: /* sql */ `
|
|
28
|
+
SELECT
|
|
29
|
+
tc.constraint_name as constraint_name,
|
|
30
|
+
kcu.column_name as column_name,
|
|
31
|
+
kcu.referenced_table_name as referenced_table_name,
|
|
32
|
+
kcu.referenced_column_name as referenced_column_name,
|
|
33
|
+
rc.update_rule as on_update,
|
|
34
|
+
rc.delete_rule as on_delete
|
|
35
|
+
FROM information_schema.table_constraints tc
|
|
36
|
+
INNER JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
|
|
37
|
+
INNER JOIN information_schema.referential_constraints AS rc ON kcu.constraint_name = rc.constraint_name
|
|
38
|
+
WHERE constraint_type = 'FOREIGN KEY'
|
|
39
|
+
AND tc.table_schema = database()
|
|
40
|
+
AND tc.table_name = ?;
|
|
41
|
+
|
|
42
|
+
`,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const toStrapiType = column => {
|
|
46
|
+
const rootType = column.data_type.toLowerCase().match(/[^(), ]+/)[0];
|
|
47
|
+
|
|
48
|
+
switch (rootType) {
|
|
49
|
+
case 'int': {
|
|
50
|
+
if (column.column_key === 'PRI') {
|
|
51
|
+
return { type: 'increments', args: [{ primary: true }], unsigned: false };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { type: 'integer' };
|
|
55
|
+
}
|
|
56
|
+
case 'decimal': {
|
|
57
|
+
return { type: 'decimal', args: [10, 2] };
|
|
58
|
+
}
|
|
59
|
+
case 'double': {
|
|
60
|
+
return { type: 'double' };
|
|
61
|
+
}
|
|
62
|
+
case 'bigint': {
|
|
63
|
+
return { type: 'bigInteger' };
|
|
64
|
+
}
|
|
65
|
+
case 'enum': {
|
|
66
|
+
return { type: 'enum' };
|
|
67
|
+
}
|
|
68
|
+
case 'tinyint': {
|
|
69
|
+
return { type: 'boolean' };
|
|
70
|
+
}
|
|
71
|
+
case 'longtext': {
|
|
72
|
+
return { type: 'text', args: ['longtext'] };
|
|
73
|
+
}
|
|
74
|
+
case 'varchar': {
|
|
75
|
+
return { type: 'string', args: [column.character_maximum_length] };
|
|
76
|
+
}
|
|
77
|
+
case 'datetime': {
|
|
78
|
+
return { type: 'datetime', args: [{ useTz: false, precision: 6 }] };
|
|
79
|
+
}
|
|
80
|
+
case 'date': {
|
|
81
|
+
return { type: 'date' };
|
|
82
|
+
}
|
|
83
|
+
case 'time': {
|
|
84
|
+
return { type: 'time', args: [{ precision: 3 }] };
|
|
85
|
+
}
|
|
86
|
+
case 'timestamp': {
|
|
87
|
+
return { type: 'timestamp', args: [{ useTz: false, precision: 6 }] };
|
|
88
|
+
}
|
|
89
|
+
case 'json': {
|
|
90
|
+
return { type: 'jsonb' };
|
|
91
|
+
}
|
|
92
|
+
default: {
|
|
93
|
+
return { type: 'specificType', args: [column.data_type] };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
class MysqlSchemaInspector {
|
|
99
|
+
constructor(db) {
|
|
100
|
+
this.db = db;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getSchema() {
|
|
104
|
+
const schema = { tables: [] };
|
|
105
|
+
|
|
106
|
+
const tables = await this.getTables();
|
|
107
|
+
|
|
108
|
+
schema.tables = await Promise.all(
|
|
109
|
+
tables.map(async tableName => {
|
|
110
|
+
const columns = await this.getColumns(tableName);
|
|
111
|
+
const indexes = await this.getIndexes(tableName);
|
|
112
|
+
const foreignKeys = await this.getForeignKeys(tableName);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
name: tableName,
|
|
116
|
+
columns,
|
|
117
|
+
indexes,
|
|
118
|
+
foreignKeys,
|
|
119
|
+
};
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return schema;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getTables() {
|
|
127
|
+
const [rows] = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);
|
|
128
|
+
|
|
129
|
+
return rows.map(row => row.table_name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getColumns(tableName) {
|
|
133
|
+
const [rows] = await this.db.connection.raw(SQL_QUERIES.LIST_COLUMNS, [tableName]);
|
|
134
|
+
|
|
135
|
+
return rows.map(row => {
|
|
136
|
+
const { type, args = [], ...rest } = toStrapiType(row);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
type,
|
|
140
|
+
args,
|
|
141
|
+
defaultTo: row.column_default,
|
|
142
|
+
name: row.column_name,
|
|
143
|
+
notNullable: row.is_nullable === 'NO',
|
|
144
|
+
unsigned: row.column_type.endsWith(' unsigned'),
|
|
145
|
+
...rest,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async getIndexes(tableName) {
|
|
151
|
+
const [rows] = await this.db.connection.raw(SQL_QUERIES.INDEX_LIST, [tableName]);
|
|
152
|
+
|
|
153
|
+
const ret = {};
|
|
154
|
+
|
|
155
|
+
for (const index of rows) {
|
|
156
|
+
if (index.Column_name === 'id') {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!ret[index.Key_name]) {
|
|
161
|
+
ret[index.Key_name] = {
|
|
162
|
+
columns: [index.Column_name],
|
|
163
|
+
name: index.Key_name,
|
|
164
|
+
type: !index.Non_unique ? 'unique' : null,
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
ret[index.Key_name].columns.push(index.Column_name);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return Object.values(ret);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async getForeignKeys(tableName) {
|
|
175
|
+
const [rows] = await this.db.connection.raw(SQL_QUERIES.FOREIGN_KEY_LIST, [tableName]);
|
|
176
|
+
|
|
177
|
+
const ret = {};
|
|
178
|
+
|
|
179
|
+
for (const fk of rows) {
|
|
180
|
+
if (!ret[fk.constraint_name]) {
|
|
181
|
+
ret[fk.constraint_name] = {
|
|
182
|
+
name: fk.constraint_name,
|
|
183
|
+
columns: [fk.column_name],
|
|
184
|
+
referencedColumns: [fk.referenced_column_name],
|
|
185
|
+
referencedTable: fk.referenced_table_name,
|
|
186
|
+
onUpdate: fk.on_update.toUpperCase(),
|
|
187
|
+
onDelete: fk.on_delete.toUpperCase(),
|
|
188
|
+
};
|
|
189
|
+
} else {
|
|
190
|
+
ret[fk.constraint_name].columns.push(fk.column_name);
|
|
191
|
+
ret[fk.constraint_name].referencedColumns.push(fk.referenced_column_name);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Object.values(ret);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = MysqlSchemaInspector;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const errors = require('../../errors');
|
|
4
|
+
const { Dialect } = require('../dialect');
|
|
5
|
+
const PostgresqlSchemaInspector = require('./schema-inspector');
|
|
6
|
+
|
|
7
|
+
class PostgresDialect extends Dialect {
|
|
8
|
+
constructor(db) {
|
|
9
|
+
super(db);
|
|
10
|
+
|
|
11
|
+
this.schemaInspector = new PostgresqlSchemaInspector(db);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
useReturning() {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initialize() {
|
|
19
|
+
this.db.connection.client.driver.types.setTypeParser(1700, 'text', parseFloat);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
usesForeignKeys() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getSqlType(type) {
|
|
27
|
+
switch (type) {
|
|
28
|
+
case 'timestamp': {
|
|
29
|
+
return 'datetime';
|
|
30
|
+
}
|
|
31
|
+
default: {
|
|
32
|
+
return type;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
transformErrors(error) {
|
|
38
|
+
switch (error.code) {
|
|
39
|
+
case '23502': {
|
|
40
|
+
throw new errors.NotNullConstraint({ column: error.column });
|
|
41
|
+
}
|
|
42
|
+
default: {
|
|
43
|
+
super.transformErrors(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = PostgresDialect;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SQL_QUERIES = {
|
|
4
|
+
TABLE_LIST: /* sql */ `
|
|
5
|
+
SELECT *
|
|
6
|
+
FROM information_schema.tables
|
|
7
|
+
WHERE
|
|
8
|
+
table_schema = ?
|
|
9
|
+
AND table_type = 'BASE TABLE'
|
|
10
|
+
AND table_name != 'geometry_columns'
|
|
11
|
+
AND table_name != 'spatial_ref_sys';
|
|
12
|
+
`,
|
|
13
|
+
LIST_COLUMNS: /* sql */ `
|
|
14
|
+
SELECT data_type, column_name, character_maximum_length, column_default, is_nullable
|
|
15
|
+
FROM information_schema.columns
|
|
16
|
+
WHERE table_schema = ? AND table_name = ?;
|
|
17
|
+
`,
|
|
18
|
+
INDEX_LIST: /* sql */ `
|
|
19
|
+
SELECT
|
|
20
|
+
ix.indexrelid,
|
|
21
|
+
i.relname as index_name,
|
|
22
|
+
a.attname as column_name,
|
|
23
|
+
ix.indisunique as is_unique,
|
|
24
|
+
ix.indisprimary as is_primary
|
|
25
|
+
FROM
|
|
26
|
+
pg_class t,
|
|
27
|
+
pg_namespace s,
|
|
28
|
+
pg_class i,
|
|
29
|
+
pg_index ix,
|
|
30
|
+
pg_attribute a
|
|
31
|
+
WHERE
|
|
32
|
+
t.oid = ix.indrelid
|
|
33
|
+
AND i.oid = ix.indexrelid
|
|
34
|
+
AND a.attrelid = t.oid
|
|
35
|
+
AND a.attnum = ANY(ix.indkey)
|
|
36
|
+
AND t.relkind = 'r'
|
|
37
|
+
AND t.relnamespace = s.oid
|
|
38
|
+
AND s.nspname = ?
|
|
39
|
+
AND t.relname = ?;
|
|
40
|
+
`,
|
|
41
|
+
FOREIGN_KEY_LIST: /* sql */ `
|
|
42
|
+
SELECT
|
|
43
|
+
tco."constraint_name" as constraint_name,
|
|
44
|
+
kcu."column_name" as column_name,
|
|
45
|
+
rel_kcu."table_name" as foreign_table,
|
|
46
|
+
rel_kcu."column_name" as fk_column_name,
|
|
47
|
+
rco.update_rule as on_update,
|
|
48
|
+
rco.delete_rule as on_delete
|
|
49
|
+
FROM information_schema.table_constraints tco
|
|
50
|
+
JOIN information_schema.key_column_usage kcu
|
|
51
|
+
ON tco.constraint_schema = kcu.constraint_schema
|
|
52
|
+
AND tco.constraint_name = kcu.constraint_name
|
|
53
|
+
JOIN information_schema.referential_constraints rco
|
|
54
|
+
ON tco.constraint_schema = rco.constraint_schema
|
|
55
|
+
AND tco.constraint_name = rco.constraint_name
|
|
56
|
+
JOIN information_schema.key_column_usage rel_kcu
|
|
57
|
+
ON rco.unique_constraint_schema = rel_kcu.constraint_schema
|
|
58
|
+
AND rco.unique_constraint_name = rel_kcu.constraint_name
|
|
59
|
+
AND kcu.ordinal_position = rel_kcu.ordinal_position
|
|
60
|
+
WHERE
|
|
61
|
+
tco.constraint_type = 'FOREIGN KEY'
|
|
62
|
+
AND tco.constraint_schema = ?
|
|
63
|
+
AND tco.table_name = ?
|
|
64
|
+
ORDER BY kcu.table_schema, kcu.table_name, kcu.ordinal_position, kcu.constraint_name;
|
|
65
|
+
`,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const toStrapiType = column => {
|
|
69
|
+
const rootType = column.data_type.toLowerCase().match(/[^(), ]+/)[0];
|
|
70
|
+
|
|
71
|
+
switch (rootType) {
|
|
72
|
+
case 'integer': {
|
|
73
|
+
// find a way to figure out the increments
|
|
74
|
+
return { type: 'integer' };
|
|
75
|
+
}
|
|
76
|
+
case 'text': {
|
|
77
|
+
return { type: 'text', args: ['longtext'] };
|
|
78
|
+
}
|
|
79
|
+
case 'boolean': {
|
|
80
|
+
return { type: 'boolean' };
|
|
81
|
+
}
|
|
82
|
+
case 'character': {
|
|
83
|
+
return { type: 'string', args: [column.character_maximum_length] };
|
|
84
|
+
}
|
|
85
|
+
case 'timestamp': {
|
|
86
|
+
return { type: 'datetime', args: [{ useTz: false, precision: 6 }] };
|
|
87
|
+
}
|
|
88
|
+
case 'date': {
|
|
89
|
+
return { type: 'date' };
|
|
90
|
+
}
|
|
91
|
+
case 'time': {
|
|
92
|
+
return { type: 'time', args: [{ precision: 3 }] };
|
|
93
|
+
}
|
|
94
|
+
case 'numeric': {
|
|
95
|
+
return { type: 'decimal', args: [10, 2] };
|
|
96
|
+
}
|
|
97
|
+
case 'real':
|
|
98
|
+
case 'double': {
|
|
99
|
+
return { type: 'double' };
|
|
100
|
+
}
|
|
101
|
+
case 'bigint': {
|
|
102
|
+
return { type: 'bigInteger' };
|
|
103
|
+
}
|
|
104
|
+
case 'jsonb': {
|
|
105
|
+
return { type: 'jsonb' };
|
|
106
|
+
}
|
|
107
|
+
default: {
|
|
108
|
+
return { type: 'specificType', args: [column.data_type] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
class PostgresqlSchemaInspector {
|
|
114
|
+
constructor(db) {
|
|
115
|
+
this.db = db;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getSchema() {
|
|
119
|
+
const schema = { tables: [] };
|
|
120
|
+
|
|
121
|
+
const tables = await this.getTables();
|
|
122
|
+
|
|
123
|
+
schema.tables = await Promise.all(
|
|
124
|
+
tables.map(async tableName => {
|
|
125
|
+
const columns = await this.getColumns(tableName);
|
|
126
|
+
const indexes = await this.getIndexes(tableName);
|
|
127
|
+
const foreignKeys = await this.getForeignKeys(tableName);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
name: tableName,
|
|
131
|
+
columns,
|
|
132
|
+
indexes,
|
|
133
|
+
foreignKeys,
|
|
134
|
+
};
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return schema;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getDatabaseSchema() {
|
|
142
|
+
return this.db.connection.getSchemaName() || 'public';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getTables() {
|
|
146
|
+
const { rows } = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST, [
|
|
147
|
+
this.getDatabaseSchema(),
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
return rows.map(row => row.table_name);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getColumns(tableName) {
|
|
154
|
+
const { rows } = await this.db.connection.raw(SQL_QUERIES.LIST_COLUMNS, [
|
|
155
|
+
this.getDatabaseSchema(),
|
|
156
|
+
tableName,
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
return rows.map(row => {
|
|
160
|
+
const { type, args = [], ...rest } = toStrapiType(row);
|
|
161
|
+
|
|
162
|
+
const defaultTo =
|
|
163
|
+
row.column_default && row.column_default.includes('nextval(') ? null : row.column_default;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
type,
|
|
167
|
+
args,
|
|
168
|
+
defaultTo,
|
|
169
|
+
name: row.column_name,
|
|
170
|
+
notNullable: row.is_nullable === 'NO',
|
|
171
|
+
unsigned: false,
|
|
172
|
+
...rest,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async getIndexes(tableName) {
|
|
178
|
+
const { rows } = await this.db.connection.raw(SQL_QUERIES.INDEX_LIST, [
|
|
179
|
+
this.getDatabaseSchema(),
|
|
180
|
+
tableName,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
const ret = {};
|
|
184
|
+
|
|
185
|
+
for (const index of rows) {
|
|
186
|
+
if (index.column_name === 'id') {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!ret[index.indexrelid]) {
|
|
191
|
+
ret[index.indexrelid] = {
|
|
192
|
+
columns: [index.column_name],
|
|
193
|
+
name: index.index_name,
|
|
194
|
+
type: index.is_primary ? 'primary' : index.is_unique ? 'unique' : null,
|
|
195
|
+
};
|
|
196
|
+
} else {
|
|
197
|
+
ret[index.indexrelid].columns.push(index.column_name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return Object.values(ret);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async getForeignKeys(tableName) {
|
|
205
|
+
const { rows } = await this.db.connection.raw(SQL_QUERIES.FOREIGN_KEY_LIST, [
|
|
206
|
+
this.getDatabaseSchema(),
|
|
207
|
+
tableName,
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
const ret = {};
|
|
211
|
+
|
|
212
|
+
for (const fk of rows) {
|
|
213
|
+
if (!ret[fk.constraint_name]) {
|
|
214
|
+
ret[fk.constraint_name] = {
|
|
215
|
+
name: fk.constraint_name,
|
|
216
|
+
columns: [fk.column_name],
|
|
217
|
+
referencedColumns: [fk.fk_column_name],
|
|
218
|
+
referencedTable: fk.foreign_table,
|
|
219
|
+
onUpdate: fk.on_update.toUpperCase(),
|
|
220
|
+
onDelete: fk.on_delete.toUpperCase(),
|
|
221
|
+
};
|
|
222
|
+
} else {
|
|
223
|
+
ret[fk.constraint_name].columns.push(fk.column_name);
|
|
224
|
+
ret[fk.constraint_name].referencedColumns.push(fk.fk_column_name);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return Object.values(ret);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = PostgresqlSchemaInspector;
|