From 2043e286517cf8790bd8df90fd94f99994240513 Mon Sep 17 00:00:00 2001 From: Ulises Rangel Date: Mon, 9 Mar 2026 11:25:32 -0500 Subject: [PATCH] fix: terminal node constraint projection in expansion step BED-7471 --- .../test/translation_cases/multipart.sql | 2 +- .../translation_cases/pattern_binding.sql | 2 +- .../translation_cases/pattern_expansion.sql | 4 +- cypher/models/pgsql/translate/expansion.go | 292 ++++++++---------- 4 files changed, 125 insertions(+), 175 deletions(-) diff --git a/cypher/models/pgsql/test/translation_cases/multipart.sql b/cypher/models/pgsql/test/translation_cases/multipart.sql index 421de57..af6da1b 100644 --- a/cypher/models/pgsql/test/translation_cases/multipart.sql +++ b/cypher/models/pgsql/test/translation_cases/multipart.sql @@ -45,7 +45,7 @@ with s0 as (with s1 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposit with s0 as (select 'a' as i0), s1 as (select s0.i0 as i0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from s0, node n0 where ((n0.properties ->> 'domain') = ' ' and (n0.properties ->> 'name') like s0.i0 || '%') and n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as o from s1; -- case: match (dc)-[r:EdgeKind1*0..]->(g:NodeKind1) where g.objectid ends with '-516' with collect(dc) as exclude match p = (c:NodeKind2)-[n:EdgeKind2]->(u:NodeKind2)-[:EdgeKind2*1..]->(g:NodeKind1) where g.objectid ends with '-512' and not c in exclude return p limit 100 -with s0 as (with s1 as (with recursive s2(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, false, e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n1 on n1.id = e0.end_id join node n0 on n0.id = e0.start_id where ((n1.properties ->> 'objectid') like '%-516') and n1.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3]::int2[]) union select s2.root_id, e0.start_id, s2.depth + 1, false, e0.id = any (s2.path), s2.path || e0.id from s2 join edge e0 on e0.end_id = s2.next_id join node n0 on n0.id = e0.start_id where e0.kind_id = any (array [3]::int2[]) and s2.depth <= 15 and not s2.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s2.path)) as e0, s2.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s2 join node n1 on n1.id = s2.root_id join node n0 on n0.id = s2.next_id) select array_remove(coalesce(array_agg(s1.n0)::nodecomposite[], array []::nodecomposite[])::nodecomposite[], null)::nodecomposite[] as i0 from s1), s3 as (select (e1.id, e1.start_id, e1.end_id, e1.kind_id, e1.properties)::edgecomposite as e1, s0.i0 as i0, (n2.id, n2.kind_ids, n2.properties)::nodecomposite as n2, (n3.id, n3.kind_ids, n3.properties)::nodecomposite as n3 from s0, edge e1 join node n2 on n2.id = e1.start_id join node n3 on n3.id = e1.end_id where n3.kind_ids operator (pg_catalog.@>) array [2]::int2[] and e1.kind_id = any (array [4]::int2[]) and (not n2.id = any ((s0.i0).id)) and n2.kind_ids operator (pg_catalog.@>) array [2]::int2[]), s4 as (with recursive s5(root_id, next_id, depth, satisfied, is_cycle, path) as (select e2.start_id, e2.end_id, 1, false, e2.start_id = e2.end_id, array [e2.id] from s3 join edge e2 on (s3.n3).id = e2.start_id join node n4 on n4.id = e2.end_id where e2.kind_id = any (array [4]::int2[]) union select s5.root_id, e2.end_id, s5.depth + 1, ((n4.properties ->> 'objectid') like '%-512') and n4.kind_ids operator (pg_catalog.@>) array [1]::int2[], e2.id = any (s5.path), s5.path || e2.id from s5 join edge e2 on e2.start_id = s5.next_id join node n4 on n4.id = e2.end_id where e2.kind_id = any (array [4]::int2[]) and s5.depth <= 15 and not s5.is_cycle) select s3.e1 as e1, (select array_agg((e2.id, e2.start_id, e2.end_id, e2.kind_id, e2.properties)::edgecomposite) from edge e2 where e2.id = any (s5.path)) as e2, s5.path as ep1, s3.i0 as i0, s3.n2 as n2, (n3.id, n3.kind_ids, n3.properties)::nodecomposite as n3, (n4.id, n4.kind_ids, n4.properties)::nodecomposite as n4 from s3, s5 join node n3 on n3.id = s5.root_id join node n4 on n4.id = s5.next_id) select edges_to_path(variadic array [(s4.e1).id]::int8[] || s4.ep1)::pathcomposite as p from s4 limit 100; +with s0 as (with s1 as (with recursive s2(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, false, e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n1 on n1.id = e0.end_id join node n0 on n0.id = e0.start_id where ((n1.properties ->> 'objectid') like '%-516') and n1.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3]::int2[]) union select s2.root_id, e0.start_id, s2.depth + 1, false, e0.id = any (s2.path), s2.path || e0.id from s2 join edge e0 on e0.end_id = s2.next_id join node n0 on n0.id = e0.start_id where e0.kind_id = any (array [3]::int2[]) and s2.depth <= 15 and not s2.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s2.path)) as e0, s2.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s2 join node n1 on n1.id = s2.root_id join node n0 on n0.id = s2.next_id) select array_remove(coalesce(array_agg(s1.n0)::nodecomposite[], array []::nodecomposite[])::nodecomposite[], null)::nodecomposite[] as i0 from s1), s3 as (select (e1.id, e1.start_id, e1.end_id, e1.kind_id, e1.properties)::edgecomposite as e1, s0.i0 as i0, (n2.id, n2.kind_ids, n2.properties)::nodecomposite as n2, (n3.id, n3.kind_ids, n3.properties)::nodecomposite as n3 from s0, edge e1 join node n2 on n2.id = e1.start_id join node n3 on n3.id = e1.end_id where n3.kind_ids operator (pg_catalog.@>) array [2]::int2[] and e1.kind_id = any (array [4]::int2[]) and (not n2.id = any ((s0.i0).id)) and n2.kind_ids operator (pg_catalog.@>) array [2]::int2[]), s4 as (with recursive s5(root_id, next_id, depth, satisfied, is_cycle, path) as (select e2.start_id, e2.end_id, 1, ((n4.properties ->> 'objectid') like '%-512') and n4.kind_ids operator (pg_catalog.@>) array [1]::int2[], e2.start_id = e2.end_id, array [e2.id] from s3 join edge e2 on (s3.n3).id = e2.start_id join node n4 on n4.id = e2.end_id where e2.kind_id = any (array [4]::int2[]) union select s5.root_id, e2.end_id, s5.depth + 1, ((n4.properties ->> 'objectid') like '%-512') and n4.kind_ids operator (pg_catalog.@>) array [1]::int2[], e2.id = any (s5.path), s5.path || e2.id from s5 join edge e2 on e2.start_id = s5.next_id join node n4 on n4.id = e2.end_id where e2.kind_id = any (array [4]::int2[]) and s5.depth <= 15 and not s5.is_cycle) select s3.e1 as e1, (select array_agg((e2.id, e2.start_id, e2.end_id, e2.kind_id, e2.properties)::edgecomposite) from edge e2 where e2.id = any (s5.path)) as e2, s5.path as ep1, s3.i0 as i0, s3.n2 as n2, (n3.id, n3.kind_ids, n3.properties)::nodecomposite as n3, (n4.id, n4.kind_ids, n4.properties)::nodecomposite as n4 from s3, s5 join node n3 on n3.id = s5.root_id join node n4 on n4.id = s5.next_id where s5.satisfied) select edges_to_path(variadic array [(s4.e1).id]::int8[] || s4.ep1)::pathcomposite as p from s4 limit 100; -- case: match (n:NodeKind1)<-[:EdgeKind1]-(:NodeKind2) where n.objectid ends with '-516' with n, count(n) as dc_count where dc_count = 1 return n with s0 as (with s1 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.end_id join node n1 on n1.id = e0.start_id where n1.kind_ids operator (pg_catalog.@>) array [2]::int2[] and e0.kind_id = any (array [3]::int2[]) and ((n0.properties ->> 'objectid') like '%-516') and n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as n0, count(s1.n0)::int8 as i0 from s1 group by n0) select s0.n0 as n from s0 where (s0.i0 = 1); diff --git a/cypher/models/pgsql/test/translation_cases/pattern_binding.sql b/cypher/models/pgsql/test/translation_cases/pattern_binding.sql index 4df5957..92be6f0 100644 --- a/cypher/models/pgsql/test/translation_cases/pattern_binding.sql +++ b/cypher/models/pgsql/test/translation_cases/pattern_binding.sql @@ -54,7 +54,7 @@ with s0 as (select (e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::e 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.end_id or n0.id = e0.start_id join node n1 on n1.id = e0.end_id or n1.id = e0.start_id where (n0.id <> n1.id) and n1.kind_ids operator (pg_catalog.@>) array [1]::int2[] and n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select edges_to_path(variadic array [(s0.e0).id]::int8[])::pathcomposite as p from s0; -- 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; +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, (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 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 where s2.satisfied) select edges_to_path(variadic array [(s1.e0).id]::int8[] || s1.ep0)::pathcomposite as p from s1 limit 1000; -- case: match (u:NodeKind1) where u.samaccountname in ["foo", "bar"] match p = (u)-[:EdgeKind1|EdgeKind2*1..3]->(t) where coalesce(t.system_tags, '') contains 'admin_tier_0' return p limit 1000 with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((n0.properties ->> 'samaccountname') = any (array ['foo', 'bar']::text[])) 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 e0.start_id, e0.end_id, 1, (coalesce((n1.properties ->> 'system_tags'), '')::text like '%admin_tier_0%'), e0.start_id = e0.end_id, array [e0.id] from s0 join edge e0 on e0.start_id = (s0.n0).id join node n1 on n1.id = e0.end_id where e0.kind_id = any (array [3, 4]::int2[]) union select s2.root_id, e0.end_id, s2.depth + 1, (coalesce((n1.properties ->> 'system_tags'), '')::text like '%admin_tier_0%'), e0.id = any (s2.path), s2.path || e0.id from s2 join edge e0 on e0.start_id = s2.next_id join node n1 on n1.id = e0.end_id where e0.kind_id = any (array [3, 4]::int2[]) and s2.depth <= 3 and not s2.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s2.path)) as e0, s2.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s0, s2 join node n0 on n0.id = s2.root_id join node n1 on n1.id = s2.next_id where s2.satisfied) select edges_to_path(variadic ep0)::pathcomposite as p from s1 limit 1000; diff --git a/cypher/models/pgsql/test/translation_cases/pattern_expansion.sql b/cypher/models/pgsql/test/translation_cases/pattern_expansion.sql index c819721..0825641 100644 --- a/cypher/models/pgsql/test/translation_cases/pattern_expansion.sql +++ b/cypher/models/pgsql/test/translation_cases/pattern_expansion.sql @@ -66,10 +66,10 @@ with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, false, e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n0 on n0.id = e0.end_id join node n1 on n1.id = e0.start_id where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3, 4]::int2[]) union select s1.root_id, e0.start_id, s1.depth + 1, false, e0.id = any (s1.path), s1.path || e0.id from s1 join edge e0 on e0.end_id = s1.next_id join node n1 on n1.id = e0.start_id where e0.kind_id = any (array [3, 4]::int2[]) and s1.depth <= 15 and not s1.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s1.path)) as e0, s1.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s1 join node n0 on n0.id = s1.root_id join node n1 on n1.id = s1.next_id) select edges_to_path(variadic ep0)::pathcomposite as p from s0 limit 10; -- case: match p = (:NodeKind1)<-[:EdgeKind1|EdgeKind2*..]-(:NodeKind2)<-[:EdgeKind1|EdgeKind2*2..]-(:NodeKind1) return p limit 10 -with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n0 on n0.id = e0.end_id join node n1 on n1.id = e0.start_id where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3, 4]::int2[]) union select s1.root_id, e0.start_id, s1.depth + 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.id = any (s1.path), s1.path || e0.id from s1 join edge e0 on e0.end_id = s1.next_id join node n1 on n1.id = e0.start_id where e0.kind_id = any (array [3, 4]::int2[]) and s1.depth <= 15 and not s1.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s1.path)) as e0, s1.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s1 join node n0 on n0.id = s1.root_id join node n1 on n1.id = s1.next_id where s1.satisfied), s2 as (with recursive s3(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.end_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) union select s3.root_id, e1.end_id, s3.depth + 1, n2.kind_ids operator (pg_catalog.@>) array [1]::int2[], e1.id = any (s3.path), s3.path || e1.id from s3 join edge e1 on e1.end_id = s3.next_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) and s3.depth <= 15 and not s3.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 (s3.path)) as e1, s0.ep0 as ep0, s3.path as ep1, 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, s3 join node n1 on n1.id = s3.root_id join node n2 on n2.id = s3.next_id where s3.depth >= 2) select edges_to_path(variadic s2.ep1 || s2.ep0)::pathcomposite as p from s2 limit 10; +with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n0 on n0.id = e0.end_id join node n1 on n1.id = e0.start_id where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3, 4]::int2[]) union select s1.root_id, e0.start_id, s1.depth + 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.id = any (s1.path), s1.path || e0.id from s1 join edge e0 on e0.end_id = s1.next_id join node n1 on n1.id = e0.start_id where e0.kind_id = any (array [3, 4]::int2[]) and s1.depth <= 15 and not s1.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s1.path)) as e0, s1.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s1 join node n0 on n0.id = s1.root_id join node n1 on n1.id = s1.next_id where s1.satisfied), s2 as (with recursive s3(root_id, next_id, depth, satisfied, is_cycle, path) as (select e1.end_id, e1.start_id, 1, n2.kind_ids operator (pg_catalog.@>) array [1]::int2[], e1.end_id = e1.start_id, array [e1.id] from s0 join edge e1 on (s0.n1).id = e1.end_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) union select s3.root_id, e1.start_id, s3.depth + 1, n2.kind_ids operator (pg_catalog.@>) array [1]::int2[], e1.id = any (s3.path), s3.path || e1.id from s3 join edge e1 on e1.end_id = s3.next_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) and s3.depth <= 15 and not s3.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 (s3.path)) as e1, s0.ep0 as ep0, s3.path as ep1, 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, s3 join node n1 on n1.id = s3.root_id join node n2 on n2.id = s3.next_id where s3.depth >= 2 and s3.satisfied) select edges_to_path(variadic s2.ep1 || s2.ep0)::pathcomposite as p from s2 limit 10; -- case: match p = (:NodeKind1)<-[:EdgeKind1|EdgeKind2*..]-(:NodeKind2)<-[:EdgeKind1|EdgeKind2*..]-(:NodeKind1) return p limit 10 -with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n0 on n0.id = e0.end_id join node n1 on n1.id = e0.start_id where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3, 4]::int2[]) union select s1.root_id, e0.start_id, s1.depth + 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.id = any (s1.path), s1.path || e0.id from s1 join edge e0 on e0.end_id = s1.next_id join node n1 on n1.id = e0.start_id where e0.kind_id = any (array [3, 4]::int2[]) and s1.depth <= 15 and not s1.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s1.path)) as e0, s1.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s1 join node n0 on n0.id = s1.root_id join node n1 on n1.id = s1.next_id where s1.satisfied), s2 as (with recursive s3(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.end_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) union select s3.root_id, e1.end_id, s3.depth + 1, n2.kind_ids operator (pg_catalog.@>) array [1]::int2[], e1.id = any (s3.path), s3.path || e1.id from s3 join edge e1 on e1.end_id = s3.next_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) and s3.depth <= 15 and not s3.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 (s3.path)) as e1, s0.ep0 as ep0, s3.path as ep1, 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, s3 join node n1 on n1.id = s3.root_id join node n2 on n2.id = s3.next_id) select edges_to_path(variadic s2.ep1 || s2.ep0)::pathcomposite as p from s2 limit 10; +with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n0 on n0.id = e0.end_id join node n1 on n1.id = e0.start_id where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[] and e0.kind_id = any (array [3, 4]::int2[]) union select s1.root_id, e0.start_id, s1.depth + 1, n1.kind_ids operator (pg_catalog.@>) array [2]::int2[], e0.id = any (s1.path), s1.path || e0.id from s1 join edge e0 on e0.end_id = s1.next_id join node n1 on n1.id = e0.start_id where e0.kind_id = any (array [3, 4]::int2[]) and s1.depth <= 15 and not s1.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s1.path)) as e0, s1.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s1 join node n0 on n0.id = s1.root_id join node n1 on n1.id = s1.next_id where s1.satisfied), s2 as (with recursive s3(root_id, next_id, depth, satisfied, is_cycle, path) as (select e1.end_id, e1.start_id, 1, n2.kind_ids operator (pg_catalog.@>) array [1]::int2[], e1.end_id = e1.start_id, array [e1.id] from s0 join edge e1 on (s0.n1).id = e1.end_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) union select s3.root_id, e1.start_id, s3.depth + 1, n2.kind_ids operator (pg_catalog.@>) array [1]::int2[], e1.id = any (s3.path), s3.path || e1.id from s3 join edge e1 on e1.end_id = s3.next_id join node n2 on n2.id = e1.start_id where e1.kind_id = any (array [3, 4]::int2[]) and s3.depth <= 15 and not s3.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 (s3.path)) as e1, s0.ep0 as ep0, s3.path as ep1, 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, s3 join node n1 on n1.id = s3.root_id join node n2 on n2.id = s3.next_id where s3.satisfied) select edges_to_path(variadic s2.ep1 || s2.ep0)::pathcomposite as p from s2 limit 10; -- case: match p = (n:NodeKind1)-[:EdgeKind1|EdgeKind2*1..2]->(r:NodeKind2) where r.name =~ '(?i)Global Administrator.*|User Administrator.*|Cloud Application Administrator.*|Authentication Policy Administrator.*|Exchange Administrator.*|Helpdesk Administrator.*|Privileged Authentication Administrator.*' return p limit 10 with s0 as (with recursive s1(root_id, next_id, depth, satisfied, is_cycle, path) as (select e0.end_id, e0.start_id, 1, n0.kind_ids operator (pg_catalog.@>) array [1]::int2[], e0.end_id = e0.start_id, array [e0.id] from edge e0 join node n1 on n1.id = e0.end_id join node n0 on n0.id = e0.start_id where ((n1.properties ->> 'name') ~ '(?i)Global Administrator.*|User Administrator.*|Cloud Application Administrator.*|Authentication Policy Administrator.*|Exchange Administrator.*|Helpdesk Administrator.*|Privileged Authentication Administrator.*') and n1.kind_ids operator (pg_catalog.@>) array [2]::int2[] and e0.kind_id = any (array [3, 4]::int2[]) union select s1.root_id, e0.start_id, s1.depth + 1, n0.kind_ids operator (pg_catalog.@>) array [1]::int2[], e0.id = any (s1.path), s1.path || e0.id from s1 join edge e0 on e0.end_id = s1.next_id join node n0 on n0.id = e0.start_id where e0.kind_id = any (array [3, 4]::int2[]) and s1.depth <= 2 and not s1.is_cycle) select (select array_agg((e0.id, e0.start_id, e0.end_id, e0.kind_id, e0.properties)::edgecomposite) from edge e0 where e0.id = any (s1.path)) as e0, s1.path as ep0, (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0, (n1.id, n1.kind_ids, n1.properties)::nodecomposite as n1 from s1 join node n1 on n1.id = s1.root_id join node n0 on n0.id = s1.next_id where s1.satisfied) select edges_to_path(variadic ep0)::pathcomposite as p from s0 limit 10; diff --git a/cypher/models/pgsql/translate/expansion.go b/cypher/models/pgsql/translate/expansion.go index 59e2bc0..b38afa6 100644 --- a/cypher/models/pgsql/translate/expansion.go +++ b/cypher/models/pgsql/translate/expansion.go @@ -689,6 +689,8 @@ func (s *Translator) buildExpansionPatternRoot(traversalStep *TraversalStep, exp expansion.ProjectionStatement.Projection = expansionModel.Projection expansion.PrimerStatement.Where = pgsql.OptionalAnd(expansionModel.PrimerNodeConstraints, expansionModel.EdgeConstraints) expansion.RecursiveStatement.Where = pgsql.OptionalAnd(expansionModel.EdgeConstraints, expansionModel.RecursiveConstraints) + expansion.PrimerStatement.Projection = s.buildExpansionPrimerProjection(traversalStep) + expansion.RecursiveStatement.Projection = s.buildExpansionRecursiveProjection(traversalStep) // If the left node was already bound at time of translation connect this expansion to the // previously materialized node @@ -826,109 +828,10 @@ func (s *Translator) buildExpansionPatternRoot(traversalStep *TraversalStep, exp }}, }) - // If there are right node constraints, project them as part of the primer statement's projection - if expansionModel.TerminalNodeSatisfactionProjection != nil { - if terminalCriteriaProjection, err := pgsql.As[pgsql.SelectItem](expansionModel.TerminalNodeSatisfactionProjection); err != nil { - return pgsql.Query{}, err - } else { - expansion.PrimerStatement.Projection = []pgsql.SelectItem{ - expansionModel.EdgeStartColumn, - expansionModel.EdgeEndColumn, - pgsql.NewLiteral(1, pgsql.Int), - terminalCriteriaProjection, - pgsql.NewBinaryExpression( - expansionModel.EdgeStartColumn, - pgsql.OperatorEquals, - expansionModel.EdgeEndColumn, - ), - pgsql.ArrayLiteral{ - Values: []pgsql.Expression{ - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - }, - }, - } - - expansion.RecursiveStatement.Projection = []pgsql.SelectItem{ - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionRootID}, - expansionModel.EdgeEndColumn, - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, - pgsql.OperatorAdd, - pgsql.NewLiteral(1, pgsql.Int), - ), - terminalCriteriaProjection, - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - pgsql.OperatorEquals, - pgsql.NewAnyExpression(pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, pgsql.ExpansionPath), - ), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, - pgsql.OperatorConcatenate, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - ), - } - - // Constraints that target the terminal node may crop up here where it's finally in scope. Additionally, - // only accept paths that are marked satisfied from the recursive descent CTE - if constraints, err := s.treeTranslator.ConsumeConstraintsFromVisibleSet(expansionModel.Frame.Visible); err != nil { - return pgsql.Query{}, err - } else if projectionConstraints, err := ConjoinExpressions(s.kindMapper, []pgsql.Expression{pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionSatisfied}, constraints.Expression}); err != nil { - return pgsql.Query{}, err - } else { - expansion.ProjectionStatement.Where = projectionConstraints - } - } + if projectionConstraints, err := s.buildExpansionProjectionConstraints(traversalStep); err != nil { + return pgsql.Query{}, err } else { - expansion.PrimerStatement.Projection = []pgsql.SelectItem{ - expansionModel.EdgeStartColumn, - expansionModel.EdgeEndColumn, - pgsql.NewLiteral(1, pgsql.Int), - pgsql.NewLiteral(false, pgsql.Boolean), - pgsql.NewBinaryExpression( - expansionModel.EdgeStartColumn, - pgsql.OperatorEquals, - expansionModel.EdgeEndColumn, - ), - pgsql.ArrayLiteral{ - Values: []pgsql.Expression{ - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - }, - }, - } - - expansion.RecursiveStatement.Projection = []pgsql.SelectItem{ - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionRootID}, - expansionModel.EdgeEndColumn, - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, - pgsql.OperatorAdd, - pgsql.NewLiteral(1, pgsql.Int), - ), - pgsql.NewLiteral(false, pgsql.Boolean), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - pgsql.OperatorEquals, - pgsql.NewAnyExpression(pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, pgsql.ExpansionPath), - ), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, - pgsql.OperatorConcatenate, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - ), - } - } - - // Check for min-path depth as this will also filter the final expansion projection - if expansionModel.Options.MinDepth.Set && expansionModel.Options.MinDepth.Value > 1 { - expansion.ProjectionStatement.Where = pgsql.OptionalAnd( - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, - pgsql.OperatorGreaterThanOrEqualTo, - pgsql.NewLiteral(expansionModel.Options.MinDepth.Value, pgsql.Int), - ), - expansion.ProjectionStatement.Where, - ) + expansion.ProjectionStatement.Where = projectionConstraints } return expansion.Build(expansionModel.Frame.Binding.Identifier), nil @@ -937,51 +840,11 @@ func (s *Translator) buildExpansionPatternRoot(traversalStep *TraversalStep, exp func (s *Translator) buildExpansionPatternStep(traversalStep *TraversalStep, expansion *ExpansionBuilder) (pgsql.Query, error) { expansionModel := traversalStep.Expansion - expansion.PrimerStatement = pgsql.Select{ - Projection: []pgsql.SelectItem{ - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnStartID}, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnEndID}, - pgsql.NewLiteral(1, pgsql.Int), - pgsql.NewLiteral(false, pgsql.Boolean), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnStartID}, - pgsql.OperatorEquals, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnEndID}, - ), - pgsql.ArrayLiteral{ - Values: []pgsql.Expression{ - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - }, - }, - }, - } - - expansion.RecursiveStatement = pgsql.Select{ - Projection: []pgsql.SelectItem{ - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionRootID}, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnEndID}, - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, - pgsql.OperatorAdd, - pgsql.NewLiteral(1, pgsql.Int), - ), - pgsql.NewLiteral(false, pgsql.Boolean), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - pgsql.OperatorEquals, - pgsql.NewAnyExpression(pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, pgsql.ColumnPath}, pgsql.ExpansionPath), - ), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, pgsql.ColumnPath}, - pgsql.OperatorConcatenate, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - ), - }, - } - expansion.ProjectionStatement.Projection = expansionModel.Projection expansion.PrimerStatement.Where = pgsql.OptionalAnd(expansionModel.PrimerNodeConstraints, expansionModel.EdgeConstraints) expansion.RecursiveStatement.Where = pgsql.OptionalAnd(expansionModel.EdgeConstraints, expansionModel.RecursiveConstraints) + expansion.PrimerStatement.Projection = s.buildExpansionPrimerProjection(traversalStep) + expansion.RecursiveStatement.Projection = s.buildExpansionRecursiveProjection(traversalStep) expansion.PrimerStatement.From = append(expansion.PrimerStatement.From, pgsql.FromClause{ Source: pgsql.TableReference{ @@ -1080,49 +943,136 @@ func (s *Translator) buildExpansionPatternStep(traversalStep *TraversalStep, exp }}, }) - // If there are terminal constraints, project them as part of the recursive lookup + if projectionConstraints, err := s.buildExpansionProjectionConstraints(traversalStep); err != nil { + return pgsql.Query{}, err + } else { + expansion.ProjectionStatement.Where = projectionConstraints + } + + return pgsql.Query{ + Body: expansion.Build(expansionModel.Frame.Binding.Identifier), + }, nil +} + +func (s *Translator) buildExpansionPrimerProjection(traversalStep *TraversalStep) []pgsql.SelectItem { + expansionModel := traversalStep.Expansion + if expansionModel.TerminalNodeSatisfactionProjection != nil { - if terminalCriteriaProjection, err := pgsql.As[pgsql.SelectItem](expansionModel.TerminalNodeSatisfactionProjection); err != nil { - return pgsql.Query{}, err - } else { - expansion.RecursiveStatement.Projection = []pgsql.SelectItem{ - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionRootID}, - pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnEndID}, - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, - pgsql.OperatorAdd, - pgsql.NewLiteral(1, pgsql.Int), - ), - terminalCriteriaProjection, - pgsql.NewBinaryExpression( + return []pgsql.SelectItem{ + expansionModel.EdgeStartColumn, + expansionModel.EdgeEndColumn, + pgsql.NewLiteral(1, pgsql.Int), + expansionModel.TerminalNodeSatisfactionProjection, + pgsql.NewBinaryExpression( + expansionModel.EdgeStartColumn, + pgsql.OperatorEquals, + expansionModel.EdgeEndColumn, + ), + pgsql.ArrayLiteral{ + Values: []pgsql.Expression{ pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - pgsql.OperatorEquals, - pgsql.NewAnyExpression(pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, pgsql.ExpansionPath), - ), - pgsql.NewBinaryExpression( - pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, - pgsql.OperatorConcatenate, + }, + }, + } + } else { + return []pgsql.SelectItem{ + expansionModel.EdgeStartColumn, + expansionModel.EdgeEndColumn, + pgsql.NewLiteral(1, pgsql.Int), + pgsql.NewLiteral(false, pgsql.Boolean), + pgsql.NewBinaryExpression( + expansionModel.EdgeStartColumn, + pgsql.OperatorEquals, + expansionModel.EdgeEndColumn, + ), + pgsql.ArrayLiteral{ + Values: []pgsql.Expression{ pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, - ), - } + }, + }, + } + } +} + +func (s *Translator) buildExpansionRecursiveProjection(traversalStep *TraversalStep) []pgsql.SelectItem { + expansionModel := traversalStep.Expansion + + if expansionModel.TerminalNodeSatisfactionProjection != nil { + return []pgsql.SelectItem{ + pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionRootID}, + expansionModel.EdgeEndColumn, + pgsql.NewBinaryExpression( + pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, + pgsql.OperatorAdd, + pgsql.NewLiteral(1, pgsql.Int), + ), + expansionModel.TerminalNodeSatisfactionProjection, + pgsql.NewBinaryExpression( + pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, + pgsql.OperatorEquals, + pgsql.NewAnyExpression(pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, pgsql.ExpansionPath), + ), + pgsql.NewBinaryExpression( + pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, + pgsql.OperatorConcatenate, + pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, + ), + } + } else { + return []pgsql.SelectItem{ + pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionRootID}, + expansionModel.EdgeEndColumn, + pgsql.NewBinaryExpression( + pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, + pgsql.OperatorAdd, + pgsql.NewLiteral(1, pgsql.Int), + ), + pgsql.NewLiteral(false, pgsql.Boolean), + pgsql.NewBinaryExpression( + pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, + pgsql.OperatorEquals, + pgsql.NewAnyExpression(pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, pgsql.ExpansionPath), + ), + pgsql.NewBinaryExpression( + pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionPath}, + pgsql.OperatorConcatenate, + pgsql.CompoundIdentifier{traversalStep.Edge.Identifier, pgsql.ColumnID}, + ), + } + } +} + +func (s *Translator) buildExpansionProjectionConstraints(traversalStep *TraversalStep) (pgsql.Expression, error) { + var ( + expansionModel = traversalStep.Expansion + projectionConstraints pgsql.Expression + constraints *Constraint + err error + ) + + // Constraints that target the terminal node may crop up here where it's finally in scope. Additionally, + // only accept paths that are marked satisfied from the recursive descent CTE + if expansionModel.TerminalNodeSatisfactionProjection != nil { + if constraints, err = s.treeTranslator.ConsumeConstraintsFromVisibleSet(expansionModel.Frame.Visible); err != nil { + return projectionConstraints, err + } else if projectionConstraints, err = ConjoinExpressions(s.kindMapper, []pgsql.Expression{pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionSatisfied}, constraints.Expression}); err != nil { + return projectionConstraints, err } } // Check for min-path depth as this will also filter the final expansion projection if expansionModel.Options.MinDepth.Set && expansionModel.Options.MinDepth.Value > 1 { - expansion.ProjectionStatement.Where = pgsql.OptionalAnd( + projectionConstraints = pgsql.OptionalAnd( pgsql.NewBinaryExpression( pgsql.CompoundIdentifier{expansionModel.Frame.Binding.Identifier, expansionDepth}, pgsql.OperatorGreaterThanOrEqualTo, pgsql.NewLiteral(expansionModel.Options.MinDepth.Value, pgsql.Int), ), - expansion.ProjectionStatement.Where, + projectionConstraints, ) } - return pgsql.Query{ - Body: expansion.Build(expansionModel.Frame.Binding.Identifier), - }, nil + return projectionConstraints, nil } func (s *Translator) translateTraversalPatternPartWithExpansion(isFirstTraversalStep bool, traversalStep *TraversalStep) error {