use std::{collections::HashMap, sync::Arc};
use arrow_array::cast::AsArray;
use arrow_array::{
Array, ArrayRef, BinaryArray, BinaryViewArray, FixedSizeListArray, GenericListArray,
LargeBinaryArray, LargeStringArray, MapArray, OffsetSizeTrait, RecordBatch, StringArray,
StringViewArray, StructArray, UInt32Array, new_empty_array, new_null_array,
};
use arrow_buffer::{NullBuffer, NullBufferBuilder, OffsetBuffer};
use arrow_schema::{ArrowError, DataType, Field as ArrowField};
use arrow_select::dictionary::garbage_collect_any_dictionary;
use arrow_select::interleave::interleave;
use lance_arrow::FieldExt;
use lance_core::datatypes::Field as LanceField;
use lance_core::{Error, Result, datatypes::Schema};
use lance_file::version::ConcreteFileVersion;
pub(super) fn compact_blank_source(
source: &RecordBatch,
storage_version: ConcreteFileVersion,
write_schema: Option<&Schema>,
) -> Result<RecordBatch> {
debug_assert_eq!(source.num_rows(), 1);
let source = arrow_select::take::take_record_batch(source, &UInt32Array::from(vec![0]))?;
let plans = blank_plans(&source, storage_version, write_schema)?;
let columns = source
.columns()
.iter()
.zip(&plans)
.map(|(array, plan)| compact_for_plan(array, plan))
.collect::<Result<Vec<_>>>()?;
Ok(RecordBatch::try_new(source.schema(), columns)?)
}
fn compact_for_plan(array: &ArrayRef, plan: &BlankPlan) -> Result<ArrayRef> {
match plan {
BlankPlan::Interleave(filler) => Ok(filler.clone()),
BlankPlan::List { .. } => empty_one_row_list(array),
BlankPlan::Map { .. } => empty_one_row_map(array),
BlankPlan::Struct(child_plans) => {
let struct_array = array.as_struct();
let children = struct_array
.columns()
.iter()
.zip(child_plans)
.map(|(child, child_plan)| compact_for_plan(child, child_plan))
.collect::<Result<Vec<_>>>()?;
Ok(Arc::new(StructArray::try_new_with_length(
struct_array.fields().clone(),
children,
struct_array.nulls().cloned(),
struct_array.len(),
)?))
}
BlankPlan::FixedSizeList { .. } | BlankPlan::Take => {
compact_nested_dictionaries(array.clone())
}
}
}
fn empty_one_row_list(array: &ArrayRef) -> Result<ArrayRef> {
match array.data_type() {
DataType::List(field) => {
let list = array.as_list::<i32>();
Ok(Arc::new(GenericListArray::<i32>::try_new(
field.clone(),
OffsetBuffer::<i32>::from_lengths([0]),
new_empty_array(field.data_type()),
list.nulls().cloned(),
)?))
}
DataType::LargeList(field) => {
let list = array.as_list::<i64>();
Ok(Arc::new(GenericListArray::<i64>::try_new(
field.clone(),
OffsetBuffer::<i64>::from_lengths([0]),
new_empty_array(field.data_type()),
list.nulls().cloned(),
)?))
}
other => Err(Error::internal(format!(
"Fragment Updater: list blank source on non-list column {other}"
))),
}
}
fn empty_one_row_map(array: &ArrayRef) -> Result<ArrayRef> {
match array.data_type() {
DataType::Map(entries_field, ordered) => {
let map = array.as_map();
let entries = new_empty_array(entries_field.data_type());
Ok(Arc::new(MapArray::try_new(
entries_field.clone(),
OffsetBuffer::<i32>::from_lengths([0]),
entries.as_struct().clone(),
map.nulls().cloned(),
*ordered,
)?))
}
other => Err(Error::internal(format!(
"Fragment Updater: map blank source on non-map column {other}"
))),
}
}
fn compact_nested_dictionaries(array: ArrayRef) -> Result<ArrayRef> {
match array.data_type() {
DataType::Dictionary(_, _) => {
let dictionary = garbage_collect_any_dictionary(array.as_any_dictionary())?;
let values =
compact_nested_dictionaries(dictionary.as_any_dictionary().values().clone())?;
Ok(dictionary.as_any_dictionary().with_values(values))
}
DataType::Struct(_) => {
let array = array.as_struct();
let columns = array
.columns()
.iter()
.cloned()
.map(compact_nested_dictionaries)
.collect::<Result<Vec<_>>>()?;
Ok(Arc::new(StructArray::try_new_with_length(
array.fields().clone(),
columns,
array.nulls().cloned(),
array.len(),
)?))
}
DataType::List(field) => {
let array = array.as_list::<i32>();
let values = compact_nested_dictionaries(array.values().clone())?;
Ok(Arc::new(GenericListArray::<i32>::try_new(
field.clone(),
array.offsets().clone(),
values,
array.nulls().cloned(),
)?))
}
DataType::LargeList(field) => {
let array = array.as_list::<i64>();
let values = compact_nested_dictionaries(array.values().clone())?;
Ok(Arc::new(GenericListArray::<i64>::try_new(
field.clone(),
array.offsets().clone(),
values,
array.nulls().cloned(),
)?))
}
DataType::FixedSizeList(field, size) => {
let array = array.as_fixed_size_list();
let values = compact_nested_dictionaries(array.values().clone())?;
Ok(Arc::new(FixedSizeListArray::try_new_with_length(
field.clone(),
*size,
values,
array.nulls().cloned(),
array.len(),
)?))
}
DataType::Map(entries, ordered) => {
let entries = entries.clone();
let ordered = *ordered;
let entries_unstorable = entries.is_nullable()
|| array
.as_map_opt()
.map(|map| map.entries().null_count() != 0)
.unwrap_or(true);
if entries_unstorable {
return Ok(array);
}
let map = array.as_map();
let compacted_entries = compact_nested_dictionaries(Arc::new(map.entries().clone()))?;
Ok(Arc::new(MapArray::try_new(
entries,
map.offsets().clone(),
compacted_entries.as_struct().clone(),
map.nulls().cloned(),
ordered,
)?))
}
_ => Ok(array),
}
}
pub(super) fn add_blanks(
batch: RecordBatch,
batch_offsets: &[u32],
storage_version: ConcreteFileVersion,
write_schema: Option<&Schema>,
) -> Result<RecordBatch> {
if batch_offsets.is_empty() {
return Ok(batch);
}
if batch.num_rows() == 0 {
return Err(Error::not_supported(
"Fragment Updater: missing too many rows in merge, run compaction to materialize \
deletions first",
));
}
let plans = blank_plans(&batch, storage_version, write_schema)?;
add_blanks_with_plans(batch, batch_offsets, &plans)
}
pub(super) fn blank_plans(
batch: &RecordBatch,
storage_version: ConcreteFileVersion,
write_schema: Option<&Schema>,
) -> Result<Vec<BlankPlan>> {
let cheap_blanks = match storage_version {
ConcreteFileVersion::V1 => false,
ConcreteFileVersion::V2_0
| ConcreteFileVersion::V2_1
| ConcreteFileVersion::V2_2
| ConcreteFileVersion::V2_3 => true,
};
if !cheap_blanks {
return Ok((0..batch.num_columns()).map(|_| BlankPlan::Take).collect());
}
let write_fields = write_schema.map(|schema| {
schema
.fields
.iter()
.map(|field| (field.name.as_str(), field))
.collect::<HashMap<_, _>>()
});
batch
.schema()
.fields()
.iter()
.zip(batch.columns())
.map(|(field, array)| {
let write_field = match &write_fields {
Some(fields) => Some(*fields.get(field.name().as_str()).ok_or_else(|| {
Error::internal(format!(
"Fragment Updater: field {} is missing from the write schema",
field.name()
))
})?),
None => None,
};
blank_plan(field, write_field, array)
})
.collect()
}
pub(super) fn add_blanks_with_plans(
batch: RecordBatch,
batch_offsets: &[u32],
plans: &[BlankPlan],
) -> Result<RecordBatch> {
debug_assert!(!batch_offsets.is_empty());
debug_assert_eq!(batch.num_columns(), plans.len());
if batch.num_rows() == 0 {
return Err(Error::internal(
"Fragment Updater: cannot place blanks in a batch with no live rows",
));
}
let needs_take = plans.iter().any(BlankPlan::needs_take);
let needs_interleave = plans.iter().any(BlankPlan::needs_interleave);
let output_len = batch
.num_rows()
.checked_add(batch_offsets.len())
.ok_or_else(|| Error::internal("Fragment Updater: blank output row count overflow"))?;
let mut take_indices = needs_take.then(|| Vec::with_capacity(output_len));
let mut interleave_indices = needs_interleave.then(|| Vec::with_capacity(output_len));
let num_live_rows = u32::try_from(batch.num_rows()).map_err(|_| {
Error::internal(format!(
"Fragment Updater: blank source has {} rows, exceeding the u32 row-address limit",
batch.num_rows()
))
})?;
let mut batch_pos = 0;
let mut next_id = 0;
for (idx, batch_offset) in batch_offsets.iter().enumerate() {
let num_rows = batch_offset.checked_sub(next_id).ok_or_else(|| {
Error::internal(format!(
"Fragment Updater: blank offsets must be strictly increasing, but offset \
{batch_offset} (entry {idx} of {}) is below the expected minimum {next_id}",
batch_offsets.len()
))
})?;
if num_rows > num_live_rows - batch_pos {
return Err(Error::internal(format!(
"Fragment Updater: blank offset {batch_offset} (entry {idx} of \
{}) needs {num_rows} more live rows before it, but {} of the batch's \
{num_live_rows} are still unused",
batch_offsets.len(),
num_live_rows - batch_pos
)));
}
if let Some(indices) = take_indices.as_mut() {
indices.extend(batch_pos..batch_pos + num_rows);
indices.push(0);
}
if let Some(indices) = interleave_indices.as_mut() {
indices.extend((batch_pos..batch_pos + num_rows).map(|pos| (0, pos as usize)));
indices.push((1, 0));
}
next_id = batch_offset.checked_add(1).ok_or_else(|| {
Error::internal(format!(
"Fragment Updater: blank offset {batch_offset} cannot be followed by another row"
))
})?;
batch_pos = batch_pos.checked_add(num_rows).ok_or_else(|| {
Error::internal("Fragment Updater: live-row selection position overflow")
})?;
}
if let Some(indices) = take_indices.as_mut() {
indices.extend(batch_pos..num_live_rows);
}
if let Some(indices) = interleave_indices.as_mut() {
indices.extend((batch_pos..num_live_rows).map(|pos| (0, pos as usize)));
}
let take_indices = take_indices.map(UInt32Array::from);
let arrays = batch
.columns()
.iter()
.zip(plans)
.map(|(array, plan)| {
apply_blank_plan(
array,
plan,
take_indices.as_ref(),
interleave_indices.as_deref(),
)
.map_err(|error| Error::arrow(format!("Failed to add blanks: {error}")))
})
.collect::<Result<Vec<_>>>()?;
let batch = RecordBatch::try_new(batch.schema(), arrays)?;
Ok(batch)
}
pub(super) enum BlankPlan {
Take,
Interleave(ArrayRef),
List { blank_is_null: bool },
Map { blank_is_null: bool },
FixedSizeList {
child: Box<Self>,
blank_is_null: bool,
},
Struct(Vec<Self>),
}
impl BlankPlan {
fn needs_take(&self) -> bool {
match self {
Self::Take => true,
Self::Interleave(_) | Self::List { .. } | Self::Map { .. } => false,
Self::FixedSizeList { .. } | Self::Struct(_) => true,
}
}
fn needs_interleave(&self) -> bool {
match self {
Self::Take => false,
Self::Interleave(_)
| Self::List { .. }
| Self::Map { .. }
| Self::FixedSizeList { .. }
| Self::Struct(_) => true,
}
}
}
fn interleaved_nulls(
array: &dyn Array,
blank_is_null: bool,
indices: &[(usize, usize)],
) -> std::result::Result<Option<NullBuffer>, ArrowError> {
let mut nulls = NullBufferBuilder::new(indices.len());
for (source, row) in indices {
let is_valid = match source {
0 => array.is_valid(*row),
1 => !blank_is_null,
_ => {
return Err(ArrowError::InvalidArgumentError(format!(
"blank plan has invalid source {source}"
)));
}
};
nulls.append(is_valid);
}
Ok(nulls.finish())
}
fn blank_offsets<O: OffsetSizeTrait>(
kind: &str,
value_offsets: &[O],
num_live_rows: usize,
indices: &[(usize, usize)],
) -> std::result::Result<OffsetBuffer<O>, ArrowError> {
let first = *value_offsets.first().ok_or_else(|| {
ArrowError::ComputeError(format!("{kind} blank plan has no initial offset"))
})?;
let mut offsets = Vec::with_capacity(indices.len() + 1);
offsets.push(first);
let mut next_live_row = 0;
for (source, row) in indices {
let last = *offsets.last().ok_or_else(|| {
ArrowError::ComputeError(format!("{kind} blank plan has no preceding offset"))
})?;
let next = match source {
0 if *row == next_live_row && next_live_row < num_live_rows => {
next_live_row += 1;
value_offsets[row + 1]
}
0 => {
return Err(ArrowError::InvalidArgumentError(format!(
"{kind} blank plan requires live row {next_live_row}, got {row}"
)));
}
1 if *row == 0 => last,
1 => {
return Err(ArrowError::InvalidArgumentError(format!(
"{kind} blank plan requires filler row 0, got {row}"
)));
}
source => {
return Err(ArrowError::InvalidArgumentError(format!(
"{kind} blank plan has invalid source {source}"
)));
}
};
if next < last {
return Err(ArrowError::ComputeError(format!(
"{kind} blank plan produced a non-monotonic offset: {next:?} after {last:?}"
)));
}
offsets.push(next);
}
if next_live_row != num_live_rows {
return Err(ArrowError::InvalidArgumentError(format!(
"{kind} blank plan selected {next_live_row} of {num_live_rows} live rows"
)));
}
Ok(OffsetBuffer::new(offsets.into()))
}
fn apply_list_blank_plan<O: OffsetSizeTrait>(
array: &GenericListArray<O>,
blank_is_null: bool,
indices: &[(usize, usize)],
) -> std::result::Result<ArrayRef, ArrowError> {
let field = match array.data_type() {
DataType::List(field) | DataType::LargeList(field) => field.clone(),
data_type => {
return Err(ArrowError::InvalidArgumentError(format!(
"list blank plan requires a list array, got {data_type}"
)));
}
};
Ok(Arc::new(GenericListArray::<O>::try_new(
field,
blank_offsets("list", array.value_offsets(), array.len(), indices)?,
array.values().clone(),
interleaved_nulls(array, blank_is_null, indices)?,
)?))
}
fn apply_map_blank_plan(
array: &MapArray,
blank_is_null: bool,
indices: &[(usize, usize)],
) -> std::result::Result<ArrayRef, ArrowError> {
let (entries, ordered) = match array.data_type() {
DataType::Map(entries, ordered) => (entries.clone(), *ordered),
data_type => {
return Err(ArrowError::InvalidArgumentError(format!(
"map blank plan requires a map array, got {data_type}"
)));
}
};
Ok(Arc::new(MapArray::try_new(
entries,
blank_offsets("map", array.value_offsets(), array.len(), indices)?,
array.entries().clone(),
interleaved_nulls(array, blank_is_null, indices)?,
ordered,
)?))
}
fn apply_fixed_size_list_blank_plan(
array: &FixedSizeListArray,
child_plan: &BlankPlan,
blank_is_null: bool,
take_indices: &UInt32Array,
interleave_indices: &[(usize, usize)],
) -> std::result::Result<ArrayRef, ArrowError> {
let (field, list_size) = match array.data_type() {
DataType::FixedSizeList(field, size) => (field.clone(), *size),
data_type => {
return Err(ArrowError::InvalidArgumentError(format!(
"fixed-size-list blank plan requires a FixedSizeList array, got {data_type}"
)));
}
};
let size = usize::try_from(list_size).map_err(|_| {
ArrowError::InvalidArgumentError(format!("FixedSizeList has a negative size {list_size}"))
})?;
let output_len = interleave_indices.len();
if take_indices.len() != output_len {
return Err(ArrowError::InvalidArgumentError(format!(
"fixed-size-list blank plan got {} take indices for {output_len} output rows",
take_indices.len()
)));
}
let values = if size == 0 {
array.values().clone()
} else {
let child_len = output_len.checked_mul(size).ok_or_else(|| {
ArrowError::ComputeError("FixedSizeList child selection length overflow".to_string())
})?;
let mut child_take_indices = child_plan
.needs_take()
.then(|| Vec::with_capacity(child_len));
let mut child_interleave_indices = child_plan
.needs_interleave()
.then(|| Vec::with_capacity(child_len));
for (output_row, (source, _)) in interleave_indices.iter().enumerate() {
let input_row = take_indices.value(output_row) as usize;
let start = input_row.checked_mul(size).ok_or_else(|| {
ArrowError::ComputeError("FixedSizeList child offset overflow".to_string())
})?;
let end = start.checked_add(size).ok_or_else(|| {
ArrowError::ComputeError("FixedSizeList child range overflow".to_string())
})?;
for child_row in start..end {
if let Some(indices) = child_take_indices.as_mut() {
indices.push(u32::try_from(child_row).map_err(|_| {
ArrowError::ComputeError(
"FixedSizeList child index exceeds UInt32 capacity".to_string(),
)
})?);
}
if let Some(indices) = child_interleave_indices.as_mut() {
indices.push(match source {
0 => (0, child_row),
1 => (1, 0),
_ => {
return Err(ArrowError::InvalidArgumentError(format!(
"fixed-size-list blank plan has invalid source {source}"
)));
}
});
}
}
}
let child_take_indices = child_take_indices.map(UInt32Array::from);
apply_blank_plan(
array.values(),
child_plan,
child_take_indices.as_ref(),
child_interleave_indices.as_deref(),
)?
};
let mut nulls = NullBufferBuilder::new(output_len);
for (output_row, (source, _)) in interleave_indices.iter().enumerate() {
let input_row = take_indices.value(output_row) as usize;
nulls.append(match source {
0 => array.is_valid(input_row),
1 => !blank_is_null && array.is_valid(input_row),
_ => {
return Err(ArrowError::InvalidArgumentError(format!(
"fixed-size-list blank plan has invalid source {source}"
)));
}
});
}
Ok(Arc::new(FixedSizeListArray::try_new_with_length(
field,
list_size,
values,
nulls.finish(),
output_len,
)?))
}
fn apply_blank_plan(
array: &ArrayRef,
plan: &BlankPlan,
take_indices: Option<&UInt32Array>,
interleave_indices: Option<&[(usize, usize)]>,
) -> std::result::Result<ArrayRef, ArrowError> {
match plan {
BlankPlan::Take => arrow::compute::take(
array.as_ref(),
take_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("take indices are missing".to_string())
})?,
None,
),
BlankPlan::Interleave(filler) => interleave(
&[array.as_ref(), filler.as_ref()],
interleave_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("interleave indices are missing".to_string())
})?,
),
BlankPlan::List { blank_is_null } => match array.data_type() {
DataType::List(_) => apply_list_blank_plan(
array.as_list::<i32>(),
*blank_is_null,
interleave_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("interleave indices are missing".to_string())
})?,
),
DataType::LargeList(_) => apply_list_blank_plan(
array.as_list::<i64>(),
*blank_is_null,
interleave_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("interleave indices are missing".to_string())
})?,
),
data_type => Err(ArrowError::InvalidArgumentError(format!(
"list blank plan requires a list array, got {data_type}"
))),
},
BlankPlan::Map { blank_is_null } => {
let array = array.as_map_opt().ok_or_else(|| {
ArrowError::InvalidArgumentError(format!(
"map blank plan requires a map array, got {}",
array.data_type()
))
})?;
apply_map_blank_plan(
array,
*blank_is_null,
interleave_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("interleave indices are missing".to_string())
})?,
)
}
BlankPlan::FixedSizeList {
child,
blank_is_null,
} => {
let array = array.as_fixed_size_list_opt().ok_or_else(|| {
ArrowError::InvalidArgumentError(format!(
"fixed-size-list blank plan requires a FixedSizeList array, got {}",
array.data_type()
))
})?;
apply_fixed_size_list_blank_plan(
array,
child,
*blank_is_null,
take_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("take indices are missing".to_string())
})?,
interleave_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("interleave indices are missing".to_string())
})?,
)
}
BlankPlan::Struct(plans) => {
let struct_array = array.as_struct_opt().ok_or_else(|| {
ArrowError::InvalidArgumentError(format!(
"struct blank plan requires a struct array, got {}",
array.data_type()
))
})?;
let children = struct_array
.columns()
.iter()
.zip(plans)
.map(|(array, plan)| {
apply_blank_plan(array, plan, take_indices, interleave_indices)
})
.collect::<std::result::Result<Vec<_>, _>>()?;
let indices = take_indices.ok_or_else(|| {
ArrowError::InvalidArgumentError("take indices are missing".to_string())
})?;
let mut nulls = NullBufferBuilder::new(indices.len());
for row in indices.values() {
nulls.append(struct_array.is_valid(*row as usize));
}
Ok(Arc::new(StructArray::try_new_with_length(
struct_array.fields().clone(),
children,
nulls.finish(),
indices.len(),
)?))
}
}
}
fn blank_plan(
arrow_field: &ArrowField,
write_field: Option<&LanceField>,
array: &ArrayRef,
) -> Result<BlankPlan> {
const EMPTY: &[u8] = &[];
let is_nullable = arrow_field.is_nullable() && write_field.is_none_or(|field| field.nullable);
if arrow_field.is_blob_v2() || write_field.is_some_and(|field| field.is_blob_v2()) {
return Ok(blob_blank_plan(array, write_field));
}
let can_be_null = is_nullable;
let filler = |empty: ArrayRef| {
BlankPlan::Interleave(if can_be_null {
new_null_array(array.data_type(), 1)
} else {
empty
})
};
let plan = match array.data_type() {
DataType::Utf8 => filler(Arc::new(StringArray::from(vec![""]))),
DataType::LargeUtf8 => filler(Arc::new(LargeStringArray::from(vec![""]))),
DataType::Utf8View => filler(Arc::new(StringViewArray::from(vec![""]))),
DataType::Binary => filler(Arc::new(BinaryArray::from(vec![EMPTY]))),
DataType::LargeBinary => filler(Arc::new(LargeBinaryArray::from(vec![EMPTY]))),
DataType::BinaryView => filler(Arc::new(BinaryViewArray::from(vec![EMPTY]))),
DataType::List(_) | DataType::LargeList(_) => BlankPlan::List {
blank_is_null: can_be_null,
},
DataType::Map(entries_field, _) => {
let entries_unstorable = entries_field.is_nullable()
|| array
.as_map_opt()
.map(|map| map.entries().null_count() != 0)
.unwrap_or(true);
if entries_unstorable {
BlankPlan::Take
} else {
BlankPlan::Map {
blank_is_null: can_be_null,
}
}
}
DataType::FixedSizeList(child_field, _) => {
let child = blank_plan(
child_field,
write_child_field(write_field, child_field.name()),
array.as_fixed_size_list().values(),
)?;
if matches!(child, BlankPlan::Take) {
BlankPlan::Take
} else {
BlankPlan::FixedSizeList {
child: Box::new(child),
blank_is_null: can_be_null,
}
}
}
DataType::Struct(fields) => {
let plans = fields
.iter()
.zip(array.as_struct().columns())
.map(|(field, child)| {
let write_child = required_write_child_field(write_field, field.name())?;
blank_plan(field, write_child, child)
})
.collect::<Result<Vec<_>>>()?;
if plans.iter().all(|plan| matches!(plan, BlankPlan::Take)) {
BlankPlan::Take
} else {
BlankPlan::Struct(plans)
}
}
_ => BlankPlan::Take,
};
Ok(plan)
}
fn blob_blank_plan(array: &ArrayRef, write_field: Option<&LanceField>) -> BlankPlan {
let Some(fields) = array.as_struct_opt().map(|array| array.fields().clone()) else {
return BlankPlan::Take;
};
let Some(data_index) = fields
.iter()
.position(|field| field.name() == "data" && field.data_type() == &DataType::LargeBinary)
else {
return BlankPlan::Take;
};
if fields.iter().any(|field| field.name() == "kind") {
return BlankPlan::Take;
}
let child_nullable = |field: &ArrowField| {
field.is_nullable()
&& write_child_field(write_field, field.name()).is_none_or(|f| f.nullable)
};
if fields
.iter()
.enumerate()
.any(|(index, field)| index != data_index && !child_nullable(field))
{
return BlankPlan::Take;
}
let plans = fields
.iter()
.enumerate()
.map(|(index, field)| {
if index == data_index {
BlankPlan::Interleave(Arc::new(LargeBinaryArray::from(vec![&[] as &[u8]])))
} else {
BlankPlan::Interleave(new_null_array(field.data_type(), 1))
}
})
.collect();
BlankPlan::Struct(plans)
}
fn write_child_field<'a>(
write_parent: Option<&'a LanceField>,
child_name: &str,
) -> Option<&'a LanceField> {
write_parent?
.children
.iter()
.find(|child| child.name == child_name)
}
fn required_write_child_field<'a>(
write_parent: Option<&'a LanceField>,
child_name: &str,
) -> Result<Option<&'a LanceField>> {
let Some(write_parent) = write_parent else {
return Ok(None);
};
write_child_field(Some(write_parent), child_name)
.map(Some)
.ok_or_else(|| {
Error::internal(format!(
"Fragment Updater: child field {child_name} is missing from write-schema field {}",
write_parent.name
))
})
}