Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4d59f10
Expose input schema to aggregate expression planners
osipovartem Sep 1, 2026
8461571
Expose input schema to scalar expression planners
osipovartem Sep 1, 2026
9c8e619
Apply ILIKE when expanding wildcard arguments
osipovartem Sep 1, 2026
f05ec59
Plan qualified wildcard function options
osipovartem Sep 1, 2026
dd65516
Add optional decimal literal normalization
osipovartem Sep 1, 2026
5763680
Format SQLParser workspace dependency
osipovartem Sep 1, 2026
6e701b0
Merge pull request #48 from Embucket/expr-planner-scalar-schema
osipovartem Sep 1, 2026
09477f0
Update h2 to address RUSTSEC-2026-0258
osipovartem Sep 1, 2026
fbf447b
Merge pull request #49 from Embucket/qualified-wildcard-options
osipovartem Sep 1, 2026
970c995
Merge remote-tracking branch 'origin/expr-planner-aggregate-schema' i…
osipovartem Sep 1, 2026
a4762a3
Merge pull request #50 from Embucket/snowflake-decimal-literals
osipovartem Sep 1, 2026
55182c3
Update settings SQL logic test expectations
osipovartem Sep 2, 2026
30afc45
Fix wildcard keys for USING outer joins
osipovartem Sep 2, 2026
cbb778b
Merge pull request #51 from Embucket/full-using-wildcard-coalesce
osipovartem Sep 2, 2026
904df42
Avoid target schema propagation into INSERT SELECT
osipovartem Sep 2, 2026
3048eab
Update USING join view snapshot
osipovartem Sep 2, 2026
eaf0ce6
Update USING join unparser expectation
osipovartem Sep 2, 2026
dad65bd
Merge pull request #52 from Embucket/fix-insert-select-target-schema
osipovartem Sep 2, 2026
a95a514
Do not treat volatile expressions as constants
osipovartem Sep 2, 2026
db33ea3
Preserve rows when sorting zero-column batches
osipovartem Sep 2, 2026
7fee309
Merge pull request #53 from Embucket/fix-volatile-expression-constant
osipovartem Sep 2, 2026
d434a58
Merge pull request #54 from Embucket/fix-zero-column-sort
osipovartem Sep 2, 2026
7b12ae5
Fall back to floats for out-of-range decimal literals
osipovartem Sep 2, 2026
e017e21
Merge pull request #55 from Embucket/fix-decimal-literal-float-fallback
osipovartem Sep 2, 2026
f903f40
Update decimal fallback SQL logic expectations
osipovartem Sep 2, 2026
bc26506
Preserve Snowflake PIVOT column names
osipovartem Sep 3, 2026
75d4723
Update PIVOT integration snapshot
osipovartem Sep 3, 2026
3f2da7b
Merge pull request #56 from Embucket/snowflake-pivot-column-names
osipovartem Sep 3, 2026
00047fa
Apply PIVOT column aliases before schema validation
osipovartem Sep 3, 2026
e2413a5
Merge pull request #57 from Embucket/snowflake-double-pivot-alias
osipovartem Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ regex = "1.12"
rstest = "0.26.1"
serde_json = "1"
sha2 = "^0.11.0"
sqlparser = { version = "0.62.0", default-features = false, features = ["std", "visitor"] }
sqlparser = { git = "https://github.com/Embucket/datafusion-sqlparser-rs.git", rev = "1a3f48f60802d0f135a968a27c89a3351e78b159", default-features = false, features = [
"std",
"visitor",
] }
stacker = "0.1.24"
strum = "0.28.0"
strum_macros = "0.28.0"
Expand Down
4 changes: 4 additions & 0 deletions datafusion/common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ config_namespace! {
/// When set to true, SQL parser will parse float as decimal type
pub parse_float_as_decimal: bool, default = false

/// When set to true, insignificant trailing zeros are removed from decimal literals.
/// For example, `10.00` is planned as `DECIMAL(2, 0)` instead of `DECIMAL(4, 2)`.
pub trim_decimal_literal_trailing_zeros: bool, default = false

/// When set to true, SQL parser will normalize ident (convert ident to lowercase when not quoted)
pub enable_ident_normalization: bool, default = true

Expand Down
6 changes: 3 additions & 3 deletions datafusion/core/src/datasource/view_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,10 @@ mod tests {

insta::assert_snapshot!(batches_to_string(&results),@r"
+---------+---------+---------+
| column2 | column1 | column3 |
| column1 | column2 | column3 |
+---------+---------+---------+
| 2 | 1 | 3 |
| 5 | 4 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | 6 |
+---------+---------+---------+
");

Expand Down
2 changes: 2 additions & 0 deletions datafusion/core/src/execution/session_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,8 @@ impl SessionState {

ParserOptions {
parse_float_as_decimal: sql_parser_options.parse_float_as_decimal,
trim_decimal_literal_trailing_zeros: sql_parser_options
.trim_decimal_literal_trailing_zeros,
enable_ident_normalization: sql_parser_options.enable_ident_normalization,
enable_options_value_normalization: sql_parser_options
.enable_options_value_normalization,
Expand Down
27 changes: 27 additions & 0 deletions datafusion/core/tests/sql/joins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ use datafusion_sql::unparser::plan_to_sql;

use super::*;

#[tokio::test]
async fn natural_full_join_wildcard_coalesces_join_key() -> Result<()> {
let ctx = SessionContext::new();
let dataframe = ctx
.sql(
"WITH d1(id, name) AS (VALUES (1, 'a'), (2, 'b'), (4, 'c')),
d2(id, value) AS (VALUES (1, 'xx'), (2, 'yy'), (5, 'zz'))
SELECT * FROM d1 NATURAL FULL OUTER JOIN d2 ORDER BY id",
)
.await?;

assert_batches_eq!(
[
"+----+------+-------+",
"| id | name | value |",
"+----+------+-------+",
"| 1 | a | xx |",
"| 2 | b | yy |",
"| 4 | c | |",
"| 5 | | zz |",
"+----+------+-------+",
],
&dataframe.collect().await?
);
Ok(())
}

#[tokio::test]
async fn join_change_in_planner() -> Result<()> {
let config = SessionConfig::new().with_target_partitions(8);
Expand Down
48 changes: 42 additions & 6 deletions datafusion/core/tests/sql/pivot_unpivot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,48 @@ async fn pivot_list_is_lowered_to_filtered_aggregates() -> Result<()> {
.await?;

insta::assert_snapshot!(batches_to_string(&batches), @r"
+----+----+----+
| id | a | b |
+----+----+----+
| 1 | 10 | 20 |
| 2 | 7 | |
+----+----+----+
+----+-----+-----+
| id | 'a' | 'b' |
+----+-----+-----+
| 1 | 10 | 20 |
| 2 | 7 | |
+----+-----+-----+
");
Ok(())
}

#[tokio::test]
async fn chained_pivots_apply_explicit_column_aliases() -> Result<()> {
let batches = SessionContext::new()
.sql(
"SELECT SUM(q1_sales) AS q1_sales,
SUM(q2_sales) AS q2_sales,
MAX(q1_discount) AS q1_discount,
MAX(q2_discount) AS q2_discount
FROM (
SELECT amount,
quarter AS sales_quarter,
quarter AS discount_quarter,
discount
FROM (VALUES
(100, 'Q1', 10),
(200, 'Q2', 20)
) sales(amount, quarter, discount)
)
PIVOT(SUM(amount) FOR sales_quarter IN ('Q1', 'Q2'))
PIVOT(MAX(discount) FOR discount_quarter IN ('Q1', 'Q2'))
AS p(q1_sales, q2_sales, q1_discount, q2_discount)",
)
.await?
.collect()
.await?;

insta::assert_snapshot!(batches_to_string(&batches), @r"
+----------+----------+-------------+-------------+
| q1_sales | q2_sales | q1_discount | q2_discount |
+----------+----------+-------------+-------------+
| 100 | 200 | 10 | 20 |
+----------+----------+-------------+-------------+
");
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion datafusion/core/tests/sql/unparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ async fn optimized_duckdb_unparse_qualifies_nested_passthrough_column() -> Resul
// `o` (which is only the base-table alias one level deeper). The bug emitted
// `"o"."order_id"` inside that derived table; the fix emits a bare column.
let expected = concat!(
r#"SELECT "o"."order_id", "o"."discount_pct_2" "#,
r#"SELECT "oi"."order_id", "o"."discount_pct_2" "#,
r#"FROM "warehouse"."main"."order_items" AS "oi" "#,
r#"INNER JOIN (SELECT "order_id", "#,
r#"CASE WHEN "__common_expr_1" IS NOT NULL "#,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,24 @@ async fn volatile_scalar_udf_with_params() -> Result<()> {
];
assert_batches_eq!(expected, &result);

let result = plan_and_collect(
&ctx,
"SELECT * FROM (SELECT add_index_to_string('test') AS str FROM t) \
ORDER BY add_index_to_string('test') LIMIT 2 OFFSET 5",
)
.await?;
assert_batches_eq!(
[
"+---------+",
"| str |",
"+---------+",
"| 6) test |",
"| 7) test |",
"+---------+",
],
&result
);

let result =
plan_and_collect(&ctx, "select add_index_to_string('test_value') as str") // with fixed function parameters
.await?;
Expand Down
31 changes: 31 additions & 0 deletions datafusion/expr/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ pub trait ContextProvider {
///
/// [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]: https://datafusion.apache.org/blog/2026/01/12/extending-sql
pub trait ExprPlanner: Debug + Send + Sync {
/// Plans scalar functions, such as `CONCAT(<expr>, ...)`, with access to the input schema.
///
/// Returns the original scalar function if planning is not possible.
fn plan_scalar_with_schema(
&self,
expr: RawScalarExpr,
_schema: &DFSchema,
) -> Result<PlannerResult<RawScalarExpr>> {
Ok(PlannerResult::Original(expr))
}

/// Plan the binary operation between two expressions, returns original
/// BinaryExpr if not possible
fn plan_binary_op(
Expand Down Expand Up @@ -273,6 +284,19 @@ pub trait ExprPlanner: Debug + Send + Sync {
Ok(PlannerResult::Original(expr))
}

/// Plans aggregate functions with access to the input schema.
///
/// The default implementation delegates to [`Self::plan_aggregate`] so existing planners do
/// not need to change. Planners that need to resolve schema-dependent arguments, such as a
/// qualified wildcard, can override this method instead.
fn plan_aggregate_with_schema(
&self,
expr: RawAggregateExpr,
_schema: &DFSchema,
) -> Result<PlannerResult<RawAggregateExpr>> {
self.plan_aggregate(expr)
}

/// Plans window functions, such as `COUNT(<expr>)`
///
/// Returns original expression arguments if not possible
Expand Down Expand Up @@ -330,6 +354,13 @@ pub struct RawAggregateExpr {
pub null_treatment: Option<NullTreatment>,
}

/// This structure is used by scalar function expression planners.
#[derive(Debug, Clone)]
pub struct RawScalarExpr {
pub func: Arc<ScalarUDF>,
pub args: Vec<Expr>,
}

/// This structure is used by `WindowFunctionPlanner` to plan operators with
/// custom expressions.
#[derive(Debug, Clone)]
Expand Down
Loading
Loading