Skip to content

Update Cypher Function Translations - BED-8261#77

Merged
zinic merged 6 commits into
SpecterOps:mainfrom
zinic:BP-526
May 12, 2026
Merged

Update Cypher Function Translations - BED-8261#77
zinic merged 6 commits into
SpecterOps:mainfrom
zinic:BP-526

Conversation

@zinic
Copy link
Copy Markdown
Contributor

@zinic zinic commented May 11, 2026

Description

Adds PostgreSQL Cypher translation support for path relationship functions, including relationships(), startNode(), and endNode(), and fixes related Cypher semantic drift around labels() and type() handling. Also hardens PG function translation edge cases and updates fixtures/docs for the new behavior.

Resolves: BED-8261

Type of Change

  • Chore (a change that does not modify the application functionality)
  • Bug fix (a change that fixes an issue)
  • New feature / enhancement (a change that adds new functionality)
  • Refactor (no behaviour change)
  • Test coverage
  • Build / CI / tooling
  • Documentation

Testing

  • Unit tests added / updated
  • Integration tests added / updated
  • Full test suite run (make test_all with CONNECTION_STRING set)

Screenshots (if appropriate):

N/A

Driver Impact

  • PostgreSQL driver (drivers/pg)
  • Neo4j driver (drivers/neo4j)

Checklist

  • Code is formatted
  • All existing tests pass
  • go.mod / go.sum are up to date if dependencies changed

Summary by CodeRabbit

Release Notes

  • New Features

    • Added startNode() and endNode() Cypher functions for retrieving path segment endpoints
    • Extended labels() function support for node label filtering and aggregation operations
    • Added type() function support for relationship type retrieval and filtering
    • Enhanced shortest path queries with improved relationship filtering capabilities
  • Tests

    • Added comprehensive test coverage for new label and relationship type operations

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db411913-7615-4be5-83d1-7fb24d329cbc

📥 Commits

Reviewing files that changed from the base of the PR and between dd8b0c8 and ae5d313.

📒 Files selected for processing (27)
  • cypher/Cypher Syntax Support.md
  • cypher/models/cypher/functions.go
  • cypher/models/pgsql/functions.go
  • cypher/models/pgsql/pgtypes.go
  • cypher/models/pgsql/test/translation_cases/multipart.sql
  • cypher/models/pgsql/test/translation_cases/nodes.sql
  • cypher/models/pgsql/test/translation_cases/pattern_binding.sql
  • cypher/models/pgsql/test/translation_cases/scalar_aggregation.sql
  • cypher/models/pgsql/test/translation_cases/shortest_paths.sql
  • cypher/models/pgsql/test/translation_cases/stepwise_traversal.sql
  • cypher/models/pgsql/test/translation_cases/unwind.sql
  • cypher/models/pgsql/test/translation_test.go
  • cypher/models/pgsql/translate/expression.go
  • cypher/models/pgsql/translate/function.go
  • cypher/models/pgsql/translate/function_test.go
  • cypher/models/pgsql/translate/hinting.go
  • cypher/models/pgsql/translate/hinting_test.go
  • cypher/models/pgsql/translate/path_functions.go
  • cypher/models/pgsql/translate/projection.go
  • cypher/models/pgsql/translate/with.go
  • drivers/pg/query/sql/schema_down.sql
  • drivers/pg/query/sql/schema_up.sql
  • integration/testdata/cases/aggregation.json
  • integration/testdata/cases/nodes.json
  • integration/testdata/cases/stepwise.json
  • integration/testdata/cases/unwind_inline.json
  • integration/testdata/templates/pattern_shapes.json

Walkthrough

This PR adds support for Cypher path functions (relationships(), startNode(), endNode()) by introducing PostgreSQL schema helper functions, extending the type system with composite property access, implementing path composite field resolution infrastructure, and updating function translation logic to handle node labels and relationship type strings. All changes include comprehensive SQL translation fixtures and integration test data.

Changes

Core Cypher Path Functions & Schema Foundation

Layer / File(s) Summary
Cypher & PostgreSQL Function Identifiers
cypher/models/cypher/functions.go, cypher/models/pgsql/functions.go, cypher/models/pgsql/pgtypes.go
New exported Cypher function constants (StartNodeFunction, EndNodeFunction) and PostgreSQL function/type identifiers (FunctionKindName, FunctionStartNode, FunctionEndNode, FunctionGenerateSubscripts, TableKind, ColumnName).
PostgreSQL Schema Helper Functions
drivers/pg/query/sql/schema_up.sql, drivers/pg/query/sql/schema_down.sql, cypher/Cypher Syntax Support.md
Three new SQL functions (kind_name, start_node, end_node) for retrieving kind names and edge start/end nodes; corresponding teardown drops; documentation for relationships(), startNode(), and endNode() Cypher functions with examples.

Expression Translation & Type Hinting

Layer / File(s) Summary
Composite Property Lookup Translation
cypher/models/pgsql/translate/expression.go
Add helpers to detect composite-typed expressions and translate property lookups into OperatorPropertyLookup using RowColumnReference to ColumnProperties for node/edge property access.
IN Operator & TypeCast Rewriting
cypher/models/pgsql/translate/expression.go
Update IN operator binary expression rewriting with fallback AnyExpression wrapping when TypeCast rewrite does not occur, using the cast's type hint.
Relationship Type Kind-ID Hinting
cypher/models/pgsql/translate/hinting.go, cypher/models/pgsql/translate/hinting_test.go
Add relationshipTypeKindIDExpression and literalKindID helpers to convert text type() comparisons to mapped kind-ID literals in applyTypeFunctionLikeTypeHints; gate conversions for equality/inequality operators; test coverage for empty mapper case.

Function Translation for Labels, Type, & Collect

Layer / File(s) Summary
Node Labels & Collect Expression Preparation
cypher/models/pgsql/translate/function.go
Add prepareCollectExpression to infer array types for collect() by resolving identifier bindings and applying JSONB conversion; add translateNodeLabelsExpression to construct ordered label-ID arrays using TableKind and generate_subscripts; add ColumnEdges special case for path component function translation.
translateFunction Updates & Collect Wrapping
cypher/models/pgsql/translate/function.go, cypher/models/pgsql/translate/function_test.go
Update EdgeTypeFunction to emit kind_name() calls, NodeLabelsFunction to use translateNodeLabelsExpression, and CollectFunction to use prepareCollectExpression with type inference; simplify functionWrapCollectToArray coalesce typing; add test for collect binding validation.

Path Composite Field Resolution

Layer / File(s) Summary
Path Composite Edge Expression Builder & Field Resolution
cypher/models/pgsql/translate/path_functions.go
New module implementing pathCompositeEdgesExpression to construct EdgeComposite[] arrays from path dependencies and resolvePathCompositeFieldReference to resolve RowColumnReference against path-composite bindings, supporting only edges column with LastProjection rewrites.
Recursive Path Composite Field Reference Rewriter
cypher/models/pgsql/translate/path_functions.go
Implement resolvePathCompositeFieldReferences, a comprehensive recursive expression-tree rewriter that walks and updates path-composite references across SELECT projections, FROM/JOIN clauses, WHERE, GROUP BY, HAVING, and nested subqueries.
Projection Builder & WITH Integration
cypher/models/pgsql/translate/projection.go, cypher/models/pgsql/translate/with.go
Integrate path-composite field resolution into buildExternalProjection, buildInlineProjection, buildTailProjection for expressions, and translateWith for constraint and identifier-projection handling; guard projection alias assignment for path-composite bindings.

SQL Fixtures & Integration Tests

Layer / File(s) Summary
SQL Translation Test Fixtures
cypher/models/pgsql/test/translation_cases/*.sql
Updated multipart, nodes, pattern_binding, scalar_aggregation, shortest_paths, stepwise_traversal, and unwind translation fixtures; new test cases for labels(n) filtering/aggregation, type(r) selection and filtering (equality, inequality, IN, STARTS WITH), path predicates via relationships(p), and shortest-path relationship exclusion via NONE.
Integration Test Data & Test Helpers
integration/testdata/cases/*.json, integration/testdata/templates/*.json, cypher/models/pgsql/test/translation_test.go
Expanded integration testdata with aggregation labels case, comprehensive nodes labels tests, stepwise type(r) filter variants, unwind labels case; new pattern_shapes template for shortest-path relationship predicate functions; refactored newKindMapper test helper to register kinds via loop.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • SpecterOps/DAWGS#76: Related modification to Cypher path-component/function translation and pgsql function identifiers.
  • SpecterOps/DAWGS#49: Related changes to projection.go, with.go, and path/composite field resolution infrastructure.

Suggested reviewers

  • urangel

Poem

🐰 A path through the forest, relationships found,
Start and end nodes dance all around,
Labels collected, kinds named with care,
Path functions tested, composite fields fair!
Hop hop to review this translation delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR description includes all required template sections with substantive details: clear objective (adds Cypher path relationship function support), ticket reference (BED-8261), appropriate type selections, testing confirmations, driver impact, and checklist items.
Title check ✅ Passed The PR title 'Update Cypher Function Translations - BED-8261' directly addresses the main changes: adding support for three new Cypher functions (relationships, startNode, endNode) and fixing function translation/behavior for labels and type functions.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 4

🤖 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 `@cypher/Cypher` Syntax Support.md:
- Around line 248-250: The fenced code blocks containing Cypher examples (e.g.,
the snippets starting with "match p = (a)-[*1..]->(b) return relationships(p)",
the longer "match p = (a)-[*1..]->(b) where none(r in relationships(p) where
startNode(r).name = 'blocked') return p", and "match ()-[r]->() return
endNode(r)") are missing language identifiers and trigger MD040; update each
triple-backtick fence to include the "cypher" language tag (```cypher) so the
examples are properly tagged for syntax highlighting and linting.

In `@cypher/models/pgsql/translate/function.go`:
- Around line 56-67: The error message in prepareCollectExpression is misleading
because it prints functionName where the missing argument identifier should be;
update the fmt.Errorf call in the branch handling unwrapParenthetical ->
pgsql.Identifier to include the actual identifier (typedArgument) and keep
functionName for context (e.g., "binding not found for collect function argument
<identifier> (function: <functionName>)"); locate this in
prepareCollectExpression near the Scope.Lookup(...) check and adjust the error
string accordingly so it surfaces the real unbound argument and the function
name.

In `@cypher/models/pgsql/translate/hinting.go`:
- Around line 185-201: In literalKindID add a defensive length check before
accessing kindIDs[0]: after calling kindMapper.MapKinds(kinds) verify
len(kindIDs) > 0 and if it's zero return pgsql.Literal{}, false, nil so the
caller treats it as "not converted" rather than panicking; keep existing error
returns for MapKinds and AsKinds unchanged and only add this guard before
constructing the pgsql.NewLiteral.

In `@integration/testdata/cases/aggregation.json`:
- Around line 29-33: The test case "return the size of collected labels lists"
currently uses a brittle exact_int: 3 assertion; change the assertion to a
lower-bound style (e.g., replace the "assert": {"exact_int": 3} with a minimum
assertion such as {"min_int": 3} or the project's equivalent "gte"/"min"
assertion) so the test asserts at least 3 results instead of an exact
cardinality and stays robust as the base dataset grows; update only the
assertion object for the case with cypher "MATCH (n) WITH collect(labels(n)) as
label_sets RETURN size(label_sets)" to use the project's lower-bound assertion
keyword.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c790b28-94c2-4b27-8c30-df734396843d

📥 Commits

Reviewing files that changed from the base of the PR and between eea249f and 8fdcd3a.

📒 Files selected for processing (26)
  • cypher/Cypher Syntax Support.md
  • cypher/models/cypher/functions.go
  • cypher/models/pgsql/functions.go
  • cypher/models/pgsql/model.go
  • cypher/models/pgsql/pgtypes.go
  • cypher/models/pgsql/test/translation_cases/multipart.sql
  • cypher/models/pgsql/test/translation_cases/nodes.sql
  • cypher/models/pgsql/test/translation_cases/scalar_aggregation.sql
  • cypher/models/pgsql/test/translation_cases/shortest_paths.sql
  • cypher/models/pgsql/test/translation_cases/stepwise_traversal.sql
  • cypher/models/pgsql/test/translation_cases/unwind.sql
  • cypher/models/pgsql/test/translation_test.go
  • cypher/models/pgsql/translate/expression.go
  • cypher/models/pgsql/translate/function.go
  • cypher/models/pgsql/translate/hinting.go
  • cypher/models/pgsql/translate/path_functions.go
  • cypher/models/pgsql/translate/projection.go
  • cypher/models/pgsql/translate/quantifiers.go
  • cypher/models/pgsql/translate/with.go
  • drivers/pg/query/sql/schema_down.sql
  • drivers/pg/query/sql/schema_up.sql
  • integration/testdata/cases/aggregation.json
  • integration/testdata/cases/nodes.json
  • integration/testdata/cases/stepwise.json
  • integration/testdata/cases/unwind_inline.json
  • integration/testdata/templates/pattern_shapes.json

Comment on lines +248 to +250
```
match p = (a)-[*1..]->(b) return relationships(p)
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to the new fenced Cypher examples.

These fenced blocks omit a language tag and trigger MD040 (fenced-code-language).

✏️ Suggested patch
-```
+```cypher
 match p = (a)-[*1..]->(b) return relationships(p)

- +cypher
match p = (a)-[*1..]->(b)
where none(r in relationships(p) where startNode(r).name = 'blocked')
return p


-```
+```cypher
match ()-[r]->() return endNode(r)
</details>


Also applies to: 256-260, 266-268

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 248-248: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @cypher/Cypher Syntax Support.md around lines 248 - 250, The fenced code
blocks containing Cypher examples (e.g., the snippets starting with "match p =
(a)-[*1..]->(b) return relationships(p)", the longer "match p = (a)-[*1..]->(b)
where none(r in relationships(p) where startNode(r).name = 'blocked') return p",
and "match ()-[r]->() return endNode(r)") are missing language identifiers and
trigger MD040; update each triple-backtick fence to include the "cypher"
language tag (```cypher) so the examples are properly tagged for syntax
highlighting and linting.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- d98c2f50 -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment thread cypher/models/pgsql/translate/function.go
Comment thread cypher/models/pgsql/translate/hinting.go
Comment thread integration/testdata/cases/aggregation.json
Copy link
Copy Markdown

@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: 1

🤖 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 `@cypher/models/pgsql/test/translation_cases/pattern_binding.sql`:
- Around line 26-27: The any(...) predicate is tautological because the CTE s1
only allows edges with kind_id in array [3,4] (EdgeKind1|EdgeKind2) while the
final WHERE checks type(r) STARTS WITH 'EdgeKind', which always holds; update
the predicate so it discriminates: either change the STARTS WITH check to an
exact match (e.g., kind_name(i0.kind_id) = 'EdgeKind1' or = 'EdgeKind2') or
adjust the allowed kinds in the s1/e0 filter (the array [3,4]::int2[]) to
include a non-matching kind, or replace the final condition with a stricter
check (e.g., kind_name(i0.kind_id) = ANY(array[...]::text[])); target the final
WHERE expression that uses kind_name(i0.kind_id)::text like 'EdgeKind%' and the
s1/e0 edge-kind filter so the predicate is not always true.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 701d5a48-f22e-4f0d-bbb8-bd5993f60ed2

📥 Commits

Reviewing files that changed from the base of the PR and between 8fdcd3a and dd8b0c8.

📒 Files selected for processing (10)
  • cypher/models/pgsql/functions.go
  • cypher/models/pgsql/test/translation_cases/pattern_binding.sql
  • cypher/models/pgsql/test/translation_cases/stepwise_traversal.sql
  • cypher/models/pgsql/translate/function.go
  • cypher/models/pgsql/translate/hinting.go
  • drivers/pg/query/sql/schema_down.sql
  • drivers/pg/query/sql/schema_up.sql
  • integration/testdata/cases/aggregation.json
  • integration/testdata/cases/stepwise.json
  • integration/testdata/templates/pattern_shapes.json
✅ Files skipped from review due to trivial changes (2)
  • drivers/pg/query/sql/schema_down.sql
  • integration/testdata/cases/aggregation.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • cypher/models/pgsql/translate/function.go
  • cypher/models/pgsql/translate/hinting.go
  • cypher/models/pgsql/functions.go
  • drivers/pg/query/sql/schema_up.sql

Comment thread cypher/models/pgsql/test/translation_cases/pattern_binding.sql
@zinic zinic force-pushed the BP-526 branch 2 times, most recently from b28e37d to 575b815 Compare May 12, 2026 04:57
@zinic zinic changed the title Update Cypher Function Translations - BP-526 Update Cypher Function Translations - BED-8261 May 12, 2026
@zinic zinic merged commit ae5d313 into SpecterOps:main May 12, 2026
4 of 6 checks passed
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.

1 participant