@strapi/database 4.0.0-next.8 → 4.0.2
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 +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 +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 +67 -22
- 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 +374 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +47 -50
- package/lib/schema/schema.js +21 -26
- package/lib/schema/storage.js +79 -0
- package/lib/utils/content-types.js +0 -1
- package/package.json +26 -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
package/lib/metadata/index.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module metadata
|
|
3
|
-
*
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
'use strict';
|
|
7
2
|
|
|
8
3
|
const _ = require('lodash/fp');
|
|
@@ -16,43 +11,13 @@ class Metadata extends Map {
|
|
|
16
11
|
}
|
|
17
12
|
}
|
|
18
13
|
|
|
14
|
+
// TODO: check if there isn't an attribute with an id already
|
|
19
15
|
/**
|
|
20
16
|
* Create Metadata from models configurations
|
|
21
|
-
*
|
|
22
|
-
* timestamps => not optional anymore but auto added. Auto added on the content type or in the db layer ?
|
|
23
|
-
*
|
|
24
|
-
* options => options are handled on the layer above. Options convert to fields on the CT
|
|
25
|
-
*
|
|
26
|
-
* filters => not in v1
|
|
27
|
-
*
|
|
28
|
-
* attributes
|
|
29
|
-
*
|
|
30
|
-
* - type
|
|
31
|
-
* - mapping field name - column name
|
|
32
|
-
* - mapping field type - column type
|
|
33
|
-
* - formatter / parser => coming from field type so no
|
|
34
|
-
* - indexes / checks / contstraints
|
|
35
|
-
* - relations => reference to the target model (function or string to avoid circular deps ?)
|
|
36
|
-
* - name of the LEFT/RIGHT side foreign keys
|
|
37
|
-
* - name of join table
|
|
38
|
-
*
|
|
39
|
-
* - compo/dz => reference to the components
|
|
40
|
-
* - validators
|
|
41
|
-
* - hooks
|
|
42
|
-
* - default value
|
|
43
|
-
* - required -> should add a not null option instead of the API required
|
|
44
|
-
* - unique -> should add a DB unique option instead of the unique in the API (Unique by locale or something else for example)
|
|
45
|
-
*
|
|
46
|
-
* lifecycles
|
|
47
|
-
*
|
|
48
|
-
* private fields ? => handled on a different layer
|
|
49
17
|
* @param {object[]} models
|
|
50
18
|
* @returns {Metadata}
|
|
51
19
|
*/
|
|
52
20
|
const createMetadata = (models = []) => {
|
|
53
|
-
// TODO: reorder to make sure we can create everything or delete everything in the right order
|
|
54
|
-
// TODO: allow passing the join config in the attribute
|
|
55
|
-
// TODO: allow passing column config in the attribute
|
|
56
21
|
const metadata = new Metadata();
|
|
57
22
|
|
|
58
23
|
// init pass
|
|
@@ -62,7 +27,6 @@ const createMetadata = (models = []) => {
|
|
|
62
27
|
uid: model.uid,
|
|
63
28
|
tableName: model.tableName,
|
|
64
29
|
attributes: {
|
|
65
|
-
// TODO: check if there isn't an attribute with an id already
|
|
66
30
|
id: {
|
|
67
31
|
type: 'increments',
|
|
68
32
|
},
|
|
@@ -83,67 +47,12 @@ const createMetadata = (models = []) => {
|
|
|
83
47
|
for (const [attributeName, attribute] of Object.entries(meta.attributes)) {
|
|
84
48
|
try {
|
|
85
49
|
if (types.isComponent(attribute.type)) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Object.assign(attribute, {
|
|
89
|
-
type: 'relation',
|
|
90
|
-
relation: attribute.repeatable === true ? 'oneToMany' : 'oneToOne',
|
|
91
|
-
target: attribute.component,
|
|
92
|
-
joinTable: {
|
|
93
|
-
name: meta.componentLink.tableName,
|
|
94
|
-
joinColumn: {
|
|
95
|
-
name: 'entity_id',
|
|
96
|
-
referencedColumn: 'id',
|
|
97
|
-
},
|
|
98
|
-
inverseJoinColumn: {
|
|
99
|
-
name: 'component_id',
|
|
100
|
-
referencedColumn: 'id',
|
|
101
|
-
},
|
|
102
|
-
on: {
|
|
103
|
-
field: attributeName,
|
|
104
|
-
},
|
|
105
|
-
orderBy: {
|
|
106
|
-
order: 'asc',
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
|
|
50
|
+
createComponent(attributeName, attribute, meta, metadata);
|
|
111
51
|
continue;
|
|
112
52
|
}
|
|
113
53
|
|
|
114
54
|
if (types.isDynamicZone(attribute.type)) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Object.assign(attribute, {
|
|
118
|
-
type: 'relation',
|
|
119
|
-
relation: 'morphToMany',
|
|
120
|
-
// TODO: handle restrictions at some point
|
|
121
|
-
// target: attribute.components,
|
|
122
|
-
joinTable: {
|
|
123
|
-
name: meta.componentLink.tableName,
|
|
124
|
-
joinColumn: {
|
|
125
|
-
name: 'entity_id',
|
|
126
|
-
referencedColumn: 'id',
|
|
127
|
-
},
|
|
128
|
-
morphColumn: {
|
|
129
|
-
idColumn: {
|
|
130
|
-
name: 'component_id',
|
|
131
|
-
referencedColumn: 'id',
|
|
132
|
-
},
|
|
133
|
-
typeColumn: {
|
|
134
|
-
name: 'component_type',
|
|
135
|
-
},
|
|
136
|
-
typeField: '__component',
|
|
137
|
-
},
|
|
138
|
-
on: {
|
|
139
|
-
field: attributeName,
|
|
140
|
-
},
|
|
141
|
-
orderBy: {
|
|
142
|
-
order: 'asc',
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
55
|
+
createDynamicZone(attributeName, attribute, meta, metadata);
|
|
147
56
|
continue;
|
|
148
57
|
}
|
|
149
58
|
|
|
@@ -151,7 +60,10 @@ const createMetadata = (models = []) => {
|
|
|
151
60
|
createRelation(attributeName, attribute, meta, metadata);
|
|
152
61
|
continue;
|
|
153
62
|
}
|
|
63
|
+
|
|
64
|
+
createAttribute(attributeName, attribute, meta, metadata);
|
|
154
65
|
} catch (error) {
|
|
66
|
+
console.log(error);
|
|
155
67
|
throw new Error(
|
|
156
68
|
`Error on attribute ${attributeName} in model ${meta.singularName}(${meta.uid}): ${error.message}`
|
|
157
69
|
);
|
|
@@ -159,6 +71,15 @@ const createMetadata = (models = []) => {
|
|
|
159
71
|
}
|
|
160
72
|
}
|
|
161
73
|
|
|
74
|
+
for (const meta of metadata.values()) {
|
|
75
|
+
const columnToAttribute = Object.keys(meta.attributes).reduce((acc, key) => {
|
|
76
|
+
const attribute = meta.attributes[key];
|
|
77
|
+
return Object.assign(acc, { [attribute.columnName || key]: key });
|
|
78
|
+
}, {});
|
|
79
|
+
|
|
80
|
+
meta.columnToAttribute = columnToAttribute;
|
|
81
|
+
}
|
|
82
|
+
|
|
162
83
|
return metadata;
|
|
163
84
|
};
|
|
164
85
|
|
|
@@ -201,7 +122,7 @@ const createCompoLinkModelMeta = baseModelMeta => {
|
|
|
201
122
|
type: 'integer',
|
|
202
123
|
column: {
|
|
203
124
|
unsigned: true,
|
|
204
|
-
defaultTo:
|
|
125
|
+
defaultTo: 0,
|
|
205
126
|
},
|
|
206
127
|
},
|
|
207
128
|
},
|
|
@@ -209,10 +130,16 @@ const createCompoLinkModelMeta = baseModelMeta => {
|
|
|
209
130
|
{
|
|
210
131
|
name: `${baseModelMeta.tableName}_field_index`,
|
|
211
132
|
columns: ['field'],
|
|
133
|
+
type: null,
|
|
212
134
|
},
|
|
213
135
|
{
|
|
214
136
|
name: `${baseModelMeta.tableName}_component_type_index`,
|
|
215
137
|
columns: ['component_type'],
|
|
138
|
+
type: null,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: `${baseModelMeta.tableName}_entity_fk`,
|
|
142
|
+
columns: ['entity_id'],
|
|
216
143
|
},
|
|
217
144
|
],
|
|
218
145
|
foreignKeys: [
|
|
@@ -227,4 +154,66 @@ const createCompoLinkModelMeta = baseModelMeta => {
|
|
|
227
154
|
};
|
|
228
155
|
};
|
|
229
156
|
|
|
157
|
+
const createDynamicZone = (attributeName, attribute, meta) => {
|
|
158
|
+
Object.assign(attribute, {
|
|
159
|
+
type: 'relation',
|
|
160
|
+
relation: 'morphToMany',
|
|
161
|
+
// TODO: handle restrictions at some point
|
|
162
|
+
// target: attribute.components,
|
|
163
|
+
joinTable: {
|
|
164
|
+
name: meta.componentLink.tableName,
|
|
165
|
+
joinColumn: {
|
|
166
|
+
name: 'entity_id',
|
|
167
|
+
referencedColumn: 'id',
|
|
168
|
+
},
|
|
169
|
+
morphColumn: {
|
|
170
|
+
idColumn: {
|
|
171
|
+
name: 'component_id',
|
|
172
|
+
referencedColumn: 'id',
|
|
173
|
+
},
|
|
174
|
+
typeColumn: {
|
|
175
|
+
name: 'component_type',
|
|
176
|
+
},
|
|
177
|
+
typeField: '__component',
|
|
178
|
+
},
|
|
179
|
+
on: {
|
|
180
|
+
field: attributeName,
|
|
181
|
+
},
|
|
182
|
+
orderBy: {
|
|
183
|
+
order: 'asc',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const createComponent = (attributeName, attribute, meta) => {
|
|
190
|
+
Object.assign(attribute, {
|
|
191
|
+
type: 'relation',
|
|
192
|
+
relation: attribute.repeatable === true ? 'oneToMany' : 'oneToOne',
|
|
193
|
+
target: attribute.component,
|
|
194
|
+
joinTable: {
|
|
195
|
+
name: meta.componentLink.tableName,
|
|
196
|
+
joinColumn: {
|
|
197
|
+
name: 'entity_id',
|
|
198
|
+
referencedColumn: 'id',
|
|
199
|
+
},
|
|
200
|
+
inverseJoinColumn: {
|
|
201
|
+
name: 'component_id',
|
|
202
|
+
referencedColumn: 'id',
|
|
203
|
+
},
|
|
204
|
+
on: {
|
|
205
|
+
field: attributeName,
|
|
206
|
+
},
|
|
207
|
+
orderBy: {
|
|
208
|
+
order: 'asc',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const createAttribute = (attributeName, attribute) => {
|
|
215
|
+
const columnName = _.snakeCase(attributeName);
|
|
216
|
+
Object.assign(attribute, { columnName });
|
|
217
|
+
};
|
|
218
|
+
|
|
230
219
|
module.exports = createMetadata;
|
|
@@ -235,6 +235,12 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
|
|
|
235
235
|
},
|
|
236
236
|
},
|
|
237
237
|
},
|
|
238
|
+
indexes: [
|
|
239
|
+
{
|
|
240
|
+
name: `${joinTableName}_fk`,
|
|
241
|
+
columns: [joinColumnName],
|
|
242
|
+
},
|
|
243
|
+
],
|
|
238
244
|
foreignKeys: [
|
|
239
245
|
{
|
|
240
246
|
name: `${joinTableName}_fk`,
|
|
@@ -415,6 +421,16 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
415
421
|
},
|
|
416
422
|
// TODO: add extra pivot attributes -> user should use an intermediate entity
|
|
417
423
|
},
|
|
424
|
+
indexes: [
|
|
425
|
+
{
|
|
426
|
+
name: `${joinTableName}_fk`,
|
|
427
|
+
columns: [joinColumnName],
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: `${joinTableName}_inv_fk`,
|
|
431
|
+
columns: [inverseJoinColumnName],
|
|
432
|
+
},
|
|
433
|
+
],
|
|
418
434
|
foreignKeys: [
|
|
419
435
|
{
|
|
420
436
|
name: `${joinTableName}_fk`,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
const Umzug = require('umzug');
|
|
6
|
+
|
|
7
|
+
const createStorage = require('./storage');
|
|
8
|
+
|
|
9
|
+
// TODO: check multiple commands in one sql statement
|
|
10
|
+
const migrationResolver = path => {
|
|
11
|
+
// if sql file run with knex raw
|
|
12
|
+
if (path.match(/\.sql$/)) {
|
|
13
|
+
const sql = fse.readFileSync(path, 'utf8');
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
up: knex => knex.raw(sql),
|
|
17
|
+
down() {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// NOTE: we can add some ts register if we want to handle ts migration files at some point
|
|
22
|
+
return require(path);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const createUmzugProvider = db => {
|
|
26
|
+
const migrationDir = path.join(strapi.dirs.root, 'database/migrations');
|
|
27
|
+
|
|
28
|
+
fse.ensureDirSync(migrationDir);
|
|
29
|
+
|
|
30
|
+
const wrapFn = fn => db => db.getConnection().transaction(trx => Promise.resolve(fn(trx)));
|
|
31
|
+
const storage = createStorage({ db, tableName: 'strapi_migrations' });
|
|
32
|
+
|
|
33
|
+
return new Umzug({
|
|
34
|
+
storage,
|
|
35
|
+
migrations: {
|
|
36
|
+
path: migrationDir,
|
|
37
|
+
pattern: /\.(js|sql)$/,
|
|
38
|
+
params: [db],
|
|
39
|
+
wrap: wrapFn,
|
|
40
|
+
customResolver: migrationResolver,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// NOTE: when needed => add internal migrations for core & plugins. How do we overlap them with users migrations ?
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates migrations provider
|
|
49
|
+
* @type {import('.').createMigrationsProvider}
|
|
50
|
+
*/
|
|
51
|
+
const createMigrationsProvider = db => {
|
|
52
|
+
const migrations = createUmzugProvider(db);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
async shouldRun() {
|
|
56
|
+
const pending = await migrations.pending();
|
|
57
|
+
|
|
58
|
+
return pending.length > 0;
|
|
59
|
+
},
|
|
60
|
+
async up() {
|
|
61
|
+
await migrations.up();
|
|
62
|
+
},
|
|
63
|
+
async down() {
|
|
64
|
+
await migrations.down();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
module.exports = { createMigrationsProvider };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const createStorage = (opts = {}) => {
|
|
4
|
+
const { db, tableName = 'strapi_migrations' } = opts;
|
|
5
|
+
|
|
6
|
+
const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
|
|
7
|
+
|
|
8
|
+
const createMigrationTable = () => {
|
|
9
|
+
return db.getSchemaConnection().createTable(tableName, table => {
|
|
10
|
+
table.increments('id');
|
|
11
|
+
table.string('name');
|
|
12
|
+
table.datetime('time', { useTz: false });
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
async logMigration(migrationName) {
|
|
18
|
+
await db
|
|
19
|
+
.getConnection()
|
|
20
|
+
.insert({
|
|
21
|
+
name: migrationName,
|
|
22
|
+
time: new Date(),
|
|
23
|
+
})
|
|
24
|
+
.into(tableName);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async unlogMigration(migrationName) {
|
|
28
|
+
await db
|
|
29
|
+
.getConnection(tableName)
|
|
30
|
+
.del()
|
|
31
|
+
.where({ name: migrationName });
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async executed() {
|
|
35
|
+
if (!(await hasMigrationTable())) {
|
|
36
|
+
await createMigrationTable();
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const logs = await db
|
|
41
|
+
.getConnection(tableName)
|
|
42
|
+
.select()
|
|
43
|
+
.from(tableName)
|
|
44
|
+
.orderBy('time');
|
|
45
|
+
|
|
46
|
+
return logs.map(log => log.name);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = createStorage;
|
|
@@ -51,12 +51,10 @@ const createJoin = (ctx, { alias, attributeName, attribute }) => {
|
|
|
51
51
|
return createPivotJoin(qb, joinTable, alias, tragetMeta);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// TODO: polymorphic relations
|
|
55
|
-
|
|
56
54
|
return alias;
|
|
57
55
|
};
|
|
58
56
|
|
|
59
|
-
// TODO:
|
|
57
|
+
// TODO: toColumnName for orderBy & on
|
|
60
58
|
const applyJoin = (qb, join) => {
|
|
61
59
|
const {
|
|
62
60
|
method = 'leftJoin',
|
|
@@ -64,12 +62,12 @@ const applyJoin = (qb, join) => {
|
|
|
64
62
|
referencedTable,
|
|
65
63
|
referencedColumn,
|
|
66
64
|
rootColumn,
|
|
67
|
-
rootTable =
|
|
65
|
+
rootTable = qb.alias,
|
|
68
66
|
on,
|
|
69
67
|
orderBy,
|
|
70
68
|
} = join;
|
|
71
69
|
|
|
72
|
-
qb[method]({
|
|
70
|
+
qb[method](`${referencedTable} as ${alias}`, inner => {
|
|
73
71
|
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
|
|
74
72
|
|
|
75
73
|
if (on) {
|
|
@@ -4,19 +4,23 @@ const _ = require('lodash/fp');
|
|
|
4
4
|
|
|
5
5
|
const types = require('../../types');
|
|
6
6
|
const { createJoin } = require('./join');
|
|
7
|
+
const { toColumnName } = require('./transform');
|
|
7
8
|
|
|
8
|
-
// TODO: convert field names to columns names
|
|
9
9
|
const processOrderBy = (orderBy, ctx) => {
|
|
10
|
-
const { db, uid, qb, alias
|
|
10
|
+
const { db, uid, qb, alias } = ctx;
|
|
11
|
+
const meta = db.metadata.get(uid);
|
|
12
|
+
const { attributes } = meta;
|
|
11
13
|
|
|
12
14
|
if (typeof orderBy === 'string') {
|
|
13
|
-
const attribute =
|
|
15
|
+
const attribute = attributes[orderBy];
|
|
14
16
|
|
|
15
17
|
if (!attribute) {
|
|
16
18
|
throw new Error(`Attribute ${orderBy} not found on model ${uid}`);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
const columnName = toColumnName(meta, orderBy);
|
|
22
|
+
|
|
23
|
+
return [{ column: qb.aliasColumn(columnName, alias) }];
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
if (Array.isArray(orderBy)) {
|
|
@@ -26,15 +30,25 @@ const processOrderBy = (orderBy, ctx) => {
|
|
|
26
30
|
if (_.isPlainObject(orderBy)) {
|
|
27
31
|
return Object.entries(orderBy).flatMap(([key, direction]) => {
|
|
28
32
|
const value = orderBy[key];
|
|
29
|
-
const attribute =
|
|
33
|
+
const attribute = attributes[key];
|
|
30
34
|
|
|
31
35
|
if (!attribute) {
|
|
32
36
|
throw new Error(`Attribute ${key} not found on model ${uid}`);
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
if (types.isScalar(attribute.type)) {
|
|
40
|
+
const columnName = toColumnName(meta, key);
|
|
41
|
+
|
|
42
|
+
return { column: qb.aliasColumn(columnName, alias), order: direction };
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
if (attribute.type === 'relation') {
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
const subAlias = createJoin(ctx, {
|
|
47
|
+
alias: alias || qb.alias,
|
|
48
|
+
uid,
|
|
49
|
+
attributeName: key,
|
|
50
|
+
attribute,
|
|
51
|
+
});
|
|
38
52
|
|
|
39
53
|
return processOrderBy(value, {
|
|
40
54
|
db,
|
|
@@ -44,10 +58,6 @@ const processOrderBy = (orderBy, ctx) => {
|
|
|
44
58
|
});
|
|
45
59
|
}
|
|
46
60
|
|
|
47
|
-
if (types.isScalar(attribute.type)) {
|
|
48
|
-
return { column: `${alias}.${key}`, order: direction };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
61
|
throw new Error(`You cannot order on ${attribute.type} types`);
|
|
52
62
|
});
|
|
53
63
|
}
|
|
@@ -33,7 +33,7 @@ const processPopulate = (populate, ctx) => {
|
|
|
33
33
|
|
|
34
34
|
let populateMap = {};
|
|
35
35
|
|
|
36
|
-
if (populate === false) {
|
|
36
|
+
if (populate === false || _.isNil(populate)) {
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -94,8 +94,17 @@ const processPopulate = (populate, ctx) => {
|
|
|
94
94
|
return finalPopulate;
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
// Omit limit & offset to avoid needing a query per result to avoid making too many queries
|
|
98
|
-
const pickPopulateParams = _.pick([
|
|
97
|
+
// TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries
|
|
98
|
+
const pickPopulateParams = _.pick([
|
|
99
|
+
'select',
|
|
100
|
+
'count',
|
|
101
|
+
'where',
|
|
102
|
+
'populate',
|
|
103
|
+
'orderBy',
|
|
104
|
+
'limit',
|
|
105
|
+
'offset',
|
|
106
|
+
'filters',
|
|
107
|
+
]);
|
|
99
108
|
|
|
100
109
|
// TODO: cleanup code
|
|
101
110
|
// TODO: create aliases for pivot columns
|
|
@@ -114,7 +123,11 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
114
123
|
const attribute = meta.attributes[key];
|
|
115
124
|
const targetMeta = db.metadata.get(attribute.target);
|
|
116
125
|
|
|
117
|
-
const populateValue =
|
|
126
|
+
const populateValue = {
|
|
127
|
+
filters: qb.state.filters,
|
|
128
|
+
...pickPopulateParams(populate[key]),
|
|
129
|
+
};
|
|
130
|
+
|
|
118
131
|
const isCount = populateValue.count === true;
|
|
119
132
|
|
|
120
133
|
const fromTargetRow = rowOrRows => fromRow(targetMeta, rowOrRows);
|
|
@@ -181,7 +194,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
181
194
|
const rows = await qb
|
|
182
195
|
.init(populateValue)
|
|
183
196
|
.join({
|
|
184
|
-
alias
|
|
197
|
+
alias,
|
|
185
198
|
referencedTable: joinTable.name,
|
|
186
199
|
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
187
200
|
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
@@ -265,7 +278,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
265
278
|
const rows = await qb
|
|
266
279
|
.init(populateValue)
|
|
267
280
|
.join({
|
|
268
|
-
alias
|
|
281
|
+
alias,
|
|
269
282
|
referencedTable: joinTable.name,
|
|
270
283
|
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
271
284
|
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
@@ -299,7 +312,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
299
312
|
const rows = await qb
|
|
300
313
|
.init(populateValue)
|
|
301
314
|
.join({
|
|
302
|
-
alias
|
|
315
|
+
alias,
|
|
303
316
|
referencedTable: joinTable.name,
|
|
304
317
|
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
305
318
|
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
@@ -344,7 +357,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
344
357
|
const rows = await qb
|
|
345
358
|
.init(populateValue)
|
|
346
359
|
.join({
|
|
347
|
-
alias
|
|
360
|
+
alias,
|
|
348
361
|
referencedTable: joinTable.name,
|
|
349
362
|
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
350
363
|
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
@@ -378,7 +391,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
378
391
|
const rows = await qb
|
|
379
392
|
.init(populateValue)
|
|
380
393
|
.join({
|
|
381
|
-
alias
|
|
394
|
+
alias,
|
|
382
395
|
referencedTable: joinTable.name,
|
|
383
396
|
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
384
397
|
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
@@ -461,7 +474,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
461
474
|
const rows = await qb
|
|
462
475
|
.init(populateValue)
|
|
463
476
|
.join({
|
|
464
|
-
alias
|
|
477
|
+
alias,
|
|
465
478
|
referencedTable: joinTable.name,
|
|
466
479
|
referencedColumn: joinColumn.name,
|
|
467
480
|
rootColumn: joinColumn.referencedColumn,
|
|
@@ -538,6 +551,12 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
538
551
|
for (const type in idsByType) {
|
|
539
552
|
const ids = idsByType[type];
|
|
540
553
|
|
|
554
|
+
// type was removed but still in morph relation
|
|
555
|
+
if (!db.metadata.get(type)) {
|
|
556
|
+
map[type] = {};
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
|
|
541
560
|
const qb = db.entityManager.createQueryBuilder(type);
|
|
542
561
|
|
|
543
562
|
const rows = await qb
|
|
@@ -596,6 +615,12 @@ const applyPopulate = async (results, populate, ctx) => {
|
|
|
596
615
|
for (const type in idsByType) {
|
|
597
616
|
const ids = idsByType[type];
|
|
598
617
|
|
|
618
|
+
// type was removed but still in morph relation
|
|
619
|
+
if (!db.metadata.get(type)) {
|
|
620
|
+
map[type] = {};
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
|
|
599
624
|
const qb = db.entityManager.createQueryBuilder(type);
|
|
600
625
|
|
|
601
626
|
const rows = await qb
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
const _ = require('lodash/fp');
|
|
4
4
|
|
|
5
5
|
const types = require('../../types');
|
|
6
|
+
const { toColumnName } = require('./transform');
|
|
6
7
|
|
|
7
|
-
const applySearch = (
|
|
8
|
-
const {
|
|
8
|
+
const applySearch = (knex, query, ctx) => {
|
|
9
|
+
const { qb, uid, db } = ctx;
|
|
10
|
+
const meta = db.metadata.get(uid);
|
|
9
11
|
|
|
10
|
-
const { attributes } =
|
|
12
|
+
const { attributes } = meta;
|
|
11
13
|
|
|
12
14
|
const searchColumns = ['id'];
|
|
13
15
|
|
|
@@ -29,22 +31,34 @@ const applySearch = (qb, query, ctx) => {
|
|
|
29
31
|
|
|
30
32
|
switch (db.dialect.client) {
|
|
31
33
|
case 'postgres': {
|
|
32
|
-
searchColumns.forEach(attr =>
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
searchColumns.forEach(attr => {
|
|
35
|
+
const columnName = toColumnName(meta, attr);
|
|
36
|
+
return knex.orWhereRaw(`??::text ILIKE ?`, [
|
|
37
|
+
qb.aliasColumn(columnName),
|
|
38
|
+
`%${escapeQuery(query, '*%\\')}%`,
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
35
41
|
|
|
36
42
|
break;
|
|
37
43
|
}
|
|
38
44
|
case 'sqlite': {
|
|
39
|
-
searchColumns.forEach(attr =>
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
searchColumns.forEach(attr => {
|
|
46
|
+
const columnName = toColumnName(meta, attr);
|
|
47
|
+
return knex.orWhereRaw(`?? LIKE ? ESCAPE '\\'`, [
|
|
48
|
+
qb.aliasColumn(columnName),
|
|
49
|
+
`%${escapeQuery(query, '*%\\')}%`,
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
42
52
|
break;
|
|
43
53
|
}
|
|
44
54
|
case 'mysql': {
|
|
45
|
-
searchColumns.forEach(attr =>
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
searchColumns.forEach(attr => {
|
|
56
|
+
const columnName = toColumnName(meta, attr);
|
|
57
|
+
return knex.orWhereRaw(`?? LIKE ?`, [
|
|
58
|
+
qb.aliasColumn(columnName),
|
|
59
|
+
`%${escapeQuery(query, '*%\\')}%`,
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
48
62
|
break;
|
|
49
63
|
}
|
|
50
64
|
default: {
|