This is an automated email from the ASF dual-hosted git repository.
xiaokang 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 878aa5c9 feat(Rust): add `VertexInfo` support (#835)
878aa5c9 is described below
commit 878aa5c9cbb244843b69f4820b7c648f89aa0a49
Author: Jinye Wu <[email protected]>
AuthorDate: Tue Jan 27 11:21:32 2026 +0800
feat(Rust): add `VertexInfo` support (#835)
* update
* update
* update
* update
---
rust/Cargo.lock | 106 +++++++++
rust/Cargo.toml | 3 +
rust/include/graphar_rs.h | 22 ++
rust/src/ffi.rs | 57 +++++
rust/src/graphar_rs.cc | 62 +++++
rust/src/{lib.rs => info/mod.rs} | 19 +-
rust/src/info/version.rs | 53 +++++
rust/src/info/vertex_info.rs | 496 +++++++++++++++++++++++++++++++++++++++
rust/src/lib.rs | 2 +
rust/src/property.rs | 93 +++++++-
10 files changed, 896 insertions(+), 17 deletions(-)
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 4317f99b..9faaed42 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -8,6 +8,12 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
[[package]]
name = "cc"
version = "1.2.49"
@@ -18,6 +24,12 @@ dependencies = [
"shlex",
]
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
[[package]]
name = "clap"
version = "4.5.53"
@@ -132,6 +144,22 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
@@ -144,6 +172,18 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
[[package]]
name = "graphar-rs"
version = "0.1.0"
@@ -151,6 +191,7 @@ dependencies = [
"cmake",
"cxx",
"cxx-build",
+ "tempfile",
]
[[package]]
@@ -169,6 +210,12 @@ dependencies = [
"hashbrown",
]
+[[package]]
+name = "libc"
+version = "0.2.180"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+
[[package]]
name = "link-cplusplus"
version = "1.0.12"
@@ -178,6 +225,18 @@ dependencies = [
"cc",
]
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
[[package]]
name = "proc-macro2"
version = "1.0.103"
@@ -196,6 +255,25 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rustix"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
[[package]]
name = "scratch"
version = "1.0.9"
@@ -255,6 +333,19 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "tempfile"
+version = "3.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
[[package]]
name = "termcolor"
version = "1.4.1"
@@ -276,6 +367,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -299,3 +399,9 @@ checksum =
"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 71b9f07b..ba5c9108 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -28,3 +28,6 @@ cxx = "1.0.190"
[build-dependencies]
cxx-build = "1.0.190"
cmake = "0.1.54"
+
+[dev-dependencies]
+tempfile = "3"
diff --git a/rust/include/graphar_rs.h b/rust/include/graphar_rs.h
index f04ae645..d7f313fb 100644
--- a/rust/include/graphar_rs.h
+++ b/rust/include/graphar_rs.h
@@ -19,6 +19,7 @@
#pragma once
+#include <cstdint>
#include <memory>
#include <string>
#include <vector>
@@ -26,15 +27,19 @@
#include "graphar/fwd.h"
#include "graphar/graph_info.h"
#include "graphar/types.h"
+#include "graphar/version_parser.h"
#include "rust/cxx.h"
namespace graphar {
using SharedPropertyGroup = std::shared_ptr<PropertyGroup>;
+using ConstInfoVersion = const InfoVersion;
}
namespace graphar_rs {
rust::String to_type_name(const graphar::DataType &type);
+std::shared_ptr<graphar::ConstInfoVersion> new_const_info_version(int32_t
version);
+
std::unique_ptr<graphar::Property>
new_property(const std::string &name, std::shared_ptr<graphar::DataType> type,
bool is_primary, bool is_nullable,
@@ -56,7 +61,24 @@ void
property_vec_emplace_property(std::vector<graphar::Property> &properties,
bool is_primary, bool is_nullable,
graphar::Cardinality cardinality);
+std::unique_ptr<std::vector<graphar::Property>>
+property_vec_clone(const std::vector<graphar::Property> &properties);
+
void property_group_vec_push_property_group(
std::vector<graphar::SharedPropertyGroup> &property_groups,
std::shared_ptr<graphar::PropertyGroup> property_group);
+
+std::unique_ptr<std::vector<graphar::SharedPropertyGroup>>
+property_group_vec_clone(
+ const std::vector<graphar::SharedPropertyGroup> &property_groups);
+
+std::shared_ptr<graphar::VertexInfo>
+create_vertex_info(const std::string &type, graphar::IdType chunk_size,
+ const std::vector<graphar::SharedPropertyGroup>
&property_groups,
+ const rust::Vec<rust::String> &labels,
+ const std::string &prefix,
+ std::shared_ptr<graphar::ConstInfoVersion> version);
+
+void vertex_info_save(const graphar::VertexInfo &vertex_info, const
std::string &path);
+std::unique_ptr<std::string> vertex_info_dump(const graphar::VertexInfo
&vertex_info);
} // namespace graphar_rs
diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs
index a649f3d5..d6b1e0a1 100644
--- a/rust/src/ffi.rs
+++ b/rust/src/ffi.rs
@@ -19,6 +19,7 @@ use cxx::{ExternType, SharedPtr};
/// A shared pointer wrapper for `graphar::PropertyGroup`.
#[repr(transparent)]
+#[derive(Clone)]
pub struct SharedPropertyGroup(pub(crate) SharedPtr<graphar::PropertyGroup>);
unsafe impl ExternType for SharedPropertyGroup {
@@ -173,6 +174,9 @@ pub(crate) mod graphar {
is_nullable: bool,
cardinality: Cardinality,
);
+
+ #[namespace = "graphar_rs"]
+ fn property_vec_clone(properties: &CxxVector<Property>) ->
UniquePtr<CxxVector<Property>>;
}
// `PropertyGroup`
@@ -193,6 +197,59 @@ pub(crate) mod graphar {
property_groups: Pin<&mut CxxVector<SharedPropertyGroup>>,
property_group: SharedPtr<PropertyGroup>,
);
+
+ #[namespace = "graphar_rs"]
+ fn property_group_vec_clone(
+ property_groups: &CxxVector<SharedPropertyGroup>,
+ ) -> UniquePtr<CxxVector<SharedPropertyGroup>>;
+ }
+
+ // `InfoVersion`
+ //
+ // TODO: upstream C++ APIs still use `int` in a few places for versioning;
+ // prefer fixed-width integer types in the public C++ interface.
+ unsafe extern "C++" {
+ type InfoVersion;
+ type ConstInfoVersion;
+
+ #[namespace = "graphar_rs"]
+ fn new_const_info_version(version: i32) ->
Result<SharedPtr<ConstInfoVersion>>;
+ }
+
+ // `VertexInfo`
+ unsafe extern "C++" {
+ type VertexInfo;
+
+ fn GetType(&self) -> &CxxString;
+ fn GetChunkSize(&self) -> i64;
+ fn GetPrefix(&self) -> &CxxString;
+ fn version(&self) -> &SharedPtr<ConstInfoVersion>;
+ fn GetLabels(&self) -> &CxxVector<CxxString>;
+
+ // TODO: upstream C++ uses `int` for this return type; prefer
fixed-width.
+ fn PropertyGroupNum(&self) -> i32;
+
+ fn GetPropertyGroups(&self) -> &CxxVector<SharedPropertyGroup>;
+ fn GetPropertyGroup(&self, property_name: &CxxString) ->
SharedPtr<PropertyGroup>;
+
+ // TODO: upstream C++ uses `int` for this parameter; prefer
fixed-width.
+ fn GetPropertyGroupByIndex(&self, index: i32) ->
SharedPtr<PropertyGroup>;
+
+ #[namespace = "graphar_rs"]
+ fn create_vertex_info(
+ type_: &CxxString,
+ chunk_size: i64,
+ property_groups: &CxxVector<SharedPropertyGroup>,
+ labels: &Vec<String>,
+ prefix: &CxxString,
+ version: SharedPtr<ConstInfoVersion>,
+ ) -> Result<SharedPtr<VertexInfo>>;
+
+ #[namespace = "graphar_rs"]
+ fn vertex_info_save(vertex_info: &VertexInfo, path: &CxxString) ->
Result<()>;
+
+ #[namespace = "graphar_rs"]
+ fn vertex_info_dump(vertex_info: &VertexInfo) ->
Result<UniquePtr<CxxString>>;
}
unsafe extern "C++" {
diff --git a/rust/src/graphar_rs.cc b/rust/src/graphar_rs.cc
index 15c138cc..d806ef99 100644
--- a/rust/src/graphar_rs.cc
+++ b/rust/src/graphar_rs.cc
@@ -19,6 +19,7 @@
#include "graphar_rs.h"
+#include <stdexcept>
#include <utility>
namespace graphar_rs {
@@ -26,6 +27,12 @@ rust::String to_type_name(const graphar::DataType &type) {
return rust::String(type.ToTypeName());
}
+std::shared_ptr<graphar::ConstInfoVersion>
+new_const_info_version(int32_t version) {
+ // Let any upstream exceptions propagate to Rust via `cxx::Exception`.
+ return std::make_shared<graphar::InfoVersion>(static_cast<int>(version));
+}
+
std::unique_ptr<graphar::Property>
new_property(const std::string &name, std::shared_ptr<graphar::DataType> type,
bool is_primary, bool is_nullable,
@@ -67,9 +74,64 @@ void
property_vec_emplace_property(std::vector<graphar::Property> &properties,
properties.emplace_back(name, type, is_primary, is_nullable, cardinality);
}
+std::unique_ptr<std::vector<graphar::Property>>
+property_vec_clone(const std::vector<graphar::Property> &properties) {
+ return std::make_unique<std::vector<graphar::Property>>(properties);
+}
+
void property_group_vec_push_property_group(
std::vector<graphar::SharedPropertyGroup> &property_groups,
std::shared_ptr<graphar::PropertyGroup> property_group) {
property_groups.emplace_back(std::move(property_group));
}
+
+std::unique_ptr<std::vector<graphar::SharedPropertyGroup>>
+property_group_vec_clone(
+ const std::vector<graphar::SharedPropertyGroup> &property_groups) {
+ return std::make_unique<std::vector<graphar::SharedPropertyGroup>>(
+ property_groups);
+}
+
+std::shared_ptr<graphar::VertexInfo> create_vertex_info(
+ const std::string &type, graphar::IdType chunk_size,
+ const std::vector<graphar::SharedPropertyGroup> &property_groups,
+ const rust::Vec<rust::String> &labels, const std::string &prefix,
+ std::shared_ptr<graphar::ConstInfoVersion> version) {
+ if (type.empty()) {
+ throw std::runtime_error("CreateVertexInfo: type must not be empty");
+ }
+ if (chunk_size <= 0) {
+ throw std::runtime_error("CreateVertexInfo: chunk_size must be > 0");
+ }
+
+ std::vector<std::string> label_vec;
+ label_vec.reserve(labels.size());
+ for (size_t i = 0; i < labels.size(); ++i) {
+ label_vec.emplace_back(std::string(labels[i]));
+ }
+
+ auto vertex_info = graphar::CreateVertexInfo(type, chunk_size,
property_groups,
+ label_vec, prefix,
std::move(version));
+ if (vertex_info == nullptr) {
+ throw std::runtime_error("CreateVertexInfo: returned nullptr");
+ }
+ return vertex_info;
+}
+
+void vertex_info_save(const graphar::VertexInfo &vertex_info,
+ const std::string &path) {
+ auto status = vertex_info.Save(path);
+ if (!status.ok()) {
+ throw std::runtime_error(status.message());
+ }
+}
+
+std::unique_ptr<std::string>
+vertex_info_dump(const graphar::VertexInfo &vertex_info) {
+ auto dumped = vertex_info.Dump();
+ if (!dumped) {
+ throw std::runtime_error(dumped.error().message());
+ }
+ return std::make_unique<std::string>(std::move(dumped).value());
+}
} // namespace graphar_rs
diff --git a/rust/src/lib.rs b/rust/src/info/mod.rs
similarity index 73%
copy from rust/src/lib.rs
copy to rust/src/info/mod.rs
index 6f6bcc73..e46a62a7 100644
--- a/rust/src/lib.rs
+++ b/rust/src/info/mod.rs
@@ -15,19 +15,10 @@
// specific language governing permissions and limitations
// under the License.
-//! Rust bindings for GraphAr.
+//! Graph metadata bindings.
-#![deny(missing_docs)]
+mod version;
+mod vertex_info;
-use cxx::CxxString;
-
-mod ffi;
-
-/// GraphAr property.
-pub mod property;
-/// GraphAr logical data types.
-pub mod types;
-
-fn cxx_string_to_string(value: &CxxString) -> String {
- String::from_utf8_lossy(value.as_bytes()).into_owned()
-}
+pub use version::InfoVersion;
+pub use vertex_info::{VertexInfo, VertexInfoBuilder};
diff --git a/rust/src/info/version.rs b/rust/src/info/version.rs
new file mode 100644
index 00000000..b10b30f5
--- /dev/null
+++ b/rust/src/info/version.rs
@@ -0,0 +1,53 @@
+// 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 info version bindings.
+
+use crate::ffi;
+use cxx::SharedPtr;
+
+/// A GraphAr `InfoVersion` value.
+///
+/// This is a thin wrapper around `std::shared_ptr<const
graphar::InfoVersion>`.
+#[derive(Clone)]
+pub struct InfoVersion(pub(crate) SharedPtr<ffi::graphar::ConstInfoVersion>);
+
+impl InfoVersion {
+ /// Create a new `InfoVersion` by version number.
+ ///
+ /// TODO: upstream C++ constructor takes `int`; prefer fixed-width integer
types.
+ pub fn new(version: i32) -> Result<Self, cxx::Exception> {
+ Ok(Self(ffi::graphar::new_const_info_version(version)?))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_info_version_new_ok_and_err() {
+ let v1 = InfoVersion::new(1).unwrap();
+ let _v1_clone = v1.clone();
+
+ // Use an obviously invalid version to keep this test future-proof.
+ // Upstream may add support for version 2.
+ let err = InfoVersion::new(-1).err().unwrap();
+ let msg = err.to_string();
+ assert!(!msg.is_empty(), "unexpected empty error message");
+ }
+}
diff --git a/rust/src/info/vertex_info.rs b/rust/src/info/vertex_info.rs
new file mode 100644
index 00000000..11d9a714
--- /dev/null
+++ b/rust/src/info/vertex_info.rs
@@ -0,0 +1,496 @@
+// 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.
+
+//! Vertex metadata bindings.
+
+use super::version::InfoVersion;
+use crate::{cxx_string_to_string, ffi, property::PropertyGroup,
property::PropertyGroupVector};
+use cxx::{CxxVector, SharedPtr, UniquePtr, let_cxx_string};
+use std::borrow::Cow;
+use std::path::Path;
+
+/// GraphAr vertex metadata (`graphar::VertexInfo`).
+#[derive(Clone)]
+pub struct VertexInfo(pub(crate) SharedPtr<ffi::graphar::VertexInfo>);
+
+impl VertexInfo {
+ /// Create a builder for [`VertexInfo`].
+ ///
+ /// This is the preferred API when constructing `VertexInfo` in Rust, since
+ /// the raw constructor has many parameters.
+ pub fn builder<S: Into<String>>(
+ r#type: S,
+ chunk_size: i64,
+ property_groups: PropertyGroupVector,
+ ) -> VertexInfoBuilder {
+ VertexInfoBuilder::new(r#type, chunk_size, property_groups)
+ }
+
+ /// Create a new `VertexInfo`.
+ ///
+ /// The `prefix` is a logical prefix string used by GraphAr (it is not a
+ /// filesystem path).
+ ///
+ /// 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.
+ pub fn new<S: AsRef<[u8]>, P: AsRef<[u8]>>(
+ r#type: S,
+ chunk_size: i64,
+ property_groups: PropertyGroupVector,
+ labels: Vec<String>,
+ prefix: P,
+ version: Option<InfoVersion>,
+ ) -> Self {
+ Self::try_new(r#type, chunk_size, property_groups, labels, prefix,
version).unwrap()
+ }
+
+ /// Try to create a new `VertexInfo`.
+ ///
+ /// This returns an error if `type` is empty, `chunk_size <= 0`, or if the
+ /// upstream GraphAr implementation rejects the inputs.
+ pub fn try_new<S: AsRef<[u8]>, P: AsRef<[u8]>>(
+ r#type: S,
+ chunk_size: i64,
+ property_groups: PropertyGroupVector,
+ labels: Vec<String>,
+ prefix: P,
+ version: Option<InfoVersion>,
+ ) -> Result<Self, cxx::Exception> {
+ let_cxx_string!(ty = r#type.as_ref());
+ let_cxx_string!(prefix = prefix);
+
+ let groups_ref = property_groups
+ .as_ref()
+ .expect("property group vec should be valid");
+ let version = version.map(|v| v.0).unwrap_or_else(SharedPtr::null);
+
+ Ok(Self(ffi::graphar::create_vertex_info(
+ &ty, chunk_size, groups_ref, &labels, &prefix, version,
+ )?))
+ }
+
+ /// Return the vertex type name.
+ pub fn type_name(&self) -> String {
+ cxx_string_to_string(self.0.GetType())
+ }
+
+ /// Return the chunk size.
+ pub fn chunk_size(&self) -> i64 {
+ self.0.GetChunkSize()
+ }
+
+ /// Return the logical prefix.
+ pub fn prefix(&self) -> String {
+ cxx_string_to_string(self.0.GetPrefix())
+ }
+
+ /// Return the optional format version.
+ pub fn version(&self) -> Option<InfoVersion> {
+ let sp = self.0.version();
+ if sp.is_null() {
+ None
+ } else {
+ Some(InfoVersion(sp.clone()))
+ }
+ }
+
+ /// Return the labels of this vertex type.
+ pub fn labels(&self) -> Vec<String> {
+ let labels = self.0.GetLabels();
+ let mut out = Vec::with_capacity(labels.len());
+ for label in labels {
+ out.push(cxx_string_to_string(label));
+ }
+ out
+ }
+
+ /// Return the underlying label vector.
+ ///
+ /// This is an advanced API that exposes `cxx` types and ties the returned
+ /// reference to the lifetime of `self`.
+ pub fn labels_cxx(&self) -> &CxxVector<cxx::CxxString> {
+ self.0.GetLabels()
+ }
+
+ /// Return the number of property groups.
+ ///
+ /// TODO: upstream C++ uses `int` for this return type; prefer fixed-width.
+ pub fn property_group_num(&self) -> i32 {
+ self.0.PropertyGroupNum()
+ }
+
+ /// Return property groups.
+ ///
+ /// This is an advanced API that exposes `cxx` types and ties the returned
+ /// reference to the lifetime of `self`.
+ pub fn property_groups_cxx(&self) -> &CxxVector<PropertyGroup> {
+ self.0.GetPropertyGroups()
+ }
+
+ /// Return property groups.
+ ///
+ /// This allocates a new `Vec`. Prefer [`VertexInfo::property_groups_iter`]
+ /// if you only need to iterate.
+ pub fn property_groups(&self) -> Vec<PropertyGroup> {
+ self.property_groups_iter().collect()
+ }
+
+ /// Iterate over property groups without allocating a `Vec`.
+ pub fn property_groups_iter(&self) -> impl Iterator<Item = PropertyGroup>
+ '_ {
+ self.0.GetPropertyGroups().iter().cloned()
+ }
+
+ /// Return the property group containing the given property.
+ ///
+ /// Returns `None` if the property is not found.
+ pub fn property_group<S: AsRef<[u8]>>(&self, property_name: S) ->
Option<PropertyGroup> {
+ let_cxx_string!(name = property_name);
+
+ let sp = self.0.GetPropertyGroup(&name);
+ if sp.is_null() {
+ None
+ } else {
+ Some(PropertyGroup::from_inner(sp))
+ }
+ }
+
+ /// Return the property group at the given index.
+ ///
+ /// This returns an owned [`PropertyGroup`] (backed by a C++ `shared_ptr`)
+ /// without allocating a `Vec`.
+ ///
+ /// If you only need a borrowed reference and want bounds checking, prefer
+ /// [`VertexInfo::property_groups_cxx`] and `cxx::CxxVector::get`, or
+ /// [`VertexInfo::property_groups_iter`] with `nth`.
+ /// TODO: upstream C++ uses `int` for this parameter; prefer fixed-width.
+ ///
+ /// Returns `None` if the index is out of range.
+ pub fn property_group_by_index(&self, index: i32) -> Option<PropertyGroup>
{
+ let sp = self.0.GetPropertyGroupByIndex(index);
+ if sp.is_null() {
+ None
+ } else {
+ Some(PropertyGroup::from_inner(sp))
+ }
+ }
+
+ /// Save this `VertexInfo` to the given path.
+ ///
+ /// On Unix, this passes the raw `OsStr` bytes to C++ to avoid lossy UTF-8
+ /// conversion. On non-Unix platforms, this falls back to converting the
+ /// path to UTF-8 using [`Path::to_string_lossy`].
+ pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), cxx::Exception> {
+ let path = path.as_ref();
+
+ #[cfg(unix)]
+ let path_bytes: Cow<[u8]> = {
+ use std::os::unix::ffi::OsStrExt;
+ Cow::Borrowed(path.as_os_str().as_bytes())
+ };
+
+ #[cfg(not(unix))]
+ let path_bytes: Cow<[u8]> =
Cow::Owned(path.to_string_lossy().into_owned().into_bytes());
+
+ let_cxx_string!(p = path_bytes.as_ref());
+ ffi::graphar::vertex_info_save(&self.0, &p)?;
+ Ok(())
+ }
+
+ /// Dump this `VertexInfo` as YAML string.
+ pub fn dump(&self) -> Result<String, cxx::Exception> {
+ let dumped: UniquePtr<cxx::CxxString> =
ffi::graphar::vertex_info_dump(&self.0)?;
+ Ok(dumped.to_string())
+ }
+}
+
+/// A builder for constructing a [`VertexInfo`].
+///
+/// Defaults:
+/// - `labels = []`
+/// - `prefix = ""` (GraphAr may set a default prefix based on type)
+/// - `version = None`
+pub struct VertexInfoBuilder {
+ r#type: String,
+ chunk_size: i64,
+ property_groups: PropertyGroupVector,
+ labels: Vec<String>,
+ prefix: Vec<u8>,
+ version: Option<InfoVersion>,
+}
+
+impl VertexInfoBuilder {
+ /// Create a new builder with required fields.
+ pub fn new<S: Into<String>>(
+ r#type: S,
+ chunk_size: i64,
+ property_groups: PropertyGroupVector,
+ ) -> Self {
+ Self {
+ r#type: r#type.into(),
+ chunk_size,
+ property_groups,
+ labels: Vec::new(),
+ prefix: Vec::new(),
+ version: None,
+ }
+ }
+
+ /// Set vertex labels.
+ pub fn labels(mut self, labels: Vec<String>) -> Self {
+ self.labels = labels;
+ self
+ }
+
+ /// Set vertex labels from a string iterator.
+ pub fn labels_from_iter<I, S>(mut self, labels: I) -> Self
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<str>,
+ {
+ self.labels = labels.into_iter().map(|s|
s.as_ref().to_string()).collect();
+ self
+ }
+
+ /// Push a single label.
+ pub fn push_label<S: Into<String>>(mut self, label: S) -> Self {
+ self.labels.push(label.into());
+ self
+ }
+
+ /// Set the logical prefix.
+ pub fn prefix<P: AsRef<[u8]>>(mut self, prefix: P) -> Self {
+ self.prefix = prefix.as_ref().to_vec();
+ self
+ }
+
+ /// Set the info format version.
+ pub fn version(mut self, version: InfoVersion) -> Self {
+ self.version = Some(version);
+ self
+ }
+
+ /// Set the optional info format version.
+ pub fn version_opt(mut self, version: Option<InfoVersion>) -> Self {
+ self.version = version;
+ self
+ }
+
+ /// Build a [`VertexInfo`].
+ ///
+ /// Panics if GraphAr rejects the builder inputs. Prefer
[`VertexInfoBuilder::try_build`]
+ /// if you want to handle errors.
+ pub fn build(self) -> VertexInfo {
+ self.try_build().unwrap()
+ }
+
+ /// Try to build a [`VertexInfo`].
+ pub fn try_build(self) -> Result<VertexInfo, cxx::Exception> {
+ let Self {
+ r#type,
+ chunk_size,
+ property_groups,
+ labels,
+ prefix,
+ version,
+ } = self;
+
+ VertexInfo::try_new(r#type, chunk_size, property_groups, labels,
prefix, version)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::property::{PropertyBuilder, PropertyGroup, PropertyVec};
+ use crate::types::{DataType, FileType};
+ use tempfile::tempdir;
+
+ fn cxx_string_to_string_for_test(s: &cxx::CxxString) -> String {
+ String::from_utf8_lossy(s.as_bytes()).into_owned()
+ }
+
+ fn make_property_groups() -> PropertyGroupVector {
+ let mut props = PropertyVec::new();
+ props.emplace(PropertyBuilder::new("id",
DataType::int64()).primary_key(true));
+ props.emplace(PropertyBuilder::new("name", DataType::string()));
+
+ let pg = PropertyGroup::new(props, FileType::Parquet, "id_name/");
+ let mut groups = PropertyGroupVector::new();
+ groups.push(pg);
+ groups
+ }
+
+ #[test]
+ fn test_vertex_info_try_new_error_paths() {
+ let groups = PropertyGroupVector::new();
+
+ // type cannot be empty
+ let msg = VertexInfo::try_new("", 1, groups.clone(), vec![], "", None)
+ .err()
+ .unwrap()
+ .to_string();
+ assert!(
+ msg.contains("CreateVertexInfo") && msg.contains("type must not be
empty"),
+ "unexpected error message: {msg:?}"
+ );
+
+ // `chunk_size` cannot be less than 1
+ let msg = VertexInfo::try_new("person", 0, groups.clone(), vec![], "",
None)
+ .err()
+ .unwrap()
+ .to_string();
+ assert!(
+ msg.contains("CreateVertexInfo") && msg.contains("chunk_size must
be > 0"),
+ "unexpected error message: {msg:?}"
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_vertex_info_new_panics_on_invalid_args() {
+ let groups = PropertyGroupVector::new();
+ // type cannot be empty
+ let _ = VertexInfo::new("", 1, groups, vec![], "", None);
+ }
+
+ #[test]
+ fn test_vertex_info_builder() {
+ let version = InfoVersion::new(1).unwrap();
+ let groups = make_property_groups();
+
+ // Create `VertexInfo` using builder API.
+ let vertex_info = VertexInfo::builder("person", 1024, groups)
+ .labels(vec!["l0".to_string()])
+ .labels_from_iter(["l1", "l2"])
+ .push_label("l3")
+ .prefix("person/")
+ .version(version)
+ .build();
+
+ assert_eq!(vertex_info.type_name(), "person");
+ assert_eq!(vertex_info.chunk_size(), 1024);
+ assert_eq!(vertex_info.prefix(), "person/");
+
+ assert!(vertex_info.version().is_some());
+
+ let labels = vertex_info.labels();
+ assert_eq!(
+ labels,
+ vec!["l1".to_string(), "l2".to_string(), "l3".to_string()]
+ );
+
+ let labels_cxx = vertex_info.labels_cxx();
+ assert_eq!(labels_cxx.len(), 3);
+ assert_eq!(
+ cxx_string_to_string_for_test(labels_cxx.get(0).unwrap()),
+ "l1"
+ );
+
+ assert_eq!(vertex_info.property_group_num(), 1);
+
+ let groups_cxx = vertex_info.property_groups_cxx();
+ assert_eq!(groups_cxx.len(), 1);
+ assert!(groups_cxx.get(0).unwrap().has_property("id"));
+
+ let groups_vec = vertex_info.property_groups();
+ assert_eq!(groups_vec.len(), 1);
+
+ let groups_iter: Vec<_> = vertex_info.property_groups_iter().collect();
+ assert_eq!(groups_iter.len(), 1);
+ }
+
+ #[test]
+ fn test_vertex_info_property_group_lookups() {
+ let groups = make_property_groups();
+
+ let vertex_info = VertexInfo::builder("person", 1024, groups)
+ .prefix("person/")
+ .version_opt(None)
+ .build();
+
+ assert!(vertex_info.version().is_none());
+
+ assert!(vertex_info.property_group("id").is_some());
+ assert!(vertex_info.property_group("missing").is_none());
+
+ let by_index = vertex_info.property_group_by_index(0).unwrap();
+ assert!(by_index.has_property("id"));
+
+ assert!(vertex_info.property_group_by_index(1).is_none());
+ assert!(vertex_info.property_group_by_index(-1).is_none());
+ }
+
+ #[test]
+ fn test_vertex_info_dump_and_save() {
+ let groups = make_property_groups();
+
+ let vertex_info = VertexInfo::builder("person", 1024, groups)
+ .labels_from_iter(["l1"])
+ .prefix("person/")
+ .build();
+
+ let dumped = vertex_info.dump().unwrap();
+ assert!(!dumped.trim().is_empty(), "dumped={dumped:?}");
+ assert!(dumped.contains("person"), "dumped={dumped:?}");
+ assert!(dumped.contains("person/"), "dumped={dumped:?}");
+ assert!(dumped.contains("l1"), "dumped={dumped:?}");
+ assert!(dumped.contains("1024"), "dumped={dumped:?}");
+
+ let dir = tempdir().unwrap();
+ let path = dir.path().join("vertex_info.yaml");
+ vertex_info.save(&path).unwrap();
+
+ let metadata = std::fs::metadata(&path).unwrap();
+ assert!(metadata.is_file());
+ assert!(metadata.len() > 0);
+
+ let saved = std::fs::read_to_string(&path).unwrap();
+ assert!(!saved.trim().is_empty(), "saved={saved:?}");
+ assert!(saved.contains("person"), "saved={saved:?}");
+ assert!(saved.contains("person/"), "saved={saved:?}");
+ assert!(saved.contains("l1"), "saved={saved:?}");
+ assert!(saved.contains("1024"), "saved={saved:?}");
+ }
+
+ #[cfg(unix)]
+ #[test]
+ fn test_vertex_info_save_non_utf8_path() {
+ use std::os::unix::ffi::OsStringExt;
+
+ let groups = make_property_groups();
+ let vertex_info = VertexInfo::builder("person", 1024, groups)
+ .labels_from_iter(["l1"])
+ .prefix("person/")
+ .build();
+
+ let dir = tempdir().unwrap();
+
+ let mut path = dir.path().to_path_buf();
+ path.push(std::ffi::OsString::from_vec(
+ b"vertex_info_\xFF_non_utf8.yaml".to_vec(),
+ ));
+
+ std::fs::File::create(&path).unwrap();
+ std::fs::remove_file(&path).unwrap();
+
+ vertex_info.save(&path).unwrap();
+ let metadata = std::fs::metadata(&path).unwrap();
+ assert!(metadata.is_file());
+ assert!(metadata.len() > 0);
+ }
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 6f6bcc73..2c2d21d3 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -23,6 +23,8 @@ use cxx::CxxString;
mod ffi;
+/// GraphAr metadata.
+pub mod info;
/// GraphAr property.
pub mod property;
/// GraphAr logical data types.
diff --git a/rust/src/property.rs b/rust/src/property.rs
index eae7cc9f..c33f8d86 100644
--- a/rust/src/property.rs
+++ b/rust/src/property.rs
@@ -162,6 +162,13 @@ impl Default for PropertyVec {
}
}
+impl Clone for PropertyVec {
+ fn clone(&self) -> Self {
+ let src = self.0.as_ref().expect("properties vec should be valid");
+ Self(ffi::graphar::property_vec_clone(src))
+ }
+}
+
impl Deref for PropertyVec {
type Target = UniquePtr<CxxVector<ffi::graphar::Property>>;
@@ -213,9 +220,13 @@ impl PropertyVec {
}
/// A group of properties stored in the same file(s).
-pub struct PropertyGroup(SharedPtr<ffi::graphar::PropertyGroup>);
+pub type PropertyGroup = ffi::SharedPropertyGroup;
impl PropertyGroup {
+ pub(crate) fn from_inner(inner: SharedPtr<ffi::graphar::PropertyGroup>) ->
Self {
+ Self(inner)
+ }
+
/// Create a new property group.
///
/// The `prefix` is a logical prefix string used by GraphAr (it is not a
@@ -250,7 +261,7 @@ impl PropertyGroup {
}
/// A vector of property groups.
-pub struct PropertyGroupVector(UniquePtr<CxxVector<ffi::SharedPropertyGroup>>);
+pub struct PropertyGroupVector(UniquePtr<CxxVector<PropertyGroup>>);
impl Default for PropertyGroupVector {
fn default() -> Self {
@@ -258,8 +269,15 @@ impl Default for PropertyGroupVector {
}
}
+impl Clone for PropertyGroupVector {
+ fn clone(&self) -> Self {
+ let src = self.0.as_ref().expect("property group vec should be valid");
+ Self(ffi::graphar::property_group_vec_clone(src))
+ }
+}
+
impl Deref for PropertyGroupVector {
- type Target = UniquePtr<CxxVector<ffi::SharedPropertyGroup>>;
+ type Target = UniquePtr<CxxVector<PropertyGroup>>;
fn deref(&self) -> &Self::Target {
&self.0
@@ -429,4 +447,73 @@ mod tests {
vec.push(pg2);
assert_eq!(vec.deref().len(), 2);
}
+
+ fn make_property_vec_for_clone() -> PropertyVec {
+ let mut props = PropertyVec::new();
+ props.emplace(PropertyBuilder::new("id",
DataType::int64()).primary_key(true));
+ props.push(Property::new(
+ "name",
+ DataType::string(),
+ false,
+ true,
+ Cardinality::Single,
+ ));
+ props
+ }
+
+ #[test]
+ fn test_property_vec_clone_independent_container() {
+ let mut original = make_property_vec_for_clone();
+ let cloned = original.clone();
+
+ assert_eq!(original.len(), 2);
+ assert_eq!(cloned.len(), 2);
+
+ let id_prop = Property::new("id2", DataType::int64(), true, true,
Cardinality::Single);
+ original.push(id_prop);
+ assert_eq!(original.len(), 3);
+
+ // Mutating the original container should not affect the cloned one.
+ assert_eq!(cloned.len(), 2);
+
+ let pg = PropertyGroup::new(cloned, FileType::Parquet, "clone_check/");
+ let mut names: Vec<_> = pg.properties().into_iter().map(|p|
p.name()).collect();
+ names.sort();
+ assert_eq!(names, vec!["id".to_string(), "name".to_string()]);
+ }
+
+ #[test]
+ fn test_property_group_vector_clone_independent_container() {
+ let mut props1 = PropertyVec::new();
+ props1.emplace(PropertyBuilder::new("id1",
DataType::int64()).primary_key(true));
+ let pg1 = PropertyGroup::new(props1, FileType::Parquet, "pg1/");
+
+ let mut props2 = PropertyVec::new();
+ props2.emplace(PropertyBuilder::new("id2",
DataType::int64()).primary_key(true));
+ let pg2 = PropertyGroup::new(props2, FileType::Parquet, "pg2/");
+
+ let mut groups = PropertyGroupVector::new();
+ groups.push(pg1);
+ groups.push(pg2);
+
+ let cloned = groups.clone();
+ assert_eq!(groups.len(), 2);
+ assert_eq!(cloned.len(), 2);
+
+ assert!(cloned.get(0).unwrap().has_property("id1"));
+ assert!(cloned.get(1).unwrap().has_property("id2"));
+
+ let mut props3 = PropertyVec::new();
+ props3.emplace(PropertyBuilder::new("id3",
DataType::int64()).primary_key(true));
+ let pg3 = PropertyGroup::new(props3, FileType::Parquet, "pg3/");
+ groups.push(pg3);
+
+ assert_eq!(groups.len(), 3);
+ assert_eq!(cloned.len(), 2);
+
+ let cloned_props = cloned.get(0).unwrap().properties();
+ assert_eq!(cloned_props.len(), 1);
+ assert_eq!(cloned_props[0].name(), "id1");
+ assert_eq!(cloned_props[0].data_type().id(), Type::Int64);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]