laminar_sql/datafusion/exec/
mod.rs1use std::fmt::{Debug, Formatter};
8use std::sync::Arc;
9
10use arrow_schema::{SchemaRef, SortOptions};
11use datafusion::execution::{SendableRecordBatchStream, TaskContext};
12use datafusion::physical_expr::{
13 expressions::Column, EquivalenceProperties, LexOrdering, Partitioning, PhysicalSortExpr,
14};
15use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType, SchedulingType};
16use datafusion::physical_plan::{DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties};
17use datafusion_common::DataFusionError;
18use datafusion_expr::Expr;
19
20use super::source::{SortColumn, StreamSourceRef};
21
22pub struct StreamingScanExec {
34 source: StreamSourceRef,
36 schema: SchemaRef,
38 projection: Option<Vec<usize>>,
40 filters: Vec<Expr>,
42 properties: Arc<PlanProperties>,
44}
45
46impl StreamingScanExec {
47 pub fn new(
53 source: StreamSourceRef,
54 projection: Option<Vec<usize>>,
55 filters: Vec<Expr>,
56 ) -> Self {
57 let source_schema = source.schema();
58 let source_ordering = source.output_ordering();
59
60 let schema = match &projection {
61 Some(indices) => {
62 let fields: Vec<_> = indices
63 .iter()
64 .map(|&i| source_schema.field(i).clone())
65 .collect();
66 Arc::new(arrow_schema::Schema::new(fields))
67 }
68 None => source_schema,
69 };
70
71 let eq_properties = Self::build_equivalence_properties(&schema, source_ordering.as_deref());
72
73 let properties = Arc::new(
77 PlanProperties::new(
78 eq_properties,
79 Partitioning::UnknownPartitioning(1),
80 EmissionType::Incremental,
81 Boundedness::Unbounded {
82 requires_infinite_memory: false,
83 },
84 )
85 .with_scheduling_type(SchedulingType::NonCooperative),
86 );
87
88 Self {
89 source,
90 schema,
91 projection,
92 filters,
93 properties,
94 }
95 }
96
97 fn build_equivalence_properties(
102 schema: &SchemaRef,
103 ordering: Option<&[SortColumn]>,
104 ) -> EquivalenceProperties {
105 let mut eq = EquivalenceProperties::new(Arc::clone(schema));
106
107 if let Some(sort_columns) = ordering {
108 let sort_exprs: Vec<PhysicalSortExpr> = sort_columns
109 .iter()
110 .filter_map(|sc| {
111 schema.index_of(&sc.name).ok().map(|idx| {
113 PhysicalSortExpr::new(
114 Arc::new(Column::new(&sc.name, idx)),
115 SortOptions {
116 descending: sc.descending,
117 nulls_first: sc.nulls_first,
118 },
119 )
120 })
121 })
122 .collect();
123
124 if !sort_exprs.is_empty() {
125 eq.add_ordering(sort_exprs);
126 }
127 }
128
129 eq
130 }
131
132 #[must_use]
134 pub fn source(&self) -> &StreamSourceRef {
135 &self.source
136 }
137
138 #[must_use]
140 pub fn projection(&self) -> Option<&[usize]> {
141 self.projection.as_deref()
142 }
143
144 #[must_use]
146 pub fn filters(&self) -> &[Expr] {
147 &self.filters
148 }
149}
150
151impl Debug for StreamingScanExec {
152 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("StreamingScanExec")
154 .field("source", &self.source)
155 .field("schema", &self.schema)
156 .field("projection", &self.projection)
157 .field("filters", &self.filters)
158 .finish_non_exhaustive()
159 }
160}
161
162impl DisplayAs for StreamingScanExec {
163 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter<'_>) -> std::fmt::Result {
164 match t {
165 DisplayFormatType::Default | DisplayFormatType::Verbose => {
166 write!(f, "StreamingScanExec: ")?;
167 if let Some(proj) = &self.projection {
168 write!(f, "projection=[{proj:?}]")?;
169 } else {
170 write!(f, "projection=[*]")?;
171 }
172 if !self.filters.is_empty() {
173 write!(f, ", filters={:?}", self.filters)?;
174 }
175 Ok(())
176 }
177 DisplayFormatType::TreeRender => {
178 write!(f, "StreamingScanExec")
179 }
180 }
181 }
182}
183
184impl ExecutionPlan for StreamingScanExec {
185 fn as_any(&self) -> &dyn std::any::Any {
186 self
187 }
188
189 fn name(&self) -> &'static str {
190 "StreamingScanExec"
191 }
192
193 fn schema(&self) -> SchemaRef {
194 Arc::clone(&self.schema)
195 }
196
197 fn properties(&self) -> &Arc<PlanProperties> {
198 &self.properties
199 }
200
201 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
202 vec![]
204 }
205
206 fn with_new_children(
207 self: Arc<Self>,
208 children: Vec<Arc<dyn ExecutionPlan>>,
209 ) -> Result<Arc<dyn ExecutionPlan>, DataFusionError> {
210 if children.is_empty() {
211 Ok(self)
213 } else {
214 Err(DataFusionError::Plan(
215 "StreamingScanExec cannot have children".to_string(),
216 ))
217 }
218 }
219
220 fn execute(
221 &self,
222 partition: usize,
223 _context: Arc<TaskContext>,
224 ) -> Result<SendableRecordBatchStream, DataFusionError> {
225 if partition != 0 {
226 return Err(DataFusionError::Plan(format!(
227 "StreamingScanExec only supports partition 0, got {partition}"
228 )));
229 }
230
231 self.source
232 .stream(self.projection.clone(), self.filters.clone())
233 }
234}
235
236impl datafusion::physical_plan::ExecutionPlanProperties for StreamingScanExec {
238 fn output_partitioning(&self) -> &Partitioning {
239 self.properties.output_partitioning()
240 }
241
242 fn output_ordering(&self) -> Option<&LexOrdering> {
243 self.properties.output_ordering()
244 }
245
246 fn boundedness(&self) -> Boundedness {
247 Boundedness::Unbounded {
248 requires_infinite_memory: false,
249 }
250 }
251
252 fn pipeline_behavior(&self) -> EmissionType {
253 EmissionType::Incremental
254 }
255
256 fn equivalence_properties(&self) -> &EquivalenceProperties {
257 self.properties.equivalence_properties()
258 }
259}
260
261#[cfg(test)]
262mod tests;