This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 19a1e58071 Migrate datafusion/sql tests to insta, part1 (#15497)
19a1e58071 is described below
commit 19a1e58071cd60e74faabab805d97fd269418925
Author: Tommy shu <[email protected]>
AuthorDate: Sun Mar 30 17:37:17 2025 -0400
Migrate datafusion/sql tests to insta, part1 (#15497)
* Add `insta` as a dependency in Cargo.toml and Cargo.lock
* WIP, migrate simple test cases to `insta`
* taplo fmt
* strip backtrace from errors in SQL integration tests
* Use `allow_duplicates!` macro in SQL integration tests for error snapshots
* remove backtrace from error in test_select_distinct_order_by
* WIP: migrate all `prepare_stmt_replace_params_quick_test` to `insta`
---
Cargo.lock | 1 +
datafusion/sql/Cargo.toml | 1 +
datafusion/sql/tests/sql_integration.rs | 715 ++++++++++++++++++++------------
3 files changed, 444 insertions(+), 273 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 8aba95bdcc..aa4ffeb17c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2564,6 +2564,7 @@ dependencies = [
"datafusion-functions-window",
"env_logger",
"indexmap 2.8.0",
+ "insta",
"log",
"paste",
"recursive",
diff --git a/datafusion/sql/Cargo.toml b/datafusion/sql/Cargo.toml
index 4435ee0f56..b778db4676 100644
--- a/datafusion/sql/Cargo.toml
+++ b/datafusion/sql/Cargo.toml
@@ -61,5 +61,6 @@ datafusion-functions-aggregate = { workspace = true }
datafusion-functions-nested = { workspace = true }
datafusion-functions-window = { workspace = true }
env_logger = { workspace = true }
+insta = { workspace = true }
paste = "^1.0"
rstest = { workspace = true }
diff --git a/datafusion/sql/tests/sql_integration.rs
b/datafusion/sql/tests/sql_integration.rs
index 866c08ed02..6fd2f76973 100644
--- a/datafusion/sql/tests/sql_integration.rs
+++ b/datafusion/sql/tests/sql_integration.rs
@@ -48,6 +48,7 @@ use datafusion_functions_aggregate::{
use datafusion_functions_aggregate::{average::avg_udaf,
grouping::grouping_udaf};
use datafusion_functions_nested::make_array::make_array_udf;
use datafusion_functions_window::rank::rank_udwf;
+use insta::{allow_duplicates, assert_snapshot};
use rstest::rstest;
use sqlparser::dialect::{Dialect, GenericDialect, HiveDialect, MySqlDialect};
@@ -225,9 +226,10 @@ fn cast_to_invalid_decimal_type_precision_0() {
{
let sql = "SELECT CAST(10 AS DECIMAL(0))";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Decimal(precision = 0, scale = 0) should
satisfy `0 < precision <= 76`, and `scale <= precision`.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r"Error during planning: Decimal(precision = 0, scale = 0) should
satisfy `0 < precision <= 76`, and `scale <= precision`."
);
}
}
@@ -248,9 +250,10 @@ fn cast_to_invalid_decimal_type_precision_gt_76() {
{
let sql = "SELECT CAST(10 AS DECIMAL(79))";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Decimal(precision = 79, scale = 0) should
satisfy `0 < precision <= 76`, and `scale <= precision`.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r"Error during planning: Decimal(precision = 79, scale = 0)
should satisfy `0 < precision <= 76`, and `scale <= precision`."
);
}
}
@@ -261,9 +264,10 @@ fn cast_to_invalid_decimal_type_precision_lt_scale() {
{
let sql = "SELECT CAST(10 AS DECIMAL(5, 10))";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Decimal(precision = 5, scale = 10) should
satisfy `0 < precision <= 76`, and `scale <= precision`.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r"Error during planning: Decimal(precision = 5, scale = 10)
should satisfy `0 < precision <= 76`, and `scale <= precision`."
);
}
}
@@ -559,9 +563,12 @@ fn select_column_does_not_exist() {
fn select_repeated_column() {
let sql = "SELECT age, age FROM person";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Projections require unique expression names
but the expression \"person.age\" at position 0 and \"person.age\" at position
1 have the same name. Consider aliasing (\"AS\") one of them.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Projections require unique expression names but
the expression "person.age" at position 0 and "person.age" at position 1 have
the same name. Consider aliasing ("AS") one of them.
+ "#
);
}
@@ -735,9 +742,10 @@ fn table_with_column_alias_number_cols() {
let sql = "SELECT a, b, c
FROM lineitem l (a, b)";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Source table contains 3 columns but only 2
names given as column alias",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r"Error during planning: Source table contains 3 columns but only 2
names given as column alias"
);
}
@@ -745,9 +753,10 @@ fn table_with_column_alias_number_cols() {
fn select_with_ambiguous_column() {
let sql = "SELECT id FROM person a, person b";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Schema error: Ambiguous reference to unqualified field id",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r"Schema error: Ambiguous reference to unqualified field id"
);
}
@@ -794,10 +803,11 @@ fn select_with_having() {
FROM person
HAVING age > 100 AND age < 200";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: HAVING clause references: person.age >
Int64(100) AND person.age < Int64(200) must appear in the GROUP BY clause or be
used in an aggregate function",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r"Error during planning: HAVING clause references: person.age >
Int64(100) AND person.age < Int64(200) must appear in the GROUP BY clause or be
used in an aggregate function"
+ );
}
#[test]
@@ -806,10 +816,13 @@ fn select_with_having_referencing_column_not_in_select() {
FROM person
HAVING first_name = 'M'";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: HAVING clause references:
person.first_name = Utf8(\"M\") must appear in the GROUP BY clause or be used
in an aggregate function",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: HAVING clause references: person.first_name =
Utf8("M") must appear in the GROUP BY clause or be used in an aggregate function
+ "#
+ );
}
#[test]
@@ -819,10 +832,13 @@ fn select_with_having_refers_to_invalid_column() {
GROUP BY id
HAVING first_name = 'M'";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in HAVING must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"person.first_name\" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only \"person.id, max(person.age)\" appears in the SELECT clause
satisfies this requirement",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Column in HAVING must be in GROUP BY or an
aggregate function: While expanding wildcard, column "person.first_name" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only "person.id, max(person.age)" appears in the SELECT clause
satisfies this requirement
+ "#
+ );
}
#[test]
@@ -831,10 +847,13 @@ fn
select_with_having_referencing_column_nested_in_select_expression() {
FROM person
HAVING age > 100";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: HAVING clause references: person.age >
Int64(100) must appear in the GROUP BY clause or be used in an aggregate
function",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: HAVING clause references: person.age >
Int64(100) must appear in the GROUP BY clause or be used in an aggregate
function
+ "#
+ );
}
#[test]
@@ -843,10 +862,11 @@ fn select_with_having_with_aggregate_not_in_select() {
FROM person
HAVING MAX(age) > 100";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"person.first_name\" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only \"max(person.age)\" appears in the SELECT clause satisfies this
requirement",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column "person.first_name" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only "max(person.age)" appears in the SELECT clause satisfies this
requirement"#
+ );
}
#[test]
@@ -879,9 +899,12 @@ fn
select_aggregate_with_having_referencing_column_not_in_select() {
FROM person
HAVING first_name = 'M'";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in HAVING must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"person.first_name\" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only \"count(*)\" appears in the SELECT clause satisfies this
requirement",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Column in HAVING must be in GROUP BY or an
aggregate function: While expanding wildcard, column "person.first_name" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only "count(*)" appears in the SELECT clause satisfies this
requirement
+ "#
);
}
@@ -1000,9 +1023,12 @@ fn
select_aggregate_with_group_by_with_having_referencing_column_not_in_group_by
GROUP BY first_name
HAVING MAX(age) > 10 AND last_name = 'M'";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in HAVING must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"person.last_name\" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only \"person.first_name, max(person.age)\" appears in the SELECT
clause satisfies this requirement",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Column in HAVING must be in GROUP BY or an
aggregate function: While expanding wildcard, column "person.last_name" must
appear in the GROUP BY clause or must be part of an aggregate function,
currently only "person.first_name, max(person.age)" appears in the SELECT
clause satisfies this requirement
+ "#
);
}
@@ -1134,9 +1160,12 @@ fn select_simple_aggregate_column_does_not_exist() {
fn select_simple_aggregate_repeated_aggregate() {
let sql = "SELECT MIN(age), MIN(age) FROM person";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Projections require unique expression names
but the expression \"min(person.age)\" at position 0 and \"min(person.age)\" at
position 1 have the same name. Consider aliasing (\"AS\") one of them.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Projections require unique expression names but
the expression "min(person.age)" at position 0 and "min(person.age)" at
position 1 have the same name. Consider aliasing ("AS") one of them.
+ "#
);
}
@@ -1175,9 +1204,12 @@ fn select_from_typed_string_values() {
fn select_simple_aggregate_repeated_aggregate_with_repeated_aliases() {
let sql = "SELECT MIN(age) AS a, MIN(age) AS a FROM person";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Projections require unique expression names
but the expression \"min(person.age) AS a\" at position 0 and \"min(person.age)
AS a\" at position 1 have the same name. Consider aliasing (\"AS\") one of
them.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Projections require unique expression names but
the expression "min(person.age) AS a" at position 0 and "min(person.age) AS a"
at position 1 have the same name. Consider aliasing ("AS") one of them.
+ "#
);
}
@@ -1205,9 +1237,12 @@ fn select_simple_aggregate_with_groupby_with_aliases() {
fn select_simple_aggregate_with_groupby_with_aliases_repeated() {
let sql = "SELECT state AS a, MIN(age) AS a FROM person GROUP BY state";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Projections require unique expression names
but the expression \"person.state AS a\" at position 0 and \"min(person.age) AS
a\" at position 1 have the same name. Consider aliasing (\"AS\") one of them.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Projections require unique expression names but
the expression "person.state AS a" at position 0 and "min(person.age) AS a" at
position 1 have the same name. Consider aliasing ("AS") one of them.
+ "#
);
}
@@ -1225,11 +1260,13 @@ fn
select_simple_aggregate_with_groupby_column_unselected() {
fn
select_simple_aggregate_with_groupby_and_column_in_group_by_does_not_exist() {
let sql = "SELECT sum(age) FROM person GROUP BY doesnotexist";
let err = logical_plan(sql).expect_err("query should have failed");
- let expected = "Schema error: No field named doesnotexist. \
- Valid fields are \"sum(person.age)\", \
- person.id, person.first_name, person.last_name, person.age,
person.state, \
- person.salary, person.birth_date, person.\"😀\".";
- assert_eq!(err.strip_backtrace(), expected);
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Schema error: No field named doesnotexist. Valid fields are
"sum(person.age)", person.id, person.first_name, person.last_name, person.age,
person.state, person.salary, person.birth_date, person."😀".
+ "#
+ );
}
#[test]
@@ -1243,9 +1280,12 @@ fn
select_simple_aggregate_with_groupby_and_column_in_aggregate_does_not_exist()
fn select_interval_out_of_range() {
let sql = "SELECT INTERVAL '100000000000000000 day'";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Arrow error: Invalid argument error: Unable to represent
100000000000000000 days in a signed 32-bit integer",
+
+ assert_snapshot!(
err.strip_backtrace(),
+ @r#"
+ Arrow error: Invalid argument error: Unable to represent
100000000000000000 days in a signed 32-bit integer
+ "#
);
}
@@ -1279,16 +1319,22 @@ fn
select_simple_aggregate_with_groupby_can_use_positions() {
fn select_simple_aggregate_with_groupby_position_out_of_range() {
let sql = "SELECT state, MIN(age) FROM person GROUP BY 0";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Cannot find column with position 0 in SELECT
clause. Valid columns: 1 to 2",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Cannot find column with position 0 in SELECT
clause. Valid columns: 1 to 2
+ "#
);
let sql2 = "SELECT state, MIN(age) FROM person GROUP BY 5";
let err2 = logical_plan(sql2).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Cannot find column with position 5 in SELECT
clause. Valid columns: 1 to 2",
- err2.strip_backtrace()
+
+ assert_snapshot!(
+ err2.strip_backtrace(),
+ @r#"
+ Error during planning: Cannot find column with position 5 in SELECT
clause. Valid columns: 1 to 2
+ "#
);
}
@@ -1306,9 +1352,12 @@ fn select_simple_aggregate_with_groupby_can_use_alias() {
fn select_simple_aggregate_with_groupby_aggregate_repeated() {
let sql = "SELECT state, MIN(age), MIN(age) FROM person GROUP BY state";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Projections require unique expression names
but the expression \"min(person.age)\" at position 1 and \"min(person.age)\" at
position 2 have the same name. Consider aliasing (\"AS\") one of them.",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Projections require unique expression names but
the expression "min(person.age)" at position 1 and "min(person.age)" at
position 2 have the same name. Consider aliasing ("AS") one of them.
+ "#
);
}
@@ -1364,20 +1413,26 @@ fn
select_simple_aggregate_with_groupby_non_column_expression_nested_and_not_res
// The query should fail, because age + 9 is not in the group by.
let sql = "SELECT ((age + 1) / 2) * (age + 9), MIN(first_name) FROM person
GROUP BY age + 1";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"person.age\" must appear
in the GROUP BY clause or must be part of an aggregate function, currently only
\"person.age + Int64(1), min(person.first_name)\" appears in the SELECT clause
satisfies this requirement",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column "person.age" must appear
in the GROUP BY clause or must be part of an aggregate function, currently only
"person.age + Int64(1), min(person.first_name)" appears in the SELECT clause
satisfies this requirement
+ "#
+ );
}
#[test]
fn
select_simple_aggregate_with_groupby_non_column_expression_and_its_column_selected()
{
let sql = "SELECT age, MIN(first_name) FROM person GROUP BY age + 1";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"person.age\" must appear
in the GROUP BY clause or must be part of an aggregate function, currently only
\"person.age + Int64(1), min(person.first_name)\" appears in the SELECT clause
satisfies this requirement",
- err.strip_backtrace()
- );
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column "person.age" must appear
in the GROUP BY clause or must be part of an aggregate function, currently only
"person.age + Int64(1), min(person.first_name)" appears in the SELECT clause
satisfies this requirement
+ "#
+ );
}
#[test]
@@ -1528,9 +1583,12 @@ fn select_order_by_index_of_0() {
let err = logical_plan(sql)
.expect_err("query should have failed")
.strip_backtrace();
- assert_eq!(
- "Error during planning: Order by index starts at 1 for column indexes",
- err
+
+ assert_snapshot!(
+ err,
+ @r#"
+ Error during planning: Order by index starts at 1 for column indexes
+ "#
);
}
@@ -1540,9 +1598,12 @@ fn select_order_by_index_oob() {
let err = logical_plan(sql)
.expect_err("query should have failed")
.strip_backtrace();
- assert_eq!(
- "Error during planning: Order by column out of bounds, specified: 2,
max: 1",
- err
+
+ assert_snapshot!(
+ err,
+ @r#"
+ Error during planning: Order by column out of bounds, specified: 2,
max: 1
+ "#
);
}
@@ -1635,9 +1696,12 @@ fn select_7480_1() {
fn select_7480_2() {
let sql = "SELECT c1, c13, MIN(c12) FROM aggregate_test_100 GROUP BY c1";
let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column \"aggregate_test_100.c13\"
must appear in the GROUP BY clause or must be part of an aggregate function,
currently only \"aggregate_test_100.c1, min(aggregate_test_100.c12)\" appears
in the SELECT clause satisfies this requirement",
- err.strip_backtrace()
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: Column in SELECT must be in GROUP BY or an
aggregate function: While expanding wildcard, column "aggregate_test_100.c13"
must appear in the GROUP BY clause or must be part of an aggregate function,
currently only "aggregate_test_100.c1, min(aggregate_test_100.c12)" appears in
the SELECT clause satisfies this requirement
+ "#
);
}
@@ -1722,12 +1786,19 @@ fn create_external_table_with_compression_type() {
"CREATE EXTERNAL TABLE t STORED AS ARROW LOCATION 'foo.arrow' OPTIONS
('format.compression' 'gzip')",
"CREATE EXTERNAL TABLE t STORED AS ARROW LOCATION 'foo.arrow' OPTIONS
('format.compression' 'bzip2')",
];
- for sql in sqls {
- let err = logical_plan(sql).expect_err("query should have failed");
- assert_eq!(
- "Error during planning: File compression type cannot be set for
PARQUET, AVRO, or ARROW files.",
- err.strip_backtrace()
- );
+
+ allow_duplicates! {
+ for sql in sqls {
+ let err = logical_plan(sql).expect_err("query should have failed");
+
+ assert_snapshot!(
+ err.strip_backtrace(),
+ @r#"
+ Error during planning: File compression type cannot be set for
PARQUET, AVRO, or ARROW files.
+ "#
+ );
+
+ }
}
}
@@ -2520,18 +2591,6 @@ fn prepare_stmt_quick_test(
plan
}
-fn prepare_stmt_replace_params_quick_test(
- plan: LogicalPlan,
- param_values: impl Into<ParamValues>,
- expected_plan: &str,
-) -> LogicalPlan {
- // replace params
- let plan = plan.with_param_values(param_values).unwrap();
- assert_eq!(format!("{plan}"), expected_plan);
-
- plan
-}
-
#[test]
fn select_partially_qualified_column() {
let sql = r#"SELECT person.first_name FROM public.person"#;
@@ -2826,11 +2885,16 @@ fn hive_aggregate_with_filter() -> Result<()> {
let dialect = &HiveDialect {};
let sql = "SELECT sum(age) FILTER (WHERE age > 4) FROM person";
let plan = logical_plan_with_dialect(sql, dialect)?;
- let expected = "Projection: sum(person.age) FILTER (WHERE person.age >
Int64(4))\
- \n Aggregate: groupBy=[[]], aggr=[[sum(person.age) FILTER (WHERE
person.age > Int64(4))]]\
- \n TableScan: person"
- .to_string();
- assert_eq!(plan.display_indent().to_string(), expected);
+
+ assert_snapshot!(
+ plan,
+ @r###"
+ Projection: sum(person.age) FILTER (WHERE person.age > Int64(4))
+ Aggregate: groupBy=[[]], aggr=[[sum(person.age) FILTER (WHERE
person.age > Int64(4))]]
+ TableScan: person
+ "###
+ );
+
Ok(())
}
@@ -2853,19 +2917,27 @@ fn order_by_unaliased_name() {
#[test]
fn order_by_ambiguous_name() {
let sql = "select * from person a join person b using (id) order by age";
- let expected = "Schema error: Ambiguous reference to unqualified field
age";
+ let err = logical_plan(sql).unwrap_err().strip_backtrace();
- let err = logical_plan(sql).unwrap_err();
- assert_eq!(err.strip_backtrace(), expected);
+ assert_snapshot!(
+ err,
+ @r###"
+ Schema error: Ambiguous reference to unqualified field age
+ "###
+ );
}
#[test]
fn group_by_ambiguous_name() {
let sql = "select max(id) from person a join person b using (id) group by
age";
- let expected = "Schema error: Ambiguous reference to unqualified field
age";
+ let err = logical_plan(sql).unwrap_err().strip_backtrace();
- let err = logical_plan(sql).unwrap_err();
- assert_eq!(err.strip_backtrace(), expected);
+ assert_snapshot!(
+ err,
+ @r###"
+ Schema error: Ambiguous reference to unqualified field age
+ "###
+ );
}
#[test]
@@ -3107,14 +3179,17 @@ fn test_select_order_by() {
fn test_select_distinct_order_by() {
let sql = "SELECT distinct '1' from person order by id";
- let expected =
- "Error during planning: For SELECT DISTINCT, ORDER BY expressions
person.id must appear in select list";
-
// It should return error.
let result = logical_plan(sql);
assert!(result.is_err());
- let err = result.err().unwrap();
- assert_eq!(err.strip_backtrace(), expected);
+ let err = result.err().unwrap().strip_backtrace();
+
+ assert_snapshot!(
+ err,
+ @r###"
+ Error during planning: For SELECT DISTINCT, ORDER BY expressions
person.id must appear in select list
+ "###
+ );
}
#[rstest]
@@ -3203,13 +3278,17 @@ fn test_ambiguous_column_references_in_on_join() {
INNER JOIN person as p2
ON id = 1";
- let expected = "Schema error: Ambiguous reference to unqualified field id";
-
// It should return error.
let result = logical_plan(sql);
assert!(result.is_err());
- let err = result.err().unwrap();
- assert_eq!(err.strip_backtrace(), expected);
+ let err = result.err().unwrap().strip_backtrace();
+
+ assert_snapshot!(
+ err,
+ @r###"
+ Schema error: Ambiguous reference to unqualified field id
+ "###
+ );
}
#[test]
@@ -3233,9 +3312,12 @@ fn test_prepare_statement_to_plan_panic_param_format() {
// param is not number following the $ sign
// panic due to error returned from the parser
let sql = "PREPARE my_plan(INT) AS SELECT id, age FROM person WHERE age =
$foo";
- assert_eq!(
+
+ assert_snapshot!(
logical_plan(sql).unwrap_err().strip_backtrace(),
- "Error during planning: Invalid placeholder, not a number: $foo"
+ @r###"
+ Error during planning: Invalid placeholder, not a number: $foo
+ "###
);
}
@@ -3244,9 +3326,12 @@ fn test_prepare_statement_to_plan_panic_param_zero() {
// param is zero following the $ sign
// panic due to error returned from the parser
let sql = "PREPARE my_plan(INT) AS SELECT id, age FROM person WHERE age =
$0";
- assert_eq!(
+
+ assert_snapshot!(
logical_plan(sql).unwrap_err().strip_backtrace(),
- "Error during planning: Invalid placeholder, zero is not a valid
index: $0"
+ @r###"
+ Error during planning: Invalid placeholder, zero is not a valid index:
$0
+ "###
);
}
@@ -3320,11 +3405,15 @@ fn test_prepare_statement_to_plan_no_param() {
///////////////////
// replace params with values
let param_values = vec![ScalarValue::Int32(Some(10))];
- let expected_plan = "Projection: person.id, person.age\
- \n Filter: person.age = Int64(10)\
- \n TableScan: person";
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age = Int64(10)
+ TableScan: person
+ "
+ );
//////////////////////////////////////////
// no embedded parameter and no declare it
@@ -3342,11 +3431,15 @@ fn test_prepare_statement_to_plan_no_param() {
///////////////////
// replace params with values
let param_values: Vec<ScalarValue> = vec![];
- let expected_plan = "Projection: person.id, person.age\
- \n Filter: person.age = Int64(10)\
- \n TableScan: person";
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age = Int64(10)
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3356,12 +3449,14 @@ fn
test_prepare_statement_to_plan_one_param_no_value_panic() {
let plan = logical_plan(sql).unwrap();
// declare 1 param but provide 0
let param_values: Vec<ScalarValue> = vec![];
- assert_eq!(
+
+ assert_snapshot!(
plan.with_param_values(param_values)
- .unwrap_err()
- .strip_backtrace(),
- "Error during planning: Expected 1 parameters, got 0"
- );
+ .unwrap_err()
+ .strip_backtrace(),
+ @r###"
+ Error during planning: Expected 1 parameters, got 0
+ "###);
}
#[test]
@@ -3371,11 +3466,14 @@ fn
test_prepare_statement_to_plan_one_param_one_value_different_type_panic() {
let plan = logical_plan(sql).unwrap();
// declare 1 param but provide 0
let param_values = vec![ScalarValue::Float64(Some(20.0))];
- assert_eq!(
+
+ assert_snapshot!(
plan.with_param_values(param_values)
.unwrap_err()
.strip_backtrace(),
- "Error during planning: Expected parameter of type Int32, got Float64
at index 0"
+ @r###"
+ Error during planning: Expected parameter of type Int32, got Float64
at index 0
+ "###
);
}
@@ -3386,11 +3484,14 @@ fn
test_prepare_statement_to_plan_no_param_on_value_panic() {
let plan = logical_plan(sql).unwrap();
// declare 1 param but provide 0
let param_values = vec![ScalarValue::Int32(Some(10))];
- assert_eq!(
+
+ assert_snapshot!(
plan.with_param_values(param_values)
.unwrap_err()
.strip_backtrace(),
- "Error during planning: Expected 0 parameters, got 1"
+ @r###"
+ Error during planning: Expected 0 parameters, got 1
+ "###
);
}
@@ -3407,9 +3508,14 @@ fn test_prepare_statement_to_plan_params_as_constants() {
///////////////////
// replace params with values
let param_values = vec![ScalarValue::Int32(Some(10))];
- let expected_plan = "Projection: Int32(10) AS $1\n EmptyRelation";
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: Int32(10) AS $1
+ EmptyRelation
+ "
+ );
///////////////////////////////////////
let sql = "PREPARE my_plan(INT) AS SELECT 1 + $1";
@@ -3423,10 +3529,14 @@ fn test_prepare_statement_to_plan_params_as_constants()
{
///////////////////
// replace params with values
let param_values = vec![ScalarValue::Int32(Some(10))];
- let expected_plan =
- "Projection: Int64(1) + Int32(10) AS Int64(1) + $1\n EmptyRelation";
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: Int64(1) + Int32(10) AS Int64(1) + $1
+ EmptyRelation
+ "
+ );
///////////////////////////////////////
let sql = "PREPARE my_plan(INT, DOUBLE) AS SELECT 1 + $1 + $2";
@@ -3443,11 +3553,14 @@ fn test_prepare_statement_to_plan_params_as_constants()
{
ScalarValue::Int32(Some(10)),
ScalarValue::Float64(Some(10.0)),
];
- let expected_plan =
- "Projection: Int64(1) + Int32(10) + Float64(10) AS Int64(1) + $1 + $2\
- \n EmptyRelation";
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: Int64(1) + Int32(10) + Float64(10) AS Int64(1) + $1 + $2
+ EmptyRelation
+ "
+ );
}
#[test]
@@ -3471,17 +3584,18 @@ Projection: person.id, orders.order_id
assert_eq!(actual_types, expected_types);
// replace params with values
- let param_values = vec![ScalarValue::Int32(Some(10))].into();
- let expected_plan = r#"
-Projection: person.id, orders.order_id
- Inner Join: Filter: person.id = orders.customer_id AND person.age =
Int32(10)
- TableScan: person
- TableScan: orders
- "#
- .trim();
- let plan = plan.replace_params_with_values(¶m_values).unwrap();
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let param_values = vec![ScalarValue::Int32(Some(10))];
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, orders.order_id
+ Inner Join: Filter: person.id = orders.customer_id AND person.age =
Int32(10)
+ TableScan: person
+ TableScan: orders
+ "
+ );
}
#[test]
@@ -3503,16 +3617,17 @@ Projection: person.id, person.age
assert_eq!(actual_types, expected_types);
// replace params with values
- let param_values = vec![ScalarValue::Int32(Some(10))].into();
- let expected_plan = r#"
-Projection: person.id, person.age
- Filter: person.age = Int32(10)
- TableScan: person
- "#
- .trim();
- let plan = plan.replace_params_with_values(¶m_values).unwrap();
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let param_values = vec![ScalarValue::Int32(Some(10))];
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age = Int32(10)
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3537,17 +3652,17 @@ Projection: person.id, person.age
assert_eq!(actual_types, expected_types);
// replace params with values
- let param_values =
- vec![ScalarValue::Int32(Some(10)),
ScalarValue::Int32(Some(30))].into();
- let expected_plan = r#"
-Projection: person.id, person.age
- Filter: person.age BETWEEN Int32(10) AND Int32(30)
- TableScan: person
- "#
- .trim();
- let plan = plan.replace_params_with_values(¶m_values).unwrap();
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let param_values = vec![ScalarValue::Int32(Some(10)),
ScalarValue::Int32(Some(30))];
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age BETWEEN Int32(10) AND Int32(30)
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3574,21 +3689,22 @@ Projection: person.id, person.age
assert_eq!(actual_types, expected_types);
// replace params with values
- let param_values = vec![ScalarValue::UInt32(Some(10))].into();
- let expected_plan = r#"
-Projection: person.id, person.age
- Filter: person.age = (<subquery>)
- Subquery:
- Projection: max(person.age)
- Aggregate: groupBy=[[]], aggr=[[max(person.age)]]
- Filter: person.id = UInt32(10)
- TableScan: person
- TableScan: person
- "#
- .trim();
- let plan = plan.replace_params_with_values(¶m_values).unwrap();
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let param_values = vec![ScalarValue::UInt32(Some(10))];
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age = (<subquery>)
+ Subquery:
+ Projection: max(person.age)
+ Aggregate: groupBy=[[]], aggr=[[max(person.age)]]
+ Filter: person.id = UInt32(10)
+ TableScan: person
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3614,18 +3730,18 @@ Dml: op=[Update] table=[person]
assert_eq!(actual_types, expected_types);
// replace params with values
- let param_values =
- vec![ScalarValue::Int32(Some(42)),
ScalarValue::UInt32(Some(1))].into();
- let expected_plan = r#"
-Dml: op=[Update] table=[person]
- Projection: person.id AS id, person.first_name AS first_name,
person.last_name AS last_name, Int32(42) AS age, person.state AS state,
person.salary AS salary, person.birth_date AS birth_date, person.😀 AS 😀
- Filter: person.id = UInt32(1)
- TableScan: person
- "#
- .trim();
- let plan = plan.replace_params_with_values(¶m_values).unwrap();
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let param_values = vec![ScalarValue::Int32(Some(42)),
ScalarValue::UInt32(Some(1))];
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Dml: op=[Update] table=[person]
+ Projection: person.id AS id, person.first_name AS first_name,
person.last_name AS last_name, Int32(42) AS age, person.state AS state,
person.salary AS salary, person.birth_date AS birth_date, person.😀 AS 😀
+ Filter: person.id = UInt32(1)
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3654,16 +3770,16 @@ fn test_prepare_statement_insert_infer() {
ScalarValue::UInt32(Some(1)),
ScalarValue::from("Alan"),
ScalarValue::from("Turing"),
- ]
- .into();
- let expected_plan = "Dml: op=[Insert Into] table=[person]\
- \n Projection: column1 AS id, column2 AS first_name,
column3 AS last_name, \
- CAST(NULL AS Int32) AS age, CAST(NULL AS
Utf8) AS state, CAST(NULL AS Float64) AS salary, \
- CAST(NULL AS Timestamp(Nanosecond, None))
AS birth_date, CAST(NULL AS Int32) AS 😀\
- \n Values: (UInt32(1) AS $1, Utf8(\"Alan\") AS $2,
Utf8(\"Turing\") AS $3)";
- let plan = plan.replace_params_with_values(¶m_values).unwrap();
-
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ ];
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r#"
+ Dml: op=[Insert Into] table=[person]
+ Projection: column1 AS id, column2 AS first_name, column3 AS last_name,
CAST(NULL AS Int32) AS age, CAST(NULL AS Utf8) AS state, CAST(NULL AS Float64)
AS salary, CAST(NULL AS Timestamp(Nanosecond, None)) AS birth_date, CAST(NULL
AS Int32) AS 😀
+ Values: (UInt32(1) AS $1, Utf8("Alan") AS $2, Utf8("Turing") AS $3)
+ "#
+ );
}
#[test]
@@ -3682,11 +3798,16 @@ fn test_prepare_statement_to_plan_one_param() {
///////////////////
// replace params with values
let param_values = vec![ScalarValue::Int32(Some(10))];
- let expected_plan = "Projection: person.id, person.age\
- \n Filter: person.age = Int32(10)\
- \n TableScan: person";
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age = Int32(10)
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3707,11 +3828,16 @@ fn test_prepare_statement_to_plan_data_type() {
///////////////////
// replace params with values still succeed and use Float64
let param_values = vec![ScalarValue::Float64(Some(10.0))];
- let expected_plan = "Projection: person.id, person.age\
- \n Filter: person.age = Float64(10)\
- \n TableScan: person";
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r"
+ Projection: person.id, person.age
+ Filter: person.age = Float64(10)
+ TableScan: person
+ "
+ );
}
#[test]
@@ -3740,12 +3866,16 @@ fn test_prepare_statement_to_plan_multi_params() {
ScalarValue::Float64(Some(200.0)),
ScalarValue::from("xyz"),
];
- let expected_plan =
- "Projection: person.id, person.age, Utf8(\"xyz\") AS $6\
- \n Filter: person.age IN ([Int32(10), Int32(20)]) AND person.salary >
Float64(100) AND person.salary < Float64(200) OR person.first_name <
Utf8(\"abc\")\
- \n TableScan: person";
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r#"
+ Projection: person.id, person.age, Utf8("xyz") AS $6
+ Filter: person.age IN ([Int32(10), Int32(20)]) AND person.salary >
Float64(100) AND person.salary < Float64(200) OR person.first_name < Utf8("abc")
+ TableScan: person
+ "#
+ );
}
#[test]
@@ -3777,14 +3907,18 @@ fn test_prepare_statement_to_plan_having() {
ScalarValue::Float64(Some(200.0)),
ScalarValue::Float64(Some(300.0)),
];
- let expected_plan =
- "Projection: person.id, sum(person.age)\
- \n Filter: sum(person.age) < Int32(10) AND sum(person.age) >
Int64(10) OR sum(person.age) IN ([Float64(200), Float64(300)])\
- \n Aggregate: groupBy=[[person.id]], aggr=[[sum(person.age)]]\
- \n Filter: person.salary > Float64(100)\
- \n TableScan: person";
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r#"
+ Projection: person.id, sum(person.age)
+ Filter: sum(person.age) < Int32(10) AND sum(person.age) > Int64(10) OR
sum(person.age) IN ([Float64(200), Float64(300)])
+ Aggregate: groupBy=[[person.id]], aggr=[[sum(person.age)]]
+ Filter: person.salary > Float64(100)
+ TableScan: person
+ "#
+ );
}
#[test]
@@ -3804,10 +3938,15 @@ fn test_prepare_statement_to_plan_limit() {
// replace params with values
let param_values = vec![ScalarValue::Int64(Some(10)),
ScalarValue::Int64(Some(200))];
- let expected_plan = "Limit: skip=10, fetch=200\
- \n Projection: person.id\
- \n TableScan: person";
- prepare_stmt_replace_params_quick_test(plan, param_values, expected_plan);
+ let plan_with_params = plan.with_param_values(param_values).unwrap();
+ assert_snapshot!(
+ plan_with_params,
+ @r#"
+ Limit: skip=10, fetch=200
+ Projection: person.id
+ TableScan: person
+ "#
+ );
}
#[test]
@@ -3893,15 +4032,28 @@ fn test_multi_grouping_sets() {
#[test]
fn test_field_not_found_window_function() {
let order_by_sql = "SELECT count() OVER (order by a);";
- let order_by_err = logical_plan(order_by_sql).expect_err("query should
have failed");
- let expected = "Schema error: No field named a.";
- assert_eq!(order_by_err.strip_backtrace(), expected);
+ let order_by_err = logical_plan(order_by_sql)
+ .expect_err("query should have failed")
+ .strip_backtrace();
+
+ assert_snapshot!(
+ order_by_err,
+ @r###"
+ Schema error: No field named a.
+ "###
+ );
let partition_by_sql = "SELECT count() OVER (PARTITION BY a);";
- let partition_by_err =
- logical_plan(partition_by_sql).expect_err("query should have failed");
- let expected = "Schema error: No field named a.";
- assert_eq!(partition_by_err.strip_backtrace(), expected);
+ let partition_by_err = logical_plan(partition_by_sql)
+ .expect_err("query should have failed")
+ .strip_backtrace();
+
+ assert_snapshot!(
+ partition_by_err,
+ @r###"
+ Schema error: No field named a.
+ "###
+ );
let qualified_sql =
"SELECT order_id, MAX(qty) OVER (PARTITION BY orders.order_id) from
orders";
@@ -3929,10 +4081,13 @@ fn test_parse_escaped_string_literal_value() {
quick_test(sql, expected);
let sql = r"SELECT character_length(E'\000') AS len";
- assert_eq!(
- logical_plan(sql).unwrap_err().strip_backtrace(),
- "SQL error: TokenizerError(\"Unterminated encoded string literal at
Line: 1, Column: 25\")"
- )
+
+ assert_snapshot!(
+ logical_plan(sql).unwrap_err(),
+ @r###"
+ SQL error: TokenizerError("Unterminated encoded string literal at
Line: 1, Column: 25")
+ "###
+ );
}
#[test]
@@ -4048,22 +4203,36 @@ fn test_custom_type_plan() -> Result<()> {
}
let plan = plan_sql(sql);
- let expected =
- "Projection: CAST(Utf8(\"2001-01-01 18:00:00\") AS
Timestamp(Nanosecond, None))\
- \n EmptyRelation";
- assert_eq!(plan.to_string(), expected);
+
+ assert_snapshot!(
+ plan,
+ @r###"
+ Projection: CAST(Utf8("2001-01-01 18:00:00") AS Timestamp(Nanosecond,
None))
+ EmptyRelation
+ "###
+ );
let plan = plan_sql("SELECT CAST(TIMESTAMP '2001-01-01 18:00:00' AS
DATETIME)");
- let expected = "Projection: CAST(CAST(Utf8(\"2001-01-01 18:00:00\") AS
Timestamp(Nanosecond, None)) AS Timestamp(Nanosecond, None))\
- \n EmptyRelation";
- assert_eq!(plan.to_string(), expected);
+
+ assert_snapshot!(
+ plan,
+ @r###"
+ Projection: CAST(CAST(Utf8("2001-01-01 18:00:00") AS
Timestamp(Nanosecond, None)) AS Timestamp(Nanosecond, None))
+ EmptyRelation
+ "###
+ );
let plan = plan_sql(
"SELECT ARRAY[DATETIME '2001-01-01 18:00:00', DATETIME '2001-01-02
18:00:00']",
);
- let expected = "Projection: make_array(CAST(Utf8(\"2001-01-01 18:00:00\")
AS Timestamp(Nanosecond, None)), CAST(Utf8(\"2001-01-02 18:00:00\") AS
Timestamp(Nanosecond, None)))\
- \n EmptyRelation";
- assert_eq!(plan.to_string(), expected);
+
+ assert_snapshot!(
+ plan,
+ @r###"
+ Projection: make_array(CAST(Utf8("2001-01-01 18:00:00") AS
Timestamp(Nanosecond, None)), CAST(Utf8("2001-01-02 18:00:00") AS
Timestamp(Nanosecond, None)))
+ EmptyRelation
+ "###
+ );
Ok(())
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]