leangraph 1.1.0 → 1.1.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.
- package/dist/auth.js +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/db.d.ts +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +56 -9
- package/dist/db.js.map +1 -1
- package/dist/executor.d.ts +108 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +1160 -323
- package/dist/executor.js.map +1 -1
- package/dist/parser.d.ts +15 -3
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +231 -42
- package/dist/parser.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +3 -0
- package/dist/routes.js.map +1 -1
- package/dist/translator.d.ts +36 -0
- package/dist/translator.d.ts.map +1 -1
- package/dist/translator.js +555 -104
- package/dist/translator.js.map +1 -1
- package/package.json +1 -1
package/dist/parser.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface PathExpression {
|
|
|
21
21
|
type: "path";
|
|
22
22
|
variable: string;
|
|
23
23
|
patterns: (NodePattern | RelationshipPattern)[];
|
|
24
|
+
pathFunction?: "shortestPath" | "allShortestPaths";
|
|
24
25
|
}
|
|
25
26
|
export interface ParameterRef {
|
|
26
27
|
type: "parameter";
|
|
@@ -52,7 +53,7 @@ export interface MapPropertyValue {
|
|
|
52
53
|
}
|
|
53
54
|
export type PropertyValue = string | number | boolean | null | ParameterRef | VariableRef | PropertyRef | BinaryPropertyValue | MapPropertyValue | FunctionPropertyValue | PropertyValue[];
|
|
54
55
|
export interface WhereCondition {
|
|
55
|
-
type: "comparison" | "and" | "or" | "not" | "contains" | "startsWith" | "endsWith" | "isNull" | "isNotNull" | "exists" | "in" | "listPredicate" | "patternMatch" | "expression";
|
|
56
|
+
type: "comparison" | "and" | "or" | "not" | "contains" | "startsWith" | "endsWith" | "isNull" | "isNotNull" | "exists" | "in" | "listPredicate" | "patternMatch" | "expression" | "regex";
|
|
56
57
|
left?: Expression;
|
|
57
58
|
right?: Expression;
|
|
58
59
|
operator?: "=" | "<>" | "<" | ">" | "<=" | ">=";
|
|
@@ -81,7 +82,7 @@ export interface ObjectProperty {
|
|
|
81
82
|
value: Expression;
|
|
82
83
|
}
|
|
83
84
|
export interface Expression {
|
|
84
|
-
type: "property" | "literal" | "parameter" | "variable" | "function" | "case" | "binary" | "object" | "comparison" | "listComprehension" | "listPredicate" | "patternComprehension" | "unary" | "labelPredicate" | "propertyAccess" | "indexAccess" | "in" | "stringOp";
|
|
85
|
+
type: "property" | "literal" | "parameter" | "variable" | "function" | "case" | "binary" | "object" | "comparison" | "listComprehension" | "listPredicate" | "patternComprehension" | "unary" | "labelPredicate" | "propertyAccess" | "indexAccess" | "in" | "stringOp" | "existsPattern" | "reduce" | "filter" | "extract";
|
|
85
86
|
variable?: string;
|
|
86
87
|
property?: string;
|
|
87
88
|
value?: PropertyValue;
|
|
@@ -114,6 +115,9 @@ export interface Expression {
|
|
|
114
115
|
index?: Expression;
|
|
115
116
|
list?: Expression;
|
|
116
117
|
stringOperator?: "CONTAINS" | "STARTS WITH" | "ENDS WITH";
|
|
118
|
+
accumulator?: string;
|
|
119
|
+
initialValue?: Expression;
|
|
120
|
+
reduceExpr?: Expression;
|
|
117
121
|
}
|
|
118
122
|
export interface ReturnItem {
|
|
119
123
|
expression: Expression;
|
|
@@ -204,7 +208,13 @@ export interface CallClause {
|
|
|
204
208
|
yields?: string[];
|
|
205
209
|
where?: WhereCondition;
|
|
206
210
|
}
|
|
207
|
-
export
|
|
211
|
+
export interface ForeachClause {
|
|
212
|
+
type: "FOREACH";
|
|
213
|
+
variable: string;
|
|
214
|
+
expression: Expression;
|
|
215
|
+
body: Clause[];
|
|
216
|
+
}
|
|
217
|
+
export type Clause = CreateClause | MatchClause | MergeClause | SetClause | DeleteClause | RemoveClause | ReturnClause | WithClause | UnwindClause | UnionClause | CallClause | ForeachClause;
|
|
208
218
|
export interface Query {
|
|
209
219
|
clauses: Clause[];
|
|
210
220
|
}
|
|
@@ -259,6 +269,8 @@ export declare class Parser {
|
|
|
259
269
|
private parseUnwind;
|
|
260
270
|
private parseUnwindExpression;
|
|
261
271
|
private parseCall;
|
|
272
|
+
private parseForeach;
|
|
273
|
+
private parseForeachBodyClause;
|
|
262
274
|
/**
|
|
263
275
|
* Parse a pattern, which can be a single node or a chain of relationships.
|
|
264
276
|
* For chained patterns like (a)-[:R1]->(b)-[:R2]->(c), this returns multiple
|
package/dist/parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC3C,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,WAAW,GAAG,mBAAmB,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
|
package/dist/parser.js
CHANGED
|
@@ -49,6 +49,7 @@ const KEYWORDS = new Set([
|
|
|
49
49
|
"CALL",
|
|
50
50
|
"YIELD",
|
|
51
51
|
"REMOVE",
|
|
52
|
+
"FOREACH",
|
|
52
53
|
]);
|
|
53
54
|
class Tokenizer {
|
|
54
55
|
input;
|
|
@@ -226,6 +227,11 @@ class Tokenizer {
|
|
|
226
227
|
this.column += 2;
|
|
227
228
|
return { type: "GTE", value: ">=", position: startPos, line: startLine, column: startColumn };
|
|
228
229
|
}
|
|
230
|
+
if (twoChars === "=~") {
|
|
231
|
+
this.pos += 2;
|
|
232
|
+
this.column += 2;
|
|
233
|
+
return { type: "REGEX_MATCH", value: "=~", position: startPos, line: startLine, column: startColumn };
|
|
234
|
+
}
|
|
229
235
|
}
|
|
230
236
|
// Single character tokens
|
|
231
237
|
const singleCharTokens = {
|
|
@@ -290,50 +296,58 @@ class Tokenizer {
|
|
|
290
296
|
readString(quote, startPos, startLine, startColumn) {
|
|
291
297
|
this.pos++;
|
|
292
298
|
this.column++;
|
|
293
|
-
|
|
299
|
+
const segments = [];
|
|
300
|
+
let segmentStart = this.pos;
|
|
294
301
|
while (this.pos < this.input.length) {
|
|
295
302
|
const char = this.input[this.pos];
|
|
296
303
|
if (char === quote) {
|
|
304
|
+
if (segmentStart < this.pos) {
|
|
305
|
+
segments.push(this.input.slice(segmentStart, this.pos));
|
|
306
|
+
}
|
|
297
307
|
this.pos++;
|
|
298
308
|
this.column++;
|
|
299
|
-
return { type: "STRING", value, position: startPos, line: startLine, column: startColumn };
|
|
309
|
+
return { type: "STRING", value: segments.join(""), position: startPos, line: startLine, column: startColumn };
|
|
300
310
|
}
|
|
301
311
|
if (char === "\\") {
|
|
312
|
+
// Add segment before escape
|
|
313
|
+
if (segmentStart < this.pos) {
|
|
314
|
+
segments.push(this.input.slice(segmentStart, this.pos));
|
|
315
|
+
}
|
|
302
316
|
this.pos++;
|
|
303
317
|
this.column++;
|
|
304
318
|
const escaped = this.input[this.pos];
|
|
305
319
|
if (escaped === "n") {
|
|
306
|
-
|
|
320
|
+
segments.push("\n");
|
|
307
321
|
this.pos++;
|
|
308
322
|
this.column++;
|
|
309
323
|
}
|
|
310
324
|
else if (escaped === "t") {
|
|
311
|
-
|
|
325
|
+
segments.push("\t");
|
|
312
326
|
this.pos++;
|
|
313
327
|
this.column++;
|
|
314
328
|
}
|
|
315
329
|
else if (escaped === "r") {
|
|
316
|
-
|
|
330
|
+
segments.push("\r");
|
|
317
331
|
this.pos++;
|
|
318
332
|
this.column++;
|
|
319
333
|
}
|
|
320
334
|
else if (escaped === "b") {
|
|
321
|
-
|
|
335
|
+
segments.push("\b");
|
|
322
336
|
this.pos++;
|
|
323
337
|
this.column++;
|
|
324
338
|
}
|
|
325
339
|
else if (escaped === "f") {
|
|
326
|
-
|
|
340
|
+
segments.push("\f");
|
|
327
341
|
this.pos++;
|
|
328
342
|
this.column++;
|
|
329
343
|
}
|
|
330
344
|
else if (escaped === "\\") {
|
|
331
|
-
|
|
345
|
+
segments.push("\\");
|
|
332
346
|
this.pos++;
|
|
333
347
|
this.column++;
|
|
334
348
|
}
|
|
335
349
|
else if (escaped === quote) {
|
|
336
|
-
|
|
350
|
+
segments.push(quote);
|
|
337
351
|
this.pos++;
|
|
338
352
|
this.column++;
|
|
339
353
|
}
|
|
@@ -342,29 +356,28 @@ class Tokenizer {
|
|
|
342
356
|
this.pos++;
|
|
343
357
|
this.column++;
|
|
344
358
|
const hexStart = this.pos;
|
|
345
|
-
|
|
359
|
+
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
346
360
|
for (let i = 0; i < 4; i++) {
|
|
347
361
|
if (this.pos >= this.input.length) {
|
|
348
362
|
throw new SyntaxError(`Invalid unicode escape sequence: incomplete \\u escape at position ${hexStart - 2}`);
|
|
349
363
|
}
|
|
350
364
|
const c = this.input[this.pos];
|
|
351
365
|
if (!/[0-9a-fA-F]/.test(c)) {
|
|
352
|
-
throw new SyntaxError(`Invalid unicode escape sequence: \\u${
|
|
366
|
+
throw new SyntaxError(`Invalid unicode escape sequence: \\u${this.input.slice(hexStart, this.pos)}${c}`);
|
|
353
367
|
}
|
|
354
|
-
hex += c;
|
|
355
368
|
this.pos++;
|
|
356
369
|
this.column++;
|
|
357
370
|
}
|
|
358
|
-
|
|
371
|
+
segments.push(String.fromCharCode(parseInt(hex, 16)));
|
|
359
372
|
}
|
|
360
373
|
else {
|
|
361
|
-
|
|
374
|
+
segments.push(escaped);
|
|
362
375
|
this.pos++;
|
|
363
376
|
this.column++;
|
|
364
377
|
}
|
|
378
|
+
segmentStart = this.pos;
|
|
365
379
|
}
|
|
366
380
|
else {
|
|
367
|
-
value += char;
|
|
368
381
|
this.pos++;
|
|
369
382
|
this.column++;
|
|
370
383
|
}
|
|
@@ -372,47 +385,53 @@ class Tokenizer {
|
|
|
372
385
|
throw new Error(`Unterminated string at position ${startPos}`);
|
|
373
386
|
}
|
|
374
387
|
readNumber(startPos, startLine, startColumn) {
|
|
375
|
-
|
|
388
|
+
const segments = [];
|
|
376
389
|
if (this.input[this.pos] === "-") {
|
|
377
|
-
|
|
390
|
+
segments.push("-");
|
|
378
391
|
this.pos++;
|
|
379
392
|
this.column++;
|
|
380
393
|
}
|
|
381
394
|
// Handle numbers starting with . like .5
|
|
382
395
|
if (this.input[this.pos] === ".") {
|
|
383
|
-
|
|
396
|
+
segments.push("0.");
|
|
384
397
|
this.pos++;
|
|
385
398
|
this.column++;
|
|
399
|
+
const digitStart = this.pos;
|
|
386
400
|
while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
|
|
387
|
-
value += this.input[this.pos];
|
|
388
401
|
this.pos++;
|
|
389
402
|
this.column++;
|
|
390
403
|
}
|
|
404
|
+
if (digitStart < this.pos) {
|
|
405
|
+
segments.push(this.input.slice(digitStart, this.pos));
|
|
406
|
+
}
|
|
391
407
|
// Handle scientific notation for numbers starting with . (e.g., .5e10)
|
|
392
408
|
if (this.input[this.pos] === "e" || this.input[this.pos] === "E") {
|
|
393
409
|
const nextChar = this.input[this.pos + 1];
|
|
394
410
|
if (this.isDigit(nextChar) ||
|
|
395
411
|
((nextChar === "+" || nextChar === "-") && this.isDigit(this.input[this.pos + 2]))) {
|
|
396
|
-
|
|
412
|
+
segments.push(this.input[this.pos]); // 'e' or 'E'
|
|
397
413
|
this.pos++;
|
|
398
414
|
this.column++;
|
|
399
415
|
if (this.input[this.pos] === "+" || this.input[this.pos] === "-") {
|
|
400
|
-
|
|
416
|
+
segments.push(this.input[this.pos]);
|
|
401
417
|
this.pos++;
|
|
402
418
|
this.column++;
|
|
403
419
|
}
|
|
420
|
+
const expStart = this.pos;
|
|
404
421
|
while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
|
|
405
|
-
value += this.input[this.pos];
|
|
406
422
|
this.pos++;
|
|
407
423
|
this.column++;
|
|
408
424
|
}
|
|
425
|
+
if (expStart < this.pos) {
|
|
426
|
+
segments.push(this.input.slice(expStart, this.pos));
|
|
427
|
+
}
|
|
409
428
|
}
|
|
410
429
|
}
|
|
411
|
-
return { type: "NUMBER", value, position: startPos, line: startLine, column: startColumn };
|
|
430
|
+
return { type: "NUMBER", value: segments.join(""), position: startPos, line: startLine, column: startColumn };
|
|
412
431
|
}
|
|
413
432
|
// Check for hexadecimal: 0x or 0X
|
|
414
433
|
if (this.input[this.pos] === "0" && (this.input[this.pos + 1] === "x" || this.input[this.pos + 1] === "X")) {
|
|
415
|
-
|
|
434
|
+
segments.push("0x");
|
|
416
435
|
this.pos += 2;
|
|
417
436
|
this.column += 2;
|
|
418
437
|
// Must have at least one hex digit
|
|
@@ -420,33 +439,40 @@ class Tokenizer {
|
|
|
420
439
|
throw new SyntaxError(`Invalid hexadecimal integer: incomplete hex literal`);
|
|
421
440
|
}
|
|
422
441
|
// Read hex digits
|
|
442
|
+
const hexStart = this.pos;
|
|
423
443
|
while (this.pos < this.input.length && this.isHexDigit(this.input[this.pos])) {
|
|
424
|
-
value += this.input[this.pos];
|
|
425
444
|
this.pos++;
|
|
426
445
|
this.column++;
|
|
427
446
|
}
|
|
447
|
+
segments.push(this.input.slice(hexStart, this.pos));
|
|
428
448
|
// Check for invalid characters immediately following (e.g., 0x1g)
|
|
429
449
|
if (this.pos < this.input.length && this.isIdentifierChar(this.input[this.pos])) {
|
|
430
450
|
throw new SyntaxError(`Invalid hexadecimal integer: invalid character '${this.input[this.pos]}'`);
|
|
431
451
|
}
|
|
432
|
-
return { type: "NUMBER", value, position: startPos, line: startLine, column: startColumn };
|
|
452
|
+
return { type: "NUMBER", value: segments.join(""), position: startPos, line: startLine, column: startColumn };
|
|
433
453
|
}
|
|
454
|
+
const intStart = this.pos;
|
|
434
455
|
while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
|
|
435
|
-
value += this.input[this.pos];
|
|
436
456
|
this.pos++;
|
|
437
457
|
this.column++;
|
|
438
458
|
}
|
|
459
|
+
if (intStart < this.pos) {
|
|
460
|
+
segments.push(this.input.slice(intStart, this.pos));
|
|
461
|
+
}
|
|
439
462
|
// Only read decimal part if . is followed by a digit (not another .)
|
|
440
463
|
// This prevents "1..2" from being tokenized as "1." + "." + "2"
|
|
441
464
|
if (this.input[this.pos] === "." && this.isDigit(this.input[this.pos + 1])) {
|
|
442
|
-
|
|
465
|
+
segments.push(".");
|
|
443
466
|
this.pos++;
|
|
444
467
|
this.column++;
|
|
468
|
+
const fracStart = this.pos;
|
|
445
469
|
while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
|
|
446
|
-
value += this.input[this.pos];
|
|
447
470
|
this.pos++;
|
|
448
471
|
this.column++;
|
|
449
472
|
}
|
|
473
|
+
if (fracStart < this.pos) {
|
|
474
|
+
segments.push(this.input.slice(fracStart, this.pos));
|
|
475
|
+
}
|
|
450
476
|
}
|
|
451
477
|
// Handle scientific notation (e.g., 1e9, 1E-9, 1.5e+10)
|
|
452
478
|
if (this.input[this.pos] === "e" || this.input[this.pos] === "E") {
|
|
@@ -454,24 +480,27 @@ class Tokenizer {
|
|
|
454
480
|
// Check if followed by digit, + digit, or - digit
|
|
455
481
|
if (this.isDigit(nextChar) ||
|
|
456
482
|
((nextChar === "+" || nextChar === "-") && this.isDigit(this.input[this.pos + 2]))) {
|
|
457
|
-
|
|
483
|
+
segments.push(this.input[this.pos]); // 'e' or 'E'
|
|
458
484
|
this.pos++;
|
|
459
485
|
this.column++;
|
|
460
486
|
// Handle optional sign
|
|
461
487
|
if (this.input[this.pos] === "+" || this.input[this.pos] === "-") {
|
|
462
|
-
|
|
488
|
+
segments.push(this.input[this.pos]);
|
|
463
489
|
this.pos++;
|
|
464
490
|
this.column++;
|
|
465
491
|
}
|
|
466
492
|
// Read exponent digits
|
|
493
|
+
const expStart = this.pos;
|
|
467
494
|
while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
|
|
468
|
-
value += this.input[this.pos];
|
|
469
495
|
this.pos++;
|
|
470
496
|
this.column++;
|
|
471
497
|
}
|
|
498
|
+
if (expStart < this.pos) {
|
|
499
|
+
segments.push(this.input.slice(expStart, this.pos));
|
|
500
|
+
}
|
|
472
501
|
}
|
|
473
502
|
}
|
|
474
|
-
return { type: "NUMBER", value, position: startPos, line: startLine, column: startColumn };
|
|
503
|
+
return { type: "NUMBER", value: segments.join(""), position: startPos, line: startLine, column: startColumn };
|
|
475
504
|
}
|
|
476
505
|
isHexDigit(char) {
|
|
477
506
|
return (char >= "0" && char <= "9") ||
|
|
@@ -479,38 +508,45 @@ class Tokenizer {
|
|
|
479
508
|
(char >= "A" && char <= "F");
|
|
480
509
|
}
|
|
481
510
|
readIdentifier() {
|
|
482
|
-
|
|
511
|
+
const startPos = this.pos;
|
|
483
512
|
while (this.pos < this.input.length && this.isIdentifierChar(this.input[this.pos])) {
|
|
484
|
-
value += this.input[this.pos];
|
|
485
513
|
this.pos++;
|
|
486
514
|
this.column++;
|
|
487
515
|
}
|
|
488
|
-
return
|
|
516
|
+
return this.input.slice(startPos, this.pos);
|
|
489
517
|
}
|
|
490
518
|
readBacktickIdentifier(startPos, startLine, startColumn) {
|
|
491
519
|
this.pos++; // consume opening backtick
|
|
492
520
|
this.column++;
|
|
493
|
-
|
|
521
|
+
const segments = [];
|
|
522
|
+
let segmentStart = this.pos;
|
|
494
523
|
while (this.pos < this.input.length) {
|
|
495
524
|
const char = this.input[this.pos];
|
|
496
525
|
if (char === "`") {
|
|
497
526
|
// Check for escaped backtick (double backtick)
|
|
498
527
|
if (this.input[this.pos + 1] === "`") {
|
|
499
|
-
|
|
528
|
+
// Add segment before escape
|
|
529
|
+
if (segmentStart < this.pos) {
|
|
530
|
+
segments.push(this.input.slice(segmentStart, this.pos));
|
|
531
|
+
}
|
|
532
|
+
segments.push("`");
|
|
500
533
|
this.pos += 2;
|
|
501
534
|
this.column += 2;
|
|
535
|
+
segmentStart = this.pos;
|
|
502
536
|
continue;
|
|
503
537
|
}
|
|
504
538
|
// End of identifier
|
|
539
|
+
if (segmentStart < this.pos) {
|
|
540
|
+
segments.push(this.input.slice(segmentStart, this.pos));
|
|
541
|
+
}
|
|
505
542
|
this.pos++;
|
|
506
543
|
this.column++;
|
|
507
|
-
return { type: "IDENTIFIER", value, position: startPos, line: startLine, column: startColumn };
|
|
544
|
+
return { type: "IDENTIFIER", value: segments.join(""), position: startPos, line: startLine, column: startColumn };
|
|
508
545
|
}
|
|
509
546
|
if (char === "\n") {
|
|
510
547
|
this.line++;
|
|
511
548
|
this.column = 0; // Will be incremented below
|
|
512
549
|
}
|
|
513
|
-
value += char;
|
|
514
550
|
this.pos++;
|
|
515
551
|
this.column++;
|
|
516
552
|
}
|
|
@@ -684,6 +720,8 @@ export class Parser {
|
|
|
684
720
|
return this.parseUnwind();
|
|
685
721
|
case "CALL":
|
|
686
722
|
return this.parseCall();
|
|
723
|
+
case "FOREACH":
|
|
724
|
+
return this.parseForeach();
|
|
687
725
|
default:
|
|
688
726
|
throw new Error(`Unexpected keyword '${token.value}'`);
|
|
689
727
|
}
|
|
@@ -764,13 +802,28 @@ export class Parser {
|
|
|
764
802
|
const savedPos = this.pos;
|
|
765
803
|
const identifier = this.advance().value;
|
|
766
804
|
if (this.check("EQUALS")) {
|
|
767
|
-
// This is a path expression: p = (a)-[r]->(b)
|
|
805
|
+
// This is a path expression: p = (a)-[r]->(b) or p = shortestPath(...)
|
|
768
806
|
this.advance(); // consume "="
|
|
807
|
+
// Check for path function: shortestPath() or allShortestPaths()
|
|
808
|
+
let pathFunction;
|
|
809
|
+
if (this.check("IDENTIFIER")) {
|
|
810
|
+
const funcName = this.peek().value.toLowerCase();
|
|
811
|
+
if (funcName === "shortestpath" || funcName === "allshortestpaths") {
|
|
812
|
+
pathFunction = funcName === "shortestpath" ? "shortestPath" : "allShortestPaths";
|
|
813
|
+
this.advance(); // consume function name
|
|
814
|
+
this.expect("LPAREN"); // consume opening paren
|
|
815
|
+
}
|
|
816
|
+
}
|
|
769
817
|
const patterns = this.parsePatternChain();
|
|
818
|
+
// If we had a path function, consume the closing paren
|
|
819
|
+
if (pathFunction) {
|
|
820
|
+
this.expect("RPAREN");
|
|
821
|
+
}
|
|
770
822
|
return {
|
|
771
823
|
type: "path",
|
|
772
824
|
variable: identifier,
|
|
773
|
-
patterns
|
|
825
|
+
patterns,
|
|
826
|
+
...(pathFunction && { pathFunction })
|
|
774
827
|
};
|
|
775
828
|
}
|
|
776
829
|
else {
|
|
@@ -1280,6 +1333,53 @@ export class Parser {
|
|
|
1280
1333
|
}
|
|
1281
1334
|
return { type: "CALL", procedure: procedureName, args, yields, where };
|
|
1282
1335
|
}
|
|
1336
|
+
parseForeach() {
|
|
1337
|
+
this.expect("KEYWORD", "FOREACH");
|
|
1338
|
+
this.expect("LPAREN");
|
|
1339
|
+
// Parse iteration variable
|
|
1340
|
+
const variable = this.expectIdentifier();
|
|
1341
|
+
// Expect IN keyword
|
|
1342
|
+
this.expect("KEYWORD", "IN");
|
|
1343
|
+
// Parse the list expression
|
|
1344
|
+
const expression = this.parseExpression();
|
|
1345
|
+
// Expect PIPE separator
|
|
1346
|
+
this.expect("PIPE");
|
|
1347
|
+
// Parse body clauses until we hit the closing paren
|
|
1348
|
+
const body = [];
|
|
1349
|
+
while (!this.check("RPAREN")) {
|
|
1350
|
+
const clause = this.parseForeachBodyClause();
|
|
1351
|
+
if (clause) {
|
|
1352
|
+
body.push(clause);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
this.expect("RPAREN");
|
|
1356
|
+
return { type: "FOREACH", variable, expression, body };
|
|
1357
|
+
}
|
|
1358
|
+
parseForeachBodyClause() {
|
|
1359
|
+
const token = this.peek();
|
|
1360
|
+
if (token.type === "RPAREN" || token.type === "EOF")
|
|
1361
|
+
return null;
|
|
1362
|
+
if (token.type !== "KEYWORD") {
|
|
1363
|
+
throw new Error(`Unexpected token '${token.value}' in FOREACH body, expected SET, CREATE, DELETE, MERGE, or FOREACH`);
|
|
1364
|
+
}
|
|
1365
|
+
switch (token.value) {
|
|
1366
|
+
case "SET":
|
|
1367
|
+
return this.parseSet();
|
|
1368
|
+
case "CREATE":
|
|
1369
|
+
return this.parseCreate();
|
|
1370
|
+
case "DELETE":
|
|
1371
|
+
case "DETACH":
|
|
1372
|
+
return this.parseDelete();
|
|
1373
|
+
case "MERGE":
|
|
1374
|
+
return this.parseMerge();
|
|
1375
|
+
case "REMOVE":
|
|
1376
|
+
return this.parseRemove();
|
|
1377
|
+
case "FOREACH":
|
|
1378
|
+
return this.parseForeach();
|
|
1379
|
+
default:
|
|
1380
|
+
throw new Error(`Unsupported clause '${token.value}' in FOREACH body. Only SET, CREATE, DELETE, MERGE, REMOVE, and FOREACH are allowed.`);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1283
1383
|
/**
|
|
1284
1384
|
* Parse a pattern, which can be a single node or a chain of relationships.
|
|
1285
1385
|
* For chained patterns like (a)-[:R1]->(b)-[:R2]->(c), this returns multiple
|
|
@@ -1815,6 +1915,12 @@ export class Parser {
|
|
|
1815
1915
|
const listExpr = this.parseInListExpression();
|
|
1816
1916
|
return { type: "in", left, list: listExpr };
|
|
1817
1917
|
}
|
|
1918
|
+
// Check for regex operator =~
|
|
1919
|
+
if (this.check("REGEX_MATCH")) {
|
|
1920
|
+
this.advance();
|
|
1921
|
+
const right = this.parseExpression();
|
|
1922
|
+
return { type: "regex", left, right };
|
|
1923
|
+
}
|
|
1818
1924
|
// Comparison operators - handle chained comparisons like 1 < n.num < 3
|
|
1819
1925
|
const opToken = this.peek();
|
|
1820
1926
|
let operator;
|
|
@@ -2310,6 +2416,15 @@ export class Parser {
|
|
|
2310
2416
|
if (nextToken && nextToken.type === "LPAREN") {
|
|
2311
2417
|
const functionName = this.advance().value.toUpperCase();
|
|
2312
2418
|
this.advance(); // LPAREN
|
|
2419
|
+
// Check if this is EXISTS with a pattern
|
|
2420
|
+
if (functionName === "EXISTS") {
|
|
2421
|
+
// EXISTS((pattern)) - check if next token is LPAREN (start of pattern)
|
|
2422
|
+
if (this.check("LPAREN") && this.isPatternStart()) {
|
|
2423
|
+
const patterns = this.parsePatternChain();
|
|
2424
|
+
this.expect("RPAREN");
|
|
2425
|
+
return { type: "existsPattern", patterns };
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2313
2428
|
// Check if this is a list predicate: ALL, ANY, NONE, SINGLE
|
|
2314
2429
|
const listPredicates = ["ALL", "ANY", "NONE", "SINGLE"];
|
|
2315
2430
|
if (listPredicates.includes(functionName)) {
|
|
@@ -2329,6 +2444,80 @@ export class Parser {
|
|
|
2329
2444
|
}
|
|
2330
2445
|
}
|
|
2331
2446
|
}
|
|
2447
|
+
// Check if this is a REDUCE expression: reduce(acc = init, x IN list | expr)
|
|
2448
|
+
if (functionName === "REDUCE") {
|
|
2449
|
+
// Parse accumulator = initialValue
|
|
2450
|
+
const accumulatorToken = this.expect("IDENTIFIER");
|
|
2451
|
+
this.expect("EQUALS");
|
|
2452
|
+
const initialValue = this.parseExpression();
|
|
2453
|
+
// Expect COMMA
|
|
2454
|
+
this.expect("COMMA");
|
|
2455
|
+
// Parse variable IN listExpr
|
|
2456
|
+
const iteratorToken = this.expect("IDENTIFIER");
|
|
2457
|
+
if (!this.checkKeyword("IN")) {
|
|
2458
|
+
throw new Error("Expected IN keyword in reduce()");
|
|
2459
|
+
}
|
|
2460
|
+
this.advance(); // consume IN
|
|
2461
|
+
const listExpr = this.parseExpression();
|
|
2462
|
+
// Expect PIPE
|
|
2463
|
+
this.expect("PIPE");
|
|
2464
|
+
// Parse the reduce expression
|
|
2465
|
+
const reduceExpr = this.parseExpression();
|
|
2466
|
+
this.expect("RPAREN");
|
|
2467
|
+
return {
|
|
2468
|
+
type: "reduce",
|
|
2469
|
+
accumulator: accumulatorToken.value,
|
|
2470
|
+
initialValue,
|
|
2471
|
+
variable: iteratorToken.value,
|
|
2472
|
+
listExpr,
|
|
2473
|
+
reduceExpr,
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
// Check if this is a FILTER expression: filter(x IN list WHERE predicate)
|
|
2477
|
+
if (functionName === "FILTER") {
|
|
2478
|
+
// Parse variable IN listExpr
|
|
2479
|
+
const iteratorToken = this.expect("IDENTIFIER");
|
|
2480
|
+
if (!this.checkKeyword("IN")) {
|
|
2481
|
+
throw new Error("Expected IN keyword in filter()");
|
|
2482
|
+
}
|
|
2483
|
+
this.advance(); // consume IN
|
|
2484
|
+
const listExpr = this.parseExpression();
|
|
2485
|
+
// Expect WHERE
|
|
2486
|
+
if (!this.checkKeyword("WHERE")) {
|
|
2487
|
+
throw new Error("Expected WHERE keyword in filter()");
|
|
2488
|
+
}
|
|
2489
|
+
this.advance(); // consume WHERE
|
|
2490
|
+
// Parse the filter predicate
|
|
2491
|
+
const filterCondition = this.parseWhereCondition();
|
|
2492
|
+
this.expect("RPAREN");
|
|
2493
|
+
return {
|
|
2494
|
+
type: "filter",
|
|
2495
|
+
variable: iteratorToken.value,
|
|
2496
|
+
listExpr,
|
|
2497
|
+
filterCondition,
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
// Check if this is an EXTRACT expression: extract(x IN list | expr)
|
|
2501
|
+
if (functionName === "EXTRACT") {
|
|
2502
|
+
// Parse variable IN listExpr
|
|
2503
|
+
const iteratorToken = this.expect("IDENTIFIER");
|
|
2504
|
+
if (!this.checkKeyword("IN")) {
|
|
2505
|
+
throw new Error("Expected IN keyword in extract()");
|
|
2506
|
+
}
|
|
2507
|
+
this.advance(); // consume IN
|
|
2508
|
+
const listExpr = this.parseExpression();
|
|
2509
|
+
// Expect PIPE
|
|
2510
|
+
this.expect("PIPE");
|
|
2511
|
+
// Parse the map expression
|
|
2512
|
+
const mapExpr = this.parseExpression();
|
|
2513
|
+
this.expect("RPAREN");
|
|
2514
|
+
return {
|
|
2515
|
+
type: "extract",
|
|
2516
|
+
variable: iteratorToken.value,
|
|
2517
|
+
listExpr,
|
|
2518
|
+
mapExpr,
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2332
2521
|
const args = [];
|
|
2333
2522
|
// Check for DISTINCT keyword after opening paren (for aggregation functions)
|
|
2334
2523
|
let distinct;
|