diff --git a/flowquery-py/src/graph/pattern_expression.py b/flowquery-py/src/graph/pattern_expression.py index 3ebefd2..00fed7d 100644 --- a/flowquery-py/src/graph/pattern_expression.py +++ b/flowquery-py/src/graph/pattern_expression.py @@ -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 diff --git a/flowquery-py/src/parsing/parser.py b/flowquery-py/src/parsing/parser.py index ced4e77..3d16cd2 100644 --- a/flowquery-py/src/parsing/parser.py +++ b/flowquery-py/src/parsing/parser.py @@ -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() @@ -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]: @@ -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: diff --git a/flowquery-py/tests/parsing/test_parser.py b/flowquery-py/tests/parsing/test_parser.py index fc0d0dc..b1153a4 100644 --- a/flowquery-py/tests/parsing/test_parser.py +++ b/flowquery-py/tests/parsing/test_parser.py @@ -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") diff --git a/src/graph/pattern_expression.ts b/src/graph/pattern_expression.ts index 65f414e..4a90506 100644 --- a/src/graph/pattern_expression.ts +++ b/src/graph/pattern_expression.ts @@ -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 { this._evaluation = false; this.endNode.todoNext = async () => { diff --git a/src/parsing/parser.ts b/src/parsing/parser.ts index b8fdf3e..b0d3695 100644 --- a/src/parsing/parser.ts +++ b/src/parsing/parser.ts @@ -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) { @@ -536,6 +533,7 @@ class Parser extends BaseParser { } pattern.addElement(node); } + pattern.verify(); return pattern; } @@ -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) { diff --git a/tests/parsing/parser.test.ts b/tests/parsing/parser.test.ts index 6f5c1a0..7af20fe 100644 --- a/tests/parsing/parser.test.ts +++ b/tests/parsing/parser.test.ts @@ -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"); +});