blob: c6496f60595df2668e83a73824ee6ef9c13d851a [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.
use std::ffi::c_void;
use arrow_array::ffi::{FFI_ArrowArray, FFI_ArrowSchema};
use arrow_array::{Array, StructArray};
use futures::StreamExt;
use paimon::spec::{DataField, DataType, Datum, Predicate, PredicateBuilder};
use paimon::table::{ArrowRecordBatchStream, Table};
use paimon::Plan;
use crate::error::{check_non_null, paimon_error, validate_cstr, PaimonErrorCode};
use crate::result::{
paimon_result_new_read, paimon_result_next_batch, paimon_result_plan, paimon_result_predicate,
paimon_result_read_builder, paimon_result_record_batch_reader, paimon_result_table_scan,
};
use crate::runtime;
use crate::types::*;
// Helper to free a wrapper struct that contains a Table clone.
unsafe fn free_table_wrapper<T>(ptr: *mut T, get_inner: impl FnOnce(&T) -> *mut c_void) {
if !ptr.is_null() {
let wrapper = Box::from_raw(ptr);
let inner = get_inner(&wrapper);
if !inner.is_null() {
drop(Box::from_raw(inner as *mut Table));
}
}
}
// Helper to box a ReadBuilderState and return a raw pointer.
unsafe fn box_read_builder_state(state: ReadBuilderState) -> *mut paimon_read_builder {
let inner = Box::into_raw(Box::new(state)) as *mut c_void;
Box::into_raw(Box::new(paimon_read_builder { inner }))
}
// Helper to box a TableReadState and return a raw pointer.
unsafe fn box_table_read_state(state: TableReadState) -> *mut paimon_table_read {
let inner = Box::into_raw(Box::new(state)) as *mut c_void;
Box::into_raw(Box::new(paimon_table_read { inner }))
}
// ======================= Table ===============================
/// Free a paimon_table.
///
/// # Safety
/// Only call with a table returned from `paimon_catalog_get_table`.
#[no_mangle]
pub unsafe extern "C" fn paimon_table_free(table: *mut paimon_table) {
free_table_wrapper(table, |t| t.inner);
}
/// Create a new ReadBuilder from a Table.
///
/// # Safety
/// `table` must be a valid pointer from `paimon_catalog_get_table`, or null (returns error).
#[no_mangle]
pub unsafe extern "C" fn paimon_table_new_read_builder(
table: *const paimon_table,
) -> paimon_result_read_builder {
if let Err(e) = check_non_null(table, "table") {
return paimon_result_read_builder {
read_builder: std::ptr::null_mut(),
error: e,
};
}
let table_ref = &*((*table).inner as *const Table);
let state = ReadBuilderState {
table: table_ref.clone(),
projected_columns: None,
filter: None,
};
paimon_result_read_builder {
read_builder: box_read_builder_state(state),
error: std::ptr::null_mut(),
}
}
// ======================= ReadBuilder ===============================
/// Free a paimon_read_builder.
///
/// # Safety
/// Only call with a read_builder returned from `paimon_table_new_read_builder`.
#[no_mangle]
pub unsafe extern "C" fn paimon_read_builder_free(rb: *mut paimon_read_builder) {
if !rb.is_null() {
let wrapper = Box::from_raw(rb);
if !wrapper.inner.is_null() {
drop(Box::from_raw(wrapper.inner as *mut ReadBuilderState));
}
}
}
/// Set column projection for a ReadBuilder.
///
/// The `columns` parameter is a null-terminated array of null-terminated C strings.
/// Output order follows the caller-specified order. Unknown or duplicate names
/// cause `paimon_read_builder_new_read()` to fail; an empty list is a valid
/// zero-column projection.
///
/// # Safety
/// `rb` must be a valid pointer from `paimon_table_new_read_builder`, or null (returns error).
/// `columns` must be a null-terminated array of null-terminated C strings, or null for no projection.
#[no_mangle]
pub unsafe extern "C" fn paimon_read_builder_with_projection(
rb: *mut paimon_read_builder,
columns: *const *const std::ffi::c_char,
) -> *mut paimon_error {
if let Err(e) = check_non_null(rb, "rb") {
return e;
}
let state = &mut *((*rb).inner as *mut ReadBuilderState);
if columns.is_null() {
state.projected_columns = None;
return std::ptr::null_mut();
}
let mut col_names = Vec::new();
let mut ptr = columns;
while !(*ptr).is_null() {
let c_str = std::ffi::CStr::from_ptr(*ptr);
match c_str.to_str() {
Ok(s) => col_names.push(s.to_string()),
Err(e) => {
return paimon_error::from_paimon(paimon::Error::ConfigInvalid {
message: format!("Invalid UTF-8 in column name: {e}"),
});
}
}
ptr = ptr.add(1);
}
state.projected_columns = Some(col_names);
std::ptr::null_mut()
}
/// Set a filter predicate for scan planning.
///
/// The predicate is consumed (ownership transferred to the read builder).
/// Pass null to clear any previously set filter.
///
/// # Safety
/// `rb` must be a valid pointer from `paimon_table_new_read_builder`, or null (returns error).
/// `predicate` must be a valid pointer from a `paimon_predicate_*` function, or null.
#[no_mangle]
pub unsafe extern "C" fn paimon_read_builder_with_filter(
rb: *mut paimon_read_builder,
predicate: *mut paimon_predicate,
) -> *mut paimon_error {
if let Err(e) = check_non_null(rb, "rb") {
return e;
}
let state = &mut *((*rb).inner as *mut ReadBuilderState);
if predicate.is_null() {
state.filter = None;
return std::ptr::null_mut();
}
let pred_wrapper = Box::from_raw(predicate);
let pred = Box::from_raw(pred_wrapper.inner as *mut Predicate);
state.filter = Some(*pred);
std::ptr::null_mut()
}
/// Create a new TableScan from a ReadBuilder.
///
/// # Safety
/// `rb` must be a valid pointer from `paimon_table_new_read_builder`, or null (returns error).
#[no_mangle]
pub unsafe extern "C" fn paimon_read_builder_new_scan(
rb: *const paimon_read_builder,
) -> paimon_result_table_scan {
if let Err(e) = check_non_null(rb, "rb") {
return paimon_result_table_scan {
scan: std::ptr::null_mut(),
error: e,
};
}
let state = &*((*rb).inner as *const ReadBuilderState);
let scan_state = TableScanState {
table: state.table.clone(),
filter: state.filter.clone(),
};
let inner = Box::into_raw(Box::new(scan_state)) as *mut c_void;
paimon_result_table_scan {
scan: Box::into_raw(Box::new(paimon_table_scan { inner })),
error: std::ptr::null_mut(),
}
}
/// Create a new TableRead from a ReadBuilder.
///
/// # Safety
/// `rb` must be a valid pointer from `paimon_table_new_read_builder`, or null (returns error).
#[no_mangle]
pub unsafe extern "C" fn paimon_read_builder_new_read(
rb: *const paimon_read_builder,
) -> paimon_result_new_read {
if let Err(e) = check_non_null(rb, "rb") {
return paimon_result_new_read {
read: std::ptr::null_mut(),
error: e,
};
}
let state = &*((*rb).inner as *const ReadBuilderState);
let mut rb_rust = state.table.new_read_builder();
// Apply projection if set
if let Some(ref columns) = state.projected_columns {
let col_refs: Vec<&str> = columns.iter().map(|s| s.as_str()).collect();
rb_rust.with_projection(&col_refs);
}
// Apply filter if set
if let Some(ref filter) = state.filter {
rb_rust.with_filter(filter.clone());
}
match rb_rust.new_read() {
Ok(table_read) => {
let read_state = TableReadState {
table: state.table.clone(),
read_type: table_read.read_type().to_vec(),
data_predicates: table_read.data_predicates().to_vec(),
};
paimon_result_new_read {
read: box_table_read_state(read_state),
error: std::ptr::null_mut(),
}
}
Err(e) => paimon_result_new_read {
read: std::ptr::null_mut(),
error: paimon_error::from_paimon(e),
},
}
}
// ======================= TableScan ===============================
/// Free a paimon_table_scan.
///
/// # Safety
/// Only call with a scan returned from `paimon_read_builder_new_scan`.
#[no_mangle]
pub unsafe extern "C" fn paimon_table_scan_free(scan: *mut paimon_table_scan) {
if !scan.is_null() {
let wrapper = Box::from_raw(scan);
if !wrapper.inner.is_null() {
drop(Box::from_raw(wrapper.inner as *mut TableScanState));
}
}
}
/// Execute a scan plan to get splits.
///
/// # Safety
/// `scan` must be a valid pointer from `paimon_read_builder_new_scan`, or null (returns error).
#[no_mangle]
pub unsafe extern "C" fn paimon_table_scan_plan(
scan: *const paimon_table_scan,
) -> paimon_result_plan {
if let Err(e) = check_non_null(scan, "scan") {
return paimon_result_plan {
plan: std::ptr::null_mut(),
error: e,
};
}
let scan_state = &*((*scan).inner as *const TableScanState);
let mut rb = scan_state.table.new_read_builder();
if let Some(ref filter) = scan_state.filter {
rb.with_filter(filter.clone());
}
let table_scan = rb.new_scan();
match runtime().block_on(table_scan.plan()) {
Ok(plan) => {
let wrapper = Box::new(paimon_plan {
inner: Box::into_raw(Box::new(plan)) as *mut c_void,
});
paimon_result_plan {
plan: Box::into_raw(wrapper),
error: std::ptr::null_mut(),
}
}
Err(e) => paimon_result_plan {
plan: std::ptr::null_mut(),
error: paimon_error::from_paimon(e),
},
}
}
// ======================= Plan ===============================
/// Free a paimon_plan.
///
/// # Safety
/// Only call with a plan returned from `paimon_table_scan_plan`.
#[no_mangle]
pub unsafe extern "C" fn paimon_plan_free(plan: *mut paimon_plan) {
if !plan.is_null() {
let p = Box::from_raw(plan);
if !p.inner.is_null() {
drop(Box::from_raw(p.inner as *mut Plan));
}
}
}
/// Return the number of data splits in a plan.
///
/// # Safety
/// `plan` must be a valid pointer from `paimon_table_scan_plan`, or null (returns 0).
#[no_mangle]
pub unsafe extern "C" fn paimon_plan_num_splits(plan: *const paimon_plan) -> usize {
if plan.is_null() {
return 0;
}
let plan_ref = &*((*plan).inner as *const Plan);
plan_ref.splits().len()
}
// ======================= TableRead ===============================
/// Free a paimon_table_read.
///
/// # Safety
/// Only call with a read returned from `paimon_read_builder_new_read`.
#[no_mangle]
pub unsafe extern "C" fn paimon_table_read_free(read: *mut paimon_table_read) {
if !read.is_null() {
let wrapper = Box::from_raw(read);
if !wrapper.inner.is_null() {
drop(Box::from_raw(wrapper.inner as *mut TableReadState));
}
}
}
/// Read table data as Arrow record batches via a streaming reader.
///
/// Returns a `paimon_record_batch_reader` that yields one batch at a time
/// via `paimon_record_batch_reader_next`. This avoids loading all batches
/// into memory at once.
///
/// `offset` and `length` select a contiguous sub-range of splits from the
/// plan. The range is clamped to the available splits (out-of-range values
/// are silently adjusted).
///
/// # Safety
/// `read` and `plan` must be valid pointers from previous paimon C calls, or null (returns error).
#[no_mangle]
pub unsafe extern "C" fn paimon_table_read_to_arrow(
read: *const paimon_table_read,
plan: *const paimon_plan,
offset: usize,
length: usize,
) -> paimon_result_record_batch_reader {
if let Err(e) = check_non_null(read, "read") {
return paimon_result_record_batch_reader {
reader: std::ptr::null_mut(),
error: e,
};
}
if let Err(e) = check_non_null(plan, "plan") {
return paimon_result_record_batch_reader {
reader: std::ptr::null_mut(),
error: e,
};
}
let state = &*((*read).inner as *const TableReadState);
let plan_ref = &*((*plan).inner as *const Plan);
let all_splits = plan_ref.splits();
let start = offset.min(all_splits.len());
let end = (offset.saturating_add(length)).min(all_splits.len());
let selected = &all_splits[start..end];
let table_read = paimon::table::TableRead::new(
&state.table,
state.read_type.clone(),
state.data_predicates.clone(),
);
match table_read.to_arrow(selected) {
Ok(stream) => {
let reader = Box::new(stream);
let wrapper = Box::new(paimon_record_batch_reader {
inner: Box::into_raw(reader) as *mut c_void,
});
paimon_result_record_batch_reader {
reader: Box::into_raw(wrapper),
error: std::ptr::null_mut(),
}
}
Err(e) => paimon_result_record_batch_reader {
reader: std::ptr::null_mut(),
error: paimon_error::from_paimon(e),
},
}
}
// ======================= RecordBatchReader ===============================
/// Get the next Arrow record batch from the reader.
///
/// When the stream is exhausted, both `batch.array` and `batch.schema` will
/// be null. On error, `error` will be non-null.
///
/// After importing each batch, call `paimon_arrow_batch_free` to free the
/// ArrowArray and ArrowSchema container structs.
///
/// # Safety
/// `reader` must be a valid pointer from `paimon_table_read_to_arrow`, or null (returns error).
#[no_mangle]
pub unsafe extern "C" fn paimon_record_batch_reader_next(
reader: *mut paimon_record_batch_reader,
) -> paimon_result_next_batch {
if let Err(e) = check_non_null(reader, "reader") {
return paimon_result_next_batch {
batch: paimon_arrow_batch {
array: std::ptr::null_mut(),
schema: std::ptr::null_mut(),
},
error: e,
};
}
let stream = &mut *((*reader).inner as *mut ArrowRecordBatchStream);
match runtime().block_on(stream.next()) {
Some(Ok(batch)) => {
let schema = batch.schema();
let struct_array = StructArray::from(batch);
let ffi_array = FFI_ArrowArray::new(&struct_array.to_data());
let ffi_schema = match FFI_ArrowSchema::try_from(schema.as_ref()) {
Ok(s) => s,
Err(e) => {
return paimon_result_next_batch {
batch: paimon_arrow_batch {
array: std::ptr::null_mut(),
schema: std::ptr::null_mut(),
},
error: paimon_error::from_paimon(paimon::Error::UnexpectedError {
message: format!("Failed to export Arrow schema: {e}"),
source: Some(Box::new(e)),
}),
};
}
};
let array_ptr = Box::into_raw(Box::new(ffi_array)) as *mut c_void;
let schema_ptr = Box::into_raw(Box::new(ffi_schema)) as *mut c_void;
paimon_result_next_batch {
batch: paimon_arrow_batch {
array: array_ptr,
schema: schema_ptr,
},
error: std::ptr::null_mut(),
}
}
Some(Err(e)) => paimon_result_next_batch {
batch: paimon_arrow_batch {
array: std::ptr::null_mut(),
schema: std::ptr::null_mut(),
},
error: paimon_error::from_paimon(e),
},
None => paimon_result_next_batch {
batch: paimon_arrow_batch {
array: std::ptr::null_mut(),
schema: std::ptr::null_mut(),
},
error: std::ptr::null_mut(),
},
}
}
/// Free a paimon_record_batch_reader.
///
/// # Safety
/// Only call with a reader returned from `paimon_table_read_to_arrow`.
#[no_mangle]
pub unsafe extern "C" fn paimon_record_batch_reader_free(reader: *mut paimon_record_batch_reader) {
if !reader.is_null() {
let wrapper = Box::from_raw(reader);
if !wrapper.inner.is_null() {
drop(Box::from_raw(wrapper.inner as *mut ArrowRecordBatchStream));
}
}
}
/// Free the ArrowArray and ArrowSchema container structs for a single batch.
///
/// # Safety
/// `batch` must contain valid pointers returned by `paimon_record_batch_reader_next`.
#[no_mangle]
pub unsafe extern "C" fn paimon_arrow_batch_free(batch: paimon_arrow_batch) {
if !batch.array.is_null() {
drop(Box::from_raw(batch.array as *mut FFI_ArrowArray));
}
if !batch.schema.is_null() {
drop(Box::from_raw(batch.schema as *mut FFI_ArrowSchema));
}
}
// ======================= Predicate ===============================
/// Convert a C datum to a Rust Datum.
unsafe fn datum_from_c(d: &paimon_datum) -> Result<Datum, *mut paimon_error> {
match d.tag {
0 => Ok(Datum::Bool(d.int_val != 0)),
1 => Ok(Datum::TinyInt(d.int_val as i8)),
2 => Ok(Datum::SmallInt(d.int_val as i16)),
3 => Ok(Datum::Int(d.int_val as i32)),
4 => Ok(Datum::Long(d.int_val)),
5 => Ok(Datum::Float(d.double_val as f32)),
6 => Ok(Datum::Double(d.double_val)),
7 => {
if d.str_len == 0 {
return Ok(Datum::String(String::new()));
}
if d.str_data.is_null() {
return Err(paimon_error::new(
PaimonErrorCode::InvalidInput,
"null string data in datum with non-zero length".to_string(),
));
}
let bytes = std::slice::from_raw_parts(d.str_data, d.str_len);
let s = std::str::from_utf8(bytes).map_err(|e| {
paimon_error::new(
PaimonErrorCode::InvalidInput,
format!("invalid UTF-8 in datum string: {e}"),
)
})?;
Ok(Datum::String(s.to_string()))
}
8 => Ok(Datum::Date(d.int_val as i32)),
9 => Ok(Datum::Time(d.int_val as i32)),
10 => Ok(Datum::Timestamp {
millis: d.int_val,
nanos: d.int_val2 as i32,
}),
11 => Ok(Datum::LocalZonedTimestamp {
millis: d.int_val,
nanos: d.int_val2 as i32,
}),
12 => {
let unscaled = ((d.int_val2 as i128) << 64) | (d.int_val as u64 as i128);
Ok(Datum::Decimal {
unscaled,
precision: d.uint_val,
scale: d.uint_val2,
})
}
13 => {
if d.str_data.is_null() && d.str_len > 0 {
return Err(paimon_error::new(
PaimonErrorCode::InvalidInput,
"null bytes data in datum".to_string(),
));
}
let bytes = if d.str_len > 0 {
std::slice::from_raw_parts(d.str_data, d.str_len).to_vec()
} else {
Vec::new()
};
Ok(Datum::Bytes(bytes))
}
_ => Err(paimon_error::new(
PaimonErrorCode::InvalidInput,
format!("unknown datum tag: {}", d.tag),
)),
}
}
/// Coerce an integer-family datum to match the target column's integer type.
///
/// FFI callers (e.g. Go) often pass a narrower integer literal (Int) for a
/// wider column (BigInt). This function widens or narrows the datum to match,
/// checking range for narrowing conversions.
///
/// Non-integer datums or non-integer columns are returned as-is.
fn coerce_integer_datum(
datum: Datum,
fields: &[DataField],
column: &str,
) -> Result<Datum, *mut paimon_error> {
let val = match &datum {
Datum::TinyInt(v) => *v as i64,
Datum::SmallInt(v) => *v as i64,
Datum::Int(v) => *v as i64,
Datum::Long(v) => *v,
_ => return Ok(datum),
};
let Some(field) = fields.iter().find(|f| f.name() == column) else {
// Column not found; let PredicateBuilder produce the proper error.
return Ok(datum);
};
match field.data_type() {
DataType::TinyInt(_) if !matches!(datum, Datum::TinyInt(_)) => {
if val < i8::MIN as i64 || val > i8::MAX as i64 {
Err(paimon_error::new(
PaimonErrorCode::InvalidInput,
format!("value {val} out of range for TinyInt column '{column}'"),
))
} else {
Ok(Datum::TinyInt(val as i8))
}
}
DataType::SmallInt(_) if !matches!(datum, Datum::SmallInt(_)) => {
if val < i16::MIN as i64 || val > i16::MAX as i64 {
Err(paimon_error::new(
PaimonErrorCode::InvalidInput,
format!("value {val} out of range for SmallInt column '{column}'"),
))
} else {
Ok(Datum::SmallInt(val as i16))
}
}
DataType::Int(_) if !matches!(datum, Datum::Int(_)) => {
if val < i32::MIN as i64 || val > i32::MAX as i64 {
Err(paimon_error::new(
PaimonErrorCode::InvalidInput,
format!("value {val} out of range for Int column '{column}'"),
))
} else {
Ok(Datum::Int(val as i32))
}
}
DataType::BigInt(_) if !matches!(datum, Datum::Long(_)) => Ok(Datum::Long(val)),
_ => Ok(datum),
}
}
/// Helper to build a leaf predicate that takes a datum, via PredicateBuilder.
unsafe fn build_leaf_predicate_datum(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: &paimon_datum,
build_fn: impl FnOnce(&PredicateBuilder, &str, Datum) -> paimon::Result<Predicate>,
) -> paimon_result_predicate {
if let Err(e) = check_non_null(table, "table") {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
};
}
let col_name = match validate_cstr(column, "column") {
Ok(s) => s,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
let d = match datum_from_c(datum) {
Ok(d) => d,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
let table_ref = &*((*table).inner as *const Table);
let fields = table_ref.schema().fields();
let d = match coerce_integer_datum(d, fields, &col_name) {
Ok(d) => d,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
let pb = PredicateBuilder::new(fields);
match build_fn(&pb, &col_name, d) {
Ok(pred) => {
let inner = Box::into_raw(Box::new(pred)) as *mut c_void;
paimon_result_predicate {
predicate: Box::into_raw(Box::new(paimon_predicate { inner })),
error: std::ptr::null_mut(),
}
}
Err(e) => paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: paimon_error::from_paimon(e),
},
}
}
/// Helper to build a leaf predicate without a datum (IS NULL / IS NOT NULL).
unsafe fn build_leaf_predicate(
table: *const paimon_table,
column: *const std::ffi::c_char,
build_fn: impl FnOnce(&PredicateBuilder, &str) -> paimon::Result<Predicate>,
) -> paimon_result_predicate {
if let Err(e) = check_non_null(table, "table") {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
};
}
let col_name = match validate_cstr(column, "column") {
Ok(s) => s,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
let table_ref = &*((*table).inner as *const Table);
let pb = PredicateBuilder::new(table_ref.schema().fields());
match build_fn(&pb, &col_name) {
Ok(pred) => {
let inner = Box::into_raw(Box::new(pred)) as *mut c_void;
paimon_result_predicate {
predicate: Box::into_raw(Box::new(paimon_predicate { inner })),
error: std::ptr::null_mut(),
}
}
Err(e) => paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: paimon_error::from_paimon(e),
},
}
}
/// Create an equality predicate: `column = datum`.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_equal(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: paimon_datum,
) -> paimon_result_predicate {
build_leaf_predicate_datum(table, column, &datum, |pb, col, d| pb.equal(col, d))
}
/// Create a not-equal predicate: `column != datum`.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_not_equal(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: paimon_datum,
) -> paimon_result_predicate {
build_leaf_predicate_datum(table, column, &datum, |pb, col, d| pb.not_equal(col, d))
}
/// Create a less-than predicate: `column < datum`.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_less_than(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: paimon_datum,
) -> paimon_result_predicate {
build_leaf_predicate_datum(table, column, &datum, |pb, col, d| pb.less_than(col, d))
}
/// Create a less-or-equal predicate: `column <= datum`.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_less_or_equal(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: paimon_datum,
) -> paimon_result_predicate {
build_leaf_predicate_datum(table, column, &datum, |pb, col, d| pb.less_or_equal(col, d))
}
/// Create a greater-than predicate: `column > datum`.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_greater_than(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: paimon_datum,
) -> paimon_result_predicate {
build_leaf_predicate_datum(table, column, &datum, |pb, col, d| pb.greater_than(col, d))
}
/// Create a greater-or-equal predicate: `column >= datum`.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_greater_or_equal(
table: *const paimon_table,
column: *const std::ffi::c_char,
datum: paimon_datum,
) -> paimon_result_predicate {
build_leaf_predicate_datum(table, column, &datum, |pb, col, d| {
pb.greater_or_equal(col, d)
})
}
/// Create an IS NULL predicate.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_is_null(
table: *const paimon_table,
column: *const std::ffi::c_char,
) -> paimon_result_predicate {
build_leaf_predicate(table, column, |pb, col| pb.is_null(col))
}
/// Create an IS NOT NULL predicate.
///
/// # Safety
/// `table` and `column` must be valid pointers.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_is_not_null(
table: *const paimon_table,
column: *const std::ffi::c_char,
) -> paimon_result_predicate {
build_leaf_predicate(table, column, |pb, col| pb.is_not_null(col))
}
/// Create an IN predicate: `column IN (datum1, datum2, ...)`.
///
/// # Safety
/// `table`, `column`, and `datums` must be valid pointers. `datums_len` must be the length.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_is_in(
table: *const paimon_table,
column: *const std::ffi::c_char,
datums: *const paimon_datum,
datums_len: usize,
) -> paimon_result_predicate {
build_leaf_predicate_datums(table, column, datums, datums_len, |pb, col, values| {
pb.is_in(col, values)
})
}
/// Create a NOT IN predicate: `column NOT IN (datum1, datum2, ...)`.
///
/// # Safety
/// `table`, `column`, and `datums` must be valid pointers. `datums_len` must be the length.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_is_not_in(
table: *const paimon_table,
column: *const std::ffi::c_char,
datums: *const paimon_datum,
datums_len: usize,
) -> paimon_result_predicate {
build_leaf_predicate_datums(table, column, datums, datums_len, |pb, col, values| {
pb.is_not_in(col, values)
})
}
/// Helper to build an IN/NOT IN predicate with a datum array.
unsafe fn build_leaf_predicate_datums(
table: *const paimon_table,
column: *const std::ffi::c_char,
datums: *const paimon_datum,
datums_len: usize,
build_fn: impl FnOnce(&PredicateBuilder, &str, Vec<Datum>) -> paimon::Result<Predicate>,
) -> paimon_result_predicate {
if let Err(e) = check_non_null(table, "table") {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
};
}
let col_name = match validate_cstr(column, "column") {
Ok(s) => s,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
if datums.is_null() && datums_len > 0 {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: paimon_error::new(
PaimonErrorCode::InvalidInput,
"null datums pointer with non-zero length".to_string(),
),
};
}
let slice = if datums_len > 0 {
std::slice::from_raw_parts(datums, datums_len)
} else {
&[]
};
let values: Result<Vec<Datum>, _> = slice.iter().map(|d| datum_from_c(d)).collect();
let values = match values {
Ok(v) => v,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
let table_ref = &*((*table).inner as *const Table);
let fields = table_ref.schema().fields();
let values: Result<Vec<Datum>, _> = values
.into_iter()
.map(|d| coerce_integer_datum(d, fields, &col_name))
.collect();
let values = match values {
Ok(v) => v,
Err(e) => {
return paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: e,
}
}
};
let pb = PredicateBuilder::new(fields);
match build_fn(&pb, &col_name, values) {
Ok(pred) => {
let inner = Box::into_raw(Box::new(pred)) as *mut c_void;
paimon_result_predicate {
predicate: Box::into_raw(Box::new(paimon_predicate { inner })),
error: std::ptr::null_mut(),
}
}
Err(e) => paimon_result_predicate {
predicate: std::ptr::null_mut(),
error: paimon_error::from_paimon(e),
},
}
}
/// Combine two predicates with AND. Consumes both inputs.
///
/// # Safety
/// `a` and `b` must be valid pointers from predicate functions.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_and(
a: *mut paimon_predicate,
b: *mut paimon_predicate,
) -> *mut paimon_predicate {
let pred_a = *Box::from_raw(Box::from_raw(a).inner as *mut Predicate);
let pred_b = *Box::from_raw(Box::from_raw(b).inner as *mut Predicate);
let combined = Predicate::and(vec![pred_a, pred_b]);
let inner = Box::into_raw(Box::new(combined)) as *mut c_void;
Box::into_raw(Box::new(paimon_predicate { inner }))
}
/// Combine two predicates with OR. Consumes both inputs.
///
/// # Safety
/// `a` and `b` must be valid pointers from predicate functions.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_or(
a: *mut paimon_predicate,
b: *mut paimon_predicate,
) -> *mut paimon_predicate {
let pred_a = *Box::from_raw(Box::from_raw(a).inner as *mut Predicate);
let pred_b = *Box::from_raw(Box::from_raw(b).inner as *mut Predicate);
let combined = Predicate::or(vec![pred_a, pred_b]);
let inner = Box::into_raw(Box::new(combined)) as *mut c_void;
Box::into_raw(Box::new(paimon_predicate { inner }))
}
/// Negate a predicate with NOT. Consumes the input.
///
/// # Safety
/// `p` must be a valid pointer from a predicate function.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_not(p: *mut paimon_predicate) -> *mut paimon_predicate {
let pred = *Box::from_raw(Box::from_raw(p).inner as *mut Predicate);
let negated = Predicate::negate(pred);
let inner = Box::into_raw(Box::new(negated)) as *mut c_void;
Box::into_raw(Box::new(paimon_predicate { inner }))
}
/// Free a paimon_predicate.
///
/// # Safety
/// Only call with a predicate returned from paimon predicate functions.
#[no_mangle]
pub unsafe extern "C" fn paimon_predicate_free(p: *mut paimon_predicate) {
if !p.is_null() {
let wrapper = Box::from_raw(p);
if !wrapper.inner.is_null() {
drop(Box::from_raw(wrapper.inner as *mut Predicate));
}
}
}