Skip to content

Commit f10ad0b

Browse files
authored
[core][fix] Fix and improve with statements (#2107)
1 parent 9f606ba commit f10ad0b

File tree

3 files changed

+46
-58
lines changed

3 files changed

+46
-58
lines changed

fixcore/fixcore/db/arango_query.py

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -576,79 +576,68 @@ def with_usage(in_crsr: str, usage: WithUsage, term: Term, limit: Optional[Limit
576576

577577
def with_clause(in_crsr: str, clause: WithClause, limit: Optional[Limit]) -> str:
578578
nonlocal query_part
579-
# this is the general structure of the with_clause that is created
580-
#
581-
# FOR cloud in foo FILTER @0 in cloud.kinds
582-
# FOR account IN 0..1 OUTBOUND cloud foo_default
583-
# OPTIONS { bfs: true, uniqueVertices: 'global' }
584-
# FILTER (cloud._key==account._key) or (@1 in account.kinds)
585-
# FOR region in 0..1 OUTBOUND account foo_default
586-
# OPTIONS { bfs: true, uniqueVertices: 'global' }
587-
# FILTER (cloud._key==region._key) or (@2 in region.kinds)
588-
# FOR zone in 0..1 OUTBOUND region foo_default
589-
# OPTIONS { bfs: true, uniqueVertices: 'global' }
590-
# FILTER (cloud._key==zone._key) or (@3 in zone.kinds)
591-
# COLLECT l4_cloud = cloud, l4_account=account, l4_region=region WITH COUNT INTO counter3
592-
# FILTER (l4_cloud._key==l4_region._key) or (counter3>=0)
593-
# COLLECT l3_cloud = l4_cloud, l3_account=l4_account WITH COUNT INTO counter2
594-
# FILTER (l3_cloud._key==l3_account._key) or (counter2>=0) // ==2 regions
595-
# COLLECT l2_cloud = l3_cloud WITH COUNT INTO counter1
596-
# FILTER (counter1>=0) //counter is +1 since the node itself is always bypassed
597-
# RETURN ({cloud: l2_cloud._key, count:counter1})
598-
current = ctx.next_counter("with_clause")
599-
600-
def cursor_in(depth: int) -> str:
601-
return f"c{current}_{depth}"
602-
603-
l0crsr = cursor_in(0)
579+
# LET incoming = (FOR cloud IN `fix_view` SEARCH cloud.kinds == @b0 RETURN cloud)
580+
# LET clouds = (
581+
# FOR cloud IN incoming
582+
# LET accounts = (
583+
# FOR account IN 1..1 OUTBOUND cloud `fix_test` OPTIONS { bfs: true, uniqueVertices: 'global' }
584+
# FILTER "account" IN account.kinds
585+
# LIMIT 1
586+
# LET regions = (
587+
# FOR region IN 1..1 OUTBOUND account `fix_test` OPTIONS { bfs: true, uniqueVertices: 'global' }
588+
# FILTER "region" IN region.kinds
589+
# LIMIT 1
590+
# LET resources = (
591+
# FOR resource IN 1..1 OUTBOUND region `fix_test` OPTIONS {bfs:true,uniqueVertices:'global'}
592+
# LIMIT 1
593+
# RETURN resource
594+
# )
595+
# FILTER LENGTH(resources)>0
596+
# RETURN region
597+
# )
598+
# FILTER LENGTH(regions)==0
599+
# RETURN account
600+
# )
601+
# FILTER LENGTH(accounts) >0 //any
602+
# RETURN cloud
603+
# )
604+
# FOR cloud IN clouds RETURN cloud
604605

605606
def traversal_filter(cl: WithClause, in_crs: str, depth: int) -> str:
606607
nav = cl.navigation
607-
crsr = cursor_in(depth)
608+
let_crs = ctx.next_crs()
609+
for_crsr = ctx.next_crs()
608610
direction = "OUTBOUND" if nav.direction == Direction.outbound else "INBOUND"
609611
unique = "uniqueEdges: 'path'" if with_edges else "uniqueVertices: 'global'"
610-
pre, term_string, post = term(crsr, cl.term) if cl.term else (None, "true", None)
612+
pre, term_string, post = term(for_crsr, cl.term) if cl.term else (None, "true", None)
611613
pre_string = " " + pre if pre else ""
612614
post_string = f" AND ({post})" if post else ""
613615
filter_clause = f"({term_string})"
614-
inner = traversal_filter(cl.with_clause, crsr, depth + 1) if cl.with_clause else ""
615-
filter_root = f"({l0crsr}._key=={crsr}._key) or " if depth > 0 else ""
616+
inner = traversal_filter(cl.with_clause, for_crsr, depth + 1) if cl.with_clause else ""
616617
edge_type_traversals = f", {direction} ".join(f"`{db.edge_collection(et)}`" for et in nav.edge_types)
617618
return (
618-
f"FOR {crsr} IN 0..{nav.until} {direction} {in_crs} "
619+
f"LET {let_crs} = (FOR {for_crsr} IN {nav.start}..{nav.until} {direction} {in_crs} "
619620
f"{edge_type_traversals} OPTIONS {{ bfs: true, {unique} }} "
620-
f"{pre_string} FILTER {filter_root}{filter_clause}{post_string} "
621-
) + inner
622-
623-
def collect_filter(cl: WithClause, depth: int) -> str:
624-
fltr = cl.with_filter
625-
if cl.with_clause:
626-
collects = ", ".join(f"l{depth-1}_l{i}_res=l{depth}_l{i}_res" for i in range(0, depth))
627-
else:
628-
collects = ", ".join(f"l{depth-1}_l{i}_res={cursor_in(i)}" for i in range(0, depth))
629-
630-
if depth == 1:
631-
# note: the traversal starts from 0 (only 0 and 1 is allowed)
632-
# when we start from 1: increase the count by one to not count the start node
633-
# when we start from 0: the start node is expected in the count already
634-
filter_term = f"FILTER counter1{fltr.op}{fltr.num + cl.navigation.start}"
635-
else:
636-
root_key = f"l{depth-1}_l0_res._key==l{depth-1}_l{depth-1}_res._key"
637-
filter_term = f"FILTER ({root_key}) or (counter{depth}{fltr.op}{fltr.num})"
638-
639-
inner = collect_filter(cl.with_clause, depth + 1) if cl.with_clause else ""
640-
return inner + f"COLLECT {collects} WITH COUNT INTO counter{depth} {filter_term} "
621+
f"{pre_string} FILTER {filter_clause}{post_string} "
622+
# for all possible predicates, it is enough to limit the list by num + 1
623+
# empty: if we find one element, it is not empty
624+
# any: if we find one element, it is any
625+
# count op x: if we find x+1 elements, we can always answer the predicate
626+
f"LIMIT {cl.with_filter.num + 1} "
627+
f"{inner} RETURN {for_crsr})"
628+
f"FILTER LENGTH({let_crs}){cl.with_filter.op}{cl.with_filter.num} "
629+
)
641630

642631
out = ctx.next_crs()
643-
632+
l0crsr = ctx.next_crs()
644633
limited = f" LIMIT {limit.offset}, {limit.length} " if limit else " "
634+
needs_sort = p.sort and query.aggregate is not None
645635
query_part += (
646636
f"LET {out} =( FOR {l0crsr} in {in_crsr} "
647-
+ traversal_filter(clause, l0crsr, 1)
648-
+ collect_filter(clause, 1)
649-
+ (sort("l0_l0_res", p.sort) if p.sort else "")
637+
+ traversal_filter(clause, l0crsr, 0)
638+
+ (sort(l0crsr, p.sort) if needs_sort else "")
650639
+ limited
651-
+ "RETURN l0_l0_res) "
640+
+ f"RETURN {l0crsr}) "
652641
)
653642
return out
654643

fixcore/fixcore/query/query_parser.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ def with_clause_parser() -> Parser:
310310
term = yield filter_term_parser.optional()
311311
with_clause = yield with_clause_parser.optional()
312312
yield rparen_p
313-
assert 0 <= nav.start <= 1, "with traversal need to start from 0 or 1"
314313
return WithClause(with_filter, nav, term, with_clause)
315314

316315

fixcore/tests/fixcore/db/graphdb_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ async def query(q: str) -> List[Json]:
566566
assert len(await query("is(bla) with(empty, <-- is(bla))")) == 100
567567
assert len(await query('is(bla) with(count==1, <-- is(foo) and id=~"1")')) == 10
568568
assert len(await query('is(bla) with(count==2, <-- is(foo) and id=~"1")')) == 0
569-
assert len(await query("is(bla) with(any, <-- with(any, <-- is(foo)))")) == 100
569+
assert len(await query("is(bla) with(any, <-- with(any, <-- is(foo)))")) == 0
570570

571571

572572
@mark.asyncio

0 commit comments

Comments
 (0)