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


##########
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());

Review Comment:
   [P1] Register the data file before `open()` can fail
   
   `_path` becomes a physical file before compression validation and 
transformer initialization, but the dynamic-partition caller adds this writer 
to `_partitions_to_writers` only after `open()` succeeds. For example, an 
Iceberg Parquet table with `write.parquet.compression-codec=gzip` reaches 
`create_file()` with `TFileCompressType::GZ` and then returns `Unsupported 
compress type`; the local writer is destroyed before either the active-writer 
cleanup or this new closed-file callback owns the path. `HdfsFileWriter` only 
closes that empty file in its destructor, so a failed MERGE/UPDATE leaves it 
orphaned. Transfer cleanup ownership immediately after creation, or 
register/close the dynamic writer on open failure, and cover this post-create 
failure path with an object-store assertion.
   



##########
regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_merge_duplicate_source_negative.groovy:
##########
@@ -68,35 +78,94 @@ suite("test_iceberg_write_merge_duplicate_source_negative",
     """
     sql """insert into duplicate_source_target values (1, 'A', 'committed')"""
 
+    String committedFile = (sql """
+        select file_path from duplicate_source_target\$files order by 
file_path limit 1
+    """)[0][0].toString()
+    int dataDirectoryEnd = committedFile.indexOf('/data/') + '/data'.length()
+    assertTrue(dataDirectoryEnd >= '/data'.length(), "Unexpected Iceberg data 
path: ${committedFile}")
+    String dataObjectPath = committedFile.substring(0, dataDirectoryEnd)
+            .replaceFirst('^s3a?://warehouse', 'minio/warehouse')
+    long objectsBefore = countDataObjects(dataObjectPath)
+
     long snapshotsBefore =
             (sql """select count(*) from 
duplicate_source_target\$snapshots""")[0][0] as long
     long filesBefore =
             (sql """select count(*) from 
duplicate_source_target\$files""")[0][0] as long
 
     // Negative scenario: Iceberg MERGE cardinality permits only one source row
-    // to update a target row. The entire statement must fail before 
publishing.
+    // to update a target row. Tiny blocks and files force a valid row to roll 
before
+    // the late duplicate, and the entire statement must still leave no data 
object behind.
+    sql """set batch_size = 1"""
+    // A single pipeline instance preserves the ordered source sequence needed 
to prove late cleanup.

Review Comment:
   [P2] Make the late-rollover sequence deterministic
   
   `parallel_pipeline_task_num=1` does not make this a single global sink. The 
new `MERGE_PARTITIONED` path sends the unmatched INSERT by its Iceberg 
partition while both matched UPDATE rows use the row-id channel, so the 
duplicate can fail in one `VIcebergMergeSink` before the insert channel has 
closed any data file. In that execution the object count still stays unchanged 
and this passes without exercising `_closed_files` cleanup. This is a distinct 
follow-up to the existing rollover thread: add a barrier/failpoint that 
confirms a data file has closed and been recorded before releasing the 
duplicate row, then keep the object-store assertion.
   



##########
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 recognized engine, a missing catalog reaches this line: 
`paddingEngineName()` only checks catalog existence when the engine is omitted, 
`checkEngineWithCatalog()` falls through on a null catalog, and 
`targetTableExists()` returns false. `createUnboundTableSink()` then evaluates 
`curCatalog.getClass()` and throws a `NullPointerException`, whereas the former 
`Env.createTable()` ordering returned `Unknown catalog`. This is distinct from 
the existing Paimon-target diagnostic thread because neither the catalog nor 
the table exists here. Resolve the catalog before sink prevalidation (or make 
the factory report the standard error) and cover explicit-engine CTAS against 
an unknown catalog.
   



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