objection 3.1.5 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/lib/model/AjvValidator.js +173 -18
- package/lib/model/Model.js +3 -1
- package/lib/model/graph/ModelGraphNode.js +12 -0
- package/lib/model/modelToJson.js +2 -1
- package/lib/queryBuilder/QueryBuilder.js +339 -47
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
- package/lib/queryBuilder/ReferenceBuilder.js +5 -3
- package/lib/queryBuilder/RelationExpression.js +21 -9
- package/lib/queryBuilder/StaticHookArguments.js +12 -1
- package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
- package/lib/queryBuilder/graph/GraphOptions.js +21 -2
- package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
- package/lib/queryBuilder/join/JoinResultParser.js +22 -0
- package/lib/queryBuilder/join/RelationJoiner.js +71 -5
- package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
- package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
- package/lib/queryBuilder/operations/InsertOperation.js +121 -12
- package/lib/queryBuilder/operations/NoneOperation.js +37 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
- package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
- package/lib/queryBuilder/operations/select/Selection.js +95 -4
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
- package/lib/queryBuilder/whereGroups.js +125 -0
- package/lib/relations/Relation.js +5 -2
- package/lib/relations/RelationProperty.js +7 -3
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
- package/lib/utils/deprecate.js +6 -0
- package/lib/utils/identifierMapping.js +23 -3
- package/lib/utils/objectUtils.js +9 -5
- package/lib/utils/parseFieldExpression.js +28 -0
- package/package.json +22 -14
- package/typings/objection/index.d.ts +339 -68
|
@@ -43,8 +43,10 @@ const { SelectOperation } = require('./operations/select/SelectOperation');
|
|
|
43
43
|
const { EagerOperation } = require('./operations/eager/EagerOperation');
|
|
44
44
|
const { RangeOperation } = require('./operations/RangeOperation');
|
|
45
45
|
const { FirstOperation } = require('./operations/FirstOperation');
|
|
46
|
+
const { NoneOperation } = require('./operations/NoneOperation');
|
|
46
47
|
const { FromOperation } = require('./operations/FromOperation');
|
|
47
48
|
const { KnexOperation } = require('./operations/KnexOperation');
|
|
49
|
+
const { wrapWhereGroups } = require('./whereGroups');
|
|
48
50
|
|
|
49
51
|
class QueryBuilder extends QueryBuilderBase {
|
|
50
52
|
static init(self, modelClass) {
|
|
@@ -56,8 +58,13 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
56
58
|
self._modifiers = {};
|
|
57
59
|
|
|
58
60
|
self._allowedGraphExpression = null;
|
|
61
|
+
self._graphModifiersAtPath = [];
|
|
62
|
+
// The eager algorithm that was most recently used explicitly, see `withGraph()`.
|
|
63
|
+
self._graphAlgorithm = null;
|
|
64
|
+
self._isJoinChildQuery = false;
|
|
59
65
|
self._findOperationOptions = modelClass.defaultFindOptions;
|
|
60
66
|
self._relatedQueryFor = null;
|
|
67
|
+
self._internalSelections = new Set();
|
|
61
68
|
|
|
62
69
|
self._findOperationFactory = findOperationFactory;
|
|
63
70
|
self._insertOperationFactory = insertOperationFactory;
|
|
@@ -76,6 +83,27 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
76
83
|
return RelationExpression.create(expr).toPojo();
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
childQueryOf(query, options = {}) {
|
|
87
|
+
super.childQueryOf(query, options);
|
|
88
|
+
|
|
89
|
+
if (query) {
|
|
90
|
+
this._isJoinChildQuery = !!options.isJoinChildQuery;
|
|
91
|
+
|
|
92
|
+
if (query.isObjectionQueryBuilder) {
|
|
93
|
+
this._graphAlgorithm = query._graphAlgorithm;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Returns true if this query was created for a relation that is loaded using
|
|
101
|
+
// `withGraphJoined()`. Such queries are only used to build the join and are
|
|
102
|
+
// never executed themselves.
|
|
103
|
+
isJoinChildQuery() {
|
|
104
|
+
return this._isJoinChildQuery;
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
tableNameFor(modelClassOrTableName, newTableName) {
|
|
80
108
|
return super.tableNameFor(getTableName(modelClassOrTableName), newTableName);
|
|
81
109
|
}
|
|
@@ -189,20 +217,52 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
189
217
|
}
|
|
190
218
|
|
|
191
219
|
withGraphFetched(exp, options = {}) {
|
|
192
|
-
return this.
|
|
220
|
+
return this._withGraphUsing(exp, options, getWhereInEagerAlgorithm(this));
|
|
193
221
|
}
|
|
194
222
|
|
|
195
223
|
withGraphJoined(exp, options = {}) {
|
|
196
|
-
return this.
|
|
224
|
+
return this._withGraphUsing(exp, options, getJoinEagerAlgorithm(this));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Like `withGraphFetched()` or `withGraphJoined()` with `options.algorithm`
|
|
228
|
+
// set to 'fetch' or 'join'. Without it, the top-level relations that are
|
|
229
|
+
// already loaded by one of the algorithms are merged into it, and new ones
|
|
230
|
+
// use the most recently used algorithm.
|
|
231
|
+
withGraph(exp, { algorithm, ...options } = {}) {
|
|
232
|
+
if (algorithm !== undefined) {
|
|
233
|
+
return this._withGraphUsing(exp, options, getEagerAlgorithmByName(this, algorithm));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const parsedExp = parseRelationExpression(this.modelClass(), exp);
|
|
237
|
+
|
|
238
|
+
for (const [algorithm, groupExp] of groupByEagerAlgorithm(this, parsedExp)) {
|
|
239
|
+
this._withGraph(groupExp, options, algorithm);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_withGraphUsing(exp, options, algorithm) {
|
|
246
|
+
this._graphAlgorithm = algorithm;
|
|
247
|
+
return this._withGraph(exp, options, algorithm);
|
|
197
248
|
}
|
|
198
249
|
|
|
199
250
|
_withGraph(exp, options, algorithm) {
|
|
200
251
|
const eagerOp = ensureEagerOperation(this, algorithm);
|
|
201
252
|
const parsedExp = parseRelationExpression(this.modelClass(), exp);
|
|
202
253
|
|
|
254
|
+
// The `joinOperation` option only applies to the relations of this call,
|
|
255
|
+
// see `RelationJoiner`. All other options are shared by all calls.
|
|
256
|
+
const { joinOperation, ...graphOptions } = options;
|
|
257
|
+
|
|
203
258
|
eagerOp.expression = eagerOp.expression.merge(parsedExp);
|
|
204
|
-
eagerOp.graphOptions = { ...eagerOp.graphOptions, ...
|
|
259
|
+
eagerOp.graphOptions = { ...eagerOp.graphOptions, ...graphOptions };
|
|
205
260
|
|
|
261
|
+
if (joinOperation) {
|
|
262
|
+
eagerOp.joinOperations.push({ expression: parsedExp, joinOperation });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
checkMixedEager(this);
|
|
206
266
|
checkEager(this);
|
|
207
267
|
return this;
|
|
208
268
|
}
|
|
@@ -223,28 +283,30 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
223
283
|
}
|
|
224
284
|
|
|
225
285
|
graphExpressionObject() {
|
|
226
|
-
const
|
|
286
|
+
const expression = getGraphExpression(this);
|
|
227
287
|
|
|
228
|
-
if (
|
|
229
|
-
return
|
|
288
|
+
if (expression && !expression.isEmpty) {
|
|
289
|
+
return expression.toPojo();
|
|
230
290
|
} else {
|
|
231
291
|
return null;
|
|
232
292
|
}
|
|
233
293
|
}
|
|
234
294
|
|
|
235
295
|
graphModifiersAtPath() {
|
|
236
|
-
const
|
|
296
|
+
const expression = getGraphExpression(this);
|
|
237
297
|
|
|
238
|
-
if (
|
|
239
|
-
return
|
|
298
|
+
if (expression && !expression.isEmpty) {
|
|
299
|
+
return this._graphModifiersAtPath.map((it) => Object.assign({}, it));
|
|
240
300
|
} else {
|
|
241
301
|
return [];
|
|
242
302
|
}
|
|
243
303
|
}
|
|
244
304
|
|
|
245
305
|
modifyGraph(path, modifier) {
|
|
246
|
-
|
|
247
|
-
|
|
306
|
+
// The modifiers are shared by all eager operations. They only take effect
|
|
307
|
+
// for the paths that exist in each operation's expression.
|
|
308
|
+
ensureEagerOperation(this);
|
|
309
|
+
this._graphModifiersAtPath.push({ path, modifier });
|
|
248
310
|
return this;
|
|
249
311
|
}
|
|
250
312
|
|
|
@@ -301,8 +363,7 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
301
363
|
}
|
|
302
364
|
|
|
303
365
|
hasWithGraph() {
|
|
304
|
-
|
|
305
|
-
return !!eagerOp && !eagerOp.expression.isEmpty;
|
|
366
|
+
return findEagerOperations(this).some((eagerOp) => !eagerOp.expression.isEmpty);
|
|
306
367
|
}
|
|
307
368
|
|
|
308
369
|
isSelectAll() {
|
|
@@ -350,6 +411,9 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
350
411
|
builder._modifiers = { ...this._modifiers };
|
|
351
412
|
|
|
352
413
|
builder._allowedGraphExpression = this._allowedGraphExpression;
|
|
414
|
+
builder._graphModifiersAtPath = this._graphModifiersAtPath.slice();
|
|
415
|
+
builder._graphAlgorithm = this._graphAlgorithm;
|
|
416
|
+
builder._isJoinChildQuery = this._isJoinChildQuery;
|
|
353
417
|
builder._findOperationOptions = this._findOperationOptions;
|
|
354
418
|
builder._relatedQueryFor = this._relatedQueryFor;
|
|
355
419
|
|
|
@@ -371,8 +435,17 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
371
435
|
return builder;
|
|
372
436
|
}
|
|
373
437
|
|
|
438
|
+
// Registers a property of the result models that was selected internally,
|
|
439
|
+
// e.g. by an eager operation, without the user asking for it. Such properties
|
|
440
|
+
// are omitted from the result models before `runAfter` hooks are called.
|
|
441
|
+
addInternalSelection(prop) {
|
|
442
|
+
this._internalSelections.add(prop);
|
|
443
|
+
return this;
|
|
444
|
+
}
|
|
445
|
+
|
|
374
446
|
clearWithGraph() {
|
|
375
447
|
this.clear(EagerOperation);
|
|
448
|
+
this._graphAlgorithm = null;
|
|
376
449
|
return this;
|
|
377
450
|
}
|
|
378
451
|
|
|
@@ -419,9 +492,27 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
419
492
|
async resultSize() {
|
|
420
493
|
const knex = this.knex();
|
|
421
494
|
const builder = this.clone().clear(/orderBy|offset|limit/);
|
|
495
|
+
const joinEagerOp = builder.findOperation(JoinEagerOperation);
|
|
496
|
+
|
|
497
|
+
if (joinEagerOp) {
|
|
498
|
+
// `withGraphJoined` needs the table metadata to build its selects.
|
|
499
|
+
await joinEagerOp.onBefore3(builder);
|
|
500
|
+
}
|
|
422
501
|
|
|
423
502
|
const countQuery = knex.count('* as count').from((knexBuilder) => {
|
|
424
|
-
|
|
503
|
+
// Also clear the order on the built knex query, since `orderBy` calls
|
|
504
|
+
// can be added at build time, e.g. by a relation's `modify`.
|
|
505
|
+
knexBuilder = builder.toKnexQuery(knexBuilder).clearOrder();
|
|
506
|
+
|
|
507
|
+
if (joinEagerOp) {
|
|
508
|
+
// The joins of `withGraphJoined` return one row per joined child.
|
|
509
|
+
// Count the distinct root models instead.
|
|
510
|
+
const tableRef = builder.tableRef();
|
|
511
|
+
const idColumns = builder.modelClass().getIdColumnArray();
|
|
512
|
+
knexBuilder.clearSelect().distinct(idColumns.map((col) => `${tableRef}.${col}`));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
knexBuilder.as('temp');
|
|
425
516
|
});
|
|
426
517
|
|
|
427
518
|
if (this.internalOptions().debug) {
|
|
@@ -808,6 +899,10 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
808
899
|
return this.addOperation(new FirstOperation('first'), args);
|
|
809
900
|
}
|
|
810
901
|
|
|
902
|
+
none() {
|
|
903
|
+
return this.addOperation(new NoneOperation('none'), []);
|
|
904
|
+
}
|
|
905
|
+
|
|
811
906
|
joinRelated(expression, options) {
|
|
812
907
|
ensureJoinRelatedOperation(this, 'innerJoin').addCall({
|
|
813
908
|
expression,
|
|
@@ -880,6 +975,26 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
880
975
|
return this;
|
|
881
976
|
}
|
|
882
977
|
|
|
978
|
+
updateById(id, modelOrObject) {
|
|
979
|
+
return this.findById(id)
|
|
980
|
+
.update(modelOrObject)
|
|
981
|
+
.runBefore((_, builder) => {
|
|
982
|
+
if (!builder.internalOptions().skipUndefined) {
|
|
983
|
+
assertIdNotUndefined(id, `undefined was passed to updateById`);
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
patchById(id, modelOrObject) {
|
|
989
|
+
return this.findById(id)
|
|
990
|
+
.patch(modelOrObject)
|
|
991
|
+
.runBefore((_, builder) => {
|
|
992
|
+
if (!builder.internalOptions().skipUndefined) {
|
|
993
|
+
assertIdNotUndefined(id, `undefined was passed to patchById`);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
|
|
883
998
|
deleteById(id) {
|
|
884
999
|
return this.findById(id)
|
|
885
1000
|
.delete()
|
|
@@ -922,6 +1037,10 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
922
1037
|
return this.addOperation(new FromOperation('from'), args);
|
|
923
1038
|
}
|
|
924
1039
|
|
|
1040
|
+
updateFrom(...args) {
|
|
1041
|
+
return this.addOperation(new KnexOperation('updateFrom'), args);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
925
1044
|
table(...args) {
|
|
926
1045
|
return this.addOperation(new FromOperation('table'), args);
|
|
927
1046
|
}
|
|
@@ -930,6 +1049,15 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
930
1049
|
if (arguments.length === 0) {
|
|
931
1050
|
return this._relatedQueryFor;
|
|
932
1051
|
} else {
|
|
1052
|
+
// The write operation factories installed by the static `relatedQuery()`
|
|
1053
|
+
// read `for()` when the write method is called. Setting it afterwards
|
|
1054
|
+
// would make the query silently target the wrong rows.
|
|
1055
|
+
if (this._findOperationFactory !== findOperationFactory && !this.isFind()) {
|
|
1056
|
+
throw new Error(
|
|
1057
|
+
'for() must be called before insert, update, patch, delete, relate or unrelate on queries created using the static relatedQuery method',
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
933
1061
|
this._relatedQueryFor = relatedQueryFor;
|
|
934
1062
|
return this;
|
|
935
1063
|
}
|
|
@@ -952,48 +1080,143 @@ function getTableName(modelClassOrTableName) {
|
|
|
952
1080
|
}
|
|
953
1081
|
}
|
|
954
1082
|
|
|
1083
|
+
// A query builder can hold at most one eager operation of each kind: one
|
|
1084
|
+
// `JoinEagerOperation` for `withGraphJoined` and one `WhereInEagerOperation`
|
|
1085
|
+
// (or its subclass `NaiveEagerOperation`) for `withGraphFetched`.
|
|
955
1086
|
function ensureEagerOperation(builder, algorithm = null) {
|
|
956
1087
|
const modelClass = builder.modelClass();
|
|
957
1088
|
const defaultGraphOptions = modelClass.getDefaultGraphOptions();
|
|
958
|
-
const eagerOp = builder.findOperation(EagerOperation);
|
|
959
1089
|
|
|
960
|
-
if (algorithm) {
|
|
961
|
-
const
|
|
1090
|
+
if (!algorithm) {
|
|
1091
|
+
const eagerOp = builder.findOperation(EagerOperation);
|
|
962
1092
|
|
|
963
|
-
if (eagerOp
|
|
1093
|
+
if (eagerOp) {
|
|
964
1094
|
return eagerOp;
|
|
965
|
-
}
|
|
966
|
-
const newEagerOp = new EagerOperationClass('eager', {
|
|
967
|
-
defaultGraphOptions,
|
|
968
|
-
});
|
|
1095
|
+
}
|
|
969
1096
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
1097
|
+
algorithm = getWhereInEagerAlgorithm(builder);
|
|
1098
|
+
}
|
|
973
1099
|
|
|
974
|
-
|
|
975
|
-
|
|
1100
|
+
const EagerOperationClass = getOperationClassForEagerAlgorithm(builder, algorithm);
|
|
1101
|
+
const EagerOperationKind = getEagerOperationKind(EagerOperationClass);
|
|
1102
|
+
const eagerOp = builder.findOperation(EagerOperationKind);
|
|
976
1103
|
|
|
977
|
-
|
|
978
|
-
|
|
1104
|
+
if (eagerOp && eagerOp.constructor === EagerOperationClass) {
|
|
1105
|
+
return eagerOp;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const newEagerOp = new EagerOperationClass('eager', {
|
|
1109
|
+
defaultGraphOptions,
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
if (eagerOp) {
|
|
1113
|
+
// Switching between algorithms of the same kind, e.g. from
|
|
1114
|
+
// `WhereInEagerOperation` to `NaiveEagerOperation`.
|
|
1115
|
+
newEagerOp.cloneFrom(eagerOp);
|
|
1116
|
+
builder.clear(EagerOperationKind);
|
|
1117
|
+
} else if (!builder.has(EagerOperation)) {
|
|
1118
|
+
// The `modifyGraph` modifiers only live as long as an eager operation.
|
|
1119
|
+
builder._graphModifiersAtPath = [];
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
builder.addOperation(newEagerOp);
|
|
1123
|
+
|
|
1124
|
+
// Keep the joined operation before the fetched one (see `findEagerOperations`),
|
|
1125
|
+
// even if it is added after `addImplicitOperations`, e.g. in `runBefore`.
|
|
1126
|
+
const fetchOp =
|
|
1127
|
+
EagerOperationKind === JoinEagerOperation && builder.findOperation(WhereInEagerOperation);
|
|
1128
|
+
|
|
1129
|
+
if (fetchOp) {
|
|
1130
|
+
builder.removeOperation(fetchOp).addOperation(fetchOp);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return newEagerOp;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function getEagerOperationKind(EagerOperationClass) {
|
|
1137
|
+
return EagerOperationClass === JoinEagerOperation ? JoinEagerOperation : WhereInEagerOperation;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function findEagerOperations(builder) {
|
|
1141
|
+
// Joined operations first, so that their `onBuild` runs before the fetched
|
|
1142
|
+
// one's. See `WhereInEagerOperation.onBuild`.
|
|
1143
|
+
return [
|
|
1144
|
+
builder.findOperation(JoinEagerOperation),
|
|
1145
|
+
builder.findOperation(WhereInEagerOperation),
|
|
1146
|
+
].filter(Boolean);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function getGraphExpression(builder) {
|
|
1150
|
+
const eagerOps = findEagerOperations(builder);
|
|
1151
|
+
|
|
1152
|
+
if (eagerOps.length === 0) {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
return eagerOps.reduce(
|
|
1157
|
+
(expression, eagerOp) => expression.merge(eagerOp.expression),
|
|
1158
|
+
RelationExpression.create(),
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function getEagerAlgorithmByName(builder, name) {
|
|
1163
|
+
if (name === 'fetch') {
|
|
1164
|
+
return getWhereInEagerAlgorithm(builder);
|
|
1165
|
+
} else if (name === 'join') {
|
|
1166
|
+
return getJoinEagerAlgorithm(builder);
|
|
979
1167
|
} else {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const EagerOperationClass = getOperationClassForEagerAlgorithm(
|
|
984
|
-
builder,
|
|
985
|
-
getWhereInEagerAlgorithm(builder),
|
|
986
|
-
);
|
|
1168
|
+
throw new Error(`unknown graph algorithm "${name}", expected "fetch" or "join"`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
987
1171
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1172
|
+
function getEagerAlgorithmOfOperation(builder, eagerOp) {
|
|
1173
|
+
if (eagerOp instanceof JoinEagerOperation) {
|
|
1174
|
+
return getJoinEagerAlgorithm(builder);
|
|
1175
|
+
} else if (eagerOp instanceof NaiveEagerOperation) {
|
|
1176
|
+
return getNaiveEagerAlgorithm(builder);
|
|
1177
|
+
} else {
|
|
1178
|
+
return getWhereInEagerAlgorithm(builder);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
991
1181
|
|
|
992
|
-
|
|
1182
|
+
// Splits `expression` by its top-level relations into one expression per eager
|
|
1183
|
+
// algorithm. Relations that are already loaded by one of the eager operations
|
|
1184
|
+
// keep its algorithm, new ones use the most recently used one.
|
|
1185
|
+
function groupByEagerAlgorithm(builder, expression) {
|
|
1186
|
+
const defaultAlgorithm = builder._graphAlgorithm || getWhereInEagerAlgorithm(builder);
|
|
1187
|
+
const { node } = expression;
|
|
1188
|
+
const rootProps = {};
|
|
1189
|
+
const groups = new Map();
|
|
1190
|
+
|
|
1191
|
+
for (const key of Object.keys(node)) {
|
|
1192
|
+
if (key.startsWith('$')) {
|
|
1193
|
+
rootProps[key] = node[key];
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
993
1196
|
|
|
994
|
-
|
|
1197
|
+
for (const childName of node.$childNames) {
|
|
1198
|
+
const eagerOp = findEagerOperations(builder).find(
|
|
1199
|
+
(op) => op.expression.node.$allRecursive || op.expression.node[childName],
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
const algorithm = eagerOp ? getEagerAlgorithmOfOperation(builder, eagerOp) : defaultAlgorithm;
|
|
1203
|
+
let group = groups.get(algorithm);
|
|
1204
|
+
|
|
1205
|
+
if (!group) {
|
|
1206
|
+
group = { ...rootProps, $childNames: [] };
|
|
1207
|
+
groups.set(algorithm, group);
|
|
995
1208
|
}
|
|
1209
|
+
|
|
1210
|
+
group.$childNames.push(childName);
|
|
1211
|
+
group[childName] = node[childName];
|
|
996
1212
|
}
|
|
1213
|
+
|
|
1214
|
+
if (groups.size === 0) {
|
|
1215
|
+
// No top-level relations, e.g. `*` or an empty expression.
|
|
1216
|
+
return [[defaultAlgorithm, expression]];
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
return [...groups].map(([algorithm, group]) => [algorithm, RelationExpression.create(group)]);
|
|
997
1220
|
}
|
|
998
1221
|
|
|
999
1222
|
function getWhereInEagerAlgorithm(builder) {
|
|
@@ -1036,14 +1259,44 @@ function parseRelationExpression(modelClass, exp) {
|
|
|
1036
1259
|
}
|
|
1037
1260
|
}
|
|
1038
1261
|
|
|
1262
|
+
function checkMixedEager(builder) {
|
|
1263
|
+
const joinOp = builder.findOperation(JoinEagerOperation);
|
|
1264
|
+
const fetchOp = builder.findOperation(WhereInEagerOperation);
|
|
1265
|
+
|
|
1266
|
+
if (!joinOp || !fetchOp) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const joinNode = joinOp.expression.node;
|
|
1271
|
+
const fetchNode = fetchOp.expression.node;
|
|
1272
|
+
const isUsed = (node) => node.$allRecursive || node.$childNames.length > 0;
|
|
1273
|
+
|
|
1274
|
+
if (!isUsed(joinNode) || !isUsed(fetchNode)) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (joinNode.$allRecursive || fetchNode.$allRecursive) {
|
|
1279
|
+
throw new Error(
|
|
1280
|
+
'relation expression `*` cannot be used when mixing withGraphJoined and withGraphFetched',
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
for (const childName of joinNode.$childNames) {
|
|
1285
|
+
if (fetchNode.$childNames.includes(childName)) {
|
|
1286
|
+
throw new Error(
|
|
1287
|
+
`relation \`${childName}\` cannot be loaded with both withGraphJoined and withGraphFetched`,
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1039
1293
|
function checkEager(builder) {
|
|
1040
|
-
const
|
|
1294
|
+
const expr = getGraphExpression(builder);
|
|
1041
1295
|
|
|
1042
|
-
if (!
|
|
1296
|
+
if (!expr) {
|
|
1043
1297
|
return;
|
|
1044
1298
|
}
|
|
1045
1299
|
|
|
1046
|
-
const expr = eagerOp.expression;
|
|
1047
1300
|
const allowedExpr = builder.allowedGraphExpression();
|
|
1048
1301
|
|
|
1049
1302
|
if (expr.numChildren > 0 && allowedExpr && !allowedExpr.isSubExpression(expr)) {
|
|
@@ -1065,6 +1318,7 @@ function findQueryExecutorOperation(builder) {
|
|
|
1065
1318
|
function beforeExecute(builder) {
|
|
1066
1319
|
let promise = Promise.resolve();
|
|
1067
1320
|
|
|
1321
|
+
checkRelatedQueryFor(builder);
|
|
1068
1322
|
builder = addImplicitOperations(builder);
|
|
1069
1323
|
|
|
1070
1324
|
// Resolve all before hooks before building and executing the query
|
|
@@ -1110,6 +1364,7 @@ function afterExecute(builder, result) {
|
|
|
1110
1364
|
|
|
1111
1365
|
promise = chainOperationHooks(promise, builder, 'onAfter1');
|
|
1112
1366
|
promise = chainOperationHooks(promise, builder, 'onAfter2');
|
|
1367
|
+
promise = promise.then((result) => omitInternalSelections(builder, result));
|
|
1113
1368
|
|
|
1114
1369
|
promise = chainHooks(promise, builder, builder.context().runAfter);
|
|
1115
1370
|
promise = chainHooks(promise, builder, builder.internalContext().runAfter);
|
|
@@ -1119,6 +1374,22 @@ function afterExecute(builder, result) {
|
|
|
1119
1374
|
return promise;
|
|
1120
1375
|
}
|
|
1121
1376
|
|
|
1377
|
+
function omitInternalSelections(builder, result) {
|
|
1378
|
+
const props = builder._internalSelections;
|
|
1379
|
+
|
|
1380
|
+
if (props.size && Array.isArray(result) && isObject(result[0])) {
|
|
1381
|
+
const modelClass = builder.resultModelClass();
|
|
1382
|
+
|
|
1383
|
+
for (const model of result) {
|
|
1384
|
+
for (const prop of props) {
|
|
1385
|
+
modelClass.omitImpl(model, prop);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
return result;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1122
1393
|
class ReturnImmediatelyException {
|
|
1123
1394
|
constructor(value) {
|
|
1124
1395
|
this.value = value;
|
|
@@ -1181,6 +1452,7 @@ function ensureJoinRelatedOperation(builder, joinOperation) {
|
|
|
1181
1452
|
}
|
|
1182
1453
|
|
|
1183
1454
|
function prebuildQuery(builder) {
|
|
1455
|
+
checkRelatedQueryFor(builder);
|
|
1184
1456
|
builder = addImplicitOperations(builder);
|
|
1185
1457
|
builder = callOnBuildHooks(builder);
|
|
1186
1458
|
|
|
@@ -1193,6 +1465,17 @@ function prebuildQuery(builder) {
|
|
|
1193
1465
|
}
|
|
1194
1466
|
}
|
|
1195
1467
|
|
|
1468
|
+
function checkRelatedQueryFor(builder) {
|
|
1469
|
+
// `for()` is only consumed by the operation factories installed by the static
|
|
1470
|
+
// `relatedQuery()`. The default ones ignore it, which would silently make the
|
|
1471
|
+
// query target all rows of the table.
|
|
1472
|
+
if (builder.for() !== null && builder._findOperationFactory === findOperationFactory) {
|
|
1473
|
+
throw new Error(
|
|
1474
|
+
'for() can only be used with queries created using the static relatedQuery method',
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1196
1479
|
function addImplicitOperations(builder) {
|
|
1197
1480
|
if (builder.isFind()) {
|
|
1198
1481
|
// If no write operations have been called at this point this query is a
|
|
@@ -1215,10 +1498,13 @@ function addFindOperation(builder) {
|
|
|
1215
1498
|
}
|
|
1216
1499
|
|
|
1217
1500
|
function moveEagerOperationToEnd(builder) {
|
|
1218
|
-
const
|
|
1501
|
+
const eagerOps = findEagerOperations(builder);
|
|
1219
1502
|
|
|
1220
1503
|
builder.clear(EagerOperation);
|
|
1221
|
-
|
|
1504
|
+
|
|
1505
|
+
for (const eagerOp of eagerOps) {
|
|
1506
|
+
builder.addOperation(eagerOp);
|
|
1507
|
+
}
|
|
1222
1508
|
}
|
|
1223
1509
|
|
|
1224
1510
|
function callOnBuildHooks(builder) {
|
|
@@ -1226,7 +1512,7 @@ function callOnBuildHooks(builder) {
|
|
|
1226
1512
|
callOnBuildFuncs(builder, builder.internalContext().onBuild);
|
|
1227
1513
|
|
|
1228
1514
|
builder.executeOnBuild();
|
|
1229
|
-
return builder;
|
|
1515
|
+
return wrapWhereGroups(builder);
|
|
1230
1516
|
}
|
|
1231
1517
|
|
|
1232
1518
|
function callOnBuildFuncs(builder, func) {
|
|
@@ -1240,6 +1526,12 @@ function callOnBuildFuncs(builder, func) {
|
|
|
1240
1526
|
function buildKnexQuery(builder, knexBuilder = builder.knex().queryBuilder()) {
|
|
1241
1527
|
knexBuilder = builder.executeOnBuildKnex(knexBuilder);
|
|
1242
1528
|
|
|
1529
|
+
const noneOperation = builder.findOperation(NoneOperation);
|
|
1530
|
+
|
|
1531
|
+
if (noneOperation) {
|
|
1532
|
+
knexBuilder = noneOperation.onAfterBuildKnex(knexBuilder);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1243
1535
|
const fromOperation = builder.findLastOperation(QueryBuilderBase.FromSelector);
|
|
1244
1536
|
|
|
1245
1537
|
if (!builder.isPartial()) {
|
|
@@ -26,6 +26,7 @@ class QueryBuilderOperationSupport {
|
|
|
26
26
|
self._parentQuery = null;
|
|
27
27
|
self._isPartialQuery = false;
|
|
28
28
|
self._activeOperations = [];
|
|
29
|
+
self._whereGroup = null;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
static forClass(modelClass) {
|
|
@@ -413,6 +414,10 @@ class QueryBuilderOperationSupport {
|
|
|
413
414
|
const shouldAdd = this.callOperationMethod(operation, 'onAdd', [this, args]);
|
|
414
415
|
|
|
415
416
|
if (shouldAdd) {
|
|
417
|
+
if (this._whereGroup) {
|
|
418
|
+
operation.whereGroup = this._whereGroup;
|
|
419
|
+
}
|
|
420
|
+
|
|
416
421
|
if (this._activeOperations.length) {
|
|
417
422
|
const { operation: parentOperation, hookName } = last(this._activeOperations);
|
|
418
423
|
parentOperation.addChildOperation(hookName, operation);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
parseFieldExpression,
|
|
5
|
+
toPostgresJsonPathLiteral,
|
|
6
|
+
} = require('../utils/parseFieldExpression');
|
|
4
7
|
const { isObject } = require('../utils/objectUtils');
|
|
5
8
|
|
|
6
9
|
class ReferenceBuilder {
|
|
@@ -185,8 +188,7 @@ class ReferenceBuilder {
|
|
|
185
188
|
|
|
186
189
|
if (this._parsedExpr.access.length > 0) {
|
|
187
190
|
const extractor = this._cast ? '#>>' : '#>';
|
|
188
|
-
|
|
189
|
-
return `??${extractor}'{${jsonFieldRef}}'`;
|
|
191
|
+
return `??${extractor}${toPostgresJsonPathLiteral(this._parsedExpr.access)}`;
|
|
190
192
|
} else {
|
|
191
193
|
return '??';
|
|
192
194
|
}
|
|
@@ -229,6 +229,14 @@ function toString(node) {
|
|
|
229
229
|
let childExpr = childNames.map((childName) => node[childName]).map(toString);
|
|
230
230
|
let str = node.$relation;
|
|
231
231
|
|
|
232
|
+
if (node.$modify.length) {
|
|
233
|
+
str += `(${node.$modify.join(', ')})`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (node.$name !== node.$relation) {
|
|
237
|
+
str += ` as ${node.$name}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
232
240
|
if (node.$recursive) {
|
|
233
241
|
if (isNumber(node.$recursive)) {
|
|
234
242
|
str += '.^' + node.$recursive;
|
|
@@ -245,14 +253,6 @@ function toString(node) {
|
|
|
245
253
|
childExpr = childExpr[0];
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
if (node.$modify.length) {
|
|
249
|
-
str += `(${node.$modify.join(', ')})`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (node.$name !== node.$relation) {
|
|
253
|
-
str += ` as ${node.$name}`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
256
|
if (childExpr) {
|
|
257
257
|
if (str) {
|
|
258
258
|
return `${str}.${childExpr}`;
|
|
@@ -355,7 +355,19 @@ function normalizeAllRecursive(node, allRecusive) {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
function normalizeChildNames(node) {
|
|
358
|
-
|
|
358
|
+
const childNames = getChildNames(node);
|
|
359
|
+
|
|
360
|
+
if (node && node.$childNames) {
|
|
361
|
+
// The actual keys of the node are authoritative, since the node may be a
|
|
362
|
+
// modified copy of `graphExpressionObject()` with a stale `$childNames`.
|
|
363
|
+
// Keep the order of `$childNames` for the children that still exist.
|
|
364
|
+
return union(
|
|
365
|
+
node.$childNames.filter((childName) => childNames.includes(childName)),
|
|
366
|
+
childNames,
|
|
367
|
+
);
|
|
368
|
+
} else {
|
|
369
|
+
return childNames;
|
|
370
|
+
}
|
|
359
371
|
}
|
|
360
372
|
|
|
361
373
|
function findExpressionsAtPath(target, path, results) {
|