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,431 @@
1
+ import { hasSameColumns, hasSameKey } from './graphKeys.js';
2
+
3
+ // What a graph insert or upsert does with each model of the graph and each row
4
+ // of the current graph: The rules that decide it, and the pruning of branches
5
+ // that nothing is written in.
6
+ export class GraphPlan {
7
+ constructor({ nodes, graphOptions, keptNodes = new Map(), laterNodes = new Set() }) {
8
+ this.nodes = nodes;
9
+ // The nodes that an earlier pass left to this one before writing them:
10
+ this.laterNodes = laterNodes;
11
+ this.currentNodes = [];
12
+ this.deferredNodes = new Set();
13
+ // The nodes that earlier passes of the graph decided to keep or inserted,
14
+ // by row:
15
+ this.earlierKeptNodes = keptNodes;
16
+ this.graphOptions = graphOptions;
17
+ this.actions = null;
18
+ // The decisions only change while the current graph is loaded, and are
19
+ // cached once it is, see `cacheDecisions()`:
20
+ this._intents = null;
21
+ this._nodeActions = null;
22
+ this._activeNodes = null;
23
+ this._activeCurrentNodes = null;
24
+ this._keptNodesByRowKey = null;
25
+ }
26
+
27
+ get activeNodes() {
28
+ return (this._activeNodes ??= this.nodes.filter((node) => !node.isPruned));
29
+ }
30
+
31
+ get activeCurrentNodes() {
32
+ return (this._activeCurrentNodes ??= this.currentNodes.filter(
33
+ (currentNode) => !currentNode.isPruned,
34
+ ));
35
+ }
36
+
37
+ // Leave `nodes` to a later pass of the graph.
38
+ defer(nodes) {
39
+ this.deferredNodes = new Set(nodes);
40
+ this.nodes = this.nodes.filter((node) => !this.deferredNodes.has(node));
41
+ this._activeNodes = null;
42
+ }
43
+
44
+ cacheDecisions() {
45
+ this._intents = new Map();
46
+ this._nodeActions = new Map();
47
+ }
48
+
49
+ // What the data asks to do with `node`, before the options that disable it:
50
+ // Models marked with `#unrelate` or `#delete` are removed, references and
51
+ // models with ids that the options let relate are related, models at their
52
+ // place are patched, and new models are inserted. Models with ids that are
53
+ // neither are `'missing'`.
54
+ intentFor(node) {
55
+ let intent = this._intents?.get(node);
56
+
57
+ if (!intent) {
58
+ intent = this._getIntent(node);
59
+ this._intents?.set(node, intent);
60
+ }
61
+
62
+ return intent;
63
+ }
64
+
65
+ _getIntent(node) {
66
+ const insertMissing = this.graphOptions.has(node, 'insertMissing');
67
+
68
+ return node.toBeUnrelatedOrDeleted
69
+ ? 'remove'
70
+ : node.isReference
71
+ ? 'relate'
72
+ : node.isMissing
73
+ ? insertMissing
74
+ ? 'insert'
75
+ : this.findInsertingNode(node)
76
+ ? 'relate'
77
+ : 'missing'
78
+ : node.isDbReference || this._canRelate(node)
79
+ ? 'relate'
80
+ : node.current
81
+ ? 'patch'
82
+ : !node.hasId || insertMissing
83
+ ? 'insert'
84
+ : 'missing';
85
+ }
86
+
87
+ // Return true if the model of the `#ref` model `node` writes what `node`
88
+ // would write to the row: The model is inserted, or it's written and its path
89
+ // allows any write that the path of `node` allows.
90
+ isCoveredByModel(node) {
91
+ const { actions, graphOptions } = this;
92
+ const { referencedNode } = node;
93
+ const action = actions.get(referencedNode);
94
+ const allowsWrite = (node) => graphOptions.shouldPatch(node) || graphOptions.shouldUpdate(node);
95
+ return (
96
+ action === 'insert' ||
97
+ (['relate', 'patch'].includes(action) && (allowsWrite(referencedNode) || !allowsWrite(node)))
98
+ );
99
+ }
100
+
101
+ // Return true if the graph writes the branch below `node`: below models at
102
+ // their place, inserted models, related models with fetched rows and `#ref`
103
+ // models, but not below removed or ignored ones.
104
+ writesBranchOf(node) {
105
+ const action = this.actionFor(node);
106
+
107
+ return (
108
+ !node.toBeUnrelatedOrDeleted &&
109
+ (!!node.current ||
110
+ action === 'insert' ||
111
+ (action === 'relate' && (!!node.row || node.isReference)))
112
+ );
113
+ }
114
+
115
+ // Find the model of the graph that inserts the missing row of `node` with its
116
+ // id, so that `node` relates it once it's inserted. Models that are missing
117
+ // themselves aren't inserted.
118
+ findInsertingNode(node) {
119
+ return this.nodes.find(
120
+ (other) =>
121
+ other !== node &&
122
+ other.rowKey === node.rowKey &&
123
+ !(other.isMissing && !this.graphOptions.has(other, 'insertMissing')) &&
124
+ this.actionFor(other) === 'insert',
125
+ );
126
+ }
127
+
128
+ // What the graph does with `node`: `'insert'`, `'relate'`, `'patch'` or
129
+ // `'ignore'`. Once the writes start, read `actions` instead.
130
+ actionFor(node) {
131
+ let action = this._nodeActions?.get(node);
132
+
133
+ if (!action) {
134
+ action = this._getAction(node);
135
+ this._nodeActions?.set(node, action);
136
+ }
137
+
138
+ return action;
139
+ }
140
+
141
+ _getAction(node) {
142
+ const { graphOptions } = this;
143
+
144
+ switch (this.intentFor(node)) {
145
+ case 'insert':
146
+ return graphOptions.has(node, 'noInsert') ? 'ignore' : 'insert';
147
+ case 'relate':
148
+ return !graphOptions.has(node, 'noRelate') ? 'relate' : node.current ? 'patch' : 'ignore';
149
+ case 'patch':
150
+ return 'patch';
151
+ default:
152
+ return 'ignore';
153
+ }
154
+ }
155
+
156
+ // What the graph does with the row of `currentNode`: `'delete'`,
157
+ // `'unrelate'` or `'keep'`.
158
+ actionForCurrent(currentNode) {
159
+ const { node } = currentNode;
160
+ const has = (optionName) => this.graphOptions.has(currentNode, optionName);
161
+
162
+ if (node) {
163
+ // `#unrelate` takes precedence over `#delete` if both are set.
164
+ return node.toBeUnrelated ? 'unrelate' : node.toBeDeleted ? 'delete' : 'keep';
165
+ } else if (this._hasIgnoredOtherModel(currentNode)) {
166
+ return 'keep';
167
+ } else {
168
+ const keptNodes = this.getKeptNodes(currentNode);
169
+
170
+ if (keptNodes.some((keptNode) => !this._isUnrelatedWhenKept(currentNode, keptNode))) {
171
+ return 'keep';
172
+ } else if (keptNodes.length > 0 || has('unrelate')) {
173
+ // Rows that the graph keeps in another place must not be deleted
174
+ // (#214). They're unrelated instead, which replaces the delete, so
175
+ // `noDelete` disables it too, unless the `unrelate` option asks for it.
176
+ return has('noUnrelate') || (!has('unrelate') && has('noDelete')) ? 'keep' : 'unrelate';
177
+ } else {
178
+ return has('noDelete') ? 'keep' : 'delete';
179
+ }
180
+ }
181
+ }
182
+
183
+ // Leave out the branches that require no writes. We never do anything below
184
+ // models that are removed, and we never delete recursively.
185
+ prune() {
186
+ const isInsertOnly = this.graphOptions.isInsertOnly();
187
+ const passNodes = new Set([...this.nodes, ...this.currentNodes]);
188
+ // The rows that were fetched for pruned nodes are left alone too. Nodes of
189
+ // later passes aren't pruned here:
190
+ const pruneNodes = (nodes) => {
191
+ for (const node of nodes.filter((node) => passNodes.has(node))) {
192
+ node.isPruned = true;
193
+
194
+ for (const row of node.row ? [node.row, ...node.row.descendants] : []) {
195
+ row.isPruned = true;
196
+ }
197
+ }
198
+
199
+ this._activeNodes = null;
200
+ this._activeCurrentNodes = null;
201
+ };
202
+ const pruneDescendants = (nodes) => pruneNodes(nodes.flatMap((node) => node.descendants));
203
+
204
+ // Nothing is written below models that are neither inserted nor at their
205
+ // place. `upsertGraph()` upserts the branches below related models, whose
206
+ // rows were fetched for that, and below `#ref` models of new models, but
207
+ // `insertGraph()` leaves them out (#217).
208
+ pruneDescendants(
209
+ this.activeNodes.filter(
210
+ (node) =>
211
+ !node.current &&
212
+ this.intentFor(node) !== 'insert' &&
213
+ !(
214
+ this.actionFor(node) === 'relate' &&
215
+ (node.row || (node.isReference && !this.graphOptions.isInsertOnly()))
216
+ ),
217
+ ),
218
+ );
219
+
220
+ // Nothing is written below models that `noInsert` skips, as their
221
+ // descendants would be orphans (#215), except for the branches of their
222
+ // children that the references of written models need: the ones that
223
+ // models outside of all these branches point into, and then the ones that
224
+ // the models of kept branches point into. The references of ignored models
225
+ // are never resolved.
226
+ const isSkipped = (node) =>
227
+ this.intentFor(node) === 'insert' && this.actionFor(node) === 'ignore';
228
+ const isBranch = (node) => !!node.parent && isSkipped(node.parent);
229
+ const getBranches = (node) => {
230
+ const branches = [];
231
+
232
+ for (let ancestor = node; ancestor; ancestor = ancestor.parent) {
233
+ if (isBranch(ancestor)) {
234
+ branches.push(ancestor);
235
+ }
236
+ }
237
+
238
+ return branches;
239
+ };
240
+ const getReferencedNodes = (node) =>
241
+ this.actionFor(node) === 'ignore'
242
+ ? []
243
+ : [node.referencedNode, ...node.propertyReferences.map((ref) => ref.referencedNode)].filter(
244
+ (referencedNode) => referencedNode && !referencedNode.isPruned,
245
+ );
246
+
247
+ const keptBranches = new Set();
248
+ const visitedNodes = new Set();
249
+ const getNewKeptNodes = () =>
250
+ this.activeNodes.filter(
251
+ (node) =>
252
+ !visitedNodes.has(node) && getBranches(node).every((branch) => keptBranches.has(branch)),
253
+ );
254
+
255
+ for (let nodes = getNewKeptNodes(); nodes.length > 0; nodes = getNewKeptNodes()) {
256
+ for (const node of nodes) {
257
+ visitedNodes.add(node);
258
+
259
+ for (const branch of getReferencedNodes(node).flatMap(getBranches)) {
260
+ keptBranches.add(branch);
261
+ }
262
+ }
263
+ }
264
+
265
+ pruneNodes(
266
+ this.activeNodes
267
+ .filter((node) => isBranch(node) && !keptBranches.has(node))
268
+ .flatMap((node) => [node, ...node.descendants]),
269
+ );
270
+
271
+ if (!isInsertOnly) {
272
+ // Rows that the graph removes are removed without their descendants. Don't
273
+ // remove rows in relations that the graph doesn't mention.
274
+ const removedNodes = this.activeCurrentNodes.filter((currentNode) => !currentNode.node);
275
+ pruneDescendants(removedNodes);
276
+
277
+ for (const currentNode of removedNodes) {
278
+ const parentNode = currentNode.parent?.node;
279
+
280
+ if (parentNode && parentNode.obj[currentNode.relation.name] === undefined) {
281
+ currentNode.isPruned = true;
282
+ this._activeCurrentNodes = null;
283
+ }
284
+ }
285
+
286
+ // Models marked with `#unrelate` or `#delete` are unrelated or deleted as
287
+ // a whole, in the graph and in the current graph.
288
+ const markedNodes = this.activeNodes.filter((node) => node.toBeUnrelatedOrDeleted);
289
+ pruneDescendants(markedNodes);
290
+ pruneDescendants(markedNodes.map((node) => node.current).filter(Boolean));
291
+ }
292
+
293
+ return this;
294
+ }
295
+
296
+ // Decide what to do with each node once, before the first write, since the
297
+ // writes change the models that the rules read. Only call this after
298
+ // pruning.
299
+ plan() {
300
+ const actions = new Map();
301
+
302
+ for (const node of this.activeNodes) {
303
+ actions.set(node, this.actionFor(node));
304
+ }
305
+
306
+ for (const currentNode of this.activeCurrentNodes) {
307
+ actions.set(currentNode, this.actionForCurrent(currentNode));
308
+ }
309
+
310
+ this.actions = actions;
311
+ return this;
312
+ }
313
+
314
+ _canRelate(node) {
315
+ return (
316
+ this.graphOptions.has(node, 'relate') &&
317
+ !node.current &&
318
+ !node.isRoot &&
319
+ hasRelateProp(node) &&
320
+ !node.isMissing
321
+ );
322
+ }
323
+
324
+ // An ignored model that isn't the current model of a to-one relation doesn't
325
+ // replace it, so we keep the current one instead of removing it as missing.
326
+ _hasIgnoredOtherModel(currentNode) {
327
+ const { relation } = currentNode;
328
+ const parentNode = currentNode.parent?.node;
329
+
330
+ return (
331
+ relation.isOneToOne() &&
332
+ !!parentNode &&
333
+ parentNode.activeChildren.some(
334
+ (child) => child.relation === relation && this.actionFor(child) === 'ignore',
335
+ )
336
+ );
337
+ }
338
+
339
+ // A row that the graph keeps in another place, or that an earlier pass
340
+ // inserted, is unrelated here, unless one of these places sets the same key
341
+ // column, by relating, patching or inserting it through such a relation, or
342
+ // through its own BelongsToOneRelation, e.g. `parent` for `children`: Then
343
+ // that place writes the key, which may not be nullable. A has-one row that
344
+ // its own BelongsToOneRelation or a later pass keeps is still unrelated if
345
+ // this pass fills its slot with another row, as the foreign key may be
346
+ // unique (#398).
347
+ _isUnrelatedWhenKept(currentNode, keptNode) {
348
+ const { relation } = currentNode;
349
+ const isKeptInRelation = hasSameKey(relation, keptNode.relation);
350
+ const isKeptByOwner =
351
+ !isKeptInRelation &&
352
+ keptNode.activeChildren.some(
353
+ (child) =>
354
+ child.relation.keySide === 'owner' &&
355
+ this.actionFor(child) !== 'ignore' &&
356
+ hasSameColumns(child.relation.ownerProp, relation.relatedProp),
357
+ );
358
+ return (
359
+ relation.keySide !== 'related' ||
360
+ !(isKeptInRelation || isKeptByOwner) ||
361
+ ((isKeptByOwner || this.deferredNodes.has(keptNode)) && this._hasNewRowInSlot(currentNode))
362
+ );
363
+ }
364
+
365
+ // Return true if this pass inserts or relates a row into the slot of the
366
+ // has-one relation that `currentNode` is in.
367
+ _hasNewRowInSlot(currentNode) {
368
+ return (
369
+ currentNode.relation.isOneToOne() &&
370
+ this.nodes.some(
371
+ (node) =>
372
+ node.relation === currentNode.relation &&
373
+ node.parent.existing === currentNode.parent &&
374
+ ['insert', 'relate'].includes(this.actionFor(node)),
375
+ )
376
+ );
377
+ }
378
+
379
+ // The nodes that keep the row of `node` in other places of the graph, by
380
+ // relating or patching it there, or that an earlier pass inserted.
381
+ getKeptNodes(node) {
382
+ return this.keptNodes.get(node.rowKey) || [];
383
+ }
384
+
385
+ // The nodes that keep their rows in the next passes, once this one is
386
+ // written: the ones that it decided to keep before its writes, and the ones
387
+ // that it inserts.
388
+ getNextKeptNodes() {
389
+ const keptNodes = new Map(this.keptNodes);
390
+
391
+ for (const node of this.activeNodes) {
392
+ if (this.actions.get(node) === 'insert') {
393
+ addKeptNode(keptNodes, node);
394
+ }
395
+ }
396
+
397
+ return keptNodes;
398
+ }
399
+
400
+ // The nodes that keep their rows by relating or patching them, by row, also
401
+ // the ones that earlier passes decided to keep or inserted. A row may be kept
402
+ // in several places.
403
+ get keptNodes() {
404
+ if (!this._keptNodesByRowKey) {
405
+ this._keptNodesByRowKey = new Map(this.earlierKeptNodes);
406
+
407
+ for (const node of [...this.activeNodes, ...this.deferredNodes]) {
408
+ const action = this.actionFor(node);
409
+
410
+ if (node.obj.$hasId() && (action === 'relate' || action === 'patch')) {
411
+ addKeptNode(this._keptNodesByRowKey, node);
412
+ }
413
+ }
414
+ }
415
+
416
+ return this._keptNodesByRowKey;
417
+ }
418
+ }
419
+
420
+ function addKeptNode(keptNodes, node) {
421
+ keptNodes.set(node.rowKey, [...(keptNodes.get(node.rowKey) || []), node]);
422
+ }
423
+
424
+ // For `HasMany` and `HasOne` relations, the relate prop is the id of the
425
+ // related model. Use the id state of the input graph, since the parent's
426
+ // insert copies the foreign key into the related model, which can complete
427
+ // a composite id that contains it and would turn an insert into a relate (#2544).
428
+ function hasRelateProp(node) {
429
+ const { relation } = node;
430
+ return relation.keySide === 'related' ? node.hasId : relation.hasRelateProp(node.obj);
431
+ }