use std::sync::Arc;
use arrow::datatypes::{Schema, SchemaRef};
use datafusion::{
common::JoinSide as DataFusionJoinSide, logical_expr::JoinType as DataFusionJoinType, physical_plan::ExecutionPlan,
};
use query_planning::{
expr::ColumnRef as NativeColumnRef,
plan::{JoinOutputColumn as NativeJoinOutputColumn, JoinSide as NativeJoinSide},
};
use super::{
FieldBinding, LoweredNativeInput, RequiredFields, executable_schema_and_bindings,
join::{CanonicalJoin, CanonicalJoinKind},
};
use crate::{
capability::CapabilityReason,
native::{
LoweringFailure,
normalize::{NormalizedNativeCrossJoin, NormalizedNativeHashJoin, NormalizedNativeNestedLoopJoin},
},
};
pub(super) struct JoinOutputColumns {
pub(super) projection: Vec<NativeJoinOutputColumn>,
pub(super) schema: SchemaRef,
pub(super) bindings: Vec<FieldBinding>,
pub(super) mark_column_name: Option<String>,
}
pub(super) struct JoinOutputRequests {
pub(super) outputs: Vec<JoinOutputRequest>,
schema: SchemaRef,
bindings: Vec<FieldBinding>,
mark_column_name: Option<String>,
mark_binding_index: Option<usize>,
}
impl JoinOutputRequests {
pub(super) fn lower(
self,
left: &LoweredNativeInput,
right: &LoweredNativeInput,
canonical: CanonicalJoin,
) -> Result<JoinOutputColumns, LoweringFailure> {
if self.outputs.is_empty() {
return hidden_row_count_preserving_output(left, right, canonical);
}
let non_mark_bindings: Vec<_> = self
.bindings
.iter()
.enumerate()
.filter(|(index, _)| self.mark_binding_index.is_none_or(|mark_index| *index != mark_index))
.map(|(_, binding)| binding)
.collect();
let projection = self
.outputs
.iter()
.zip(non_mark_bindings.iter())
.map(|(request, output_binding)| {
let side = canonical.native_side_for_datafusion_side(request.datafusion_side)?;
let input = if request.datafusion_side == DataFusionJoinSide::Left { left } else { right };
let input_binding = input.binding_for_frontend_index(request.input_index)?;
let input_ordinal = match side {
NativeJoinSide::Left => 0,
NativeJoinSide::Right => 1,
};
let column = NativeColumnRef::input(
input_ordinal,
input_binding.native_name.as_str(),
input_binding.frontend_name.as_str(),
);
Ok(NativeJoinOutputColumn::new(side, column, output_binding.native_name.clone()))
})
.collect::<Result<Vec<_>, LoweringFailure>>()?;
Ok(JoinOutputColumns {
projection,
schema: self.schema,
bindings: self.bindings,
mark_column_name: self.mark_column_name,
})
}
}
fn hidden_row_count_preserving_output(
left: &LoweredNativeInput,
right: &LoweredNativeInput,
canonical: CanonicalJoin,
) -> Result<JoinOutputColumns, LoweringFailure> {
const HIDDEN_JOIN_ROW_PRESENCE_COLUMN: &str = "__algeon_native_join_row_presence";
let (datafusion_side, input, input_binding) =
hidden_row_count_input(left, right, canonical).ok_or(CapabilityReason::UnsupportedExpression)?;
let native_side = canonical.native_side_for_datafusion_side(datafusion_side)?;
let input_ordinal = match native_side {
NativeJoinSide::Left => 0,
NativeJoinSide::Right => 1,
};
let input_field = input
.schema
.field_with_name(input_binding.native_name.as_str())
.map_err(|_error| CapabilityReason::UnsupportedExpression)?;
let field = input_field.clone().with_name(HIDDEN_JOIN_ROW_PRESENCE_COLUMN);
let projection = vec![NativeJoinOutputColumn::new(
native_side,
NativeColumnRef::input(input_ordinal, input_binding.native_name.as_str(), input_binding.frontend_name.as_str()),
HIDDEN_JOIN_ROW_PRESENCE_COLUMN.to_string(),
)];
let schema = Arc::new(Schema::new(vec![field]));
let bindings = vec![FieldBinding {
frontend_index: usize::MAX,
frontend_name: HIDDEN_JOIN_ROW_PRESENCE_COLUMN.to_string(),
native_name: HIDDEN_JOIN_ROW_PRESENCE_COLUMN.to_string(),
}];
Ok(JoinOutputColumns { projection, schema, bindings, mark_column_name: None })
}
fn hidden_row_count_input<'a>(
left: &'a LoweredNativeInput,
right: &'a LoweredNativeInput,
canonical: CanonicalJoin,
) -> Option<(DataFusionJoinSide, &'a LoweredNativeInput, &'a FieldBinding)> {
if matches!(canonical.kind(), CanonicalJoinKind::LeftSemi | CanonicalJoinKind::LeftAnti) {
return first_binding_for_side(left, right, canonical.datafusion_side_for_native_left());
}
first_binding_for_side(left, right, DataFusionJoinSide::Left)
.or_else(|| first_binding_for_side(left, right, DataFusionJoinSide::Right))
}
fn first_binding_for_side<'a>(
left: &'a LoweredNativeInput,
right: &'a LoweredNativeInput,
side: DataFusionJoinSide,
) -> Option<(DataFusionJoinSide, &'a LoweredNativeInput, &'a FieldBinding)> {
match side {
DataFusionJoinSide::Left => left.bindings.first().map(|binding| (DataFusionJoinSide::Left, left, binding)),
DataFusionJoinSide::Right => right.bindings.first().map(|binding| (DataFusionJoinSide::Right, right, binding)),
DataFusionJoinSide::None => None,
}
}
pub(super) struct JoinOutputRequest {
pub(super) datafusion_side: DataFusionJoinSide,
pub(super) input_index: usize,
pub(super) predicate_required: bool,
}
pub(super) fn hash_join_output_requests(
join: &NormalizedNativeHashJoin<'_>,
required_output_fields: &RequiredFields,
) -> Result<JoinOutputRequests, LoweringFailure> {
join_output_requests(
required_output_fields,
&join.join.left().schema(),
&join.join.right().schema(),
&join.join.schema(),
join.join.projection.as_deref(),
join.join.join_type(),
)
}
pub(super) fn nested_loop_join_output_requests(
join: &NormalizedNativeNestedLoopJoin<'_>,
required_output_fields: &RequiredFields,
) -> Result<JoinOutputRequests, LoweringFailure> {
join_output_requests(
required_output_fields,
&join.join.left().schema(),
&join.join.right().schema(),
&join.join.schema(),
join.join.projection().as_deref(),
join.join.join_type(),
)
}
pub(super) fn cross_join_output_requests(
join: &NormalizedNativeCrossJoin<'_>,
required_output_fields: &RequiredFields,
) -> Result<JoinOutputRequests, LoweringFailure> {
join_output_requests(
required_output_fields,
&join.join.left().schema(),
&join.join.right().schema(),
&join.join.schema(),
None,
&DataFusionJoinType::Inner,
)
}
fn join_output_requests(
required_output_fields: &RequiredFields,
left_schema: &SchemaRef,
right_schema: &SchemaRef,
output_schema: &SchemaRef,
projection_indices: Option<&[usize]>,
datafusion_join_type: &DataFusionJoinType,
) -> Result<JoinOutputRequests, LoweringFailure> {
let (schema, bindings) = executable_schema_and_bindings(output_schema, required_output_fields)?;
let predicate_output_indices = required_output_fields
.predicate_indices(output_schema.fields().len())?
.into_iter()
.collect::<std::collections::BTreeSet<_>>();
let mut outputs = Vec::new();
let mut mark_column_name: Option<String> = None;
let mut mark_binding_index: Option<usize> = None;
for (binding_index, output_index) in
required_output_fields.requested_indices(output_schema.fields().len())?.into_iter().enumerate()
{
let join_schema_index = match projection_indices {
Some(projection) => *projection.get(output_index).ok_or(CapabilityReason::UnsupportedExpression)?,
None => output_index,
};
let (datafusion_side, input_index) =
datafusion_join_output_side(datafusion_join_type, join_schema_index, left_schema, right_schema)?;
if datafusion_side == DataFusionJoinSide::None {
mark_column_name = Some(bindings[binding_index].native_name.clone());
mark_binding_index = Some(binding_index);
continue;
}
let input_schema = if datafusion_side == DataFusionJoinSide::Left { left_schema } else { right_schema };
input_schema.fields().get(input_index).ok_or(CapabilityReason::UnsupportedExpression)?;
outputs.push(JoinOutputRequest {
datafusion_side,
input_index,
predicate_required: predicate_output_indices.contains(&output_index),
});
}
Ok(JoinOutputRequests { outputs, schema, bindings, mark_column_name, mark_binding_index })
}
pub(super) fn datafusion_join_output_side(
join_type: &DataFusionJoinType,
join_schema_index: usize,
left_schema: &SchemaRef,
right_schema: &SchemaRef,
) -> Result<(DataFusionJoinSide, usize), LoweringFailure> {
match join_type {
DataFusionJoinType::RightSemi | DataFusionJoinType::RightAnti => {
Ok((DataFusionJoinSide::Right, join_schema_index))
},
DataFusionJoinType::LeftSemi | DataFusionJoinType::LeftAnti => {
Ok((DataFusionJoinSide::Left, join_schema_index))
},
DataFusionJoinType::LeftMark if join_schema_index >= left_schema.fields().len() => {
Ok((DataFusionJoinSide::None, join_schema_index))
},
DataFusionJoinType::LeftMark => Ok((DataFusionJoinSide::Left, join_schema_index)),
DataFusionJoinType::RightMark if join_schema_index >= right_schema.fields().len() => {
Ok((DataFusionJoinSide::None, join_schema_index))
},
DataFusionJoinType::RightMark => Ok((DataFusionJoinSide::Right, join_schema_index)),
_ if join_schema_index < left_schema.fields().len() => Ok((DataFusionJoinSide::Left, join_schema_index)),
_ => Ok((DataFusionJoinSide::Right, join_schema_index - left_schema.fields().len())),
}
}