use std::collections::BTreeMap;
use super::{
AggregateFunction, GraphAlgorithm, JoinSide, JoinType, NodeId,
aggregate::AggregateStateContract,
rejection::{Reason, Rejection, RejectionOwner},
};
use crate::{
plan::{
EdgeDedupStrategy, EdgeWeightCombiner, MlAlgorithm, MlRelationRole, VectorAlgorithm, VectorAlgorithmNode,
VectorAlgorithmParams,
},
policy::PlanningPolicy,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CapabilityScope {
Node,
Edge,
Subgraph,
}
impl CapabilityScope {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Node => "node",
Self::Edge => "edge",
Self::Subgraph => "subgraph",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CapabilityDecision {
Satisfied,
Rejected {
reason: Reason,
facts: BTreeMap<&'static str, String>,
},
}
impl CapabilityDecision {
pub(super) fn rejected(reason: Reason, facts: impl IntoIterator<Item = (&'static str, String)>) -> Self {
Self::Rejected { reason, facts: facts.into_iter().collect() }
}
pub(crate) const fn rejection_reason(&self) -> Option<Reason> {
match self {
Self::Rejected { reason, .. } => Some(*reason),
Self::Satisfied => None,
}
}
pub(crate) fn rejection_facts(&self) -> Option<&BTreeMap<&'static str, String>> {
match self {
Self::Rejected { facts, .. } => Some(facts),
Self::Satisfied => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeCapability {
pub node_id: NodeId,
pub operator: NodeOperator,
pub executable: CapabilityDecision,
pub aggregate_state: Option<AggregateStateContract>,
pub max_output_rows: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeOperator {
Source {
format: SourceFormatCapability,
},
Filter,
Projection,
Aggregate {
grouping: AggregateGrouping,
functions: Vec<AggregateFunction>,
},
Window {
function_count: usize,
},
Sort {
ordering: OrderingRequirement,
},
Limit,
Join {
join_type: JoinType,
key_count: usize,
residual: ResidualRequirement,
build_side: JoinSide,
},
Union {
input_count: usize,
},
EdgeNormalize {
add_reverse: bool,
dedup: EdgeDedupStrategy,
weight_combiner: EdgeWeightCombiner,
},
GraphAlgorithm {
algorithm: GraphAlgorithm,
},
VectorAlgorithm {
algorithm: VectorAlgorithm,
},
MlAlgorithm {
algorithm: MlAlgorithm,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SourceFormatCapability {
LocalParquet,
GpuResident,
LiteralBatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AggregateGrouping {
Scalar,
Grouped,
GroupingSets,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OrderingRequirement {
Required,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResidualRequirement {
None,
Required,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EdgeContract {
pub producer: NodeId,
pub consumer: EdgeConsumer,
pub contract_kinds: Vec<EdgeContractKind>,
pub input_role: InputRole,
pub decision: CapabilityDecision,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EdgeContractKind {
ResidentSourceInput {
format: SourceFormatCapability,
},
ResidentAggregateInput,
WindowInput,
JoinBuildInput {
side: JoinSide,
},
JoinProbeInput {
side: JoinSide,
},
UnionInput {
input_index: usize,
},
SharedProducer,
ResidentFrameFlow,
JoinMaterialization,
SortInput,
GraphAlgorithmInput,
VectorAlgorithmInput {
role: VectorInputRole,
},
MlAlgorithmInput {
role: MlRelationRole,
},
ExplicitSinkMaterialization,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EdgeConsumer {
Node(NodeId),
Sink(ResultSinkKind),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ResultSinkKind {
HostArrow,
GpuResident,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputRole {
UnaryInput,
AggregateInput,
WindowInput,
SortInput,
UnionInput {
input_index: usize,
},
GraphAlgorithmInput,
VectorAlgorithmInput {
role: VectorInputRole,
},
MlAlgorithmInput {
role: MlRelationRole,
},
LimitInput,
JoinBuildInput {
side: JoinSide,
},
JoinProbeInput {
side: JoinSide,
},
SinkInput,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VectorInputRole {
Dataset,
Queries,
Input,
X,
Y,
InitialCentroids,
}
impl VectorInputRole {
pub const fn as_str(self) -> &'static str {
match self {
Self::Dataset => "dataset",
Self::Queries => "queries",
Self::Input => "input",
Self::X => "x",
Self::Y => "y",
Self::InitialCentroids => "initial_centroids",
}
}
pub fn for_vector_input(vector: &VectorAlgorithmNode, input_index: usize) -> Self {
match (&vector.spec.params, input_index) {
(
VectorAlgorithmParams::BruteForceKnn(_)
| VectorAlgorithmParams::IvfFlat(_)
| VectorAlgorithmParams::IvfPq(_)
| VectorAlgorithmParams::Cagra(_),
0,
) => Self::Dataset,
(
VectorAlgorithmParams::BruteForceKnn(_)
| VectorAlgorithmParams::IvfFlat(_)
| VectorAlgorithmParams::IvfPq(_)
| VectorAlgorithmParams::Cagra(_),
1,
) => Self::Queries,
(VectorAlgorithmParams::PairwiseDistance(_), 0) => Self::X,
(VectorAlgorithmParams::PairwiseDistance(_), 1) => Self::Y,
(VectorAlgorithmParams::TopK(_), 0) => Self::Input,
(VectorAlgorithmParams::KMeans(_), 0) | (VectorAlgorithmParams::Pca(_), 0) => Self::Input,
(VectorAlgorithmParams::KMeans(_), 1) => Self::InitialCentroids,
_ => Self::Input,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InvariantId(
pub usize,
);
impl InvariantId {
pub(crate) const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubgraphInvariant {
pub(crate) invariant_id: InvariantId,
pub kind: SubgraphInvariantKind,
pub nodes: Vec<NodeId>,
pub(crate) required_schedule: ScheduleConstraint,
pub(crate) required_resources: Vec<ResidentResource>,
pub decision: CapabilityDecision,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SubgraphInvariantKind {
BuildBeforeProbe {
join_id: NodeId,
},
TopologicalBuildProbeSchedule,
SharedResidentSubplan,
AggregateDerivesFromSharedProducer,
FinalBarrierOrder,
ProjectionTransparentResidency,
ResidentUnaryChainPreservesResidency,
ResidualUsesBothInputs {
join_id: NodeId,
},
ResidentLifetimeDominatesConsumers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum ScheduleConstraint {
None,
BuildBeforeProbe,
Topological,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResidentResource {
NodeOutput(NodeId),
JoinBuildInput {
join_id: NodeId,
input: NodeId,
},
JoinProbeInput {
join_id: NodeId,
input: NodeId,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CapabilityRejectionCategory {
UnsupportedExpressionFamily,
UnsupportedArrowDtype,
UnsupportedNodeOperator,
MissingEdgeContract,
MissingSubgraphInvariant,
UnsupportedSinkBoundary,
UnsupportedCompiledPipelineEnvelope,
}
impl CapabilityRejectionCategory {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::UnsupportedExpressionFamily => "unsupported_expression_family",
Self::UnsupportedArrowDtype => "unsupported_arrow_dtype",
Self::UnsupportedNodeOperator => "unsupported_node_operator",
Self::MissingEdgeContract => "missing_edge_contract",
Self::MissingSubgraphInvariant => "missing_subgraph_invariant",
Self::UnsupportedSinkBoundary => "unsupported_sink_boundary",
Self::UnsupportedCompiledPipelineEnvelope => "unsupported_compiled_pipeline_envelope",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapabilityAnalysis {
pub nodes: Vec<NodeCapability>,
pub edges: Vec<EdgeContract>,
pub invariants: Vec<SubgraphInvariant>,
pub(crate) policy: PlanningPolicy,
}
impl CapabilityAnalysis {
pub fn has_rejections(&self) -> bool {
self.nodes.iter().any(|node| matches!(node.executable, CapabilityDecision::Rejected { .. }))
|| self.edges.iter().any(|edge| matches!(edge.decision, CapabilityDecision::Rejected { .. }))
|| self.invariants.iter().any(|invariant| matches!(invariant.decision, CapabilityDecision::Rejected { .. }))
}
pub fn first_rejection(&self) -> Option<Rejection> {
self.nodes
.iter()
.find_map(|node| {
Some(Rejection {
owner: RejectionOwner::Node(node.node_id),
reason: node.executable.rejection_reason()?,
facts: node.executable.rejection_facts()?.clone(),
})
})
.or_else(|| {
self.edges.iter().find_map(|edge| {
Some(Rejection {
owner: RejectionOwner::Edge { producer: edge.producer, consumer: edge.consumer },
reason: edge.decision.rejection_reason()?,
facts: edge.decision.rejection_facts()?.clone(),
})
})
})
.or_else(|| {
self.invariants.iter().find_map(|invariant| {
Some(Rejection {
owner: RejectionOwner::Invariant(invariant.invariant_id),
reason: invariant.decision.rejection_reason()?,
facts: invariant.decision.rejection_facts()?.clone(),
})
})
})
}
}