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 5bb4003b feat(Rust): add `GraphInfo` support (#883)
5bb4003b is described below
commit 5bb4003b9a90f07423da99f0bc4e9aabbb480ca9
Author: Jinye Wu <[email protected]>
AuthorDate: Mon Mar 2 13:51:38 2026 +0800
feat(Rust): add `GraphInfo` support (#883)
* update GraphInfo
* refactor(rust): align info bindings with upstream usize APIs
* update
---------
Co-authored-by: Codex <codex@localhost>
---
rust/include/graphar_rs.h | 28 ++
rust/src/ffi.rs | 105 ++++++++
rust/src/graphar_rs.cc | 88 ++++++-
rust/src/info/edge_info.rs | 2 +
rust/src/info/graph_info.rs | 599 +++++++++++++++++++++++++++++++++++++++++++
rust/src/info/mod.rs | 2 +
rust/src/info/vertex_info.rs | 3 +-
7 files changed, 825 insertions(+), 2 deletions(-)
diff --git a/rust/include/graphar_rs.h b/rust/include/graphar_rs.h
index 230cd7e1..3d096e5a 100644
--- a/rust/include/graphar_rs.h
+++ b/rust/include/graphar_rs.h
@@ -32,6 +32,10 @@
#include "rust/cxx.h"
namespace graphar {
+struct MaybeIndex;
+
+using SharedVertexInfo = std::shared_ptr<VertexInfo>;
+using SharedEdgeInfo = std::shared_ptr<EdgeInfo>;
using SharedPropertyGroup = std::shared_ptr<PropertyGroup>;
using SharedAdjacentList = std::shared_ptr<AdjacentList>;
using ConstInfoVersion = const InfoVersion;
@@ -89,6 +93,26 @@ std::shared_ptr<graphar::EdgeInfo> create_edge_info(
const std::string& prefix,
std::shared_ptr<graphar::ConstInfoVersion> version);
+std::shared_ptr<graphar::GraphInfo> load_graph_info(const std::string& path);
+std::shared_ptr<graphar::GraphInfo> create_graph_info(
+ const std::string& name,
+ const std::vector<graphar::SharedVertexInfo>& vertex_infos,
+ const std::vector<graphar::SharedEdgeInfo>& edge_infos,
+ const rust::Vec<rust::String>& labels, const std::string& prefix,
+ std::shared_ptr<graphar::ConstInfoVersion> version);
+graphar::MaybeIndex graph_info_vertex_info_index(
+ const graphar::GraphInfo& graph_info, const std::string& type);
+graphar::MaybeIndex graph_info_edge_info_index(
+ const graphar::GraphInfo& graph_info, const std::string& src_type,
+ const std::string& edge_type, const std::string& dst_type);
+
+void vertex_info_vec_push_vertex_info(
+ std::vector<graphar::SharedVertexInfo>& vertex_infos,
+ std::shared_ptr<graphar::VertexInfo> vertex_info);
+void edge_info_vec_push_edge_info(
+ std::vector<graphar::SharedEdgeInfo>& edge_infos,
+ std::shared_ptr<graphar::EdgeInfo> edge_info);
+
void vertex_info_save(const graphar::VertexInfo& vertex_info,
const std::string& path);
std::unique_ptr<std::string> vertex_info_dump(
@@ -101,4 +125,8 @@ void push_adjacent_list(graphar::AdjacentListVector& v,
void edge_info_save(const graphar::EdgeInfo& edge_info,
const std::string& path);
std::unique_ptr<std::string> edge_info_dump(const graphar::EdgeInfo&
edge_info);
+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);
} // namespace graphar_rs
diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs
index 9855a8ad..15c20584 100644
--- a/rust/src/ffi.rs
+++ b/rust/src/ffi.rs
@@ -17,6 +17,26 @@
use cxx::{ExternType, SharedPtr};
+use crate::ffi::graphar::MaybeIndex;
+
+#[repr(transparent)]
+#[derive(Clone)]
+pub struct SharedVertexInfo(pub(crate) SharedPtr<graphar::VertexInfo>);
+
+unsafe impl ExternType for SharedVertexInfo {
+ type Id = cxx::type_id!("graphar::SharedVertexInfo");
+ type Kind = cxx::kind::Opaque;
+}
+
+#[repr(transparent)]
+#[derive(Clone)]
+pub struct SharedEdgeInfo(pub(crate) SharedPtr<graphar::EdgeInfo>);
+
+unsafe impl ExternType for SharedEdgeInfo {
+ type Id = cxx::type_id!("graphar::SharedEdgeInfo");
+ type Kind = cxx::kind::Opaque;
+}
+
#[repr(transparent)]
#[derive(Clone)]
pub struct SharedPropertyGroup(pub(crate) SharedPtr<graphar::PropertyGroup>);
@@ -348,6 +368,81 @@ pub(crate) mod graphar {
fn edge_info_dump(edge_info: &EdgeInfo) ->
Result<UniquePtr<CxxString>>;
}
+ struct MaybeIndex {
+ has_value: bool,
+ index: usize,
+ }
+
+ // `GraphInfo`
+ unsafe extern "C++" {
+ type GraphInfo;
+
+ fn GetName(&self) -> &CxxString;
+ fn GetLabels(&self) -> &CxxVector<CxxString>;
+ fn GetPrefix(&self) -> &CxxString;
+ fn version(&self) -> &SharedPtr<ConstInfoVersion>;
+ fn GetVertexInfo(&self, type_: &CxxString) -> SharedPtr<VertexInfo>;
+ fn GetEdgeInfo(
+ &self,
+ src_type: &CxxString,
+ edge_type: &CxxString,
+ dst_type: &CxxString,
+ ) -> SharedPtr<EdgeInfo>;
+ fn VertexInfoNum(&self) -> usize;
+ fn EdgeInfoNum(&self) -> usize;
+ fn GetVertexInfos(&self) -> &CxxVector<SharedVertexInfo>;
+ fn GetEdgeInfos(&self) -> &CxxVector<SharedEdgeInfo>;
+
+ #[namespace = "graphar_rs"]
+ fn load_graph_info(path: &CxxString) -> Result<SharedPtr<GraphInfo>>;
+ #[namespace = "graphar_rs"]
+ fn create_graph_info(
+ name: &CxxString,
+ vertex_infos: &CxxVector<SharedVertexInfo>,
+ edge_infos: &CxxVector<SharedEdgeInfo>,
+ labels: &Vec<String>,
+ prefix: &CxxString,
+ version: SharedPtr<ConstInfoVersion>,
+ ) -> Result<SharedPtr<GraphInfo>>;
+ #[namespace = "graphar_rs"]
+ fn graph_info_save(graph_info: &GraphInfo, path: &CxxString) ->
Result<()>;
+ #[namespace = "graphar_rs"]
+ fn graph_info_dump(graph_info: &GraphInfo) ->
Result<UniquePtr<CxxString>>;
+ #[namespace = "graphar_rs"]
+ fn graph_info_vertex_info_index(graph_info: &GraphInfo, type_:
&CxxString) -> MaybeIndex;
+ #[namespace = "graphar_rs"]
+ fn graph_info_edge_info_index(
+ graph_info: &GraphInfo,
+ src_type: &CxxString,
+ edge_type: &CxxString,
+ dst_type: &CxxString,
+ ) -> MaybeIndex;
+ }
+
+ unsafe extern "C++" {
+ #[namespace = "graphar_rs"]
+ fn vertex_info_vec_push_vertex_info(
+ vertex_infos: Pin<&mut CxxVector<SharedVertexInfo>>,
+ vertex_info: SharedPtr<VertexInfo>,
+ );
+
+ #[namespace = "graphar_rs"]
+ fn edge_info_vec_push_edge_info(
+ edge_infos: Pin<&mut CxxVector<SharedEdgeInfo>>,
+ edge_info: SharedPtr<EdgeInfo>,
+ );
+ }
+
+ unsafe extern "C++" {
+ type SharedVertexInfo = crate::ffi::SharedVertexInfo;
+ }
+ impl CxxVector<SharedVertexInfo> {}
+
+ unsafe extern "C++" {
+ type SharedEdgeInfo = crate::ffi::SharedEdgeInfo;
+ }
+ impl CxxVector<SharedEdgeInfo> {}
+
unsafe extern "C++" {
type SharedPropertyGroup = crate::ffi::SharedPropertyGroup;
}
@@ -358,3 +453,13 @@ pub(crate) mod graphar {
}
impl CxxVector<SharedAdjacentList> {}
}
+
+impl From<MaybeIndex> for Option<usize> {
+ fn from(value: MaybeIndex) -> Self {
+ if value.has_value {
+ Some(value.index)
+ } else {
+ None
+ }
+ }
+}
diff --git a/rust/src/graphar_rs.cc b/rust/src/graphar_rs.cc
index 36c5c7da..0d9f68a9 100644
--- a/rust/src/graphar_rs.cc
+++ b/rust/src/graphar_rs.cc
@@ -17,8 +17,10 @@
* under the License.
*/
-#include "graphar_rs.h"
+#include "graphar-rs/src/ffi.rs.h"
+#include <cstddef>
+#include <optional>
#include <stdexcept>
#include <utility>
@@ -157,6 +159,73 @@ std::shared_ptr<graphar::EdgeInfo> create_edge_info(
return edge_info;
}
+std::shared_ptr<graphar::GraphInfo> load_graph_info(const std::string& path) {
+ auto loaded = graphar::GraphInfo::Load(path);
+ if (!loaded) {
+ throw std::runtime_error(loaded.error().message());
+ }
+ return std::move(loaded).value();
+}
+
+std::shared_ptr<graphar::GraphInfo> create_graph_info(
+ const std::string& name,
+ const std::vector<graphar::SharedVertexInfo>& vertex_infos,
+ const std::vector<graphar::SharedEdgeInfo>& edge_infos,
+ const rust::Vec<rust::String>& labels, const std::string& prefix,
+ std::shared_ptr<graphar::ConstInfoVersion> version) {
+ if (name.empty()) {
+ throw std::runtime_error("CreateGraphInfo: name must not be empty");
+ }
+
+ 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 graph_info = graphar::CreateGraphInfo(name, vertex_infos, edge_infos,
+ label_vec, prefix, version);
+ if (graph_info == nullptr) {
+ throw std::runtime_error("CreateGraphInfo: returned nullptr");
+ }
+ // if (!graph_info->IsValidated()) {
+ // throw std::runtime_error("CreateGraphInfo: graph info is not
validated");
+ // }
+ return graph_info;
+}
+
+static graphar::MaybeIndex optional_to_maybe_index(std::optional<size_t> opt) {
+ if (opt) {
+ return graphar::MaybeIndex{true, *opt};
+ } else {
+ return graphar::MaybeIndex{false, 0};
+ }
+}
+
+graphar::MaybeIndex graph_info_vertex_info_index(
+ const graphar::GraphInfo& graph_info, const std::string& type) {
+ return optional_to_maybe_index(graph_info.GetVertexInfoIndex(type));
+}
+
+graphar::MaybeIndex graph_info_edge_info_index(
+ const graphar::GraphInfo& graph_info, const std::string& src_type,
+ const std::string& edge_type, const std::string& dst_type) {
+ return optional_to_maybe_index(
+ graph_info.GetEdgeInfoIndex(src_type, edge_type, dst_type));
+}
+
+void vertex_info_vec_push_vertex_info(
+ std::vector<graphar::SharedVertexInfo>& vertex_infos,
+ std::shared_ptr<graphar::VertexInfo> vertex_info) {
+ vertex_infos.emplace_back(std::move(vertex_info));
+}
+
+void edge_info_vec_push_edge_info(
+ std::vector<graphar::SharedEdgeInfo>& edge_infos,
+ std::shared_ptr<graphar::EdgeInfo> edge_info) {
+ edge_infos.emplace_back(std::move(edge_info));
+}
+
void vertex_info_save(const graphar::VertexInfo& vertex_info,
const std::string& path) {
auto status = vertex_info.Save(path);
@@ -199,4 +268,21 @@ std::unique_ptr<std::string> edge_info_dump(
}
return std::make_unique<std::string>(std::move(r).value());
}
+
+void graph_info_save(const graphar::GraphInfo& graph_info,
+ const std::string& path) {
+ auto status = graph_info.Save(path);
+ if (!status.ok()) {
+ throw std::runtime_error(status.message());
+ }
+}
+
+std::unique_ptr<std::string> graph_info_dump(
+ const graphar::GraphInfo& graph_info) {
+ auto dumped = graph_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/info/edge_info.rs b/rust/src/info/edge_info.rs
index 0ef70f53..af693c46 100644
--- a/rust/src/info/edge_info.rs
+++ b/rust/src/info/edge_info.rs
@@ -201,6 +201,8 @@ impl EdgeInfo {
/// Return the number of property groups.
///
+ /// The upstream C++ API uses `int`; this binding returns `usize` for a
+ /// Rust-idiomatic count type.
pub fn property_group_num(&self) -> usize {
self.0.PropertyGroupNum()
}
diff --git a/rust/src/info/graph_info.rs b/rust/src/info/graph_info.rs
new file mode 100644
index 00000000..e8c55ed7
--- /dev/null
+++ b/rust/src/info/graph_info.rs
@@ -0,0 +1,599 @@
+// 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 graph-level metadata bindings.
+
+use std::path::Path;
+
+use cxx::{CxxVector, SharedPtr, UniquePtr, let_cxx_string};
+
+use crate::ffi;
+
+use super::{EdgeInfo, InfoVersion, VertexInfo};
+
+/// Graph-level metadata (`graphar::GraphInfo`).
+#[derive(Clone)]
+pub struct GraphInfo(pub(crate) SharedPtr<ffi::graphar::GraphInfo>);
+
+impl GraphInfo {
+ /// Create a builder for [`GraphInfo`].
+ pub fn builder(name: impl Into<String>) -> GraphInfoBuilder {
+ GraphInfoBuilder::new(name)
+ }
+
+ /// Create a new `GraphInfo`.
+ ///
+ /// Panics if GraphAr rejects the inputs. Prefer [`GraphInfo::try_new`] if
+ /// you want to handle errors.
+ pub fn new<N: AsRef<str>, P: AsRef<str>>(
+ name: N,
+ vertex_infos: Vec<VertexInfo>,
+ edge_infos: Vec<EdgeInfo>,
+ labels: Vec<String>,
+ prefix: P,
+ version: Option<InfoVersion>,
+ ) -> Self {
+ Self::try_new(name, vertex_infos, edge_infos, labels, prefix,
version).unwrap()
+ }
+
+ /// Try to create a new `GraphInfo`.
+ pub fn try_new<N: AsRef<str>, P: AsRef<str>>(
+ name: N,
+ vertex_infos: Vec<VertexInfo>,
+ edge_infos: Vec<EdgeInfo>,
+ labels: Vec<String>,
+ prefix: P,
+ version: Option<InfoVersion>,
+ ) -> crate::Result<Self> {
+ let_cxx_string!(name = name.as_ref());
+ let_cxx_string!(prefix = prefix.as_ref());
+
+ let mut vertex_info_vec = CxxVector::new();
+ vertex_info_vec.pin_mut().reserve(vertex_infos.len());
+ for vertex_info in vertex_infos {
+ ffi::graphar::vertex_info_vec_push_vertex_info(
+ vertex_info_vec.pin_mut(),
+ vertex_info.0,
+ );
+ }
+ let vertex_info_vec_ref = vertex_info_vec
+ .as_ref()
+ .expect("vertex info vector should be valid");
+
+ let mut edge_info_vec: UniquePtr<CxxVector<ffi::SharedEdgeInfo>> =
CxxVector::new();
+ edge_info_vec.pin_mut().reserve(edge_infos.len());
+ for edge_info in edge_infos {
+
ffi::graphar::edge_info_vec_push_edge_info(edge_info_vec.pin_mut(),
edge_info.0);
+ }
+ let edge_info_vec_ref = edge_info_vec
+ .as_ref()
+ .expect("edge info vector should be valid");
+
+ let version = version.map(|v| v.0).unwrap_or_else(SharedPtr::null);
+ let inner = ffi::graphar::create_graph_info(
+ &name,
+ vertex_info_vec_ref,
+ edge_info_vec_ref,
+ &labels,
+ &prefix,
+ version,
+ )?;
+ Ok(Self(inner))
+ }
+
+ /// Load graph metadata from a YAML file.
+ ///
+ /// Note: `path` must be valid UTF-8. On Unix, paths can contain arbitrary
+ /// bytes; such non-UTF8 paths return [`crate::Error::NonUtf8Path`].
+ pub fn load<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
+ let path_str = crate::path_to_utf8_str(path.as_ref())?;
+ let_cxx_string!(p = path_str);
+ Ok(Self(ffi::graphar::load_graph_info(&p)?))
+ }
+
+ /// Return the graph name.
+ pub fn name(&self) -> String {
+ self.0.GetName().to_string()
+ }
+
+ /// Return graph labels.
+ 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(label.to_string());
+ }
+ out
+ }
+
+ /// Return the logical prefix.
+ pub fn prefix(&self) -> String {
+ self.0.GetPrefix().to_string()
+ }
+
+ /// Return the optional info version.
+ pub fn version(&self) -> Option<InfoVersion> {
+ let sp = self.0.version();
+ if sp.is_null() {
+ None
+ } else {
+ Some(InfoVersion(sp.clone()))
+ }
+ }
+
+ /// Return the vertex info with the given type.
+ ///
+ /// Returns `None` if the type is not found.
+ pub fn vertex_info<S: AsRef<str>>(&self, r#type: S) -> Option<VertexInfo> {
+ let_cxx_string!(ty = r#type.as_ref());
+ let sp = self.0.GetVertexInfo(&ty);
+ if sp.is_null() {
+ None
+ } else {
+ Some(VertexInfo(sp))
+ }
+ }
+
+ /// Return the edge info with the given edge triplet.
+ ///
+ /// Returns `None` if the edge triplet is not found.
+ pub fn edge_info<S1: AsRef<str>, S2: AsRef<str>, S3: AsRef<str>>(
+ &self,
+ src_type: S1,
+ edge_type: S2,
+ dst_type: S3,
+ ) -> Option<EdgeInfo> {
+ let_cxx_string!(src = src_type.as_ref());
+ let_cxx_string!(edge = edge_type.as_ref());
+ let_cxx_string!(dst = dst_type.as_ref());
+ let sp = self.0.GetEdgeInfo(&src, &edge, &dst);
+ if sp.is_null() {
+ None
+ } else {
+ Some(EdgeInfo(sp))
+ }
+ }
+
+ /// Return the index of the vertex info with the given type.
+ ///
+ /// Returns `None` if the type is not found.
+ pub fn vertex_info_index<S: AsRef<str>>(&self, r#type: S) -> Option<usize>
{
+ let_cxx_string!(ty = r#type.as_ref());
+ ffi::graphar::graph_info_vertex_info_index(&self.0, &ty).into()
+ }
+
+ /// Return the index of the edge info with the given edge triplet.
+ ///
+ /// Returns `None` if the edge triplet is not found.
+ pub fn edge_info_index<S1: AsRef<str>, S2: AsRef<str>, S3: AsRef<str>>(
+ &self,
+ src_type: S1,
+ edge_type: S2,
+ dst_type: S3,
+ ) -> Option<usize> {
+ let_cxx_string!(src = src_type.as_ref());
+ let_cxx_string!(edge = edge_type.as_ref());
+ let_cxx_string!(dst = dst_type.as_ref());
+ ffi::graphar::graph_info_edge_info_index(&self.0, &src, &edge,
&dst).into()
+ }
+
+ /// Return the number of vertex infos.
+ pub fn vertex_info_num(&self) -> usize {
+ self.0.VertexInfoNum()
+ }
+
+ /// Return the number of edge infos.
+ pub fn edge_info_num(&self) -> usize {
+ self.0.EdgeInfoNum()
+ }
+
+ /// Return vertex infos.
+ ///
+ /// This allocates a new `Vec`. Prefer [`GraphInfo::vertex_infos_iter`]
+ /// if you only need to iterate.
+ pub fn vertex_infos(&self) -> Vec<VertexInfo> {
+ self.vertex_infos_iter().collect()
+ }
+
+ /// Iterate over vertex infos without allocating a `Vec`.
+ pub fn vertex_infos_iter(&self) -> impl Iterator<Item = VertexInfo> + '_ {
+ self.0
+ .GetVertexInfos()
+ .iter()
+ .map(|item| VertexInfo(item.0.clone()))
+ }
+
+ /// Return edge infos.
+ ///
+ /// This allocates a new `Vec`. Prefer [`GraphInfo::edge_infos_iter`]
+ /// if you only need to iterate.
+ pub fn edge_infos(&self) -> Vec<EdgeInfo> {
+ self.edge_infos_iter().collect()
+ }
+
+ /// Iterate over edge infos without allocating a `Vec`.
+ pub fn edge_infos_iter(&self) -> impl Iterator<Item = EdgeInfo> + '_ {
+ self.0
+ .GetEdgeInfos()
+ .iter()
+ .map(|item| EdgeInfo(item.0.clone()))
+ }
+
+ /// Save this `GraphInfo` to the given path.
+ ///
+ /// Note: `path` must be valid UTF-8. On Unix, paths can contain arbitrary
+ /// bytes; such non-UTF8 paths return [`crate::Error::NonUtf8Path`].
+ pub fn save<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
+ let path_str = crate::path_to_utf8_str(path.as_ref())?;
+ let_cxx_string!(p = path_str);
+ ffi::graphar::graph_info_save(&self.0, &p)?;
+ Ok(())
+ }
+
+ /// Dump this `GraphInfo` as a YAML string.
+ pub fn dump(&self) -> crate::Result<String> {
+ let dumped = ffi::graphar::graph_info_dump(&self.0)?;
+ let dumped = dumped.as_ref().expect("graph info dump should be valid");
+ Ok(dumped.to_string())
+ }
+}
+
+/// A builder for constructing a [`GraphInfo`].
+///
+/// Defaults:
+/// - `vertex_infos = []`
+/// - `edge_infos = []`
+/// - `labels = []`
+/// - `prefix = "./"` (matches upstream `graphar::GraphInfo` default and keeps
the graph info validated; empty prefix is considered invalid upstream)
+/// - `version = None`
+pub struct GraphInfoBuilder {
+ name: String,
+ vertex_infos: Vec<VertexInfo>,
+ edge_infos: Vec<EdgeInfo>,
+ labels: Vec<String>,
+ prefix: String,
+ version: Option<InfoVersion>,
+}
+
+impl GraphInfoBuilder {
+ /// Create a new builder with the required graph name.
+ ///
+ /// The default `prefix` is `"./"` to match upstream GraphAr C++
+ /// `graphar::GraphInfo` behavior.
+ pub fn new(name: impl Into<String>) -> Self {
+ Self {
+ name: name.into(),
+ vertex_infos: Vec::new(),
+ edge_infos: Vec::new(),
+ labels: Vec::new(),
+ prefix: "./".to_string(),
+ version: None,
+ }
+ }
+
+ /// Push a single vertex info.
+ pub fn push_vertex_info(mut self, vertex_info: VertexInfo) -> Self {
+ self.vertex_infos.push(vertex_info);
+ self
+ }
+
+ /// Replace vertex infos with the given vector.
+ pub fn vertex_infos(mut self, vertex_infos: Vec<VertexInfo>) -> Self {
+ self.vertex_infos = vertex_infos;
+ self
+ }
+
+ /// Push a single edge info.
+ pub fn push_edge_info(mut self, edge_info: EdgeInfo) -> Self {
+ self.edge_infos.push(edge_info);
+ self
+ }
+
+ /// Replace edge infos with the given vector.
+ pub fn edge_infos(mut self, edge_infos: Vec<EdgeInfo>) -> Self {
+ self.edge_infos = edge_infos;
+ self
+ }
+
+ /// Set graph labels.
+ pub fn labels(mut self, labels: Vec<String>) -> Self {
+ self.labels = labels;
+ self
+ }
+
+ /// Set graph 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.
+ ///
+ /// If unset, [`GraphInfoBuilder`] uses `"./"` by default.
+ pub fn prefix<P: AsRef<str>>(mut self, prefix: P) -> Self {
+ self.prefix = prefix.as_ref().to_owned();
+ 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 [`GraphInfo`].
+ ///
+ /// Panics if GraphAr rejects the builder inputs. Prefer
+ /// [`GraphInfoBuilder::try_build`] if you want to handle errors.
+ pub fn build(self) -> GraphInfo {
+ self.try_build().unwrap()
+ }
+
+ /// Try to build a [`GraphInfo`].
+ pub fn try_build(self) -> crate::Result<GraphInfo> {
+ let Self {
+ name,
+ vertex_infos,
+ edge_infos,
+ labels,
+ prefix,
+ version,
+ } = self;
+ GraphInfo::try_new(name, vertex_infos, edge_infos, labels, prefix,
version)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::info::{AdjListType, AdjacentList, AdjacentListVector};
+ use crate::property::{PropertyBuilder, PropertyGroup, PropertyGroupVector,
PropertyVec};
+ use crate::types::{DataType, FileType};
+ use tempfile::tempdir;
+
+ fn make_vertex_info(r#type: &str) -> VertexInfo {
+ let mut props = PropertyVec::new();
+ props.emplace(PropertyBuilder::new("id",
DataType::int64()).primary_key(true));
+ let group = PropertyGroup::new(props, FileType::Parquet,
format!("{}_props/", r#type));
+ let mut groups = PropertyGroupVector::new();
+ groups.push(group);
+
+ VertexInfo::builder(r#type, 1024)
+ .property_groups(groups)
+ .labels_from_iter(["label"])
+ .prefix(format!("{}/", r#type))
+ .build()
+ }
+
+ fn make_edge_info(src_type: &str, edge_type: &str, dst_type: &str) ->
EdgeInfo {
+ let mut adjacent_lists = AdjacentListVector::new();
+ adjacent_lists.push(AdjacentList::new(
+ AdjListType::OrderedBySource,
+ FileType::Parquet,
+ Some("ordered_by_source/"),
+ ));
+
+ let mut props = PropertyVec::new();
+ props.emplace(PropertyBuilder::new("weight", DataType::float64()));
+ let mut groups = PropertyGroupVector::new();
+ groups.push(PropertyGroup::new(props, FileType::Parquet, "weight/"));
+
+ EdgeInfo::builder(src_type, edge_type, dst_type, 1024, 100, 100)
+ .directed(true)
+ .adjacent_lists(adjacent_lists)
+ .property_groups(groups)
+ .prefix(format!("{src_type}_{edge_type}_{dst_type}/"))
+ .build()
+ }
+
+ #[test]
+ fn test_graph_info_builder_roundtrip_getters_and_lookups() {
+ let person = make_vertex_info("person");
+ let software = make_vertex_info("software");
+ let knows = make_edge_info("person", "knows", "person");
+
+ let graph_info = GraphInfo::builder("my_graph")
+ .push_vertex_info(person)
+ .push_vertex_info(software)
+ .push_edge_info(knows)
+ .labels(vec!["l0".to_string()])
+ .labels_from_iter(["l1", "l2"])
+ .push_label("l3")
+ .prefix("graph/")
+ .version(InfoVersion::new(1).unwrap())
+ .build();
+
+ assert_eq!(graph_info.name(), "my_graph");
+ assert_eq!(graph_info.prefix(), "graph/");
+ assert!(graph_info.version().is_some());
+
+ assert_eq!(
+ graph_info.labels(),
+ vec!["l1".to_string(), "l2".to_string(), "l3".to_string()]
+ );
+
+ assert_eq!(graph_info.vertex_info_num(), 2);
+ assert_eq!(graph_info.edge_info_num(), 1);
+ assert_eq!(graph_info.vertex_info_index("person"), Some(0));
+ assert_eq!(graph_info.vertex_info_index("software"), Some(1));
+ assert_eq!(graph_info.vertex_info_index("missing"), None);
+
+ assert_eq!(
+ graph_info.edge_info_index("person", "knows", "person"),
+ Some(0)
+ );
+ assert_eq!(
+ graph_info.edge_info_index("person", "unknown", "person"),
+ None
+ );
+
+ assert!(graph_info.vertex_info("person").is_some());
+ assert!(graph_info.vertex_info(String::from("software")).is_some());
+ assert!(graph_info.vertex_info("missing").is_none());
+
+ assert!(graph_info.edge_info("person", "knows", "person").is_some());
+ assert!(
+ graph_info
+ .edge_info("person", "missing", "person")
+ .is_none()
+ );
+
+ let vertex_infos = graph_info.vertex_infos();
+ assert_eq!(vertex_infos.len(), 2);
+ let vertex_infos_iter: Vec<_> =
graph_info.vertex_infos_iter().collect();
+ assert_eq!(vertex_infos_iter.len(), 2);
+
+ let edge_infos = graph_info.edge_infos();
+ assert_eq!(edge_infos.len(), 1);
+ let edge_infos_iter: Vec<_> = graph_info.edge_infos_iter().collect();
+ assert_eq!(edge_infos_iter.len(), 1);
+ }
+
+ #[test]
+ fn test_graph_info_builder_replace_collections_and_version_opt() {
+ let person = make_vertex_info("person");
+ let software = make_vertex_info("software");
+ let knows = make_edge_info("person", "knows", "person");
+
+ let graph_info = GraphInfo::builder("my_graph")
+ .push_vertex_info(make_vertex_info("tmp"))
+ .vertex_infos(vec![person, software])
+ .push_edge_info(make_edge_info("tmp", "tmp", "tmp"))
+ .edge_infos(vec![knows])
+ .version_opt(None)
+ .build();
+
+ assert_eq!(graph_info.vertex_info_num(), 2);
+ assert_eq!(graph_info.edge_info_num(), 1);
+ assert!(graph_info.version().is_none());
+ }
+
+ #[test]
+ fn test_graph_info_builder_default_prefix() {
+ let graph_info = GraphInfo::builder("my_graph")
+ .push_vertex_info(make_vertex_info("person"))
+ .push_edge_info(make_edge_info("person", "knows", "person"))
+ .build();
+
+ assert_eq!(graph_info.prefix(), "./");
+ }
+
+ #[test]
+ fn test_graph_info_dump_save_and_load() {
+ let graph_info = GraphInfo::builder("my_graph")
+ .push_vertex_info(make_vertex_info("person"))
+ .push_edge_info(make_edge_info("person", "knows", "person"))
+ .prefix("graph/")
+ .labels_from_iter(["person"])
+ .build();
+
+ let dumped = graph_info.dump().unwrap();
+ assert!(!dumped.trim().is_empty(), "dumped={dumped:?}");
+ assert!(dumped.contains("name: my_graph"), "dumped={dumped:?}");
+ assert!(dumped.contains("prefix: graph/"), "dumped={dumped:?}");
+ assert!(
+ dumped.contains("person_knows_person.edge.yaml"),
+ "dumped={dumped:?}"
+ );
+ assert!(dumped.contains("person.vertex.yaml"), "dumped={dumped:?}");
+
+ let dir = tempdir().unwrap();
+ let graph_path = dir.path().join("my_graph.graph.yaml");
+ graph_info.save(&graph_path).unwrap();
+ graph_info
+ .vertex_info("person")
+ .unwrap()
+ .save(dir.path().join("person.vertex.yaml"))
+ .unwrap();
+ graph_info
+ .edge_info("person", "knows", "person")
+ .unwrap()
+ .save(dir.path().join("person_knows_person.edge.yaml"))
+ .unwrap();
+
+ let loaded = GraphInfo::load(&graph_path).unwrap();
+ assert_eq!(loaded.name(), "my_graph");
+ assert_eq!(loaded.prefix(), "graph/");
+ assert_eq!(loaded.vertex_info_num(), 1);
+ assert_eq!(loaded.edge_info_num(), 1);
+ assert!(loaded.vertex_info("person").is_some());
+ assert!(loaded.edge_info("person", "knows", "person").is_some());
+ }
+
+ #[test]
+ fn test_graph_info_try_new_error_paths() {
+ let err = GraphInfo::try_new("", vec![], vec![], vec![], "graph/",
None)
+ .err()
+ .unwrap();
+ let msg = err.to_string();
+ assert!(
+ msg.contains("CreateGraphInfo") && msg.contains("name must not be
empty"),
+ "unexpected error message: {msg:?}"
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_graph_info_new_panics_on_invalid_args() {
+ let _ = GraphInfo::new("", vec![], vec![], vec![], "graph/", None);
+ }
+
+ #[cfg(unix)]
+ #[test]
+ fn test_graph_info_save_and_load_non_utf8_path() {
+ use std::os::unix::ffi::OsStringExt;
+
+ let graph_info = GraphInfo::builder("my_graph")
+ .push_vertex_info(make_vertex_info("person"))
+ .push_edge_info(make_edge_info("person", "knows", "person"))
+ .build();
+
+ let dir = tempdir().unwrap();
+ let mut path = dir.path().to_path_buf();
+ path.push(std::ffi::OsString::from_vec(
+ b"graph_info_\xFF_non_utf8.yaml".to_vec(),
+ ));
+
+ let err = graph_info.save(&path).err().unwrap();
+ assert!(
+ matches!(err, crate::Error::NonUtf8Path(_)),
+ "unexpected error: {err:?}"
+ );
+
+ let err = GraphInfo::load(&path).err().unwrap();
+ assert!(
+ matches!(err, crate::Error::NonUtf8Path(_)),
+ "unexpected error: {err:?}"
+ );
+ }
+}
diff --git a/rust/src/info/mod.rs b/rust/src/info/mod.rs
index 3e032f9e..2a6cb072 100644
--- a/rust/src/info/mod.rs
+++ b/rust/src/info/mod.rs
@@ -19,6 +19,7 @@
mod adjacent_list;
mod edge_info;
+mod graph_info;
mod version;
mod vertex_info;
@@ -27,5 +28,6 @@ pub use crate::ffi::graphar::AdjListType;
pub use adjacent_list::{AdjacentList, AdjacentListVector};
pub use edge_info::{EdgeInfo, EdgeInfoBuilder};
+pub use graph_info::{GraphInfo, GraphInfoBuilder};
pub use version::InfoVersion;
pub use vertex_info::{VertexInfo, VertexInfoBuilder};
diff --git a/rust/src/info/vertex_info.rs b/rust/src/info/vertex_info.rs
index 10b93ad9..df098fb1 100644
--- a/rust/src/info/vertex_info.rs
+++ b/rust/src/info/vertex_info.rs
@@ -122,6 +122,8 @@ impl VertexInfo {
/// Return the number of property groups.
///
+ /// The upstream C++ API uses `int`; this binding returns `usize` for a
+ /// Rust-idiomatic count type.
pub fn property_group_num(&self) -> usize {
self.0.PropertyGroupNum()
}
@@ -169,7 +171,6 @@ impl VertexInfo {
/// 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`.
- ///
/// Returns `None` if the index is out of range.
pub fn property_group_by_index(&self, index: usize) ->
Option<PropertyGroup> {
let sp = self.0.GetPropertyGroupByIndex(index);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]