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::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]
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(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 fn visit_identifier(&self, ident: &Ident) -> PolarsResult<Expr> {
292 Ok(col(ident.value.as_str()))
293 }
294
295 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 let op = if negated {
324 SQLBinaryOperator::NotEq
325 } else {
326 SQLBinaryOperator::Eq
327 };
328 self.visit_binary_op(expr, &op, pattern)
329 } else {
330 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 fn convert_temporal_strings(&mut self, left: &Expr, right: &Expr) -> Expr {
367 if let (Some(name), Some(s), expr_dtype) = match (left, right) {
368 (Expr::Column(name), Expr::Literal(lv)) if lv.extract_str().is_some() => {
370 (Some(name.clone()), Some(lv.extract_str().unwrap()), None)
371 },
372 (Expr::Cast { expr, dtype, .. }, Expr::Literal(lv)) if lv.extract_str().is_some() => {
374 let s = lv.extract_str().unwrap();
375 match &**expr {
376 Expr::Column(name) => (Some(name.clone()), Some(s), Some(dtype)),
377 _ => (None, Some(s), Some(dtype)),
378 }
379 },
380 _ => (None, None, None),
381 } {
382 if expr_dtype.is_none() && self.active_schema.is_none() {
383 right.clone()
384 } else {
385 let left_dtype = expr_dtype.map_or_else(
386 || {
387 self.active_schema
388 .as_ref()
389 .and_then(|schema| schema.get(&name))
390 },
391 |dt| dt.as_literal(),
392 );
393 match left_dtype {
394 Some(DataType::Time) if is_iso_time(s) => {
395 right.clone().str().to_time(StrptimeOptions {
396 strict: true,
397 ..Default::default()
398 })
399 },
400 Some(DataType::Date) if is_iso_date(s) => {
401 right.clone().str().to_date(StrptimeOptions {
402 strict: true,
403 ..Default::default()
404 })
405 },
406 Some(DataType::Datetime(tu, tz)) if is_iso_datetime(s) || is_iso_date(s) => {
407 if s.len() == 10 {
408 lit(format!("{s}T00:00:00"))
410 } else {
411 lit(s.replacen(' ', "T", 1))
412 }
413 .str()
414 .to_datetime(
415 Some(*tu),
416 tz.clone(),
417 StrptimeOptions {
418 strict: true,
419 ..Default::default()
420 },
421 lit("latest"),
422 )
423 },
424 _ => right.clone(),
425 }
426 }
427 } else {
428 right.clone()
429 }
430 }
431
432 fn struct_field_access_expr(
433 &mut self,
434 expr: &Expr,
435 path: &str,
436 infer_index: bool,
437 ) -> PolarsResult<Expr> {
438 let path_elems = if path.starts_with('{') && path.ends_with('}') {
439 path.trim_matches(|c| c == '{' || c == '}')
440 } else {
441 path
442 }
443 .split(',');
444
445 let mut expr = expr.clone();
446 for p in path_elems {
447 let p = p.trim();
448 expr = if infer_index {
449 match p.parse::<i64>() {
450 Ok(idx) => expr.list().get(lit(idx), true),
451 Err(_) => expr.struct_().field_by_name(p),
452 }
453 } else {
454 expr.struct_().field_by_name(p)
455 }
456 }
457 Ok(expr)
458 }
459
460 fn visit_binary_op(
464 &mut self,
465 left: &SQLExpr,
466 op: &SQLBinaryOperator,
467 right: &SQLExpr,
468 ) -> PolarsResult<Expr> {
469 let (lhs, mut rhs) = match (left, op, right) {
471 (_, SQLBinaryOperator::Minus, SQLExpr::Interval(v)) => {
472 let duration = interval_to_duration(v, false)?;
473 return Ok(self
474 .visit_expr(left)?
475 .dt()
476 .offset_by(lit(format!("-{duration}"))));
477 },
478 (_, SQLBinaryOperator::Plus, SQLExpr::Interval(v)) => {
479 let duration = interval_to_duration(v, false)?;
480 return Ok(self
481 .visit_expr(left)?
482 .dt()
483 .offset_by(lit(format!("{duration}"))));
484 },
485 (SQLExpr::Interval(v1), _, SQLExpr::Interval(v2)) => {
486 let d1 = interval_to_duration(v1, false)?;
488 let d2 = interval_to_duration(v2, false)?;
489 let res = match op {
490 SQLBinaryOperator::Gt => Ok(lit(d1 > d2)),
491 SQLBinaryOperator::Lt => Ok(lit(d1 < d2)),
492 SQLBinaryOperator::GtEq => Ok(lit(d1 >= d2)),
493 SQLBinaryOperator::LtEq => Ok(lit(d1 <= d2)),
494 SQLBinaryOperator::NotEq => Ok(lit(d1 != d2)),
495 SQLBinaryOperator::Eq | SQLBinaryOperator::Spaceship => Ok(lit(d1 == d2)),
496 _ => polars_bail!(SQLInterface: "invalid interval comparison operator"),
497 };
498 if res.is_ok() {
499 return res;
500 }
501 (self.visit_expr(left)?, self.visit_expr(right)?)
502 },
503 _ => (self.visit_expr(left)?, self.visit_expr(right)?),
504 };
505 rhs = self.convert_temporal_strings(&lhs, &rhs);
506
507 Ok(match op {
508 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)
535 },
536 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),
542 _ => polars_bail!(SQLSyntax: "invalid pattern for '~' operator: {:?}", rhs),
543 },
544 SQLBinaryOperator::PGRegexNotMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => lhs.str().contains(rhs, true).not(),
546 _ => polars_bail!(SQLSyntax: "invalid pattern for '!~' operator: {:?}", rhs),
547 },
548 SQLBinaryOperator::PGRegexIMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => {
550 let pat = lv.extract_str().unwrap();
551 lhs.str().contains(lit(format!("(?i){pat}")), true)
552 },
553 _ => polars_bail!(SQLSyntax: "invalid pattern for '~*' operator: {:?}", rhs),
554 },
555 SQLBinaryOperator::PGRegexNotIMatch => match rhs { Expr::Literal(ref lv) if lv.extract_str().is_some() => {
557 let pat = lv.extract_str().unwrap();
558 lhs.str().contains(lit(format!("(?i){pat}")), true).not()
559 },
560 _ => {
561 polars_bail!(SQLSyntax: "invalid pattern for '!~*' operator: {:?}", rhs)
562 },
563 },
564 SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch | SQLBinaryOperator::PGILikeMatch | SQLBinaryOperator::PGNotILikeMatch => { let expr = if matches!(
572 op,
573 SQLBinaryOperator::PGLikeMatch | SQLBinaryOperator::PGNotLikeMatch
574 ) {
575 SQLExpr::Like {
576 negated: matches!(op, SQLBinaryOperator::PGNotLikeMatch),
577 any: false,
578 expr: Box::new(left.clone()),
579 pattern: Box::new(right.clone()),
580 escape_char: None,
581 }
582 } else {
583 SQLExpr::ILike {
584 negated: matches!(op, SQLBinaryOperator::PGNotILikeMatch),
585 any: false,
586 expr: Box::new(left.clone()),
587 pattern: Box::new(right.clone()),
588 escape_char: None,
589 }
590 };
591 self.visit_expr(&expr)?
592 },
593 SQLBinaryOperator::Arrow | SQLBinaryOperator::LongArrow => match rhs { Expr::Literal(lv) if lv.extract_str().is_some() => {
598 let path = lv.extract_str().unwrap();
599 let mut expr = self.struct_field_access_expr(&lhs, path, false)?;
600 if let SQLBinaryOperator::LongArrow = op {
601 expr = expr.cast(DataType::String);
602 }
603 expr
604 },
605 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(idx))) => {
606 let mut expr = self.struct_field_access_expr(&lhs, &idx.to_string(), true)?;
607 if let SQLBinaryOperator::LongArrow = op {
608 expr = expr.cast(DataType::String);
609 }
610 expr
611 },
612 _ => {
613 polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", right)
614 },
615 },
616 SQLBinaryOperator::HashArrow | SQLBinaryOperator::HashLongArrow => { match rhs {
618 Expr::Literal(lv) if lv.extract_str().is_some() => {
619 let path = lv.extract_str().unwrap();
620 let mut expr = self.struct_field_access_expr(&lhs, path, true)?;
621 if let SQLBinaryOperator::HashLongArrow = op {
622 expr = expr.cast(DataType::String);
623 }
624 expr
625 },
626 _ => {
627 polars_bail!(SQLSyntax: "invalid json/struct path-extract definition: {:?}", rhs)
628 }
629 }
630 },
631 other => {
632 polars_bail!(SQLInterface: "operator {:?} is not currently supported", other)
633 },
634 })
635 }
636
637 fn visit_unary_op(&mut self, op: &UnaryOperator, expr: &SQLExpr) -> PolarsResult<Expr> {
641 let expr = self.visit_expr(expr)?;
642 Ok(match (op, expr.clone()) {
643 (UnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
645 lit(n)
646 },
647 (UnaryOperator::Plus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n)))) => {
648 lit(n)
649 },
650 (UnaryOperator::Minus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n)))) => {
651 lit(-n)
652 },
653 (UnaryOperator::Minus, Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Float(n)))) => {
654 lit(-n)
655 },
656 (UnaryOperator::Plus, _) => lit(0) + expr,
658 (UnaryOperator::Minus, _) => lit(0) - expr,
659 (UnaryOperator::Not, _) => expr.not(),
660 other => polars_bail!(SQLInterface: "unary operator {:?} is not supported", other),
661 })
662 }
663
664 fn visit_function(&mut self, function: &SQLFunction) -> PolarsResult<Expr> {
670 let mut visitor = SQLFunctionVisitor {
671 func: function,
672 ctx: self.ctx,
673 active_schema: self.active_schema,
674 };
675 visitor.visit_function()
676 }
677
678 fn visit_all(
682 &mut self,
683 left: &SQLExpr,
684 compare_op: &SQLBinaryOperator,
685 right: &SQLExpr,
686 ) -> PolarsResult<Expr> {
687 let left = self.visit_expr(left)?;
688 let right = self.visit_expr(right)?;
689
690 match compare_op {
691 SQLBinaryOperator::Gt => Ok(left.gt(right.max())),
692 SQLBinaryOperator::Lt => Ok(left.lt(right.min())),
693 SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.max())),
694 SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.min())),
695 SQLBinaryOperator::Eq => polars_bail!(SQLSyntax: "ALL cannot be used with ="),
696 SQLBinaryOperator::NotEq => polars_bail!(SQLSyntax: "ALL cannot be used with !="),
697 _ => polars_bail!(SQLInterface: "invalid comparison operator"),
698 }
699 }
700
701 fn visit_any(
705 &mut self,
706 left: &SQLExpr,
707 compare_op: &SQLBinaryOperator,
708 right: &SQLExpr,
709 ) -> PolarsResult<Expr> {
710 let left = self.visit_expr(left)?;
711 let right = self.visit_expr(right)?;
712
713 match compare_op {
714 SQLBinaryOperator::Gt => Ok(left.gt(right.min())),
715 SQLBinaryOperator::Lt => Ok(left.lt(right.max())),
716 SQLBinaryOperator::GtEq => Ok(left.gt_eq(right.min())),
717 SQLBinaryOperator::LtEq => Ok(left.lt_eq(right.max())),
718 SQLBinaryOperator::Eq => Ok(left.is_in(right, false)),
719 SQLBinaryOperator::NotEq => Ok(left.is_in(right, false).not()),
720 _ => polars_bail!(SQLInterface: "invalid comparison operator"),
721 }
722 }
723
724 fn visit_array_expr(
726 &mut self,
727 elements: &[SQLExpr],
728 result_as_element: bool,
729 dtype_expr_match: Option<&Expr>,
730 ) -> PolarsResult<Expr> {
731 let mut elems = self.array_expr_to_series(elements)?;
732
733 if let (Some(Expr::Column(name)), Some(schema)) =
736 (dtype_expr_match, self.active_schema.as_ref())
737 {
738 if elems.dtype() == &DataType::String {
739 if let Some(dtype) = schema.get(name) {
740 if matches!(
741 dtype,
742 DataType::Date | DataType::Time | DataType::Datetime(_, _)
743 ) {
744 elems = elems.strict_cast(dtype)?;
745 }
746 }
747 }
748 }
749
750 let res = if result_as_element {
753 elems.implode()?.into_series()
754 } else {
755 elems
756 };
757 Ok(lit(res))
758 }
759
760 fn visit_cast(
764 &mut self,
765 expr: &SQLExpr,
766 dtype: &SQLDataType,
767 format: &Option<CastFormat>,
768 cast_kind: &CastKind,
769 ) -> PolarsResult<Expr> {
770 if format.is_some() {
771 return Err(
772 polars_err!(SQLInterface: "use of FORMAT is not currently supported in CAST"),
773 );
774 }
775 let expr = self.visit_expr(expr)?;
776
777 #[cfg(feature = "json")]
778 if dtype == &SQLDataType::JSON {
779 return Ok(expr.str().json_decode(<Option<DataTypeExpr>>::None, None));
780 }
781 let polars_type = map_sql_dtype_to_polars(dtype)?;
782 Ok(match cast_kind {
783 CastKind::Cast | CastKind::DoubleColon => expr.strict_cast(polars_type),
784 CastKind::TryCast | CastKind::SafeCast => expr.cast(polars_type),
785 })
786 }
787
788 fn visit_literal(&self, value: &SQLValue) -> PolarsResult<Expr> {
794 Ok(match value {
796 SQLValue::Boolean(b) => lit(*b),
797 SQLValue::DollarQuotedString(s) => lit(s.value.clone()),
798 #[cfg(feature = "binary_encoding")]
799 SQLValue::HexStringLiteral(x) => {
800 if x.len() % 2 != 0 {
801 polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
802 };
803 lit(hex::decode(x.clone()).unwrap())
804 },
805 SQLValue::Null => Expr::Literal(LiteralValue::untyped_null()),
806 SQLValue::Number(s, _) => {
807 if s.contains('.') {
809 s.parse::<f64>().map(lit).map_err(|_| ())
810 } else {
811 s.parse::<i64>().map(lit).map_err(|_| ())
812 }
813 .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
814 },
815 SQLValue::SingleQuotedByteStringLiteral(b) => {
816 bitstring_to_bytes_literal(b)?
820 },
821 SQLValue::SingleQuotedString(s) => lit(s.clone()),
822 other => {
823 polars_bail!(SQLInterface: "value {:?} is not a supported literal type", other)
824 },
825 })
826 }
827
828 fn visit_any_value(
830 &self,
831 value: &SQLValue,
832 op: Option<&UnaryOperator>,
833 ) -> PolarsResult<AnyValue> {
834 Ok(match value {
835 SQLValue::Boolean(b) => AnyValue::Boolean(*b),
836 SQLValue::DollarQuotedString(s) => AnyValue::StringOwned(s.clone().value.into()),
837 #[cfg(feature = "binary_encoding")]
838 SQLValue::HexStringLiteral(x) => {
839 if x.len() % 2 != 0 {
840 polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x)
841 };
842 AnyValue::BinaryOwned(hex::decode(x.clone()).unwrap())
843 },
844 SQLValue::Null => AnyValue::Null,
845 SQLValue::Number(s, _) => {
846 let negate = match op {
847 Some(UnaryOperator::Minus) => true,
848 Some(UnaryOperator::Plus) | None => false,
850 Some(op) => {
851 polars_bail!(SQLInterface: "unary op {:?} not supported for numeric SQL value", op)
852 },
853 };
854 if s.contains('.') {
856 s.parse::<f64>()
857 .map(|n: f64| AnyValue::Float64(if negate { -n } else { n }))
858 .map_err(|_| ())
859 } else {
860 s.parse::<i64>()
861 .map(|n: i64| AnyValue::Int64(if negate { -n } else { n }))
862 .map_err(|_| ())
863 }
864 .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))?
865 },
866 SQLValue::SingleQuotedByteStringLiteral(b) => {
867 let bytes_literal = bitstring_to_bytes_literal(b)?;
869 match bytes_literal {
870 Expr::Literal(lv) if lv.extract_binary().is_some() => {
871 AnyValue::BinaryOwned(lv.extract_binary().unwrap().to_vec())
872 },
873 _ => {
874 polars_bail!(SQLInterface: "failed to parse bitstring literal: {:?}", b)
875 },
876 }
877 },
878 SQLValue::SingleQuotedString(s) => AnyValue::StringOwned(s.as_str().into()),
879 other => polars_bail!(SQLInterface: "value {:?} is not currently supported", other),
880 })
881 }
882
883 fn visit_between(
886 &mut self,
887 expr: &SQLExpr,
888 negated: bool,
889 low: &SQLExpr,
890 high: &SQLExpr,
891 ) -> PolarsResult<Expr> {
892 let expr = self.visit_expr(expr)?;
893 let low = self.visit_expr(low)?;
894 let high = self.visit_expr(high)?;
895
896 let low = self.convert_temporal_strings(&expr, &low);
897 let high = self.convert_temporal_strings(&expr, &high);
898 Ok(if negated {
899 expr.clone().lt(low).or(expr.gt(high))
900 } else {
901 expr.clone().gt_eq(low).and(expr.lt_eq(high))
902 })
903 }
904
905 fn visit_trim(
908 &mut self,
909 expr: &SQLExpr,
910 trim_where: &Option<TrimWhereField>,
911 trim_what: &Option<Box<SQLExpr>>,
912 trim_characters: &Option<Vec<SQLExpr>>,
913 ) -> PolarsResult<Expr> {
914 if trim_characters.is_some() {
915 return Err(polars_err!(SQLSyntax: "unsupported TRIM syntax (custom chars)"));
917 };
918 let expr = self.visit_expr(expr)?;
919 let trim_what = trim_what.as_ref().map(|e| self.visit_expr(e)).transpose()?;
920 let trim_what = match trim_what {
921 Some(Expr::Literal(lv)) if lv.extract_str().is_some() => {
922 Some(PlSmallStr::from_str(lv.extract_str().unwrap()))
923 },
924 None => None,
925 _ => return self.err(&expr),
926 };
927 Ok(match (trim_where, trim_what) {
928 (None | Some(TrimWhereField::Both), None) => {
929 expr.str().strip_chars(lit(LiteralValue::untyped_null()))
930 },
931 (None | Some(TrimWhereField::Both), Some(val)) => expr.str().strip_chars(lit(val)),
932 (Some(TrimWhereField::Leading), None) => expr
933 .str()
934 .strip_chars_start(lit(LiteralValue::untyped_null())),
935 (Some(TrimWhereField::Leading), Some(val)) => expr.str().strip_chars_start(lit(val)),
936 (Some(TrimWhereField::Trailing), None) => expr
937 .str()
938 .strip_chars_end(lit(LiteralValue::untyped_null())),
939 (Some(TrimWhereField::Trailing), Some(val)) => expr.str().strip_chars_end(lit(val)),
940 })
941 }
942
943 fn visit_in_subquery(
945 &mut self,
946 expr: &SQLExpr,
947 subquery: &Subquery,
948 negated: bool,
949 ) -> PolarsResult<Expr> {
950 let subquery_result = self.visit_subquery(subquery, SubqueryRestriction::SingleColumn)?;
951 let expr = self.visit_expr(expr)?;
952 Ok(if negated {
953 expr.is_in(subquery_result, false).not()
954 } else {
955 expr.is_in(subquery_result, false)
956 })
957 }
958
959 fn visit_case_when_then(&mut self, expr: &SQLExpr) -> PolarsResult<Expr> {
961 if let SQLExpr::Case {
962 operand,
963 conditions,
964 results,
965 else_result,
966 } = expr
967 {
968 polars_ensure!(
969 conditions.len() == results.len(),
970 SQLSyntax: "WHEN and THEN expressions must have the same length"
971 );
972 polars_ensure!(
973 !conditions.is_empty(),
974 SQLSyntax: "WHEN and THEN expressions must have at least one element"
975 );
976
977 let mut when_thens = conditions.iter().zip(results.iter());
978 let first = when_thens.next();
979 if first.is_none() {
980 polars_bail!(SQLSyntax: "WHEN and THEN expressions must have at least one element");
981 }
982 let else_res = match else_result {
983 Some(else_res) => self.visit_expr(else_res)?,
984 None => lit(LiteralValue::untyped_null()), };
986 if let Some(operand_expr) = operand {
987 let first_operand_expr = self.visit_expr(operand_expr)?;
988
989 let first = first.unwrap();
990 let first_cond = first_operand_expr.eq(self.visit_expr(first.0)?);
991 let first_then = self.visit_expr(first.1)?;
992 let expr = when(first_cond).then(first_then);
993 let next = when_thens.next();
994
995 let mut when_then = if let Some((cond, res)) = next {
996 let second_operand_expr = self.visit_expr(operand_expr)?;
997 let cond = second_operand_expr.eq(self.visit_expr(cond)?);
998 let res = self.visit_expr(res)?;
999 expr.when(cond).then(res)
1000 } else {
1001 return Ok(expr.otherwise(else_res));
1002 };
1003 for (cond, res) in when_thens {
1004 let new_operand_expr = self.visit_expr(operand_expr)?;
1005 let cond = new_operand_expr.eq(self.visit_expr(cond)?);
1006 let res = self.visit_expr(res)?;
1007 when_then = when_then.when(cond).then(res);
1008 }
1009 return Ok(when_then.otherwise(else_res));
1010 }
1011
1012 let first = first.unwrap();
1013 let first_cond = self.visit_expr(first.0)?;
1014 let first_then = self.visit_expr(first.1)?;
1015 let expr = when(first_cond).then(first_then);
1016 let next = when_thens.next();
1017
1018 let mut when_then = if let Some((cond, res)) = next {
1019 let cond = self.visit_expr(cond)?;
1020 let res = self.visit_expr(res)?;
1021 expr.when(cond).then(res)
1022 } else {
1023 return Ok(expr.otherwise(else_res));
1024 };
1025 for (cond, res) in when_thens {
1026 let cond = self.visit_expr(cond)?;
1027 let res = self.visit_expr(res)?;
1028 when_then = when_then.when(cond).then(res);
1029 }
1030 Ok(when_then.otherwise(else_res))
1031 } else {
1032 unreachable!()
1033 }
1034 }
1035
1036 fn err(&self, expr: &Expr) -> PolarsResult<Expr> {
1037 polars_bail!(SQLInterface: "expression {:?} is not currently supported", expr);
1038 }
1039}
1040
1041pub fn sql_expr<S: AsRef<str>>(s: S) -> PolarsResult<Expr> {
1059 let mut ctx = SQLContext::new();
1060
1061 let mut parser = Parser::new(&GenericDialect);
1062 parser = parser.with_options(ParserOptions {
1063 trailing_commas: true,
1064 ..Default::default()
1065 });
1066
1067 let mut ast = parser
1068 .try_with_sql(s.as_ref())
1069 .map_err(to_sql_interface_err)?;
1070 let expr = ast.parse_select_item().map_err(to_sql_interface_err)?;
1071
1072 Ok(match &expr {
1073 SelectItem::ExprWithAlias { expr, alias } => {
1074 let expr = parse_sql_expr(expr, &mut ctx, None)?;
1075 expr.alias(alias.value.as_str())
1076 },
1077 SelectItem::UnnamedExpr(expr) => parse_sql_expr(expr, &mut ctx, None)?,
1078 _ => polars_bail!(SQLInterface: "unable to parse '{}' as Expr", s.as_ref()),
1079 })
1080}
1081
1082pub(crate) fn interval_to_duration(interval: &Interval, fixed: bool) -> PolarsResult<Duration> {
1083 if interval.last_field.is_some()
1084 || interval.leading_field.is_some()
1085 || interval.leading_precision.is_some()
1086 || interval.fractional_seconds_precision.is_some()
1087 {
1088 polars_bail!(SQLSyntax: "unsupported interval syntax ('{}')", interval)
1089 }
1090 let s = match &*interval.value {
1091 SQLExpr::UnaryOp { .. } => {
1092 polars_bail!(SQLSyntax: "unary ops are not valid on interval strings; found {}", interval.value)
1093 },
1094 SQLExpr::Value(SQLValue::SingleQuotedString(s)) => Some(s),
1095 _ => None,
1096 };
1097 match s {
1098 Some(s) if s.contains('-') => {
1099 polars_bail!(SQLInterface: "minus signs are not yet supported in interval strings; found '{}'", s)
1100 },
1101 Some(s) => {
1102 let duration = Duration::parse_interval(s);
1105 if fixed && duration.months() != 0 {
1106 polars_bail!(SQLSyntax: "fixed-duration interval cannot contain years, quarters, or months; found {}", s)
1107 };
1108 Ok(duration)
1109 },
1110 None => polars_bail!(SQLSyntax: "invalid interval {:?}", interval),
1111 }
1112}
1113
1114pub(crate) fn parse_sql_expr(
1115 expr: &SQLExpr,
1116 ctx: &mut SQLContext,
1117 active_schema: Option<&Schema>,
1118) -> PolarsResult<Expr> {
1119 let mut visitor = SQLExprVisitor { ctx, active_schema };
1120 visitor.visit_expr(expr)
1121}
1122
1123pub(crate) fn parse_sql_array(expr: &SQLExpr, ctx: &mut SQLContext) -> PolarsResult<Series> {
1124 match expr {
1125 SQLExpr::Array(arr) => {
1126 let mut visitor = SQLExprVisitor {
1127 ctx,
1128 active_schema: None,
1129 };
1130 visitor.array_expr_to_series(arr.elem.as_slice())
1131 },
1132 _ => polars_bail!(SQLSyntax: "Expected array expression, found {:?}", expr),
1133 }
1134}
1135
1136pub(crate) fn parse_extract_date_part(expr: Expr, field: &DateTimeField) -> PolarsResult<Expr> {
1137 let field = match field {
1138 DateTimeField::Custom(Ident { value, .. }) => {
1140 let value = value.to_ascii_lowercase();
1141 match value.as_str() {
1142 "millennium" | "millennia" => &DateTimeField::Millennium,
1143 "century" | "centuries" => &DateTimeField::Century,
1144 "decade" | "decades" => &DateTimeField::Decade,
1145 "isoyear" => &DateTimeField::Isoyear,
1146 "year" | "years" | "y" => &DateTimeField::Year,
1147 "quarter" | "quarters" => &DateTimeField::Quarter,
1148 "month" | "months" | "mon" | "mons" => &DateTimeField::Month,
1149 "dayofyear" | "doy" => &DateTimeField::DayOfYear,
1150 "dayofweek" | "dow" => &DateTimeField::DayOfWeek,
1151 "isoweek" | "week" | "weeks" => &DateTimeField::IsoWeek,
1152 "isodow" => &DateTimeField::Isodow,
1153 "day" | "days" | "d" => &DateTimeField::Day,
1154 "hour" | "hours" | "h" => &DateTimeField::Hour,
1155 "minute" | "minutes" | "mins" | "min" | "m" => &DateTimeField::Minute,
1156 "second" | "seconds" | "sec" | "secs" | "s" => &DateTimeField::Second,
1157 "millisecond" | "milliseconds" | "ms" => &DateTimeField::Millisecond,
1158 "microsecond" | "microseconds" | "us" => &DateTimeField::Microsecond,
1159 "nanosecond" | "nanoseconds" | "ns" => &DateTimeField::Nanosecond,
1160 #[cfg(feature = "timezones")]
1161 "timezone" => &DateTimeField::Timezone,
1162 "time" => &DateTimeField::Time,
1163 "epoch" => &DateTimeField::Epoch,
1164 _ => {
1165 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", value)
1166 },
1167 }
1168 },
1169 _ => field,
1170 };
1171 Ok(match field {
1172 DateTimeField::Millennium => expr.dt().millennium(),
1173 DateTimeField::Century => expr.dt().century(),
1174 DateTimeField::Decade => expr.dt().year() / typed_lit(10i32),
1175 DateTimeField::Isoyear => expr.dt().iso_year(),
1176 DateTimeField::Year => expr.dt().year(),
1177 DateTimeField::Quarter => expr.dt().quarter(),
1178 DateTimeField::Month => expr.dt().month(),
1179 DateTimeField::Week(weekday) => {
1180 if weekday.is_some() {
1181 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1182 }
1183 expr.dt().week()
1184 },
1185 DateTimeField::IsoWeek => expr.dt().week(),
1186 DateTimeField::DayOfYear | DateTimeField::Doy => expr.dt().ordinal_day(),
1187 DateTimeField::DayOfWeek | DateTimeField::Dow => {
1188 let w = expr.dt().weekday();
1189 when(w.clone().eq(typed_lit(7i8)))
1190 .then(typed_lit(0i8))
1191 .otherwise(w)
1192 },
1193 DateTimeField::Isodow => expr.dt().weekday(),
1194 DateTimeField::Day => expr.dt().day(),
1195 DateTimeField::Hour => expr.dt().hour(),
1196 DateTimeField::Minute => expr.dt().minute(),
1197 DateTimeField::Second => expr.dt().second(),
1198 DateTimeField::Millisecond | DateTimeField::Milliseconds => {
1199 (expr.clone().dt().second() * typed_lit(1_000f64))
1200 + expr.dt().nanosecond().div(typed_lit(1_000_000f64))
1201 },
1202 DateTimeField::Microsecond | DateTimeField::Microseconds => {
1203 (expr.clone().dt().second() * typed_lit(1_000_000f64))
1204 + expr.dt().nanosecond().div(typed_lit(1_000f64))
1205 },
1206 DateTimeField::Nanosecond | DateTimeField::Nanoseconds => {
1207 (expr.clone().dt().second() * typed_lit(1_000_000_000f64)) + expr.dt().nanosecond()
1208 },
1209 DateTimeField::Time => expr.dt().time(),
1210 #[cfg(feature = "timezones")]
1211 DateTimeField::Timezone => expr.dt().base_utc_offset().dt().total_seconds(),
1212 DateTimeField::Epoch => {
1213 expr.clone()
1214 .dt()
1215 .timestamp(TimeUnit::Nanoseconds)
1216 .div(typed_lit(1_000_000_000i64))
1217 + expr.dt().nanosecond().div(typed_lit(1_000_000_000f64))
1218 },
1219 _ => {
1220 polars_bail!(SQLSyntax: "EXTRACT/DATE_PART does not support '{}' part", field)
1221 },
1222 })
1223}
1224
1225pub(crate) fn adjust_one_indexed_param(idx: Expr, null_if_zero: bool) -> Expr {
1228 match idx {
1229 Expr::Literal(sc) if sc.is_null() => lit(LiteralValue::untyped_null()),
1230 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(0))) => {
1231 if null_if_zero {
1232 lit(LiteralValue::untyped_null())
1233 } else {
1234 idx
1235 }
1236 },
1237 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) if n < 0 => idx,
1238 Expr::Literal(LiteralValue::Dyn(DynLiteralValue::Int(n))) => lit(n - 1),
1239 _ => when(idx.clone().gt(lit(0)))
1242 .then(idx.clone() - lit(1))
1243 .otherwise(if null_if_zero {
1244 when(idx.clone().eq(lit(0)))
1245 .then(lit(LiteralValue::untyped_null()))
1246 .otherwise(idx.clone())
1247 } else {
1248 idx.clone()
1249 }),
1250 }
1251}
1252
1253fn resolve_column<'a>(
1254 ctx: &'a mut SQLContext,
1255 ident_root: &'a Ident,
1256 name: &'a str,
1257 dtype: &'a DataType,
1258) -> PolarsResult<(Expr, Option<&'a DataType>)> {
1259 let resolved = ctx.resolve_name(&ident_root.value, name);
1260 let resolved = resolved.as_str();
1261 Ok((
1262 if name != resolved {
1263 col(resolved).alias(name)
1264 } else {
1265 col(name)
1266 },
1267 Some(dtype),
1268 ))
1269}
1270
1271pub(crate) fn resolve_compound_identifier(
1272 ctx: &mut SQLContext,
1273 idents: &[Ident],
1274 active_schema: Option<&Schema>,
1275) -> PolarsResult<Vec<Expr>> {
1276 let ident_root = &idents[0];
1278 let mut remaining_idents = idents.iter().skip(1);
1279 let mut lf = ctx.get_table_from_current_scope(&ident_root.value);
1280
1281 let schema = if let Some(ref mut lf) = lf {
1282 lf.schema_with_arenas(&mut ctx.lp_arena, &mut ctx.expr_arena)
1283 } else {
1284 Ok(Arc::new(if let Some(active_schema) = active_schema {
1285 active_schema.clone()
1286 } else {
1287 Schema::default()
1288 }))
1289 }?;
1290
1291 let col_dtype: PolarsResult<(Expr, Option<&DataType>)> = if lf.is_none() && schema.is_empty() {
1292 Ok((col(ident_root.value.as_str()), None))
1293 } else {
1294 let name = &remaining_idents.next().unwrap().value;
1295 if lf.is_some() && name == "*" {
1296 return Ok(schema
1297 .iter_names_and_dtypes()
1298 .map(|(name, dtype)| resolve_column(ctx, ident_root, name, dtype).unwrap().0)
1299 .collect::<Vec<_>>());
1300 };
1301 let root_is_field = schema.get(&ident_root.value).is_some();
1302 if lf.is_none() && root_is_field {
1303 remaining_idents = idents.iter().skip(1);
1304 Ok((
1305 col(ident_root.value.as_str()),
1306 schema.get(&ident_root.value),
1307 ))
1308 } else if lf.is_none() && !root_is_field {
1309 polars_bail!(
1310 SQLInterface: "no table or struct column named '{}' found",
1311 ident_root
1312 )
1313 } else if let Some((_, name, dtype)) = schema.get_full(name) {
1314 resolve_column(ctx, ident_root, name, dtype)
1315 } else {
1316 polars_bail!(
1317 SQLInterface: "no column named '{}' found in table '{}'",
1318 name,
1319 ident_root
1320 )
1321 }
1322 };
1323
1324 let (mut column, mut dtype) = col_dtype?;
1326 for ident in remaining_idents {
1327 let name = ident.value.as_str();
1328 match dtype {
1329 Some(DataType::Struct(fields)) if name == "*" => {
1330 return Ok(fields
1331 .iter()
1332 .map(|fld| column.clone().struct_().field_by_name(&fld.name))
1333 .collect());
1334 },
1335 Some(DataType::Struct(fields)) => {
1336 dtype = fields
1337 .iter()
1338 .find(|fld| fld.name == name)
1339 .map(|fld| &fld.dtype);
1340 },
1341 Some(dtype) if name == "*" => {
1342 polars_bail!(SQLSyntax: "cannot expand '*' on non-Struct dtype; found {:?}", dtype)
1343 },
1344 _ => {
1345 dtype = None;
1346 },
1347 }
1348 column = column.struct_().field_by_name(name);
1349 }
1350 Ok(vec![column])
1351}