@abedmurrar/objection 0.0.0-stage → 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/LICENSE +23 -0
- package/README.md +105 -2
- package/lib/index.mjs +6 -0
- package/lib/initialize.js +11 -0
- package/lib/model/AjvValidator.js +278 -0
- package/lib/model/Model.js +772 -0
- package/lib/model/ModifierNotFoundError.js +10 -0
- package/lib/model/NotFoundError.js +19 -0
- package/lib/model/RelationDoesNotExistError.js +11 -0
- package/lib/model/ValidationError.js +46 -0
- package/lib/model/Validator.js +20 -0
- package/lib/model/getModel.js +7 -0
- package/lib/model/graph/ModelGraph.js +60 -0
- package/lib/model/graph/ModelGraphBuilder.js +188 -0
- package/lib/model/graph/ModelGraphEdge.js +40 -0
- package/lib/model/graph/ModelGraphNode.js +201 -0
- package/lib/model/inheritModel.js +19 -0
- package/lib/model/modelBindKnex.js +69 -0
- package/lib/model/modelClone.js +102 -0
- package/lib/model/modelColPropMap.js +24 -0
- package/lib/model/modelConcurrency.js +24 -0
- package/lib/model/modelId.js +57 -0
- package/lib/model/modelJsonAttributes.js +72 -0
- package/lib/model/modelParseRelations.js +83 -0
- package/lib/model/modelQueryProps.js +116 -0
- package/lib/model/modelSet.js +116 -0
- package/lib/model/modelTableMetadata.js +71 -0
- package/lib/model/modelToJson.js +157 -0
- package/lib/model/modelUtils.js +40 -0
- package/lib/model/modelValidate.js +40 -0
- package/lib/model/modelValues.js +113 -0
- package/lib/model/modelVisitor.js +34 -0
- package/lib/objection.js +89 -0
- package/lib/queryBuilder/FunctionBuilder.js +291 -0
- package/lib/queryBuilder/InternalOptions.js +24 -0
- package/lib/queryBuilder/JoinBuilder.js +124 -0
- package/lib/queryBuilder/QueryBuilder.js +1136 -0
- package/lib/queryBuilder/QueryBuilderBase.js +666 -0
- package/lib/queryBuilder/QueryBuilderContext.js +20 -0
- package/lib/queryBuilder/QueryBuilderContextBase.js +26 -0
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +484 -0
- package/lib/queryBuilder/QueryBuilderUserContext.js +25 -0
- package/lib/queryBuilder/RawBuilder.js +78 -0
- package/lib/queryBuilder/ReferenceBuilder.js +246 -0
- package/lib/queryBuilder/RelationExpression.js +373 -0
- package/lib/queryBuilder/StaticHookArguments.js +122 -0
- package/lib/queryBuilder/ValueBuilder.js +115 -0
- package/lib/queryBuilder/expressions.js +121 -0
- package/lib/queryBuilder/functions/mysql.js +762 -0
- package/lib/queryBuilder/graph/GraphAction.js +54 -0
- package/lib/queryBuilder/graph/GraphData.js +12 -0
- package/lib/queryBuilder/graph/GraphFetcher.js +155 -0
- package/lib/queryBuilder/graph/GraphNodeDbExistence.js +146 -0
- package/lib/queryBuilder/graph/GraphOperation.js +21 -0
- package/lib/queryBuilder/graph/GraphOptions.js +165 -0
- package/lib/queryBuilder/graph/GraphUpsert.js +231 -0
- package/lib/queryBuilder/graph/delete/GraphDelete.js +15 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +56 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +116 -0
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +183 -0
- package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +70 -0
- package/lib/queryBuilder/graph/patch/GraphPatch.js +15 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +205 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +26 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +96 -0
- package/lib/queryBuilder/join/JoinResultColumn.js +19 -0
- package/lib/queryBuilder/join/JoinResultParser.js +106 -0
- package/lib/queryBuilder/join/RelationJoiner.js +258 -0
- package/lib/queryBuilder/join/TableNode.js +136 -0
- package/lib/queryBuilder/join/TableTree.js +69 -0
- package/lib/queryBuilder/join/utils.js +19 -0
- package/lib/queryBuilder/operations/DelegateOperation.js +101 -0
- package/lib/queryBuilder/operations/DeleteOperation.js +47 -0
- package/lib/queryBuilder/operations/FindByIdOperation.js +29 -0
- package/lib/queryBuilder/operations/FindByIdsOperation.js +23 -0
- package/lib/queryBuilder/operations/FindOperation.js +147 -0
- package/lib/queryBuilder/operations/FirstOperation.js +22 -0
- package/lib/queryBuilder/operations/FromOperation.js +91 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +38 -0
- package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +35 -0
- package/lib/queryBuilder/operations/InsertGraphOperation.js +87 -0
- package/lib/queryBuilder/operations/InsertOperation.js +173 -0
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +37 -0
- package/lib/queryBuilder/operations/InstanceFindOperation.js +17 -0
- package/lib/queryBuilder/operations/InstanceInsertOperation.js +20 -0
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +45 -0
- package/lib/queryBuilder/operations/JoinRelatedOperation.js +83 -0
- package/lib/queryBuilder/operations/KnexOperation.js +11 -0
- package/lib/queryBuilder/operations/MergeOperation.js +43 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +173 -0
- package/lib/queryBuilder/operations/OnBuildKnexOperation.js +23 -0
- package/lib/queryBuilder/operations/OnBuildOperation.js +23 -0
- package/lib/queryBuilder/operations/OnErrorOperation.js +23 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +258 -0
- package/lib/queryBuilder/operations/RangeOperation.js +37 -0
- package/lib/queryBuilder/operations/RelateOperation.js +20 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +24 -0
- package/lib/queryBuilder/operations/RunAfterOperation.js +23 -0
- package/lib/queryBuilder/operations/RunBeforeOperation.js +23 -0
- package/lib/queryBuilder/operations/UnrelateOperation.js +18 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +63 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +130 -0
- package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +35 -0
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +47 -0
- package/lib/queryBuilder/operations/WhereCompositeOperation.js +48 -0
- package/lib/queryBuilder/operations/WhereInOperation.js +46 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +46 -0
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +10 -0
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +227 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +29 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +37 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +30 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +33 -0
- package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +155 -0
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +135 -0
- package/lib/queryBuilder/operations/select/SelectOperation.js +50 -0
- package/lib/queryBuilder/operations/select/Selection.js +112 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +84 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +90 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +95 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +787 -0
- package/lib/queryBuilder/parsers/relationExpressionParser.js +930 -0
- package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +17 -0
- package/lib/queryBuilder/transformations/QueryTransformation.js +9 -0
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +57 -0
- package/lib/queryBuilder/transformations/index.js +9 -0
- package/lib/queryBuilder/whereScopes.js +199 -0
- package/lib/relations/Relation.js +317 -0
- package/lib/relations/RelationDeleteOperation.js +23 -0
- package/lib/relations/RelationFindOperation.js +116 -0
- package/lib/relations/RelationInsertOperation.js +25 -0
- package/lib/relations/RelationOwner.js +199 -0
- package/lib/relations/RelationProperty.js +207 -0
- package/lib/relations/RelationUpdateOperation.js +23 -0
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +18 -0
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +33 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +39 -0
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +45 -0
- package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +46 -0
- package/lib/relations/hasMany/HasManyInsertOperation.js +45 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +48 -0
- package/lib/relations/hasMany/HasManyRelation.js +41 -0
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +26 -0
- package/lib/relations/hasOne/HasOneRelation.js +10 -0
- package/lib/relations/hasOneThrough/HasOneThroughRelation.js +10 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +82 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +405 -0
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +29 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +8 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +20 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +8 -0
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +82 -0
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +40 -0
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +64 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +11 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +27 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +11 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +8 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +65 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +8 -0
- package/lib/transaction.js +66 -0
- package/lib/utils/assert.js +19 -0
- package/lib/utils/buildUtils.js +19 -0
- package/lib/utils/classUtils.js +9 -0
- package/lib/utils/cloneDeep.js +365 -0
- package/lib/utils/createModifier.js +37 -0
- package/lib/utils/deprecate.js +11 -0
- package/lib/utils/identifierMapping.js +188 -0
- package/lib/utils/internalPropUtils.js +7 -0
- package/lib/utils/knexUtils.js +81 -0
- package/lib/utils/mixin.js +18 -0
- package/lib/utils/mysqlJsonPath.js +70 -0
- package/lib/utils/normalizeIds.js +127 -0
- package/lib/utils/objectUtils.js +314 -0
- package/lib/utils/parseFieldExpression.js +73 -0
- package/lib/utils/postgresJsonPath.js +96 -0
- package/lib/utils/promiseUtils/after.js +14 -0
- package/lib/utils/promiseUtils/afterReturn.js +14 -0
- package/lib/utils/promiseUtils/index.js +15 -0
- package/lib/utils/promiseUtils/isPromise.js +8 -0
- package/lib/utils/promiseUtils/map.js +96 -0
- package/lib/utils/promiseUtils/mapAfterAllReturn.js +23 -0
- package/lib/utils/promiseUtils/try.js +19 -0
- package/lib/utils/resolveModel.js +104 -0
- package/lib/utils/tmpColumnUtils.js +7 -0
- package/package.json +113 -4
- package/typings/objection/index.d.mts +362 -0
- package/typings/objection/index.d.ts +4131 -0
- package/typings/objection/relation-expression.d.ts +237 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const promiseUtils = require('../../../utils/promiseUtils');
|
|
3
|
+
const { EagerOperation } = require('./EagerOperation');
|
|
4
|
+
const { isMsSql, isOracle, isSqlite } = require('../../../utils/knexUtils');
|
|
5
|
+
const { isObject, asArray, flatten, chunk } = require('../../../utils/objectUtils');
|
|
6
|
+
const { ValidationErrorType } = require('../../../model/ValidationError');
|
|
7
|
+
const { createModifier } = require('../../../utils/createModifier');
|
|
8
|
+
const { RelationDoesNotExistError } = require('../../../model/RelationDoesNotExistError');
|
|
9
|
+
const { RelationOwner } = require('../../../relations/RelationOwner');
|
|
10
|
+
const { getReadConcurrency } = require('../../../model/modelConcurrency');
|
|
11
|
+
class WhereInEagerOperation extends EagerOperation {
|
|
12
|
+
constructor(name, opt) {
|
|
13
|
+
super(name, opt);
|
|
14
|
+
this.relationsToFetch = [];
|
|
15
|
+
this.omitProps = [];
|
|
16
|
+
}
|
|
17
|
+
batchSize(knex) {
|
|
18
|
+
if (this.graphOptions.maxBatchSize) {
|
|
19
|
+
return this.graphOptions.maxBatchSize;
|
|
20
|
+
}
|
|
21
|
+
else if (isMsSql(knex)) {
|
|
22
|
+
// On MSSQL the parameter limit is actually 2100, but since I couldn't figure out
|
|
23
|
+
// if the limit is for all parameters in a query or for individual clauses, we set
|
|
24
|
+
// the limit to 2000 to leave 100 parameters for where clauses etc.
|
|
25
|
+
return 2000;
|
|
26
|
+
}
|
|
27
|
+
else if (isOracle(knex)) {
|
|
28
|
+
return 1000;
|
|
29
|
+
}
|
|
30
|
+
else if (isSqlite(knex)) {
|
|
31
|
+
// SQLITE_MAX_VARIABLE_NUMBER is 999 by default
|
|
32
|
+
return 999;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// I'm sure there is some kind of limit for other databases too, but let's lower
|
|
36
|
+
// this if someone ever hits those limits.
|
|
37
|
+
return 10000;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
onBuild(builder) {
|
|
41
|
+
const relationsToFetch = findRelationsToFetch(builder.modelClass(), this.buildFinalExpression());
|
|
42
|
+
const { selectionsToAdd, selectedProps } = findRelationPropsToSelect(builder, relationsToFetch);
|
|
43
|
+
if (selectionsToAdd.length) {
|
|
44
|
+
builder.select(selectionsToAdd);
|
|
45
|
+
}
|
|
46
|
+
this.relationsToFetch = relationsToFetch;
|
|
47
|
+
this.omitProps = selectedProps;
|
|
48
|
+
}
|
|
49
|
+
async onAfter2(builder, result) {
|
|
50
|
+
const modelClass = builder.resultModelClass();
|
|
51
|
+
if (!result) {
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
const models = asArray(result);
|
|
55
|
+
// Check models to be actual objects, to filter out `count` results (#2397).
|
|
56
|
+
if (!models.length || !isObject(models[0])) {
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
await promiseUtils.map(this.relationsToFetch, (it) => this.fetchRelation(builder, models, it.relation, it.childExpression), { concurrency: getReadConcurrency(modelClass, builder.unsafeKnex()) });
|
|
60
|
+
const intOpt = builder.internalOptions();
|
|
61
|
+
if (!this.omitProps.length || intOpt.keepImplicitJoinProps) {
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
// Now that relations have been fetched for `models` we can omit the
|
|
65
|
+
// columns that were implicitly selected by this class.
|
|
66
|
+
for (let i = 0, l = result.length; i < l; ++i) {
|
|
67
|
+
const model = result[i];
|
|
68
|
+
for (let c = 0, lc = this.omitProps.length; c < lc; ++c) {
|
|
69
|
+
modelClass.omitImpl(model, this.omitProps[c]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
async fetchRelation(builder, models, relation, expr) {
|
|
75
|
+
const modelClass = builder.resultModelClass();
|
|
76
|
+
const batchSize = this.batchSize(builder.knex());
|
|
77
|
+
const modelBatches = chunk(models, batchSize);
|
|
78
|
+
const result = await promiseUtils.map(modelBatches, (batch) => this.fetchRelationBatch(builder, batch, relation, expr), {
|
|
79
|
+
concurrency: getReadConcurrency(modelClass, builder.unsafeKnex()),
|
|
80
|
+
});
|
|
81
|
+
return flatten(result);
|
|
82
|
+
}
|
|
83
|
+
fetchRelationBatch(builder, models, relation, expr) {
|
|
84
|
+
if (this.shouldSkipFetched(models, relation, expr)) {
|
|
85
|
+
return this.createSkippedQuery(builder, models, relation, expr);
|
|
86
|
+
}
|
|
87
|
+
const queryBuilder = this.createRelationQuery(builder, relation, expr);
|
|
88
|
+
const findOperation = relation.find(queryBuilder, RelationOwner.create(models));
|
|
89
|
+
findOperation.alwaysReturnArray = true;
|
|
90
|
+
findOperation.assignResultToOwner = true;
|
|
91
|
+
findOperation.relationProperty = expr.node.$name;
|
|
92
|
+
queryBuilder.addOperation(findOperation, []);
|
|
93
|
+
for (const modifierName of expr.node.$modify) {
|
|
94
|
+
const modifier = createModifier({
|
|
95
|
+
modifier: modifierName,
|
|
96
|
+
modelClass: relation.relatedModelClass,
|
|
97
|
+
modifiers: this.buildFinalModifiers(builder),
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
modifier(queryBuilder);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const modelClass = builder.modelClass();
|
|
104
|
+
if (err instanceof modelClass.ModifierNotFoundError) {
|
|
105
|
+
throw modelClass.createValidationError({
|
|
106
|
+
type: ValidationErrorType.RelationExpression,
|
|
107
|
+
message: `could not find modifier "${modifierName}" for relation "${relation.name}"`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return queryBuilder;
|
|
116
|
+
}
|
|
117
|
+
shouldSkipFetched(models, relation, expr) {
|
|
118
|
+
if (!this.graphOptions.skipFetched) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (models.some((it) => it[expr.node.$name] === undefined)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const relationsToFetch = findRelationsToFetch(relation.relatedModelClass, expr);
|
|
125
|
+
const childModels = getRelatedModels(models, expr);
|
|
126
|
+
// We can only skip fetching a relation if all already fetched models
|
|
127
|
+
// have all needed relation properties so that we can fetch the next
|
|
128
|
+
// level of relations.
|
|
129
|
+
for (const { relation } of relationsToFetch) {
|
|
130
|
+
const { ownerProp } = relation;
|
|
131
|
+
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
132
|
+
const prop = ownerProp.props[c];
|
|
133
|
+
for (const model of childModels) {
|
|
134
|
+
if (model[prop] === undefined) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
createSkippedQuery(builder, models, relation, expr) {
|
|
143
|
+
const childModels = getRelatedModels(models, expr);
|
|
144
|
+
return relation.relatedModelClass
|
|
145
|
+
.query()
|
|
146
|
+
.childQueryOf(builder)
|
|
147
|
+
.findOptions({ dontCallFindHooks: true })
|
|
148
|
+
.withGraphFetched(expr, this.graphOptions)
|
|
149
|
+
.resolve(childModels);
|
|
150
|
+
}
|
|
151
|
+
createRelationQuery(builder, relation, expr) {
|
|
152
|
+
return relation.relatedModelClass
|
|
153
|
+
.query()
|
|
154
|
+
.childQueryOf(builder)
|
|
155
|
+
.withGraphFetched(expr, this.graphOptions)
|
|
156
|
+
.modifiers(this.buildFinalModifiers(builder));
|
|
157
|
+
}
|
|
158
|
+
clone() {
|
|
159
|
+
const clone = super.clone();
|
|
160
|
+
clone.relationsToFetch = this.relationsToFetch.slice();
|
|
161
|
+
clone.omitProps = this.omitProps.slice();
|
|
162
|
+
return clone;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function findRelationsToFetch(modelClass, eagerExpression) {
|
|
166
|
+
const relationsToFetch = [];
|
|
167
|
+
try {
|
|
168
|
+
eagerExpression.forEachChildExpression(modelClass, (childExpression, relation) => {
|
|
169
|
+
relationsToFetch.push({
|
|
170
|
+
childExpression,
|
|
171
|
+
relation,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (err instanceof RelationDoesNotExistError) {
|
|
177
|
+
throw modelClass.createValidationError({
|
|
178
|
+
type: ValidationErrorType.RelationExpression,
|
|
179
|
+
message: `unknown relation "${err.relationName}" in an eager expression`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
return relationsToFetch;
|
|
185
|
+
}
|
|
186
|
+
function findRelationPropsToSelect(builder, relationsToFetch) {
|
|
187
|
+
const selectionsToAdd = [];
|
|
188
|
+
const selectedProps = [];
|
|
189
|
+
// Collect columns that need to be selected for the eager fetch
|
|
190
|
+
// to work that are not currently selected.
|
|
191
|
+
for (const { relation } of relationsToFetch) {
|
|
192
|
+
const ownerProp = relation.ownerProp;
|
|
193
|
+
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
194
|
+
const fullCol = ownerProp.ref(builder, c).fullColumn(builder);
|
|
195
|
+
const prop = ownerProp.props[c];
|
|
196
|
+
const col = ownerProp.cols[c];
|
|
197
|
+
if (!builder.hasSelectionAs(fullCol, col) && selectionsToAdd.indexOf(fullCol) === -1) {
|
|
198
|
+
selectedProps.push(prop);
|
|
199
|
+
selectionsToAdd.push(fullCol);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
selectionsToAdd,
|
|
205
|
+
selectedProps,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function getRelatedModels(models, expr) {
|
|
209
|
+
const allRelated = [];
|
|
210
|
+
for (const model of models) {
|
|
211
|
+
const related = model[expr.node.$name];
|
|
212
|
+
if (related) {
|
|
213
|
+
if (Array.isArray(related)) {
|
|
214
|
+
for (const rel of related) {
|
|
215
|
+
allRelated.push(rel);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
allRelated.push(related);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return allRelated;
|
|
224
|
+
}
|
|
225
|
+
module.exports = {
|
|
226
|
+
WhereInEagerOperation,
|
|
227
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const postgresJsonApi = require('./postgresJsonApi');
|
|
3
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
4
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
5
|
+
const { jsonCastsForDialect } = require('../../../utils/mysqlJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
// `whereJsonHasAny` and `whereJsonHasAll`.
|
|
8
|
+
class WhereJsonHasOperation extends ObjectionToKnexConvertingOperation {
|
|
9
|
+
onBuildKnex(knexBuilder, builder) {
|
|
10
|
+
const dialect = getJsonDialect(builder.unsafeKnex(), this.name);
|
|
11
|
+
const args = this.getKnexArgs(builder);
|
|
12
|
+
let [sql, bindings] = dialect === 'mysql'
|
|
13
|
+
? mysqlJsonApi.whereJsonHas(args[0], this.opt.operator, args[1], builder.unsafeKnex())
|
|
14
|
+
: postgresJsonApi.whereJsonFieldRightStringArrayOnLeftQuery(builder.knex(), args[0], this.opt.operator, args[1]);
|
|
15
|
+
if (dialect === 'mysql') {
|
|
16
|
+
sql = jsonCastsForDialect(sql, builder.unsafeKnex());
|
|
17
|
+
}
|
|
18
|
+
if (this.opt.bool === 'or') {
|
|
19
|
+
knexBuilder = knexBuilder.orWhereRaw(sql, bindings);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
knexBuilder = knexBuilder.whereRaw(sql, bindings);
|
|
23
|
+
}
|
|
24
|
+
return knexBuilder;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
module.exports = {
|
|
28
|
+
WhereJsonHasOperation,
|
|
29
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const jsonApi = require('./postgresJsonApi');
|
|
3
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
4
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
5
|
+
const { jsonCastsForDialect } = require('../../../utils/mysqlJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
// `whereJsonNotArray` and `whereJsonNotObject`.
|
|
8
|
+
class WhereJsonNotObjectOperation extends ObjectionToKnexConvertingOperation {
|
|
9
|
+
onBuildKnex(knexBuilder, builder) {
|
|
10
|
+
const dialect = getJsonDialect(builder.unsafeKnex(), this.name);
|
|
11
|
+
const fieldExpression = this.getKnexArgs(builder)[0];
|
|
12
|
+
if (dialect === 'mysql') {
|
|
13
|
+
const [rawSql, bindings] = mysqlJsonApi.whereJsonNotContains(fieldExpression, this.opt.compareValue);
|
|
14
|
+
const sql = jsonCastsForDialect(rawSql, builder.unsafeKnex());
|
|
15
|
+
return this.opt.bool === 'or'
|
|
16
|
+
? knexBuilder.orWhereRaw(sql, bindings)
|
|
17
|
+
: knexBuilder.whereRaw(sql, bindings);
|
|
18
|
+
}
|
|
19
|
+
return this.whereJsonNotObject(knexBuilder, builder.knex(), fieldExpression);
|
|
20
|
+
}
|
|
21
|
+
whereJsonNotObject(knexBuilder, knex, fieldExpression) {
|
|
22
|
+
const innerQuery = (innerQuery) => {
|
|
23
|
+
const builder = jsonApi.whereJsonbRefOnLeftJsonbValOrRefOnRight(innerQuery, fieldExpression, '@>', this.opt.compareValue, 'not');
|
|
24
|
+
builder.orWhereRaw(...jsonApi.whereJsonFieldQuery(knex, fieldExpression, 'IS', null));
|
|
25
|
+
};
|
|
26
|
+
if (this.opt.bool === 'or') {
|
|
27
|
+
knexBuilder = knexBuilder.orWhere(innerQuery);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
knexBuilder = knexBuilder.where(innerQuery);
|
|
31
|
+
}
|
|
32
|
+
return knexBuilder;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
module.exports = {
|
|
36
|
+
WhereJsonNotObjectOperation,
|
|
37
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const postgresJsonApi = require('./postgresJsonApi');
|
|
3
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
4
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
5
|
+
const { jsonCastsForDialect } = require('../../../utils/mysqlJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
// `whereJsonSupersetOf`, `whereJsonSubsetOf` and their variants.
|
|
8
|
+
class WhereJsonOperation extends ObjectionToKnexConvertingOperation {
|
|
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;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
module.exports = {
|
|
29
|
+
WhereJsonOperation,
|
|
30
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
3
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
4
|
+
const { getMariaDbVersion } = require('../../../utils/knexUtils');
|
|
5
|
+
const { jsonCastsForDialect } = require('../../../utils/mysqlJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
// `whereJsonOverlaps` and its variants. Postgres has no operator for this.
|
|
8
|
+
class WhereJsonOverlapsOperation extends ObjectionToKnexConvertingOperation {
|
|
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;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
module.exports = {
|
|
32
|
+
WhereJsonOverlapsOperation,
|
|
33
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { parseFieldExpression } = require('../../../utils/parseFieldExpression');
|
|
3
|
+
const { asArray, isObject, isPlainObject, isString } = require('../../../utils/objectUtils');
|
|
4
|
+
const { toMySqlJsonPath } = require('../../../utils/mysqlJsonPath');
|
|
5
|
+
const { isMariaDb } = require('../../../utils/knexUtils');
|
|
6
|
+
// The MySQL versions of the `whereJson*` methods. They mirror the postgres
|
|
7
|
+
// operators (`@>`, `<@`, `?|`, `?&`) so that the methods find the same rows on
|
|
8
|
+
// both databases.
|
|
9
|
+
//
|
|
10
|
+
// The parts of the queries are `{ sql, bindings, type }` objects. `type` is
|
|
11
|
+
// `'object'` or `'nonObject'` when the JSON type of the part is known.
|
|
12
|
+
// Joins sql parts: sql`JSON_CONTAINS(${target}, ${candidate})`.
|
|
13
|
+
function sql(strings, ...parts) {
|
|
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 };
|
|
21
|
+
}
|
|
22
|
+
function literal(text) {
|
|
23
|
+
return { sql: text, bindings: [] };
|
|
24
|
+
}
|
|
25
|
+
// A field expression like `Table.column:a.b[0]`.
|
|
26
|
+
function field(expression) {
|
|
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
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function jsonValue(value) {
|
|
39
|
+
return {
|
|
40
|
+
sql: 'CAST(? AS JSON)',
|
|
41
|
+
bindings: [JSON.stringify(value)],
|
|
42
|
+
type: isPlainObject(value) ? 'object' : 'nonObject',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// The right hand side of the methods: a field expression or a JSON value.
|
|
46
|
+
function fieldOrValue(value) {
|
|
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
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// `target @> candidate`. On postgres an object is only contained in an object, but
|
|
58
|
+
// MySQL's `JSON_CONTAINS()` also finds objects inside arrays.
|
|
59
|
+
function contains(target, candidate) {
|
|
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;
|
|
75
|
+
}
|
|
76
|
+
function not(part, prefix) {
|
|
77
|
+
return prefix === 'not' ? sql `NOT ${part}` : part;
|
|
78
|
+
}
|
|
79
|
+
function toRawArgs(part) {
|
|
80
|
+
return [part.sql, part.bindings];
|
|
81
|
+
}
|
|
82
|
+
// `whereJsonSupersetOf` (`@>`) and `whereJsonSubsetOf` (`<@`).
|
|
83
|
+
function whereJsonContains(fieldExpression, operator, jsonObjectOrFieldExpression, 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));
|
|
88
|
+
}
|
|
89
|
+
// `whereJsonNotArray` and `whereJsonNotObject`. Like on postgres, NULL is neither
|
|
90
|
+
// an array nor an object.
|
|
91
|
+
function whereJsonNotContains(fieldExpression, compareValue) {
|
|
92
|
+
const left = field(fieldExpression);
|
|
93
|
+
return toRawArgs(sql `(NOT ${contains(left, jsonValue(compareValue))} OR ${left} IS NULL)`);
|
|
94
|
+
}
|
|
95
|
+
// `whereJsonHasAny` (`?|`) and `whereJsonHasAll` (`?&`). Like on postgres, the keys
|
|
96
|
+
// are looked for in the keys of objects, in the string items of arrays and in
|
|
97
|
+
// 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})`;
|
|
141
|
+
}
|
|
142
|
+
return toRawArgs(sql `(CASE JSON_TYPE(${left}) WHEN 'OBJECT' THEN ${objectCheck} WHEN 'ARRAY' THEN ${arrayCheck} WHEN 'STRING' THEN ${stringCheck} ELSE FALSE END)`);
|
|
143
|
+
}
|
|
144
|
+
// `whereJsonOverlaps`. A NULL or missing field doesn't overlap anything, so
|
|
145
|
+
// `whereJsonNotOverlaps` finds it.
|
|
146
|
+
function whereJsonOverlaps(fieldExpression, jsonObjectOrFieldExpression, prefix) {
|
|
147
|
+
const overlaps = sql `JSON_OVERLAPS(${field(fieldExpression)}, ${fieldOrValue(jsonObjectOrFieldExpression)})`;
|
|
148
|
+
return toRawArgs(prefix === 'not' ? sql `NOT COALESCE(${overlaps}, FALSE)` : overlaps);
|
|
149
|
+
}
|
|
150
|
+
module.exports = {
|
|
151
|
+
whereJsonContains,
|
|
152
|
+
whereJsonNotContains,
|
|
153
|
+
whereJsonHas,
|
|
154
|
+
whereJsonOverlaps,
|
|
155
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const parser = require('../../../utils/parseFieldExpression');
|
|
3
|
+
const { asArray, isObject, isString } = require('../../../utils/objectUtils');
|
|
4
|
+
const { toPgJsonPathLiteral, toPgQuotedColumn } = require('../../../utils/postgresJsonPath');
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {String} FieldExpression
|
|
7
|
+
*
|
|
8
|
+
* Field expressions allow one to refer to separate JSONB fields inside columns.
|
|
9
|
+
*
|
|
10
|
+
* Syntax: <column reference>[:<json field reference>]
|
|
11
|
+
*
|
|
12
|
+
* e.g. `Person.jsonColumnName:details.names[1]` would refer to value `'Second'`
|
|
13
|
+
* in column `Person.jsonColumnName` which has
|
|
14
|
+
* `{ details: { names: ['First', 'Second', 'Last'] } }` object stored in it.
|
|
15
|
+
*
|
|
16
|
+
* First part `<column reference>` is compatible with column references used in
|
|
17
|
+
* knex e.g. `MyFancyTable.tributeToThBestColumnNameEver`.
|
|
18
|
+
*
|
|
19
|
+
* Second part describes a path to an attribute inside the referred column.
|
|
20
|
+
* It is optional and it always starts with colon which follows directly with
|
|
21
|
+
* first path element. e.g. `Table.jsonObjectColumnName:jsonFieldName` or
|
|
22
|
+
* `Table.jsonArrayColumn:[321]`.
|
|
23
|
+
*
|
|
24
|
+
* Syntax supports `[<key or index>]` and `.<key or index>` flavors of reference
|
|
25
|
+
* to json keys / array indexes:
|
|
26
|
+
*
|
|
27
|
+
* e.g. both `Table.myColumn:[1][3]` and `Table.myColumn:1.3` would access correctly
|
|
28
|
+
* both of the following objects `[null, [null,null,null, "I was accessed"]]` and
|
|
29
|
+
* `{ "1": { "3" : "I was accessed" } }`
|
|
30
|
+
*
|
|
31
|
+
* Caveats when using special characters in keys:
|
|
32
|
+
*
|
|
33
|
+
* 1. `objectColumn.key` This is the most common syntax, good if you are
|
|
34
|
+
* not using dots or square brackets `[]` in your json object key name.
|
|
35
|
+
* 2. Keys containing dots `objectColumn:[keywith.dots]` Column `{ "keywith.dots" : "I was referred" }`
|
|
36
|
+
* 3. Keys containing square brackets `column['[]']` `{ "[]" : "This is getting ridiculous..." }`
|
|
37
|
+
* 4. Keys containing square brackets and quotes
|
|
38
|
+
* `objectColumn:['Double."Quote".[]']` and `objectColumn:["Sinlge.'Quote'.[]"]`
|
|
39
|
+
* Column `{ "Double.\"Quote\".[]" : "I was referred", "Single.'Quote'.[]" : "Mee too!" }`
|
|
40
|
+
* 99. Keys containing dots, square brackets, single quotes and double quotes in one json key is
|
|
41
|
+
* not currently supported
|
|
42
|
+
*/
|
|
43
|
+
function parseFieldExpression(expression, extractAsText) {
|
|
44
|
+
const parsed = parser.parseFieldExpression(expression);
|
|
45
|
+
const extractor = extractAsText ? '#>>' : '#>';
|
|
46
|
+
return `${toPgQuotedColumn(parsed.columnName)}${extractor}${toPgJsonPathLiteral(parsed.access)}`;
|
|
47
|
+
}
|
|
48
|
+
function whereJsonbRefOnLeftJsonbValOrRefOnRight(builder, fieldExpression, operator, jsonObjectOrFieldExpression, queryPrefix) {
|
|
49
|
+
const queryParams = whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(fieldExpression, operator, jsonObjectOrFieldExpression, queryPrefix);
|
|
50
|
+
return builder.whereRaw.apply(builder, queryParams);
|
|
51
|
+
}
|
|
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(' ')];
|
|
69
|
+
}
|
|
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)];
|
|
76
|
+
}
|
|
77
|
+
throw new Error('Invalid right hand expression.');
|
|
78
|
+
}
|
|
79
|
+
function whereJsonFieldRightStringArrayOnLeftQuery(knex, fieldExpression, operator, 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];
|
|
90
|
+
}
|
|
91
|
+
function whereJsonFieldQuery(knex, fieldExpression, operator, value) {
|
|
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];
|
|
117
|
+
}
|
|
118
|
+
function normalizeOperator(knex, operator) {
|
|
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
|
+
}
|
|
128
|
+
}
|
|
129
|
+
module.exports = {
|
|
130
|
+
parseFieldExpression: parseFieldExpression,
|
|
131
|
+
whereJsonbRefOnLeftJsonbValOrRefOnRight: whereJsonbRefOnLeftJsonbValOrRefOnRight,
|
|
132
|
+
whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams: whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams,
|
|
133
|
+
whereJsonFieldRightStringArrayOnLeftQuery: whereJsonFieldRightStringArrayOnLeftQuery,
|
|
134
|
+
whereJsonFieldQuery: whereJsonFieldQuery,
|
|
135
|
+
};
|