Gabriel39 commented on code in PR #66345:
URL: https://github.com/apache/doris/pull/66345#discussion_r3717619328


##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveWritePlanProvider.java:
##########
@@ -99,19 +100,75 @@ public ConnectorSinkPlan planWrite(ConnectorSession 
session, ConnectorWriteHandl
         HiveTableHandle tableHandle = (HiveTableHandle) 
handle.getTableHandle();
         HiveConnectorTransaction transaction = currentTransaction(session);
 
-        // Load the table under the catalog auth context; it drives both the 
location resolution
-        // (buildWriteContext) and the sink assembly (buildSink). beginWrite 
re-loads it for its own
-        // begin-guard — the double-load is accepted (mirrors iceberg), 
keeping the flow simple.
+        // One fresh table generation drives validation, transaction state, 
location resolution, and sink
+        // assembly. Reloading in beginWrite would reopen a TOCTOU window 
after the schema fence.
         HmsTableInfo table = loadTable(tableHandle);
+        validateBoundWriteMetadata(table, handle);
         HiveWriteContext writeContext = buildWriteContext(session, 
tableHandle, table, handle);
-        transaction.beginWrite(session, tableHandle.getDbName(), 
tableHandle.getTableName(), writeContext);
+        transaction.beginWrite(session, tableHandle.getDbName(), 
tableHandle.getTableName(), writeContext, table);
 
         THiveTableSink sink = buildSink(session, tableHandle, table, handle, 
writeContext);
         TDataSink dataSink = new TDataSink(TDataSinkType.HIVE_TABLE_SINK);
         dataSink.setHiveTableSink(sink);
         return new ConnectorSinkPlan(dataSink);
     }
 
+    private static void validateBoundWriteMetadata(HmsTableInfo table, 
ConnectorWriteHandle handle) {
+        String boundIdentity = handle.getBoundWriteMetadataIdentity();
+        if (boundIdentity != null && 
!boundIdentity.equals(writeMetadataIdentity(table))) {
+            // Bound expressions and live THiveTableSink ordinals must 
describe one HMS schema generation;
+            // accepting a reorder here silently writes each value under 
another column name.
+            throw new DorisConnectorException(
+                    "Hive write metadata changed after the write was bound; 
retry the statement");
+        }
+
+        List<ConnectorColumn> boundColumns = handle.getBoundTargetColumns();
+        int liveColumnCount = table.getColumns().size() + 
table.getPartitionKeys().size();
+        if (boundColumns.isEmpty()) {
+            return;
+        }
+        if (boundColumns.size() != liveColumnCount) {
+            throw schemaChangedException();
+        }
+        for (int i = 0; i < boundColumns.size(); i++) {
+            ConnectorColumn live = i < table.getColumns().size()
+                    ? table.getColumns().get(i)
+                    : table.getPartitionKeys().get(i - 
table.getColumns().size());
+            if 
(!boundColumns.get(i).getName().equalsIgnoreCase(live.getName())) {
+                throw schemaChangedException();
+            }
+        }
+    }
+
+    private static DorisConnectorException schemaChangedException() {
+        return new DorisConnectorException(
+                "Hive table schema changed after the write was bound; retry 
the statement");
+    }
+
+    static String writeMetadataIdentity(HmsTableInfo table) {
+        StringBuilder identity = new StringBuilder();
+        appendMetadataToken(identity, "data-columns");
+        for (ConnectorColumn column : table.getColumns()) {
+            appendColumnIdentity(identity, column);
+        }
+        appendMetadataToken(identity, "partition-columns");
+        for (ConnectorColumn column : table.getPartitionKeys()) {
+            appendColumnIdentity(identity, column);
+        }
+        return identity.toString();
+    }
+
+    private static void appendColumnIdentity(StringBuilder identity, 
ConnectorColumn column) {
+        appendMetadataToken(identity, 
column.getName().toLowerCase(Locale.ROOT));
+        appendMetadataToken(identity, column.getType());

Review Comment:
   Fixed by introducing HiveWriteMetadataSnapshot, which derives the binding 
columns and a fixed-size generation identity from the same table/default 
inputs. The identity recursively records nested type shape and covers 
SerDe/OpenCSV coercion and effective defaults. Added nested-leaf, 
OpenCSV-to-LazySimple, and default-only race tests.



##########
fe/fe-connector/fe-connector-spi/src/main/java/org/apache/doris/connector/spi/ConnectorTableSchema.java:
##########
@@ -121,6 +124,18 @@ public ConnectorTableSchema(String tableName,
             String tableFormatType,
             Map<String, String> properties,
             Set<ConnectorCapability> tableCapabilities) {
+        this(tableName, columns, tableFormatType, properties, 
tableCapabilities, null);

Review Comment:
   Fixed. reflectSiblingCapabilities now rebuilds the schema with the 
six-argument constructor and preserves 
siblingSchema.getWriteMetadataIdentity(). Added a delegated sibling regression 
assertion.



##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveWritePlanProvider.java:
##########
@@ -99,19 +100,75 @@ public ConnectorSinkPlan planWrite(ConnectorSession 
session, ConnectorWriteHandl
         HiveTableHandle tableHandle = (HiveTableHandle) 
handle.getTableHandle();
         HiveConnectorTransaction transaction = currentTransaction(session);
 
-        // Load the table under the catalog auth context; it drives both the 
location resolution
-        // (buildWriteContext) and the sink assembly (buildSink). beginWrite 
re-loads it for its own
-        // begin-guard — the double-load is accepted (mirrors iceberg), 
keeping the flow simple.
+        // One fresh table generation drives validation, transaction state, 
location resolution, and sink
+        // assembly. Reloading in beginWrite would reopen a TOCTOU window 
after the schema fence.
         HmsTableInfo table = loadTable(tableHandle);
+        validateBoundWriteMetadata(table, handle);

Review Comment:
   Fixed. The plan now carries the HMS creation metadata and full effective 
metadata identity into the transaction. commit() reloads and validates that 
identity before classification and again immediately before publisher setup. 
Added same-shape recreation and post-plan schema-reorder tests that assert no 
HMS statistics are published.



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