From 9fc96186e84e2fd6cfe89710b71ddcfe42a5c64f Mon Sep 17 00:00:00 2001 From: QuakeWang Date: Fri, 4 Sep 2026 15:33:25 +0800 Subject: [PATCH] fix(c): align append vector filter with core behavior The C binding test still expected data-evolution vector filters to be rejected after the core added scalar pre-filter support. Update the API contract and verify filtered append-table reads against the Rust core result. Signed-off-by: QuakeWang --- bindings/c/src/tests.rs | 54 +++++++++++++++++++++------------ bindings/c/src/vector_search.rs | 5 ++- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/bindings/c/src/tests.rs b/bindings/c/src/tests.rs index 31b62de27..6cf7bdb4a 100644 --- a/bindings/c/src/tests.rs +++ b/bindings/c/src/tests.rs @@ -3263,32 +3263,48 @@ fn vector_search_pk_filter_excludes_neighbor() { } #[test] -fn vector_search_append_filter_returns_invalid_input() { - let path = "memory:/vsearch_append_filter_err"; +fn vector_search_append_filter_matches_rust() { + let path = "memory:/vsearch_append_filter_read"; let table = build_append_vector_table(path); + let query = [1.0f32, 0.0]; + let limit = 3; + + let unfiltered = rust_execute_read_pairs(&table, "embedding", query.to_vec(), limit, None); + assert!( + unfiltered.iter().any(|(id, _)| *id == 0), + "fixture must include the filtered nearest neighbor in the unfiltered Top-K" + ); + + let rust_filter = PredicateBuilder::new(table.schema().fields()) + .greater_or_equal("id", Datum::Int(1)) + .unwrap(); + let rust_pairs = rust_execute_read_pairs( + &table, + "embedding", + query.to_vec(), + limit, + Some(rust_filter), + ); + assert_eq!( + rust_pairs.len(), + limit, + "filter-before-Top-K must refill the result after excluding id 0" + ); + assert!( + rust_pairs.iter().all(|(id, _)| *id >= 1), + "Rust core returned a row excluded by the filter" + ); + let handle = unsafe { wrap_table(table) }; unsafe { let predicate = build_predicate_ge(handle, "id", 1); - let builder = c_vector_builder(handle, "embedding", &[1.0f32, 0.0], 3, predicate); - let result = paimon_vector_search_builder_execute_read(builder); - paimon_vector_search_builder_free(builder); + let builder = c_vector_builder(handle, "embedding", &query, limit, predicate); + let c_pairs = c_execute_read_pairs(builder); - assert!( - result.reader.is_null(), - "errored read must not yield a reader" - ); - assert!(!result.error.is_null(), "DE filter must fail loud"); assert_eq!( - (*result.error).code, - PaimonErrorCode::InvalidInput as i32, - "DE filter error must map to InvalidInput" - ); - let message = error_message(result.error); - assert!( - message.contains("primary-key vector path"), - "unexpected error message: {message}" + c_pairs, rust_pairs, + "filtered C pairs must match the Rust core reference" ); - paimon_error_free(result.error); unwrap_table(handle); } } diff --git a/bindings/c/src/vector_search.rs b/bindings/c/src/vector_search.rs index 80d79efed..f63e152d0 100644 --- a/bindings/c/src/vector_search.rs +++ b/bindings/c/src/vector_search.rs @@ -185,7 +185,10 @@ pub unsafe extern "C" fn paimon_vector_search_builder_with_options( std::ptr::null_mut() } -/// Set an optional scalar residual filter for a vector-search builder. +/// Set an optional scalar predicate applied before vector Top-K. +/// +/// The Rust core resolves the predicate to an allow-list for the selected +/// primary-key or data-evolution/global-index search path. /// /// The predicate is consumed (ownership transferred to the builder). Pass null /// to clear any previously set filter.