#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AdvisoryStatistic {
Exact(u64),
Inexact(u64),
Absent,
}
impl AdvisoryStatistic {
pub const fn value(self) -> Option<u64> {
match self {
Self::Exact(value) | Self::Inexact(value) => Some(value),
Self::Absent => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AdvisoryOutputStatistics {
pub rows: AdvisoryStatistic,
pub bytes: AdvisoryStatistic,
}
impl AdvisoryOutputStatistics {
pub const fn is_absent(self) -> bool {
matches!((self.rows, self.bytes), (AdvisoryStatistic::Absent, AdvisoryStatistic::Absent))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AdvisoryUnknownReason {
MissingStatistics,
StatisticsComputationFailed,
OutputSchemaChanged,
LimitBytesUnavailable,
CartesianFallback,
RewrittenSubtree,
ConflictingOccurrences,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdvisoryOutputBinding {
pub node: super::NodeId,
pub schema: super::SchemaRef,
pub frontend: String,
pub frontend_version: String,
pub statistics: AdvisoryOutputStatistics,
pub unknown_reason: Option<AdvisoryUnknownReason>,
}
impl AdvisoryOutputBinding {
pub(super) fn invalidate(&mut self, reason: AdvisoryUnknownReason) {
self.statistics.rows = AdvisoryStatistic::Absent;
self.statistics.bytes = AdvisoryStatistic::Absent;
self.unknown_reason = Some(reason);
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arrow::datatypes::{DataType, Field, Schema};
use super::*;
use crate::{
expr::{Expr, LimitSpec},
plan::{PlanNode, ProjectionOutput, QueryPlanBuilder, RewrittenNodes},
source::{GpuResidentSource, SourceNode},
};
#[test]
fn advisory_rewrite_invalidates_changed_dependency_without_changing_execution_identity() {
let schema = Arc::new(Schema::new(vec![Field::new("key", DataType::Int64, false)]));
let mut builder = QueryPlanBuilder::new();
let source = builder.add_source(SourceNode::GpuResident(GpuResidentSource::new("input", Arc::clone(&schema))));
let root = builder
.add_projection(source, vec![ProjectionOutput::new(Expr::column("key"), schema.field(0).clone())])
.unwrap();
let plain = builder.clone().finish_gpu_resident(root).unwrap();
builder
.set_advisory_output(AdvisoryOutputBinding {
node: root,
schema: Arc::clone(&schema),
frontend: "test frontend".to_owned(),
frontend_version: "1".to_owned(),
statistics: AdvisoryOutputStatistics {
rows: AdvisoryStatistic::Inexact(100),
bytes: AdvisoryStatistic::Inexact(800),
},
unknown_reason: None,
})
.unwrap();
let plan = builder.clone().finish_gpu_resident(root).unwrap();
assert_eq!(plain.execution_fingerprint(), plan.execution_fingerprint());
let unchanged = plan.rewrite(RewrittenNodes::in_place(plan.nodes().to_vec(), root)).unwrap();
assert_eq!(unchanged.advisory_outputs(), plan.advisory_outputs());
let mut nodes = plan.nodes().to_vec();
nodes[source.index()].kind = PlanNode::Source(Box::new(SourceNode::GpuResident(GpuResidentSource::new(
"different input",
Arc::clone(&schema),
))));
let rewritten = plan.rewrite(RewrittenNodes::in_place(nodes, root)).unwrap();
assert!(rewritten.advisory_outputs()[0].statistics.is_absent());
assert_eq!(rewritten.advisory_outputs()[0].unknown_reason, Some(AdvisoryUnknownReason::RewrittenSubtree));
let limited = builder.add_limit(root, LimitSpec { skip: 90, fetch: Some(20) }).unwrap();
let limited = builder.finish_gpu_resident(limited).unwrap();
let limit_statistics =
limited.advisory_outputs().iter().find(|binding| binding.node == limited.root_id()).unwrap();
assert_eq!(limit_statistics.statistics.rows, AdvisoryStatistic::Inexact(10));
assert_eq!(limit_statistics.statistics.bytes, AdvisoryStatistic::Absent);
}
}