diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 052f39a9c..4bb944f3b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -191,6 +191,7 @@ dependencies = [ "cmake", "cxx", "cxx-build", + "pastey", "pkg-config", "tempfile", ] @@ -238,6 +239,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "pkg-config" version = "0.3.32" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7a66444f0..df758a105 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -24,6 +24,7 @@ license = "Apache-2.0" [dependencies] # anyhow = "1.0.100" cxx = "1.0.190" +pastey = "0.2.1" [build-dependencies] cxx-build = "1.0.190" diff --git a/rust/build.rs b/rust/build.rs index 40fce4623..d4ecba2e7 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -23,11 +23,17 @@ use std::path::{Path, PathBuf}; fn link_libraries() { println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH"); - let _arrow = pkg_config::Config::new() - .statik(false) - .cargo_metadata(true) - .probe("arrow") - .expect("Arrow development files not found via pkg-config. Set PKG_CONFIG_PATH if needed."); + for package in ["arrow", "parquet", "arrow-dataset", "arrow-acero"] { + pkg_config::Config::new() + .statik(false) + .cargo_metadata(true) + .probe(package) + .unwrap_or_else(|_| { + panic!( + "{package} development files not found via pkg-config. Set PKG_CONFIG_PATH if needed." + ) + }); + } println!("cargo:rustc-link-lib=graphar"); println!("cargo:rustc-link-lib=graphar_thirdparty"); @@ -39,7 +45,7 @@ fn build_ffi(bridge_file: &str, out_name: &str, source_file: &str, include_paths build.includes(include_paths); // TODO support MSVC - build.flag("-std=c++17"); + build.flag("-std=c++20"); build.flag("-fdiagnostics-color=always"); build.compile(out_name); diff --git a/rust/include/graphar_rs.h b/rust/include/graphar_rs.h index 3d096e5a2..527fae309 100644 --- a/rust/include/graphar_rs.h +++ b/rust/include/graphar_rs.h @@ -25,12 +25,15 @@ #include #include -#include "graphar/fwd.h" -#include "graphar/graph_info.h" -#include "graphar/types.h" -#include "graphar/version_parser.h" +#include "graphar/api/high_level_writer.h" +#include "graphar/api/info.h" #include "rust/cxx.h" +using i32 = int32_t; +using i64 = int64_t; +using f32 = float; +using f64 = double; + namespace graphar { struct MaybeIndex; @@ -129,4 +132,70 @@ void graph_info_save(const graphar::GraphInfo& graph_info, const std::string& path); std::unique_ptr graph_info_dump( const graphar::GraphInfo& graph_info); + +// =========================== Builder =========================== +// `Vertex` +std::unique_ptr new_vertex_builder(); + +inline bool vertex_builder_is_empty(const graphar::builder::Vertex& v) { + return v.Empty(); +} + +inline bool vertex_builder_is_multi_property(const graphar::builder::Vertex& v, + const std::string& name) { + return v.IsMultiProperty(name); +} + +inline bool vertex_builder_contains_property(const graphar::builder::Vertex& v, + const std::string& name) { + const auto& props = v.GetProperties(); + return props.find(name) != props.end(); +} + +#define DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(type) \ + inline void vertex_builder_add_property_##type( \ + graphar::builder::Vertex& v, const std::string& name, type val) { \ + v.AddProperty(name, val); \ + } + +DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(bool) +DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(i32) +DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(i64) +DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(f32) +DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(f64) + +inline void vertex_builder_add_property_string(graphar::builder::Vertex& v, + const std::string& name, + const std::string& val) { + v.AddProperty(name, val); +} + +#define DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(type) \ + inline void vertex_builder_add_property_##type##_with_cardinality( \ + graphar::builder::Vertex& v, graphar::Cardinality cardinality, \ + const std::string& name, type val) { \ + v.AddProperty(cardinality, name, val); \ + } + +DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(bool) +DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(i32) +DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(i64) +DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(f32) +DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(f64) + +inline void vertex_builder_add_property_string_with_cardinality( + graphar::builder::Vertex& v, graphar::Cardinality cardinality, + const std::string& name, const std::string& val) { + v.AddProperty(cardinality, name, val); +} + +void add_vertex(graphar::builder::VerticesBuilder& builder, + graphar::builder::Vertex& v); + +std::unique_ptr +new_vertices_builder(const std::shared_ptr& vertex_info, + const std::string& path_prefix, i64 start_idx); + +void vertices_dump(graphar::builder::VerticesBuilder& builder); + } // namespace graphar_rs diff --git a/rust/src/builder/mod.rs b/rust/src/builder/mod.rs new file mode 100644 index 000000000..b35f32c17 --- /dev/null +++ b/rust/src/builder/mod.rs @@ -0,0 +1,11 @@ +//! High-level builders for writing GraphAr data. + +/// Vertex builders for writing vertex chunks. +pub mod vertex; + +mod property_value; + +/// A value that can be written into a GraphAr property. +pub use property_value::PropertyValue; + +pub use vertex::{Vertex, VerticesBuilder}; diff --git a/rust/src/builder/property_value.rs b/rust/src/builder/property_value.rs new file mode 100644 index 000000000..35dcbdd46 --- /dev/null +++ b/rust/src/builder/property_value.rs @@ -0,0 +1,35 @@ +// 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. + +//! Shared property value abstractions for builders. + +use crate::types::Cardinality; + +#[doc(hidden)] +pub(crate) mod sealed { + pub trait Sealed {} +} + +/// A value that can be written into a GraphAr property. +/// +/// This trait is sealed and cannot be implemented outside this crate. +pub trait PropertyValue: sealed::Sealed { + /// Add this value as a property on the given target. + fn add_to(self, target: &mut Target, name: &str); + /// Add this value as a property on the given target with the specified cardinality. + fn add_to_with_cardinality(self, target: &mut Target, name: &str, cardinality: Cardinality); +} diff --git a/rust/src/builder/vertex.rs b/rust/src/builder/vertex.rs new file mode 100644 index 000000000..51f26fc12 --- /dev/null +++ b/rust/src/builder/vertex.rs @@ -0,0 +1,519 @@ +// 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); + +macro_rules! impl_vertex_property_value { + ($($ty:tt),+ $(,)?) => { + $( + impl sealed::Sealed for $ty {} + + impl PropertyValue for $ty { + fn add_to(self, vertex: &mut Vertex, name: &str) { + let_cxx_string!(name = name); + pastey::paste! { + ffi::graphar::[](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.pin_mut(), + cardinality, + &name, + self, + ); + } + } + } + )+ + }; +} + +impl_vertex_property_value!(bool, i32, i64, f32, f64); + +impl sealed::Sealed for String {} +impl PropertyValue 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 for &str {} +impl PropertyValue 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(&mut self, name: S, val: V) + where + S: AsRef, + V: PropertyValue, + { + val.add_to(self, name.as_ref()); + } + + /// Add a boolean property. + pub fn add_property_bool>(&mut self, name: S, val: bool) { + self.add_property(name, val); + } + + /// Add an `i32` property. + pub fn add_property_i32>(&mut self, name: S, val: i32) { + self.add_property(name, val); + } + + /// Add an `i64` property. + pub fn add_property_i64>(&mut self, name: S, val: i64) { + self.add_property(name, val); + } + + /// Add an `f32` property. + pub fn add_property_f32>(&mut self, name: S, val: f32) { + self.add_property(name, val); + } + + /// Add an `f64` property. + pub fn add_property_f64>(&mut self, name: S, val: f64) { + self.add_property(name, val); + } + + /// Add a string property. + pub fn add_property_string, V: AsRef>(&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(&mut self, cardinality: Cardinality, name: S, val: V) + where + S: AsRef, + V: PropertyValue, + { + 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>(&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>(&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(&mut self, name: S, val: V) + where + S: AsRef, + V: PropertyValue, + { + self.add_property_with_cardinality(Cardinality::List, name, val); + } + + /// Append multiple values into a LIST property. + pub fn add_property_list(&mut self, name: &str, values: I) + where + I: IntoIterator, + V: PropertyValue, + { + for v in values { + self.add_property_list_item(name, v); + } + } + + /// Append a value into a SET property. + pub fn add_property_set_item(&mut self, name: S, val: V) + where + S: AsRef, + V: PropertyValue, + { + self.add_property_with_cardinality(Cardinality::Set, name, val); + } + + /// Append multiple values into a SET property. + pub fn add_property_set(&mut self, name: &str, values: I) + where + I: IntoIterator, + V: PropertyValue, + { + 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); + +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>( + vertex_info: &VertexInfo, + prefix: P, + start_idx: i64, + ) -> crate::Result { + 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>(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(_))), + } + } +} diff --git a/rust/src/error.rs b/rust/src/error.rs index fcc873ce9..f727f41c8 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -28,6 +28,13 @@ pub type Result = std::result::Result; pub enum Error { /// An exception raised from the C++ GraphAr implementation and propagated via `cxx`. Cxx(cxx::Exception), + /// An input argument is rejected by the Rust binding before calling into GraphAr. + InvalidArgument { + /// The argument name. + name: &'static str, + /// The reason why this argument is invalid. + reason: String, + }, /// A filesystem path is not valid UTF-8. NonUtf8Path(PathBuf), } @@ -36,6 +43,9 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Cxx(e) => write!(f, "C++ exception: {e}"), + Self::InvalidArgument { name, reason } => { + write!(f, "invalid argument {name}: {reason}") + } Self::NonUtf8Path(path) => write!(f, "path is not valid UTF-8: {}", path.display()), } } @@ -45,6 +55,7 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Cxx(e) => Some(e), + Self::InvalidArgument { .. } => None, Self::NonUtf8Path(_) => None, } } @@ -89,4 +100,15 @@ mod tests { assert!(msg.contains("C++ exception:"), "msg={msg:?}"); assert!(StdError::source(&err).is_some()); } + + #[test] + fn test_invalid_argument_display_and_source() { + let err = Error::InvalidArgument { + name: "prefix", + reason: "prefix must end with '/'".to_string(), + }; + let msg = err.to_string(); + assert!(msg.contains("invalid argument prefix"), "msg={msg:?}"); + assert!(StdError::source(&err).is_none()); + } } diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs index 15c205844..d388d2fcc 100644 --- a/rust/src/ffi.rs +++ b/rust/src/ffi.rs @@ -452,6 +452,121 @@ pub(crate) mod graphar { type SharedAdjacentList = crate::ffi::SharedAdjacentList; } impl CxxVector {} + + // =========================== Builder =========================== + // `builder::Vertex` + #[namespace = "graphar::builder"] + unsafe extern "C++" { + #[rust_name = "BuilderVertex"] + type Vertex; + + #[namespace = "graphar_rs"] + fn new_vertex_builder() -> UniquePtr; + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_bool( + vertex: Pin<&mut BuilderVertex>, + name: &CxxString, + val: bool, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_i32( + vertex: Pin<&mut BuilderVertex>, + name: &CxxString, + val: i32, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_i64( + vertex: Pin<&mut BuilderVertex>, + name: &CxxString, + val: i64, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_f32( + vertex: Pin<&mut BuilderVertex>, + name: &CxxString, + val: f32, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_f64( + vertex: Pin<&mut BuilderVertex>, + name: &CxxString, + val: f64, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_string( + vertex: Pin<&mut BuilderVertex>, + name: &CxxString, + val: &CxxString, + ); + + #[namespace = "graphar_rs"] + fn vertex_builder_is_empty(vertex: &BuilderVertex) -> bool; + #[namespace = "graphar_rs"] + fn vertex_builder_is_multi_property(vertex: &BuilderVertex, name: &CxxString) -> bool; + #[namespace = "graphar_rs"] + fn vertex_builder_contains_property(vertex: &BuilderVertex, name: &CxxString) -> bool; + + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_bool_with_cardinality( + vertex: Pin<&mut BuilderVertex>, + cardinality: Cardinality, + name: &CxxString, + val: bool, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_i32_with_cardinality( + vertex: Pin<&mut BuilderVertex>, + cardinality: Cardinality, + name: &CxxString, + val: i32, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_i64_with_cardinality( + vertex: Pin<&mut BuilderVertex>, + cardinality: Cardinality, + name: &CxxString, + val: i64, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_f32_with_cardinality( + vertex: Pin<&mut BuilderVertex>, + cardinality: Cardinality, + name: &CxxString, + val: f32, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_f64_with_cardinality( + vertex: Pin<&mut BuilderVertex>, + cardinality: Cardinality, + name: &CxxString, + val: f64, + ); + #[namespace = "graphar_rs"] + fn vertex_builder_add_property_string_with_cardinality( + vertex: Pin<&mut BuilderVertex>, + cardinality: Cardinality, + name: &CxxString, + val: &CxxString, + ); + } + + // `builder::VerticesBuilder` + #[namespace = "graphar::builder"] + unsafe extern "C++" { + type VerticesBuilder; + + #[namespace = "graphar_rs"] + fn add_vertex(builder: Pin<&mut VerticesBuilder>, v: Pin<&mut BuilderVertex>) + -> Result<()>; + #[namespace = "graphar_rs"] + fn new_vertices_builder( + vertex_info: &SharedPtr, + path_prefix: &CxxString, + start_idx: i64, + ) -> Result>; + #[namespace = "graphar_rs"] + fn vertices_dump(builder: Pin<&mut VerticesBuilder>) -> Result<()>; + } } impl From for Option { diff --git a/rust/src/graphar_rs.cc b/rust/src/graphar_rs.cc index 0d9f68a97..022d1c1f0 100644 --- a/rust/src/graphar_rs.cc +++ b/rust/src/graphar_rs.cc @@ -285,4 +285,39 @@ std::unique_ptr graph_info_dump( } return std::make_unique(std::move(dumped).value()); } + +// =========================== Builder =========================== +// `Vertex` +std::unique_ptr new_vertex_builder() { + return std::make_unique(); +} + +void add_vertex(graphar::builder::VerticesBuilder& builder, + graphar::builder::Vertex& v) { + auto status = builder.AddVertex(v); + if (!status.ok()) { + throw std::runtime_error(status.message()); + } +} + +std::unique_ptr +new_vertices_builder(const std::shared_ptr& vertex_info, + const std::string& path_prefix, i64 start_idx) { + if (vertex_info == nullptr) { + throw std::runtime_error("VerticesBuilder: vertex_info must not be null"); + } + if (start_idx < 0) { + throw std::runtime_error("VerticesBuilder: start_idx must be >= 0"); + } + + return std::make_unique( + vertex_info, path_prefix, static_cast(start_idx)); +} + +void vertices_dump(graphar::builder::VerticesBuilder &builder) { + auto status = builder.Dump(); + if (!status.ok()) { + throw std::runtime_error(status.message()); + } +} } // namespace graphar_rs diff --git a/rust/src/info/adjacent_list.rs b/rust/src/info/adjacent_list.rs index 8ef28e994..d1efe4eaf 100644 --- a/rust/src/info/adjacent_list.rs +++ b/rust/src/info/adjacent_list.rs @@ -38,6 +38,8 @@ impl AdjacentList { /// /// If `path_prefix` is `None`, GraphAr will use a default prefix derived /// from `ty` (e.g. `ordered_by_source/`). + /// + /// GraphAr conventions typically use a trailing slash (`/`) for prefixes. pub fn new>( ty: AdjListType, file_type: FileType, diff --git a/rust/src/info/edge_info.rs b/rust/src/info/edge_info.rs index af693c466..839a473ba 100644 --- a/rust/src/info/edge_info.rs +++ b/rust/src/info/edge_info.rs @@ -56,6 +56,8 @@ impl EdgeInfo { /// /// The `prefix` is a logical prefix string used by GraphAr (it is not a filesystem path). /// + /// GraphAr conventions typically use a trailing slash (`/`) for prefixes. + /// /// Panics if GraphAr rejects the inputs (including, but not limited to, /// empty type names, non-positive chunk sizes, or empty adjacency list /// vector). Prefer [`EdgeInfo::try_new`] if you want to handle errors. @@ -338,6 +340,7 @@ impl EdgeInfoBuilder { /// Set the logical prefix. /// /// This is a logical prefix string used by GraphAr (it is not a filesystem path). + /// GraphAr conventions typically use a trailing slash (`/`) for prefixes. pub fn prefix>(mut self, prefix: P) -> Self { self.prefix = prefix.as_ref().to_owned(); self diff --git a/rust/src/info/vertex_info.rs b/rust/src/info/vertex_info.rs index df098fb1d..f78185cb8 100644 --- a/rust/src/info/vertex_info.rs +++ b/rust/src/info/vertex_info.rs @@ -40,6 +40,8 @@ impl VertexInfo { /// The `prefix` is a logical prefix string used by GraphAr (it is not a /// filesystem path). /// + /// GraphAr conventions typically use a trailing slash (`/`) for prefixes. + /// /// Panics if GraphAr rejects the inputs (including, but not limited to, /// `type` being empty or `chunk_size <= 0`). Prefer [`VertexInfo::try_new`] /// if you want to handle errors. @@ -254,6 +256,7 @@ impl VertexInfoBuilder { /// Set the logical prefix. /// /// This is a logical prefix string used by GraphAr (it is not a filesystem path). + /// GraphAr conventions typically use a trailing slash (`/`) for prefixes. pub fn prefix>(mut self, prefix: P) -> Self { self.prefix = prefix.as_ref().to_owned(); self diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9a44a40a9..225ab43c8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -23,6 +23,8 @@ use std::path::Path; mod ffi; +/// GraphAr data builders for writing chunks. +pub mod builder; /// Error types for this crate. pub mod error; /// GraphAr metadata information types. diff --git a/rust/src/property.rs b/rust/src/property.rs index ef13fd524..a7bddea3e 100644 --- a/rust/src/property.rs +++ b/rust/src/property.rs @@ -245,6 +245,8 @@ impl PropertyGroup { /// /// The `prefix` is a logical prefix string used by GraphAr (it is not a /// filesystem path). + /// + /// GraphAr conventions typically use a trailing slash (`/`) for prefixes. pub fn new>(properties: PropertyVec, file_type: FileType, prefix: S) -> Self { let_cxx_string!(prefix = prefix.as_ref()); let inner = ffi::graphar::CreatePropertyGroup(properties.as_ref(), file_type, &prefix);