mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-18 03:58:26 +00:00
feat: guard generated metadata on overwrite
This commit is contained in:
@@ -35,3 +35,19 @@ pub fn reject_caller_authored_generated_column_schema(schema: &Schema) -> Result
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Conditionally admit an input schema for append vs overwrite.
|
||||
///
|
||||
/// Overwrite is schema replacement and must reject reserved top-level field
|
||||
/// metadata. Append is not schema replacement: caller field metadata is
|
||||
/// discarded by cast-to-table-schema, so reserved input keys remain accepted.
|
||||
pub fn reject_caller_authored_generated_column_schema_on_overwrite(
|
||||
schema: &Schema,
|
||||
is_overwrite: bool,
|
||||
) -> Result<()> {
|
||||
if is_overwrite {
|
||||
reject_caller_authored_generated_column_schema(schema)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use super::{ARROW_FILE_CONTENT_TYPE, ARROW_STREAM_CONTENT_TYPE};
|
||||
use crate::blob::BlobFile;
|
||||
use crate::data::scannable::{PeekedScannable, Scannable, estimate_write_partitions};
|
||||
use crate::expr::expr_to_sql_string;
|
||||
use crate::function::schema_admission::reject_caller_authored_generated_column_schema_on_overwrite;
|
||||
use crate::function::{CreateGeneratedColumnJobSpec, GeneratedColumnBindingSnapshot};
|
||||
use crate::index::Index;
|
||||
use crate::index::IndexStatistics;
|
||||
@@ -2320,6 +2321,7 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
|
||||
})
|
||||
}
|
||||
async fn add(&self, mut add: AddDataBuilder) -> Result<AddResult> {
|
||||
add.admit_input_schema()?;
|
||||
self.check_mutable().await?;
|
||||
|
||||
let table_schema = self.schema().await?;
|
||||
@@ -3188,6 +3190,10 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
|
||||
write_params: lance::dataset::WriteParams,
|
||||
) -> Result<Arc<dyn ExecutionPlan>> {
|
||||
let overwrite = matches!(write_params.mode, lance::dataset::WriteMode::Overwrite);
|
||||
reject_caller_authored_generated_column_schema_on_overwrite(
|
||||
input.schema().as_ref(),
|
||||
overwrite,
|
||||
)?;
|
||||
Ok(Arc::new(insert::RemoteWriteExec::new(
|
||||
self.name.clone(),
|
||||
self.identifier.clone(),
|
||||
|
||||
@@ -54,6 +54,7 @@ use crate::database::listing::LANCE_FILE_EXTENSION;
|
||||
use crate::database::read_freshness::TableFreshness;
|
||||
use crate::embeddings::{EmbeddingDefinition, EmbeddingRegistry, MemoryRegistry};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::schema_admission::reject_caller_authored_generated_column_schema_on_overwrite;
|
||||
use crate::function::{
|
||||
CreateGeneratedColumnJobSpec, GeneratedColumnBindingSnapshot, GeneratedColumnStatus,
|
||||
};
|
||||
@@ -95,6 +96,8 @@ mod delete_generated_column_invalidation_contract;
|
||||
#[cfg(test)]
|
||||
mod merge_insert_generated_column_reject_contract;
|
||||
#[cfg(test)]
|
||||
mod overwrite_generated_column_schema_admission_contract;
|
||||
#[cfg(test)]
|
||||
mod schema_metadata_updates_dependency_contract;
|
||||
#[cfg(test)]
|
||||
mod update_field_metadata_generated_column_guard_contract;
|
||||
@@ -3234,6 +3237,7 @@ impl BaseTable for NativeTable {
|
||||
}
|
||||
|
||||
async fn add(&self, mut add: AddDataBuilder) -> Result<AddResult> {
|
||||
add.admit_input_schema()?;
|
||||
self.dataset.ensure_mutable()?;
|
||||
let ds_wrapper = self.dataset.clone();
|
||||
// One exact dataset supplies table definition, schema, binding snapshot,
|
||||
@@ -3796,9 +3800,13 @@ impl BaseTable for NativeTable {
|
||||
input: Arc<dyn datafusion_physical_plan::ExecutionPlan>,
|
||||
write_params: WriteParams,
|
||||
) -> Result<Arc<dyn datafusion_physical_plan::ExecutionPlan>> {
|
||||
let is_overwrite = matches!(write_params.mode, WriteMode::Overwrite);
|
||||
reject_caller_authored_generated_column_schema_on_overwrite(
|
||||
input.schema().as_ref(),
|
||||
is_overwrite,
|
||||
)?;
|
||||
// One exact dataset supplies planning basis and the InsertExec basis.
|
||||
let dataset = self.dataset.get().await?;
|
||||
let is_overwrite = matches!(write_params.mode, WriteMode::Overwrite);
|
||||
// Reject generated-table overwrite before returning an execution plan.
|
||||
let schema_metadata_updates =
|
||||
generated_column_invalidation::plan_native_append_generated_column_invalidation(
|
||||
|
||||
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::data::scannable::Scannable;
|
||||
use crate::data::scannable::scannable_with_embeddings;
|
||||
use crate::embeddings::EmbeddingRegistry;
|
||||
use crate::function::schema_admission::reject_caller_authored_generated_column_schema_on_overwrite;
|
||||
use crate::table::datafusion::cast::cast_to_table_schema;
|
||||
use crate::table::datafusion::reject_nan::reject_nan_vectors;
|
||||
use crate::table::datafusion::scannable_exec::ScannableExec;
|
||||
@@ -151,6 +152,27 @@ impl AddDataBuilder {
|
||||
self.parent.clone().add(self).await
|
||||
}
|
||||
|
||||
/// Effective overwrite for schema-replacement admission and planning.
|
||||
///
|
||||
/// True when either `WriteOptions.lance_write_params.mode` is
|
||||
/// [`WriteMode::Overwrite`] or [`AddDataMode::Overwrite`] is set.
|
||||
pub(crate) fn is_effective_overwrite(&self) -> bool {
|
||||
self.write_options
|
||||
.lance_write_params
|
||||
.as_ref()
|
||||
.is_some_and(|p| matches!(p.mode, WriteMode::Overwrite))
|
||||
|| matches!(self.mode, AddDataMode::Overwrite)
|
||||
}
|
||||
|
||||
/// Borrowed preflight: reject reserved generated-column metadata on
|
||||
/// effective overwrite before source scan or table/network work.
|
||||
pub(crate) fn admit_input_schema(&self) -> Result<()> {
|
||||
reject_caller_authored_generated_column_schema_on_overwrite(
|
||||
self.data.schema().as_ref(),
|
||||
self.is_effective_overwrite(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a DataFusion execution plan that applies embeddings, casts data to
|
||||
/// the table schema, and optionally rejects NaN vectors.
|
||||
///
|
||||
@@ -161,12 +183,7 @@ impl AddDataBuilder {
|
||||
table_schema: &Schema,
|
||||
table_def: &TableDefinition,
|
||||
) -> Result<PreprocessingOutput> {
|
||||
let overwrite = self
|
||||
.write_options
|
||||
.lance_write_params
|
||||
.as_ref()
|
||||
.is_some_and(|p| matches!(p.mode, WriteMode::Overwrite))
|
||||
|| matches!(self.mode, AddDataMode::Overwrite);
|
||||
let overwrite = self.is_effective_overwrite();
|
||||
|
||||
if !overwrite {
|
||||
validate_schema(&self.data.schema(), table_schema)?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user