diff --git a/crates/polars-error/src/lib.rs b/crates/polars-error/src/lib.rs index d6841de1eb66..63669029acbd 100644 --- a/crates/polars-error/src/lib.rs +++ b/crates/polars-error/src/lib.rs @@ -75,6 +75,10 @@ pub enum PolarsError { SchemaMismatch(ErrString), #[error("lengths don't match: {0}")] ShapeMismatch(ErrString), + #[error("{0}")] + SQLInterface(ErrString), + #[error("{0}")] + SQLSyntax(ErrString), #[error("string caches don't match: {0}")] StringCacheMismatch(ErrString), #[error("field not found: {0}")] @@ -198,6 +202,8 @@ impl PolarsError { ShapeMismatch(msg) => ShapeMismatch(func(msg).into()), StringCacheMismatch(msg) => StringCacheMismatch(func(msg).into()), StructFieldNotFound(msg) => StructFieldNotFound(func(msg).into()), + SQLInterface(msg) => SQLInterface(func(msg).into()), + SQLSyntax(msg) => SQLSyntax(func(msg).into()), _ => unreachable!(), } } diff --git a/crates/polars-sql/src/context.rs b/crates/polars-sql/src/context.rs index a44788ae0ace..115dbc3828dc 100644 --- a/crates/polars-sql/src/context.rs +++ b/crates/polars-sql/src/context.rs @@ -119,7 +119,7 @@ impl SQLContext { .parse_statements() .map_err(to_compute_err)?; - polars_ensure!(ast.len() == 1, ComputeError: "One (and only one) statement at a time please"); + polars_ensure!(ast.len() == 1, SQLInterface: "one (and only one) statement at a time please"); let res = self.execute_statement(ast.first().unwrap())?; // Ensure the result uses the proper arenas. @@ -168,7 +168,7 @@ impl SQLContext { stmt @ Statement::Explain { .. } => self.execute_explain(stmt)?, stmt @ Statement::Truncate { .. } => self.execute_truncate_table(stmt)?, _ => polars_bail!( - ComputeError: "SQL statement type {:?} is not supported", ast, + SQLInterface: "statement type {:?} is not supported", ast, ), }) } @@ -221,9 +221,9 @@ impl SQLContext { right, } => self.process_union(left, right, set_quantifier, query), SetExpr::SetOperation { op, .. } => { - polars_bail!(InvalidOperation: "'{}' operation not yet supported", op) + polars_bail!(SQLInterface: "'{}' operation not yet supported", op) }, - op => polars_bail!(InvalidOperation: "'{}' operation not yet supported", op), + op => polars_bail!(SQLInterface: "'{}' operation not yet supported", op), } } @@ -259,7 +259,7 @@ impl SQLContext { concatenated.map(|lf| lf.unique(None, UniqueKeepStrategy::Any)) }, #[allow(unreachable_patterns)] - _ => polars_bail!(InvalidOperation: "'UNION {}' is not yet supported", quantifier), + _ => polars_bail!(SQLInterface: "'UNION {}' is not yet supported", quantifier), } } @@ -318,10 +318,12 @@ impl SQLContext { .lazy(); Ok(lf.clone()) } else { - polars_bail!(ComputeError: "table '{}' does not exist", tbl); + polars_bail!(SQLInterface: "table '{}' does not exist", tbl); } }, - _ => polars_bail!(ComputeError: "TRUNCATE does not support use of 'partitions'"), + _ => { + polars_bail!(SQLInterface: "TRUNCATE does not support use of 'partitions'") + }, } } else { unreachable!() @@ -335,7 +337,7 @@ impl SQLContext { fn register_ctes(&mut self, query: &Query) -> PolarsResult<()> { if let Some(with) = &query.with { if with.recursive { - polars_bail!(ComputeError: "recursive CTEs are not supported") + polars_bail!(SQLInterface: "recursive CTEs are not supported") } for cte in &with.cte_tables { let cte_name = cte.alias.name.value.clone(); @@ -386,7 +388,7 @@ impl SQLContext { JoinOperator::CrossJoin => lf.cross_join(rf, Some(format!(":{}", r_name))), join_type => { polars_bail!( - InvalidOperation: + SQLInterface: "join type '{:?}' not yet supported by polars-sql", join_type ); }, @@ -424,7 +426,7 @@ impl SQLContext { let sql_tbl: &TableWithJoins = select_stmt .from .first() - .ok_or_else(|| polars_err!(ComputeError: "no table name provided in query"))?; + .ok_or_else(|| polars_err!(SQLSyntax: "no table name provided in query"))?; let mut lf = self.execute_from_statement(sql_tbl)?; let mut contains_wildcard = false; @@ -482,8 +484,8 @@ impl SQLContext { } if matches!(**expr, SQLExpr::Value(SQLValue::Number(_, _))) => { if let SQLExpr::Value(SQLValue::Number(ref idx, _)) = **expr { Err(polars_err!( - ComputeError: - "group_by error: expected a positive integer or valid expression; got -{}", + SQLSyntax: + "GROUP BY error: expected a positive integer or valid expression; got -{}", idx )) } else { @@ -496,8 +498,8 @@ impl SQLContext { Ok(projections[idx - 1].clone()) }, SQLExpr::Value(v) => Err(polars_err!( - ComputeError: - "group_by error: expected a positive integer or valid expression; got {}", v, + SQLSyntax: + "GROUP BY error: expected a positive integer or valid expression; got {}", v, )), _ => parse_sql_expr(e, self, schema.as_deref()), }) @@ -621,10 +623,7 @@ impl SQLContext { if let Expr::Column(name) = expr { Ok(name.to_string()) } else { - Err(polars_err!( - ComputeError: - "DISTINCT ON only supports column names" - )) + Err(polars_err!(SQLSyntax:"DISTINCT ON only supports column names")) } }) .collect::>>()?; @@ -700,7 +699,7 @@ impl SQLContext { let tbl_name = name.0.first().unwrap().value.as_str(); // CREATE TABLE IF NOT EXISTS if *if_not_exists && self.table_map.contains_key(tbl_name) { - polars_bail!(ComputeError: "relation {} already exists", tbl_name); + polars_bail!(SQLInterface: "relation {} already exists", tbl_name); // CREATE OR REPLACE TABLE } if let Some(query) = query { @@ -713,7 +712,7 @@ impl SQLContext { .lazy(); Ok(out) } else { - polars_bail!(ComputeError: "only CREATE TABLE AS SELECT is supported"); + polars_bail!(SQLInterface: "only `CREATE TABLE AS SELECT` is currently supported"); } } else { unreachable!() @@ -740,7 +739,7 @@ impl SQLContext { None => Ok((tbl_name.to_string(), lf)), } } else { - polars_bail!(ComputeError: "relation '{}' was not found", tbl_name); + polars_bail!(SQLInterface: "relation '{}' was not found", tbl_name); } }, TableFactor::Derived { @@ -748,14 +747,13 @@ impl SQLContext { subquery, alias, } => { - polars_ensure!(!(*lateral), ComputeError: "LATERAL not supported"); - + polars_ensure!(!(*lateral), SQLInterface: "LATERAL not supported"); if let Some(alias) = alias { let lf = self.execute_query_no_ctes(subquery)?; self.table_map.insert(alias.name.value.clone(), lf.clone()); Ok((alias.name.value.clone(), lf)) } else { - polars_bail!(ComputeError: "derived tables must have aliases"); + polars_bail!(SQLSyntax: "derived tables must have aliases"); } }, TableFactor::UNNEST { @@ -784,13 +782,13 @@ impl SQLContext { .collect::>()?; polars_ensure!(!column_names.is_empty(), - ComputeError: + SQLSyntax: "UNNEST table alias must also declare column names, eg: {} (a,b,c)", alias.name.to_string() ); if column_names.len() != column_values.len() { let plural = if column_values.len() > 1 { "s" } else { "" }; polars_bail!( - ComputeError: + SQLSyntax: "UNNEST table alias requires {} column name{}, found {}", column_values.len(), plural, column_names.len() ); } @@ -810,17 +808,17 @@ impl SQLContext { if *with_offset { // TODO: make a PR to `sqlparser-rs` to support 'ORDINALITY' // (note that 'OFFSET' is BigQuery-specific syntax, not PostgreSQL) - polars_bail!(ComputeError: "UNNEST tables do not (yet) support WITH OFFSET/ORDINALITY"); + polars_bail!(SQLInterface: "UNNEST tables do not (yet) support WITH OFFSET/ORDINALITY"); } self.table_map.insert(table_name.clone(), lf.clone()); Ok((table_name.clone(), lf)) } else { - polars_bail!(ComputeError: "UNNEST table must have an alias"); + polars_bail!(SQLSyntax: "UNNEST table must have an alias"); } }, // Support bare table, optional with alias for now - _ => polars_bail!(ComputeError: "not yet implemented: {}", relation), + _ => polars_bail!(SQLInterface: "not yet implemented: {}", relation), } } @@ -858,7 +856,7 @@ impl SQLContext { descending.push(!ob.asc.unwrap_or(true)); polars_ensure!( ob.nulls_first.is_none(), - ComputeError: "nulls first/last is not yet supported", + SQLInterface: "NULLS FIRST/LAST is not (yet) supported", ); } @@ -879,7 +877,7 @@ impl SQLContext { ) -> PolarsResult { polars_ensure!( !contains_wildcard, - ComputeError: "group_by error: can't process wildcard in group_by" + SQLSyntax: "GROUP BY error: can't process wildcard in group_by" ); let schema_before = lf.schema_with_arenas(&mut self.lp_arena, &mut self.expr_arena)?; let group_by_keys_schema = @@ -919,7 +917,7 @@ impl SQLContext { } else if let Expr::Column(_) = e { // Non-aggregated columns must be part of the GROUP BY clause if !group_by_keys_schema.contains(&field.name) { - polars_bail!(ComputeError: "'{}' should participate in the GROUP BY clause or an aggregate function", &field.name); + polars_bail!(SQLSyntax: "'{}' should participate in the GROUP BY clause or an aggregate function", &field.name); } } } @@ -962,10 +960,10 @@ impl SQLContext { ) => Ok(lf.slice( offset .parse() - .map_err(|e| polars_err!(ComputeError: "OFFSET conversion error: {}", e))?, + .map_err(|e| polars_err!(SQLInterface: "OFFSET conversion error: {}", e))?, limit .parse() - .map_err(|e| polars_err!(ComputeError: "LIMIT conversion error: {}", e))?, + .map_err(|e| polars_err!(SQLInterface: "LIMIT conversion error: {}", e))?, )), ( Some(Offset { @@ -976,17 +974,17 @@ impl SQLContext { ) => Ok(lf.slice( offset .parse() - .map_err(|e| polars_err!(ComputeError: "OFFSET conversion error: {}", e))?, + .map_err(|e| polars_err!(SQLInterface: "OFFSET conversion error: {}", e))?, IdxSize::MAX, )), (None, Some(SQLExpr::Value(SQLValue::Number(limit, _)))) => Ok(lf.limit( limit .parse() - .map_err(|e| polars_err!(ComputeError: "LIMIT conversion error: {}", e))?, + .map_err(|e| polars_err!(SQLInterface: "LIMIT conversion error: {}", e))?, )), (None, None) => Ok(lf), _ => polars_bail!( - ComputeError: "non-numeric arguments for LIMIT/OFFSET are not supported", + SQLSyntax: "non-numeric arguments for LIMIT/OFFSET are not supported", ), } } @@ -1002,7 +1000,7 @@ impl SQLContext { [tbl_name] => { let lf = self.table_map.get_mut(&tbl_name.value).ok_or_else(|| { polars_err!( - ComputeError: "no table named '{}' found", + SQLInterface: "no table named '{}' found", tbl_name ) })?; @@ -1010,7 +1008,7 @@ impl SQLContext { cols(schema.iter_names()) }, e => polars_bail!( - ComputeError: "invalid wildcard expression: {:?}", + SQLSyntax: "invalid wildcard expression: {:?}", e ), }; @@ -1024,7 +1022,7 @@ impl SQLContext { contains_wildcard_exclude: &mut bool, ) -> PolarsResult { if options.opt_except.is_some() { - polars_bail!(InvalidOperation: "EXCEPT not supported; use EXCLUDE instead") + polars_bail!(SQLSyntax: "EXCEPT not supported; use EXCLUDE instead") } Ok(match &options.opt_exclude { Some(ExcludeSelectItem::Single(ident)) => { diff --git a/crates/polars-sql/src/functions.rs b/crates/polars-sql/src/functions.rs index 037e9ef9fd21..60ed81fe6b0b 100644 --- a/crates/polars-sql/src/functions.rs +++ b/crates/polars-sql/src/functions.rs @@ -779,7 +779,7 @@ impl PolarsSQLFunctions { if ctx.function_registry.contains(other) { Self::Udf(other.to_string()) } else { - polars_bail!(InvalidOperation: "unsupported SQL function: {}", other); + polars_bail!(SQLInterface: "unsupported function: {}", other); } }, }) @@ -816,13 +816,13 @@ impl SQLFunctionVisitor<'_> { Ok(e.round(match decimals { Expr::Literal(LiteralValue::Int(n)) => { if n >= 0 { n as u32 } else { - polars_bail!(InvalidOperation: "Round does not (yet) support negative 'decimals': {}", function.args[1]) + polars_bail!(SQLInterface: "ROUND does not (yet) support negative 'n_decimals' ({})", function.args[1]) } }, - _ => polars_bail!(InvalidOperation: "invalid 'decimals' for Round: {}", function.args[1]), + _ => polars_bail!(SQLSyntax: "invalid 'n_decimals' for ROUND ({})", function.args[1]), })) }), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for Round: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for ROUND; expected 1 or 2, found {}", function.args.len()), }, Sign => self.visit_unary(Expr::sign), Sqrt => self.visit_unary(Expr::sqrt), @@ -858,15 +858,18 @@ impl SQLFunctionVisitor<'_> { 3 => self.try_visit_ternary(|cond: Expr, expr1: Expr, expr2: Expr| { Ok(when(cond).then(expr1).otherwise(expr2)) }), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for If: {}", function.args.len() + _ => polars_bail!(SQLSyntax: "invalid number of arguments for IF: {}", function.args.len() ), }, IfNull => match function.args.len() { 2 => self.visit_variadic(coalesce), - _ => polars_bail!(InvalidOperation:"Invalid number of arguments for IfNull: {}", function.args.len()) + _ => polars_bail!(SQLSyntax:"Invalid number of arguments for IFNULL: {}", function.args.len()) }, Least => self.visit_variadic(|exprs: &[Expr]| min_horizontal(exprs).unwrap()), - NullIf => self.visit_binary(|l: Expr, r: Expr| when(l.clone().eq(r)).then(lit(LiteralValue::Null)).otherwise(l)), + NullIf => match function.args.len() { + 2 => self.visit_binary(|l: Expr, r: Expr| when(l.clone().eq(r)).then(lit(LiteralValue::Null)).otherwise(l)), + _ => polars_bail!(SQLSyntax:"Invalid number of arguments for NULLIF: {}", function.args.len()) + }, // ---- // Date functions @@ -874,13 +877,13 @@ impl SQLFunctionVisitor<'_> { Date => match function.args.len() { 1 => self.visit_unary(|e| e.str().to_date(StrptimeOptions::default())), 2 => self.visit_binary(|e, fmt| e.str().to_date(fmt)), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for Date: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for DATE: {}", function.args.len()), }, DatePart => self.try_visit_binary(|e, part| { match part { Expr::Literal(LiteralValue::String(p)) => parse_date_part(e, &p), _ => { - polars_bail!(InvalidOperation: "invalid 'part' for DatePart: {}", function.args[1]); + polars_bail!(SQLSyntax: "invalid 'part' for DATE_PART: {}", function.args[1]); } } }), @@ -890,17 +893,17 @@ impl SQLFunctionVisitor<'_> { // ---- BitLength => self.visit_unary(|e| e.str().len_bytes() * lit(8)), Concat => if function.args.is_empty() { - polars_bail!(InvalidOperation: "invalid number of arguments for Concat: 0"); + polars_bail!(SQLSyntax: "invalid number of arguments for CONCAT: 0"); } else { self.visit_variadic(|exprs: &[Expr]| concat_str(exprs, "", true)) }, ConcatWS => if function.args.len() < 2 { - polars_bail!(InvalidOperation: "invalid number of arguments for ConcatWS: {}", function.args.len()); + polars_bail!(SQLSyntax: "invalid number of arguments for CONCAT_WS: {}", function.args.len()); } else { self.try_visit_variadic(|exprs: &[Expr]| { match &exprs[0] { Expr::Literal(LiteralValue::String(s)) => Ok(concat_str(&exprs[1..], s, true)), - _ => polars_bail!(InvalidOperation: "ConcatWS 'separator' must be a literal string; found {:?}", exprs[0]), + _ => polars_bail!(SQLSyntax: "CONCAT_WS 'separator' must be a literal string; found {:?}", exprs[0]), } }) }, @@ -915,7 +918,7 @@ impl SQLFunctionVisitor<'_> { let len = if n > 0 { lit(n) } else { (e.clone().str().len_chars() + lit(n)).clip_min(lit(0)) }; e.str().slice(lit(0), len) }, - Expr::Literal(_) => polars_bail!(InvalidOperation: "invalid 'n_chars' for Left: {}", function.args[1]), + Expr::Literal(_) => polars_bail!(SQLSyntax: "invalid 'n_chars' for LEFT: {}", function.args[1]), _ => { when(length.clone().gt_eq(lit(0))) .then(e.clone().str().slice(lit(0), length.clone().abs())) @@ -928,7 +931,7 @@ impl SQLFunctionVisitor<'_> { LTrim => match function.args.len() { 1 => self.visit_unary(|e| e.str().strip_chars_start(lit(Null))), 2 => self.visit_binary(|e, s| e.str().strip_chars_start(s)), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for LTrim: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for LTRIM: {}", function.args.len()), }, OctetLength => self.visit_unary(|e| e.str().len_bytes()), StrPos => { @@ -942,23 +945,23 @@ impl SQLFunctionVisitor<'_> { match (pat, flags) { (Expr::Literal(LiteralValue::String(s)), Expr::Literal(LiteralValue::String(f))) => { if f.is_empty() { - polars_bail!(InvalidOperation: "invalid/empty 'flags' for RegexpLike: {}", function.args[2]); + polars_bail!(SQLSyntax: "invalid/empty 'flags' for REGEXP_LIKE: {}", function.args[2]); }; lit(format!("(?{}){}", f, s)) }, _ => { - polars_bail!(InvalidOperation: "invalid arguments for RegexpLike: {}, {}", function.args[1], function.args[2]); + polars_bail!(SQLSyntax: "invalid arguments for REGEXP_LIKE: {}, {}", function.args[1], function.args[2]); }, }, true)) }), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for RegexpLike: {}",function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for REGEXP_LIKE: {}",function.args.len()), }, Replace => match function.args.len() { 3 => self.try_visit_ternary(|e, old, new| { Ok(e.str().replace_all(old, new, true)) }), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for Replace: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for REPLACE: {}", function.args.len()), }, Reverse => self.visit_unary(|e| e.str().reverse()), Right => self.try_visit_binary(|e, length| { @@ -970,7 +973,7 @@ impl SQLFunctionVisitor<'_> { let offset = if n < 0 { lit(n.abs()) } else { e.clone().str().len_chars().cast(DataType::Int32) - lit(n) }; e.str().slice(offset, lit(Null)) }, - Expr::Literal(_) => polars_bail!(InvalidOperation: "invalid 'n_chars' for Right: {}", function.args[1]), + Expr::Literal(_) => polars_bail!(SQLSyntax: "invalid 'n_chars' for RIGHT: {}", function.args[1]), _ => { when(length.clone().lt(lit(0))) .then(e.clone().str().slice(length.clone().abs(), lit(Null))) @@ -981,7 +984,7 @@ impl SQLFunctionVisitor<'_> { RTrim => match function.args.len() { 1 => self.visit_unary(|e| e.str().strip_chars_end(lit(Null))), 2 => self.visit_binary(|e, s| e.str().strip_chars_end(s)), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for RTrim: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for RTRIM: {}", function.args.len()), }, StartsWith => self.visit_binary(|e, s| e.str().starts_with(s)), Substring => match function.args.len() { @@ -991,7 +994,7 @@ impl SQLFunctionVisitor<'_> { Expr::Literal(Null) => lit(Null), Expr::Literal(LiteralValue::Int(n)) if n <= 0 => e, Expr::Literal(LiteralValue::Int(n)) => e.str().slice(lit(n - 1), lit(Null)), - Expr::Literal(_) => polars_bail!(InvalidOperation: "invalid 'start' for Substring: {}", function.args[1]), + Expr::Literal(_) => polars_bail!(SQLSyntax: "invalid 'start' for SUBSTR: {}", function.args[1]), _ => start.clone() + lit(1), }) }), @@ -999,15 +1002,15 @@ impl SQLFunctionVisitor<'_> { Ok(match (start.clone(), length.clone()) { (Expr::Literal(Null), _) | (_, Expr::Literal(Null)) => lit(Null), (_, Expr::Literal(LiteralValue::Int(n))) if n < 0 => { - polars_bail!(InvalidOperation: "Substring does not support negative length: {}", function.args[2]) + polars_bail!(SQLSyntax: "SUBSTR does not support negative length: {}", function.args[2]) }, (Expr::Literal(LiteralValue::Int(n)), _) if n > 0 => e.str().slice(lit(n - 1), length.clone()), (Expr::Literal(LiteralValue::Int(n)), _) => { e.str().slice(lit(0), (length.clone() + lit(n - 1)).clip_min(lit(0))) }, - (Expr::Literal(_), _) => polars_bail!(InvalidOperation: "invalid 'start' for Substring: {}", function.args[1]), + (Expr::Literal(_), _) => polars_bail!(SQLSyntax: "invalid 'start' for SUBSTR: {}", function.args[1]), (_, Expr::Literal(LiteralValue::Float(_))) => { - polars_bail!(InvalidOperation: "invalid 'length' for Substring: {}", function.args[1]) + polars_bail!(SQLSyntax: "invalid 'length' for SUBSTR: {}", function.args[1]) }, _ => { let adjusted_start = start.clone() - lit(1); @@ -1017,7 +1020,7 @@ impl SQLFunctionVisitor<'_> { } }) }), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for Substring: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for SUBSTR: {}", function.args.len()), } Upper => self.visit_unary(|e| e.str().to_uppercase()), @@ -1058,10 +1061,10 @@ impl SQLFunctionVisitor<'_> { e.list().eval(col("").fill_null(lit(v)), false).list().join(sep, false) }) }, - _ => polars_bail!(InvalidOperation: "invalid null value for ArrayToString: {}", function.args[2]), + _ => polars_bail!(SQLSyntax: "invalid null value for ARRAY_TO_STRING: {}", function.args[2]), } }), - _ => polars_bail!(InvalidOperation: "invalid number of arguments for ArrayToString: {}", function.args.len()), + _ => polars_bail!(SQLSyntax: "invalid number of arguments for ARRAY_TO_STRING: {}", function.args.len()), } ArrayUnique => self.visit_unary(|e| e.list().unique()), Explode => self.visit_unary(|e| e.explode()), @@ -1076,7 +1079,7 @@ impl SQLFunctionVisitor<'_> { if let FunctionArgExpr::Expr(e) = arg { parse_sql_expr(e, self.ctx, None) } else { - polars_bail!(ComputeError: "Only expressions are supported in UDFs") + polars_bail!(SQLInterface: "only expressions are supported in UDFs") } }) .collect::>>()?; @@ -1084,7 +1087,7 @@ impl SQLFunctionVisitor<'_> { self.ctx .function_registry .get_udf(func_name)? - .ok_or_else(|| polars_err!(ComputeError: "UDF {} not found", func_name))? + .ok_or_else(|| polars_err!(SQLInterface: "UDF {} not found", func_name))? .call(args) } @@ -1108,7 +1111,7 @@ impl SQLFunctionVisitor<'_> { self.apply_cumulative_window(f, cumulative_f, spec) }, Some(WindowType::NamedWindow(named_window)) => polars_bail!( - InvalidOperation: "Named windows are not supported yet. Got {:?}", + SQLInterface: "Named windows are not supported yet; found {:?}", named_window ), _ => self.visit_unary(f), @@ -1283,7 +1286,7 @@ impl SQLFunctionVisitor<'_> { } }, Some(WindowType::NamedWindow(named_window)) => polars_bail!( - InvalidOperation: "Named windows are not supported yet. Got: {:?}", + SQLInterface: "Named windows are not supported yet; found: {:?}", named_window ), None => expr, @@ -1292,8 +1295,8 @@ impl SQLFunctionVisitor<'_> { fn not_supported_error(&self) -> PolarsResult { polars_bail!( - InvalidOperation: - "No function matches the given name and arguments: `{}`", + SQLInterface: + "no function matches the given name and arguments: `{}`", self.func.to_string() ); } @@ -1325,10 +1328,10 @@ impl FromSQLExpr for f64 { SQLExpr::Value(v) => match v { SQLValue::Number(s, _) => s .parse() - .map_err(|_| polars_err!(ComputeError: "can't parse literal {:?}", s)), - _ => polars_bail!(ComputeError: "can't parse literal {:?}", v), + .map_err(|_| polars_err!(SQLInterface: "can't parse literal {:?}", s)), + _ => polars_bail!(SQLInterface: "can't parse literal {:?}", v), }, - _ => polars_bail!(ComputeError: "can't parse literal {:?}", expr), + _ => polars_bail!(SQLInterface: "can't parse literal {:?}", expr), } } } @@ -1341,9 +1344,9 @@ impl FromSQLExpr for bool { match expr { SQLExpr::Value(v) => match v { SQLValue::Boolean(v) => Ok(*v), - _ => polars_bail!(ComputeError: "can't parse boolean {:?}", v), + _ => polars_bail!(SQLInterface: "can't parse boolean {:?}", v), }, - _ => polars_bail!(ComputeError: "can't parse boolean {:?}", expr), + _ => polars_bail!(SQLInterface: "can't parse boolean {:?}", expr), } } } @@ -1356,9 +1359,9 @@ impl FromSQLExpr for String { match expr { SQLExpr::Value(v) => match v { SQLValue::SingleQuotedString(s) => Ok(s.clone()), - _ => polars_bail!(ComputeError: "can't parse literal {:?}", v), + _ => polars_bail!(SQLInterface: "can't parse literal {:?}", v), }, - _ => polars_bail!(ComputeError: "can't parse literal {:?}", expr), + _ => polars_bail!(SQLInterface: "can't parse literal {:?}", expr), } } } @@ -1374,9 +1377,9 @@ impl FromSQLExpr for StrptimeOptions { format: Some(s.clone()), ..StrptimeOptions::default() }), - _ => polars_bail!(ComputeError: "can't parse literal {:?}", v), + _ => polars_bail!(SQLInterface: "can't parse literal {:?}", v), }, - _ => polars_bail!(ComputeError: "can't parse literal {:?}", expr), + _ => polars_bail!(SQLInterface: "can't parse literal {:?}", expr), } } } diff --git a/crates/polars-sql/src/sql_expr.rs b/crates/polars-sql/src/sql_expr.rs index 321f55a3c548..9c78dd1368d1 100644 --- a/crates/polars-sql/src/sql_expr.rs +++ b/crates/polars-sql/src/sql_expr.rs @@ -36,7 +36,7 @@ fn timeunit_from_precision(prec: &Option) -> PolarsResult { Some(n) if (4u64..=6u64).contains(n) => TimeUnit::Microseconds, Some(n) if (7u64..=9u64).contains(n) => TimeUnit::Nanoseconds, Some(n) => { - polars_bail!(ComputeError: "invalid temporal type precision; expected 1-9, found {}", n) + polars_bail!(SQLSyntax: "invalid temporal type precision; expected 1-9, found {}", n) }, }) } @@ -94,7 +94,7 @@ pub(crate) fn map_sql_polars_datatype(data_type: &SQLDataType) -> PolarsResult DataType::Float32, Some(n) if (25u64..=53u64).contains(n) => DataType::Float64, Some(n) => { - polars_bail!(ComputeError: "unsupported `float` size; expected a value between 1 and 53, found {}", n) + polars_bail!(SQLSyntax: "unsupported `float` size; expected a value between 1 and 53, found {}", n) }, None => DataType::Float64, }, @@ -122,14 +122,14 @@ pub(crate) fn map_sql_polars_datatype(data_type: &SQLDataType) -> PolarsResult match tz { TimezoneInfo::None => DataType::Time, _ => { - polars_bail!(ComputeError: "`time` with timezone is not supported; found tz={}", tz) + polars_bail!(SQLInterface: "`time` with timezone is not supported; found tz={}", tz) }, }, SQLDataType::Datetime(prec) => DataType::Datetime(timeunit_from_precision(prec)?, None), SQLDataType::Timestamp(prec, tz) => match tz { TimezoneInfo::None => DataType::Datetime(timeunit_from_precision(prec)?, None), _ => { - polars_bail!(ComputeError: "`timestamp` with timezone is not (yet) supported; found tz={}", tz) + polars_bail!(SQLInterface: "`timestamp` with timezone is not (yet) supported; found tz={}", tz) }, }, @@ -161,12 +161,16 @@ pub(crate) fn map_sql_polars_datatype(data_type: &SQLDataType) -> PolarsResult DataType::Int8, _ => { - polars_bail!(ComputeError: "SQL datatype {:?} is not currently supported", value) + polars_bail!(SQLInterface: "datatype {:?} is not currently supported", value) }, }, - _ => polars_bail!(ComputeError: "SQL datatype {:?} is not currently supported", idents), + _ => { + polars_bail!(SQLInterface: "datatype {:?} is not currently supported", idents) + }, + }, + _ => { + polars_bail!(SQLInterface: "datatype {:?} is not currently supported", data_type) }, - _ => polars_bail!(ComputeError: "SQL datatype {:?} is not currently supported", data_type), }) } @@ -193,15 +197,15 @@ impl SQLExprVisitor<'_> { SQLExpr::Value(v) => self.visit_any_value(v, None), SQLExpr::UnaryOp { op, expr } => match expr.as_ref() { SQLExpr::Value(v) => self.visit_any_value(v, Some(op)), - _ => Err(polars_err!(ComputeError: "SQL expression {:?} is not yet supported", e)), + _ => Err(polars_err!(SQLInterface: "expression {:?} is not yet supported", e)), }, SQLExpr::Array(_) => { // TODO: nested arrays (handle FnMut issues) // let srs = self.array_expr_to_series(&[e.clone()])?; // Ok(AnyValue::List(srs)) - Err(polars_err!(ComputeError: "SQL interface does not yet support nested array literals:\n{:?}", e)) + Err(polars_err!(SQLInterface: "nested array literals are not yet supported:\n{:?}", e)) }, - _ => Err(polars_err!(ComputeError: "SQL expression {:?} is not yet supported", e)), + _ => Err(polars_err!(SQLInterface: "expression {:?} is not yet supported", e)), }) .collect::>>()?; @@ -302,7 +306,7 @@ impl SQLExprVisitor<'_> { .contains(self.visit_expr(pattern)?, true); Ok(if *negated { matches.not() } else { matches }) }, - SQLExpr::Subquery(_) => polars_bail!(InvalidOperation: "Unexpected SQL Subquery"), + SQLExpr::Subquery(_) => polars_bail!(SQLInterface: "unexpected subquery"), SQLExpr::Trim { expr, trim_where, @@ -318,7 +322,7 @@ impl SQLExprVisitor<'_> { SQLExpr::Value(value) => self.visit_literal(value), e @ SQLExpr::Case { .. } => self.visit_case_when_then(e), other => { - polars_bail!(InvalidOperation: "SQL expression {:?} is not yet supported", other) + polars_bail!(SQLInterface: "expression {:?} is not yet supported", other) }, } } @@ -329,7 +333,7 @@ impl SQLExprVisitor<'_> { restriction: SubqueryRestriction, ) -> PolarsResult { if subquery.with.is_some() { - polars_bail!(InvalidOperation: "SQL subquery cannot be given CTEs"); + polars_bail!(SQLSyntax: "SQL subquery cannot be a CTE `with` clause"); } let mut lf = self.ctx.execute_query_no_ctes(subquery)?; @@ -337,7 +341,7 @@ impl SQLExprVisitor<'_> { if restriction == SubqueryRestriction::SingleColumn { if schema.len() != 1 { - polars_bail!(InvalidOperation: "SQL subquery will return more than one column"); + polars_bail!(SQLSyntax: "SQL subquery returns more than one column"); } let rand_string: String = thread_rng() .sample_iter(&Alphanumeric) @@ -355,8 +359,7 @@ impl SQLExprVisitor<'_> { )); } }; - - polars_bail!(InvalidOperation: "SQL subquery type not supported"); + polars_bail!(SQLInterface: "subquery type not supported"); } /// Visit a single SQL identifier. @@ -377,7 +380,7 @@ impl SQLExprVisitor<'_> { .get_table_from_current_scope(&tbl_name.value) .ok_or_else(|| { polars_err!( - ComputeError: "no table or alias named '{}' found", + SQLInterface: "no table or alias named '{}' found", tbl_name ) })?; @@ -393,14 +396,14 @@ impl SQLExprVisitor<'_> { }) } else { polars_bail!( - ColumnNotFound: "no column named '{}' found in table '{}'", + SQLInterface: "no column named '{}' found in table '{}'", column_name, tbl_name ) } }, _ => polars_bail!( - ComputeError: "invalid identifier {:?}", + SQLInterface: "invalid identifier {:?}", idents ), } @@ -415,12 +418,12 @@ impl SQLExprVisitor<'_> { case_insensitive: bool, ) -> PolarsResult { if escape_char.is_some() { - polars_bail!(InvalidOperation: "ESCAPE char for LIKE/ILIKE is not yet supported; found '{}'", escape_char.unwrap()); + polars_bail!(SQLInterface: "ESCAPE char for LIKE/ILIKE is not yet supported; found '{}'", escape_char.unwrap()); } let pat = match self.visit_expr(pattern) { Ok(Expr::Literal(LiteralValue::String(s))) => s, _ => { - polars_bail!(InvalidOperation: "LIKE/ILIKE pattern must be a string literal; found {}", pattern) + polars_bail!(SQLSyntax: "LIKE/ILIKE pattern must be a string literal; found {}", pattern) }, }; if pat.is_empty() || (!case_insensitive && pat.chars().all(|c| !matches!(c, '%' | '_'))) { @@ -544,25 +547,29 @@ impl SQLExprVisitor<'_> { // ---- SQLBinaryOperator::PGRegexMatch => match right { Expr::Literal(LiteralValue::String(_)) => left.str().contains(right, true), - _ => polars_bail!(ComputeError: "invalid pattern for '~' operator: {:?}", right), + _ => polars_bail!(SQLSyntax: "invalid pattern for '~' operator: {:?}", right), }, SQLBinaryOperator::PGRegexNotMatch => match right { Expr::Literal(LiteralValue::String(_)) => left.str().contains(right, true).not(), - _ => polars_bail!(ComputeError: "invalid pattern for '!~' operator: {:?}", right), + _ => polars_bail!(SQLSyntax: "invalid pattern for '!~' operator: {:?}", right), }, SQLBinaryOperator::PGRegexIMatch => match right { Expr::Literal(LiteralValue::String(pat)) => { left.str().contains(lit(format!("(?i){}", pat)), true) }, - _ => polars_bail!(ComputeError: "invalid pattern for '~*' operator: {:?}", right), + _ => polars_bail!(SQLSyntax: "invalid pattern for '~*' operator: {:?}", right), }, SQLBinaryOperator::PGRegexNotIMatch => match right { Expr::Literal(LiteralValue::String(pat)) => { left.str().contains(lit(format!("(?i){}", pat)), true).not() }, - _ => polars_bail!(ComputeError: "invalid pattern for '!~*' operator: {:?}", right), + _ => { + polars_bail!(SQLSyntax: "invalid pattern for '!~*' operator: {:?}", right) + }, + }, + other => { + polars_bail!(SQLInterface: "SQL operator {:?} is not yet supported", other) }, - other => polars_bail!(ComputeError: "SQL operator {:?} is not yet supported", other), }) } @@ -581,7 +588,7 @@ impl SQLExprVisitor<'_> { (UnaryOperator::Plus, _) => lit(0) + expr, (UnaryOperator::Minus, _) => lit(0) - expr, (UnaryOperator::Not, _) => expr.not(), - other => polars_bail!(InvalidOperation: "unary operator {:?} is not supported", other), + other => polars_bail!(SQLInterface: "unary operator {:?} is not supported", other), }) } @@ -615,9 +622,9 @@ impl SQLExprVisitor<'_> { BinaryOperator::Lt => Ok(left.lt(right.min())), BinaryOperator::GtEq => Ok(left.gt_eq(right.max())), BinaryOperator::LtEq => Ok(left.lt_eq(right.min())), - BinaryOperator::Eq => polars_bail!(ComputeError: "ALL cannot be used with ="), - BinaryOperator::NotEq => polars_bail!(ComputeError: "ALL cannot be used with !="), - _ => polars_bail!(ComputeError: "invalid comparison operator"), + BinaryOperator::Eq => polars_bail!(SQLSyntax: "ALL cannot be used with ="), + BinaryOperator::NotEq => polars_bail!(SQLSyntax: "ALL cannot be used with !="), + _ => polars_bail!(SQLInterface: "invalid comparison operator"), } } @@ -640,7 +647,7 @@ impl SQLExprVisitor<'_> { BinaryOperator::LtEq => Ok(left.lt_eq(right.max())), BinaryOperator::Eq => Ok(left.is_in(right)), BinaryOperator::NotEq => Ok(left.is_in(right).not()), - _ => polars_bail!(ComputeError: "invalid comparison operator"), + _ => polars_bail!(SQLInterface: "invalid comparison operator"), } } @@ -687,7 +694,9 @@ impl SQLExprVisitor<'_> { strict: bool, ) -> PolarsResult { if format.is_some() { - return Err(polars_err!(ComputeError: "unsupported use of FORMAT in CAST expression")); + return Err( + polars_err!(SQLInterface: "use of FORMAT is not currently supported in CAST"), + ); } let expr = self.visit_expr(expr)?; @@ -715,7 +724,7 @@ impl SQLExprVisitor<'_> { #[cfg(feature = "binary_encoding")] SQLValue::HexStringLiteral(x) => { if x.len() % 2 != 0 { - polars_bail!(ComputeError: "hex string literal must have an even number of digits; found '{}'", x) + polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x) }; lit(hex::decode(x.clone()).unwrap()) }, @@ -727,7 +736,7 @@ impl SQLExprVisitor<'_> { } else { s.parse::().map(lit).map_err(|_| ()) } - .map_err(|_| polars_err!(ComputeError: "cannot parse literal: {:?}", s))? + .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {:?}", s))? }, SQLValue::SingleQuotedByteStringLiteral(b) => { // note: for PostgreSQL this represents a BIT string literal (eg: b'10101') not a BYTE string @@ -736,7 +745,7 @@ impl SQLExprVisitor<'_> { bitstring_to_bytes_literal(b)? }, SQLValue::SingleQuotedString(s) => lit(s.clone()), - other => polars_bail!(ComputeError: "SQL value {:?} is not yet supported", other), + other => polars_bail!(SQLInterface: "SQL value {:?} is not supported", other), }) } @@ -755,7 +764,7 @@ impl SQLExprVisitor<'_> { // no op should be taken as plus. Some(UnaryOperator::Plus) | None => false, Some(op) => { - polars_bail!(ComputeError: "Unary op {:?} not supported for numeric SQL value", op) + polars_bail!(SQLInterface: "unary op {:?} not supported for numeric SQL value", op) }, }; // Check for existence of decimal separator dot @@ -768,12 +777,12 @@ impl SQLExprVisitor<'_> { .map(|n: i64| AnyValue::Int64(if negate { -n } else { n })) .map_err(|_| ()) } - .map_err(|_| polars_err!(ComputeError: "cannot parse literal: {s:?}"))? + .map_err(|_| polars_err!(SQLInterface: "cannot parse literal: {s:?}"))? }, #[cfg(feature = "binary_encoding")] SQLValue::HexStringLiteral(x) => { if x.len() % 2 != 0 { - polars_bail!(ComputeError: "hex string literal must have an even number of digits; found '{}'", x) + polars_bail!(SQLSyntax: "hex string literal must have an even number of digits; found '{}'", x) }; AnyValue::BinaryOwned(hex::decode(x.clone()).unwrap()) }, @@ -782,13 +791,15 @@ impl SQLExprVisitor<'_> { let bytes_literal = bitstring_to_bytes_literal(b)?; match bytes_literal { Expr::Literal(LiteralValue::Binary(v)) => AnyValue::BinaryOwned(v.to_vec()), - _ => polars_bail!(ComputeError: "failed to parse bitstring literal: {:?}", b), + _ => { + polars_bail!(SQLInterface: "failed to parse bitstring literal: {:?}", b) + }, } }, SQLValue::SingleQuotedString(s) | SQLValue::DoubleQuotedString(s) => { AnyValue::StringOwned(s.into()) }, - other => polars_bail!(ComputeError: "SQL value {:?} is not yet supported", other), + other => polars_bail!(SQLInterface: "SQL value {:?} is not yet supported", other), }) } @@ -825,7 +836,7 @@ impl SQLExprVisitor<'_> { ) -> PolarsResult { if trim_characters.is_some() { // TODO: allow compact snowflake/bigquery syntax? - return Err(polars_err!(ComputeError: "unsupported TRIM syntax")); + return Err(polars_err!(SQLSyntax: "unsupported TRIM syntax (custom chars)")); }; let expr = self.visit_expr(expr)?; let trim_what = trim_what.as_ref().map(|e| self.visit_expr(e)).transpose()?; @@ -857,7 +868,7 @@ impl SQLExprVisitor<'_> { if let Some(limit) = &expr.limit { let limit = match self.visit_expr(limit)? { Expr::Literal(LiteralValue::Int(n)) if n >= 0 => n as usize, - _ => polars_bail!(ComputeError: "limit in ARRAY_AGG must be a positive integer"), + _ => polars_bail!(SQLSyntax: "limit in ARRAY_AGG must be a positive integer"), }; base = base.head(Some(limit)); } @@ -866,7 +877,7 @@ impl SQLExprVisitor<'_> { } polars_ensure!( !expr.within_group, - ComputeError: "ARRAY_AGG WITHIN GROUP is not yet supported" + SQLInterface: "ARRAY_AGG WITHIN GROUP is not yet supported" ); Ok(base.implode()) } @@ -912,21 +923,21 @@ impl SQLExprVisitor<'_> { { polars_ensure!( conditions.len() == results.len(), - ComputeError: "WHEN and THEN expressions must have the same length" + SQLSyntax: "WHEN and THEN expressions must have the same length" ); polars_ensure!( !conditions.is_empty(), - ComputeError: "WHEN and THEN expressions must have at least one element" + SQLSyntax: "WHEN and THEN expressions must have at least one element" ); let mut when_thens = conditions.iter().zip(results.iter()); let first = when_thens.next(); if first.is_none() { - polars_bail!(ComputeError: "WHEN and THEN expressions must have at least one element"); + polars_bail!(SQLSyntax: "WHEN and THEN expressions must have at least one element"); } let else_res = match else_result { Some(else_res) => self.visit_expr(else_res)?, - None => polars_bail!(ComputeError: "ELSE expression is required"), + None => polars_bail!(SQLSyntax: "ELSE expression is required"), }; if let Some(operand_expr) = operand { let first_operand_expr = self.visit_expr(operand_expr)?; @@ -979,7 +990,7 @@ impl SQLExprVisitor<'_> { } fn err(&self, expr: &Expr) -> PolarsResult { - polars_bail!(ComputeError: "SQL expression {:?} is not yet supported", expr); + polars_bail!(SQLInterface: "expression {:?} is not yet supported", expr); } } @@ -1000,7 +1011,7 @@ fn collect_compound_identifiers( Ok((vec![col(col_a)], vec![col(col_b)])) } } else { - polars_bail!(InvalidOperation: "collect_compound_identifiers: Expected left.len() == 2 && right.len() == 2, but found left.len() == {:?}, right.len() == {:?}", left.len(), right.len()); + polars_bail!(SQLInterface: "collect_compound_identifiers: Expected left.len() == 2 && right.len() == 2, but found left.len() == {:?}, right.len() == {:?}", left.len(), right.len()); } } @@ -1017,7 +1028,7 @@ fn process_join_on( { collect_compound_identifiers(left, right, left_name, right_name) } else { - polars_bail!(InvalidOperation: "SQL join clauses support '=' constraints on identifiers; found lhs={:?}, rhs={:?}", left, right); + polars_bail!(SQLInterface: "JOIN clauses support '=' constraints on identifiers; found lhs={:?}, rhs={:?}", left, right); } }, BinaryOperator::And => { @@ -1028,13 +1039,13 @@ fn process_join_on( Ok((left_i, right_i)) }, _ => { - polars_bail!(InvalidOperation: "SQL join clauses support '=' constraints combined with 'AND'; found op = '{:?}'", op); + polars_bail!(SQLInterface: "JOIN clauses support '=' constraints combined with 'AND'; found op = '{:?}'", op); }, } } else if let SQLExpr::Nested(expr) = expression { process_join_on(expr, left_name, right_name) } else { - polars_bail!(InvalidOperation: "SQL join clauses support '=' constraints combined with 'AND'; found expression = {:?}", expression); + polars_bail!(SQLInterface: "JOIN clauses support '=' constraints combined with 'AND'; found expression = {:?}", expression); } } @@ -1052,9 +1063,8 @@ pub(super) fn process_join_constraint( return Ok((left_on, right_on)); } if op != &BinaryOperator::Eq { - polars_bail!(InvalidOperation: - "SQL interface (currently) only supports basic equi-join \ - constraints; found '{:?}' op in\n{:?}", op, constraint) + polars_bail!(SQLInterface: + "only equi-join constraints are supported; found '{:?}' op in\n{:?}", op, constraint) } match (left.as_ref(), right.as_ref()) { (SQLExpr::CompoundIdentifier(left), SQLExpr::CompoundIdentifier(right)) => { @@ -1072,7 +1082,7 @@ pub(super) fn process_join_constraint( return Ok((using.clone(), using.clone())); } } - polars_bail!(InvalidOperation: "unsupported SQL join constraint:\n{:?}", constraint); + polars_bail!(SQLInterface: "unsupported SQL join constraint:\n{:?}", constraint); } /// parse a SQL expression to a polars expression @@ -1110,7 +1120,7 @@ pub fn sql_expr>(s: S) -> PolarsResult { expr.alias(&alias.value) }, SelectItem::UnnamedExpr(expr) => parse_sql_expr(expr, &mut ctx, None)?, - _ => polars_bail!(InvalidOperation: "Unable to parse '{}' as Expr", s.as_ref()), + _ => polars_bail!(SQLInterface: "unable to parse '{}' as Expr", s.as_ref()), }) } @@ -1181,7 +1191,7 @@ fn parse_extract(expr: Expr, field: &DateTimeField) -> PolarsResult { + expr.dt().nanosecond().div(typed_lit(1_000_000_000f64)) }, _ => { - polars_bail!(ComputeError: "EXTRACT function does not support {}", field) + polars_bail!(SQLInterface: "EXTRACT function does not support {}", field) }, }) } @@ -1214,7 +1224,7 @@ pub(crate) fn parse_date_part(expr: Expr, part: &str) -> PolarsResult { "time" => &DateTimeField::Time, "epoch" => &DateTimeField::Epoch, _ => { - polars_bail!(ComputeError: "DATE_PART function does not support '{}'", part) + polars_bail!(SQLInterface: "DATE_PART function does not support '{}'", part) }, }, ) @@ -1223,7 +1233,10 @@ pub(crate) fn parse_date_part(expr: Expr, part: &str) -> PolarsResult { fn bitstring_to_bytes_literal(b: &String) -> PolarsResult { let n_bits = b.len(); if !b.chars().all(|c| c == '0' || c == '1') || n_bits > 64 { - polars_bail!(ComputeError: "bit string literal should contain only 0s and 1s and have length <= 64; found '{}' with length {}", b, n_bits) + polars_bail!( + SQLSyntax: + "bit string literal should contain only 0s and 1s and have length <= 64; found '{}' with length {}", b, n_bits + ) } let s = b.as_str(); Ok(lit(match n_bits { diff --git a/crates/polars-sql/src/table_functions.rs b/crates/polars-sql/src/table_functions.rs index 94b03ce152ca..2d32094a04df 100644 --- a/crates/polars-sql/src/table_functions.rs +++ b/crates/polars-sql/src/table_functions.rs @@ -56,7 +56,7 @@ impl FromStr for PolarsTableFunctions { "read_ipc" => PolarsTableFunctions::ReadIpc, #[cfg(feature = "json")] "read_json" => PolarsTableFunctions::ReadJson, - _ => polars_bail!(ComputeError: "'{}' is not a supported table function", s), + _ => polars_bail!(SQLInterface: "'{}' is not a supported table function", s), }) } } @@ -79,7 +79,7 @@ impl PolarsTableFunctions { #[cfg(feature = "csv")] fn read_csv(&self, args: &[FunctionArg]) -> PolarsResult<(String, LazyFrame)> { - polars_ensure!(!args.is_empty(), ComputeError: "read_csv expected a path"); + polars_ensure!(!args.is_empty(), SQLSyntax: "`read_csv` expected a path"); use polars_lazy::frame::LazyFileListReader; let path = self.get_file_path_from_arg(&args[0])?; @@ -89,7 +89,7 @@ impl PolarsTableFunctions { #[cfg(feature = "parquet")] fn read_parquet(&self, args: &[FunctionArg]) -> PolarsResult<(String, LazyFrame)> { - polars_ensure!(!args.is_empty(), ComputeError: "read_parquet expected a path"); + polars_ensure!(!args.is_empty(), SQLSyntax: "`read_parquet` expected a path"); let path = self.get_file_path_from_arg(&args[0])?; let lf = LazyFrame::scan_parquet(&path, Default::default())?; @@ -98,7 +98,7 @@ impl PolarsTableFunctions { #[cfg(feature = "ipc")] fn read_ipc(&self, args: &[FunctionArg]) -> PolarsResult<(String, LazyFrame)> { - polars_ensure!(!args.is_empty(), ComputeError: "read_ipc expected a path"); + polars_ensure!(!args.is_empty(), SQLSyntax: "`read_ipc` expected a path"); let path = self.get_file_path_from_arg(&args[0])?; let lf = LazyFrame::scan_ipc(&path, Default::default())?; @@ -106,7 +106,7 @@ impl PolarsTableFunctions { } #[cfg(feature = "json")] fn read_ndjson(&self, args: &[FunctionArg]) -> PolarsResult<(String, LazyFrame)> { - polars_ensure!(!args.is_empty(), ComputeError: "read_json expected a path"); + polars_ensure!(!args.is_empty(), SQLSyntax: "`read_json` expected a path"); use polars_lazy::frame::LazyFileListReader; use polars_lazy::prelude::LazyJsonLineReader; @@ -124,8 +124,8 @@ impl PolarsTableFunctions { SQLValue::SingleQuotedString(s), ))) => Ok(s.to_string()), _ => polars_bail!( - ComputeError: - "only a single quoted string is accepted as the first parameter; received: {}", arg, + SQLSyntax: + "only a single quoted string is accepted for the parameter; found: {}", arg, ), } } diff --git a/py-polars/polars/__init__.py b/py-polars/polars/__init__.py index 007da7153c3b..d7c996c7dfe1 100644 --- a/py-polars/polars/__init__.py +++ b/py-polars/polars/__init__.py @@ -89,6 +89,8 @@ SchemaError, SchemaFieldNotFoundError, ShapeError, + SQLInterfaceError, + SQLSyntaxError, StructFieldNotFoundError, UnstableWarning, ) @@ -241,15 +243,17 @@ "OutOfBoundsError", "PolarsError", "PolarsPanicError", + "SQLInterfaceError", + "SQLSyntaxError", "SchemaError", "SchemaFieldNotFoundError", "ShapeError", "StructFieldNotFoundError", # warnings - "PolarsWarning", "CategoricalRemappingWarning", "ChronoFormatWarning", "MapWithoutReturnDtypeWarning", + "PolarsWarning", "UnstableWarning", # core classes "DataFrame", @@ -271,20 +275,20 @@ "Field", "Float32", "Float64", + "Int8", "Int16", "Int32", "Int64", - "Int8", "List", "Null", "Object", "String", "Struct", "Time", + "UInt8", "UInt16", "UInt32", "UInt64", - "UInt8", "Unknown", "Utf8", # polars.datatypes: dtype groups @@ -347,19 +351,19 @@ "zeros", # polars.functions.aggregation "all", - "any", - "cum_sum", - "cumsum", - "max", - "min", - "sum", "all_horizontal", + "any", "any_horizontal", + "cum_sum", "cum_sum_horizontal", + "cumsum", "cumsum_horizontal", + "max", "max_horizontal", "mean_horizontal", + "min", "min_horizontal", + "sum", "sum_horizontal", # polars.functions.lazy "apply", diff --git a/py-polars/polars/exceptions.py b/py-polars/polars/exceptions.py index d62a2134b8fe..ad6093366836 100644 --- a/py-polars/polars/exceptions.py +++ b/py-polars/polars/exceptions.py @@ -14,6 +14,8 @@ SchemaError, SchemaFieldNotFoundError, ShapeError, + SQLInterfaceError, + SQLSyntaxError, StringCacheMismatchError, StructFieldNotFoundError, ) @@ -77,6 +79,12 @@ class SchemaFieldNotFoundError(PolarsError): # type: ignore[no-redef, misc] class ShapeError(PolarsError): # type: ignore[no-redef, misc] """Exception raised when trying to perform operations on data structures with incompatible shapes.""" # noqa: W505 + class SQLInterfaceError(PolarsError): # type: ignore[no-redef, misc] + """Exception raised when an error occurs in the SQL interface.""" + + class SQLSyntaxError(PolarsError): # type: ignore[no-redef, misc] + """Exception raised from the SQL interface when encountering invalid syntax.""" + class StringCacheMismatchError(PolarsError): # type: ignore[no-redef, misc] """Exception raised when string caches come from different sources.""" @@ -155,9 +163,10 @@ class CustomUFuncWarning(PolarsWarning): # type: ignore[misc] __all__ = [ "ArrowError", + "CategoricalRemappingWarning", + "ChronoFormatWarning", "ColumnNotFoundError", "ComputeError", - "ChronoFormatWarning", "DuplicateError", "InvalidOperationError", "MapWithoutReturnDtypeWarning", @@ -165,12 +174,13 @@ class CustomUFuncWarning(PolarsWarning): # type: ignore[misc] "NoDataError", "NoRowsReturnedError", "OutOfBoundsError", - "PolarsInefficientMapWarning", - "CategoricalRemappingWarning", "PolarsError", + "PolarsInefficientMapWarning", "PolarsPanicError", "PolarsWarning", "RowsError", + "SQLInterfaceError", + "SQLSyntaxError", "SchemaError", "SchemaFieldNotFoundError", "ShapeError", diff --git a/py-polars/src/error.rs b/py-polars/src/error.rs index f2fe6065b936..f73ab2798fc7 100644 --- a/py-polars/src/error.rs +++ b/py-polars/src/error.rs @@ -55,6 +55,8 @@ impl std::convert::From for PyErr { }, PolarsError::NoData(err) => NoDataError::new_err(err.to_string()), PolarsError::OutOfBounds(err) => OutOfBoundsError::new_err(err.to_string()), + PolarsError::SQLInterface(name) => SQLInterfaceError::new_err(name.to_string()), + PolarsError::SQLSyntax(name) => SQLSyntaxError::new_err(name.to_string()), PolarsError::SchemaFieldNotFound(name) => { SchemaFieldNotFoundError::new_err(name.to_string()) }, @@ -93,6 +95,8 @@ create_exception!(polars.exceptions, DuplicateError, PolarsBaseError); create_exception!(polars.exceptions, InvalidOperationError, PolarsBaseError); create_exception!(polars.exceptions, NoDataError, PolarsBaseError); create_exception!(polars.exceptions, OutOfBoundsError, PolarsBaseError); +create_exception!(polars.exceptions, SQLInterfaceError, PolarsBaseError); +create_exception!(polars.exceptions, SQLSyntaxError, PolarsBaseError); create_exception!(polars.exceptions, SchemaError, PolarsBaseError); create_exception!(polars.exceptions, SchemaFieldNotFoundError, PolarsBaseError); create_exception!(polars.exceptions, ShapeError, PolarsBaseError); diff --git a/py-polars/src/lib.rs b/py-polars/src/lib.rs index 4cce971059ef..6c9ffb42b6a6 100644 --- a/py-polars/src/lib.rs +++ b/py-polars/src/lib.rs @@ -49,8 +49,8 @@ use crate::dataframe::PyDataFrame; use crate::error::{ CategoricalRemappingWarning, ColumnNotFoundError, ComputeError, DuplicateError, InvalidOperationError, MapWithoutReturnDtypeWarning, NoDataError, OutOfBoundsError, - PolarsBaseError, PolarsBaseWarning, PyPolarsErr, SchemaError, SchemaFieldNotFoundError, - StructFieldNotFoundError, + PolarsBaseError, PolarsBaseWarning, PyPolarsErr, SQLInterfaceError, SQLSyntaxError, + SchemaError, SchemaFieldNotFoundError, StructFieldNotFoundError, }; use crate::expr::PyExpr; use crate::functions::PyStringCacheHolder; @@ -335,6 +335,13 @@ fn polars(py: Python, m: &Bound) -> PyResult<()> { .unwrap(); m.add("PolarsPanicError", py.get_type_bound::()) .unwrap(); + m.add( + "SQLInterfaceError", + py.get_type_bound::(), + ) + .unwrap(); + m.add("SQLSyntaxError", py.get_type_bound::()) + .unwrap(); m.add("SchemaError", py.get_type_bound::()) .unwrap(); m.add( diff --git a/py-polars/tests/unit/sql/test_array.py b/py-polars/tests/unit/sql/test_array.py index 432c64512ffb..75e8463e7b7a 100644 --- a/py-polars/tests/unit/sql/test_array.py +++ b/py-polars/tests/unit/sql/test_array.py @@ -3,7 +3,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import SQLInterfaceError, SQLSyntaxError from polars.testing import assert_frame_equal @@ -88,37 +88,37 @@ def test_unnest_table_function(array_keyword: str) -> None: def test_unnest_table_function_errors() -> None: with pl.SQLContext(df=None, eager=True) as ctx: with pytest.raises( - ComputeError, + SQLSyntaxError, match=r'UNNEST table alias must also declare column names, eg: "frame data" \(a,b,c\)', ): ctx.execute('SELECT * FROM UNNEST([1, 2, 3]) AS "frame data"') with pytest.raises( - ComputeError, + SQLSyntaxError, match="UNNEST table alias requires 1 column name, found 2", ): ctx.execute("SELECT * FROM UNNEST([1, 2, 3]) AS tbl (a, b)") with pytest.raises( - ComputeError, + SQLSyntaxError, match="UNNEST table alias requires 2 column names, found 1", ): ctx.execute("SELECT * FROM UNNEST([1,2,3], [3,4,5]) AS tbl (a)") with pytest.raises( - ComputeError, + SQLSyntaxError, match=r"UNNEST table must have an alias", ): ctx.execute("SELECT * FROM UNNEST([1, 2, 3])") with pytest.raises( - ComputeError, + SQLInterfaceError, match=r"UNNEST tables do not \(yet\) support WITH OFFSET/ORDINALITY", ): ctx.execute("SELECT * FROM UNNEST([1, 2, 3]) tbl (colx) WITH OFFSET") with pytest.raises( - ComputeError, - match="SQL interface does not yet support nested array literals", + SQLInterfaceError, + match="nested array literals are not yet supported", ): pl.sql_expr("[[1,2,3]] AS nested") diff --git a/py-polars/tests/unit/sql/test_cast.py b/py-polars/tests/unit/sql/test_cast.py index 2184e488cdc1..7731dcd817a5 100644 --- a/py-polars/tests/unit/sql/test_cast.py +++ b/py-polars/tests/unit/sql/test_cast.py @@ -6,7 +6,7 @@ import polars as pl import polars.selectors as cs -from polars.exceptions import ComputeError +from polars.exceptions import ComputeError, SQLInterfaceError from polars.testing import assert_frame_equal @@ -141,7 +141,10 @@ def test_cast() -> None: (True, True), ] - with pytest.raises(ComputeError, match="unsupported use of FORMAT in CAST"): + with pytest.raises( + SQLInterfaceError, + match="use of FORMAT is not currently supported in CAST", + ): pl.SQLContext(df=df, eager=True).execute( "SELECT CAST(a AS STRING FORMAT 'HEX') FROM df" ) diff --git a/py-polars/tests/unit/sql/test_conditional.py b/py-polars/tests/unit/sql/test_conditional.py index 572bb3b87ee8..0b95d4ed8cd6 100644 --- a/py-polars/tests/unit/sql/test_conditional.py +++ b/py-polars/tests/unit/sql/test_conditional.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import InvalidOperationError +from polars.exceptions import SQLSyntaxError from polars.testing import assert_frame_equal @@ -58,7 +58,6 @@ def test_control_flow(foods_ipc_path: Path) -> None: """, eager=True, ) - assert res.to_dict(as_series=False) == { "coalsc": [1, 4, 2, 3, 6, 4], "nullif x_y": [1, None, 2, None, None, 4], @@ -68,9 +67,9 @@ def test_control_flow(foods_ipc_path: Path) -> None: "both": [1, None, 2, 3, None, 4], "x_eq_y": ["ne", "ne", "ne", "eq", "ne", "ne"], } + for null_func in ("IFNULL", "NULLIF"): - # both functions expect only 2 arguments - with pytest.raises(InvalidOperationError): + with pytest.raises(SQLSyntaxError): # both functions expect TWO arguments pl.SQLContext(df=nums).execute(f"SELECT {null_func}(x,y,z) FROM df") diff --git a/py-polars/tests/unit/sql/test_functions.py b/py-polars/tests/unit/sql/test_functions.py index 1449daa52482..04055eb01c03 100644 --- a/py-polars/tests/unit/sql/test_functions.py +++ b/py-polars/tests/unit/sql/test_functions.py @@ -5,7 +5,7 @@ import pytest import polars as pl -from polars.exceptions import InvalidOperationError +from polars.exceptions import SQLInterfaceError from polars.testing import assert_frame_equal @@ -32,6 +32,7 @@ def test_sql_expr() -> None: # expect expressions that can't reasonably be parsed as expressions to raise # (for example: those that explicitly reference tables and/or use wildcards) with pytest.raises( - InvalidOperationError, match=r"Unable to parse 'xyz\.\*' as Expr" + SQLInterfaceError, + match=r"unable to parse 'xyz\.\*' as Expr", ): pl.sql_expr("xyz.*") diff --git a/py-polars/tests/unit/sql/test_group_by.py b/py-polars/tests/unit/sql/test_group_by.py index 4e4cff802f69..d07895e793b6 100644 --- a/py-polars/tests/unit/sql/test_group_by.py +++ b/py-polars/tests/unit/sql/test_group_by.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import SQLSyntaxError from polars.testing import assert_frame_equal @@ -222,19 +222,19 @@ def test_group_by_errors() -> None: ) with pytest.raises( - ComputeError, + SQLSyntaxError, match=r"expected a positive integer or valid expression; got -99", ): df.sql("SELECT a, SUM(b) FROM self GROUP BY -99, a") with pytest.raises( - ComputeError, + SQLSyntaxError, match=r"expected a positive integer or valid expression; got '!!!'", ): df.sql("SELECT a, SUM(b) FROM self GROUP BY a, '!!!'") with pytest.raises( - ComputeError, + SQLSyntaxError, match=r"'a' should participate in the GROUP BY clause or an aggregate function", ): df.sql("SELECT a, SUM(b) FROM self GROUP BY b") diff --git a/py-polars/tests/unit/sql/test_joins.py b/py-polars/tests/unit/sql/test_joins.py index 97d375bb62a2..b61324ef7a90 100644 --- a/py-polars/tests/unit/sql/test_joins.py +++ b/py-polars/tests/unit/sql/test_joins.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import InvalidOperationError +from polars.exceptions import SQLInterfaceError from polars.testing import assert_frame_equal @@ -296,8 +296,8 @@ def test_join_misc_16255() -> None: def test_non_equi_joins(constraint: str) -> None: # no support (yet) for non equi-joins in polars joins with pytest.raises( - InvalidOperationError, - match=r"SQL interface \(currently\) only supports basic equi-join constraints", + SQLInterfaceError, + match=r"only equi-join constraints are supported", ), pl.SQLContext({"tbl": pl.DataFrame({"a": [1, 2, 3], "b": [4, 3, 2]})}) as ctx: ctx.execute( f""" diff --git a/py-polars/tests/unit/sql/test_literals.py b/py-polars/tests/unit/sql/test_literals.py index 4e63b0af4f9e..8a5b86f68331 100644 --- a/py-polars/tests/unit/sql/test_literals.py +++ b/py-polars/tests/unit/sql/test_literals.py @@ -3,7 +3,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import SQLInterfaceError, SQLSyntaxError def test_bit_hex_literals() -> None: @@ -50,26 +50,26 @@ def test_bit_hex_filter() -> None: def test_bit_hex_errors() -> None: with pl.SQLContext(test=None) as ctx: with pytest.raises( - ComputeError, + SQLSyntaxError, match="bit string literal should contain only 0s and 1s", ): ctx.execute("SELECT b'007' FROM test", eager=True) with pytest.raises( - ComputeError, + SQLSyntaxError, match="hex string literal must have an even number of digits", ): ctx.execute("SELECT x'00F' FROM test", eager=True) with pytest.raises( - ComputeError, + SQLSyntaxError, match="hex string literal must have an even number of digits", ): pl.sql_expr("colx IN (x'FF',x'123')") with pytest.raises( - ComputeError, - match=r'NationalStringLiteral\("hmmm"\) is not yet supported', + SQLInterfaceError, + match=r'NationalStringLiteral\("hmmm"\) is not supported', ): pl.sql_expr("N'hmmm'") diff --git a/py-polars/tests/unit/sql/test_miscellaneous.py b/py-polars/tests/unit/sql/test_miscellaneous.py index 7cbc19d23d1c..8165e0def383 100644 --- a/py-polars/tests/unit/sql/test_miscellaneous.py +++ b/py-polars/tests/unit/sql/test_miscellaneous.py @@ -5,7 +5,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import SQLInterfaceError from polars.testing import assert_frame_equal @@ -82,7 +82,7 @@ def test_distinct() -> None: # test unregistration ctx.unregister("df") - with pytest.raises(ComputeError, match=".*'df'.*not found"): + with pytest.raises(SQLInterfaceError, match="relation 'df' was not found"): ctx.execute("SELECT * FROM df") diff --git a/py-polars/tests/unit/sql/test_numeric.py b/py-polars/tests/unit/sql/test_numeric.py index 17d51c356629..4cdd5d6b8982 100644 --- a/py-polars/tests/unit/sql/test_numeric.py +++ b/py-polars/tests/unit/sql/test_numeric.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import InvalidOperationError +from polars.exceptions import SQLInterfaceError, SQLSyntaxError from polars.testing import assert_frame_equal, assert_series_equal if TYPE_CHECKING: @@ -106,11 +106,11 @@ def test_round_ndigits_errors() -> None: df = pl.DataFrame({"n": [99.999]}) with pl.SQLContext(df=df, eager=True) as ctx: with pytest.raises( - InvalidOperationError, match="invalid 'decimals' for Round: ??" + SQLSyntaxError, match=r"invalid 'n_decimals' for ROUND \('!!'\)" ): - ctx.execute("SELECT ROUND(n,'??') AS n FROM df") + ctx.execute("SELECT ROUND(n,'!!') AS n FROM df") with pytest.raises( - InvalidOperationError, match="Round .* negative 'decimals': -1" + SQLInterfaceError, match=r"ROUND .* negative 'n_decimals' \(-1\)" ): ctx.execute("SELECT ROUND(n,-1) AS n FROM df") diff --git a/py-polars/tests/unit/sql/test_regex.py b/py-polars/tests/unit/sql/test_regex.py index bbb878012a02..23434a6069f5 100644 --- a/py-polars/tests/unit/sql/test_regex.py +++ b/py-polars/tests/unit/sql/test_regex.py @@ -5,7 +5,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError, InvalidOperationError +from polars.exceptions import SQLSyntaxError @pytest.fixture() @@ -82,11 +82,11 @@ def test_regex_operators_error() -> None: df = pl.LazyFrame({"sval": ["ABC", "abc", "000", "A0C", "a0c"]}) with pl.SQLContext(df=df, eager=True) as ctx: with pytest.raises( - ComputeError, match="invalid pattern for '~' operator: dyn .*12345" + SQLSyntaxError, match="invalid pattern for '~' operator: dyn .*12345" ): ctx.execute("SELECT * FROM df WHERE sval ~ 12345") with pytest.raises( - ComputeError, + SQLSyntaxError, match=r"""invalid pattern for '!~\*' operator: col\("abcde"\)""", ): ctx.execute("SELECT * FROM df WHERE sval !~* abcde") @@ -126,19 +126,19 @@ def test_regexp_like( def test_regexp_like_errors() -> None: with pl.SQLContext(df=pl.DataFrame({"scol": ["xyz"]})) as ctx: with pytest.raises( - InvalidOperationError, - match="invalid/empty 'flags' for RegexpLike", + SQLSyntaxError, + match="invalid/empty 'flags' for REGEXP_LIKE", ): ctx.execute("SELECT * FROM df WHERE REGEXP_LIKE(scol,'[x-z]+','')") with pytest.raises( - InvalidOperationError, - match="invalid arguments for RegexpLike", + SQLSyntaxError, + match="invalid arguments for REGEXP_LIKE", ): ctx.execute("SELECT * FROM df WHERE REGEXP_LIKE(scol,999,999)") with pytest.raises( - InvalidOperationError, - match="invalid number of arguments for RegexpLike", + SQLSyntaxError, + match="invalid number of arguments for REGEXP_LIKE", ): ctx.execute("SELECT * FROM df WHERE REGEXP_LIKE(scol)") diff --git a/py-polars/tests/unit/sql/test_strings.py b/py-polars/tests/unit/sql/test_strings.py index f8084c273092..b27399e0290f 100644 --- a/py-polars/tests/unit/sql/test_strings.py +++ b/py-polars/tests/unit/sql/test_strings.py @@ -5,7 +5,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError, InvalidOperationError +from polars.exceptions import SQLSyntaxError from polars.testing import assert_frame_equal @@ -76,7 +76,7 @@ def test_string_concat() -> None: ) def test_string_concat_errors(invalid_concat: str) -> None: lf = pl.LazyFrame({"x": ["a", "b", "c"]}) - with pytest.raises(InvalidOperationError, match="invalid number of arguments"): + with pytest.raises(SQLSyntaxError, match="invalid number of arguments"): pl.SQLContext(data=lf).execute(f"SELECT {invalid_concat} FROM data") @@ -100,8 +100,8 @@ def test_string_left_right_reverse() -> None: } for func, invalid in (("LEFT", "'xyz'"), ("RIGHT", "6.66")): with pytest.raises( - InvalidOperationError, - match=f"invalid 'n_chars' for {func.capitalize()}: {invalid}", + SQLSyntaxError, + match=f"invalid 'n_chars' for {func}: {invalid}", ): ctx.execute(f"""SELECT {func}(txt,{invalid}) FROM df""").collect() @@ -317,7 +317,7 @@ def test_string_replace() -> None: res = out["words"].to_list() assert res == ["English breakfast tea is the best tea", "", None] - with pytest.raises(InvalidOperationError, match="invalid number of arguments"): + with pytest.raises(SQLSyntaxError, match="invalid number of arguments"): ctx.execute("SELECT REPLACE(words,'coffee') FROM df") @@ -348,8 +348,8 @@ def test_string_substr() -> None: ).collect() with pytest.raises( - InvalidOperationError, - match="Substring does not support negative length: -99", + SQLSyntaxError, + match="SUBSTR does not support negative length: -99", ): ctx.execute("SELECT SUBSTR(scol,2,-99) FROM df") @@ -372,24 +372,18 @@ def test_string_substr() -> None: def test_string_trim(foods_ipc_path: Path) -> None: lf = pl.scan_ipc(foods_ipc_path) - out = pl.SQLContext(foods1=lf).execute( + out = lf.sql( """ SELECT DISTINCT TRIM(LEADING 'vmf' FROM category) as new_category - FROM foods1 - ORDER BY new_category DESC - """, - eager=True, - ) + FROM self ORDER BY new_category DESC + """ + ).collect() assert out.to_dict(as_series=False) == { "new_category": ["seafood", "ruit", "egetables", "eat"] } with pytest.raises( - ComputeError, - match="unsupported TRIM", + SQLSyntaxError, + match="unsupported TRIM syntax", ): - # currently unsupported (snowflake) trim syntax - pl.SQLContext(foods=lf).execute( - """ - SELECT DISTINCT TRIM('*^xxxx^*', '^*') as new_category FROM foods - """, - ) + # currently unsupported (snowflake-style) trim syntax + lf.sql("SELECT DISTINCT TRIM('*^xxxx^*', '^*') as new_category FROM self") diff --git a/py-polars/tests/unit/sql/test_subqueries.py b/py-polars/tests/unit/sql/test_subqueries.py index 6c28372cd811..830384b84eea 100644 --- a/py-polars/tests/unit/sql/test_subqueries.py +++ b/py-polars/tests/unit/sql/test_subqueries.py @@ -133,7 +133,8 @@ def test_in_subquery() -> None: ) with pytest.raises( - pl.InvalidOperationError, match="SQL subquery will return more than one column" + pl.SQLSyntaxError, + match="SQL subquery returns more than one column", ): sql.execute( """ diff --git a/py-polars/tests/unit/sql/test_table_operations.py b/py-polars/tests/unit/sql/test_table_operations.py index a989ad858116..5992dd71e701 100644 --- a/py-polars/tests/unit/sql/test_table_operations.py +++ b/py-polars/tests/unit/sql/test_table_operations.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import SQLInterfaceError from polars.testing import assert_frame_equal @@ -30,7 +30,7 @@ def test_drop_table(test_frame: pl.LazyFrame) -> None: res = ctx.execute("DROP TABLE frame") assert_frame_equal(res, expected) - with pytest.raises(ComputeError, match="'frame' was not found"): + with pytest.raises(SQLInterfaceError, match="'frame' was not found"): ctx.execute("SELECT * FROM frame") diff --git a/py-polars/tests/unit/sql/test_temporal.py b/py-polars/tests/unit/sql/test_temporal.py index 06dd442a95d5..f6bf073b7141 100644 --- a/py-polars/tests/unit/sql/test_temporal.py +++ b/py-polars/tests/unit/sql/test_temporal.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import ComputeError, SQLSyntaxError from polars.testing import assert_frame_equal @@ -238,7 +238,7 @@ def test_timestamp_time_unit_errors() -> None: with pl.SQLContext(frame_data=df, eager=True) as ctx: for prec in (0, 15): with pytest.raises( - ComputeError, + SQLSyntaxError, match=f"invalid temporal type precision; expected 1-9, found {prec}", ): ctx.execute(f"SELECT ts::timestamp({prec}) FROM frame_data")