use super::physical::{JoinPlan, TaskPhysical};
use crate::{
error::Result,
expr::SortExpr,
plan::{NodeId, WindowSpec},
};
#[derive(Debug, Clone, PartialEq)]
pub struct CompiledPipeline {
tasks: Vec<PipelineTask>,
edges: Vec<PipelineEdge>,
barriers: Vec<PipelineBarrier>,
resources: Vec<ResidentResource>,
sink: SinkBoundary,
}
impl CompiledPipeline {
pub(crate) fn new(
tasks: Vec<PipelineTask>,
edges: Vec<PipelineEdge>,
barriers: Vec<PipelineBarrier>,
resources: Vec<ResidentResource>,
sink: SinkBoundary,
) -> Self {
Self { tasks, edges, barriers, resources, sink }
}
#[cfg(any(test, feature = "test-hooks"))]
#[doc(hidden)]
pub fn from_parts_unvalidated(
tasks: Vec<PipelineTask>,
edges: Vec<PipelineEdge>,
barriers: Vec<PipelineBarrier>,
resources: Vec<ResidentResource>,
sink: SinkBoundary,
) -> Self {
Self::new(tasks, edges, barriers, resources, sink)
}
pub fn tasks(&self) -> &[PipelineTask] {
&self.tasks
}
pub fn edges(&self) -> &[PipelineEdge] {
&self.edges
}
pub fn barriers(&self) -> &[PipelineBarrier] {
&self.barriers
}
pub fn resources(&self) -> &[ResidentResource] {
&self.resources
}
pub fn sink(&self) -> &SinkBoundary {
&self.sink
}
pub fn debug_render(&self) -> String {
self.to_string()
}
pub fn validate(&self) -> Result<()> {
super::validate::validate_pipeline(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PipelineTaskId(usize);
impl PipelineTaskId {
pub const fn new(index: usize) -> Self {
Self(index)
}
pub const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PipelineEdgeId(usize);
impl PipelineEdgeId {
pub const fn new(index: usize) -> Self {
Self(index)
}
pub(crate) const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PipelineBarrierId(usize);
impl PipelineBarrierId {
pub const fn new(index: usize) -> Self {
Self(index)
}
pub(crate) const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ResidentResourceId(usize);
impl ResidentResourceId {
pub const fn new(index: usize) -> Self {
Self(index)
}
pub(crate) const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipelineTask {
id: PipelineTaskId,
physical: TaskPhysical,
plan_node: Option<NodeId>,
input_resources: Vec<ResidentResourceId>,
output_resources: Vec<ResidentResourceId>,
accepted_input_form: AcceptedInputForm,
}
impl PipelineTask {
pub fn new(id: PipelineTaskId, physical: TaskPhysical) -> Self {
Self {
id,
physical,
plan_node: None,
input_resources: Vec::new(),
output_resources: Vec::new(),
accepted_input_form: AcceptedInputForm::Frame,
}
}
pub(crate) fn for_plan_node(id: PipelineTaskId, physical: TaskPhysical, plan_node: NodeId) -> Self {
Self { plan_node: Some(plan_node), ..Self::new(id, physical) }
}
pub fn with_input_resources(mut self, input_resources: Vec<ResidentResourceId>) -> Self {
self.input_resources = input_resources;
self
}
pub fn with_output_resources(mut self, output_resources: Vec<ResidentResourceId>) -> Self {
self.output_resources = output_resources;
self
}
pub(crate) fn with_accepted_input_form(mut self, accepted_input_form: AcceptedInputForm) -> Self {
self.accepted_input_form = accepted_input_form;
self
}
pub(crate) fn with_physical(mut self, physical: TaskPhysical) -> Self {
self.physical = physical;
self
}
pub fn id(&self) -> PipelineTaskId {
self.id
}
pub fn kind(&self) -> PipelineTaskKind {
self.physical.kind()
}
pub fn physical(&self) -> &TaskPhysical {
&self.physical
}
pub fn plan_node(&self) -> Option<NodeId> {
self.plan_node
}
pub fn input_resources(&self) -> &[ResidentResourceId] {
&self.input_resources
}
pub fn output_resources(&self) -> &[ResidentResourceId] {
&self.output_resources
}
pub fn accepted_input_form(&self) -> AcceptedInputForm {
self.accepted_input_form
}
pub fn sort_strategy(&self) -> Option<SortStrategy> {
match self.physical {
TaskPhysical::FinalSort(strategy) => Some(strategy),
_ => None,
}
}
pub fn join_plan(&self) -> Option<&JoinPlan> {
match &self.physical {
TaskPhysical::JoinBuild(plan) | TaskPhysical::JoinProbe(plan) => Some(plan),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AcceptedInputForm {
Frame,
FrameOrStream,
FrameStreamOrSourceStream,
AnyProducedForm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SortStrategy {
FullSort,
FusedTopK {
limit_task: PipelineTaskId,
limit_node: NodeId,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PipelineTaskKind {
SourceRead,
GpuResidentSource,
LiteralBatchSource,
Unary,
Aggregate,
Window,
JoinBuild,
JoinProbe,
JoinMaterialize,
FinalSort,
Limit,
Union,
EdgeNormalize,
GraphAlgorithm,
VectorAlgorithm,
MlAlgorithm,
SinkMaterialize,
}
impl PipelineTaskKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::SourceRead => "source_read",
Self::GpuResidentSource => "gpu_resident_source",
Self::LiteralBatchSource => "literal_batch_source",
Self::Unary => "unary",
Self::Aggregate => "aggregate",
Self::Window => "window",
Self::JoinBuild => "join_build",
Self::JoinProbe => "join_probe",
Self::JoinMaterialize => "join_materialize",
Self::FinalSort => "final_sort",
Self::Limit => "limit",
Self::Union => "union",
Self::EdgeNormalize => "edge_normalize",
Self::GraphAlgorithm => "graph_algorithm",
Self::VectorAlgorithm => "vector_algorithm",
Self::MlAlgorithm => "ml_algorithm",
Self::SinkMaterialize => "sink_materialize",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipelineEdge {
id: PipelineEdgeId,
from: PipelineTaskId,
to: PipelineTaskId,
kind: PipelineEdgeKind,
resources: Vec<ResidentResourceId>,
}
impl PipelineEdge {
pub fn new(id: PipelineEdgeId, from: PipelineTaskId, to: PipelineTaskId, kind: PipelineEdgeKind) -> Self {
Self { id, from, to, kind, resources: Vec::new() }
}
pub fn with_resources(mut self, resources: Vec<ResidentResourceId>) -> Self {
self.resources = resources;
self
}
pub fn id(&self) -> PipelineEdgeId {
self.id
}
pub fn from(&self) -> PipelineTaskId {
self.from
}
pub fn to(&self) -> PipelineTaskId {
self.to
}
pub fn kind(&self) -> PipelineEdgeKind {
self.kind
}
pub fn resources(&self) -> &[ResidentResourceId] {
&self.resources
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PipelineEdgeKind {
Control,
ResidentFrame,
JoinBuildInput,
JoinProbeInput,
SharedResidentProducer,
BarrierOutput,
SinkInput,
}
impl PipelineEdgeKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Control => "control",
Self::ResidentFrame => "resident_frame",
Self::JoinBuildInput => "join_build_input",
Self::JoinProbeInput => "join_probe_input",
Self::SharedResidentProducer => "shared_resident_producer",
Self::BarrierOutput => "barrier_output",
Self::SinkInput => "sink_input",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipelineBarrier {
id: PipelineBarrierId,
kind: PipelineBarrierKind,
upstream_tasks: Vec<PipelineTaskId>,
downstream_tasks: Vec<PipelineTaskId>,
}
impl PipelineBarrier {
pub fn new(id: PipelineBarrierId, kind: PipelineBarrierKind) -> Self {
Self { id, kind, upstream_tasks: Vec::new(), downstream_tasks: Vec::new() }
}
pub fn with_upstream_tasks(mut self, upstream_tasks: Vec<PipelineTaskId>) -> Self {
self.upstream_tasks = upstream_tasks;
self
}
pub fn with_downstream_tasks(mut self, downstream_tasks: Vec<PipelineTaskId>) -> Self {
self.downstream_tasks = downstream_tasks;
self
}
pub fn id(&self) -> PipelineBarrierId {
self.id
}
pub fn kind(&self) -> PipelineBarrierKind {
self.kind
}
pub fn upstream_tasks(&self) -> &[PipelineTaskId] {
&self.upstream_tasks
}
pub fn downstream_tasks(&self) -> &[PipelineTaskId] {
&self.downstream_tasks
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PipelineBarrierKind {
SourceChunkBoundary,
AggregateBoundary,
JoinBuildBeforeProbe,
JoinMaterialization,
SortBoundary,
SinkBoundary,
}
impl PipelineBarrierKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::SourceChunkBoundary => "source_chunk_boundary",
Self::AggregateBoundary => "aggregate_boundary",
Self::JoinBuildBeforeProbe => "join_build_before_probe",
Self::JoinMaterialization => "join_materialization",
Self::SortBoundary => "sort_boundary",
Self::SinkBoundary => "sink_boundary",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResidentResource {
id: ResidentResourceId,
producer: PipelineTaskId,
consumers: Vec<PipelineTaskId>,
lifetime: ResidentLifetime,
data_form: ResidentDataForm,
sharing: ResourceSharing,
ordering: Option<ResourceOrdering>,
}
impl ResidentResource {
pub fn new(
id: ResidentResourceId,
producer: PipelineTaskId,
consumers: Vec<PipelineTaskId>,
lifetime: ResidentLifetime,
) -> Self {
let sharing =
if consumers.len() > 1 { ResourceSharing::SharedImmutableFrame } else { ResourceSharing::SingleConsume };
Self { id, producer, consumers, lifetime, data_form: ResidentDataForm::Frame, sharing, ordering: None }
}
pub(crate) fn with_consumption(mut self, data_form: ResidentDataForm, sharing: ResourceSharing) -> Self {
self.data_form = data_form;
self.sharing = sharing;
self
}
pub(crate) fn with_ordering(mut self, ordering: Option<ResourceOrdering>) -> Self {
self.ordering = ordering;
self
}
pub fn id(&self) -> ResidentResourceId {
self.id
}
pub fn producer(&self) -> PipelineTaskId {
self.producer
}
pub fn consumers(&self) -> &[PipelineTaskId] {
&self.consumers
}
pub fn lifetime(&self) -> ResidentLifetime {
self.lifetime
}
pub fn data_form(&self) -> ResidentDataForm {
self.data_form
}
pub fn sharing(&self) -> ResourceSharing {
self.sharing
}
pub fn ordering(&self) -> Option<&ResourceOrdering> {
self.ordering.as_ref()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResourceOrdering {
keys: Vec<SortExpr>,
scope: OrderingScope,
}
impl ResourceOrdering {
pub fn keys(&self) -> &[SortExpr] {
&self.keys
}
pub fn scope(&self) -> OrderingScope {
self.scope
}
pub(crate) fn global(keys: Vec<SortExpr>) -> Self {
Self { keys, scope: OrderingScope::Global }
}
pub(crate) fn with_keys(&self, keys: Vec<SortExpr>) -> Self {
Self { keys, scope: self.scope }
}
pub(crate) fn covers_window(&self, window: &WindowSpec) -> bool {
if self.scope != OrderingScope::Global {
return false;
}
let partition_prefix = self.keys.iter().take_while(|key| window.partition_by.contains(&key.expr)).count();
let prefix = &self.keys[..partition_prefix];
let order_keys: Vec<&SortExpr> =
window.order_by.iter().filter(|key| !window.partition_by.contains(&key.expr)).collect();
let rest = &self.keys[partition_prefix..];
window.partition_by.iter().all(|expr| prefix.iter().any(|key| key.expr == *expr))
&& rest.len() >= order_keys.len()
&& rest.iter().zip(order_keys).all(|(carried, required)| carried == required)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OrderingScope {
PerChunk,
Global,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResidentDataForm {
Frame,
FrameStream,
SourceStream,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceSharing {
SingleConsume,
SharedImmutableFrame,
IndependentCursorStream,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResidentLifetime {
TaskLocal {
task: PipelineTaskId,
},
BetweenTasks {
producer: PipelineTaskId,
},
SinkOutput {
producer: PipelineTaskId,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SinkBoundary {
kind: SinkBoundaryKind,
input: PipelineTaskId,
}
impl SinkBoundary {
pub fn new(kind: SinkBoundaryKind, input: PipelineTaskId) -> Self {
Self { kind, input }
}
pub fn kind(&self) -> SinkBoundaryKind {
self.kind
}
pub fn input(&self) -> PipelineTaskId {
self.input
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SinkBoundaryKind {
HostArrowMaterialization,
GpuResidentOutput,
}
impl SinkBoundaryKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::HostArrowMaterialization => "host_arrow_materialization",
Self::GpuResidentOutput => "gpu_resident_output",
}
}
}