This is an automated email from the ASF dual-hosted git repository.
yangxk1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-graphar.git
The following commit(s) were added to refs/heads/main by this push:
new bc4407b9 feat(Rust): add `builder::Vertex` and
`builder::VerticesBuilder` support (#911)
bc4407b9 is described below
commit bc4407b9ce47e3eeca9cab518e029d0a31731f62
Author: Jinye Wu <[email protected]>
AuthorDate: Mon Jun 29 19:47:12 2026 +0800
feat(Rust): add `builder::Vertex` and `builder::VerticesBuilder` support
(#911)
* update
* add `builder::Vertex`
* update
* update
* remove Vertex id
* update
* update dependency
---
rust/Cargo.lock | 7 +
rust/Cargo.toml | 1 +
rust/build.rs | 18 +-
rust/include/graphar_rs.h | 77 +++-
rust/src/builder/mod.rs | 11 +
rust/src/{lib.rs => builder/property_value.rs} | 33 +-
rust/src/builder/vertex.rs | 519 +++++++++++++++++++++++++
rust/src/error.rs | 22 ++
rust/src/ffi.rs | 115 ++++++
rust/src/graphar_rs.cc | 35 ++
rust/src/info/adjacent_list.rs | 2 +
rust/src/info/edge_info.rs | 3 +
rust/src/info/vertex_info.rs | 3 +
rust/src/lib.rs | 2 +
rust/src/property.rs | 2 +
15 files changed, 821 insertions(+), 29 deletions(-)
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 052f39a9..4bb944f3 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 7a66444f..df758a10 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 40fce462..d4ecba2e 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 3d096e5a..527fae30 100644
--- a/rust/include/graphar_rs.h
+++ b/rust/include/graphar_rs.h
@@ -25,12 +25,15 @@
#include <string>
#include <vector>
-#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<std::string> graph_info_dump(
const graphar::GraphInfo& graph_info);
+
+// =========================== Builder ===========================
+// `Vertex`
+std::unique_ptr<graphar::builder::Vertex> 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<graphar::builder::VerticesBuilder>
+new_vertices_builder(const std::shared_ptr<graphar::VertexInfo>& 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 00000000..b35f32c1
--- /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/lib.rs b/rust/src/builder/property_value.rs
similarity index 55%
copy from rust/src/lib.rs
copy to rust/src/builder/property_value.rs
index 9a44a40a..35dcbdd4 100644
--- a/rust/src/lib.rs
+++ b/rust/src/builder/property_value.rs
@@ -15,26 +15,21 @@
// specific language governing permissions and limitations
// under the License.
-//! Rust bindings for GraphAr.
+//! Shared property value abstractions for builders.
-#![deny(missing_docs)]
+use crate::types::Cardinality;
-use std::path::Path;
-
-mod ffi;
-
-/// Error types for this crate.
-pub mod error;
-/// GraphAr metadata information types.
-pub mod info;
-/// GraphAr property.
-pub mod property;
-/// GraphAr logical data types.
-pub mod types;
-
-pub use error::{Error, Result};
+#[doc(hidden)]
+pub(crate) mod sealed {
+ pub trait Sealed<Target> {}
+}
-fn path_to_utf8_str(path: &Path) -> Result<&str> {
- path.to_str()
- .ok_or_else(|| Error::NonUtf8Path(path.to_owned()))
+/// A value that can be written into a GraphAr property.
+///
+/// This trait is sealed and cannot be implemented outside this crate.
+pub trait PropertyValue<Target>: sealed::Sealed<Target> {
+ /// 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 00000000..51f26fc1
--- /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<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(_))),
+ }
+ }
+}
diff --git a/rust/src/error.rs b/rust/src/error.rs
index fcc873ce..f727f41c 100644
--- a/rust/src/error.rs
+++ b/rust/src/error.rs
@@ -28,6 +28,13 @@ pub type Result<T> = std::result::Result<T, Error>;
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 15c20584..d388d2fc 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<SharedAdjacentList> {}
+
+ // =========================== Builder ===========================
+ // `builder::Vertex`
+ #[namespace = "graphar::builder"]
+ unsafe extern "C++" {
+ #[rust_name = "BuilderVertex"]
+ type Vertex;
+
+ #[namespace = "graphar_rs"]
+ fn new_vertex_builder() -> UniquePtr<BuilderVertex>;
+ #[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<VertexInfo>,
+ path_prefix: &CxxString,
+ start_idx: i64,
+ ) -> Result<UniquePtr<VerticesBuilder>>;
+ #[namespace = "graphar_rs"]
+ fn vertices_dump(builder: Pin<&mut VerticesBuilder>) -> Result<()>;
+ }
}
impl From<MaybeIndex> for Option<usize> {
diff --git a/rust/src/graphar_rs.cc b/rust/src/graphar_rs.cc
index 0d9f68a9..022d1c1f 100644
--- a/rust/src/graphar_rs.cc
+++ b/rust/src/graphar_rs.cc
@@ -285,4 +285,39 @@ std::unique_ptr<std::string> graph_info_dump(
}
return std::make_unique<std::string>(std::move(dumped).value());
}
+
+// =========================== Builder ===========================
+// `Vertex`
+std::unique_ptr<graphar::builder::Vertex> new_vertex_builder() {
+ return std::make_unique<graphar::builder::Vertex>();
+}
+
+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<graphar::builder::VerticesBuilder>
+new_vertices_builder(const std::shared_ptr<graphar::VertexInfo>& 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<graphar::builder::VerticesBuilder>(
+ vertex_info, path_prefix, static_cast<graphar::IdType>(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 8ef28e99..d1efe4ea 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<P: AsRef<str>>(
ty: AdjListType,
file_type: FileType,
diff --git a/rust/src/info/edge_info.rs b/rust/src/info/edge_info.rs
index af693c46..839a473b 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<P: AsRef<str>>(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 df098fb1..f78185cb 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<P: AsRef<str>>(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 9a44a40a..225ab43c 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 ef13fd52..a7bddea3 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<S: AsRef<str>>(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);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]