objection 3.6.0 → 3.8.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 (60) hide show
  1. package/lib/model/AjvValidator.js +132 -52
  2. package/lib/model/modelClone.js +0 -2
  3. package/lib/queryBuilder/QueryBuilder.js +128 -24
  4. package/lib/queryBuilder/QueryBuilderOperationSupport.js +15 -3
  5. package/lib/queryBuilder/RelationExpression.js +6 -4
  6. package/lib/queryBuilder/StaticHookArguments.js +4 -1
  7. package/lib/queryBuilder/graph/GraphOptions.js +36 -79
  8. package/lib/queryBuilder/graph/GraphUpsert.js +35 -7
  9. package/lib/queryBuilder/graph/insert/GraphInsert.js +2 -0
  10. package/lib/queryBuilder/join/RelationJoiner.js +104 -12
  11. package/lib/queryBuilder/join/utils.js +2 -2
  12. package/lib/queryBuilder/operations/DelegateOperation.js +22 -0
  13. package/lib/queryBuilder/operations/FirstOperation.js +5 -3
  14. package/lib/queryBuilder/operations/InsertGraphOperation.js +1 -0
  15. package/lib/queryBuilder/operations/InstanceDeleteOperation.js +2 -1
  16. package/lib/queryBuilder/operations/InstanceFindOperation.js +2 -1
  17. package/lib/queryBuilder/operations/InstanceUpdateOperation.js +8 -2
  18. package/lib/queryBuilder/operations/LimitPerParentOperation.js +145 -0
  19. package/lib/queryBuilder/operations/QueryBuilderOperation.js +31 -0
  20. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +22 -5
  21. package/lib/queryBuilder/operations/UpdateOperation.js +40 -3
  22. package/lib/queryBuilder/operations/WhereGroupOperation.js +2 -8
  23. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +5 -0
  24. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +28 -2
  25. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +2 -2
  26. package/lib/queryBuilder/operations/select/SelectOperation.js +8 -4
  27. package/lib/queryBuilder/operations/select/Selection.js +21 -20
  28. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +3 -1
  29. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +4 -9
  30. package/lib/queryBuilder/parsers/relationExpressionParser.js +6 -4
  31. package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +7 -3
  32. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +4 -7
  33. package/lib/queryBuilder/whereGroups.js +35 -40
  34. package/lib/queryBuilder/withoutOrder.js +33 -0
  35. package/lib/relations/Relation.js +4 -0
  36. package/lib/relations/RelationFindOperation.js +30 -43
  37. package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +4 -2
  38. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +65 -19
  39. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +31 -42
  40. package/lib/relations/manyToMany/ManyToManyRelation.js +15 -16
  41. package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +0 -13
  42. package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +9 -5
  43. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +1 -5
  44. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +1 -5
  45. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +9 -0
  46. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +1 -5
  47. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +16 -1
  48. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +92 -16
  49. package/lib/utils/identifierMapping.js +10 -19
  50. package/lib/utils/knexUtils.js +14 -0
  51. package/lib/utils/objectUtils.js +38 -6
  52. package/lib/utils/parseFieldExpression.js +22 -3
  53. package/lib/utils/warnings.js +26 -0
  54. package/lib/utils/wrapDbError.js +34 -20
  55. package/package.json +5 -4
  56. package/typings/objection/index.d.ts +54 -26
  57. package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +0 -27
  58. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +0 -16
  59. package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +0 -12
  60. package/lib/utils/deprecate.js +0 -21
@@ -26,9 +26,12 @@ class StaticHookArguments {
26
26
  return () => {
27
27
  // The `runAfter()` callbacks of the query being executed (including the
28
28
  // ones installed by `throwIfNotFound()` and `traverse()`) are meant for
29
- // its result, not for the result of this find query.
29
+ // its result, not for the result of this find query. The same goes for
30
+ // the value it was resolved with, e.g. the 0 of an update that only
31
+ // changes the join rows of a many-to-many relation.
30
32
  return this[BUILDER_SYMBOL].toFindQuery()
31
33
  .clear('runAfter')
34
+ .clearResolve()
32
35
  .clearWithGraphFetched()
33
36
  .runAfter(toArray);
34
37
  };
@@ -15,20 +15,7 @@ const UNRELATE = 'unrelate';
15
15
  const INSERT_MISSING = 'insertMissing';
16
16
  const FETCH_STRATEGY = 'fetchStrategy';
17
17
  const ALLOW_REFS = 'allowRefs';
18
-
19
- const OPTION_NAMES = [
20
- NO_RELATE,
21
- NO_UNRELATE,
22
- NO_INSERT,
23
- NO_UPDATE,
24
- NO_DELETE,
25
- UPDATE,
26
- RELATE,
27
- UNRELATE,
28
- INSERT_MISSING,
29
- FETCH_STRATEGY,
30
- ALLOW_REFS,
31
- ];
18
+ const ALLOW_MARKERS = 'allowMarkers';
32
19
 
33
20
  // The options that are matched against the relation path of each node, see
34
21
  // `_hasOption()`.
@@ -44,6 +31,8 @@ const NODE_OPTION_NAMES = [
44
31
  INSERT_MISSING,
45
32
  ];
46
33
 
34
+ const OPTION_NAMES = [...NODE_OPTION_NAMES, FETCH_STRATEGY, ALLOW_REFS, ALLOW_MARKERS];
35
+
47
36
  const FetchStrategy = {
48
37
  OnlyIdentifiers: 'OnlyIdentifiers',
49
38
  Everything: 'Everything',
@@ -52,19 +41,19 @@ const FetchStrategy = {
52
41
 
53
42
  class GraphOptions {
54
43
  // `rootRelationPath` is the relation path of the root node of a recursive
55
- // upsert in the original graph, against which relation expressions match.
56
- constructor(options, rootRelationPath = []) {
44
+ // upsert in the original graph, against which the relation paths and
45
+ // expressions of the options match. `insertOnly` is only set by
46
+ // `insertGraph`, which never fetches, updates or removes existing rows.
47
+ constructor(options, rootRelationPath = [], insertOnly = false) {
57
48
  if (options instanceof GraphOptions) {
58
49
  this.options = options.options;
59
50
  this.rootRelationPath = options.rootRelationPath;
51
+ this.insertOnly = options.insertOnly;
60
52
  } else {
61
53
  this.options = parseRelationExpressions(options);
62
54
  this.rootRelationPath = rootRelationPath;
55
+ this.insertOnly = insertOnly;
63
56
  }
64
-
65
- // Caches the result of matching relation expressions against relation
66
- // paths, by option name and relation path key.
67
- this.expressionMatches = new Map();
68
57
  }
69
58
 
70
59
  isFetchStrategy(strategy) {
@@ -80,17 +69,12 @@ class GraphOptions {
80
69
  }
81
70
 
82
71
  isInsertOnly() {
83
- // NO_RELATE is not in the list, since the `insert only` mode does
84
- // relate things that can be related using inserts.
85
- // TODO: Use a special key for this.
86
- return [NO_DELETE, NO_UPDATE, NO_UNRELATE, INSERT_MISSING].every((opt) => {
87
- return this.options[opt] === true;
88
- });
72
+ return this.insertOnly;
89
73
  }
90
74
 
91
75
  // Like `shouldRelate` but ignores settings that explicitly disable relate operations.
92
76
  shouldRelateIgnoreDisable(node, graphData) {
93
- if (node.toBeUnrelated || node.toBeDeleted) {
77
+ if (node.toBeUnrelatedOrDeleted) {
94
78
  return false;
95
79
  }
96
80
 
@@ -114,7 +98,7 @@ class GraphOptions {
114
98
 
115
99
  // Like `shouldInsert` but ignores settings that explicitly disable insert operations.
116
100
  shouldInsertIgnoreDisable(node, graphData) {
117
- if (node.toBeUnrelated || node.toBeDeleted) {
101
+ if (node.toBeUnrelatedOrDeleted) {
118
102
  return false;
119
103
  }
120
104
 
@@ -136,7 +120,7 @@ class GraphOptions {
136
120
  // Like `shouldPatch() || shouldUpdate()` but ignores settings that explicitly disable
137
121
  // update or patch operations.
138
122
  shouldPatchOrUpdateIgnoreDisable(node, graphData) {
139
- if (node.toBeUnrelated || node.toBeDeleted) {
123
+ if (node.toBeUnrelatedOrDeleted) {
140
124
  return false;
141
125
  }
142
126
 
@@ -206,33 +190,23 @@ class GraphOptions {
206
190
  return !!this.options[ALLOW_REFS];
207
191
  }
208
192
 
209
- rebasedOptions(newRoot) {
210
- const newOpt = {};
211
- const newRootRelationPath = newRoot.relationPathKey;
212
-
213
- for (const name of Object.keys(this.options)) {
214
- const value = this.options[name];
215
-
216
- if (Array.isArray(value)) {
217
- newOpt[name] = value
218
- .filter((it) => it === newRootRelationPath || it.startsWith(`${newRootRelationPath}.`))
219
- .map((it) => it.slice(newRootRelationPath.length + 1))
220
- .filter((it) => !!it);
221
- } else {
222
- newOpt[name] = value;
223
- }
224
- }
193
+ allowMarkers() {
194
+ return !!this.options[ALLOW_MARKERS];
195
+ }
225
196
 
226
- return new GraphOptions(newOpt, [...this.rootRelationPath, ...newRoot.relationPath]);
197
+ // The options of recursive upserts keep matching against the relation paths
198
+ // of the original graph, which the relation paths of the new root lead to.
199
+ rebasedOptions(newRoot) {
200
+ return new GraphOptions(this.options, [...this.rootRelationPath, ...newRoot.relationPath]);
227
201
  }
228
202
 
229
203
  _hasOption(node, optionName) {
230
204
  const option = this.options[optionName];
231
205
 
232
206
  if (isRelationExpression(option)) {
233
- return this._matchesRelationExpression(node, optionName, option);
207
+ return matchesRelationExpression(option, this._relationPath(node));
234
208
  } else if (Array.isArray(option)) {
235
- return option.indexOf(node.relationPathKey) !== -1;
209
+ return option.includes(this._relationPath(node).join('.'));
236
210
  } else if (typeof option === 'boolean') {
237
211
  return option;
238
212
  } else if (option === undefined) {
@@ -244,36 +218,9 @@ class GraphOptions {
244
218
  }
245
219
  }
246
220
 
247
- // A node matches a relation expression when its relation path leads to a
248
- // leaf of the expression, like it would match the same relation paths in an
249
- // array: `'movies.reviews'` matches `movies.reviews` but not `movies`.
250
- // `'*'` matches all relations below it and recursive expressions like
251
- // `'parent.^'` match every level of the recursion. The root has no relation
252
- // path and never matches.
253
- _matchesRelationExpression(node, optionName, expression) {
254
- const relationPath = [...this.rootRelationPath, ...node.relationPath];
255
-
256
- if (relationPath.length === 0) {
257
- return false;
258
- }
259
-
260
- const relationPathKey = relationPath.join('.');
261
-
262
- let matches = this.expressionMatches.get(optionName);
263
-
264
- if (!matches) {
265
- matches = new Map();
266
- this.expressionMatches.set(optionName, matches);
267
- }
268
-
269
- let isMatch = matches.get(relationPathKey);
270
-
271
- if (isMatch === undefined) {
272
- isMatch = matchesRelationPath(expression, relationPath);
273
- matches.set(relationPathKey, isMatch);
274
- }
275
-
276
- return isMatch;
221
+ // The relation path of the node in the original graph, see `rebasedOptions()`.
222
+ _relationPath(node) {
223
+ return [...this.rootRelationPath, ...node.relationPath];
277
224
  }
278
225
  }
279
226
 
@@ -344,7 +291,17 @@ function findAlias(node) {
344
291
  return null;
345
292
  }
346
293
 
347
- function matchesRelationPath(expression, relationPath) {
294
+ // A relation path matches a relation expression when it leads to a leaf of
295
+ // the expression, like it would match the same relation paths in an array:
296
+ // `'movies.reviews'` matches `movies.reviews` but not `movies`. `'*'` matches
297
+ // all relations below it and recursive expressions like `'parent.^'` match
298
+ // every level of the recursion. The root has no relation path and never
299
+ // matches.
300
+ function matchesRelationExpression(expression, relationPath) {
301
+ if (relationPath.length === 0) {
302
+ return false;
303
+ }
304
+
348
305
  for (const relationName of relationPath) {
349
306
  expression = expression.childExpression(relationName);
350
307
 
@@ -14,16 +14,17 @@ const { RelationExpression } = require('../RelationExpression');
14
14
  const { GraphNodeDbExistence } = require('./GraphNodeDbExistence');
15
15
  const { GraphData } = require('./GraphData');
16
16
  const { uniqBy, asArray, isObject } = require('../../utils/objectUtils');
17
- const { deprecate } = require('../../utils/deprecate');
17
+ const { warnOnce } = require('../../utils/warnings');
18
18
 
19
19
  class GraphUpsert {
20
- constructor({ rootModelClass, objects, upsertOptions }) {
20
+ constructor({ rootModelClass, objects, upsertOptions, insertOnly = false }) {
21
21
  checkCanBeConvertedToModels(rootModelClass, objects);
22
22
  warnOnUnknownOptions(upsertOptions);
23
23
 
24
24
  this.objects = rootModelClass.ensureModelArray(objects, GraphUpsert.modelOptions);
25
25
  this.isArray = Array.isArray(objects);
26
26
  this.upsertOpt = upsertOptions;
27
+ this.insertOnly = insertOnly;
27
28
  }
28
29
 
29
30
  static get modelOptions() {
@@ -39,7 +40,7 @@ class GraphUpsert {
39
40
  // TODO: Throw an error instead in objection 4.0.
40
41
  static warnOnConflict(builder, method) {
41
42
  if (builder.has(isOnConflictOperation)) {
42
- deprecate(
43
+ warnOnce(
43
44
  `onConflict(), ignore() and merge() are not supported by ${method}(). ` +
44
45
  'Insert the conflicting rows with a separate insert() query instead. ' +
45
46
  'This will throw in objection 4.0.',
@@ -49,7 +50,7 @@ class GraphUpsert {
49
50
 
50
51
  run(builder) {
51
52
  const modelClass = builder.modelClass();
52
- const graphOptions = new GraphOptions(this.upsertOpt);
53
+ const graphOptions = new GraphOptions(this.upsertOpt, [], this.insertOnly);
53
54
 
54
55
  const graph = ModelGraph.create(modelClass, this.objects);
55
56
  assignDbRefsAsRelateProps(graph);
@@ -74,7 +75,7 @@ function warnOnUnknownOptions(upsertOptions) {
74
75
 
75
76
  for (const name of Object.keys(upsertOptions)) {
76
77
  if (!OPTION_NAMES.includes(name)) {
77
- deprecate(
78
+ warnOnce(
78
79
  `Unknown graph option "${name}" is ignored. ` +
79
80
  `Valid options are: ${OPTION_NAMES.join(', ')}. This will throw in objection 4.0.`,
80
81
  );
@@ -245,7 +246,10 @@ function checkForErrors(builder) {
245
246
  checkForNotFoundErrors(graphData, builder);
246
247
  checkForUnallowedRelationErrors(graphData, builder);
247
248
  checkForUnallowedReferenceErrors(graphData, builder);
249
+ // Root models can't be marked even with `allowMarkers`, so report that
250
+ // first instead of pointing to the option.
248
251
  checkForRootUnrelateOrDeleteErrors(graphData, builder);
252
+ checkForUnallowedMarkerErrors(graphData, builder);
249
253
 
250
254
  if (graphData.graphOptions.isInsertOnly()) {
251
255
  checkForHasManyRelateErrors(graphData);
@@ -290,8 +294,7 @@ function checkForNotFoundErrors(graphData, builder) {
290
294
  for (const node of graph.nodes) {
291
295
  if (
292
296
  node.obj.$hasId() &&
293
- !node.toBeUnrelated &&
294
- !node.toBeDeleted &&
297
+ !node.toBeUnrelatedOrDeleted &&
295
298
  !graphOptions.shouldInsertIgnoreDisable(node, graphData) &&
296
299
  !graphOptions.shouldRelateIgnoreDisable(node, graphData) &&
297
300
  !currentGraph.nodeForNode(node)
@@ -344,6 +347,31 @@ function checkForUnallowedReferenceErrors(graphData, builder) {
344
347
  }
345
348
  }
346
349
 
350
+ // `#unrelate` and `#delete` markers come from the graph data, which may be
351
+ // untrusted input. Like `#ref`, they have to be allowed explicitly (#2410).
352
+ // An insert has nothing to unrelate or delete, so `insertGraph` rejects them.
353
+ function checkForUnallowedMarkerErrors(graphData, builder) {
354
+ const { graph, graphOptions } = graphData;
355
+ const isInsertOnly = graphOptions.isInsertOnly();
356
+
357
+ if (graphOptions.allowMarkers() && !isInsertOnly) {
358
+ return;
359
+ }
360
+
361
+ const node = graph.nodes.find((node) => node.toBeUnrelatedOrDeleted);
362
+
363
+ if (node) {
364
+ const { graphUnrelateProp, graphDeleteProp } = node.modelClass;
365
+
366
+ throw builder.modelClass().createValidationError({
367
+ type: ValidationErrorType.InvalidGraph,
368
+ message: isInsertOnly
369
+ ? `${graphUnrelateProp} and ${graphDeleteProp} can only be used in upsertGraph, not in insertGraph`
370
+ : `${graphUnrelateProp} and ${graphDeleteProp} markers are not allowed in a graph by default. see the allowMarkers upsertGraph option`,
371
+ });
372
+ }
373
+ }
374
+
347
375
  function checkForRootUnrelateOrDeleteErrors(graphData, builder) {
348
376
  for (const node of graphData.graph.nodes) {
349
377
  if (!node.parentEdge && node.toBeUnrelatedOrDeleted) {
@@ -140,6 +140,8 @@ class GraphInsert extends GraphOperation {
140
140
  edge.relation.isObjectionBelongsToOneRelation &&
141
141
  edge.isOwnerNode(node) &&
142
142
  edge.relatedNode.isReference &&
143
+ // The owner is patched by its id, which models without one don't have (#2305).
144
+ node.modelClass.getIdColumnArray().length > 0 &&
143
145
  // Both ends need to be inserted for the foreign key to be patched in later.
144
146
  this.graphOptions.shouldInsert(node, this.graphData) &&
145
147
  this.graphOptions.shouldInsert(edge.relatedNode.referencedNode, this.graphData) &&
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { uniqBy } = require('../../utils/objectUtils');
4
- const { Selection } = require('../operations/select/Selection');
4
+ const { Selection, mapIdentifier } = require('../operations/select/Selection');
5
5
  const { SelectOperation } = require('../operations/select/SelectOperation');
6
6
  const { createModifier } = require('../../utils/createModifier');
7
7
  const { map: mapPromise } = require('../../utils/promiseUtils');
@@ -89,6 +89,13 @@ class RelationJoiner {
89
89
  }
90
90
  }
91
91
 
92
+ // Whether the joins can return more than one row per root model.
93
+ hasToManyJoins(builder) {
94
+ return this._getTableTree(builder).nodes.some(
95
+ (tableNode) => tableNode.relation && !tableNode.relation.isOneToOne(),
96
+ );
97
+ }
98
+
92
99
  parseResult(builder, flatRows) {
93
100
  const parser = JoinResultParser.create({
94
101
  tableTree: this._getTableTree(builder),
@@ -203,7 +210,60 @@ class RelationJoiner {
203
210
  }
204
211
  }
205
212
 
206
- builder.select(selectionsToStrings(selections));
213
+ // Each name is selected only once, as SQL Server returns duplicate names as
214
+ // arrays of values, and the other databases silently return one of them.
215
+ removeRootSelectAll(builder);
216
+ const tableTree = this._getTableTree(builder);
217
+ const userSelectionsByName = new Map();
218
+
219
+ for (const selection of findJoinSelections(builder)) {
220
+ // Only the columns of the joined tables are known for `table.*`:
221
+ const node = isSelectAll(selection) ? tableTree.nodesByAlias.get(selection.table) : null;
222
+ const names = node
223
+ ? getAllColumnSelectionsForNode({ builder, tableNode: node }).map((it) => it.column)
224
+ : [selection.name];
225
+
226
+ for (const name of names) {
227
+ userSelectionsByName.set(mapIdentifier(builder, name), { selection, name });
228
+ }
229
+ }
230
+
231
+ const idAliases = new Set(
232
+ tableTree.nodes.flatMap((node) =>
233
+ getIdSelectionsForNode({ tableNode: node }).map((it) => it.alias),
234
+ ),
235
+ );
236
+
237
+ builder.select(
238
+ selectionsToStrings(
239
+ selections.filter(({ table, column, alias }) => {
240
+ const { selection, name } = userSelectionsByName.get(mapIdentifier(builder, alias)) || {};
241
+
242
+ if (!selection) {
243
+ return true;
244
+ } else if (selection.column) {
245
+ if (!Selection.doesSelect(builder, selection, `${table}.${column}`)) {
246
+ throw new Error(
247
+ `withGraphJoined: the selection '${selectionToString(selection)}' selects '${name}', ` +
248
+ `the name under which the column '${table}.${column}' is selected. ` +
249
+ 'Select it under a different name.',
250
+ );
251
+ }
252
+ } else if (idAliases.has(alias)) {
253
+ // Raws, subqueries and aggregates select values that we can't see
254
+ // into. They replace our columns, but not the ids that we need to
255
+ // parse the result.
256
+ throw new Error(
257
+ `withGraphJoined: a raw, subquery or aggregate selection selects '${name}', ` +
258
+ `the name under which the id column '${table}.${column}' is selected. ` +
259
+ 'Select it under a different name, or select the id column itself.',
260
+ );
261
+ }
262
+
263
+ return false;
264
+ }),
265
+ ),
266
+ );
207
267
  }
208
268
  }
209
269
 
@@ -268,8 +328,9 @@ function ensureIdAndRelationPropsAreSelected({ builder, tableNode }) {
268
328
  ),
269
329
  ];
270
330
 
331
+ const hasSelectionAs = createHasSelectionAs(builder);
271
332
  const selectStrings = uniqBy(cols)
272
- .filter((col) => !hasSelectionAs(builder, col))
333
+ .filter((col) => !hasSelectionAs(col))
273
334
  .map((col) => `${tableRef}.${col}`);
274
335
 
275
336
  return builder.select(selectStrings);
@@ -297,9 +358,10 @@ function getSelectionsForNode({ builder, tableNode, userSelectQueries }) {
297
358
  selections = allColumnSelections.concat(selections);
298
359
  } else {
299
360
  const idSelections = getIdSelectionsForNode({ tableNode });
361
+ const hasUserSelectionAs = createHasUserSelectionAs(userSelectQuery, tableNode);
300
362
 
301
363
  for (const idSelection of idSelections) {
302
- if (!hasUserSelectionAs(userSelectQuery, tableNode, idSelection.column)) {
364
+ if (!hasUserSelectionAs(idSelection.column)) {
303
365
  selections.push(idSelection);
304
366
  internalSelections.push(idSelection);
305
367
  }
@@ -334,14 +396,16 @@ function findJoinSelections(builder) {
334
396
  return selections;
335
397
  }
336
398
 
337
- // Like `builder.hasSelectionAs(column, column)`, but ignoring the columns
338
- // passed to aggregates, as they don't select them.
339
- function hasSelectionAs(builder, column) {
399
+ // Create a `(column) => builder.hasSelectionAs(column, column)` function that
400
+ // ignores the columns passed to aggregates, as they don't select them. Clone
401
+ // the builder once, not once per column.
402
+ function createHasSelectionAs(builder) {
340
403
  if (builder.has(isAggregateOperation)) {
404
+ const selectionQuery = builder.clone().clear(isAggregateOperation);
341
405
  // Explicit, as a query with only aggregates doesn't select all columns.
342
- return builder.clone().clear(isAggregateOperation).hasSelectionAs(column, column, true);
406
+ return (column) => selectionQuery.hasSelectionAs(column, column, true);
343
407
  } else {
344
- return builder.hasSelectionAs(column, column);
408
+ return (column) => builder.hasSelectionAs(column, column);
345
409
  }
346
410
  }
347
411
 
@@ -355,11 +419,12 @@ function findUserSelections(builder, tableNode) {
355
419
  }
356
420
  }
357
421
 
358
- function hasUserSelectionAs(builder, tableNode, column) {
422
+ function createHasUserSelectionAs(builder, tableNode) {
359
423
  if (tableNode.parentNode) {
360
- return hasSelectionAs(builder, column);
424
+ return createHasSelectionAs(builder);
361
425
  } else {
362
- return builder.hasSelectionAs(column, column);
426
+ // Qualified, as the root query can also select the columns of the joined tables.
427
+ return (column) => builder.hasSelectionAs(`${tableNode.alias}.${column}`, column);
363
428
  }
364
429
  }
365
430
 
@@ -416,6 +481,28 @@ function getIdSelectionsForNode({ tableNode }) {
416
481
  });
417
482
  }
418
483
 
484
+ // The selections of all columns of the root table replace the user's `*`, which
485
+ // would select them a second time, and a bare `*` also the columns of the
486
+ // joined tables under the same names.
487
+ function removeRootSelectAll(builder) {
488
+ const tableRef = builder.tableRef();
489
+ const isRootSelectAll = (selection) =>
490
+ selection.column === '*' && (!selection.table || selection.table === tableRef);
491
+ const isSelect = (op) =>
492
+ op.constructor === SelectOperation && !isAggregateOperation(op) && !op.isDistinctOn;
493
+
494
+ builder.forEachOperation(isSelect, (op) => {
495
+ op.args = op.args.filter((arg) => {
496
+ const selection = Selection.create(arg);
497
+ return !selection || !isRootSelectAll(selection);
498
+ });
499
+ op.selections = op.selections.filter((selection) => !isRootSelectAll(selection));
500
+ });
501
+
502
+ // An empty `distinct()` still makes the whole select distinct.
503
+ builder.clear((op) => isSelect(op) && op.name !== 'distinct' && op.args.length === 0);
504
+ }
505
+
419
506
  function selectionsToStrings(selections) {
420
507
  return selections.map((selection) => {
421
508
  const selectStr = `${selection.table}.${selection.column}`;
@@ -423,6 +510,11 @@ function selectionsToStrings(selections) {
423
510
  });
424
511
  }
425
512
 
513
+ function selectionToString({ table, column, alias }) {
514
+ const selection = table ? `${table}.${column}` : column;
515
+ return alias ? `${selection} as ${alias}` : selection;
516
+ }
517
+
426
518
  function isSelectAll(selection) {
427
519
  return selection.column === '*';
428
520
  }
@@ -6,8 +6,8 @@ const { isPostgres, isMySql, isMsSql, isSqlite } = require('../../utils/knexUtil
6
6
  const RELATION_RECURSION_LIMIT = 64;
7
7
 
8
8
  // The maximum length of the column aliases of the joined relations, after
9
- // which the database would truncate them. Unknown databases use the strictest
10
- // of the known limits, the one of postgres.
9
+ // which the database would truncate them. Other databases keep the limit of
10
+ // postgres, which isn't the strictest one: Oracle before 12.2 only allows 30.
11
11
  function getIdentifierLengthLimit(knex) {
12
12
  if (isPostgres(knex)) {
13
13
  return 63;
@@ -55,6 +55,17 @@ class DelegateOperation extends QueryBuilderOperation {
55
55
  return this.onBuild !== DelegateOperation.prototype.onBuild || this.delegate.hasOnBuild();
56
56
  }
57
57
 
58
+ onAfterBuild(builder) {
59
+ return this.delegate.onAfterBuild(builder);
60
+ }
61
+
62
+ hasOnAfterBuild() {
63
+ return (
64
+ this.onAfterBuild !== DelegateOperation.prototype.onAfterBuild ||
65
+ this.delegate.hasOnAfterBuild()
66
+ );
67
+ }
68
+
58
69
  onBuildKnex(knexBuilder, builder) {
59
70
  return this.delegate.onBuildKnex(knexBuilder, builder);
60
71
  }
@@ -110,6 +121,17 @@ class DelegateOperation extends QueryBuilderOperation {
110
121
  );
111
122
  }
112
123
 
124
+ queryBatches(builder) {
125
+ return this.delegate.queryBatches(builder);
126
+ }
127
+
128
+ hasQueryBatches() {
129
+ return (
130
+ this.queryBatches !== DelegateOperation.prototype.queryBatches ||
131
+ this.delegate.hasQueryBatches()
132
+ );
133
+ }
134
+
113
135
  onError(builder, error) {
114
136
  return this.delegate.onError(builder, error);
115
137
  }
@@ -3,10 +3,12 @@
3
3
  const { QueryBuilderOperation } = require('./QueryBuilderOperation');
4
4
 
5
5
  class FirstOperation extends QueryBuilderOperation {
6
- onBuildKnex(knexBuilder, builder) {
7
- const modelClass = builder.modelClass();
6
+ shouldLimit(builder) {
7
+ return builder.isFind() && builder.modelClass().useLimitInFirst;
8
+ }
8
9
 
9
- if (builder.isFind() && modelClass.useLimitInFirst) {
10
+ onBuildKnex(knexBuilder, builder) {
11
+ if (this.shouldLimit(builder)) {
10
12
  knexBuilder = knexBuilder.limit(1);
11
13
  }
12
14
 
@@ -42,6 +42,7 @@ class InsertGraphOperation extends DelegateOperation {
42
42
  noUnrelate: true,
43
43
  insertMissing: true,
44
44
  }),
45
+ insertOnly: true,
45
46
  });
46
47
 
47
48
  // We resolve this query here and will not execute it. This is because the root
@@ -3,6 +3,7 @@
3
3
  const { DeleteOperation } = require('./DeleteOperation');
4
4
  const { InstanceFindOperation } = require('./InstanceFindOperation');
5
5
  const { assertHasId } = require('../../utils/assert');
6
+ const { addWhereGroup } = require('../whereGroups');
6
7
 
7
8
  class InstanceDeleteOperation extends DeleteOperation {
8
9
  constructor(name, opt) {
@@ -20,7 +21,7 @@ class InstanceDeleteOperation extends DeleteOperation {
20
21
  super.onBuild(builder);
21
22
 
22
23
  assertHasId(this.instance);
23
- builder.findById(this.instance.$id());
24
+ addWhereGroup(builder, () => builder.findById(this.instance.$id()));
24
25
  }
25
26
 
26
27
  async onAfter2(builder, result) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { FindOperation } = require('./FindOperation');
4
4
  const { assertHasId } = require('../../utils/assert');
5
+ const { addWhereGroup } = require('../whereGroups');
5
6
 
6
7
  class InstanceFindOperation extends FindOperation {
7
8
  constructor(name, opt) {
@@ -11,7 +12,7 @@ class InstanceFindOperation extends FindOperation {
11
12
 
12
13
  onBuild(builder) {
13
14
  assertHasId(this.instance);
14
- builder.findById(this.instance.$id());
15
+ addWhereGroup(builder, () => builder.findById(this.instance.$id()));
15
16
  }
16
17
  }
17
18
 
@@ -4,6 +4,7 @@ const { UpdateOperation } = require('./UpdateOperation');
4
4
  const { InstanceFindOperation } = require('./InstanceFindOperation');
5
5
  const { assertHasId } = require('../../utils/assert');
6
6
  const { isObject } = require('../../utils/objectUtils');
7
+ const { addWhereGroup } = require('../whereGroups');
7
8
 
8
9
  class InstanceUpdateOperation extends UpdateOperation {
9
10
  constructor(name, opt) {
@@ -27,7 +28,7 @@ class InstanceUpdateOperation extends UpdateOperation {
27
28
  super.onBuild(builder);
28
29
 
29
30
  assertHasId(this.instance);
30
- builder.findById(this.instance.$id());
31
+ addWhereGroup(builder, () => builder.findById(this.instance.$id()));
31
32
  }
32
33
 
33
34
  async onAfter2(builder, result) {
@@ -37,7 +38,12 @@ class InstanceUpdateOperation extends UpdateOperation {
37
38
  }
38
39
 
39
40
  result = await super.onAfter2(builder, result);
40
- this.instance.$set(this.model);
41
+
42
+ // A query resolved without updating anything, e.g. with `none()`, leaves
43
+ // the instance untouched.
44
+ if (!this.isResolvedWithoutUpdate(builder)) {
45
+ this.instance.$set(this.model);
46
+ }
41
47
 
42
48
  if (isObject(result)) {
43
49
  this.instance.$set(result);