polars_sql/
sql_expr.rs

1//! Expressions that are supported by the Polars SQL interface.
2//!
3//! This is useful for syntax highlighting
4//!
5//! This module defines:
6//! - all Polars SQL keywords [`all_keywords`]
7//! - all of polars SQL functions [`all_functions`]
8
9use std::fmt::Display;
10use std::ops::Div;
11
12use polars_core::prelude::*;
13use polars_lazy::prelude::*;
14use polars_plan::plans::DynLiteralValue;
15use polars_plan::prelude::typed_lit;
16use polars_time::Duration;
17use rand::distributions::Alphanumeric;
18use rand::{Rng, thread_rng};
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21use sqlparser::ast::{
22    BinaryOperator as SQLBinaryOperator, CastFormat, CastKind, DataType as SQLDataType,
23    DateTimeField, Expr as SQLExpr, Function as SQLFunction, Ident, Interval, Query as Subquery,
24    SelectItem, Subscript, TimezoneInfo, TrimWhereField, UnaryOperator, Value as SQLValue,
25};
26use sqlparser::dialect::GenericDialect;
27use sqlparser::parser::{Parser, ParserOptions};
28
29use crate::SQLContext;
30use crate::functions::SQLFunctionVisitor;
31use crate::types::{
32    bitstring_to_bytes_literal, is_iso_date, is_iso_datetime, is_iso_time, map_sql_dtype_to_polars,
33};
34
35#[inline]
36#[cold]
37#[must_use]
38/// Convert a Display-able error to PolarsError::SQLInterface
39pub fn to_sql_interface_err(err: impl Display) -> PolarsError {
40    PolarsError::SQLInterface(err.to_string().into())
41}
42
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
45/// Categorises the type of (allowed) subquery constraint
46pub enum SubqueryRestriction {
47    /// Subquery must return a single column
48    SingleColumn,
49    // SingleRow,
50    // SingleValue,
51    // Any
52}
53
54/// Recursively walks a SQL Expr to create a polars Expr
55pub(crate) struct SQLExprVisitor<'a> {
56    ctx: &'a mut SQLContext,
57    active_schema: Option<&'a Schema>,
58}
59
60impl SQLExprVisitor<'_> {
61    fn array_expr_to_series(&mut self, elements: &[SQLExpr]) -> PolarsResult<Series> {
62        let mut array_elements = Vec::with_capacity(elements.len());
63        for e in elements {
64            let val = match e {
65                SQLExpr::Value(v) => self.visit_any_value(v, None),
66                SQLExpr::UnaryOp { op, expr } => match expr.as_ref() {
67                    SQLExpr::Value(v) => self.visit_any_value(v, Some(op)),
68                    _ => Err(polars_err!(SQLInterface: "array element {:?} is not supported", e)),
69                },
70                SQLExpr::Array(values) => {
71                    let srs = self.array_expr_to_series(&values.elem)?;
72                    Ok(AnyValue::List(srs))
73                },
74                _ => Err(polars_err!(SQLInterface: "array element {:?} is not supported", e)),
75            }?
76            .into_static();
77            array_elements.push(val);
78        }
79        Series::from_any_values(PlSmallStr::EMPTY, &array_elements, true)
80    }
81
82    fn visit_expr(&mut self, expr: &SQLExpr) -> PolarsResult<Expr> {
83        match expr {
84            SQLExpr::AllOp {
85                left,
86                compare_op,
87                right,
88            } => self.visit_all(left, compare_op, right),
89            SQLExpr::AnyOp {
90                left,
91                compare_op,
92                right,
93                is_some: _,
94            } => self.visit_any(left, compare_op, right),
95            SQLExpr::Array(arr) => self.visit_array_expr(&arr.elem, true, None),
96            SQLExpr::Between {
97                expr,
98                negated,
99                low,
100                high,
101            } => self.visit_between(expr, *negated, low, high),
102            SQLExpr::BinaryOp { left, op, right } => self.visit_binary_op(left, op, right),
103            SQLExpr::Cast {
104                kind,
105                expr,
106                data_type,
107                format,
108            } => self.visit_cast(expr, data_type, format, kind),
109            SQLExpr::Ceil { expr, .. } => Ok(self.visit_expr(expr)?.ceil()),
110            SQLExpr::CompoundIdentifier(idents) => self.visit_compound_identifier(idents),
111            SQLExpr::Extract {
112                field,
113                syntax: _,
114                expr,
115            } => parse_extract_date_part(self.visit_expr(expr)?, field),
116            SQLExpr::Floor { expr, .. } => Ok(self.visit_expr(expr)?.floor()),
117            SQLExpr::Function(function) => self.visit_function(function),
118            SQLExpr::Identifier(ident) => self.visit_identifier(ident),
119            SQLExpr::InList {
120                expr,
121                list,
122                negated,
123            } => {
124                let expr = self.visit_expr(expr)?;
125                let elems = self.visit_array_expr(list, true, Some(&expr))?;
126                let is_in = expr.is_in(elems, false);
127                Ok(if *negated { is_in.not() } else { is_in })
128            },
129            SQLExpr::InSubquery {
130                expr,
131                subquery,
132                negated,
133            } => self.visit_in_subquery(expr, subquery, *negated),
134            SQLExpr::Interval(interval) => Ok(lit(interval_to_duration(interval, true)?)),
135            SQLExpr::IsDistinctFrom(e1, e2) => {
136                Ok(self.visit_expr(e1)?.neq_missing(self.visit_expr(e2)?))
137            },
138            SQLExpr::IsFalse(expr) => Ok(self.visit_expr(expr)?.eq(lit(false))),
139            SQLExpr::IsNotDistinctFrom(e1, e2) => {
140                Ok(self.visit_expr(e1)?.eq_missing(self.visit_expr(e2)?))
141            },
142            SQLExpr::IsNotFalse(expr) => Ok(self.visit_expr(expr)?.eq(lit(false)).not()),
143            SQLExpr::IsNotNull(expr) => Ok(self.visit_expr(expr)?.is_not_null()),
144            SQLExpr::IsNotTrue(expr) => Ok(self.visit_expr(expr)?.eq(lit(true)).not()),
145            SQLExpr::IsNull(expr) => Ok(self.visit_expr(expr)?.is_null()),
146            SQLExpr::IsTrue(expr) => Ok(self.visit_expr(expr)?.eq(lit(true))),
147            SQLExpr::Like {
148                negated,
149                any,
150                expr,
151                pattern,
152                escape_char,
153            } => {
154                if *any {
155                    polars_bail!(SQLSyntax: "LIKE ANY is not a supported syntax")
156                }
157                self.visit_like(*negated, expr, pattern, escape_char, false)
158            },
159            SQLExpr::ILike {
160                negated,
161                any,
162                expr,
163                pattern,
164                escape_char,
165            } => {
166                if *any {
167                    polars_bail!(SQLSyntax: "ILIKE ANY is not a supported syntax")
168                }
169                self.visit_like(*negated, expr, pattern, escape_char, true)
170            },
171            SQLExpr::Nested(expr) => self.visit_expr(expr),
172            SQLExpr::Position { expr, r#in } => Ok(
173                // note: SQL is 1-indexed
174                (self
175                    .visit_expr(r#in)?
176                    .str()
177                    .find(self.visit_expr(expr)?, true)
178                    + typed_lit(1u32))
179                .fill_null(typed_lit(0u32)),
180            ),
181            SQLExpr::RLike {
182                // note: parses both RLIKE and REGEXP
183                negated,
184                expr,
185                pattern,
186                regexp: _,
187            } => {
188                let matches = self
189                    .visit_expr(expr)?
190                    .str()
191                    .contains(self.visit_expr(pattern)?, true);
192                Ok(if *negated { matches.not() } else { matches })
193            },
194            SQLExpr::Subscript { expr, subscript } => self.visit_subscript(expr, subscript),
195            SQLExpr::Subquery(_) => polars_bail!(SQLInterface: "unexpected subquery"),
196            SQLExpr::Trim {
197                expr,
198                trim_where,
199                trim_what,
200                trim_characters,
201            } => self.visit_trim(expr, trim_where, trim_what, trim_characters),
202            SQLExpr::TypedString { data_type, value } => match data_type {
203                SQLDataType::Date => {
204                    if is_iso_date(value) {
205                        Ok(lit(value.as_str()).cast(DataType::Date))
206                    } else {
207                        polars_bail!(SQLSyntax: "invalid DATE literal '{}'", value)
208                    }
209                },
210                SQLDataType::Time(None, TimezoneInfo::None) => {
211                    if is_iso_time(value) {
212                        Ok(lit(value.as_str()).str().to_time(StrptimeOptions {
213                            strict: true,
214                            ..Default::default()
215                        }))
216                    } else {
217                        polars_bail!(SQLSyntax: "invalid TIME literal '{}'", value)
218                    }
219                },
220                SQLDataType::Timestamp(None, TimezoneInfo::None) | SQLDataType::Datetime(None) => {
221                    if is_iso_datetime(value) {
222                        Ok(lit(value.as_str()).str().to_datetime(
223                            None,
224                            None,
225                            StrptimeOptions {
226                                strict: true,
227                                ..Default::default()
228                            },
229                            lit("latest"),
230                        ))
231                    } else {
232                        let fn_name = match data_type {
233                            SQLDataType::Timestamp(_, _) => "TIMESTAMP",
234                            SQLDataType::Datetime(_) => "DATETIME",
235                            _ => unreachable!(),
236                        };
237                        polars_bail!(SQLSyntax: "invalid {} literal '{}'", fn_name, value)
238                    }
239                },
240                _ => {
241                    polars_bail!(SQLInterface: "typed literal should be one of DATE, DATETIME, TIME, or TIMESTAMP (found {})", data_type)
242                },
243            },
244            SQLExpr::UnaryOp { op, expr } => self.visit_unary_op(op, expr),
245            SQLExpr::Value(value) => self.visit_literal(value),
246            SQLExpr::Wildcard(_) => Ok(Expr::Wildcard),
247            e @ SQLExpr::Case { .. } => self.visit_case_when_then(e),
248            other => {
249                polars_bail!(SQLInterface: "expression {:?} is not currently supported", other)
250            },
251        }
252    }
253
254    fn visit_subquery(
255        &mut self,
256        subquery: &Subquery,
257        restriction: SubqueryRestriction,
258    ) -> PolarsResult<Expr> {
259        if subquery.with.is_some() {
260            polars_bail!(SQLSyntax: "SQL subquery cannot be a CTE 'WITH' clause");
261        }
262        let mut lf = self.ctx.execute_query_no_ctes(subquery)?;
263        let schema = self.ctx.get_frame_schema(&mut lf)?;
264
265        if restriction == SubqueryRestriction::SingleColumn {
266            if schema.len() != 1 {
267                polars_bail!(SQLSyntax: "SQL subquery returns more than one column");
268            }
269            let rand_string: String = thread_rng()
270                .sample_iter(&Alphanumeric)
271                .take(16)
272                .map(char::from)
273                .collect();
274
275            let schema_entry = schema.get_at_index(0);
276            if let Some((old_name, _)) = schema_entry {
277                let new_name = String::from(old_name.as_str()) + rand_string.as_str();
278                lf = lf.rename([old_name.to_string()], [new_name.clone()], true);
279                return Ok(Expr::SubPlan(
280                    SpecialEq::new(Arc::new(lf.logical_plan)),
281                    vec![new_name],
282                ));
283            }
284        };
285        polars_bail!(SQLInterface: "subquery type not supported");
286    }
287
288    /// Visit a single SQL identifier.
289    ///
290    /// e.g. column
291    fn visit_identifier(&self, ident: &Ident) -> PolarsResult<Expr> {
292        Ok(col(ident.value.as_str()))
293    }
294
295    /// Visit a compound SQL identifier
296    ///
297    /// e.g. tbl.column, struct.field, tbl.struct.field (inc. nested struct fields)
298    fn visit_compound_identifier(&mut self, idents: &[Ident]) -> PolarsResult<Expr> {
299        Ok(resolve_compound_identifier(self.ctx, idents, self.active_schema)?[0].clone())
300    }
301
302    fn visit_like(
303        &mut self,
304        negated: bool,
305        expr: &SQLExpr,
306        pattern: &SQLExpr,
307        escape_char: &Option<String>,
308        case_insensitive: bool,
309    ) -> PolarsResult<Expr> {
310        if escape_char.is_some() {
311            polars_bail!(SQLInterface: "ESCAPE char for LIKE/ILIKE is not currently supported; found '{}'", escape_char.clone().unwrap());
312        }
313        let pat = match self.visit_expr(pattern) {
314            Ok(Expr::Literal(lv)) if lv.extract_str().is_some() => {
315                PlSmallStr::from_str(lv.extract_str().unwrap())
316            },
317            _ => {
318                polars_bail!(SQLSyntax: "LIKE/ILIKE pattern must be a string literal; found {}", pattern)
319            },
320        };
321        if pat.is_empty() || (!case_insensitive && pat.chars().all(|c| !matches!(c, '%' | '_'))) {
322            // empty string or other exact literal match (eg: no wildcard chars)
323            let op = if negated {
324                SQLBinaryOperator::NotEq
325            } else {
326                SQLBinaryOperator::Eq
327            };
328            self.visit_binary_op(expr, &op, pattern)
329        } else {
330            // create regex from pattern containing SQL wildcard chars ('%' => '.*', '_' => '.')
331            let mut rx = regex::escape(pat.as_str())
332                .replace('%', ".*")
333                .replace('_', ".");
334
335            rx = format!(
336                "^{}{}$",
337                if case_insensitive { "(?is)" } else { "(?s)" },
338                rx
339            );
340
341            let expr = self.visit_expr(expr)?;
342            let matches = expr.str().contains(lit(rx), true);
343            Ok(if negated { matches.not() } else { matches })
344        }
345    }
346
347    fn visit_subscript(&mut self, expr: &SQLExpr, subscript: &Subscript) -> PolarsResult<Expr> {
348        let expr = self.visit_expr(expr)?;
349        Ok(match subscript {
350            Subscript::Index { index } => {
351                let idx = adjust_one_indexed_param(self.visit_expr(index)?, true);
352                expr.list().get(idx, true)
353            },
354            Subscript::Slice { .. } => {
355                polars_bail!(SQLSyntax: "array slice syntax is not currently supported")
356            },
357        })
358    }
359
360    /// Handle implicit temporal string comparisons.
361    ///
362    /// eg: "dt >= '2024-04-30'", or "dtm::date = '2077-10-10'"
363    fn convert_temporal_strings(&mut self, left: &Expr, right: &Expr) -> Expr {
364        if let (Some(name), Some(s), expr_dtype) = match (left, right) {
365            // identify "col <op> string" expressions
366            (Expr::Column(name), Expr::Literal(lv)) if lv.extract_str().is_some() => {
367                (Some(name.clone()), Some(lv.extract_str().unwrap()), None)
368            },
369            // identify "CAST(expr AS type) <op> string" and/or "expr::type <op> string" expressions
370            (Expr::Cast { expr, dtype, .. }, Expr::Literal(lv)) if lv.extract_str().is_some() => {
371                let s = lv.extract_str().unwrap();
372                match &**expr {
373                    Expr::Column(name) => (Some(name.clone()), Some(s), Some(dtype)),
374                    _ => (None, Some(s), Some(dtype)),
375                }
376            },
377            _ => (None, None, None),
378        } {
379            if expr_dtype.is_none() && self.active_schema.is_none() {
380                right.clone()
381            } else {
382                let left_dtype = expr_dtype.or_else(|| {
383                    self.active_schema
384                        .as_ref()
385                        .and_then(|schema| schema.get(&name))
386                });
387                match left_dtype {
388                    Some(DataType::Time) if is_iso_time(s) => {
389                        right.clone().str().to_time(StrptimeOptions {
390                            strict: true,
391                            ..Default::default()
392                        })
393                    },
394                    Some(DataType::Date) if is_iso_date(s) => {
395                        right.clone().str().to_date(StrptimeOptions {
396                            strict: true,
397                            ..Default::default()
398                        })
399                    },
400                    Some(DataType::Datetime(tu, tz)) if is_iso_datetime(s) || is_iso_date(s) => {
401                        if s.len() == 10 {
402                            // handle upcast from ISO date string (10 chars) to datetime
403                            lit(format!("{}T00:00:00", s))
404                        } else {
405                            lit(s.replacen(' ', "T", 1))
406                        }
407                        .str()
408                        .to_datetime(
409                            Some(*tu),
410                            tz.clone(),
411                            StrptimeOptions {
412                                strict: true,
413                                ..Default::default()
414                            },
415                            lit("latest"),
416                        )
417                    },
418                    _ => right.clone(),
419                }
420            }
421        } else {
422            right.clone()
423        }
424    }
425
426    fn struct_field_access_expr(
427        &mut self,
428        expr: &Expr,
429        path: &str,
430        infer_index: bool,
431    ) -> PolarsResult<Expr> {
432        let path_elems = if path.starts_with('{') && path.ends_with('}') {
433            path.trim_matches(|c| c == '{' || c == '}')
434        } else {
435            path
436        }
437        .split(',');
438
439        let mut expr = expr.clone();
440        for p in path_elems {
441            let p = p.trim();
442            expr = if infer_index {
443                match p.parse::<i64>() {
444                    Ok(idx) => expr.list().get(lit(idx), true),
445                    Err(_) => expr.struct_().field_by_name(p),
446                }
447            } else {
448                expr.struct_().field_by_name(p)
449            }
450        }
451        Ok(expr)
452    }
453
454    /// Visit a SQL binary operator.
455    ///
456    /// e.g. "column + 1", "column1 <= column2"
457    fn visit_binary_op(
458        &mut self,
459        left: &SQLExpr,
460        op: &SQLBinaryOperator,
461        right: &SQLExpr,
462    ) -> PolarsResult<Expr> {
463        // need special handling for interval offsets and comparisons
464        let (lhs, mut rhs) = match (left, op, right) {
465            (_, SQLBinaryOperator::Minus, SQLExpr::Interval(v)) => {
466                let duration = interval_to_duration(v, false)?;
467                return Ok(self
468                    .visit_expr(left)?
469                    .dt()
470                    .offset_by(lit(format!("-{}", duration))));
471            },
472            (_, SQLBinaryOperator::Plus, SQLExpr::Interval(v)) => {
473                let duration = interval_to_duration(v, false)?;
474                return Ok(self
475                    .visit_expr(left)?
476                    .dt()
477                    .offset_by(lit(format!("{}", duration))));
478            },
479            (SQLExpr::Interval(v1), _, SQLExpr::Interval(v2)) => {
480                // shortcut interval comparison evaluation (-> bool)
481                let d1 = interval_to_duration(v1, false)?;
482                let d2 = interval_to_duration(v2, false)?;
483                let res = match op {
484                    SQLBinaryOperator::Gt => Ok(lit(d1 > d2)),
485                    SQLBinaryOperator::Lt => Ok(lit(d1 < d2)),
486                    SQLBinaryOperator::GtEq => Ok(lit(d1 >= d2)),
487                    SQLBinaryOperator::LtEq => Ok(lit(d1 <= d2)),
488                    SQLBinaryOperator::NotEq => Ok(lit(d1 != d2)),
489                    SQLBinaryOperator::Eq | SQLBinaryOperator::Spaceship => Ok(lit(d1 == d2)),
490                    _ => polars_bail!(SQLInterface: "invalid interval comparison operator"),
491                };
492                if res.is_ok() {
493                    return res;
494                }
495                (self.visit_expr(left)?, self.visit_expr(right)?)
496            },
497            _ => (self.visit_expr(left)?, self.visit_expr(right)?),
498        };
499        rhs = self.convert_temporal_strings(&lhs, &rhs);
500
501        Ok(match op {
502            // ----
503            // Bitwise operators
504            // ----
505            SQLBinaryOperator::BitwiseAnd => lhs.and(rhs),  // "x & y"
506            SQLBinaryOperator::BitwiseOr => lhs.or(rhs),  // "x | y"
507            SQLBinaryOperator::Xor => lhs.xor(rhs),  // "x XOR y"
508
509            // ----
510            // General operators
511            // ----
512            SQLBinaryOperator::And => lhs.and(rhs),  // "x AND y"
513            SQLBinaryOperator::Divide => lhs / rhs,  // "x / y"
514            SQLBinaryOperator::DuckIntegerDivide => lhs.floor_div(rhs).cast(DataType::Int64),  // "x // y"
515            SQLBinaryOperator::Eq => lhs.eq(rhs),  // "x = y"
516            SQLBinaryOperator::Gt => lhs.gt(rhs),  // "x > y"
517            SQLBinaryOperator::GtEq => lhs.gt_eq(rhs),  // "x >= y"
518            SQLBinaryOperator::Lt => lhs.lt(rhs),  // "x < y"
519            SQLBinaryOperator::LtEq => lhs.lt_eq(rhs),  // "x <= y"
520            SQLBinaryOperator::Minus => lhs - rhs,  // "x - y"
521            SQLBinaryOperator::Modulo => lhs % rhs,  // "x % y"
522            SQLBinaryOperator::Multiply => lhs * rhs,  // "x * y"
523            SQLBinaryOperator::NotEq => lhs.eq(rhs).not(),  // "x != y"
524            SQLBinaryOperator::Or => lhs.or(rhs),  // "x OR y"
525            SQLBinaryOperator::Plus => lhs + rhs,  // "x + y"
526            SQLBinaryOperator::Spaceship => lhs.eq_missing(rhs),  // "x <=> y"
527            SQLBinaryOperator::StringConcat => {  // "x || y"
528                lhs.cast(DataType::String) + rhs.cast(DataType::String)
529            },
530            SQLBinaryOperator::PGStartsWith => lhs.str().starts_with(rhs),  // "x ^@ y"
531            // ----
532            // Regular expression operators
533            // ----
534            SQLBinaryOperator::PGRegexMatch => match rhs {  // "x ~ y"
535                Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true),
536                _ => polars_bail!(SQLSyntax: "invalid pattern for '~' operator: {:?}", rhs),
537            },
538            SQLBinaryOperator::PGRegexNotMatch => match rhs {  // "x !~ y"
539                Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true).not(),
540                _ => polars_bail!(SQLSyntax: "invalid pattern for '!~' operator: {:?}", rhs),
541            },
542            SQLBinaryOperator::PGRegexIMatch => match rhs {  // "x ~* y"
543                Expr::Literal(ref lv) if lv.extract_str().is_some() => {
544                    let pat = lv.extract_str().unwrap();
545                    lhs.str().contains(lit(format!("(?i){}", pat)), true)
546                },
547                _ => polars_bail!(SQLSyntax: "invalid pattern for '~*' operator: {:?}", rhs),
548            },
549            SQLBinaryOperator::PGRegexNotIMatch => match rhs {  // "x !~* y"
550                Expr::Literal(ref lv) if lv.extract_str().is_some() => {
551                    let pat = lv.extract_str().unwrap();
552                    lhs.str().contains(lit(format!("(?i){}", pat)), true).not()
553                },
554                _ => {
555                    polars_bail!(SQLSyntax: "invalid pattern for '!~*' operator: {:?}", rhs)
556                },
557            },
558            // ----
559            // LIKE/ILIKE operators
560            // ----
561            SQLBinaryOperator::PGLikeMatch  // "x ~~ y"
562            | SQLBinaryOperator::PGNotLikeMatch  // "x !~~ y"
563            | SQLBinaryOperator::PGILikeMatch  // "x ~~* y"
564            | SQLBinaryOperator::PGNotILikeMatch => {  // "x !~~* y"
565                let expr = if matches!(
566                    op,
567                    SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch
568                ) {
569                    SQLExpr::Like {
570                        negated: matches!(op, SQLBinaryOperator::PGNotLikeMatch),
571                        any: false,
572                        expr: Box::new(left.clone()),
573                        pattern: Box::new(right.clone()),
574                        escape_char: None,
575                    }
576                } else {
577                    SQLExpr::ILike {
578                        negated: matches!(op, SQLBinaryOperator::PGNotILikeMatch),
579                        any: false,
580                        expr: Box::new(left.clone()),
581                        pattern: Box::new(right.clone()),
582                        escape_char: None,
583                    }
584                };
585                self.visit_expr(&expr)?
586            },
587            // ----
588            // JSON/Struct field access operators
589            // ----
590            SQLBinaryOperator::Arrow | SQLBinaryOperator::LongArrow => match rhs {  // "x -> y", "x ->> y"
591                Expr::Literal(lv) if lv.extract_str().is_some() => {
592                    let path = lv.extract_str().unwrap();
593                    let mut expr = self.struct_field_access_expr(&lhs, path, false)?;
594                    if let SQLBinaryOperator::LongArrow = op {
595                        expr = expr.cast(DataType::String);
596                    }
597                    expr
598                },
599                Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(idx))) => {
600                    let mut expr = self.struct_field_access_expr(&lhs, &idx.to_string(), true)?;
601                    if let SQLBinaryOperator::LongArrow = op {
602                        expr = expr.cast(DataType::String);
603                    }
604                    expr
605                },
606                _ => {
607                    polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", right)
608                },
609            },
610            SQLBinaryOperator::HashArrow | SQLBinaryOperator::HashLongArrow => {  // "x #> y", "x #>> y"
611                match rhs {
612                    Expr::Literal(lv) if lv.extract_str().is_some() => {
613                        let path = lv.extract_str().unwrap();
614                        let mut expr = self.struct_field_access_expr(&lhs, path, true)?;
615                        if let SQLBinaryOperator::HashLongArrow = op {
616                            expr = expr.cast(DataType::String);
617                        }
618                        expr
619                    },
620                    _ => {
621                        polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", rhs)
622                    }
623                }
624            },
625            other => {
626                polars_bail!(SQLInterface: "operator {:?} is not currently supported", other)
627            },
628        })
629    }
630
631    /// Visit a SQL unary operator.
632    ///
633    /// e.g. +column or -column
634    fn visit_unary_op(&mut self, op: &UnaryOperator, expr: &SQLExpr) -> PolarsResult<Expr> {
635        let expr = self.visit_expr(expr)?;
636        Ok(match (op, expr.clone()) {
637            // simplify the parse tree by special-casing common unary +/- ops
638            (UnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
639                lit(n)
640            },
641            (UnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n)))) => {
642                lit(n)
643            },
644            (UnaryOperator::Minus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
645                lit(-n)
646            },
647            (UnaryOperator::Minus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n)))) => {
648                lit(-n)
649            },
650            // general case
651            (UnaryOperator::Plus, _) => lit(0) + expr,
652            (UnaryOperator::Minus, _) => lit(0) - expr,
653            (UnaryOperator::Not, _) => expr.not(),
654            other => polars_bail!(SQLInterface: "unary operator {:?} is not supported", other),
655        })
656    }
657
658    /// Visit a SQL function.
659    ///
660    /// e.g. SUM(column) or COUNT(*)
661    ///
662    /// See [SQLFunctionVisitor] for more details
663    fn visit_function(&mut self, function: &SQLFunction) -> PolarsResult<Expr> {
664        let mut visitor = SQLFunctionVisitor {
665            func: function,
666            ctx: self.ctx,
667            active_schema: self.active_schema,
668        };
669        visitor.visit_function()
670    }
671
672    /// Visit a SQL `ALL` expression.
673    ///
674    /// e.g. `a > ALL(y)`
675    fn visit_all(
676        &mut self,
677        left: &SQLExpr,
678        compare_op: &SQLBinaryOperator,
679        right: &SQLExpr,
680    ) -> PolarsResult<Expr> {
681        let left = self.visit_expr(left)?;
682        let right = self.visit_expr(right)?;
683
684        match compare_op {
685            SQLBinaryOperator::Gt => Ok(left.gt(right.max())),
686            SQLBinaryOperator::Lt => Ok(left.lt(right.min())),
687            SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.max())),
688            SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.min())),
689            SQLBinaryOperator::Eq => polars_bail!(SQLSyntax: "ALL cannot be used with ="),
690            SQLBinaryOperator::NotEq => polars_bail!(SQLSyntax: "ALL cannot be used with !="),
691            _ => polars_bail!(SQLInterface: "invalid comparison operator"),
692        }
693    }
694
695    /// Visit a SQL `ANY` expression.
696    ///
697    /// e.g. `a != ANY(y)`
698    fn visit_any(
699        &mut self,
700        left: &SQLExpr,
701        compare_op: &SQLBinaryOperator,
702        right: &SQLExpr,
703    ) -> PolarsResult<Expr> {
704        let left = self.visit_expr(left)?;
705        let right = self.visit_expr(right)?;
706
707        match compare_op {
708            SQLBinaryOperator::Gt => Ok(left.gt(right.min())),
709            SQLBinaryOperator::Lt => Ok(left.lt(right.max())),
710            SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.min())),
711            SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.max())),
712            SQLBinaryOperator::Eq => Ok(left.is_in(right, false)),
713            SQLBinaryOperator::NotEq => Ok(left.is_in(right, false).not()),
714            _ => polars_bail!(SQLInterface: "invalid comparison operator"),
715        }
716    }
717
718    /// Visit a SQL `ARRAY` list (including `IN` values).
719    fn visit_array_expr(
720        &mut self,
721        elements: &[SQLExpr],
722        result_as_element: bool,
723        dtype_expr_match: Option<&Expr>,
724    ) -> PolarsResult<Expr> {
725        let mut elems = self.array_expr_to_series(elements)?;
726
727        // handle implicit temporal strings, eg: "dt IN ('2024-04-30','2024-05-01')".
728        // (not yet as versatile as the temporal string conversions in visit_binary_op)
729        if let (Some(Expr::Column(name)), Some(schema)) =
730            (dtype_expr_match, self.active_schema.as_ref())
731        {
732            if elems.dtype() == &DataType::String {
733                if let Some(dtype) = schema.get(name) {
734                    if matches!(
735                        dtype,
736                        DataType::Date | DataType::Time | DataType::Datetime(_, _)
737                    ) {
738                        elems = elems.strict_cast(dtype)?;
739                    }
740                }
741            }
742        }
743
744        // if we are parsing the list as an element in a series, implode.
745        // otherwise, return the series as-is.
746        let res = if result_as_element {
747            elems.implode()?.into_series()
748        } else {
749            elems
750        };
751        Ok(lit(res))
752    }
753
754    /// Visit a SQL `CAST` or `TRY_CAST` expression.
755    ///
756    /// e.g. `CAST(col AS INT)`, `col::int4`, or `TRY_CAST(col AS VARCHAR)`,
757    fn visit_cast(
758        &mut self,
759        expr: &SQLExpr,
760        dtype: &SQLDataType,
761        format: &Option<CastFormat>,
762        cast_kind: &CastKind,
763    ) -> PolarsResult<Expr> {
764        if format.is_some() {
765            return Err(
766                polars_err!(SQLInterface: "use of FORMAT is not currently supported in CAST"),
767            );
768        }
769        let expr = self.visit_expr(expr)?;
770
771        #[cfg(feature = "json")]
772        if dtype == &SQLDataType::JSON {
773            return Ok(expr.str().json_decode(None, None));
774        }
775        let polars_type = map_sql_dtype_to_polars(dtype)?;
776        Ok(match cast_kind {
777            CastKind::Cast | CastKind::DoubleColon => expr.strict_cast(polars_type),
778            CastKind::TryCast | CastKind::SafeCast => expr.cast(polars_type),
779        })
780    }
781
782    /// Visit a SQL literal.
783    ///
784    /// e.g. 1, 'foo', 1.0, NULL
785    ///
786    /// See [SQLValue] and [LiteralValue] for more details
787    fn visit_literal(&self, value: &SQLValue) -> PolarsResult<Expr> {
788        // note: double-quoted strings will be parsed as identifiers, not literals
789        Ok(match value {
790            SQLValue::Boolean(b) => lit(*b),
791            SQLValue::DollarQuotedString(s) => lit(s.value.clone()),
792            #[cfg(feature = "binary_encoding")]
793            SQLValue::HexStringLiteral(x) => {
794                if x.len() % 2 != 0 {
795                    polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
796                };
797                lit(hex::decode(x.clone()).unwrap())
798            },
799            SQLValue::Null => Expr::Literal(LiteralValue::untyped_null()),
800            SQLValue::Number(s, _) => {
801                // Check for existence of decimal separator dot
802                if s.contains('.') {
803                    s.parse::<f64>().map(lit).map_err(|_| ())
804                } else {
805                    s.parse::<i64>().map(lit).map_err(|_| ())
806                }
807                .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
808            },
809            SQLValue::SingleQuotedByteStringLiteral(b) => {
810                // note: for PostgreSQL this represents a BIT string literal (eg: b'10101') not a BYTE string
811                // literal (see https://www.postgresql.org/docs/current/datatype-bit.html), but sqlparser-rs
812                // patterned the token name after BigQuery (where b'str' really IS a byte string)
813                bitstring_to_bytes_literal(b)?
814            },
815            SQLValue::SingleQuotedString(s) => lit(s.clone()),
816            other => {
817                polars_bail!(SQLInterface: "value {:?} is not a supported literal type", other)
818            },
819        })
820    }
821
822    /// Visit a SQL literal (like [visit_literal]), but return AnyValue instead of Expr.
823    fn visit_any_value(
824        &self,
825        value: &SQLValue,
826        op: Option<&UnaryOperator>,
827    ) -> PolarsResult<AnyValue> {
828        Ok(match value {
829            SQLValue::Boolean(b) => AnyValue::Boolean(*b),
830            SQLValue::DollarQuotedString(s) => AnyValue::StringOwned(s.clone().value.into()),
831            #[cfg(feature = "binary_encoding")]
832            SQLValue::HexStringLiteral(x) => {
833                if x.len() % 2 != 0 {
834                    polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
835                };
836                AnyValue::BinaryOwned(hex::decode(x.clone()).unwrap())
837            },
838            SQLValue::Null => AnyValue::Null,
839            SQLValue::Number(s, _) => {
840                let negate = match op {
841                    Some(UnaryOperator::Minus) => true,
842                    // no op should be taken as plus.
843                    Some(UnaryOperator::Plus) | None => false,
844                    Some(op) => {
845                        polars_bail!(SQLInterface: "unary op {:?} not supported for numeric SQL value", op)
846                    },
847                };
848                // Check for existence of decimal separator dot
849                if s.contains('.') {
850                    s.parse::<f64>()
851                        .map(|n: f64| AnyValue::Float64(if negate { -n } else { n }))
852                        .map_err(|_| ())
853                } else {
854                    s.parse::<i64>()
855                        .map(|n: i64| AnyValue::Int64(if negate { -n } else { n }))
856                        .map_err(|_| ())
857                }
858                .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
859            },
860            SQLValue::SingleQuotedByteStringLiteral(b) => {
861                // note: for PostgreSQL this represents a BIT literal (eg: b'10101') not BYTE
862                let bytes_literal = bitstring_to_bytes_literal(b)?;
863                match bytes_literal {
864                    Expr::Literal(lv) if lv.extract_binary().is_some() => {
865                        AnyValue::BinaryOwned(lv.extract_binary().unwrap().to_vec())
866                    },
867                    _ => {
868                        polars_bail!(SQLInterface: "failed to parse bitstring literal: {:?}", b)
869                    },
870                }
871            },
872            SQLValue::SingleQuotedString(s) => AnyValue::StringOwned(s.as_str().into()),
873            other => polars_bail!(SQLInterface: "value {:?} is not currently supported", other),
874        })
875    }
876
877    /// Visit a SQL `BETWEEN` expression.
878    /// See [sqlparser::ast::Expr::Between] for more details
879    fn visit_between(
880        &mut self,
881        expr: &SQLExpr,
882        negated: bool,
883        low: &SQLExpr,
884        high: &SQLExpr,
885    ) -> PolarsResult<Expr> {
886        let expr = self.visit_expr(expr)?;
887        let low = self.visit_expr(low)?;
888        let high = self.visit_expr(high)?;
889
890        let low = self.convert_temporal_strings(&expr, &low);
891        let high = self.convert_temporal_strings(&expr, &high);
892        Ok(if negated {
893            expr.clone().lt(low).or(expr.gt(high))
894        } else {
895            expr.clone().gt_eq(low).and(expr.lt_eq(high))
896        })
897    }
898
899    /// Visit a SQL `TRIM` function.
900    /// See [sqlparser::ast::Expr::Trim] for more details
901    fn visit_trim(
902        &mut self,
903        expr: &SQLExpr,
904        trim_where: &Option<TrimWhereField>,
905        trim_what: &Option<Box<SQLExpr>>,
906        trim_characters: &Option<Vec<SQLExpr>>,
907    ) -> PolarsResult<Expr> {
908        if trim_characters.is_some() {
909            // TODO: allow compact snowflake/bigquery syntax?
910            return Err(polars_err!(SQLSyntax: "unsupported TRIM syntax (custom chars)"));
911        };
912        let expr = self.visit_expr(expr)?;
913        let trim_what = trim_what.as_ref().map(|e| self.visit_expr(e)).transpose()?;
914        let trim_what = match trim_what {
915            Some(Expr::Literal(lv)) if lv.extract_str().is_some() => {
916                Some(PlSmallStr::from_str(lv.extract_str().unwrap()))
917            },
918            None => None,
919            _ => return self.err(&expr),
920        };
921        Ok(match (trim_where, trim_what) {
922            (None | Some(TrimWhereField::Both), None) => {
923                expr.str().strip_chars(lit(LiteralValue::untyped_null()))
924            },
925            (None | Some(TrimWhereField::Both), Some(val)) => expr.str().strip_chars(lit(val)),
926            (Some(TrimWhereField::Leading), None) => expr
927                .str()
928                .strip_chars_start(lit(LiteralValue::untyped_null())),
929            (Some(TrimWhereField::Leading), Some(val)) => expr.str().strip_chars_start(lit(val)),
930            (Some(TrimWhereField::Trailing), None) => expr
931                .str()
932                .strip_chars_end(lit(LiteralValue::untyped_null())),
933            (Some(TrimWhereField::Trailing), Some(val)) => expr.str().strip_chars_end(lit(val)),
934        })
935    }
936
937    /// Visit a SQL subquery inside and `IN` expression.
938    fn visit_in_subquery(
939        &mut self,
940        expr: &SQLExpr,
941        subquery: &Subquery,
942        negated: bool,
943    ) -> PolarsResult<Expr> {
944        let subquery_result = self.visit_subquery(subquery, SubqueryRestriction::SingleColumn)?;
945        let expr = self.visit_expr(expr)?;
946        Ok(if negated {
947            expr.is_in(subquery_result, false).not()
948        } else {
949            expr.is_in(subquery_result, false)
950        })
951    }
952
953    /// Visit `CASE` control flow expression.
954    fn visit_case_when_then(&mut self, expr: &SQLExpr) -> PolarsResult<Expr> {
955        if let SQLExpr::Case {
956            operand,
957            conditions,
958            results,
959            else_result,
960        } = expr
961        {
962            polars_ensure!(
963                conditions.len() == results.len(),
964                SQLSyntax: "WHEN and THEN expressions must have the same length"
965            );
966            polars_ensure!(
967                !conditions.is_empty(),
968                SQLSyntax: "WHEN and THEN expressions must have at least one element"
969            );
970
971            let mut when_thens = conditions.iter().zip(results.iter());
972            let first = when_thens.next();
973            if first.is_none() {
974                polars_bail!(SQLSyntax: "WHEN and THEN expressions must have at least one element");
975            }
976            let else_res = match else_result {
977                Some(else_res) => self.visit_expr(else_res)?,
978                None => lit(LiteralValue::untyped_null()), // ELSE clause is optional; when omitted, it is implicitly NULL
979            };
980            if let Some(operand_expr) = operand {
981                let first_operand_expr = self.visit_expr(operand_expr)?;
982
983                let first = first.unwrap();
984                let first_cond = first_operand_expr.eq(self.visit_expr(first.0)?);
985                let first_then = self.visit_expr(first.1)?;
986                let expr = when(first_cond).then(first_then);
987                let next = when_thens.next();
988
989                let mut when_then = if let Some((cond, res)) = next {
990                    let second_operand_expr = self.visit_expr(operand_expr)?;
991                    let cond = second_operand_expr.eq(self.visit_expr(cond)?);
992                    let res = self.visit_expr(res)?;
993                    expr.when(cond).then(res)
994                } else {
995                    return Ok(expr.otherwise(else_res));
996                };
997                for (cond, res) in when_thens {
998                    let new_operand_expr = self.visit_expr(operand_expr)?;
999                    let cond = new_operand_expr.eq(self.visit_expr(cond)?);
1000                    let res = self.visit_expr(res)?;
1001                    when_then = when_then.when(cond).then(res);
1002                }
1003                return Ok(when_then.otherwise(else_res));
1004            }
1005
1006            let first = first.unwrap();
1007            let first_cond = self.visit_expr(first.0)?;
1008            let first_then = self.visit_expr(first.1)?;
1009            let expr = when(first_cond).then(first_then);
1010            let next = when_thens.next();
1011
1012            let mut when_then = if let Some((cond, res)) = next {
1013                let cond = self.visit_expr(cond)?;
1014                let res = self.visit_expr(res)?;
1015                expr.when(cond).then(res)
1016            } else {
1017                return Ok(expr.otherwise(else_res));
1018            };
1019            for (cond, res) in when_thens {
1020                let cond = self.visit_expr(cond)?;
1021                let res = self.visit_expr(res)?;
1022                when_then = when_then.when(cond).then(res);
1023            }
1024            Ok(when_then.otherwise(else_res))
1025        } else {
1026            unreachable!()
1027        }
1028    }
1029
1030    fn err(&self, expr: &Expr) -> PolarsResult<Expr> {
1031        polars_bail!(SQLInterface: "expression {:?} is not currently supported", expr);
1032    }
1033}
1034
1035/// parse a SQL expression to a polars expression
1036/// # Example
1037/// ```rust
1038/// # use polars_sql::{SQLContext, sql_expr};
1039/// # use polars_core::prelude::*;
1040/// # use polars_lazy::prelude::*;
1041/// # fn main() {
1042///
1043/// let mut ctx = SQLContext::new();
1044/// let df = df! {
1045///    "a" =>  [1, 2, 3],
1046/// }
1047/// .unwrap();
1048/// let expr = sql_expr("MAX(a)").unwrap();
1049/// df.lazy().select(vec![expr]).collect().unwrap();
1050/// # }
1051/// ```
1052pub fn sql_expr<S: AsRef<str>>(s: S) -> PolarsResult<Expr> {
1053    let mut ctx = SQLContext::new();
1054
1055    let mut parser = Parser::new(&GenericDialect);
1056    parser = parser.with_options(ParserOptions {
1057        trailing_commas: true,
1058        ..Default::default()
1059    });
1060
1061    let mut ast = parser
1062        .try_with_sql(s.as_ref())
1063        .map_err(to_sql_interface_err)?;
1064    let expr = ast.parse_select_item().map_err(to_sql_interface_err)?;
1065
1066    Ok(match &expr {
1067        SelectItem::ExprWithAlias { expr, alias } => {
1068            let expr = parse_sql_expr(expr, &mut ctx, None)?;
1069            expr.alias(alias.value.as_str())
1070        },
1071        SelectItem::UnnamedExpr(expr) => parse_sql_expr(expr, &mut ctx, None)?,
1072        _ => polars_bail!(SQLInterface: "unable to parse '{}' as Expr", s.as_ref()),
1073    })
1074}
1075
1076pub(crate) fn interval_to_duration(interval: &Interval, fixed: bool) -> PolarsResult<Duration> {
1077    if interval.last_field.is_some()
1078        || interval.leading_field.is_some()
1079        || interval.leading_precision.is_some()
1080        || interval.fractional_seconds_precision.is_some()
1081    {
1082        polars_bail!(SQLSyntax: "unsupported interval syntax ('{}')", interval)
1083    }
1084    let s = match &*interval.value {
1085        SQLExpr::UnaryOp { .. } => {
1086            polars_bail!(SQLSyntax: "unary ops are not valid on interval strings; found {}", interval.value)
1087        },
1088        SQLExpr::Value(SQLValue::SingleQuotedString(s)) => Some(s),
1089        _ => None,
1090    };
1091    match s {
1092        Some(s) if s.contains('-') => {
1093            polars_bail!(SQLInterface: "minus signs are not yet supported in interval strings; found '{}'", s)
1094        },
1095        Some(s) => {
1096            // years, quarters, and months do not have a fixed duration; these
1097            // interval parts can only be used with respect to a reference point
1098            let duration = Duration::parse_interval(s);
1099            if fixed && duration.months() != 0 {
1100                polars_bail!(SQLSyntax: "fixed-duration interval cannot contain years, quarters, or months; found {}", s)
1101            };
1102            Ok(duration)
1103        },
1104        None => polars_bail!(SQLSyntax: "invalid interval {:?}", interval),
1105    }
1106}
1107
1108pub(crate) fn parse_sql_expr(
1109    expr: &SQLExpr,
1110    ctx: &mut SQLContext,
1111    active_schema: Option<&Schema>,
1112) -> PolarsResult<Expr> {
1113    let mut visitor = SQLExprVisitor { ctx, active_schema };
1114    visitor.visit_expr(expr)
1115}
1116
1117pub(crate) fn parse_sql_array(expr: &SQLExpr, ctx: &mut SQLContext) -> PolarsResult<Series> {
1118    match expr {
1119        SQLExpr::Array(arr) => {
1120            let mut visitor = SQLExprVisitor {
1121                ctx,
1122                active_schema: None,
1123            };
1124            visitor.array_expr_to_series(arr.elem.as_slice())
1125        },
1126        _ => polars_bail!(SQLSyntax: "Expected array expression, found {:?}", expr),
1127    }
1128}
1129
1130pub(crate) fn parse_extract_date_part(expr: Expr, field: &DateTimeField) -> PolarsResult<Expr> {
1131    let field = match field {
1132        // handle 'DATE_PART' and all valid abbreviations/alternates
1133        DateTimeField::Custom(Ident { value, .. }) => {
1134            let value = value.to_ascii_lowercase();
1135            match value.as_str() {
1136                "millennium" | "millennia" => &DateTimeField::Millennium,
1137                "century" | "centuries" => &DateTimeField::Century,
1138                "decade" | "decades" => &DateTimeField::Decade,
1139                "isoyear" => &DateTimeField::Isoyear,
1140                "year" | "years" | "y" => &DateTimeField::Year,
1141                "quarter" | "quarters" => &DateTimeField::Quarter,
1142                "month" | "months" | "mon" | "mons" => &DateTimeField::Month,
1143                "dayofyear" | "doy" => &DateTimeField::DayOfYear,
1144                "dayofweek" | "dow" => &DateTimeField::DayOfWeek,
1145                "isoweek" | "week" | "weeks" => &DateTimeField::IsoWeek,
1146                "isodow" => &DateTimeField::Isodow,
1147                "day" | "days" | "d" => &DateTimeField::Day,
1148                "hour" | "hours" | "h" => &DateTimeField::Hour,
1149                "minute" | "minutes" | "mins" | "min" | "m" => &DateTimeField::Minute,
1150                "second" | "seconds" | "sec" | "secs" | "s" => &DateTimeField::Second,
1151                "millisecond" | "milliseconds" | "ms" => &DateTimeField::Millisecond,
1152                "microsecond" | "microseconds" | "us" => &DateTimeField::Microsecond,
1153                "nanosecond" | "nanoseconds" | "ns" => &DateTimeField::Nanosecond,
1154                #[cfg(feature = "timezones")]
1155                "timezone" => &DateTimeField::Timezone,
1156                "time" => &DateTimeField::Time,
1157                "epoch" => &DateTimeField::Epoch,
1158                _ => {
1159                    polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", value)
1160                },
1161            }
1162        },
1163        _ => field,
1164    };
1165    Ok(match field {
1166        DateTimeField::Millennium => expr.dt().millennium(),
1167        DateTimeField::Century => expr.dt().century(),
1168        DateTimeField::Decade => expr.dt().year() / typed_lit(10i32),
1169        DateTimeField::Isoyear => expr.dt().iso_year(),
1170        DateTimeField::Year => expr.dt().year(),
1171        DateTimeField::Quarter => expr.dt().quarter(),
1172        DateTimeField::Month => expr.dt().month(),
1173        DateTimeField::Week(weekday) => {
1174            if weekday.is_some() {
1175                polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1176            }
1177            expr.dt().week()
1178        },
1179        DateTimeField::IsoWeek => expr.dt().week(),
1180        DateTimeField::DayOfYear | DateTimeField::Doy => expr.dt().ordinal_day(),
1181        DateTimeField::DayOfWeek | DateTimeField::Dow => {
1182            let w = expr.dt().weekday();
1183            when(w.clone().eq(typed_lit(7i8)))
1184                .then(typed_lit(0i8))
1185                .otherwise(w)
1186        },
1187        DateTimeField::Isodow => expr.dt().weekday(),
1188        DateTimeField::Day => expr.dt().day(),
1189        DateTimeField::Hour => expr.dt().hour(),
1190        DateTimeField::Minute => expr.dt().minute(),
1191        DateTimeField::Second => expr.dt().second(),
1192        DateTimeField::Millisecond | DateTimeField::Milliseconds => {
1193            (expr.clone().dt().second() * typed_lit(1_000f64))
1194                + expr.dt().nanosecond().div(typed_lit(1_000_000f64))
1195        },
1196        DateTimeField::Microsecond | DateTimeField::Microseconds => {
1197            (expr.clone().dt().second() * typed_lit(1_000_000f64))
1198                + expr.dt().nanosecond().div(typed_lit(1_000f64))
1199        },
1200        DateTimeField::Nanosecond | DateTimeField::Nanoseconds => {
1201            (expr.clone().dt().second() * typed_lit(1_000_000_000f64)) + expr.dt().nanosecond()
1202        },
1203        DateTimeField::Time => expr.dt().time(),
1204        #[cfg(feature = "timezones")]
1205        DateTimeField::Timezone => expr.dt().base_utc_offset().dt().total_seconds(),
1206        DateTimeField::Epoch => {
1207            expr.clone()
1208                .dt()
1209                .timestamp(TimeUnit::Nanoseconds)
1210                .div(typed_lit(1_000_000_000i64))
1211                + expr.dt().nanosecond().div(typed_lit(1_000_000_000f64))
1212        },
1213        _ => {
1214            polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1215        },
1216    })
1217}
1218
1219/// Allow an expression that represents a 1-indexed parameter to
1220/// be adjusted from 1-indexed (SQL) to 0-indexed (Rust/Polars)
1221pub(crate) fn adjust_one_indexed_param(idx: Expr, null_if_zero: bool) -> Expr {
1222    match idx {
1223        Expr::Literal(sc) if sc.is_null() => lit(LiteralValue::untyped_null()),
1224        Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(0))) => {
1225            if null_if_zero {
1226                lit(LiteralValue::untyped_null())
1227            } else {
1228                idx
1229            }
1230        },
1231        Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) if n < 0 => idx,
1232        Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) => lit(n - 1),
1233        // TODO: when 'saturating_sub' is available, should be able
1234        //  to streamline the when/then/otherwise block below -
1235        _ => when(idx.clone().gt(lit(0)))
1236            .then(idx.clone() - lit(1))
1237            .otherwise(if null_if_zero {
1238                when(idx.clone().eq(lit(0)))
1239                    .then(lit(LiteralValue::untyped_null()))
1240                    .otherwise(idx.clone())
1241            } else {
1242                idx.clone()
1243            }),
1244    }
1245}
1246
1247fn resolve_column<'a>(
1248    ctx: &'a mut SQLContext,
1249    ident_root: &'a Ident,
1250    name: &'a str,
1251    dtype: &'a DataType,
1252) -> PolarsResult<(Expr, Option<&'a DataType>)> {
1253    let resolved = ctx.resolve_name(&ident_root.value, name);
1254    let resolved = resolved.as_str();
1255    Ok((
1256        if name != resolved {
1257            col(resolved).alias(name)
1258        } else {
1259            col(name)
1260        },
1261        Some(dtype),
1262    ))
1263}
1264
1265pub(crate) fn resolve_compound_identifier(
1266    ctx: &mut SQLContext,
1267    idents: &[Ident],
1268    active_schema: Option<&Schema>,
1269) -> PolarsResult<Vec<Expr>> {
1270    // inference priority: table > struct > column
1271    let ident_root = &idents[0];
1272    let mut remaining_idents = idents.iter().skip(1);
1273    let mut lf = ctx.get_table_from_current_scope(&ident_root.value);
1274
1275    let schema = if let Some(ref mut lf) = lf {
1276        lf.schema_with_arenas(&mut ctx.lp_arena, &mut ctx.expr_arena)
1277    } else {
1278        Ok(Arc::new(if let Some(active_schema) = active_schema {
1279            active_schema.clone()
1280        } else {
1281            Schema::default()
1282        }))
1283    }?;
1284
1285    let col_dtype: PolarsResult<(Expr, Option<&DataType>)> = if lf.is_none() && schema.is_empty() {
1286        Ok((col(ident_root.value.as_str()), None))
1287    } else {
1288        let name = &remaining_idents.next().unwrap().value;
1289        if lf.is_some() && name == "*" {
1290            return Ok(schema
1291                .iter_names_and_dtypes()
1292                .map(|(name, dtype)| resolve_column(ctx, ident_root, name, dtype).unwrap().0)
1293                .collect::<Vec<_>>());
1294        };
1295        let root_is_field = schema.get(&ident_root.value).is_some();
1296        if lf.is_none() && root_is_field {
1297            remaining_idents = idents.iter().skip(1);
1298            Ok((
1299                col(ident_root.value.as_str()),
1300                schema.get(&ident_root.value),
1301            ))
1302        } else if lf.is_none() && !root_is_field {
1303            polars_bail!(
1304                SQLInterface: "no table or struct column named '{}' found",
1305                ident_root
1306            )
1307        } else if let Some((_, name, dtype)) = schema.get_full(name) {
1308            resolve_column(ctx, ident_root, name, dtype)
1309        } else {
1310            polars_bail!(
1311                SQLInterface: "no column named '{}' found in table '{}'",
1312                name,
1313                ident_root
1314            )
1315        }
1316    };
1317
1318    // additional ident levels index into struct fields
1319    let (mut column, mut dtype) = col_dtype?;
1320    for ident in remaining_idents {
1321        let name = ident.value.as_str();
1322        match dtype {
1323            Some(DataType::Struct(fields)) if name == "*" => {
1324                return Ok(fields
1325                    .iter()
1326                    .map(|fld| column.clone().struct_().field_by_name(&fld.name))
1327                    .collect());
1328            },
1329            Some(DataType::Struct(fields)) => {
1330                dtype = fields
1331                    .iter()
1332                    .find(|fld| fld.name == name)
1333                    .map(|fld| &fld.dtype);
1334            },
1335            Some(dtype) if name == "*" => {
1336                polars_bail!(SQLSyntax: "cannot expand '*' on non-Struct dtype; found {:?}", dtype)
1337            },
1338            _ => {
1339                dtype = None;
1340            },
1341        }
1342        column = column.struct_().field_by_name(name);
1343    }
1344    Ok(vec![column])
1345}