use std::sync::Arc;
use arrow_schema::SchemaRef;
use async_trait::async_trait;
use datafusion_catalog::{Session, TableProvider};
use datafusion_common::{Result, plan_err};
use datafusion_execution::{SendableRecordBatchStream, TaskContext};
use datafusion_expr::{Expr, TableType};
use datafusion_physical_expr::{EquivalenceProperties, Partitioning};
use datafusion_physical_plan::{
DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
coalesce_partitions::CoalescePartitionsExec,
empty::EmptyExec,
execution_plan::{Boundedness, EmissionType},
projection::ProjectionExec,
stream::RecordBatchStreamAdapter,
};
use futures::{TryStreamExt, stream};
use lance::dataset::scanner::{RowAddrMask, RowAddrTreeMap};
use lance::{Dataset, index::DatasetIndexInternalExt};
use lance_core::datatypes::BlobHandling;
use lance_index::metrics::NoOpMetricsCollector;
use super::duplicate_pairs::{
DuplicatePairTask, DuplicatePairsConfig, DuplicatePairsExec, DuplicatePairsOutput,
plan_duplicate_pairs,
};
use crate::table::{NativeTableExt, datafusion::BaseTableAdapter};
const REPRESENTATIVE_SORT_MEMORY_BYTES: usize = 256 * 1024 * 1024;
const MATERIALIZE_BATCH_ROWS: usize = 1024;
#[derive(Debug, Clone, Default)]
pub enum PartitionSelection {
#[default]
All,
First {
count: usize,
},
Random {
count: usize,
seed: u64,
},
}
#[derive(Debug, Clone)]
pub struct VectorQueryOptions {
pub column: String,
pub distance_threshold: f32,
pub selection: PartitionSelection,
pub output: DuplicatePairsOutput,
pub dedup: bool,
}
impl BaseTableAdapter {
pub async fn vector_query_snapshot(&self, version: Option<u64>) -> Result<Arc<Dataset>> {
let native = self.table.as_native().ok_or_else(|| {
datafusion_common::DataFusionError::Plan(
"vector queries require a native indexed table".into(),
)
})?;
if version == Some(0) {
return plan_err!("dataset_version must be positive");
}
let current = native
.dataset
.get()
.await
.map_err(|e| datafusion_common::DataFusionError::External(Box::new(e)))?;
Ok(match version {
Some(version) => Arc::new(current.checkout_version(version).await?),
None => {
let mut latest = (*current).clone();
latest.checkout_latest().await?;
Arc::new(latest)
}
})
}
}
#[derive(Debug)]
pub struct VectorQueryTable {
dataset: Arc<Dataset>,
config: DuplicatePairsConfig,
tasks: Vec<DuplicatePairTask>,
options: VectorQueryOptions,
schema: SchemaRef,
}
impl VectorQueryTable {
pub async fn try_new(dataset: Arc<Dataset>, options: VectorQueryOptions) -> Result<Self> {
if options.dedup
&& (!matches!(options.selection, PartitionSelection::All)
|| options.output.id_column.is_some())
{
return plan_err!(
"vector_dedup requires all partitions and returns original rows; sampling and id_column are pair-query options"
);
}
let config = DuplicatePairsConfig {
dataset_version: dataset.version().version,
column: options.column.clone(),
distance_threshold: options.distance_threshold,
};
let mut tasks = plan_duplicate_pairs(dataset.clone(), &config).await?;
tasks.sort_by_key(|task| (task.segment_id, task.partition_id));
let mut nonempty = Vec::new();
for group in tasks.chunk_by(|a, b| a.segment_id == b.segment_id) {
let index = dataset
.open_vector_index(&config.column, &group[0].segment_id, &NoOpMetricsCollector)
.await?;
nonempty.extend(
group
.iter()
.filter(|task| index.partition_size(task.partition_id) > 0)
.cloned(),
);
}
match options.selection {
PartitionSelection::All => {}
PartitionSelection::First { count } | PartitionSelection::Random { count, .. }
if count == 0 =>
{
return plan_err!("partition count must be positive");
}
PartitionSelection::First { count } => nonempty.truncate(count),
PartitionSelection::Random { count, seed } => {
let segments = tasks
.iter()
.map(|t| t.segment_id)
.collect::<std::collections::BTreeSet<_>>()
.len();
let mut ranked = nonempty
.into_iter()
.enumerate()
.map(|(ordinal, task)| {
let key = if segments == 1 {
task.partition_id as u64
} else {
ordinal as u64
};
(sample_rank(seed, key), task)
})
.collect::<Vec<_>>();
ranked.sort_by_key(|(rank, task)| (*rank, task.segment_id, task.partition_id));
ranked.truncate(count);
nonempty = ranked.into_iter().map(|(_, task)| task).collect();
nonempty.sort_by_key(|task| (task.segment_id, task.partition_id));
}
}
let schema = if options.dedup {
let mut scanner = dataset.scan();
scanner.blob_handling(BlobHandling::AllBinary);
scanner.schema().await?
} else {
options.output.schema(&dataset)?
};
Ok(Self {
dataset,
config,
tasks: nonempty,
options,
schema,
})
}
}
fn sample_rank(seed: u64, ordinal: u64) -> u64 {
let mut value = seed.wrapping_add(ordinal).wrapping_add(0x9E3779B97F4A7C15);
value = (value ^ (value >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
value = (value ^ (value >> 27)).wrapping_mul(0x94D049BB133111EB);
value ^ (value >> 31)
}
#[async_trait]
impl TableProvider for VectorQueryTable {
fn schema(&self) -> SchemaRef {
self.schema.clone()
}
fn table_type(&self) -> TableType {
TableType::View
}
async fn scan(
&self,
_state: &dyn Session,
projection: Option<&Vec<usize>>,
_filters: &[Expr],
_limit: Option<usize>,
) -> Result<Arc<dyn ExecutionPlan>> {
let output = if self.options.dedup {
DuplicatePairsOutput::default()
} else {
self.options.output.clone()
};
let pairs: Arc<dyn ExecutionPlan> = if self.tasks.is_empty() {
Arc::new(EmptyExec::new(output.schema(&self.dataset)?))
} else {
Arc::new(
DuplicatePairsExec::try_new(
self.dataset.clone(),
self.config.clone(),
self.tasks.clone(),
)?
.with_output(output)?,
)
};
let plan: Arc<dyn ExecutionPlan> = if self.options.dedup {
Arc::new(VectorDedupExec::new(
self.dataset.clone(),
pairs,
self.schema.clone(),
))
} else {
pairs
};
if let Some(indices) = projection {
let expressions: Vec<_> = indices
.iter()
.map(|&i| {
(
Arc::new(datafusion_physical_expr::expressions::Column::new(
self.schema.field(i).name(),
i,
))
as Arc<dyn datafusion_physical_expr::PhysicalExpr>,
self.schema.field(i).name().clone(),
)
})
.collect();
Ok(Arc::new(ProjectionExec::try_new(expressions, plan)?))
} else {
Ok(plan)
}
}
}
#[derive(Debug)]
pub struct VectorDedupExec {
dataset: Arc<Dataset>,
pairs: Arc<dyn ExecutionPlan>,
properties: Arc<PlanProperties>,
}
impl VectorDedupExec {
fn new(dataset: Arc<Dataset>, pairs: Arc<dyn ExecutionPlan>, schema: SchemaRef) -> Self {
Self {
dataset,
pairs,
properties: Arc::new(PlanProperties::new(
EquivalenceProperties::new(schema),
Partitioning::UnknownPartitioning(1),
EmissionType::Final,
Boundedness::Bounded,
)),
}
}
}
impl DisplayAs for VectorDedupExec {
fn fmt_as(&self, _: DisplayFormatType, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"VectorDedupExec: source_version={}, policy=direct_representative",
self.dataset.version().version
)
}
}
impl ExecutionPlan for VectorDedupExec {
fn name(&self) -> &str {
"VectorDedupExec"
}
fn properties(&self) -> &Arc<PlanProperties> {
&self.properties
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
vec![&self.pairs]
}
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
) -> Result<Arc<dyn ExecutionPlan>> {
if children.len() != 1 {
return plan_err!("VectorDedupExec requires one pair input");
}
Ok(Arc::new(Self::new(
self.dataset.clone(),
children[0].clone(),
self.schema(),
)))
}
fn execute(
&self,
partition: usize,
context: Arc<TaskContext>,
) -> Result<SendableRecordBatchStream> {
if partition != 0 {
return plan_err!("invalid dedup output partition");
}
let dataset = self.dataset.clone();
let pairs = CoalescePartitionsExec::new(self.pairs.clone()).execute(0, context)?;
let stream = stream::once(async move {
let (removed, _) = crate::materialized_view::vector_dedup::select_representatives(
pairs,
REPRESENTATIVE_SORT_MEMORY_BYTES,
)
.await
.map_err(|e| datafusion_common::DataFusionError::External(Box::new(e)))?;
let mut scanner = dataset.scan();
scanner
.with_row_addr_prefilter(RowAddrMask::from_block(RowAddrTreeMap::from_iter(
removed,
)))
.blob_handling(BlobHandling::AllBinary)
.batch_size(MATERIALIZE_BATCH_ROWS)
.strict_batch_size(true);
Ok::<_, datafusion_common::DataFusionError>(
scanner
.try_into_stream()
.await?
.map_err(datafusion_common::DataFusionError::from),
)
})
.try_flatten();
Ok(Box::pin(RecordBatchStreamAdapter::new(
self.schema(),
stream,
)))
}
}