@abedmurrar/objection 0.0.0-stage → 4.0.0-alpha.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 +69 -2
- package/lib/.eslintrc.json +8 -0
- package/lib/initialize.js +14 -0
- package/lib/model/AjvValidator.js +325 -0
- package/lib/model/Model.js +942 -0
- package/lib/model/ModifierNotFoundError.js +12 -0
- package/lib/model/NotFoundError.js +23 -0
- package/lib/model/RelationDoesNotExistError.js +14 -0
- package/lib/model/ValidationError.js +54 -0
- package/lib/model/Validator.js +26 -0
- package/lib/model/getModel.js +9 -0
- package/lib/model/graph/ModelGraph.js +80 -0
- package/lib/model/graph/ModelGraphBuilder.js +241 -0
- package/lib/model/graph/ModelGraphEdge.js +52 -0
- package/lib/model/graph/ModelGraphNode.js +240 -0
- package/lib/model/inheritModel.js +28 -0
- package/lib/model/modelBindKnex.js +83 -0
- package/lib/model/modelClone.js +121 -0
- package/lib/model/modelColPropMap.js +34 -0
- package/lib/model/modelId.js +62 -0
- package/lib/model/modelJsonAttributes.js +92 -0
- package/lib/model/modelParseRelations.js +82 -0
- package/lib/model/modelQueryProps.js +147 -0
- package/lib/model/modelSet.js +136 -0
- package/lib/model/modelTableMetadata.js +83 -0
- package/lib/model/modelToJson.js +187 -0
- package/lib/model/modelUtils.js +47 -0
- package/lib/model/modelValidate.js +48 -0
- package/lib/model/modelValues.js +126 -0
- package/lib/model/modelVisitor.js +41 -0
- package/lib/objection.js +114 -0
- package/lib/queryBuilder/FunctionBuilder.js +339 -0
- package/lib/queryBuilder/InternalOptions.js +29 -0
- package/lib/queryBuilder/JoinBuilder.js +157 -0
- package/lib/queryBuilder/QueryBuilder.js +1398 -0
- package/lib/queryBuilder/QueryBuilderBase.js +884 -0
- package/lib/queryBuilder/QueryBuilderContext.js +27 -0
- package/lib/queryBuilder/QueryBuilderContextBase.js +35 -0
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +558 -0
- package/lib/queryBuilder/QueryBuilderUserContext.js +31 -0
- package/lib/queryBuilder/RawBuilder.js +93 -0
- package/lib/queryBuilder/ReferenceBuilder.js +284 -0
- package/lib/queryBuilder/RelationExpression.js +431 -0
- package/lib/queryBuilder/StaticHookArguments.js +134 -0
- package/lib/queryBuilder/ValueBuilder.js +127 -0
- package/lib/queryBuilder/expressions.js +151 -0
- package/lib/queryBuilder/functions/mysql.js +817 -0
- package/lib/queryBuilder/graph/GraphAction.js +69 -0
- package/lib/queryBuilder/graph/GraphData.js +14 -0
- package/lib/queryBuilder/graph/GraphFetcher.js +191 -0
- package/lib/queryBuilder/graph/GraphNodeDbExistence.js +184 -0
- package/lib/queryBuilder/graph/GraphOperation.js +27 -0
- package/lib/queryBuilder/graph/GraphOptions.js +207 -0
- package/lib/queryBuilder/graph/GraphUpsert.js +305 -0
- package/lib/queryBuilder/graph/delete/GraphDelete.js +20 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +74 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +145 -0
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +225 -0
- package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +92 -0
- package/lib/queryBuilder/graph/patch/GraphPatch.js +20 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +258 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +31 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +126 -0
- package/lib/queryBuilder/join/JoinResultColumn.js +23 -0
- package/lib/queryBuilder/join/JoinResultParser.js +119 -0
- package/lib/queryBuilder/join/RelationJoiner.js +341 -0
- package/lib/queryBuilder/join/TableNode.js +163 -0
- package/lib/queryBuilder/join/TableTree.js +85 -0
- package/lib/queryBuilder/join/utils.js +25 -0
- package/lib/queryBuilder/operations/DelegateOperation.js +141 -0
- package/lib/queryBuilder/operations/DeleteOperation.js +51 -0
- package/lib/queryBuilder/operations/FindByIdOperation.js +37 -0
- package/lib/queryBuilder/operations/FindByIdsOperation.js +29 -0
- package/lib/queryBuilder/operations/FindOperation.js +164 -0
- package/lib/queryBuilder/operations/FirstOperation.js +27 -0
- package/lib/queryBuilder/operations/FromOperation.js +110 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +49 -0
- package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +45 -0
- package/lib/queryBuilder/operations/InsertGraphOperation.js +108 -0
- package/lib/queryBuilder/operations/InsertOperation.js +206 -0
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +46 -0
- package/lib/queryBuilder/operations/InstanceFindOperation.js +21 -0
- package/lib/queryBuilder/operations/InstanceInsertOperation.js +24 -0
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +59 -0
- package/lib/queryBuilder/operations/JoinRelatedOperation.js +103 -0
- package/lib/queryBuilder/operations/KnexOperation.js +14 -0
- package/lib/queryBuilder/operations/MergeOperation.js +56 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +192 -0
- package/lib/queryBuilder/operations/OnBuildKnexOperation.js +29 -0
- package/lib/queryBuilder/operations/OnBuildOperation.js +29 -0
- package/lib/queryBuilder/operations/OnErrorOperation.js +29 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +297 -0
- package/lib/queryBuilder/operations/RangeOperation.js +47 -0
- package/lib/queryBuilder/operations/RelateOperation.js +27 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +30 -0
- package/lib/queryBuilder/operations/RunAfterOperation.js +29 -0
- package/lib/queryBuilder/operations/RunBeforeOperation.js +29 -0
- package/lib/queryBuilder/operations/UnrelateOperation.js +23 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +80 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +155 -0
- package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +45 -0
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +66 -0
- package/lib/queryBuilder/operations/WhereCompositeOperation.js +57 -0
- package/lib/queryBuilder/operations/WhereInOperation.js +48 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +61 -0
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +51 -0
- package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +13 -0
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +285 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +36 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +53 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +31 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +31 -0
- package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +167 -0
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +164 -0
- package/lib/queryBuilder/operations/select/SelectOperation.js +66 -0
- package/lib/queryBuilder/operations/select/Selection.js +123 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +117 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +100 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +104 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +886 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +97 -0
- package/lib/queryBuilder/parsers/relationExpressionParser.js +1042 -0
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +141 -0
- package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +22 -0
- package/lib/queryBuilder/transformations/QueryTransformation.js +11 -0
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +71 -0
- package/lib/queryBuilder/transformations/index.js +15 -0
- package/lib/queryBuilder/whereScopes.js +227 -0
- package/lib/relations/Relation.js +401 -0
- package/lib/relations/RelationDeleteOperation.js +29 -0
- package/lib/relations/RelationFindOperation.js +149 -0
- package/lib/relations/RelationInsertOperation.js +33 -0
- package/lib/relations/RelationOwner.js +236 -0
- package/lib/relations/RelationProperty.js +247 -0
- package/lib/relations/RelationUpdateOperation.js +30 -0
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +23 -0
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +44 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +50 -0
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +53 -0
- package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +61 -0
- package/lib/relations/hasMany/HasManyInsertOperation.js +61 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +64 -0
- package/lib/relations/hasMany/HasManyRelation.js +49 -0
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +33 -0
- package/lib/relations/hasOne/HasOneRelation.js +13 -0
- package/lib/relations/hasOneThrough/HasOneThroughRelation.js +13 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +105 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +507 -0
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +36 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +10 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +26 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +12 -0
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +105 -0
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +52 -0
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +81 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +14 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +34 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +10 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +82 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +12 -0
- package/lib/transaction.js +96 -0
- package/lib/utils/assert.js +25 -0
- package/lib/utils/buildUtils.js +21 -0
- package/lib/utils/classUtils.js +13 -0
- package/lib/utils/clone.js +2329 -0
- package/lib/utils/createModifier.js +42 -0
- package/lib/utils/deprecate.js +15 -0
- package/lib/utils/identifierMapping.js +227 -0
- package/lib/utils/internalPropUtils.js +11 -0
- package/lib/utils/knexUtils.js +76 -0
- package/lib/utils/mixin.js +25 -0
- package/lib/utils/mysqlJsonPath.js +55 -0
- package/lib/utils/normalizeIds.js +144 -0
- package/lib/utils/objectUtils.js +416 -0
- package/lib/utils/parseFieldExpression.js +51 -0
- package/lib/utils/postgresJsonPath.js +110 -0
- package/lib/utils/promiseUtils/after.js +17 -0
- package/lib/utils/promiseUtils/afterReturn.js +17 -0
- package/lib/utils/promiseUtils/index.js +17 -0
- package/lib/utils/promiseUtils/isPromise.js +11 -0
- package/lib/utils/promiseUtils/map.js +110 -0
- package/lib/utils/promiseUtils/mapAfterAllReturn.js +29 -0
- package/lib/utils/promiseUtils/try.js +22 -0
- package/lib/utils/resolveModel.js +107 -0
- package/lib/utils/tmpColumnUtils.js +16 -0
- package/package.json +86 -4
- package/typings/objection/index.d.ts +4104 -0
- package/typings/objection/relation-expression.d.ts +237 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const promiseUtils = require('../../../utils/promiseUtils');
|
|
4
|
+
|
|
5
|
+
const { EagerOperation } = require('./EagerOperation');
|
|
6
|
+
const { isMsSql, isOracle, isSqlite } = require('../../../utils/knexUtils');
|
|
7
|
+
const { isObject, asArray, flatten, chunk } = require('../../../utils/objectUtils');
|
|
8
|
+
const { ValidationErrorType } = require('../../../model/ValidationError');
|
|
9
|
+
const { createModifier } = require('../../../utils/createModifier');
|
|
10
|
+
const { RelationDoesNotExistError } = require('../../../model/RelationDoesNotExistError');
|
|
11
|
+
const { RelationOwner } = require('../../../relations/RelationOwner');
|
|
12
|
+
|
|
13
|
+
class WhereInEagerOperation extends EagerOperation {
|
|
14
|
+
constructor(name, opt) {
|
|
15
|
+
super(name, opt);
|
|
16
|
+
|
|
17
|
+
this.relationsToFetch = [];
|
|
18
|
+
this.omitProps = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
batchSize(knex) {
|
|
22
|
+
if (this.graphOptions.maxBatchSize) {
|
|
23
|
+
return this.graphOptions.maxBatchSize;
|
|
24
|
+
} else if (isMsSql(knex)) {
|
|
25
|
+
// On MSSQL the parameter limit is actually 2100, but since I couldn't figure out
|
|
26
|
+
// if the limit is for all parameters in a query or for individual clauses, we set
|
|
27
|
+
// the limit to 2000 to leave 100 parameters for where clauses etc.
|
|
28
|
+
return 2000;
|
|
29
|
+
} else if (isOracle(knex)) {
|
|
30
|
+
return 1000;
|
|
31
|
+
} else if (isSqlite(knex)) {
|
|
32
|
+
// SQLITE_MAX_VARIABLE_NUMBER is 999 by default
|
|
33
|
+
return 999;
|
|
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
|
+
|
|
41
|
+
onBuild(builder) {
|
|
42
|
+
const relationsToFetch = findRelationsToFetch(
|
|
43
|
+
builder.modelClass(),
|
|
44
|
+
this.buildFinalExpression(),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const { selectionsToAdd, selectedProps } = findRelationPropsToSelect(builder, relationsToFetch);
|
|
48
|
+
|
|
49
|
+
if (selectionsToAdd.length) {
|
|
50
|
+
builder.select(selectionsToAdd);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.relationsToFetch = relationsToFetch;
|
|
54
|
+
this.omitProps = selectedProps;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async onAfter2(builder, result) {
|
|
58
|
+
const modelClass = builder.resultModelClass();
|
|
59
|
+
|
|
60
|
+
if (!result) {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const models = asArray(result);
|
|
65
|
+
|
|
66
|
+
// Check models to be actual objects, to filter out `count` results (#2397).
|
|
67
|
+
if (!models.length || !isObject(models[0])) {
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await promiseUtils.map(
|
|
72
|
+
this.relationsToFetch,
|
|
73
|
+
(it) => this.fetchRelation(builder, models, it.relation, it.childExpression),
|
|
74
|
+
{ concurrency: modelClass.getConcurrency(builder.unsafeKnex()) },
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const intOpt = builder.internalOptions();
|
|
78
|
+
|
|
79
|
+
if (!this.omitProps.length || intOpt.keepImplicitJoinProps) {
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Now that relations have been fetched for `models` we can omit the
|
|
84
|
+
// columns that were implicitly selected by this class.
|
|
85
|
+
for (let i = 0, l = result.length; i < l; ++i) {
|
|
86
|
+
const model = result[i];
|
|
87
|
+
|
|
88
|
+
for (let c = 0, lc = this.omitProps.length; c < lc; ++c) {
|
|
89
|
+
modelClass.omitImpl(model, this.omitProps[c]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async fetchRelation(builder, models, relation, expr) {
|
|
97
|
+
const modelClass = builder.resultModelClass();
|
|
98
|
+
const batchSize = this.batchSize(builder.knex());
|
|
99
|
+
const modelBatches = chunk(models, batchSize);
|
|
100
|
+
|
|
101
|
+
const result = await promiseUtils.map(
|
|
102
|
+
modelBatches,
|
|
103
|
+
(batch) => this.fetchRelationBatch(builder, batch, relation, expr),
|
|
104
|
+
{
|
|
105
|
+
concurrency: modelClass.getConcurrency(builder.unsafeKnex()),
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return flatten(result);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fetchRelationBatch(builder, models, relation, expr) {
|
|
113
|
+
if (this.shouldSkipFetched(models, relation, expr)) {
|
|
114
|
+
return this.createSkippedQuery(builder, models, relation, expr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const queryBuilder = this.createRelationQuery(builder, relation, expr);
|
|
118
|
+
const findOperation = relation.find(queryBuilder, RelationOwner.create(models));
|
|
119
|
+
|
|
120
|
+
findOperation.alwaysReturnArray = true;
|
|
121
|
+
findOperation.assignResultToOwner = true;
|
|
122
|
+
findOperation.relationProperty = expr.node.$name;
|
|
123
|
+
|
|
124
|
+
queryBuilder.addOperation(findOperation, []);
|
|
125
|
+
|
|
126
|
+
for (const modifierName of expr.node.$modify) {
|
|
127
|
+
const modifier = createModifier({
|
|
128
|
+
modifier: modifierName,
|
|
129
|
+
modelClass: relation.relatedModelClass,
|
|
130
|
+
modifiers: this.buildFinalModifiers(builder),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
modifier(queryBuilder);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const modelClass = builder.modelClass();
|
|
137
|
+
|
|
138
|
+
if (err instanceof modelClass.ModifierNotFoundError) {
|
|
139
|
+
throw modelClass.createValidationError({
|
|
140
|
+
type: ValidationErrorType.RelationExpression,
|
|
141
|
+
message: `could not find modifier "${modifierName}" for relation "${relation.name}"`,
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return queryBuilder;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
shouldSkipFetched(models, relation, expr) {
|
|
153
|
+
if (!this.graphOptions.skipFetched) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (models.some((it) => it[expr.node.$name] === undefined)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const relationsToFetch = findRelationsToFetch(relation.relatedModelClass, expr);
|
|
162
|
+
const childModels = getRelatedModels(models, expr);
|
|
163
|
+
|
|
164
|
+
// We can only skip fetching a relation if all already fetched models
|
|
165
|
+
// have all needed relation properties so that we can fetch the next
|
|
166
|
+
// level of relations.
|
|
167
|
+
for (const { relation } of relationsToFetch) {
|
|
168
|
+
const { ownerProp } = relation;
|
|
169
|
+
|
|
170
|
+
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
171
|
+
const prop = ownerProp.props[c];
|
|
172
|
+
|
|
173
|
+
for (const model of childModels) {
|
|
174
|
+
if (model[prop] === undefined) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
createSkippedQuery(builder, models, relation, expr) {
|
|
185
|
+
const childModels = getRelatedModels(models, expr);
|
|
186
|
+
|
|
187
|
+
return relation.relatedModelClass
|
|
188
|
+
.query()
|
|
189
|
+
.childQueryOf(builder)
|
|
190
|
+
.findOptions({ dontCallFindHooks: true })
|
|
191
|
+
.withGraphFetched(expr, this.graphOptions)
|
|
192
|
+
.resolve(childModels);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
createRelationQuery(builder, relation, expr) {
|
|
196
|
+
return relation.relatedModelClass
|
|
197
|
+
.query()
|
|
198
|
+
.childQueryOf(builder)
|
|
199
|
+
.withGraphFetched(expr, this.graphOptions)
|
|
200
|
+
.modifiers(this.buildFinalModifiers(builder));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
clone() {
|
|
204
|
+
const clone = super.clone();
|
|
205
|
+
|
|
206
|
+
clone.relationsToFetch = this.relationsToFetch.slice();
|
|
207
|
+
clone.omitProps = this.omitProps.slice();
|
|
208
|
+
|
|
209
|
+
return clone;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function findRelationsToFetch(modelClass, eagerExpression) {
|
|
214
|
+
const relationsToFetch = [];
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
eagerExpression.forEachChildExpression(modelClass, (childExpression, relation) => {
|
|
218
|
+
relationsToFetch.push({
|
|
219
|
+
childExpression,
|
|
220
|
+
relation,
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (err instanceof RelationDoesNotExistError) {
|
|
225
|
+
throw modelClass.createValidationError({
|
|
226
|
+
type: ValidationErrorType.RelationExpression,
|
|
227
|
+
message: `unknown relation "${err.relationName}" in an eager expression`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return relationsToFetch;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function findRelationPropsToSelect(builder, relationsToFetch) {
|
|
237
|
+
const selectionsToAdd = [];
|
|
238
|
+
const selectedProps = [];
|
|
239
|
+
|
|
240
|
+
// Collect columns that need to be selected for the eager fetch
|
|
241
|
+
// to work that are not currently selected.
|
|
242
|
+
for (const { relation } of relationsToFetch) {
|
|
243
|
+
const ownerProp = relation.ownerProp;
|
|
244
|
+
|
|
245
|
+
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
246
|
+
const fullCol = ownerProp.ref(builder, c).fullColumn(builder);
|
|
247
|
+
const prop = ownerProp.props[c];
|
|
248
|
+
const col = ownerProp.cols[c];
|
|
249
|
+
|
|
250
|
+
if (!builder.hasSelectionAs(fullCol, col) && selectionsToAdd.indexOf(fullCol) === -1) {
|
|
251
|
+
selectedProps.push(prop);
|
|
252
|
+
selectionsToAdd.push(fullCol);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
selectionsToAdd,
|
|
259
|
+
selectedProps,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getRelatedModels(models, expr) {
|
|
264
|
+
const allRelated = [];
|
|
265
|
+
|
|
266
|
+
for (const model of models) {
|
|
267
|
+
const related = model[expr.node.$name];
|
|
268
|
+
|
|
269
|
+
if (related) {
|
|
270
|
+
if (Array.isArray(related)) {
|
|
271
|
+
for (const rel of related) {
|
|
272
|
+
allRelated.push(rel);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
allRelated.push(related);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return allRelated;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = {
|
|
284
|
+
WhereInEagerOperation,
|
|
285
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const postgresJsonApi = require('./postgresJsonApi');
|
|
4
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
5
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
|
|
8
|
+
// `whereJsonHasAny` and `whereJsonHasAll`.
|
|
9
|
+
class WhereJsonHasOperation extends ObjectionToKnexConvertingOperation {
|
|
10
|
+
onBuildKnex(knexBuilder, builder) {
|
|
11
|
+
const dialect = getJsonDialect(builder.unsafeKnex(), this.name);
|
|
12
|
+
const args = this.getKnexArgs(builder);
|
|
13
|
+
|
|
14
|
+
const [sql, bindings] =
|
|
15
|
+
dialect === 'mysql'
|
|
16
|
+
? mysqlJsonApi.whereJsonHas(args[0], this.opt.operator, args[1])
|
|
17
|
+
: postgresJsonApi.whereJsonFieldRightStringArrayOnLeftQuery(
|
|
18
|
+
builder.knex(),
|
|
19
|
+
args[0],
|
|
20
|
+
this.opt.operator,
|
|
21
|
+
args[1],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (this.opt.bool === 'or') {
|
|
25
|
+
knexBuilder = knexBuilder.orWhereRaw(sql, bindings);
|
|
26
|
+
} else {
|
|
27
|
+
knexBuilder = knexBuilder.whereRaw(sql, bindings);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return knexBuilder;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
WhereJsonHasOperation,
|
|
36
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const jsonApi = require('./postgresJsonApi');
|
|
4
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
5
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
|
|
8
|
+
// `whereJsonNotArray` and `whereJsonNotObject`.
|
|
9
|
+
class WhereJsonNotObjectOperation extends ObjectionToKnexConvertingOperation {
|
|
10
|
+
onBuildKnex(knexBuilder, builder) {
|
|
11
|
+
const dialect = getJsonDialect(builder.unsafeKnex(), this.name);
|
|
12
|
+
const fieldExpression = this.getKnexArgs(builder)[0];
|
|
13
|
+
|
|
14
|
+
if (dialect === 'mysql') {
|
|
15
|
+
const [sql, bindings] = mysqlJsonApi.whereJsonNotContains(
|
|
16
|
+
fieldExpression,
|
|
17
|
+
this.opt.compareValue,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return this.opt.bool === 'or'
|
|
21
|
+
? knexBuilder.orWhereRaw(sql, bindings)
|
|
22
|
+
: knexBuilder.whereRaw(sql, bindings);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return this.whereJsonNotObject(knexBuilder, builder.knex(), fieldExpression);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
whereJsonNotObject(knexBuilder, knex, fieldExpression) {
|
|
29
|
+
const innerQuery = (innerQuery) => {
|
|
30
|
+
const builder = jsonApi.whereJsonbRefOnLeftJsonbValOrRefOnRight(
|
|
31
|
+
innerQuery,
|
|
32
|
+
fieldExpression,
|
|
33
|
+
'@>',
|
|
34
|
+
this.opt.compareValue,
|
|
35
|
+
'not',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
builder.orWhereRaw(...jsonApi.whereJsonFieldQuery(knex, fieldExpression, 'IS', null));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (this.opt.bool === 'or') {
|
|
42
|
+
knexBuilder = knexBuilder.orWhere(innerQuery);
|
|
43
|
+
} else {
|
|
44
|
+
knexBuilder = knexBuilder.where(innerQuery);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return knexBuilder;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
WhereJsonNotObjectOperation,
|
|
53
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const postgresJsonApi = require('./postgresJsonApi');
|
|
4
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
5
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
6
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
7
|
+
|
|
8
|
+
// `whereJsonSupersetOf`, `whereJsonSubsetOf` and their variants.
|
|
9
|
+
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);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return knexBuilder;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
WhereJsonOperation,
|
|
31
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mysqlJsonApi = require('./mysqlJsonApi');
|
|
4
|
+
const { getJsonDialect } = require('../../../utils/postgresJsonPath');
|
|
5
|
+
const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
|
|
6
|
+
|
|
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
|
+
|
|
12
|
+
if (knex && getJsonDialect(knex, this.name) !== 'mysql') {
|
|
13
|
+
throw new Error(`${this.name} is only supported on mysql.`);
|
|
14
|
+
}
|
|
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
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
WhereJsonOverlapsOperation,
|
|
31
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseFieldExpression } = require('../../../utils/parseFieldExpression');
|
|
4
|
+
const { asArray, isObject, isPlainObject, isString } = require('../../../utils/objectUtils');
|
|
5
|
+
const { toMySqlJsonPath } = require('../../../utils/mysqlJsonPath');
|
|
6
|
+
|
|
7
|
+
// The MySQL versions of the `whereJson*` methods. They mirror the postgres
|
|
8
|
+
// operators (`@>`, `<@`, `?|`, `?&`) so that the methods find the same rows on
|
|
9
|
+
// both databases.
|
|
10
|
+
//
|
|
11
|
+
// The parts of the queries are `{ sql, bindings, type }` objects. `type` is
|
|
12
|
+
// `'object'` or `'nonObject'` when the JSON type of the part is known.
|
|
13
|
+
|
|
14
|
+
// Joins sql parts: sql`JSON_CONTAINS(${target}, ${candidate})`.
|
|
15
|
+
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 };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function literal(text) {
|
|
28
|
+
return { sql: text, bindings: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A field expression like `Table.column:a.b[0]`.
|
|
32
|
+
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
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function jsonValue(value) {
|
|
46
|
+
return {
|
|
47
|
+
sql: 'CAST(? AS JSON)',
|
|
48
|
+
bindings: [JSON.stringify(value)],
|
|
49
|
+
type: isPlainObject(value) ? 'object' : 'nonObject',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The right hand side of the methods: a field expression or a JSON value.
|
|
54
|
+
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
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// `target @> candidate`. On postgres an object is only contained in an object, but
|
|
65
|
+
// MySQL's `JSON_CONTAINS()` also finds objects inside arrays.
|
|
66
|
+
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;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function not(part, prefix) {
|
|
84
|
+
return prefix === 'not' ? sql`NOT ${part}` : part;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toRawArgs(part) {
|
|
88
|
+
return [part.sql, part.bindings];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// `whereJsonSupersetOf` (`@>`) and `whereJsonSubsetOf` (`<@`).
|
|
92
|
+
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));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// `whereJsonNotArray` and `whereJsonNotObject`. Like on postgres, NULL is neither
|
|
101
|
+
// an array nor an object.
|
|
102
|
+
function whereJsonNotContains(fieldExpression, compareValue) {
|
|
103
|
+
const left = field(fieldExpression);
|
|
104
|
+
return toRawArgs(sql`(NOT ${contains(left, jsonValue(compareValue))} OR ${left} IS NULL)`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// `whereJsonHasAny` (`?|`) and `whereJsonHasAll` (`?&`). Like on postgres, the keys
|
|
108
|
+
// are looked for in the keys of objects, in the string items of arrays and in
|
|
109
|
+
// 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.');
|
|
119
|
+
}
|
|
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
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// `whereJsonOverlaps`. A NULL or missing field doesn't overlap anything, so
|
|
153
|
+
// `whereJsonNotOverlaps` finds it.
|
|
154
|
+
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);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
whereJsonContains,
|
|
164
|
+
whereJsonNotContains,
|
|
165
|
+
whereJsonHas,
|
|
166
|
+
whereJsonOverlaps,
|
|
167
|
+
};
|