use std::sync::Arc;
use ahash::HashSet;
use common::future::stream::{self, Yielder};
use futures::StreamExt;
use super::scan::extract_rid;
use crate::exec::{
AccessMode, ContextLevel, ExecOperator, ExecutionContext, FlowResult, OperatorMetrics,
OutputOrdering, ValueBatch, ValueBatchStream, buffer_stream, monitor_stream,
};
use crate::val::RecordId;
#[derive(Debug, Clone)]
pub struct DistinctRecords {
pub(crate) input: Arc<dyn ExecOperator>,
pub(crate) metrics: Arc<OperatorMetrics>,
}
impl DistinctRecords {
pub(crate) fn new(input: Arc<dyn ExecOperator>) -> Self {
Self {
input,
metrics: Arc::new(OperatorMetrics::new()),
}
}
}
impl ExecOperator for DistinctRecords {
fn name(&self) -> &'static str {
"DistinctRecords"
}
fn required_context(&self) -> ContextLevel {
ContextLevel::Database.max(self.input.required_context())
}
fn access_mode(&self) -> AccessMode {
self.input.access_mode()
}
fn children(&self) -> Vec<&Arc<dyn ExecOperator>> {
vec![&self.input]
}
fn metrics(&self) -> Option<&OperatorMetrics> {
Some(&self.metrics)
}
fn output_ordering(&self) -> OutputOrdering {
self.input.output_ordering()
}
fn execute(&self, ctx: &ExecutionContext) -> FlowResult<ValueBatchStream> {
let mut input_stream = buffer_stream(
self.input.execute(ctx)?,
self.input.access_mode(),
self.input.cardinality_hint(),
ctx.root().ctx.config.exec.operator_buffer_size,
);
let ctx = ctx.clone();
let stream = stream::try_async_stream(async move |mut yielder: Yielder<_>| {
let mut seen: HashSet<RecordId> = HashSet::default();
while let Some(batch_result) = input_stream.next().await {
crate::exec::operators::check_cancelled(&ctx)?;
let batch = batch_result?;
let mut values = Vec::new();
for value in batch.into_values() {
match extract_rid(&value) {
Some(rid) => {
if seen.insert(rid) {
values.push(value);
}
}
None => values.push(value),
}
}
if !values.is_empty() {
yielder.emit(ValueBatch::new(values)).await;
}
}
Ok(())
});
Ok(monitor_stream(Box::pin(stream), "DistinctRecords", &self.metrics))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::operators::test_util::{ValuesOperator, collect, root_ctx};
use crate::val::{Object, Value};
fn row(table: &str, id: i64, tag: &str) -> Value {
let mut o = Object::default();
o.insert("id".to_string(), Value::RecordId(RecordId::new(table.into(), id)));
o.insert("tag".to_string(), Value::from(tag));
Value::Object(o)
}
#[tokio::test]
async fn a_repeated_record_id_is_admitted_once() {
let input = ValuesOperator::new(vec![row("t", 1, "x"), row("t", 1, "y"), row("t", 2, "z")]);
let op: Arc<dyn ExecOperator> = Arc::new(DistinctRecords::new(input));
let out = collect(&op, &root_ctx()).await;
assert_eq!(
out,
vec![row("t", 1, "x"), row("t", 2, "z")],
"the first row for an id survives"
);
}
#[tokio::test]
async fn ids_from_different_tables_are_different_records() {
let input = ValuesOperator::new(vec![row("a", 1, "x"), row("b", 1, "x")]);
let op: Arc<dyn ExecOperator> = Arc::new(DistinctRecords::new(input));
let out = collect(&op, &root_ctx()).await;
assert_eq!(out.len(), 2, "the table is part of the identity");
}
#[tokio::test]
async fn a_row_without_an_id_passes_through() {
let input = ValuesOperator::new(vec![Value::from(1), Value::from(1), row("t", 1, "x")]);
let op: Arc<dyn ExecOperator> = Arc::new(DistinctRecords::new(input));
let out = collect(&op, &root_ctx()).await;
assert_eq!(out.len(), 3);
}
}