objection 3.1.5 → 3.3.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 (69) hide show
  1. package/README.md +16 -16
  2. package/lib/model/AjvValidator.js +173 -18
  3. package/lib/model/Model.js +3 -1
  4. package/lib/model/graph/ModelGraphNode.js +12 -0
  5. package/lib/model/modelToJson.js +2 -1
  6. package/lib/queryBuilder/QueryBuilder.js +339 -47
  7. package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
  8. package/lib/queryBuilder/ReferenceBuilder.js +5 -3
  9. package/lib/queryBuilder/RelationExpression.js +21 -9
  10. package/lib/queryBuilder/StaticHookArguments.js +12 -1
  11. package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
  12. package/lib/queryBuilder/graph/GraphOptions.js +21 -2
  13. package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
  14. package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
  15. package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
  16. package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
  17. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
  18. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
  19. package/lib/queryBuilder/join/JoinResultParser.js +22 -0
  20. package/lib/queryBuilder/join/RelationJoiner.js +71 -5
  21. package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
  22. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
  23. package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
  24. package/lib/queryBuilder/operations/InsertOperation.js +121 -12
  25. package/lib/queryBuilder/operations/NoneOperation.js +37 -0
  26. package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
  27. package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
  28. package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
  29. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
  30. package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
  31. package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
  32. package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
  33. package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
  34. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
  35. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
  36. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
  37. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
  38. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
  39. package/lib/queryBuilder/operations/select/Selection.js +95 -4
  40. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
  41. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
  42. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
  43. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
  44. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
  45. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
  46. package/lib/queryBuilder/whereGroups.js +125 -0
  47. package/lib/relations/Relation.js +5 -2
  48. package/lib/relations/RelationProperty.js +7 -3
  49. package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
  50. package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
  51. package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
  52. package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
  53. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
  54. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
  55. package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
  56. package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
  57. package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
  58. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
  59. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
  60. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
  61. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
  62. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
  63. package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
  64. package/lib/utils/deprecate.js +6 -0
  65. package/lib/utils/identifierMapping.js +23 -3
  66. package/lib/utils/objectUtils.js +9 -5
  67. package/lib/utils/parseFieldExpression.js +28 -0
  68. package/package.json +22 -14
  69. package/typings/objection/index.d.ts +339 -68
@@ -15,7 +15,6 @@ class WhereInEagerOperation extends EagerOperation {
15
15
  super(name, opt);
16
16
 
17
17
  this.relationsToFetch = [];
18
- this.omitProps = [];
19
18
  }
20
19
 
21
20
  batchSize(knex) {
@@ -41,7 +40,7 @@ class WhereInEagerOperation extends EagerOperation {
41
40
  onBuild(builder) {
42
41
  const relationsToFetch = findRelationsToFetch(
43
42
  builder.modelClass(),
44
- this.buildFinalExpression(),
43
+ this.buildFinalExpression(builder),
45
44
  );
46
45
 
47
46
  const { selectionsToAdd, selectedProps } = findRelationPropsToSelect(builder, relationsToFetch);
@@ -50,8 +49,15 @@ class WhereInEagerOperation extends EagerOperation {
50
49
  builder.select(selectionsToAdd);
51
50
  }
52
51
 
52
+ // Omit the implicitly selected columns from the result once the relations
53
+ // have been fetched.
54
+ if (!builder.internalOptions().keepImplicitJoinProps) {
55
+ for (const prop of selectedProps) {
56
+ builder.addInternalSelection(prop);
57
+ }
58
+ }
59
+
53
60
  this.relationsToFetch = relationsToFetch;
54
- this.omitProps = selectedProps;
55
61
  }
56
62
 
57
63
  async onAfter2(builder, result) {
@@ -74,22 +80,6 @@ class WhereInEagerOperation extends EagerOperation {
74
80
  { concurrency: modelClass.getConcurrency(builder.unsafeKnex()) },
75
81
  );
76
82
 
77
- const intOpt = builder.internalOptions();
78
-
79
- if (!this.omitProps.length || intOpt.keepImplicitJoinProps) {
80
- return result;
81
- }
82
-
83
- // Now that relations have been fetched for `models` we can omit the
84
- // columns that were implicitly selected by this class.
85
- for (let i = 0, l = result.length; i < l; ++i) {
86
- const model = result[i];
87
-
88
- for (let c = 0, lc = this.omitProps.length; c < lc; ++c) {
89
- modelClass.omitImpl(model, this.omitProps[c]);
90
- }
91
- }
92
-
93
83
  return result;
94
84
  }
95
85
 
@@ -204,7 +194,6 @@ class WhereInEagerOperation extends EagerOperation {
204
194
  const clone = super.clone();
205
195
 
206
196
  clone.relationsToFetch = this.relationsToFetch.slice();
207
- clone.omitProps = this.omitProps.slice();
208
197
 
209
198
  return clone;
210
199
  }
@@ -7,7 +7,7 @@ class WhereJsonHasPostgresOperation extends ObjectionToKnexConvertingOperation {
7
7
  onBuildKnex(knexBuilder, builder) {
8
8
  const args = this.getKnexArgs(builder);
9
9
 
10
- const sql = jsonApi.whereJsonFieldRightStringArrayOnLeftQuery(
10
+ const rawArgs = jsonApi.whereJsonFieldRightStringArrayOnLeftQuery(
11
11
  builder.knex(),
12
12
  args[0],
13
13
  this.opt.operator,
@@ -15,9 +15,9 @@ class WhereJsonHasPostgresOperation extends ObjectionToKnexConvertingOperation {
15
15
  );
16
16
 
17
17
  if (this.opt.bool === 'or') {
18
- knexBuilder = knexBuilder.orWhereRaw(sql);
18
+ knexBuilder = knexBuilder.orWhereRaw(...rawArgs);
19
19
  } else {
20
- knexBuilder = knexBuilder.whereRaw(sql);
20
+ knexBuilder = knexBuilder.whereRaw(...rawArgs);
21
21
  }
22
22
 
23
23
  return knexBuilder;
@@ -18,7 +18,7 @@ class WhereJsonNotObjectPostgresOperation extends ObjectionToKnexConvertingOpera
18
18
  'not',
19
19
  );
20
20
 
21
- builder.orWhereRaw(jsonApi.whereJsonFieldQuery(knex, fieldExpression, 'IS', null));
21
+ builder.orWhereRaw(...jsonApi.whereJsonFieldQuery(knex, fieldExpression, 'IS', null));
22
22
  };
23
23
 
24
24
  if (this.opt.bool === 'or') {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const parser = require('../../../utils/parseFieldExpression');
4
4
  const { asArray, isObject, isString } = require('../../../utils/objectUtils');
5
+ const { isKnexRaw, isKnexQueryBuilder } = require('../../../utils/knexUtils');
5
6
 
6
7
  /**
7
8
  * @typedef {String} FieldExpression
@@ -38,16 +39,24 @@ const { asArray, isObject, isString } = require('../../../utils/objectUtils');
38
39
  * 4. Keys containing square brackets and quotes
39
40
  * `objectColumn:['Double."Quote".[]']` and `objectColumn:["Sinlge.'Quote'.[]"]`
40
41
  * Column `{ "Double.\"Quote\".[]" : "I was referred", "Single.'Quote'.[]" : "Mee too!" }`
42
+ * 5. Empty keys `objectColumn:[""]` or `objectColumn:['']` Column `{ "" : "I was referred" }`
41
43
  * 99. Keys containing dots, square brackets, single quotes and double quotes in one json key is
42
44
  * not currently supported
43
45
  */
44
46
 
47
+ // Returns the SQL and bindings of a field expression. The column is passed as an
48
+ // identifier binding (`??`), so that knex's `wrapIdentifier` (e.g. used by
49
+ // `knexSnakeCaseMappers()`) is applied to it.
45
50
  function parseFieldExpression(expression, extractAsText) {
46
51
  let parsed = parser.parseFieldExpression(expression);
47
- let jsonRefs = parsed.access.map((it) => it.ref).join(',');
52
+ let bindings = [parsed.columnName];
53
+ // Reference the bare column when there is no json path, so that Postgres can
54
+ // use indexes on it. `#>> '{}'` is still needed to extract the value as text.
55
+ if (parsed.access.length === 0 && !extractAsText) {
56
+ return ['??', bindings];
57
+ }
48
58
  let extractor = extractAsText ? '#>>' : '#>';
49
- let middleQuotedColumnName = parsed.columnName.split('.').join('"."');
50
- return `"${middleQuotedColumnName}"${extractor}'{${jsonRefs}}'`;
59
+ return [`??${extractor}${parser.toPostgresJsonPathLiteral(parsed.access)}`, bindings];
51
60
  }
52
61
 
53
62
  function whereJsonbRefOnLeftJsonbValOrRefOnRight(
@@ -72,36 +81,36 @@ function whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(
72
81
  jsonObjectOrFieldExpression,
73
82
  queryPrefix,
74
83
  ) {
75
- let fieldReference = parseFieldExpression(fieldExpression);
84
+ let [fieldReference, bindings] = parseFieldExpression(fieldExpression);
85
+ let [rightHandSql, rightHandBindings] = rightHandExpression(jsonObjectOrFieldExpression);
86
+ let query = ['(', fieldReference, ')::jsonb', operator, rightHandSql];
87
+
88
+ if (queryPrefix) {
89
+ query.unshift(queryPrefix);
90
+ }
91
+
92
+ return [query.join(' '), [...bindings, ...rightHandBindings]];
93
+ }
76
94
 
95
+ function rightHandExpression(jsonObjectOrFieldExpression) {
77
96
  if (isString(jsonObjectOrFieldExpression)) {
78
- let rightHandReference = parseFieldExpression(jsonObjectOrFieldExpression);
79
- let refRefQuery = [
80
- '(',
81
- fieldReference,
82
- ')::jsonb',
83
- operator,
84
- '(',
85
- rightHandReference,
86
- ')::jsonb',
87
- ];
88
- if (queryPrefix) {
89
- refRefQuery.unshift(queryPrefix);
90
- }
91
- return [refRefQuery.join(' ')];
97
+ let [rightHandReference, bindings] = parseFieldExpression(jsonObjectOrFieldExpression);
98
+ return [`( ${rightHandReference} )::jsonb`, bindings];
99
+ } else if (
100
+ isKnexRaw(jsonObjectOrFieldExpression) ||
101
+ isKnexQueryBuilder(jsonObjectOrFieldExpression)
102
+ ) {
103
+ // ref(), val(), raw() and subqueries, already converted to knex by the operation.
104
+ return ['( ? )::jsonb', [jsonObjectOrFieldExpression]];
92
105
  } else if (isObject(jsonObjectOrFieldExpression)) {
93
- let refValQuery = ['(', fieldReference, ')::jsonb', operator, '?::jsonb'];
94
- if (queryPrefix) {
95
- refValQuery.unshift(queryPrefix);
96
- }
97
- return [refValQuery.join(' '), JSON.stringify(jsonObjectOrFieldExpression)];
106
+ return ['?::jsonb', [JSON.stringify(jsonObjectOrFieldExpression)]];
98
107
  }
99
108
 
100
109
  throw new Error('Invalid right hand expression.');
101
110
  }
102
111
 
103
112
  function whereJsonFieldRightStringArrayOnLeftQuery(knex, fieldExpression, operator, keys) {
104
- let fieldReference = parseFieldExpression(fieldExpression);
113
+ let [fieldReference, bindings] = parseFieldExpression(fieldExpression);
105
114
  keys = asArray(keys);
106
115
 
107
116
  let questionMarksArray = keys.map((key) => {
@@ -111,19 +120,19 @@ function whereJsonFieldRightStringArrayOnLeftQuery(knex, fieldExpression, operat
111
120
  return '?';
112
121
  });
113
122
 
114
- let rawSqlTemplateString = 'array[' + questionMarksArray.join(',') + ']';
115
- let rightHandExpression = knex.raw(rawSqlTemplateString, keys);
116
-
117
- return `${fieldReference} ${operator.replace('?', '\\?')} ${rightHandExpression}`;
123
+ return [
124
+ `${fieldReference} ${operator.replace('?', '\\?')} array[${questionMarksArray.join(',')}]`,
125
+ [...bindings, ...keys],
126
+ ];
118
127
  }
119
128
 
120
129
  function whereJsonFieldQuery(knex, fieldExpression, operator, value) {
121
- let fieldReference = parseFieldExpression(fieldExpression, true);
130
+ let [fieldReference, bindings] = parseFieldExpression(fieldExpression, true);
122
131
  let normalizedOperator = normalizeOperator(knex, operator);
123
132
 
124
133
  // json type comparison takes json type in string format
125
134
  let cast;
126
- let escapedValue = knex.raw(' ?', [value]);
135
+ let valuePlaceholder = '?';
127
136
  let type = typeof value;
128
137
 
129
138
  if (type === 'number') {
@@ -134,12 +143,16 @@ function whereJsonFieldQuery(knex, fieldExpression, operator, value) {
134
143
  cast = '::TEXT';
135
144
  } else if (value === null) {
136
145
  cast = '::TEXT';
137
- escapedValue = 'NULL';
146
+ valuePlaceholder = 'NULL';
138
147
  } else {
139
148
  throw new Error('Value must be string, number, boolean or null.');
140
149
  }
141
150
 
142
- return `(${fieldReference})${cast} ${normalizedOperator} ${escapedValue}`;
151
+ // Bind the value so that `?` in it isn't read as a binding placeholder.
152
+ return [
153
+ `(${fieldReference})${cast} ${normalizedOperator} ${valuePlaceholder}`,
154
+ value === null ? bindings : [...bindings, value],
155
+ ];
143
156
  }
144
157
 
145
158
  function normalizeOperator(knex, operator) {
@@ -1,14 +1,21 @@
1
1
  'use strict';
2
2
 
3
- const { isString, isObject } = require('../../../utils/objectUtils');
3
+ const { isString, isObject, isPlainObject } = require('../../../utils/objectUtils');
4
+ const { isKnexQueryBuilder, isKnexRaw } = require('../../../utils/knexUtils');
4
5
 
5
6
  const ALIAS_REGEX = /\s+as\s+/i;
6
7
 
8
+ // Matches an alias at the end of a raw sql string: `as "alias"`, `as \`alias\``,
9
+ // `as [alias]`, `as ??`, `as :name:` or `as alias`.
10
+ const RAW_ALIAS_REGEX =
11
+ /\s+as\s+(?:"([^"]+)"|`([^`]+)`|\[([^\]]+)\]|(\?\?)|:(\w+):|([a-z_][\w$]*))\s*$/i;
12
+
7
13
  class Selection {
8
14
  constructor(table, column, alias) {
9
15
  this.table = table;
10
16
  this.column = column;
11
17
  this.alias = alias;
18
+ this.keepsSelectAll = false;
12
19
  }
13
20
 
14
21
  get name() {
@@ -23,6 +30,12 @@ class Selection {
23
30
  return createSelectionFromReference(selection);
24
31
  } else if (selection.isObjectionRawBuilder) {
25
32
  return createSelectionFromRaw(selection);
33
+ } else if (selection.isObjectionQueryBuilderBase) {
34
+ return createSelectionFromQueryBuilder(selection);
35
+ } else if (isKnexQueryBuilder(selection)) {
36
+ return createSelectionFromAlias(selection._single.as);
37
+ } else if (isKnexRaw(selection)) {
38
+ return createSelectionFromAlias(parseRawAlias(selection.sql, selection.bindings));
26
39
  } else {
27
40
  return null;
28
41
  }
@@ -50,10 +63,11 @@ class Selection {
50
63
  if (selectionInBuilder.column === '*') {
51
64
  if (selectionInBuilder.table) {
52
65
  if (selectionToTest.column === '*') {
53
- return selectionToTest.table === selectionInBuilder.table;
66
+ return isSameTable(builder, selectionToTest.table, selectionInBuilder.table);
54
67
  } else {
55
68
  return (
56
- selectionToTest.table === null || selectionToTest.table === selectionInBuilder.table
69
+ selectionToTest.table === null ||
70
+ isSameTable(builder, selectionToTest.table, selectionInBuilder.table)
57
71
  );
58
72
  }
59
73
  } else {
@@ -67,7 +81,8 @@ class Selection {
67
81
  } else {
68
82
  return (
69
83
  selectionToTest.column === selectionInBuilder.column &&
70
- (selectionToTest.table === null || selectionToTest.table === selectionInBuilderTable)
84
+ (selectionToTest.table === null ||
85
+ isSameTable(builder, selectionToTest.table, selectionInBuilderTable))
71
86
  );
72
87
  }
73
88
  }
@@ -82,6 +97,36 @@ Object.defineProperties(Selection.prototype, {
82
97
  },
83
98
  });
84
99
 
100
+ // Table names that only differ in a way that knex's `wrapIdentifier` mapping
101
+ // (for example `knexSnakeCaseMappers()`) cancels out refer to the same table:
102
+ // `dataExtension` and `data_extension` both end up as `data_extension`.
103
+ function isSameTable(builder, table1, table2) {
104
+ if (table1 === table2) {
105
+ return true;
106
+ } else if (!table1 || !table2) {
107
+ return false;
108
+ }
109
+
110
+ const knex = builder.unsafeKnex();
111
+ const wrapIdentifier = knex && knex.client && knex.client.config.wrapIdentifier;
112
+
113
+ if (!wrapIdentifier) {
114
+ return false;
115
+ }
116
+
117
+ const mapTable = (table) =>
118
+ table
119
+ .split('.')
120
+ .map((part) => wrapIdentifier(part, identity))
121
+ .join('.');
122
+
123
+ return mapTable(table1) === mapTable(table2);
124
+ }
125
+
126
+ function identity(value) {
127
+ return value;
128
+ }
129
+
85
130
  function createSelectionFromReference(ref) {
86
131
  return new Selection(ref.tableName, ref.column, ref.alias);
87
132
  }
@@ -89,6 +134,52 @@ function createSelectionFromReference(ref) {
89
134
  function createSelectionFromRaw(raw) {
90
135
  if (raw.alias) {
91
136
  return new Selection(null, null, raw.alias);
137
+ } else {
138
+ return createSelectionFromAlias(parseRawAlias(raw._sql, raw._args));
139
+ }
140
+ }
141
+
142
+ function parseRawAlias(sql, bindings) {
143
+ const match = isString(sql) && RAW_ALIAS_REGEX.exec(sql);
144
+
145
+ if (!match) {
146
+ return null;
147
+ }
148
+
149
+ const [, doubleQuoted, backQuoted, bracketQuoted, binding, namedBinding, unquoted] = match;
150
+
151
+ if (binding) {
152
+ return Array.isArray(bindings) ? bindings[bindings.length - 1] : null;
153
+ } else if (namedBinding) {
154
+ if (Array.isArray(bindings) && bindings.length === 1) {
155
+ // `raw(sql, { name: value })` of objection.
156
+ bindings = bindings[0];
157
+ }
158
+
159
+ return isPlainObject(bindings) ? bindings[namedBinding] : null;
160
+ } else if (unquoted) {
161
+ // Some databases, like postgres, fold unquoted identifiers to lower case
162
+ // and others don't, so only lower case ones can be used reliably.
163
+ return unquoted === unquoted.toLowerCase() ? unquoted : null;
164
+ } else {
165
+ return doubleQuoted || backQuoted || bracketQuoted;
166
+ }
167
+ }
168
+
169
+ // Subqueries aliased using `as()` select a column with the alias as name.
170
+ function createSelectionFromQueryBuilder(builder) {
171
+ const asOperation = builder.findLastOperation('as');
172
+ return createSelectionFromAlias(asOperation && asOperation.args[0]);
173
+ }
174
+
175
+ function createSelectionFromAlias(alias) {
176
+ if (isString(alias)) {
177
+ const selection = new Selection(null, null, alias);
178
+ // These selections weren't recognized in the past. To not change which
179
+ // other columns `withGraphJoined` selects, they don't count as explicit
180
+ // selections when deciding whether all columns are selected.
181
+ selection.keepsSelectAll = true;
182
+ return selection;
92
183
  } else {
93
184
  return null;
94
185
  }
@@ -26,6 +26,12 @@ class WhereInCompositeMsSqlOperation extends ObjectionToKnexConvertingOperation
26
26
  }
27
27
 
28
28
  buildComposite(knex, knexBuilder, columns, values) {
29
+ if (Array.isArray(values) && values.length === 0) {
30
+ // knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
31
+ // into `1 = 1`.
32
+ return this.whereIn(knexBuilder, columns, []);
33
+ }
34
+
29
35
  const helperColumns = columns.map((_, index) => getTempColumn(index));
30
36
 
31
37
  if (Array.isArray(values)) {
@@ -48,7 +48,11 @@ function buildCompositeArgs(knex, columns, values) {
48
48
  }
49
49
 
50
50
  function buildCompositeValueArgs(columns, values) {
51
- if (!Array.isArray(values[0])) {
51
+ if (values.length === 0) {
52
+ // knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
53
+ // into `1 = 1`.
54
+ return [columns, []];
55
+ } else if (!Array.isArray(values[0])) {
52
56
  return [columns, [values]];
53
57
  } else {
54
58
  return [columns, values];
@@ -54,6 +54,15 @@ function buildCompositeArgs(columns, values) {
54
54
  throw new Error(`sqlite doesn't support multi-column where in clauses`);
55
55
  }
56
56
 
57
+ if (values.length === 0) {
58
+ // knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
59
+ // into `1 = 1`.
60
+ return {
61
+ method: 'whereIn',
62
+ args: [columns, []],
63
+ };
64
+ }
65
+
57
66
  // Sqlite doesn't support the `where in` syntax for multiple columns but
58
67
  // we can emulate it using grouped `or` clauses.
59
68
  return {
@@ -166,10 +166,10 @@ function peg$parse(input, options) {
166
166
  peg$c4 = peg$literalExpectation('[', false),
167
167
  peg$c5 = '"',
168
168
  peg$c6 = peg$literalExpectation('"', false),
169
- peg$c7 = "'",
170
- peg$c8 = peg$literalExpectation("'", false),
171
- peg$c9 = ']',
172
- peg$c10 = peg$literalExpectation(']', false),
169
+ peg$c7 = ']',
170
+ peg$c8 = peg$literalExpectation(']', false),
171
+ peg$c9 = "'",
172
+ peg$c10 = peg$literalExpectation("'", false),
173
173
  peg$c11 = function (key) {
174
174
  return { type: 'object', ref: Array.isArray(key) ? key[1] : key };
175
175
  },
@@ -415,7 +415,7 @@ function peg$parse(input, options) {
415
415
  }
416
416
 
417
417
  function peg$parsebracketStringRef() {
418
- var s0, s1, s2, s3, s4, s5;
418
+ var s0, s1, s2, s3, s4, s5, s6, s7;
419
419
 
420
420
  s0 = peg$currPos;
421
421
  if (input.charCodeAt(peg$currPos) === 91) {
@@ -451,8 +451,31 @@ function peg$parse(input, options) {
451
451
  }
452
452
  }
453
453
  if (s5 !== peg$FAILED) {
454
- s3 = [s3, s4, s5];
455
- s2 = s3;
454
+ s6 = peg$currPos;
455
+ peg$silentFails++;
456
+ if (input.charCodeAt(peg$currPos) === 93) {
457
+ s7 = peg$c7;
458
+ peg$currPos++;
459
+ } else {
460
+ s7 = peg$FAILED;
461
+ if (peg$silentFails === 0) {
462
+ peg$fail(peg$c8);
463
+ }
464
+ }
465
+ peg$silentFails--;
466
+ if (s7 !== peg$FAILED) {
467
+ peg$currPos = s6;
468
+ s6 = void 0;
469
+ } else {
470
+ s6 = peg$FAILED;
471
+ }
472
+ if (s6 !== peg$FAILED) {
473
+ s3 = [s3, s4, s5, s6];
474
+ s2 = s3;
475
+ } else {
476
+ peg$currPos = s2;
477
+ s2 = peg$FAILED;
478
+ }
456
479
  } else {
457
480
  peg$currPos = s2;
458
481
  s2 = peg$FAILED;
@@ -468,29 +491,52 @@ function peg$parse(input, options) {
468
491
  if (s2 === peg$FAILED) {
469
492
  s2 = peg$currPos;
470
493
  if (input.charCodeAt(peg$currPos) === 39) {
471
- s3 = peg$c7;
494
+ s3 = peg$c9;
472
495
  peg$currPos++;
473
496
  } else {
474
497
  s3 = peg$FAILED;
475
498
  if (peg$silentFails === 0) {
476
- peg$fail(peg$c8);
499
+ peg$fail(peg$c10);
477
500
  }
478
501
  }
479
502
  if (s3 !== peg$FAILED) {
480
503
  s4 = peg$parsestringWithoutSingleQuotes();
481
504
  if (s4 !== peg$FAILED) {
482
505
  if (input.charCodeAt(peg$currPos) === 39) {
483
- s5 = peg$c7;
506
+ s5 = peg$c9;
484
507
  peg$currPos++;
485
508
  } else {
486
509
  s5 = peg$FAILED;
487
510
  if (peg$silentFails === 0) {
488
- peg$fail(peg$c8);
511
+ peg$fail(peg$c10);
489
512
  }
490
513
  }
491
514
  if (s5 !== peg$FAILED) {
492
- s3 = [s3, s4, s5];
493
- s2 = s3;
515
+ s6 = peg$currPos;
516
+ peg$silentFails++;
517
+ if (input.charCodeAt(peg$currPos) === 93) {
518
+ s7 = peg$c7;
519
+ peg$currPos++;
520
+ } else {
521
+ s7 = peg$FAILED;
522
+ if (peg$silentFails === 0) {
523
+ peg$fail(peg$c8);
524
+ }
525
+ }
526
+ peg$silentFails--;
527
+ if (s7 !== peg$FAILED) {
528
+ peg$currPos = s6;
529
+ s6 = void 0;
530
+ } else {
531
+ s6 = peg$FAILED;
532
+ }
533
+ if (s6 !== peg$FAILED) {
534
+ s3 = [s3, s4, s5, s6];
535
+ s2 = s3;
536
+ } else {
537
+ peg$currPos = s2;
538
+ s2 = peg$FAILED;
539
+ }
494
540
  } else {
495
541
  peg$currPos = s2;
496
542
  s2 = peg$FAILED;
@@ -509,12 +555,12 @@ function peg$parse(input, options) {
509
555
  }
510
556
  if (s2 !== peg$FAILED) {
511
557
  if (input.charCodeAt(peg$currPos) === 93) {
512
- s3 = peg$c9;
558
+ s3 = peg$c7;
513
559
  peg$currPos++;
514
560
  } else {
515
561
  s3 = peg$FAILED;
516
562
  if (peg$silentFails === 0) {
517
- peg$fail(peg$c10);
563
+ peg$fail(peg$c8);
518
564
  }
519
565
  }
520
566
  if (s3 !== peg$FAILED) {
@@ -554,12 +600,12 @@ function peg$parse(input, options) {
554
600
  s2 = peg$parseinteger();
555
601
  if (s2 !== peg$FAILED) {
556
602
  if (input.charCodeAt(peg$currPos) === 93) {
557
- s3 = peg$c9;
603
+ s3 = peg$c7;
558
604
  peg$currPos++;
559
605
  } else {
560
606
  s3 = peg$FAILED;
561
607
  if (peg$silentFails === 0) {
562
- peg$fail(peg$c10);
608
+ peg$fail(peg$c8);
563
609
  }
564
610
  }
565
611
  if (s3 !== peg$FAILED) {
@@ -719,21 +765,17 @@ function peg$parse(input, options) {
719
765
  peg$fail(peg$c22);
720
766
  }
721
767
  }
722
- if (s2 !== peg$FAILED) {
723
- while (s2 !== peg$FAILED) {
724
- s1.push(s2);
725
- if (peg$c21.test(input.charAt(peg$currPos))) {
726
- s2 = input.charAt(peg$currPos);
727
- peg$currPos++;
728
- } else {
729
- s2 = peg$FAILED;
730
- if (peg$silentFails === 0) {
731
- peg$fail(peg$c22);
732
- }
768
+ while (s2 !== peg$FAILED) {
769
+ s1.push(s2);
770
+ if (peg$c21.test(input.charAt(peg$currPos))) {
771
+ s2 = input.charAt(peg$currPos);
772
+ peg$currPos++;
773
+ } else {
774
+ s2 = peg$FAILED;
775
+ if (peg$silentFails === 0) {
776
+ peg$fail(peg$c22);
733
777
  }
734
778
  }
735
- } else {
736
- s1 = peg$FAILED;
737
779
  }
738
780
  if (s1 !== peg$FAILED) {
739
781
  peg$savedPos = s0;
@@ -758,21 +800,17 @@ function peg$parse(input, options) {
758
800
  peg$fail(peg$c24);
759
801
  }
760
802
  }
761
- if (s2 !== peg$FAILED) {
762
- while (s2 !== peg$FAILED) {
763
- s1.push(s2);
764
- if (peg$c23.test(input.charAt(peg$currPos))) {
765
- s2 = input.charAt(peg$currPos);
766
- peg$currPos++;
767
- } else {
768
- s2 = peg$FAILED;
769
- if (peg$silentFails === 0) {
770
- peg$fail(peg$c24);
771
- }
803
+ while (s2 !== peg$FAILED) {
804
+ s1.push(s2);
805
+ if (peg$c23.test(input.charAt(peg$currPos))) {
806
+ s2 = input.charAt(peg$currPos);
807
+ peg$currPos++;
808
+ } else {
809
+ s2 = peg$FAILED;
810
+ if (peg$silentFails === 0) {
811
+ peg$fail(peg$c24);
772
812
  }
773
813
  }
774
- } else {
775
- s1 = peg$FAILED;
776
814
  }
777
815
  if (s1 !== peg$FAILED) {
778
816
  peg$savedPos = s0;
@@ -31,6 +31,7 @@
31
31
  * 4. Keys containing square brackets and quotes
32
32
  * `objectColumn:['Double."Quote".[]']` and `objectColumn:["Sinlge.'Quote'.[]"]`
33
33
  * Column `{ "Double.\"Quote\".[]" : "I was referred", "Sinlge.'Quote'.[]" : "Mee too!" }`
34
+ * 5. Empty keys `objectColumn:[""]` or `objectColumn:['']` Column `{ "" : "I was referred" }`
34
35
  * 99. Keys containing dots, square brackets, single quotes and double quotes in one json key is
35
36
  * not currently supported
36
37
  *
@@ -60,8 +61,8 @@ start =
60
61
  bracketStringRef =
61
62
  '['
62
63
  key:(
63
- '"' stringWithoutDoubleQuotes '"' /
64
- "'" stringWithoutSingleQuotes "'" /
64
+ '"' stringWithoutDoubleQuotes '"' &']' /
65
+ "'" stringWithoutSingleQuotes "'" &']' /
65
66
  stringWithoutSquareBrackets
66
67
  )
67
68
  ']'
@@ -86,10 +87,10 @@ stringWithoutColon =
86
87
  chars:([^:])+ { return chars.join(""); }
87
88
 
88
89
  stringWithoutDoubleQuotes =
89
- chars:([^"])+ { return chars.join(""); }
90
+ chars:([^"])* { return chars.join(""); }
90
91
 
91
92
  stringWithoutSingleQuotes =
92
- chars:([^'])+ { return chars.join(""); }
93
+ chars:([^'])* { return chars.join(""); }
93
94
 
94
95
  stringWithoutSquareBracketsOrDots =
95
96
  chars:([^.\x5D\x5B])+ { return chars.join(""); }