@@ -881,36 +881,56 @@ def inout(
881881 in_crsr : str , start : int , until : int , edge_type : str , direction : str , edge_filter : Optional [Term ]
882882 ) -> str :
883883 nonlocal query_part
884- in_c = ctx .next_crs ("io_in" )
884+ start_c = ctx .next_crs ("graph_start" )
885+ in_c = ctx .next_crs ("gc" )
886+ in_edge = f"{ in_c } _edge"
887+ in_path = f"{ in_c } _path"
888+ in_r = f"{ in_c } _result"
885889 out = ctx .next_crs ("io_out" )
886- out_crsr = ctx .next_crs ("io_crs" )
887- e = ctx .next_crs ("io_link" )
888890 unique = "uniqueEdges: 'path'" if with_edges else "uniqueVertices: 'global'"
889891 dir_bound = "OUTBOUND" if direction == Direction .outbound else "INBOUND"
890- inout_result = (
891- # merge edge and vertex properties - will be split in the output transformer
892- f"MERGE({ out_crsr } , {{_from:{ e } ._from, _to:{ e } ._to, _link_id:{ e } ._id, _link_reported:{ e } .reported}})"
893- if with_edges
894- else out_crsr
895- )
892+
893+ # the path array contains the whole path from the start node.
894+ # in the case of start > 0, we need to slice the array to get the correct part
895+ def slice_or_all (in_p_part : str ) -> str :
896+ return f"SLICE({ in_path } .{ in_p_part } , { start } )" if start > 0 else f"{ in_path } .{ in_p_part } "
897+
898+ # Edge filter: decision to include the source element is not possible while traversing it.
899+ # When the target node is reached and edge properties are available, the decision can be made.
900+ # In case the filter succeeds, we need to select all vertices and edges on the path.
901+ # No filter but with_edges: another nested for loop required to return the node and edge
902+ # No filter and no with_edges: only the node is returned
903+ if edge_filter :
904+ # walk the path and return all vertices (and possibly edges)
905+ # this means intermediate nodes are returned multiple times and have to be made distinct
906+ # since we return nodes first, the edges can always be resolved
907+ walk_array = slice_or_all ("vertices" )
908+ walk_array = f'APPEND({ walk_array } , { slice_or_all ("edges" )} )' if with_edges else walk_array
909+ inout_result = f"FOR { in_r } in { walk_array } RETURN DISTINCT({ in_r } )"
910+ elif with_edges :
911+ # return the node and edge via a nested for loop
912+ inout_result = f"FOR { in_r } in [{ in_c } , { in_edge } ] FILTER { in_r } !=null RETURN DISTINCT({ in_r } )"
913+ else :
914+ # return only the node
915+ inout_result = f"RETURN DISTINCT { in_c } "
916+
896917 if outer_merge and part_idx == 0 :
897918 graph_cursor = in_crsr
898919 outer_for = ""
899920 else :
900- graph_cursor = in_c
901- outer_for = f"FOR { in_c } in { in_crsr } "
921+ graph_cursor = start_c
922+ outer_for = f"FOR { start_c } in { in_crsr } "
902923
903924 # optional: add the edge filter to the query
904- pre , fltr , post = term (e , edge_filter ) if edge_filter else (None , None , None )
925+ pre , fltr , post = term (in_edge , edge_filter ) if edge_filter else (None , None , None )
905926 pre_string = " " + pre if pre else ""
906927 post_string = f" AND ({ post } )" if post else ""
907928 filter_string = "" if not fltr and not post_string else f"{ pre_string } FILTER { fltr } { post_string } "
908929 query_part += (
909930 f"LET { out } =({ outer_for } "
910931 # suggested by jsteemann: use crs._id instead of crs (stored in the view and more efficient)
911- f"FOR { out_crsr } , { e } IN { start } ..{ until } { dir_bound } { graph_cursor } ._id "
912- f"`{ db .edge_collection (edge_type )} ` OPTIONS {{ bfs: true, { unique } }}{ filter_string } "
913- f"RETURN DISTINCT { inout_result } )"
932+ f"FOR { in_c } , { in_edge } , { in_path } IN { start } ..{ until } { dir_bound } { graph_cursor } ._id "
933+ f"`{ db .edge_collection (edge_type )} ` OPTIONS {{ bfs: true, { unique } }}{ filter_string } { inout_result } )"
914934 )
915935 return out
916936
0 commit comments