Conversation
WalkthroughThis PR updates test translation cases in Cypher/PostgreSQL patterns, replacing domain/name-based filtering with objectid-based predicates in multipart patterns, adding two new pattern binding test cases with recursive traversals, and adjusting a selectivity weight constant for bound identifier optimization. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cypher/models/pgsql/test/translation_cases/pattern_binding.sql`:
- Around line 56-57: The recursive CTE never applies the terminal-node predicate
because the s2 column "satisfied" is computed but not used and the base case
sets satisfied to false; update the s2 recursive definition in the CTE (symbols:
s1, s2, satisfied, path, ep0) so the base case computes satisfied using the same
expression as the recursive step (e.g. check coalesce((n2.properties ->>
'system_tags'),'')::text like '%admin_tier_0%' and n2.kind_ids @>
array[2]::int2[]) and then add a WHERE filter in the outer select from s2 (or
inside s1) to only emit rows where satisfied = true before constructing p;
ensure the modified logic mirrors the patterns used around lines 10 and 42
(compute satisfied in both base and recursive branches and filter by satisfied
when selecting paths).
| -- case: match p = (:NodeKind1)-[:EdgeKind1]->(:NodeKind2)-[:EdgeKind2*1..]->(t:NodeKind2) where coalesce(t.system_tags, '') contains 'admin_tier_0' return p limit 1000 | ||
| with s0 as (select (e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite as e0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from edge e0 join node n0 on n0.id = e0.start_id join node n1 on n1.id = e0.end_id where n1.kind_ids operator (pg_catalog.@>) array [2]::int2[] and e0.kind_id = any (array [3]::int2[]) and n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]), s1 as (with recursive s2(root_id, next_id, depth, satisfied, is_cycle, path) as (select e1.start_id, e1.end_id, 1, false, e1.start_id = e1.end_id, array [e1.id] from s0 join edge e1 on (s0.n1).id = e1.start_id join node n2 on n2.id = e1.end_id where e1.kind_id = any (array [4]::int2[]) union select s2.root_id, e1.end_id, s2.depth + 1, (coalesce((n2.properties ->> 'system_tags'), '')::text like '%admin_tier_0%') and n2.kind_ids operator (pg_catalog.@>) array [2]::int2[], e1.id = any (s2.path), s2.path || e1.id from s2 join edge e1 on e1.start_id = s2.next_id join node n2 on n2.id = e1.end_id where e1.kind_id = any (array [4]::int2[]) and s2.depth <= 15 and not s2.is_cycle) select s0.e0 as e0, (select array_agg((e1.id, e1.start_id, e1.end_id, e1.kind_id, e1.properties)::edgecomposite) from edge e1 where e1.id = any (s2.path)) as e1, s2.path as ep0, s0.n0 as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1, (n2.id, n2.kind_ids, n2.properties)::nodecomposite as n2 from s0, s2 join node n1 on n1.id = s2.root_id join node n2 on n2.id = s2.next_id) select edges_to_path(variadic array [(s1.e0).id]::int8[] || s1.ep0)::pathcomposite as p from s1 limit 1000; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the file size and read lines around 56-57
wc -l cypher/models/pgsql/test/translation_cases/pattern_binding.sqlRepository: SpecterOps/DAWGS
Length of output: 125
🏁 Script executed:
# Read the file to see the context around lines 56-57
sed -n '50,70p' cypher/models/pgsql/test/translation_cases/pattern_binding.sql | cat -nRepository: SpecterOps/DAWGS
Length of output: 8025
🏁 Script executed:
# Search for all satisfied-related patterns in the file
rg -n "satisfied" cypher/models/pgsql/test/translation_cases/pattern_binding.sql -A 2 -B 2Repository: SpecterOps/DAWGS
Length of output: 7644
Missing terminal-node filter in recursive path.
Line 56–57 computes satisfied but never filters on it, so the t.system_tags constraint from the Cypher predicate is not enforced in the SQL. The base case should also compute satisfied correctly rather than hardcoding false. Please add the filter and update the base case to match the pattern used in similar cases (lines 10, 42).
🐛 Suggested fix
- with s0 as (select ...), s1 as (with recursive s2(root_id, next_id, depth, satisfied, is_cycle, path) as (
- select e1.start_id, e1.end_id, 1, false, e1.start_id = e1.end_id, array [e1.id]
+ with s0 as (select ...), s1 as (with recursive s2(root_id, next_id, depth, satisfied, is_cycle, path) as (
+ select e1.start_id, e1.end_id, 1,
+ (coalesce((n2.properties ->> 'system_tags'), '')::text like '%admin_tier_0%')
+ and n2.kind_ids operator (pg_catalog.@>) array [2]::int2[],
+ e1.start_id = e1.end_id, array [e1.id]
from s0 join edge e1 on (s0.n1).id = e1.start_id join node n2 on n2.id = e1.end_id
where e1.kind_id = any (array [4]::int2[])
union
select s2.root_id, e1.end_id, s2.depth + 1,
(coalesce((n2.properties ->> 'system_tags'), '')::text like '%admin_tier_0%')
and n2.kind_ids operator (pg_catalog.@>) array [2]::int2[],
e1.id = any (s2.path), s2.path || e1.id
from s2 join edge e1 on e1.start_id = s2.next_id join node n2 on n2.id = e1.end_id
where e1.kind_id = any (array [4]::int2[]) and s2.depth <= 15 and not s2.is_cycle
- ) select ... from s0, s2 join node n1 on n1.id = s2.root_id join node n2 on n2.id = s2.next_id) select ...
+ ) select ... from s0, s2 join node n1 on n1.id = s2.root_id join node n2 on n2.id = s2.next_id
+ where s2.satisfied
+ ) select ...🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cypher/models/pgsql/test/translation_cases/pattern_binding.sql` around lines
56 - 57, The recursive CTE never applies the terminal-node predicate because the
s2 column "satisfied" is computed but not used and the base case sets satisfied
to false; update the s2 recursive definition in the CTE (symbols: s1, s2,
satisfied, path, ep0) so the base case computes satisfied using the same
expression as the recursive step (e.g. check coalesce((n2.properties ->>
'system_tags'),'')::text like '%admin_tier_0%' and n2.kind_ids @>
array[2]::int2[]) and then add a WHERE filter in the outer select from s2 (or
inside s1) to only emit rows where satisfied = true before constructing p;
ensure the modified logic mirrors the patterns used around lines 10 and 42
(compute satisfied in both base and recursive branches and filter by satisfied
when selecting paths).
seanjSO
left a comment
There was a problem hiding this comment.
I think this looks good! It's probably still worth getting another set of eyes here because I am still trying to get my brain wrapped around selectivity...
Queries that contain bound right nodes that are referenced in a different frame result in translations that surface the SQLSTATE42P01 error upon query execution. Selectivity optimization rewrites that flip query directions seem to be part of the underlying issue.
This fix mitigates these errors in some queries that are experiencing the issue by increasing the selectivity measure for bound nodes which decreases the chance of a query rewrite.
Each query noted in the issues has been added as a test case and were verified to execute without error.
Summary by CodeRabbit
Release Notes
Tests
Performance