| // 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. |
| |
| #![allow(clippy::missing_safety_doc)] |
| |
| use paimon_vindex_core::distance::MetricType; |
| use paimon_vindex_core::index::{ |
| VectorIndexConfig, VectorIndexMetadata, VectorIndexReader, VectorIndexTrainer, |
| VectorIndexTraining, VectorIndexWriter, VectorSearchParams, |
| }; |
| use paimon_vindex_core::io::{ReadRequest, SeekRead, SeekWrite}; |
| use std::cell::RefCell; |
| use std::collections::HashMap; |
| use std::ffi::{CStr, CString}; |
| use std::io; |
| use std::os::raw::{c_char, c_int, c_void}; |
| use std::panic::{self, AssertUnwindSafe}; |
| use std::{ptr, slice}; |
| |
| pub const PAIMON_VINDEX_INDEX_TYPE_IVF_FLAT: u32 = 0; |
| pub const PAIMON_VINDEX_INDEX_TYPE_IVF_PQ: u32 = 1; |
| pub const PAIMON_VINDEX_INDEX_TYPE_IVF_HNSW_FLAT: u32 = 2; |
| pub const PAIMON_VINDEX_INDEX_TYPE_IVF_HNSW_SQ: u32 = 3; |
| pub const PAIMON_VINDEX_INDEX_TYPE_IVF_RQ: u32 = 4; |
| |
| pub const PAIMON_VINDEX_METRIC_L2: u32 = 0; |
| pub const PAIMON_VINDEX_METRIC_INNER_PRODUCT: u32 = 1; |
| pub const PAIMON_VINDEX_METRIC_COSINE: u32 = 2; |
| |
| thread_local! { |
| static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) }; |
| } |
| |
| fn set_error(msg: impl Into<String>) { |
| let msg = msg.into().replace('\0', "\\0"); |
| LAST_ERROR.with(|e| { |
| *e.borrow_mut() = CString::new(msg).ok(); |
| }); |
| } |
| |
| fn panic_message(e: &Box<dyn std::any::Any + Send>) -> String { |
| if let Some(s) = e.downcast_ref::<String>() { |
| format!("native panic: {}", s) |
| } else if let Some(s) = e.downcast_ref::<&str>() { |
| format!("native panic: {}", s) |
| } else { |
| "native panic: unknown".to_string() |
| } |
| } |
| |
| fn ffi_status<F>(f: F) -> c_int |
| where |
| F: FnOnce() -> Result<(), String>, |
| { |
| match panic::catch_unwind(AssertUnwindSafe(f)) { |
| Ok(Ok(())) => 0, |
| Ok(Err(e)) => { |
| set_error(e); |
| -1 |
| } |
| Err(e) => { |
| set_error(panic_message(&e)); |
| -1 |
| } |
| } |
| } |
| |
| fn ffi_ptr<T, F>(f: F) -> *mut T |
| where |
| F: FnOnce() -> Result<*mut T, String>, |
| { |
| match panic::catch_unwind(AssertUnwindSafe(f)) { |
| Ok(Ok(value)) => value, |
| Ok(Err(e)) => { |
| set_error(e); |
| ptr::null_mut() |
| } |
| Err(e) => { |
| set_error(panic_message(&e)); |
| ptr::null_mut() |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub extern "C" fn paimon_vindex_last_error() -> *const c_char { |
| LAST_ERROR.with(|e| match &*e.borrow() { |
| Some(msg) => msg.as_ptr(), |
| None => ptr::null(), |
| }) |
| } |
| |
| // ======================== IO callbacks ======================== |
| |
| #[repr(C)] |
| pub struct PaimonVindexOutputFile { |
| pub ctx: *mut c_void, |
| pub write_fn: Option<unsafe extern "C" fn(*mut c_void, *const u8, usize) -> c_int>, |
| pub flush_fn: Option<unsafe extern "C" fn(*mut c_void) -> c_int>, |
| pub get_pos_fn: Option<unsafe extern "C" fn(*mut c_void) -> i64>, |
| } |
| |
| struct FfiOutputFile { |
| raw: PaimonVindexOutputFile, |
| pos: u64, |
| } |
| |
| unsafe impl Send for FfiOutputFile {} |
| |
| impl FfiOutputFile { |
| fn flush(&mut self) -> io::Result<()> { |
| if let Some(flush_fn) = self.raw.flush_fn { |
| let result = unsafe { flush_fn(self.raw.ctx) }; |
| if result != 0 { |
| return Err(io::Error::other("flush callback failed")); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| impl SeekWrite for FfiOutputFile { |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| if let Some(write_fn) = self.raw.write_fn { |
| let result = unsafe { write_fn(self.raw.ctx, buf.as_ptr(), buf.len()) }; |
| if result != 0 { |
| return Err(io::Error::other("write callback failed")); |
| } |
| self.pos = self |
| .pos |
| .checked_add(buf.len() as u64) |
| .ok_or_else(|| io::Error::other("output position overflow"))?; |
| Ok(()) |
| } else { |
| Err(io::Error::other("write_fn is null")) |
| } |
| } |
| |
| fn pos(&self) -> u64 { |
| if let Some(get_pos_fn) = self.raw.get_pos_fn { |
| let pos = unsafe { get_pos_fn(self.raw.ctx) }; |
| if pos >= 0 { |
| return pos as u64; |
| } |
| } |
| self.pos |
| } |
| } |
| |
| #[repr(C)] |
| pub struct PaimonVindexInputFile { |
| pub ctx: *mut c_void, |
| pub read_at_fn: Option<unsafe extern "C" fn(*mut c_void, u64, *mut u8, usize) -> c_int>, |
| } |
| |
| struct FfiInputFile { |
| raw: PaimonVindexInputFile, |
| } |
| |
| unsafe impl Send for FfiInputFile {} |
| |
| impl SeekRead for FfiInputFile { |
| fn pread(&mut self, ranges: &mut [ReadRequest<'_>]) -> io::Result<()> { |
| if let Some(read_at_fn) = self.raw.read_at_fn { |
| for range in ranges { |
| let result = unsafe { |
| read_at_fn( |
| self.raw.ctx, |
| range.pos, |
| range.buf.as_mut_ptr(), |
| range.buf.len(), |
| ) |
| }; |
| if result != 0 { |
| return Err(io::Error::other(format!( |
| "read_at callback failed at offset {} length {}", |
| range.pos, |
| range.buf.len() |
| ))); |
| } |
| } |
| Ok(()) |
| } else { |
| Err(io::Error::other("read_at_fn is null")) |
| } |
| } |
| } |
| |
| // ======================== Common structs ======================== |
| |
| #[repr(C)] |
| pub struct PaimonVindexMetadata { |
| pub index_type: u32, |
| pub dimension: usize, |
| pub nlist: usize, |
| pub metric: u32, |
| pub total_vectors: i64, |
| pub pq_m: usize, |
| pub hnsw_m: usize, |
| pub hnsw_ef_construction: usize, |
| pub hnsw_max_level: usize, |
| } |
| |
| #[repr(C)] |
| #[derive(Clone, Copy)] |
| pub struct PaimonVindexSearchParams { |
| pub top_k: usize, |
| pub nprobe: usize, |
| pub ef_search: usize, |
| pub query_bits: usize, |
| } |
| |
| pub struct PaimonVindexTrainerHandle { |
| inner: Option<VectorIndexTrainer>, |
| } |
| |
| pub struct PaimonVindexTrainingHandle { |
| inner: Option<VectorIndexTraining>, |
| } |
| |
| pub struct PaimonVindexWriterHandle { |
| inner: VectorIndexWriter, |
| } |
| |
| pub struct PaimonVindexReaderHandle { |
| inner: VectorIndexReader<FfiInputFile>, |
| } |
| |
| fn metadata_to_ffi(metadata: VectorIndexMetadata) -> PaimonVindexMetadata { |
| let (hnsw_m, hnsw_ef_construction, hnsw_max_level) = metadata |
| .hnsw |
| .map(|h| (h.m, h.ef_construction, h.max_level)) |
| .unwrap_or((0, 0, 0)); |
| PaimonVindexMetadata { |
| index_type: metadata.index_type as u32, |
| dimension: metadata.dimension, |
| nlist: metadata.nlist, |
| metric: metric_code(metadata.metric), |
| total_vectors: metadata.total_vectors, |
| pq_m: metadata.pq_m.unwrap_or(0), |
| hnsw_m, |
| hnsw_ef_construction, |
| hnsw_max_level, |
| } |
| } |
| |
| fn metric_code(metric: MetricType) -> u32 { |
| match metric { |
| MetricType::L2 => PAIMON_VINDEX_METRIC_L2, |
| MetricType::InnerProduct => PAIMON_VINDEX_METRIC_INNER_PRODUCT, |
| MetricType::Cosine => PAIMON_VINDEX_METRIC_COSINE, |
| } |
| } |
| |
| unsafe fn options_from_raw( |
| keys: *const *const c_char, |
| values: *const *const c_char, |
| len: usize, |
| ) -> Result<HashMap<String, String>, String> { |
| if len > 0 && (keys.is_null() || values.is_null()) { |
| return Err("option keys or values pointer is null".to_string()); |
| } |
| let key_ptrs = if len == 0 { |
| &[] |
| } else { |
| unsafe { slice::from_raw_parts(keys, len) } |
| }; |
| let value_ptrs = if len == 0 { |
| &[] |
| } else { |
| unsafe { slice::from_raw_parts(values, len) } |
| }; |
| let mut options = HashMap::with_capacity(len); |
| for idx in 0..len { |
| let key_ptr = key_ptrs[idx]; |
| let value_ptr = value_ptrs[idx]; |
| if key_ptr.is_null() { |
| return Err(format!("option key {} is null", idx)); |
| } |
| if value_ptr.is_null() { |
| return Err(format!("option value {} is null", idx)); |
| } |
| let key = unsafe { CStr::from_ptr(key_ptr) } |
| .to_str() |
| .map_err(|_| format!("option key {} contains invalid UTF-8", idx))? |
| .to_string(); |
| let value = unsafe { CStr::from_ptr(value_ptr) } |
| .to_str() |
| .map_err(|_| format!("option value {} contains invalid UTF-8", idx))? |
| .to_string(); |
| if options.insert(key.clone(), value).is_some() { |
| return Err(format!("duplicate option key '{}'", key)); |
| } |
| } |
| Ok(options) |
| } |
| |
| unsafe fn writer_mut<'a>( |
| handle: *mut PaimonVindexWriterHandle, |
| ) -> Result<&'a mut PaimonVindexWriterHandle, String> { |
| if handle.is_null() { |
| Err("null writer handle".to_string()) |
| } else { |
| Ok(unsafe { &mut *handle }) |
| } |
| } |
| |
| unsafe fn trainer_mut<'a>( |
| handle: *mut PaimonVindexTrainerHandle, |
| ) -> Result<&'a mut PaimonVindexTrainerHandle, String> { |
| if handle.is_null() { |
| Err("null trainer handle".to_string()) |
| } else { |
| Ok(unsafe { &mut *handle }) |
| } |
| } |
| |
| unsafe fn trainer_ref<'a>( |
| handle: *const PaimonVindexTrainerHandle, |
| ) -> Result<&'a PaimonVindexTrainerHandle, String> { |
| if handle.is_null() { |
| Err("null trainer handle".to_string()) |
| } else { |
| Ok(unsafe { &*handle }) |
| } |
| } |
| |
| unsafe fn training_mut<'a>( |
| handle: *mut PaimonVindexTrainingHandle, |
| ) -> Result<&'a mut PaimonVindexTrainingHandle, String> { |
| if handle.is_null() { |
| Err("null training handle".to_string()) |
| } else { |
| Ok(unsafe { &mut *handle }) |
| } |
| } |
| |
| unsafe fn reader_mut<'a>( |
| handle: *mut PaimonVindexReaderHandle, |
| ) -> Result<&'a mut PaimonVindexReaderHandle, String> { |
| if handle.is_null() { |
| Err("null reader handle".to_string()) |
| } else { |
| Ok(unsafe { &mut *handle }) |
| } |
| } |
| |
| unsafe fn reader_ref<'a>( |
| handle: *const PaimonVindexReaderHandle, |
| ) -> Result<&'a PaimonVindexReaderHandle, String> { |
| if handle.is_null() { |
| Err("null reader handle".to_string()) |
| } else { |
| Ok(unsafe { &*handle }) |
| } |
| } |
| |
| unsafe fn writer_ref<'a>( |
| handle: *const PaimonVindexWriterHandle, |
| ) -> Result<&'a PaimonVindexWriterHandle, String> { |
| if handle.is_null() { |
| Err("null writer handle".to_string()) |
| } else { |
| Ok(unsafe { &*handle }) |
| } |
| } |
| |
| fn checked_len(a: usize, b: usize, name: &str) -> Result<usize, String> { |
| a.checked_mul(b) |
| .ok_or_else(|| format!("{} length overflow", name)) |
| } |
| |
| unsafe fn const_slice<'a, T>(ptr: *const T, len: usize, name: &str) -> Result<&'a [T], String> { |
| if len == 0 { |
| Ok(&[]) |
| } else if ptr.is_null() { |
| Err(format!("{} pointer is null", name)) |
| } else { |
| Ok(unsafe { slice::from_raw_parts(ptr, len) }) |
| } |
| } |
| |
| unsafe fn mut_slice<'a, T>(ptr: *mut T, len: usize, name: &str) -> Result<&'a mut [T], String> { |
| if len == 0 { |
| Ok(&mut []) |
| } else if ptr.is_null() { |
| Err(format!("{} pointer is null", name)) |
| } else { |
| Ok(unsafe { slice::from_raw_parts_mut(ptr, len) }) |
| } |
| } |
| |
| fn copy_search_result( |
| ids: &[i64], |
| distances: &[f32], |
| out_ids: *mut i64, |
| out_distances: *mut f32, |
| result_len: usize, |
| expected_len: usize, |
| ) -> Result<(), String> { |
| if ids.len() != expected_len || distances.len() != expected_len { |
| return Err(format!( |
| "native result length mismatch: ids={}, distances={}, expected={}", |
| ids.len(), |
| distances.len(), |
| expected_len |
| )); |
| } |
| if result_len < expected_len { |
| return Err(format!( |
| "result buffers length {} is smaller than required {}", |
| result_len, expected_len |
| )); |
| } |
| let out_ids = unsafe { mut_slice(out_ids, expected_len, "out_ids") }?; |
| let out_distances = unsafe { mut_slice(out_distances, expected_len, "out_distances") }?; |
| out_ids.copy_from_slice(ids); |
| out_distances.copy_from_slice(distances); |
| Ok(()) |
| } |
| |
| fn search_params_from_ffi(params: PaimonVindexSearchParams) -> VectorSearchParams { |
| VectorSearchParams { |
| top_k: params.top_k, |
| nprobe: params.nprobe, |
| ef_search: params.ef_search, |
| query_bits: params.query_bits, |
| } |
| } |
| |
| // ======================== Trainer / Writer ======================== |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_trainer_open( |
| keys: *const *const c_char, |
| values: *const *const c_char, |
| num_options: usize, |
| ) -> *mut PaimonVindexTrainerHandle { |
| ffi_ptr(|| { |
| let options = unsafe { options_from_raw(keys, values, num_options) }?; |
| let config = VectorIndexConfig::from_options(&options) |
| .map_err(|e| format!("invalid vector index options: {}", e))?; |
| let trainer = |
| VectorIndexTrainer::new(config).map_err(|e| format!("create trainer: {}", e))?; |
| Ok(Box::into_raw(Box::new(PaimonVindexTrainerHandle { |
| inner: Some(trainer), |
| }))) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_trainer_free(handle: *mut PaimonVindexTrainerHandle) { |
| if !handle.is_null() { |
| unsafe { |
| drop(Box::from_raw(handle)); |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_trainer_dimension( |
| handle: *const PaimonVindexTrainerHandle, |
| out: *mut usize, |
| ) -> c_int { |
| ffi_status(|| { |
| if out.is_null() { |
| return Err("out pointer is null".to_string()); |
| } |
| let handle = unsafe { trainer_ref(handle) }?; |
| let trainer = handle |
| .inner |
| .as_ref() |
| .ok_or_else(|| "trainer has already finished".to_string())?; |
| unsafe { |
| *out = trainer.dimension(); |
| } |
| Ok(()) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_trainer_add_training_vectors( |
| handle: *mut PaimonVindexTrainerHandle, |
| data: *const f32, |
| vector_count: usize, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { trainer_mut(handle) }?; |
| let trainer = handle |
| .inner |
| .as_mut() |
| .ok_or_else(|| "trainer has already finished".to_string())?; |
| let len = checked_len(vector_count, trainer.dimension(), "training data")?; |
| let data = unsafe { const_slice(data, len, "data") }?; |
| trainer |
| .add_training_vectors_mut(data, vector_count) |
| .map(|_| ()) |
| .map_err(|e| format!("add training vectors: {}", e)) |
| }) |
| } |
| |
| /// Finishes training and consumes the trainer's internal state, but does not free `handle`. |
| /// Callers must still call `paimon_vindex_trainer_free(handle)` after this returns. |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_trainer_finish( |
| handle: *mut PaimonVindexTrainerHandle, |
| ) -> *mut PaimonVindexTrainingHandle { |
| ffi_ptr(|| { |
| let handle = unsafe { trainer_mut(handle) }?; |
| let trainer = handle |
| .inner |
| .take() |
| .ok_or_else(|| "trainer has already finished".to_string())?; |
| let training = trainer |
| .finish() |
| .map_err(|e| format!("finish training: {}", e))?; |
| Ok(Box::into_raw(Box::new(PaimonVindexTrainingHandle { |
| inner: Some(training), |
| }))) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_training_free(handle: *mut PaimonVindexTrainingHandle) { |
| if !handle.is_null() { |
| unsafe { |
| drop(Box::from_raw(handle)); |
| } |
| } |
| } |
| |
| /// Opens a writer by consuming the training state inside `training`, but does not free the handle. |
| /// Callers must still call `paimon_vindex_training_free(training)` after this returns. |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_writer_open( |
| training: *mut PaimonVindexTrainingHandle, |
| ) -> *mut PaimonVindexWriterHandle { |
| ffi_ptr(|| { |
| let training = unsafe { training_mut(training) }?; |
| let training = training |
| .inner |
| .take() |
| .ok_or_else(|| "training has already been consumed".to_string())?; |
| Ok(Box::into_raw(Box::new(PaimonVindexWriterHandle { |
| inner: VectorIndexWriter::new(training), |
| }))) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_writer_free(handle: *mut PaimonVindexWriterHandle) { |
| if !handle.is_null() { |
| unsafe { |
| drop(Box::from_raw(handle)); |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_writer_dimension( |
| handle: *const PaimonVindexWriterHandle, |
| out: *mut usize, |
| ) -> c_int { |
| ffi_status(|| { |
| if out.is_null() { |
| return Err("out pointer is null".to_string()); |
| } |
| let handle = unsafe { writer_ref(handle) }?; |
| unsafe { |
| *out = handle.inner.dimension(); |
| } |
| Ok(()) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_writer_add_vectors( |
| handle: *mut PaimonVindexWriterHandle, |
| ids: *const i64, |
| data: *const f32, |
| vector_count: usize, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { writer_mut(handle) }?; |
| let len = checked_len(vector_count, handle.inner.dimension(), "vector data")?; |
| let ids = unsafe { const_slice(ids, vector_count, "ids") }?; |
| let data = unsafe { const_slice(data, len, "data") }?; |
| handle |
| .inner |
| .add_vectors(ids, data, vector_count) |
| .map_err(|e| format!("add_vectors: {}", e)) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_writer_write_index( |
| handle: *mut PaimonVindexWriterHandle, |
| output_file: PaimonVindexOutputFile, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { writer_mut(handle) }?; |
| let mut output = FfiOutputFile { |
| raw: output_file, |
| pos: 0, |
| }; |
| handle |
| .inner |
| .write(&mut output) |
| .map_err(|e| format!("write index: {}", e))?; |
| output.flush().map_err(|e| format!("flush index: {}", e)) |
| }) |
| } |
| |
| // ======================== Reader ======================== |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_open( |
| input_file: PaimonVindexInputFile, |
| ) -> *mut PaimonVindexReaderHandle { |
| ffi_ptr(|| { |
| let input = FfiInputFile { raw: input_file }; |
| let reader = VectorIndexReader::open(input).map_err(|e| format!("open reader: {}", e))?; |
| Ok(Box::into_raw(Box::new(PaimonVindexReaderHandle { |
| inner: reader, |
| }))) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_free(handle: *mut PaimonVindexReaderHandle) { |
| if !handle.is_null() { |
| unsafe { |
| drop(Box::from_raw(handle)); |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_metadata( |
| handle: *const PaimonVindexReaderHandle, |
| out: *mut PaimonVindexMetadata, |
| ) -> c_int { |
| ffi_status(|| { |
| if out.is_null() { |
| return Err("out pointer is null".to_string()); |
| } |
| let handle = unsafe { reader_ref(handle) }?; |
| unsafe { |
| *out = metadata_to_ffi(handle.inner.metadata()); |
| } |
| Ok(()) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_optimize_for_search( |
| handle: *mut PaimonVindexReaderHandle, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { reader_mut(handle) }?; |
| handle |
| .inner |
| .optimize_for_search() |
| .map_err(|e| format!("optimize_for_search: {}", e)) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_search( |
| handle: *mut PaimonVindexReaderHandle, |
| query: *const f32, |
| params: PaimonVindexSearchParams, |
| out_ids: *mut i64, |
| out_distances: *mut f32, |
| result_len: usize, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { reader_mut(handle) }?; |
| let query = unsafe { const_slice(query, handle.inner.dimension(), "query") }?; |
| let params = search_params_from_ffi(params); |
| let (ids, distances) = handle |
| .inner |
| .search(query, params) |
| .map_err(|e| format!("search: {}", e))?; |
| copy_search_result( |
| &ids, |
| &distances, |
| out_ids, |
| out_distances, |
| result_len, |
| params.top_k, |
| ) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_search_with_roaring_filter( |
| handle: *mut PaimonVindexReaderHandle, |
| query: *const f32, |
| params: PaimonVindexSearchParams, |
| roaring_filter: *const u8, |
| roaring_filter_len: usize, |
| out_ids: *mut i64, |
| out_distances: *mut f32, |
| result_len: usize, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { reader_mut(handle) }?; |
| let query = unsafe { const_slice(query, handle.inner.dimension(), "query") }?; |
| let filter = unsafe { const_slice(roaring_filter, roaring_filter_len, "roaring_filter") }?; |
| let params = search_params_from_ffi(params); |
| let (ids, distances) = handle |
| .inner |
| .search_with_roaring_filter(query, params, filter) |
| .map_err(|e| format!("search_with_roaring_filter: {}", e))?; |
| copy_search_result( |
| &ids, |
| &distances, |
| out_ids, |
| out_distances, |
| result_len, |
| params.top_k, |
| ) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_search_batch( |
| handle: *mut PaimonVindexReaderHandle, |
| queries: *const f32, |
| query_count: usize, |
| params: PaimonVindexSearchParams, |
| out_ids: *mut i64, |
| out_distances: *mut f32, |
| result_len: usize, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { reader_mut(handle) }?; |
| let query_len = checked_len(query_count, handle.inner.dimension(), "queries")?; |
| let queries = unsafe { const_slice(queries, query_len, "queries") }?; |
| let params = search_params_from_ffi(params); |
| let expected_len = checked_len(query_count, params.top_k, "batch result")?; |
| let (ids, distances) = handle |
| .inner |
| .search_batch(queries, query_count, params) |
| .map_err(|e| format!("search_batch: {}", e))?; |
| copy_search_result( |
| &ids, |
| &distances, |
| out_ids, |
| out_distances, |
| result_len, |
| expected_len, |
| ) |
| }) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_vindex_reader_search_batch_with_roaring_filter( |
| handle: *mut PaimonVindexReaderHandle, |
| queries: *const f32, |
| query_count: usize, |
| params: PaimonVindexSearchParams, |
| roaring_filter: *const u8, |
| roaring_filter_len: usize, |
| out_ids: *mut i64, |
| out_distances: *mut f32, |
| result_len: usize, |
| ) -> c_int { |
| ffi_status(|| { |
| let handle = unsafe { reader_mut(handle) }?; |
| let query_len = checked_len(query_count, handle.inner.dimension(), "queries")?; |
| let queries = unsafe { const_slice(queries, query_len, "queries") }?; |
| let filter = unsafe { const_slice(roaring_filter, roaring_filter_len, "roaring_filter") }?; |
| let params = search_params_from_ffi(params); |
| let expected_len = checked_len(query_count, params.top_k, "batch result")?; |
| let (ids, distances) = handle |
| .inner |
| .search_batch_with_roaring_filter(queries, query_count, params, filter) |
| .map_err(|e| format!("search_batch_with_roaring_filter: {}", e))?; |
| copy_search_result( |
| &ids, |
| &distances, |
| out_ids, |
| out_distances, |
| result_len, |
| expected_len, |
| ) |
| }) |
| } |