use lance_core::{Error, Result};
use lance_io::object_store::ObjectStore;
use super::FRAG_REUSE_INDEX_NAME;
use crate::feature_flags::FLAG_FRAGMENT_REUSE_INDEX;
use crate::format::{IndexMetadata, Manifest};
use crate::io::commit::ManifestLocation;
use crate::io::manifest::read_manifest_indexes;
pub fn is_tagged(index: &IndexMetadata) -> bool {
index.name == FRAG_REUSE_INDEX_NAME && index.index_version != 0
}
pub fn uses_tagged_fri(manifest: &Manifest, existing_fri_entry: Option<&IndexMetadata>) -> bool {
manifest.reader_feature_flags & FLAG_FRAGMENT_REUSE_INDEX != 0
|| existing_fri_entry.is_some_and(is_tagged)
}
pub fn validate_flags(manifest: &Manifest, indices: &[IndexMetadata]) -> Result<()> {
if indices.iter().any(is_tagged)
&& (manifest.reader_feature_flags & manifest.writer_feature_flags)
& FLAG_FRAGMENT_REUSE_INDEX
== 0
{
return Err(Error::corrupt_file_named(
"manifest",
"tagged FRI metadata requires both reader and writer feature flags",
));
}
Ok(())
}
#[deprecated(
since = "13.0.0-beta.17",
note = "guards the legacy clone workflow; use the dataset clone APIs, which relocate fragment reuse mapping references"
)]
pub async fn ensure_clone_supported(
store: &ObjectStore,
location: &ManifestLocation,
manifest: &Manifest,
) -> Result<()> {
if manifest.reader_feature_flags & FLAG_FRAGMENT_REUSE_INDEX == 0 {
return Ok(());
}
let indices = read_manifest_indexes(store, location, manifest).await?;
if indices.iter().any(is_tagged) {
return Err(Error::not_supported(
"Cloning tagged FRI requires row-map reference relocation. Please upgrade to a version supporting FRI clone",
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::commit::{ManifestNamingScheme, write_manifest_file_to_path};
use crate::transaction::test_support::sample_manifest;
use object_store::path::Path;
use uuid::Uuid;
fn entry(name: &str, index_version: i32) -> IndexMetadata {
IndexMetadata {
uuid: Uuid::nil(),
fields: vec![],
covering_fields: vec![],
name: name.into(),
dataset_version: 1,
fragment_bitmap: None,
index_details: None,
index_version,
created_at: None,
base_id: None,
files: None,
}
}
#[test]
fn tagged_requires_the_reserved_name_and_a_nonzero_version() {
assert!(!is_tagged(&entry(FRAG_REUSE_INDEX_NAME, 0)));
assert!(is_tagged(&entry(FRAG_REUSE_INDEX_NAME, 1)));
assert!(is_tagged(&entry(FRAG_REUSE_INDEX_NAME, 2)));
assert!(!is_tagged(&entry("user_idx", 0)));
assert!(!is_tagged(&entry("user_idx", 1)));
}
#[test]
fn sticky_flag_or_tagged_entry_makes_the_table_tagged() {
let mut flagged = crate::transaction::test_support::sample_manifest();
flagged.reader_feature_flags |= FLAG_FRAGMENT_REUSE_INDEX;
let unflagged = crate::transaction::test_support::sample_manifest();
assert!(uses_tagged_fri(&flagged, None));
assert!(uses_tagged_fri(
&flagged,
Some(&entry(FRAG_REUSE_INDEX_NAME, 0))
));
assert!(uses_tagged_fri(
&unflagged,
Some(&entry(FRAG_REUSE_INDEX_NAME, 1))
));
assert!(!uses_tagged_fri(
&unflagged,
Some(&entry(FRAG_REUSE_INDEX_NAME, 0))
));
assert!(!uses_tagged_fri(&unflagged, None));
}
async fn write_located(
store: &ObjectStore,
manifest: &mut Manifest,
indices: Vec<IndexMetadata>,
) -> ManifestLocation {
let path = Path::from("_versions/1.manifest");
let written = write_manifest_file_to_path(store, manifest, Some(indices), &path, None)
.await
.unwrap();
ManifestLocation {
version: 1,
path,
size: Some(written.size as u64),
naming_scheme: ManifestNamingScheme::V1,
e_tag: written.e_tag,
identity: None,
}
}
#[tokio::test]
#[allow(deprecated)]
async fn ensure_clone_supported_accepts_history_without_tagged_entries() {
let store = ObjectStore::memory();
let unflagged = sample_manifest();
let missing = ManifestLocation {
version: 1,
path: Path::from("_versions/missing.manifest"),
size: None,
naming_scheme: ManifestNamingScheme::V1,
e_tag: None,
identity: None,
};
ensure_clone_supported(&store, &missing, &unflagged)
.await
.unwrap();
let mut flagged = sample_manifest();
flagged.reader_feature_flags |= FLAG_FRAGMENT_REUSE_INDEX;
flagged.writer_feature_flags |= FLAG_FRAGMENT_REUSE_INDEX;
let location = write_located(
&store,
&mut flagged,
vec![entry(FRAG_REUSE_INDEX_NAME, 0), entry("user_idx", 1)],
)
.await;
ensure_clone_supported(&store, &location, &flagged)
.await
.unwrap();
}
#[tokio::test]
#[allow(deprecated)]
async fn ensure_clone_supported_rejects_tagged_history() {
let store = ObjectStore::memory();
let mut flagged = sample_manifest();
flagged.reader_feature_flags |= FLAG_FRAGMENT_REUSE_INDEX;
flagged.writer_feature_flags |= FLAG_FRAGMENT_REUSE_INDEX;
let location =
write_located(&store, &mut flagged, vec![entry(FRAG_REUSE_INDEX_NAME, 1)]).await;
let err = ensure_clone_supported(&store, &location, &flagged)
.await
.unwrap_err();
assert!(
matches!(err, Error::NotSupported { .. }),
"expected NotSupported, got {err:?}"
);
assert!(
err.to_string()
.contains("Cloning tagged FRI requires row-map reference relocation"),
"unexpected message: {err}"
);
}
}