use std::fmt;
use super::{AdvisoryOutputStatistics, ColumnRef, Expr, NodeId};
#[derive(Debug, Clone, PartialEq)]
pub struct JoinNode {
pub inputs: [NodeId; 2],
pub spec: JoinSpec,
}
#[derive(Debug, Clone, PartialEq)]
pub struct JoinSpec {
pub join_type: JoinType,
pub keys: Vec<JoinKey>,
pub residual_predicate: Option<Expr>,
pub output_projection: Vec<JoinOutputColumn>,
pub null_equality: JoinNullEquality,
pub build_side: JoinSide,
pub datafusion_order_preserved_side: JoinSide,
pub build_artifact_id: Option<HashJoinBuildArtifactId>,
pub(crate) at_most_one_match_side: Option<JoinSide>,
#[doc(hidden)]
pub advisory_output_statistics: Option<AdvisoryOutputStatistics>,
}
impl JoinSpec {
pub fn new(join_type: JoinType, keys: Vec<JoinKey>) -> Self {
Self {
join_type,
keys,
residual_predicate: None,
output_projection: Vec::new(),
null_equality: JoinNullEquality::NullsDoNotMatch,
build_side: JoinSide::Right,
datafusion_order_preserved_side: JoinSide::Right,
build_artifact_id: None,
at_most_one_match_side: None,
advisory_output_statistics: None,
}
}
pub fn with_residual_predicate(mut self, predicate: Expr) -> Self {
self.residual_predicate = Some(predicate);
self
}
pub fn with_output_projection(mut self, output_projection: Vec<JoinOutputColumn>) -> Self {
self.output_projection = output_projection;
self
}
pub fn with_null_equality(mut self, null_equality: JoinNullEquality) -> Self {
self.null_equality = null_equality;
self
}
pub fn with_build_side(mut self, build_side: JoinSide) -> Self {
self.build_side = build_side;
self
}
pub fn with_datafusion_order_preserved_side(mut self, side: JoinSide) -> Self {
self.datafusion_order_preserved_side = side;
self
}
pub fn with_build_artifact_id(mut self, id: impl Into<String>) -> Self {
self.build_artifact_id = Some(HashJoinBuildArtifactId::new(id));
self
}
pub fn with_at_most_one_match_per_bounded_row(mut self, bounded_side: JoinSide) -> Self {
self.at_most_one_match_side = Some(bounded_side);
self
}
#[doc(hidden)]
pub fn with_advisory_output_statistics(mut self, statistics: AdvisoryOutputStatistics) -> Self {
self.advisory_output_statistics = Some(statistics);
self
}
pub(crate) fn has_at_most_one_match_per_bounded_row(&self, bounded_side: JoinSide) -> bool {
self.at_most_one_match_side == Some(bounded_side)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HashJoinBuildArtifactId(String);
impl HashJoinBuildArtifactId {
pub(crate) fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Display for HashJoinBuildArtifactId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JoinKey {
pub left: ColumnRef,
pub right: ColumnRef,
}
impl JoinKey {
pub fn new(left: ColumnRef, right: ColumnRef) -> Self {
Self { left, right }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JoinOutputColumn {
pub side: JoinSide,
pub column: ColumnRef,
pub output_name: String,
}
impl JoinOutputColumn {
pub fn new(side: JoinSide, column: ColumnRef, output_name: impl Into<String>) -> Self {
Self { side, column, output_name: output_name.into() }
}
pub fn left(column: ColumnRef, output_name: impl Into<String>) -> Self {
Self::new(JoinSide::Left, column, output_name)
}
pub fn right(column: ColumnRef, output_name: impl Into<String>) -> Self {
Self::new(JoinSide::Right, column, output_name)
}
}
impl fmt::Display for JoinOutputColumn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{} AS {}", self.side, self.column.name(), self.output_name)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum JoinType {
Inner,
Left,
Full,
LeftSemi,
LeftAnti {
null_aware: bool,
},
Cross,
LeftMark {
mark_column: String,
},
}
impl JoinType {
pub const LEFT_ANTI: Self = Self::LeftAnti { null_aware: false };
pub fn is_null_aware(&self) -> bool {
matches!(self, Self::LeftAnti { null_aware: true })
}
pub fn mark_column(&self) -> Option<&str> {
match self {
Self::LeftMark { mark_column } => Some(mark_column),
Self::Inner | Self::Left | Self::Full | Self::LeftSemi | Self::LeftAnti { .. } | Self::Cross => None,
}
}
}
impl fmt::Display for JoinType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Inner => "inner",
Self::Left => "left",
Self::Full => "full",
Self::LeftSemi => "left_semi",
Self::LeftAnti { .. } => "left_anti",
Self::Cross => "cross",
Self::LeftMark { .. } => "left_mark",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JoinNullEquality {
NullsDoNotMatch,
NullsMatch,
}
impl fmt::Display for JoinNullEquality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::NullsDoNotMatch => "nulls_do_not_match",
Self::NullsMatch => "nulls_match",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JoinSide {
Left,
Right,
}
impl fmt::Display for JoinSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Left => "left",
Self::Right => "right",
})
}
}