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.
Files changed (250) hide show
  1. package/lib/dbErrors/LICENSE +21 -0
  2. package/lib/dbErrors/errorCodes/mssql.js +19 -0
  3. package/lib/dbErrors/errorCodes/mysql.js +805 -0
  4. package/lib/dbErrors/errorCodes/postgres.js +241 -0
  5. package/lib/dbErrors/errors/CheckViolationError.js +10 -0
  6. package/lib/dbErrors/errors/ConstraintViolationError.js +3 -0
  7. package/lib/dbErrors/errors/DBError.js +9 -0
  8. package/lib/dbErrors/errors/DataError.js +3 -0
  9. package/lib/dbErrors/errors/ForeignKeyViolationError.js +11 -0
  10. package/lib/dbErrors/errors/NotNullViolationError.js +12 -0
  11. package/lib/dbErrors/errors/UniqueViolationError.js +12 -0
  12. package/lib/dbErrors/index.js +59 -0
  13. package/lib/dbErrors/parsers/mssql/DBError/CheckViolationError/parser.js +28 -0
  14. package/lib/dbErrors/parsers/mssql/DBError/DataError/parser.js +25 -0
  15. package/lib/dbErrors/parsers/mssql/DBError/ForeignKeyViolationError/parser.js +39 -0
  16. package/lib/dbErrors/parsers/mssql/DBError/NotNullViolationError/parser.js +30 -0
  17. package/lib/dbErrors/parsers/mssql/DBError/UniqueViolationError/parser.js +53 -0
  18. package/lib/dbErrors/parsers/mssql/DBError/parser.js +51 -0
  19. package/lib/dbErrors/parsers/mssql/DBError/util.js +16 -0
  20. package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/ForeignKeyViolationError/parser.js +43 -0
  21. package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/NotNullViolationError/parser.js +29 -0
  22. package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/UniqueViolationError/parser.js +27 -0
  23. package/lib/dbErrors/parsers/mysql/DBError/ConstraintViolationError/parser.js +23 -0
  24. package/lib/dbErrors/parsers/mysql/DBError/DataError/parser.js +24 -0
  25. package/lib/dbErrors/parsers/mysql/DBError/parser.js +28 -0
  26. package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/CheckViolationError/parser.js +18 -0
  27. package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/ForeignKeyViolationError/parser.js +18 -0
  28. package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/NotNullViolationError/parser.js +18 -0
  29. package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/UniqueViolationError/parser.js +40 -0
  30. package/lib/dbErrors/parsers/postgres/DBError/ConstraintViolationError/parser.js +25 -0
  31. package/lib/dbErrors/parsers/postgres/DBError/DataError/parser.js +16 -0
  32. package/lib/dbErrors/parsers/postgres/DBError/parser.js +27 -0
  33. package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/CheckViolationError/parser.js +20 -0
  34. package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/ForeignKeyViolationError/parser.js +20 -0
  35. package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/NotNullViolationError/parser.js +27 -0
  36. package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/UniqueViolationError/parser.js +28 -0
  37. package/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/parser.js +24 -0
  38. package/lib/dbErrors/parsers/sqlite/DBError/parser.js +23 -0
  39. package/lib/dbErrors/utils/sqlState.js +3 -0
  40. package/lib/initialize.js +1 -7
  41. package/lib/model/AjvValidator.js +51 -117
  42. package/lib/model/Model.js +84 -59
  43. package/lib/model/ModifierNotFoundError.js +1 -7
  44. package/lib/model/NotFoundError.js +1 -7
  45. package/lib/model/RelationDoesNotExistError.js +1 -7
  46. package/lib/model/ValidationError.js +3 -10
  47. package/lib/model/Validator.js +1 -7
  48. package/lib/model/getModel.js +7 -6
  49. package/lib/model/inheritModel.js +1 -7
  50. package/lib/model/modelBindKnex.js +4 -10
  51. package/lib/model/modelClone.js +5 -11
  52. package/lib/model/modelColPropMap.js +3 -10
  53. package/lib/model/modelId.js +3 -10
  54. package/lib/model/modelJsonAttributes.js +35 -16
  55. package/lib/model/modelParseRelations.js +2 -8
  56. package/lib/model/modelQueryProps.js +11 -23
  57. package/lib/model/modelSet.js +89 -41
  58. package/lib/model/modelTableMetadata.js +4 -11
  59. package/lib/model/modelToJson.js +6 -13
  60. package/lib/model/modelUtils.js +14 -13
  61. package/lib/model/modelValidate.js +3 -9
  62. package/lib/model/modelValues.js +5 -14
  63. package/lib/model/modelVisitor.js +1 -7
  64. package/lib/objection.js +33 -71
  65. package/lib/queryBuilder/FunctionBuilder.js +4 -11
  66. package/lib/queryBuilder/InternalOptions.js +4 -9
  67. package/lib/queryBuilder/JoinBuilder.js +5 -8
  68. package/lib/queryBuilder/QueryBuilder.js +126 -95
  69. package/lib/queryBuilder/QueryBuilderBase.js +41 -123
  70. package/lib/queryBuilder/QueryBuilderContext.js +2 -8
  71. package/lib/queryBuilder/QueryBuilderContextBase.js +2 -8
  72. package/lib/queryBuilder/QueryBuilderOperationSupport.js +7 -21
  73. package/lib/queryBuilder/QueryBuilderUserContext.js +1 -7
  74. package/lib/queryBuilder/RawBuilder.js +5 -13
  75. package/lib/queryBuilder/ReferenceBuilder.js +20 -48
  76. package/lib/queryBuilder/RelationExpression.js +18 -16
  77. package/lib/queryBuilder/StaticHookArguments.js +74 -16
  78. package/lib/queryBuilder/ValueBuilder.js +11 -13
  79. package/lib/queryBuilder/getJoinBuilder.js +10 -0
  80. package/lib/queryBuilder/graph/GraphLoader.js +314 -0
  81. package/lib/queryBuilder/graph/GraphNode.js +241 -0
  82. package/lib/queryBuilder/graph/GraphOptions.js +55 -248
  83. package/lib/queryBuilder/graph/GraphPlan.js +431 -0
  84. package/lib/queryBuilder/graph/GraphSchedule.js +513 -0
  85. package/lib/queryBuilder/graph/GraphUpsert.js +519 -412
  86. package/lib/queryBuilder/graph/GraphWriter.js +508 -0
  87. package/lib/queryBuilder/graph/graphKeys.js +929 -0
  88. package/lib/queryBuilder/join/JoinResultColumn.js +1 -7
  89. package/lib/queryBuilder/join/JoinResultParser.js +6 -11
  90. package/lib/queryBuilder/join/RelationJoiner.js +66 -84
  91. package/lib/queryBuilder/join/TableNode.js +2 -8
  92. package/lib/queryBuilder/join/TableTree.js +3 -9
  93. package/lib/queryBuilder/join/utils.js +5 -15
  94. package/lib/queryBuilder/jsonDialects/JsonDialect.js +51 -0
  95. package/lib/queryBuilder/jsonDialects/MySqlJsonDialect.js +81 -0
  96. package/lib/queryBuilder/jsonDialects/PostgresJsonDialect.js +45 -0
  97. package/lib/queryBuilder/jsonDialects/SqliteJsonDialect.js +67 -0
  98. package/lib/queryBuilder/jsonDialects/index.js +44 -0
  99. package/lib/queryBuilder/operations/DelegateOperation.js +2 -8
  100. package/lib/queryBuilder/operations/DeleteOperation.js +10 -39
  101. package/lib/queryBuilder/operations/FindByIdOperation.js +7 -12
  102. package/lib/queryBuilder/operations/FindByIdsOperation.js +4 -8
  103. package/lib/queryBuilder/operations/FindOperation.js +14 -34
  104. package/lib/queryBuilder/operations/FirstOperation.js +6 -10
  105. package/lib/queryBuilder/operations/FromOperation.js +3 -9
  106. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +13 -12
  107. package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +4 -10
  108. package/lib/queryBuilder/operations/InsertGraphOperation.js +7 -18
  109. package/lib/queryBuilder/operations/InsertOperation.js +118 -45
  110. package/lib/queryBuilder/operations/InstanceDeleteOperation.js +5 -12
  111. package/lib/queryBuilder/operations/InstanceFindOperation.js +4 -11
  112. package/lib/queryBuilder/operations/InstanceInsertOperation.js +2 -8
  113. package/lib/queryBuilder/operations/InstanceUpdateOperation.js +7 -14
  114. package/lib/queryBuilder/operations/JoinRelatedOperation.js +5 -11
  115. package/lib/queryBuilder/operations/KnexOperation.js +2 -8
  116. package/lib/queryBuilder/operations/LimitPerParentOperation.js +7 -69
  117. package/lib/queryBuilder/operations/MergeOperation.js +5 -11
  118. package/lib/queryBuilder/operations/NoneOperation.js +2 -8
  119. package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +20 -49
  120. package/lib/queryBuilder/operations/OnBuildKnexOperation.js +2 -8
  121. package/lib/queryBuilder/operations/OnBuildOperation.js +2 -8
  122. package/lib/queryBuilder/operations/OnErrorOperation.js +2 -8
  123. package/lib/queryBuilder/operations/QueryBuilderOperation.js +1 -7
  124. package/lib/queryBuilder/operations/RangeOperation.js +11 -26
  125. package/lib/queryBuilder/operations/RelateOperation.js +2 -8
  126. package/lib/queryBuilder/operations/ReturningOperation.js +3 -9
  127. package/lib/queryBuilder/operations/RunAfterOperation.js +2 -12
  128. package/lib/queryBuilder/operations/RunBeforeOperation.js +2 -8
  129. package/lib/queryBuilder/operations/UnrelateOperation.js +2 -8
  130. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +14 -17
  131. package/lib/queryBuilder/operations/UpdateOperation.js +32 -132
  132. package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +8 -11
  133. package/lib/queryBuilder/operations/UpsertGraphOperation.js +6 -12
  134. package/lib/queryBuilder/operations/WhereCompositeOperation.js +3 -9
  135. package/lib/queryBuilder/operations/WhereGroupOperation.js +2 -8
  136. package/lib/queryBuilder/operations/eager/EagerOperation.js +3 -9
  137. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +72 -10
  138. package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +2 -8
  139. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +23 -44
  140. package/lib/queryBuilder/operations/fieldExpressions.js +122 -0
  141. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +20 -0
  142. package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +44 -0
  143. package/lib/queryBuilder/operations/jsonApi/WhereJsonTypeOperation.js +14 -0
  144. package/lib/queryBuilder/operations/select/SelectOperation.js +11 -21
  145. package/lib/queryBuilder/operations/select/Selection.js +19 -17
  146. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +4 -10
  147. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +4 -10
  148. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +4 -10
  149. package/lib/queryBuilder/operations/windowOrder.js +56 -0
  150. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +3 -7
  151. package/lib/queryBuilder/parsers/relationExpressionParser.js +42 -43
  152. package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +39 -36
  153. package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +2 -8
  154. package/lib/queryBuilder/transformations/QueryTransformation.js +1 -7
  155. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +3 -9
  156. package/lib/queryBuilder/transformations/index.js +3 -11
  157. package/lib/queryBuilder/whereGroups.js +9 -24
  158. package/lib/queryBuilder/withoutOrder.js +4 -11
  159. package/lib/relations/Relation.js +47 -28
  160. package/lib/relations/RelationDeleteOperation.js +4 -14
  161. package/lib/relations/RelationFindOperation.js +119 -90
  162. package/lib/relations/RelationInsertOperation.js +2 -8
  163. package/lib/relations/RelationOwner.js +5 -11
  164. package/lib/relations/RelationProperty.js +4 -10
  165. package/lib/relations/RelationUpdateOperation.js +4 -14
  166. package/lib/relations/RelationWriteResultOperation.js +15 -0
  167. package/lib/relations/belongsToMany/BelongsToManyDeleteOperation.js +23 -0
  168. package/lib/relations/belongsToMany/BelongsToManyFindOperation.js +35 -0
  169. package/lib/relations/belongsToMany/BelongsToManyInsertOperation.js +28 -0
  170. package/lib/relations/belongsToMany/BelongsToManyRelateOperation.js +16 -0
  171. package/lib/relations/belongsToMany/BelongsToManyRelation.js +276 -0
  172. package/lib/relations/belongsToMany/BelongsToManyUnrelateOperation.js +11 -0
  173. package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +2 -8
  174. package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +6 -11
  175. package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +3 -9
  176. package/lib/relations/belongsToOne/BelongsToOneRelation.js +8 -19
  177. package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +53 -27
  178. package/lib/relations/hasMany/HasManyInsertOperation.js +5 -10
  179. package/lib/relations/hasMany/HasManyRelateOperation.js +5 -11
  180. package/lib/relations/hasMany/HasManyRelation.js +9 -11
  181. package/lib/relations/hasMany/HasManyUnrelateOperation.js +3 -9
  182. package/lib/relations/hasOne/HasOneRelation.js +2 -8
  183. package/lib/relations/hasOneThrough/HasOneThroughRelation.js +2 -8
  184. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +10 -15
  185. package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +2 -8
  186. package/lib/relations/manyToMany/ManyToManyRelation.js +82 -50
  187. package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +2 -8
  188. package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +5 -9
  189. package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +2 -8
  190. package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +3 -9
  191. package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +20 -59
  192. package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +6 -11
  193. package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +4 -10
  194. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +3 -9
  195. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +5 -9
  196. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +3 -9
  197. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +3 -9
  198. package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +3 -9
  199. package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +5 -9
  200. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +64 -72
  201. package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +3 -9
  202. package/lib/transaction.js +3 -9
  203. package/lib/utils/assert.js +3 -11
  204. package/lib/utils/buildUtils.js +2 -8
  205. package/lib/utils/classUtils.js +1 -7
  206. package/lib/utils/createModifier.js +2 -8
  207. package/lib/utils/identifierMapping.js +23 -27
  208. package/lib/utils/internalPropUtils.js +1 -7
  209. package/lib/utils/knexUtils.js +84 -40
  210. package/lib/utils/mixin.js +3 -10
  211. package/lib/utils/normalizeIds.js +2 -8
  212. package/lib/utils/objectUtils.js +41 -60
  213. package/lib/utils/parseFieldExpression.js +16 -16
  214. package/lib/utils/promiseUtils/after.js +2 -8
  215. package/lib/utils/promiseUtils/afterReturn.js +2 -8
  216. package/lib/utils/promiseUtils/index.js +7 -16
  217. package/lib/utils/promiseUtils/isPromise.js +2 -8
  218. package/lib/utils/promiseUtils/map.js +2 -8
  219. package/lib/utils/promiseUtils/mapAfterAllReturn.js +2 -8
  220. package/lib/utils/promiseUtils/try.js +2 -8
  221. package/lib/utils/resolveModel.js +10 -17
  222. package/lib/utils/tmpColumnUtils.js +2 -9
  223. package/lib/utils/warnings.js +2 -14
  224. package/package.json +25 -17
  225. package/typings/objection/index.d.ts +3217 -2179
  226. package/lib/.eslintrc.json +0 -8
  227. package/lib/model/graph/ModelGraph.js +0 -80
  228. package/lib/model/graph/ModelGraphBuilder.js +0 -241
  229. package/lib/model/graph/ModelGraphEdge.js +0 -52
  230. package/lib/model/graph/ModelGraphNode.js +0 -238
  231. package/lib/queryBuilder/graph/GraphAction.js +0 -69
  232. package/lib/queryBuilder/graph/GraphData.js +0 -53
  233. package/lib/queryBuilder/graph/GraphFetcher.js +0 -197
  234. package/lib/queryBuilder/graph/GraphNodeDbExistence.js +0 -184
  235. package/lib/queryBuilder/graph/GraphOperation.js +0 -27
  236. package/lib/queryBuilder/graph/delete/GraphDelete.js +0 -73
  237. package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +0 -88
  238. package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +0 -58
  239. package/lib/queryBuilder/graph/insert/GraphInsert.js +0 -296
  240. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +0 -233
  241. package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +0 -92
  242. package/lib/queryBuilder/graph/patch/GraphPatch.js +0 -20
  243. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +0 -289
  244. package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +0 -31
  245. package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +0 -126
  246. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +0 -30
  247. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +0 -37
  248. package/lib/queryBuilder/operations/jsonApi/WhereJsonPostgresOperation.js +0 -62
  249. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +0 -192
  250. 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
+ }