Skip to content

Commit 38ab5af

Browse files
authored
ffi: StructArray creation (#7260)
StructArrays are needed for Scan API to test filtering and column selection from C side. Signed-off-by: Mikhail Kot <to@myrrc.dev>
1 parent 0368c39 commit 38ab5af

File tree

8 files changed

+390
-30
lines changed

8 files changed

+390
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vortex-ffi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ vortex = { workspace = true, features = ["object_store"] }
3434

3535
[dev-dependencies]
3636
tempfile = { workspace = true }
37+
vortex-array = { workspace = true, features = ["_test-harness"] }
3738

3839
[build-dependencies]
3940
cbindgen = { workspace = true }

vortex-ffi/cinclude/vortex.h

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ typedef struct vx_session vx_session;
413413
*/
414414
typedef struct vx_string vx_string;
415415

416+
typedef struct vx_struct_column_builder vx_struct_column_builder;
417+
416418
/**
417419
* Represents a Vortex struct data type, without top-level nullability.
418420
*/
@@ -588,7 +590,7 @@ const vx_array *vx_array_new_null(size_t len);
588590
*
589591
* Example:
590592
*
591-
* const vx_error* error = nullptr;
593+
* const vx_error* error = NULL;
592594
* vx_validity validity = {};
593595
* validity.type = VX_VALIDITY_NON_NULLABLE;
594596
* uint32_t buffer[] = {1, 2, 3};
@@ -1105,6 +1107,57 @@ size_t vx_string_len(const vx_string *ptr);
11051107
*/
11061108
const char *vx_string_ptr(const vx_string *ptr);
11071109

1110+
/**
1111+
* Free an owned [`vx_struct_column_builder`] object.
1112+
*/
1113+
void vx_struct_column_builder_free(vx_struct_column_builder *ptr);
1114+
1115+
/**
1116+
* Create a new column-wise struct array builder with given validity and a
1117+
* capacity hint. validity can't be NULL.
1118+
* If you don't know capacity, pass 0.
1119+
* if validity is NULL, returns NULL.
1120+
*/
1121+
vx_struct_column_builder *vx_struct_column_builder_new(const vx_validity *validity, size_t capacity);
1122+
1123+
/**
1124+
* Add a named field to a struct array builder.
1125+
* All arguments must be non-NULL.
1126+
* If field's length doesn't match lengths of previous fields, sets error.
1127+
* If an error is returned, the builder is still valid, and caller must
1128+
* deallocate it using vx_struct_column_builder_free.
1129+
*/
1130+
void vx_struct_column_builder_add_field(vx_struct_column_builder *builder,
1131+
const char *name,
1132+
const vx_array *field,
1133+
vx_error **error);
1134+
1135+
/**
1136+
* Finalize a struct array builder, returning a struct array.
1137+
* Consumes the builder. Caller doesn't need to free the builder after calling
1138+
* this function.
1139+
*
1140+
* Example:
1141+
*
1142+
* vx_error* error = NULL;
1143+
*
1144+
* vx_validity validity = {};
1145+
* validity.type = VX_VALIDITY_NON_NULLABLE;
1146+
*
1147+
* const vx_array* field_array = vx_array_new_null(5);
1148+
* const vx_struct_column_builder* builder =
1149+
* vx_struct_column_builder_new(&validity, 1);
1150+
*
1151+
* vx_struct_column_builder_add_field(builder, "age", field_array, &error);
1152+
*
1153+
* vx_array* struct_array = vx_struct_column_builder_finalize(builder, &error);
1154+
*
1155+
* vx_array_free(struct_array);
1156+
* vx_array_free(field_array);
1157+
*
1158+
*/
1159+
const vx_array *vx_struct_column_builder_finalize(vx_struct_column_builder *builder, vx_error **error);
1160+
11081161
/**
11091162
* Free an owned [`vx_struct_fields`] object.
11101163
*/

vortex-ffi/src/array.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ pub enum vx_validity_type {
118118

119119
#[repr(C)]
120120
pub struct vx_validity {
121-
r#type: vx_validity_type,
121+
pub r#type: vx_validity_type,
122122
/// If type is not VX_VALIDITY_ARRAY, this is NULL.
123123
/// If type is VX_VALIDITY_ARRAY, this is set to an owned boolean validity
124124
/// array which must be freed by the caller.
125-
array: *const vx_array,
125+
pub array: *const vx_array,
126126
}
127127

128128
impl From<&vx_validity> for Validity {
@@ -281,7 +281,7 @@ unsafe fn primitive_from_raw<T: vortex::dtype::NativePType>(
281281
///
282282
/// Example:
283283
///
284-
/// const vx_error* error = nullptr;
284+
/// const vx_error* error = NULL;
285285
/// vx_validity validity = {};
286286
/// validity.type = VX_VALIDITY_NON_NULLABLE;
287287
/// uint32_t buffer[] = {1, 2, 3};
@@ -425,7 +425,6 @@ mod tests {
425425
use vortex::array::IntoArray;
426426
use vortex::array::arrays::BoolArray;
427427
use vortex::array::arrays::PrimitiveArray;
428-
use vortex::array::arrays::StructArray;
429428
use vortex::array::arrays::VarBinViewArray;
430429
use vortex::array::validity::Validity;
431430
use vortex::buffer::buffer;
@@ -434,15 +433,28 @@ mod tests {
434433
use vortex::expr::eq;
435434
use vortex::expr::lit;
436435
use vortex::expr::root;
436+
use vortex_array::arrays::StructArray;
437437

438438
use crate::array::*;
439439
use crate::binary::vx_binary_free;
440440
use crate::dtype::vx_dtype_get_variant;
441441
use crate::dtype::vx_dtype_variant;
442442
use crate::error::vx_error_free;
443+
use crate::error::vx_error_get_message;
443444
use crate::expression::vx_expression_free;
444445
use crate::string::vx_string_free;
445446

447+
fn assert_no_error(error: *mut vx_error) {
448+
if !error.is_null() {
449+
let message;
450+
unsafe {
451+
message = vx_string::as_str(vx_error_get_message(error)).to_owned();
452+
vx_error_free(error);
453+
}
454+
panic!("{message}");
455+
}
456+
}
457+
446458
#[test]
447459
// TODO(joe): enable once this is fixed https://github.com/Amanieu/parking_lot/issues/477
448460
#[cfg_attr(miri, ignore)]
@@ -491,7 +503,7 @@ mod tests {
491503

492504
let mut error = ptr::null_mut();
493505
let sliced = vx_array_slice(ffi_array, 1, 4, &raw mut error);
494-
assert!(error.is_null());
506+
assert_no_error(error);
495507
assert_eq!(vx_array_len(sliced), 3);
496508
assert_eq!(vx_array_get_i32(sliced, 0), 2);
497509
assert_eq!(vx_array_get_i32(sliced, 1), 3);
@@ -515,14 +527,14 @@ mod tests {
515527

516528
let mut error = ptr::null_mut();
517529
assert!(!vx_array_element_is_invalid(ffi_array, 0, &raw mut error));
518-
assert!(error.is_null());
530+
assert_no_error(error);
519531
assert!(vx_array_element_is_invalid(ffi_array, 1, &raw mut error));
520-
assert!(error.is_null());
532+
assert_no_error(error);
521533
assert!(!vx_array_element_is_invalid(ffi_array, 2, &raw mut error));
522-
assert!(error.is_null());
534+
assert_no_error(error);
523535

524536
let null_count = vx_array_invalid_count(ffi_array, &raw mut error);
525-
assert!(error.is_null());
537+
assert_no_error(error);
526538
assert_eq!(null_count, 1);
527539

528540
vx_array_free(ffi_array);
@@ -547,11 +559,11 @@ mod tests {
547559

548560
let mut error = ptr::null_mut();
549561
let field0 = vx_array_get_field(ffi_array, 0, &raw mut error);
550-
assert!(error.is_null());
562+
assert_no_error(error);
551563
assert_eq!(vx_array_len(field0), 3);
552564

553565
let field1 = vx_array_get_field(ffi_array, 1, &raw mut error);
554-
assert!(error.is_null());
566+
assert_no_error(error);
555567
assert_eq!(vx_array_len(field1), 3);
556568
assert_eq!(vx_array_get_u8(field1, 0), 30);
557569
assert_eq!(vx_array_get_u8(field1, 1), 25);
@@ -592,7 +604,7 @@ mod tests {
592604
&raw const validity,
593605
&raw mut error,
594606
);
595-
assert!(error.is_null());
607+
assert_no_error(error);
596608
assert!(!ffi_i32.is_null());
597609

598610
assert!(vx_array_is_primitive(ffi_i32, vx_ptype::PTYPE_I32));
@@ -610,7 +622,7 @@ mod tests {
610622
&raw const validity,
611623
&raw mut error,
612624
);
613-
assert!(error.is_null());
625+
assert_no_error(error);
614626
assert!(!ffi_u64.is_null());
615627
assert!(vx_array_is_primitive(ffi_u64, vx_ptype::PTYPE_U64));
616628
assert_eq!(vx_array_get_u64(ffi_u64, 0), u64::MAX);
@@ -627,7 +639,7 @@ mod tests {
627639
&raw const validity,
628640
&raw mut error,
629641
);
630-
assert!(error.is_null());
642+
assert_no_error(error);
631643
assert!(!ffi_f64.is_null());
632644
assert!(vx_array_is_primitive(ffi_f64, vx_ptype::PTYPE_F64));
633645
assert_eq!(vx_array_get_f64(ffi_f64, 0), f64::NEG_INFINITY);
@@ -646,7 +658,7 @@ mod tests {
646658
&raw const validity,
647659
&raw mut error,
648660
);
649-
assert!(error.is_null());
661+
assert_no_error(error);
650662
assert!(!ffi_f16.is_null());
651663
assert_eq!(vx_array_get_f16(ffi_f16, 0), f16::from_f32(1.0));
652664
assert_eq!(vx_array_get_f16(ffi_f16, 1), f16::from_f32(-0.5));
@@ -746,8 +758,8 @@ mod tests {
746758
vx_error_free(error);
747759

748760
let res = vx_array_apply(array, expression, &raw mut error);
761+
assert_no_error(error);
749762
assert!(!res.is_null());
750-
assert!(error.is_null());
751763
{
752764
let res = vx_array::as_ref(res);
753765
let buffer = res.to_bool().to_bit_buffer();

vortex-ffi/src/expression.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::slice;
99
use std::sync::Arc;
1010

1111
use vortex::dtype::FieldName;
12+
use vortex::error::VortexExpect;
1213
use vortex::expr::Expression;
1314
use vortex::expr::and_collect;
1415
use vortex::expr::get_item;
@@ -22,6 +23,8 @@ use vortex::scalar_fn::ScalarFnVTableExt;
2223
use vortex::scalar_fn::fns::binary::Binary;
2324
use vortex::scalar_fn::fns::operators::Operator;
2425

26+
use crate::to_field_names;
27+
2528
// Expressions are Arc'ed inside
2629
crate::box_wrapper!(
2730
/// A node in a Vortex expression tree.
@@ -80,19 +83,8 @@ pub unsafe extern "C" fn vx_expression_select(
8083
if child.is_null() {
8184
return ptr::null_mut();
8285
}
83-
84-
#[expect(clippy::expect_used)]
85-
let names: Vec<FieldName> = (0..len)
86-
.map(|i| unsafe {
87-
let name = *names.offset(i.try_into().expect("pointer offset overflow"));
88-
let name = CStr::from_ptr(name)
89-
.to_str()
90-
.expect("converting pointer to str");
91-
let name: Arc<str> = Arc::from(name);
92-
name.into()
93-
})
94-
.collect();
95-
86+
let names =
87+
unsafe { to_field_names(names, len) }.vortex_expect("converting names to field names");
9688
let expr = select(names, vx_expression::as_ref(child).clone());
9789
vx_expression::new(Box::new(expr))
9890
}

vortex-ffi/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,19 @@ mod ptype;
1919
mod session;
2020
mod sink;
2121
mod string;
22+
mod struct_array;
2223
mod struct_fields;
2324

2425
use std::ffi::CStr;
2526
use std::ffi::c_char;
27+
use std::sync::Arc;
2628
use std::sync::LazyLock;
2729

2830
pub use log::vx_log_level;
31+
use vortex::dtype::FieldName;
32+
use vortex::error::VortexExpect;
33+
use vortex::error::VortexResult;
34+
use vortex::error::vortex_err;
2935
use vortex::io::runtime::current::CurrentThreadRuntime;
3036

3137
#[cfg(all(feature = "mimalloc", not(miri)))]
@@ -49,3 +55,25 @@ pub(crate) unsafe fn to_string_vec(ptr: *const *const c_char, len: usize) -> Vec
4955
})
5056
.collect()
5157
}
58+
59+
/// SAFETY: name must be a non-NULL pointer
60+
pub(crate) unsafe fn to_field_name(name: *const c_char) -> VortexResult<FieldName> {
61+
let name = unsafe { CStr::from_ptr(name) }
62+
.to_str()
63+
.map_err(|e| vortex_err!("{e}"))?;
64+
let name: Arc<str> = Arc::from(name);
65+
Ok(name.into())
66+
}
67+
68+
/// SAFETY: names must be a non-NULL pointer valid for reads up to len.
69+
pub(crate) unsafe fn to_field_names(
70+
names: *const *const c_char,
71+
len: usize,
72+
) -> VortexResult<Vec<FieldName>> {
73+
(0..len)
74+
.map(|i| unsafe {
75+
let name = *names.offset(i.try_into().vortex_expect("pointer offset overflow"));
76+
to_field_name(name)
77+
})
78+
.collect()
79+
}

0 commit comments

Comments
 (0)