Skip to content

fix: accept WITH after SET/REMOVE/DELETE and in chained multi-part queries#310

Merged
DecisionNerd merged 2 commits into
mainfrom
fix/257-with-clause-positions
Mar 1, 2026
Merged

fix: accept WITH after SET/REMOVE/DELETE and in chained multi-part queries#310
DecisionNerd merged 2 commits into
mainfrom
fix/257-with-clause-positions

Conversation

@DecisionNerd
Copy link
Copy Markdown
Owner

@DecisionNerd DecisionNerd commented Mar 1, 2026

Closes #257

Summary

  • Grammar: Restructures multi_part_query from reading_or_writing_clauses+ with_clause+ into a proper multi_part_segment+ pattern where each segment is (segment_clause* with_clause), matching the openCypher spec
  • Grammar: segment_clause covers all clause types before WITH: reading clauses (MATCH, OPTIONAL MATCH, UNWIND, CALL), writing clauses (CREATE, MERGE), and updating clauses (SET, REMOVE, DELETE)
  • Grammar: exists_expr/count_expr now accept query inside {}, enabling WITH inside EXISTS/COUNT subqueries
  • Transformer: New multi_part_segment and segment_clause handlers flatten clauses into the existing CypherQuery accumulator

Patterns Now Supported

```cypher
-- SET before WITH
MATCH (n) SET n.x = 1 WITH n RETURN n

-- REMOVE before WITH
MATCH (n:T) REMOVE n:T WITH n RETURN n

-- SET after a prior WITH (chained segment)
MATCH (n) WITH n SET n.x = 99 WITH n RETURN n.x AS val

-- Three-segment chains
MATCH (a) WITH a MATCH (b) WITH a, b MATCH (c) WITH a, b, c RETURN a, b, c

-- EXISTS containing WITH
MATCH (n) WHERE EXISTS { MATCH (n)-[]->(m) WITH m RETURN m } RETURN n
```

Test plan

  • 13 new parse-level tests covering all WITH position patterns
  • 10 new execution-level tests verifying correct results
  • All 2,166 existing unit tests pass (0 regressions)
  • Coverage: 88.54% total (above 85% threshold)
  • `ruff format`, `ruff check`, `mypy` all pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • UNION queries now accept WITH-style subqueries, enabling more flexible unions.
    • EXISTS and COUNT can contain full nested queries.
    • Added NONE and SINGLE quantifiers alongside ALL/ANY.
    • Multi-part queries now support segment-based sequencing of clauses.
    • Float literals accept scientific notation and leading-decimal formats.
  • Tests

    • New unit and integration tests validating WITH clause positions and multi-segment behavior.

…eries (#257)

Restructures the multi_part_query grammar rule to support all openCypher-valid
WITH clause positions:

- Grammar: Replace reading_or_writing_clauses+ with_clause+ with
  multi_part_segment+ pattern, where each segment is (segment_clause* with_clause)
- Grammar: segment_clause now includes set_clause, remove_clause, delete_clause
  in addition to reading and writing clauses, allowing patterns like:
    MATCH (n) WITH n SET n.x = 1 WITH n RETURN n
    MATCH (n) WITH n REMOVE n:T WITH n RETURN n
    MATCH (n) SET n.x = 1 WITH n RETURN n
- Grammar: exists_expr and count_expr now accept query (not single_part_query)
  inside {}, enabling WITH inside EXISTS/COUNT subqueries
- Transformer: Add multi_part_segment and segment_clause handlers that return
  flat clause lists for consistent flattening in multi_part_query
- Tests: 23 new parse + execution tests covering all new patterns

Fixes patterns where WITH appeared after update clauses (SET, REMOVE, DELETE)
in multi-part pipelines, which previously raised grammar parse errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 1, 2026

Walkthrough

Restructures the Cypher grammar to support WITH in more clause positions via segment-based multi-part queries; updates expressions (EXISTS, COUNT, quantifiers) and FLOAT token; adds transformer methods for segments; and adds unit and integration tests validating WITH clause positions and execution.

Changes

Cohort / File(s) Summary
Grammar core
src/graphforge/parser/cypher.lark
Expanded union_query to accept with_query; replaced reading_or_writing_clauses+ with multi_part_segment+ in multi_part_query; added multi_part_segment and segment_clause; exists_expr/count_expr now accept query; added NONE/SINGLE quantifiers; broadened FLOAT regex.
Parser transformer
src/graphforge/parser/parser.py
Added multi_part_segment and segment_clause transformer methods; removed writing_clause and reading_or_writing_clauses handlers; updated multi_part_query flattening to use segments; added float overflow guard.
Unit tests
tests/unit/parser/test_with_clause_positions.py
New parametrized unit tests asserting the parser accepts WITH in various clause positions and sequences.
Integration tests
tests/integration/test_with_clause_positions.py
New integration test suite exercising multi-part queries with WITH across MATCH/CREATE/SET/REMOVE/UNWIND/EXISTS and verifying execution results and state changes.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant Lexer as Lexer/Parser (lark)
participant Transformer as ASTTransformer
participant Executor as Query Executor
rect rgba(200,200,255,0.5)
Client->>Lexer: send Cypher source
Lexer->>Transformer: produce parse tree
end
rect rgba(200,255,200,0.5)
Transformer->>Transformer: invoke segment_clause / multi_part_segment
Transformer->>Transformer: flatten multi_part_query -> AST
end
rect rgba(255,200,200,0.5)
Transformer->>Executor: emit AST
Executor->>Executor: execute multi-part segments (WITH propagation)
Executor->>Client: return results
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

tests, release:patch

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: accepting WITH after SET/REMOVE/DELETE and in chained multi-part queries.
Description check ✅ Passed The PR description comprehensively covers the changes made, includes related issue reference, provides clear examples of now-supported patterns, and lists thorough testing performed.
Linked Issues check ✅ Passed The PR fully addresses issue #257 by restructuring multi_part_query grammar to accept WITH in all valid openCypher positions, implementing segment-based sequencing with proper clause handling.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue #257 objectives: grammar restructuring for multi-part queries, segment/clause handling, EXISTS/COUNT updates, and corresponding transformer logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/257-with-clause-positions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/unit/parser/test_with_clause_positions.py (2)

11-79: Consider using @pytest.mark.parametrize to reduce duplication.

The parse-level tests follow a repetitive pattern: call parse_cypher() and assert the result is not None. This could be consolidated into a single parametrized test for better maintainability.

♻️ Example refactor using parametrize
import pytest

class TestWithClausePositionParsing:
    """Parse-level tests — verify grammar accepts all WITH positions."""

    `@pytest.mark.parametrize`("query,description", [
        ("MATCH (n) SET n.x = 1 WITH n RETURN n", "MATCH SET WITH RETURN"),
        ("MATCH (n:T) REMOVE n:T WITH n RETURN n", "MATCH REMOVE WITH RETURN"),
        ("MATCH (n:T) DELETE n WITH 1 AS done RETURN done", "MATCH DELETE WITH RETURN"),
        ("MATCH (n) WITH n SET n.x = 1 WITH n RETURN n", "two WITH segments"),
        ("MATCH (n:T) WITH n REMOVE n:T WITH n RETURN n", "WITH REMOVE WITH"),
        ("MATCH (a) WITH a MATCH (b) WITH a, b RETURN a, b", "triple part"),
        ("MATCH (a) WITH a MATCH (b) WITH a, b MATCH (c) WITH a, b, c RETURN a, b, c", "three WITH segments"),
        ("CREATE (n:T) WITH n SET n.x = 1 WITH n RETURN n.x AS val", "CREATE WITH SET WITH"),
        ("MATCH (n) SET n.x = 1 RETURN n", "SET without subsequent WITH"),
        ("MATCH (n) WITH n WITH n AS m RETURN m", "consecutive WITH clauses"),
        ("MATCH (n) WHERE EXISTS { MATCH (n)-[]->(m) WITH m RETURN m } RETURN n", "EXISTS with WITH inside"),
        ("MATCH (n) WHERE n.x > 1 WITH n RETURN n", "WHERE WITH"),
        ("UNWIND [1, 2, 3] AS x WITH x MATCH (n {id: x}) RETURN n", "UNWIND WITH MATCH"),
    ])
    def test_with_clause_positions(self, query, description):
        """Verify grammar accepts WITH in various positions."""
        ast = parse_cypher(query)
        assert ast is not None, f"Failed to parse: {description}"

As per coding guidelines: "Use @pytest.mark.parametrize for testing the same logic with different inputs to avoid code duplication."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/parser/test_with_clause_positions.py` around lines 11 - 79, Tests
in TestWithClausePositionParsing duplicate the same parse_cypher() + assert
pattern across many test_... methods; consolidate them by replacing the
individual test_* methods with a single parametrized test using
pytest.mark.parametrize that iterates over the query strings and descriptions
and calls parse_cypher(query) with assert ast is not None (include a helpful
message), referencing the existing test class TestWithClausePositionParsing and
the parse_cypher function to locate where to change.

82-84: Execution tests may belong in tests/integration/.

These tests exercise the full stack via GraphForge.execute(), which is end-to-end query execution rather than isolated unit testing. Per the test organization guidelines, integration tests that verify end-to-end behavior should reside in tests/integration/.

As per coding guidelines: "Organize tests into unit/ (< 1ms, isolated), integration/ (end-to-end queries)..." and "Integration tests must test end-to-end Cypher query execution with GraphForge.execute()."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/parser/test_with_clause_positions.py` around lines 82 - 84, Tests
under the TestWithClausePositionExecution class exercise end-to-end query
execution via GraphForge.execute() and should be moved from unit to integration
tests; relocate the TestWithClausePositionExecution class and its tests to
tests/integration/, update any imports or test helpers (e.g., ensure GraphForge
and fixtures are imported from the integration test utilities), and adjust test
module/package names or pytest markers as needed so the tests run with the
integration suite rather than the fast unit suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/graphforge/parser/cypher.lark`:
- Around line 31-33: Remove the unused grammar rule writing_clause (which simply
aliases create_clause and merge_clause) from cypher.lark and delete the
corresponding unused transformer method in parser.py (the writing_clause
transformer), since segment_clause already references create_clause and
merge_clause directly and nothing calls writing_clause; also remove the
misleading "backward compatibility" comment. Ensure no other rules or tests
reference writing_clause before deleting.

In `@src/graphforge/parser/parser.py`:
- Around line 148-150: The branch checking for isinstance(item, WithClause) in
parser.py is dead code because WithClause instances are only produced nested
inside lists by multi_part_segment and never appear directly in
multi_part_query; remove the elif block that appends item to all_clauses (the
WithClause branch) and ensure all_clauses continues to be built from
list-unpacked clauses produced by multi_part_segment so the remaining logic
handles WithClause instances when present.

---

Nitpick comments:
In `@tests/unit/parser/test_with_clause_positions.py`:
- Around line 11-79: Tests in TestWithClausePositionParsing duplicate the same
parse_cypher() + assert pattern across many test_... methods; consolidate them
by replacing the individual test_* methods with a single parametrized test using
pytest.mark.parametrize that iterates over the query strings and descriptions
and calls parse_cypher(query) with assert ast is not None (include a helpful
message), referencing the existing test class TestWithClausePositionParsing and
the parse_cypher function to locate where to change.
- Around line 82-84: Tests under the TestWithClausePositionExecution class
exercise end-to-end query execution via GraphForge.execute() and should be moved
from unit to integration tests; relocate the TestWithClausePositionExecution
class and its tests to tests/integration/, update any imports or test helpers
(e.g., ensure GraphForge and fixtures are imported from the integration test
utilities), and adjust test module/package names or pytest markers as needed so
the tests run with the integration suite rather than the fast unit suite.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 865913d and 67a122d.

📒 Files selected for processing (3)
  • src/graphforge/parser/cypher.lark
  • src/graphforge/parser/parser.py
  • tests/unit/parser/test_with_clause_positions.py

Comment thread src/graphforge/parser/cypher.lark Outdated
Comment thread src/graphforge/parser/parser.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 1, 2026

Codecov Report

❌ Patch coverage is 82.60870% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.11%. Comparing base (865913d) to head (b5bc562).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #310      +/-   ##
==========================================
+ Coverage   90.09%   90.11%   +0.01%     
==========================================
  Files          38       38              
  Lines       10512    10537      +25     
  Branches     2155     2158       +3     
==========================================
+ Hits         9471     9495      +24     
- Misses        726      731       +5     
+ Partials      315      311       -4     
Flag Coverage Δ
full-coverage 90.11% <82.60%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
parser 96.41% <82.60%> (-0.02%) ⬇️
planner 94.88% <ø> (ø)
executor 86.06% <ø> (ø)
storage 93.31% <ø> (ø)
ast 96.18% <ø> (ø)
types 97.00% <ø> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 865913d...b5bc562. Read the comment docs.

- Remove dead `writing_clause` grammar rule and its transformer methods
  (`writing_clause`, `reading_or_writing_clauses`) — superseded by
  `segment_clause` which covers create/merge directly
- Remove unreachable `isinstance(item, WithClause)` branch in
  `multi_part_query` transformer — `multi_part_segment` always returns lists
- Parametrize `TestWithClausePositionParsing` with
  `@pytest.mark.parametrize` to reduce repetition
- Move `TestWithClausePositionExecution` from `tests/unit/` to
  `tests/integration/test_with_clause_positions.py` per project conventions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tests/integration/test_with_clause_positions.py (2)

42-53: test_three_segment_chain should validate result values, not only row count.

At Line 52, a length check alone can miss projection/scoping regressions across the chained WITH segments.

🧪 Suggested assertion upgrade
     def test_three_segment_chain(self):
         """Three-segment multi-part query works correctly."""
         gf = GraphForge()
-        gf.execute("CREATE (:A)-[:R]->(:B)-[:R]->(:C)")
+        gf.execute("CREATE (:A {v: 1})-[:R]->(:B {v: 2})-[:R]->(:C {v: 3})")
         result = gf.execute(
             "MATCH (a:A) WITH a "
             "MATCH (a)-[:R]->(b:B) WITH a, b "
             "MATCH (b)-[:R]->(c:C) WITH a, b, c "
-            "RETURN a, b, c"
+            "RETURN a.v AS av, b.v AS bv, c.v AS cv"
         )
         assert len(result) == 1
+        assert result[0]["av"].value == 1
+        assert result[0]["bv"].value == 2
+        assert result[0]["cv"].value == 3
As per coding guidelines `tests/integration/**/*.py`: Integration tests must test end-to-end Cypher query execution with GraphForge.execute() and verify result values.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/test_with_clause_positions.py` around lines 42 - 53, Update
test_three_segment_chain to assert the returned row contains the expected node
values rather than only checking len(result). After running gf.execute in
test_three_segment_chain, inspect result[0] and assert that keys 'a', 'b', and
'c' exist and have the expected properties (e.g., labels or unique identifiers)
produced by the CREATE call; use the GraphForge.execute return shape
(result[0]['a'], result[0]['b'], result[0]['c']) to compare against expected
node identities/labels so the test validates projection/scoping across the
chained WITH segments.

10-108: Add an end-to-end DELETE ... WITH ... execution test.

DELETE is part of the feature scope, but this integration suite currently validates SET and REMOVE flows only. Adding one delete case would close that gap.

🧪 Suggested additional integration test
 class TestWithClausePositionExecution:
@@
+    def test_match_delete_with_return(self):
+        """MATCH ... DELETE ... WITH ... RETURN executes correctly."""
+        gf = GraphForge()
+        gf.execute("CREATE (:T {id: 1})")
+        result = gf.execute("MATCH (n:T) DELETE n WITH 1 AS done RETURN done")
+        assert len(result) == 1
+        assert result[0]["done"].value == 1
+        remaining = gf.execute("MATCH (n:T) RETURN n")
+        assert len(remaining) == 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/test_with_clause_positions.py` around lines 10 - 108, Add
an integration test in TestWithClausePositionExecution (e.g., def
test_delete_with_with_return) that covers DELETE combined with WITH: create two
:T nodes (distinct v values), run a multi-segment query that MATCHes one node
and DELETEs it then uses WITH to continue the query and MATCH remaining :T nodes
(for example "MATCH (n:T {v:1}) DELETE n WITH 1 AS dummy MATCH (m:T) RETURN
count(m) AS cnt"), and assert the returned count equals 1 to confirm the deleted
node is gone; reference the class TestWithClausePositionExecution and add the
new test method name (test_delete_with_with_return) to locate where to add it.
src/graphforge/parser/parser.py (1)

145-152: Guard against silent clause loss in multi_part_query.

At Line 145, items that are neither list nor CypherQuery are dropped silently. This can hide grammar/transformer drift and produce truncated ASTs without a clear failure.

♻️ Proposed hardening
         for item in items:
             if isinstance(item, list):
                 # multi_part_segment returns a list of clauses
                 all_clauses.extend(item)
             elif isinstance(item, CypherQuery):
                 # final_query_part returns a CypherQuery
                 all_clauses.extend(item.clauses)
+            else:
+                raise TypeError(
+                    f"Unexpected item in multi_part_query transformer: {type(item).__name__}"
+                )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/graphforge/parser/parser.py` around lines 145 - 152, The loop over items
in multi_part_query silently drops any element that is not a list or CypherQuery
(variables: items, all_clauses, CypherQuery), so add a defensive else branch
that surfaces unexpected types: detect the unexpected item, raise a clear
exception (or at minimum log an error with the item's type and repr) referencing
the multi_part_query context and include the offending item and its type to fail
fast and avoid silent clause loss; ensure the message mentions the unexpected
type and the variable name (item) so downstream callers can trace
grammar/transformer drift.
tests/unit/parser/test_with_clause_positions.py (1)

11-31: Strengthen parse assertions and include a COUNT { ... WITH ... } case.

At Line 40, assert ast is not None only proves “no exception.” It won’t catch AST-shape regressions from the new segment/count paths. Also, the changed count_expr grammar path is not covered in this test set.

🧪 Suggested test hardening
+from graphforge.ast.query import CypherQuery
 from graphforge.parser.parser import parse_cypher

 _WITH_POSITION_CASES = [
     ("MATCH (n) SET n.x = 1 WITH n RETURN n", "MATCH SET WITH RETURN"),
@@
     (
         "MATCH (n) WHERE EXISTS { MATCH (n)-[]->(m) WITH m RETURN m } RETURN n",
         "EXISTS subquery with WITH inside",
     ),
+    (
+        "MATCH (n) WHERE COUNT { MATCH (n)-[]->(m) WITH m RETURN m } > 0 RETURN n",
+        "COUNT subquery with WITH inside",
+    ),
@@
     def test_with_clause_positions(self, query: str, description: str) -> None:
         """Verify grammar accepts WITH in various positions."""
         ast = parse_cypher(query)
-        assert ast is not None, f"Failed to parse: {description}"
+        assert isinstance(ast, CypherQuery), f"Unexpected AST type for: {description}"
+        assert ast.clauses, f"Empty AST clauses for: {description}"
As per coding guidelines `tests/unit/**/*.py`: Unit tests must test a single component in isolation using fixtures like empty_graph and assertions on AST/operator types.

Also applies to: 37-41

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/parser/test_with_clause_positions.py` around lines 11 - 31, The
test currently only checks `ast is not None` (involving `_WITH_POSITION_CASES`)
which won't detect AST-shape regressions or the new `count_expr` grammar path;
update the test to (1) add a case covering a COUNT with an inner subquery that
contains a WITH (e.g. a "COUNT { ... WITH ... }" scenario added to
`_WITH_POSITION_CASES`), (2) replace or augment the weak `assert ast is not
None` with concrete assertions against the parsed AST/operator types (use the
same parsing entry used in the test to produce `ast` and assert expected node
types/structure for the top-level statement and the WITH segment), and (3) use
the prescribed fixture (e.g. `empty_graph`) so the unit test isolates parsing
behavior rather than execution context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/graphforge/parser/parser.py`:
- Around line 145-152: The loop over items in multi_part_query silently drops
any element that is not a list or CypherQuery (variables: items, all_clauses,
CypherQuery), so add a defensive else branch that surfaces unexpected types:
detect the unexpected item, raise a clear exception (or at minimum log an error
with the item's type and repr) referencing the multi_part_query context and
include the offending item and its type to fail fast and avoid silent clause
loss; ensure the message mentions the unexpected type and the variable name
(item) so downstream callers can trace grammar/transformer drift.

In `@tests/integration/test_with_clause_positions.py`:
- Around line 42-53: Update test_three_segment_chain to assert the returned row
contains the expected node values rather than only checking len(result). After
running gf.execute in test_three_segment_chain, inspect result[0] and assert
that keys 'a', 'b', and 'c' exist and have the expected properties (e.g., labels
or unique identifiers) produced by the CREATE call; use the GraphForge.execute
return shape (result[0]['a'], result[0]['b'], result[0]['c']) to compare against
expected node identities/labels so the test validates projection/scoping across
the chained WITH segments.
- Around line 10-108: Add an integration test in TestWithClausePositionExecution
(e.g., def test_delete_with_with_return) that covers DELETE combined with WITH:
create two :T nodes (distinct v values), run a multi-segment query that MATCHes
one node and DELETEs it then uses WITH to continue the query and MATCH remaining
:T nodes (for example "MATCH (n:T {v:1}) DELETE n WITH 1 AS dummy MATCH (m:T)
RETURN count(m) AS cnt"), and assert the returned count equals 1 to confirm the
deleted node is gone; reference the class TestWithClausePositionExecution and
add the new test method name (test_delete_with_with_return) to locate where to
add it.

In `@tests/unit/parser/test_with_clause_positions.py`:
- Around line 11-31: The test currently only checks `ast is not None` (involving
`_WITH_POSITION_CASES`) which won't detect AST-shape regressions or the new
`count_expr` grammar path; update the test to (1) add a case covering a COUNT
with an inner subquery that contains a WITH (e.g. a "COUNT { ... WITH ... }"
scenario added to `_WITH_POSITION_CASES`), (2) replace or augment the weak
`assert ast is not None` with concrete assertions against the parsed
AST/operator types (use the same parsing entry used in the test to produce `ast`
and assert expected node types/structure for the top-level statement and the
WITH segment), and (3) use the prescribed fixture (e.g. `empty_graph`) so the
unit test isolates parsing behavior rather than execution context.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67a122d and b5bc562.

📒 Files selected for processing (4)
  • src/graphforge/parser/cypher.lark
  • src/graphforge/parser/parser.py
  • tests/integration/test_with_clause_positions.py
  • tests/unit/parser/test_with_clause_positions.py

@DecisionNerd DecisionNerd merged commit a980b6b into main Mar 1, 2026
23 checks passed
DecisionNerd added a commit that referenced this pull request Mar 1, 2026
Grammar and parser changes were already included in #310 (squashed). This
commit adds the corresponding test suite and TCK harness fix:

- Tests: 31 unit tests covering all float literal forms:
  - Standard: 1.0, 3.14 (regression)
  - Leading-dot: .1, .5, .3405892687
  - Exponent notation: 1e9, 1E9, 1e-5, 1e308, 1.5e10, 2.3E-4
  - Mixed: .1e9, .1e-5, .1E-5
  - Overflow: 1.34E999 raises ValueError
  - Negative (unary minus + float): -1e9, -.5, etc.
- TCK harness: fix _parse_value in conftest.py to handle exponent-notation
  floats like 1e308 and 1e-305 that have no decimal point (previously
  fell through to CypherString instead of CypherFloat)

All 30 Literals5 TCK scenarios now pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DecisionNerd DecisionNerd deleted the fix/257-with-clause-positions branch March 1, 2026 21:26
DecisionNerd added a commit that referenced this pull request Mar 1, 2026
…311)

Grammar and parser changes were already included in #310 (squashed). This
commit adds the corresponding test suite and TCK harness fix:

- Tests: 31 unit tests covering all float literal forms:
  - Standard: 1.0, 3.14 (regression)
  - Leading-dot: .1, .5, .3405892687
  - Exponent notation: 1e9, 1E9, 1e-5, 1e308, 1.5e10, 2.3E-4
  - Mixed: .1e9, .1e-5, .1E-5
  - Overflow: 1.34E999 raises ValueError
  - Negative (unary minus + float): -1e9, -.5, etc.
- TCK harness: fix _parse_value in conftest.py to handle exponent-notation
  floats like 1e308 and 1e-305 that have no decimal point (previously
  fell through to CypherString instead of CypherFloat)

All 30 Literals5 TCK scenarios now pass.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: WITH keyword not accepted after certain clause positions in multi-clause queries (93 TCK failures)

1 participant