// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. use pyo3::PyMappingProtocol; use pyo3::{basic::CompareOp, prelude::*, PyNumberProtocol, PyObjectProtocol}; use std::convert::{From, Into}; use datafusion::arrow::datatypes::DataType; use datafusion::logical_plan::{col, lit, Expr}; use datafusion::scalar::ScalarValue; /// An PyExpr that can be used on a DataFrame #[pyclass(name = "Expression", module = "datafusion", subclass)] #[derive(Debug, Clone)] pub(crate) struct PyExpr { pub(crate) expr: Expr, } impl From for Expr { fn from(expr: PyExpr) -> Expr { expr.expr } } impl Into for Expr { fn into(self) -> PyExpr { PyExpr { expr: self } } } #[pyproto] impl PyNumberProtocol for PyExpr { fn __add__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok((lhs.expr + rhs.expr).into()) } fn __sub__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok((lhs.expr - rhs.expr).into()) } fn __truediv__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok((lhs.expr / rhs.expr).into()) } fn __mul__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok((lhs.expr * rhs.expr).into()) } fn __mod__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok(lhs.expr.clone().modulus(rhs.expr).into()) } fn __and__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok(lhs.expr.clone().and(rhs.expr).into()) } fn __or__(lhs: PyExpr, rhs: PyExpr) -> PyResult { Ok(lhs.expr.clone().or(rhs.expr).into()) } fn __invert__(&self) -> PyResult { Ok(self.expr.clone().not().into()) } } #[pyproto] impl PyObjectProtocol for PyExpr { fn __richcmp__(&self, other: PyExpr, op: CompareOp) -> PyExpr { let expr = match op { CompareOp::Lt => self.expr.clone().lt(other.expr), CompareOp::Le => self.expr.clone().lt_eq(other.expr), CompareOp::Eq => self.expr.clone().eq(other.expr), CompareOp::Ne => self.expr.clone().not_eq(other.expr), CompareOp::Gt => self.expr.clone().gt(other.expr), CompareOp::Ge => self.expr.clone().gt_eq(other.expr), }; expr.into() } fn __str__(&self) -> PyResult { Ok(format!("{}", self.expr)) } } #[pymethods] impl PyExpr { #[staticmethod] pub fn literal(value: ScalarValue) -> PyExpr { lit(value).into() } #[staticmethod] pub fn column(value: &str) -> PyExpr { col(value).into() } /// assign a name to the PyExpr pub fn alias(&self, name: &str) -> PyExpr { self.expr.clone().alias(name).into() } /// Create a sort PyExpr from an existing PyExpr. #[args(ascending = true, nulls_first = true)] pub fn sort(&self, ascending: bool, nulls_first: bool) -> PyExpr { self.expr.clone().sort(ascending, nulls_first).into() } pub fn is_null(&self) -> PyExpr { self.expr.clone().is_null().into() } pub fn cast(&self, to: DataType) -> PyExpr { // self.expr.cast_to() requires DFSchema to validate that the cast // is supported, omit that for now let expr = Expr::Cast { expr: Box::new(self.expr.clone()), data_type: to, }; expr.into() } } #[pyproto] impl PyMappingProtocol for PyExpr { fn __getitem__(&self, key: &str) -> PyResult { Ok(Expr::GetIndexedField { expr: Box::new(self.expr.clone()), key: ScalarValue::Utf8(Some(key.to_string()).to_owned()), } .into()) } }