@abedmurrar/objection 0.0.0-stage → 4.0.0-beta.0

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 (190) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +105 -2
  3. package/lib/index.mjs +6 -0
  4. package/lib/initialize.js +11 -0
  5. package/lib/model/AjvValidator.js +278 -0
  6. package/lib/model/Model.js +772 -0
  7. package/lib/model/ModifierNotFoundError.js +10 -0
  8. package/lib/model/NotFoundError.js +19 -0
  9. package/lib/model/RelationDoesNotExistError.js +11 -0
  10. package/lib/model/ValidationError.js +46 -0
  11. package/lib/model/Validator.js +20 -0
  12. package/lib/model/getModel.js +7 -0
  13. package/lib/model/graph/ModelGraph.js +60 -0
  14. package/lib/model/graph/ModelGraphBuilder.js +188 -0
  15. package/lib/model/graph/ModelGraphEdge.js +40 -0
  16. package/lib/model/graph/ModelGraphNode.js +201 -0
  17. package/lib/model/inheritModel.js +19 -0
  18. package/lib/model/modelBindKnex.js +69 -0
  19. package/lib/model/modelClone.js +102 -0
  20. package/lib/model/modelColPropMap.js +24 -0
  21. package/lib/model/modelConcurrency.js +24 -0
  22. package/lib/model/modelId.js +57 -0
  23. package/lib/model/modelJsonAttributes.js +72 -0
  24. package/lib/model/modelParseRelations.js +83 -0
  25. package/lib/model/modelQueryProps.js +116 -0
  26. package/lib/model/modelSet.js +116 -0
  27. package/lib/model/modelTableMetadata.js +71 -0
  28. package/lib/model/modelToJson.js +157 -0
  29. package/lib/model/modelUtils.js +40 -0
  30. package/lib/model/modelValidate.js +40 -0
  31. package/lib/model/modelValues.js +113 -0
  32. package/lib/model/modelVisitor.js +34 -0
  33. package/lib/objection.js +89 -0
  34. package/lib/queryBuilder/FunctionBuilder.js +291 -0
  35. package/lib/queryBuilder/InternalOptions.js +24 -0
  36. package/lib/queryBuilder/JoinBuilder.js +124 -0
  37. package/lib/queryBuilder/QueryBuilder.js +1136 -0
  38. package/lib/queryBuilder/QueryBuilderBase.js +666 -0
  39. package/lib/queryBuilder/QueryBuilderContext.js +20 -0
  40. package/lib/queryBuilder/QueryBuilderContextBase.js +26 -0
  41. package/lib/queryBuilder/QueryBuilderOperationSupport.js +484 -0
  42. package/lib/queryBuilder/QueryBuilderUserContext.js +25 -0
  43. package/lib/queryBuilder/RawBuilder.js +78 -0
  44. package/lib/queryBuilder/ReferenceBuilder.js +246 -0
  45. package/lib/queryBuilder/RelationExpression.js +373 -0
  46. package/lib/queryBuilder/StaticHookArguments.js +122 -0
  47. package/lib/queryBuilder/ValueBuilder.js +115 -0
  48. package/lib/queryBuilder/expressions.js +121 -0
  49. package/lib/queryBuilder/functions/mysql.js +762 -0
  50. package/lib/queryBuilder/graph/GraphAction.js +54 -0
  51. package/lib/queryBuilder/graph/GraphData.js +12 -0
  52. package/lib/queryBuilder/graph/GraphFetcher.js +155 -0
  53. package/lib/queryBuilder/graph/GraphNodeDbExistence.js +146 -0
  54. package/lib/queryBuilder/graph/GraphOperation.js +21 -0
  55. package/lib/queryBuilder/graph/GraphOptions.js +165 -0
  56. package/lib/queryBuilder/graph/GraphUpsert.js +231 -0
  57. package/lib/queryBuilder/graph/delete/GraphDelete.js +15 -0
  58. package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +56 -0
  59. package/lib/queryBuilder/graph/insert/GraphInsert.js +116 -0
  60. package/lib/queryBuilder/graph/insert/GraphInsertAction.js +183 -0
  61. package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +70 -0
  62. package/lib/queryBuilder/graph/patch/GraphPatch.js +15 -0
  63. package/lib/queryBuilder/graph/patch/GraphPatchAction.js +205 -0
  64. package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +26 -0
  65. package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +96 -0
  66. package/lib/queryBuilder/join/JoinResultColumn.js +19 -0
  67. package/lib/queryBuilder/join/JoinResultParser.js +106 -0
  68. package/lib/queryBuilder/join/RelationJoiner.js +258 -0
  69. package/lib/queryBuilder/join/TableNode.js +136 -0
  70. package/lib/queryBuilder/join/TableTree.js +69 -0
  71. package/lib/queryBuilder/join/utils.js +19 -0
  72. package/lib/queryBuilder/operations/DelegateOperation.js +101 -0
  73. package/lib/queryBuilder/operations/DeleteOperation.js +47 -0
  74. package/lib/queryBuilder/operations/FindByIdOperation.js +29 -0
  75. package/lib/queryBuilder/operations/FindByIdsOperation.js +23 -0
  76. package/lib/queryBuilder/operations/FindOperation.js +147 -0
  77. package/lib/queryBuilder/operations/FirstOperation.js +22 -0
  78. package/lib/queryBuilder/operations/FromOperation.js +91 -0
  79. package/lib/queryBuilder/operations/InsertAndFetchOperation.js +38 -0
  80. package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +35 -0
  81. package/lib/queryBuilder/operations/InsertGraphOperation.js +87 -0
  82. package/lib/queryBuilder/operations/InsertOperation.js +173 -0
  83. package/lib/queryBuilder/operations/InstanceDeleteOperation.js +37 -0
  84. package/lib/queryBuilder/operations/InstanceFindOperation.js +17 -0
  85. package/lib/queryBuilder/operations/InstanceInsertOperation.js +20 -0
  86. package/lib/queryBuilder/operations/InstanceUpdateOperation.js +45 -0
  87. package/lib/queryBuilder/operations/JoinRelatedOperation.js +83 -0
  88. package/lib/queryBuilder/operations/KnexOperation.js +11 -0
  89. package/lib/queryBuilder/operations/MergeOperation.js +43 -0
  90. package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +173 -0
  91. package/lib/queryBuilder/operations/OnBuildKnexOperation.js +23 -0
  92. package/lib/queryBuilder/operations/OnBuildOperation.js +23 -0
  93. package/lib/queryBuilder/operations/OnErrorOperation.js +23 -0
  94. package/lib/queryBuilder/operations/QueryBuilderOperation.js +258 -0
  95. package/lib/queryBuilder/operations/RangeOperation.js +37 -0
  96. package/lib/queryBuilder/operations/RelateOperation.js +20 -0
  97. package/lib/queryBuilder/operations/ReturningOperation.js +24 -0
  98. package/lib/queryBuilder/operations/RunAfterOperation.js +23 -0
  99. package/lib/queryBuilder/operations/RunBeforeOperation.js +23 -0
  100. package/lib/queryBuilder/operations/UnrelateOperation.js +18 -0
  101. package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +63 -0
  102. package/lib/queryBuilder/operations/UpdateOperation.js +130 -0
  103. package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +35 -0
  104. package/lib/queryBuilder/operations/UpsertGraphOperation.js +47 -0
  105. package/lib/queryBuilder/operations/WhereCompositeOperation.js +48 -0
  106. package/lib/queryBuilder/operations/WhereInOperation.js +46 -0
  107. package/lib/queryBuilder/operations/eager/EagerOperation.js +46 -0
  108. package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +41 -0
  109. package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +10 -0
  110. package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +227 -0
  111. package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +29 -0
  112. package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +37 -0
  113. package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +30 -0
  114. package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +33 -0
  115. package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +155 -0
  116. package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +135 -0
  117. package/lib/queryBuilder/operations/select/SelectOperation.js +50 -0
  118. package/lib/queryBuilder/operations/select/Selection.js +112 -0
  119. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +84 -0
  120. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +90 -0
  121. package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +95 -0
  122. package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +787 -0
  123. package/lib/queryBuilder/parsers/relationExpressionParser.js +930 -0
  124. package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +17 -0
  125. package/lib/queryBuilder/transformations/QueryTransformation.js +9 -0
  126. package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +57 -0
  127. package/lib/queryBuilder/transformations/index.js +9 -0
  128. package/lib/queryBuilder/whereScopes.js +199 -0
  129. package/lib/relations/Relation.js +317 -0
  130. package/lib/relations/RelationDeleteOperation.js +23 -0
  131. package/lib/relations/RelationFindOperation.js +116 -0
  132. package/lib/relations/RelationInsertOperation.js +25 -0
  133. package/lib/relations/RelationOwner.js +199 -0
  134. package/lib/relations/RelationProperty.js +207 -0
  135. package/lib/relations/RelationUpdateOperation.js +23 -0
  136. package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +18 -0
  137. package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +33 -0
  138. package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +39 -0
  139. package/lib/relations/belongsToOne/BelongsToOneRelation.js +45 -0
  140. package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +46 -0
  141. package/lib/relations/hasMany/HasManyInsertOperation.js +45 -0
  142. package/lib/relations/hasMany/HasManyRelateOperation.js +48 -0
  143. package/lib/relations/hasMany/HasManyRelation.js +41 -0
  144. package/lib/relations/hasMany/HasManyUnrelateOperation.js +26 -0
  145. package/lib/relations/hasOne/HasOneRelation.js +10 -0
  146. package/lib/relations/hasOneThrough/HasOneThroughRelation.js +10 -0
  147. package/lib/relations/manyToMany/ManyToManyModifyMixin.js +82 -0
  148. package/lib/relations/manyToMany/ManyToManyRelation.js +405 -0
  149. package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +29 -0
  150. package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +8 -0
  151. package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +20 -0
  152. package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +8 -0
  153. package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +82 -0
  154. package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +40 -0
  155. package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +64 -0
  156. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +11 -0
  157. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +27 -0
  158. package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +11 -0
  159. package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +8 -0
  160. package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +65 -0
  161. package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +8 -0
  162. package/lib/transaction.js +66 -0
  163. package/lib/utils/assert.js +19 -0
  164. package/lib/utils/buildUtils.js +19 -0
  165. package/lib/utils/classUtils.js +9 -0
  166. package/lib/utils/cloneDeep.js +365 -0
  167. package/lib/utils/createModifier.js +37 -0
  168. package/lib/utils/deprecate.js +11 -0
  169. package/lib/utils/identifierMapping.js +188 -0
  170. package/lib/utils/internalPropUtils.js +7 -0
  171. package/lib/utils/knexUtils.js +81 -0
  172. package/lib/utils/mixin.js +18 -0
  173. package/lib/utils/mysqlJsonPath.js +70 -0
  174. package/lib/utils/normalizeIds.js +127 -0
  175. package/lib/utils/objectUtils.js +314 -0
  176. package/lib/utils/parseFieldExpression.js +73 -0
  177. package/lib/utils/postgresJsonPath.js +96 -0
  178. package/lib/utils/promiseUtils/after.js +14 -0
  179. package/lib/utils/promiseUtils/afterReturn.js +14 -0
  180. package/lib/utils/promiseUtils/index.js +15 -0
  181. package/lib/utils/promiseUtils/isPromise.js +8 -0
  182. package/lib/utils/promiseUtils/map.js +96 -0
  183. package/lib/utils/promiseUtils/mapAfterAllReturn.js +23 -0
  184. package/lib/utils/promiseUtils/try.js +19 -0
  185. package/lib/utils/resolveModel.js +104 -0
  186. package/lib/utils/tmpColumnUtils.js +7 -0
  187. package/package.json +113 -4
  188. package/typings/objection/index.d.mts +362 -0
  189. package/typings/objection/index.d.ts +4131 -0
  190. package/typings/objection/relation-expression.d.ts +237 -0
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+ const { isPostgres } = require('../../utils/knexUtils');
3
+ const POSTGRES_MAX_INSERT_BATCH_SIZE = 100;
4
+ const MAX_CONCURRENCY = 100;
5
+ class GraphAction {
6
+ constructor(graphData) {
7
+ this.graphData = graphData;
8
+ }
9
+ static get ReturningAllSelector() {
10
+ return (op) => {
11
+ // Only select `returning('*')` operation.
12
+ return op.name === 'returning' && op.args.includes('*');
13
+ };
14
+ }
15
+ static getConcurrency(builder, nodes) {
16
+ return nodes.reduce((minConcurrency, node) => {
17
+ return Math.min(minConcurrency, node.modelClass.getConcurrency(builder.unsafeKnex()));
18
+ }, MAX_CONCURRENCY);
19
+ }
20
+ get graph() {
21
+ return this.graphData.graph;
22
+ }
23
+ get currentGraph() {
24
+ return this.graphData.currentGraph;
25
+ }
26
+ get graphOptions() {
27
+ return this.graphData.graphOptions;
28
+ }
29
+ run(builder) {
30
+ return null;
31
+ }
32
+ _getConcurrency(builder, nodes) {
33
+ return GraphAction.getConcurrency(builder, nodes);
34
+ }
35
+ _getBatchSize(builder) {
36
+ return isPostgres(builder.unsafeKnex()) ? POSTGRES_MAX_INSERT_BATCH_SIZE : 1;
37
+ }
38
+ _resolveReferences(node) {
39
+ if (node.isReference) {
40
+ this._resolveReference(node);
41
+ }
42
+ }
43
+ _resolveReference(node) {
44
+ const refNode = node.referencedNode;
45
+ for (const prop of Object.keys(refNode.obj)) {
46
+ if (!Object.hasOwn(node.obj, prop)) {
47
+ node.obj[prop] = refNode.obj[prop];
48
+ }
49
+ }
50
+ }
51
+ }
52
+ module.exports = {
53
+ GraphAction,
54
+ };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+ class GraphData {
3
+ constructor({ graph, currentGraph, graphOptions, nodeDbExistence }) {
4
+ this.graph = graph;
5
+ this.currentGraph = currentGraph;
6
+ this.graphOptions = graphOptions;
7
+ this.nodeDbExistence = nodeDbExistence;
8
+ }
9
+ }
10
+ module.exports = {
11
+ GraphData,
12
+ };
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+ const { asArray, groupBy } = require('../../utils/objectUtils');
3
+ const { ModelGraph } = require('../../model/graph/ModelGraph');
4
+ const { FetchStrategy } = require('./GraphOptions');
5
+ const { RelationExpression } = require('../RelationExpression');
6
+ class GraphFetcher {
7
+ /**
8
+ * Given a graph and options, fetches the current state of that graph
9
+ * from the database and returns it as a ModelGraph instance.
10
+ */
11
+ static async fetchCurrentGraph({ builder, graph, graphOptions }) {
12
+ const { rootObjects } = graph;
13
+ const rootIds = getRootIds(rootObjects);
14
+ const modelClass = builder.modelClass();
15
+ if (rootIds.length === 0) {
16
+ return Promise.resolve(ModelGraph.create(modelClass, []));
17
+ }
18
+ const eagerExpr = RelationExpression.fromModelGraph(rootObjects);
19
+ const models = await modelClass
20
+ .query()
21
+ .childQueryOf(builder, childQueryOptions())
22
+ .modify(propagateMethodCallsFromQuery(builder))
23
+ .modify(buildFetchQuerySelects(graph, graphOptions, eagerExpr))
24
+ .findByIds(rootIds)
25
+ .withGraphFetched(eagerExpr)
26
+ .internalOptions(fetchQueryInternalOptions());
27
+ return ModelGraph.create(modelClass, models);
28
+ }
29
+ }
30
+ function getRootIds(rootObjects) {
31
+ return asArray(rootObjects)
32
+ .filter((it) => it.$hasId())
33
+ .map((root) => root.$id());
34
+ }
35
+ function propagateMethodCallsFromQuery(builder) {
36
+ return (fetchBuilder) => {
37
+ // Propagate some method calls from the root query.
38
+ for (const method of ['forUpdate', 'forShare', 'forNoKeyUpdate', 'forKeyShare']) {
39
+ if (builder.has(method)) {
40
+ fetchBuilder[method]();
41
+ }
42
+ }
43
+ };
44
+ }
45
+ function buildFetchQuerySelects(graph, graphOptions, eagerExpr) {
46
+ return (builder) => {
47
+ const nodesByRelationPath = groupNodesByRelationPath(graph, eagerExpr);
48
+ for (const [relationPath, nodes] of nodesByRelationPath.entries()) {
49
+ const selectModifier = createFetchSelectModifier(nodes, graphOptions);
50
+ if (!relationPath) {
51
+ builder.modify(selectModifier);
52
+ }
53
+ else {
54
+ builder.modifyGraph(relationPath, selectModifier);
55
+ }
56
+ }
57
+ };
58
+ }
59
+ function groupNodesByRelationPath(graph, eagerExpr) {
60
+ const nodesByRelationPath = groupBy(graph.nodes, (node) => node.relationPathKey);
61
+ // Not all relation paths have nodes. Relations with nulls or empty arrays
62
+ // don't have nodes, but will still need to be fetched. Add these to the
63
+ // map as empty arrays.
64
+ forEachPath(eagerExpr.node, (relationPath) => {
65
+ if (!nodesByRelationPath.has(relationPath)) {
66
+ nodesByRelationPath.set(relationPath, []);
67
+ }
68
+ });
69
+ return nodesByRelationPath;
70
+ }
71
+ function createFetchSelectModifier(nodes, graphOptions) {
72
+ if (graphOptions.isFetchStrategy(FetchStrategy.OnlyIdentifiers)) {
73
+ return createIdentifierSelector();
74
+ }
75
+ else if (graphOptions.isFetchStrategy(FetchStrategy.OnlyNeeded)) {
76
+ return createInputColumnSelector(nodes);
77
+ }
78
+ else {
79
+ return () => { };
80
+ }
81
+ }
82
+ // Returns a function that only selects the id column.
83
+ function createIdentifierSelector() {
84
+ return (builder) => {
85
+ builder.select(builder.fullIdColumn());
86
+ };
87
+ }
88
+ // Returns a function that only selects the columns that exist in the input.
89
+ function createInputColumnSelector(nodes) {
90
+ return (builder) => {
91
+ const selects = new Map();
92
+ for (const node of nodes) {
93
+ const databaseJson = node.obj.$toDatabaseJson(builder);
94
+ for (const column of Object.keys(databaseJson)) {
95
+ if (!shouldSelectColumn(column, selects, node)) {
96
+ continue;
97
+ }
98
+ const selection = createManyToManyExtraSelectionIfNeeded(builder, column, node) ||
99
+ createSelection(builder, column, node);
100
+ selects.set(column, selection);
101
+ }
102
+ }
103
+ const selectArr = Array.from(selects.values());
104
+ const idColumns = asArray(builder.fullIdColumn());
105
+ for (const idColumn of idColumns) {
106
+ if (!selectArr.includes(idColumn)) {
107
+ // Always select the identifers.
108
+ selectArr.push(idColumn);
109
+ }
110
+ }
111
+ builder.select(selectArr);
112
+ };
113
+ }
114
+ function shouldSelectColumn(column, selects, node) {
115
+ const modelClass = node.modelClass;
116
+ return (!selects.has(column) &&
117
+ column !== modelClass.propertyNameToColumnName(modelClass.dbRefProp) &&
118
+ column !== modelClass.propertyNameToColumnName(modelClass.uidRefProp) &&
119
+ column !== modelClass.propertyNameToColumnName(modelClass.uidProp));
120
+ }
121
+ function createManyToManyExtraSelectionIfNeeded(builder, column, node) {
122
+ if (node.parentEdge && node.parentEdge.relation.isObjectionManyToManyRelation) {
123
+ const relation = node.parentEdge.relation;
124
+ const extra = relation.joinTableExtras.find((extra) => extra.aliasCol === column);
125
+ if (extra) {
126
+ return `${builder.tableRefFor(relation.joinModelClass)}.${extra.joinTableCol} as ${extra.aliasCol}`;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ function createSelection(builder, column, node) {
132
+ return `${builder.tableRefFor(node.modelClass)}.${column}`;
133
+ }
134
+ function childQueryOptions() {
135
+ return {
136
+ fork: true,
137
+ isInternalQuery: true,
138
+ };
139
+ }
140
+ function fetchQueryInternalOptions() {
141
+ return {
142
+ keepImplicitJoinProps: true,
143
+ };
144
+ }
145
+ function forEachPath(eagerExprNode, cb, path = []) {
146
+ for (const relation of eagerExprNode.$childNames) {
147
+ path.push(relation);
148
+ cb(path.join('.'));
149
+ forEachPath(eagerExprNode[relation], cb, path);
150
+ path.pop();
151
+ }
152
+ }
153
+ module.exports = {
154
+ GraphFetcher,
155
+ };
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+ const { GraphData } = require('./GraphData');
3
+ const { GraphAction } = require('./GraphAction');
4
+ const promiseUtils = require('../../utils/promiseUtils');
5
+ /**
6
+ * This weird little class is responsible for checking (and maintaining information)
7
+ * whether certain nodes exist in the database.
8
+ *
9
+ * Note that this information is only calculated for nodes for which `insertMissing`
10
+ * option is true.
11
+ */
12
+ class GraphNodeDbExistence {
13
+ static createEveryNodeExistsExistence() {
14
+ return new GraphNodeDbExistence(new Map());
15
+ }
16
+ /**
17
+ * Goes through the graphs and identifies nodes that may not exist in the db
18
+ * (nodes that have an id, but are not found in `currentGraph`) and
19
+ * creates an instance of GraphNodeDbExistence that can be used to
20
+ * synchronously check if a node exist in the database.
21
+ */
22
+ static async create({ builder, graph, graphOptions, currentGraph }) {
23
+ if (graphOptions.isInsertOnly()) {
24
+ // With insertGraph, we never want to do anything but inserts.
25
+ return GraphNodeDbExistence.createEveryNodeExistsExistence();
26
+ }
27
+ const graphData = new GraphData({
28
+ graph,
29
+ graphOptions,
30
+ currentGraph,
31
+ // We don't yet have an instance of GraphNodeDbExistence since we are
32
+ // creating one. We can (and should) safely use an instance that
33
+ // assumes that all nodes exist in the db for the purposes of this
34
+ // method.
35
+ nodeDbExistence: GraphNodeDbExistence.createEveryNodeExistsExistence(),
36
+ });
37
+ const { mayNotExist, mayNotExistNodes } = createMayNotExistMap(graphData);
38
+ if (mayNotExist.size == 0) {
39
+ // Early exit if we found no items for which we should check their
40
+ // existence in the db.
41
+ return GraphNodeDbExistence.createEveryNodeExistsExistence();
42
+ }
43
+ const dontExist = await createDontExistMap({ builder, mayNotExist, mayNotExistNodes });
44
+ return new GraphNodeDbExistence(dontExist);
45
+ }
46
+ constructor(dontExist) {
47
+ this.dontExist = dontExist;
48
+ }
49
+ doesNodeExistInDb(node) {
50
+ const idMap = this.dontExist.get(node.modelClass);
51
+ if (!idMap) {
52
+ return true;
53
+ }
54
+ return !idMap.has(node.obj.$idKey());
55
+ }
56
+ }
57
+ function createMayNotExistMap(graphData) {
58
+ const { graph, currentGraph, graphOptions } = graphData;
59
+ const mayNotExist = new Map();
60
+ const mayNotExistNodes = [];
61
+ for (const node of graph.nodes) {
62
+ if (
63
+ // As an optimization, only consider nodes for which `insertMissing` is true.
64
+ // We only need the information for those nodes.
65
+ graphOptions.shouldInsertMissing(node) &&
66
+ // Only consider nodes that will be related. We don't consider nodes that
67
+ // would get inserted with an id. Those will still result in a unique
68
+ // constraint error.
69
+ graphOptions.shouldRelate(node, graphData) &&
70
+ // Relate nodes may not have an id if they are `#ref` nodes. Only consider
71
+ // nodes that have an id so that we can check the existence.
72
+ node.hasId &&
73
+ // We can ignore nodes if they are found anywhere in the graph. `shouldRelate`
74
+ // only checks if the node is found in the same relation.
75
+ !hasNodeById(currentGraph, node)) {
76
+ if (!mayNotExist.has(node.modelClass)) {
77
+ mayNotExist.set(node.modelClass, new Map());
78
+ }
79
+ mayNotExist.get(node.modelClass).set(node.obj.$idKey(), node.obj.$id());
80
+ mayNotExistNodes.push(node);
81
+ }
82
+ }
83
+ return {
84
+ mayNotExist,
85
+ mayNotExistNodes,
86
+ };
87
+ }
88
+ function hasNodeById(currentGraph, nodeToFind) {
89
+ const { modelClass } = nodeToFind;
90
+ const tableToFind = modelClass.getTableName();
91
+ const idProps = modelClass.getIdPropertyArray();
92
+ return currentGraph.nodes.some((node) => {
93
+ return (node.modelClass.getTableName() === tableToFind &&
94
+ idProps.every((idProp) => node.obj[idProp] === nodeToFind.obj[idProp]));
95
+ });
96
+ }
97
+ async function createDontExistMap({ builder, mayNotExist, mayNotExistNodes }) {
98
+ const dontExist = cloneExistenceMap(mayNotExist);
99
+ const existenceCheckQueries = createExistenceCheckQueries({ builder, mayNotExist });
100
+ const results = await promiseUtils.map(existenceCheckQueries, (builder) => builder.execute(), {
101
+ concurrency: GraphAction.getConcurrency(builder, mayNotExistNodes),
102
+ });
103
+ // Remove all items from the mayNotExist map that we have just proven to exist
104
+ // by executing the existenceCheckQueries.
105
+ for (const modelResult of results) {
106
+ for (const item of modelResult) {
107
+ const modelClass = item.constructor;
108
+ const idMap = dontExist.get(modelClass);
109
+ // Exist, remove from the map.
110
+ idMap.delete(item.$idKey());
111
+ }
112
+ }
113
+ // Now only items that don't exist in the db are left in this map.
114
+ return dontExist;
115
+ }
116
+ function createExistenceCheckQueries({ builder, mayNotExist }) {
117
+ const builders = [];
118
+ for (const [modelClass, idMap] of mayNotExist.entries()) {
119
+ const ids = Array.from(idMap.values());
120
+ // Create one query per model class (table) to fetch the identifiers
121
+ // of the nodes that may not exist. These queries should be super fast
122
+ // since they come straight from the index.
123
+ builders.push(modelClass
124
+ .query()
125
+ .childQueryOf(builder, childQueryOptions())
126
+ .findByIds(ids)
127
+ .select(modelClass.getIdColumnArray()));
128
+ }
129
+ return builders;
130
+ }
131
+ function childQueryOptions() {
132
+ return {
133
+ fork: true,
134
+ isInternalQuery: true,
135
+ };
136
+ }
137
+ function cloneExistenceMap(exixtenseMap) {
138
+ const clone = new Map(exixtenseMap);
139
+ for (const modelClass of clone.keys()) {
140
+ clone.set(modelClass, new Map(clone.get(modelClass)));
141
+ }
142
+ return clone;
143
+ }
144
+ module.exports = {
145
+ GraphNodeDbExistence,
146
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+ class GraphOperation {
3
+ constructor(graphData) {
4
+ this.graphData = graphData;
5
+ }
6
+ get graph() {
7
+ return this.graphData.graph;
8
+ }
9
+ get currentGraph() {
10
+ return this.graphData.currentGraph;
11
+ }
12
+ get graphOptions() {
13
+ return this.graphData.graphOptions;
14
+ }
15
+ createActions() {
16
+ return [];
17
+ }
18
+ }
19
+ module.exports = {
20
+ GraphOperation,
21
+ };
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+ const NO_RELATE = 'noRelate';
3
+ const NO_UNRELATE = 'noUnrelate';
4
+ const NO_INSERT = 'noInsert';
5
+ const NO_UPDATE = 'noUpdate';
6
+ const NO_DELETE = 'noDelete';
7
+ const UPDATE = 'update';
8
+ const RELATE = 'relate';
9
+ const UNRELATE = 'unrelate';
10
+ const INSERT_MISSING = 'insertMissing';
11
+ const FETCH_STRATEGY = 'fetchStrategy';
12
+ const ALLOW_REFS = 'allowRefs';
13
+ const FetchStrategy = {
14
+ OnlyIdentifiers: 'OnlyIdentifiers',
15
+ Everything: 'Everything',
16
+ OnlyNeeded: 'OnlyNeeded',
17
+ };
18
+ class GraphOptions {
19
+ constructor(options) {
20
+ if (options instanceof GraphOptions) {
21
+ this.options = options.options;
22
+ }
23
+ else {
24
+ this.options = options;
25
+ }
26
+ }
27
+ isFetchStrategy(strategy) {
28
+ if (!FetchStrategy[strategy]) {
29
+ throw new Error(`unknown strategy "${strategy}"`);
30
+ }
31
+ if (!this.options[FETCH_STRATEGY]) {
32
+ return strategy === FetchStrategy.OnlyNeeded;
33
+ }
34
+ else {
35
+ return this.options[FETCH_STRATEGY] === strategy;
36
+ }
37
+ }
38
+ isInsertOnly() {
39
+ // NO_RELATE is not in the list, since the `insert only` mode does
40
+ // relate things that can be related using inserts.
41
+ // TODO: Use a special key for this.
42
+ return [NO_DELETE, NO_UPDATE, NO_UNRELATE, INSERT_MISSING].every((opt) => {
43
+ return this.options[opt] === true;
44
+ });
45
+ }
46
+ // Like `shouldRelate` but ignores settings that explicitly disable relate operations.
47
+ shouldRelateIgnoreDisable(node, graphData) {
48
+ if (node.isReference || node.isDbReference) {
49
+ return true;
50
+ }
51
+ return (this._hasOption(node, RELATE) &&
52
+ !getCurrentNode(node, graphData) &&
53
+ !!node.parentEdge &&
54
+ !!node.parentEdge.relation &&
55
+ node.parentEdge.relation.hasRelateProp(node.obj) &&
56
+ graphData.nodeDbExistence.doesNodeExistInDb(node));
57
+ }
58
+ shouldRelate(node, graphData) {
59
+ return !this._hasOption(node, NO_RELATE) && this.shouldRelateIgnoreDisable(node, graphData);
60
+ }
61
+ // Like `shouldInsert` but ignores settings that explicitly disable insert operations.
62
+ shouldInsertIgnoreDisable(node, graphData) {
63
+ return (!getCurrentNode(node, graphData) &&
64
+ !this.shouldRelateIgnoreDisable(node, graphData) &&
65
+ (!node.hasId || this.shouldInsertMissing(node)));
66
+ }
67
+ shouldInsert(node, graphData) {
68
+ return !this._hasOption(node, NO_INSERT) && this.shouldInsertIgnoreDisable(node, graphData);
69
+ }
70
+ shouldInsertMissing(node) {
71
+ return this._hasOption(node, INSERT_MISSING);
72
+ }
73
+ // Like `shouldPatch() || shouldUpdate()` but ignores settings that explicitly disable
74
+ // update or patch operations.
75
+ shouldPatchOrUpdateIgnoreDisable(node, graphData) {
76
+ if (this.shouldRelate(node, graphData)) {
77
+ // We should update all nodes that are going to be related. Note that
78
+ // we don't actually update anything unless there is something to update
79
+ // so this is just a preliminary test.
80
+ return true;
81
+ }
82
+ return !!getCurrentNode(node, graphData);
83
+ }
84
+ shouldPatch(node, graphData) {
85
+ return (this.shouldPatchOrUpdateIgnoreDisable(node, graphData) &&
86
+ !this._hasOption(node, NO_UPDATE) &&
87
+ !this._hasOption(node, UPDATE));
88
+ }
89
+ shouldUpdate(node, graphData) {
90
+ return (this.shouldPatchOrUpdateIgnoreDisable(node, graphData) &&
91
+ !this._hasOption(node, NO_UPDATE) &&
92
+ this._hasOption(node, UPDATE));
93
+ }
94
+ // Like `shouldUnrelate` but ignores settings that explicitly disable unrelate operations.
95
+ shouldUnrelateIgnoreDisable(currentNode) {
96
+ return this._hasOption(currentNode, UNRELATE);
97
+ }
98
+ shouldUnrelate(currentNode, graphData) {
99
+ return (!getNode(currentNode, graphData.graph) &&
100
+ !this._hasOption(currentNode, NO_UNRELATE) &&
101
+ this.shouldUnrelateIgnoreDisable(currentNode));
102
+ }
103
+ shouldDelete(currentNode, graphData) {
104
+ return (!getNode(currentNode, graphData.graph) &&
105
+ !this._hasOption(currentNode, NO_DELETE) &&
106
+ !this.shouldUnrelateIgnoreDisable(currentNode));
107
+ }
108
+ shouldInsertOrRelate(node, graphData) {
109
+ return this.shouldInsert(node, graphData) || this.shouldRelate(node, graphData);
110
+ }
111
+ shouldDeleteOrUnrelate(currentNode, graphData) {
112
+ return this.shouldDelete(currentNode, graphData) || this.shouldUnrelate(currentNode, graphData);
113
+ }
114
+ allowRefs() {
115
+ return !!this.options[ALLOW_REFS];
116
+ }
117
+ rebasedOptions(newRoot) {
118
+ const newOpt = {};
119
+ const newRootRelationPath = newRoot.relationPathKey;
120
+ for (const name of Object.keys(this.options)) {
121
+ const value = this.options[name];
122
+ if (Array.isArray(value)) {
123
+ newOpt[name] = value
124
+ .filter((it) => it.startsWith(newRootRelationPath))
125
+ .map((it) => it.slice(newRootRelationPath.length + 1))
126
+ .filter((it) => !!it);
127
+ }
128
+ else {
129
+ newOpt[name] = value;
130
+ }
131
+ }
132
+ return new GraphOptions(newOpt);
133
+ }
134
+ _hasOption(node, optionName) {
135
+ const option = this.options[optionName];
136
+ if (Array.isArray(option)) {
137
+ return option.indexOf(node.relationPathKey) !== -1;
138
+ }
139
+ else if (typeof option === 'boolean') {
140
+ return option;
141
+ }
142
+ else if (option === undefined) {
143
+ return false;
144
+ }
145
+ else {
146
+ throw new Error(`expected ${optionName} option value "${option}" to be an instance of boolean or array of strings`);
147
+ }
148
+ }
149
+ }
150
+ function getCurrentNode(node, graphData) {
151
+ if (!graphData || !node) {
152
+ return null;
153
+ }
154
+ return graphData.currentGraph.nodeForNode(node);
155
+ }
156
+ function getNode(currentNode, graph) {
157
+ if (!graph || !currentNode) {
158
+ return null;
159
+ }
160
+ return graph.nodeForNode(currentNode);
161
+ }
162
+ module.exports = {
163
+ GraphOptions,
164
+ FetchStrategy,
165
+ };