@strapi/database 4.0.0-next.9 → 4.0.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/jest.config.js +10 -0
- package/lib/dialects/dialect.js +45 -0
- package/lib/dialects/index.js +7 -113
- 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 +73 -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 +17 -1
- 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 +374 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +47 -50
- package/lib/schema/schema.js +22 -27
- package/lib/schema/storage.js +79 -0
- package/lib/utils/content-types.js +0 -1
- package/package.json +27 -21
- package/examples/data.sqlite +0 -0
- 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,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;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
const errors = require('../../errors');
|
|
7
|
+
const { Dialect } = require('../dialect');
|
|
8
|
+
const SqliteSchmeaInspector = require('./schema-inspector');
|
|
9
|
+
|
|
10
|
+
class SqliteDialect extends Dialect {
|
|
11
|
+
constructor(db) {
|
|
12
|
+
super(db);
|
|
13
|
+
|
|
14
|
+
this.schemaInspector = new SqliteSchmeaInspector(db);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
configure() {
|
|
18
|
+
this.db.config.connection.connection.filename = path.resolve(
|
|
19
|
+
this.db.config.connection.connection.filename
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const dbDir = path.dirname(this.db.config.connection.connection.filename);
|
|
23
|
+
|
|
24
|
+
fse.ensureDirSync(dbDir);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async initialize() {
|
|
28
|
+
await this.db.connection.raw('pragma foreign_keys = on');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
canAlterConstraints() {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getSqlType(type) {
|
|
36
|
+
switch (type) {
|
|
37
|
+
case 'enum': {
|
|
38
|
+
return 'text';
|
|
39
|
+
}
|
|
40
|
+
case 'double':
|
|
41
|
+
case 'decimal': {
|
|
42
|
+
return 'float';
|
|
43
|
+
}
|
|
44
|
+
case 'timestamp': {
|
|
45
|
+
return 'datetime';
|
|
46
|
+
}
|
|
47
|
+
default: {
|
|
48
|
+
return type;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async startSchemaUpdate() {
|
|
54
|
+
await this.db.connection.raw(`pragma foreign_keys = off`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async endSchemaUpdate() {
|
|
58
|
+
await this.db.connection.raw(`pragma foreign_keys = on`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
transformErrors(error) {
|
|
62
|
+
switch (error.errno) {
|
|
63
|
+
case 19: {
|
|
64
|
+
throw new errors.NotNullConstraint(); // TODO: extract column name
|
|
65
|
+
}
|
|
66
|
+
default: {
|
|
67
|
+
super.transformErrors(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = SqliteDialect;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SQL_QUERIES = {
|
|
4
|
+
TABLE_LIST: `select name from sqlite_master where type = 'table' and name NOT LIKE 'sqlite%'`,
|
|
5
|
+
TABLE_INFO: `pragma table_info(??)`,
|
|
6
|
+
INDEX_LIST: 'pragma index_list(??)',
|
|
7
|
+
INDEX_INFO: 'pragma index_info(??)',
|
|
8
|
+
FOREIGN_KEY_LIST: 'pragma foreign_key_list(??)',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const toStrapiType = column => {
|
|
12
|
+
const { type } = column;
|
|
13
|
+
|
|
14
|
+
const rootType = type.toLowerCase().match(/[^(), ]+/)[0];
|
|
15
|
+
|
|
16
|
+
switch (rootType) {
|
|
17
|
+
case 'integer': {
|
|
18
|
+
if (column.pk) {
|
|
19
|
+
return { type: 'increments', args: [{ primary: true }] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { type: 'integer' };
|
|
23
|
+
}
|
|
24
|
+
case 'float': {
|
|
25
|
+
return { type: 'float', args: [10, 2] };
|
|
26
|
+
}
|
|
27
|
+
case 'bigint': {
|
|
28
|
+
return { type: 'bigInteger' };
|
|
29
|
+
}
|
|
30
|
+
case 'varchar': {
|
|
31
|
+
const length = type.slice(8, type.length - 1);
|
|
32
|
+
|
|
33
|
+
return { type: 'string', args: [Number(length)] };
|
|
34
|
+
}
|
|
35
|
+
case 'text': {
|
|
36
|
+
return { type: 'text', args: ['longtext'] };
|
|
37
|
+
}
|
|
38
|
+
case 'json': {
|
|
39
|
+
return { type: 'jsonb' };
|
|
40
|
+
}
|
|
41
|
+
case 'boolean': {
|
|
42
|
+
return { type: 'boolean' };
|
|
43
|
+
}
|
|
44
|
+
case 'datetime': {
|
|
45
|
+
return { type: 'datetime', args: [{ useTz: false, precision: 6 }] };
|
|
46
|
+
}
|
|
47
|
+
case 'date': {
|
|
48
|
+
return { type: 'date' };
|
|
49
|
+
}
|
|
50
|
+
case 'time': {
|
|
51
|
+
return { type: 'time', args: [{ precision: 3 }] };
|
|
52
|
+
}
|
|
53
|
+
default: {
|
|
54
|
+
return { type: 'specificType', args: [column.data_type] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
class SqliteSchemaInspector {
|
|
60
|
+
constructor(db) {
|
|
61
|
+
this.db = db;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getSchema() {
|
|
65
|
+
const schema = { tables: [] };
|
|
66
|
+
const tables = await this.getTables();
|
|
67
|
+
|
|
68
|
+
for (const tableName of tables) {
|
|
69
|
+
const columns = await this.getColumns(tableName);
|
|
70
|
+
const indexes = await this.getIndexes(tableName);
|
|
71
|
+
const foreignKeys = await this.getForeignKeys(tableName);
|
|
72
|
+
|
|
73
|
+
schema.tables.push({
|
|
74
|
+
name: tableName,
|
|
75
|
+
columns,
|
|
76
|
+
indexes,
|
|
77
|
+
foreignKeys,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return schema;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getTables() {
|
|
85
|
+
const rows = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);
|
|
86
|
+
|
|
87
|
+
return rows.map(row => row.name);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async getColumns(tableName) {
|
|
91
|
+
const rows = await this.db.connection.raw(SQL_QUERIES.TABLE_INFO, [tableName]);
|
|
92
|
+
|
|
93
|
+
return rows.map(row => {
|
|
94
|
+
const { type, args = [], ...rest } = toStrapiType(row);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
type,
|
|
98
|
+
args,
|
|
99
|
+
name: row.name,
|
|
100
|
+
defaultTo: row.dflt_value,
|
|
101
|
+
notNullable: row.notnull !== null ? Boolean(row.notnull) : null,
|
|
102
|
+
unsigned: false,
|
|
103
|
+
...rest,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async getIndexes(tableName) {
|
|
109
|
+
const indexes = await this.db.connection.raw(SQL_QUERIES.INDEX_LIST, [tableName]);
|
|
110
|
+
|
|
111
|
+
const ret = [];
|
|
112
|
+
|
|
113
|
+
for (const index of indexes.filter(index => !index.name.startsWith('sqlite_'))) {
|
|
114
|
+
const res = await this.db.connection.raw(SQL_QUERIES.INDEX_INFO, [index.name]);
|
|
115
|
+
|
|
116
|
+
ret.push({
|
|
117
|
+
columns: res.map(row => row.name),
|
|
118
|
+
name: index.name,
|
|
119
|
+
type: index.unique ? 'unique' : null,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return ret;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getForeignKeys(tableName) {
|
|
127
|
+
const fks = await this.db.connection.raw(SQL_QUERIES.FOREIGN_KEY_LIST, [tableName]);
|
|
128
|
+
|
|
129
|
+
const ret = {};
|
|
130
|
+
|
|
131
|
+
for (const fk of fks) {
|
|
132
|
+
if (!ret[fk.id]) {
|
|
133
|
+
ret[fk.id] = {
|
|
134
|
+
// TODO: name, // find name
|
|
135
|
+
columns: [fk.from],
|
|
136
|
+
referencedColumns: [fk.to],
|
|
137
|
+
referencedTable: fk.table,
|
|
138
|
+
onUpdate: fk.on_update.toUpperCase(),
|
|
139
|
+
onDelete: fk.on_delete.toUpperCase(),
|
|
140
|
+
};
|
|
141
|
+
} else {
|
|
142
|
+
ret[fk.id].columns.push(fk.from);
|
|
143
|
+
ret[fk.id].referencedColumns.push(fk.to);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Object.values(ret);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = SqliteSchemaInspector;
|
package/lib/entity-manager.js
CHANGED
|
@@ -29,8 +29,7 @@ const toAssocs = data => {
|
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
32
|
+
const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
34
33
|
const { attributes } = metadata;
|
|
35
34
|
|
|
36
35
|
const obj = {};
|
|
@@ -38,7 +37,6 @@ const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
|
38
37
|
for (const attributeName in attributes) {
|
|
39
38
|
const attribute = attributes[attributeName];
|
|
40
39
|
|
|
41
|
-
// TODO: convert to column name
|
|
42
40
|
if (types.isScalar(attribute.type)) {
|
|
43
41
|
const field = createField(attribute);
|
|
44
42
|
|
|
@@ -65,7 +63,6 @@ const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
|
65
63
|
if (types.isRelation(attribute.type)) {
|
|
66
64
|
// oneToOne & manyToOne
|
|
67
65
|
if (attribute.joinColumn && attribute.owner) {
|
|
68
|
-
// TODO: ensure joinColumn name respect convention ?
|
|
69
66
|
const joinColumnName = attribute.joinColumn.name;
|
|
70
67
|
|
|
71
68
|
// allow setting to null
|
|
@@ -145,7 +142,7 @@ const createEntityManager = db => {
|
|
|
145
142
|
await db.lifecycles.run('beforeCount', uid, { params });
|
|
146
143
|
|
|
147
144
|
const res = await this.createQueryBuilder(uid)
|
|
148
|
-
.init(_.pick(['_q', 'where'], params))
|
|
145
|
+
.init(_.pick(['_q', 'where', 'filters'], params))
|
|
149
146
|
.count()
|
|
150
147
|
.first()
|
|
151
148
|
.execute();
|
|
@@ -167,7 +164,7 @@ const createEntityManager = db => {
|
|
|
167
164
|
throw new Error('Create expects a data object');
|
|
168
165
|
}
|
|
169
166
|
|
|
170
|
-
const dataToInsert =
|
|
167
|
+
const dataToInsert = processData(metadata, data, { withDefaults: true });
|
|
171
168
|
|
|
172
169
|
const [id] = await this.createQueryBuilder(uid)
|
|
173
170
|
.insert(dataToInsert)
|
|
@@ -175,7 +172,7 @@ const createEntityManager = db => {
|
|
|
175
172
|
|
|
176
173
|
await this.attachRelations(uid, id, data);
|
|
177
174
|
|
|
178
|
-
// TODO: in case there is
|
|
175
|
+
// TODO: in case there is no select or populate specified return the inserted data ?
|
|
179
176
|
// TODO: do not trigger the findOne lifecycles ?
|
|
180
177
|
const result = await this.findOne(uid, {
|
|
181
178
|
where: { id },
|
|
@@ -199,7 +196,7 @@ const createEntityManager = db => {
|
|
|
199
196
|
throw new Error('CreateMany expects data to be an array');
|
|
200
197
|
}
|
|
201
198
|
|
|
202
|
-
const dataToInsert = data.map(datum =>
|
|
199
|
+
const dataToInsert = data.map(datum => processData(metadata, datum, { withDefaults: true }));
|
|
203
200
|
|
|
204
201
|
if (_.isEmpty(dataToInsert)) {
|
|
205
202
|
throw new Error('Nothing to insert');
|
|
@@ -242,7 +239,7 @@ const createEntityManager = db => {
|
|
|
242
239
|
|
|
243
240
|
const { id } = entity;
|
|
244
241
|
|
|
245
|
-
const dataToUpdate =
|
|
242
|
+
const dataToUpdate = processData(metadata, data);
|
|
246
243
|
|
|
247
244
|
if (!_.isEmpty(dataToUpdate)) {
|
|
248
245
|
await this.createQueryBuilder(uid)
|
|
@@ -272,7 +269,7 @@ const createEntityManager = db => {
|
|
|
272
269
|
const metadata = db.metadata.get(uid);
|
|
273
270
|
const { where, data } = params;
|
|
274
271
|
|
|
275
|
-
const dataToUpdate =
|
|
272
|
+
const dataToUpdate = processData(metadata, data);
|
|
276
273
|
|
|
277
274
|
if (_.isEmpty(dataToUpdate)) {
|
|
278
275
|
throw new Error('Update requires data');
|
|
@@ -299,7 +296,7 @@ const createEntityManager = db => {
|
|
|
299
296
|
throw new Error('Delete requires a where parameter');
|
|
300
297
|
}
|
|
301
298
|
|
|
302
|
-
// TODO:
|
|
299
|
+
// TODO: do not trigger the findOne lifecycles ?
|
|
303
300
|
const entity = await this.findOne(uid, {
|
|
304
301
|
select: select && ['id'].concat(select),
|
|
305
302
|
where,
|
|
@@ -472,7 +469,6 @@ const createEntityManager = db => {
|
|
|
472
469
|
const { joinTable } = attribute;
|
|
473
470
|
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
474
471
|
|
|
475
|
-
// TODO: validate logic of delete
|
|
476
472
|
if (isOneToAny(attribute) && isBidirectional(attribute)) {
|
|
477
473
|
await this.createQueryBuilder(joinTable.name)
|
|
478
474
|
.delete()
|
|
@@ -654,7 +650,10 @@ const createEntityManager = db => {
|
|
|
654
650
|
.where(joinTable.on || {})
|
|
655
651
|
.execute();
|
|
656
652
|
|
|
657
|
-
if (
|
|
653
|
+
if (
|
|
654
|
+
isBidirectional(attribute) &&
|
|
655
|
+
['oneToOne', 'oneToMany'].includes(attribute.relation)
|
|
656
|
+
) {
|
|
658
657
|
await this.createQueryBuilder(joinTable.name)
|
|
659
658
|
.delete()
|
|
660
659
|
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })
|
|
@@ -772,6 +771,7 @@ const createEntityManager = db => {
|
|
|
772
771
|
continue;
|
|
773
772
|
}
|
|
774
773
|
|
|
774
|
+
// do not need to delete links when using foreign keys
|
|
775
775
|
if (db.dialect.usesForeignKeys()) {
|
|
776
776
|
return;
|
|
777
777
|
}
|
|
@@ -812,7 +812,7 @@ const createEntityManager = db => {
|
|
|
812
812
|
const entry = await this.findOne(uid, {
|
|
813
813
|
select: ['id'],
|
|
814
814
|
where: { id: entity.id },
|
|
815
|
-
populate
|
|
815
|
+
populate,
|
|
816
816
|
});
|
|
817
817
|
|
|
818
818
|
return Object.assign({}, entity, entry);
|
|
@@ -837,6 +837,10 @@ const createEntityManager = db => {
|
|
|
837
837
|
},
|
|
838
838
|
});
|
|
839
839
|
|
|
840
|
+
if (!entry) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
|
|
840
844
|
return entry[field];
|
|
841
845
|
},
|
|
842
846
|
|
package/lib/entity-repository.js
CHANGED
|
@@ -95,9 +95,8 @@ const createRepository = (uid, db) => {
|
|
|
95
95
|
return db.entityManager.deleteRelations(uid, id);
|
|
96
96
|
},
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return db.entityManager.populate(uid, entity, field, params);
|
|
98
|
+
populate(entity, populate) {
|
|
99
|
+
return db.entityManager.populate(uid, entity, populate);
|
|
101
100
|
},
|
|
102
101
|
|
|
103
102
|
load(entity, field, params) {
|
package/lib/errors.js
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/* DatabaseError */
|
|
4
|
+
class DatabaseError extends Error {
|
|
5
|
+
constructor(message, details = {}) {
|
|
6
|
+
super();
|
|
7
|
+
this.name = 'DatabaseError';
|
|
8
|
+
this.message = message || 'A database error occured';
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class NotNullConstraint extends DatabaseError {
|
|
4
14
|
constructor({ column = '' } = {}) {
|
|
5
15
|
super();
|
|
6
16
|
this.name = 'NotNullConstraint';
|
|
7
|
-
this.message = `Not null constraint violation${column ? `
|
|
17
|
+
this.message = `Not null constraint violation${column ? ` on column ${column}` : ''}.`;
|
|
18
|
+
this.details = { column };
|
|
8
19
|
this.stack = '';
|
|
9
20
|
}
|
|
10
21
|
}
|
|
11
22
|
|
|
23
|
+
class InvalidTimeError extends DatabaseError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super();
|
|
26
|
+
this.name = 'InvalidTimeFormat';
|
|
27
|
+
this.message = message || 'Invalid time format, expected HH:mm:ss.SSS';
|
|
28
|
+
this.details = {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class InvalidDateError extends DatabaseError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super();
|
|
35
|
+
this.name = 'InvalidTimeFormat';
|
|
36
|
+
this.message = message || 'Invalid date format, expected YYYY-MM-DD';
|
|
37
|
+
this.details = {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class InvalidDateTimeError extends DatabaseError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super();
|
|
44
|
+
this.name = 'InvalidTimeFormat';
|
|
45
|
+
this.message = message || 'Invalid datetime format, expected a timestamp or an ISO date';
|
|
46
|
+
this.details = {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
12
50
|
module.exports = {
|
|
51
|
+
DatabaseError,
|
|
13
52
|
NotNullConstraint,
|
|
53
|
+
InvalidTimeError,
|
|
54
|
+
InvalidDateError,
|
|
55
|
+
InvalidDateTimeError,
|
|
14
56
|
};
|
package/lib/fields.d.ts
CHANGED