objection 3.2.0 → 3.4.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 (64) hide show
  1. package/README.md +13 -13
  2. package/lib/model/AjvValidator.js +220 -13
  3. package/lib/model/Model.js +3 -1
  4. package/lib/model/modelId.js +10 -2
  5. package/lib/model/modelQueryProps.js +7 -0
  6. package/lib/queryBuilder/QueryBuilder.js +191 -8
  7. package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
  8. package/lib/queryBuilder/ReferenceBuilder.js +5 -3
  9. package/lib/queryBuilder/RelationExpression.js +13 -1
  10. package/lib/queryBuilder/StaticHookArguments.js +12 -1
  11. package/lib/queryBuilder/graph/GraphFetcher.js +1 -3
  12. package/lib/queryBuilder/graph/GraphOptions.js +30 -1
  13. package/lib/queryBuilder/graph/GraphUpsert.js +38 -1
  14. package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
  15. package/lib/queryBuilder/graph/insert/GraphInsert.js +107 -8
  16. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +8 -1
  17. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +8 -2
  18. package/lib/queryBuilder/join/JoinResultParser.js +22 -0
  19. package/lib/queryBuilder/join/RelationJoiner.js +80 -27
  20. package/lib/queryBuilder/join/utils.js +20 -2
  21. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
  22. package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
  23. package/lib/queryBuilder/operations/InsertOperation.js +129 -13
  24. package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
  25. package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
  26. package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
  27. package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
  28. package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
  29. package/lib/queryBuilder/operations/eager/EagerOperation.js +4 -0
  30. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +2 -6
  31. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +10 -33
  32. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
  33. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
  34. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +41 -34
  35. package/lib/queryBuilder/operations/select/Selection.js +95 -4
  36. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
  37. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
  38. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
  39. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
  40. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
  41. package/lib/queryBuilder/whereGroups.js +125 -0
  42. package/lib/relations/Relation.js +5 -2
  43. package/lib/relations/RelationFindOperation.js +46 -2
  44. package/lib/relations/RelationProperty.js +3 -2
  45. package/lib/relations/hasMany/HasManyRelateOperation.js +5 -2
  46. package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
  47. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
  48. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
  49. package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
  50. package/lib/relations/manyToMany/ManyToManyRelation.js +104 -51
  51. package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +4 -2
  52. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
  53. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
  54. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
  55. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
  56. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +12 -2
  57. package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
  58. package/lib/utils/assert.js +9 -0
  59. package/lib/utils/deprecate.js +6 -0
  60. package/lib/utils/identifierMapping.js +15 -1
  61. package/lib/utils/parseFieldExpression.js +28 -0
  62. package/lib/utils/wrapDbError.js +36 -0
  63. package/package.json +8 -7
  64. package/typings/objection/index.d.ts +281 -29
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { JoinRowGraphInsertAction } = require('./JoinRowGraphInsertAction');
4
+ const { DeferredRelationGraphInsertAction } = require('./DeferredRelationGraphInsertAction');
4
5
  const { GraphInsertAction } = require('./GraphInsertAction');
5
6
  const { GraphOperation } = require('../GraphOperation');
6
7
  const { ModelGraphEdge } = require('../../../model/graph/ModelGraphEdge');
@@ -9,10 +10,15 @@ class GraphInsert extends GraphOperation {
9
10
  constructor(...args) {
10
11
  super(...args);
11
12
  this.dependencies = this._createDependencyMap();
13
+ this.deferredEdges = new Set();
12
14
  }
13
15
 
14
16
  createActions() {
15
- return [...this._createNormalActions(), ...this._createJoinRowActions()];
17
+ return [
18
+ ...this._createNormalActions(),
19
+ ...this._createDeferredRelationActions(),
20
+ ...this._createJoinRowActions(),
21
+ ];
16
22
  }
17
23
 
18
24
  _createDependencyMap() {
@@ -76,6 +82,10 @@ class GraphInsert extends GraphOperation {
76
82
  });
77
83
 
78
84
  if (nodesToInsert.length === 0) {
85
+ if (handledNodes.size !== this.graph.nodes.length && this._breakCycle(handledNodes)) {
86
+ continue;
87
+ }
88
+
79
89
  break;
80
90
  }
81
91
 
@@ -83,6 +93,7 @@ class GraphInsert extends GraphOperation {
83
93
  new GraphInsertAction(this.graphData, {
84
94
  nodes: nodesToInsert,
85
95
  dependencies: this.dependencies,
96
+ deferredEdges: this.deferredEdges,
86
97
  }),
87
98
  );
88
99
 
@@ -98,19 +109,71 @@ class GraphInsert extends GraphOperation {
98
109
  return actions;
99
110
  }
100
111
 
101
- _isHandled(node, handledNodes) {
102
- return handledNodes.has(node);
112
+ // Tries to break a dependency cycle by deferring one `BelongsToOneRelation`
113
+ // edge that points to a `#ref` node. The owner node is then inserted without
114
+ // the foreign key, which is patched in by `DeferredRelationGraphInsertAction`
115
+ // once the referenced node has been inserted. Cycles can only exist through
116
+ // `#ref` nodes, since the graph is a tree otherwise.
117
+ _breakCycle(handledNodes) {
118
+ for (const node of this.graph.nodes) {
119
+ if (this._isHandled(node, handledNodes)) {
120
+ continue;
121
+ }
122
+
123
+ for (const edge of this._pendingDependencyEdges(node, handledNodes)) {
124
+ if (
125
+ this._canDeferEdge(edge, node) &&
126
+ this._isBlockedBy(edge.relatedNode, node, handledNodes, new Set())
127
+ ) {
128
+ this.deferredEdges.add(edge);
129
+ return true;
130
+ }
131
+ }
132
+ }
133
+
134
+ return false;
103
135
  }
104
136
 
105
- _hasDependencies(node, handledNodes) {
106
- if (!this.dependencies.has(node)) {
137
+ _canDeferEdge(edge, node) {
138
+ return (
139
+ edge.type === ModelGraphEdge.Type.Relation &&
140
+ edge.relation.isObjectionBelongsToOneRelation &&
141
+ edge.isOwnerNode(node) &&
142
+ edge.relatedNode.isReference &&
143
+ // Both ends need to be inserted for the foreign key to be patched in later.
144
+ this.graphOptions.shouldInsert(node, this.graphData) &&
145
+ this.graphOptions.shouldInsert(edge.relatedNode.referencedNode, this.graphData) &&
146
+ !this._isReadByPropertyReference(node, edge.relation.ownerProp)
147
+ );
148
+ }
149
+
150
+ // Returns true if a `#ref{}` property reference reads one of the `props` of
151
+ // `node`. Such references may be resolved before a deferred prop is set,
152
+ // which would resolve them to `undefined`.
153
+ _isReadByPropertyReference(node, props) {
154
+ return node.refEdges.some((edge) => {
155
+ return (
156
+ edge.refType === ModelGraphEdge.ReferenceType.Property &&
157
+ edge.isRelatedNode(node) &&
158
+ props.props.includes(edge.refRelatedDataPath[0])
159
+ );
160
+ });
161
+ }
162
+
163
+ // Returns true if `node` (transitively) waits for `blockingNode` to be handled.
164
+ _isBlockedBy(node, blockingNode, handledNodes, visitedNodes) {
165
+ if (node === blockingNode) {
166
+ return true;
167
+ }
168
+
169
+ if (visitedNodes.has(node)) {
107
170
  return false;
108
171
  }
109
172
 
110
- for (const edge of this.dependencies.get(node)) {
111
- const dependencyNode = edge.getOtherNode(node);
173
+ visitedNodes.add(node);
112
174
 
113
- if (!handledNodes.has(dependencyNode) && !this.currentGraph.nodeForNode(dependencyNode)) {
175
+ for (const edge of this._pendingDependencyEdges(node, handledNodes)) {
176
+ if (this._isBlockedBy(edge.getOtherNode(node), blockingNode, handledNodes, visitedNodes)) {
114
177
  return true;
115
178
  }
116
179
  }
@@ -118,6 +181,42 @@ class GraphInsert extends GraphOperation {
118
181
  return false;
119
182
  }
120
183
 
184
+ _createDeferredRelationActions() {
185
+ if (this.deferredEdges.size === 0) {
186
+ return [];
187
+ }
188
+
189
+ return [
190
+ new DeferredRelationGraphInsertAction(this.graphData, {
191
+ edges: [...this.deferredEdges],
192
+ }),
193
+ ];
194
+ }
195
+
196
+ _isHandled(node, handledNodes) {
197
+ return handledNodes.has(node);
198
+ }
199
+
200
+ _hasDependencies(node, handledNodes) {
201
+ return this._pendingDependencyEdges(node, handledNodes).length > 0;
202
+ }
203
+
204
+ // Returns the dependency edges of `node` whose other node still needs to be
205
+ // inserted first. Deferred edges are ignored.
206
+ _pendingDependencyEdges(node, handledNodes) {
207
+ const edges = this.dependencies.get(node) || [];
208
+
209
+ return edges.filter((edge) => {
210
+ const dependencyNode = edge.getOtherNode(node);
211
+
212
+ return (
213
+ !this.deferredEdges.has(edge) &&
214
+ !handledNodes.has(dependencyNode) &&
215
+ !this.currentGraph.nodeForNode(dependencyNode)
216
+ );
217
+ });
218
+ }
219
+
121
220
  _markHandled(node, handledNodes) {
122
221
  handledNodes.add(node);
123
222
 
@@ -14,12 +14,15 @@ const promiseUtils = require('../../../utils/promiseUtils');
14
14
  * when needed.
15
15
  */
16
16
  class GraphInsertAction extends GraphAction {
17
- constructor(graphData, { nodes, dependencies }) {
17
+ constructor(graphData, { nodes, dependencies, deferredEdges }) {
18
18
  super(graphData);
19
19
 
20
20
  // Nodes to insert.
21
21
  this.nodes = nodes;
22
22
  this.dependencies = dependencies;
23
+ // Dependency edges that are resolved once the dependency node has been
24
+ // inserted, and are patched in by `DeferredRelationGraphInsertAction`.
25
+ this.deferredEdges = deferredEdges;
23
26
  }
24
27
 
25
28
  run(builder) {
@@ -63,6 +66,10 @@ class GraphInsertAction extends GraphAction {
63
66
 
64
67
  if (edges) {
65
68
  for (const edge of edges) {
69
+ if (this.deferredEdges.has(edge)) {
70
+ continue;
71
+ }
72
+
66
73
  // `node` needs `dependencyNode` to have been inserted (and it has been).
67
74
  const dependencyNode = edge.getOtherNode(node);
68
75
 
@@ -3,7 +3,7 @@
3
3
  const { getModel } = require('../../../model/getModel');
4
4
  const { GraphAction } = require('../GraphAction');
5
5
  const { isInternalProp } = require('../../../utils/internalPropUtils');
6
- const { difference, isObject, jsonEquals } = require('../../../utils/objectUtils');
6
+ const { difference, isObject, isPlainObject, jsonEquals } = require('../../../utils/objectUtils');
7
7
  const promiseUtils = require('../../../utils/promiseUtils');
8
8
 
9
9
  class GraphPatchAction extends GraphAction {
@@ -251,13 +251,19 @@ function childQueryOptions() {
251
251
  }
252
252
 
253
253
  function nonStrictEquals(val1, val2) {
254
- if (val1 == val2) {
254
+ // Only use loose equality for non-JSON values, so that for example `false`
255
+ // and `[]` are not considered equal. See issue #1874.
256
+ if (!isJsonValue(val1) && !isJsonValue(val2) && val1 == val2) {
255
257
  return true;
256
258
  }
257
259
 
258
260
  return jsonEquals(val1, val2);
259
261
  }
260
262
 
263
+ function isJsonValue(value) {
264
+ return Array.isArray(value) || isPlainObject(value);
265
+ }
266
+
261
267
  function isToBeUnrelatedOrDeleted(node) {
262
268
  return !!node && node.toBeUnrelatedOrDeleted;
263
269
  }
@@ -22,6 +22,8 @@ class JoinResultParser {
22
22
  return flatRows;
23
23
  }
24
24
 
25
+ this._checkIdColumns(flatRows[0]);
26
+
25
27
  this.columnsByTableNode = this._createColumns(flatRows[0]);
26
28
  this.parentMap = new Map();
27
29
  this.rootModels = [];
@@ -93,6 +95,26 @@ class JoinResultParser {
93
95
  }
94
96
  }
95
97
 
98
+ _checkIdColumns(row) {
99
+ for (const tableNode of this.tableTree.nodes) {
100
+ const idColumns = tableNode.modelClass.getIdColumnArray();
101
+ const hasIdColumns =
102
+ idColumns.length > 0 &&
103
+ idColumns.every((column) =>
104
+ Object.prototype.hasOwnProperty.call(row, tableNode.getColumnAliasForColumn(column)),
105
+ );
106
+
107
+ if (!hasIdColumns) {
108
+ throw new Error(
109
+ `withGraphJoined: the id column(s) [${idColumns.join(', ')}] of model ${tableNode.modelClass.name} ` +
110
+ `are missing from the result, so the joined rows can't be grouped into models. ` +
111
+ `withGraphJoined requires all models in the relation expression to have a primary key ` +
112
+ `(idColumn) that exists in the table. Use withGraphFetched for models without one.`,
113
+ );
114
+ }
115
+ }
116
+ }
117
+
96
118
  _createColumns(row) {
97
119
  const columns = Object.keys(row).map((columnAlias) =>
98
120
  JoinResultColumn.create({ tableTree: this.tableTree, columnAlias }),
@@ -8,7 +8,7 @@ const { ValidationErrorType } = require('../../model/ValidationError');
8
8
 
9
9
  const { TableTree } = require('./TableTree');
10
10
  const { JoinResultParser } = require('./JoinResultParser');
11
- const { ID_LENGTH_LIMIT } = require('./utils');
11
+ const { getIdentifierLengthLimit } = require('./utils');
12
12
 
13
13
  /**
14
14
  * Given a relation expression, builds a query using joins to fetch it.
@@ -24,6 +24,8 @@ class RelationJoiner {
24
24
  this.modifiers = null;
25
25
 
26
26
  this.options = defaultOptions();
27
+ // The `joinOperation` options of the individual `withGraphJoined` calls.
28
+ this.joinOperations = [];
27
29
  this.tableTree = null;
28
30
  this.internalSelections = null;
29
31
  }
@@ -49,6 +51,11 @@ class RelationJoiner {
49
51
  return this;
50
52
  }
51
53
 
54
+ setJoinOperations(joinOperations) {
55
+ this.joinOperations = joinOperations;
56
+ return this;
57
+ }
58
+
52
59
  /**
53
60
  * Fetches the column information needed for building the select clauses.
54
61
  *
@@ -81,23 +88,6 @@ class RelationJoiner {
81
88
  }
82
89
  }
83
90
 
84
- releaseInternalRootSelection(column) {
85
- const rootTableNode = this.tableTree && this.tableTree.rootNode;
86
-
87
- if (!rootTableNode || !this.internalSelections) {
88
- return false;
89
- }
90
-
91
- const alias = rootTableNode.getColumnAliasForColumn(column);
92
- const count = this.internalSelections.length;
93
-
94
- this.internalSelections = this.internalSelections.filter(
95
- (it) => it.table !== rootTableNode.alias || it.alias !== alias,
96
- );
97
-
98
- return this.internalSelections.length !== count;
99
- }
100
-
101
91
  parseResult(builder, flatRows) {
102
92
  const parser = JoinResultParser.create({
103
93
  tableTree: this._getTableTree(builder),
@@ -134,12 +124,18 @@ class RelationJoiner {
134
124
  // also contain selects.
135
125
  userSelectQuery.modify(tableNode.relation.modify);
136
126
 
127
+ if (selectsOnlyAliasesKeepingSelectAll(userSelectQuery.findAllSelections())) {
128
+ // All columns are selected from the joined subquery in the main query,
129
+ // so the subquery needs to select them too.
130
+ subqueryToJoin.select(`${subqueryToJoin.tableRef()}.*`);
131
+ }
132
+
137
133
  // Save the query that contains the user specified selects
138
134
  // for later use.
139
135
  userSelectQueries.set(tableNode, userSelectQuery);
140
136
 
141
137
  tableNode.relation.join(builder, {
142
- joinOperation: this.options.joinOperation,
138
+ joinOperation: this._getJoinOperation(tableNode),
143
139
 
144
140
  ownerTable: tableNode.parentNode.alias,
145
141
  relatedTableAlias: tableNode.alias,
@@ -156,6 +152,26 @@ class RelationJoiner {
156
152
  }
157
153
  }
158
154
 
155
+ // Returns the `joinOperation` of the last `withGraphJoined` call that
156
+ // contains the relation of `tableNode`, falling back to the default.
157
+ _getJoinOperation(tableNode) {
158
+ const path = [];
159
+
160
+ for (let node = tableNode; node.parentNode; node = node.parentNode) {
161
+ path.unshift(node.relationProperty);
162
+ }
163
+
164
+ for (let i = this.joinOperations.length - 1; i >= 0; --i) {
165
+ const { expression, joinOperation } = this.joinOperations[i];
166
+
167
+ if (containsPath(expression, path)) {
168
+ return joinOperation;
169
+ }
170
+ }
171
+
172
+ return this.options.joinOperation;
173
+ }
174
+
159
175
  _buildSelects({ builder, tableNode, userSelectQueries }) {
160
176
  const { selections, internalSelections } = getSelectionsForNode({
161
177
  builder,
@@ -163,14 +179,28 @@ class RelationJoiner {
163
179
  userSelectQueries,
164
180
  });
165
181
 
182
+ const lengthLimit = getIdentifierLengthLimit(builder.knex());
183
+
166
184
  for (const selection of selections) {
167
- checkAliasLength(tableNode.modelClass, selection.name);
185
+ checkAliasLength(tableNode.modelClass, selection.name, lengthLimit);
168
186
  }
169
187
 
170
188
  // Save the selections that were added internally (not by the user)
171
- // so that we can later remove the corresponding properties when
172
- // parsing the result.
173
- this.internalSelections = internalSelections;
189
+ // so that we can later remove the corresponding properties. The root
190
+ // ones are registered with the builder, which removes them from the
191
+ // result models after all eager operations have run, as they may still
192
+ // need them. The nested ones are removed when parsing the result.
193
+ this.internalSelections = [];
194
+
195
+ for (const selection of internalSelections) {
196
+ if (selection.table === tableNode.alias) {
197
+ builder.addInternalSelection(
198
+ tableNode.modelClass.columnNameToPropertyName(selection.column),
199
+ );
200
+ } else {
201
+ this.internalSelections.push(selection);
202
+ }
203
+ }
174
204
 
175
205
  builder.select(selectionsToStrings(selections));
176
206
  }
@@ -185,9 +215,21 @@ function defaultOptions() {
185
215
  };
186
216
  }
187
217
 
218
+ function containsPath(expression, path) {
219
+ for (const name of path) {
220
+ expression = expression.childExpression(name);
221
+
222
+ if (!expression) {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ return true;
228
+ }
229
+
188
230
  function createSubqueryToJoin({ builder, tableNode, modifiers }) {
189
231
  const { relation, expression, modelClass } = tableNode;
190
- const modifierQuery = modelClass.query().childQueryOf(builder);
232
+ const modifierQuery = modelClass.query().childQueryOf(builder, { isJoinChildQuery: true });
191
233
 
192
234
  for (const modifierName of expression.node.$modify) {
193
235
  const modifier = createModifier({
@@ -341,14 +383,25 @@ function isNotSelectAll(selection) {
341
383
  }
342
384
 
343
385
  function isSelectAllSelectionSet(selections) {
386
+ selections = selections.filter(isNotKeepingSelectAll);
344
387
  return selections.length === 0 || selections.some(isSelectAll);
345
388
  }
346
389
 
347
- function checkAliasLength(modelClass, alias) {
348
- if (alias.length > ID_LENGTH_LIMIT) {
390
+ function isNotKeepingSelectAll(selection) {
391
+ return !selection.keepsSelectAll;
392
+ }
393
+
394
+ function selectsOnlyAliasesKeepingSelectAll(selections) {
395
+ return selections.length > 0 && selections.every((it) => it.keepsSelectAll);
396
+ }
397
+
398
+ function checkAliasLength(modelClass, alias, lengthLimit) {
399
+ if (alias.length > lengthLimit) {
349
400
  throw modelClass.createValidationError({
350
401
  type: ValidationErrorType.RelationExpression,
351
- message: `identifier ${alias} is over ${ID_LENGTH_LIMIT} characters long and would be truncated by the database engine.`,
402
+ message:
403
+ `identifier ${alias} is over ${lengthLimit} characters long and would be truncated by the database engine. ` +
404
+ 'Use the `minimize` option of withGraphJoined() to shorten the aliases.',
352
405
  });
353
406
  }
354
407
  }
@@ -1,10 +1,27 @@
1
1
  'use strict';
2
2
 
3
3
  const { ValidationErrorType } = require('../../model/ValidationError');
4
+ const { isPostgres, isMySql, isMsSql, isSqlite } = require('../../utils/knexUtils');
4
5
 
5
- const ID_LENGTH_LIMIT = 63;
6
6
  const RELATION_RECURSION_LIMIT = 64;
7
7
 
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.
11
+ function getIdentifierLengthLimit(knex) {
12
+ if (isPostgres(knex)) {
13
+ return 63;
14
+ } else if (isMySql(knex)) {
15
+ return 256;
16
+ } else if (isMsSql(knex)) {
17
+ return 128;
18
+ } else if (isSqlite(knex)) {
19
+ return Infinity;
20
+ } else {
21
+ return 63;
22
+ }
23
+ }
24
+
8
25
  // Given a relation expression, goes through all first level children.
9
26
  function forEachChildExpression(expr, modelClass, callback) {
10
27
  if (expr.node.$allRecursive || expr.maxRecursionDepth > RELATION_RECURSION_LIMIT) {
@@ -18,8 +35,9 @@ function forEachChildExpression(expr, modelClass, callback) {
18
35
  }
19
36
 
20
37
  module.exports = {
21
- ID_LENGTH_LIMIT,
22
38
  RELATION_RECURSION_LIMIT,
23
39
 
40
+ getIdentifierLengthLimit,
41
+
24
42
  forEachChildExpression,
25
43
  };
@@ -22,10 +22,16 @@ class InsertAndFetchOperation extends DelegateOperation {
22
22
  const modelClass = builder.modelClass();
23
23
  const insertedModels = await super.onAfter2(builder, inserted);
24
24
 
25
- const insertedModelArray = asArray(insertedModels);
25
+ // Models without identifiers can't be fetched. This happens when a row
26
+ // with a database generated identifier is ignored by `onConflict()`.
27
+ const insertedModelArray = asArray(insertedModels).filter((model) => model && model.$hasId());
26
28
  const idProps = modelClass.getIdPropertyArray();
27
29
  const ids = insertedModelArray.map((model) => model.$id());
28
30
 
31
+ if (ids.length === 0) {
32
+ return insertedModels;
33
+ }
34
+
29
35
  const fetchedModels = await modelClass
30
36
  .query()
31
37
  .childQueryOf(builder)
@@ -51,8 +51,8 @@ class InsertGraphOperation extends DelegateOperation {
51
51
  return retVal;
52
52
  }
53
53
 
54
- onBefore1(_, result) {
55
- // Do nothing.
54
+ onBefore1(builder, result) {
55
+ GraphUpsert.warnOnConflict(builder, this.name);
56
56
  return result;
57
57
  }
58
58