flowquery 1.0.67 → 1.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -16
- package/dist/flowquery.min.js +1 -1
- package/dist/parsing/statement_info_crawler.d.ts +136 -12
- package/dist/parsing/statement_info_crawler.d.ts.map +1 -1
- package/dist/parsing/statement_info_crawler.js +459 -24
- package/dist/parsing/statement_info_crawler.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,18 +5,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const database_1 = __importDefault(require("../graph/database"));
|
|
7
7
|
const node_1 = __importDefault(require("../graph/node"));
|
|
8
|
+
const node_reference_1 = __importDefault(require("../graph/node_reference"));
|
|
8
9
|
const relationship_1 = __importDefault(require("../graph/relationship"));
|
|
9
10
|
const ast_node_1 = __importDefault(require("./ast_node"));
|
|
10
11
|
const lookup_1 = __importDefault(require("./data_structures/lookup"));
|
|
12
|
+
const expression_1 = __importDefault(require("./expressions/expression"));
|
|
13
|
+
const f_string_1 = __importDefault(require("./expressions/f_string"));
|
|
11
14
|
const identifier_1 = __importDefault(require("./expressions/identifier"));
|
|
15
|
+
const operator_1 = require("./expressions/operator");
|
|
16
|
+
const parameter_reference_1 = __importDefault(require("./expressions/parameter_reference"));
|
|
12
17
|
const reference_1 = __importDefault(require("./expressions/reference"));
|
|
13
18
|
const subquery_expression_1 = __importDefault(require("./expressions/subquery_expression"));
|
|
19
|
+
const aggregated_return_1 = __importDefault(require("./operations/aggregated_return"));
|
|
20
|
+
const aggregated_with_1 = __importDefault(require("./operations/aggregated_with"));
|
|
14
21
|
const create_node_1 = __importDefault(require("./operations/create_node"));
|
|
15
22
|
const create_relationship_1 = __importDefault(require("./operations/create_relationship"));
|
|
16
23
|
const delete_node_1 = __importDefault(require("./operations/delete_node"));
|
|
17
24
|
const delete_relationship_1 = __importDefault(require("./operations/delete_relationship"));
|
|
18
25
|
const load_1 = __importDefault(require("./operations/load"));
|
|
19
26
|
const match_1 = __importDefault(require("./operations/match"));
|
|
27
|
+
const order_by_1 = __importDefault(require("./operations/order_by"));
|
|
28
|
+
const return_1 = __importDefault(require("./operations/return"));
|
|
29
|
+
const with_1 = __importDefault(require("./operations/with"));
|
|
20
30
|
/**
|
|
21
31
|
* Walks one or more parsed FlowQuery statement ASTs and extracts a
|
|
22
32
|
* `StatementInfo` describing the labels, relationship types, sources, and
|
|
@@ -41,12 +51,18 @@ class StatementInfoCrawler {
|
|
|
41
51
|
this._relProps = new Map();
|
|
42
52
|
this._nodeSources = new Map();
|
|
43
53
|
this._relSources = new Map();
|
|
54
|
+
this._nodeLiterals = new Map();
|
|
55
|
+
this._relLiterals = new Map();
|
|
56
|
+
this._nodeDeclaredProps = new Map();
|
|
57
|
+
this._relDeclaredProps = new Map();
|
|
58
|
+
this._nodeDeclaredSources = new Map();
|
|
59
|
+
this._relDeclaredSources = new Map();
|
|
44
60
|
this._ownCreatedNodeLabels = new Set();
|
|
45
61
|
this._ownCreatedRelTypes = new Set();
|
|
46
62
|
/**
|
|
47
63
|
* For each inline CREATE VIRTUAL clause, the (label or type) it
|
|
48
64
|
* declares and its inner statement AST. The label key receives the
|
|
49
|
-
* sources collected from the statement during {@link
|
|
65
|
+
* sources collected from the statement during {@link resolveRegisteredDefinitions}.
|
|
50
66
|
*/
|
|
51
67
|
this._ownNodeCreates = [];
|
|
52
68
|
this._ownRelCreates = [];
|
|
@@ -63,7 +79,7 @@ class StatementInfoCrawler {
|
|
|
63
79
|
for (const root of roots) {
|
|
64
80
|
this.crawlStatement(root);
|
|
65
81
|
}
|
|
66
|
-
this.
|
|
82
|
+
this.resolveRegisteredDefinitions();
|
|
67
83
|
return this.snapshot();
|
|
68
84
|
}
|
|
69
85
|
toIterable(statements) {
|
|
@@ -79,6 +95,12 @@ class StatementInfoCrawler {
|
|
|
79
95
|
this._relProps = new Map();
|
|
80
96
|
this._nodeSources = new Map();
|
|
81
97
|
this._relSources = new Map();
|
|
98
|
+
this._nodeLiterals = new Map();
|
|
99
|
+
this._relLiterals = new Map();
|
|
100
|
+
this._nodeDeclaredProps = new Map();
|
|
101
|
+
this._relDeclaredProps = new Map();
|
|
102
|
+
this._nodeDeclaredSources = new Map();
|
|
103
|
+
this._relDeclaredSources = new Map();
|
|
82
104
|
this._ownCreatedNodeLabels = new Set();
|
|
83
105
|
this._ownCreatedRelTypes = new Set();
|
|
84
106
|
this._ownNodeCreates = [];
|
|
@@ -140,17 +162,26 @@ class StatementInfoCrawler {
|
|
|
140
162
|
for (const pattern of op.patterns) {
|
|
141
163
|
for (const element of pattern.chain) {
|
|
142
164
|
if (element instanceof node_1.default) {
|
|
143
|
-
|
|
165
|
+
// For a WITH-rebound `MATCH (u)`, the chain
|
|
166
|
+
// element's own labels are empty; chase the
|
|
167
|
+
// underlying binding so labels/properties land
|
|
168
|
+
// on the original :User node.
|
|
169
|
+
const effectiveLabels = element.labels.length > 0
|
|
170
|
+
? element.labels
|
|
171
|
+
: this.effectiveLabelsFor(element);
|
|
172
|
+
for (const lbl of effectiveLabels)
|
|
144
173
|
this._nodeLabels.add(lbl);
|
|
145
|
-
for (const propKey of element.properties
|
|
146
|
-
this.addNodeProp(
|
|
174
|
+
for (const [propKey, expr] of element.properties) {
|
|
175
|
+
this.addNodeProp(effectiveLabels, propKey);
|
|
176
|
+
this.tryAddNodeLiteral(effectiveLabels, propKey, expr);
|
|
147
177
|
}
|
|
148
178
|
}
|
|
149
179
|
else if (element instanceof relationship_1.default) {
|
|
150
180
|
for (const t of element.types)
|
|
151
181
|
this._relTypes.add(t);
|
|
152
|
-
for (const propKey of element.properties
|
|
182
|
+
for (const [propKey, expr] of element.properties) {
|
|
153
183
|
this.addRelProp(element.types, propKey);
|
|
184
|
+
this.tryAddRelLiteral(element.types, propKey, expr);
|
|
154
185
|
}
|
|
155
186
|
}
|
|
156
187
|
}
|
|
@@ -166,16 +197,22 @@ class StatementInfoCrawler {
|
|
|
166
197
|
this.collectPropertyAccesses(op);
|
|
167
198
|
}
|
|
168
199
|
}
|
|
169
|
-
|
|
170
|
-
// Sources from inline CREATE VIRTUAL clauses
|
|
200
|
+
resolveRegisteredDefinitions() {
|
|
201
|
+
// Sources and declared properties from inline CREATE VIRTUAL clauses
|
|
202
|
+
// in the crawled statements.
|
|
171
203
|
for (const { label, statement } of this._ownNodeCreates) {
|
|
172
204
|
this.collectSources(statement, this.getOrCreate(this._nodeSources, label));
|
|
205
|
+
this.collectSources(statement, this.getOrCreate(this._nodeDeclaredSources, label));
|
|
206
|
+
this.collectDeclaredProps(statement, this.getOrCreate(this._nodeDeclaredProps, label));
|
|
173
207
|
}
|
|
174
208
|
for (const { type, statement } of this._ownRelCreates) {
|
|
175
209
|
this.collectSources(statement, this.getOrCreate(this._relSources, type));
|
|
210
|
+
this.collectSources(statement, this.getOrCreate(this._relDeclaredSources, type));
|
|
211
|
+
this.collectDeclaredProps(statement, this.getOrCreate(this._relDeclaredProps, type));
|
|
176
212
|
}
|
|
177
|
-
// Sources from already-registered virtuals
|
|
178
|
-
// reference (e.g. MATCH/DELETE against
|
|
213
|
+
// Sources / declared properties from already-registered virtuals
|
|
214
|
+
// that the crawled statements reference (e.g. MATCH/DELETE against
|
|
215
|
+
// a virtual registered earlier).
|
|
179
216
|
const db = database_1.default.getInstance();
|
|
180
217
|
for (const label of this._nodeLabels) {
|
|
181
218
|
if (this._ownCreatedNodeLabels.has(label))
|
|
@@ -183,6 +220,8 @@ class StatementInfoCrawler {
|
|
|
183
220
|
const physical = db.nodes.get(label);
|
|
184
221
|
if (physical === null || physical === void 0 ? void 0 : physical.statement) {
|
|
185
222
|
this.collectSources(physical.statement, this.getOrCreate(this._nodeSources, label));
|
|
223
|
+
this.collectSources(physical.statement, this.getOrCreate(this._nodeDeclaredSources, label));
|
|
224
|
+
this.collectDeclaredProps(physical.statement, this.getOrCreate(this._nodeDeclaredProps, label));
|
|
186
225
|
}
|
|
187
226
|
}
|
|
188
227
|
for (const type of this._relTypes) {
|
|
@@ -194,6 +233,8 @@ class StatementInfoCrawler {
|
|
|
194
233
|
for (const physical of typeMap.values()) {
|
|
195
234
|
if (physical.statement) {
|
|
196
235
|
this.collectSources(physical.statement, this.getOrCreate(this._relSources, type));
|
|
236
|
+
this.collectSources(physical.statement, this.getOrCreate(this._relDeclaredSources, type));
|
|
237
|
+
this.collectDeclaredProps(physical.statement, this.getOrCreate(this._relDeclaredProps, type));
|
|
197
238
|
}
|
|
198
239
|
}
|
|
199
240
|
}
|
|
@@ -206,6 +247,13 @@ class StatementInfoCrawler {
|
|
|
206
247
|
}
|
|
207
248
|
return set;
|
|
208
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Walks the AST rooted at `root` and records every property access
|
|
252
|
+
* (via `Lookup`) on a known node/relationship binding, every literal
|
|
253
|
+
* value supplied via equality / `IN` predicates, and descends into
|
|
254
|
+
* subqueries plus the privately-held WHERE / ORDER BY ASTs of
|
|
255
|
+
* RETURN-style operations.
|
|
256
|
+
*/
|
|
209
257
|
collectPropertyAccesses(root) {
|
|
210
258
|
const visited = new Set();
|
|
211
259
|
const stack = [root];
|
|
@@ -215,20 +263,13 @@ class StatementInfoCrawler {
|
|
|
215
263
|
continue;
|
|
216
264
|
visited.add(node);
|
|
217
265
|
if (node instanceof lookup_1.default) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.addNodeProp(referred.labels, propName);
|
|
226
|
-
}
|
|
227
|
-
else if (referred instanceof relationship_1.default) {
|
|
228
|
-
this.addRelProp(referred.types, propName);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
266
|
+
this.handleLookupAccess(node);
|
|
267
|
+
}
|
|
268
|
+
if (node instanceof operator_1.Equals) {
|
|
269
|
+
this.handleEqualityLiteral(node);
|
|
270
|
+
}
|
|
271
|
+
else if (node instanceof operator_1.In) {
|
|
272
|
+
this.handleInLiteral(node);
|
|
232
273
|
}
|
|
233
274
|
for (const child of node.getChildren()) {
|
|
234
275
|
stack.push(child);
|
|
@@ -240,6 +281,269 @@ class StatementInfoCrawler {
|
|
|
240
281
|
if (inner !== undefined)
|
|
241
282
|
stack.push(inner);
|
|
242
283
|
}
|
|
284
|
+
// RETURN / AggregatedReturn hold WHERE and ORDER BY clauses in
|
|
285
|
+
// private fields; descend into the ones with expression trees.
|
|
286
|
+
if (node instanceof return_1.default) {
|
|
287
|
+
const w = node._where;
|
|
288
|
+
const o = node._orderBy;
|
|
289
|
+
if (w)
|
|
290
|
+
stack.push(w);
|
|
291
|
+
if (o)
|
|
292
|
+
stack.push(o);
|
|
293
|
+
}
|
|
294
|
+
// OrderBy stores its sort expressions in a private array of
|
|
295
|
+
// SortField objects rather than as AST children; descend
|
|
296
|
+
// explicitly.
|
|
297
|
+
if (node instanceof order_by_1.default) {
|
|
298
|
+
for (const field of node.fields) {
|
|
299
|
+
stack.push(field.expression);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
handleLookupAccess(node) {
|
|
305
|
+
const target = this.resolveLookupTarget(node);
|
|
306
|
+
if (target === null)
|
|
307
|
+
return;
|
|
308
|
+
if (target.kind === "node") {
|
|
309
|
+
this.addNodeProp(target.labels, target.prop);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
this.addRelProp(target.types, target.prop);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Resolves a `Lookup` of the shape `alias.prop` to the labels/types
|
|
317
|
+
* of the bound entity and the property name. Returns `null` if the
|
|
318
|
+
* lookup isn't of that shape or its variable doesn't resolve to a
|
|
319
|
+
* Node/Relationship.
|
|
320
|
+
*/
|
|
321
|
+
resolveLookupTarget(lookup) {
|
|
322
|
+
const variable = lookup.variable;
|
|
323
|
+
const index = lookup.index;
|
|
324
|
+
if (!(variable instanceof reference_1.default) || !(index instanceof identifier_1.default))
|
|
325
|
+
return null;
|
|
326
|
+
const propName = index.value();
|
|
327
|
+
if (typeof propName !== "string" || propName.length === 0)
|
|
328
|
+
return null;
|
|
329
|
+
const referred = this.resolveBoundEntity(variable.referred);
|
|
330
|
+
if (referred instanceof node_1.default) {
|
|
331
|
+
return { kind: "node", labels: referred.labels, prop: propName };
|
|
332
|
+
}
|
|
333
|
+
if (referred instanceof relationship_1.default) {
|
|
334
|
+
return { kind: "rel", types: referred.types, prop: propName };
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Unwraps a binding chain to the underlying graph entity that
|
|
340
|
+
* carries the labels / types relevant for lineage. Handles the
|
|
341
|
+
* common WITH-rebind pattern:
|
|
342
|
+
*
|
|
343
|
+
* ```
|
|
344
|
+
* MATCH (u:User) ... WITH u MATCH (u) RETURN u.name
|
|
345
|
+
* ```
|
|
346
|
+
*
|
|
347
|
+
* After `WITH u`, the variable `u` is bound to the WITH's
|
|
348
|
+
* `Expression` (a passthrough Reference to the original Node). The
|
|
349
|
+
* second `MATCH (u)` then creates a `NodeReference` whose own
|
|
350
|
+
* `labels` are empty. Without unwrapping, `u.name` in `RETURN`
|
|
351
|
+
* resolves to that empty-labels rebind and the property is silently
|
|
352
|
+
* dropped from lineage. By chasing through `Reference.referred`,
|
|
353
|
+
* single-child `Expression` nodes, and `NodeReference.reference`
|
|
354
|
+
* (when the immediate labels are empty), we recover the original
|
|
355
|
+
* `Node(:User)` binding.
|
|
356
|
+
*/
|
|
357
|
+
resolveBoundEntity(node) {
|
|
358
|
+
const seen = new Set();
|
|
359
|
+
let current = node;
|
|
360
|
+
while (current !== undefined && !seen.has(current)) {
|
|
361
|
+
seen.add(current);
|
|
362
|
+
// NodeReference subclasses Node and copies its base's
|
|
363
|
+
// labels at construction. When those copied labels are
|
|
364
|
+
// empty (`MATCH (u)` after a WITH-rebind), chase the
|
|
365
|
+
// underlying binding to recover the original labels.
|
|
366
|
+
if (current instanceof node_reference_1.default) {
|
|
367
|
+
if (current.labels.length > 0)
|
|
368
|
+
return current;
|
|
369
|
+
const next = current.reference;
|
|
370
|
+
current = next === null ? undefined : next;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (current instanceof node_1.default)
|
|
374
|
+
return current;
|
|
375
|
+
if (current instanceof relationship_1.default)
|
|
376
|
+
return current;
|
|
377
|
+
if (current instanceof reference_1.default) {
|
|
378
|
+
current = current.referred;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (current instanceof expression_1.default) {
|
|
382
|
+
// Pass-through projections (`WITH u`, `RETURN u AS u`)
|
|
383
|
+
// wrap a single Reference; arbitrary computed
|
|
384
|
+
// expressions have no useful binding to attribute
|
|
385
|
+
// properties to.
|
|
386
|
+
const children = current.getChildren();
|
|
387
|
+
if (children.length === 1) {
|
|
388
|
+
current = children[0];
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Returns the effective labels for a MATCH chain `Node` element.
|
|
399
|
+
* If the element itself is unlabeled (typical for a WITH-rebound
|
|
400
|
+
* `MATCH (u)`), unwrap its binding to inherit the original labels.
|
|
401
|
+
*/
|
|
402
|
+
effectiveLabelsFor(element) {
|
|
403
|
+
if (element instanceof node_reference_1.default) {
|
|
404
|
+
const resolved = this.resolveBoundEntity(element);
|
|
405
|
+
if (resolved instanceof node_1.default) {
|
|
406
|
+
return resolved.labels;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return element.labels;
|
|
410
|
+
}
|
|
411
|
+
handleEqualityLiteral(op) {
|
|
412
|
+
const lhs = op.lhs;
|
|
413
|
+
const rhs = op.rhs;
|
|
414
|
+
// Try both orientations: `alias.prop = literal` and `literal = alias.prop`.
|
|
415
|
+
this.tryRecordPropEquality(lhs, rhs);
|
|
416
|
+
this.tryRecordPropEquality(rhs, lhs);
|
|
417
|
+
}
|
|
418
|
+
tryRecordPropEquality(side, other) {
|
|
419
|
+
if (!(side instanceof lookup_1.default))
|
|
420
|
+
return;
|
|
421
|
+
if (!this.isLiteralAst(other))
|
|
422
|
+
return;
|
|
423
|
+
const target = this.resolveLookupTarget(side);
|
|
424
|
+
if (target === null)
|
|
425
|
+
return;
|
|
426
|
+
const value = this.safeEvaluate(other);
|
|
427
|
+
if (value === undefined)
|
|
428
|
+
return;
|
|
429
|
+
if (target.kind === "node") {
|
|
430
|
+
this.addNodeLiteralValue(target.labels, target.prop, value);
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
this.addRelLiteralValue(target.types, target.prop, value);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
handleInLiteral(op) {
|
|
437
|
+
const lhs = op.lhs;
|
|
438
|
+
const rhs = op.rhs;
|
|
439
|
+
if (!(lhs instanceof lookup_1.default))
|
|
440
|
+
return;
|
|
441
|
+
if (!this.isLiteralAst(rhs))
|
|
442
|
+
return;
|
|
443
|
+
const target = this.resolveLookupTarget(lhs);
|
|
444
|
+
if (target === null)
|
|
445
|
+
return;
|
|
446
|
+
const value = this.safeEvaluate(rhs);
|
|
447
|
+
if (!Array.isArray(value))
|
|
448
|
+
return;
|
|
449
|
+
for (const item of value) {
|
|
450
|
+
if (target.kind === "node") {
|
|
451
|
+
this.addNodeLiteralValue(target.labels, target.prop, item);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
this.addRelLiteralValue(target.types, target.prop, item);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Returns true iff the AST subtree contains only literal nodes (no
|
|
460
|
+
* References, ParameterReferences, Lookups, FStrings, or
|
|
461
|
+
* SubqueryExpressions). Used to guard literal-value extraction
|
|
462
|
+
* against runtime-dependent expressions.
|
|
463
|
+
*/
|
|
464
|
+
isLiteralAst(node) {
|
|
465
|
+
if (node instanceof reference_1.default ||
|
|
466
|
+
node instanceof parameter_reference_1.default ||
|
|
467
|
+
node instanceof lookup_1.default ||
|
|
468
|
+
node instanceof f_string_1.default ||
|
|
469
|
+
node instanceof subquery_expression_1.default) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
for (const child of node.getChildren()) {
|
|
473
|
+
if (!this.isLiteralAst(child))
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
safeEvaluate(node) {
|
|
479
|
+
try {
|
|
480
|
+
return node.value();
|
|
481
|
+
}
|
|
482
|
+
catch (_a) {
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
tryAddNodeLiteral(labels, prop, expr) {
|
|
487
|
+
if (!this.isLiteralAst(expr))
|
|
488
|
+
return;
|
|
489
|
+
const value = this.safeEvaluate(expr);
|
|
490
|
+
if (value === undefined)
|
|
491
|
+
return;
|
|
492
|
+
this.addNodeLiteralValue(labels, prop, value);
|
|
493
|
+
}
|
|
494
|
+
tryAddRelLiteral(types, prop, expr) {
|
|
495
|
+
if (!this.isLiteralAst(expr))
|
|
496
|
+
return;
|
|
497
|
+
const value = this.safeEvaluate(expr);
|
|
498
|
+
if (value === undefined)
|
|
499
|
+
return;
|
|
500
|
+
this.addRelLiteralValue(types, prop, value);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Walks a virtual definition's inner statement to find the final
|
|
504
|
+
* RETURN-style projection and records its aliases as the declared
|
|
505
|
+
* property set. Falls back to the last WITH if no RETURN exists.
|
|
506
|
+
*/
|
|
507
|
+
collectDeclaredProps(statement, target) {
|
|
508
|
+
let op = null;
|
|
509
|
+
try {
|
|
510
|
+
op = statement.firstChild();
|
|
511
|
+
}
|
|
512
|
+
catch (_a) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
let lastReturn = null;
|
|
516
|
+
let lastWith = null;
|
|
517
|
+
while (op !== null) {
|
|
518
|
+
if (op instanceof return_1.default || op instanceof aggregated_return_1.default) {
|
|
519
|
+
lastReturn = op;
|
|
520
|
+
}
|
|
521
|
+
else if (op instanceof with_1.default || op instanceof aggregated_with_1.default) {
|
|
522
|
+
lastWith = op;
|
|
523
|
+
}
|
|
524
|
+
op = op.next;
|
|
525
|
+
}
|
|
526
|
+
const projection = lastReturn !== null && lastReturn !== void 0 ? lastReturn : lastWith;
|
|
527
|
+
if (projection === null)
|
|
528
|
+
return;
|
|
529
|
+
for (const alias of this.projectionAliases(projection)) {
|
|
530
|
+
target.add(alias);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Yields the alias of every projected expression in a Projection.
|
|
535
|
+
* Mirrors the alias-resolution logic of `Projection.expressions()`
|
|
536
|
+
* (which is protected).
|
|
537
|
+
*/
|
|
538
|
+
*projectionAliases(projection) {
|
|
539
|
+
var _a;
|
|
540
|
+
const children = projection.getChildren();
|
|
541
|
+
for (let i = 0; i < children.length; i++) {
|
|
542
|
+
const expr = children[i];
|
|
543
|
+
const alias = (_a = expr.alias) !== null && _a !== void 0 ? _a : `expr${i}`;
|
|
544
|
+
if (typeof alias === "string" && alias.length > 0) {
|
|
545
|
+
yield alias;
|
|
546
|
+
}
|
|
243
547
|
}
|
|
244
548
|
}
|
|
245
549
|
collectSources(statement, target) {
|
|
@@ -297,6 +601,85 @@ class StatementInfoCrawler {
|
|
|
297
601
|
set.add(prop);
|
|
298
602
|
}
|
|
299
603
|
}
|
|
604
|
+
addNodeLiteralValue(labels, prop, value) {
|
|
605
|
+
for (const label of labels) {
|
|
606
|
+
if (!label)
|
|
607
|
+
continue;
|
|
608
|
+
let propMap = this._nodeLiterals.get(label);
|
|
609
|
+
if (propMap === undefined) {
|
|
610
|
+
propMap = new Map();
|
|
611
|
+
this._nodeLiterals.set(label, propMap);
|
|
612
|
+
}
|
|
613
|
+
this.appendUniqueLiteral(propMap, prop, value);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
addRelLiteralValue(types, prop, value) {
|
|
617
|
+
for (const type of types) {
|
|
618
|
+
if (!type)
|
|
619
|
+
continue;
|
|
620
|
+
let propMap = this._relLiterals.get(type);
|
|
621
|
+
if (propMap === undefined) {
|
|
622
|
+
propMap = new Map();
|
|
623
|
+
this._relLiterals.set(type, propMap);
|
|
624
|
+
}
|
|
625
|
+
this.appendUniqueLiteral(propMap, prop, value);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
appendUniqueLiteral(propMap, prop, value) {
|
|
629
|
+
let arr = propMap.get(prop);
|
|
630
|
+
if (arr === undefined) {
|
|
631
|
+
arr = [];
|
|
632
|
+
propMap.set(prop, arr);
|
|
633
|
+
}
|
|
634
|
+
for (const existing of arr) {
|
|
635
|
+
if (this.literalsEqual(existing, value))
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
arr.push(value);
|
|
639
|
+
}
|
|
640
|
+
literalsEqual(a, b) {
|
|
641
|
+
if (a === b)
|
|
642
|
+
return true;
|
|
643
|
+
if (a === null || b === null || a === undefined || b === undefined)
|
|
644
|
+
return false;
|
|
645
|
+
if (typeof a !== typeof b)
|
|
646
|
+
return false;
|
|
647
|
+
if (typeof a !== "object")
|
|
648
|
+
return false;
|
|
649
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
650
|
+
return false;
|
|
651
|
+
if (Array.isArray(a)) {
|
|
652
|
+
if (a.length !== b.length)
|
|
653
|
+
return false;
|
|
654
|
+
for (let i = 0; i < a.length; i++) {
|
|
655
|
+
if (!this.literalsEqual(a[i], b[i]))
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
const ka = Object.keys(a);
|
|
661
|
+
const kb = Object.keys(b);
|
|
662
|
+
if (ka.length !== kb.length)
|
|
663
|
+
return false;
|
|
664
|
+
for (const k of ka) {
|
|
665
|
+
if (!Object.prototype.hasOwnProperty.call(b, k))
|
|
666
|
+
return false;
|
|
667
|
+
if (!this.literalsEqual(a[k], b[k]))
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
literalsSnapshot(map, key) {
|
|
673
|
+
const propMap = map.get(key);
|
|
674
|
+
if (propMap === undefined || propMap.size === 0)
|
|
675
|
+
return {};
|
|
676
|
+
const out = {};
|
|
677
|
+
const sortedKeys = Array.from(propMap.keys()).sort();
|
|
678
|
+
for (const propKey of sortedKeys) {
|
|
679
|
+
out[propKey] = [...propMap.get(propKey)];
|
|
680
|
+
}
|
|
681
|
+
return out;
|
|
682
|
+
}
|
|
300
683
|
snapshot() {
|
|
301
684
|
const allSources = new Set();
|
|
302
685
|
const nodes = {};
|
|
@@ -310,6 +693,7 @@ class StatementInfoCrawler {
|
|
|
310
693
|
nodes[label] = {
|
|
311
694
|
properties: props ? Array.from(props).sort() : [],
|
|
312
695
|
sources: sources ? Array.from(sources).sort() : [],
|
|
696
|
+
literal_values: this.literalsSnapshot(this._nodeLiterals, label),
|
|
313
697
|
};
|
|
314
698
|
}
|
|
315
699
|
const relationships = {};
|
|
@@ -323,8 +707,31 @@ class StatementInfoCrawler {
|
|
|
323
707
|
relationships[type] = {
|
|
324
708
|
properties: props ? Array.from(props).sort() : [],
|
|
325
709
|
sources: sources ? Array.from(sources).sort() : [],
|
|
710
|
+
literal_values: this.literalsSnapshot(this._relLiterals, type),
|
|
326
711
|
};
|
|
327
712
|
}
|
|
713
|
+
const declaredNodes = {};
|
|
714
|
+
for (const label of this._nodeLabels) {
|
|
715
|
+
const props = this._nodeDeclaredProps.get(label);
|
|
716
|
+
const sources = this._nodeDeclaredSources.get(label);
|
|
717
|
+
if ((props && props.size > 0) || (sources && sources.size > 0)) {
|
|
718
|
+
declaredNodes[label] = {
|
|
719
|
+
properties: props ? Array.from(props).sort() : [],
|
|
720
|
+
sources: sources ? Array.from(sources).sort() : [],
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const declaredRelationships = {};
|
|
725
|
+
for (const type of this._relTypes) {
|
|
726
|
+
const props = this._relDeclaredProps.get(type);
|
|
727
|
+
const sources = this._relDeclaredSources.get(type);
|
|
728
|
+
if ((props && props.size > 0) || (sources && sources.size > 0)) {
|
|
729
|
+
declaredRelationships[type] = {
|
|
730
|
+
properties: props ? Array.from(props).sort() : [],
|
|
731
|
+
sources: sources ? Array.from(sources).sort() : [],
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
328
735
|
const info = {
|
|
329
736
|
node_labels: Array.from(this._nodeLabels).sort(),
|
|
330
737
|
relationship_types: Array.from(this._relTypes).sort(),
|
|
@@ -333,6 +740,10 @@ class StatementInfoCrawler {
|
|
|
333
740
|
relationship_properties: {},
|
|
334
741
|
nodes,
|
|
335
742
|
relationships,
|
|
743
|
+
declared: {
|
|
744
|
+
nodes: declaredNodes,
|
|
745
|
+
relationships: declaredRelationships,
|
|
746
|
+
},
|
|
336
747
|
};
|
|
337
748
|
for (const [label, props] of this._nodeProps) {
|
|
338
749
|
info.node_properties[label] = Array.from(props).sort();
|
|
@@ -346,13 +757,33 @@ class StatementInfoCrawler {
|
|
|
346
757
|
* Returns a deep copy of a StatementInfo so callers can mutate it freely.
|
|
347
758
|
*/
|
|
348
759
|
static clone(info) {
|
|
760
|
+
var _a, _b, _c, _d;
|
|
349
761
|
const cloneProps = (props) => {
|
|
350
762
|
const out = {};
|
|
351
763
|
for (const [k, v] of Object.entries(props))
|
|
352
764
|
out[k] = [...v];
|
|
353
765
|
return out;
|
|
354
766
|
};
|
|
767
|
+
const cloneLiterals = (literals) => {
|
|
768
|
+
const out = {};
|
|
769
|
+
for (const [k, v] of Object.entries(literals)) {
|
|
770
|
+
out[k] = v.map((item) => typeof item === "object" && item !== null ? structuredClone(item) : item);
|
|
771
|
+
}
|
|
772
|
+
return out;
|
|
773
|
+
};
|
|
355
774
|
const cloneEntities = (entities) => {
|
|
775
|
+
var _a;
|
|
776
|
+
const out = {};
|
|
777
|
+
for (const [k, v] of Object.entries(entities)) {
|
|
778
|
+
out[k] = {
|
|
779
|
+
properties: [...v.properties],
|
|
780
|
+
sources: [...v.sources],
|
|
781
|
+
literal_values: cloneLiterals((_a = v.literal_values) !== null && _a !== void 0 ? _a : {}),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
return out;
|
|
785
|
+
};
|
|
786
|
+
const cloneDeclared = (entities) => {
|
|
356
787
|
const out = {};
|
|
357
788
|
for (const [k, v] of Object.entries(entities)) {
|
|
358
789
|
out[k] = {
|
|
@@ -370,6 +801,10 @@ class StatementInfoCrawler {
|
|
|
370
801
|
relationship_properties: cloneProps(info.relationship_properties),
|
|
371
802
|
nodes: cloneEntities(info.nodes),
|
|
372
803
|
relationships: cloneEntities(info.relationships),
|
|
804
|
+
declared: {
|
|
805
|
+
nodes: cloneDeclared((_b = (_a = info.declared) === null || _a === void 0 ? void 0 : _a.nodes) !== null && _b !== void 0 ? _b : {}),
|
|
806
|
+
relationships: cloneDeclared((_d = (_c = info.declared) === null || _c === void 0 ? void 0 : _c.relationships) !== null && _d !== void 0 ? _d : {}),
|
|
807
|
+
},
|
|
373
808
|
};
|
|
374
809
|
}
|
|
375
810
|
}
|