objection 3.8.0 → 3.9.1
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/lib/model/AjvValidator.js +22 -6
- package/lib/model/Model.js +2 -0
- package/lib/model/graph/ModelGraphNode.js +2 -16
- package/lib/model/modelJsonAttributes.js +20 -11
- package/lib/model/modelQueryProps.js +78 -6
- package/lib/model/modelSet.js +6 -8
- package/lib/model/modelToJson.js +9 -2
- package/lib/model/modelUtils.js +1 -1
- package/lib/model/modelValidate.js +2 -1
- package/lib/model/modelValues.js +43 -50
- package/lib/queryBuilder/QueryBuilder.js +17 -7
- package/lib/queryBuilder/graph/GraphData.js +39 -0
- package/lib/queryBuilder/graph/GraphFetcher.js +7 -1
- package/lib/queryBuilder/graph/GraphOptions.js +66 -12
- package/lib/queryBuilder/graph/GraphUpsert.js +92 -10
- package/lib/queryBuilder/graph/delete/GraphDelete.js +55 -2
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +13 -5
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +7 -6
- package/lib/queryBuilder/graph/insert/GraphInsert.js +66 -22
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +8 -13
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +16 -3
- package/lib/queryBuilder/join/JoinResultParser.js +13 -8
- package/lib/queryBuilder/join/RelationJoiner.js +3 -2
- package/lib/queryBuilder/join/TableNode.js +6 -68
- package/lib/queryBuilder/operations/DeleteOperation.js +20 -0
- package/lib/queryBuilder/operations/InsertOperation.js +4 -2
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +2 -0
- package/lib/queryBuilder/operations/LimitPerParentOperation.js +22 -15
- package/lib/queryBuilder/operations/UpdateOperation.js +40 -3
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +1 -1
- package/lib/queryBuilder/operations/select/Selection.js +25 -3
- package/lib/relations/RelationFindOperation.js +7 -4
- package/lib/relations/RelationOwner.js +2 -5
- package/lib/relations/RelationProperty.js +3 -14
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +5 -1
- package/lib/relations/hasMany/HasManyInsertOperation.js +4 -0
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +1 -1
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +19 -12
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +1 -16
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +80 -21
- package/lib/utils/knexUtils.js +7 -0
- package/lib/utils/objectUtils.js +5 -0
- package/lib/utils/parseFieldExpression.js +17 -5
- package/lib/utils/promiseUtils/map.js +24 -7
- package/lib/utils/wrapDbError.js +14 -0
- package/package.json +1 -1
- package/typings/objection/index.d.ts +21 -11
|
@@ -161,8 +161,11 @@ class GraphOptions {
|
|
|
161
161
|
|
|
162
162
|
return (
|
|
163
163
|
!node &&
|
|
164
|
+
!hasIgnoredOtherModel(currentNode, graphData) &&
|
|
164
165
|
!this._hasOption(currentNode, NO_UNRELATE) &&
|
|
165
|
-
this.shouldUnrelateIgnoreDisable(currentNode)
|
|
166
|
+
(this.shouldUnrelateIgnoreDisable(currentNode) ||
|
|
167
|
+
// The unrelate replaces a delete, so `noDelete` disables it too.
|
|
168
|
+
(!this._hasOption(currentNode, NO_DELETE) && shouldUnrelateKeptRow(currentNode, graphData)))
|
|
166
169
|
);
|
|
167
170
|
}
|
|
168
171
|
|
|
@@ -173,8 +176,11 @@ class GraphOptions {
|
|
|
173
176
|
|
|
174
177
|
return (
|
|
175
178
|
!node &&
|
|
179
|
+
!hasIgnoredOtherModel(currentNode, graphData) &&
|
|
176
180
|
!this._hasOption(currentNode, NO_DELETE) &&
|
|
177
|
-
!this.shouldUnrelateIgnoreDisable(currentNode)
|
|
181
|
+
!this.shouldUnrelateIgnoreDisable(currentNode) &&
|
|
182
|
+
// Rows that the graph keeps in another place must not be deleted (#214).
|
|
183
|
+
!graphData.keptNodeForRow(currentNode)
|
|
178
184
|
);
|
|
179
185
|
}
|
|
180
186
|
|
|
@@ -182,6 +188,18 @@ class GraphOptions {
|
|
|
182
188
|
return this.shouldInsert(node, graphData) || this.shouldRelate(node, graphData);
|
|
183
189
|
}
|
|
184
190
|
|
|
191
|
+
// The graph ignores models that it neither inserts, relates nor patches, like
|
|
192
|
+
// models marked with `#unrelate` or `#delete` and models skipped by `noInsert`
|
|
193
|
+
// or `noRelate`. Inserted models have ids and can look like models to relate
|
|
194
|
+
// afterwards, so we check that they were inserted too.
|
|
195
|
+
shouldIgnore(node, graphData) {
|
|
196
|
+
return (
|
|
197
|
+
!node.userData.inserted &&
|
|
198
|
+
!this.shouldInsertOrRelate(node, graphData) &&
|
|
199
|
+
!this.shouldPatchOrUpdateIgnoreDisable(node, graphData)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
185
203
|
shouldDeleteOrUnrelate(currentNode, graphData) {
|
|
186
204
|
return this.shouldDelete(currentNode, graphData) || this.shouldUnrelate(currentNode, graphData);
|
|
187
205
|
}
|
|
@@ -264,27 +282,30 @@ function parseRelationExpression(optionName, expression) {
|
|
|
264
282
|
}
|
|
265
283
|
|
|
266
284
|
// Relation paths are matched by relation name, so aliases would never match.
|
|
267
|
-
const alias =
|
|
285
|
+
const alias = findNode(parsedExpression.node, (node) => node.$name !== node.$relation);
|
|
268
286
|
|
|
269
287
|
if (alias) {
|
|
270
288
|
throw createError(`aliases like "${alias.$relation} as ${alias.$name}" are not supported`);
|
|
271
289
|
}
|
|
272
290
|
|
|
291
|
+
// `*` and `^` only have a meaning at the end of a relation path. Anywhere else
|
|
292
|
+
// they are parsed as relation names that would never match.
|
|
293
|
+
const token = findNode(parsedExpression.node, (node) => /^(\*|\^\d*)$/.test(node.$relation));
|
|
294
|
+
|
|
295
|
+
if (token) {
|
|
296
|
+
throw createError(`"${token.$relation}" is only supported at the end of a relation path`);
|
|
297
|
+
}
|
|
298
|
+
|
|
273
299
|
return parsedExpression;
|
|
274
300
|
}
|
|
275
301
|
|
|
276
|
-
function
|
|
302
|
+
function findNode(node, predicate) {
|
|
277
303
|
for (const childName of node.$childNames) {
|
|
278
304
|
const child = node[childName];
|
|
305
|
+
const found = predicate(child) ? child : findNode(child, predicate);
|
|
279
306
|
|
|
280
|
-
if (
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const alias = findAlias(child);
|
|
285
|
-
|
|
286
|
-
if (alias) {
|
|
287
|
-
return alias;
|
|
307
|
+
if (found) {
|
|
308
|
+
return found;
|
|
288
309
|
}
|
|
289
310
|
}
|
|
290
311
|
|
|
@@ -344,6 +365,39 @@ function hasRelateProp(node) {
|
|
|
344
365
|
}
|
|
345
366
|
}
|
|
346
367
|
|
|
368
|
+
// An ignored model that isn't the current model of a BelongsToOneRelation
|
|
369
|
+
// doesn't replace it, so we keep the current one instead of removing it as
|
|
370
|
+
// missing.
|
|
371
|
+
function hasIgnoredOtherModel(currentNode, graphData) {
|
|
372
|
+
const { relation } = currentNode.parentEdge;
|
|
373
|
+
const parentNode = getNode(currentNode.parentNode, graphData.graph);
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
relation.isObjectionBelongsToOneRelation &&
|
|
377
|
+
!!parentNode &&
|
|
378
|
+
parentNode.edges.some(
|
|
379
|
+
(edge) =>
|
|
380
|
+
edge.relation === relation &&
|
|
381
|
+
edge.isOwnerNode(parentNode) &&
|
|
382
|
+
graphData.graphOptions.shouldIgnore(edge.relatedNode, graphData),
|
|
383
|
+
)
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// A row that the graph keeps in another place is unrelated here instead of
|
|
388
|
+
// being deleted, unless it's a has-many row that the graph relates through the
|
|
389
|
+
// same relation: That relate moves it by overwriting the foreign key, which
|
|
390
|
+
// may not be nullable.
|
|
391
|
+
function shouldUnrelateKeptRow(currentNode, graphData) {
|
|
392
|
+
const keptNode = graphData.keptNodeForRow(currentNode);
|
|
393
|
+
const { relation } = currentNode.parentEdge;
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
!!keptNode &&
|
|
397
|
+
!(relation.isObjectionHasManyRelation && keptNode.parentEdge?.relation === relation)
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
347
401
|
function getNode(currentNode, graph) {
|
|
348
402
|
if (!graph || !currentNode) {
|
|
349
403
|
return null;
|
|
@@ -6,7 +6,7 @@ const { createNotModelError } = require('../../model/graph/ModelGraphBuilder');
|
|
|
6
6
|
const { GraphFetcher } = require('../graph/GraphFetcher');
|
|
7
7
|
const { GraphInsert } = require('./insert/GraphInsert');
|
|
8
8
|
const { GraphPatch } = require('./patch/GraphPatch');
|
|
9
|
-
const { GraphDelete } = require('./delete/GraphDelete');
|
|
9
|
+
const { GraphDelete, GraphDereferencedBelongsToOneDelete } = require('./delete/GraphDelete');
|
|
10
10
|
const { GraphRecursiveUpsert } = require('./recursiveUpsert/GraphRecursiveUpsert');
|
|
11
11
|
const { GraphOptions, OPTION_NAMES } = require('./GraphOptions');
|
|
12
12
|
const { ValidationErrorType } = require('../../model/ValidationError');
|
|
@@ -37,7 +37,7 @@ class GraphUpsert {
|
|
|
37
37
|
// by `ignore()` aren't returned, which leaves the root models without ids,
|
|
38
38
|
// so that related rows would be inserted with missing or wrong foreign keys.
|
|
39
39
|
// The clause is ignored, and a warning is logged once per method.
|
|
40
|
-
// TODO: Throw an error instead in objection 4.0.
|
|
40
|
+
// TODO: Throw an error instead in objection 4.0 (#53).
|
|
41
41
|
static warnOnConflict(builder, method) {
|
|
42
42
|
if (builder.has(isOnConflictOperation)) {
|
|
43
43
|
warnOnce(
|
|
@@ -60,13 +60,14 @@ class GraphUpsert {
|
|
|
60
60
|
.then(pruneGraphs())
|
|
61
61
|
.then(checkForJoinTableRelatedTableErrors(builder))
|
|
62
62
|
.then(executeOperations(builder))
|
|
63
|
+
.then(removeReferenceProps(graph.nodes))
|
|
63
64
|
.then(returnResult(this.objects, this.isArray));
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
// Misspelled options are silently ignored, so the operation runs with the
|
|
68
69
|
// default behaviour instead (#84). A warning is logged once per option.
|
|
69
|
-
// TODO: Throw an error instead in objection 4.0.
|
|
70
|
+
// TODO: Throw an error instead in objection 4.0 (#53).
|
|
70
71
|
function warnOnUnknownOptions(upsertOptions) {
|
|
71
72
|
// Recursive upserts pass on the already checked options of their parent.
|
|
72
73
|
if (upsertOptions instanceof GraphOptions) {
|
|
@@ -128,6 +129,7 @@ function fetchCurrentGraph(builder, graphOptions, graph) {
|
|
|
128
129
|
function pruneGraphs() {
|
|
129
130
|
return (graphData) => {
|
|
130
131
|
pruneRelatedBranches(graphData);
|
|
132
|
+
pruneSkippedBranches(graphData);
|
|
131
133
|
|
|
132
134
|
if (!graphData.graphOptions.isInsertOnly()) {
|
|
133
135
|
pruneDeletedBranches(graphData);
|
|
@@ -149,6 +151,31 @@ function pruneRelatedBranches(graphData) {
|
|
|
149
151
|
removeBranchesFromGraph(findRoots(relateNodes), graphData.graph);
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
// Nothing is written below nodes that `noInsert` skips, as their descendants
|
|
155
|
+
// would be orphans (#215). Branches that references from outside point into
|
|
156
|
+
// are kept, since the references need their models.
|
|
157
|
+
function pruneSkippedBranches(graphData) {
|
|
158
|
+
const { graph, graphOptions } = graphData;
|
|
159
|
+
|
|
160
|
+
const skippedNodes = graph.nodes.filter((node) => {
|
|
161
|
+
return (
|
|
162
|
+
graphOptions.shouldInsertIgnoreDisable(node, graphData) &&
|
|
163
|
+
!graphOptions.shouldInsert(node, graphData) &&
|
|
164
|
+
!isReferencedFromOutside(node.descendantRelationNodes)
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
removeBranchesFromGraph(findRoots(skippedNodes), graph);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isReferencedFromOutside(nodes) {
|
|
172
|
+
const nodeSet = new Set(nodes);
|
|
173
|
+
|
|
174
|
+
return nodes.some((node) =>
|
|
175
|
+
node.refEdges.some((edge) => edge.isRelatedNode(node) && !nodeSet.has(edge.ownerNode)),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
152
179
|
function pruneDeletedBranches(graphData) {
|
|
153
180
|
const { graph, currentGraph } = graphData;
|
|
154
181
|
|
|
@@ -253,6 +280,7 @@ function checkForErrors(builder) {
|
|
|
253
280
|
|
|
254
281
|
if (graphData.graphOptions.isInsertOnly()) {
|
|
255
282
|
checkForHasManyRelateErrors(graphData);
|
|
283
|
+
warnOnInsertBelowRelate(graphData);
|
|
256
284
|
}
|
|
257
285
|
|
|
258
286
|
return graphData;
|
|
@@ -290,10 +318,18 @@ function checkForJoinTableRelatedTableErrors(builder) {
|
|
|
290
318
|
|
|
291
319
|
function checkForNotFoundErrors(graphData, builder) {
|
|
292
320
|
const { graphOptions, currentGraph, graph } = graphData;
|
|
321
|
+
// The branches below related nodes are upserted and checked by nested
|
|
322
|
+
// `upsertGraph` calls, against the current graphs of the related nodes:
|
|
323
|
+
const nestedNodes = new Set(
|
|
324
|
+
graph.nodes
|
|
325
|
+
.filter((node) => graphOptions.shouldRelate(node, graphData))
|
|
326
|
+
.flatMap((node) => node.descendantRelationNodes),
|
|
327
|
+
);
|
|
293
328
|
|
|
294
329
|
for (const node of graph.nodes) {
|
|
295
330
|
if (
|
|
296
331
|
node.obj.$hasId() &&
|
|
332
|
+
!nestedNodes.has(node) &&
|
|
297
333
|
!node.toBeUnrelatedOrDeleted &&
|
|
298
334
|
!graphOptions.shouldInsertIgnoreDisable(node, graphData) &&
|
|
299
335
|
!graphOptions.shouldRelateIgnoreDisable(node, graphData) &&
|
|
@@ -400,18 +436,52 @@ function checkForHasManyRelateErrors(graphData) {
|
|
|
400
436
|
}
|
|
401
437
|
}
|
|
402
438
|
|
|
439
|
+
// `upsertGraph` upserts the branches below related models recursively, but
|
|
440
|
+
// `insertGraph` prunes them, which drops the models that would be inserted
|
|
441
|
+
// there (#217). A warning is logged once.
|
|
442
|
+
// TODO: Throw an error instead in objection 4.0 (#53).
|
|
443
|
+
function warnOnInsertBelowRelate(graphData) {
|
|
444
|
+
const { graph, graphOptions } = graphData;
|
|
445
|
+
|
|
446
|
+
const hasInsertBelowRelate = graph.nodes.some(
|
|
447
|
+
(node) =>
|
|
448
|
+
graphOptions.shouldRelate(node, graphData) &&
|
|
449
|
+
node.descendantRelationNodes.some((descendantNode) =>
|
|
450
|
+
graphOptions.shouldInsert(descendantNode, graphData),
|
|
451
|
+
),
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
if (hasInsertBelowRelate) {
|
|
455
|
+
warnOnce(
|
|
456
|
+
'insertGraph() ignores new models below related models. ' +
|
|
457
|
+
'Insert them with a separate query or use upsertGraph() instead. ' +
|
|
458
|
+
'This will throw in objection 4.0.',
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
403
463
|
function executeOperations(builder) {
|
|
404
464
|
return (graphData) => {
|
|
405
465
|
const operations = graphData.graphOptions.isInsertOnly()
|
|
406
466
|
? [GraphInsert]
|
|
407
|
-
: [
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
467
|
+
: [
|
|
468
|
+
GraphDelete,
|
|
469
|
+
GraphInsert,
|
|
470
|
+
GraphPatch,
|
|
471
|
+
GraphDereferencedBelongsToOneDelete,
|
|
472
|
+
GraphRecursiveUpsert,
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
// Create all actions before chaining the first ones, which would run even
|
|
476
|
+
// if creating the actions of a later operation throws, e.g. for a cycle.
|
|
477
|
+
const operationActions = operations.map((Operation) =>
|
|
478
|
+
new Operation(graphData).createActions(),
|
|
479
|
+
);
|
|
412
480
|
|
|
413
|
-
|
|
414
|
-
|
|
481
|
+
return operationActions.reduce(
|
|
482
|
+
(promise, actions) => promise.then(() => executeActions(builder, actions)),
|
|
483
|
+
Promise.resolve(),
|
|
484
|
+
);
|
|
415
485
|
};
|
|
416
486
|
}
|
|
417
487
|
|
|
@@ -422,6 +492,18 @@ function executeActions(builder, actions) {
|
|
|
422
492
|
);
|
|
423
493
|
}
|
|
424
494
|
|
|
495
|
+
// The reference markers only describe the input graph, don't return them.
|
|
496
|
+
// `nodes` are the nodes before pruning, since the results of pruned branches
|
|
497
|
+
// contain markers too.
|
|
498
|
+
function removeReferenceProps(nodes) {
|
|
499
|
+
return () => {
|
|
500
|
+
for (const node of nodes) {
|
|
501
|
+
delete node.obj[node.modelClass.uidRefProp];
|
|
502
|
+
delete node.obj[node.modelClass.dbRefProp];
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
425
507
|
function isOnConflictOperation(op) {
|
|
426
508
|
return op.name === 'onConflict' || op.name === 'ignore' || op.name === 'merge';
|
|
427
509
|
}
|
|
@@ -2,19 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
const { GraphOperation } = require('../GraphOperation');
|
|
4
4
|
const { GraphDeleteAction } = require('./GraphDeleteAction');
|
|
5
|
+
const { getPropertyTypes } = require('../../../model/modelJsonAttributes');
|
|
5
6
|
|
|
6
7
|
class GraphDelete extends GraphOperation {
|
|
8
|
+
// The owner of a BelongsToOneRelation row references it until `GraphPatch`
|
|
9
|
+
// points the owner to a new row or to null. These rows are deleted
|
|
10
|
+
// afterwards by `GraphDereferencedBelongsToOneDelete`, all others before.
|
|
11
|
+
get deletesDereferencedRows() {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
createActions() {
|
|
8
16
|
return [
|
|
9
17
|
new GraphDeleteAction(this.graphData, {
|
|
10
|
-
nodes: this.currentGraph.nodes.filter(
|
|
11
|
-
|
|
18
|
+
nodes: this.currentGraph.nodes.filter(
|
|
19
|
+
(currentNode) =>
|
|
20
|
+
this.graphOptions.shouldDeleteOrUnrelate(currentNode, this.graphData) &&
|
|
21
|
+
isDereferencedBelongsToOneNode(currentNode, this.graphData) ===
|
|
22
|
+
this.deletesDereferencedRows,
|
|
12
23
|
),
|
|
24
|
+
areReferenced: !this.deletesDereferencedRows,
|
|
13
25
|
}),
|
|
14
26
|
];
|
|
15
27
|
}
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
class GraphDereferencedBelongsToOneDelete extends GraphDelete {
|
|
31
|
+
get deletesDereferencedRows() {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isDereferencedBelongsToOneNode(currentNode, graphData) {
|
|
37
|
+
const { graph, graphOptions } = graphData;
|
|
38
|
+
const relation = currentNode.parentEdge?.relation;
|
|
39
|
+
const parentNode = graph.nodeForNode(currentNode.parentNode);
|
|
40
|
+
|
|
41
|
+
if (relation?.isObjectionBelongsToOneRelation && parentNode) {
|
|
42
|
+
const node = graph.nodeForNode(currentNode);
|
|
43
|
+
const isReplaced = parentNode.edges.some(
|
|
44
|
+
(edge) =>
|
|
45
|
+
edge.relation === relation &&
|
|
46
|
+
edge.isOwnerNode(parentNode) &&
|
|
47
|
+
!graphOptions.shouldIgnore(edge.relatedNode, graphData),
|
|
48
|
+
);
|
|
49
|
+
const isRemoved =
|
|
50
|
+
parentNode.obj[relation.name] === null || (!!node && node.toBeUnrelatedOrDeleted);
|
|
51
|
+
|
|
52
|
+
// Removing the row sets the owner's foreign key to null, but only where
|
|
53
|
+
// its schema allows it. Otherwise we delete the row first and leave the
|
|
54
|
+
// owner's reference to the database, as the column may be `NOT NULL`.
|
|
55
|
+
return (
|
|
56
|
+
isReplaced ||
|
|
57
|
+
(isRemoved &&
|
|
58
|
+
relation.ownerProp.props.every((prop) => isNullable(relation.ownerModelClass, prop)))
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isNullable(modelClass, prop) {
|
|
66
|
+
const schema = modelClass.getJsonSchema()?.properties?.[prop];
|
|
67
|
+
return !!schema && (schema.nullable === true || getPropertyTypes(schema).includes('null'));
|
|
68
|
+
}
|
|
69
|
+
|
|
18
70
|
module.exports = {
|
|
19
71
|
GraphDelete,
|
|
72
|
+
GraphDereferencedBelongsToOneDelete,
|
|
20
73
|
};
|
|
@@ -5,10 +5,14 @@ const { groupBy } = require('../../../utils/objectUtils');
|
|
|
5
5
|
const promiseUtils = require('../../../utils/promiseUtils');
|
|
6
6
|
|
|
7
7
|
class GraphDeleteAction extends GraphAction {
|
|
8
|
-
constructor(graphData, { nodes }) {
|
|
8
|
+
constructor(graphData, { nodes, areReferenced = true }) {
|
|
9
9
|
super(graphData);
|
|
10
10
|
// Nodes to delete.
|
|
11
11
|
this.nodes = nodes;
|
|
12
|
+
// Whether the parents still reference the nodes. Rows of
|
|
13
|
+
// BelongsToOneRelations can be deleted after their owners were patched to
|
|
14
|
+
// reference new rows or null, so they can't be found through the relation.
|
|
15
|
+
this.areReferenced = areReferenced;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
run(builder) {
|
|
@@ -47,10 +51,14 @@ class GraphDeleteAction extends GraphAction {
|
|
|
47
51
|
);
|
|
48
52
|
|
|
49
53
|
nodesByShouldUnrelate.forEach((nodes, shouldUnrelate) => {
|
|
50
|
-
const builder =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const builder = (
|
|
55
|
+
this.areReferenced
|
|
56
|
+
? parentNode.obj.$relatedQuery(relation.name)
|
|
57
|
+
: relation.relatedModelClass.query()
|
|
58
|
+
).childQueryOf(parentBuilder);
|
|
59
|
+
|
|
60
|
+
if (!this.areReferenced || !relation.isObjectionBelongsToOneRelation) {
|
|
61
|
+
// This is useless in case of a referenced BelongsToOneRelation.
|
|
54
62
|
builder.findByIds(nodes.map((node) => node.obj.$id()));
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -7,9 +7,8 @@ const promiseUtils = require('../../../utils/promiseUtils');
|
|
|
7
7
|
/**
|
|
8
8
|
* Patches in the foreign keys of `BelongsToOneRelation` edges that `GraphInsert`
|
|
9
9
|
* had to defer to break a dependency cycle. The owner nodes of these edges were
|
|
10
|
-
* inserted without the foreign keys
|
|
11
|
-
*
|
|
12
|
-
* do is one patch query per owner.
|
|
10
|
+
* inserted without the foreign keys, which are copied from the related nodes
|
|
11
|
+
* and patched in with one query per owner.
|
|
13
12
|
*/
|
|
14
13
|
class DeferredRelationGraphInsertAction extends GraphAction {
|
|
15
14
|
constructor(graphData, { edges }) {
|
|
@@ -36,11 +35,13 @@ class DeferredRelationGraphInsertAction extends GraphAction {
|
|
|
36
35
|
const ownerIdProp = modelClass.getIdRelationProperty();
|
|
37
36
|
const patch = {};
|
|
38
37
|
|
|
39
|
-
for (const { relation } of edges) {
|
|
40
|
-
const { ownerProp } = relation;
|
|
38
|
+
for (const { relation, relatedNode } of edges) {
|
|
39
|
+
const { ownerProp, relatedProp } = relation;
|
|
41
40
|
|
|
42
41
|
ownerProp.forEach((i) => {
|
|
43
|
-
|
|
42
|
+
const value = relatedProp.getProp(relatedNode.obj, i);
|
|
43
|
+
ownerProp.setProp(ownerNode.obj, i, value);
|
|
44
|
+
ownerProp.patch(patch, i, value);
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -46,7 +46,12 @@ class GraphInsert extends GraphOperation {
|
|
|
46
46
|
// In case of HasManyRelation the related node depends on the owner node
|
|
47
47
|
// because the related node has the foreign key.
|
|
48
48
|
this._addDependency(edge.relatedNode, edge, dependencies);
|
|
49
|
-
} else if (
|
|
49
|
+
} else if (
|
|
50
|
+
edge.relation.isObjectionBelongsToOneRelation &&
|
|
51
|
+
// Ignored models don't replace the current one, so their keys must not
|
|
52
|
+
// be copied to the owner either.
|
|
53
|
+
!this.graphOptions.shouldIgnore(edge.relatedNode, this.graphData)
|
|
54
|
+
) {
|
|
50
55
|
// In case of BelongsToOneRelation the owner node depends on the related
|
|
51
56
|
// node because the owner node has the foreign key.
|
|
52
57
|
this._addDependency(edge.ownerNode, edge, dependencies);
|
|
@@ -78,10 +83,23 @@ class GraphInsert extends GraphOperation {
|
|
|
78
83
|
// new ones. We need to pass all nodes to `GraphInsertActions` so that we
|
|
79
84
|
// can resolve all dependencies.
|
|
80
85
|
const nodesToInsert = this.graph.nodes.filter((node) => {
|
|
81
|
-
return
|
|
86
|
+
return (
|
|
87
|
+
!node.isReference && !handledNodes.has(node) && !this._hasDependencies(node, handledNodes)
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// `#ref` nodes aren't inserted, so they can be resolved in the same
|
|
92
|
+
// action as the nodes they depend on, once these are inserted.
|
|
93
|
+
const insertedNodes = new Set([...handledNodes, ...nodesToInsert]);
|
|
94
|
+
const refNodes = this.graph.nodes.filter((node) => {
|
|
95
|
+
return (
|
|
96
|
+
node.isReference && !handledNodes.has(node) && !this._hasDependencies(node, insertedNodes)
|
|
97
|
+
);
|
|
82
98
|
});
|
|
83
99
|
|
|
84
|
-
|
|
100
|
+
const nodes = [...nodesToInsert, ...refNodes];
|
|
101
|
+
|
|
102
|
+
if (nodes.length === 0) {
|
|
85
103
|
if (handledNodes.size !== this.graph.nodes.length && this._breakCycle(handledNodes)) {
|
|
86
104
|
continue;
|
|
87
105
|
}
|
|
@@ -92,13 +110,14 @@ class GraphInsert extends GraphOperation {
|
|
|
92
110
|
actions.push(
|
|
93
111
|
new GraphInsertAction(this.graphData, {
|
|
94
112
|
nodes: nodesToInsert,
|
|
113
|
+
refNodes,
|
|
95
114
|
dependencies: this.dependencies,
|
|
96
115
|
deferredEdges: this.deferredEdges,
|
|
97
116
|
}),
|
|
98
117
|
);
|
|
99
118
|
|
|
100
|
-
for (const node of
|
|
101
|
-
|
|
119
|
+
for (const node of nodes) {
|
|
120
|
+
handledNodes.add(node);
|
|
102
121
|
}
|
|
103
122
|
}
|
|
104
123
|
|
|
@@ -116,7 +135,7 @@ class GraphInsert extends GraphOperation {
|
|
|
116
135
|
// `#ref` nodes, since the graph is a tree otherwise.
|
|
117
136
|
_breakCycle(handledNodes) {
|
|
118
137
|
for (const node of this.graph.nodes) {
|
|
119
|
-
if (
|
|
138
|
+
if (handledNodes.has(node)) {
|
|
120
139
|
continue;
|
|
121
140
|
}
|
|
122
141
|
|
|
@@ -195,16 +214,12 @@ class GraphInsert extends GraphOperation {
|
|
|
195
214
|
];
|
|
196
215
|
}
|
|
197
216
|
|
|
198
|
-
_isHandled(node, handledNodes) {
|
|
199
|
-
return handledNodes.has(node);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
217
|
_hasDependencies(node, handledNodes) {
|
|
203
218
|
return this._pendingDependencyEdges(node, handledNodes).length > 0;
|
|
204
219
|
}
|
|
205
220
|
|
|
206
221
|
// Returns the dependency edges of `node` whose other node still needs to be
|
|
207
|
-
//
|
|
222
|
+
// handled first. Deferred edges are ignored.
|
|
208
223
|
_pendingDependencyEdges(node, handledNodes) {
|
|
209
224
|
const edges = this.dependencies.get(node) || [];
|
|
210
225
|
|
|
@@ -214,22 +229,51 @@ class GraphInsert extends GraphOperation {
|
|
|
214
229
|
return (
|
|
215
230
|
!this.deferredEdges.has(edge) &&
|
|
216
231
|
!handledNodes.has(dependencyNode) &&
|
|
217
|
-
!this.currentGraph.nodeForNode(dependencyNode)
|
|
232
|
+
(!this.currentGraph.nodeForNode(dependencyNode) ||
|
|
233
|
+
this._readsFilledProps(edge, dependencyNode))
|
|
218
234
|
);
|
|
219
235
|
});
|
|
220
236
|
}
|
|
221
237
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// also mark those nodes as handled as there is nothing to actually
|
|
229
|
-
// insert.
|
|
230
|
-
for (const refNode of node.referencingNodes) {
|
|
231
|
-
this._markHandled(refNode, handledNodes);
|
|
238
|
+
// Returns true if the reference `edge` reads props that the graph fills into
|
|
239
|
+
// `node`. The keys of existing models are known right away, but these props
|
|
240
|
+
// only once they're filled in (#356).
|
|
241
|
+
_readsFilledProps(edge, node) {
|
|
242
|
+
if (edge.type === ModelGraphEdge.Type.Relation) {
|
|
243
|
+
return false;
|
|
232
244
|
}
|
|
245
|
+
|
|
246
|
+
const filledProps = this._getFilledProps(node);
|
|
247
|
+
// A `#ref` node stands in for `node` in its relations, which read only
|
|
248
|
+
// their keys from it:
|
|
249
|
+
const readProps =
|
|
250
|
+
edge.refType === ModelGraphEdge.ReferenceType.Property
|
|
251
|
+
? [edge.refRelatedDataPath[0]]
|
|
252
|
+
: this._getRelationProps(edge.ownerNode.edges, edge.ownerNode);
|
|
253
|
+
|
|
254
|
+
return readProps.some((prop) => filledProps.includes(prop));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Returns the props of `node` that the graph fills in from other models,
|
|
258
|
+
// through its relations and its property references.
|
|
259
|
+
_getFilledProps(node) {
|
|
260
|
+
const edges = this.dependencies.get(node) || [];
|
|
261
|
+
|
|
262
|
+
return [
|
|
263
|
+
...this._getRelationProps(edges, node),
|
|
264
|
+
...edges
|
|
265
|
+
.filter((edge) => edge.refType === ModelGraphEdge.ReferenceType.Property)
|
|
266
|
+
.map((edge) => edge.refOwnerDataPath[0]),
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Returns the props of `node` that the relation edges among `edges` join on.
|
|
271
|
+
_getRelationProps(edges, node) {
|
|
272
|
+
return edges
|
|
273
|
+
.filter((edge) => edge.type === ModelGraphEdge.Type.Relation)
|
|
274
|
+
.flatMap((edge) => {
|
|
275
|
+
return (edge.isOwnerNode(node) ? edge.relation.ownerProp : edge.relation.relatedProp).props;
|
|
276
|
+
});
|
|
233
277
|
}
|
|
234
278
|
|
|
235
279
|
_createJoinRowActions() {
|
|
@@ -14,22 +14,24 @@ const promiseUtils = require('../../../utils/promiseUtils');
|
|
|
14
14
|
* when needed.
|
|
15
15
|
*/
|
|
16
16
|
class GraphInsertAction extends GraphAction {
|
|
17
|
-
constructor(graphData, { nodes, dependencies, deferredEdges }) {
|
|
17
|
+
constructor(graphData, { nodes, refNodes, dependencies, deferredEdges }) {
|
|
18
18
|
super(graphData);
|
|
19
19
|
|
|
20
|
-
// Nodes to insert.
|
|
20
|
+
// Nodes to insert, and `#ref` nodes to resolve once they are inserted.
|
|
21
21
|
this.nodes = nodes;
|
|
22
|
+
this.refNodes = refNodes;
|
|
22
23
|
this.dependencies = dependencies;
|
|
23
24
|
// Dependency edges that are resolved once the dependency node has been
|
|
24
25
|
// inserted, and are patched in by `DeferredRelationGraphInsertAction`.
|
|
25
26
|
this.deferredEdges = deferredEdges;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
run(builder) {
|
|
29
|
+
async run(builder) {
|
|
29
30
|
const batches = this._createInsertBatches(builder);
|
|
30
31
|
const concurrency = this._getConcurrency(builder, this.nodes);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
await promiseUtils.map(batches, (batch) => this._insertBatch(builder, batch), { concurrency });
|
|
34
|
+
this._resolveDependencies(this.refNodes);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
_createInsertBatches(builder) {
|
|
@@ -49,7 +51,6 @@ class GraphInsertAction extends GraphAction {
|
|
|
49
51
|
async _insertBatch(parentBuilder, nodes) {
|
|
50
52
|
await this._beforeInsert(nodes);
|
|
51
53
|
await this._insert(parentBuilder, nodes);
|
|
52
|
-
await this._afterInsert(nodes);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
_beforeInsert(nodes) {
|
|
@@ -82,6 +83,8 @@ class GraphInsertAction extends GraphAction {
|
|
|
82
83
|
_resolveDependency(dependencyNode, edge) {
|
|
83
84
|
if (edge.type === ModelGraphEdge.Type.Relation && !edge.relation.joinTable) {
|
|
84
85
|
this._resolveRelationDependency(dependencyNode, edge);
|
|
86
|
+
} else if (edge.refType === ModelGraphEdge.ReferenceType.Object) {
|
|
87
|
+
this._resolveReferences(edge.getOtherNode(dependencyNode));
|
|
85
88
|
} else if (edge.refType === ModelGraphEdge.ReferenceType.Property) {
|
|
86
89
|
this._resolvePropertyReferenceNode(dependencyNode, edge);
|
|
87
90
|
}
|
|
@@ -219,14 +222,6 @@ class GraphInsertAction extends GraphAction {
|
|
|
219
222
|
}),
|
|
220
223
|
);
|
|
221
224
|
}
|
|
222
|
-
|
|
223
|
-
_afterInsert(nodes) {
|
|
224
|
-
for (const node of nodes) {
|
|
225
|
-
for (const refNode of node.referencingNodes) {
|
|
226
|
-
this._resolveDependency(refNode, refNode.parentEdge);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
225
|
}
|
|
231
226
|
|
|
232
227
|
function getModelClass(node) {
|