Skip to content

Commit 46d9370

Browse files
committed
rewrite upsertGraph and insertGraph to use better, shared code
1 parent 303099a commit 46d9370

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3402
-3479
lines changed

lib/model/Model.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ class Model {
258258
return propKey(this, props);
259259
}
260260

261+
$idKey() {
262+
return this.$propKey(this.constructor.getIdPropertyArray());
263+
}
264+
261265
$clone(opt) {
262266
return clone(this, !!(opt && opt.shallow), false);
263267
}
@@ -524,7 +528,7 @@ class Model {
524528
/**
525529
* NB. for v2.0, this can simply return `this.knex().fn`.
526530
* However, in order to maintain backwards comparability of a bug that didn't
527-
* have this method as a setter, the returned value needs to be callable and
531+
* have this method as a getter, the returned value needs to be callable and
528532
* return the "same" `knex#FunctionHelper` instance.
529533
* The effect is that `Model.fn.now()` and `Model.fn().now()` produce the same result.
530534
*/

lib/model/graph/ModelGraph.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const { ModelGraphBuilder } = require('./ModelGraphBuilder');
2+
const NOT_CALCULATED = {};
3+
4+
class ModelGraph {
5+
constructor(nodes, edges) {
6+
this.nodes = nodes;
7+
this.edges = edges;
8+
9+
// These are calculated lazily.
10+
this._nodesByObjects = NOT_CALCULATED;
11+
this._nodesByIdPathKeys = NOT_CALCULATED;
12+
}
13+
14+
static create(rootModelClass, roots) {
15+
const builder = ModelGraphBuilder.buildGraph(rootModelClass, roots);
16+
return new ModelGraph(builder.nodes, builder.edges);
17+
}
18+
19+
static createEmpty() {
20+
return new ModelGraph([], []);
21+
}
22+
23+
nodeForObject(obj) {
24+
if (!obj) {
25+
return null;
26+
}
27+
28+
if (this._nodesByObjects === NOT_CALCULATED) {
29+
this._nodesByObjects = createNodesByObjectsMap(this.nodes);
30+
}
31+
32+
return this._nodesByObjects.get(obj) || null;
33+
}
34+
35+
nodeForNode(node) {
36+
if (!node) {
37+
return null;
38+
}
39+
40+
if (this._nodesByIdPathKeys === NOT_CALCULATED) {
41+
this._nodesByIdPathKeys = createNodesByIdPathKeysMap(this.nodes);
42+
}
43+
44+
return this._nodesByIdPathKeys.get(node.idPathKey) || null;
45+
}
46+
}
47+
48+
function createNodesByObjectsMap(nodes) {
49+
const nodesByObjects = new Map();
50+
51+
for (const node of nodes) {
52+
nodesByObjects.set(node.obj, node);
53+
}
54+
55+
return nodesByObjects;
56+
}
57+
58+
function createNodesByIdPathKeysMap(nodes) {
59+
const nodesByIdPathKeys = new Map();
60+
61+
for (const node of nodes) {
62+
const idPathKey = node.idPathKey;
63+
64+
if (idPathKey !== null) {
65+
nodesByIdPathKeys.set(idPathKey, node);
66+
}
67+
}
68+
69+
return nodesByIdPathKeys;
70+
}
71+
72+
module.exports = {
73+
ModelGraph
74+
};
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
const { isObject, isString } = require('../../utils/objectUtils');
2+
const { ModelGraphNode } = require('./ModelGraphNode');
3+
const { ModelGraphEdge } = require('./ModelGraphEdge');
4+
5+
class ModelGraphBuilder {
6+
constructor() {
7+
this.nodes = [];
8+
this.edges = [];
9+
}
10+
11+
static buildGraph(rootModelClass, roots) {
12+
const builder = new this();
13+
builder._buildGraph(rootModelClass, roots);
14+
15+
return builder;
16+
}
17+
18+
_buildGraph(rootModelClass, roots) {
19+
if (roots) {
20+
if (Array.isArray(roots)) {
21+
this._buildNodes(rootModelClass, roots);
22+
} else {
23+
this._buildNode(rootModelClass, roots);
24+
}
25+
}
26+
27+
this._buildReferences();
28+
}
29+
30+
_buildNodes(modelClass, objs, parentNode = null, relation = null) {
31+
objs.forEach((obj, index) => {
32+
this._buildNode(modelClass, obj, parentNode, relation, index);
33+
});
34+
}
35+
36+
_buildNode(modelClass, obj, parentNode = null, relation = null, index = null) {
37+
const node = new ModelGraphNode(modelClass, obj);
38+
this.nodes.push(node);
39+
40+
if (parentNode) {
41+
const edge = new ModelGraphEdge(
42+
ModelGraphEdge.Type.Relation,
43+
parentNode,
44+
node,
45+
relation,
46+
index
47+
);
48+
49+
node.parentEdge = edge;
50+
this._addEdge(parentNode, node, edge);
51+
}
52+
53+
this._buildRelationNodes(node);
54+
}
55+
56+
_buildRelationNodes(node) {
57+
for (const relation of node.modelClass.getRelationArray()) {
58+
const relatedObjects = node.obj[relation.name];
59+
60+
if (!relatedObjects) {
61+
continue;
62+
}
63+
64+
if (relation.isOneToOne()) {
65+
this._buildNode(relation.relatedModelClass, relatedObjects, node, relation);
66+
} else {
67+
this._buildNodes(relation.relatedModelClass, relatedObjects, node, relation);
68+
}
69+
}
70+
}
71+
72+
_buildReferences() {
73+
const nodesByUid = this._nodesByUid();
74+
75+
this._buildObjectReferences(nodesByUid);
76+
this._buildPropertyReferences(nodesByUid);
77+
}
78+
79+
_nodesByUid() {
80+
const nodesByUid = new Map();
81+
82+
for (const node of this.nodes) {
83+
const uid = node.uid;
84+
85+
if (uid === undefined) {
86+
continue;
87+
}
88+
89+
nodesByUid.set(uid, node);
90+
}
91+
92+
return nodesByUid;
93+
}
94+
95+
_buildObjectReferences(nodesByUid) {
96+
for (const node of this.nodes) {
97+
const ref = node.reference;
98+
99+
if (ref === undefined) {
100+
continue;
101+
}
102+
103+
const refNode = nodesByUid.get(ref);
104+
105+
if (!refNode) {
106+
throw createReferenceFoundError(node, ref);
107+
}
108+
109+
const edge = new ModelGraphEdge(ModelGraphEdge.Type.Reference, node, refNode);
110+
edge.refType = ModelGraphEdge.ReferenceType.Object;
111+
112+
this._addEdge(node, refNode, edge);
113+
}
114+
}
115+
116+
_buildPropertyReferences(nodesByUid) {
117+
for (const node of this.nodes) {
118+
const relations = node.modelClass.getRelations();
119+
120+
for (const prop of Object.keys(node.obj)) {
121+
if (relations[prop]) {
122+
continue;
123+
}
124+
125+
this._buildPropertyReference(nodesByUid, node, prop);
126+
}
127+
}
128+
}
129+
130+
_buildPropertyReference(nodesByUid, node, prop) {
131+
visitStrings(node.obj[prop], [prop], (str, path) => {
132+
forEachMatch(node.modelClass.propRefRegex, str, match => {
133+
const [_, ref, refPath] = match;
134+
const refNode = nodesByUid.get(ref);
135+
136+
if (!refNode) {
137+
throw createReferenceFoundError(node, ref);
138+
}
139+
140+
const edge = new ModelGraphEdge(ModelGraphEdge.Type.Reference, node, refNode);
141+
142+
edge.refType = ModelGraphEdge.ReferenceType.Property;
143+
edge.refMatch = match[0];
144+
edge.refOwnerDataPath = path.slice();
145+
edge.refRelatedDataPath = refPath.split('.');
146+
147+
this._addEdge(node, refNode, edge);
148+
});
149+
});
150+
}
151+
152+
_addEdge(ownerNode, relatedNode, edge) {
153+
this.edges.push(edge);
154+
155+
ownerNode.edges.push(edge);
156+
relatedNode.edges.push(edge);
157+
158+
if (edge.type === ModelGraphEdge.Type.Reference) {
159+
ownerNode.refEdges.push(edge);
160+
relatedNode.refEdges.push(edge);
161+
}
162+
}
163+
}
164+
165+
function visitStrings(value, path, visit) {
166+
if (Array.isArray(value)) {
167+
visitStringsInArray(value, path, visit);
168+
} else if (isObject(value)) {
169+
visitStringsInObject(value, path, visit);
170+
} else if (isString(value)) {
171+
visit(value, path);
172+
}
173+
}
174+
175+
function visitStringsInArray(value, path, visit) {
176+
for (let i = 0; i < value.length; ++i) {
177+
path.push(i);
178+
visitStrings(value[i], path, visit);
179+
path.pop();
180+
}
181+
}
182+
183+
function visitStringsInObject(value, path, visit) {
184+
for (const prop of Object.keys(value)) {
185+
path.push(prop);
186+
visitStrings(value[prop], path, visit);
187+
path.pop();
188+
}
189+
}
190+
191+
function forEachMatch(regex, str, cb) {
192+
let matchResult = regex.exec(str);
193+
194+
while (matchResult) {
195+
cb(matchResult);
196+
matchResult = regex.exec(str);
197+
}
198+
}
199+
200+
function createReferenceFoundError(node, ref) {
201+
return new Error('no reference found');
202+
}
203+
204+
module.exports = {
205+
ModelGraphBuilder
206+
};

lib/model/graph/ModelGraphEdge.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const Type = {
2+
Relation: 'Relation',
3+
Reference: 'Reference'
4+
};
5+
6+
const ReferenceType = {
7+
Object: 'Object',
8+
Property: 'Property'
9+
};
10+
11+
class ModelGraphEdge {
12+
constructor(type, ownerNode, relatedNode, relation = null, relationIndex = null) {
13+
this.type = type;
14+
15+
this.ownerNode = ownerNode;
16+
this.relatedNode = relatedNode;
17+
this.relation = relation;
18+
this.relationIndex = relationIndex;
19+
20+
this.refType = null;
21+
this.refMatch = null;
22+
23+
this.refOwnerDataPath = null;
24+
this.refRelatedDataPath = null;
25+
}
26+
27+
static get Type() {
28+
return Type;
29+
}
30+
31+
static get ReferenceType() {
32+
return ReferenceType;
33+
}
34+
35+
getOtherNode(node) {
36+
return this.isOwnerNode(node) ? this.relatedNode : this.ownerNode;
37+
}
38+
39+
isOwnerNode(node) {
40+
return node === this.ownerNode;
41+
}
42+
43+
isRelatedNode(node) {
44+
return node === this.relatedNode;
45+
}
46+
}
47+
48+
module.exports = {
49+
ModelGraphEdge
50+
};

0 commit comments

Comments
 (0)