@abedmurrar/objection 4.0.0-alpha.0 → 4.0.0-beta.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 (192) hide show
  1. package/README.md +103 -67
  2. package/lib/index.mjs +6 -0
  3. package/lib/initialize.js +6 -9
  4. package/lib/model/AjvValidator.js +235 -282
  5. package/lib/model/Model.js +653 -823
  6. package/lib/model/ModifierNotFoundError.js +5 -7
  7. package/lib/model/NotFoundError.js +14 -18
  8. package/lib/model/RelationDoesNotExistError.js +6 -9
  9. package/lib/model/ValidationError.js +33 -41
  10. package/lib/model/Validator.js +15 -21
  11. package/lib/model/getModel.js +1 -3
  12. package/lib/model/graph/ModelGraph.js +44 -64
  13. package/lib/model/graph/ModelGraphBuilder.js +164 -217
  14. package/lib/model/graph/ModelGraphEdge.js +31 -43
  15. package/lib/model/graph/ModelGraphNode.js +192 -231
  16. package/lib/model/inheritModel.js +9 -18
  17. package/lib/model/modelBindKnex.js +48 -62
  18. package/lib/model/modelClone.js +79 -98
  19. package/lib/model/modelColPropMap.js +16 -26
  20. package/lib/model/modelConcurrency.js +24 -0
  21. package/lib/model/modelId.js +44 -49
  22. package/lib/model/modelJsonAttributes.js +56 -76
  23. package/lib/model/modelParseRelations.js +68 -67
  24. package/lib/model/modelQueryProps.js +76 -107
  25. package/lib/model/modelSet.js +92 -112
  26. package/lib/model/modelTableMetadata.js +60 -72
  27. package/lib/model/modelToJson.js +120 -150
  28. package/lib/model/modelUtils.js +30 -37
  29. package/lib/model/modelValidate.js +34 -42
  30. package/lib/model/modelValues.js +78 -91
  31. package/lib/model/modelVisitor.js +22 -29
  32. package/lib/objection.js +42 -67
  33. package/lib/queryBuilder/FunctionBuilder.js +231 -279
  34. package/lib/queryBuilder/InternalOptions.js +19 -24
  35. package/lib/queryBuilder/JoinBuilder.js +116 -149
  36. package/lib/queryBuilder/QueryBuilder.js +1009 -1271
  37. package/lib/queryBuilder/QueryBuilderBase.js +647 -865
  38. package/lib/queryBuilder/QueryBuilderContext.js +14 -21
  39. package/lib/queryBuilder/QueryBuilderContextBase.js +20 -29
  40. package/lib/queryBuilder/QueryBuilderOperationSupport.js +462 -536
  41. package/lib/queryBuilder/QueryBuilderUserContext.js +19 -25
  42. package/lib/queryBuilder/RawBuilder.js +58 -73
  43. package/lib/queryBuilder/ReferenceBuilder.js +232 -270
  44. package/lib/queryBuilder/RelationExpression.js +322 -380
  45. package/lib/queryBuilder/StaticHookArguments.js +94 -106
  46. package/lib/queryBuilder/ValueBuilder.js +100 -112
  47. package/lib/queryBuilder/expressions.js +86 -116
  48. package/lib/queryBuilder/functions/mysql.js +642 -697
  49. package/lib/queryBuilder/graph/GraphAction.js +44 -59
  50. package/lib/queryBuilder/graph/GraphData.js +7 -9
  51. package/lib/queryBuilder/graph/GraphFetcher.js +115 -151
  52. package/lib/queryBuilder/graph/GraphNodeDbExistence.js +114 -152
  53. package/lib/queryBuilder/graph/GraphOperation.js +16 -22
  54. package/lib/queryBuilder/graph/GraphOptions.js +143 -185
  55. package/lib/queryBuilder/graph/GraphUpsert.js +167 -241
  56. package/lib/queryBuilder/graph/delete/GraphDelete.js +8 -13
  57. package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +44 -62
  58. package/lib/queryBuilder/graph/insert/GraphInsert.js +99 -128
  59. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +151 -193
  60. package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +57 -79
  61. package/lib/queryBuilder/graph/patch/GraphPatch.js +8 -13
  62. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +181 -234
  63. package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +16 -21
  64. package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +80 -110
  65. package/lib/queryBuilder/join/JoinResultColumn.js +14 -18
  66. package/lib/queryBuilder/join/JoinResultParser.js +88 -101
  67. package/lib/queryBuilder/join/RelationJoiner.js +205 -288
  68. package/lib/queryBuilder/join/TableNode.js +117 -144
  69. package/lib/queryBuilder/join/TableTree.js +60 -76
  70. package/lib/queryBuilder/join/utils.js +10 -16
  71. package/lib/queryBuilder/operations/DelegateOperation.js +93 -133
  72. package/lib/queryBuilder/operations/DeleteOperation.js +33 -37
  73. package/lib/queryBuilder/operations/FindByIdOperation.js +20 -28
  74. package/lib/queryBuilder/operations/FindByIdsOperation.js +17 -23
  75. package/lib/queryBuilder/operations/FindOperation.js +114 -131
  76. package/lib/queryBuilder/operations/FirstOperation.js +14 -19
  77. package/lib/queryBuilder/operations/FromOperation.js +69 -88
  78. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +28 -39
  79. package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +25 -35
  80. package/lib/queryBuilder/operations/InsertGraphOperation.js +77 -98
  81. package/lib/queryBuilder/operations/InsertOperation.js +132 -165
  82. package/lib/queryBuilder/operations/InstanceDeleteOperation.js +27 -36
  83. package/lib/queryBuilder/operations/InstanceFindOperation.js +9 -13
  84. package/lib/queryBuilder/operations/InstanceInsertOperation.js +13 -17
  85. package/lib/queryBuilder/operations/InstanceUpdateOperation.js +32 -46
  86. package/lib/queryBuilder/operations/JoinRelatedOperation.js +66 -86
  87. package/lib/queryBuilder/operations/KnexOperation.js +4 -7
  88. package/lib/queryBuilder/operations/MergeOperation.js +32 -45
  89. package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +131 -150
  90. package/lib/queryBuilder/operations/OnBuildKnexOperation.js +17 -23
  91. package/lib/queryBuilder/operations/OnBuildOperation.js +17 -23
  92. package/lib/queryBuilder/operations/OnErrorOperation.js +17 -23
  93. package/lib/queryBuilder/operations/QueryBuilderOperation.js +231 -270
  94. package/lib/queryBuilder/operations/RangeOperation.js +30 -40
  95. package/lib/queryBuilder/operations/RelateOperation.js +14 -21
  96. package/lib/queryBuilder/operations/ReturningOperation.js +12 -18
  97. package/lib/queryBuilder/operations/RunAfterOperation.js +17 -23
  98. package/lib/queryBuilder/operations/RunBeforeOperation.js +17 -23
  99. package/lib/queryBuilder/operations/UnrelateOperation.js +12 -17
  100. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +48 -65
  101. package/lib/queryBuilder/operations/UpdateOperation.js +104 -129
  102. package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +25 -35
  103. package/lib/queryBuilder/operations/UpsertGraphOperation.js +36 -55
  104. package/lib/queryBuilder/operations/WhereCompositeOperation.js +30 -39
  105. package/lib/queryBuilder/operations/WhereInOperation.js +34 -36
  106. package/lib/queryBuilder/operations/eager/EagerOperation.js +37 -52
  107. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +34 -44
  108. package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +4 -7
  109. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +193 -251
  110. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +18 -25
  111. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +25 -41
  112. package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +19 -20
  113. package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +23 -21
  114. package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +106 -118
  115. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +79 -108
  116. package/lib/queryBuilder/operations/select/SelectOperation.js +37 -53
  117. package/lib/queryBuilder/operations/select/Selection.js +94 -105
  118. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +68 -101
  119. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +65 -75
  120. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +75 -84
  121. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +760 -859
  122. package/lib/queryBuilder/parsers/relationExpressionParser.js +840 -952
  123. package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +10 -15
  124. package/lib/queryBuilder/transformations/QueryTransformation.js +4 -6
  125. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +35 -49
  126. package/lib/queryBuilder/transformations/index.js +3 -9
  127. package/lib/queryBuilder/whereScopes.js +158 -186
  128. package/lib/relations/Relation.js +279 -363
  129. package/lib/relations/RelationDeleteOperation.js +16 -22
  130. package/lib/relations/RelationFindOperation.js +100 -133
  131. package/lib/relations/RelationInsertOperation.js +19 -27
  132. package/lib/relations/RelationOwner.js +167 -204
  133. package/lib/relations/RelationProperty.js +177 -217
  134. package/lib/relations/RelationUpdateOperation.js +16 -23
  135. package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +11 -16
  136. package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +24 -35
  137. package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +30 -41
  138. package/lib/relations/belongsToOne/BelongsToOneRelation.js +33 -41
  139. package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +38 -53
  140. package/lib/relations/hasMany/HasManyInsertOperation.js +35 -51
  141. package/lib/relations/hasMany/HasManyRelateOperation.js +38 -54
  142. package/lib/relations/hasMany/HasManyRelation.js +30 -38
  143. package/lib/relations/hasMany/HasManyUnrelateOperation.js +19 -26
  144. package/lib/relations/hasOne/HasOneRelation.js +4 -7
  145. package/lib/relations/hasOneThrough/HasOneThroughRelation.js +4 -7
  146. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +67 -90
  147. package/lib/relations/manyToMany/ManyToManyRelation.js +349 -451
  148. package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +19 -26
  149. package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +3 -5
  150. package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +14 -20
  151. package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +3 -7
  152. package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +71 -94
  153. package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +31 -43
  154. package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +50 -67
  155. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +4 -7
  156. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +20 -27
  157. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +5 -10
  158. package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +3 -5
  159. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +55 -72
  160. package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +3 -7
  161. package/lib/transaction.js +53 -83
  162. package/lib/utils/assert.js +15 -21
  163. package/lib/utils/buildUtils.js +17 -19
  164. package/lib/utils/classUtils.js +7 -11
  165. package/lib/utils/cloneDeep.js +365 -0
  166. package/lib/utils/createModifier.js +36 -41
  167. package/lib/utils/deprecate.js +8 -12
  168. package/lib/utils/identifierMapping.js +153 -192
  169. package/lib/utils/internalPropUtils.js +4 -8
  170. package/lib/utils/knexUtils.js +61 -56
  171. package/lib/utils/mixin.js +16 -23
  172. package/lib/utils/mysqlJsonPath.js +55 -40
  173. package/lib/utils/normalizeIds.js +110 -127
  174. package/lib/utils/objectUtils.js +236 -338
  175. package/lib/utils/parseFieldExpression.js +66 -44
  176. package/lib/utils/postgresJsonPath.js +48 -62
  177. package/lib/utils/promiseUtils/after.js +10 -13
  178. package/lib/utils/promiseUtils/afterReturn.js +10 -13
  179. package/lib/utils/promiseUtils/index.js +15 -17
  180. package/lib/utils/promiseUtils/isPromise.js +6 -9
  181. package/lib/utils/promiseUtils/map.js +75 -89
  182. package/lib/utils/promiseUtils/mapAfterAllReturn.js +17 -23
  183. package/lib/utils/promiseUtils/try.js +15 -18
  184. package/lib/utils/resolveModel.js +91 -94
  185. package/lib/utils/tmpColumnUtils.js +4 -13
  186. package/package.json +50 -23
  187. package/typings/objection/index.d.mts +362 -0
  188. package/typings/objection/index.d.ts +36 -9
  189. package/lib/.eslintrc.json +0 -8
  190. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +0 -97
  191. package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +0 -141
  192. package/lib/utils/clone.js +0 -2329
@@ -1,31 +1,30 @@
1
1
  'use strict';
2
-
3
2
  const postgresJsonApi = require('./postgresJsonApi');
4
3
  const mysqlJsonApi = require('./mysqlJsonApi');
5
4
  const { getJsonDialect } = require('../../../utils/postgresJsonPath');
5
+ const { jsonCastsForDialect } = require('../../../utils/mysqlJsonPath');
6
6
  const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
7
-
8
7
  // `whereJsonSupersetOf`, `whereJsonSubsetOf` and their variants.
9
8
  class WhereJsonOperation extends ObjectionToKnexConvertingOperation {
10
- onBuildKnex(knexBuilder, builder) {
11
- const dialect = getJsonDialect(builder.unsafeKnex(), this.name);
12
- const args = this.getKnexArgs(builder);
13
- const jsonApi =
14
- dialect === 'mysql'
15
- ? mysqlJsonApi.whereJsonContains
16
- : postgresJsonApi.whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams;
17
- const rawArgs = jsonApi(args[0], this.opt.operator, args[1], this.opt.prefix);
18
-
19
- if (this.opt.bool === 'or') {
20
- knexBuilder = knexBuilder.orWhereRaw.apply(knexBuilder, rawArgs);
21
- } else {
22
- knexBuilder = knexBuilder.whereRaw.apply(knexBuilder, rawArgs);
9
+ onBuildKnex(knexBuilder, builder) {
10
+ const dialect = getJsonDialect(builder.unsafeKnex(), this.name);
11
+ const args = this.getKnexArgs(builder);
12
+ const jsonApi = dialect === 'mysql'
13
+ ? mysqlJsonApi.whereJsonContains
14
+ : postgresJsonApi.whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams;
15
+ const rawArgs = jsonApi(args[0], this.opt.operator, args[1], this.opt.prefix);
16
+ if (dialect === 'mysql') {
17
+ rawArgs[0] = jsonCastsForDialect(rawArgs[0], builder.unsafeKnex());
18
+ }
19
+ if (this.opt.bool === 'or') {
20
+ knexBuilder = knexBuilder.orWhereRaw.apply(knexBuilder, rawArgs);
21
+ }
22
+ else {
23
+ knexBuilder = knexBuilder.whereRaw.apply(knexBuilder, rawArgs);
24
+ }
25
+ return knexBuilder;
23
26
  }
24
-
25
- return knexBuilder;
26
- }
27
27
  }
28
-
29
28
  module.exports = {
30
- WhereJsonOperation,
29
+ WhereJsonOperation,
31
30
  };
@@ -1,31 +1,33 @@
1
1
  'use strict';
2
-
3
2
  const mysqlJsonApi = require('./mysqlJsonApi');
4
3
  const { getJsonDialect } = require('../../../utils/postgresJsonPath');
4
+ const { getMariaDbVersion } = require('../../../utils/knexUtils');
5
+ const { jsonCastsForDialect } = require('../../../utils/mysqlJsonPath');
5
6
  const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
6
-
7
7
  // `whereJsonOverlaps` and its variants. Postgres has no operator for this.
8
8
  class WhereJsonOverlapsOperation extends ObjectionToKnexConvertingOperation {
9
- onBuildKnex(knexBuilder, builder) {
10
- const knex = builder.unsafeKnex();
11
-
12
- if (knex && getJsonDialect(knex, this.name) !== 'mysql') {
13
- throw new Error(`${this.name} is only supported on mysql.`);
9
+ onBuildKnex(knexBuilder, builder) {
10
+ const knex = builder.unsafeKnex();
11
+ if (knex && getJsonDialect(knex, this.name) !== 'mysql') {
12
+ throw new Error(`${this.name} is only supported on mysql.`);
13
+ }
14
+ const mariaDbVersion = knex && getMariaDbVersion(knex);
15
+ if (mariaDbVersion &&
16
+ (mariaDbVersion[0] < 10 || (mariaDbVersion[0] === 10 && mariaDbVersion[1] < 9))) {
17
+ throw new Error(`${this.name} needs MariaDB 10.9 or newer, which has JSON_OVERLAPS().`);
18
+ }
19
+ const args = this.getKnexArgs(builder);
20
+ const [rawSql, bindings] = mysqlJsonApi.whereJsonOverlaps(args[0], args[1], this.opt.prefix);
21
+ const sql = jsonCastsForDialect(rawSql, knex);
22
+ if (this.opt.bool === 'or') {
23
+ knexBuilder = knexBuilder.orWhereRaw(sql, bindings);
24
+ }
25
+ else {
26
+ knexBuilder = knexBuilder.whereRaw(sql, bindings);
27
+ }
28
+ return knexBuilder;
14
29
  }
15
-
16
- const args = this.getKnexArgs(builder);
17
- const [sql, bindings] = mysqlJsonApi.whereJsonOverlaps(args[0], args[1], this.opt.prefix);
18
-
19
- if (this.opt.bool === 'or') {
20
- knexBuilder = knexBuilder.orWhereRaw(sql, bindings);
21
- } else {
22
- knexBuilder = knexBuilder.whereRaw(sql, bindings);
23
- }
24
-
25
- return knexBuilder;
26
- }
27
30
  }
28
-
29
31
  module.exports = {
30
- WhereJsonOverlapsOperation,
32
+ WhereJsonOverlapsOperation,
31
33
  };
@@ -1,167 +1,155 @@
1
1
  'use strict';
2
-
3
2
  const { parseFieldExpression } = require('../../../utils/parseFieldExpression');
4
3
  const { asArray, isObject, isPlainObject, isString } = require('../../../utils/objectUtils');
5
4
  const { toMySqlJsonPath } = require('../../../utils/mysqlJsonPath');
6
-
5
+ const { isMariaDb } = require('../../../utils/knexUtils');
7
6
  // The MySQL versions of the `whereJson*` methods. They mirror the postgres
8
7
  // operators (`@>`, `<@`, `?|`, `?&`) so that the methods find the same rows on
9
8
  // both databases.
10
9
  //
11
10
  // The parts of the queries are `{ sql, bindings, type }` objects. `type` is
12
11
  // `'object'` or `'nonObject'` when the JSON type of the part is known.
13
-
14
12
  // Joins sql parts: sql`JSON_CONTAINS(${target}, ${candidate})`.
15
13
  function sql(strings, ...parts) {
16
- let text = strings[0];
17
- const bindings = [];
18
-
19
- parts.forEach((part, i) => {
20
- text += part.sql + strings[i + 1];
21
- bindings.push(...part.bindings);
22
- });
23
-
24
- return { sql: text, bindings };
14
+ let text = strings[0];
15
+ const bindings = [];
16
+ parts.forEach((part, i) => {
17
+ text += part.sql + strings[i + 1];
18
+ bindings.push(...part.bindings);
19
+ });
20
+ return { sql: text, bindings };
25
21
  }
26
-
27
22
  function literal(text) {
28
- return { sql: text, bindings: [] };
23
+ return { sql: text, bindings: [] };
29
24
  }
30
-
31
25
  // A field expression like `Table.column:a.b[0]`.
32
26
  function field(expression) {
33
- const parsed = parseFieldExpression(expression);
34
-
35
- if (parsed.access.length === 0) {
36
- return { sql: '??', bindings: [parsed.columnName] };
37
- } else {
38
- return {
39
- sql: 'JSON_EXTRACT(??, ?)',
40
- bindings: [parsed.columnName, toMySqlJsonPath(parsed.access)],
41
- };
42
- }
27
+ const parsed = parseFieldExpression(expression);
28
+ if (parsed.access.length === 0) {
29
+ return { sql: '??', bindings: [parsed.columnName] };
30
+ }
31
+ else {
32
+ return {
33
+ sql: 'JSON_EXTRACT(??, ?)',
34
+ bindings: [parsed.columnName, toMySqlJsonPath(parsed.access)],
35
+ };
36
+ }
43
37
  }
44
-
45
38
  function jsonValue(value) {
46
- return {
47
- sql: 'CAST(? AS JSON)',
48
- bindings: [JSON.stringify(value)],
49
- type: isPlainObject(value) ? 'object' : 'nonObject',
50
- };
39
+ return {
40
+ sql: 'CAST(? AS JSON)',
41
+ bindings: [JSON.stringify(value)],
42
+ type: isPlainObject(value) ? 'object' : 'nonObject',
43
+ };
51
44
  }
52
-
53
45
  // The right hand side of the methods: a field expression or a JSON value.
54
46
  function fieldOrValue(value) {
55
- if (isString(value)) {
56
- return field(value);
57
- } else if (isObject(value)) {
58
- return jsonValue(value);
59
- } else {
60
- throw new Error('Invalid right hand expression.');
61
- }
47
+ if (isString(value)) {
48
+ return field(value);
49
+ }
50
+ else if (isObject(value)) {
51
+ return jsonValue(value);
52
+ }
53
+ else {
54
+ throw new Error('Invalid right hand expression.');
55
+ }
62
56
  }
63
-
64
57
  // `target @> candidate`. On postgres an object is only contained in an object, but
65
58
  // MySQL's `JSON_CONTAINS()` also finds objects inside arrays.
66
59
  function contains(target, candidate) {
67
- const containsSql = sql`JSON_CONTAINS(${target}, ${candidate})`;
68
- let guard = null;
69
-
70
- if (candidate.type === 'nonObject' || target.type === 'object') {
71
- guard = null;
72
- } else if (candidate.type === 'object') {
73
- guard = target.type === 'nonObject' ? literal('FALSE') : sql`JSON_TYPE(${target}) = 'OBJECT'`;
74
- } else if (target.type === 'nonObject') {
75
- guard = sql`JSON_TYPE(${candidate}) <> 'OBJECT'`;
76
- } else {
77
- guard = sql`(JSON_TYPE(${candidate}) <> 'OBJECT' OR JSON_TYPE(${target}) = 'OBJECT')`;
78
- }
79
-
80
- return guard ? sql`(${containsSql} AND ${guard})` : containsSql;
60
+ const containsSql = sql `JSON_CONTAINS(${target}, ${candidate})`;
61
+ let guard = null;
62
+ if (candidate.type === 'nonObject' || target.type === 'object') {
63
+ guard = null;
64
+ }
65
+ else if (candidate.type === 'object') {
66
+ guard = target.type === 'nonObject' ? literal('FALSE') : sql `JSON_TYPE(${target}) = 'OBJECT'`;
67
+ }
68
+ else if (target.type === 'nonObject') {
69
+ guard = sql `JSON_TYPE(${candidate}) <> 'OBJECT'`;
70
+ }
71
+ else {
72
+ guard = sql `(JSON_TYPE(${candidate}) <> 'OBJECT' OR JSON_TYPE(${target}) = 'OBJECT')`;
73
+ }
74
+ return guard ? sql `(${containsSql} AND ${guard})` : containsSql;
81
75
  }
82
-
83
76
  function not(part, prefix) {
84
- return prefix === 'not' ? sql`NOT ${part}` : part;
77
+ return prefix === 'not' ? sql `NOT ${part}` : part;
85
78
  }
86
-
87
79
  function toRawArgs(part) {
88
- return [part.sql, part.bindings];
80
+ return [part.sql, part.bindings];
89
81
  }
90
-
91
82
  // `whereJsonSupersetOf` (`@>`) and `whereJsonSubsetOf` (`<@`).
92
83
  function whereJsonContains(fieldExpression, operator, jsonObjectOrFieldExpression, prefix) {
93
- const left = field(fieldExpression);
94
- const right = fieldOrValue(jsonObjectOrFieldExpression);
95
- const condition = operator === '@>' ? contains(left, right) : contains(right, left);
96
-
97
- return toRawArgs(not(condition, prefix));
84
+ const left = field(fieldExpression);
85
+ const right = fieldOrValue(jsonObjectOrFieldExpression);
86
+ const condition = operator === '@>' ? contains(left, right) : contains(right, left);
87
+ return toRawArgs(not(condition, prefix));
98
88
  }
99
-
100
89
  // `whereJsonNotArray` and `whereJsonNotObject`. Like on postgres, NULL is neither
101
90
  // an array nor an object.
102
91
  function whereJsonNotContains(fieldExpression, compareValue) {
103
- const left = field(fieldExpression);
104
- return toRawArgs(sql`(NOT ${contains(left, jsonValue(compareValue))} OR ${left} IS NULL)`);
92
+ const left = field(fieldExpression);
93
+ return toRawArgs(sql `(NOT ${contains(left, jsonValue(compareValue))} OR ${left} IS NULL)`);
105
94
  }
106
-
107
95
  // `whereJsonHasAny` (`?|`) and `whereJsonHasAll` (`?&`). Like on postgres, the keys
108
96
  // are looked for in the keys of objects, in the string items of arrays and in
109
97
  // strings.
110
- function whereJsonHas(fieldExpression, operator, keys) {
111
- const left = field(fieldExpression);
112
- const all = operator === '?&';
113
-
114
- keys = asArray(keys);
115
-
116
- for (const key of keys) {
117
- if (!isString(key)) {
118
- throw new Error('All keys to find must be strings.');
98
+ function whereJsonHas(fieldExpression, operator, keys, knex = null) {
99
+ const left = field(fieldExpression);
100
+ const all = operator === '?&';
101
+ keys = asArray(keys);
102
+ for (const key of keys) {
103
+ if (!isString(key)) {
104
+ throw new Error('All keys to find must be strings.');
105
+ }
106
+ }
107
+ if (keys.length === 0) {
108
+ return toRawArgs(all ? sql `${left} IS NOT NULL` : literal('FALSE'));
109
+ }
110
+ const paths = {
111
+ sql: keys.map(() => '?').join(', '),
112
+ bindings: keys.map((key) => toMySqlJsonPath([{ type: 'object', ref: key }])),
113
+ };
114
+ const keysJson = jsonValue(keys);
115
+ const stringChecks = keys.map((key) => ({
116
+ sql: `JSON_UNQUOTE(${left.sql}) = ?`,
117
+ bindings: [...left.bindings, key],
118
+ }));
119
+ const stringCheck = {
120
+ sql: `(${stringChecks.map((it) => it.sql).join(all ? ' AND ' : ' OR ')})`,
121
+ bindings: stringChecks.flatMap((it) => it.bindings),
122
+ };
123
+ const objectCheck = sql `JSON_CONTAINS_PATH(${left}, ${literal(all ? "'all'" : "'one'")}, ${paths})`;
124
+ let arrayCheck;
125
+ if (all) {
126
+ arrayCheck = sql `JSON_CONTAINS(${left}, ${keysJson})`;
127
+ }
128
+ else if (knex && isMariaDb(knex)) {
129
+ // MariaDB has JSON_OVERLAPS() only since 10.9.
130
+ const containsChecks = keys.map((key) => ({
131
+ sql: `JSON_CONTAINS(${left.sql}, ?)`,
132
+ bindings: [...left.bindings, JSON.stringify(key)],
133
+ }));
134
+ arrayCheck = {
135
+ sql: `(${containsChecks.map((it) => it.sql).join(' OR ')})`,
136
+ bindings: containsChecks.flatMap((it) => it.bindings),
137
+ };
138
+ }
139
+ else {
140
+ arrayCheck = sql `JSON_OVERLAPS(${left}, ${keysJson})`;
119
141
  }
120
- }
121
-
122
- if (keys.length === 0) {
123
- return toRawArgs(all ? sql`${left} IS NOT NULL` : literal('FALSE'));
124
- }
125
-
126
- const paths = {
127
- sql: keys.map(() => '?').join(', '),
128
- bindings: keys.map((key) => toMySqlJsonPath([{ type: 'object', ref: key }])),
129
- };
130
-
131
- const keysJson = jsonValue(keys);
132
- const stringChecks = keys.map((key) => ({
133
- sql: `JSON_UNQUOTE(${left.sql}) = ?`,
134
- bindings: [...left.bindings, key],
135
- }));
136
-
137
- const stringCheck = {
138
- sql: `(${stringChecks.map((it) => it.sql).join(all ? ' AND ' : ' OR ')})`,
139
- bindings: stringChecks.flatMap((it) => it.bindings),
140
- };
141
-
142
- const objectCheck = sql`JSON_CONTAINS_PATH(${left}, ${literal(all ? "'all'" : "'one'")}, ${paths})`;
143
- const arrayCheck = all
144
- ? sql`JSON_CONTAINS(${left}, ${keysJson})`
145
- : sql`JSON_OVERLAPS(${left}, ${keysJson})`;
146
-
147
- return toRawArgs(
148
- sql`(CASE JSON_TYPE(${left}) WHEN 'OBJECT' THEN ${objectCheck} WHEN 'ARRAY' THEN ${arrayCheck} WHEN 'STRING' THEN ${stringCheck} ELSE FALSE END)`,
149
- );
142
+ return toRawArgs(sql `(CASE JSON_TYPE(${left}) WHEN 'OBJECT' THEN ${objectCheck} WHEN 'ARRAY' THEN ${arrayCheck} WHEN 'STRING' THEN ${stringCheck} ELSE FALSE END)`);
150
143
  }
151
-
152
144
  // `whereJsonOverlaps`. A NULL or missing field doesn't overlap anything, so
153
145
  // `whereJsonNotOverlaps` finds it.
154
146
  function whereJsonOverlaps(fieldExpression, jsonObjectOrFieldExpression, prefix) {
155
- const overlaps = sql`JSON_OVERLAPS(${field(fieldExpression)}, ${fieldOrValue(
156
- jsonObjectOrFieldExpression,
157
- )})`;
158
-
159
- return toRawArgs(prefix === 'not' ? sql`NOT COALESCE(${overlaps}, FALSE)` : overlaps);
147
+ const overlaps = sql `JSON_OVERLAPS(${field(fieldExpression)}, ${fieldOrValue(jsonObjectOrFieldExpression)})`;
148
+ return toRawArgs(prefix === 'not' ? sql `NOT COALESCE(${overlaps}, FALSE)` : overlaps);
160
149
  }
161
-
162
150
  module.exports = {
163
- whereJsonContains,
164
- whereJsonNotContains,
165
- whereJsonHas,
166
- whereJsonOverlaps,
151
+ whereJsonContains,
152
+ whereJsonNotContains,
153
+ whereJsonHas,
154
+ whereJsonOverlaps,
167
155
  };
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
-
3
2
  const parser = require('../../../utils/parseFieldExpression');
4
3
  const { asArray, isObject, isString } = require('../../../utils/objectUtils');
5
4
  const { toPgJsonPathLiteral, toPgQuotedColumn } = require('../../../utils/postgresJsonPath');
6
-
7
5
  /**
8
6
  * @typedef {String} FieldExpression
9
7
  *
@@ -42,123 +40,96 @@ const { toPgJsonPathLiteral, toPgQuotedColumn } = require('../../../utils/postgr
42
40
  * 99. Keys containing dots, square brackets, single quotes and double quotes in one json key is
43
41
  * not currently supported
44
42
  */
45
-
46
43
  function parseFieldExpression(expression, extractAsText) {
47
- let parsed = parser.parseFieldExpression(expression);
48
- let extractor = extractAsText ? '#>>' : '#>';
49
- return `${toPgQuotedColumn(parsed.columnName)}${extractor}${toPgJsonPathLiteral(parsed.access)}`;
44
+ const parsed = parser.parseFieldExpression(expression);
45
+ const extractor = extractAsText ? '#>>' : '#>';
46
+ return `${toPgQuotedColumn(parsed.columnName)}${extractor}${toPgJsonPathLiteral(parsed.access)}`;
50
47
  }
51
-
52
- function whereJsonbRefOnLeftJsonbValOrRefOnRight(
53
- builder,
54
- fieldExpression,
55
- operator,
56
- jsonObjectOrFieldExpression,
57
- queryPrefix,
58
- ) {
59
- let queryParams = whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(
60
- fieldExpression,
61
- operator,
62
- jsonObjectOrFieldExpression,
63
- queryPrefix,
64
- );
65
- return builder.whereRaw.apply(builder, queryParams);
48
+ function whereJsonbRefOnLeftJsonbValOrRefOnRight(builder, fieldExpression, operator, jsonObjectOrFieldExpression, queryPrefix) {
49
+ const queryParams = whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(fieldExpression, operator, jsonObjectOrFieldExpression, queryPrefix);
50
+ return builder.whereRaw.apply(builder, queryParams);
66
51
  }
67
-
68
- function whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(
69
- fieldExpression,
70
- operator,
71
- jsonObjectOrFieldExpression,
72
- queryPrefix,
73
- ) {
74
- let fieldReference = parseFieldExpression(fieldExpression);
75
-
76
- if (isString(jsonObjectOrFieldExpression)) {
77
- let rightHandReference = parseFieldExpression(jsonObjectOrFieldExpression);
78
- let refRefQuery = [
79
- '(',
80
- fieldReference,
81
- ')::jsonb',
82
- operator,
83
- '(',
84
- rightHandReference,
85
- ')::jsonb',
86
- ];
87
- if (queryPrefix) {
88
- refRefQuery.unshift(queryPrefix);
52
+ function whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(fieldExpression, operator, jsonObjectOrFieldExpression, queryPrefix) {
53
+ const fieldReference = parseFieldExpression(fieldExpression);
54
+ if (isString(jsonObjectOrFieldExpression)) {
55
+ const rightHandReference = parseFieldExpression(jsonObjectOrFieldExpression);
56
+ const refRefQuery = [
57
+ '(',
58
+ fieldReference,
59
+ ')::jsonb',
60
+ operator,
61
+ '(',
62
+ rightHandReference,
63
+ ')::jsonb',
64
+ ];
65
+ if (queryPrefix) {
66
+ refRefQuery.unshift(queryPrefix);
67
+ }
68
+ return [refRefQuery.join(' ')];
89
69
  }
90
- return [refRefQuery.join(' ')];
91
- } else if (isObject(jsonObjectOrFieldExpression)) {
92
- let refValQuery = ['(', fieldReference, ')::jsonb', operator, '?::jsonb'];
93
- if (queryPrefix) {
94
- refValQuery.unshift(queryPrefix);
70
+ else if (isObject(jsonObjectOrFieldExpression)) {
71
+ const refValQuery = ['(', fieldReference, ')::jsonb', operator, '?::jsonb'];
72
+ if (queryPrefix) {
73
+ refValQuery.unshift(queryPrefix);
74
+ }
75
+ return [refValQuery.join(' '), JSON.stringify(jsonObjectOrFieldExpression)];
95
76
  }
96
- return [refValQuery.join(' '), JSON.stringify(jsonObjectOrFieldExpression)];
97
- }
98
-
99
- throw new Error('Invalid right hand expression.');
77
+ throw new Error('Invalid right hand expression.');
100
78
  }
101
-
102
79
  function whereJsonFieldRightStringArrayOnLeftQuery(knex, fieldExpression, operator, keys) {
103
- let fieldReference = parseFieldExpression(fieldExpression);
104
- keys = asArray(keys);
105
-
106
- let questionMarksArray = keys.map((key) => {
107
- if (!isString(key)) {
108
- throw new Error('All keys to find must be strings.');
109
- }
110
- return '?';
111
- });
112
-
113
- let rightHandExpression = 'array[' + questionMarksArray.join(',') + ']';
114
-
115
- return [`${fieldReference} ${operator.replace('?', '\\?')} ${rightHandExpression}`, keys];
80
+ const fieldReference = parseFieldExpression(fieldExpression);
81
+ keys = asArray(keys);
82
+ const questionMarksArray = keys.map((key) => {
83
+ if (!isString(key)) {
84
+ throw new Error('All keys to find must be strings.');
85
+ }
86
+ return '?';
87
+ });
88
+ const rightHandExpression = 'array[' + questionMarksArray.join(',') + ']';
89
+ return [`${fieldReference} ${operator.replace('?', '\\?')} ${rightHandExpression}`, keys];
116
90
  }
117
-
118
91
  function whereJsonFieldQuery(knex, fieldExpression, operator, value) {
119
- let fieldReference = parseFieldExpression(fieldExpression, true);
120
- let normalizedOperator = normalizeOperator(knex, operator);
121
-
122
- // json type comparison takes json type in string format
123
- let cast;
124
- let valuePlaceholder = '?';
125
- let bindings = [value];
126
- let type = typeof value;
127
-
128
- if (type === 'number') {
129
- cast = '::NUMERIC';
130
- } else if (type === 'boolean') {
131
- cast = '::BOOLEAN';
132
- } else if (type === 'string') {
133
- cast = '::TEXT';
134
- } else if (value === null) {
135
- cast = '::TEXT';
136
- valuePlaceholder = 'NULL';
137
- bindings = [];
138
- } else {
139
- throw new Error('Value must be string, number, boolean or null.');
140
- }
141
-
142
- return [`(${fieldReference})${cast} ${normalizedOperator} ${valuePlaceholder}`, bindings];
92
+ const fieldReference = parseFieldExpression(fieldExpression, true);
93
+ const normalizedOperator = normalizeOperator(knex, operator);
94
+ // json type comparison takes json type in string format
95
+ let cast;
96
+ let valuePlaceholder = '?';
97
+ let bindings = [value];
98
+ const type = typeof value;
99
+ if (type === 'number') {
100
+ cast = '::NUMERIC';
101
+ }
102
+ else if (type === 'boolean') {
103
+ cast = '::BOOLEAN';
104
+ }
105
+ else if (type === 'string') {
106
+ cast = '::TEXT';
107
+ }
108
+ else if (value === null) {
109
+ cast = '::TEXT';
110
+ valuePlaceholder = 'NULL';
111
+ bindings = [];
112
+ }
113
+ else {
114
+ throw new Error('Value must be string, number, boolean or null.');
115
+ }
116
+ return [`(${fieldReference})${cast} ${normalizedOperator} ${valuePlaceholder}`, bindings];
143
117
  }
144
-
145
118
  function normalizeOperator(knex, operator) {
146
- let trimmedLowerCase = operator.trim().toLowerCase();
147
-
148
- switch (trimmedLowerCase) {
149
- case 'is':
150
- case 'is not':
151
- return trimmedLowerCase;
152
- default:
153
- return knex.client.formatter().operator(operator);
154
- }
119
+ const trimmedLowerCase = operator.trim().toLowerCase();
120
+ switch (trimmedLowerCase) {
121
+ case 'is':
122
+ case 'is not':
123
+ return trimmedLowerCase;
124
+ default:
125
+ // knex has no public way to check an operator, and only `is` is used.
126
+ throw new Error(`unsupported operator "${operator}" in a JSON field query`);
127
+ }
155
128
  }
156
-
157
129
  module.exports = {
158
- parseFieldExpression: parseFieldExpression,
159
- whereJsonbRefOnLeftJsonbValOrRefOnRight: whereJsonbRefOnLeftJsonbValOrRefOnRight,
160
- whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams:
161
- whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams,
162
- whereJsonFieldRightStringArrayOnLeftQuery: whereJsonFieldRightStringArrayOnLeftQuery,
163
- whereJsonFieldQuery: whereJsonFieldQuery,
130
+ parseFieldExpression: parseFieldExpression,
131
+ whereJsonbRefOnLeftJsonbValOrRefOnRight: whereJsonbRefOnLeftJsonbValOrRefOnRight,
132
+ whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams: whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams,
133
+ whereJsonFieldRightStringArrayOnLeftQuery: whereJsonFieldRightStringArrayOnLeftQuery,
134
+ whereJsonFieldQuery: whereJsonFieldQuery,
164
135
  };