laminar_sql/datafusion/
table_provider.rs1use std::sync::Arc;
8
9use arrow_schema::SchemaRef;
10use async_trait::async_trait;
11use datafusion::catalog::Session;
12use datafusion::datasource::TableProvider;
13use datafusion::physical_plan::ExecutionPlan;
14use datafusion_common::DataFusionError;
15use datafusion_expr::{Expr, TableProviderFilterPushDown, TableType};
16
17use super::exec::StreamingScanExec;
18use super::source::StreamSourceRef;
19
20#[derive(Debug)]
39pub struct StreamingTableProvider {
40 name: String,
42 source: StreamSourceRef,
44}
45
46impl StreamingTableProvider {
47 #[must_use]
49 pub fn new(name: impl Into<String>, source: StreamSourceRef) -> Self {
50 Self {
51 name: name.into(),
52 source,
53 }
54 }
55
56 #[must_use]
58 pub fn name(&self) -> &str {
59 &self.name
60 }
61
62 #[must_use]
64 pub fn source(&self) -> &StreamSourceRef {
65 &self.source
66 }
67}
68
69#[async_trait]
70impl TableProvider for StreamingTableProvider {
71 fn as_any(&self) -> &dyn std::any::Any {
72 self
73 }
74
75 fn schema(&self) -> SchemaRef {
76 self.source.schema()
77 }
78
79 fn table_type(&self) -> TableType {
80 TableType::Base
82 }
83
84 fn supports_filters_pushdown(
85 &self,
86 filters: &[&Expr],
87 ) -> Result<Vec<TableProviderFilterPushDown>, DataFusionError> {
88 let expr_refs: Vec<Expr> = filters.iter().map(|e| (*e).clone()).collect();
90 let supported = self.source.supports_filters(&expr_refs);
91
92 Ok(supported
93 .into_iter()
94 .map(|s| {
95 if s {
96 TableProviderFilterPushDown::Exact
97 } else {
98 TableProviderFilterPushDown::Unsupported
99 }
100 })
101 .collect())
102 }
103
104 async fn scan(
105 &self,
106 _state: &dyn Session,
107 projection: Option<&Vec<usize>>,
108 filters: &[Expr],
109 _limit: Option<usize>,
110 ) -> Result<Arc<dyn ExecutionPlan>, DataFusionError> {
111 Ok(Arc::new(StreamingScanExec::new(
116 Arc::clone(&self.source),
117 projection.cloned(),
118 filters.to_vec(),
119 )))
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::datafusion::source::StreamSource;
127 use arrow_schema::{DataType, Field, Schema};
128 use datafusion::execution::SendableRecordBatchStream;
129
130 #[derive(Debug)]
131 struct MockSource {
132 schema: SchemaRef,
133 supports_eq_filter: bool,
134 }
135
136 #[async_trait]
137 impl StreamSource for MockSource {
138 fn schema(&self) -> SchemaRef {
139 Arc::clone(&self.schema)
140 }
141
142 fn stream(
143 &self,
144 _projection: Option<Vec<usize>>,
145 _filters: Vec<Expr>,
146 ) -> Result<SendableRecordBatchStream, DataFusionError> {
147 Err(DataFusionError::NotImplemented("mock".to_string()))
148 }
149
150 fn supports_filters(&self, filters: &[Expr]) -> Vec<bool> {
151 filters
152 .iter()
153 .map(|f| {
154 if self.supports_eq_filter {
155 matches!(f, Expr::BinaryExpr(e) if e.op == datafusion_expr::Operator::Eq)
157 } else {
158 false
159 }
160 })
161 .collect()
162 }
163 }
164
165 fn test_schema() -> SchemaRef {
166 Arc::new(Schema::new(vec![
167 Field::new("id", DataType::Int64, false),
168 Field::new("name", DataType::Utf8, true),
169 ]))
170 }
171
172 #[test]
173 fn test_table_provider_schema() {
174 let schema = test_schema();
175 let source: StreamSourceRef = Arc::new(MockSource {
176 schema: Arc::clone(&schema),
177 supports_eq_filter: false,
178 });
179 let provider = StreamingTableProvider::new("test_table", source);
180
181 assert_eq!(provider.schema(), schema);
182 assert_eq!(provider.name(), "test_table");
183 }
184
185 #[test]
186 fn test_table_provider_type() {
187 let schema = test_schema();
188 let source: StreamSourceRef = Arc::new(MockSource {
189 schema,
190 supports_eq_filter: false,
191 });
192 let provider = StreamingTableProvider::new("test", source);
193
194 assert_eq!(provider.table_type(), TableType::Base);
195 }
196
197 #[test]
198 fn test_filter_pushdown_unsupported() {
199 let schema = test_schema();
200 let source: StreamSourceRef = Arc::new(MockSource {
201 schema,
202 supports_eq_filter: false,
203 });
204 let provider = StreamingTableProvider::new("test", source);
205
206 let filter = Expr::Literal(datafusion_common::ScalarValue::Int64(Some(1)), None);
207 let result = provider.supports_filters_pushdown(&[&filter]).unwrap();
208
209 assert_eq!(result.len(), 1);
210 assert!(matches!(
211 result[0],
212 TableProviderFilterPushDown::Unsupported
213 ));
214 }
215
216 #[test]
217 fn test_filter_pushdown_supported() {
218 let schema = test_schema();
219 let source: StreamSourceRef = Arc::new(MockSource {
220 schema,
221 supports_eq_filter: true,
222 });
223 let provider = StreamingTableProvider::new("test", source);
224
225 let filter = Expr::BinaryExpr(datafusion_expr::BinaryExpr {
227 left: Box::new(Expr::Column(datafusion_common::Column::new_unqualified(
228 "id",
229 ))),
230 op: datafusion_expr::Operator::Eq,
231 right: Box::new(Expr::Literal(
232 datafusion_common::ScalarValue::Int64(Some(1)),
233 None,
234 )),
235 });
236 let result = provider.supports_filters_pushdown(&[&filter]).unwrap();
237
238 assert_eq!(result.len(), 1);
239 assert!(matches!(result[0], TableProviderFilterPushDown::Exact));
240 }
241
242 #[tokio::test]
243 async fn test_scan_creates_exec() {
244 use crate::datafusion::create_session_context;
245
246 let schema = test_schema();
247 let source: StreamSourceRef = Arc::new(MockSource {
248 schema: Arc::clone(&schema),
249 supports_eq_filter: false,
250 });
251 let provider = StreamingTableProvider::new("test", source);
252
253 let ctx = create_session_context();
254 let session_state = ctx.state();
255
256 let exec = provider
257 .scan(&session_state, None, &[], None)
258 .await
259 .unwrap();
260
261 assert!(exec.as_any().is::<StreamingScanExec>());
263 assert_eq!(exec.schema(), schema);
264 }
265
266 #[tokio::test]
267 async fn test_scan_with_projection() {
268 use crate::datafusion::create_session_context;
269
270 let schema = test_schema();
271 let source: StreamSourceRef = Arc::new(MockSource {
272 schema,
273 supports_eq_filter: false,
274 });
275 let provider = StreamingTableProvider::new("test", source);
276
277 let ctx = create_session_context();
278 let session_state = ctx.state();
279
280 let projection = vec![0]; let exec = provider
282 .scan(&session_state, Some(&projection), &[], None)
283 .await
284 .unwrap();
285
286 let output_schema = exec.schema();
287 assert_eq!(output_schema.fields().len(), 1);
288 assert_eq!(output_schema.field(0).name(), "id");
289 }
290}