@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.
- package/README.md +103 -67
- package/lib/index.mjs +6 -0
- package/lib/initialize.js +6 -9
- package/lib/model/AjvValidator.js +235 -282
- package/lib/model/Model.js +653 -823
- package/lib/model/ModifierNotFoundError.js +5 -7
- package/lib/model/NotFoundError.js +14 -18
- package/lib/model/RelationDoesNotExistError.js +6 -9
- package/lib/model/ValidationError.js +33 -41
- package/lib/model/Validator.js +15 -21
- package/lib/model/getModel.js +1 -3
- package/lib/model/graph/ModelGraph.js +44 -64
- package/lib/model/graph/ModelGraphBuilder.js +164 -217
- package/lib/model/graph/ModelGraphEdge.js +31 -43
- package/lib/model/graph/ModelGraphNode.js +192 -231
- package/lib/model/inheritModel.js +9 -18
- package/lib/model/modelBindKnex.js +48 -62
- package/lib/model/modelClone.js +79 -98
- package/lib/model/modelColPropMap.js +16 -26
- package/lib/model/modelConcurrency.js +24 -0
- package/lib/model/modelId.js +44 -49
- package/lib/model/modelJsonAttributes.js +56 -76
- package/lib/model/modelParseRelations.js +68 -67
- package/lib/model/modelQueryProps.js +76 -107
- package/lib/model/modelSet.js +92 -112
- package/lib/model/modelTableMetadata.js +60 -72
- package/lib/model/modelToJson.js +120 -150
- package/lib/model/modelUtils.js +30 -37
- package/lib/model/modelValidate.js +34 -42
- package/lib/model/modelValues.js +78 -91
- package/lib/model/modelVisitor.js +22 -29
- package/lib/objection.js +42 -67
- package/lib/queryBuilder/FunctionBuilder.js +231 -279
- package/lib/queryBuilder/InternalOptions.js +19 -24
- package/lib/queryBuilder/JoinBuilder.js +116 -149
- package/lib/queryBuilder/QueryBuilder.js +1009 -1271
- package/lib/queryBuilder/QueryBuilderBase.js +647 -865
- package/lib/queryBuilder/QueryBuilderContext.js +14 -21
- package/lib/queryBuilder/QueryBuilderContextBase.js +20 -29
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +462 -536
- package/lib/queryBuilder/QueryBuilderUserContext.js +19 -25
- package/lib/queryBuilder/RawBuilder.js +58 -73
- package/lib/queryBuilder/ReferenceBuilder.js +232 -270
- package/lib/queryBuilder/RelationExpression.js +322 -380
- package/lib/queryBuilder/StaticHookArguments.js +94 -106
- package/lib/queryBuilder/ValueBuilder.js +100 -112
- package/lib/queryBuilder/expressions.js +86 -116
- package/lib/queryBuilder/functions/mysql.js +642 -697
- package/lib/queryBuilder/graph/GraphAction.js +44 -59
- package/lib/queryBuilder/graph/GraphData.js +7 -9
- package/lib/queryBuilder/graph/GraphFetcher.js +115 -151
- package/lib/queryBuilder/graph/GraphNodeDbExistence.js +114 -152
- package/lib/queryBuilder/graph/GraphOperation.js +16 -22
- package/lib/queryBuilder/graph/GraphOptions.js +143 -185
- package/lib/queryBuilder/graph/GraphUpsert.js +167 -241
- package/lib/queryBuilder/graph/delete/GraphDelete.js +8 -13
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +44 -62
- package/lib/queryBuilder/graph/insert/GraphInsert.js +99 -128
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +151 -193
- package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +57 -79
- package/lib/queryBuilder/graph/patch/GraphPatch.js +8 -13
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +181 -234
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +16 -21
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +80 -110
- package/lib/queryBuilder/join/JoinResultColumn.js +14 -18
- package/lib/queryBuilder/join/JoinResultParser.js +88 -101
- package/lib/queryBuilder/join/RelationJoiner.js +205 -288
- package/lib/queryBuilder/join/TableNode.js +117 -144
- package/lib/queryBuilder/join/TableTree.js +60 -76
- package/lib/queryBuilder/join/utils.js +10 -16
- package/lib/queryBuilder/operations/DelegateOperation.js +93 -133
- package/lib/queryBuilder/operations/DeleteOperation.js +33 -37
- package/lib/queryBuilder/operations/FindByIdOperation.js +20 -28
- package/lib/queryBuilder/operations/FindByIdsOperation.js +17 -23
- package/lib/queryBuilder/operations/FindOperation.js +114 -131
- package/lib/queryBuilder/operations/FirstOperation.js +14 -19
- package/lib/queryBuilder/operations/FromOperation.js +69 -88
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +28 -39
- package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +25 -35
- package/lib/queryBuilder/operations/InsertGraphOperation.js +77 -98
- package/lib/queryBuilder/operations/InsertOperation.js +132 -165
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +27 -36
- package/lib/queryBuilder/operations/InstanceFindOperation.js +9 -13
- package/lib/queryBuilder/operations/InstanceInsertOperation.js +13 -17
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +32 -46
- package/lib/queryBuilder/operations/JoinRelatedOperation.js +66 -86
- package/lib/queryBuilder/operations/KnexOperation.js +4 -7
- package/lib/queryBuilder/operations/MergeOperation.js +32 -45
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +131 -150
- package/lib/queryBuilder/operations/OnBuildKnexOperation.js +17 -23
- package/lib/queryBuilder/operations/OnBuildOperation.js +17 -23
- package/lib/queryBuilder/operations/OnErrorOperation.js +17 -23
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +231 -270
- package/lib/queryBuilder/operations/RangeOperation.js +30 -40
- package/lib/queryBuilder/operations/RelateOperation.js +14 -21
- package/lib/queryBuilder/operations/ReturningOperation.js +12 -18
- package/lib/queryBuilder/operations/RunAfterOperation.js +17 -23
- package/lib/queryBuilder/operations/RunBeforeOperation.js +17 -23
- package/lib/queryBuilder/operations/UnrelateOperation.js +12 -17
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +48 -65
- package/lib/queryBuilder/operations/UpdateOperation.js +104 -129
- package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +25 -35
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +36 -55
- package/lib/queryBuilder/operations/WhereCompositeOperation.js +30 -39
- package/lib/queryBuilder/operations/WhereInOperation.js +34 -36
- package/lib/queryBuilder/operations/eager/EagerOperation.js +37 -52
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +34 -44
- package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +4 -7
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +193 -251
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +18 -25
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +25 -41
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +19 -20
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +23 -21
- package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +106 -118
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +79 -108
- package/lib/queryBuilder/operations/select/SelectOperation.js +37 -53
- package/lib/queryBuilder/operations/select/Selection.js +94 -105
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +68 -101
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +65 -75
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +75 -84
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +760 -859
- package/lib/queryBuilder/parsers/relationExpressionParser.js +840 -952
- package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +10 -15
- package/lib/queryBuilder/transformations/QueryTransformation.js +4 -6
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +35 -49
- package/lib/queryBuilder/transformations/index.js +3 -9
- package/lib/queryBuilder/whereScopes.js +158 -186
- package/lib/relations/Relation.js +279 -363
- package/lib/relations/RelationDeleteOperation.js +16 -22
- package/lib/relations/RelationFindOperation.js +100 -133
- package/lib/relations/RelationInsertOperation.js +19 -27
- package/lib/relations/RelationOwner.js +167 -204
- package/lib/relations/RelationProperty.js +177 -217
- package/lib/relations/RelationUpdateOperation.js +16 -23
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +11 -16
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +24 -35
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +30 -41
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +33 -41
- package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +38 -53
- package/lib/relations/hasMany/HasManyInsertOperation.js +35 -51
- package/lib/relations/hasMany/HasManyRelateOperation.js +38 -54
- package/lib/relations/hasMany/HasManyRelation.js +30 -38
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +19 -26
- package/lib/relations/hasOne/HasOneRelation.js +4 -7
- package/lib/relations/hasOneThrough/HasOneThroughRelation.js +4 -7
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +67 -90
- package/lib/relations/manyToMany/ManyToManyRelation.js +349 -451
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +19 -26
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +3 -5
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +14 -20
- package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +3 -7
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +71 -94
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +31 -43
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +50 -67
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +4 -7
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +20 -27
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +5 -10
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +3 -5
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +55 -72
- package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +3 -7
- package/lib/transaction.js +53 -83
- package/lib/utils/assert.js +15 -21
- package/lib/utils/buildUtils.js +17 -19
- package/lib/utils/classUtils.js +7 -11
- package/lib/utils/cloneDeep.js +365 -0
- package/lib/utils/createModifier.js +36 -41
- package/lib/utils/deprecate.js +8 -12
- package/lib/utils/identifierMapping.js +153 -192
- package/lib/utils/internalPropUtils.js +4 -8
- package/lib/utils/knexUtils.js +61 -56
- package/lib/utils/mixin.js +16 -23
- package/lib/utils/mysqlJsonPath.js +55 -40
- package/lib/utils/normalizeIds.js +110 -127
- package/lib/utils/objectUtils.js +236 -338
- package/lib/utils/parseFieldExpression.js +66 -44
- package/lib/utils/postgresJsonPath.js +48 -62
- package/lib/utils/promiseUtils/after.js +10 -13
- package/lib/utils/promiseUtils/afterReturn.js +10 -13
- package/lib/utils/promiseUtils/index.js +15 -17
- package/lib/utils/promiseUtils/isPromise.js +6 -9
- package/lib/utils/promiseUtils/map.js +75 -89
- package/lib/utils/promiseUtils/mapAfterAllReturn.js +17 -23
- package/lib/utils/promiseUtils/try.js +15 -18
- package/lib/utils/resolveModel.js +91 -94
- package/lib/utils/tmpColumnUtils.js +4 -13
- package/package.json +50 -23
- package/typings/objection/index.d.mts +362 -0
- package/typings/objection/index.d.ts +36 -9
- package/lib/.eslintrc.json +0 -8
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +0 -97
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +0 -141
- 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
77
|
+
return prefix === 'not' ? sql `NOT ${part}` : part;
|
|
85
78
|
}
|
|
86
|
-
|
|
87
79
|
function toRawArgs(part) {
|
|
88
|
-
|
|
80
|
+
return [part.sql, part.bindings];
|
|
89
81
|
}
|
|
90
|
-
|
|
91
82
|
// `whereJsonSupersetOf` (`@>`) and `whereJsonSubsetOf` (`<@`).
|
|
92
83
|
function whereJsonContains(fieldExpression, operator, jsonObjectOrFieldExpression, prefix) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const parsed = parser.parseFieldExpression(expression);
|
|
45
|
+
const extractor = extractAsText ? '#>>' : '#>';
|
|
46
|
+
return `${toPgQuotedColumn(parsed.columnName)}${extractor}${toPgJsonPathLiteral(parsed.access)}`;
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
whereJsonFieldQuery: whereJsonFieldQuery,
|
|
130
|
+
parseFieldExpression: parseFieldExpression,
|
|
131
|
+
whereJsonbRefOnLeftJsonbValOrRefOnRight: whereJsonbRefOnLeftJsonbValOrRefOnRight,
|
|
132
|
+
whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams: whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams,
|
|
133
|
+
whereJsonFieldRightStringArrayOnLeftQuery: whereJsonFieldRightStringArrayOnLeftQuery,
|
|
134
|
+
whereJsonFieldQuery: whereJsonFieldQuery,
|
|
164
135
|
};
|