924060929 commented on PR #65329:
URL: https://github.com/apache/doris/pull/65329#issuecomment-5032954601

   Focusing on the FE side only. Verified against head `6a88d6c`; not 
re-raising anything already covered in the existing threads.
   
   ### 1. `legacyMode` is unreachable in production, and it silently drops an 
existing behavior
   
   `Alter.java` now always dispatches through the `ColumnPath` overload, so 
`modifyTopLevelColumn(...)` is always called with `legacyMode = false`. I could 
not find any production caller reaching `legacyMode = true`:
   
   - `ExternalCatalog.modifyColumn(TableIf, Column, ColumnPosition)` has zero 
callers after this PR. Note the asymmetry — 
`addColumn`/`dropColumn`/`renameColumn` were rewritten to delegate to their 
`ColumnPath` counterparts, but `modifyColumn` kept its old body and is now 
orphaned.
   - `IcebergMetadataOps` is the only `ExternalMetadataOps` impl overriding 
`modifyColumn`, and it overrides the 5-arg version, so the interface bridge 
never falls through to the legacy one.
   - The only things keeping it alive are 
`testLegacyModifyColumnTreatsNullabilityAsExplicit` and 
`testLegacyComplexModifyRestoresRecursiveNullableSemantics`.
   
   That matters because of this, in `applyStructChange` / `applyListChange` / 
`applyMapChange`:
   
   ```java
   if (legacyMode && !oldField.isOptional() && newField.getContainsNull()) {
       updateSchema.makeColumnOptional(fieldPath);
   }
   ```
   
   `complexColType` has no nullability clause (`identifier COLON dataType 
commentSpec?`) and `visitComplexColType` hardcodes `nullable = true`, so 
`getContainsNull()` is always `true` for parsed SQL. On master the condition 
therefore reduces to `!oldField.isOptional()` — `ALTER TABLE t MODIFY COLUMN s 
STRUCT<...>` unconditionally promotes required nested fields to optional. After 
this PR it does nothing, and nothing compensates: `applyExplicitNullableChange` 
only touches the resolved target path, and `StructField` carries 
`commentSpecified` but no `nullableSpecified`. If that promotion was the 
statement's only intent, `UpdateSchema` ends up with an empty change set and 
the statement returns success with no diagnostic.
   
   The tests show it: `testExplicitNullableModifyMakesRequiredFieldsOptional` 
asserts `makeColumnOptional` on `info` and `info.metric`, while the legacy test 
on the same schema asserts `info`, `info.metric`, `info.child.value`, 
`info.events.element`, `info.attrs.value`.
   
   Suggestion: drop the `legacyMode` parameter (it only serves tests) and state 
the behavior change with the migration path (`MODIFY COLUMN s.a INT NULL`) — or 
reject the statement instead of silently succeeding.
   
   ### 2. `nereids/types/StructField.toSql()` backticks are unrelated to the 
feature and create an inconsistency
   
   `SqlUtils.getIdentSql()` quotes unconditionally, so every struct field name 
in the nereids renderer becomes `` `id` ``. `SHOW CREATE TABLE` / `DESC` are 
unaffected (they go through fe-type `catalog.StructType` / `Type`), but error 
messages are: cast errors, the `arrays_overlap` / `array_union` / `array_sort` 
family, `InPredicate`, aggregate-type incompatibility, nested column pruning. A 
user now sees ``STRUCT<`id`:INT>`` in a cast error and `struct<id:int>` in 
`DESC` for the same column.
   
   The comment half of the change is a genuine fix — the old code emitted 
`COMMENT foo` unquoted, which isn't valid SQL — please keep it. The name 
quoting looks like a separate PR, or should be conditional.
   
   Also worth knowing: `schema_change_p0/test_modify_struct.groovy` and 
`test_varchar_sc_in_complex.groovy` do exact `assertEquals(x.toLowerCase(), 
"struct<col:varchar(30),...>")` on DESC output. They survive only because the 
catalog renderer wasn't touched.
   
   ### 3. Moving `op.validate(ctx)` after table resolution is cross-cutting
   
   `AlterTableCommand.validate()` now resolves the table before running op 
validation, so ALTER on a non-existent table reports "Unknown table" instead of 
the op-level error. That's the actual reason 
`test_table_level_compaction_policy.groovy` needed changing: its `CREATE TABLE` 
inside `test { }` never executed (`TestAction.sql()` is a setter — only the 
last statement runs), so it had been altering a table that didn't exist.
   
   I swept `regression-test/suites` for the same pattern (CREATE + ALTER inside 
one `test { }` block) and that file is the only hit, so the fix looks complete. 
Still worth a line in the description, since the reordering applies to all 
table types, not just Iceberg.
   
   ### 4. `toSql()` no longer emits `NULL` / `COMMENT ""`, and that string is 
persisted
   
   `AddColumnOp.toSql()` / `ModifyColumnOp.toSql()` now route through 
`toSql(columnNameSql, commentSpecified)`, so both keywords are omitted when 
unspecified. The round trip is semantically safe (`nullableSpecified` is only 
false when `nullableType` was `DEFAULT`/`UNKNOWN`, where `isNullable` equals 
what a reparse recomputes, and `validate()` never rewrites `isNullable`). But 
this string goes into `AlterJobV2.rawSql` / `TableAddOrDropColumnsInfo.rawSql` 
— the edit log and the CCR binlog — so the emitted SQL shape changes for 
essentially every ADD/MODIFY COLUMN. Worth confirming ccr-syncer doesn't 
pattern-match on it.
   
   ### Minor
   
   - `columnDefWithPath`'s second alternative duplicates `columnDef` (minus 
`DEFAULT` / `ON UPDATE`), and `visitColumnDefWithPath` duplicates ~45 lines of 
`visitColumnDef`. A single rule with `(DOT colNames+=identifier)*` would 
collapse both, and would let `ADD COLUMN s.b INT DEFAULT 1` produce a proper 
error instead of a raw syntax error.
   - `nullableSpecified` / `commentSpecified` are transient intent flags on 
`Column`, a persisted catalog object, carried only to cross the 
`CatalogIf.modifyColumn(TableIf, Column, ...)` boundary. Passing the 
`ColumnDefinition` (or a small request object) would keep parse-time intent out 
of the schema type.
   - `ColumnDefinition.toSql()` and `toSql(String)` differ in comment 
semantics; the javadoc on the latter doesn't say so.
   - #65675 touches both `StructField` classes as well — worth deciding the 
merge order.
   - The checklist is empty, but this adds syntax (nested paths, `RENAME COLUMN 
... TO ...`) and changes behavior (an omitted `COMMENT` no longer clears the 
Iceberg doc; top-level `MODIFY COLUMN x COMMENT` on Iceberg goes from 
unsupported to supported). Looks like "Behavior changed: Yes" plus a docs PR. 
The syntax table also doesn't mention that `TO` is now accepted.
   
   ### Checked, not a problem
   
   - `.map(RuleContext::getText)` on identifiers is safe — `PostProcessor` 
rewrites `BACKQUOTED_IDENTIFIER` into a bare `IDENTIFIER` token during parsing, 
including `` `a``b` ``.
   - The new `visitQualifiedName` override can't affect other statements — all 
three grammar parents are explicitly overridden, and `visitChildren` only 
forwards single-child nodes.
   - `validateStructTypeChanges`'s `newFields.get(i)` can't go out of range; 
`ColumnType`'s "Cannot reduce struct fields" guard runs first and recurses 
through ARRAY/MAP element types. A local size assertion would still make the 
invariant visible at the point of use.
   - The red `BE UT (macOS)` is the runner's JDK 25 vs JDK 17 issue, unrelated 
to this PR.
   


-- 
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]

Reply via email to