mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-22 14:08:21 +00:00
fix: accept non-null Function inputs for nullable parameters (#4006)
Remote Function bindings can expose a nullable parameter schema even when the source table column is non-nullable. Binding validation rebuilt the exact input schema from table nullability and rejected this safe widening. Accept non-null table columns for nullable Function parameters while continuing to reject nullable table columns for non-null parameters. All other input schema fields remain exact, including named multi-input ordering, names, types, and metadata.
This commit is contained in:
@@ -651,17 +651,20 @@ fn ensure_binding_matches_schema(schema: &ArrowSchema, binding: &FunctionBinding
|
||||
input.field_path
|
||||
)));
|
||||
}
|
||||
if field.is_nullable() != input.nullable {
|
||||
// A non-null source is within a nullable parameter's domain. The
|
||||
// reverse can pass nulls to a Function that does not accept them.
|
||||
if field.is_nullable() && !input.nullable {
|
||||
return Err(invalid_function(format!(
|
||||
"Function input '{}' no longer matches binding '{}'",
|
||||
"Function input column '{}' is nullable, but parameter '{}' in binding '{}' is non-nullable",
|
||||
input.field_path,
|
||||
input.parameter,
|
||||
binding.binding_id()
|
||||
)));
|
||||
}
|
||||
let parameter_field = ArrowField::new(
|
||||
input.parameter.clone(),
|
||||
field.data_type().clone(),
|
||||
field.is_nullable(),
|
||||
input.nullable,
|
||||
)
|
||||
.with_metadata(field.metadata().clone());
|
||||
let json = lance_namespace::schema::arrow_schema_to_json(&ArrowSchema::new(vec![
|
||||
@@ -2210,6 +2213,47 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn function_binding_schema(title_nullable: bool, body_nullable: bool) -> ArrowSchema {
|
||||
ArrowSchema::new(vec![
|
||||
ArrowField::new("title", DataType::Utf8, title_nullable),
|
||||
ArrowField::new("body", DataType::Utf8, body_nullable),
|
||||
ArrowField::new("search_text", DataType::Utf8, true),
|
||||
ArrowField::new("search_token_count", DataType::Int64, true),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_nullable_function_inputs_can_bind_to_nullable_parameters() {
|
||||
let binding = FunctionBinding::from_json(include_str!(
|
||||
"../../tests/fixtures/first_class_functions/v1/remote_function_binding.json"
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
ensure_binding_matches_schema(&function_binding_schema(false, false), &binding).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nullable_function_input_cannot_bind_to_non_nullable_parameter() {
|
||||
let mut raw_binding: Value = serde_json::from_str(include_str!(
|
||||
"../../tests/fixtures/first_class_functions/v1/remote_function_binding.json"
|
||||
))
|
||||
.unwrap();
|
||||
raw_binding["inputs"][0]["nullable"] = Value::Bool(false);
|
||||
raw_binding["input_schema"]["fields"][0]["nullable"] = Value::Bool(false);
|
||||
let binding: FunctionBinding = serde_json::from_value(raw_binding).unwrap();
|
||||
|
||||
let err = ensure_binding_matches_schema(&function_binding_schema(true, false), &binding)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(&err, Error::InvalidInput { message }
|
||||
if message.contains("input column 'title' is nullable")
|
||||
&& message.contains("parameter 'title'")
|
||||
&& message.contains("binding 'fb_01K3TEXT'")
|
||||
&& message.contains("non-nullable")),
|
||||
"{err:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_binding_metadata_survives_schema_round_trip() {
|
||||
let binding = FunctionBinding::from_json(include_str!(
|
||||
|
||||
Reference in New Issue
Block a user