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 rand::Rng;
18use rand::distr::Alphanumeric;
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]
38pub 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)]
45pub enum SubqueryRestriction {
47 SingleColumn,
49 }
53
54pub(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 (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 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(all().as_expr()),
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, schema) = self
265 .ctx
266 .execute_isolated(|ctx| ctx.execute_query_no_ctes(subquery))?;
267
268 if restriction == SubqueryRestriction::SingleColumn {
269 if schema.len() != 1 {
270 polars_bail!(SQLSyntax: "SQL subquery returns more than one column");
271 }
272 let rand_string: String = rand::rng()
273 .sample_iter(&Alphanumeric)
274 .take(16)
275 .map(char::from)
276 .collect();
277
278 let schema_entry = schema.get_at_index(0);
279 if let Some((old_name, _)) = schema_entry {
280 let new_name = String::from(old_name.as_str()) + rand_string.as_str();
281 lf = lf.rename([old_name.to_string()], [new_name.clone()], true);
282 return Ok(Expr::SubPlan(
283 SpecialEq::new(Arc::new(lf.logical_plan)),
284 vec![new_name],
285 ));
286 }
287 };
288 polars_bail!(SQLInterface: "subquery type not supported");
289 }
290
291 fn visit_identifier(&self, ident: &Ident) -> PolarsResult<Expr> {
295 Ok(col(ident.value.as_str()))
296 }
297
298 fn visit_compound_identifier(&mut self, idents: &[Ident]) -> PolarsResult<Expr> {
302 Ok(resolve_compound_identifier(self.ctx, idents, self.active_schema)?[0].clone())
303 }
304
305 fn visit_like(
306 &mut self,
307 negated: bool,
308 expr: &SQLExpr,
309 pattern: &SQLExpr,
310 escape_char: &Option<String>,
311 case_insensitive: bool,
312 ) -> PolarsResult<Expr> {
313 if escape_char.is_some() {
314 polars_bail!(SQLInterface: "ESCAPE char for LIKE/ILIKE is not currently supported; found '{}'", escape_char.clone().unwrap());
315 }
316 let pat = match self.visit_expr(pattern) {
317 Ok(Expr::Literal(lv)) if lv.extract_str().is_some() => {
318 PlSmallStr::from_str(lv.extract_str().unwrap())
319 },
320 _ => {
321 polars_bail!(SQLSyntax: "LIKE/ILIKE pattern must be a string literal; found {}", pattern)
322 },
323 };
324 if pat.is_empty() || (!case_insensitive && pat.chars().all(|c| !matches!(c, '%' | '_'))) {
325 let op = if negated {
327 SQLBinaryOperator::NotEq
328 } else {
329 SQLBinaryOperator::Eq
330 };
331 self.visit_binary_op(expr, &op, pattern)
332 } else {
333 let mut rx = regex::escape(pat.as_str())
335 .replace('%', ".*")
336 .replace('_', ".");
337
338 rx = format!(
339 "^{}{}$",
340 if case_insensitive { "(?is)" } else { "(?s)" },
341 rx
342 );
343
344 let expr = self.visit_expr(expr)?;
345 let matches = expr.str().contains(lit(rx), true);
346 Ok(if negated { matches.not() } else { matches })
347 }
348 }
349
350 fn visit_subscript(&mut self, expr: &SQLExpr, subscript: &Subscript) -> PolarsResult<Expr> {
351 let expr = self.visit_expr(expr)?;
352 Ok(match subscript {
353 Subscript::Index { index } => {
354 let idx = adjust_one_indexed_param(self.visit_expr(index)?, true);
355 expr.list().get(idx, true)
356 },
357 Subscript::Slice { .. } => {
358 polars_bail!(SQLSyntax: "array slice syntax is not currently supported")
359 },
360 })
361 }
362
363 fn convert_temporal_strings(&mut self, left: &Expr, right: &Expr) -> Expr {
370 if let (Some(name), Some(s), expr_dtype) = match (left, right) {
371 (Expr::Column(name), Expr::Literal(lv)) if lv.extract_str().is_some() => {
373 (Some(name.clone()), Some(lv.extract_str().unwrap()), None)
374 },
375 (Expr::Cast { expr, dtype, .. }, Expr::Literal(lv)) if lv.extract_str().is_some() => {
377 let s = lv.extract_str().unwrap();
378 match &**expr {
379 Expr::Column(name) => (Some(name.clone()), Some(s), Some(dtype)),
380 _ => (None, Some(s), Some(dtype)),
381 }
382 },
383 _ => (None, None, None),
384 } {
385 if expr_dtype.is_none() && self.active_schema.is_none() {
386 right.clone()
387 } else {
388 let left_dtype = expr_dtype.map_or_else(
389 || {
390 self.active_schema
391 .as_ref()
392 .and_then(|schema| schema.get(&name))
393 },
394 |dt| dt.as_literal(),
395 );
396 match left_dtype {
397 Some(DataType::Time) if is_iso_time(s) => {
398 right.clone().str().to_time(StrptimeOptions {
399 strict: true,
400 ..Default::default()
401 })
402 },
403 Some(DataType::Date) if is_iso_date(s) => {
404 right.clone().str().to_date(StrptimeOptions {
405 strict: true,
406 ..Default::default()
407 })
408 },
409 Some(DataType::Datetime(tu, tz)) if is_iso_datetime(s) || is_iso_date(s) => {
410 if s.len() == 10 {
411 lit(format!("{s}T00:00:00"))
413 } else {
414 lit(s.replacen(' ', "T", 1))
415 }
416 .str()
417 .to_datetime(
418 Some(*tu),
419 tz.clone(),
420 StrptimeOptions {
421 strict: true,
422 ..Default::default()
423 },
424 lit("latest"),
425 )
426 },
427 _ => right.clone(),
428 }
429 }
430 } else {
431 right.clone()
432 }
433 }
434
435 fn struct_field_access_expr(
436 &mut self,
437 expr: &Expr,
438 path: &str,
439 infer_index: bool,
440 ) -> PolarsResult<Expr> {
441 let path_elems = if path.starts_with('{') && path.ends_with('}') {
442 path.trim_matches(|c| c == '{' || c == '}')
443 } else {
444 path
445 }
446 .split(',');
447
448 let mut expr = expr.clone();
449 for p in path_elems {
450 let p = p.trim();
451 expr = if infer_index {
452 match p.parse::<i64>() {
453 Ok(idx) => expr.list().get(lit(idx), true),
454 Err(_) => expr.struct_().field_by_name(p),
455 }
456 } else {
457 expr.struct_().field_by_name(p)
458 }
459 }
460 Ok(expr)
461 }
462
463 fn visit_binary_op(
467 &mut self,
468 left: &SQLExpr,
469 op: &SQLBinaryOperator,
470 right: &SQLExpr,
471 ) -> PolarsResult<Expr> {
472 if matches!(left, SQLExpr::Subquery(_)) || matches!(right, SQLExpr::Subquery(_)) {
474 let (suggestion, str_op) = match op {
475 SQLBinaryOperator::NotEq => ("; use 'NOT IN' instead", "!=".to_string()),
476 SQLBinaryOperator::Eq => ("; use 'IN' instead", format!("{op}")),
477 _ => ("", format!("{op}")),
478 };
479 polars_bail!(
480 SQLSyntax: "subquery comparisons with '{str_op}' are not supported{suggestion}"
481 );
482 }
483
484 let (lhs, mut rhs) = match (left, op, right) {
486 (_, SQLBinaryOperator::Minus, SQLExpr::Interval(v)) => {
487 let duration = interval_to_duration(v, false)?;
488 return Ok(self
489 .visit_expr(left)?
490 .dt()
491 .offset_by(lit(format!("-{duration}"))));
492 },
493 (_, SQLBinaryOperator::Plus, SQLExpr::Interval(v)) => {
494 let duration = interval_to_duration(v, false)?;
495 return Ok(self
496 .visit_expr(left)?
497 .dt()
498 .offset_by(lit(format!("{duration}"))));
499 },
500 (SQLExpr::Interval(v1), _, SQLExpr::Interval(v2)) => {
501 let d1 = interval_to_duration(v1, false)?;
503 let d2 = interval_to_duration(v2, false)?;
504 let res = match op {
505 SQLBinaryOperator::Gt => Ok(lit(d1 > d2)),
506 SQLBinaryOperator::Lt => Ok(lit(d1 < d2)),
507 SQLBinaryOperator::GtEq => Ok(lit(d1 >= d2)),
508 SQLBinaryOperator::LtEq => Ok(lit(d1 <= d2)),
509 SQLBinaryOperator::NotEq => Ok(lit(d1 != d2)),
510 SQLBinaryOperator::Eq | SQLBinaryOperator::Spaceship => Ok(lit(d1 == d2)),
511 _ => polars_bail!(SQLInterface: "invalid interval comparison operator"),
512 };
513 if res.is_ok() {
514 return res;
515 }
516 (self.visit_expr(left)?, self.visit_expr(right)?)
517 },
518 _ => (self.visit_expr(left)?, self.visit_expr(right)?),
519 };
520 rhs = self.convert_temporal_strings(&lhs, &rhs);
521
522 Ok(match op {
523 SQLBinaryOperator::BitwiseAnd => lhs.and(rhs), SQLBinaryOperator::BitwiseOr => lhs.or(rhs), SQLBinaryOperator::Xor => lhs.xor(rhs), SQLBinaryOperator::And => lhs.and(rhs), SQLBinaryOperator::Divide => lhs / rhs, SQLBinaryOperator::DuckIntegerDivide => lhs.floor_div(rhs).cast(DataType::Int64), 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::Minus => lhs - rhs, SQLBinaryOperator::Modulo => lhs % rhs, SQLBinaryOperator::Multiply => lhs * rhs, SQLBinaryOperator::NotEq => lhs.eq(rhs).not(), SQLBinaryOperator::Or => lhs.or(rhs), SQLBinaryOperator::Plus => lhs + rhs, SQLBinaryOperator::Spaceship => lhs.eq_missing(rhs), SQLBinaryOperator::StringConcat => { lhs.cast(DataType::String) + rhs.cast(DataType::String)
550 },
551 SQLBinaryOperator::PGStartsWith => lhs.str().starts_with(rhs), SQLBinaryOperator::PGRegexMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true),
557 _ => polars_bail!(SQLSyntax: "invalid pattern for '~' operator: {:?}", rhs),
558 },
559 SQLBinaryOperator::PGRegexNotMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true).not(),
561 _ => polars_bail!(SQLSyntax: "invalid pattern for '!~' operator: {:?}", rhs),
562 },
563 SQLBinaryOperator::PGRegexIMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => {
565 let pat = lv.extract_str().unwrap();
566 lhs.str().contains(lit(format!("(?i){pat}")), true)
567 },
568 _ => polars_bail!(SQLSyntax: "invalid pattern for '~*' operator: {:?}", rhs),
569 },
570 SQLBinaryOperator::PGRegexNotIMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => {
572 let pat = lv.extract_str().unwrap();
573 lhs.str().contains(lit(format!("(?i){pat}")), true).not()
574 },
575 _ => {
576 polars_bail!(SQLSyntax: "invalid pattern for '!~*' operator: {:?}", rhs)
577 },
578 },
579 SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch | SQLBinaryOperator::PGILikeMatch | SQLBinaryOperator::PGNotILikeMatch => { let expr = if matches!(
587 op,
588 SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch
589 ) {
590 SQLExpr::Like {
591 negated: matches!(op, SQLBinaryOperator::PGNotLikeMatch),
592 any: false,
593 expr: Box::new(left.clone()),
594 pattern: Box::new(right.clone()),
595 escape_char: None,
596 }
597 } else {
598 SQLExpr::ILike {
599 negated: matches!(op, SQLBinaryOperator::PGNotILikeMatch),
600 any: false,
601 expr: Box::new(left.clone()),
602 pattern: Box::new(right.clone()),
603 escape_char: None,
604 }
605 };
606 self.visit_expr(&expr)?
607 },
608 SQLBinaryOperator::Arrow | SQLBinaryOperator::LongArrow => match rhs { 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, false)?;
615 if let SQLBinaryOperator::LongArrow = op {
616 expr = expr.cast(DataType::String);
617 }
618 expr
619 },
620 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(idx))) => {
621 let mut expr = self.struct_field_access_expr(&lhs, &idx.to_string(), true)?;
622 if let SQLBinaryOperator::LongArrow = op {
623 expr = expr.cast(DataType::String);
624 }
625 expr
626 },
627 _ => {
628 polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", right)
629 },
630 },
631 SQLBinaryOperator::HashArrow | SQLBinaryOperator::HashLongArrow => { match rhs {
633 Expr::Literal(lv) if lv.extract_str().is_some() => {
634 let path = lv.extract_str().unwrap();
635 let mut expr = self.struct_field_access_expr(&lhs, path, true)?;
636 if let SQLBinaryOperator::HashLongArrow = op {
637 expr = expr.cast(DataType::String);
638 }
639 expr
640 },
641 _ => {
642 polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", rhs)
643 }
644 }
645 },
646 other => {
647 polars_bail!(SQLInterface: "operator {:?} is not currently supported", other)
648 },
649 })
650 }
651
652 fn visit_unary_op(&mut self, op: &UnaryOperator, expr: &SQLExpr) -> PolarsResult<Expr> {
656 let expr = self.visit_expr(expr)?;
657 Ok(match (op, expr.clone()) {
658 (UnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
660 lit(n)
661 },
662 (UnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n)))) => {
663 lit(n)
664 },
665 (UnaryOperator::Minus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
666 lit(-n)
667 },
668 (UnaryOperator::Minus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n)))) => {
669 lit(-n)
670 },
671 (UnaryOperator::Plus, _) => lit(0) + expr,
673 (UnaryOperator::Minus, _) => lit(0) - expr,
674 (UnaryOperator::Not, _) => match &expr {
675 Expr::Column(name)
676 if self
677 .active_schema
678 .and_then(|schema| schema.get(name))
679 .is_some_and(|dtype| matches!(dtype, DataType::Boolean)) =>
680 {
681 expr.not()
683 },
684 _ => expr.strict_cast(DataType::Boolean).not(),
686 },
687 other => polars_bail!(SQLInterface: "unary operator {:?} is not supported", other),
688 })
689 }
690
691 fn visit_function(&mut self, function: &SQLFunction) -> PolarsResult<Expr> {
697 let mut visitor = SQLFunctionVisitor {
698 func: function,
699 ctx: self.ctx,
700 active_schema: self.active_schema,
701 };
702 visitor.visit_function()
703 }
704
705 fn visit_all(
709 &mut self,
710 left: &SQLExpr,
711 compare_op: &SQLBinaryOperator,
712 right: &SQLExpr,
713 ) -> PolarsResult<Expr> {
714 let left = self.visit_expr(left)?;
715 let right = self.visit_expr(right)?;
716
717 match compare_op {
718 SQLBinaryOperator::Gt => Ok(left.gt(right.max())),
719 SQLBinaryOperator::Lt => Ok(left.lt(right.min())),
720 SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.max())),
721 SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.min())),
722 SQLBinaryOperator::Eq => polars_bail!(SQLSyntax: "ALL cannot be used with ="),
723 SQLBinaryOperator::NotEq => polars_bail!(SQLSyntax: "ALL cannot be used with !="),
724 _ => polars_bail!(SQLInterface: "invalid comparison operator"),
725 }
726 }
727
728 fn visit_any(
732 &mut self,
733 left: &SQLExpr,
734 compare_op: &SQLBinaryOperator,
735 right: &SQLExpr,
736 ) -> PolarsResult<Expr> {
737 let left = self.visit_expr(left)?;
738 let right = self.visit_expr(right)?;
739
740 match compare_op {
741 SQLBinaryOperator::Gt => Ok(left.gt(right.min())),
742 SQLBinaryOperator::Lt => Ok(left.lt(right.max())),
743 SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.min())),
744 SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.max())),
745 SQLBinaryOperator::Eq => Ok(left.is_in(right, false)),
746 SQLBinaryOperator::NotEq => Ok(left.is_in(right, false).not()),
747 _ => polars_bail!(SQLInterface: "invalid comparison operator"),
748 }
749 }
750
751 fn visit_array_expr(
753 &mut self,
754 elements: &[SQLExpr],
755 result_as_element: bool,
756 dtype_expr_match: Option<&Expr>,
757 ) -> PolarsResult<Expr> {
758 let mut elems = self.array_expr_to_series(elements)?;
759
760 if let (Some(Expr::Column(name)), Some(schema)) =
763 (dtype_expr_match, self.active_schema.as_ref())
764 {
765 if elems.dtype() == &DataType::String {
766 if let Some(dtype) = schema.get(name) {
767 if matches!(
768 dtype,
769 DataType::Date | DataType::Time | DataType::Datetime(_, _)
770 ) {
771 elems = elems.strict_cast(dtype)?;
772 }
773 }
774 }
775 }
776
777 let res = if result_as_element {
780 elems.implode()?.into_series()
781 } else {
782 elems
783 };
784 Ok(lit(res))
785 }
786
787 fn visit_cast(
791 &mut self,
792 expr: &SQLExpr,
793 dtype: &SQLDataType,
794 format: &Option<CastFormat>,
795 cast_kind: &CastKind,
796 ) -> PolarsResult<Expr> {
797 if format.is_some() {
798 return Err(
799 polars_err!(SQLInterface: "use of FORMAT is not currently supported in CAST"),
800 );
801 }
802 let expr = self.visit_expr(expr)?;
803
804 #[cfg(feature = "json")]
805 if dtype == &SQLDataType::JSON {
806 return Ok(expr.str().json_decode(DataType::Struct(Vec::new())));
808 }
809 let polars_type = map_sql_dtype_to_polars(dtype)?;
810 Ok(match cast_kind {
811 CastKind::Cast | CastKind::DoubleColon => expr.strict_cast(polars_type),
812 CastKind::TryCast | CastKind::SafeCast => expr.cast(polars_type),
813 })
814 }
815
816 fn visit_literal(&self, value: &SQLValue) -> PolarsResult<Expr> {
822 Ok(match value {
824 SQLValue::Boolean(b) => lit(*b),
825 SQLValue::DollarQuotedString(s) => lit(s.value.clone()),
826 #[cfg(feature = "binary_encoding")]
827 SQLValue::HexStringLiteral(x) => {
828 if x.len() % 2 != 0 {
829 polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
830 };
831 lit(hex::decode(x.clone()).unwrap())
832 },
833 SQLValue::Null => Expr::Literal(LiteralValue::untyped_null()),
834 SQLValue::Number(s, _) => {
835 if s.contains('.') {
837 s.parse::<f64>().map(lit).map_err(|_| ())
838 } else {
839 s.parse::<i64>().map(lit).map_err(|_| ())
840 }
841 .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
842 },
843 SQLValue::SingleQuotedByteStringLiteral(b) => {
844 bitstring_to_bytes_literal(b)?
848 },
849 SQLValue::SingleQuotedString(s) => lit(s.clone()),
850 other => {
851 polars_bail!(SQLInterface: "value {:?} is not a supported literal type", other)
852 },
853 })
854 }
855
856 fn visit_any_value(
858 &self,
859 value: &SQLValue,
860 op: Option<&UnaryOperator>,
861 ) -> PolarsResult<AnyValue<'_>> {
862 Ok(match value {
863 SQLValue::Boolean(b) => AnyValue::Boolean(*b),
864 SQLValue::DollarQuotedString(s) => AnyValue::StringOwned(s.clone().value.into()),
865 #[cfg(feature = "binary_encoding")]
866 SQLValue::HexStringLiteral(x) => {
867 if x.len() % 2 != 0 {
868 polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
869 };
870 AnyValue::BinaryOwned(hex::decode(x.clone()).unwrap())
871 },
872 SQLValue::Null => AnyValue::Null,
873 SQLValue::Number(s, _) => {
874 let negate = match op {
875 Some(UnaryOperator::Minus) => true,
876 Some(UnaryOperator::Plus) | None => false,
878 Some(op) => {
879 polars_bail!(SQLInterface: "unary op {:?} not supported for numeric SQL value", op)
880 },
881 };
882 if s.contains('.') {
884 s.parse::<f64>()
885 .map(|n: f64| AnyValue::Float64(if negate { -n } else { n }))
886 .map_err(|_| ())
887 } else {
888 s.parse::<i64>()
889 .map(|n: i64| AnyValue::Int64(if negate { -n } else { n }))
890 .map_err(|_| ())
891 }
892 .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
893 },
894 SQLValue::SingleQuotedByteStringLiteral(b) => {
895 let bytes_literal = bitstring_to_bytes_literal(b)?;
897 match bytes_literal {
898 Expr::Literal(lv) if lv.extract_binary().is_some() => {
899 AnyValue::BinaryOwned(lv.extract_binary().unwrap().to_vec())
900 },
901 _ => {
902 polars_bail!(SQLInterface: "failed to parse bitstring literal: {:?}", b)
903 },
904 }
905 },
906 SQLValue::SingleQuotedString(s) => AnyValue::StringOwned(s.as_str().into()),
907 other => polars_bail!(SQLInterface: "value {:?} is not currently supported", other),
908 })
909 }
910
911 fn visit_between(
914 &mut self,
915 expr: &SQLExpr,
916 negated: bool,
917 low: &SQLExpr,
918 high: &SQLExpr,
919 ) -> PolarsResult<Expr> {
920 let expr = self.visit_expr(expr)?;
921 let low = self.visit_expr(low)?;
922 let high = self.visit_expr(high)?;
923
924 let low = self.convert_temporal_strings(&expr, &low);
925 let high = self.convert_temporal_strings(&expr, &high);
926 Ok(if negated {
927 expr.clone().lt(low).or(expr.gt(high))
928 } else {
929 expr.clone().gt_eq(low).and(expr.lt_eq(high))
930 })
931 }
932
933 fn visit_trim(
936 &mut self,
937 expr: &SQLExpr,
938 trim_where: &Option<TrimWhereField>,
939 trim_what: &Option<Box<SQLExpr>>,
940 trim_characters: &Option<Vec<SQLExpr>>,
941 ) -> PolarsResult<Expr> {
942 if trim_characters.is_some() {
943 return Err(polars_err!(SQLSyntax: "unsupported TRIM syntax (custom chars)"));
945 };
946 let expr = self.visit_expr(expr)?;
947 let trim_what = trim_what.as_ref().map(|e| self.visit_expr(e)).transpose()?;
948 let trim_what = match trim_what {
949 Some(Expr::Literal(lv)) if lv.extract_str().is_some() => {
950 Some(PlSmallStr::from_str(lv.extract_str().unwrap()))
951 },
952 None => None,
953 _ => return self.err(&expr),
954 };
955 Ok(match (trim_where, trim_what) {
956 (None | Some(TrimWhereField::Both), None) => {
957 expr.str().strip_chars(lit(LiteralValue::untyped_null()))
958 },
959 (None | Some(TrimWhereField::Both), Some(val)) => expr.str().strip_chars(lit(val)),
960 (Some(TrimWhereField::Leading), None) => expr
961 .str()
962 .strip_chars_start(lit(LiteralValue::untyped_null())),
963 (Some(TrimWhereField::Leading), Some(val)) => expr.str().strip_chars_start(lit(val)),
964 (Some(TrimWhereField::Trailing), None) => expr
965 .str()
966 .strip_chars_end(lit(LiteralValue::untyped_null())),
967 (Some(TrimWhereField::Trailing), Some(val)) => expr.str().strip_chars_end(lit(val)),
968 })
969 }
970
971 fn visit_in_subquery(
973 &mut self,
974 expr: &SQLExpr,
975 subquery: &Subquery,
976 negated: bool,
977 ) -> PolarsResult<Expr> {
978 let subquery_result = self
979 .visit_subquery(subquery, SubqueryRestriction::SingleColumn)?
980 .implode();
981 let expr = self.visit_expr(expr)?;
982 Ok(if negated {
983 expr.is_in(subquery_result, false).not()
984 } else {
985 expr.is_in(subquery_result, false)
986 })
987 }
988
989 fn visit_case_when_then(&mut self, expr: &SQLExpr) -> PolarsResult<Expr> {
991 if let SQLExpr::Case {
992 operand,
993 conditions,
994 results,
995 else_result,
996 } = expr
997 {
998 polars_ensure!(
999 conditions.len() == results.len(),
1000 SQLSyntax: "WHEN and THEN expressions must have the same length"
1001 );
1002 polars_ensure!(
1003 !conditions.is_empty(),
1004 SQLSyntax: "WHEN and THEN expressions must have at least one element"
1005 );
1006
1007 let mut when_thens = conditions.iter().zip(results.iter());
1008 let first = when_thens.next();
1009 if first.is_none() {
1010 polars_bail!(SQLSyntax: "WHEN and THEN expressions must have at least one element");
1011 }
1012 let else_res = match else_result {
1013 Some(else_res) => self.visit_expr(else_res)?,
1014 None => lit(LiteralValue::untyped_null()), };
1016 if let Some(operand_expr) = operand {
1017 let first_operand_expr = self.visit_expr(operand_expr)?;
1018
1019 let first = first.unwrap();
1020 let first_cond = first_operand_expr.eq(self.visit_expr(first.0)?);
1021 let first_then = self.visit_expr(first.1)?;
1022 let expr = when(first_cond).then(first_then);
1023 let next = when_thens.next();
1024
1025 let mut when_then = if let Some((cond, res)) = next {
1026 let second_operand_expr = self.visit_expr(operand_expr)?;
1027 let cond = second_operand_expr.eq(self.visit_expr(cond)?);
1028 let res = self.visit_expr(res)?;
1029 expr.when(cond).then(res)
1030 } else {
1031 return Ok(expr.otherwise(else_res));
1032 };
1033 for (cond, res) in when_thens {
1034 let new_operand_expr = self.visit_expr(operand_expr)?;
1035 let cond = new_operand_expr.eq(self.visit_expr(cond)?);
1036 let res = self.visit_expr(res)?;
1037 when_then = when_then.when(cond).then(res);
1038 }
1039 return Ok(when_then.otherwise(else_res));
1040 }
1041
1042 let first = first.unwrap();
1043 let first_cond = self.visit_expr(first.0)?;
1044 let first_then = self.visit_expr(first.1)?;
1045 let expr = when(first_cond).then(first_then);
1046 let next = when_thens.next();
1047
1048 let mut when_then = if let Some((cond, res)) = next {
1049 let cond = self.visit_expr(cond)?;
1050 let res = self.visit_expr(res)?;
1051 expr.when(cond).then(res)
1052 } else {
1053 return Ok(expr.otherwise(else_res));
1054 };
1055 for (cond, res) in when_thens {
1056 let cond = self.visit_expr(cond)?;
1057 let res = self.visit_expr(res)?;
1058 when_then = when_then.when(cond).then(res);
1059 }
1060 Ok(when_then.otherwise(else_res))
1061 } else {
1062 unreachable!()
1063 }
1064 }
1065
1066 fn err(&self, expr: &Expr) -> PolarsResult<Expr> {
1067 polars_bail!(SQLInterface: "expression {:?} is not currently supported", expr);
1068 }
1069}
1070
1071pub fn sql_expr<S: AsRef<str>>(s: S) -> PolarsResult<Expr> {
1089 let mut ctx = SQLContext::new();
1090
1091 let mut parser = Parser::new(&GenericDialect);
1092 parser = parser.with_options(ParserOptions {
1093 trailing_commas: true,
1094 ..Default::default()
1095 });
1096
1097 let mut ast = parser
1098 .try_with_sql(s.as_ref())
1099 .map_err(to_sql_interface_err)?;
1100 let expr = ast.parse_select_item().map_err(to_sql_interface_err)?;
1101
1102 Ok(match &expr {
1103 SelectItem::ExprWithAlias { expr, alias } => {
1104 let expr = parse_sql_expr(expr, &mut ctx, None)?;
1105 expr.alias(alias.value.as_str())
1106 },
1107 SelectItem::UnnamedExpr(expr) => parse_sql_expr(expr, &mut ctx, None)?,
1108 _ => polars_bail!(SQLInterface: "unable to parse '{}' as Expr", s.as_ref()),
1109 })
1110}
1111
1112pub(crate) fn interval_to_duration(interval: &Interval, fixed: bool) -> PolarsResult<Duration> {
1113 if interval.last_field.is_some()
1114 || interval.leading_field.is_some()
1115 || interval.leading_precision.is_some()
1116 || interval.fractional_seconds_precision.is_some()
1117 {
1118 polars_bail!(SQLSyntax: "unsupported interval syntax ('{}')", interval)
1119 }
1120 let s = match &*interval.value {
1121 SQLExpr::UnaryOp { .. } => {
1122 polars_bail!(SQLSyntax: "unary ops are not valid on interval strings; found {}", interval.value)
1123 },
1124 SQLExpr::Value(SQLValue::SingleQuotedString(s)) => Some(s),
1125 _ => None,
1126 };
1127 match s {
1128 Some(s) if s.contains('-') => {
1129 polars_bail!(SQLInterface: "minus signs are not yet supported in interval strings; found '{}'", s)
1130 },
1131 Some(s) => {
1132 let duration = Duration::parse_interval(s);
1135 if fixed && duration.months() != 0 {
1136 polars_bail!(SQLSyntax: "fixed-duration interval cannot contain years, quarters, or months; found {}", s)
1137 };
1138 Ok(duration)
1139 },
1140 None => polars_bail!(SQLSyntax: "invalid interval {:?}", interval),
1141 }
1142}
1143
1144pub(crate) fn parse_sql_expr(
1145 expr: &SQLExpr,
1146 ctx: &mut SQLContext,
1147 active_schema: Option<&Schema>,
1148) -> PolarsResult<Expr> {
1149 let mut visitor = SQLExprVisitor { ctx, active_schema };
1150 visitor.visit_expr(expr)
1151}
1152
1153pub(crate) fn parse_sql_array(expr: &SQLExpr, ctx: &mut SQLContext) -> PolarsResult<Series> {
1154 match expr {
1155 SQLExpr::Array(arr) => {
1156 let mut visitor = SQLExprVisitor {
1157 ctx,
1158 active_schema: None,
1159 };
1160 visitor.array_expr_to_series(arr.elem.as_slice())
1161 },
1162 _ => polars_bail!(SQLSyntax: "Expected array expression, found {:?}", expr),
1163 }
1164}
1165
1166pub(crate) fn parse_extract_date_part(expr: Expr, field: &DateTimeField) -> PolarsResult<Expr> {
1167 let field = match field {
1168 DateTimeField::Custom(Ident { value, .. }) => {
1170 let value = value.to_ascii_lowercase();
1171 match value.as_str() {
1172 "millennium" | "millennia" => &DateTimeField::Millennium,
1173 "century" | "centuries" => &DateTimeField::Century,
1174 "decade" | "decades" => &DateTimeField::Decade,
1175 "isoyear" => &DateTimeField::Isoyear,
1176 "year" | "years" | "y" => &DateTimeField::Year,
1177 "quarter" | "quarters" => &DateTimeField::Quarter,
1178 "month" | "months" | "mon" | "mons" => &DateTimeField::Month,
1179 "dayofyear" | "doy" => &DateTimeField::DayOfYear,
1180 "dayofweek" | "dow" => &DateTimeField::DayOfWeek,
1181 "isoweek" | "week" | "weeks" => &DateTimeField::IsoWeek,
1182 "isodow" => &DateTimeField::Isodow,
1183 "day" | "days" | "d" => &DateTimeField::Day,
1184 "hour" | "hours" | "h" => &DateTimeField::Hour,
1185 "minute" | "minutes" | "mins" | "min" | "m" => &DateTimeField::Minute,
1186 "second" | "seconds" | "sec" | "secs" | "s" => &DateTimeField::Second,
1187 "millisecond" | "milliseconds" | "ms" => &DateTimeField::Millisecond,
1188 "microsecond" | "microseconds" | "us" => &DateTimeField::Microsecond,
1189 "nanosecond" | "nanoseconds" | "ns" => &DateTimeField::Nanosecond,
1190 #[cfg(feature = "timezones")]
1191 "timezone" => &DateTimeField::Timezone,
1192 "time" => &DateTimeField::Time,
1193 "epoch" => &DateTimeField::Epoch,
1194 _ => {
1195 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", value)
1196 },
1197 }
1198 },
1199 _ => field,
1200 };
1201 Ok(match field {
1202 DateTimeField::Millennium => expr.dt().millennium(),
1203 DateTimeField::Century => expr.dt().century(),
1204 DateTimeField::Decade => expr.dt().year() / typed_lit(10i32),
1205 DateTimeField::Isoyear => expr.dt().iso_year(),
1206 DateTimeField::Year => expr.dt().year(),
1207 DateTimeField::Quarter => expr.dt().quarter(),
1208 DateTimeField::Month => expr.dt().month(),
1209 DateTimeField::Week(weekday) => {
1210 if weekday.is_some() {
1211 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1212 }
1213 expr.dt().week()
1214 },
1215 DateTimeField::IsoWeek => expr.dt().week(),
1216 DateTimeField::DayOfYear | DateTimeField::Doy => expr.dt().ordinal_day(),
1217 DateTimeField::DayOfWeek | DateTimeField::Dow => {
1218 let w = expr.dt().weekday();
1219 when(w.clone().eq(typed_lit(7i8)))
1220 .then(typed_lit(0i8))
1221 .otherwise(w)
1222 },
1223 DateTimeField::Isodow => expr.dt().weekday(),
1224 DateTimeField::Day => expr.dt().day(),
1225 DateTimeField::Hour => expr.dt().hour(),
1226 DateTimeField::Minute => expr.dt().minute(),
1227 DateTimeField::Second => expr.dt().second(),
1228 DateTimeField::Millisecond | DateTimeField::Milliseconds => {
1229 (expr.clone().dt().second() * typed_lit(1_000f64))
1230 + expr.dt().nanosecond().div(typed_lit(1_000_000f64))
1231 },
1232 DateTimeField::Microsecond | DateTimeField::Microseconds => {
1233 (expr.clone().dt().second() * typed_lit(1_000_000f64))
1234 + expr.dt().nanosecond().div(typed_lit(1_000f64))
1235 },
1236 DateTimeField::Nanosecond | DateTimeField::Nanoseconds => {
1237 (expr.clone().dt().second() * typed_lit(1_000_000_000f64)) + expr.dt().nanosecond()
1238 },
1239 DateTimeField::Time => expr.dt().time(),
1240 #[cfg(feature = "timezones")]
1241 DateTimeField::Timezone => expr.dt().base_utc_offset().dt().total_seconds(false),
1242 DateTimeField::Epoch => {
1243 expr.clone()
1244 .dt()
1245 .timestamp(TimeUnit::Nanoseconds)
1246 .div(typed_lit(1_000_000_000i64))
1247 + expr.dt().nanosecond().div(typed_lit(1_000_000_000f64))
1248 },
1249 _ => {
1250 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1251 },
1252 })
1253}
1254
1255pub(crate) fn adjust_one_indexed_param(idx: Expr, null_if_zero: bool) -> Expr {
1258 match idx {
1259 Expr::Literal(sc) if sc.is_null() => lit(LiteralValue::untyped_null()),
1260 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(0))) => {
1261 if null_if_zero {
1262 lit(LiteralValue::untyped_null())
1263 } else {
1264 idx
1265 }
1266 },
1267 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) if n < 0 => idx,
1268 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) => lit(n - 1),
1269 _ => when(idx.clone().gt(lit(0)))
1272 .then(idx.clone() - lit(1))
1273 .otherwise(if null_if_zero {
1274 when(idx.clone().eq(lit(0)))
1275 .then(lit(LiteralValue::untyped_null()))
1276 .otherwise(idx.clone())
1277 } else {
1278 idx.clone()
1279 }),
1280 }
1281}
1282
1283fn resolve_column<'a>(
1284 ctx: &'a mut SQLContext,
1285 ident_root: &'a Ident,
1286 name: &'a str,
1287 dtype: &'a DataType,
1288) -> PolarsResult<(Expr, Option<&'a DataType>)> {
1289 let resolved = ctx.resolve_name(&ident_root.value, name);
1290 let resolved = resolved.as_str();
1291 Ok((
1292 if name != resolved {
1293 col(resolved).alias(name)
1294 } else {
1295 col(name)
1296 },
1297 Some(dtype),
1298 ))
1299}
1300
1301pub(crate) fn resolve_compound_identifier(
1302 ctx: &mut SQLContext,
1303 idents: &[Ident],
1304 active_schema: Option<&Schema>,
1305) -> PolarsResult<Vec<Expr>> {
1306 let ident_root = &idents[0];
1308 let mut remaining_idents = idents.iter().skip(1);
1309 let mut lf = ctx.get_table_from_current_scope(&ident_root.value);
1310
1311 let schema = if let Some(ref mut lf) = lf {
1312 lf.schema_with_arenas(&mut ctx.lp_arena, &mut ctx.expr_arena)
1313 } else {
1314 Ok(Arc::new(if let Some(active_schema) = active_schema {
1315 active_schema.clone()
1316 } else {
1317 Schema::default()
1318 }))
1319 }?;
1320
1321 let col_dtype: PolarsResult<(Expr, Option<&DataType>)> = if lf.is_none() && schema.is_empty() {
1322 Ok((col(ident_root.value.as_str()), None))
1323 } else {
1324 let name = &remaining_idents.next().unwrap().value;
1325 if lf.is_some() && name == "*" {
1326 return Ok(schema
1327 .iter_names_and_dtypes()
1328 .map(|(name, dtype)| resolve_column(ctx, ident_root, name, dtype).unwrap().0)
1329 .collect::<Vec<_>>());
1330 };
1331 let root_is_field = schema.get(&ident_root.value).is_some();
1332 if lf.is_none() && root_is_field {
1333 remaining_idents = idents.iter().skip(1);
1334 Ok((
1335 col(ident_root.value.as_str()),
1336 schema.get(&ident_root.value),
1337 ))
1338 } else if lf.is_none() && !root_is_field {
1339 polars_bail!(
1340 SQLInterface: "no table or struct column named '{}' found",
1341 ident_root
1342 )
1343 } else if let Some((_, name, dtype)) = schema.get_full(name) {
1344 resolve_column(ctx, ident_root, name, dtype)
1345 } else {
1346 polars_bail!(
1347 SQLInterface: "no column named '{}' found in table '{}'",
1348 name,
1349 ident_root
1350 )
1351 }
1352 };
1353
1354 let (mut column, mut dtype) = col_dtype?;
1356 for ident in remaining_idents {
1357 let name = ident.value.as_str();
1358 match dtype {
1359 Some(DataType::Struct(fields)) if name == "*" => {
1360 return Ok(fields
1361 .iter()
1362 .map(|fld| column.clone().struct_().field_by_name(&fld.name))
1363 .collect());
1364 },
1365 Some(DataType::Struct(fields)) => {
1366 dtype = fields
1367 .iter()
1368 .find(|fld| fld.name == name)
1369 .map(|fld| &fld.dtype);
1370 },
1371 Some(dtype) if name == "*" => {
1372 polars_bail!(SQLSyntax: "cannot expand '*' on non-Struct dtype; found {:?}", dtype)
1373 },
1374 _ => {
1375 dtype = None;
1376 },
1377 }
1378 column = column.struct_().field_by_name(name);
1379 }
1380 Ok(vec![column])
1381}