Unparser turns NULL list literals into `[]` and fails on NULL struct/map literals
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 72/100
Línea de trabajo
Empieza en Unparser::scalar_to_sql en datafusion/sql/src/unparser/expr.rs. Las ramas de List (alrededor de las líneas 1450-1454) renderizan los valores hijos sin comprobar si son nulos, y las ramas Struct, Map y Union (alrededor de las líneas 1579-1581) devuelven not_impl_err! incluso para nulos. Reproduce el problema con las dos consultas CASE del issue y luego comprueba si los nulos tipados como Int32(None) ya se convierten a NULL en el unparser. Está terminado cuando los literales NULL de tipos anidados se convierten a NULL y las pruebas existentes del unparser de datafusion-sql siguen pasando.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Describe the bug
The SQL unparser mishandles NULL literals of nested types:
- A NULL list literal unparses as
[], so the generated SQL returns an empty array where the plan returns NULL, with no error. - A NULL struct, map, or union literal fails with
NotImplemented("Unsupported scalar: ...").
Optimized plans contain these literals whenever type coercion casts a NULL to a nested type and constant folding turns the cast into a literal, e.g. CASE WHEN ... THEN NULL ELSE list_col END or ... UNION ALL SELECT NULL.
The cause is in Unparser::scalar_to_sql. The list arms render the scalar's child values without checking whether the list itself is null (unparser/expr.rs#L1450-L1454), and the Struct/Map/Union arms return not_impl_err! even for nulls (unparser/expr.rs#L1579-L1581). Every other typed null, such as Int32(None) or DurationSecond(None), unparses as NULL.
To Reproduce
use datafusion::error::Result;
use datafusion::prelude::*;
use datafusion::sql::unparser::plan_to_sql;
#[tokio::main]
async fn main() -> Result<()> {
let ctx = SessionContext::new();
ctx.sql("CREATE TABLE t AS SELECT 1 AS a, [1, 2] AS l, named_struct('x', 1) AS s")
.await?;
for sql in [
"SELECT CASE WHEN a > 0 THEN NULL ELSE l END AS c FROM t",
"SELECT CASE WHEN a > 0 THEN NULL ELSE s END AS c FROM t",
] {
let plan = ctx.sql(sql).await?.into_optimized_plan()?;
println!("{:?}", plan_to_sql(&plan).map(|s| s.to_string()));
}
Ok(())
}
Output:
Ok("SELECT CASE WHEN (t.a > 0) THEN [] ELSE t.l END AS c FROM t")
Err(NotImplemented("Unsupported scalar: Struct({x:})"))
Running the first unparsed query returns [] where the original returns NULL. SELECT l FROM t UNION ALL SELECT NULL FROM t similarly unparses to ... UNION ALL SELECT [] AS l FROM t. Map columns fail the same way as struct columns.
Expected behavior
NULL nested-type literals unparse as NULL, like other typed nulls, e.g. CASE WHEN (t.a > 0) THEN NULL ELSE t.l END.
A guard arm at the top of scalar_to_sql is enough:
ScalarValue::List(_)
| ScalarValue::LargeList(_)
| ScalarValue::FixedSizeList(_)
| ScalarValue::ListView(_)
| ScalarValue::LargeListView(_)
| ScalarValue::Struct(_)
| ScalarValue::Map(_)
| ScalarValue::Union(..)
if v.is_null() =>
{
Ok(ast::Expr::value(ast::Value::Null))
}
With this arm, the queries above, the UNION ALL case, and the map case all unparse to SQL that returns the same results as the original query, and the existing datafusion-sql tests still pass.
Additional context
- Non-null struct and map literals, such as a constant-folded
named_struct('x', 1), also fail withUnsupported scalar. Rendering those needs dialect-specific syntax, so they are out of scope here. - Once #26311 merges,
CASE WHEN a > 0 THEN s ELSE NULL ENDfails the same way. Until then the unparser drops the failing ELSE, which happens to give the same result forELSE NULL. - Line references are pinned to commit 0d79252f61bd.
- Lenguaje dominante
- Rust
- Estrellas
- 9.4k
- Forks
- 2.5k
- Merge medio
- 4 d 9 h
- PR fusionados (30 d)
- 329
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de apache/datafusion
-
Deduplicate bucket computation in width_bucket_interval_mdn_exactPosiblemente ocupada @nightcityblade la tomó hoy. Abiertoenhancement good first issue help wanted spark tech debt
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
apache/datafusion#26307 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Use the shared aggr_test_schema in CSV scan tests instead of a local copyPosiblemente ocupada @med9110 la tomó hoy. Abiertocore enhancement good first issue help wanted tech debt
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
apache/datafusion#26306 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
gcd benchmarks pass number_rows: 0 for 100k-row inputsPosiblemente ocupada @med9110 la tomó hoy. Abiertobug functions good first issue help wanted tech debt
Dificultad 1/5 Menos de una hora Aptitud para principiantes 92/100
apache/datafusion#26297 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Struct union type coercion takes field nullability from the wrong field when field order differsPosiblemente ocupada @med9110 la tomó hoy. Abiertobug help wanted logical-expr tech debt
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
apache/datafusion#26296 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
apache/datafusion#26290 ·
Los mantenedores suelen responder en 1 día
Todos los issues de apache/datafusion
Issues similares
-
Custom hosts written as trailing-dot FQDNs (`host.local.`) aren't detected as local backendsPosiblemente ocupada @roboomp la tomó hoy. Abiertobug prio:p2 providers triaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
can1357/oh-my-pi#15798 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
requires-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
apache/datafusion-comet#7001 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
-
builtin-port enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
cmlarsen/writemorebetter#235 ·
Los mantenedores suelen responder en 1 día
-
Add a Storybook section to CONTRIBUTING.mdPosiblemente ocupada @ChanakyaG2004 la tomó hoy. Abiertodocumentation good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
FlowwStar/FlowStar#995 · 2 comentarios ·
Los mantenedores suelen responder en 1 día