1use 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 polars_time::chunkedarray::StringMethods;
18use polars_utils::unique_column_name;
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21use sqlparser::ast::{
22 AccessExpr, 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, TypedString,
25 UnaryOperator as SQLUnaryOperator, Value as SQLValue, ValueWithSpan,
26};
27use sqlparser::dialect::GenericDialect;
28use sqlparser::keywords;
29use sqlparser::parser::{Parser, ParserOptions};
30use sqlparser::tokenizer::Token;
31
32use crate::SQLContext;
33use crate::functions::SQLFunctionVisitor;
34use crate::types::{
35 bitstring_to_bytes_literal, is_iso_date, is_iso_datetime, is_iso_time, map_sql_dtype_to_polars,
36 timeunit_from_precision,
37};
38
39#[inline]
40#[cold]
41#[must_use]
42pub fn to_sql_interface_err(err: impl Display) -> PolarsError {
44 PolarsError::SQLInterface(err.to_string().into())
45}
46
47fn sql_unknown() -> Expr {
49 lit(NULL).cast(DataType::Boolean)
50}
51
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
53#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
54pub enum SubqueryRestriction {
56 SingleColumn,
58 }
62
63pub(crate) struct SQLExprVisitor<'a> {
65 ctx: &'a mut SQLContext,
66 active_schema: Option<&'a Schema>,
67}
68
69fn cast_literal_series(s: &Series, dtype: &DataType) -> PolarsResult<Series> {
72 if s.dtype() == &DataType::String {
73 let ca = s.str()?;
74 match dtype {
75 DataType::Date => return Ok(ca.as_date(None, false)?.into_series()),
76 DataType::Time => return Ok(ca.as_time(None, false)?.into_series()),
77 DataType::Datetime(tu, tz) => {
78 let ambiguous = StringChunked::from_slice(PlSmallStr::EMPTY, &["latest"]);
79 return Ok(ca
80 .as_datetime(None, *tu, false, false, tz.as_ref(), &ambiguous)?
81 .into_series());
82 },
83 _ => {},
84 }
85 }
86 s.strict_cast(dtype)
87}
88
89fn extract_literal_with_op<'a>(
91 expr: &'a SQLExpr,
92 outer_op: Option<&'a SQLUnaryOperator>,
93) -> Option<(&'a SQLValue, Option<&'a SQLUnaryOperator>)> {
94 match expr {
95 SQLExpr::Value(ValueWithSpan { value: v, .. }) => Some((v, outer_op)),
96 SQLExpr::UnaryOp { op, expr } if outer_op.is_none() => match expr.as_ref() {
97 SQLExpr::Value(ValueWithSpan { value: v, .. }) => Some((v, Some(op))),
98 _ => None,
99 },
100 _ => None,
101 }
102}
103
104fn resolve_common_dtype(dtypes: &[DataType], desc: Option<&str>) -> PolarsResult<Option<DataType>> {
106 let Some(first) = dtypes.first() else {
107 return Ok(None);
108 };
109 let desc = desc.unwrap_or("");
110 for dtype in &dtypes[1..] {
111 polars_ensure!(
112 dtype == first,
113 SQLInterface: "{desc}expected consistent dtypes (found {first:?} and {dtype:?})",
114 );
115 }
116 Ok(Some(first.clone()))
117}
118
119impl SQLExprVisitor<'_> {
120 fn array_expr_to_series(&mut self, elements: &[SQLExpr]) -> PolarsResult<Series> {
121 let mut array_elements = Vec::with_capacity(elements.len());
122 let mut cast_dtypes = Vec::new();
123 for e in elements {
124 let val = match e {
125 SQLExpr::Value(ValueWithSpan { value: v, .. }) => self.visit_any_value(v, None),
126 SQLExpr::UnaryOp { op, expr } => match extract_literal_with_op(expr, Some(op)) {
127 Some((v, op)) => self.visit_any_value(v, op),
128 None => match expr.as_ref() {
129 SQLExpr::Cast {
130 data_type, expr: inner, format: None, ..
131 } => {
132 cast_dtypes.push(map_sql_dtype_to_polars(data_type)?);
133 match extract_literal_with_op(inner, Some(op)) {
134 Some((v, op)) => self.visit_any_value(v, op),
135 None => Err(polars_err!(SQLInterface: "array element {:?} is not supported", e)),
136 }
137 },
138 _ => Err(polars_err!(SQLInterface: "array element {:?} is not supported", e)),
139 },
140 },
141 SQLExpr::Array(values) => {
142 let srs = self.array_expr_to_series(&values.elem)?;
143 Ok(AnyValue::List(srs))
144 },
145 SQLExpr::TypedString(TypedString {
146 data_type,
147 value: ValueWithSpan {
148 value: SQLValue::SingleQuotedString(v), ..
149 },
150 ..
151 }) => {
152 cast_dtypes.push(self.resolve_typed_literal_dtype(data_type, v)?);
153 Ok(AnyValue::StringOwned(v.as_str().into()))
154 },
155 SQLExpr::Cast {
156 data_type, expr, format: None, ..
157 } => {
158 cast_dtypes.push(map_sql_dtype_to_polars(data_type)?);
159 match extract_literal_with_op(expr, None) {
160 Some((v, op)) => self.visit_any_value(v, op),
161 None => Err(polars_err!(SQLInterface: "array element {:?} is not supported", e)),
162 }
163 },
164 _ => Err(polars_err!(SQLInterface: "array element {:?} is not supported", e)),
165 }?
166 .into_static();
167 array_elements.push(val);
168 }
169 let mut s = Series::from_any_values(PlSmallStr::EMPTY, &array_elements, true)?;
170 if let Some(dtype) = resolve_common_dtype(&cast_dtypes, Some("array literal "))? {
171 s = cast_literal_series(&s, &dtype)?;
172 }
173 Ok(s)
174 }
175
176 fn visit_expr(&mut self, expr: &SQLExpr) -> PolarsResult<Expr> {
177 match expr {
178 SQLExpr::AllOp {
179 left,
180 compare_op,
181 right,
182 } => self.visit_all(left, compare_op, right),
183 SQLExpr::AnyOp {
184 left,
185 compare_op,
186 right,
187 is_some: _,
188 } => self.visit_any(left, compare_op, right),
189 SQLExpr::Array(arr) => self.visit_array_expr(&arr.elem, true, None),
190 SQLExpr::Between {
191 expr,
192 negated,
193 low,
194 high,
195 } => self.visit_between(expr, *negated, low, high),
196 SQLExpr::BinaryOp { left, op, right } => self.visit_binary_op(left, op, right),
197 SQLExpr::Cast {
198 kind,
199 expr,
200 data_type,
201 format,
202 array: _,
203 } => self.visit_cast(expr, data_type, format, kind),
204 SQLExpr::Ceil { expr, .. } => Ok(self.visit_expr(expr)?.ceil()),
205 SQLExpr::CompoundFieldAccess { root, access_chain } => {
206 if access_chain.len() == 1 {
208 match &access_chain[0] {
209 AccessExpr::Subscript(subscript) => {
210 return self.visit_subscript(root, subscript);
211 },
212 AccessExpr::Dot(_) => {
213 polars_bail!(SQLSyntax: "dot-notation field access is currently unsupported: {:?}", access_chain[0])
214 },
215 }
216 }
217 polars_bail!(SQLSyntax: "complex field access chains are currently unsupported: {:?}", access_chain[0])
219 },
220 SQLExpr::CompoundIdentifier(idents) => self.visit_compound_identifier(idents),
221 SQLExpr::Extract {
222 field,
223 syntax: _,
224 expr,
225 } => parse_extract_date_part(self.visit_expr(expr)?, field),
226 SQLExpr::Floor { expr, .. } => Ok(self.visit_expr(expr)?.floor()),
227 SQLExpr::Function(function) => self.visit_function(function),
228 SQLExpr::Identifier(ident) => self.visit_identifier(ident),
229 SQLExpr::InList {
230 expr,
231 list,
232 negated,
233 } => {
234 let expr = self.visit_expr(expr)?;
235 let elems = self.visit_array_expr(list, false, Some(&expr))?;
236 let set_has_null = matches!(
237 &elems,
238 Expr::Literal(LiteralValue::Series(s)) if s.null_count() > 0
239 );
240 let membership = expr.is_in(elems.implode(false), false);
241 let is_in = if set_has_null {
242 membership.or(sql_unknown())
244 } else {
245 membership
246 };
247 Ok(if *negated { is_in.not() } else { is_in })
248 },
249 SQLExpr::InSubquery {
250 expr,
251 subquery,
252 negated,
253 } => self.visit_in_subquery(expr, subquery, *negated),
254 SQLExpr::Interval(interval) => Ok(lit(interval_to_duration(interval, true)?)),
255 SQLExpr::IsDistinctFrom(e1, e2) => {
256 Ok(self.visit_expr(e1)?.neq_missing(self.visit_expr(e2)?))
257 },
258 SQLExpr::IsFalse(expr) => Ok(self.visit_expr(expr)?.eq(lit(false))),
259 SQLExpr::IsNotDistinctFrom(e1, e2) => {
260 Ok(self.visit_expr(e1)?.eq_missing(self.visit_expr(e2)?))
261 },
262 SQLExpr::IsNotFalse(expr) => Ok(self.visit_expr(expr)?.eq(lit(false)).not()),
263 SQLExpr::IsNotNull(expr) => Ok(self.visit_expr(expr)?.is_not_null()),
264 SQLExpr::IsNotTrue(expr) => Ok(self.visit_expr(expr)?.eq(lit(true)).not()),
265 SQLExpr::IsNull(expr) => Ok(self.visit_expr(expr)?.is_null()),
266 SQLExpr::IsTrue(expr) => Ok(self.visit_expr(expr)?.eq(lit(true))),
267 SQLExpr::Like {
268 negated,
269 any,
270 expr,
271 pattern,
272 escape_char,
273 } => {
274 if *any {
275 polars_bail!(SQLSyntax: "LIKE ANY is not a supported syntax")
276 }
277 let escape_str = escape_char.as_ref().and_then(|v| match &**v {
278 SQLValue::SingleQuotedString(s) => Some(s.clone()),
279 _ => None,
280 });
281 self.visit_like(*negated, expr, pattern, &escape_str, false)
282 },
283 SQLExpr::ILike {
284 negated,
285 any,
286 expr,
287 pattern,
288 escape_char,
289 } => {
290 if *any {
291 polars_bail!(SQLSyntax: "ILIKE ANY is not a supported syntax")
292 }
293 let escape_str = escape_char.as_ref().and_then(|v| match &**v {
294 SQLValue::SingleQuotedString(s) => Some(s.clone()),
295 _ => None,
296 });
297 self.visit_like(*negated, expr, pattern, &escape_str, true)
298 },
299 SQLExpr::Nested(expr) => self.visit_expr(expr),
300 SQLExpr::Position { expr, r#in } => Ok(
301 (self
303 .visit_expr(r#in)?
304 .str()
305 .find(self.visit_expr(expr)?, true)
306 + typed_lit(1u32))
307 .fill_null(typed_lit(0u32)),
308 ),
309 SQLExpr::RLike {
310 negated,
312 expr,
313 pattern,
314 regexp: _,
315 } => {
316 let matches = self
317 .visit_expr(expr)?
318 .str()
319 .contains(self.visit_expr(pattern)?, true);
320 Ok(if *negated { matches.not() } else { matches })
321 },
322 SQLExpr::Subquery(_) => polars_bail!(SQLInterface: "unexpected subquery"),
323 SQLExpr::Substring {
324 expr,
325 substring_from,
326 substring_for,
327 ..
328 } => self.visit_substring(expr, substring_from.as_deref(), substring_for.as_deref()),
329 SQLExpr::Trim {
330 expr,
331 trim_where,
332 trim_what,
333 trim_characters,
334 } => self.visit_trim(expr, trim_where, trim_what, trim_characters),
335 SQLExpr::TypedString(TypedString {
336 data_type,
337 value:
338 ValueWithSpan {
339 value: SQLValue::SingleQuotedString(v),
340 ..
341 },
342 uses_odbc_syntax: _,
343 }) => {
344 let dtype = self.resolve_typed_literal_dtype(data_type, v)?;
345 match dtype {
346 DataType::Date => Ok(lit(v.as_str()).cast(DataType::Date)),
347 DataType::Time => Ok(lit(v.as_str()).str().to_time(StrptimeOptions {
348 strict: true,
349 ..Default::default()
350 })),
351 DataType::Datetime(_, _) => Ok(lit(v.as_str()).str().to_datetime(
352 None,
353 None,
354 StrptimeOptions {
355 strict: true,
356 ..Default::default()
357 },
358 lit("latest"),
359 )),
360 _ => unreachable!(),
361 }
362 },
363 SQLExpr::UnaryOp { op, expr } => self.visit_unary_op(op, expr),
364 SQLExpr::Value(ValueWithSpan { value, .. }) => self.visit_literal(value),
365 SQLExpr::Wildcard(_) => Ok(all().as_expr()),
366 e @ SQLExpr::Case { .. } => self.visit_case_when_then(e),
367 other => {
368 polars_bail!(SQLInterface: "expression {:?} is not currently supported", other)
369 },
370 }
371 }
372
373 fn visit_subquery(
374 &mut self,
375 subquery: &Subquery,
376 restriction: SubqueryRestriction,
377 ) -> PolarsResult<Expr> {
378 if subquery.with.is_some() {
379 polars_bail!(SQLSyntax: "SQL subquery cannot be a CTE 'WITH' clause");
380 }
381 let lf = self
384 .ctx
385 .execute_isolated(|ctx| ctx.execute_query_no_ctes(subquery))?;
386
387 if restriction == SubqueryRestriction::SingleColumn {
388 let new_name = unique_column_name();
389 return Ok(Expr::SubPlan(
390 SpecialEq::new(Arc::new(lf.logical_plan)),
391 vec![(
393 new_name.clone(),
394 first().as_expr().implode(true).alias(new_name.clone()),
395 )],
396 ));
397 };
398 polars_bail!(SQLInterface: "subquery type not supported");
399 }
400
401 fn visit_identifier(&self, ident: &Ident) -> PolarsResult<Expr> {
405 Ok(col(ident.value.as_str()))
406 }
407
408 fn visit_compound_identifier(&mut self, idents: &[Ident]) -> PolarsResult<Expr> {
412 Ok(resolve_compound_identifier(self.ctx, idents, self.active_schema)?[0].clone())
413 }
414
415 fn visit_like(
416 &mut self,
417 negated: bool,
418 expr: &SQLExpr,
419 pattern: &SQLExpr,
420 escape_char: &Option<String>,
421 case_insensitive: bool,
422 ) -> PolarsResult<Expr> {
423 if escape_char.is_some() {
424 polars_bail!(SQLInterface: "ESCAPE char for LIKE/ILIKE is not currently supported; found '{}'", escape_char.clone().unwrap());
425 }
426 let pat = match self.visit_expr(pattern) {
427 Ok(Expr::Literal(lv)) if lv.extract_str().is_some() => {
428 PlSmallStr::from_str(lv.extract_str().unwrap())
429 },
430 _ => {
431 polars_bail!(SQLSyntax: "LIKE/ILIKE pattern must be a string literal; found {}", pattern)
432 },
433 };
434 if pat.is_empty() || (!case_insensitive && pat.chars().all(|c| !matches!(c, '%' | '_'))) {
435 let op = if negated {
437 SQLBinaryOperator::NotEq
438 } else {
439 SQLBinaryOperator::Eq
440 };
441 self.visit_binary_op(expr, &op, pattern)
442 } else {
443 let mut rx = regex::escape(pat.as_str())
445 .replace('%', ".*")
446 .replace('_', ".");
447
448 rx = format!(
449 "^{}{}$",
450 if case_insensitive { "(?is)" } else { "(?s)" },
451 rx
452 );
453
454 let expr = self.visit_expr(expr)?;
455 let matches = expr.str().contains(lit(rx), true);
456 Ok(if negated { matches.not() } else { matches })
457 }
458 }
459
460 fn visit_subscript(&mut self, expr: &SQLExpr, subscript: &Subscript) -> PolarsResult<Expr> {
461 let expr = self.visit_expr(expr)?;
462 Ok(match subscript {
463 Subscript::Index { index } => {
464 let idx = adjust_one_indexed_param(self.visit_expr(index)?, true);
465 expr.list().get(idx, true)
466 },
467 Subscript::Slice { .. } => {
468 polars_bail!(SQLSyntax: "array slice syntax is not currently supported")
469 },
470 })
471 }
472
473 fn convert_temporal_strings(&mut self, left: &Expr, right: &Expr) -> Expr {
480 if let (Some(name), Some(s), expr_dtype) = match (left, right) {
481 (Expr::Column(name), Expr::Literal(lv)) if lv.extract_str().is_some() => {
483 (Some(name.clone()), Some(lv.extract_str().unwrap()), None)
484 },
485 (Expr::Cast { expr, dtype, .. }, Expr::Literal(lv)) if lv.extract_str().is_some() => {
487 let s = lv.extract_str().unwrap();
488 match &**expr {
489 Expr::Column(name) => (Some(name.clone()), Some(s), Some(dtype)),
490 _ => (None, Some(s), Some(dtype)),
491 }
492 },
493 _ => (None, None, None),
494 } {
495 if expr_dtype.is_none() && self.active_schema.is_none() {
496 right.clone()
497 } else {
498 let left_dtype = expr_dtype.map_or_else(
499 || {
500 self.active_schema
501 .as_ref()
502 .and_then(|schema| schema.get(&name))
503 },
504 |dt| dt.as_literal(),
505 );
506 match left_dtype {
507 Some(DataType::Time) if is_iso_time(s) => {
508 right.clone().str().to_time(StrptimeOptions {
509 strict: true,
510 ..Default::default()
511 })
512 },
513 Some(DataType::Date) if is_iso_date(s) => {
514 right.clone().str().to_date(StrptimeOptions {
515 strict: true,
516 ..Default::default()
517 })
518 },
519 Some(DataType::Datetime(tu, tz)) if is_iso_datetime(s) || is_iso_date(s) => {
520 if s.len() == 10 {
521 lit(format!("{s}T00:00:00"))
523 } else {
524 lit(s.replacen(' ', "T", 1))
525 }
526 .str()
527 .to_datetime(
528 Some(*tu),
529 tz.clone(),
530 StrptimeOptions {
531 strict: true,
532 ..Default::default()
533 },
534 lit("latest"),
535 )
536 },
537 _ => right.clone(),
538 }
539 }
540 } else {
541 right.clone()
542 }
543 }
544
545 fn struct_field_access_expr(
546 &mut self,
547 expr: &Expr,
548 path: &str,
549 infer_index: bool,
550 ) -> PolarsResult<Expr> {
551 let path_elems = if path.starts_with('{') && path.ends_with('}') {
552 path.trim_matches(|c| c == '{' || c == '}')
553 } else {
554 path
555 }
556 .split(',');
557
558 let mut expr = expr.clone();
559 for p in path_elems {
560 let p = p.trim();
561 expr = if infer_index {
562 match p.parse::<i64>() {
563 Ok(idx) => expr.list().get(lit(idx), true),
564 Err(_) => expr.struct_().field_by_name(p),
565 }
566 } else {
567 expr.struct_().field_by_name(p)
568 }
569 }
570 Ok(expr)
571 }
572
573 fn visit_binary_op(
577 &mut self,
578 left: &SQLExpr,
579 op: &SQLBinaryOperator,
580 right: &SQLExpr,
581 ) -> PolarsResult<Expr> {
582 if matches!(left, SQLExpr::Subquery(_)) || matches!(right, SQLExpr::Subquery(_)) {
584 let (suggestion, str_op) = match op {
585 SQLBinaryOperator::NotEq => ("; use 'NOT IN' instead", "!=".to_string()),
586 SQLBinaryOperator::Eq => ("; use 'IN' instead", format!("{op}")),
587 _ => ("", format!("{op}")),
588 };
589 polars_bail!(
590 SQLSyntax: "subquery comparisons with '{str_op}' are not supported{suggestion}"
591 );
592 }
593
594 let (lhs, mut rhs) = match (left, op, right) {
596 (_, SQLBinaryOperator::Minus, SQLExpr::Interval(v)) => {
597 let duration = interval_to_duration(v, false)?;
598 return Ok(self
599 .visit_expr(left)?
600 .dt()
601 .offset_by(lit(format!("-{duration}"))));
602 },
603 (_, SQLBinaryOperator::Plus, SQLExpr::Interval(v)) => {
604 let duration = interval_to_duration(v, false)?;
605 return Ok(self
606 .visit_expr(left)?
607 .dt()
608 .offset_by(lit(format!("{duration}"))));
609 },
610 (SQLExpr::Interval(v1), _, SQLExpr::Interval(v2)) => {
611 let d1 = interval_to_duration(v1, false)?;
613 let d2 = interval_to_duration(v2, false)?;
614 let res = match op {
615 SQLBinaryOperator::Gt => Ok(lit(d1 > d2)),
616 SQLBinaryOperator::Lt => Ok(lit(d1 < d2)),
617 SQLBinaryOperator::GtEq => Ok(lit(d1 >= d2)),
618 SQLBinaryOperator::LtEq => Ok(lit(d1 <= d2)),
619 SQLBinaryOperator::NotEq => Ok(lit(d1 != d2)),
620 SQLBinaryOperator::Eq | SQLBinaryOperator::Spaceship => Ok(lit(d1 == d2)),
621 _ => polars_bail!(SQLInterface: "invalid interval comparison operator"),
622 };
623 if res.is_ok() {
624 return res;
625 }
626 (self.visit_expr(left)?, self.visit_expr(right)?)
627 },
628 _ => (self.visit_expr(left)?, self.visit_expr(right)?),
629 };
630 rhs = self.convert_temporal_strings(&lhs, &rhs);
631
632 Ok(match op {
633 SQLBinaryOperator::BitwiseAnd => lhs.and(rhs), SQLBinaryOperator::BitwiseOr => lhs.or(rhs), SQLBinaryOperator::Xor => lhs.xor(rhs), SQLBinaryOperator::Eq => lhs.eq(rhs), SQLBinaryOperator::Gt => lhs.gt(rhs), SQLBinaryOperator::GtEq => lhs.gt_eq(rhs), SQLBinaryOperator::Lt => lhs.lt(rhs), SQLBinaryOperator::LtEq => lhs.lt_eq(rhs), SQLBinaryOperator::NotEq => lhs.eq(rhs).not(), SQLBinaryOperator::Spaceship => lhs.eq_missing(rhs), SQLBinaryOperator::And => lhs.and(rhs), SQLBinaryOperator::Or => lhs.or(rhs), SQLBinaryOperator::Divide => lhs.true_div(rhs), SQLBinaryOperator::DuckIntegerDivide => lhs.floor_div(rhs).cast(DataType::Int64), SQLBinaryOperator::Minus => lhs - rhs, SQLBinaryOperator::Modulo => lhs % rhs, SQLBinaryOperator::Multiply => lhs * rhs, SQLBinaryOperator::Plus => lhs + rhs, SQLBinaryOperator::PGRegexMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true),
672 _ => polars_bail!(SQLSyntax: "invalid pattern for '~' operator: {:?}", rhs),
673 },
674 SQLBinaryOperator::PGRegexNotMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true).not(),
676 _ => polars_bail!(SQLSyntax: "invalid pattern for '!~' operator: {:?}", rhs),
677 },
678 SQLBinaryOperator::PGRegexIMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => {
680 let pat = lv.extract_str().unwrap();
681 lhs.str().contains(lit(format!("(?i){pat}")), true)
682 },
683 _ => polars_bail!(SQLSyntax: "invalid pattern for '~*' operator: {:?}", rhs),
684 },
685 SQLBinaryOperator::PGRegexNotIMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => {
687 let pat = lv.extract_str().unwrap();
688 lhs.str().contains(lit(format!("(?i){pat}")), true).not()
689 },
690 _ => {
691 polars_bail!(SQLSyntax: "invalid pattern for '!~*' operator: {:?}", rhs)
692 },
693 },
694 SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch | SQLBinaryOperator::PGILikeMatch | SQLBinaryOperator::PGNotILikeMatch => { let expr = if matches!(
702 op,
703 SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch
704 ) {
705 SQLExpr::Like {
706 negated: matches!(op, SQLBinaryOperator::PGNotLikeMatch),
707 any: false,
708 expr: Box::new(left.clone()),
709 pattern: Box::new(right.clone()),
710 escape_char: None,
711 }
712 } else {
713 SQLExpr::ILike {
714 negated: matches!(op, SQLBinaryOperator::PGNotILikeMatch),
715 any: false,
716 expr: Box::new(left.clone()),
717 pattern: Box::new(right.clone()),
718 escape_char: None,
719 }
720 };
721 self.visit_expr(&expr)?
722 },
723 SQLBinaryOperator::PGStartsWith => lhs.str().starts_with(rhs), SQLBinaryOperator::StringConcat => { lhs.cast(DataType::String) + rhs.cast(DataType::String)
729 },
730 SQLBinaryOperator::Arrow | SQLBinaryOperator::LongArrow => match rhs { Expr::Literal(lv) if lv.extract_str().is_some() => {
735 let path = lv.extract_str().unwrap();
736 let mut expr = self.struct_field_access_expr(&lhs, path, false)?;
737 if let SQLBinaryOperator::LongArrow = op {
738 expr = expr.cast(DataType::String);
739 }
740 expr
741 },
742 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(idx))) => {
743 let mut expr = self.struct_field_access_expr(&lhs, &idx.to_string(), true)?;
744 if let SQLBinaryOperator::LongArrow = op {
745 expr = expr.cast(DataType::String);
746 }
747 expr
748 },
749 _ => {
750 polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", right)
751 },
752 },
753 SQLBinaryOperator::HashArrow | SQLBinaryOperator::HashLongArrow => { match rhs {
755 Expr::Literal(lv) if lv.extract_str().is_some() => {
756 let path = lv.extract_str().unwrap();
757 let mut expr = self.struct_field_access_expr(&lhs, path, true)?;
758 if let SQLBinaryOperator::HashLongArrow = op {
759 expr = expr.cast(DataType::String);
760 }
761 expr
762 },
763 _ => {
764 polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", rhs)
765 }
766 }
767 },
768 other => {
769 polars_bail!(SQLInterface: "operator {:?} is not currently supported", other)
770 },
771 })
772 }
773
774 fn visit_unary_op(&mut self, op: &SQLUnaryOperator, expr: &SQLExpr) -> PolarsResult<Expr> {
778 let expr = self.visit_expr(expr)?;
779 Ok(match (op, expr.clone()) {
780 (SQLUnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
782 lit(n)
783 },
784 (
785 SQLUnaryOperator::Plus,
786 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n))),
787 ) => lit(n),
788 (
789 SQLUnaryOperator::Minus,
790 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))),
791 ) => lit(-n),
792 (
793 SQLUnaryOperator::Minus,
794 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n))),
795 ) => lit(-n),
796 (SQLUnaryOperator::Plus, _) => lit(0) + expr,
798 (SQLUnaryOperator::Minus, _) => lit(0) - expr,
799 (SQLUnaryOperator::Not, _) => match &expr {
800 Expr::Column(name)
801 if self
802 .active_schema
803 .and_then(|schema| schema.get(name))
804 .is_some_and(|dtype| matches!(dtype, DataType::Boolean)) =>
805 {
806 expr.not()
808 },
809 _ => expr.strict_cast(DataType::Boolean).not(),
811 },
812 other => polars_bail!(SQLInterface: "unary operator {:?} is not supported", other),
813 })
814 }
815
816 fn visit_function(&mut self, function: &SQLFunction) -> PolarsResult<Expr> {
822 let mut visitor = SQLFunctionVisitor {
823 func: function,
824 ctx: self.ctx,
825 active_schema: self.active_schema,
826 filter: None,
827 };
828 visitor.visit_function()
829 }
830
831 fn visit_all(
835 &mut self,
836 left: &SQLExpr,
837 compare_op: &SQLBinaryOperator,
838 right: &SQLExpr,
839 ) -> PolarsResult<Expr> {
840 let left = self.visit_expr(left)?;
841 let right = self.visit_expr(right)?;
842
843 match compare_op {
844 SQLBinaryOperator::Gt => Ok(left.gt(right.max())),
845 SQLBinaryOperator::Lt => Ok(left.lt(right.min())),
846 SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.max())),
847 SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.min())),
848 SQLBinaryOperator::Eq => polars_bail!(SQLSyntax: "ALL cannot be used with ="),
849 SQLBinaryOperator::NotEq => polars_bail!(SQLSyntax: "ALL cannot be used with !="),
850 _ => polars_bail!(SQLInterface: "invalid comparison operator"),
851 }
852 }
853
854 fn visit_any(
858 &mut self,
859 left: &SQLExpr,
860 compare_op: &SQLBinaryOperator,
861 right: &SQLExpr,
862 ) -> PolarsResult<Expr> {
863 let left = self.visit_expr(left)?;
864 let right = self.visit_expr(right)?;
865
866 match compare_op {
867 SQLBinaryOperator::Gt => Ok(left.gt(right.min())),
868 SQLBinaryOperator::Lt => Ok(left.lt(right.max())),
869 SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.min())),
870 SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.max())),
871 SQLBinaryOperator::Eq => Ok(left.is_in(right, false)),
872 SQLBinaryOperator::NotEq => Ok(left.is_in(right, false).not()),
873 _ => polars_bail!(SQLInterface: "invalid comparison operator"),
874 }
875 }
876
877 fn visit_array_expr(
879 &mut self,
880 elements: &[SQLExpr],
881 result_as_element: bool,
882 dtype_expr_match: Option<&Expr>,
883 ) -> PolarsResult<Expr> {
884 let mut elems = self.array_expr_to_series(elements)?;
885
886 if let (Some(Expr::Column(name)), Some(schema)) =
889 (dtype_expr_match, self.active_schema.as_ref())
890 {
891 if elems.dtype() == &DataType::String {
892 if let Some(dtype) = schema.get(name) {
893 if matches!(
894 dtype,
895 DataType::Date | DataType::Time | DataType::Datetime(_, _)
896 ) {
897 elems = elems.strict_cast(dtype)?;
898 }
899 }
900 }
901 }
902
903 let res = if result_as_element {
906 elems.implode()?.into_series()
907 } else {
908 elems
909 };
910 Ok(lit(res))
911 }
912
913 fn visit_cast(
917 &mut self,
918 expr: &SQLExpr,
919 dtype: &SQLDataType,
920 format: &Option<CastFormat>,
921 cast_kind: &CastKind,
922 ) -> PolarsResult<Expr> {
923 if format.is_some() {
924 return Err(
925 polars_err!(SQLInterface: "use of FORMAT is not currently supported in CAST"),
926 );
927 }
928 let expr = self.visit_expr(expr)?;
929
930 #[cfg(feature = "json")]
931 if dtype == &SQLDataType::JSON {
932 return Ok(expr.str().json_decode(DataType::Struct(Vec::new())));
934 }
935 let polars_type = map_sql_dtype_to_polars(dtype)?;
936 Ok(match cast_kind {
937 CastKind::Cast | CastKind::DoubleColon => expr.strict_cast(polars_type),
938 CastKind::TryCast | CastKind::SafeCast => expr.cast(polars_type),
939 })
940 }
941
942 fn visit_literal(&self, value: &SQLValue) -> PolarsResult<Expr> {
948 Ok(match value {
950 SQLValue::Boolean(b) => lit(*b),
951 SQLValue::DollarQuotedString(s) => lit(s.value.clone()),
952 #[cfg(feature = "binary_encoding")]
953 SQLValue::HexStringLiteral(x) => {
954 if x.len() % 2 != 0 {
955 polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
956 };
957 lit(hex::decode(x.clone())
958 .map_err(|_| polars_err!(SQLSyntax: "invalid hex string literal: '{}'", x))?)
959 },
960 SQLValue::Null => Expr::Literal(LiteralValue::untyped_null()),
961 SQLValue::Number(s, _) => {
962 if s.contains('.') {
964 s.parse::<f64>().map(lit).map_err(|_| ())
965 } else {
966 s.parse::<i64>().map(lit).map_err(|_| ())
967 }
968 .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
969 },
970 SQLValue::SingleQuotedByteStringLiteral(b) => {
971 bitstring_to_bytes_literal(b)?
975 },
976 SQLValue::SingleQuotedString(s) => lit(s.clone()),
977 other => {
978 polars_bail!(SQLInterface: "value {:?} is not a supported literal type", other)
979 },
980 })
981 }
982
983 fn visit_any_value(
985 &self,
986 value: &SQLValue,
987 op: Option<&SQLUnaryOperator>,
988 ) -> PolarsResult<AnyValue<'_>> {
989 Ok(match value {
990 SQLValue::Boolean(b) => AnyValue::Boolean(*b),
991 SQLValue::DollarQuotedString(s) => AnyValue::StringOwned(s.clone().value.into()),
992 #[cfg(feature = "binary_encoding")]
993 SQLValue::HexStringLiteral(x) => {
994 if x.len() % 2 != 0 {
995 polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
996 };
997 AnyValue::BinaryOwned(
998 hex::decode(x.clone()).map_err(
999 |_| polars_err!(SQLSyntax: "invalid hex string literal: '{}'", x),
1000 )?,
1001 )
1002 },
1003 SQLValue::Null => AnyValue::Null,
1004 SQLValue::Number(s, _) => {
1005 let negate = match op {
1006 Some(SQLUnaryOperator::Minus) => true,
1007 Some(SQLUnaryOperator::Plus) | None => false,
1009 Some(op) => {
1010 polars_bail!(SQLInterface: "unary op {:?} not supported for numeric SQL value", op)
1011 },
1012 };
1013 if s.contains('.') {
1015 s.parse::<f64>()
1016 .map(|n: f64| AnyValue::Float64(if negate { -n } else { n }))
1017 .map_err(|_| ())
1018 } else {
1019 s.parse::<i64>()
1020 .map(|n: i64| AnyValue::Int64(if negate { -n } else { n }))
1021 .map_err(|_| ())
1022 }
1023 .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
1024 },
1025 SQLValue::SingleQuotedByteStringLiteral(b) => {
1026 let bytes_literal = bitstring_to_bytes_literal(b)?;
1028 match bytes_literal {
1029 Expr::Literal(lv) if lv.extract_binary().is_some() => {
1030 AnyValue::BinaryOwned(lv.extract_binary().unwrap().to_vec())
1031 },
1032 _ => {
1033 polars_bail!(SQLInterface: "failed to parse bitstring literal: {:?}", b)
1034 },
1035 }
1036 },
1037 SQLValue::SingleQuotedString(s) => AnyValue::StringOwned(s.as_str().into()),
1038 other => polars_bail!(SQLInterface: "value {:?} is not currently supported", other),
1039 })
1040 }
1041
1042 fn resolve_typed_literal_dtype(
1044 &self,
1045 data_type: &SQLDataType,
1046 value: &str,
1047 ) -> PolarsResult<DataType> {
1048 match data_type {
1049 SQLDataType::Date => {
1050 polars_ensure!(is_iso_date(value), SQLSyntax: "invalid DATE literal '{}'", value);
1051 Ok(DataType::Date)
1052 },
1053 SQLDataType::Time(None, TimezoneInfo::None) => {
1054 polars_ensure!(is_iso_time(value), SQLSyntax: "invalid TIME literal '{}'", value);
1055 Ok(DataType::Time)
1056 },
1057 SQLDataType::Timestamp(prec, TimezoneInfo::None) | SQLDataType::Datetime(prec) => {
1058 let fn_name = match data_type {
1059 SQLDataType::Timestamp(_, _) => "TIMESTAMP",
1060 _ => "DATETIME",
1061 };
1062 polars_ensure!(
1063 is_iso_datetime(value),
1064 SQLSyntax: "invalid {} literal '{}'", fn_name, value,
1065 );
1066 Ok(DataType::Datetime(timeunit_from_precision(prec)?, None))
1067 },
1068 _ => {
1069 polars_bail!(SQLInterface: "typed literal should be one of DATE, DATETIME, TIME, or TIMESTAMP (found {})", data_type)
1070 },
1071 }
1072 }
1073
1074 fn visit_between(
1077 &mut self,
1078 expr: &SQLExpr,
1079 negated: bool,
1080 low: &SQLExpr,
1081 high: &SQLExpr,
1082 ) -> PolarsResult<Expr> {
1083 let expr = self.visit_expr(expr)?;
1084 let low = self.visit_expr(low)?;
1085 let high = self.visit_expr(high)?;
1086
1087 let low = self.convert_temporal_strings(&expr, &low);
1088 let high = self.convert_temporal_strings(&expr, &high);
1089 Ok(if negated {
1090 expr.clone().lt(low).or(expr.gt(high))
1091 } else {
1092 expr.clone().gt_eq(low).and(expr.lt_eq(high))
1093 })
1094 }
1095
1096 fn visit_trim(
1099 &mut self,
1100 expr: &SQLExpr,
1101 trim_where: &Option<TrimWhereField>,
1102 trim_what: &Option<Box<SQLExpr>>,
1103 trim_characters: &Option<Vec<SQLExpr>>,
1104 ) -> PolarsResult<Expr> {
1105 if trim_characters.is_some() {
1106 return Err(polars_err!(SQLSyntax: "unsupported TRIM syntax (custom chars)"));
1108 };
1109 let expr = self.visit_expr(expr)?;
1110 let trim_what = trim_what.as_ref().map(|e| self.visit_expr(e)).transpose()?;
1111 let trim_what = match trim_what {
1112 Some(Expr::Literal(lv)) if lv.extract_str().is_some() => {
1113 Some(PlSmallStr::from_str(lv.extract_str().unwrap()))
1114 },
1115 None => None,
1116 _ => return self.err(&expr),
1117 };
1118 Ok(match (trim_where, trim_what) {
1119 (None | Some(TrimWhereField::Both), None) => {
1120 expr.str().strip_chars(lit(LiteralValue::untyped_null()))
1121 },
1122 (None | Some(TrimWhereField::Both), Some(val)) => expr.str().strip_chars(lit(val)),
1123 (Some(TrimWhereField::Leading), None) => expr
1124 .str()
1125 .strip_chars_start(lit(LiteralValue::untyped_null())),
1126 (Some(TrimWhereField::Leading), Some(val)) => expr.str().strip_chars_start(lit(val)),
1127 (Some(TrimWhereField::Trailing), None) => expr
1128 .str()
1129 .strip_chars_end(lit(LiteralValue::untyped_null())),
1130 (Some(TrimWhereField::Trailing), Some(val)) => expr.str().strip_chars_end(lit(val)),
1131 })
1132 }
1133
1134 fn visit_substring(
1135 &mut self,
1136 expr: &SQLExpr,
1137 substring_from: Option<&SQLExpr>,
1138 substring_for: Option<&SQLExpr>,
1139 ) -> PolarsResult<Expr> {
1140 let e = self.visit_expr(expr)?;
1141
1142 match (substring_from, substring_for) {
1143 (Some(from_expr), Some(for_expr)) => {
1145 let start = self.visit_expr(from_expr)?;
1146 let length = self.visit_expr(for_expr)?;
1147
1148 Ok(match (start.clone(), length.clone()) {
1150 (Expr::Literal(lv), _) | (_, Expr::Literal(lv)) if lv.is_null() => lit(lv),
1151 (_, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) if n < 0 => {
1152 polars_bail!(SQLSyntax: "SUBSTR does not support negative length ({})", n)
1153 },
1154 (Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))), _) if n > 0 => {
1155 e.str().slice(lit(n - 1), length)
1156 },
1157 (Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))), _) => e
1158 .str()
1159 .slice(lit(0), (length + lit(n - 1)).clip_min(lit(0))),
1160 (Expr::Literal(_), _) => {
1161 polars_bail!(SQLSyntax: "invalid 'start' for SUBSTRING")
1162 },
1163 (_, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(_)))) => {
1164 polars_bail!(SQLSyntax: "invalid 'length' for SUBSTRING")
1165 },
1166 _ => {
1167 let adjusted_start = start - lit(1);
1168 when(adjusted_start.clone().lt(lit(0)))
1169 .then(e.clone().str().slice(
1170 lit(0),
1171 (length.clone() + adjusted_start.clone()).clip_min(lit(0)),
1172 ))
1173 .otherwise(e.str().slice(adjusted_start, length))
1174 },
1175 })
1176 },
1177 (Some(from_expr), None) => {
1179 let start = self.visit_expr(from_expr)?;
1180
1181 Ok(match start {
1182 Expr::Literal(lv) if lv.is_null() => lit(lv),
1183 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) if n <= 0 => e,
1184 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) => {
1185 e.str().slice(lit(n - 1), lit(LiteralValue::untyped_null()))
1186 },
1187 Expr::Literal(_) => {
1188 polars_bail!(SQLSyntax: "invalid 'start' for SUBSTRING")
1189 },
1190 _ => e
1191 .str()
1192 .slice(start - lit(1), lit(LiteralValue::untyped_null())),
1193 })
1194 },
1195 (None, _) => {
1197 polars_bail!(SQLSyntax: "SUBSTR expects 2-3 arguments (found 1)")
1198 },
1199 }
1200 }
1201
1202 fn visit_in_subquery(
1204 &mut self,
1205 expr: &SQLExpr,
1206 subquery: &Subquery,
1207 negated: bool,
1208 ) -> PolarsResult<Expr> {
1209 let subquery_result = self.visit_subquery(subquery, SubqueryRestriction::SingleColumn)?;
1210 let expr = self.visit_expr(expr)?;
1211 let Expr::SubPlan(_, cols) = &subquery_result else {
1212 unreachable!("SingleColumn subquery must lower to a SubPlan");
1213 };
1214 let value_set = col(cols[0].0.clone()).first();
1215 let membership = expr.is_in(subquery_result, false);
1216 let set_has_null = value_set.clone().list().contains(lit(NULL), true);
1217 let set_is_empty = value_set.list().len().eq(lit(0u32));
1218 let is_in = when(set_is_empty)
1219 .then(lit(false))
1220 .otherwise(membership.or(set_has_null.and(sql_unknown())));
1221
1222 Ok(if negated { is_in.not() } else { is_in })
1223 }
1224
1225 fn visit_case_when_then(&mut self, expr: &SQLExpr) -> PolarsResult<Expr> {
1227 if let SQLExpr::Case {
1228 case_token: _,
1229 end_token: _,
1230 operand,
1231 conditions,
1232 else_result,
1233 } = expr
1234 {
1235 polars_ensure!(
1236 !conditions.is_empty(),
1237 SQLSyntax: "WHEN and THEN expressions must have at least one element"
1238 );
1239
1240 let mut when_thens = conditions.iter();
1241 let first = when_thens.next();
1242 if first.is_none() {
1243 polars_bail!(SQLSyntax: "WHEN and THEN expressions must have at least one element");
1244 }
1245 let else_res = match else_result {
1246 Some(else_res) => self.visit_expr(else_res)?,
1247 None => lit(LiteralValue::untyped_null()), };
1249 if let Some(operand_expr) = operand {
1250 let first_operand_expr = self.visit_expr(operand_expr)?;
1251
1252 let first = first.unwrap();
1253 let first_cond = first_operand_expr.eq(self.visit_expr(&first.condition)?);
1254 let first_then = self.visit_expr(&first.result)?;
1255 let expr = when(first_cond).then(first_then);
1256 let next = when_thens.next();
1257
1258 let mut when_then = if let Some(case_when) = next {
1259 let second_operand_expr = self.visit_expr(operand_expr)?;
1260 let cond = second_operand_expr.eq(self.visit_expr(&case_when.condition)?);
1261 let res = self.visit_expr(&case_when.result)?;
1262 expr.when(cond).then(res)
1263 } else {
1264 return Ok(expr.otherwise(else_res));
1265 };
1266 for case_when in when_thens {
1267 let new_operand_expr = self.visit_expr(operand_expr)?;
1268 let cond = new_operand_expr.eq(self.visit_expr(&case_when.condition)?);
1269 let res = self.visit_expr(&case_when.result)?;
1270 when_then = when_then.when(cond).then(res);
1271 }
1272 return Ok(when_then.otherwise(else_res));
1273 }
1274
1275 let first = first.unwrap();
1276 let first_cond = self.visit_expr(&first.condition)?;
1277 let first_then = self.visit_expr(&first.result)?;
1278 let expr = when(first_cond).then(first_then);
1279 let next = when_thens.next();
1280
1281 let mut when_then = if let Some(case_when) = next {
1282 let cond = self.visit_expr(&case_when.condition)?;
1283 let res = self.visit_expr(&case_when.result)?;
1284 expr.when(cond).then(res)
1285 } else {
1286 return Ok(expr.otherwise(else_res));
1287 };
1288 for case_when in when_thens {
1289 let cond = self.visit_expr(&case_when.condition)?;
1290 let res = self.visit_expr(&case_when.result)?;
1291 when_then = when_then.when(cond).then(res);
1292 }
1293 Ok(when_then.otherwise(else_res))
1294 } else {
1295 unreachable!()
1296 }
1297 }
1298
1299 fn err(&self, expr: &Expr) -> PolarsResult<Expr> {
1300 polars_bail!(SQLInterface: "expression {:?} is not currently supported", expr);
1301 }
1302}
1303
1304pub fn sql_expr<S: AsRef<str>>(s: S) -> PolarsResult<Expr> {
1322 let mut ctx = SQLContext::new();
1323 let s = s.as_ref();
1324
1325 let mut parser = Parser::new(&GenericDialect);
1326 parser = parser.with_options(ParserOptions {
1327 trailing_commas: true,
1328 ..Default::default()
1329 });
1330
1331 let mut ast = parser.try_with_sql(s).map_err(to_sql_interface_err)?;
1333 if let Token::Word(word) = &ast.peek_token().token {
1334 if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&word.keyword) {
1335 polars_bail!(SQLInterface: "expected an expression (found '{}' clause)", word.value)
1336 }
1337 }
1338 let expr = ast
1339 .parse_select_item()
1340 .map_err(|_| polars_err!(SQLInterface: "unable to parse '{}' as Expr", s))?;
1341
1342 match &ast.peek_token().token {
1344 Token::EOF => {},
1345 Token::Word(word) if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&word.keyword) => {
1346 polars_bail!(SQLInterface: "expected an expression (found '{}' clause)", word.value)
1347 },
1348 token => {
1349 polars_bail!(SQLInterface: "invalid expression (found unexpected token '{}')", token)
1350 },
1351 }
1352 Ok(match &expr {
1353 SelectItem::ExprWithAlias { expr, alias } => {
1354 let expr = parse_sql_expr(expr, &mut ctx, None)?;
1355 expr.alias(alias.value.as_str())
1356 },
1357 SelectItem::UnnamedExpr(expr) => parse_sql_expr(expr, &mut ctx, None)?,
1358 _ => polars_bail!(SQLInterface: "unable to parse '{}' as Expr", s),
1359 })
1360}
1361
1362pub(crate) fn interval_to_duration(interval: &Interval, fixed: bool) -> PolarsResult<Duration> {
1363 if interval.last_field.is_some()
1364 || interval.leading_field.is_some()
1365 || interval.leading_precision.is_some()
1366 || interval.fractional_seconds_precision.is_some()
1367 {
1368 polars_bail!(SQLSyntax: "unsupported interval syntax ('{}')", interval)
1369 }
1370 let s = match &*interval.value {
1371 SQLExpr::UnaryOp { .. } => {
1372 polars_bail!(SQLSyntax: "unary ops are not valid on interval strings; found {}", interval.value)
1373 },
1374 SQLExpr::Value(ValueWithSpan {
1375 value: SQLValue::SingleQuotedString(s),
1376 ..
1377 }) => Some(s),
1378 _ => None,
1379 };
1380 match s {
1381 Some(s) if s.contains('-') => {
1382 polars_bail!(SQLInterface: "minus signs are not yet supported in interval strings; found '{}'", s)
1383 },
1384 Some(s) => {
1385 let duration = Duration::parse_interval(s);
1388 if fixed && duration.months() != 0 {
1389 polars_bail!(SQLSyntax: "fixed-duration interval cannot contain years, quarters, or months; found {}", s)
1390 };
1391 Ok(duration)
1392 },
1393 None => polars_bail!(SQLSyntax: "invalid interval {:?}", interval),
1394 }
1395}
1396
1397pub(crate) fn parse_sql_expr(
1398 expr: &SQLExpr,
1399 ctx: &mut SQLContext,
1400 active_schema: Option<&Schema>,
1401) -> PolarsResult<Expr> {
1402 let mut visitor = SQLExprVisitor { ctx, active_schema };
1403 visitor.visit_expr(expr)
1404}
1405
1406pub(crate) fn parse_sql_array(expr: &SQLExpr, ctx: &mut SQLContext) -> PolarsResult<Series> {
1407 match expr {
1408 SQLExpr::Array(arr) => {
1409 let mut visitor = SQLExprVisitor {
1410 ctx,
1411 active_schema: None,
1412 };
1413 visitor.array_expr_to_series(arr.elem.as_slice())
1414 },
1415 _ => polars_bail!(SQLSyntax: "Expected array expression, found {:?}", expr),
1416 }
1417}
1418
1419pub(crate) fn parse_extract_date_part(expr: Expr, field: &DateTimeField) -> PolarsResult<Expr> {
1420 let field = match field {
1421 DateTimeField::Custom(Ident { value, .. }) => {
1423 let value = value.to_ascii_lowercase();
1424 match value.as_str() {
1425 "millennium" | "millennia" => &DateTimeField::Millennium,
1426 "century" | "centuries" | "c" => &DateTimeField::Century,
1427 "decade" | "decades" => &DateTimeField::Decade,
1428 "isoyear" => &DateTimeField::Isoyear,
1429 "year" | "years" | "y" => &DateTimeField::Year,
1430 "quarter" | "quarters" => &DateTimeField::Quarter,
1431 "month" | "months" | "mon" | "mons" => &DateTimeField::Month,
1432 "dayofyear" | "doy" => &DateTimeField::DayOfYear,
1433 "dayofweek" | "dow" => &DateTimeField::DayOfWeek,
1434 "isoweek" | "week" | "weeks" => &DateTimeField::IsoWeek,
1435 "isodow" => &DateTimeField::Isodow,
1436 "day" | "days" | "dayofmonth" | "d" => &DateTimeField::Day,
1437 "hour" | "hours" | "h" => &DateTimeField::Hour,
1438 "minute" | "minutes" | "mins" | "min" | "m" => &DateTimeField::Minute,
1439 "second" | "seconds" | "sec" | "secs" | "s" => &DateTimeField::Second,
1440 "millisecond" | "milliseconds" | "ms" => &DateTimeField::Millisecond,
1441 "microsecond" | "microseconds" | "us" => &DateTimeField::Microsecond,
1442 "nanosecond" | "nanoseconds" | "ns" => &DateTimeField::Nanosecond,
1443 #[cfg(feature = "timezones")]
1444 "timezone" => &DateTimeField::Timezone,
1445 "time" => &DateTimeField::Time,
1446 "epoch" => &DateTimeField::Epoch,
1447 _ => {
1448 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", value)
1449 },
1450 }
1451 },
1452 _ => field,
1453 };
1454 Ok(match field {
1455 DateTimeField::Millennium => expr.dt().millennium(),
1456 DateTimeField::Century => expr.dt().century(),
1457 DateTimeField::Decade => expr.dt().year() / typed_lit(10i32),
1458 DateTimeField::Isoyear => expr.dt().iso_year(),
1459 DateTimeField::Year | DateTimeField::Years => expr.dt().year(),
1460 DateTimeField::Quarter => expr.dt().quarter(),
1461 DateTimeField::Month | DateTimeField::Months => expr.dt().month(),
1462 DateTimeField::Week(weekday) => {
1463 if weekday.is_some() {
1464 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1465 }
1466 expr.dt().week()
1467 },
1468 DateTimeField::IsoWeek | DateTimeField::Weeks => expr.dt().week(),
1469 DateTimeField::DayOfYear | DateTimeField::Doy => expr.dt().ordinal_day(),
1470 DateTimeField::DayOfWeek | DateTimeField::Dow => {
1471 let w = expr.dt().weekday();
1472 when(w.clone().eq(typed_lit(7i8)))
1473 .then(typed_lit(0i8))
1474 .otherwise(w)
1475 },
1476 DateTimeField::Isodow => expr.dt().weekday(),
1477 DateTimeField::Day | DateTimeField::Days => expr.dt().day(),
1478 DateTimeField::Hour | DateTimeField::Hours => expr.dt().hour(),
1479 DateTimeField::Minute | DateTimeField::Minutes => expr.dt().minute(),
1480 DateTimeField::Second | DateTimeField::Seconds => expr.dt().second(),
1481 DateTimeField::Millisecond | DateTimeField::Milliseconds => {
1482 (expr.clone().dt().second() * typed_lit(1_000f64))
1483 + expr.dt().nanosecond().div(typed_lit(1_000_000f64))
1484 },
1485 DateTimeField::Microsecond | DateTimeField::Microseconds => {
1486 (expr.clone().dt().second() * typed_lit(1_000_000f64))
1487 + expr.dt().nanosecond().div(typed_lit(1_000f64))
1488 },
1489 DateTimeField::Nanosecond | DateTimeField::Nanoseconds => {
1490 (expr.clone().dt().second() * typed_lit(1_000_000_000f64)) + expr.dt().nanosecond()
1491 },
1492 DateTimeField::Time => expr.dt().time(),
1493 #[cfg(feature = "timezones")]
1494 DateTimeField::Timezone => expr.dt().base_utc_offset().dt().total_seconds(false),
1495 DateTimeField::Epoch => {
1496 expr.clone()
1497 .dt()
1498 .timestamp(TimeUnit::Nanoseconds)
1499 .div(typed_lit(1_000_000_000i64))
1500 + expr.dt().nanosecond().div(typed_lit(1_000_000_000f64))
1501 },
1502 _ => {
1503 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1504 },
1505 })
1506}
1507
1508pub(crate) fn adjust_one_indexed_param(idx: Expr, null_if_zero: bool) -> Expr {
1511 match idx {
1512 Expr::Literal(sc) if sc.is_null() => lit(LiteralValue::untyped_null()),
1513 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(0))) => {
1514 if null_if_zero {
1515 lit(LiteralValue::untyped_null())
1516 } else {
1517 idx
1518 }
1519 },
1520 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) if n < 0 => idx,
1521 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) => lit(n - 1),
1522 _ => when(idx.clone().gt(lit(0)))
1525 .then(idx.clone() - lit(1))
1526 .otherwise(if null_if_zero {
1527 when(idx.clone().eq(lit(0)))
1528 .then(lit(LiteralValue::untyped_null()))
1529 .otherwise(idx.clone())
1530 } else {
1531 idx.clone()
1532 }),
1533 }
1534}
1535
1536fn resolve_column<'a>(
1537 ctx: &'a mut SQLContext,
1538 ident_root: &'a Ident,
1539 name: &'a str,
1540 dtype: &'a DataType,
1541) -> PolarsResult<(Expr, Option<&'a DataType>)> {
1542 let resolved = ctx.resolve_name(&ident_root.value, name);
1543 let resolved = resolved.as_str();
1544 Ok((
1545 if name != resolved {
1546 col(resolved).alias(name)
1547 } else {
1548 col(name)
1549 },
1550 Some(dtype),
1551 ))
1552}
1553
1554pub(crate) fn resolve_compound_identifier(
1555 ctx: &mut SQLContext,
1556 idents: &[Ident],
1557 active_schema: Option<&Schema>,
1558) -> PolarsResult<Vec<Expr>> {
1559 let ident_root = &idents[0];
1561 let mut remaining_idents = idents.iter().skip(1);
1562 let mut lf = ctx.get_table_from_current_scope(&ident_root.value);
1563
1564 let schema = if let Some(ref mut lf) = lf {
1566 lf.schema_with_arenas(&mut ctx.lp_arena, &mut ctx.expr_arena)?
1567 } else {
1568 Arc::new(active_schema.cloned().unwrap_or_default())
1569 };
1570
1571 if lf.is_none() && schema.is_empty() {
1573 let (mut column, mut dtype): (Expr, Option<&DataType>) =
1574 (col(ident_root.value.as_str()), None);
1575
1576 for ident in remaining_idents {
1578 let name = ident.value.as_str();
1579 match dtype {
1580 Some(DataType::Struct(fields)) if name == "*" => {
1581 return Ok(fields
1582 .iter()
1583 .map(|fld| column.clone().struct_().field_by_name(&fld.name))
1584 .collect());
1585 },
1586 Some(DataType::Struct(fields)) => {
1587 dtype = fields
1588 .iter()
1589 .find(|fld| fld.name == name)
1590 .map(|fld| &fld.dtype);
1591 },
1592 Some(dtype) if name == "*" => {
1593 polars_bail!(SQLSyntax: "cannot expand '*' on non-Struct dtype; found {:?}", dtype)
1594 },
1595 _ => dtype = None,
1596 }
1597 column = column.struct_().field_by_name(name);
1598 }
1599 return Ok(vec![column]);
1600 }
1601
1602 let name = &remaining_idents.next().unwrap().value;
1603
1604 if lf.is_some() && name == "*" {
1606 return schema
1607 .iter_names_and_dtypes()
1608 .map(|(name, dtype)| resolve_column(ctx, ident_root, name, dtype).map(|(expr, _)| expr))
1609 .collect();
1610 }
1611
1612 let col_dtype: PolarsResult<(Expr, Option<&DataType>)> =
1614 match (lf.is_none(), schema.get(&ident_root.value)) {
1615 (true, Some(dtype)) => {
1617 remaining_idents = idents.iter().skip(1);
1618 Ok((col(ident_root.value.as_str()), Some(dtype)))
1619 },
1620 (true, None) => {
1622 polars_bail!(
1623 SQLInterface: "no table or struct column named '{}' found",
1624 ident_root
1625 )
1626 },
1627 (false, _) => {
1629 if let Some((_, col_name, dtype)) = schema.get_full(name) {
1630 resolve_column(ctx, ident_root, col_name, dtype)
1631 } else {
1632 polars_bail!(
1633 SQLInterface: "no column named '{}' found in table '{}'",
1634 name, ident_root
1635 )
1636 }
1637 },
1638 };
1639
1640 let (mut column, mut dtype) = col_dtype?;
1642 for ident in remaining_idents {
1643 let name = ident.value.as_str();
1644 match dtype {
1645 Some(DataType::Struct(fields)) if name == "*" => {
1646 return Ok(fields
1647 .iter()
1648 .map(|fld| column.clone().struct_().field_by_name(&fld.name))
1649 .collect());
1650 },
1651 Some(DataType::Struct(fields)) => {
1652 dtype = fields
1653 .iter()
1654 .find(|fld| fld.name == name)
1655 .map(|fld| &fld.dtype);
1656 },
1657 Some(dtype) if name == "*" => {
1658 polars_bail!(SQLSyntax: "cannot expand '*' on non-Struct dtype; found {:?}", dtype)
1659 },
1660 _ => {
1661 dtype = None;
1662 },
1663 }
1664 column = column.struct_().field_by_name(name);
1665 }
1666 Ok(vec![column])
1667}