Skip to content

Commit 086d7b1

Browse files
authored
fix(interactive): fix bugs when try to get labels or properties from an None entry (#4341)
<!-- Thanks for your contribution! please review https://github.com/alibaba/GraphScope/blob/main/CONTRIBUTING.md before opening an issue. --> ## What do these changes do? <!-- Please give a short brief about these changes. --> As titled. ## Related issue number <!-- Are there any issues opened that will be resolved by merging this change? --> Fixes #4340
1 parent 939e4a7 commit 086d7b1

File tree

9 files changed

+103
-49
lines changed

9 files changed

+103
-49
lines changed

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,15 @@ public static QueryContext get_simple_match_query_17_test() {
219219
List<String> expected = Arrays.asList("Record<{$f0: 851}>");
220220
return new QueryContext(query, expected);
221221
}
222+
223+
public static QueryContext get_simple_match_query_18_test() {
224+
String query =
225+
"MATCH (country:PLACE {name:"
226+
+ " \"India\"})<-[:ISPARTOF]-(:PLACE)<-[:ISLOCATEDIN]-(zombie:PERSON)\n"
227+
+ "OPTIONAL MATCH (zombie)<-[:HASCREATOR]-(message)\n"
228+
+ "WHERE message.length < 100\n"
229+
+ " Return count(country);";
230+
List<String> expected = Arrays.asList("Record<{$f0: 39783}>");
231+
return new QueryContext(query, expected);
232+
}
222233
}

interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/integration/ldbc/SimpleMatchTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ public void run_simple_match_17_test() {
162162
Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
163163
}
164164

165+
@Test
166+
public void run_simple_match_18_test() {
167+
QueryContext testQuery = SimpleMatchQueries.get_simple_match_query_18_test();
168+
Result result = session.run(testQuery.getQuery());
169+
Assert.assertEquals(testQuery.getExpectedResult().toString(), result.list().toString());
170+
}
171+
165172
@AfterClass
166173
public static void afterClass() {
167174
if (session != null) {

interactive_engine/executor/ir/graph_proxy/src/apis/graph/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ use crate::utils::expr::eval_pred::PEvaluator;
3030

3131
pub mod element;
3232
pub type ID = i64;
33+
// a special id for Null graph elements.
34+
pub const NULL_ID: ID = ID::MAX;
3335

3436
pub fn read_id<R: ReadExt>(reader: &mut R) -> io::Result<ID> {
3537
reader.read_i64()

interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ impl InnerOpr {
833833
mod tests {
834834
use ahash::HashMap;
835835
use dyn_type::DateTimeFormats;
836-
use ir_common::{expr_parse::str_to_expr_pb, generated::physical::physical_opr::operator};
836+
use ir_common::expr_parse::str_to_expr_pb;
837837

838838
use super::*;
839839
use crate::apis::{DynDetails, Vertex};

interactive_engine/executor/ir/integrated/tests/optional_expand_test.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ mod common;
2121
mod test {
2222
use std::sync::Arc;
2323

24-
use dyn_type::Object;
2524
use graph_proxy::apis::{register_graph, GraphElement};
2625
use graph_proxy::create_exp_store;
2726
use graph_store::ldbc::LDBCVertexParser;
@@ -96,8 +95,7 @@ mod test {
9695
while let Some(Ok(record)) = result.next() {
9796
if let Some(element) = record.get(None).unwrap().as_vertex() {
9897
result_ids.push(element.id() as usize)
99-
} else if let Some(obj) = record.get(None).unwrap().as_object() {
100-
assert_eq!(obj, &Object::None);
98+
} else if record.get(None).unwrap().is_none() {
10199
none_cnt += 1;
102100
}
103101
}
@@ -131,8 +129,7 @@ mod test {
131129
println!("record: {:?}", record);
132130
if let Some(element) = record.get(None).unwrap().as_vertex() {
133131
result_ids.push(element.id() as usize)
134-
} else if let Some(obj) = record.get(None).unwrap().as_object() {
135-
assert_eq!(obj, &Object::None);
132+
} else if record.get(None).unwrap().is_none() {
136133
none_cnt += 1;
137134
}
138135
}
@@ -168,8 +165,7 @@ mod test {
168165
while let Some(Ok(record)) = result.next() {
169166
if let Some(e) = record.get(None).unwrap().as_edge() {
170167
result_edges.push((e.src_id as usize, e.dst_id as usize));
171-
} else if let Some(obj) = record.get(None).unwrap().as_object() {
172-
assert_eq!(obj, &Object::None);
168+
} else if record.get(None).unwrap().is_none() {
173169
none_cnt += 1;
174170
}
175171
}
@@ -268,11 +264,8 @@ mod test {
268264
while let Some(Ok(record)) = result.next() {
269265
if let Some(element) = record.get(None).unwrap().as_vertex() {
270266
result_ids.push(element.id() as usize);
271-
} else if let Some(obj) = record.get(None).unwrap().as_object() {
272-
assert_eq!(obj, &Object::None);
267+
} else if record.get(None).unwrap().is_none() {
273268
none_cnt += 1;
274-
} else {
275-
unreachable!()
276269
}
277270
}
278271
result_ids.sort();

interactive_engine/executor/ir/runtime/src/process/entry.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use std::sync::Arc;
2424

2525
use ahash::HashMap;
2626
use dyn_type::{BorrowObject, Object};
27+
use graph_proxy::apis::graph::NULL_ID;
2728
use graph_proxy::apis::VertexOrEdge;
2829
use graph_proxy::apis::{Edge, Element, GraphElement, GraphPath, PropertyValue, Vertex, ID};
2930
use ir_common::error::ParsePbError;
@@ -51,6 +52,8 @@ pub enum EntryType {
5152
Intersection,
5253
/// Type of collection consisting of entries
5354
Collection,
55+
/// A Null graph element entry type
56+
Null,
5457
}
5558

5659
pub trait Entry: Debug + Send + Sync + AsAny + Element {
@@ -104,6 +107,7 @@ impl DynEntry {
104107
.as_object()
105108
.map(|obj| obj.eq(&Object::None))
106109
.unwrap_or(false),
110+
EntryType::Null => true,
107111
_ => false,
108112
}
109113
}
@@ -184,6 +188,9 @@ impl Encode for DynEntry {
184188
.unwrap()
185189
.write_to(writer)?;
186190
}
191+
EntryType::Null => {
192+
writer.write_u8(9)?;
193+
}
187194
}
188195
Ok(())
189196
}
@@ -225,6 +232,7 @@ impl Decode for DynEntry {
225232
let general_intersect = GeneralIntersectionEntry::read_from(reader)?;
226233
Ok(DynEntry::new(general_intersect))
227234
}
235+
9 => Ok(DynEntry::new(NullEntry)),
228236
_ => unreachable!(),
229237
}
230238
}
@@ -247,7 +255,7 @@ impl Element for DynEntry {
247255
impl GraphElement for DynEntry {
248256
fn id(&self) -> ID {
249257
match self.get_type() {
250-
EntryType::Vertex | EntryType::Edge | EntryType::Path => {
258+
EntryType::Vertex | EntryType::Edge | EntryType::Path | EntryType::Null => {
251259
self.inner.as_graph_element().unwrap().id()
252260
}
253261
_ => unreachable!(),
@@ -256,7 +264,7 @@ impl GraphElement for DynEntry {
256264

257265
fn label(&self) -> Option<i32> {
258266
match self.get_type() {
259-
EntryType::Vertex | EntryType::Edge | EntryType::Path => {
267+
EntryType::Vertex | EntryType::Edge | EntryType::Path | EntryType::Null => {
260268
self.inner.as_graph_element().unwrap().label()
261269
}
262270
_ => unreachable!(),
@@ -265,7 +273,7 @@ impl GraphElement for DynEntry {
265273

266274
fn get_property(&self, key: &NameOrId) -> Option<PropertyValue> {
267275
match self.get_type() {
268-
EntryType::Vertex | EntryType::Edge | EntryType::Path => self
276+
EntryType::Vertex | EntryType::Edge | EntryType::Path | EntryType::Null => self
269277
.inner
270278
.as_graph_element()
271279
.unwrap()
@@ -276,7 +284,7 @@ impl GraphElement for DynEntry {
276284

277285
fn get_all_properties(&self) -> Option<HashMap<NameOrId, Object>> {
278286
match self.get_type() {
279-
EntryType::Vertex | EntryType::Edge | EntryType::Path => self
287+
EntryType::Vertex | EntryType::Edge | EntryType::Path | EntryType::Null => self
280288
.inner
281289
.as_graph_element()
282290
.unwrap()
@@ -306,6 +314,7 @@ impl Hash for DynEntry {
306314
.as_any_ref()
307315
.downcast_ref::<PairEntry>()
308316
.hash(state),
317+
EntryType::Null => self.hash(state),
309318
}
310319
}
311320
}
@@ -335,6 +344,7 @@ impl PartialEq for DynEntry {
335344
.as_any_ref()
336345
.downcast_ref::<PairEntry>()
337346
.eq(&other.as_any_ref().downcast_ref::<PairEntry>()),
347+
EntryType::Null => other.get_type() == EntryType::Null,
338348
}
339349
} else {
340350
false
@@ -373,6 +383,7 @@ impl PartialOrd for DynEntry {
373383
.as_any_ref()
374384
.downcast_ref::<PairEntry>()
375385
.partial_cmp(&other.as_any_ref().downcast_ref::<PairEntry>()),
386+
EntryType::Null => None,
376387
}
377388
} else {
378389
None
@@ -548,6 +559,50 @@ impl Decode for CollectionEntry {
548559
}
549560
}
550561

562+
// NullEntry represents a null graph element, e.g., a null vertex generated by optional edge_expand.
563+
#[derive(Debug, Clone, Default, PartialEq, PartialOrd, Eq, Hash)]
564+
pub struct NullEntry;
565+
566+
impl_as_any!(NullEntry);
567+
568+
impl Entry for NullEntry {
569+
fn get_type(&self) -> EntryType {
570+
EntryType::Null
571+
}
572+
}
573+
574+
impl Element for NullEntry {
575+
fn as_graph_element(&self) -> Option<&dyn GraphElement> {
576+
Some(self)
577+
}
578+
579+
fn len(&self) -> usize {
580+
0
581+
}
582+
583+
fn as_borrow_object(&self) -> BorrowObject {
584+
BorrowObject::None
585+
}
586+
}
587+
588+
impl GraphElement for NullEntry {
589+
fn id(&self) -> ID {
590+
NULL_ID
591+
}
592+
593+
fn label(&self) -> Option<i32> {
594+
None
595+
}
596+
597+
fn get_property(&self, _key: &NameOrId) -> Option<PropertyValue> {
598+
None
599+
}
600+
601+
fn get_all_properties(&self) -> Option<HashMap<NameOrId, Object>> {
602+
None
603+
}
604+
}
605+
551606
impl TryFrom<result_pb::Element> for DynEntry {
552607
type Error = ParsePbError;
553608
fn try_from(e: result_pb::Element) -> Result<Self, Self::Error> {

interactive_engine/executor/ir/runtime/src/process/operator/flatmap/edge_expand.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use ir_common::KeyId;
2525
use pegasus::api::function::{DynIter, FlatMapFunction, FnResult};
2626

2727
use crate::error::{FnExecError, FnGenError, FnGenResult};
28-
use crate::process::entry::{Entry, EntryType};
28+
use crate::process::entry::{Entry, EntryType, NullEntry};
2929
use crate::process::operator::flatmap::FlatMapFuncGen;
3030
use crate::process::record::{Record, RecordExpandIter, RecordPathExpandIter};
3131

@@ -50,7 +50,7 @@ impl<E: Entry + 'static> FlatMapFunction<Record, Record> for EdgeExpandOperator<
5050
// the case of expand edge, and get end vertex;
5151
ExpandOpt::Vertex => {
5252
if self.is_optional && iter.peek().is_none() {
53-
input.append(Object::None, self.alias);
53+
input.append(NullEntry, self.alias);
5454
Ok(Box::new(vec![input].into_iter()))
5555
} else {
5656
let neighbors_iter = iter.map(|e| {
@@ -74,7 +74,7 @@ impl<E: Entry + 'static> FlatMapFunction<Record, Record> for EdgeExpandOperator<
7474
// the case of expand neighbors, including edges/vertices
7575
ExpandOpt::Edge => {
7676
if self.is_optional && iter.peek().is_none() {
77-
input.append(Object::None, self.alias);
77+
input.append(NullEntry, self.alias);
7878
Ok(Box::new(vec![input].into_iter()))
7979
} else {
8080
Ok(Box::new(RecordExpandIter::new(

interactive_engine/executor/ir/runtime/src/process/operator/map/get_v.rs

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
1616
use std::convert::TryInto;
1717

18-
use dyn_type::Object;
1918
use graph_proxy::apis::GraphElement;
2019
use graph_proxy::apis::{get_graph, DynDetails, GraphPath, QueryParams, Vertex};
2120
use graph_proxy::utils::expr::eval_pred::EvalPred;
@@ -26,7 +25,7 @@ use ir_common::{KeyId, LabelId};
2625
use pegasus::api::function::{FilterMapFunction, FnResult};
2726

2827
use crate::error::{FnExecError, FnExecResult, FnGenError, FnGenResult};
29-
use crate::process::entry::{DynEntry, Entry};
28+
use crate::process::entry::{DynEntry, Entry, NullEntry};
3029
use crate::process::operator::map::FilterMapFuncGen;
3130
use crate::process::record::Record;
3231

@@ -118,16 +117,9 @@ impl FilterMapFunction<Record, Record> for GetVertexOperator {
118117
} else {
119118
Err(FnExecError::unexpected_data_error("unreachable path end entry in GetV"))?
120119
}
121-
} else if let Some(obj) = entry.as_object() {
122-
if Object::None.eq(obj) {
123-
input.append(Object::None, self.alias);
124-
Ok(Some(input))
125-
} else {
126-
Err(FnExecError::unexpected_data_error(&format!(
127-
"Can only apply `GetV` on an object that is not None. The entry is {:?}",
128-
entry
129-
)))?
130-
}
120+
} else if entry.is_none() {
121+
input.append(NullEntry, self.alias);
122+
Ok(Some(input))
131123
} else {
132124
Err(FnExecError::unexpected_data_error( &format!(
133125
"Can only apply `GetV` (`Auxilia` instead) on an edge or path entry, while the entry is {:?}", entry
@@ -251,27 +243,20 @@ impl FilterMapFunction<Record, Record> for AuxiliaOperator {
251243
} else {
252244
return Ok(None);
253245
}
254-
} else if let Some(obj) = entry.as_object() {
255-
if Object::None.eq(obj) {
256-
if let Some(predicate) = &self.query_params.filter {
257-
let res = predicate
258-
.eval_bool(Some(&input))
259-
.map_err(|e| FnExecError::from(e))?;
260-
if res {
261-
input.append(Object::None, self.alias);
262-
return Ok(Some(input));
263-
} else {
264-
return Ok(None);
265-
}
266-
} else {
267-
input.append(Object::None, self.alias);
246+
} else if entry.is_none() {
247+
if let Some(predicate) = &self.query_params.filter {
248+
let res = predicate
249+
.eval_bool(Some(&input))
250+
.map_err(|e| FnExecError::from(e))?;
251+
if res {
252+
input.append(NullEntry, self.alias);
268253
return Ok(Some(input));
254+
} else {
255+
return Ok(None);
269256
}
270257
} else {
271-
Err(FnExecError::unexpected_data_error(&format!(
272-
"neither Vertex nor Edge entry is accessed in `Auxilia` operator, the entry is {:?}",
273-
entry
274-
)))?
258+
input.append(NullEntry, self.alias);
259+
return Ok(Some(input));
275260
}
276261
} else {
277262
Err(FnExecError::unexpected_data_error(&format!(

interactive_engine/executor/ir/runtime/src/process/operator/sink/sink.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ impl RecordSinkEncoder {
252252
EntryType::Pair => {
253253
unreachable!()
254254
}
255+
EntryType::Null => Some(result_pb::element::Inner::Object(Object::None.into())),
255256
};
256257
result_pb::Element { inner }
257258
}

0 commit comments

Comments
 (0)