morningman commented on code in PR #69488:
URL: https://github.com/apache/doris/pull/69488#discussion_r4389341960


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertUtils.java:
##########
@@ -477,15 +491,67 @@ private static Plan normalizePlanWithoutLock(LogicalPlan 
plan, TableIf table,
     }
 
     static List<Column> connectorWriteSchema(TableIf table, boolean full) {
+        return pinnedConnectorWriteSchema(table).orElseGet(() -> 
table.getBaseSchema(full));
+    }
+
+    private static Optional<List<Column>> pinnedConnectorWriteSchema(TableIf 
table) {
         ConnectContext context = ConnectContext.get();
-        if (context != null && context.getStatementContext() != null) {
-            Optional<List<Column>> pinned =
-                    
context.getStatementContext().getConnectorWriteSchema(table.getId());
-            if (pinned.isPresent()) {
-                return pinned.get();
-            }
+        if (context == null || context.getStatementContext() == null) {
+            return Optional.empty();
         }
-        return table.getBaseSchema(full);
+        return 
context.getStatementContext().getConnectorWriteSchema(table.getId());
+    }
+
+    /**
+     * The pinned writer schema of a target whose column defaults apply to 
writes only. The read schema of such a
+     * table carries no defaults, so DEFAULT(column) in an INSERT into it must 
resolve against this schema.
+     */
+    private static Optional<List<Column>> writeOnlyDefaultColumns(TableIf 
table) {
+        if (!(table instanceof PluginDrivenExternalTable)
+                || !((PluginDrivenExternalTable) 
table).hasWriteOnlyDefaults()) {
+            return Optional.empty();
+        }
+        return pinnedConnectorWriteSchema(table);
+    }
+
+    /** Resolve DEFAULT(column) in one inserted value by name against the 
target's pinned writer schema. */
+    private static NamedExpression resolveDefaultReferences(NamedExpression 
value,
+            Optional<List<Column>> writeOnlyDefaultColumns, TableIf table, 
List<String> targetNameParts) {
+        if (!writeOnlyDefaultColumns.isPresent()) {
+            return value;
+        }
+        return (NamedExpression) 
ConnectorWriteSchemaUtils.resolveDefaultReferences(value,
+                writeOnlyDefaultColumns.get(), (ExternalTable) table, 
ConnectContext.get(), targetNameParts, null);
+    }
+
+    /**
+     * Resolve DEFAULT(column) in the select lists of an INSERT ... SELECT by 
name against the target's pinned
+     * writer schema, before the source relations are bound, so it names a 
target column whatever the source is.
+     * A derived table in FROM is a query of its own: DEFAULT(column) in it 
names a column of its own FROM clause,
+     * as without this resolution, so it is left alone.
+     */
+    private static Plan resolveSelectDefaultReferences(Plan query, 
Optional<List<Column>> writeOnlyDefaultColumns,
+            TableIf table, List<String> targetNameParts) {
+        if (query instanceof LogicalSubQueryAlias) {
+            return query;
+        }
+        Plan plan = query.withChildren(child -> resolveSelectDefaultReferences(
+                child, writeOnlyDefaultColumns, table, targetNameParts));
+        if (plan instanceof LogicalProject) {
+            LogicalProject<?> project = (LogicalProject<?>) plan;
+            return project.withProjects(project.getProjects().stream()
+                    .map(projection -> resolveDefaultReferences(

Review Comment:
   Fixed in 37a93201c64. `UnboundStar` keeps the `REPLACE` list of `SELECT * 
REPLACE(expr AS c)` in a field instead of as expression children, so resolving 
the expression tree of each projection never reached it. 
`InsertUtils.resolveDefaultReferences` now also resolves the replacements of a 
star against the pinned writer schema and rebuilds the star with the same 
qualifier and `EXCEPT` list. `INSERT INTO t(c) SELECT * REPLACE(DEFAULT(c) AS 
c) FROM src` now writes the target's write-default, as `SELECT DEFAULT(c) FROM 
src` does: the replacement is resolved before binding, so the default of 
`src.c` never reaches it. A replacement follows the same rules as the rest of 
the select list, so a qualifier other than the target's (`DEFAULT(src.c)`) 
fails there too. A star inside a derived table is still left to binding.
   
   I found no other part of a select list that the walk skips: `EXCEPT` accepts 
column names only, and window frame bounds accept literals only. Subqueries are 
the known limitation from 
https://github.com/apache/doris/pull/69488#discussion_r4355508080.
   
   
`InsertUtilsTest#writeOnlyDefaultInStarReplacementResolvesByTargetColumnName` 
covers `src.* EXCEPT(score) REPLACE(DEFAULT(name) AS name)`: the replacement 
becomes the pinned default, and the qualifier and the `EXCEPT` list are kept. 
With the previous commit the replacement is still `DEFAULT(name)`. branch-4.2 
and branch-4.1 resolve each projection the same way, so they have the same gap; 
I'll pass that on.
   



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