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.
- package/.github/workflows/release.yml +1 -0
- package/.husky/pre-commit +3 -2
- package/dist/flowquery.min.js +1 -1
- package/dist/parsing/expressions/reference.d.ts +1 -0
- package/dist/parsing/expressions/reference.d.ts.map +1 -1
- package/dist/parsing/expressions/reference.js +3 -0
- package/dist/parsing/expressions/reference.js.map +1 -1
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +19 -4
- package/dist/parsing/parser.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/pyproject.toml +1 -1
- package/flowquery-py/src/parsing/parser.py +14 -0
- package/flowquery-py/tests/compute/test_runner.py +67 -1
- package/flowquery-py/tests/parsing/test_parser.py +18 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/jest.config.js +6 -9
- package/misc/apps/RAG/data/chats.json +302 -0
- package/misc/apps/RAG/data/emails.json +182 -0
- package/misc/apps/RAG/data/events.json +226 -0
- package/misc/apps/RAG/data/files.json +172 -0
- package/misc/apps/RAG/data/users.json +158 -0
- package/misc/apps/RAG/jest.config.js +21 -0
- package/misc/apps/RAG/package.json +9 -2
- package/misc/apps/RAG/src/App.tsx +5 -5
- package/misc/apps/RAG/src/components/ChatContainer.tsx +53 -124
- package/misc/apps/RAG/src/components/FlowQueryAgent.ts +151 -157
- package/misc/apps/RAG/src/components/index.ts +1 -1
- package/misc/apps/RAG/src/graph/index.ts +19 -0
- package/misc/apps/RAG/src/graph/initializeGraph.ts +254 -0
- package/misc/apps/RAG/src/index.tsx +25 -13
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +146 -231
- package/misc/apps/RAG/src/prompts/index.ts +4 -4
- package/misc/apps/RAG/src/tests/graph.test.ts +35 -0
- package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +20 -21
- package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +35 -30
- package/misc/apps/RAG/src/utils/Llm.ts +248 -0
- package/misc/apps/RAG/src/utils/index.ts +7 -4
- package/misc/apps/RAG/tsconfig.json +4 -3
- package/misc/apps/RAG/webpack.config.js +40 -40
- package/package.json +1 -1
- package/src/parsing/expressions/reference.ts +8 -5
- package/src/parsing/parser.ts +19 -4
- package/tests/compute/runner.test.ts +47 -1
- package/tests/parsing/parser.test.ts +16 -0
- package/misc/apps/RAG/src/plugins/README.md +0 -139
- package/misc/apps/RAG/src/plugins/index.ts +0 -72
- package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +0 -70
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +0 -65
- package/misc/apps/RAG/src/plugins/loaders/Form.ts +0 -594
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +0 -450
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +0 -101
- package/misc/apps/RAG/src/plugins/loaders/Table.ts +0 -274
- package/misc/apps/RAG/src/plugins/loaders/Weather.ts +0 -138
|
@@ -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()
|