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