github-actions[bot] commented on code in PR #66246:
URL: https://github.com/apache/doris/pull/66246#discussion_r3676404505


##########
be/src/exec/sink/viceberg_merge_sink.cpp:
##########
@@ -201,10 +322,14 @@ Status VIcebergMergeSink::close(Status close_status) {
         COUNTER_SET(_delete_rows_counter, 
static_cast<int64_t>(_delete_row_count));
     }
 
-    if (!table_status.ok()) {
-        return table_status;
+    Status result_status = table_status.ok() ? delete_status : table_status;
+    if (_table_writer) {
+        _table_writer->finish_deferred_file_cleanup(result_status);

Review Comment:
   [P1] Keep MERGE files owned until the query-wide outcome
   
   `result_status` is local to this `VIcebergMergeSink`, so a locally 
successful instance clears
   both inner ownership lists here even though another exchange destination can 
later reject a
   duplicate target match. The same premature release occurs before the later 
Iceberg optimistic
   commit. In both cases FE rollback is a no-op for insert/MERGE files, leaving 
the successful
   sibling's unpublished data/delete/puffin objects orphaned. Retain paths at 
query/transaction
   scope (or provide a coordinator abort cleanup) and add a barrier-controlled 
multi-instance
   failure test.



##########
fe/fe-common/src/main/java/org/apache/doris/common/Config.java:
##########
@@ -2209,7 +2209,7 @@ public class Config extends ConfigBase {
      * Max data version of backends serialize block.
      */
     @ConfField(mutable = false)
-    public static int max_be_exec_version = 10;
+    public static int max_be_exec_version = 11;

Review Comment:
   [P1] Negotiate version 11 with every assigned backend
   
   `be_exec_version` now defaults to 11 and is copied query-wide, but no path 
derives the minimum
   capability of the BEs assigned to the query. An old BE does not reject 
version 11 at fragment
   startup in a release build, ignores the new optional sink field, and a local 
exchange bypasses
   the later block-serialization version check. A new-FE/old-BE or mixed-BE 
MERGE can therefore
   execute with cardinality checking enabled only on new sinks. Fence or 
schedule away BEs below
   version 11 (or negotiate a query-wide minimum) before setting this 
requirement, and test a
   mixed-version local-channel plan.



##########
fe/be-java-extensions/paimon-scanner/src/main/java/org/apache/doris/paimon/PaimonColumnValue.java:
##########
@@ -197,11 +197,12 @@ public void unpackMap(List<ColumnValue> keys, 
List<ColumnValue> values) {
 
     @Override
     public void unpackStruct(List<Integer> structFieldIndex, List<ColumnValue> 
values) {
-        // todo: support pruned struct fields
-        InternalRow row = record.getRow(idx, structFieldIndex.size());
+        RowType rowType = (RowType) dataType;
+        // Projection entries are original child indexes, so the binary row 
must keep the full RowType arity.
+        InternalRow row = record.getRow(idx, rowType.getFieldCount());
         for (int i : structFieldIndex) {
             values.add(new PaimonColumnValue(row, i, 
dorisType.getChildTypes().get(i),

Review Comment:
   [P1] Resolve pruned struct children to their original Paimon indexes
   
   Using the full Paimon row arity is correct, but production does not pass 
original child indexes
   here. Nereids rebuilds a pruned Doris `StructType`, and `VectorColumn` 
consequently supplies
   dense indexes `0..N-1`. Selecting only `s.c` from `struct<a,b,c>` therefore 
passes `i == 0`
   against the full Paimon row and decodes `a` as the sole Doris child (or 
fails on a type
   mismatch). The new test manually passes `[2]` with a full Doris struct, 
which production cannot
   construct. Map dense output children to full-row fields by name/original 
index and add a
   scanner-shaped pruned test.



##########
be/src/exec/sink/writer/iceberg/viceberg_partition_writer.cpp:
##########
@@ -61,9 +63,8 @@ Status VIcebergPartitionWriter::open(RuntimeState* state, 
RuntimeProfile* profil
     if (!_write_info.broker_addresses.empty()) {
         fs_properties.broker_addresses = &(_write_info.broker_addresses);
     }
-    io::FileDescription file_description = {
-            .path = fmt::format("{}/{}", _write_info.write_path, 
_get_target_file_name()),
-            .fs_name {}};
+    _path = fmt::format("{}/{}", _write_info.write_path, 
_get_target_file_name());
+    io::FileDescription file_description = {.path = _path, .fs_name {}};
     _fs = DORIS_TRY(FileFactory::create_fs(fs_properties, file_description));
     io::FileWriterOptions file_writer_options = {.used_by_s3_committer = 
false};
     RETURN_IF_ERROR(_fs->create_file(file_description.path, &_file_writer, 
&file_writer_options));

Review Comment:
   [P1] Register the data file before `open()` can fail
   
   `create_file()` runs before compression and transformer validation, but the 
dynamic-partition
   caller inserts the writer into `_partitions_to_writers` only after `open()` 
succeeds. The new
   closed-file callback also runs only from a successful `close()`. A 
post-create failure such as
   unsupported Parquet GZ therefore destroys the only writer reference; the 
HDFS writer destructor
   closes the handle but does not delete the physical path. Please transfer 
ownership immediately
   after creation (or use an open-scope delete guard) and cover this boundary 
with a physical
   object-count test.



##########
be/src/exec/sink/writer/iceberg/viceberg_table_writer.cpp:
##########
@@ -486,9 +487,36 @@ Status VIcebergTableWriter::close(Status status) {
         COUNTER_SET(_close_timer, _close_ns);
         COUNTER_SET(_write_file_counter, _write_file_count);
     }
+    if (!status.ok() || !result_status.ok()) {

Review Comment:
   [P1] Do not release rolled files on the pre-defer close status
   
   `VIcebergSortWriter::_close_locked()` returns `close_status` while the 
`Defer` that assigns the
   underlying partition-writer close result is still alive, so C++ materializes 
an OK return value
   before that close runs. This new success branch then clears the rolled-file 
ownership even when
   the final Parquet/ORC close or commit-data build failed, allowing only the 
earlier rolled subset
   to be committed. Run the underlying close before forming the return value 
and add a sorted
   final-close failure test.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateTableCommand.java:
##########
@@ -99,6 +101,19 @@ public void run(ConnectContext ctx, StmtExecutor executor) 
throws Exception {
             LOG.debug("Nereids start to execute the ctas command, query id: 
{}, tableName: {}",
                     ctx.queryId(), createTableInfo.getTableName());
         }
+        LogicalPlan sinkQuery = null;
+        if (!createTableInfo.isIfNotExists()) {
+            // An existence probe is used only to preserve the catalog 
diagnostic; creation still
+            // goes through the atomic catalog API and is the sole proof of 
ownership.
+            if (targetTableExists(ctx)) {
+                throw new 
AnalysisException(ErrorCode.ERR_TABLE_EXISTS_ERROR.formatErrorMsg(
+                        createTableInfo.getTableName()));
+            }
+            // Reject unsupported destinations before publishing metadata; 
rollback by table name
+            // cannot distinguish this CTAS table from a concurrent 
replacement with the same name.
+            sinkQuery = 
UnboundTableSinkCreator.createUnboundTableSink(createTableInfo.getTableNameParts(),

Review Comment:
   [P2] Validate the catalog before eager sink construction
   
   With an explicit engine, `paddingEngineName()` skips catalog lookup and
   `checkEngineWithCatalog()` falls through when the catalog is absent. 
`targetTableExists()` then
   returns false, so this new eager call reaches `UnboundTableSinkCreator`, 
where
   `curCatalog.getClass()` throws a null dereference instead of the catalog 
API's normal
   `Unknown catalog` diagnostic. Validate catalog existence before this call 
(or make the factory's
   null path explicit) and add an explicit-engine missing-catalog CTAS test.



##########
be/src/exec/sink/writer/iceberg/partition_transformers.h:
##########
@@ -167,7 +167,10 @@ class StringTruncatePartitionColumnTransform : public 
PartitionColumnTransform {
 
         // Create a temp_block to execute substring function.
         Block temp_block;
-        temp_block.insert(column_with_type_and_name);
+        // Substring requires the physical ColumnString; preserve nullability 
separately and
+        // restore the original null map after transforming the nested values.
+        temp_block.insert({string_column_ptr, 
remove_nullable(column_with_type_and_name.type),

Review Comment:
   [P1] Unwrap constant nullable strings before truncate
   
   Iceberg `UPDATE ... SET nullable_truncate_col = CAST(NULL AS STRING)` 
reaches the exchange
   transform as `ColumnConst(ColumnNullable)`: UPDATE preserves the literal 
alias, and
   `MergePartitioner` applies this transform before its later const 
materialization. The top-level
   nullable check misses the wrapper; `SubstringUtil` unwraps the const and 
then asserts that the
   remaining `ColumnNullable` is a `ColumnString`, causing the update to fail 
before the writer.
   Materialize before checking nullability (or skip a constant transform for 
routing) and add a
   `ColumnConst(ColumnNullable)`/literal-UPDATE regression.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateTableCommand.java:
##########
@@ -107,12 +122,16 @@ public void run(ConnectContext ctx, StmtExecutor 
executor) throws Exception {
             throw new AnalysisException(e.getMessage(), e.getCause());
         }
 
-        query = 
UnboundTableSinkCreator.createUnboundTableSink(createTableInfo.getTableNameParts(),
-                ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), 
query);
         try {
+            if (sinkQuery == null) {
+                // IF NOT EXISTS must honor the catalog's atomic 
existing-table result before sink
+                // validation, otherwise an unsupported connector turns the 
required no-op into an error.
+                sinkQuery = 
UnboundTableSinkCreator.createUnboundTableSink(createTableInfo.getTableNameParts(),

Review Comment:
   [P2] Do not roll back an `IF NOT EXISTS` CTAS by table name
   
   For an absent Paimon target this path creates the table, then performs 
unsupported-sink
   validation. If validation fails, `handleFallbackFailedCtas()` carries only
   `(catalog,database,table)` into a name-based drop. A concurrent client can 
drop the empty table
   and create a replacement before fallback, causing Doris to delete the 
replacement it does not
   own. Make validation/creation atomic or carry a connector-issued identity 
into a conditional
   rollback, and cover the replacement interleaving.



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