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


##########
fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionKey.java:
##########
@@ -147,14 +144,7 @@ private static Literal getDateTimeLiteral(String value, 
Type type) throws Analys
             } else if (type.isDatetimeV2()) {
                 return new DateTimeV2Literal(value);
             } else if (type.isTimeStampTz()) {
-                DateTimeV2Literal literal = new DateTimeV2Literal(value);
-                DateTimeV2Literal dtV2Lit = (DateTimeV2Literal) 
(DateTimeExtractAndTransform.convertTz(
-                        literal,
-                        new 
StringLiteral(ConnectContext.get().getSessionVariable().timeZone),
-                        new StringLiteral("UTC")));
-                return new TimestampTzLiteral((TimeStampTzType) 
DataType.fromCatalogType(type),
-                        dtV2Lit.getYear(), dtV2Lit.getMonth(), 
dtV2Lit.getDay(),
-                        dtV2Lit.getHour(), dtV2Lit.getMinute(), 
dtV2Lit.getSecond(), dtV2Lit.getMicroSecond());
+                return 
TimestampTzLiteral.fromSessionTimeZone((TimeStampTzType) 
DataType.fromCatalogType(type), value);

Review Comment:
   This fixes the range-partition path, but the LIST partition sibling path 
still bypasses this helper. `createListPartitionKeyWithTypes()` below calls 
`values.get(i).getValue(types.get(i))`, which goes through 
`LiteralExprUtils.createLiteral()` and the legacy 
`DateLiteralUtils.createDateLiteral()` TIMESTAMPTZ parser instead of 
`TimestampTzLiteral`. That parser does not have the same named/lowercase 
timezone behavior: for `2024-01-15 20:00:00 Asia/Shanghai`, 
`getTimeZoneSplitPos()` scans back using only uppercase letters and `/`, so it 
leaves the split at the end and passes an empty zone to `ZoneId.of()`; 
lowercase values such as `uTc` are not detected by `haveTimeZoneName()` either. 
A TIMESTAMPTZ `PARTITION BY LIST(ts)` value using the same IANA-zone syntax 
accepted by the new range tests will still fail or parse differently. Please 
route TIMESTAMPTZ list partition values through this same helper, or move the 
corrected parser into the shared literal creation path, and add a LIST par
 tition regression.



##########
fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java:
##########
@@ -2014,4 +2018,286 @@ public void testAutoBuckets() throws Exception {
         // due to partition size eq 0, use previous partition's(54th) bucket 
num
         Assert.assertEquals(53, partitions.get(partitions.size() - 
1).getDistributionInfo().getBucketNum());
     }
+
+    @Test
+    public void testTimeStampTzDynamicPartition() throws Exception {
+        // Set session timezone to something different from the partition 
timezone
+        // to verify scheduler uses dynamic_partition.time_zone, not session 
timezone.
+        // With time_zone = "+00:00" and session = "Asia/Shanghai" (UTC+8),
+        // a session-timezone leak would shift bounds by 8 hours (hour=16, not 
00).
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("Asia/Shanghai");
+
+            String createOlapTblStmt = "CREATE TABLE 
test.`timestamptz_dynamic_partition` (\n"
+                    + "  `k1` TIMESTAMPTZ NULL COMMENT \"\",\n"
+                    + "  `k2` int NULL COMMENT \"\"\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`, `k2`)\n"
+                    + "PARTITION BY RANGE(`k1`)\n"
+                    + "()\n"
+                    + "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"dynamic_partition.enable\" = \"true\",\n"
+                    + "\"dynamic_partition.start\" = \"-3\",\n"
+                    + "\"dynamic_partition.end\" = \"3\",\n"
+                    + "\"dynamic_partition.create_history_partition\" = 
\"true\",\n"
+                    + "\"dynamic_partition.time_unit\" = \"day\",\n"
+                    + "\"dynamic_partition.prefix\" = \"p\",\n"
+                    + "\"dynamic_partition.buckets\" = \"1\",\n"
+                    + "\"dynamic_partition.time_zone\" = \"+00:00\"\n"
+                    + ");";
+            createTable(createOlapTblStmt);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("timestamptz_dynamic_partition");
+            Assert.assertTrue(table.dynamicPartitionExists());
+
+            // Execute dynamic partition scheduling
+            Env.getCurrentEnv().getDynamicPartitionScheduler()
+                    .executeDynamicPartitionFirstTime(db.getId(), 
table.getId());
+
+            // Verify total partitions (7 = start(-3) to end(3), inclusive)
+            int partitionCount = table.getPartitionNames().size();
+            Assert.assertEquals(7, partitionCount);
+
+            // Verify partition names are clean and partition values are UTC 
timestamps
+            RangePartitionInfo partitionInfo = (RangePartitionInfo) 
table.getPartitionInfo();
+            for (Map.Entry<Long, PartitionItem> entry : 
partitionInfo.getIdToItem(false).entrySet()) {
+                RangePartitionItem item = (RangePartitionItem) 
entry.getValue();
+
+                // Verify the partition name is clean
+                String partitionName = 
table.getPartition(entry.getKey()).getName();
+                Assert.assertTrue("Partition name should start with 'p': " + 
partitionName,
+                        partitionName.startsWith("p"));
+                Assert.assertEquals("Partition name should be exactly 9 chars 
(p + yyyyMMdd): " + partitionName,
+                        9, partitionName.length());
+
+                // Verify the range endpoints are valid and correctly ordered
+                Range<PartitionKey> range = item.getItems();
+                PartitionKey lower = range.lowerEndpoint();
+                PartitionKey upper = range.upperEndpoint();
+                Assert.assertTrue("lower must be < upper: " + range,
+                        lower.compareTo(upper) < 0);
+
+                // Verify partition keys are UTC timestamps (with +00:00 
suffix)
+                List<LiteralExpr> lowerKeys = lower.getKeys();
+                Assert.assertEquals(1, lowerKeys.size());
+                String lowerStr = lowerKeys.get(0).getStringValue();
+                Assert.assertTrue("Lower key must be UTC with +00:00 suffix: " 
+ lowerStr,
+                        lowerStr.contains("+00:00"));
+
+                List<LiteralExpr> upperKeys = upper.getKeys();
+                Assert.assertEquals(1, upperKeys.size());
+                String upperStr = upperKeys.get(0).getStringValue();
+                Assert.assertTrue("Upper key must be UTC with +00:00 suffix: " 
+ upperStr,
+                        upperStr.contains("+00:00"));
+
+                // With time_zone = "+00:00", partition boundaries must be 
midnight UTC
+                // (hour = 00). If the scheduler incorrectly used session 
timezone
+                // (Asia/Shanghai, UTC+8), the hour would be 16 instead of 00.
+                String lowerHour = lowerStr.substring(11, 13);
+                Assert.assertEquals("Lower bound hour should be 00 (midnight 
UTC), proving"
+                        + " dynamic_partition.time_zone was used: " + lowerStr,
+                        "00", lowerHour);
+            }
+
+            for (Partition partition : table.getPartitions()) {
+                RangePartitionItem item = (RangePartitionItem) 
partitionInfo.getItem(partition.getId());
+                Assert.assertNotNull("Each partition should have a range 
item", item);
+            }
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testTimeStampTzDynamicPartitionWeekUnit() throws Exception {
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("Europe/London");
+
+            String createOlapTblStmt = "CREATE TABLE 
test.`timestamptz_dynamic_week` (\n"
+                    + "  `k1` TIMESTAMPTZ NULL COMMENT \"\",\n"
+                    + "  `k2` int NULL COMMENT \"\"\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`, `k2`)\n"
+                    + "PARTITION BY RANGE(`k1`)\n"
+                    + "()\n"
+                    + "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"dynamic_partition.enable\" = \"true\",\n"
+                    + "\"dynamic_partition.start\" = \"-3\",\n"
+                    + "\"dynamic_partition.end\" = \"3\",\n"
+                    + "\"dynamic_partition.create_history_partition\" = 
\"true\",\n"
+                    + "\"dynamic_partition.time_unit\" = \"week\",\n"
+                    + "\"dynamic_partition.prefix\" = \"p\",\n"
+                    + "\"dynamic_partition.buckets\" = \"1\",\n"
+                    + "\"dynamic_partition.time_zone\" = \"Asia/Tokyo\"\n"
+                    + ");";
+            createTable(createOlapTblStmt);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("timestamptz_dynamic_week");
+            Assert.assertTrue(table.dynamicPartitionExists());
+
+            Env.getCurrentEnv().getDynamicPartitionScheduler()
+                    .executeDynamicPartitionFirstTime(db.getId(), 
table.getId());
+
+            int partitionCount = table.getPartitionNames().size();
+            Assert.assertEquals(7, partitionCount);
+
+            // Verify partition names follow week pattern (clean, no timezone 
suffix)
+            RangePartitionInfo partitionInfo = (RangePartitionInfo) 
table.getPartitionInfo();
+            for (Map.Entry<Long, PartitionItem> entry : 
partitionInfo.getIdToItem(false).entrySet()) {
+                RangePartitionItem item = (RangePartitionItem) 
entry.getValue();
+                String partitionName = 
table.getPartition(entry.getKey()).getName();
+                Assert.assertTrue("Partition name should start with 'p': " + 
partitionName,
+                        partitionName.startsWith("p"));
+                // Week partition name should be like "p2026_26" (year_week)
+                Assert.assertFalse("Partition name must not contain timezone: 
" + partitionName,
+                        partitionName.contains("Asia") || 
partitionName.contains("Tokyo"));
+
+                // Verify range validity
+                Range<PartitionKey> range = item.getItems();
+                Assert.assertTrue("lower must be < upper",
+                        range.lowerEndpoint().compareTo(range.upperEndpoint()) 
< 0);
+            }
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testTimeStampTzDynamicPartitionHourUnit() throws Exception {
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("America/Chicago");
+
+            String createOlapTblStmt = "CREATE TABLE 
test.`timestamptz_dynamic_hour` (\n"
+                    + "  `k1` TIMESTAMPTZ NULL COMMENT \"\",\n"
+                    + "  `k2` int NULL COMMENT \"\"\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`, `k2`)\n"
+                    + "PARTITION BY RANGE(`k1`)\n"
+                    + "()\n"
+                    + "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"dynamic_partition.enable\" = \"true\",\n"
+                    + "\"dynamic_partition.start\" = \"-3\",\n"
+                    + "\"dynamic_partition.end\" = \"3\",\n"
+                    + "\"dynamic_partition.create_history_partition\" = 
\"true\",\n"
+                    + "\"dynamic_partition.time_unit\" = \"hour\",\n"
+                    + "\"dynamic_partition.prefix\" = \"p\",\n"
+                    + "\"dynamic_partition.buckets\" = \"1\",\n"
+                    + "\"dynamic_partition.time_zone\" = \"+00:00\"\n"
+                    + ");";
+            createTable(createOlapTblStmt);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("timestamptz_dynamic_hour");
+            Assert.assertTrue(table.dynamicPartitionExists());
+
+            Env.getCurrentEnv().getDynamicPartitionScheduler()
+                    .executeDynamicPartitionFirstTime(db.getId(), 
table.getId());
+
+            int partitionCount = table.getPartitionNames().size();
+            Assert.assertEquals(7, partitionCount);
+
+            // Hour partition names are "p" + yyyyMMddHH (10 chars)
+            RangePartitionInfo partitionInfo = (RangePartitionInfo) 
table.getPartitionInfo();
+            for (Map.Entry<Long, PartitionItem> entry : 
partitionInfo.getIdToItem(false).entrySet()) {
+                RangePartitionItem item = (RangePartitionItem) 
entry.getValue();
+                String partitionName = 
table.getPartition(entry.getKey()).getName();
+                Assert.assertTrue("Partition name should start with 'p': " + 
partitionName,
+                        partitionName.startsWith("p"));
+                // Hour partition names: p + yyyyMMddHH → length 11 (p + 10 
digits)
+                Assert.assertEquals("Hour partition name length: " + 
partitionName,
+                        11, partitionName.length());
+
+                // Verify range validity and UTC boundaries
+                Range<PartitionKey> range = item.getItems();
+                Assert.assertTrue("lower must be < upper",
+                        range.lowerEndpoint().compareTo(range.upperEndpoint()) 
< 0);
+
+                // With time_zone = "+00:00", partition boundaries should be 
UTC timestamps
+                List<LiteralExpr> lowerKeys = range.lowerEndpoint().getKeys();
+                Assert.assertEquals(1, lowerKeys.size());
+                String lowerStr = lowerKeys.get(0).getStringValue();
+                Assert.assertTrue("Lower key must have +00:00 suffix: " + 
lowerStr,
+                        lowerStr.contains("+00:00"));
+            }
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testAutoPartitionRetentionTimestampTzCutoffNormalized() throws 
Exception {
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            // Session TZ Asia/Shanghai (UTC+8). Without normalization, the
+            // buggy cutoff would be ~8h ahead of UTC, misclassifying a
+            // partition whose upper bound is just ahead of UTC now as history.
+            connectContext.getSessionVariable().setTimeZone("Asia/Shanghai");

Review Comment:
   This regression still does not exercise the background-thread case that made 
the auto-retention cutoff unsafe. `beforeClass()` creates `connectContext` with 
`UtFrameUtils.createDefaultCtx()`, which installs it as the thread-local 
context, and this test only changes that session timezone before calling 
`executeDynamicPartitionFirstTime()`. In that setup, the old suffix-free path 
would format `currentTimeStr` with `DateUtils.getTimeZone()` and parse it 
through `TimestampTzLiteral.fromSessionTimeZone()` using the same thread-local 
`Asia/Shanghai` session, so the test can pass even if the new normalization in 
`getDropPartitionOpForAutoPartition()` is removed. The production scheduler 
thread normally has no `ConnectContext`: `DateUtils.getTimeZone()` falls back 
to the JVM timezone, while TIMESTAMPTZ parsing falls back to UTC. Please clear 
and restore the thread-local context and force and restore a non-UTC JVM 
default timezone around the retention execution, or otherwise inject the sou
 rce zone, so this test covers the actual no-session fallback.



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