Files
lancedb/rust/lancedb/tests/first_class_function_slice1.rs
T
3b37ea2c7a feat: represent registered Functions by OCI image identity (#4176)
Function versions identify independent Function objects and their
numeric revisions. Rust and Python expose the object ID, location,
canonical decimal version, metadata, and availability separately from
the OCI image digest. Computed-column applications carry the complete
object reference, so existing bindings retain their identity after a
name is removed and reused.

Source authoring keeps the existing
create_function/create_function_async, job wait, and column-binding
APIs. The server coordinates baking followed by registration; users do
not have to manage manifest digests to create a Function. The previous
stored Function representation is intentionally unsupported. Existing
contract tests and shared wire fixtures are migrated to the new model.

This SDK change accompanies the final integration layer
https://github.com/lancedb/sophon/pull/7887 in the Sophon stack:
https://github.com/lancedb/sophon/pull/7885https://github.com/lancedb/sophon/pull/7886https://github.com/lancedb/sophon/pull/7887. A metadata-only revision
can retain the same executable image; Function version numbers must not
be used as image cache keys.

---------

Co-authored-by: lancedb automation <robot@lancedb.com>
Co-authored-by: Yang Cen <bubble-cal@outlook.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 12:38:59 +08:00

153 lines
5.6 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
use std::fs;
use std::path::PathBuf;
use lancedb::function::{
FunctionApplication, FunctionBinding, FunctionVersion, RefreshColumnResult,
};
use serde_json::Value;
fn fixture(name: &str) -> String {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/first_class_functions/v1")
.join(name);
fs::read_to_string(path).expect("fixture must be readable")
}
fn job_result(name: &str) -> Value {
serde_json::from_str::<Value>(&fixture(name)).expect("remote Job fixture")["result"].clone()
}
#[test]
fn function_version_job_result_matches_shared_canonical_golden() {
let result = job_result("remote_function_job.json");
let version = FunctionVersion::from_json(&result.to_string()).expect("FunctionVersion result");
assert_eq!(version.name(), "embed");
assert_eq!(version.version(), "1");
assert_ne!(version.image().manifest_digest, version.version());
assert_eq!(
version.to_canonical_json().expect("canonical JSON"),
fixture("remote_function_version.canonical.json").trim()
);
}
#[test]
fn version_identity_is_immutable_and_exact() {
let original = job_result("remote_function_job.json");
let version =
FunctionVersion::from_json(&original.to_string()).expect("FunctionVersion result");
let reopened = version.clone();
assert_eq!(reopened, version);
assert_eq!(reopened.name(), version.name());
assert_eq!(reopened.version(), version.version());
let mut changed = original;
changed["version"] = Value::String("2".to_string());
let changed = FunctionVersion::from_json(&changed.to_string()).expect("changed version");
assert_ne!(changed, version);
assert_eq!(changed.image(), version.image());
for invalid in [
version.image().manifest_digest.as_str(),
"0",
"01",
"-1",
"18446744073709551616",
] {
let mut value = serde_json::to_value(&version).unwrap();
value["version"] = Value::String(invalid.into());
assert!(FunctionVersion::from_json(&value.to_string()).is_err());
}
}
#[test]
fn application_and_binding_match_shared_remote_goldens() {
let application = FunctionApplication::from_json(&fixture("remote_function_application.json"))
.expect("application fixture");
assert_eq!(application.function().version, "1");
assert_eq!(application.output().kind, "named_struct");
assert_eq!(application.inputs().len(), 2);
assert_eq!(
application.to_canonical_json().expect("canonical JSON"),
fixture("remote_function_application.canonical.json").trim()
);
let binding = FunctionBinding::from_json(&fixture("remote_function_binding.json"))
.expect("binding fixture");
assert_eq!(binding.function().version, "1");
assert_eq!(binding.outputs()[0].output_ordinal, 0);
assert_eq!(binding.outputs()[1].output_ordinal, 1);
assert!(binding.input_schema().is_some());
assert!(binding.output_schema().is_some());
assert_eq!(
binding.to_canonical_json().expect("canonical JSON"),
fixture("remote_function_binding.canonical.json").trim()
);
}
#[test]
fn refresh_job_result_matches_shared_canonical_golden() {
let result = job_result("remote_refresh_job.json");
let result = RefreshColumnResult::from_json(&result.to_string()).expect("refresh result");
assert_eq!(result.rows_assigned, 999_998_800);
assert_eq!(result.rows_filled(), result.rows_assigned);
assert_eq!(result.version(), result.published_version);
assert_eq!(
result.to_canonical_json().expect("canonical JSON"),
fixture("remote_refresh_result.canonical.json").trim()
);
let result = RefreshColumnResult::from_json(&fixture(
"remote_refresh_result_without_published_version.json",
))
.expect("optional version");
assert_eq!(result.published_version, None);
assert_eq!(
result
.to_canonical_json()
.expect("canonical result without version"),
fixture("remote_refresh_result_without_published_version.canonical.json").trim()
);
assert_eq!(
RefreshColumnResult::from_json(
&result
.to_canonical_json()
.expect("canonical result without version")
)
.expect("round-trip result without version"),
result
);
}
#[test]
fn unknown_fields_and_discriminators_are_forward_decodable() {
let mut result = job_result("remote_function_job.json");
result["future_version_metadata"] = serde_json::json!({"retention_class": "catalog"});
result["image"]["descriptor"]["future_interface"] = serde_json::json!({"version": 2});
result["signature"]["output"]["kind"] = Value::String("future_output_shape".to_string());
let version = FunctionVersion::from_json(&result.to_string()).expect("future remote value");
assert_eq!(version.image().descriptor["future_interface"]["version"], 2);
assert_eq!(version.signature().output.kind, "future_output_shape");
assert_eq!(
serde_json::from_str::<Value>(
&version.to_canonical_json().expect("canonical future value")
)
.expect("canonical JSON")["image"]["descriptor"]["future_interface"],
serde_json::json!({"version": 2})
);
}
#[test]
fn floating_point_application_literals_are_rejected_consistently() {
let error = FunctionApplication::from_json(&fixture("remote_function_application_float.json"))
.unwrap_err();
assert!(
error
.to_string()
.contains("floating-point Function literals")
);
}