datafusion_python/common/
schema.rs1use std::borrow::Cow;
19use std::fmt::{self, Display, Formatter};
20use std::sync::Arc;
21
22use arrow::datatypes::Schema;
23use arrow::pyarrow::PyArrowType;
24use datafusion::arrow::datatypes::SchemaRef;
25use datafusion::common::Constraints;
26use datafusion::datasource::TableType;
27use datafusion::logical_expr::utils::split_conjunction;
28use datafusion::logical_expr::{Expr, TableProviderFilterPushDown, TableSource};
29use parking_lot::RwLock;
30use pyo3::prelude::*;
31
32use super::data_type::DataTypeMap;
33use super::function::SqlFunction;
34use crate::sql::logical::PyLogicalPlan;
35
36#[pyclass(
37 from_py_object,
38 name = "SqlSchema",
39 module = "datafusion.common",
40 subclass,
41 frozen
42)]
43#[derive(Debug, Clone)]
44pub struct SqlSchema {
45 name: Arc<RwLock<String>>,
46 tables: Arc<RwLock<Vec<SqlTable>>>,
47 views: Arc<RwLock<Vec<SqlView>>>,
48 functions: Arc<RwLock<Vec<SqlFunction>>>,
49}
50
51#[pyclass(
52 from_py_object,
53 name = "SqlTable",
54 module = "datafusion.common",
55 subclass
56)]
57#[derive(Debug, Clone)]
58pub struct SqlTable {
59 #[pyo3(get, set)]
60 pub name: String,
61 #[pyo3(get, set)]
62 pub columns: Vec<(String, DataTypeMap)>,
63 #[pyo3(get, set)]
64 pub primary_key: Option<String>,
65 #[pyo3(get, set)]
66 pub foreign_keys: Vec<String>,
67 #[pyo3(get, set)]
68 pub indexes: Vec<String>,
69 #[pyo3(get, set)]
70 pub constraints: Vec<String>,
71 #[pyo3(get, set)]
72 pub statistics: SqlStatistics,
73 #[pyo3(get, set)]
74 pub filepaths: Option<Vec<String>>,
75}
76
77#[pymethods]
78impl SqlTable {
79 #[new]
80 #[pyo3(signature = (table_name, columns, row_count, filepaths=None))]
81 pub fn new(
82 table_name: String,
83 columns: Vec<(String, DataTypeMap)>,
84 row_count: f64,
85 filepaths: Option<Vec<String>>,
86 ) -> Self {
87 Self {
88 name: table_name,
89 columns,
90 primary_key: None,
91 foreign_keys: Vec::new(),
92 indexes: Vec::new(),
93 constraints: Vec::new(),
94 statistics: SqlStatistics::new(row_count),
95 filepaths,
96 }
97 }
98}
99
100#[pyclass(
101 from_py_object,
102 name = "SqlView",
103 module = "datafusion.common",
104 subclass
105)]
106#[derive(Debug, Clone)]
107pub struct SqlView {
108 #[pyo3(get, set)]
109 pub name: String,
110 #[pyo3(get, set)]
111 pub definition: String, }
113
114#[pymethods]
115impl SqlSchema {
116 #[new]
117 pub fn new(schema_name: &str) -> Self {
118 Self {
119 name: Arc::new(RwLock::new(schema_name.to_owned())),
120 tables: Arc::new(RwLock::new(Vec::new())),
121 views: Arc::new(RwLock::new(Vec::new())),
122 functions: Arc::new(RwLock::new(Vec::new())),
123 }
124 }
125
126 #[getter]
127 fn name(&self) -> PyResult<String> {
128 Ok(self.name.read().clone())
129 }
130
131 #[setter]
132 fn set_name(&self, value: String) -> PyResult<()> {
133 *self.name.write() = value;
134 Ok(())
135 }
136
137 #[getter]
138 fn tables(&self) -> PyResult<Vec<SqlTable>> {
139 Ok(self.tables.read().clone())
140 }
141
142 #[setter]
143 fn set_tables(&self, tables: Vec<SqlTable>) -> PyResult<()> {
144 *self.tables.write() = tables;
145 Ok(())
146 }
147
148 #[getter]
149 fn views(&self) -> PyResult<Vec<SqlView>> {
150 Ok(self.views.read().clone())
151 }
152
153 #[setter]
154 fn set_views(&self, views: Vec<SqlView>) -> PyResult<()> {
155 *self.views.write() = views;
156 Ok(())
157 }
158
159 #[getter]
160 fn functions(&self) -> PyResult<Vec<SqlFunction>> {
161 Ok(self.functions.read().clone())
162 }
163
164 #[setter]
165 fn set_functions(&self, functions: Vec<SqlFunction>) -> PyResult<()> {
166 *self.functions.write() = functions;
167 Ok(())
168 }
169
170 pub fn table_by_name(&self, table_name: &str) -> Option<SqlTable> {
171 let tables = self.tables.read();
172 tables.iter().find(|tbl| tbl.name.eq(table_name)).cloned()
173 }
174
175 pub fn add_table(&self, table: SqlTable) {
176 let mut tables = self.tables.write();
177 tables.push(table);
178 }
179
180 pub fn drop_table(&self, table_name: String) {
181 let mut tables = self.tables.write();
182 tables.retain(|x| !x.name.eq(&table_name));
183 }
184}
185
186pub struct SqlTableSource {
188 schema: SchemaRef,
189 statistics: Option<SqlStatistics>,
190 filepaths: Option<Vec<String>>,
191}
192
193impl SqlTableSource {
194 pub fn new(
196 schema: SchemaRef,
197 statistics: Option<SqlStatistics>,
198 filepaths: Option<Vec<String>>,
199 ) -> Self {
200 Self {
201 schema,
202 statistics,
203 filepaths,
204 }
205 }
206
207 pub fn statistics(&self) -> Option<&SqlStatistics> {
209 self.statistics.as_ref()
210 }
211
212 #[allow(dead_code)]
214 pub fn filepaths(&self) -> Option<&Vec<String>> {
215 self.filepaths.as_ref()
216 }
217}
218
219impl TableSource for SqlTableSource {
221 fn schema(&self) -> SchemaRef {
222 self.schema.clone()
223 }
224
225 fn table_type(&self) -> datafusion::logical_expr::TableType {
226 datafusion::logical_expr::TableType::Base
227 }
228
229 fn supports_filters_pushdown(
230 &self,
231 filters: &[&Expr],
232 ) -> datafusion::common::Result<Vec<TableProviderFilterPushDown>> {
233 filters
234 .iter()
235 .map(|f| {
236 let filters = split_conjunction(f);
237 if filters.iter().all(|f| is_supported_push_down_expr(f)) {
238 Ok(TableProviderFilterPushDown::Exact)
240 } else if filters.iter().any(|f| is_supported_push_down_expr(f)) {
241 Ok(TableProviderFilterPushDown::Inexact)
244 } else {
245 Ok(TableProviderFilterPushDown::Unsupported)
246 }
247 })
248 .collect()
249 }
250
251 fn get_logical_plan(&self) -> Option<Cow<'_, datafusion::logical_expr::LogicalPlan>> {
252 None
253 }
254}
255
256fn is_supported_push_down_expr(_expr: &Expr) -> bool {
257 true
259}
260
261#[pyclass(
262 from_py_object,
263 frozen,
264 name = "SqlStatistics",
265 module = "datafusion.common",
266 subclass
267)]
268#[derive(Debug, Clone)]
269pub struct SqlStatistics {
270 row_count: f64,
271}
272
273#[pymethods]
274impl SqlStatistics {
275 #[new]
276 pub fn new(row_count: f64) -> Self {
277 Self { row_count }
278 }
279
280 #[pyo3(name = "getRowCount")]
281 pub fn get_row_count(&self) -> f64 {
282 self.row_count
283 }
284}
285
286#[pyclass(
287 from_py_object,
288 frozen,
289 name = "Constraints",
290 module = "datafusion.expr",
291 subclass
292)]
293#[derive(Clone)]
294pub struct PyConstraints {
295 pub constraints: Constraints,
296}
297
298impl From<PyConstraints> for Constraints {
299 fn from(constraints: PyConstraints) -> Self {
300 constraints.constraints
301 }
302}
303
304impl From<Constraints> for PyConstraints {
305 fn from(constraints: Constraints) -> Self {
306 PyConstraints { constraints }
307 }
308}
309
310impl Display for PyConstraints {
311 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
312 write!(f, "Constraints: {:?}", self.constraints)
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
317#[pyclass(
318 from_py_object,
319 frozen,
320 eq,
321 eq_int,
322 name = "TableType",
323 module = "datafusion.common"
324)]
325pub enum PyTableType {
326 Base,
327 View,
328 Temporary,
329}
330
331impl From<PyTableType> for datafusion::logical_expr::TableType {
332 fn from(table_type: PyTableType) -> Self {
333 match table_type {
334 PyTableType::Base => datafusion::logical_expr::TableType::Base,
335 PyTableType::View => datafusion::logical_expr::TableType::View,
336 PyTableType::Temporary => datafusion::logical_expr::TableType::Temporary,
337 }
338 }
339}
340
341impl From<TableType> for PyTableType {
342 fn from(table_type: TableType) -> Self {
343 match table_type {
344 datafusion::logical_expr::TableType::Base => PyTableType::Base,
345 datafusion::logical_expr::TableType::View => PyTableType::View,
346 datafusion::logical_expr::TableType::Temporary => PyTableType::Temporary,
347 }
348 }
349}
350
351#[pyclass(
352 from_py_object,
353 frozen,
354 name = "TableSource",
355 module = "datafusion.common",
356 subclass
357)]
358#[derive(Clone)]
359pub struct PyTableSource {
360 pub table_source: Arc<dyn TableSource>,
361}
362
363#[pymethods]
364impl PyTableSource {
365 pub fn schema(&self) -> PyArrowType<Schema> {
366 (*self.table_source.schema()).clone().into()
367 }
368
369 pub fn constraints(&self) -> Option<PyConstraints> {
370 self.table_source.constraints().map(|c| PyConstraints {
371 constraints: c.clone(),
372 })
373 }
374
375 pub fn table_type(&self) -> PyTableType {
376 self.table_source.table_type().into()
377 }
378
379 pub fn get_logical_plan(&self) -> Option<PyLogicalPlan> {
380 self.table_source
381 .get_logical_plan()
382 .map(|plan| PyLogicalPlan::new(plan.into_owned()))
383 }
384}