Skip to content

Commit

Permalink
Implemented support for RecursiveRel in the Rust API
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminwinger committed Jul 13, 2023
1 parent deb1734 commit a901e98
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 26 deletions.
14 changes: 11 additions & 3 deletions tools/rust_api/src/logical_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,23 @@ pub enum LogicalType {
/// Correponds to [Value::Blob](crate::value::Value::Blob)
Blob,
/// Correponds to [Value::VarList](crate::value::Value::VarList)
VarList { child_type: Box<LogicalType> },
VarList {
child_type: Box<LogicalType>,
},
/// Correponds to [Value::FixedList](crate::value::Value::FixedList)
FixedList {
child_type: Box<LogicalType>,
num_elements: u64,
},
/// Correponds to [Value::Struct](crate::value::Value::Struct)
Struct { fields: Vec<(String, LogicalType)> },
Struct {
fields: Vec<(String, LogicalType)>,
},
/// Correponds to [Value::Node](crate::value::Value::Node)
Node,
/// Correponds to [Value::Rel](crate::value::Value::Rel)
Rel,
RecursiveRel,
}

impl From<&ffi::Value> for LogicalType {
Expand Down Expand Up @@ -96,6 +101,7 @@ impl From<&ffi::LogicalType> for LogicalType {
}
LogicalTypeID::NODE => LogicalType::Node,
LogicalTypeID::REL => LogicalType::Rel,
LogicalTypeID::RECURSIVE_REL => LogicalType::RecursiveRel,
// Should be unreachable, as cxx will check that the LogicalTypeID enum matches the one
// on the C++ side.
x => panic!("Unsupported type {:?}", x),
Expand All @@ -121,7 +127,8 @@ impl From<&LogicalType> for cxx::UniquePtr<ffi::LogicalType> {
| LogicalType::String
| LogicalType::Blob
| LogicalType::Node
| LogicalType::Rel => ffi::create_logical_type(typ.id()),
| LogicalType::Rel
| LogicalType::RecursiveRel => ffi::create_logical_type(typ.id()),
LogicalType::VarList { child_type } => {
ffi::create_logical_type_var_list(child_type.as_ref().into())
}
Expand Down Expand Up @@ -165,6 +172,7 @@ impl LogicalType {
LogicalType::Struct { .. } => LogicalTypeID::STRUCT,
LogicalType::Node => LogicalTypeID::NODE,
LogicalType::Rel => LogicalTypeID::REL,
LogicalType::RecursiveRel => LogicalTypeID::RECURSIVE_REL,
}
}
}
166 changes: 143 additions & 23 deletions tools/rust_api/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ pub struct NodeVal {
}

impl NodeVal {
pub fn new(id: InternalID, label: String) -> Self {
pub fn new<I: Into<InternalID>, S: Into<String>>(id: I, label: S) -> Self {
NodeVal {
id,
label,
id: id.into(),
label: label.into(),
properties: vec![],
}
}
Expand All @@ -63,8 +63,8 @@ impl NodeVal {
/// # Arguments
/// * `key`: The name of the property
/// * `value`: The value of the property
pub fn add_property(&mut self, key: String, value: Value) {
self.properties.push((key, value));
pub fn add_property<S: Into<String>, V: Into<Value>>(&mut self, key: S, value: V) {
self.properties.push((key.into(), value.into()));
}

/// Returns all properties of the NodeVal
Expand Down Expand Up @@ -106,11 +106,11 @@ pub struct RelVal {
}

impl RelVal {
pub fn new(src_node: InternalID, dst_node: InternalID, label: String) -> Self {
pub fn new<I: Into<InternalID>, S: Into<String>>(src_node: I, dst_node: I, label: S) -> Self {
RelVal {
src_node,
dst_node,
label,
src_node: src_node.into(),
dst_node: dst_node.into(),
label: label.into(),
properties: vec![],
}
}
Expand Down Expand Up @@ -177,6 +177,15 @@ impl Ord for InternalID {
}
}

impl From<(u64, u64)> for InternalID {
fn from(value: (u64, u64)) -> Self {
InternalID {
offset: value.0,
table_id: value.1,
}
}
}

/// Data types supported by Kùzu
///
/// Also see <https://kuzudb.com/docs/cypher/data-types/overview.html>
Expand Down Expand Up @@ -222,6 +231,25 @@ pub enum Value {
Struct(Vec<(String, Value)>),
Node(NodeVal),
Rel(RelVal),
RecursiveRel {
/// Interior nodes in the Sequence of Rels
///
/// Does not include the starting or ending Node.
nodes: Vec<NodeVal>,
/// Sequence of Rels which make up the RecursiveRel
rels: Vec<RelVal>,
},
}

fn display_list<T: std::fmt::Display>(f: &mut fmt::Formatter<'_>, list: &Vec<T>) -> fmt::Result {
write!(f, "[")?;
for (i, value) in list.iter().enumerate() {
write!(f, "{}", value)?;
if i != list.len() - 1 {
write!(f, ",")?;
}
}
write!(f, "]")
}

impl std::fmt::Display for Value {
Expand All @@ -236,16 +264,7 @@ impl std::fmt::Display for Value {
Value::String(x) => write!(f, "{x}"),
Value::Blob(x) => write!(f, "{x:x?}"),
Value::Null(_) => write!(f, ""),
Value::VarList(_, x) | Value::FixedList(_, x) => {
write!(f, "[")?;
for (i, value) in x.iter().enumerate() {
write!(f, "{}", value)?;
if i != x.len() - 1 {
write!(f, ",")?;
}
}
write!(f, "]")
}
Value::VarList(_, x) | Value::FixedList(_, x) => display_list(f, x),
// Note: These don't match kuzu's toString, but we probably don't want them to
Value::Interval(x) => write!(f, "{x}"),
Value::Timestamp(x) => write!(f, "{x}"),
Expand All @@ -264,6 +283,14 @@ impl std::fmt::Display for Value {
Value::Node(x) => write!(f, "{x}"),
Value::Rel(x) => write!(f, "{x}"),
Value::InternalID(x) => write!(f, "{x}"),
Value::RecursiveRel { nodes, rels } => {
write!(f, "{{")?;
write!(f, "_NODES: ")?;
display_list(f, nodes)?;
write!(f, ", _RELS: ")?;
display_list(f, rels)?;
write!(f, "}}")
}
}
}
}
Expand Down Expand Up @@ -302,6 +329,7 @@ impl From<&Value> for LogicalType {
Value::InternalID(_) => LogicalType::InternalID,
Value::Node(_) => LogicalType::Node,
Value::Rel(_) => LogicalType::Rel,
Value::RecursiveRel { .. } => LogicalType::RecursiveRel,
}
}
}
Expand Down Expand Up @@ -400,8 +428,10 @@ impl TryFrom<&ffi::Value> for Value {
let mut node_val = NodeVal::new(id, label);
let properties = ffi::node_value_get_properties(value);
for i in 0..properties.size() {
node_val
.add_property(properties.get_name(i), properties.get_value(i).try_into()?);
node_val.add_property(
properties.get_name(i),
TryInto::<Value>::try_into(properties.get_value(i))?,
);
}
Ok(Value::Node(node_val))
}
Expand Down Expand Up @@ -432,8 +462,47 @@ impl TryFrom<&ffi::Value> for Value {
table_id: internal_id[1],
}))
}
// Should be unreachable, as cxx will check that the LogicalTypeID enum matches the one
// on the C++ side.
LogicalTypeID::RECURSIVE_REL => {
// Data is a list containing a list of nodes and a list of rels
let list = ffi::value_get_list(value);
if list.size() != 2 {
panic!("RecursiveRel should have two elements in its internal list!");
}
let value1 = list.get(0).as_ref().unwrap().try_into()?;
let value2 = list.get(1).as_ref().unwrap().try_into()?;
if let (
Value::VarList(LogicalType::Node, nodes),
Value::VarList(LogicalType::Rel, rels),
) = (value1, value2)
{
let nodes = nodes.into_iter().map(|x| {
if let Value::Node(x) = x {
x
} else {
unreachable!()
}
});
let rels = rels.into_iter().map(|x| {
if let Value::Rel(x) = x {
x
} else {
unreachable!()
}
});
Ok(Value::RecursiveRel {
nodes: nodes.collect(),
rels: rels.collect(),
})
} else {
let value1: Value = list.get(0).as_ref().unwrap().try_into()?;
let value2: Value = list.get(1).as_ref().unwrap().try_into()?;
panic!(
"RecursiveRel did not contain a list of nodes and rels: {}, {}",
value1, value2
);
}
}
// TODO(bmwinger): Better error message for types which are unsupported
x => panic!("Unsupported type {:?}", x),
}
}
Expand Down Expand Up @@ -532,6 +601,9 @@ impl TryInto<cxx::UniquePtr<ffi::Value>> for Value {
}
Value::Node(_) => Err(crate::Error::ReadOnlyType(LogicalType::Node)),
Value::Rel(_) => Err(crate::Error::ReadOnlyType(LogicalType::Rel)),
Value::RecursiveRel { .. } => {
Err(crate::Error::ReadOnlyType(LogicalType::RecursiveRel))
}
}
}
}
Expand Down Expand Up @@ -695,6 +767,7 @@ mod tests {
convert_node_type: LogicalType::Node,
convert_internal_id_type: LogicalType::InternalID,
convert_rel_type: LogicalType::Rel,
convert_recursive_rel_type: LogicalType::RecursiveRel,
}

value_tests! {
Expand Down Expand Up @@ -858,6 +931,53 @@ mod tests {
Ok(())
}

#[test]
fn test_recursive_rel() -> Result<()> {
let temp_dir = tempfile::TempDir::new()?;
let db = Database::new(temp_dir.path(), 0)?;
let conn = Connection::new(&db)?;
conn.query("CREATE NODE TABLE Person(name STRING, age INT64, PRIMARY KEY(name));")?;
conn.query("CREATE REL TABLE knows(FROM Person TO Person);")?;
conn.query("CREATE (:Person {name: \"Alice\", age: 25});")?;
conn.query("CREATE (:Person {name: \"Bob\", age: 25});")?;
conn.query("CREATE (:Person {name: \"Eve\", age: 25});")?;
conn.query(
"MATCH (p1:Person), (p2:Person)
WHERE p1.name = \"Alice\" AND p2.name = \"Bob\"
CREATE (p1)-[:knows]->(p2);",
)?;
conn.query(
"MATCH (p1:Person), (p2:Person)
WHERE p1.name = \"Bob\" AND p2.name = \"Eve\"
CREATE (p1)-[:knows]->(p2);",
)?;
let result = conn
.query(
"MATCH (a:Person)-[e*2..2]->(b:Person)
WHERE a.name = 'Alice'
RETURN e, b.name;",
)?
.next()
.unwrap();
assert_eq!(result[1], Value::String("Eve".to_string()));
assert_eq!(
result[0],
Value::RecursiveRel {
nodes: vec![NodeVal {
id: (1, 0).into(),
label: "Person".into(),
properties: vec![("name".into(), "Bob".into()), ("age".into(), 25i64.into())]
},],
rels: vec![
RelVal::new((0, 0), (1, 0), "knows"),
RelVal::new((1, 0), (2, 0), "knows"),
],
}
);
temp_dir.close()?;
Ok(())
}

#[test]
/// Test that null values are read correctly by the API
fn test_null() -> Result<()> {
Expand Down

0 comments on commit a901e98

Please sign in to comment.