objection 3.9.1 → 4.0.0-rc.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/dbErrors/LICENSE +21 -0
- package/lib/dbErrors/errorCodes/mssql.js +19 -0
- package/lib/dbErrors/errorCodes/mysql.js +805 -0
- package/lib/dbErrors/errorCodes/postgres.js +241 -0
- package/lib/dbErrors/errors/CheckViolationError.js +10 -0
- package/lib/dbErrors/errors/ConstraintViolationError.js +3 -0
- package/lib/dbErrors/errors/DBError.js +9 -0
- package/lib/dbErrors/errors/DataError.js +3 -0
- package/lib/dbErrors/errors/ForeignKeyViolationError.js +11 -0
- package/lib/dbErrors/errors/NotNullViolationError.js +12 -0
- package/lib/dbErrors/errors/UniqueViolationError.js +12 -0
- package/lib/dbErrors/index.js +59 -0
- package/lib/dbErrors/parsers/mssql/DBError/CheckViolationError/parser.js +28 -0
- package/lib/dbErrors/parsers/mssql/DBError/DataError/parser.js +25 -0
- package/lib/dbErrors/parsers/mssql/DBError/ForeignKeyViolationError/parser.js +39 -0
- package/lib/dbErrors/parsers/mssql/DBError/NotNullViolationError/parser.js +30 -0
- package/lib/dbErrors/parsers/mssql/DBError/UniqueViolationError/parser.js +53 -0
- package/lib/dbErrors/parsers/mssql/DBError/parser.js +51 -0
- package/lib/dbErrors/parsers/mssql/DBError/util.js +16 -0
- package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/ForeignKeyViolationError/parser.js +43 -0
- package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/NotNullViolationError/parser.js +29 -0
- package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/UniqueViolationError/parser.js +27 -0
- package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/parser.js +23 -0
- package/lib/dbErrors/parsers/mysql/DBError/DataError/parser.js +24 -0
- package/lib/dbErrors/parsers/mysql/DBError/parser.js +28 -0
- package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/CheckViolationError/parser.js +18 -0
- package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/ForeignKeyViolationError/parser.js +18 -0
- package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/NotNullViolationError/parser.js +18 -0
- package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/UniqueViolationError/parser.js +40 -0
- package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/parser.js +25 -0
- package/lib/dbErrors/parsers/postgres/DBError/DataError/parser.js +16 -0
- package/lib/dbErrors/parsers/postgres/DBError/parser.js +27 -0
- package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/CheckViolationError/parser.js +20 -0
- package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/ForeignKeyViolationError/parser.js +20 -0
- package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/NotNullViolationError/parser.js +27 -0
- package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/UniqueViolationError/parser.js +28 -0
- package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/parser.js +24 -0
- package/lib/dbErrors/parsers/sqlite/DBError/parser.js +23 -0
- package/lib/dbErrors/utils/sqlState.js +3 -0
- package/lib/initialize.js +1 -7
- package/lib/model/AjvValidator.js +51 -117
- package/lib/model/Model.js +84 -59
- package/lib/model/ModifierNotFoundError.js +1 -7
- package/lib/model/NotFoundError.js +1 -7
- package/lib/model/RelationDoesNotExistError.js +1 -7
- package/lib/model/ValidationError.js +3 -10
- package/lib/model/Validator.js +1 -7
- package/lib/model/getModel.js +7 -6
- package/lib/model/inheritModel.js +1 -7
- package/lib/model/modelBindKnex.js +4 -10
- package/lib/model/modelClone.js +5 -11
- package/lib/model/modelColPropMap.js +3 -10
- package/lib/model/modelId.js +3 -10
- package/lib/model/modelJsonAttributes.js +35 -16
- package/lib/model/modelParseRelations.js +2 -8
- package/lib/model/modelQueryProps.js +11 -23
- package/lib/model/modelSet.js +89 -41
- package/lib/model/modelTableMetadata.js +4 -11
- package/lib/model/modelToJson.js +6 -13
- package/lib/model/modelUtils.js +14 -13
- package/lib/model/modelValidate.js +3 -9
- package/lib/model/modelValues.js +5 -14
- package/lib/model/modelVisitor.js +1 -7
- package/lib/objection.js +33 -71
- package/lib/queryBuilder/FunctionBuilder.js +4 -11
- package/lib/queryBuilder/InternalOptions.js +4 -9
- package/lib/queryBuilder/JoinBuilder.js +5 -8
- package/lib/queryBuilder/QueryBuilder.js +126 -95
- package/lib/queryBuilder/QueryBuilderBase.js +41 -123
- package/lib/queryBuilder/QueryBuilderContext.js +2 -8
- package/lib/queryBuilder/QueryBuilderContextBase.js +2 -8
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +7 -21
- package/lib/queryBuilder/QueryBuilderUserContext.js +1 -7
- package/lib/queryBuilder/RawBuilder.js +5 -13
- package/lib/queryBuilder/ReferenceBuilder.js +20 -48
- package/lib/queryBuilder/RelationExpression.js +18 -16
- package/lib/queryBuilder/StaticHookArguments.js +74 -16
- package/lib/queryBuilder/ValueBuilder.js +11 -13
- package/lib/queryBuilder/getJoinBuilder.js +10 -0
- package/lib/queryBuilder/graph/GraphLoader.js +304 -0
- package/lib/queryBuilder/graph/GraphNode.js +235 -0
- package/lib/queryBuilder/graph/GraphOptions.js +55 -248
- package/lib/queryBuilder/graph/GraphPlan.js +395 -0
- package/lib/queryBuilder/graph/GraphUpsert.js +466 -414
- package/lib/queryBuilder/graph/GraphWriter.js +1263 -0
- package/lib/queryBuilder/join/JoinResultColumn.js +1 -7
- package/lib/queryBuilder/join/JoinResultParser.js +6 -11
- package/lib/queryBuilder/join/RelationJoiner.js +66 -84
- package/lib/queryBuilder/join/TableNode.js +2 -8
- package/lib/queryBuilder/join/TableTree.js +3 -9
- package/lib/queryBuilder/join/utils.js +5 -15
- package/lib/queryBuilder/jsonDialects/JsonDialect.js +51 -0
- package/lib/queryBuilder/jsonDialects/MySqlJsonDialect.js +81 -0
- package/lib/queryBuilder/jsonDialects/PostgresJsonDialect.js +45 -0
- package/lib/queryBuilder/jsonDialects/SqliteJsonDialect.js +67 -0
- package/lib/queryBuilder/jsonDialects/index.js +44 -0
- package/lib/queryBuilder/operations/DelegateOperation.js +2 -8
- package/lib/queryBuilder/operations/DeleteOperation.js +10 -39
- package/lib/queryBuilder/operations/FindByIdOperation.js +7 -12
- package/lib/queryBuilder/operations/FindByIdsOperation.js +4 -8
- package/lib/queryBuilder/operations/FindOperation.js +14 -34
- package/lib/queryBuilder/operations/FirstOperation.js +6 -10
- package/lib/queryBuilder/operations/FromOperation.js +3 -9
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +13 -12
- package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +4 -10
- package/lib/queryBuilder/operations/InsertGraphOperation.js +7 -18
- package/lib/queryBuilder/operations/InsertOperation.js +118 -45
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +5 -12
- package/lib/queryBuilder/operations/InstanceFindOperation.js +4 -11
- package/lib/queryBuilder/operations/InstanceInsertOperation.js +2 -8
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +7 -14
- package/lib/queryBuilder/operations/JoinRelatedOperation.js +5 -11
- package/lib/queryBuilder/operations/KnexOperation.js +2 -8
- package/lib/queryBuilder/operations/LimitPerParentOperation.js +7 -69
- package/lib/queryBuilder/operations/MergeOperation.js +5 -11
- package/lib/queryBuilder/operations/NoneOperation.js +2 -8
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +20 -49
- package/lib/queryBuilder/operations/OnBuildKnexOperation.js +2 -8
- package/lib/queryBuilder/operations/OnBuildOperation.js +2 -8
- package/lib/queryBuilder/operations/OnErrorOperation.js +2 -8
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +1 -7
- package/lib/queryBuilder/operations/RangeOperation.js +11 -26
- package/lib/queryBuilder/operations/RelateOperation.js +2 -8
- package/lib/queryBuilder/operations/ReturningOperation.js +3 -9
- package/lib/queryBuilder/operations/RunAfterOperation.js +2 -12
- package/lib/queryBuilder/operations/RunBeforeOperation.js +2 -8
- package/lib/queryBuilder/operations/UnrelateOperation.js +2 -8
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +14 -17
- package/lib/queryBuilder/operations/UpdateOperation.js +32 -132
- package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +8 -11
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +6 -12
- package/lib/queryBuilder/operations/WhereCompositeOperation.js +3 -9
- package/lib/queryBuilder/operations/WhereGroupOperation.js +2 -8
- package/lib/queryBuilder/operations/eager/EagerOperation.js +3 -9
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +72 -10
- package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +2 -8
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +23 -44
- package/lib/queryBuilder/operations/fieldExpressions.js +122 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +20 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +44 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonTypeOperation.js +14 -0
- package/lib/queryBuilder/operations/select/SelectOperation.js +11 -21
- package/lib/queryBuilder/operations/select/Selection.js +19 -17
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +4 -10
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +4 -10
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +4 -10
- package/lib/queryBuilder/operations/windowOrder.js +56 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +3 -7
- package/lib/queryBuilder/parsers/relationExpressionParser.js +42 -43
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +39 -36
- package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +2 -8
- package/lib/queryBuilder/transformations/QueryTransformation.js +1 -7
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +3 -9
- package/lib/queryBuilder/transformations/index.js +3 -11
- package/lib/queryBuilder/whereGroups.js +9 -24
- package/lib/queryBuilder/withoutOrder.js +4 -11
- package/lib/relations/Relation.js +47 -28
- package/lib/relations/RelationDeleteOperation.js +4 -14
- package/lib/relations/RelationFindOperation.js +119 -90
- package/lib/relations/RelationInsertOperation.js +2 -8
- package/lib/relations/RelationOwner.js +5 -11
- package/lib/relations/RelationProperty.js +4 -10
- package/lib/relations/RelationUpdateOperation.js +4 -14
- package/lib/relations/RelationWriteResultOperation.js +15 -0
- package/lib/relations/belongsToMany/BelongsToManyDeleteOperation.js +23 -0
- package/lib/relations/belongsToMany/BelongsToManyFindOperation.js +35 -0
- package/lib/relations/belongsToMany/BelongsToManyInsertOperation.js +28 -0
- package/lib/relations/belongsToMany/BelongsToManyRelateOperation.js +16 -0
- package/lib/relations/belongsToMany/BelongsToManyRelation.js +276 -0
- package/lib/relations/belongsToMany/BelongsToManyUnrelateOperation.js +11 -0
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +2 -8
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +6 -11
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +3 -9
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +8 -19
- package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +53 -27
- package/lib/relations/hasMany/HasManyInsertOperation.js +5 -10
- package/lib/relations/hasMany/HasManyRelateOperation.js +5 -11
- package/lib/relations/hasMany/HasManyRelation.js +9 -11
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +3 -9
- package/lib/relations/hasOne/HasOneRelation.js +2 -8
- package/lib/relations/hasOneThrough/HasOneThroughRelation.js +2 -8
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +10 -15
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +2 -8
- package/lib/relations/manyToMany/ManyToManyRelation.js +82 -50
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +2 -8
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +5 -9
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +2 -8
- package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +3 -9
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +20 -59
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +6 -11
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +4 -10
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +3 -9
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +5 -9
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +3 -9
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +3 -9
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +3 -9
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +5 -9
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +64 -72
- package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +3 -9
- package/lib/transaction.js +3 -9
- package/lib/utils/assert.js +3 -11
- package/lib/utils/buildUtils.js +2 -8
- package/lib/utils/classUtils.js +1 -7
- package/lib/utils/createModifier.js +2 -8
- package/lib/utils/identifierMapping.js +23 -27
- package/lib/utils/internalPropUtils.js +1 -7
- package/lib/utils/knexUtils.js +84 -40
- package/lib/utils/mixin.js +3 -10
- package/lib/utils/normalizeIds.js +2 -8
- package/lib/utils/objectUtils.js +41 -60
- package/lib/utils/parseFieldExpression.js +16 -16
- package/lib/utils/promiseUtils/after.js +2 -8
- package/lib/utils/promiseUtils/afterReturn.js +2 -8
- package/lib/utils/promiseUtils/index.js +7 -16
- package/lib/utils/promiseUtils/isPromise.js +2 -8
- package/lib/utils/promiseUtils/map.js +2 -8
- package/lib/utils/promiseUtils/mapAfterAllReturn.js +2 -8
- package/lib/utils/promiseUtils/try.js +2 -8
- package/lib/utils/resolveModel.js +10 -17
- package/lib/utils/tmpColumnUtils.js +2 -9
- package/lib/utils/warnings.js +2 -14
- package/package.json +25 -17
- package/typings/objection/index.d.ts +3217 -2179
- package/lib/.eslintrc.json +0 -8
- package/lib/model/graph/ModelGraph.js +0 -80
- package/lib/model/graph/ModelGraphBuilder.js +0 -241
- package/lib/model/graph/ModelGraphEdge.js +0 -52
- package/lib/model/graph/ModelGraphNode.js +0 -238
- package/lib/queryBuilder/graph/GraphAction.js +0 -69
- package/lib/queryBuilder/graph/GraphData.js +0 -53
- package/lib/queryBuilder/graph/GraphFetcher.js +0 -197
- package/lib/queryBuilder/graph/GraphNodeDbExistence.js +0 -184
- package/lib/queryBuilder/graph/GraphOperation.js +0 -27
- package/lib/queryBuilder/graph/delete/GraphDelete.js +0 -73
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +0 -88
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +0 -58
- package/lib/queryBuilder/graph/insert/GraphInsert.js +0 -296
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +0 -233
- package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +0 -92
- package/lib/queryBuilder/graph/patch/GraphPatch.js +0 -20
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +0 -289
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +0 -31
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +0 -126
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +0 -30
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +0 -37
- package/lib/queryBuilder/operations/jsonApi/WhereJsonPostgresOperation.js +0 -62
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +0 -192
- package/lib/utils/wrapDbError.js +0 -64
|
@@ -1,30 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { ValidationErrorType } = require('../../model/ValidationError');
|
|
13
|
-
const { RelationExpression } = require('../RelationExpression');
|
|
14
|
-
const { GraphNodeDbExistence } = require('./GraphNodeDbExistence');
|
|
15
|
-
const { GraphData } = require('./GraphData');
|
|
16
|
-
const { uniqBy, asArray, isObject } = require('../../utils/objectUtils');
|
|
17
|
-
const { warnOnce } = require('../../utils/warnings');
|
|
18
|
-
|
|
19
|
-
class GraphUpsert {
|
|
1
|
+
import { GraphNode, createNotModelError } from './GraphNode.js';
|
|
2
|
+
import { GraphOptions } from './GraphOptions.js';
|
|
3
|
+
import { GraphPlan } from './GraphPlan.js';
|
|
4
|
+
import { GraphWriter } from './GraphWriter.js';
|
|
5
|
+
import { loadCurrentGraph } from './GraphLoader.js';
|
|
6
|
+
import { ValidationErrorType } from '../../model/ValidationError.js';
|
|
7
|
+
import { RelationExpression } from '../RelationExpression.js';
|
|
8
|
+
import { asArray, isObject, nonStrictEquals } from '../../utils/objectUtils.js';
|
|
9
|
+
import { isInternalProp } from '../../utils/internalPropUtils.js';
|
|
10
|
+
|
|
11
|
+
export class GraphUpsert {
|
|
20
12
|
constructor({ rootModelClass, objects, upsertOptions, insertOnly = false }) {
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
// Wrap `undefined` too, so that it fails the check:
|
|
14
|
+
for (const obj of Array.isArray(objects) ? objects : [objects]) {
|
|
15
|
+
if (!isObject(obj)) {
|
|
16
|
+
throw createNotModelError(rootModelClass, obj);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
23
19
|
|
|
24
20
|
this.objects = rootModelClass.ensureModelArray(objects, GraphUpsert.modelOptions);
|
|
25
21
|
this.isArray = Array.isArray(objects);
|
|
26
|
-
this.
|
|
27
|
-
this.insertOnly = insertOnly;
|
|
22
|
+
this.graphOptions = new GraphOptions(upsertOptions, insertOnly);
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
static get modelOptions() {
|
|
@@ -36,482 +31,539 @@ class GraphUpsert {
|
|
|
36
31
|
// to. Even applying it to the root table only would be unsound: rows skipped
|
|
37
32
|
// by `ignore()` aren't returned, which leaves the root models without ids,
|
|
38
33
|
// so that related rows would be inserted with missing or wrong foreign keys.
|
|
39
|
-
|
|
40
|
-
// TODO: Throw an error instead in objection 4.0 (#53).
|
|
41
|
-
static warnOnConflict(builder, method) {
|
|
34
|
+
static checkForOnConflict(builder, method) {
|
|
42
35
|
if (builder.has(isOnConflictOperation)) {
|
|
43
|
-
|
|
36
|
+
throw new Error(
|
|
44
37
|
`onConflict(), ignore() and merge() are not supported by ${method}(). ` +
|
|
45
|
-
'Insert the conflicting rows with a separate insert() query instead.
|
|
46
|
-
'This will throw in objection 4.0.',
|
|
38
|
+
'Insert the conflicting rows with a separate insert() query instead.',
|
|
47
39
|
);
|
|
48
40
|
}
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
run(builder) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
43
|
+
async run(builder) {
|
|
44
|
+
const { graphOptions } = this;
|
|
45
|
+
const nodes = GraphNode.buildGraph(builder.modelClass(), this.roots);
|
|
46
|
+
GraphNode.linkReferences(nodes);
|
|
47
|
+
const restoreMarkers = GraphNode.removeMarkers(nodes);
|
|
48
|
+
|
|
49
|
+
// A `#ref` to a model with an id is that model, also when its relations
|
|
50
|
+
// are loaded and upserted. Models inserted in a pass have their ids in the
|
|
51
|
+
// next ones:
|
|
52
|
+
const identifyReferences = (nodes) => {
|
|
53
|
+
for (const { obj, referencedNode } of nodes) {
|
|
54
|
+
if (referencedNode?.obj.$hasId() && !obj.$hasId()) {
|
|
55
|
+
obj.$id(referencedNode.obj.$id());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
54
59
|
|
|
55
|
-
const
|
|
56
|
-
|
|
60
|
+
for (const node of nodes) {
|
|
61
|
+
if (node.parent && node.isDbReference) {
|
|
62
|
+
node.relation.setRelateProp(node.obj, asArray(node.dbReference));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
identifyReferences(nodes);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
checkGraph(builder, new GraphPlan({ nodes, graphOptions }));
|
|
70
|
+
checkPlacements(builder, nodes);
|
|
71
|
+
const writtenNodes = new Set();
|
|
72
|
+
let keptNodes = new Map();
|
|
73
|
+
|
|
74
|
+
// Rows that the graph relates into a HasManyRelation or HasOneRelation
|
|
75
|
+
// and gives relations are moved first, and their relations are upserted
|
|
76
|
+
// in another pass, against their rows as they are after the move (#214).
|
|
77
|
+
for (let passNodes = nodes; passNodes.length > 0;) {
|
|
78
|
+
const plan = new GraphPlan({
|
|
79
|
+
nodes: passNodes,
|
|
80
|
+
graphOptions,
|
|
81
|
+
keptNodes,
|
|
82
|
+
});
|
|
83
|
+
const movedNodes = graphOptions.isInsertOnly() ? [] : await loadCurrentGraph(builder, plan);
|
|
84
|
+
|
|
85
|
+
plan.defer(movedNodes.flatMap((node) => node.descendants));
|
|
86
|
+
plan.cacheDecisions();
|
|
87
|
+
checkMissingModels(builder, plan);
|
|
88
|
+
plan.prune().plan();
|
|
89
|
+
checkChangedIds(builder, plan);
|
|
90
|
+
// The rows that this pass keeps stay kept in the next ones, as decided
|
|
91
|
+
// before its writes:
|
|
92
|
+
keptNodes = plan.keptNodes;
|
|
93
|
+
|
|
94
|
+
// Models that `noInsert` or `noRelate` ignore aren't written, but the
|
|
95
|
+
// ones marked with `#delete` or `#unrelate` keep their values:
|
|
96
|
+
for (const node of plan.activeNodes) {
|
|
97
|
+
if (plan.actions.get(node) !== 'ignore' || node.toBeUnrelatedOrDeleted) {
|
|
98
|
+
writtenNodes.add(node);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
67
101
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
function warnOnUnknownOptions(upsertOptions) {
|
|
72
|
-
// Recursive upserts pass on the already checked options of their parent.
|
|
73
|
-
if (upsertOptions instanceof GraphOptions) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
102
|
+
checkReferences(builder, plan, writtenNodes);
|
|
103
|
+
checkJoinTables(builder, plan);
|
|
104
|
+
await new GraphWriter(plan).write(builder);
|
|
76
105
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
106
|
+
passNodes = movedNodes.flatMap((node) => [node, ...node.descendants]);
|
|
107
|
+
identifyReferences(passNodes);
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
// The input can be retried as it was:
|
|
111
|
+
restoreMarkers();
|
|
112
|
+
throw err;
|
|
83
113
|
}
|
|
114
|
+
|
|
115
|
+
return this.roots;
|
|
84
116
|
}
|
|
85
|
-
}
|
|
86
117
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
throw createNotModelError(modelClass, obj);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
118
|
+
get roots() {
|
|
119
|
+
return this.isArray ? this.objects : this.objects[0];
|
|
120
|
+
}
|
|
93
121
|
}
|
|
94
122
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
// Check the models with ids that are neither at their place nor related, once
|
|
124
|
+
// the current graph of a pass is loaded.
|
|
125
|
+
function checkMissingModels(builder, plan) {
|
|
126
|
+
for (const node of plan.nodes) {
|
|
127
|
+
if (plan.intentFor(node) === 'missing') {
|
|
128
|
+
const id = node.obj.$id();
|
|
129
|
+
|
|
130
|
+
throw node.modelClass.createNotFoundError(builder.context(), {
|
|
131
|
+
message: node.isMissing
|
|
132
|
+
? `model (id=${id}) does not exist. If you want to insert it with an id, use the insertMissing option`
|
|
133
|
+
: node.isRoot
|
|
134
|
+
? `root model (id=${id}) does not exist. If you want to insert it with an id, use the insertMissing option`
|
|
135
|
+
: `model (id=${id}) is not a child of model (id=${node.parent.obj.$id()}). If you want to relate it, use the relate option. If you want to insert it with an id, use the insertMissing option`,
|
|
136
|
+
dataPath: node.dataPath,
|
|
137
|
+
});
|
|
99
138
|
}
|
|
100
|
-
|
|
101
|
-
node.parentEdge.relation.setRelateProp(node.obj, asArray(node.dbReference));
|
|
102
139
|
}
|
|
103
140
|
}
|
|
104
141
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
142
|
+
// The graph writes keys of rows that can't change if they're part of the ids
|
|
143
|
+
// of the rows: the foreign key of a row that a HasMany or HasOne relate moves
|
|
144
|
+
// to another owner, and the foreign key of an existing row that its own
|
|
145
|
+
// BelongsToOne relate or insert points to another row, or that `null`,
|
|
146
|
+
// `#unrelate` or `#delete` clears. Keys that aren't known before the writes
|
|
147
|
+
// are compared only where they surely change, as new models get new keys.
|
|
148
|
+
function checkChangedIds(builder, plan) {
|
|
149
|
+
const { actions } = plan;
|
|
150
|
+
const newKey = {};
|
|
151
|
+
const getKey = (node, prop, i) =>
|
|
152
|
+
actions.get(node) === 'insert' && !node.obj.$hasId()
|
|
153
|
+
? newKey
|
|
154
|
+
: (prop.getProp(node.obj, i) ?? (node.existing && prop.getProp(node.existing.obj, i)));
|
|
155
|
+
|
|
156
|
+
const checkKey = (row, relation, rowProp, getNewKey) => {
|
|
157
|
+
const idProps = row.modelClass.getIdPropertyArray();
|
|
158
|
+
const isChanged =
|
|
159
|
+
actions.get(row) !== 'insert' &&
|
|
160
|
+
rowProp.props.some((prop, i) => {
|
|
161
|
+
const key = row.obj[prop];
|
|
162
|
+
const changedKey = getNewKey(i);
|
|
163
|
+
return (
|
|
164
|
+
idProps.includes(prop) &&
|
|
165
|
+
key !== undefined &&
|
|
166
|
+
changedKey !== undefined &&
|
|
167
|
+
(changedKey === newKey || !nonStrictEquals(key, changedKey))
|
|
168
|
+
);
|
|
169
|
+
});
|
|
114
170
|
|
|
115
|
-
|
|
116
|
-
|
|
171
|
+
if (isChanged) {
|
|
172
|
+
throw builder.modelClass().createValidationError({
|
|
173
|
+
type: ValidationErrorType.InvalidGraph,
|
|
174
|
+
message: `the graph changes the key of the ${row.modelClass.name} at ${formatPath(row.dataPathKey)} through ${relation.name}, which is part of its id`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
117
178
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
} else {
|
|
122
|
-
return GraphFetcher.fetchCurrentGraph({ builder, graph, graphOptions });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
179
|
+
for (const node of plan.activeNodes) {
|
|
180
|
+
const { relation, parent } = node;
|
|
181
|
+
const action = actions.get(node);
|
|
125
182
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
183
|
+
if (relation?.keySide === 'related' && action === 'relate') {
|
|
184
|
+
checkKey(node.referencedNode || node, relation, relation.relatedProp, (i) =>
|
|
185
|
+
getKey(parent, relation.ownerProp, i),
|
|
186
|
+
);
|
|
187
|
+
} else if (relation?.keySide === 'owner' && relation.isOneToOne()) {
|
|
188
|
+
if (node.toBeUnrelatedOrDeleted) {
|
|
189
|
+
if (node.current) {
|
|
190
|
+
checkKey(parent, relation, relation.ownerProp, () => null);
|
|
191
|
+
}
|
|
192
|
+
} else if (action === 'relate' || action === 'insert') {
|
|
193
|
+
checkKey(parent, relation, relation.ownerProp, (i) =>
|
|
194
|
+
getKey(node.referencedNode || node, relation.relatedProp, i),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
137
197
|
}
|
|
138
198
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
199
|
+
// `null` clears the key:
|
|
200
|
+
const nullNames = node.modelClass.getRelationNames().filter((name) => node.obj[name] === null);
|
|
142
201
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
!graphData.currentGraph.nodeForNode(node) &&
|
|
147
|
-
!graphData.graphOptions.shouldInsertIgnoreDisable(node, graphData)
|
|
148
|
-
);
|
|
149
|
-
});
|
|
202
|
+
for (const name of nullNames) {
|
|
203
|
+
const nullRelation = node.modelClass.getRelation(name);
|
|
150
204
|
|
|
151
|
-
|
|
205
|
+
if (nullRelation.keySide === 'owner' && nullRelation.isOneToOne()) {
|
|
206
|
+
checkKey(node, nullRelation, nullRelation.ownerProp, () => null);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
152
210
|
}
|
|
153
211
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
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
|
-
}
|
|
212
|
+
// Check the whole graph before anything is written.
|
|
213
|
+
function checkGraph(builder, plan) {
|
|
214
|
+
const { nodes, graphOptions } = plan;
|
|
215
|
+
const modelClass = builder.modelClass();
|
|
216
|
+
const createInvalidGraphError = (message) =>
|
|
217
|
+
modelClass.createValidationError({ type: ValidationErrorType.InvalidGraph, message });
|
|
170
218
|
|
|
171
|
-
|
|
172
|
-
const
|
|
219
|
+
const allowedExpression = builder.allowedGraphExpression();
|
|
220
|
+
const rootObjs = nodes.filter((node) => node.isRoot).map((node) => node.obj);
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
allowedExpression &&
|
|
224
|
+
!allowedExpression.isSubExpression(RelationExpression.fromModelGraph(rootObjs))
|
|
225
|
+
) {
|
|
226
|
+
throw modelClass.createValidationError({
|
|
227
|
+
type: ValidationErrorType.UnallowedRelation,
|
|
228
|
+
message: 'trying to upsert an unallowed relation',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
173
231
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
232
|
+
if (
|
|
233
|
+
!graphOptions.allowRefs() &&
|
|
234
|
+
nodes.some((node) => node.isReference || node.propertyReferences.length > 0)
|
|
235
|
+
) {
|
|
236
|
+
throw createInvalidGraphError(
|
|
237
|
+
'#ref references are not allowed in a graph by default. see the allowRefs insert/upsert graph option',
|
|
238
|
+
);
|
|
239
|
+
}
|
|
178
240
|
|
|
179
|
-
|
|
180
|
-
|
|
241
|
+
// `#unrelate` and `#delete` markers come from the graph data, which may be
|
|
242
|
+
// untrusted input. Like `#ref`, they have to be allowed explicitly (#2410).
|
|
243
|
+
// Root models can't be marked even with `allowMarkers`, so report that first.
|
|
244
|
+
// An insert has nothing to unrelate or delete, so `insertGraph` rejects them.
|
|
245
|
+
const markedNode = nodes.find((node) => node.toBeUnrelatedOrDeleted);
|
|
181
246
|
|
|
182
|
-
|
|
183
|
-
|
|
247
|
+
if (markedNode) {
|
|
248
|
+
const { graphUnrelateProp, graphDeleteProp } = markedNode.modelClass;
|
|
249
|
+
const markers = `${graphUnrelateProp} and ${graphDeleteProp}`;
|
|
184
250
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!
|
|
190
|
-
|
|
251
|
+
if (nodes.some((node) => node.isRoot && node.toBeUnrelatedOrDeleted)) {
|
|
252
|
+
throw createInvalidGraphError(
|
|
253
|
+
`${markers} can only be used for related models, not for root models`,
|
|
254
|
+
);
|
|
255
|
+
} else if (!graphOptions.allowMarkers()) {
|
|
256
|
+
throw createInvalidGraphError(
|
|
257
|
+
graphOptions.isInsertOnly()
|
|
258
|
+
? `${markers} can only be used in upsertGraph, not in insertGraph`
|
|
259
|
+
: `${markers} markers are not allowed in a graph by default. see the allowMarkers upsertGraph option`,
|
|
260
|
+
);
|
|
191
261
|
}
|
|
262
|
+
}
|
|
192
263
|
|
|
193
|
-
|
|
194
|
-
const
|
|
264
|
+
if (graphOptions.isInsertOnly()) {
|
|
265
|
+
const relatedNodes = nodes.filter((node) => plan.actionFor(node) === 'relate');
|
|
195
266
|
|
|
196
|
-
if (
|
|
197
|
-
|
|
267
|
+
if (relatedNodes.some((node) => node.relation.keySide === 'related')) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
'You cannot relate HasManyRelation or HasOneRelation using insertGraph, because those require update operations. Consider using upsertGraph instead.',
|
|
270
|
+
);
|
|
198
271
|
}
|
|
199
272
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// as a whole. Their relations are ignored, both in the upserted graph
|
|
209
|
-
// and in the current graph.
|
|
210
|
-
function pruneUnrelatedOrDeletedBranches(graphData) {
|
|
211
|
-
const { graph, currentGraph } = graphData;
|
|
212
|
-
|
|
213
|
-
const roots = findRoots(graph.nodes.filter((node) => node.toBeUnrelatedOrDeleted));
|
|
214
|
-
const currentRoots = roots.map((node) => currentGraph.nodeForNode(node)).filter(Boolean);
|
|
273
|
+
// `upsertGraph()` upserts the branches below related models, but
|
|
274
|
+
// `insertGraph()` leaves them out, which would drop the new models there
|
|
275
|
+
// (#217). Models with ids below related models are taken as fetched models
|
|
276
|
+
// with loaded relations, and are ignored.
|
|
277
|
+
for (const node of relatedNodes) {
|
|
278
|
+
const newNode = node.descendants.find(
|
|
279
|
+
(descendant) => !descendant.hasId && plan.actionFor(descendant) === 'insert',
|
|
280
|
+
);
|
|
215
281
|
|
|
216
|
-
|
|
217
|
-
|
|
282
|
+
if (newNode) {
|
|
283
|
+
throw createInvalidGraphError(
|
|
284
|
+
`insertGraph cannot insert the new model at ${formatPath(newNode.dataPathKey)} below a related model. Insert it with a separate query or use upsertGraph instead`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
218
289
|
}
|
|
219
290
|
|
|
220
|
-
|
|
221
|
-
|
|
291
|
+
// One row can't get two values for one column, and the slot of a
|
|
292
|
+
// HasOneRelation can't get two rows. A `#ref` or `#dbRef` model is a place of
|
|
293
|
+
// the row that it refers to. The keys that relations give a row are the values
|
|
294
|
+
// of the key props across them, or stand for these where the graph doesn't
|
|
295
|
+
// give them, e.g. for new models.
|
|
296
|
+
function checkPlacements(builder, nodes) {
|
|
297
|
+
const newRowIds = new Map();
|
|
298
|
+
const getRowId = (node) => {
|
|
299
|
+
const target = node.referencedNode || node;
|
|
300
|
+
|
|
301
|
+
if (target.obj.$hasId()) {
|
|
302
|
+
return target.rowKey;
|
|
303
|
+
} else {
|
|
304
|
+
if (!newRowIds.has(target)) {
|
|
305
|
+
newRowIds.set(target, `new:${newRowIds.size}`);
|
|
306
|
+
}
|
|
222
307
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
308
|
+
return newRowIds.get(target);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
226
311
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
312
|
+
// The values that the graph gives each row, by prop, and the model classes
|
|
313
|
+
// and relations through which the graph reaches the rows:
|
|
314
|
+
const rows = new Map();
|
|
315
|
+
const rowTables = new Map();
|
|
316
|
+
const rowRelations = new Map();
|
|
317
|
+
const setValue = (rowId, name, prop, value, path) => {
|
|
318
|
+
const values = rows.get(rowId) || new Map();
|
|
319
|
+
const other = values.get(prop);
|
|
320
|
+
rows.set(rowId, values);
|
|
321
|
+
|
|
322
|
+
if (!other) {
|
|
323
|
+
values.set(prop, { value, path });
|
|
324
|
+
} else if (!nonStrictEquals(other.value, value)) {
|
|
325
|
+
throw builder.modelClass().createValidationError({
|
|
326
|
+
type: ValidationErrorType.InvalidGraph,
|
|
327
|
+
message: `the graph gives ${prop} of one ${name} two values, at ${formatPath(other.path)} and at ${formatPath(path)}`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const setNodeValue = (node, prop, value, path) =>
|
|
332
|
+
setValue(getRowId(node), node.modelClass.name, prop, value, path);
|
|
333
|
+
|
|
334
|
+
// The join row of the pair of models that `node` and its parent are, the
|
|
335
|
+
// same through both relations of the join table:
|
|
336
|
+
const getJoinRowId = (node) =>
|
|
337
|
+
[
|
|
338
|
+
node.relation.joinTable,
|
|
339
|
+
...[
|
|
340
|
+
[node.relation.joinTableOwnerProp.cols, getRowId(node.parent)],
|
|
341
|
+
[node.relation.joinTableRelatedProp.cols, getRowId(node)],
|
|
342
|
+
]
|
|
343
|
+
.sort(([cols1], [cols2]) => cols1.join().localeCompare(cols2.join()))
|
|
344
|
+
.map(([cols, rowId]) => `${cols}=${rowId}`),
|
|
345
|
+
].join(':');
|
|
346
|
+
|
|
347
|
+
const getPath = (node, ...keys) => [node.dataPathKey, ...keys].filter(Boolean).join('.');
|
|
348
|
+
const getRelations = (node) =>
|
|
349
|
+
node.modelClass
|
|
350
|
+
.getRelationNames()
|
|
351
|
+
.filter((name) => node.obj[name] !== undefined)
|
|
352
|
+
.map((name) => node.modelClass.getRelation(name));
|
|
353
|
+
const isOwnerKey = (relation) => relation.keySide === 'owner' && relation.isOneToOne();
|
|
354
|
+
const isSlot = (relation) => relation.keySide === 'related' && relation.isOneToOne();
|
|
355
|
+
|
|
356
|
+
// The props that the models give, except for the ids and the keys that the
|
|
357
|
+
// relations of the same model set, which follow these relations:
|
|
358
|
+
for (const node of nodes) {
|
|
359
|
+
const { modelClass, obj, relation } = node;
|
|
360
|
+
const skippedProps = [
|
|
361
|
+
...modelClass.getRelationNames(),
|
|
362
|
+
...modelClass.getIdPropertyArray(),
|
|
363
|
+
...(relation?.keySide === 'related' ? relation.relatedProp.props : []),
|
|
364
|
+
// The extras of join rows belong to the join rows:
|
|
365
|
+
...(relation?.keySide === 'join' ? relation.joinTableExtras.map((e) => e.aliasProp) : []),
|
|
366
|
+
...getRelations(node)
|
|
367
|
+
.filter(isOwnerKey)
|
|
368
|
+
.flatMap((relation) => relation.ownerProp.props),
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
const rowId = getRowId(node);
|
|
372
|
+
rowTables.set(rowId, modelClass.getTableName());
|
|
373
|
+
rowRelations.set(rowId, (rowRelations.get(rowId) || new Set()).add(relation));
|
|
374
|
+
|
|
375
|
+
if (!node.toBeUnrelatedOrDeleted) {
|
|
376
|
+
for (const prop of Object.keys(obj)) {
|
|
377
|
+
if (obj[prop] !== undefined && !isInternalProp(prop) && !skippedProps.includes(prop)) {
|
|
378
|
+
setNodeValue(node, prop, obj[prop], getPath(node, prop));
|
|
230
379
|
}
|
|
231
|
-
|
|
232
|
-
parentNode = parentNode.parentNode;
|
|
233
380
|
}
|
|
381
|
+
}
|
|
234
382
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function removeNodesFromGraph(nodesToRemove, graph) {
|
|
252
|
-
const edgesToRemove = new Set();
|
|
253
|
-
|
|
254
|
-
for (const node of nodesToRemove) {
|
|
255
|
-
for (const edge of node.edges) {
|
|
256
|
-
const otherNode = edge.getOtherNode(node);
|
|
257
|
-
|
|
258
|
-
if (!nodesToRemove.has(otherNode)) {
|
|
259
|
-
otherNode.removeEdge(edge);
|
|
260
|
-
edgesToRemove.add(edge);
|
|
383
|
+
// A pair of models has one join row, which gets the extras of all places
|
|
384
|
+
// of the pair:
|
|
385
|
+
if (relation?.keySide === 'join' && !node.toBeUnrelatedOrDeleted) {
|
|
386
|
+
for (const { aliasProp } of relation.joinTableExtras) {
|
|
387
|
+
if (obj[aliasProp] !== undefined) {
|
|
388
|
+
const path = getPath(node, aliasProp);
|
|
389
|
+
setValue(
|
|
390
|
+
getJoinRowId(node),
|
|
391
|
+
`join row of ${relation.name}`,
|
|
392
|
+
aliasProp,
|
|
393
|
+
obj[aliasProp],
|
|
394
|
+
path,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
261
397
|
}
|
|
262
398
|
}
|
|
263
399
|
}
|
|
264
400
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
401
|
+
// The value of the key prop `i` of `prop` that `node` gives across a
|
|
402
|
+
// relation, from wherever the graph gives the row, or one that stands for it:
|
|
403
|
+
const getKey = (node, prop, i) =>
|
|
404
|
+
prop.getProp(node.obj, i) ??
|
|
405
|
+
rows.get(getRowId(node))?.get(prop.props[i])?.value ?? {
|
|
406
|
+
row: getRowId(node),
|
|
407
|
+
prop: prop.props[i],
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// The keys that the relations give the rows, the rows that the graph puts
|
|
411
|
+
// in the slots of HasOneRelations, and whether the rows are deleted:
|
|
412
|
+
const slots = [];
|
|
413
|
+
for (const node of nodes) {
|
|
414
|
+
const { relation, parent } = node;
|
|
415
|
+
const path = getPath(node);
|
|
416
|
+
const isUnrelated = node.toBeUnrelated;
|
|
417
|
+
const isDeleted = node.toBeDeleted && !isUnrelated;
|
|
418
|
+
|
|
419
|
+
if (relation?.keySide === 'related') {
|
|
420
|
+
if (!isDeleted) {
|
|
421
|
+
relation.relatedProp.forEach((i) => {
|
|
422
|
+
const key = isUnrelated ? null : getKey(parent, relation.ownerProp, i);
|
|
423
|
+
setNodeValue(node, relation.relatedProp.props[i], key, path);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
270
426
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (graphData.graphOptions.isInsertOnly()) {
|
|
282
|
-
checkForHasManyRelateErrors(graphData);
|
|
283
|
-
warnOnInsertBelowRelate(graphData);
|
|
427
|
+
if (isSlot(relation)) {
|
|
428
|
+
const row = node.toBeUnrelatedOrDeleted ? null : getRowId(node);
|
|
429
|
+
slots.push({ owner: parent, relation, row, path });
|
|
430
|
+
}
|
|
431
|
+
} else if (relation && isOwnerKey(relation)) {
|
|
432
|
+
relation.ownerProp.forEach((i) => {
|
|
433
|
+
const key = node.toBeUnrelatedOrDeleted ? null : getKey(node, relation.relatedProp, i);
|
|
434
|
+
setNodeValue(parent, relation.ownerProp.props[i], key, path);
|
|
435
|
+
});
|
|
284
436
|
}
|
|
285
437
|
|
|
286
|
-
|
|
287
|
-
};
|
|
288
|
-
}
|
|
438
|
+
setNodeValue(node, '#delete', isDeleted, path);
|
|
289
439
|
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// nested `upsertGraph` calls against their own current graphs.
|
|
295
|
-
function checkForJoinTableRelatedTableErrors(builder) {
|
|
296
|
-
return (graphData) => {
|
|
297
|
-
const { graphOptions, currentGraph, graph } = graphData;
|
|
298
|
-
|
|
299
|
-
for (const node of graph.nodes) {
|
|
300
|
-
const relation = node.parentEdge && node.parentEdge.relation;
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
relation &&
|
|
304
|
-
relation.isObjectionManyToManyRelation &&
|
|
305
|
-
!currentGraph.nodeForNode(node) &&
|
|
306
|
-
graphOptions.shouldInsertOrRelate(node, graphData)
|
|
307
|
-
) {
|
|
308
|
-
relation.assertCanModifyJoinTable(
|
|
309
|
-
builder,
|
|
310
|
-
graphOptions.shouldRelate(node, graphData) ? 'relate' : 'insert',
|
|
311
|
-
);
|
|
312
|
-
}
|
|
440
|
+
// All places of a pair of models keep or remove its join row:
|
|
441
|
+
if (relation?.keySide === 'join') {
|
|
442
|
+
const name = `join row of ${relation.name}`;
|
|
443
|
+
setValue(getJoinRowId(node), name, '#unrelate', node.toBeUnrelatedOrDeleted, path);
|
|
313
444
|
}
|
|
314
445
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
.filter((node) => graphOptions.shouldRelate(node, graphData))
|
|
326
|
-
.flatMap((node) => node.descendantRelationNodes),
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
for (const node of graph.nodes) {
|
|
330
|
-
if (
|
|
331
|
-
node.obj.$hasId() &&
|
|
332
|
-
!nestedNodes.has(node) &&
|
|
333
|
-
!node.toBeUnrelatedOrDeleted &&
|
|
334
|
-
!graphOptions.shouldInsertIgnoreDisable(node, graphData) &&
|
|
335
|
-
!graphOptions.shouldRelateIgnoreDisable(node, graphData) &&
|
|
336
|
-
!currentGraph.nodeForNode(node)
|
|
337
|
-
) {
|
|
338
|
-
if (!node.parentNode) {
|
|
339
|
-
throw node.modelClass.createNotFoundError(builder.context(), {
|
|
340
|
-
message: `root model (id=${node.obj.$id()}) does not exist. If you want to insert it with an id, use the insertMissing option`,
|
|
341
|
-
dataPath: node.dataPath,
|
|
342
|
-
});
|
|
343
|
-
} else {
|
|
344
|
-
throw node.modelClass.createNotFoundError(builder.context(), {
|
|
345
|
-
message: `model (id=${node.obj.$id()}) is not a child of model (id=${node.parentNode.obj.$id()}). If you want to relate it, use the relate option. If you want to insert it with an id, use the insertMissing option`,
|
|
346
|
-
dataPath: node.dataPath,
|
|
446
|
+
// `null` clears the key or the slot:
|
|
447
|
+
for (const nullRelation of getRelations(node).filter(({ name }) => node.obj[name] === null)) {
|
|
448
|
+
if (isOwnerKey(nullRelation)) {
|
|
449
|
+
nullRelation.ownerProp.forEach((i) => {
|
|
450
|
+
setNodeValue(
|
|
451
|
+
node,
|
|
452
|
+
nullRelation.ownerProp.props[i],
|
|
453
|
+
null,
|
|
454
|
+
getPath(node, nullRelation.name),
|
|
455
|
+
);
|
|
347
456
|
});
|
|
457
|
+
} else if (isSlot(nullRelation)) {
|
|
458
|
+
const path = getPath(node, nullRelation.name);
|
|
459
|
+
slots.push({ owner: node, relation: nullRelation, row: null, path });
|
|
348
460
|
}
|
|
349
461
|
}
|
|
350
462
|
}
|
|
351
|
-
}
|
|
352
463
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
464
|
+
// The rows of a table by the values that the graph gives the props of a key,
|
|
465
|
+
// with the values that stand for keys as they are and the others as strings,
|
|
466
|
+
// so that e.g. `1` and `'1'` are the same key:
|
|
467
|
+
const toIndexKey = (values) =>
|
|
468
|
+
JSON.stringify(values.map((value) => (isObject(value) ? value : String(value))));
|
|
469
|
+
const indexes = new Map();
|
|
470
|
+
const getRowsByKey = (table, props) => {
|
|
471
|
+
const indexName = `${table}:${props.join()}`;
|
|
472
|
+
let index = indexes.get(indexName);
|
|
473
|
+
|
|
474
|
+
if (!index) {
|
|
475
|
+
index = new Map();
|
|
476
|
+
indexes.set(indexName, index);
|
|
477
|
+
|
|
478
|
+
for (const [rowId, values] of rows) {
|
|
479
|
+
const keyValues = props.map((prop) => values.get(prop)?.value);
|
|
480
|
+
|
|
481
|
+
if (rowTables.get(rowId) === table && keyValues.every((value) => value != null)) {
|
|
482
|
+
const indexKey = toIndexKey(keyValues);
|
|
483
|
+
const rowIds = index.get(indexKey);
|
|
484
|
+
|
|
485
|
+
if (rowIds) {
|
|
486
|
+
rowIds.push(rowId);
|
|
487
|
+
} else {
|
|
488
|
+
index.set(indexKey, [rowId]);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
356
493
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const expression = RelationExpression.fromModelGraph(rootsObjs);
|
|
494
|
+
return index;
|
|
495
|
+
};
|
|
360
496
|
|
|
361
|
-
|
|
497
|
+
// Any other row that the graph gives the key of the owner of a slot is a
|
|
498
|
+
// second row in it, unless the relation filters its rows and the graph
|
|
499
|
+
// doesn't reach the row through it:
|
|
500
|
+
for (const { owner, relation, row, path } of slots) {
|
|
501
|
+
const { relatedModelClass, relatedProp, ownerProp } = relation;
|
|
502
|
+
const ownerKey = ownerProp.props.map((_, i) => getKey(owner, ownerProp, i));
|
|
503
|
+
const rowsByKey = getRowsByKey(relatedModelClass.getTableName(), relatedProp.props);
|
|
504
|
+
const otherRowId = (rowsByKey.get(toIndexKey(ownerKey)) || []).find(
|
|
505
|
+
(rowId) => rowId !== row && (!relation.modify || rowRelations.get(rowId).has(relation)),
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
if (otherRowId) {
|
|
509
|
+
const other = rows.get(otherRowId).get(relatedProp.props[0]);
|
|
362
510
|
throw builder.modelClass().createValidationError({
|
|
363
|
-
type: ValidationErrorType.
|
|
364
|
-
message:
|
|
511
|
+
type: ValidationErrorType.InvalidGraph,
|
|
512
|
+
message: `the graph puts two ${relatedModelClass.name} rows in ${relation.name} of one ${owner.modelClass.name}, at ${formatPath(path)} and at ${formatPath(other.path)}`,
|
|
365
513
|
});
|
|
366
514
|
}
|
|
367
515
|
}
|
|
368
516
|
}
|
|
369
517
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
message:
|
|
381
|
-
'#ref references are not allowed in a graph by default. see the allowRefs insert/upsert graph option',
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// `#unrelate` and `#delete` markers come from the graph data, which may be
|
|
387
|
-
// untrusted input. Like `#ref`, they have to be allowed explicitly (#2410).
|
|
388
|
-
// An insert has nothing to unrelate or delete, so `insertGraph` rejects them.
|
|
389
|
-
function checkForUnallowedMarkerErrors(graphData, builder) {
|
|
390
|
-
const { graph, graphOptions } = graphData;
|
|
391
|
-
const isInsertOnly = graphOptions.isInsertOnly();
|
|
392
|
-
|
|
393
|
-
if (graphOptions.allowMarkers() && !isInsertOnly) {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
518
|
+
// References need the ids of the models they point to, which the database
|
|
519
|
+
// only gives the models that the graph writes in the same pass or an earlier
|
|
520
|
+
// one. The ids of new models that the graph doesn't write, e.g. as `noInsert`
|
|
521
|
+
// skips them, are never known. Everything else reads the models as the graph
|
|
522
|
+
// gives them. The references of models that aren't written are never resolved.
|
|
523
|
+
function checkReferences(builder, plan, writtenNodes) {
|
|
524
|
+
const isReadable = (node, refPath) =>
|
|
525
|
+
writtenNodes.has(node) ||
|
|
526
|
+
node.obj.$hasId() ||
|
|
527
|
+
(!!refPath && !node.modelClass.getIdPropertyArray().includes(refPath[0]));
|
|
396
528
|
|
|
397
|
-
const node
|
|
398
|
-
|
|
399
|
-
if (node) {
|
|
400
|
-
const { graphUnrelateProp, graphDeleteProp } = node.modelClass;
|
|
401
|
-
|
|
402
|
-
throw builder.modelClass().createValidationError({
|
|
403
|
-
type: ValidationErrorType.InvalidGraph,
|
|
404
|
-
message: isInsertOnly
|
|
405
|
-
? `${graphUnrelateProp} and ${graphDeleteProp} can only be used in upsertGraph, not in insertGraph`
|
|
406
|
-
: `${graphUnrelateProp} and ${graphDeleteProp} markers are not allowed in a graph by default. see the allowMarkers upsertGraph option`,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function checkForRootUnrelateOrDeleteErrors(graphData, builder) {
|
|
412
|
-
for (const node of graphData.graph.nodes) {
|
|
413
|
-
if (!node.parentEdge && node.toBeUnrelatedOrDeleted) {
|
|
414
|
-
const { graphUnrelateProp, graphDeleteProp } = node.modelClass;
|
|
529
|
+
for (const node of plan.activeNodes.filter((node) => writtenNodes.has(node))) {
|
|
530
|
+
const { referencedNode, propertyReferences } = node;
|
|
415
531
|
|
|
532
|
+
if (
|
|
533
|
+
(referencedNode && !isReadable(referencedNode)) ||
|
|
534
|
+
propertyReferences.some(({ referencedNode, refPath }) => !isReadable(referencedNode, refPath))
|
|
535
|
+
) {
|
|
416
536
|
throw builder.modelClass().createValidationError({
|
|
417
537
|
type: ValidationErrorType.InvalidGraph,
|
|
418
|
-
message:
|
|
538
|
+
message: `the reference at ${formatPath(node.dataPathKey)} points to a model that the graph doesn't write before it`,
|
|
419
539
|
});
|
|
420
540
|
}
|
|
421
541
|
}
|
|
422
542
|
}
|
|
423
543
|
|
|
424
|
-
|
|
425
|
-
|
|
544
|
+
// Inserting or relating a model of a many-to-many relation inserts a join row,
|
|
545
|
+
// which would be a new related row if the join table is also the related table
|
|
546
|
+
// (#133).
|
|
547
|
+
function checkJoinTables(builder, plan) {
|
|
548
|
+
for (const node of plan.activeNodes) {
|
|
549
|
+
const action = plan.actions.get(node);
|
|
426
550
|
|
|
427
|
-
for (const node of graph.nodes) {
|
|
428
551
|
if (
|
|
429
|
-
|
|
430
|
-
node.
|
|
552
|
+
node.relation?.keySide === 'join' &&
|
|
553
|
+
!node.current &&
|
|
554
|
+
(action === 'insert' || action === 'relate')
|
|
431
555
|
) {
|
|
432
|
-
|
|
433
|
-
'You cannot relate HasManyRelation or HasOneRelation using insertGraph, because those require update operations. Consider using upsertGraph instead.',
|
|
434
|
-
);
|
|
556
|
+
node.relation.assertCanModifyJoinTable(builder, action);
|
|
435
557
|
}
|
|
436
558
|
}
|
|
437
559
|
}
|
|
438
560
|
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
463
|
-
function executeOperations(builder) {
|
|
464
|
-
return (graphData) => {
|
|
465
|
-
const operations = graphData.graphOptions.isInsertOnly()
|
|
466
|
-
? [GraphInsert]
|
|
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
|
-
);
|
|
480
|
-
|
|
481
|
-
return operationActions.reduce(
|
|
482
|
-
(promise, actions) => promise.then(() => executeActions(builder, actions)),
|
|
483
|
-
Promise.resolve(),
|
|
484
|
-
);
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function executeActions(builder, actions) {
|
|
489
|
-
return actions.reduce(
|
|
490
|
-
(promise, action) => promise.then(() => action.run(builder)),
|
|
491
|
-
Promise.resolve(),
|
|
492
|
-
);
|
|
561
|
+
// Name the place of a model in error messages, by its data path.
|
|
562
|
+
function formatPath(dataPathKey) {
|
|
563
|
+
const path = dataPathKey.replace(/^\./, '');
|
|
564
|
+
return path ? `'${path}'` : 'the root';
|
|
493
565
|
}
|
|
494
566
|
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
};
|
|
567
|
+
function isOnConflictOperation(operation) {
|
|
568
|
+
return ['onConflict', 'ignore', 'merge'].includes(operation.name);
|
|
505
569
|
}
|
|
506
|
-
|
|
507
|
-
function isOnConflictOperation(op) {
|
|
508
|
-
return op.name === 'onConflict' || op.name === 'ignore' || op.name === 'merge';
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function returnResult(objects, isArray) {
|
|
512
|
-
return () => (isArray ? objects : objects[0]);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
module.exports = {
|
|
516
|
-
GraphUpsert,
|
|
517
|
-
};
|