@@ -2,19 +2,26 @@ use std::collections::{HashSet, VecDeque};
22
33use neb:: dovahkiin:: expr:: serde:: Expr ;
44use neb:: dovahkiin:: integrated:: lisp:: parse_to_serde_expr;
5+ use neb:: dovahkiin:: types:: Map ;
56use neb:: index:: ranged:: tree:: btree:: Ordering ;
67use neb:: query:: planner:: { ClauseOrderExplain , QueryPlanExplain } ;
7- use neb:: ram:: cell:: OwnedCell ;
8- use neb:: ram:: types:: Id ;
8+ use neb:: ram:: cell:: { OwnedCell , ReadError } ;
9+ use neb:: ram:: types:: { Id , OwnedMap , OwnedValue } ;
910
11+ use crate :: graph:: edge:: directed:: DirectedEdge ;
12+ use crate :: graph:: edge:: undirectd:: UndirectedEdge ;
1013use crate :: graph:: edge:: bilateral:: BilateralEdge ;
1114use crate :: graph:: { EdgeDirection , EdgeType , GraphEngine } ;
15+ use crate :: server:: schema:: GraphSchema ;
1216
1317#[ derive( Debug ) ]
1418pub enum GraphQueryError {
1519 InvalidSelectionExpr ( String ) ,
1620 InvalidOrdering ( String ) ,
1721 InvalidDirection ( String ) ,
22+ SchemaNotEdge ( u32 ) ,
23+ SchemaRequiresEdgeBody ( u32 ) ,
24+ InvalidEdgeCell ( String ) ,
1825 EdgeSchemasRequired ,
1926 Internal ( String ) ,
2027}
@@ -61,6 +68,16 @@ pub struct GraphTraverseResult {
6168 pub edges : Vec < GraphTraverseEdge > ,
6269}
6370
71+ #[ derive( Debug , Clone ) ]
72+ pub struct GraphLookupEdge {
73+ pub id : Id ,
74+ pub from_id : Id ,
75+ pub to_id : Id ,
76+ pub schema_id : u32 ,
77+ pub edge_type : EdgeType ,
78+ pub body : OwnedMap ,
79+ }
80+
6481#[ derive( Debug , Clone ) ]
6582pub struct GraphQueryPlanExplain {
6683 pub disjunction : bool ,
@@ -78,10 +95,7 @@ pub struct GraphClauseOrderExplain {
7895}
7996
8097impl GraphEngine {
81- pub async fn lookup_vertex_ids (
82- & self ,
83- req : & GraphLookupParams ,
84- ) -> Result < Vec < Id > , GraphQueryError > {
98+ async fn lookup_ids ( & self , req : & GraphLookupParams ) -> Result < Vec < Id > , GraphQueryError > {
8599 let selection = parse_selection_expr ( & req. selection ) ?;
86100 let ordering = parse_ordering ( req. ordering . as_deref ( ) ) ?;
87101 let mut cursor = self
@@ -105,11 +119,18 @@ impl GraphEngine {
105119 Ok ( ids)
106120 }
107121
122+ pub async fn lookup_vertex_ids (
123+ & self ,
124+ req : & GraphLookupParams ,
125+ ) -> Result < Vec < Id > , GraphQueryError > {
126+ self . lookup_ids ( req) . await
127+ }
128+
108129 pub async fn lookup_vertices (
109130 & self ,
110131 req : & GraphLookupParams ,
111132 ) -> Result < Vec < OwnedCell > , GraphQueryError > {
112- let ids = self . lookup_vertex_ids ( req) . await ?;
133+ let ids = self . lookup_ids ( req) . await ?;
113134 let mut vertices = Vec :: new ( ) ;
114135 for id in ids {
115136 if let Some ( vertex) = self . vertex_by ( id) . await . map_err ( internal_error) ? {
@@ -119,6 +140,72 @@ impl GraphEngine {
119140 Ok ( vertices)
120141 }
121142
143+ pub async fn lookup_edges (
144+ & self ,
145+ req : & GraphLookupParams ,
146+ ) -> Result < Vec < GraphLookupEdge > , GraphQueryError > {
147+ let edge_attrs = match self . schemas . schema_type ( req. schema_id ) {
148+ Some ( GraphSchema :: Edge ( attrs) ) => attrs,
149+ _ => return Err ( GraphQueryError :: SchemaNotEdge ( req. schema_id ) ) ,
150+ } ;
151+ if !edge_attrs. has_body {
152+ return Err ( GraphQueryError :: SchemaRequiresEdgeBody ( req. schema_id ) ) ;
153+ }
154+
155+ let ids = self . lookup_ids ( req) . await ?;
156+ let mut edges = Vec :: new ( ) ;
157+ for id in ids {
158+ let cell = match self . neb_client . read_cell ( id) . await {
159+ Err ( e) => return Err ( internal_error ( e) ) ,
160+ Ok ( Err ( ReadError :: CellDoesNotExisted ) ) => continue ,
161+ Ok ( Err ( e) ) => return Err ( internal_error ( e) ) ,
162+ Ok ( Ok ( cell) ) => cell,
163+ } ;
164+
165+ let ( from_field, to_field, edge_type) = match edge_attrs. edge_type {
166+ EdgeType :: Directed => (
167+ DirectedEdge :: edge_a_field ( ) ,
168+ DirectedEdge :: edge_b_field ( ) ,
169+ EdgeType :: Directed ,
170+ ) ,
171+ EdgeType :: Undirected => (
172+ UndirectedEdge :: edge_a_field ( ) ,
173+ UndirectedEdge :: edge_b_field ( ) ,
174+ EdgeType :: Undirected ,
175+ ) ,
176+ } ;
177+
178+ let body = match cell. data {
179+ OwnedValue :: Map ( m) => m,
180+ other => {
181+ return Err ( GraphQueryError :: InvalidEdgeCell ( format ! (
182+ "edge {id:?} body is not a map: {other:?}"
183+ ) ) ) ;
184+ }
185+ } ;
186+
187+ let ( from_id, to_id) = match ( body. get_by_key_id ( from_field) , body. get_by_key_id ( to_field) ) {
188+ ( & OwnedValue :: Id ( from) , & OwnedValue :: Id ( to) ) => ( from, to) ,
189+ ( from, to) => {
190+ return Err ( GraphQueryError :: InvalidEdgeCell ( format ! (
191+ "edge {id:?} missing endpoint ids from fields {from_field}/{to_field}: {from:?}/{to:?}"
192+ ) ) ) ;
193+ }
194+ } ;
195+
196+ edges. push ( GraphLookupEdge {
197+ id,
198+ from_id,
199+ to_id,
200+ schema_id : req. schema_id ,
201+ edge_type,
202+ body,
203+ } ) ;
204+ }
205+
206+ Ok ( edges)
207+ }
208+
122209 pub async fn lookup_explain_plan (
123210 & self ,
124211 req : & GraphLookupExplainParams ,
@@ -482,4 +569,100 @@ mod tests {
482569 assert ! ( out. contains( & ids[ 1 ] ) ) ;
483570 assert ! ( out. contains( & ids[ 2 ] ) ) ;
484571 }
572+
573+ async fn setup_edge_body_fixture (
574+ port : u32 ,
575+ group : & str ,
576+ ) -> ( std:: sync:: Arc < crate :: server:: MorpheusServer > , u32 , u32 , Vec < Id > ) {
577+ let server = start_server ( port, group) . await . expect ( "server should start" ) ;
578+ let graph: & GraphEngine = & server. graph ;
579+
580+ let vertex_schema = MorpheusSchema :: new ( "gq_vertex_for_edges" , None , & EMPTY_FIELDS , true ) ;
581+ let edge_schema = MorpheusSchema :: new (
582+ "gq_edge_with_body" ,
583+ None ,
584+ & vec ! [ Field :: new_indexed( "weight" , Type :: U64 , vec![ IndexType :: Hashed ] ) ] ,
585+ false ,
586+ ) ;
587+ let vertex_schema_id = graph
588+ . new_vertex_group ( vertex_schema)
589+ . await
590+ . expect ( "vertex schema should be created" ) ;
591+ let edge_schema_id = graph
592+ . new_edge_group ( edge_schema, EdgeAttributes :: new ( EdgeType :: Directed , true ) )
593+ . await
594+ . expect ( "edge schema should be created" ) ;
595+
596+ let ids = vec ! [ Id :: new( 0 , 201 ) , Id :: new( 0 , 202 ) , Id :: new( 0 , 203 ) ] ;
597+ for id in ids. iter ( ) . copied ( ) {
598+ graph
599+ . new_vertex_with_id ( vertex_schema_id, id, OwnedMap :: new ( ) )
600+ . await
601+ . expect ( "vertex create" ) ;
602+ }
603+
604+ let mut body_a = OwnedMap :: new ( ) ;
605+ body_a. insert ( "weight" , OwnedValue :: U64 ( 7 ) ) ;
606+ let body_a = Some ( body_a) ;
607+ graph
608+ . link ( ids[ 0 ] , edge_schema_id, ids[ 1 ] , & body_a)
609+ . await
610+ . expect ( "link tx 1" )
611+ . expect ( "link 1" ) ;
612+
613+ let mut body_b = OwnedMap :: new ( ) ;
614+ body_b. insert ( "weight" , OwnedValue :: U64 ( 9 ) ) ;
615+ let body_b = Some ( body_b) ;
616+ graph
617+ . link ( ids[ 1 ] , edge_schema_id, ids[ 2 ] , & body_b)
618+ . await
619+ . expect ( "link tx 2" )
620+ . expect ( "link 2" ) ;
621+
622+ ( server, vertex_schema_id, edge_schema_id, ids)
623+ }
624+
625+ #[ tokio:: test]
626+ async fn edge_lookup_supports_body_field_name_selection ( ) {
627+ let ( server, _vertex_schema_id, edge_schema_id, ids) =
628+ setup_edge_body_fixture ( 4045 , "graph_query_edge_body_lookup" ) . await ;
629+ let graph: & GraphEngine = & server. graph ;
630+
631+ let params = GraphLookupParams {
632+ schema_id : edge_schema_id,
633+ selection : "(= weight 7u64)" . to_string ( ) ,
634+ ordering : None ,
635+ order_by_field : None ,
636+ limit : None ,
637+ offset : None ,
638+ } ;
639+
640+ let edges = graph. lookup_edges ( & params) . await . expect ( "edge lookup should succeed" ) ;
641+ assert_eq ! ( edges. len( ) , 1 ) ;
642+ assert_eq ! ( edges[ 0 ] . from_id, ids[ 0 ] ) ;
643+ assert_eq ! ( edges[ 0 ] . to_id, ids[ 1 ] ) ;
644+ assert ! ( matches!( edges[ 0 ] . body. get( "weight" ) , OwnedValue :: U64 ( 7 ) ) ) ;
645+ }
646+
647+ #[ tokio:: test]
648+ async fn edge_lookup_rejects_non_body_edge_schema ( ) {
649+ let ( server, _vertex_schema_id, edge_schema_id, _ids) =
650+ setup_graph_fixture ( 4046 , "graph_query_edge_non_body_reject" , "score" ) . await ;
651+ let graph: & GraphEngine = & server. graph ;
652+
653+ let params = GraphLookupParams {
654+ schema_id : edge_schema_id,
655+ selection : "(= score 20u64)" . to_string ( ) ,
656+ ordering : None ,
657+ order_by_field : None ,
658+ limit : None ,
659+ offset : None ,
660+ } ;
661+
662+ let result = graph. lookup_edges ( & params) . await ;
663+ assert ! ( matches!(
664+ result,
665+ Err ( crate :: graph:: query:: GraphQueryError :: SchemaRequiresEdgeBody ( _) )
666+ ) ) ;
667+ }
485668}
0 commit comments