@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.
Files changed (44) hide show
  1. package/lib/dialects/dialect.js +45 -0
  2. package/lib/dialects/index.js +6 -112
  3. package/lib/dialects/mysql/index.js +51 -0
  4. package/lib/dialects/mysql/schema-inspector.js +199 -0
  5. package/lib/dialects/postgresql/index.js +49 -0
  6. package/lib/dialects/postgresql/schema-inspector.js +232 -0
  7. package/lib/dialects/sqlite/index.js +74 -0
  8. package/lib/dialects/sqlite/schema-inspector.js +151 -0
  9. package/lib/entity-manager.js +18 -14
  10. package/lib/entity-repository.js +2 -3
  11. package/lib/errors.js +44 -2
  12. package/lib/fields.d.ts +2 -3
  13. package/lib/fields.js +7 -16
  14. package/lib/index.d.ts +53 -8
  15. package/lib/index.js +44 -27
  16. package/lib/lifecycles/index.d.ts +50 -0
  17. package/lib/{lifecycles.js → lifecycles/index.js} +25 -14
  18. package/lib/lifecycles/subscribers/index.d.ts +9 -0
  19. package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
  20. package/lib/lifecycles/subscribers/timestamps.js +65 -0
  21. package/lib/metadata/index.js +84 -95
  22. package/lib/metadata/relations.js +16 -0
  23. package/lib/migrations/index.d.ts +9 -0
  24. package/lib/migrations/index.js +69 -0
  25. package/lib/migrations/storage.js +51 -0
  26. package/lib/query/helpers/join.js +3 -5
  27. package/lib/query/helpers/order-by.js +21 -11
  28. package/lib/query/helpers/populate.js +35 -10
  29. package/lib/query/helpers/search.js +26 -12
  30. package/lib/query/helpers/transform.js +42 -14
  31. package/lib/query/helpers/where.js +92 -57
  32. package/lib/query/query-builder.js +116 -34
  33. package/lib/schema/__tests__/schema-diff.test.js +14 -1
  34. package/lib/schema/builder.js +315 -284
  35. package/lib/schema/diff.js +376 -0
  36. package/lib/schema/index.d.ts +49 -0
  37. package/lib/schema/index.js +47 -50
  38. package/lib/schema/schema.js +21 -18
  39. package/lib/schema/storage.js +79 -0
  40. package/lib/utils/content-types.js +0 -1
  41. package/package.json +27 -21
  42. package/lib/configuration.js +0 -49
  43. package/lib/schema/schema-diff.js +0 -337
  44. 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 = qb.alias } = ctx;
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 = db.metadata.get(uid).attributes[orderBy];
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
- return [{ column: `${alias}.${orderBy}` }];
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 = db.metadata.get(uid).attributes[key];
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
- // TODO: pass down some filters (e.g published at)
37
- const subAlias = createJoin(ctx, { alias, uid, attributeName: key, attribute });
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(['select', 'count', 'where', 'populate', 'orderBy']);
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 = pickPopulateParams(populate[key]);
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: 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: 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: 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: 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: 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: 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 = (qb, query, ctx) => {
8
- const { alias, uid, db } = ctx;
8
+ const applySearch = (knex, query, ctx) => {
9
+ const { qb, uid, db } = ctx;
10
+ const meta = db.metadata.get(uid);
9
11
 
10
- const { attributes } = db.metadata.get(uid);
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
- qb.orWhereRaw(`"${alias}"."${attr}"::text ILIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
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
- qb.orWhereRaw(`"${alias}"."${attr}" LIKE ? ESCAPE '\\'`, `%${escapeQuery(query, '*%\\')}%`)
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
- qb.orWhereRaw(`\`${alias}\`.\`${attr}\` LIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
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 = (metadata, row) => {
8
+ const fromRow = (meta, row) => {
9
9
  if (Array.isArray(row)) {
10
- return row.map(singleRow => fromRow(metadata, singleRow));
10
+ return row.map(singleRow => fromRow(meta, singleRow));
11
11
  }
12
12
 
13
- const { attributes } = metadata;
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
- // to field Name
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, depth = 0) => {
44
- if (depth === 0 && !_.isPlainObject(where)) {
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, depth + 1);
112
+ return processWhere(where, ctx);
54
113
  };
55
114
 
56
- const { db, uid, qb, alias = qb.alias } = ctx;
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
- if (depth == 0) {
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
- if (!attribute) {
88
- // TODO: if targeting a column name instead of an attribute
139
+ const attribute = meta.attributes[key];
89
140
 
90
- // if key as an alias don't add one
91
- if (key.indexOf('.') >= 0) {
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
- // move to if else to check for scalar / relation / components & throw for other types
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, { alias, uid, attributeName: key, attribute });
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 = { [`${subAlias}.id`]: 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
- // TODO: convert attribute name to column name
127
- // TODO: cast to DB type
128
- filters[`${alias || qb.alias}.${key}`] = processNested(value, ctx);
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 (Array.isArray(where)) {
287
- return qb.where(subQB => where.forEach(subWhere => applyWhere(subQB, subWhere)));
320
+ if (!_.isArray(where) && !_.isPlainObject(where)) {
321
+ throw new Error('Where must be an array or an object');
288
322
  }
289
323
 
290
- if (!_.isPlainObject(where)) {
291
- throw new Error('Where must be an object');
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 === 'pg') {
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