Skip to content

fix: shortestPath() / allShortestPaths() parse and raise NotImplementedError#497

Merged
DecisionNerd merged 3 commits into
mainfrom
fix/468-shortest-path-parse
May 5, 2026
Merged

fix: shortestPath() / allShortestPaths() parse and raise NotImplementedError#497
DecisionNerd merged 3 commits into
mainfrom
fix/468-shortest-path-parse

Conversation

@DecisionNerd
Copy link
Copy Markdown
Owner

@DecisionNerd DecisionNerd commented May 5, 2026

Summary

  • shortestPath(...) and allShortestPaths(...) previously raised an unhelpful SyntaxError: Unexpected token at parse time
  • Now parse correctly into a ShortestPathExpression AST node; the planner raises NotImplementedError with a clear BFS workaround message
  • All existing path-variable tests (p = (a)-[:R]->(b)) are unaffected

Changes

Layer Change
Grammar SHORTEST_PATH_KW, ALL_SHORTEST_PATHS_KW terminals; path_function rule; path_function_binding alternative in pattern
AST ShortestPathExpression(all: bool, pattern: Any)
Parser shortest_path_func, all_shortest_paths_func, path_function_binding transformer methods
Planner _reject_shortest_path() — raises NotImplementedError with BFS workaround hint

Test plan

  • MATCH p = shortestPath((a)-[*]-(b)) RETURN p raises NotImplementedError with workaround message
  • MATCH p = allShortestPaths((a)-[*]-(b)) RETURN p raises NotImplementedError
  • Error message contains MATCH path = (a)-[*1..N]-(b) BFS workaround hint
  • MATCH p = (a)-[:R]->(b) still works (regular path binding unaffected)
  • Variable-length path ([*1..3]) still works (BFS workaround pattern)
  • 10 new tests (5 parser unit + 5 integration); make pre-push green

Closes #468

🤖 Generated with Claude Code


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by CodeRabbit

  • New Features

    • Recognizes shortestPath() and allShortestPaths() in queries and returns a clear NotImplementedError that includes a suggested BFS (variable-length path) workaround.
  • Tests

    • Added unit and integration tests covering parsing of shortest-path calls, binding behavior, error messages, and that the suggested variable-length path workaround executes as expected.

…edError (#468)

- Add SHORTEST_PATH_KW / ALL_SHORTEST_PATHS_KW terminals to cypher.lark
- Add path_function grammar rule and path_function_binding pattern alternative
- Add ShortestPathExpression AST node (frozen Pydantic model)
- Add transformer methods: shortest_path_func, all_shortest_paths_func, path_function_binding
- Add _reject_shortest_path() planner validation; raises NotImplementedError with
  BFS workaround: MATCH path = (a)-[*1..N]-(b) RETURN length(path) ORDER BY length(path) LIMIT 1
- 5 parser unit tests + 5 integration tests (10 total); pre-push green

Closes #468

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

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 97.43590% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.05%. Comparing base (6be48ac) to head (5605a5d).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #497      +/-   ##
==========================================
+ Coverage   88.02%   88.05%   +0.02%     
==========================================
  Files          40       40              
  Lines       14470    14513      +43     
  Branches     3434     3439       +5     
==========================================
+ Hits        12737    12779      +42     
+ Misses       1142     1141       -1     
- Partials      591      593       +2     
Flag Coverage Δ
full-coverage 88.05% <97.43%> (+0.02%) ⬆️

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

Components Coverage Δ
parser 95.49% <100.00%> (-0.18%) ⬇️
planner 83.07% <92.30%> (+0.18%) ⬆️
executor 83.63% <ø> (ø)
storage 91.25% <ø> (ø)
ast 98.22% <100.00%> (+0.01%) ⬆️
types 94.75% <ø> (ø)

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 6be48ac...5605a5d. Read the comment docs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Added parser support and an AST node for shortestPath() / allShortestPaths() so these forms parse into ShortestPathExpression nodes; the planner now detects them and raises a NotImplementedError with a BFS-workaround hint instead of leaving users with a parse-time SyntaxError.

Changes

Shortest Path Graceful Degradation

Layer / File(s) Summary
Data Shape
src/graphforge/ast/expression.py
New ShortestPathExpression Pydantic model with all: bool and pattern: Any fields representing shortestPath() / allShortestPaths() calls.
Grammar & Tokenization
src/graphforge/parser/cypher.lark
Added path_function rule for shortestPath(pattern_parts) and allShortestPaths(pattern_parts), added path_function_binding alternative to pattern, and new tokens SHORTEST_PATH_KW and ALL_SHORTEST_PATHS_KW.
Parser Transformation
src/graphforge/parser/parser.py
Added ASTTransformer.shortest_path_func, ASTTransformer.all_shortest_paths_func, and ASTTransformer.path_function_binding to produce ShortestPathExpression instances and binding dicts.
Planner Integration
src/graphforge/planner/planner.py
Inserted call to _reject_shortest_path(ast) in QueryPlanner.plan() and added _reject_shortest_path which scans MATCH/OPTIONAL MATCH clauses for ShortestPathExpression and raises NotImplementedError including a BFS workaround example.
Tests
tests/unit/parser/test_shortest_path_parser.py, tests/integration/test_shortest_path.py
Unit tests validate parsing and AST shape (including all flag and inner pattern parts); integration tests assert NotImplementedError contains function name and workaround hint, and verify variable-length MATCH workaround still executes as expected on a small graph.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Parser
    participant AST
    participant Planner

    Client->>Parser: submit query with shortestPath()/allShortestPaths()
    Parser->>AST: transform parse tree -> ShortestPathExpression
    Client->>Planner: plan(query AST)
    Planner->>AST: scan MATCH clauses for path_function
    AST-->>Planner: ShortestPathExpression found
    Planner-->>Client: raise NotImplementedError (includes BFS workaround hint)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • DecisionNerd/graphforge#398: Modifies parser and QueryPlanner validations for path-related constructs; related surface area in parser/planner changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description includes a comprehensive summary, organized changes table, test plan with checkmarks, and issue closure reference. However, several required template sections are not completed: Type of Change checkboxes, full Changes Made list, Test Commands Run, and most Checklist items (PR Size/Quality, Code Quality, Testing, Documentation, Compliance). Complete the PR description template by checking Type of Change (Bug fix), filling Testing section with actual commands run, and confirming Checklist items (ruff, mypy, CHANGELOG.md, docstrings, coverage). Add explicit confirmation of test coverage and tool compliance.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: shortestPath() / allShortestPaths() parse and raise NotImplementedError' is clear and directly describes the main change: adding parsing support for shortest-path functions that now raise NotImplementedError instead of SyntaxError.
Linked Issues check ✅ Passed The PR successfully implements Part A of issue #468 (required scope): grammar and parser recognize shortestPath/allShortestPaths, AST includes ShortestPathExpression, planner raises NotImplementedError with BFS workaround message. All Part A acceptance criteria are met: correct parsing, helpful error messages, and valid syntax handling.
Out of Scope Changes check ✅ Passed All changes are directly in-scope for issue #468 Part A: grammar, AST, parser, and planner modifications for shortest-path support; tests validating this functionality. No extraneous changes to unrelated functionality or systems.
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
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/468-shortest-path-parse

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: 3

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

12-32: ⚡ Quick win

Parametrize the two parser happy-path cases.

test_shortest_path_parses and test_all_shortest_paths_parses are the same test shape with different inputs and expected all values, so they should be one parametrized test.

As per coding guidelines, "Use pytest parametrization (@pytest.mark.parametrize) when testing the same logic with different inputs to avoid code duplication".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/parser/test_shortest_path_parser.py` around lines 12 - 32, Combine
the two tests test_shortest_path_parses and test_all_shortest_paths_parses into
one parametrized pytest function using `@pytest.mark.parametrize`; feed it two
cases with the Cypher strings "MATCH p = shortestPath((a)-[*]-(b)) RETURN p" and
"MATCH p = allShortestPaths((a)-[*]-(b)) RETURN p" and expected values for
expr.all (False, True), then inside the single test call parse_cypher, retrieve
match = ast.clauses[0], pattern = match.patterns[0], expr =
pattern["path_function"], assert isinstance(expr, ShortestPathExpression) and
assert expr.all equals the parameterized expected value so you remove duplicate
logic in test_shortest_path_parses and test_all_shortest_paths_parses.
tests/integration/test_shortest_path.py (1)

17-42: ⚡ Quick win

Parametrize the shortest/all-shortest-path exception cases.

These four tests differ only by function name and assertion target, so a small @pytest.mark.parametrize table would remove duplication and make future message-contract additions easier to keep in sync.

As per coding guidelines, "Use pytest parametrization (@pytest.mark.parametrize) when testing the same logic with different inputs to avoid code duplication".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_shortest_path.py` around lines 17 - 42, The four tests
test_shortest_path_raises_not_implemented,
test_shortest_path_message_contains_workaround,
test_all_shortest_paths_raises_not_implemented and
test_all_shortest_paths_message_contains_workaround duplicate the same logic;
refactor them using pytest.mark.parametrize to drive both function-name variants
("shortestPath" and "allShortestPaths") and the two assertion types (match
string vs checking _WORKAROUND_HINT in exception text). Replace the duplicated
GraphForge() setup and gf.execute(...) calls with a single parametrized test
that calls GraphForge().execute with the appropriate Cypher (use the function
name to build the query) and asserts either pytest.raises(..., match=...) for
the function-name message or captures the exception and asserts _WORKAROUND_HINT
in str(exc.value); reference GraphForge.execute and _WORKAROUND_HINT in the new
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/graphforge/parser/cypher.lark`:
- Around line 354-355: The two new terminals SHORTEST_PATH_KW.2 and
ALL_SHORTEST_PATHS_KW.2 are currently case-sensitive; update their regexes to be
case-insensitive (so variants like SHORTESTPATH or allshortestpaths match) by
adding an inline case-insensitive flag to each pattern (apply to
SHORTEST_PATH_KW.2 and ALL_SHORTEST_PATHS_KW.2) while preserving the negative
lookahead for alphanumeric/underscore to avoid partial matches.

In `@src/graphforge/planner/planner.py`:
- Around line 3416-3423: The _reject_shortest_path() check currently only
inspects MatchClause and OptionalMatchClause, so path_function_binding used in
other clause types (e.g., CREATE, MERGE) is missed; update the logic that
iterates ast.clauses and clause.patterns to not restrict to
MatchClause/OptionalMatchClause — either remove the isinstance guard or extend
it to include CreateClause and MergeClause (or any clause classes that can
contain pattern/path_function_binding) and ensure you still detect dict patterns
with "path_function" and ShortestPathExpression to map to func_name handling;
modify the loop around ast.clauses/for pattern in clause.patterns accordingly in
_reject_shortest_path().
- Around line 3424-3428: The NotImplementedError raised in planner.py (the raise
NotImplementedError(...) that uses func_name and the BFS workaround message)
must include the tracking issue number; update the error text to append or
include "See tracking issue `#468`" (or include "#468") so the message contains
the issue identifier alongside the existing workaround instructions in the
NotImplementedError raised for func_name.

---

Nitpick comments:
In `@tests/integration/test_shortest_path.py`:
- Around line 17-42: The four tests test_shortest_path_raises_not_implemented,
test_shortest_path_message_contains_workaround,
test_all_shortest_paths_raises_not_implemented and
test_all_shortest_paths_message_contains_workaround duplicate the same logic;
refactor them using pytest.mark.parametrize to drive both function-name variants
("shortestPath" and "allShortestPaths") and the two assertion types (match
string vs checking _WORKAROUND_HINT in exception text). Replace the duplicated
GraphForge() setup and gf.execute(...) calls with a single parametrized test
that calls GraphForge().execute with the appropriate Cypher (use the function
name to build the query) and asserts either pytest.raises(..., match=...) for
the function-name message or captures the exception and asserts _WORKAROUND_HINT
in str(exc.value); reference GraphForge.execute and _WORKAROUND_HINT in the new
test.

In `@tests/unit/parser/test_shortest_path_parser.py`:
- Around line 12-32: Combine the two tests test_shortest_path_parses and
test_all_shortest_paths_parses into one parametrized pytest function using
`@pytest.mark.parametrize`; feed it two cases with the Cypher strings "MATCH p =
shortestPath((a)-[*]-(b)) RETURN p" and "MATCH p = allShortestPaths((a)-[*]-(b))
RETURN p" and expected values for expr.all (False, True), then inside the single
test call parse_cypher, retrieve match = ast.clauses[0], pattern =
match.patterns[0], expr = pattern["path_function"], assert isinstance(expr,
ShortestPathExpression) and assert expr.all equals the parameterized expected
value so you remove duplicate logic in test_shortest_path_parses and
test_all_shortest_paths_parses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6f25348d-6c8a-4202-b5a2-a9ee53b6e718

📥 Commits

Reviewing files that changed from the base of the PR and between 6be48ac and 2bb1067.

📒 Files selected for processing (6)
  • src/graphforge/ast/expression.py
  • src/graphforge/parser/cypher.lark
  • src/graphforge/parser/parser.py
  • src/graphforge/planner/planner.py
  • tests/integration/test_shortest_path.py
  • tests/unit/parser/test_shortest_path_parser.py

Comment thread src/graphforge/parser/cypher.lark Outdated
Comment thread src/graphforge/planner/planner.py
Comment thread src/graphforge/planner/planner.py
DecisionNerd and others added 2 commits May 5, 2026 15:25
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Case-insensitive SHORTEST_PATH_KW / ALL_SHORTEST_PATHS_KW terminals
- _reject_shortest_path: use hasattr(clause, 'patterns') instead of
  MatchClause/OptionalMatchClause guard to catch any clause with patterns
- NotImplementedError message now includes tracking issue (#468)
- Parametrize parser and integration tests to remove duplication

Co-Authored-By: Claude Sonnet 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: shortestPath() causes SyntaxError — parser does not recognise the function

1 participant