@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
|
@@ -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: {
|
|
@@ -5,12 +5,12 @@ const _ = require('lodash/fp');
|
|
|
5
5
|
const types = require('../../types');
|
|
6
6
|
const { createField } = require('../../fields');
|
|
7
7
|
|
|
8
|
-
const fromRow = (
|
|
8
|
+
const fromRow = (meta, row) => {
|
|
9
9
|
if (Array.isArray(row)) {
|
|
10
|
-
return row.map(singleRow => fromRow(
|
|
10
|
+
return row.map(singleRow => fromRow(meta, singleRow));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const { attributes } =
|
|
13
|
+
const { attributes } = meta;
|
|
14
14
|
|
|
15
15
|
if (_.isNil(row)) {
|
|
16
16
|
return null;
|
|
@@ -19,25 +19,16 @@ const fromRow = (metadata, row) => {
|
|
|
19
19
|
const obj = {};
|
|
20
20
|
|
|
21
21
|
for (const column in row) {
|
|
22
|
-
|
|
23
|
-
const attributeName = column;
|
|
24
|
-
|
|
25
|
-
if (!attributes[attributeName]) {
|
|
26
|
-
// ignore value that are not related to an attribute (join columns ...)
|
|
22
|
+
if (!_.has(column, meta.columnToAttribute)) {
|
|
27
23
|
continue;
|
|
28
24
|
}
|
|
29
25
|
|
|
26
|
+
const attributeName = meta.columnToAttribute[column];
|
|
30
27
|
const attribute = attributes[attributeName];
|
|
31
28
|
|
|
32
29
|
if (types.isScalar(attribute.type)) {
|
|
33
|
-
// TODO: we convert to column name
|
|
34
|
-
// TODO: handle default value too
|
|
35
|
-
// TODO: format data & use dialect to know which type they support (json particularly)
|
|
36
|
-
|
|
37
30
|
const field = createField(attribute);
|
|
38
31
|
|
|
39
|
-
// TODO: validate data on creation
|
|
40
|
-
// field.validate(data[attributeName]);
|
|
41
32
|
const val = row[column] === null ? null : field.fromDB(row[column]);
|
|
42
33
|
|
|
43
34
|
obj[attributeName] = val;
|
|
@@ -51,6 +42,43 @@ const fromRow = (metadata, row) => {
|
|
|
51
42
|
return obj;
|
|
52
43
|
};
|
|
53
44
|
|
|
45
|
+
const toRow = (meta, data = {}) => {
|
|
46
|
+
if (_.isNil(data)) {
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (_.isArray(data)) {
|
|
51
|
+
return data.map(datum => toRow(meta, datum));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { attributes } = meta;
|
|
55
|
+
|
|
56
|
+
for (const key in data) {
|
|
57
|
+
const attribute = attributes[key];
|
|
58
|
+
|
|
59
|
+
if (!attribute || attribute.columnName === key) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
data[attribute.columnName] = data[key];
|
|
64
|
+
delete data[key];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return data;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const toColumnName = (meta, name) => {
|
|
71
|
+
const attribute = meta.attributes[name];
|
|
72
|
+
|
|
73
|
+
if (!attribute) {
|
|
74
|
+
return name;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return attribute.columnName || name;
|
|
78
|
+
};
|
|
79
|
+
|
|
54
80
|
module.exports = {
|
|
81
|
+
toRow,
|
|
55
82
|
fromRow,
|
|
83
|
+
toColumnName,
|
|
56
84
|
};
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
const _ = require('lodash/fp');
|
|
4
4
|
|
|
5
5
|
const types = require('../../types');
|
|
6
|
+
const { createField } = require('../../fields');
|
|
6
7
|
const { createJoin } = require('./join');
|
|
8
|
+
const { toColumnName } = require('./transform');
|
|
7
9
|
|
|
8
10
|
const GROUP_OPERATORS = ['$and', '$or'];
|
|
9
11
|
const OPERATORS = [
|
|
@@ -19,8 +21,6 @@ const OPERATORS = [
|
|
|
19
21
|
'$null',
|
|
20
22
|
'$notNull',
|
|
21
23
|
'$between',
|
|
22
|
-
// '$like',
|
|
23
|
-
// '$regexp',
|
|
24
24
|
'$startsWith',
|
|
25
25
|
'$endsWith',
|
|
26
26
|
'$contains',
|
|
@@ -29,10 +29,65 @@ const OPERATORS = [
|
|
|
29
29
|
'$notContainsi',
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
+
const CAST_OPERATORS = [
|
|
33
|
+
'$not',
|
|
34
|
+
'$in',
|
|
35
|
+
'$notIn',
|
|
36
|
+
'$eq',
|
|
37
|
+
'$ne',
|
|
38
|
+
'$gt',
|
|
39
|
+
'$gte',
|
|
40
|
+
'$lt',
|
|
41
|
+
'$lte',
|
|
42
|
+
'$between',
|
|
43
|
+
];
|
|
44
|
+
|
|
32
45
|
const ARRAY_OPERATORS = ['$in', '$notIn', '$between'];
|
|
33
46
|
|
|
34
47
|
const isOperator = key => OPERATORS.includes(key);
|
|
35
48
|
|
|
49
|
+
const castValue = (value, attribute) => {
|
|
50
|
+
if (!attribute) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (types.isScalar(attribute.type)) {
|
|
55
|
+
const field = createField(attribute);
|
|
56
|
+
|
|
57
|
+
return value === null ? null : field.toDB(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return value;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const processAttributeWhere = (attribute, where, operator = '$eq') => {
|
|
64
|
+
if (_.isArray(where)) {
|
|
65
|
+
return where.map(sub => processAttributeWhere(attribute, sub, operator));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!_.isPlainObject(where)) {
|
|
69
|
+
if (CAST_OPERATORS.includes(operator)) {
|
|
70
|
+
return castValue(where, attribute);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return where;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const filters = {};
|
|
77
|
+
|
|
78
|
+
for (const key in where) {
|
|
79
|
+
const value = where[key];
|
|
80
|
+
|
|
81
|
+
if (!isOperator(key)) {
|
|
82
|
+
throw new Error(`Undefined attribute level operator ${key}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
filters[key] = processAttributeWhere(attribute, value, key);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return filters;
|
|
89
|
+
};
|
|
90
|
+
|
|
36
91
|
/**
|
|
37
92
|
* Process where parameter
|
|
38
93
|
* @param {Object} where
|
|
@@ -40,9 +95,13 @@ const isOperator = key => OPERATORS.includes(key);
|
|
|
40
95
|
* @param {number} depth
|
|
41
96
|
* @returns {Object}
|
|
42
97
|
*/
|
|
43
|
-
const processWhere = (where, ctx
|
|
44
|
-
if (
|
|
45
|
-
throw new Error('Where must be an object');
|
|
98
|
+
const processWhere = (where, ctx) => {
|
|
99
|
+
if (!_.isArray(where) && !_.isPlainObject(where)) {
|
|
100
|
+
throw new Error('Where must be an array or an object');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (_.isArray(where)) {
|
|
104
|
+
return where.map(sub => processWhere(sub, ctx));
|
|
46
105
|
}
|
|
47
106
|
|
|
48
107
|
const processNested = (where, ctx) => {
|
|
@@ -50,17 +109,17 @@ const processWhere = (where, ctx, depth = 0) => {
|
|
|
50
109
|
return where;
|
|
51
110
|
}
|
|
52
111
|
|
|
53
|
-
return processWhere(where, ctx
|
|
112
|
+
return processWhere(where, ctx);
|
|
54
113
|
};
|
|
55
114
|
|
|
56
|
-
const { db, uid, qb, alias
|
|
115
|
+
const { db, uid, qb, alias } = ctx;
|
|
116
|
+
const meta = db.metadata.get(uid);
|
|
57
117
|
|
|
58
118
|
const filters = {};
|
|
59
119
|
|
|
60
120
|
// for each key in where
|
|
61
121
|
for (const key in where) {
|
|
62
122
|
const value = where[key];
|
|
63
|
-
const attribute = db.metadata.get(uid).attributes[key];
|
|
64
123
|
|
|
65
124
|
// if operator $and $or then loop over them
|
|
66
125
|
if (GROUP_OPERATORS.includes(key)) {
|
|
@@ -74,36 +133,24 @@ const processWhere = (where, ctx, depth = 0) => {
|
|
|
74
133
|
}
|
|
75
134
|
|
|
76
135
|
if (isOperator(key)) {
|
|
77
|
-
|
|
78
|
-
throw new Error(
|
|
79
|
-
`Only $and, $or and $not can by used as root level operators. Found ${key}.`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
filters[key] = processNested(value, ctx);
|
|
84
|
-
continue;
|
|
136
|
+
throw new Error(`Only $and, $or and $not can by used as root level operators. Found ${key}.`);
|
|
85
137
|
}
|
|
86
138
|
|
|
87
|
-
|
|
88
|
-
// TODO: if targeting a column name instead of an attribute
|
|
139
|
+
const attribute = meta.attributes[key];
|
|
89
140
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
filters[key] = processNested(value, ctx);
|
|
93
|
-
} else {
|
|
94
|
-
filters[`${alias || qb.alias}.${key}`] = processNested(value, ctx);
|
|
95
|
-
}
|
|
141
|
+
if (!attribute) {
|
|
142
|
+
filters[qb.aliasColumn(key, alias)] = processAttributeWhere(null, value);
|
|
96
143
|
continue;
|
|
97
|
-
|
|
98
|
-
// throw new Error(`Attribute ${key} not found on model ${uid}`);
|
|
99
144
|
}
|
|
100
145
|
|
|
101
|
-
|
|
102
|
-
if (attribute.type === 'relation') {
|
|
103
|
-
// TODO: pass down some filters (e.g published at)
|
|
104
|
-
|
|
146
|
+
if (types.isRelation(attribute.type)) {
|
|
105
147
|
// attribute
|
|
106
|
-
const subAlias = createJoin(ctx, {
|
|
148
|
+
const subAlias = createJoin(ctx, {
|
|
149
|
+
alias: alias || qb.alias,
|
|
150
|
+
uid,
|
|
151
|
+
attributeName: key,
|
|
152
|
+
attribute,
|
|
153
|
+
});
|
|
107
154
|
|
|
108
155
|
let nestedWhere = processNested(value, {
|
|
109
156
|
db,
|
|
@@ -113,7 +160,7 @@ const processWhere = (where, ctx, depth = 0) => {
|
|
|
113
160
|
});
|
|
114
161
|
|
|
115
162
|
if (!_.isPlainObject(nestedWhere) || isOperator(_.keys(nestedWhere)[0])) {
|
|
116
|
-
nestedWhere = { [
|
|
163
|
+
nestedWhere = { [qb.aliasColumn('id', subAlias)]: nestedWhere };
|
|
117
164
|
}
|
|
118
165
|
|
|
119
166
|
// TODO: use a better merge logic (push to $and when collisions)
|
|
@@ -123,9 +170,11 @@ const processWhere = (where, ctx, depth = 0) => {
|
|
|
123
170
|
}
|
|
124
171
|
|
|
125
172
|
if (types.isScalar(attribute.type)) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
173
|
+
const columnName = toColumnName(meta, key);
|
|
174
|
+
const aliasedColumnName = qb.aliasColumn(columnName, alias);
|
|
175
|
+
|
|
176
|
+
filters[aliasedColumnName] = processAttributeWhere(attribute, value);
|
|
177
|
+
|
|
129
178
|
continue;
|
|
130
179
|
}
|
|
131
180
|
|
|
@@ -135,6 +184,7 @@ const processWhere = (where, ctx, depth = 0) => {
|
|
|
135
184
|
return filters;
|
|
136
185
|
};
|
|
137
186
|
|
|
187
|
+
// TODO: add type casting per operator at some point
|
|
138
188
|
const applyOperator = (qb, column, operator, value) => {
|
|
139
189
|
if (Array.isArray(value) && !ARRAY_OPERATORS.includes(operator)) {
|
|
140
190
|
return qb.where(subQB => {
|
|
@@ -197,7 +247,6 @@ const applyOperator = (qb, column, operator, value) => {
|
|
|
197
247
|
break;
|
|
198
248
|
}
|
|
199
249
|
case '$null': {
|
|
200
|
-
// TODO: make this better
|
|
201
250
|
if (value) {
|
|
202
251
|
qb.whereNull(column);
|
|
203
252
|
}
|
|
@@ -207,26 +256,12 @@ const applyOperator = (qb, column, operator, value) => {
|
|
|
207
256
|
if (value) {
|
|
208
257
|
qb.whereNotNull(column);
|
|
209
258
|
}
|
|
210
|
-
|
|
211
259
|
break;
|
|
212
260
|
}
|
|
213
261
|
case '$between': {
|
|
214
262
|
qb.whereBetween(column, value);
|
|
215
263
|
break;
|
|
216
264
|
}
|
|
217
|
-
// case '$regexp': {
|
|
218
|
-
// // TODO:
|
|
219
|
-
//
|
|
220
|
-
// break;
|
|
221
|
-
// }
|
|
222
|
-
// // string
|
|
223
|
-
// // TODO: use $case to make it case insensitive
|
|
224
|
-
// case '$like': {
|
|
225
|
-
// qb.where(column, 'like', value);
|
|
226
|
-
// break;
|
|
227
|
-
// }
|
|
228
|
-
|
|
229
|
-
// TODO: add casting logic
|
|
230
265
|
case '$startsWith': {
|
|
231
266
|
qb.where(column, 'like', `${value}%`);
|
|
232
267
|
break;
|
|
@@ -260,7 +295,7 @@ const applyOperator = (qb, column, operator, value) => {
|
|
|
260
295
|
// TODO: relational operators every/some/exists/size ...
|
|
261
296
|
|
|
262
297
|
default: {
|
|
263
|
-
throw new Error(`Undefined operator ${operator}`);
|
|
298
|
+
throw new Error(`Undefined attribute level operator ${operator}`);
|
|
264
299
|
}
|
|
265
300
|
}
|
|
266
301
|
};
|
|
@@ -274,7 +309,6 @@ const applyWhereToColumn = (qb, column, columnWhere) => {
|
|
|
274
309
|
return qb.where(column, columnWhere);
|
|
275
310
|
}
|
|
276
311
|
|
|
277
|
-
// TODO: handle casing
|
|
278
312
|
Object.keys(columnWhere).forEach(operator => {
|
|
279
313
|
const value = columnWhere[operator];
|
|
280
314
|
|
|
@@ -283,12 +317,12 @@ const applyWhereToColumn = (qb, column, columnWhere) => {
|
|
|
283
317
|
};
|
|
284
318
|
|
|
285
319
|
const applyWhere = (qb, where) => {
|
|
286
|
-
if (
|
|
287
|
-
|
|
320
|
+
if (!_.isArray(where) && !_.isPlainObject(where)) {
|
|
321
|
+
throw new Error('Where must be an array or an object');
|
|
288
322
|
}
|
|
289
323
|
|
|
290
|
-
if (
|
|
291
|
-
|
|
324
|
+
if (_.isArray(where)) {
|
|
325
|
+
return qb.where(subQB => where.forEach(subWhere => applyWhere(subQB, subWhere)));
|
|
292
326
|
}
|
|
293
327
|
|
|
294
328
|
Object.keys(where).forEach(key => {
|
|
@@ -316,9 +350,10 @@ const applyWhere = (qb, where) => {
|
|
|
316
350
|
|
|
317
351
|
const fieldLowerFn = qb => {
|
|
318
352
|
// Postgres requires string to be passed
|
|
319
|
-
if (qb.client.config.client === '
|
|
353
|
+
if (qb.client.config.client === 'postgres') {
|
|
320
354
|
return 'LOWER(CAST(?? AS VARCHAR))';
|
|
321
355
|
}
|
|
356
|
+
|
|
322
357
|
return 'LOWER(??)';
|
|
323
358
|
};
|
|
324
359
|
|