blob: a7bddea3ed93a839c95c297ae32add4359f0833d [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Rust bindings for GraphAr property types.
use std::ops::Deref;
use std::pin::Pin;
use crate::ffi;
use crate::types::{Cardinality, DataType, FileType};
use cxx::{CxxVector, SharedPtr, UniquePtr, let_cxx_string};
/// A property definition in the GraphAr schema.
///
/// This is a thin wrapper around the C++ `graphar::Property`.
pub struct Property(UniquePtr<ffi::graphar::Property>);
impl Property {
pub(crate) fn as_ref(&self) -> &ffi::graphar::Property {
self.0.as_ref().expect("property should be valid")
}
/// Create a new property definition.
///
/// Note: In upstream GraphAr C++ (`graphar::Property` constructor), a primary key property is
/// always treated as non-nullable. Concretely, `is_nullable` is forced to `false` when
/// `is_primary` is `true` (regardless of the provided `is_nullable` value).
///
/// This method takes `data_type` by value. Clone it if you need to reuse it.
pub fn new<S: AsRef<str>>(
name: S,
data_type: DataType,
is_primary: bool,
is_nullable: bool,
cardinality: Cardinality,
) -> Self {
let_cxx_string!(name = name.as_ref());
Self(ffi::graphar::new_property(
&name,
data_type.0,
is_primary,
is_nullable,
cardinality,
))
}
/// Return the property name.
pub fn name(&self) -> String {
ffi::graphar::property_get_name(self.as_ref()).to_string()
}
/// Return the property data type.
pub fn data_type(&self) -> DataType {
let ty = ffi::graphar::property_get_type(self.as_ref());
DataType(ty.clone())
}
/// Return whether this property is a primary key.
pub fn is_primary(&self) -> bool {
ffi::graphar::property_is_primary(self.as_ref())
}
/// Return whether this property is nullable.
///
/// Note: In upstream GraphAr C++ (`graphar::Property` constructor), a primary key property is
/// always treated as non-nullable, so this returns `false` when `is_primary()` is `true`.
pub fn is_nullable(&self) -> bool {
ffi::graphar::property_is_nullable(self.as_ref())
}
/// Return the cardinality of this property.
pub fn cardinality(&self) -> Cardinality {
ffi::graphar::property_get_cardinality(self.as_ref())
}
}
/// A builder for constructing a [`Property`].
///
/// This builder is primarily intended to be used with [`PropertyVec::emplace`]
/// to avoid allocating an intermediate `graphar::Property` on the C++ heap.
///
/// Defaults:
/// - `is_primary = false`
/// - `is_nullable = true` (matches upstream GraphAr C++ default)
/// - `cardinality = Cardinality::Single`
pub struct PropertyBuilder<S: AsRef<str>> {
name: S,
data_type: DataType,
is_primary: bool,
is_nullable: bool,
cardinality: Cardinality,
}
impl<S: AsRef<str>> PropertyBuilder<S> {
/// Create a new builder with the given name and data type.
pub fn new(name: S, data_type: DataType) -> Self {
Self {
name,
data_type,
is_primary: false,
is_nullable: true,
cardinality: Cardinality::Single,
}
}
/// Mark whether this property is a primary key.
pub fn primary_key(mut self, is_primary: bool) -> Self {
self.is_primary = is_primary;
self
}
/// Mark whether this property is nullable.
pub fn nullable(mut self, is_nullable: bool) -> Self {
self.is_nullable = is_nullable;
self
}
/// Set the cardinality of this property.
pub fn cardinality(mut self, cardinality: Cardinality) -> Self {
self.cardinality = cardinality;
self
}
/// Build a [`Property`].
pub fn build(self) -> Property {
let Self {
name,
data_type,
is_primary,
is_nullable,
cardinality,
} = self;
let_cxx_string!(name = name.as_ref());
Property(ffi::graphar::new_property(
&name,
data_type.0,
is_primary,
is_nullable,
cardinality,
))
}
}
/// A vector of properties.
///
/// This is a wrapper around a C++ `std::vector<graphar::Property>`.
pub struct PropertyVec(UniquePtr<CxxVector<ffi::graphar::Property>>);
impl Default for PropertyVec {
fn default() -> Self {
Self::new()
}
}
impl Clone for PropertyVec {
fn clone(&self) -> Self {
Self(ffi::graphar::property_vec_clone(self.as_ref()))
}
}
impl Deref for PropertyVec {
type Target = CxxVector<ffi::graphar::Property>;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl PropertyVec {
/// Create an empty property vector.
pub fn new() -> Self {
Self(CxxVector::new())
}
/// Push a property into the vector.
pub fn push(&mut self, property: Property) {
ffi::graphar::property_vec_push_property(self.pin_mut(), property.0);
}
/// Construct and append a property directly in the underlying C++ vector.
///
/// Compared to `push(builder.build())`, this avoids allocating an
/// intermediate `graphar::Property` on the C++ heap.
pub fn emplace<S: AsRef<str>>(&mut self, builder: PropertyBuilder<S>) {
let PropertyBuilder {
name,
data_type,
is_primary,
is_nullable,
cardinality,
} = builder;
let_cxx_string!(name = name.as_ref());
ffi::graphar::property_vec_emplace_property(
self.pin_mut(),
&name,
data_type.0,
is_primary,
is_nullable,
cardinality,
);
}
pub(crate) fn as_ref(&self) -> &CxxVector<ffi::graphar::Property> {
self.0.as_ref().expect("properties vec should be valid")
}
/// Borrow the underlying C++ vector mutably.
///
/// Mutating APIs on `cxx::CxxVector` require a pinned mutable reference.
///
/// # Panics
///
/// Panics if the underlying C++ vector pointer is null.
pub fn pin_mut(&mut self) -> Pin<&mut CxxVector<ffi::graphar::Property>> {
self.0.as_mut().expect("properties vec should be valid")
}
}
/// A group of properties stored in the same file(s).
pub type PropertyGroup = ffi::SharedPropertyGroup;
impl PropertyGroup {
/// Construct a `PropertyGroup` from a raw C++ shared pointer.
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
/// 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);
Self(inner)
}
/// Return properties contained in this group.
pub fn properties(&self) -> Vec<Property> {
let props_cxx = self.0.GetProperties();
let mut props = Vec::with_capacity(props_cxx.len());
for prop in props_cxx {
props.push(Property(ffi::graphar::property_clone(prop)));
}
props
}
/// Check whether this group contains the given property.
pub fn has_property(&self, property_name: &str) -> bool {
let_cxx_string!(name = property_name);
self.0.HasProperty(&name)
}
}
/// A vector of property groups.
pub struct PropertyGroupVector(UniquePtr<CxxVector<PropertyGroup>>);
impl Default for PropertyGroupVector {
fn default() -> Self {
Self::new()
}
}
impl Clone for PropertyGroupVector {
fn clone(&self) -> Self {
Self(ffi::graphar::property_group_vec_clone(self.as_ref()))
}
}
impl Deref for PropertyGroupVector {
type Target = CxxVector<PropertyGroup>;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl PropertyGroupVector {
/// Create an empty property group vector.
pub fn new() -> Self {
Self(CxxVector::new())
}
/// Push a property group into the vector.
pub fn push(&mut self, property_group: PropertyGroup) {
ffi::graphar::property_group_vec_push_property_group(self.pin_mut(), property_group.0);
}
pub(crate) fn as_ref(&self) -> &CxxVector<PropertyGroup> {
self.0.as_ref().expect("property group vec should be valid")
}
/// Borrow the underlying C++ vector mutably.
///
/// Mutating APIs on `cxx::CxxVector` require a pinned mutable reference.
///
/// # Panics
///
/// Panics if the underlying C++ vector pointer is null.
pub fn pin_mut(&mut self) -> Pin<&mut CxxVector<PropertyGroup>> {
self.0.as_mut().expect("property group vec should be valid")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Type;
#[test]
fn test_property_roundtrip_getters() {
let p = Property::new("name", DataType::string(), false, true, Cardinality::Single);
assert_eq!(p.name(), "name");
assert_eq!(p.data_type().id(), Type::String);
assert!(!p.is_primary());
assert!(p.is_nullable());
assert_eq!(p.cardinality(), Cardinality::Single);
}
#[test]
fn test_property_primary_implies_non_nullable_upstream_behavior() {
// Upstream GraphAr C++ (`graphar::Property` constructor) forces:
// is_nullable = !is_primary && is_nullable
// This test documents that behavior at the Rust binding layer.
let p = Property::new("id", DataType::int64(), true, true, Cardinality::Single);
assert!(p.is_primary());
assert!(!p.is_nullable());
}
#[test]
fn test_property_builder_default() {
let built = PropertyBuilder::new("id", DataType::int64()).build();
assert_eq!(built.name(), "id");
assert_eq!(built.data_type().id(), Type::Int64);
assert!(!built.is_primary());
assert!(built.is_nullable());
assert_eq!(built.cardinality(), Cardinality::Single);
}
#[test]
fn test_property_builder_emplace_options() {
let mut props = PropertyVec::new();
props.emplace(
PropertyBuilder::new("id", DataType::int64())
.primary_key(true)
.nullable(true),
);
props.emplace(
PropertyBuilder::new("tags", DataType::string())
.nullable(true)
.cardinality(Cardinality::List),
);
let pg = PropertyGroup::new(props, FileType::Parquet, "builder/");
let mut got = pg.properties();
got.sort_by_key(|a| a.name());
assert_eq!(got.len(), 2);
assert_eq!(got[0].name(), "id");
assert!(got[0].is_primary());
assert!(!got[0].is_nullable());
assert_eq!(got[0].cardinality(), Cardinality::Single);
assert_eq!(got[1].name(), "tags");
assert!(!got[1].is_primary());
assert!(got[1].is_nullable());
assert_eq!(got[1].cardinality(), Cardinality::List);
}
#[test]
fn test_property_vec_deref() {
let mut vec = PropertyVec::default();
assert_eq!(vec.len(), 0);
assert_eq!(vec.as_ref().len(), 0);
assert!(vec.as_ref().is_empty());
assert_eq!(vec.as_ref().capacity(), 0);
vec.pin_mut().reserve(2);
assert_eq!(vec.as_ref().capacity(), 2);
}
#[test]
fn test_property_vec_and_group_roundtrip() {
let mut props = PropertyVec::default();
props.emplace(PropertyBuilder::new("id", DataType::int64()).primary_key(true));
props.push(Property::new(
"name",
DataType::string(),
false,
true,
Cardinality::Single,
));
let pg = PropertyGroup::new(props, FileType::Parquet, "id_name/");
let mut got_names: Vec<String> = pg.properties().into_iter().map(|p| p.name()).collect();
got_names.sort();
assert_eq!(got_names, vec!["id".to_string(), "name".to_string()]);
assert!(pg.has_property("id"));
assert!(pg.has_property("name"));
assert!(!pg.has_property("missing"));
}
#[test]
fn test_property_group_vector_default_and_deref() {
let mut vec = PropertyGroupVector::default();
assert_eq!(vec.len(), 0);
assert_eq!(vec.as_ref().len(), 0);
assert!(vec.as_ref().is_empty());
vec.pin_mut().reserve(2);
assert_eq!(vec.as_ref().capacity(), 2);
}
#[test]
fn test_property_group_vector_push() {
let mut props1 = PropertyVec::new();
props1.push(Property::new(
"id1",
DataType::int64(),
true,
false,
Cardinality::Single,
));
let mut props2 = PropertyVec::new();
props2.push(Property::new(
"id",
DataType::int64(),
true,
false,
Cardinality::Single,
));
let pg1 = PropertyGroup::new(props1, FileType::Parquet, "pg1/");
let pg2 = PropertyGroup::new(props2, FileType::Parquet, "pg2/");
let mut vec = PropertyGroupVector::default();
assert_eq!(vec.as_ref().len(), 0);
assert!(vec.as_ref().is_empty());
vec.push(pg1);
assert_eq!(vec.as_ref().len(), 1);
assert!(!vec.as_ref().is_empty());
vec.push(pg2);
assert_eq!(vec.as_ref().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.as_ref().len(), 2);
assert_eq!(cloned.as_ref().len(), 2);
let id_prop = Property::new("id2", DataType::int64(), true, true, Cardinality::Single);
original.push(id_prop);
assert_eq!(original.as_ref().len(), 3);
// Mutating the original container should not affect the cloned one.
assert_eq!(cloned.as_ref().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.as_ref().len(), 2);
assert_eq!(cloned.as_ref().len(), 2);
assert!(cloned.as_ref().get(0).unwrap().has_property("id1"));
assert!(cloned.as_ref().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.as_ref().len(), 3);
assert_eq!(cloned.as_ref().len(), 2);
let cloned_props = cloned.as_ref().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);
}
}