blob: 51f26fc12a5c65c68700898a6f9bcf738c970486 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! GraphAr writer builders.
use std::pin::Pin;
use cxx::{UniquePtr, let_cxx_string};
use crate::builder::property_value::sealed;
use crate::types::Cardinality;
use crate::{builder::PropertyValue, ffi, info::VertexInfo};
/// A vertex record being constructed for use with [`VerticesBuilder`].
///
/// This is a thin wrapper around GraphAr's `graphar::builder::Vertex`.
pub struct Vertex(pub(crate) UniquePtr<ffi::graphar::BuilderVertex>);
macro_rules! impl_vertex_property_value {
($($ty:tt),+ $(,)?) => {
$(
impl sealed::Sealed<Vertex> for $ty {}
impl PropertyValue<Vertex> for $ty {
fn add_to(self, vertex: &mut Vertex, name: &str) {
let_cxx_string!(name = name);
pastey::paste! {
ffi::graphar::[<vertex_builder_add_property_ $ty>](vertex.pin_mut(), &name, self);
}
}
fn add_to_with_cardinality(
self,
vertex: &mut Vertex,
name: &str,
cardinality: Cardinality,
) {
let_cxx_string!(name = name);
pastey::paste! {
ffi::graphar::[<vertex_builder_add_property_ $ty _with_cardinality>](
vertex.pin_mut(),
cardinality,
&name,
self,
);
}
}
}
)+
};
}
impl_vertex_property_value!(bool, i32, i64, f32, f64);
impl sealed::Sealed<Vertex> for String {}
impl PropertyValue<Vertex> for String {
fn add_to(self, vertex: &mut Vertex, name: &str) {
let_cxx_string!(name = name);
let_cxx_string!(val = self.as_str());
ffi::graphar::vertex_builder_add_property_string(vertex.pin_mut(), &name, &val);
}
fn add_to_with_cardinality(self, vertex: &mut Vertex, name: &str, cardinality: Cardinality) {
let_cxx_string!(name = name);
let_cxx_string!(val = self.as_str());
ffi::graphar::vertex_builder_add_property_string_with_cardinality(
vertex.pin_mut(),
cardinality,
&name,
&val,
);
}
}
impl sealed::Sealed<Vertex> for &str {}
impl PropertyValue<Vertex> for &str {
fn add_to(self, vertex: &mut Vertex, name: &str) {
let_cxx_string!(name = name);
let_cxx_string!(val = self);
ffi::graphar::vertex_builder_add_property_string(vertex.pin_mut(), &name, &val);
}
fn add_to_with_cardinality(self, vertex: &mut Vertex, name: &str, cardinality: Cardinality) {
let_cxx_string!(name = name);
let_cxx_string!(val = self);
ffi::graphar::vertex_builder_add_property_string_with_cardinality(
vertex.pin_mut(),
cardinality,
&name,
&val,
);
}
}
impl Vertex {
pub(crate) fn as_ref(&self) -> &ffi::graphar::BuilderVertex {
self.0.as_ref().expect("vertex should be valid")
}
/// Create a new vertex record.
pub fn new() -> Self {
Self(ffi::graphar::new_vertex_builder())
}
/// Returns true if this vertex is empty.
pub fn is_empty(&self) -> bool {
ffi::graphar::vertex_builder_is_empty(self.as_ref())
}
/// Add a property to this vertex.
///
/// Supported types are: `bool`, `i32`, `i64`, `f32`, `f64`, `String`, `&str`.
pub fn add_property<S, V>(&mut self, name: S, val: V)
where
S: AsRef<str>,
V: PropertyValue<Vertex>,
{
val.add_to(self, name.as_ref());
}
/// Add a boolean property.
pub fn add_property_bool<S: AsRef<str>>(&mut self, name: S, val: bool) {
self.add_property(name, val);
}
/// Add an `i32` property.
pub fn add_property_i32<S: AsRef<str>>(&mut self, name: S, val: i32) {
self.add_property(name, val);
}
/// Add an `i64` property.
pub fn add_property_i64<S: AsRef<str>>(&mut self, name: S, val: i64) {
self.add_property(name, val);
}
/// Add an `f32` property.
pub fn add_property_f32<S: AsRef<str>>(&mut self, name: S, val: f32) {
self.add_property(name, val);
}
/// Add an `f64` property.
pub fn add_property_f64<S: AsRef<str>>(&mut self, name: S, val: f64) {
self.add_property(name, val);
}
/// Add a string property.
pub fn add_property_string<S: AsRef<str>, V: AsRef<str>>(&mut self, name: S, val: V) {
self.add_property(name, val.as_ref());
}
/// Add a property to this vertex with the specified cardinality.
///
/// Supported types are: `bool`, `i32`, `i64`, `f32`, `f64`, `String`, `&str`.
pub fn add_property_with_cardinality<S, V>(&mut self, cardinality: Cardinality, name: S, val: V)
where
S: AsRef<str>,
V: PropertyValue<Vertex>,
{
val.add_to_with_cardinality(self, name.as_ref(), cardinality);
}
/// Returns true if the property is stored as a multi-value (LIST/SET).
pub fn is_multi_property<S: AsRef<str>>(&self, name: S) -> bool {
let_cxx_string!(name = name.as_ref());
ffi::graphar::vertex_builder_is_multi_property(self.as_ref(), &name)
}
/// Returns true if this vertex contains a property with the given name.
pub fn contains_property<S: AsRef<str>>(&self, name: S) -> bool {
let_cxx_string!(name = name.as_ref());
ffi::graphar::vertex_builder_contains_property(self.as_ref(), &name)
}
/// Append a value into a LIST property.
pub fn add_property_list_item<S, V>(&mut self, name: S, val: V)
where
S: AsRef<str>,
V: PropertyValue<Vertex>,
{
self.add_property_with_cardinality(Cardinality::List, name, val);
}
/// Append multiple values into a LIST property.
pub fn add_property_list<I, V>(&mut self, name: &str, values: I)
where
I: IntoIterator<Item = V>,
V: PropertyValue<Vertex>,
{
for v in values {
self.add_property_list_item(name, v);
}
}
/// Append a value into a SET property.
pub fn add_property_set_item<S, V>(&mut self, name: S, val: V)
where
S: AsRef<str>,
V: PropertyValue<Vertex>,
{
self.add_property_with_cardinality(Cardinality::Set, name, val);
}
/// Append multiple values into a SET property.
pub fn add_property_set<I, V>(&mut self, name: &str, values: I)
where
I: IntoIterator<Item = V>,
V: PropertyValue<Vertex>,
{
for v in values {
self.add_property_set_item(name, v);
}
}
pub(crate) fn pin_mut(&mut self) -> Pin<&mut ffi::graphar::BuilderVertex> {
self.0.as_mut().expect("vertex should be valid")
}
}
impl Default for Vertex {
fn default() -> Self {
Self::new()
}
}
/// A high-level builder for writing a collection of vertices.
pub struct VerticesBuilder(pub(crate) UniquePtr<ffi::graphar::VerticesBuilder>);
impl VerticesBuilder {
/// Create a new vertices builder.
///
/// The `prefix` is a filesystem prefix string used by GraphAr (it is not a [`std::path::Path`]).
///
/// GraphAr expects `prefix` to end with a trailing slash (`/`).
pub fn try_new<P: AsRef<str>>(
vertex_info: &VertexInfo,
prefix: P,
start_idx: i64,
) -> crate::Result<Self> {
let prefix = prefix.as_ref();
if !prefix.ends_with('/') {
return Err(crate::Error::InvalidArgument {
name: "prefix",
reason: "prefix must end with '/'".to_string(),
});
}
let_cxx_string!(prefix = prefix);
let inner = ffi::graphar::new_vertices_builder(&vertex_info.0, &prefix, start_idx)?;
Ok(Self(inner))
}
/// Create a new vertices builder.
///
/// Panics if the inputs are rejected by GraphAr. Prefer [`VerticesBuilder::try_new`]
/// if you want to handle errors.
pub fn new<P: AsRef<str>>(vertex_info: &VertexInfo, prefix: P, start_idx: i64) -> Self {
Self::try_new(vertex_info, prefix, start_idx).unwrap()
}
/// Add a vertex into this builder.
pub fn add_vertex(&mut self, mut vertex: Vertex) -> crate::Result<()> {
ffi::graphar::add_vertex(self.pin_mut(), vertex.pin_mut())?;
Ok(())
}
/// Dump all currently buffered vertices into chunk files.
pub fn dump(&mut self) -> crate::Result<()> {
ffi::graphar::vertices_dump(self.pin_mut())?;
Ok(())
}
pub(crate) fn pin_mut(&mut self) -> Pin<&mut ffi::graphar::VerticesBuilder> {
self.0.as_mut().expect("vertices builder should be valid")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::info::InfoVersion;
use crate::property::{Property, PropertyGroup, PropertyGroupVector, PropertyVec};
use crate::types::{Cardinality, DataType, FileType};
use tempfile::tempdir;
fn make_vertex_info() -> VertexInfo {
let mut props = PropertyVec::new();
props.push(Property::new(
"id_i64",
DataType::int64(),
true,
false,
Cardinality::Single,
));
props.push(Property::new(
"active_bool",
DataType::bool(),
false,
false,
Cardinality::Single,
));
props.push(Property::new(
"age_i32",
DataType::int32(),
false,
false,
Cardinality::Single,
));
props.push(Property::new(
"score_f32",
DataType::float32(),
false,
false,
Cardinality::Single,
));
props.push(Property::new(
"rating_f64",
DataType::float64(),
false,
false,
Cardinality::Single,
));
props.push(Property::new(
"name_string",
DataType::string(),
false,
false,
Cardinality::Single,
));
let mut groups = PropertyGroupVector::new();
groups.push(PropertyGroup::new(props, FileType::Csv, ""));
let ver = Some(InfoVersion::new(1).unwrap());
VertexInfo::new("person", 4, groups, vec![], "", ver)
}
#[test]
fn test_vertex_add_property_dispatch_primitives() {
let mut v = Vertex::default();
assert!(v.is_empty());
assert!(!v.contains_property("age_i32"));
v.add_property("id_i64", 1_i64);
v.add_property("active_bool", true);
v.add_property("age_i32", 42_i32);
v.add_property("score_f32", 0.5_f32);
v.add_property("rating_f64", 9.5_f64);
assert!(!v.is_empty());
assert!(!v.is_multi_property("age_i32"));
assert!(v.contains_property("age_i32"));
}
#[test]
fn test_vertex_add_property_dispatch_string() {
let mut v = Vertex::default();
assert!(v.is_empty());
v.add_property("name_string", "alice");
v.add_property("name_string", "bob");
v.add_property_string("name_string", "carol");
v.add_property_string("name_string", "dave");
assert!(!v.is_empty());
assert!(!v.is_multi_property("name_string"));
}
#[test]
fn test_vertex_add_property_wrapper_methods() {
let mut v = Vertex::new();
v.add_property_bool("active_bool", true);
v.add_property_i32("age_i32", 1);
v.add_property_i64("id_i64", 2);
v.add_property_f32("score_f32", 1.0);
v.add_property_f64("rating_f64", 2.0);
v.add_property_string("name_string", "alice");
assert!(!v.is_empty());
}
#[test]
fn test_vertex_add_property_with_cardinality() {
let mut v = Vertex::new();
assert!(!v.is_multi_property("tags"));
assert!(!v.contains_property("tags"));
v.add_property_with_cardinality(Cardinality::List, "tags", "t0");
assert!(v.is_multi_property("tags"));
assert!(v.contains_property("tags"));
v.add_property_list("nums", [1_i64, 2_i64, 3_i64]);
assert!(v.is_multi_property("nums"));
assert!(v.contains_property("nums"));
v.add_property_set("uniq", ["a", "a", "b"]);
assert!(v.is_multi_property("uniq"));
assert!(v.contains_property("uniq"));
}
#[test]
fn test_vertex_add_property_with_cardinality_dispatch() {
let mut v = Vertex::new();
v.add_property_with_cardinality(Cardinality::Single, "b_single", true);
assert!(v.contains_property("b_single"));
assert!(!v.is_multi_property("b_single"));
v.add_property_with_cardinality(Cardinality::List, "b_list", true);
assert!(v.contains_property("b_list"));
assert!(v.is_multi_property("b_list"));
v.add_property_with_cardinality(Cardinality::Single, "i32_single", 1_i32);
assert!(v.contains_property("i32_single"));
assert!(!v.is_multi_property("i32_single"));
v.add_property_with_cardinality(Cardinality::List, "i32_list", 1_i32);
assert!(v.contains_property("i32_list"));
assert!(v.is_multi_property("i32_list"));
v.add_property_with_cardinality(Cardinality::Single, "i64_single", 1_i64);
assert!(v.contains_property("i64_single"));
assert!(!v.is_multi_property("i64_single"));
v.add_property_with_cardinality(Cardinality::List, "i64_list", 1_i64);
assert!(v.contains_property("i64_list"));
assert!(v.is_multi_property("i64_list"));
v.add_property_with_cardinality(Cardinality::Single, "f32_single", 1.0_f32);
assert!(v.contains_property("f32_single"));
assert!(!v.is_multi_property("f32_single"));
v.add_property_with_cardinality(Cardinality::List, "f32_list", 1.0_f32);
assert!(v.contains_property("f32_list"));
assert!(v.is_multi_property("f32_list"));
v.add_property_with_cardinality(Cardinality::Single, "f64_single", 1.0_f64);
assert!(v.contains_property("f64_single"));
assert!(!v.is_multi_property("f64_single"));
v.add_property_with_cardinality(Cardinality::List, "f64_list", 1.0_f64);
assert!(v.contains_property("f64_list"));
assert!(v.is_multi_property("f64_list"));
v.add_property_with_cardinality(Cardinality::Single, "s_single", "alice");
assert!(v.contains_property("s_single"));
assert!(!v.is_multi_property("s_single"));
v.add_property_with_cardinality(Cardinality::List, "s_list", "alice");
assert!(v.contains_property("s_list"));
assert!(v.is_multi_property("s_list"));
v.add_property_with_cardinality(Cardinality::Single, "string_single", "bob".to_string());
assert!(v.contains_property("string_single"));
assert!(!v.is_multi_property("string_single"));
v.add_property_with_cardinality(Cardinality::Set, "string_set", "bob".to_string());
assert!(v.contains_property("string_set"));
assert!(v.is_multi_property("string_set"));
}
#[test]
fn test_vertices_builder_add_and_dump() {
let info = make_vertex_info();
let tmp = tempdir().unwrap();
let prefix = tmp.path().join("vertices");
std::fs::create_dir_all(&prefix).unwrap();
let prefix = format!("{}/", prefix.display());
let mut b = VerticesBuilder::new(&info, prefix.as_str(), 0);
let mut v = Vertex::new();
v.add_property("id_i64", 1_i64);
v.add_property("active_bool", true);
v.add_property("age_i32", 42_i32);
v.add_property("score_f32", 0.5_f32);
v.add_property("rating_f64", 9.5_f64);
v.add_property("name_string", "alice");
b.add_vertex(v).unwrap();
b.dump().unwrap();
let dir = tmp.path().join("vertices");
assert!(std::fs::read_dir(&dir).unwrap().next().is_some());
}
#[test]
fn test_vertices_builder_try_new_rejects_prefix_without_trailing_slash() {
let info = make_vertex_info();
let tmp = tempdir().unwrap();
let prefix = format!("{}", tmp.path().display());
match VerticesBuilder::try_new(&info, prefix.as_str(), 0) {
Ok(_) => panic!("VerticesBuilder::try_new should reject prefixes without trailing '/'"),
Err(err) => assert!(matches!(
err,
crate::Error::InvalidArgument { name: "prefix", .. }
)),
}
}
#[test]
fn test_vertices_builder_try_new_rejects_negative_start_idx() {
let info = make_vertex_info();
let tmp = tempdir().unwrap();
let prefix = format!("{}/", tmp.path().display());
match VerticesBuilder::try_new(&info, prefix.as_str(), -1) {
Ok(_) => panic!("VerticesBuilder::try_new should reject negative start_idx"),
Err(err) => assert!(matches!(err, crate::Error::Cxx(_))),
}
}
}