This is an automated email from the ASF dual-hosted git repository.

zhangstar333 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 76d3b7eb922 [fix](test) Fix external_table_p0 DATETIME baselines and 
Iceberg object counting (#66970)
76d3b7eb922 is described below

commit 76d3b7eb922bfa4a6142e77fb9f458d599f731c2
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Thu Aug 20 17:35:17 2026 +0800

    [fix](test) Fix external_table_p0 DATETIME baselines and Iceberg object 
counting (#66970)
    
    ### What problem does this PR solve?
    
    Related PR: #66873
    
    Problem Summary:
    
    Two unrelated `external_table_p0` failures, both in test code only. No
    BE/FE code is touched.
    
    #### 1. `remote_doris` DATETIME baselines are stale after #66873
    
    #66873 changed the Arrow Flight result schema so `DATETIMEV2` is exposed
    as a **timezone-naive** Arrow timestamp. Both sides of the conversion
    agree — `write_column_to_arrow` and `read_column_from_arrow` both decode
    an empty Arrow timezone as UTC — so a cross-cluster `type=doris` read
    now round-trips the whole DATETIME domain.
    
    Under the previous timezone-aware encoding, `0000-01-01 00:00:00` was
    written as an epoch below the minimum representable value once the
    session timezone was applied, `from_unixtime` rejected it, and the value
    came back as an invalid datetime (rendered empty).
    
    `test_remote_doris_all_types_select` and `test_remote_doris_statistics`
    are the only suites that store that boundary value in a `DATETIME`
    column read over an arrow-flight Doris catalog, so they are the only
    ones whose baselines still record the old broken output:
    
    ```
    test_remote_doris_all_types_select.out
      scalar c_datetime :          →  0000-01-01T00:00
      array  a_datetime : [""]     →  ["0000-01-01 00:00:00"]
    
    test_remote_doris_statistics.out
      c_datetime  min/max : 2023-10-01 12:34:56 / (empty)
                          →  0000-01-01 00:00:00 / 9999-12-31 23:59:59
    ```
    
    The new values are exactly the ones the test inserts, and the refreshed
    baseline is now **identical cell for cell** to
    `test_query_remote_doris_as_olap_table_select`, which reads the same
    table definition and the same rows with `use_arrow_flight = false`.
    Making the two transports agree is what #66873 set out to do, so that
    equality is the check that these values are right.
    
    This is a master-wide failure, not specific to any PR: across ~60 recent
    `Doris_External_Regression` builds, every build whose base contains
    910b741ba32 fails these two suites, and every build with an older base
    passes them. Both are currently muted in TeamCity, so the mutes should
    be removed once this lands.
    
    #### 2. `test_iceberg_write_merge_duplicate_source_negative` is flaky
    (13 of 50 recent runs)
    
    ```
    mc: <ERROR> Unable to initialize new alias from the provided credentials.
    Get "http://172.17.0.6:19001/probe-.../?location=": dial tcp 
172.17.0.6:19001: i/o timeout
    ```
    
    The suite counted data objects by starting a throwaway `minio/mc`
    container per call. MinIO runs on the isolated `doris--iceberg` compose
    network (`168.38.0.0/24`) and only publishes its API on the host, while
    the ad-hoc container joins the default bridge — so every call had to
    hairpin from that bridge back to the published port. That path times out
    while the agent starts and stops other containers, and with four calls
    per run the suite failed about a quarter of the time. MinIO itself was
    healthy in every failing run, and this was the only suite in the repo
    using that pattern.
    
    Count the objects with the S3 SDK in the suite's own JVM instead,
    against the same endpoint the catalog is already configured with — the
    same approach `test_iceberg_variant_read.groovy` already uses. Two
    incidental bugs go away with it:
    
    - the listing now paginates over continuation tokens, so a table with
    more than 1000 objects can no longer be undercounted;
    - the prefix is anchored at `.../data/` rather than `.../data`, so a
    sibling directory sharing the prefix is not swept in.
---
 .../test_remote_doris_all_types_select.out         |  4 +-
 .../remote_doris/test_remote_doris_statistics.out  |  2 +-
 ...rg_write_merge_duplicate_source_negative.groovy | 73 +++++++++++++++-------
 3 files changed, 54 insertions(+), 25 deletions(-)

diff --git 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
index faa3cb627ab..a8823bb027e 100644
--- 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
+++ 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
@@ -1,12 +1,12 @@
 -- This file is automatically generated. You should know what you did if you 
want to edit this
 -- !sql --
-2025-05-18T01:00       true    -128    -32768  -2147483648     
-9223372036854775808    -1234567890123456790    -123.456        -123456.789     
-123457 -123456789012346        -1234567890123456789012345678   1970-01-01      
        A       Hello   Hello, Doris!   ["apple", "banana", "orange"]   
{"Emily":101, "age":25} {"f1":11, "f2":3.14, "f3":"Emily"}      
{"k1":"v31","k2":300,"k3":[123,456],"k4":[],"k5":{"i1":"iv1"}}
+2025-05-18T01:00       true    -128    -32768  -2147483648     
-9223372036854775808    -1234567890123456790    -123.456        -123456.789     
-123457 -123456789012346        -1234567890123456789012345678   1970-01-01      
0000-01-01T00:00        A       Hello   Hello, Doris!   ["apple", "banana", 
"orange"]   {"Emily":101, "age":25} {"f1":11, "f2":3.14, "f3":"Emily"}      
{"k1":"v31","k2":300,"k3":[123,456],"k4":[],"k5":{"i1":"iv1"}}
 2025-05-18T02:00       \N      \N      \N      \N      \N      \N      \N      
\N      \N      \N      \N      \N      \N      \N      \N      \N      \N      
\N      \N      \N
 2025-05-18T03:00       false   127     32767   2147483647      
9223372036854775807     1234567890123456789     123.456 123456.789      123457  
123456789012346 1234567890123456789012345678    9999-12-31      
9999-12-31T23:59:59                             []      {}      {"f1":11, 
"f2":3.14, "f3":"Emily"}      {}
 2025-05-18T04:00       true    0       0       0       0       0       0.0     
0       0       0       0       2023-10-01      2023-10-01T12:34:56     A       
Hello   Hello, Doris!   ["apple", "banana", "orange"]   {"Emily":101, "age":25} 
{"f1":11, "f2":3.14, "f3":"Emily"}      []
 
 -- !sql --
-2025-05-18T01:00       [1]     [-128]  [-32768]        [-2147483648]   
[-9223372036854775808]  [-1234567890123456790]  [-123.456]      [-123456.789]   
[-123457]       [-123456789012346]      [-1234567890123456789012345678] 
["0000-01-01"]  [""]    ["A"]   ["Hello"]       ["Hello, Doris!"]
+2025-05-18T01:00       [1]     [-128]  [-32768]        [-2147483648]   
[-9223372036854775808]  [-1234567890123456790]  [-123.456]      [-123456.789]   
[-123457]       [-123456789012346]      [-1234567890123456789012345678] 
["0000-01-01"]  ["0000-01-01 00:00:00"] ["A"]   ["Hello"]       ["Hello, 
Doris!"]
 2025-05-18T02:00       [null]  [null]  [null]  [null]  [null]  [null]  [null]  
[null]  [null]  [null]  [null]  [null]  [null]  [null]  [null]  [null]
 2025-05-18T03:00       [0]     [127]   [32767] [2147483647]    
[9223372036854775807]   [1234567890123456789]   [123.456]       [123456.789]    
[123457]        [123456789012346]       [1234567890123456789012345678]  
["9999-12-31"]  ["9999-12-31 23:59:59"] [""]    [""]    [""]
 2025-05-18T04:00       [1]     [0]     [0]     [0]     [0]     [0]     [0]     
[0]     [0]     [0]     [0]     ["2023-10-01"]  ["2023-10-01 12:34:56"] ["A"]   
["Hello"]       ["Hello, Doris!"]
diff --git 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
index 1d915ff3620..eecba5cbcd6 100644
--- 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
+++ 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
@@ -4,7 +4,7 @@ c_bigint        4       3       1       -9223372036854775808    
9223372036854775807     32
 c_boolean      4       2       1       0       1       4
 c_char 4       2       1               A       2
 c_date 4       3       1       1970-01-01      9999-12-31      16
-c_datetime     4       3       1       2023-10-01 12:34:56             32
+c_datetime     4       3       1       0000-01-01 00:00:00     9999-12-31 
23:59:59     32
 c_decimal18    4       3       1       -123456789012346        123456789012346 
32
 c_decimal32    4       3       1       -1234567890123456789012345678   
1234567890123456789012345678    64
 c_decimal9     4       3       1       -123457 123457  16
diff --git 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_merge_duplicate_source_negative.groovy
 
b/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_merge_duplicate_source_negative.groovy
index 075bfccad96..17ffc29d0cd 100644
--- 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_merge_duplicate_source_negative.groovy
+++ 
b/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_merge_duplicate_source_negative.groovy
@@ -15,6 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
+import com.amazonaws.auth.AWSStaticCredentialsProvider
+import com.amazonaws.auth.BasicAWSCredentials
+import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration
+import com.amazonaws.services.s3.AmazonS3ClientBuilder
+import com.amazonaws.services.s3.model.ListObjectsV2Request
+
 suite("test_iceberg_write_merge_duplicate_source_negative",
         
"p0,external,iceberg,external_docker,external_docker_iceberg,nonConcurrent") {
     String enabled = context.config.otherConfigs.get("enableIcebergTest")
@@ -27,21 +33,36 @@ suite("test_iceberg_write_merge_duplicate_source_negative",
     String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
     String catalogName = "test_iceberg_write_merge_duplicate_source_negative"
     String dbName = "iceberg_write_merge_duplicate_source_negative_db"
-    String dockerCommand = 
context.config.otherConfigs.get("externalDockerCommand") ?: "docker"
-    String mcImage = "minio/mc:RELEASE.2025-01-17T23-25-50Z"
 
-    def countDataObjects = { String objectPath ->
-        def stdout = new StringBuilder()
-        def stderr = new StringBuilder()
-        def process = new ProcessBuilder("/bin/bash", "-c",
-                "${dockerCommand} run --rm --entrypoint /bin/sh ${mcImage} -c "
-                        + "'/usr/bin/mc alias set minio 
http://${externalEnvIp}:${minioPort} "
-                        + "admin password >/dev/null "
-                        + "&& /usr/bin/mc find ${objectPath} --type f | wc 
-l'").start()
-        process.consumeProcessOutput(stdout, stderr)
-        process.waitForOrKill(30000)
-        assertEquals(0, process.exitValue(), "Failed to list Iceberg data 
objects: ${stderr}")
-        return stdout.toString().trim() as long
+    // MinIO only publishes its API on the host, while its container sits on 
the isolated
+    // `doris--iceberg` compose network. Listing objects from this JVM reuses 
the very endpoint the
+    // catalog is configured with. Shelling out to a throwaway `minio/mc` 
container instead puts the
+    // client on the default bridge, so every call has to hairpin back to the 
published port, which
+    // intermittently times out while the agent starts and stops other 
containers.
+    def minioClient = AmazonS3ClientBuilder.standard()
+            .withEndpointConfiguration(
+                    new 
EndpointConfiguration("http://${externalEnvIp}:${minioPort}";, "us-east-1"))
+            .withPathStyleAccessEnabled(true)
+            .withCredentials(new AWSStaticCredentialsProvider(
+                    new BasicAWSCredentials("admin", "password")))
+            .build()
+
+    def countDataObjects = { String bucket, String prefix ->
+        long objectCount = 0
+        String continuationToken = null
+        while (true) {
+            def listing = minioClient.listObjectsV2(new ListObjectsV2Request()
+                    .withBucketName(bucket)
+                    .withPrefix(prefix)
+                    .withContinuationToken(continuationToken))
+            // Directory placeholder keys are not data objects, mirroring `mc 
find --type f`.
+            objectCount += listing.getObjectSummaries().count { 
!it.getKey().endsWith("/") }
+            if (!listing.isTruncated()) {
+                break
+            }
+            continuationToken = listing.getNextContinuationToken()
+        }
+        return objectCount
     }
 
     sql """drop catalog if exists ${catalogName}"""
@@ -81,11 +102,18 @@ suite("test_iceberg_write_merge_duplicate_source_negative",
     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)
+    // file_path is `s3://<bucket>/<key>`; split it into the bucket and the 
table's data prefix.
+    int schemeEnd = committedFile.indexOf("://")
+    assertTrue(schemeEnd > 0, "Unexpected Iceberg data path: ${committedFile}")
+    String bucketAndKey = committedFile.substring(schemeEnd + "://".length())
+    int bucketEnd = bucketAndKey.indexOf('/')
+    assertTrue(bucketEnd > 0, "Unexpected Iceberg data path: ${committedFile}")
+    String dataBucket = bucketAndKey.substring(0, bucketEnd)
+    String objectKey = bucketAndKey.substring(bucketEnd + 1)
+    int dataDirectoryEnd = objectKey.indexOf('/data/')
+    assertTrue(dataDirectoryEnd > 0, "Unexpected Iceberg data path: 
${committedFile}")
+    String dataObjectPrefix = objectKey.substring(0, dataDirectoryEnd + 
'/data/'.length())
+    long objectsBefore = countDataObjects(dataBucket, dataObjectPrefix)
 
     long snapshotsBefore =
             (sql """select count(*) from 
duplicate_source_target\$snapshots""")[0][0] as long
@@ -126,7 +154,7 @@ suite("test_iceberg_write_merge_duplicate_source_negative",
             (sql """select count(*) from 
duplicate_source_target\$snapshots""")[0][0] as long)
     assertEquals(filesBefore,
             (sql """select count(*) from 
duplicate_source_target\$files""")[0][0] as long)
-    assertEquals(objectsBefore, countDataObjects(dataObjectPath))
+    assertEquals(objectsBefore, countDataObjects(dataBucket, dataObjectPrefix))
     order_qt_duplicate_source_atomic_state """
         select id, region, payload
         from duplicate_source_target
@@ -135,7 +163,7 @@ suite("test_iceberg_write_merge_duplicate_source_negative",
 
     // A sibling delete close can fail only after the data side has closed 
successfully. The outer
     // MERGE still owns and must remove those unpublished data objects.
-    long siblingFailureObjectsBefore = countDataObjects(dataObjectPath)
+    long siblingFailureObjectsBefore = countDataObjects(dataBucket, 
dataObjectPrefix)
     try {
         
GetDebugPoint().enableDebugPointForAllBEs("VIcebergDeleteSink.close.inject_failure")
         test {
@@ -162,10 +190,11 @@ 
suite("test_iceberg_write_merge_duplicate_source_negative",
             (sql """select count(*) from 
duplicate_source_target\$snapshots""")[0][0] as long)
     assertEquals(filesBefore,
             (sql """select count(*) from 
duplicate_source_target\$files""")[0][0] as long)
-    assertEquals(siblingFailureObjectsBefore, countDataObjects(dataObjectPath))
+    assertEquals(siblingFailureObjectsBefore, countDataObjects(dataBucket, 
dataObjectPrefix))
     order_qt_sibling_close_atomic_state """
         select id, region, payload
         from duplicate_source_target
         order by id
     """
+    minioClient.shutdown()
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to