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.
Files changed (69) hide show
  1. package/README.md +16 -16
  2. package/lib/model/AjvValidator.js +173 -18
  3. package/lib/model/Model.js +3 -1
  4. package/lib/model/graph/ModelGraphNode.js +12 -0
  5. package/lib/model/modelToJson.js +2 -1
  6. package/lib/queryBuilder/QueryBuilder.js +339 -47
  7. package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
  8. package/lib/queryBuilder/ReferenceBuilder.js +5 -3
  9. package/lib/queryBuilder/RelationExpression.js +21 -9
  10. package/lib/queryBuilder/StaticHookArguments.js +12 -1
  11. package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
  12. package/lib/queryBuilder/graph/GraphOptions.js +21 -2
  13. package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
  14. package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
  15. package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
  16. package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
  17. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
  18. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
  19. package/lib/queryBuilder/join/JoinResultParser.js +22 -0
  20. package/lib/queryBuilder/join/RelationJoiner.js +71 -5
  21. package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
  22. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
  23. package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
  24. package/lib/queryBuilder/operations/InsertOperation.js +121 -12
  25. package/lib/queryBuilder/operations/NoneOperation.js +37 -0
  26. package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
  27. package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
  28. package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
  29. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
  30. package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
  31. package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
  32. package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
  33. package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
  34. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
  35. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
  36. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
  37. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
  38. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
  39. package/lib/queryBuilder/operations/select/Selection.js +95 -4
  40. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
  41. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
  42. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
  43. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
  44. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
  45. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
  46. package/lib/queryBuilder/whereGroups.js +125 -0
  47. package/lib/relations/Relation.js +5 -2
  48. package/lib/relations/RelationProperty.js +7 -3
  49. package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
  50. package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
  51. package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
  52. package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
  53. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
  54. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
  55. package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
  56. package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
  57. package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
  58. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
  59. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
  60. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
  61. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
  62. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
  63. package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
  64. package/lib/utils/deprecate.js +6 -0
  65. package/lib/utils/identifierMapping.js +23 -3
  66. package/lib/utils/objectUtils.js +9 -5
  67. package/lib/utils/parseFieldExpression.js +28 -0
  68. package/package.json +22 -14
  69. 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._withGraph(exp, options, getWhereInEagerAlgorithm(this));
220
+ return this._withGraphUsing(exp, options, getWhereInEagerAlgorithm(this));
193
221
  }
194
222
 
195
223
  withGraphJoined(exp, options = {}) {
196
- return this._withGraph(exp, options, getJoinEagerAlgorithm(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, ...options };
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 eagerOp = this.findOperation(EagerOperation);
286
+ const expression = getGraphExpression(this);
227
287
 
228
- if (eagerOp && !eagerOp.expression.isEmpty) {
229
- return eagerOp.expression.toPojo();
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 eagerOp = this.findOperation(EagerOperation);
296
+ const expression = getGraphExpression(this);
237
297
 
238
- if (eagerOp && !eagerOp.expression.isEmpty) {
239
- return eagerOp.modifiersAtPath.map((it) => Object.assign({}, it));
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
- const eagerOp = ensureEagerOperation(this);
247
- eagerOp.modifiersAtPath.push({ path, modifier });
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
- const eagerOp = this.findOperation(EagerOperation);
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
- builder.toKnexQuery(knexBuilder).as('temp');
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 EagerOperationClass = getOperationClassForEagerAlgorithm(builder, algorithm);
1090
+ if (!algorithm) {
1091
+ const eagerOp = builder.findOperation(EagerOperation);
962
1092
 
963
- if (eagerOp instanceof EagerOperationClass) {
1093
+ if (eagerOp) {
964
1094
  return eagerOp;
965
- } else {
966
- const newEagerOp = new EagerOperationClass('eager', {
967
- defaultGraphOptions,
968
- });
1095
+ }
969
1096
 
970
- if (eagerOp) {
971
- newEagerOp.cloneFrom(eagerOp);
972
- }
1097
+ algorithm = getWhereInEagerAlgorithm(builder);
1098
+ }
973
1099
 
974
- builder.clear(EagerOperation);
975
- builder.addOperation(newEagerOp);
1100
+ const EagerOperationClass = getOperationClassForEagerAlgorithm(builder, algorithm);
1101
+ const EagerOperationKind = getEagerOperationKind(EagerOperationClass);
1102
+ const eagerOp = builder.findOperation(EagerOperationKind);
976
1103
 
977
- return newEagerOp;
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
- if (eagerOp) {
981
- return eagerOp;
982
- } else {
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
- const newEagerOp = new EagerOperationClass('eager', {
989
- defaultGraphOptions,
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
- builder.addOperation(newEagerOp);
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
- return newEagerOp;
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 eagerOp = builder.findOperation(EagerOperation);
1294
+ const expr = getGraphExpression(builder);
1041
1295
 
1042
- if (!eagerOp) {
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 eagerOp = builder.findOperation(EagerOperation);
1501
+ const eagerOps = findEagerOperations(builder);
1219
1502
 
1220
1503
  builder.clear(EagerOperation);
1221
- builder.addOperation(eagerOp);
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 { parseFieldExpression } = require('../utils/parseFieldExpression');
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
- const jsonFieldRef = this._parsedExpr.access.map((field) => field.ref).join(',');
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
- return (node && node.$childNames && node.$childNames.slice()) || getChildNames(node);
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) {