Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions flowquery-py/src/graph/pattern_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ def __init__(self):
self._evaluation: bool = False

def add_element(self, element) -> None:
"""Add an element to the pattern, ensuring it starts with a NodeReference."""
if len(self._chain) == 0 and not isinstance(element, NodeReference):
raise ValueError("PatternExpression must start with a NodeReference")
super().add_element(element)

def verify(self) -> None:
if(len(self._chain) == 0):
raise ValueError("PatternExpression cannot be empty")
if not(any(isinstance(el, NodeReference) for el in self._chain if isinstance(el, ASTNode))):
raise ValueError("PatternExpression must contain at least one NodeReference")

@property
def identifier(self):
return None
Expand Down
5 changes: 2 additions & 3 deletions flowquery-py/src/parsing/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,6 @@ def _parse_pattern_expression(self) -> Optional[PatternExpression]:
node = self._parse_node()
if node is None:
raise ValueError("Expected node definition")
if not isinstance(node, NodeReference):
raise ValueError("PatternExpression must start with a NodeReference")
pattern.add_element(node)
while True:
relationship = self._parse_relationship()
Expand All @@ -440,6 +438,7 @@ def _parse_pattern_expression(self) -> Optional[PatternExpression]:
if node is None:
raise ValueError("Expected target node definition")
pattern.add_element(node)
pattern.verify()
return pattern

def _parse_node(self) -> Optional[Node]:
Expand Down Expand Up @@ -606,7 +605,7 @@ def _parse_expression(self) -> Optional[Expression]:
if func is not None:
lookup = self._parse_lookup(func)
expression.add_node(lookup)
elif self.token.is_left_parenthesis() and self.peek() is not None and self.peek().is_identifier():
elif self.token.is_left_parenthesis() and self.peek() is not None and (self.peek().is_identifier() or self.peek().is_colon() or self.peek().is_right_parenthesis()):
# Possible graph pattern expression
pattern = self._parse_pattern_expression()
if pattern is not None:
Expand Down
6 changes: 6 additions & 0 deletions flowquery-py/tests/parsing/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,9 @@ def test_parse_statement_with_graph_pattern_in_where_clause(self):
"--- Reference (a)"
)
assert ast.print() == expected

def test_check_pattern_expression_without_noderef(self):
"""Test check pattern expression without NodeReference."""
parser = Parser()
with pytest.raises(Exception, match="PatternExpression must contain at least one NodeReference"):
parser.parse("MATCH (a:Person) WHERE (:Person)-[:KNOWS]->(:Person) RETURN a")
17 changes: 14 additions & 3 deletions src/graph/pattern_expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,22 @@ class PatternExpression extends Pattern {
throw new Error("Cannot set identifier on PatternExpression");
}
public addElement(element: Relationship | Node): void {
if (this._chain.length == 0 && !(element instanceof NodeReference)) {
throw new Error("PatternExpression must start with a NodeReference");
}
super.addElement(element);
}
public verify(): void {
if (this._chain.length === 0) {
throw new Error("PatternExpression must contain at least one element");
}
const referenced = this._chain.some((element) => {
if (element instanceof NodeReference) {
return true;
}
return false;
});
if (!referenced) {
throw new Error("PatternExpression must contain at least one NodeReference");
}
}
public async evaluate(): Promise<void> {
this._evaluation = false;
this.endNode.todoNext = async () => {
Expand Down
11 changes: 7 additions & 4 deletions src/parsing/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,9 +516,6 @@ class Parser extends BaseParser {
if (node === null) {
throw new Error("Expected node definition");
}
if (!(node instanceof NodeReference)) {
throw new Error("PatternExpression must start with a NodeReference");
}
pattern.addElement(node);
let relationship: Relationship | null = null;
while (true) {
Expand All @@ -536,6 +533,7 @@ class Parser extends BaseParser {
}
pattern.addElement(node);
}
pattern.verify();
return pattern;
}

Expand Down Expand Up @@ -709,7 +707,12 @@ class Parser extends BaseParser {
const lookup = this.parseLookup(func);
expression.addNode(lookup);
}
} else if (this.token.isLeftParenthesis() && this.peek()?.isIdentifier()) {
} else if (
this.token.isLeftParenthesis() &&
(this.peek()?.isIdentifier() ||
this.peek()?.isColon() ||
this.peek()?.isRightParenthesis())
) {
// Possible graph pattern expression
const pattern = this.parsePatternExpression();
if (pattern !== null) {
Expand Down
8 changes: 8 additions & 0 deletions tests/parsing/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,3 +752,11 @@ test("Parse statement with graph pattern in where clause", () => {
expect(relationship.type).toBe("KNOWS");
expect(target.label).toBe("Person");
});

test("Test check pattern expression without NodeReference", () => {
const parser = new Parser();
// Should throw an error because pattern does not start with NodeReference
expect(() => {
parser.parse("MATCH (a:Person) WHERE (:Person)-[:KNOWS]->(:Person) RETURN a");
}).toThrow("PatternExpression must contain at least one NodeReference");
});