flowquery 1.0.17 → 1.0.20
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/.gitattributes +3 -0
- package/.github/workflows/python-publish.yml +58 -7
- package/.github/workflows/release.yml +25 -18
- package/README.md +37 -32
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/data.d.ts.map +1 -1
- package/dist/graph/data.js +5 -3
- package/dist/graph/data.js.map +1 -1
- package/dist/graph/pattern.d.ts.map +1 -1
- package/dist/graph/pattern.js +11 -4
- package/dist/graph/pattern.js.map +1 -1
- package/dist/graph/pattern_expression.d.ts +1 -0
- package/dist/graph/pattern_expression.d.ts.map +1 -1
- package/dist/graph/pattern_expression.js +14 -3
- package/dist/graph/pattern_expression.js.map +1 -1
- package/dist/graph/relationship.d.ts.map +1 -1
- package/dist/graph/relationship.js +11 -4
- package/dist/graph/relationship.js.map +1 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/parsing/parser.d.ts +5 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +94 -73
- package/dist/parsing/parser.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/CONTRIBUTING.md +127 -0
- package/flowquery-py/README.md +13 -112
- package/flowquery-py/misc/data/test.json +10 -0
- package/flowquery-py/misc/data/users.json +242 -0
- package/flowquery-py/notebooks/TestFlowQuery.ipynb +440 -0
- package/flowquery-py/pyproject.toml +4 -1
- package/flowquery-py/src/__init__.py +2 -0
- package/flowquery-py/src/graph/data.py +4 -3
- package/flowquery-py/src/graph/pattern.py +7 -4
- package/flowquery-py/src/graph/pattern_expression.py +6 -3
- package/flowquery-py/src/graph/relationship.py +7 -0
- package/flowquery-py/src/io/command_line.py +44 -2
- package/flowquery-py/src/parsing/base_parser.py +2 -2
- package/flowquery-py/src/parsing/operations/load.py +6 -0
- package/flowquery-py/src/parsing/parser.py +78 -62
- package/flowquery-py/src/tokenization/token.py +122 -176
- package/flowquery-py/src/tokenization/tokenizer.py +4 -4
- package/flowquery-py/tests/compute/test_runner.py +10 -7
- package/flowquery-py/tests/parsing/test_parser.py +6 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/data.ts +5 -3
- package/src/graph/pattern.ts +13 -4
- package/src/graph/pattern_expression.ts +14 -3
- package/src/graph/relationship.ts +8 -0
- package/src/index.ts +5 -6
- package/src/parsing/parser.ts +93 -69
- package/tests/compute/runner.test.ts +71 -79
- package/tests/parsing/parser.test.ts +8 -0
|
@@ -21,7 +21,7 @@ class Token:
|
|
|
21
21
|
and an optional value.
|
|
22
22
|
|
|
23
23
|
Example:
|
|
24
|
-
with_token = Token.WITH
|
|
24
|
+
with_token = Token.WITH()
|
|
25
25
|
ident_token = Token.IDENTIFIER("myVar")
|
|
26
26
|
num_token = Token.NUMBER("42")
|
|
27
27
|
"""
|
|
@@ -153,73 +153,64 @@ class Token:
|
|
|
153
153
|
|
|
154
154
|
# Symbol tokens
|
|
155
155
|
|
|
156
|
-
@
|
|
157
|
-
|
|
158
|
-
def LEFT_PARENTHESIS(cls) -> Token:
|
|
156
|
+
@staticmethod
|
|
157
|
+
def LEFT_PARENTHESIS() -> Token:
|
|
159
158
|
return Token(TokenType.SYMBOL, Symbol.LEFT_PARENTHESIS.value)
|
|
160
159
|
|
|
161
160
|
def is_left_parenthesis(self) -> bool:
|
|
162
161
|
return self._type == TokenType.SYMBOL and self._value == Symbol.LEFT_PARENTHESIS.value
|
|
163
162
|
|
|
164
|
-
@
|
|
165
|
-
|
|
166
|
-
def RIGHT_PARENTHESIS(cls) -> Token:
|
|
163
|
+
@staticmethod
|
|
164
|
+
def RIGHT_PARENTHESIS() -> Token:
|
|
167
165
|
return Token(TokenType.SYMBOL, Symbol.RIGHT_PARENTHESIS.value)
|
|
168
166
|
|
|
169
167
|
def is_right_parenthesis(self) -> bool:
|
|
170
168
|
return self._type == TokenType.SYMBOL and self._value == Symbol.RIGHT_PARENTHESIS.value
|
|
171
169
|
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
def COMMA(cls) -> Token:
|
|
170
|
+
@staticmethod
|
|
171
|
+
def COMMA() -> Token:
|
|
175
172
|
return Token(TokenType.SYMBOL, Symbol.COMMA.value)
|
|
176
173
|
|
|
177
174
|
def is_comma(self) -> bool:
|
|
178
175
|
return self._type == TokenType.SYMBOL and self._value == Symbol.COMMA.value
|
|
179
176
|
|
|
180
|
-
@
|
|
181
|
-
|
|
182
|
-
def DOT(cls) -> Token:
|
|
177
|
+
@staticmethod
|
|
178
|
+
def DOT() -> Token:
|
|
183
179
|
return Token(TokenType.SYMBOL, Symbol.DOT.value)
|
|
184
180
|
|
|
185
181
|
def is_dot(self) -> bool:
|
|
186
182
|
return self._type == TokenType.SYMBOL and self._value == Symbol.DOT.value
|
|
187
183
|
|
|
188
|
-
@
|
|
189
|
-
|
|
190
|
-
def COLON(cls) -> Token:
|
|
184
|
+
@staticmethod
|
|
185
|
+
def COLON() -> Token:
|
|
191
186
|
return Token(TokenType.SYMBOL, Symbol.COLON.value)
|
|
192
187
|
|
|
193
188
|
def is_colon(self) -> bool:
|
|
194
189
|
return self._type == TokenType.SYMBOL and self._value == Symbol.COLON.value
|
|
195
190
|
|
|
196
|
-
@
|
|
197
|
-
|
|
198
|
-
def OPENING_BRACE(cls) -> Token:
|
|
191
|
+
@staticmethod
|
|
192
|
+
def OPENING_BRACE() -> Token:
|
|
199
193
|
return Token(TokenType.SYMBOL, Symbol.OPENING_BRACE.value)
|
|
200
194
|
|
|
201
195
|
def is_opening_brace(self) -> bool:
|
|
202
196
|
return self._type == TokenType.SYMBOL and self._value == Symbol.OPENING_BRACE.value
|
|
203
197
|
|
|
204
|
-
@
|
|
205
|
-
|
|
206
|
-
def CLOSING_BRACE(cls) -> Token:
|
|
198
|
+
@staticmethod
|
|
199
|
+
def CLOSING_BRACE() -> Token:
|
|
207
200
|
return Token(TokenType.SYMBOL, Symbol.CLOSING_BRACE.value)
|
|
208
201
|
|
|
209
202
|
def is_closing_brace(self) -> bool:
|
|
210
203
|
return self._type == TokenType.SYMBOL and self._value == Symbol.CLOSING_BRACE.value
|
|
211
204
|
|
|
212
|
-
@
|
|
213
|
-
|
|
214
|
-
def OPENING_BRACKET(cls) -> Token:
|
|
205
|
+
@staticmethod
|
|
206
|
+
def OPENING_BRACKET() -> Token:
|
|
215
207
|
return Token(TokenType.SYMBOL, Symbol.OPENING_BRACKET.value)
|
|
216
208
|
|
|
217
209
|
def is_opening_bracket(self) -> bool:
|
|
218
210
|
return self._type == TokenType.SYMBOL and self._value == Symbol.OPENING_BRACKET.value
|
|
219
211
|
|
|
220
|
-
@
|
|
221
|
-
|
|
222
|
-
def CLOSING_BRACKET(cls) -> Token:
|
|
212
|
+
@staticmethod
|
|
213
|
+
def CLOSING_BRACKET() -> Token:
|
|
223
214
|
return Token(TokenType.SYMBOL, Symbol.CLOSING_BRACKET.value)
|
|
224
215
|
|
|
225
216
|
def is_closing_bracket(self) -> bool:
|
|
@@ -227,9 +218,8 @@ class Token:
|
|
|
227
218
|
|
|
228
219
|
# Whitespace token
|
|
229
220
|
|
|
230
|
-
@
|
|
231
|
-
|
|
232
|
-
def WHITESPACE(cls) -> Token:
|
|
221
|
+
@staticmethod
|
|
222
|
+
def WHITESPACE() -> Token:
|
|
233
223
|
return Token(TokenType.WHITESPACE)
|
|
234
224
|
|
|
235
225
|
def is_whitespace(self) -> bool:
|
|
@@ -243,17 +233,15 @@ class Token:
|
|
|
243
233
|
def is_unary_operator(self) -> bool:
|
|
244
234
|
return self._type == TokenType.UNARY_OPERATOR
|
|
245
235
|
|
|
246
|
-
@
|
|
247
|
-
|
|
248
|
-
def ADD(cls) -> Token:
|
|
236
|
+
@staticmethod
|
|
237
|
+
def ADD() -> Token:
|
|
249
238
|
return Token(TokenType.OPERATOR, Operator.ADD.value)
|
|
250
239
|
|
|
251
240
|
def is_add(self) -> bool:
|
|
252
241
|
return self._type == TokenType.OPERATOR and self._value == Operator.ADD.value
|
|
253
242
|
|
|
254
|
-
@
|
|
255
|
-
|
|
256
|
-
def SUBTRACT(cls) -> Token:
|
|
243
|
+
@staticmethod
|
|
244
|
+
def SUBTRACT() -> Token:
|
|
257
245
|
return Token(TokenType.OPERATOR, Operator.SUBTRACT.value)
|
|
258
246
|
|
|
259
247
|
def is_subtract(self) -> bool:
|
|
@@ -262,113 +250,99 @@ class Token:
|
|
|
262
250
|
def is_negation(self) -> bool:
|
|
263
251
|
return self.is_subtract()
|
|
264
252
|
|
|
265
|
-
@
|
|
266
|
-
|
|
267
|
-
def MULTIPLY(cls) -> Token:
|
|
253
|
+
@staticmethod
|
|
254
|
+
def MULTIPLY() -> Token:
|
|
268
255
|
return Token(TokenType.OPERATOR, Operator.MULTIPLY.value)
|
|
269
256
|
|
|
270
257
|
def is_multiply(self) -> bool:
|
|
271
258
|
return self._type == TokenType.OPERATOR and self._value == Operator.MULTIPLY.value
|
|
272
259
|
|
|
273
|
-
@
|
|
274
|
-
|
|
275
|
-
def DIVIDE(cls) -> Token:
|
|
260
|
+
@staticmethod
|
|
261
|
+
def DIVIDE() -> Token:
|
|
276
262
|
return Token(TokenType.OPERATOR, Operator.DIVIDE.value)
|
|
277
263
|
|
|
278
264
|
def is_divide(self) -> bool:
|
|
279
265
|
return self._type == TokenType.OPERATOR and self._value == Operator.DIVIDE.value
|
|
280
266
|
|
|
281
|
-
@
|
|
282
|
-
|
|
283
|
-
def EXPONENT(cls) -> Token:
|
|
267
|
+
@staticmethod
|
|
268
|
+
def EXPONENT() -> Token:
|
|
284
269
|
return Token(TokenType.OPERATOR, Operator.EXPONENT.value)
|
|
285
270
|
|
|
286
271
|
def is_exponent(self) -> bool:
|
|
287
272
|
return self._type == TokenType.OPERATOR and self._value == Operator.EXPONENT.value
|
|
288
273
|
|
|
289
|
-
@
|
|
290
|
-
|
|
291
|
-
def MODULO(cls) -> Token:
|
|
274
|
+
@staticmethod
|
|
275
|
+
def MODULO() -> Token:
|
|
292
276
|
return Token(TokenType.OPERATOR, Operator.MODULO.value)
|
|
293
277
|
|
|
294
278
|
def is_modulo(self) -> bool:
|
|
295
279
|
return self._type == TokenType.OPERATOR and self._value == Operator.MODULO.value
|
|
296
280
|
|
|
297
|
-
@
|
|
298
|
-
|
|
299
|
-
def EQUALS(cls) -> Token:
|
|
281
|
+
@staticmethod
|
|
282
|
+
def EQUALS() -> Token:
|
|
300
283
|
return Token(TokenType.OPERATOR, Operator.EQUALS.value)
|
|
301
284
|
|
|
302
285
|
def is_equals(self) -> bool:
|
|
303
286
|
return self._type == TokenType.OPERATOR and self._value == Operator.EQUALS.value
|
|
304
287
|
|
|
305
|
-
@
|
|
306
|
-
|
|
307
|
-
def NOT_EQUALS(cls) -> Token:
|
|
288
|
+
@staticmethod
|
|
289
|
+
def NOT_EQUALS() -> Token:
|
|
308
290
|
return Token(TokenType.OPERATOR, Operator.NOT_EQUALS.value)
|
|
309
291
|
|
|
310
292
|
def is_not_equals(self) -> bool:
|
|
311
293
|
return self._type == TokenType.OPERATOR and self._value == Operator.NOT_EQUALS.value
|
|
312
294
|
|
|
313
|
-
@
|
|
314
|
-
|
|
315
|
-
def LESS_THAN(cls) -> Token:
|
|
295
|
+
@staticmethod
|
|
296
|
+
def LESS_THAN() -> Token:
|
|
316
297
|
return Token(TokenType.OPERATOR, Operator.LESS_THAN.value)
|
|
317
298
|
|
|
318
299
|
def is_less_than(self) -> bool:
|
|
319
300
|
return self._type == TokenType.OPERATOR and self._value == Operator.LESS_THAN.value
|
|
320
301
|
|
|
321
|
-
@
|
|
322
|
-
|
|
323
|
-
def LESS_THAN_OR_EQUAL(cls) -> Token:
|
|
302
|
+
@staticmethod
|
|
303
|
+
def LESS_THAN_OR_EQUAL() -> Token:
|
|
324
304
|
return Token(TokenType.OPERATOR, Operator.LESS_THAN_OR_EQUAL.value)
|
|
325
305
|
|
|
326
306
|
def is_less_than_or_equal(self) -> bool:
|
|
327
307
|
return self._type == TokenType.OPERATOR and self._value == Operator.LESS_THAN_OR_EQUAL.value
|
|
328
308
|
|
|
329
|
-
@
|
|
330
|
-
|
|
331
|
-
def GREATER_THAN(cls) -> Token:
|
|
309
|
+
@staticmethod
|
|
310
|
+
def GREATER_THAN() -> Token:
|
|
332
311
|
return Token(TokenType.OPERATOR, Operator.GREATER_THAN.value)
|
|
333
312
|
|
|
334
313
|
def is_greater_than(self) -> bool:
|
|
335
314
|
return self._type == TokenType.OPERATOR and self._value == Operator.GREATER_THAN.value
|
|
336
315
|
|
|
337
|
-
@
|
|
338
|
-
|
|
339
|
-
def GREATER_THAN_OR_EQUAL(cls) -> Token:
|
|
316
|
+
@staticmethod
|
|
317
|
+
def GREATER_THAN_OR_EQUAL() -> Token:
|
|
340
318
|
return Token(TokenType.OPERATOR, Operator.GREATER_THAN_OR_EQUAL.value)
|
|
341
319
|
|
|
342
320
|
def is_greater_than_or_equal(self) -> bool:
|
|
343
321
|
return self._type == TokenType.OPERATOR and self._value == Operator.GREATER_THAN_OR_EQUAL.value
|
|
344
322
|
|
|
345
|
-
@
|
|
346
|
-
|
|
347
|
-
def AND(cls) -> Token:
|
|
323
|
+
@staticmethod
|
|
324
|
+
def AND() -> Token:
|
|
348
325
|
return Token(TokenType.OPERATOR, Operator.AND.value)
|
|
349
326
|
|
|
350
327
|
def is_and(self) -> bool:
|
|
351
328
|
return self._type == TokenType.OPERATOR and self._value == Operator.AND.value
|
|
352
329
|
|
|
353
|
-
@
|
|
354
|
-
|
|
355
|
-
def OR(cls) -> Token:
|
|
330
|
+
@staticmethod
|
|
331
|
+
def OR() -> Token:
|
|
356
332
|
return Token(TokenType.OPERATOR, Operator.OR.value)
|
|
357
333
|
|
|
358
334
|
def is_or(self) -> bool:
|
|
359
335
|
return self._type == TokenType.OPERATOR and self._value == Operator.OR.value
|
|
360
336
|
|
|
361
|
-
@
|
|
362
|
-
|
|
363
|
-
def NOT(cls) -> Token:
|
|
337
|
+
@staticmethod
|
|
338
|
+
def NOT() -> Token:
|
|
364
339
|
return Token(TokenType.UNARY_OPERATOR, Operator.NOT.value)
|
|
365
340
|
|
|
366
341
|
def is_not(self) -> bool:
|
|
367
342
|
return self._type == TokenType.UNARY_OPERATOR and self._value == Operator.NOT.value
|
|
368
343
|
|
|
369
|
-
@
|
|
370
|
-
|
|
371
|
-
def IS(cls) -> Token:
|
|
344
|
+
@staticmethod
|
|
345
|
+
def IS() -> Token:
|
|
372
346
|
return Token(TokenType.OPERATOR, Operator.IS.value)
|
|
373
347
|
|
|
374
348
|
def is_is(self) -> bool:
|
|
@@ -379,249 +353,218 @@ class Token:
|
|
|
379
353
|
def is_keyword(self) -> bool:
|
|
380
354
|
return self._type == TokenType.KEYWORD
|
|
381
355
|
|
|
382
|
-
@
|
|
383
|
-
|
|
384
|
-
def WITH(cls) -> Token:
|
|
356
|
+
@staticmethod
|
|
357
|
+
def WITH() -> Token:
|
|
385
358
|
return Token(TokenType.KEYWORD, Keyword.WITH.value)
|
|
386
359
|
|
|
387
360
|
def is_with(self) -> bool:
|
|
388
361
|
return self._type == TokenType.KEYWORD and self._value == Keyword.WITH.value
|
|
389
362
|
|
|
390
|
-
@
|
|
391
|
-
|
|
392
|
-
def RETURN(cls) -> Token:
|
|
363
|
+
@staticmethod
|
|
364
|
+
def RETURN() -> Token:
|
|
393
365
|
return Token(TokenType.KEYWORD, Keyword.RETURN.value)
|
|
394
366
|
|
|
395
367
|
def is_return(self) -> bool:
|
|
396
368
|
return self._type == TokenType.KEYWORD and self._value == Keyword.RETURN.value
|
|
397
369
|
|
|
398
|
-
@
|
|
399
|
-
|
|
400
|
-
def LOAD(cls) -> Token:
|
|
370
|
+
@staticmethod
|
|
371
|
+
def LOAD() -> Token:
|
|
401
372
|
return Token(TokenType.KEYWORD, Keyword.LOAD.value)
|
|
402
373
|
|
|
403
374
|
def is_load(self) -> bool:
|
|
404
375
|
return self._type == TokenType.KEYWORD and self._value == Keyword.LOAD.value
|
|
405
376
|
|
|
406
|
-
@
|
|
407
|
-
|
|
408
|
-
def CALL(cls) -> Token:
|
|
377
|
+
@staticmethod
|
|
378
|
+
def CALL() -> Token:
|
|
409
379
|
return Token(TokenType.KEYWORD, Keyword.CALL.value)
|
|
410
380
|
|
|
411
381
|
def is_call(self) -> bool:
|
|
412
382
|
return self._type == TokenType.KEYWORD and self._value == Keyword.CALL.value
|
|
413
383
|
|
|
414
|
-
@
|
|
415
|
-
|
|
416
|
-
def YIELD(cls) -> Token:
|
|
384
|
+
@staticmethod
|
|
385
|
+
def YIELD() -> Token:
|
|
417
386
|
return Token(TokenType.KEYWORD, Keyword.YIELD.value)
|
|
418
387
|
|
|
419
388
|
def is_yield(self) -> bool:
|
|
420
389
|
return self._type == TokenType.KEYWORD and self._value == Keyword.YIELD.value
|
|
421
390
|
|
|
422
|
-
@
|
|
423
|
-
|
|
424
|
-
def JSON(cls) -> Token:
|
|
391
|
+
@staticmethod
|
|
392
|
+
def JSON() -> Token:
|
|
425
393
|
return Token(TokenType.KEYWORD, Keyword.JSON.value)
|
|
426
394
|
|
|
427
395
|
def is_json(self) -> bool:
|
|
428
396
|
return self._type == TokenType.KEYWORD and self._value == Keyword.JSON.value
|
|
429
397
|
|
|
430
|
-
@
|
|
431
|
-
|
|
432
|
-
def CSV(cls) -> Token:
|
|
398
|
+
@staticmethod
|
|
399
|
+
def CSV() -> Token:
|
|
433
400
|
return Token(TokenType.KEYWORD, Keyword.CSV.value)
|
|
434
401
|
|
|
435
402
|
def is_csv(self) -> bool:
|
|
436
403
|
return self._type == TokenType.KEYWORD and self._value == Keyword.CSV.value
|
|
437
404
|
|
|
438
|
-
@
|
|
439
|
-
|
|
440
|
-
def TEXT(cls) -> Token:
|
|
405
|
+
@staticmethod
|
|
406
|
+
def TEXT() -> Token:
|
|
441
407
|
return Token(TokenType.KEYWORD, Keyword.TEXT.value)
|
|
442
408
|
|
|
443
409
|
def is_text(self) -> bool:
|
|
444
410
|
return self._type == TokenType.KEYWORD and self._value == Keyword.TEXT.value
|
|
445
411
|
|
|
446
|
-
@
|
|
447
|
-
|
|
448
|
-
def FROM(cls) -> Token:
|
|
412
|
+
@staticmethod
|
|
413
|
+
def FROM() -> Token:
|
|
449
414
|
return Token(TokenType.KEYWORD, Keyword.FROM.value)
|
|
450
415
|
|
|
451
416
|
def is_from(self) -> bool:
|
|
452
417
|
return self._type == TokenType.KEYWORD and self._value == Keyword.FROM.value
|
|
453
418
|
|
|
454
|
-
@
|
|
455
|
-
|
|
456
|
-
def HEADERS(cls) -> Token:
|
|
419
|
+
@staticmethod
|
|
420
|
+
def HEADERS() -> Token:
|
|
457
421
|
return Token(TokenType.KEYWORD, Keyword.HEADERS.value)
|
|
458
422
|
|
|
459
423
|
def is_headers(self) -> bool:
|
|
460
424
|
return self._type == TokenType.KEYWORD and self._value == Keyword.HEADERS.value
|
|
461
425
|
|
|
462
|
-
@
|
|
463
|
-
|
|
464
|
-
def POST(cls) -> Token:
|
|
426
|
+
@staticmethod
|
|
427
|
+
def POST() -> Token:
|
|
465
428
|
return Token(TokenType.KEYWORD, Keyword.POST.value)
|
|
466
429
|
|
|
467
430
|
def is_post(self) -> bool:
|
|
468
431
|
return self._type == TokenType.KEYWORD and self._value == Keyword.POST.value
|
|
469
432
|
|
|
470
|
-
@
|
|
471
|
-
|
|
472
|
-
def UNWIND(cls) -> Token:
|
|
433
|
+
@staticmethod
|
|
434
|
+
def UNWIND() -> Token:
|
|
473
435
|
return Token(TokenType.KEYWORD, Keyword.UNWIND.value)
|
|
474
436
|
|
|
475
437
|
def is_unwind(self) -> bool:
|
|
476
438
|
return self._type == TokenType.KEYWORD and self._value == Keyword.UNWIND.value
|
|
477
439
|
|
|
478
|
-
@
|
|
479
|
-
|
|
480
|
-
def MATCH(cls) -> Token:
|
|
440
|
+
@staticmethod
|
|
441
|
+
def MATCH() -> Token:
|
|
481
442
|
return Token(TokenType.KEYWORD, Keyword.MATCH.value)
|
|
482
443
|
|
|
483
444
|
def is_match(self) -> bool:
|
|
484
445
|
return self._type == TokenType.KEYWORD and self._value == Keyword.MATCH.value
|
|
485
446
|
|
|
486
|
-
@
|
|
487
|
-
|
|
488
|
-
def AS(cls) -> Token:
|
|
447
|
+
@staticmethod
|
|
448
|
+
def AS() -> Token:
|
|
489
449
|
return Token(TokenType.KEYWORD, Keyword.AS.value)
|
|
490
450
|
|
|
491
451
|
def is_as(self) -> bool:
|
|
492
452
|
return self._type == TokenType.KEYWORD and self._value == Keyword.AS.value
|
|
493
453
|
|
|
494
|
-
@
|
|
495
|
-
|
|
496
|
-
def WHERE(cls) -> Token:
|
|
454
|
+
@staticmethod
|
|
455
|
+
def WHERE() -> Token:
|
|
497
456
|
return Token(TokenType.KEYWORD, Keyword.WHERE.value)
|
|
498
457
|
|
|
499
458
|
def is_where(self) -> bool:
|
|
500
459
|
return self._type == TokenType.KEYWORD and self._value == Keyword.WHERE.value
|
|
501
460
|
|
|
502
|
-
@
|
|
503
|
-
|
|
504
|
-
def MERGE(cls) -> Token:
|
|
461
|
+
@staticmethod
|
|
462
|
+
def MERGE() -> Token:
|
|
505
463
|
return Token(TokenType.KEYWORD, Keyword.MERGE.value)
|
|
506
464
|
|
|
507
465
|
def is_merge(self) -> bool:
|
|
508
466
|
return self._type == TokenType.KEYWORD and self._value == Keyword.MERGE.value
|
|
509
467
|
|
|
510
|
-
@
|
|
511
|
-
|
|
512
|
-
def CREATE(cls) -> Token:
|
|
468
|
+
@staticmethod
|
|
469
|
+
def CREATE() -> Token:
|
|
513
470
|
return Token(TokenType.KEYWORD, Keyword.CREATE.value)
|
|
514
471
|
|
|
515
472
|
def is_create(self) -> bool:
|
|
516
473
|
return self._type == TokenType.KEYWORD and self._value == Keyword.CREATE.value
|
|
517
474
|
|
|
518
|
-
@
|
|
519
|
-
|
|
520
|
-
def VIRTUAL(cls) -> Token:
|
|
475
|
+
@staticmethod
|
|
476
|
+
def VIRTUAL() -> Token:
|
|
521
477
|
return Token(TokenType.KEYWORD, Keyword.VIRTUAL.value)
|
|
522
478
|
|
|
523
479
|
def is_virtual(self) -> bool:
|
|
524
480
|
return self._type == TokenType.KEYWORD and self._value == Keyword.VIRTUAL.value
|
|
525
481
|
|
|
526
|
-
@
|
|
527
|
-
|
|
528
|
-
def DELETE(cls) -> Token:
|
|
482
|
+
@staticmethod
|
|
483
|
+
def DELETE() -> Token:
|
|
529
484
|
return Token(TokenType.KEYWORD, Keyword.DELETE.value)
|
|
530
485
|
|
|
531
486
|
def is_delete(self) -> bool:
|
|
532
487
|
return self._type == TokenType.KEYWORD and self._value == Keyword.DELETE.value
|
|
533
488
|
|
|
534
|
-
@
|
|
535
|
-
|
|
536
|
-
def SET(cls) -> Token:
|
|
489
|
+
@staticmethod
|
|
490
|
+
def SET() -> Token:
|
|
537
491
|
return Token(TokenType.KEYWORD, Keyword.SET.value)
|
|
538
492
|
|
|
539
493
|
def is_set(self) -> bool:
|
|
540
494
|
return self._type == TokenType.KEYWORD and self._value == Keyword.SET.value
|
|
541
495
|
|
|
542
|
-
@
|
|
543
|
-
|
|
544
|
-
def REMOVE(cls) -> Token:
|
|
496
|
+
@staticmethod
|
|
497
|
+
def REMOVE() -> Token:
|
|
545
498
|
return Token(TokenType.KEYWORD, Keyword.REMOVE.value)
|
|
546
499
|
|
|
547
500
|
def is_remove(self) -> bool:
|
|
548
501
|
return self._type == TokenType.KEYWORD and self._value == Keyword.REMOVE.value
|
|
549
502
|
|
|
550
|
-
@
|
|
551
|
-
|
|
552
|
-
def CASE(cls) -> Token:
|
|
503
|
+
@staticmethod
|
|
504
|
+
def CASE() -> Token:
|
|
553
505
|
return Token(TokenType.KEYWORD, Keyword.CASE.value)
|
|
554
506
|
|
|
555
507
|
def is_case(self) -> bool:
|
|
556
508
|
return self._type == TokenType.KEYWORD and self._value == Keyword.CASE.value
|
|
557
509
|
|
|
558
|
-
@
|
|
559
|
-
|
|
560
|
-
def WHEN(cls) -> Token:
|
|
510
|
+
@staticmethod
|
|
511
|
+
def WHEN() -> Token:
|
|
561
512
|
return Token(TokenType.KEYWORD, Keyword.WHEN.value)
|
|
562
513
|
|
|
563
514
|
def is_when(self) -> bool:
|
|
564
515
|
return self._type == TokenType.KEYWORD and self._value == Keyword.WHEN.value
|
|
565
516
|
|
|
566
|
-
@
|
|
567
|
-
|
|
568
|
-
def THEN(cls) -> Token:
|
|
517
|
+
@staticmethod
|
|
518
|
+
def THEN() -> Token:
|
|
569
519
|
return Token(TokenType.KEYWORD, Keyword.THEN.value)
|
|
570
520
|
|
|
571
521
|
def is_then(self) -> bool:
|
|
572
522
|
return self._type == TokenType.KEYWORD and self._value == Keyword.THEN.value
|
|
573
523
|
|
|
574
|
-
@
|
|
575
|
-
|
|
576
|
-
def ELSE(cls) -> Token:
|
|
524
|
+
@staticmethod
|
|
525
|
+
def ELSE() -> Token:
|
|
577
526
|
return Token(TokenType.KEYWORD, Keyword.ELSE.value)
|
|
578
527
|
|
|
579
528
|
def is_else(self) -> bool:
|
|
580
529
|
return self._type == TokenType.KEYWORD and self._value == Keyword.ELSE.value
|
|
581
530
|
|
|
582
|
-
@
|
|
583
|
-
|
|
584
|
-
def END(cls) -> Token:
|
|
531
|
+
@staticmethod
|
|
532
|
+
def END() -> Token:
|
|
585
533
|
return Token(TokenType.KEYWORD, Keyword.END.value)
|
|
586
534
|
|
|
587
535
|
def is_end(self) -> bool:
|
|
588
536
|
return self._type == TokenType.KEYWORD and self._value == Keyword.END.value
|
|
589
537
|
|
|
590
|
-
@
|
|
591
|
-
|
|
592
|
-
def NULL(cls) -> Token:
|
|
538
|
+
@staticmethod
|
|
539
|
+
def NULL() -> Token:
|
|
593
540
|
return Token(TokenType.KEYWORD, Keyword.NULL.value)
|
|
594
541
|
|
|
595
542
|
def is_null(self) -> bool:
|
|
596
543
|
return self._type == TokenType.KEYWORD and self._value == Keyword.NULL.value
|
|
597
544
|
|
|
598
|
-
@
|
|
599
|
-
|
|
600
|
-
def IN(cls) -> Token:
|
|
545
|
+
@staticmethod
|
|
546
|
+
def IN() -> Token:
|
|
601
547
|
return Token(TokenType.KEYWORD, Keyword.IN.value)
|
|
602
548
|
|
|
603
549
|
def is_in(self) -> bool:
|
|
604
550
|
return self._type == TokenType.KEYWORD and self._value == Keyword.IN.value
|
|
605
551
|
|
|
606
|
-
@
|
|
607
|
-
|
|
608
|
-
def PIPE(cls) -> Token:
|
|
552
|
+
@staticmethod
|
|
553
|
+
def PIPE() -> Token:
|
|
609
554
|
return Token(TokenType.KEYWORD, Operator.PIPE.value)
|
|
610
555
|
|
|
611
556
|
def is_pipe(self) -> bool:
|
|
612
557
|
return self._type == TokenType.KEYWORD and self._value == Operator.PIPE.value
|
|
613
558
|
|
|
614
|
-
@
|
|
615
|
-
|
|
616
|
-
def DISTINCT(cls) -> Token:
|
|
559
|
+
@staticmethod
|
|
560
|
+
def DISTINCT() -> Token:
|
|
617
561
|
return Token(TokenType.KEYWORD, Keyword.DISTINCT.value)
|
|
618
562
|
|
|
619
563
|
def is_distinct(self) -> bool:
|
|
620
564
|
return self._type == TokenType.KEYWORD and self._value == Keyword.DISTINCT.value
|
|
621
565
|
|
|
622
|
-
@
|
|
623
|
-
|
|
624
|
-
def LIMIT(cls) -> Token:
|
|
566
|
+
@staticmethod
|
|
567
|
+
def LIMIT() -> Token:
|
|
625
568
|
return Token(TokenType.KEYWORD, Keyword.LIMIT.value)
|
|
626
569
|
|
|
627
570
|
def is_limit(self) -> bool:
|
|
@@ -629,9 +572,8 @@ class Token:
|
|
|
629
572
|
|
|
630
573
|
# End of file token
|
|
631
574
|
|
|
632
|
-
@
|
|
633
|
-
|
|
634
|
-
def EOF(cls) -> Token:
|
|
575
|
+
@staticmethod
|
|
576
|
+
def EOF() -> Token:
|
|
635
577
|
return Token(TokenType.EOF)
|
|
636
578
|
|
|
637
579
|
def is_eof(self) -> bool:
|
|
@@ -654,6 +596,10 @@ class Token:
|
|
|
654
596
|
name_upper = name.upper()
|
|
655
597
|
if hasattr(Token, name_upper):
|
|
656
598
|
attr = getattr(Token, name_upper)
|
|
657
|
-
if
|
|
599
|
+
if callable(attr):
|
|
600
|
+
result = attr()
|
|
601
|
+
if isinstance(result, Token):
|
|
602
|
+
return result
|
|
603
|
+
elif isinstance(attr, Token):
|
|
658
604
|
return attr
|
|
659
605
|
return None
|
|
@@ -66,7 +66,7 @@ class Tokenizer:
|
|
|
66
66
|
|
|
67
67
|
def _get_next_token(self, last: Optional[Token] = None) -> Optional[Token]:
|
|
68
68
|
if self._walker.is_at_end:
|
|
69
|
-
return Token.EOF
|
|
69
|
+
return Token.EOF()
|
|
70
70
|
return (
|
|
71
71
|
self._comment() or
|
|
72
72
|
self._whitespace() or
|
|
@@ -144,7 +144,7 @@ class Tokenizer:
|
|
|
144
144
|
if self._walker.opening_brace():
|
|
145
145
|
yield Token.F_STRING(self._walker.get_string(position), quote_char)
|
|
146
146
|
position = self._walker.position
|
|
147
|
-
yield Token.OPENING_BRACE
|
|
147
|
+
yield Token.OPENING_BRACE()
|
|
148
148
|
self._walker.move_next() # skip the opening brace
|
|
149
149
|
position = self._walker.position
|
|
150
150
|
|
|
@@ -155,7 +155,7 @@ class Tokenizer:
|
|
|
155
155
|
else:
|
|
156
156
|
break
|
|
157
157
|
if self._walker.closing_brace():
|
|
158
|
-
yield Token.CLOSING_BRACE
|
|
158
|
+
yield Token.CLOSING_BRACE()
|
|
159
159
|
self._walker.move_next() # skip the closing brace
|
|
160
160
|
position = self._walker.position
|
|
161
161
|
break
|
|
@@ -171,7 +171,7 @@ class Tokenizer:
|
|
|
171
171
|
while not self._walker.is_at_end and self._walker.check_for_whitespace():
|
|
172
172
|
self._walker.move_next()
|
|
173
173
|
found_whitespace = True
|
|
174
|
-
return Token.WHITESPACE if found_whitespace else None
|
|
174
|
+
return Token.WHITESPACE() if found_whitespace else None
|
|
175
175
|
|
|
176
176
|
def _number(self) -> Optional[Token]:
|
|
177
177
|
start_position = self._walker.position
|
|
@@ -871,10 +871,9 @@ class TestRunner:
|
|
|
871
871
|
)
|
|
872
872
|
await match.run()
|
|
873
873
|
results = match.results
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
assert results
|
|
877
|
-
assert results[2] == {"name1": "Person 2", "name2": "Person 3"}
|
|
874
|
+
# With * meaning 0+ hops, each person also matches itself (zero-hop)
|
|
875
|
+
# Person 1→1, 1→2, 1→3, Person 2→2, 2→3, Person 3→3 + bidirectional = 7
|
|
876
|
+
assert len(results) == 7
|
|
878
877
|
|
|
879
878
|
@pytest.mark.asyncio
|
|
880
879
|
async def test_match_with_double_graph_pattern(self):
|
|
@@ -1175,7 +1174,8 @@ class TestRunner:
|
|
|
1175
1174
|
)
|
|
1176
1175
|
await match.run()
|
|
1177
1176
|
results = match.results
|
|
1178
|
-
|
|
1177
|
+
# With *0..3: Person 1 has 4 matches (0,1,2,3 hops), Person 2 has 3, Person 3 has 2, Person 4 has 1 = 10 total
|
|
1178
|
+
assert len(results) == 10
|
|
1179
1179
|
|
|
1180
1180
|
@pytest.mark.asyncio
|
|
1181
1181
|
async def test_return_match_pattern_with_variable_length_relationships(self):
|
|
@@ -1213,7 +1213,8 @@ class TestRunner:
|
|
|
1213
1213
|
)
|
|
1214
1214
|
await match.run()
|
|
1215
1215
|
results = match.results
|
|
1216
|
-
|
|
1216
|
+
# With *0..3: Person 1 has 4 matches (0,1,2,3 hops), Person 2 has 3, Person 3 has 2, Person 4 has 1 = 10 total
|
|
1217
|
+
assert len(results) == 10
|
|
1217
1218
|
|
|
1218
1219
|
@pytest.mark.asyncio
|
|
1219
1220
|
async def test_statement_with_graph_pattern_in_where_clause(self):
|
|
@@ -1332,4 +1333,6 @@ class TestRunner:
|
|
|
1332
1333
|
)
|
|
1333
1334
|
await match.run()
|
|
1334
1335
|
results = match.results
|
|
1335
|
-
|
|
1336
|
+
# With * meaning 0+ hops, Employee 1 (CEO) also matches itself (zero-hop)
|
|
1337
|
+
# Employee 1→1 (zero-hop), 2→1, 3→2→1, 4→2→1 = 4 results
|
|
1338
|
+
assert len(results) == 4
|