objection 3.9.1 → 4.0.0-rc.2
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 +314 -0
- package/lib/queryBuilder/graph/GraphNode.js +241 -0
- package/lib/queryBuilder/graph/GraphOptions.js +55 -248
- package/lib/queryBuilder/graph/GraphPlan.js +431 -0
- package/lib/queryBuilder/graph/GraphSchedule.js +513 -0
- package/lib/queryBuilder/graph/GraphUpsert.js +519 -412
- package/lib/queryBuilder/graph/GraphWriter.js +508 -0
- package/lib/queryBuilder/graph/graphKeys.js +929 -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
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { getModel } from '../../model/getModel.js';
|
|
2
|
+
import { restoreOmitFromDatabaseJson } from '../../model/modelUtils.js';
|
|
3
|
+
import { valuesKey } from '../../model/modelValues.js';
|
|
4
|
+
import { UpdateOperation } from '../operations/UpdateOperation.js';
|
|
5
|
+
import { getMaxRows, hasBatchInsert } from '../../utils/knexUtils.js';
|
|
6
|
+
import { isInternalProp } from '../../utils/internalPropUtils.js';
|
|
7
|
+
import { chunk, difference, groupBy, isObject, nonStrictEquals } from '../../utils/objectUtils.js';
|
|
8
|
+
import { map as promiseMap } from '../../utils/promiseUtils/index.js';
|
|
9
|
+
import {
|
|
10
|
+
clearKeysOfRemovedRows,
|
|
11
|
+
fetchGeneratedKeys,
|
|
12
|
+
getChangedKeysOfRelatedRows,
|
|
13
|
+
resolveDependencies,
|
|
14
|
+
resolveFilledDependencies,
|
|
15
|
+
resolveReference,
|
|
16
|
+
setKeyArrays,
|
|
17
|
+
} from './graphKeys.js';
|
|
18
|
+
|
|
19
|
+
const MAX_INSERT_BATCH_SIZE = 100;
|
|
20
|
+
const MAX_CONCURRENCY = 100;
|
|
21
|
+
|
|
22
|
+
// Write a planned graph in the steps of its `GraphSchedule`.
|
|
23
|
+
export class GraphWriter {
|
|
24
|
+
constructor(schedule) {
|
|
25
|
+
this.schedule = schedule;
|
|
26
|
+
this.plan = schedule.plan;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async write(builder) {
|
|
30
|
+
// Fill in the keys that are known before the writes. Inserted models get
|
|
31
|
+
// theirs right before their insert:
|
|
32
|
+
for (const node of this.plan.activeNodes) {
|
|
33
|
+
if (this.plan.actions.get(node) !== 'insert') {
|
|
34
|
+
this.schedule.keys.fill(node);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const step of this.schedule.steps) {
|
|
39
|
+
await this._writeStep(builder, step);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// `#ref` models return the props of the models they refer to:
|
|
43
|
+
for (const node of this.plan.activeNodes) {
|
|
44
|
+
resolveReference(node);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_writeStep(builder, step) {
|
|
49
|
+
switch (step.kind) {
|
|
50
|
+
case 'move':
|
|
51
|
+
return this._move(builder, step.nodes);
|
|
52
|
+
case 'remove':
|
|
53
|
+
return this._remove(builder, step.nodes, step.areReferenced);
|
|
54
|
+
case 'insert':
|
|
55
|
+
return this._insert(builder, step.nodes, step.references);
|
|
56
|
+
case 'deferredKeys':
|
|
57
|
+
return this._writeDeferredKeys(builder, step.dependencies);
|
|
58
|
+
case 'joinRows':
|
|
59
|
+
return this._insertJoinRows(builder, step.nodes);
|
|
60
|
+
case 'patch':
|
|
61
|
+
return this._patch(builder, step.nodes);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_move(builder, nodes) {
|
|
66
|
+
for (const node of nodes) {
|
|
67
|
+
resolveFilledDependencies(this.schedule, node);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this._patch(builder, nodes);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_remove(builder, nodes, areReferenced) {
|
|
74
|
+
const { actions } = this.plan;
|
|
75
|
+
const queries = [];
|
|
76
|
+
|
|
77
|
+
for (const [relation, relationNodes] of groupBy(nodes, (node) => node.relation)) {
|
|
78
|
+
// Each row binds its id, and the owner keys of its owner at most.
|
|
79
|
+
const maxRows = getMaxRows(
|
|
80
|
+
builder.unsafeKnex(),
|
|
81
|
+
relation.relatedModelClass.getIdColumnArray().length + relation.ownerProp.size,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
for (const [action, actionNodes] of groupBy(relationNodes, (node) => actions.get(node))) {
|
|
85
|
+
// One query would match the ids with all owners, so join rows are
|
|
86
|
+
// removed per owner, and so are unrelated rows, which may have moved
|
|
87
|
+
// from one owner to another before (#214).
|
|
88
|
+
const groups =
|
|
89
|
+
relation.keySide === 'join' || action === 'unrelate'
|
|
90
|
+
? [...groupBy(actionNodes, (node) => node.parent).values()]
|
|
91
|
+
: [actionNodes];
|
|
92
|
+
|
|
93
|
+
for (const groupNodes of groups.flatMap((nodes) => chunk(nodes, maxRows))) {
|
|
94
|
+
// Rows that reference their owners are found through the relation.
|
|
95
|
+
// BelongsToOne rows aren't referenced anymore once their owners are
|
|
96
|
+
// patched.
|
|
97
|
+
const owners = [...new Set(groupNodes.map((node) => node.parent.obj))];
|
|
98
|
+
const query = (
|
|
99
|
+
areReferenced
|
|
100
|
+
? relation.ownerModelClass.relatedQuery(relation.name).for(owners)
|
|
101
|
+
: relation.relatedModelClass.query()
|
|
102
|
+
)
|
|
103
|
+
.childQueryOf(builder)
|
|
104
|
+
.findByIds(groupNodes.map((node) => node.obj.$id()));
|
|
105
|
+
|
|
106
|
+
queries.push(action === 'unrelate' ? query.unrelate() : query.delete());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return promiseMap(queries, (query) => query.execute(), {
|
|
112
|
+
concurrency: getConcurrency(builder, nodes),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async _insert(builder, nodes, references) {
|
|
117
|
+
const batchSize = getBatchSize(builder);
|
|
118
|
+
const batches = [...groupBy(nodes, (node) => node.modelClass).values()].flatMap((nodes) =>
|
|
119
|
+
chunk(nodes, batchSize),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
await promiseMap(batches, (batch) => this._insertBatch(builder, batch), {
|
|
123
|
+
concurrency: getConcurrency(builder, nodes),
|
|
124
|
+
});
|
|
125
|
+
await this._patch(
|
|
126
|
+
builder,
|
|
127
|
+
nodes.filter((node) => this.schedule.slotMovedNodes.has(node)),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
for (const node of references) {
|
|
131
|
+
resolveDependencies(this.schedule, node);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async _insertBatch(builder, nodes) {
|
|
136
|
+
const { actions } = this.plan;
|
|
137
|
+
|
|
138
|
+
for (const node of nodes) {
|
|
139
|
+
resolveDependencies(this.schedule, node);
|
|
140
|
+
|
|
141
|
+
// Join table extras are written to the join rows.
|
|
142
|
+
if (node.relation?.keySide === 'join') {
|
|
143
|
+
node.relation.omitExtraProps([node.obj]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (actions.get(node) === 'insert') {
|
|
147
|
+
setKeyArrays(this.plan, node);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const insertNodes = nodes.filter((node) => actions.get(node) === 'insert');
|
|
152
|
+
|
|
153
|
+
if (insertNodes.length > 0) {
|
|
154
|
+
const [{ modelClass }] = insertNodes;
|
|
155
|
+
|
|
156
|
+
for (const node of insertNodes) {
|
|
157
|
+
node.obj.$validate(null, { dataPath: node.dataPathKey });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await Promise.all(
|
|
161
|
+
insertNodes.map(
|
|
162
|
+
(node) =>
|
|
163
|
+
node.parent && node.relation.beforeInsert(node.obj, builder.context(), node.parent.obj),
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await modelClass
|
|
168
|
+
.query()
|
|
169
|
+
.insert(insertNodes.map((node) => node.obj))
|
|
170
|
+
.childQueryOf(builder)
|
|
171
|
+
.copyFrom(builder, isReturningAll)
|
|
172
|
+
.execute();
|
|
173
|
+
|
|
174
|
+
await fetchGeneratedKeys(builder, modelClass, insertNodes);
|
|
175
|
+
this.schedule.keys.setInserted(insertNodes);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_writeDeferredKeys(builder, dependencies) {
|
|
180
|
+
const owners = groupBy(dependencies, (dependency) => dependency.dependent);
|
|
181
|
+
|
|
182
|
+
return promiseMap(
|
|
183
|
+
[...owners],
|
|
184
|
+
([owner, dependencies]) => {
|
|
185
|
+
const { modelClass } = owner;
|
|
186
|
+
const idProp = modelClass.getIdRelationProperty();
|
|
187
|
+
const patch = {};
|
|
188
|
+
|
|
189
|
+
this.schedule.keys.fill(owner);
|
|
190
|
+
|
|
191
|
+
for (const { relation } of dependencies) {
|
|
192
|
+
relation.ownerProp.forEach((i) => {
|
|
193
|
+
relation.ownerProp.patch(patch, i, relation.ownerProp.getProp(owner.obj, i));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return modelClass
|
|
198
|
+
.query()
|
|
199
|
+
.childQueryOf(builder)
|
|
200
|
+
.patch(patch)
|
|
201
|
+
.whereInComposite(idProp.refs(builder), [idProp.getProps(owner.obj)]);
|
|
202
|
+
},
|
|
203
|
+
{ concurrency: getConcurrency(builder, [...owners.keys()]) },
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async _insertJoinRows(builder, nodes) {
|
|
208
|
+
const knex = builder.unsafeKnex();
|
|
209
|
+
const inserts = [];
|
|
210
|
+
|
|
211
|
+
for (const [joinModelClass, joinNodes] of groupBy(nodes, (node) =>
|
|
212
|
+
node.relation.getJoinModelClass(knex),
|
|
213
|
+
)) {
|
|
214
|
+
// Each pair of models has one join row, which gets the extras of all
|
|
215
|
+
// places of the pair:
|
|
216
|
+
const joinRows = new Map();
|
|
217
|
+
|
|
218
|
+
for (const node of joinNodes) {
|
|
219
|
+
const { relation, parent } = node;
|
|
220
|
+
resolveReference(node);
|
|
221
|
+
|
|
222
|
+
const ownerKeys = this.schedule.keys.getProps(parent, relation.ownerProp);
|
|
223
|
+
const key = `${relation.name}:${valuesKey(ownerKeys)}:${relation.relatedProp.propKey(node.obj)}`;
|
|
224
|
+
const row = relation.createJoinModel(ownerKeys, node.obj);
|
|
225
|
+
joinRows.set(key, { relation, row: { ...row, ...joinRows.get(key)?.row } });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const objs = await Promise.all(
|
|
229
|
+
[...joinRows.values()].map(async ({ relation, row }) => {
|
|
230
|
+
const obj = joinModelClass.fromJson(row);
|
|
231
|
+
await relation.joinTableBeforeInsert(obj, builder.context());
|
|
232
|
+
return obj;
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// Join rows that have their ids can be inserted in batches everywhere.
|
|
237
|
+
const batchSize = objs.every((obj) => obj.$hasId())
|
|
238
|
+
? MAX_INSERT_BATCH_SIZE
|
|
239
|
+
: getBatchSize(builder);
|
|
240
|
+
|
|
241
|
+
for (const batch of chunk(objs, batchSize)) {
|
|
242
|
+
inserts.push(() => joinModelClass.query().childQueryOf(builder).insert(batch));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return promiseMap(inserts, (insert) => insert(), {
|
|
247
|
+
concurrency: getConcurrency(builder, nodes),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async _patch(builder, nodes) {
|
|
252
|
+
// The patches leave out the props that don't change, but the models that
|
|
253
|
+
// the graph returns get them back once the step is written (#371):
|
|
254
|
+
const omittedProps = new Map(nodes.map((node) => [node, node.obj.$omitFromDatabaseJson()]));
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const patches = nodes.map((node) => this._preparePatch(builder, node)).filter(Boolean);
|
|
258
|
+
const cycles = this.schedule.findReferenceCycles(patches.map(({ node }) => node));
|
|
259
|
+
// Patches whose rows reference each other lock them in opposite order
|
|
260
|
+
// when they run at the same time, which deadlocks on MySQL. We write the
|
|
261
|
+
// patches of each cycle one after the other (#385):
|
|
262
|
+
const groups = groupBy(patches, ({ node }) => cycles.get(node) || node);
|
|
263
|
+
await promiseMap(
|
|
264
|
+
[...groups.values()],
|
|
265
|
+
async (patches) => {
|
|
266
|
+
for (const patch of patches) {
|
|
267
|
+
await this._writePatch(builder, patch);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{ concurrency: getConcurrency(builder, nodes) },
|
|
271
|
+
);
|
|
272
|
+
} finally {
|
|
273
|
+
for (const [node, props] of omittedProps) {
|
|
274
|
+
restoreOmitFromDatabaseJson(node.obj, props);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Find out what to write for `node`, validate it, and return the patch, or
|
|
280
|
+
// nothing if there is nothing to write.
|
|
281
|
+
_preparePatch(builder, node) {
|
|
282
|
+
const { referencedNode } = node;
|
|
283
|
+
|
|
284
|
+
// The props that the graph fills into the model of a `#ref` model are only
|
|
285
|
+
// final now:
|
|
286
|
+
if (referencedNode) {
|
|
287
|
+
resolveReference(node, this.schedule.getFilledProps(referencedNode));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const { graphOptions } = this.plan;
|
|
291
|
+
const shouldPatch = graphOptions.shouldPatch(node);
|
|
292
|
+
const shouldUpdate = graphOptions.shouldUpdate(node);
|
|
293
|
+
// Inserts, relates and removals of BelongsToOne rows change the keys of
|
|
294
|
+
// their owners, and relates into other relations the keys of the related
|
|
295
|
+
// rows, which are written even with `noUpdate` (#53):
|
|
296
|
+
const keyProps = [
|
|
297
|
+
...getChangedKeysOfRelatedRows(this.plan, this.schedule.keys, node),
|
|
298
|
+
...clearKeysOfRemovedRows(this.plan, node),
|
|
299
|
+
...setKeyArrays(this.plan, node),
|
|
300
|
+
...(this.plan.actions.get(node) === 'relate' && node.relation.keySide === 'related'
|
|
301
|
+
? node.relation.relatedProp.props
|
|
302
|
+
: []),
|
|
303
|
+
];
|
|
304
|
+
// Changing the id never makes sense with `upsertGraph()`. A many-to-many
|
|
305
|
+
// `#ref` model whose model writes its row only writes its join row:
|
|
306
|
+
const idProps = node.modelClass.getIdPropertyArray();
|
|
307
|
+
const isJoinRowOnly =
|
|
308
|
+
!!referencedNode && node.relation.keySide === 'join' && this.plan.isCoveredByModel(node);
|
|
309
|
+
const isWritable = (prop) =>
|
|
310
|
+
!idProps.includes(prop) &&
|
|
311
|
+
!this.schedule.keys.isOnlyRead(node, prop) &&
|
|
312
|
+
(!isJoinRowOnly || node.relation.joinTableExtras.some((extra) => extra.aliasProp === prop));
|
|
313
|
+
|
|
314
|
+
const getPropsToUpdate = (changedProps) =>
|
|
315
|
+
(shouldPatch || shouldUpdate ? changedProps : keyProps).filter(isWritable);
|
|
316
|
+
|
|
317
|
+
// Leave out the props that don't change, and return true if any do.
|
|
318
|
+
const handleUpdate = ({ changedProps, unchangedProps } = this._findChanges(node)) => {
|
|
319
|
+
const propsToUpdate = getPropsToUpdate(changedProps);
|
|
320
|
+
|
|
321
|
+
if (propsToUpdate.length > 0) {
|
|
322
|
+
node.obj.$omitFromDatabaseJson(
|
|
323
|
+
difference([...changedProps, ...unchangedProps], propsToUpdate),
|
|
324
|
+
);
|
|
325
|
+
return true;
|
|
326
|
+
} else {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Without a `beforeUpdate` or `$beforeUpdate` hook, we know right away
|
|
332
|
+
// what to update. Otherwise, the hook may still change the model, and we
|
|
333
|
+
// check again after it runs, but only if the model has props to update, as
|
|
334
|
+
// relates are no updates (#2233, #2605).
|
|
335
|
+
const changes = this._findChanges(node);
|
|
336
|
+
const propsToUpdate = getPropsToUpdate(changes.changedProps);
|
|
337
|
+
const hasBeforeUpdate = hasHook(node.obj, 'beforeUpdate');
|
|
338
|
+
|
|
339
|
+
if (propsToUpdate.length === 0) {
|
|
340
|
+
return null;
|
|
341
|
+
} else if (!hasBeforeUpdate) {
|
|
342
|
+
handleUpdate(changes);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
node.obj.$validate(null, {
|
|
346
|
+
dataPath: node.dataPathKey,
|
|
347
|
+
patch: shouldPatch || !shouldUpdate,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Rows that their owners relate by their keys can't be found through the
|
|
351
|
+
// relation until the owners are patched, so they're patched directly.
|
|
352
|
+
const { relation } = node;
|
|
353
|
+
const isPatchedThroughRelation =
|
|
354
|
+
!!node.parent &&
|
|
355
|
+
(relation.keySide === 'join' ||
|
|
356
|
+
(relation.keySide === 'owner' && this.plan.actions.get(node) !== 'relate'));
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
node,
|
|
360
|
+
shouldPatch,
|
|
361
|
+
hasBeforeUpdate,
|
|
362
|
+
handleUpdate,
|
|
363
|
+
propsToUpdate,
|
|
364
|
+
isPatchedThroughRelation,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
_writePatch(
|
|
369
|
+
builder,
|
|
370
|
+
{ node, shouldPatch, hasBeforeUpdate, handleUpdate, propsToUpdate, isPatchedThroughRelation },
|
|
371
|
+
) {
|
|
372
|
+
const query = (
|
|
373
|
+
isPatchedThroughRelation
|
|
374
|
+
? // Find the row through the keys of the owner's current row, as the
|
|
375
|
+
// graph may leave them out of the owner:
|
|
376
|
+
(node.parent.existing || node.parent).obj
|
|
377
|
+
.$relatedQuery(node.relation.name)
|
|
378
|
+
.findById(node.obj.$id())
|
|
379
|
+
: (node.current?.obj || node.obj).$query()
|
|
380
|
+
)
|
|
381
|
+
.childQueryOf(builder, { fork: true, isInternalQuery: true })
|
|
382
|
+
// The current graph was fetched through the relations, so the patched
|
|
383
|
+
// models are known to be related to their parents.
|
|
384
|
+
.internalOptions({
|
|
385
|
+
skipRelationFilter: true,
|
|
386
|
+
// Join rows are patched with the extras among the props to update. The
|
|
387
|
+
// extras of new join rows are written with them, see `_findChanges()`:
|
|
388
|
+
joinTableExtras: node.relation?.keySide === 'join' && !node.current ? [] : propsToUpdate,
|
|
389
|
+
})
|
|
390
|
+
.copyFrom(builder, isReturningAll);
|
|
391
|
+
|
|
392
|
+
if (hasBeforeUpdate) {
|
|
393
|
+
query.internalContext().runBefore.push((result, query) => {
|
|
394
|
+
if (!handleUpdate()) {
|
|
395
|
+
query.internalOptions().returnImmediatelyValue = null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return result;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return (shouldPatch ? query.patch(node.obj) : query.update(node.obj))
|
|
403
|
+
.runAfter(async (result, query) => {
|
|
404
|
+
if (isObject(result) && result.$isObjectionModel) {
|
|
405
|
+
// Handle `returning('*')`.
|
|
406
|
+
node.obj.$set(result);
|
|
407
|
+
} else if (
|
|
408
|
+
(result === 0 || result === undefined) &&
|
|
409
|
+
// Not if a hook resolved the query, e.g. with `cancelQuery()`, or
|
|
410
|
+
// there was no column to write:
|
|
411
|
+
!query.findOperation(UpdateOperation).isResolvedWithoutWrite(query)
|
|
412
|
+
) {
|
|
413
|
+
// Rows patched through relations are gone from there if not found.
|
|
414
|
+
if (isPatchedThroughRelation) {
|
|
415
|
+
throw createPatchNotFoundError(builder, node);
|
|
416
|
+
} else {
|
|
417
|
+
await throwForMissingRow(builder, node);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return result;
|
|
422
|
+
})
|
|
423
|
+
.execute();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
_findChanges(node) {
|
|
427
|
+
const { obj, relation } = node;
|
|
428
|
+
const currentObj = node.current?.obj || {};
|
|
429
|
+
const relationNames = node.modelClass.getRelationNames();
|
|
430
|
+
// The extras of a new join row are written with it:
|
|
431
|
+
const extraProps =
|
|
432
|
+
relation?.keySide === 'join' && !node.current
|
|
433
|
+
? relation.joinTableExtras.map((extra) => extra.aliasProp)
|
|
434
|
+
: [];
|
|
435
|
+
const changedProps = [];
|
|
436
|
+
const unchangedProps = [];
|
|
437
|
+
|
|
438
|
+
for (const prop of Object.keys(obj)) {
|
|
439
|
+
if (!isInternalProp(prop) && !relationNames.includes(prop)) {
|
|
440
|
+
const value = obj[prop];
|
|
441
|
+
const currentValue = currentObj[prop];
|
|
442
|
+
|
|
443
|
+
// We can't know if a prop that the current model doesn't have changes,
|
|
444
|
+
// so we assume it does (#732).
|
|
445
|
+
if (
|
|
446
|
+
!extraProps.includes(prop) &&
|
|
447
|
+
(currentValue === undefined || !nonStrictEquals(currentValue, value))
|
|
448
|
+
) {
|
|
449
|
+
changedProps.push(prop);
|
|
450
|
+
} else {
|
|
451
|
+
unchangedProps.push(prop);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Query properties may change the values too.
|
|
457
|
+
if (obj.$$queryProps) {
|
|
458
|
+
changedProps.push(...Object.keys(obj.$$queryProps));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return { changedProps, unchangedProps };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function hasHook(obj, name) {
|
|
466
|
+
const Model = getModel();
|
|
467
|
+
return obj.constructor[name] !== Model[name] || obj[`$${name}`] !== Model.prototype[`$${name}`];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Throw if the patched row of `node` doesn't exist anymore, e.g. deleted
|
|
471
|
+
// through `ON DELETE CASCADE` by an earlier write of the graph. A row that still
|
|
472
|
+
// exists counts as patched, even if a trigger skipped it, or MySQL didn't count
|
|
473
|
+
// it as changed.
|
|
474
|
+
async function throwForMissingRow(builder, node) {
|
|
475
|
+
const { modelClass } = node;
|
|
476
|
+
const row = await modelClass
|
|
477
|
+
.query()
|
|
478
|
+
.childQueryOf(builder, { fork: true, isInternalQuery: true })
|
|
479
|
+
.findById(node.obj.$id())
|
|
480
|
+
.select(modelClass.getIdColumnArray());
|
|
481
|
+
|
|
482
|
+
if (!row) {
|
|
483
|
+
throw createPatchNotFoundError(builder, node);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function createPatchNotFoundError(builder, node) {
|
|
488
|
+
return node.modelClass.createNotFoundError(builder.context(), {
|
|
489
|
+
message: `model (id=${node.obj.$id()}) was not found when updating it. An earlier write of the graph or a concurrent query may have deleted it`,
|
|
490
|
+
dataPath: node.dataPath,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function getBatchSize(builder) {
|
|
495
|
+
return hasBatchInsert(builder.unsafeKnex()) ? MAX_INSERT_BATCH_SIZE : 1;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function getConcurrency(builder, nodes) {
|
|
499
|
+
const knex = builder.unsafeKnex();
|
|
500
|
+
return nodes.reduce(
|
|
501
|
+
(concurrency, node) => Math.min(concurrency, node.modelClass.getConcurrency(knex)),
|
|
502
|
+
MAX_CONCURRENCY,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function isReturningAll(operation) {
|
|
507
|
+
return operation.name === 'returning' && operation.args.includes('*');
|
|
508
|
+
}
|