flowquery 1.0.25 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.github/workflows/release.yml +1 -0
  2. package/.husky/pre-commit +3 -2
  3. package/dist/flowquery.min.js +1 -1
  4. package/dist/parsing/expressions/reference.d.ts +1 -0
  5. package/dist/parsing/expressions/reference.d.ts.map +1 -1
  6. package/dist/parsing/expressions/reference.js +3 -0
  7. package/dist/parsing/expressions/reference.js.map +1 -1
  8. package/dist/parsing/parser.d.ts.map +1 -1
  9. package/dist/parsing/parser.js +19 -4
  10. package/dist/parsing/parser.js.map +1 -1
  11. package/docs/flowquery.min.js +1 -1
  12. package/flowquery-py/pyproject.toml +1 -1
  13. package/flowquery-py/src/parsing/parser.py +14 -0
  14. package/flowquery-py/tests/compute/test_runner.py +67 -1
  15. package/flowquery-py/tests/parsing/test_parser.py +18 -0
  16. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  17. package/jest.config.js +6 -9
  18. package/misc/apps/RAG/data/chats.json +302 -0
  19. package/misc/apps/RAG/data/emails.json +182 -0
  20. package/misc/apps/RAG/data/events.json +226 -0
  21. package/misc/apps/RAG/data/files.json +172 -0
  22. package/misc/apps/RAG/data/users.json +158 -0
  23. package/misc/apps/RAG/jest.config.js +21 -0
  24. package/misc/apps/RAG/package.json +9 -2
  25. package/misc/apps/RAG/src/App.tsx +5 -5
  26. package/misc/apps/RAG/src/components/ChatContainer.tsx +53 -124
  27. package/misc/apps/RAG/src/components/FlowQueryAgent.ts +151 -157
  28. package/misc/apps/RAG/src/components/index.ts +1 -1
  29. package/misc/apps/RAG/src/graph/index.ts +19 -0
  30. package/misc/apps/RAG/src/graph/initializeGraph.ts +254 -0
  31. package/misc/apps/RAG/src/index.tsx +25 -13
  32. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +146 -231
  33. package/misc/apps/RAG/src/prompts/index.ts +4 -4
  34. package/misc/apps/RAG/src/tests/graph.test.ts +35 -0
  35. package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +20 -21
  36. package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +35 -30
  37. package/misc/apps/RAG/src/utils/Llm.ts +248 -0
  38. package/misc/apps/RAG/src/utils/index.ts +7 -4
  39. package/misc/apps/RAG/tsconfig.json +4 -3
  40. package/misc/apps/RAG/webpack.config.js +40 -40
  41. package/package.json +1 -1
  42. package/src/parsing/expressions/reference.ts +8 -5
  43. package/src/parsing/parser.ts +19 -4
  44. package/tests/compute/runner.test.ts +47 -1
  45. package/tests/parsing/parser.test.ts +16 -0
  46. package/misc/apps/RAG/src/plugins/README.md +0 -139
  47. package/misc/apps/RAG/src/plugins/index.ts +0 -72
  48. package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +0 -70
  49. package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +0 -65
  50. package/misc/apps/RAG/src/plugins/loaders/Form.ts +0 -594
  51. package/misc/apps/RAG/src/plugins/loaders/Llm.ts +0 -450
  52. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +0 -101
  53. package/misc/apps/RAG/src/plugins/loaders/Table.ts +0 -274
  54. package/misc/apps/RAG/src/plugins/loaders/Weather.ts +0 -138
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flowquery"
3
- version = "1.0.15"
3
+ version = "1.0.17"
4
4
  description = "A declarative query language for data processing pipelines"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -199,6 +199,7 @@ class Parser(BaseParser):
199
199
  return Return(expressions)
200
200
 
201
201
  def _parse_where(self) -> Optional[Where]:
202
+ self._skip_whitespace_and_comments()
202
203
  if not self.token.is_where():
203
204
  return None
204
205
  self._expect_previous_token_to_be_whitespace_or_comment()
@@ -473,6 +474,12 @@ class Parser(BaseParser):
473
474
  self._variables[identifier] = node
474
475
  elif identifier is not None:
475
476
  reference = self._variables.get(identifier)
477
+ # Resolve through Expression -> Reference -> Node (e.g., after WITH)
478
+ ref_child = reference.first_child() if isinstance(reference, Expression) else None
479
+ if isinstance(ref_child, Reference):
480
+ inner = ref_child.referred
481
+ if isinstance(inner, Node):
482
+ reference = inner
476
483
  if reference is None or not isinstance(reference, Node):
477
484
  raise ValueError(f"Undefined node reference: {identifier}")
478
485
  node = NodeReference(node, reference)
@@ -523,6 +530,13 @@ class Parser(BaseParser):
523
530
  self._variables[variable] = relationship
524
531
  elif variable is not None:
525
532
  reference = self._variables.get(variable)
533
+ # Resolve through Expression -> Reference -> Relationship (e.g., after WITH)
534
+ if isinstance(reference, Expression) and isinstance(
535
+ reference.first_child(), Reference
536
+ ):
537
+ inner = reference.first_child().referred
538
+ if isinstance(inner, Relationship):
539
+ reference = inner
526
540
  if reference is None or not isinstance(reference, Relationship):
527
541
  raise ValueError(f"Undefined relationship reference: {variable}")
528
542
  relationship = RelationshipReference(relationship, reference)
@@ -1258,6 +1258,31 @@ class TestRunner:
1258
1258
  assert results[1] == {"name1": "Person 2", "name2": "Person 3"}
1259
1259
  assert results[2] == {"name1": "Person 3", "name2": "Person 4"}
1260
1260
 
1261
+ # Test negative match
1262
+ nomatch = Runner(
1263
+ """
1264
+ MATCH (a:WherePerson), (b:WherePerson)
1265
+ WHERE (a)-[:KNOWS]->(b) <> true
1266
+ RETURN a.name AS name1, b.name AS name2
1267
+ """
1268
+ )
1269
+ await nomatch.run()
1270
+ noresults = nomatch.results
1271
+ assert len(noresults) == 13
1272
+ assert noresults[0] == {"name1": "Person 1", "name2": "Person 1"}
1273
+ assert noresults[1] == {"name1": "Person 1", "name2": "Person 3"}
1274
+ assert noresults[2] == {"name1": "Person 1", "name2": "Person 4"}
1275
+ assert noresults[3] == {"name1": "Person 2", "name2": "Person 1"}
1276
+ assert noresults[4] == {"name1": "Person 2", "name2": "Person 2"}
1277
+ assert noresults[5] == {"name1": "Person 2", "name2": "Person 4"}
1278
+ assert noresults[6] == {"name1": "Person 3", "name2": "Person 1"}
1279
+ assert noresults[7] == {"name1": "Person 3", "name2": "Person 2"}
1280
+ assert noresults[8] == {"name1": "Person 3", "name2": "Person 3"}
1281
+ assert noresults[9] == {"name1": "Person 4", "name2": "Person 1"}
1282
+ assert noresults[10] == {"name1": "Person 4", "name2": "Person 2"}
1283
+ assert noresults[11] == {"name1": "Person 4", "name2": "Person 3"}
1284
+ assert noresults[12] == {"name1": "Person 4", "name2": "Person 4"}
1285
+
1261
1286
  @pytest.mark.asyncio
1262
1287
  async def test_person_who_does_not_know_anyone(self):
1263
1288
  """Test person who does not know anyone."""
@@ -1516,6 +1541,7 @@ class TestRunner:
1516
1541
  assert results[0] == {"name1": "Person 1", "name2": "Person 2", "name3": "Person 3"}
1517
1542
  assert results[1] == {"name1": "Person 2", "name2": "Person 3", "name3": "Person 4"}
1518
1543
 
1544
+ @pytest.mark.asyncio
1519
1545
  async def test_match_with_constraints(self):
1520
1546
  await Runner(
1521
1547
  """
@@ -1643,4 +1669,44 @@ class TestRunner:
1643
1669
  await runner.run()
1644
1670
  results = runner.results
1645
1671
  assert len(results) == 1
1646
- assert results[0] == {"name1": "Node 1", "name2": "Node 2"}
1672
+ assert results[0] == {"name1": "Node 1", "name2": "Node 2"}
1673
+
1674
+ @pytest.mark.asyncio
1675
+ async def test_match_with_node_reference_passed_through_with(self):
1676
+ """Test that node variables passed through WITH can be re-referenced in subsequent MATCH."""
1677
+ await Runner("""
1678
+ CREATE VIRTUAL (:WithRefUser) AS {
1679
+ UNWIND [
1680
+ {id: 1, name: 'Alice', mail: 'alice@test.com', jobTitle: 'CEO'},
1681
+ {id: 2, name: 'Bob', mail: 'bob@test.com', jobTitle: 'VP'},
1682
+ {id: 3, name: 'Carol', mail: 'carol@test.com', jobTitle: 'VP'},
1683
+ {id: 4, name: 'Dave', mail: 'dave@test.com', jobTitle: 'Engineer'}
1684
+ ] AS record
1685
+ RETURN record.id AS id, record.name AS name, record.mail AS mail, record.jobTitle AS jobTitle
1686
+ }
1687
+ """).run()
1688
+ await Runner("""
1689
+ CREATE VIRTUAL (:WithRefUser)-[:MANAGES]-(:WithRefUser) AS {
1690
+ UNWIND [
1691
+ {left_id: 1, right_id: 2},
1692
+ {left_id: 1, right_id: 3},
1693
+ {left_id: 2, right_id: 4}
1694
+ ] AS record
1695
+ RETURN record.left_id AS left_id, record.right_id AS right_id
1696
+ }
1697
+ """).run()
1698
+ runner = Runner("""
1699
+ MATCH (ceo:WithRefUser)-[:MANAGES]->(dr1:WithRefUser)
1700
+ WHERE ceo.jobTitle = 'CEO'
1701
+ WITH ceo, dr1
1702
+ MATCH (ceo)-[:MANAGES]->(dr2:WithRefUser)
1703
+ WHERE dr1.mail <> dr2.mail
1704
+ RETURN ceo.name AS ceo, dr1.name AS dr1, dr2.name AS dr2
1705
+ """)
1706
+ await runner.run()
1707
+ results = runner.results
1708
+ # CEO (Alice) manages Bob and Carol. All distinct pairs:
1709
+ # (Alice, Bob, Carol) and (Alice, Carol, Bob)
1710
+ assert len(results) == 2
1711
+ assert results[0] == {"ceo": "Alice", "dr1": "Bob", "dr2": "Carol"}
1712
+ assert results[1] == {"ceo": "Alice", "dr1": "Carol", "dr2": "Bob"}
@@ -6,6 +6,7 @@ from flowquery.parsing.parser import Parser
6
6
  from flowquery.parsing.functions.async_function import AsyncFunction
7
7
  from flowquery.parsing.functions.function_metadata import FunctionDef
8
8
  from flowquery.parsing.operations.match import Match
9
+ from flowquery.parsing.operations.create_relationship import CreateRelationship
9
10
  from flowquery.graph.node import Node
10
11
  from flowquery.graph.relationship import Relationship
11
12
 
@@ -784,6 +785,23 @@ class TestParser:
784
785
  assert node.properties.get("value") is not None
785
786
  assert node.properties["value"].value() == "hello"
786
787
 
788
+ def test_where_in_create_virtual_sub_query(self):
789
+ """Test WHERE in CREATE VIRTUAL sub-query."""
790
+ parser = Parser()
791
+ ast = parser.parse(
792
+ "CREATE VIRTUAL (:Email)-[:HAS_ATTACHMENT]-(:File) AS {"
793
+ " LOAD JSON FROM '/data/emails.json' AS email"
794
+ " WHERE email.hasAttachments = true"
795
+ " UNWIND email.attachments AS fileId"
796
+ " RETURN email.id AS left_id, fileId AS right_id"
797
+ " }"
798
+ )
799
+ create = ast.first_child()
800
+ assert isinstance(create, CreateRelationship)
801
+ assert create.relationship is not None
802
+ assert create.relationship.type == "HAS_ATTACHMENT"
803
+ assert create.statement is not None
804
+
787
805
  def test_relationship_with_properties(self):
788
806
  """Test relationship with properties."""
789
807
  parser = Parser()