[ 
https://issues.apache.org/jira/browse/SPARK-60002?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Ivelin Tchangalov updated SPARK-60002:
--------------------------------------
    Description: 
Spark's SQL parser accepts the 
{code:java}
NULL DEFINED AS {code}
 clause of 
{code:java}
CREATE TABLE ... ROW FORMAT DELIMITED{code}
but it is never written to the table's SerDe properties.

Therefore, the same DDL over the same files returns different results in
Hive and in Spark.

This translates to data issues because data isn't read correctly.
h3. Root cause

{{AstBuilder.visitRowFormatDelimited}} builds the SerDe property map from the 
delimited
clauses but never reads {{{}ctx.nullDefinedAs{}}}. The method carries the 
acknowledgement in a
comment: {{// TODO we need proper support for the NULL format.}}

{{{}field.delim{}}}, {{{}escape.delim{}}}, {{{}colelction.delim{}}}, 
{{mapkey.delim}} and
{{line.delim}} are all stored correctly. The gap is exactly one property,
{{{}serialization.null.format{}}}, which appears nowhere in Spark's main source:
{code:java}
  grep -rn "serialization\.null\.format" --include=*.scala --include=*.java . | 
grep -v /test/
  #   (no output)
  {code}
The grammar already captures the value: {{SqlBaseParser.g4}} binds it to the 
labelled token {{{}nullDefinedAs{}}}. Its only consumer in the codebase is 
{{{}getRowFormatDelimited{}}}, the SCRIPT TRANSFORM path, which maps it to 
{{{}TOK_TABLEROWFORMATNULL{}}}. The table-DDL visitor ignores it.
h3. Reproduction

PySpark with Hive support enabled; {{DESCRIBE FORMATTED}} reports {{{}Provider: 
hive{}}}, so
this is a genuine Hive SerDe table and not a converted datasource table.
{code:sql}
  CREATE EXTERNAL TABLE t (id INT, region STRING)
  ROW FORMAT DELIMITED FIELDS TERMINATED BY '|'
  NULL DEFINED AS 'NA'
  STORED AS TEXTFILE LOCATION '...';
  {code}
Data:
{noformat}
  1|east
  2|NA
  3|west
  {noformat}
{{DESCRIBE FORMATTED }}reports
{noformat}
  Storage Properties   [serialization.format=|, field.delim=|]
  {noformat}
There is no {{{}serialization.null.format{}}}, and the row with id 2 reads 
{{region}} as the
string {{'NA'}} rather than NULL. Hive returns NULL for the same table and data.

Every sentinel value is affected: {{{}''{}}}, {{{}\N{}}}, {{{}NA{}}}, 
{{{}X{}}}, {{\t}} and {{\001}} all behave identically. {{\N}} appears to work 
only because it is the engine default.
h3. How the bug was introduced

It was never implemented.
h3. Related issues

https://issues.apache.org/jira/browse/SPARK-14583  reported the read-side 
variant of this parity gap: a sentinel supplied through {{TBLPROPERTIES}} on a 
partitioned table that Spark fails to apply on read. It was bulk-closed as 
Incomplete in 2019. That is a different vector into the same Hive parity gap 
and is not addressed by this ticket.

  was:
Spark's SQL parser accepts the 
{code:java}
NULL DEFINED AS {code}
 clause of 
{code:java}
CREATE TABLE ... ROW FORMAT DELIMITED{code}
but it is never never written to the table's SerDe properties

Hive honours the clause, so the same DDL over the same files returns different 
results in
Hive and in Spark.

Every delimited-text table whose sentinel is not Hive's default {{\N}}
reads that sentinel as literal data instead of NULL. Because the clause is 
accepted
silently, there is no signal at table-creation time. That translates to a data 
issues because data isn't read correctly.
h3. Root cause

{{AstBuilder.visitRowFormatDelimited}} builds the SerDe property map from the 
delimited
clauses but never reads {{{}ctx.nullDefinedAs{}}}. The method carries the 
acknowledgement in a
comment: {{// TODO we need proper support for the NULL format.}}

{{{}field.delim{}}}, {{{}escape.delim{}}}, {{{}colelction.delim{}}}, 
{{mapkey.delim}} and
{{line.delim}} are all stored correctly. The gap is exactly one property,
{{{}serialization.null.format{}}}, which appears nowhere in Spark's main source:
{code:java}
  grep -rn "serialization\.null\.format" --include=*.scala --include=*.java . | 
grep -v /test/
  #   (no output)
  {code}
The grammar already captures the value: {{SqlBaseParser.g4}} binds it to the 
labelled token {{{}nullDefinedAs{}}}. Its only consumer in the codebase is 
{{{}getRowFormatDelimited{}}}, the SCRIPT TRANSFORM path, which maps it to 
{{{}TOK_TABLEROWFORMATNULL{}}}. The table-DDL visitor ignores it.
h3. Reproduction

PySpark with Hive support enabled; {{DESCRIBE FORMATTED}} reports {{{}Provider: 
hive{}}}, so
this is a genuine Hive SerDe table and not a converted datasource table.
{code:sql}
  CREATE EXTERNAL TABLE t (id INT, region STRING)
  ROW FORMAT DELIMITED FIELDS TERMINATED BY '|'
  NULL DEFINED AS 'NA'
  STORED AS TEXTFILE LOCATION '...';
  {code}
Data:
{noformat}
  1|east
  2|NA
  3|west
  {noformat}
{{DESCRIBE FORMATTED t}} reports
{noformat}
  Storage Properties   [serialization.format=|, field.delim=|]
  {noformat}
There is no {{{}serialization.null.format{}}}, and the row with id 2 reads 
{{region}} as the
string {{'NA'}} rather than NULL. Hive returns NULL for the same table and data.

Every sentinel value is affected: {{{}''{}}}, {{{}\N{}}}, {{{}NA{}}}, 
{{{}X{}}}, {{\t}} and {{\001}} all
behave identically. {{\N}} appears to work only because it is the engine 
default.
h3. How the bug was introduced

It was never implemented
h3. Related issues

SPARK-14583 reported the read-side variant of this parity gap: a sentinel 
supplied through {{TBLPROPERTIES}} on a partitioned table that Spark fails to 
apply on read. It was
bulk-closed as Incomplete in 2019. That is a different vector into the same 
Hive parity gap and is not addressed by this ticket.


> CREATE TABLE ... ROW FORMAT DELIMITED silently discards NULL DEFINED AS
> -----------------------------------------------------------------------
>
>                 Key: SPARK-60002
>                 URL: https://issues.apache.org/jira/browse/SPARK-60002
>             Project: Spark
>          Issue Type: Bug
>          Components: SQL
>    Affects Versions: 3.5.6, 4.0.0
>            Reporter: Ivelin Tchangalov
>            Priority: Major
>
> Spark's SQL parser accepts the 
> {code:java}
> NULL DEFINED AS {code}
>  clause of 
> {code:java}
> CREATE TABLE ... ROW FORMAT DELIMITED{code}
> but it is never written to the table's SerDe properties.
> Therefore, the same DDL over the same files returns different results in
> Hive and in Spark.
> This translates to data issues because data isn't read correctly.
> h3. Root cause
> {{AstBuilder.visitRowFormatDelimited}} builds the SerDe property map from the 
> delimited
> clauses but never reads {{{}ctx.nullDefinedAs{}}}. The method carries the 
> acknowledgement in a
> comment: {{// TODO we need proper support for the NULL format.}}
> {{{}field.delim{}}}, {{{}escape.delim{}}}, {{{}colelction.delim{}}}, 
> {{mapkey.delim}} and
> {{line.delim}} are all stored correctly. The gap is exactly one property,
> {{{}serialization.null.format{}}}, which appears nowhere in Spark's main 
> source:
> {code:java}
>   grep -rn "serialization\.null\.format" --include=*.scala --include=*.java . 
> | grep -v /test/
>   #   (no output)
>   {code}
> The grammar already captures the value: {{SqlBaseParser.g4}} binds it to the 
> labelled token {{{}nullDefinedAs{}}}. Its only consumer in the codebase is 
> {{{}getRowFormatDelimited{}}}, the SCRIPT TRANSFORM path, which maps it to 
> {{{}TOK_TABLEROWFORMATNULL{}}}. The table-DDL visitor ignores it.
> h3. Reproduction
> PySpark with Hive support enabled; {{DESCRIBE FORMATTED}} reports 
> {{{}Provider: hive{}}}, so
> this is a genuine Hive SerDe table and not a converted datasource table.
> {code:sql}
>   CREATE EXTERNAL TABLE t (id INT, region STRING)
>   ROW FORMAT DELIMITED FIELDS TERMINATED BY '|'
>   NULL DEFINED AS 'NA'
>   STORED AS TEXTFILE LOCATION '...';
>   {code}
> Data:
> {noformat}
>   1|east
>   2|NA
>   3|west
>   {noformat}
> {{DESCRIBE FORMATTED }}reports
> {noformat}
>   Storage Properties   [serialization.format=|, field.delim=|]
>   {noformat}
> There is no {{{}serialization.null.format{}}}, and the row with id 2 reads 
> {{region}} as the
> string {{'NA'}} rather than NULL. Hive returns NULL for the same table and 
> data.
> Every sentinel value is affected: {{{}''{}}}, {{{}\N{}}}, {{{}NA{}}}, 
> {{{}X{}}}, {{\t}} and {{\001}} all behave identically. {{\N}} appears to work 
> only because it is the engine default.
> h3. How the bug was introduced
> It was never implemented.
> h3. Related issues
> https://issues.apache.org/jira/browse/SPARK-14583  reported the read-side 
> variant of this parity gap: a sentinel supplied through {{TBLPROPERTIES}} on 
> a partitioned table that Spark fails to apply on read. It was bulk-closed as 
> Incomplete in 2019. That is a different vector into the same Hive parity gap 
> and is not addressed by this ticket.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to