cloud-fan commented on code in PR #59820:
URL: https://github.com/apache/spark/pull/59820#discussion_r4400926113
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/ResolveDefaultColumnsUtil.scala:
##########
@@ -351,6 +359,9 @@ object ResolveDefaultColumns extends QueryErrorsBase
expr match {
case _: ExprLiteral => expr
case c: Cast if c.resolved => expr
+ case c @ Collate(_: ExprLiteral, _: UnresolvedCollation) =>
Review Comment:
**Non-blocking (P2):** This match keeps the escape-processed parse only for
a top-level literal, a resolved cast, or this collated literal. `Literal.sql`
writes ARRAY, MAP and STRUCT values as `ARRAY(...)`, `MAP(...)` and
`NAMED_STRUCT(...)`, escaping each nested string the same way as a top-level
one (literals.scala:720-744). Those calls parse to `CreateArray`, `CreateMap`
or `CreateNamedStruct` and fall to `case _`.
`fallbackResolveExistenceDefaultValue` then reparses the text with `new
CatalystSqlParser()` under the session's `escapedStringLiterals`.
So with `spark.sql.parser.escapedStringLiterals=true`, the problem this PR
fixes for STRING columns remains for complex columns. Take `ALTER TABLE ... ADD
COLUMNS (s STRUCT<y: STRING> DEFAULT ...)`: pre-existing rows read nested
strings with an extra backslash before each backslash and single quote, while
rows inserted later with `DEFAULT` get the original value. This applies to
nested collated strings too, and to every file reader that uses
`existenceDefaultValues`. Default mode is unaffected.
I confirmed this on this head with a decoder-level repro. A nested value of
`a\b'c`, stored as `Literal.sql` text and read back after a schema JSON round
trip, came back as `a\\b\'c` in escaped mode for ARRAY, STRUCT and MAP. A
top-level STRING default with the same value decoded correctly, as did every
type in default mode. The producer is supported: the existing InsertSuite test
that adds a `struct<x boolean, y string>` column with a default exercises it.
**Recommended change:** Treat an existence default whose escape-processed
parse is a tree of ARRAY, MAP or NAMED_STRUCT constructors with only literal
leaves as Literal.sql output, and decode it from that parse instead of the
session-parsed fallback. Literal leaves are a Literal, a Cast of a Literal, or
a collated Literal. Make CatalogV2Util.structFieldToV2Column build its
ColumnDefaultValue literal from this same decoder, for example through the
existing ResolveDefaultColumns.getExistenceDefaultValue or a Literal-level
method, instead of analyze(f, "Column analysis",
EXISTS_DEFAULT_COLUMN_METADATA_KEY). Add existence-default tests for complex
types with nested strings and for the V2 column conversion in both parser
modes, at the decoder level and through a file-source table with existing rows.
**Why this works:** A small recursive predicate in the decoder accepts
exactly the shapes Literal.sql emits for complex values: CreateArray, CreateMap
and CreateNamedStruct whose children are themselves accepted constructors or
literal leaves. A matching expression goes through the same default-column
analysis helper the new collated-literal arm already uses. That helper resolves
nested collations, constant-folds the constructor and coerces it to the column
type without reparsing the text, so nested strings keep the values the
escape-processed parse produced. Complex-type shapes the predicate rejects, and
any other shape the existing arms do not accept, still reach the existing
session-parsed fallback. The V2 conversion then evaluates the existence default
through this decoder, as getExistenceDefaultValue already does for the readers,
so the V2 literal and the readers' value come from one decoding.
**Scope:** Extend the reader decoder's canonical-shape recognition to
literal-only complex constructors, route the V2 column conversion through the
same decoder, and add complex-type and V2-conversion existence-default coverage
in both parser modes.
**Compatibility:** Session-based analysis, including session string-literal
parsing, for every existence default that reaches the unresolved-expression
fallback today, including complex-type text with a function call at any level;
the PR's handling of top-level literal, resolved-cast and collated-literal
defaults; the SQLConf identity and settings during decoding; and the V2
conversion's CURRENT_DEFAULT SQL text, which passes through unchanged.
**Risks:** Literal.sql writes NAMED_STRUCT field names without escaping, so
a field name that contains a backslash is now unescaped in escaped mode, as it
already is in default mode. The value is still assigned to the column by
position through default-value coercion. A field name with a single quote makes
the parse fail, and the decoder keeps falling back as it does today. The V2
conversion now reports an unresolvable existence default through the reader
decoder's error path, with an empty statement type instead of "Column
analysis", unless the shared entry point passes the statement type through. It
also logs the unresolved-existence-default fallback warning for text that is
not Literal.sql output. Values are unchanged in default mode.
**Constraints:** Leave the existing top-level resolved-cast arm unchanged.
No in-repo writer stores cast-wrapped function text, and this review assumes no
writer outside the repository does. Never copy or modify the active SQLConf,
and keep the session time zone and other settings in effect during analysis of
the canonical parse.
**Exclusions:**
- Literal.sql's own rendering of NAMED_STRUCT field names and PARSE_JSON
payloads predates this PR and is left unchanged.
**Success:** With spark.sql.parser.escapedStringLiterals set to true or
false, a row written before an ARRAY, MAP or STRUCT column with a
string-bearing default was added reads the same nested values as a row inserted
later with DEFAULT, including strings with backslashes, single quotes and
collations. Existence defaults whose text is a Literal.sql complex-type
constructor are decoded from the canonical parse and no longer log the
unresolved-existence-default fallback warning. Complex-type existence-default
text that contains a function call at any level still decodes with the
session's string-literal parsing. With spark.sql.parser.escapedStringLiterals
set to true or false, CatalogV2Util.structTypeToV2Columns builds each column's
ColumnDefaultValue literal with the same value that existenceDefaultValues
returns for that field, for plain, collated and nested strings, and converting
the columns back with v2ColumnsToStructType preserves that value.
**Verification:**
- **Regression:** In escaped mode, a file-source table with an existing row
and newly added ARRAY of STRING, MAP of STRING to STRING, and STRUCT with a
STRING field columns, whose defaults contain a backslash and a single quote,
returns the same values for the old row as for a row inserted with DEFAULT,
with both the vectorized and the row-based Parquet reader.
- **Regression:** In escaped mode, CatalogV2Util.structTypeToV2Columns on a
schema that has gone through a JSON round trip returns a ColumnDefaultValue
literal equal to existenceDefaultValues for STRING, collated STRING and
complex-type existence defaults whose strings contain a backslash and a single
quote. Converting the columns back with v2ColumnsToStructType and decoding
again returns the same value, and CREATE TABLE ... LIKE from a V1 table into an
in-memory V2 catalog passes the target catalog that value.
- **Behavior:** In both parser modes, decoding a schema that has gone
through a JSON round trip returns the original value for complex-type existence
defaults with plain and collated nested strings, null elements, empty strings,
and nested constructors such as an array of structs or a map whose values are
arrays.
- **Compatibility:** In escaped mode, a complex-type existence default whose
text contains a function call, such as an ARRAY over a concat of string
literals with a backslash escape, still decodes with session string-literal
parsing. Default-mode results stay unchanged for every shape.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]