This is an automated email from the ASF dual-hosted git repository.
Gabriel39 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 d3772cb05c9 [fix](iceberg) Preserve timestamp precision and semantics
in metadata scans (#68985)
d3772cb05c9 is described below
commit d3772cb05c967e29667d575f2e1c92209dc7c363
Author: Oliveira <[email protected]>
AuthorDate: Thu Oct 15 09:51:50 2026 +0800
[fix](iceberg) Preserve timestamp precision and semantics in metadata scans
(#68985)
### What problem does this PR solve?
Issue Number: N/A
Related PR: #68276 (supersedes its metadata-scanner fix; this PR targets
master)
Problem Summary:
Iceberg metadata table timestamps are encoded as epoch microseconds. The
JNI scanner converts them through milliseconds, losing fractional
precision. It also applies the session time zone to
timestamp-without-zone partition values, changing their wall-clock
value.
For example, a `$files.partition` timestamp of `1234567` microseconds is
truncated to `1970-01-01 00:00:01.234000` rather than `1970-01-01
00:00:01.234567`. Switching from UTC to Asia/Shanghai incorrectly shifts
timestamp-without-zone values by eight hours.
This PR:
- Decodes signed epoch microseconds with floor division/modulus,
preserving pre-epoch fractions.
- Propagates Iceberg types through struct/list/map values to distinguish
wall-clock timestamps from instants.
- Resolves requested field types by name while continuing to read
projected rows by position.
- Adds unit tests and a `$files.partition` regression covering
`-1000001`, `-1`, `1`, `999`, `1000`, `1234567`, and NULL,
UTC/Asia/Shanghai, both timestamp mapping settings, and a projected
partition child.
Scope is the Java Iceberg metadata scanner. This PR does not modify
regular data-table timestamp reads or the FE/native `$position_deletes`
serialization path. There are no storage-format or configuration
changes.
### Release note
Fix microsecond precision loss and incorrect session-time-zone shifts
for timestamp-without-zone values in Iceberg metadata tables.
### Check List (For Author)
- Test
- [x] Regression test
- `test_iceberg_files_timestamp_partition`: generated the golden output,
checked its timestamp values, and reran normal comparison successfully
(1 suite, 0 failures/skips).
- `test_iceberg_system_table_projection`: passed.
- `test_iceberg_position_deletes_sys_table`: passed.
- [x] Unit Test
- `IcebergSysTableColumnValueTest`: 2 tests passed; the
FE/Java-extension reactor completed successfully.
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason
Local External tests used an isolated Iceberg fixture stack and the
newly packaged metadata-scanner plugin deployed to the personal Doris
BE. The full FE/BE deployment build was interrupted during C++ submodule
downloads; FE/BE executables were not rebuilt for these runs. These are
targeted local results, not full community External/P0/coverage results.
- Behavior changed:
- [ ] No.
- [x] Yes. Metadata timestamps retain microseconds;
timestamp-without-zone values no longer shift with the session time
zone. Timestamp-with-zone values preserve their instant semantics.
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.../doris/iceberg/IcebergSysTableColumnValue.java | 36 +++++++---
.../doris/iceberg/IcebergSysTableJniScanner.java | 11 ++-
.../iceberg/IcebergSysTableColumnValueTest.java | 80 ++++++++++++++++++++++
.../test_iceberg_files_timestamp_partition.out | 73 ++++++++++++++++++++
.../iceberg/write/test_iceberg_write_stats2.out | 4 +-
.../test_iceberg_files_timestamp_partition.groovy | 72 +++++++++++++++++++
6 files changed, 265 insertions(+), 11 deletions(-)
diff --git
a/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableColumnValue.java
b/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableColumnValue.java
index 5857872de4e..a010c5dc213 100644
---
a/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableColumnValue.java
+++
b/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableColumnValue.java
@@ -20,6 +20,8 @@ package org.apache.doris.iceberg;
import org.apache.doris.jni.spi.vec.ColumnValue;
import org.apache.iceberg.StructLike;
+import org.apache.iceberg.types.Type;
+import org.apache.iceberg.types.Types;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -38,14 +40,20 @@ public class IcebergSysTableColumnValue implements
ColumnValue {
private final Object fieldData;
private final String timezone;
+ private final Type icebergType;
public IcebergSysTableColumnValue(Object fieldData) {
this(fieldData, DEFAULT_TIME_ZONE);
}
public IcebergSysTableColumnValue(Object fieldData, String timezone) {
+ this(fieldData, timezone, null);
+ }
+
+ public IcebergSysTableColumnValue(Object fieldData, String timezone, Type
icebergType) {
this.fieldData = fieldData;
this.timezone = timezone;
+ this.icebergType = icebergType;
}
@Override
@@ -136,14 +144,22 @@ public class IcebergSysTableColumnValue implements
ColumnValue {
@Override
public LocalDateTime getDateTime() {
- Instant instant = Instant.ofEpochMilli((((long) fieldData) / 1000));
- return LocalDateTime.ofInstant(instant, ZoneId.of(timezone));
+ // NTZ is a wall-clock value; only timestamptz mapped to DATETIME uses
the session zone.
+ ZoneId zone = icebergType instanceof Types.TimestampType
+ && !((Types.TimestampType) icebergType).shouldAdjustToUTC()
+ ? ZoneOffset.UTC : ZoneId.of(timezone);
+ return LocalDateTime.ofInstant(timestampInstant(), zone);
}
@Override
public LocalDateTime getTimeStampTz() {
- Instant instant = Instant.ofEpochMilli((((long) fieldData) / 1000));
- return LocalDateTime.ofInstant(instant, ZoneId.of("UTC"));
+ return LocalDateTime.ofInstant(timestampInstant(), ZoneOffset.UTC);
+ }
+
+ private Instant timestampInstant() {
+ long micros = (long) fieldData;
+ return Instant.ofEpochSecond(Math.floorDiv(micros, 1_000_000L),
+ Math.floorMod(micros, 1_000_000L) * 1_000L);
}
@Override
@@ -162,7 +178,8 @@ public class IcebergSysTableColumnValue implements
ColumnValue {
public void unpackArray(List<ColumnValue> values) {
List<?> items = (List<?>) fieldData;
for (Object item : items) {
- values.add(new IcebergSysTableColumnValue(item, timezone));
+ values.add(new IcebergSysTableColumnValue(item, timezone,
+ icebergType == null ? null :
icebergType.asListType().elementType()));
}
}
@@ -170,8 +187,10 @@ public class IcebergSysTableColumnValue implements
ColumnValue {
public void unpackMap(List<ColumnValue> keys, List<ColumnValue> values) {
Map<?, ?> data = (Map<?, ?>) fieldData;
data.forEach((key, value) -> {
- keys.add(new IcebergSysTableColumnValue(key, timezone));
- values.add(new IcebergSysTableColumnValue(value, timezone));
+ keys.add(new IcebergSysTableColumnValue(key, timezone,
+ icebergType == null ? null :
icebergType.asMapType().keyType()));
+ values.add(new IcebergSysTableColumnValue(value, timezone,
+ icebergType == null ? null :
icebergType.asMapType().valueType()));
});
}
@@ -180,7 +199,8 @@ public class IcebergSysTableColumnValue implements
ColumnValue {
StructLike record = (StructLike) fieldData;
for (Integer fieldIndex : structFieldIndex) {
Object rawValue = record.get(fieldIndex, Object.class);
- values.add(new IcebergSysTableColumnValue(rawValue, timezone));
+ values.add(new IcebergSysTableColumnValue(rawValue, timezone,
+ icebergType == null ? null :
icebergType.asStructType().fields().get(fieldIndex).type()));
}
}
}
diff --git
a/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableJniScanner.java
b/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableJniScanner.java
index cd99848ccb9..df6f6ae5124 100644
---
a/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableJniScanner.java
+++
b/fe/be-java-extensions/iceberg-metadata-scanner/src/main/java/org/apache/doris/iceberg/IcebergSysTableJniScanner.java
@@ -27,6 +27,7 @@ import com.google.common.base.Preconditions;
import org.apache.iceberg.FileScanTask;
import org.apache.iceberg.StructLike;
import org.apache.iceberg.io.CloseableIterator;
+import org.apache.iceberg.types.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,6 +45,7 @@ public class IcebergSysTableJniScanner extends JniScanner {
private final PreExecutionAuthenticator preExecutionAuthenticator;
private final FileScanTask scanTask;
private final int requiredFieldCount;
+ private final Type[] icebergTypes;
private final String timezone;
private CloseableIterator<StructLike> reader;
@@ -57,6 +59,13 @@ public class IcebergSysTableJniScanner extends JniScanner {
"required_fields should not be empty");
String[] requiredFields = requiredFieldsParam.split(",");
this.requiredFieldCount = requiredFields.length;
+ this.icebergTypes = new Type[requiredFieldCount];
+ for (int i = 0; i < requiredFieldCount; i++) {
+ // Resolve types by name, but read projected ROWS by position
below. StaticDataTask.schema()
+ // can be the full schema even when its rows contain only the
requested fields.
+ this.icebergTypes[i] =
scanTask.schema().findType(requiredFields[i]);
+ Preconditions.checkNotNull(icebergTypes[i], "Missing Iceberg
metadata field %s", requiredFields[i]);
+ }
this.timezone = params.getOrDefault("time_zone",
TimeZone.getDefault().getID());
Map<String, String> hadoopOptionParams = params.entrySet().stream()
.filter(kv -> kv.getKey().startsWith(HADOOP_OPTION_PREFIX))
@@ -104,7 +113,7 @@ public class IcebergSysTableJniScanner extends JniScanner {
// narrowed StructProjection, so a full-schema ordinal
overruns the projected row (upstream
// #65262 -- reverting this to a by-name/schema() lookup
reintroduces ArrayIndexOutOfBounds).
Object value = row.get(i, Object.class);
- ColumnValue columnValue = new
IcebergSysTableColumnValue(value, timezone);
+ ColumnValue columnValue = new
IcebergSysTableColumnValue(value, timezone, icebergTypes[i]);
appendData(i, columnValue);
}
rows++;
diff --git
a/fe/be-java-extensions/iceberg-metadata-scanner/src/test/java/org/apache/doris/iceberg/IcebergSysTableColumnValueTest.java
b/fe/be-java-extensions/iceberg-metadata-scanner/src/test/java/org/apache/doris/iceberg/IcebergSysTableColumnValueTest.java
new file mode 100644
index 00000000000..1c4186449f5
--- /dev/null
+++
b/fe/be-java-extensions/iceberg-metadata-scanner/src/test/java/org/apache/doris/iceberg/IcebergSysTableColumnValueTest.java
@@ -0,0 +1,80 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.iceberg;
+
+import org.apache.doris.jni.spi.vec.ColumnValue;
+
+import org.apache.iceberg.PartitionData;
+import org.apache.iceberg.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class IcebergSysTableColumnValueTest {
+ @Test
+ public void preservesMicrosAndTimestampSemantics() {
+ long[] micros = {-1000001L, -1L, 1L, 999L, 1000L, 1234567L};
+ String[] expected = {"1969-12-31T23:59:58.999999",
"1969-12-31T23:59:59.999999",
+ "1970-01-01T00:00:00.000001", "1970-01-01T00:00:00.000999",
+ "1970-01-01T00:00:00.001", "1970-01-01T00:00:01.234567"};
+ for (int i = 0; i < micros.length; i++) {
+ LocalDateTime utc = LocalDateTime.parse(expected[i]);
+ for (String zone : Arrays.asList("UTC", "Asia/Shanghai")) {
+ ColumnValue ntz = new IcebergSysTableColumnValue(micros[i],
zone, Types.TimestampType.withoutZone());
+ ColumnValue tz = new IcebergSysTableColumnValue(micros[i],
zone, Types.TimestampType.withZone());
+ Assertions.assertEquals(utc, ntz.getDateTime());
+ Assertions.assertEquals(zone.equals("UTC") ? utc :
utc.plusHours(8), tz.getDateTime());
+ Assertions.assertEquals(utc, tz.getTimeStampTz());
+ }
+ }
+ }
+
+ @Test
+ public void propagatesTypesThroughNestedContainers() {
+ Types.StructType struct = Types.StructType.of(
+ Types.NestedField.optional(1, "ts",
Types.TimestampType.withoutZone()),
+ Types.NestedField.optional(2, "tz",
Types.TimestampType.withZone()));
+ PartitionData record = new PartitionData(struct);
+ record.set(0, -1L);
+ record.set(1, -1L);
+ List<ColumnValue> fields = new ArrayList<>();
+ new IcebergSysTableColumnValue(record, "Asia/Shanghai", struct)
+ .unpackStruct(Arrays.asList(1, 0), fields);
+ LocalDateTime utc = LocalDateTime.parse("1969-12-31T23:59:59.999999");
+ Assertions.assertEquals(utc.plusHours(8), fields.get(0).getDateTime());
+ Assertions.assertEquals(utc, fields.get(1).getDateTime());
+
+ List<ColumnValue> elements = new ArrayList<>();
+ new IcebergSysTableColumnValue(Collections.singletonList(-1L),
"Asia/Shanghai",
+ Types.ListType.ofOptional(3,
Types.TimestampType.withoutZone())).unpackArray(elements);
+ Assertions.assertEquals(utc, elements.get(0).getDateTime());
+
+ List<ColumnValue> keys = new ArrayList<>();
+ List<ColumnValue> values = new ArrayList<>();
+ new IcebergSysTableColumnValue(Collections.singletonMap(-1L, -1L),
"Asia/Shanghai",
+ Types.MapType.ofOptional(4, 5,
Types.TimestampType.withoutZone(), Types.TimestampType.withZone()))
+ .unpackMap(keys, values);
+ Assertions.assertEquals(utc, keys.get(0).getDateTime());
+ Assertions.assertEquals(utc.plusHours(8), values.get(0).getDateTime());
+ }
+}
diff --git
a/regression-test/data/external_table_p0/iceberg/test_iceberg_files_timestamp_partition.out
b/regression-test/data/external_table_p0/iceberg/test_iceberg_files_timestamp_partition.out
new file mode 100644
index 00000000000..2a605bebeea
--- /dev/null
+++
b/regression-test/data/external_table_p0/iceberg/test_iceberg_files_timestamp_partition.out
@@ -0,0 +1,73 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !false_UTC --
+{"ts":"1969-12-31 23:59:58.999999", "tz":"1969-12-31 23:59:58.999999+00:00"}
+{"ts":"1969-12-31 23:59:59.999999", "tz":"1969-12-31 23:59:59.999999+00:00"}
+{"ts":"1970-01-01 00:00:00.000001", "tz":"1970-01-01 00:00:00.000001+00:00"}
+{"ts":"1970-01-01 00:00:00.000999", "tz":"1970-01-01 00:00:00.000999+00:00"}
+{"ts":"1970-01-01 00:00:00.001000", "tz":"1970-01-01 00:00:00.001000+00:00"}
+{"ts":"1970-01-01 00:00:01.234567", "tz":"1970-01-01 00:00:01.234567+00:00"}
+{"ts":null, "tz":null}
+
+-- !false_UTC_ntz --
+\N
+1969-12-31T23:59:58.999999
+1969-12-31T23:59:59.999999
+1970-01-01T00:00:00.000001
+1970-01-01T00:00:00.000999
+1970-01-01T00:00:00.001
+1970-01-01T00:00:01.234567
+
+-- !false_Asia_Shanghai --
+{"ts":"1969-12-31 23:59:58.999999", "tz":"1970-01-01 07:59:58.999999+08:00"}
+{"ts":"1969-12-31 23:59:59.999999", "tz":"1970-01-01 07:59:59.999999+08:00"}
+{"ts":"1970-01-01 00:00:00.000001", "tz":"1970-01-01 08:00:00.000001+08:00"}
+{"ts":"1970-01-01 00:00:00.000999", "tz":"1970-01-01 08:00:00.000999+08:00"}
+{"ts":"1970-01-01 00:00:00.001000", "tz":"1970-01-01 08:00:00.001000+08:00"}
+{"ts":"1970-01-01 00:00:01.234567", "tz":"1970-01-01 08:00:01.234567+08:00"}
+{"ts":null, "tz":null}
+
+-- !false_Asia_Shanghai_ntz --
+\N
+1969-12-31T23:59:58.999999
+1969-12-31T23:59:59.999999
+1970-01-01T00:00:00.000001
+1970-01-01T00:00:00.000999
+1970-01-01T00:00:00.001
+1970-01-01T00:00:01.234567
+
+-- !true_UTC --
+{"ts":"1969-12-31 23:59:58.999999", "tz":"1969-12-31 23:59:58.999999+00:00"}
+{"ts":"1969-12-31 23:59:59.999999", "tz":"1969-12-31 23:59:59.999999+00:00"}
+{"ts":"1970-01-01 00:00:00.000001", "tz":"1970-01-01 00:00:00.000001+00:00"}
+{"ts":"1970-01-01 00:00:00.000999", "tz":"1970-01-01 00:00:00.000999+00:00"}
+{"ts":"1970-01-01 00:00:00.001000", "tz":"1970-01-01 00:00:00.001000+00:00"}
+{"ts":"1970-01-01 00:00:01.234567", "tz":"1970-01-01 00:00:01.234567+00:00"}
+{"ts":null, "tz":null}
+
+-- !true_UTC_ntz --
+\N
+1969-12-31T23:59:58.999999
+1969-12-31T23:59:59.999999
+1970-01-01T00:00:00.000001
+1970-01-01T00:00:00.000999
+1970-01-01T00:00:00.001
+1970-01-01T00:00:01.234567
+
+-- !true_Asia_Shanghai --
+{"ts":"1969-12-31 23:59:58.999999", "tz":"1970-01-01 07:59:58.999999+08:00"}
+{"ts":"1969-12-31 23:59:59.999999", "tz":"1970-01-01 07:59:59.999999+08:00"}
+{"ts":"1970-01-01 00:00:00.000001", "tz":"1970-01-01 08:00:00.000001+08:00"}
+{"ts":"1970-01-01 00:00:00.000999", "tz":"1970-01-01 08:00:00.000999+08:00"}
+{"ts":"1970-01-01 00:00:00.001000", "tz":"1970-01-01 08:00:00.001000+08:00"}
+{"ts":"1970-01-01 00:00:01.234567", "tz":"1970-01-01 08:00:01.234567+08:00"}
+{"ts":null, "tz":null}
+
+-- !true_Asia_Shanghai_ntz --
+\N
+1969-12-31T23:59:58.999999
+1969-12-31T23:59:59.999999
+1970-01-01T00:00:00.000001
+1970-01-01T00:00:00.000999
+1970-01-01T00:00:00.001
+1970-01-01T00:00:01.234567
+
diff --git
a/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_stats2.out
b/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_stats2.out
index de452a61eaa..df313184df9 100644
---
a/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_stats2.out
+++
b/regression-test/data/external_table_p0/iceberg/write/test_iceberg_write_stats2.out
@@ -7,7 +7,7 @@ true 11 111 1.1 1.1 1111 1234.5678
1234.567890 123456789012345678.123456789012 a
0 PARQUET 2 {1:2, 2:2, 3:2, 4:2, 5:2, 6:2, 7:2, 8:2, 9:2, 10:2,
11:2, 12:2} {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
{1:0x00, 2:0x0B000000, 3:0x6F00000000000000, 4:0xCDCC8C3F,
5:0x9A9999999999F13F, 6:0x0457, 7:0x00BC614E, 8:0x499602D2,
9:0x018EE90FF6C373E0393713FA14, 10:0x616161, 11:0x9E4B0000,
12:0x005CE70F33F10500} {1:0x01, 2:0x16000000, 3:0xDE00000000000000,
4:0xCDCC0C40, 5:0x9A99999999990140, 6:0x08AE, 7:0x05397FB1, 8:0x020A75E124,
9:0x0C7748819DFFB62505316873C [...]
-- !sql_2 --
-{"bigint_col":{"column_size":74, "value_count":2, "null_value_count":0,
"nan_value_count":null, "lower_bound":111, "upper_bound":222},
"boolean_col":{"column_size":33, "value_count":2, "null_value_count":0,
"nan_value_count":null, "lower_bound":0, "upper_bound":1},
"date_col":{"column_size":66, "value_count":2, "null_value_count":0,
"nan_value_count":null, "lower_bound":"2023-01-01",
"upper_bound":"2023-06-15"}, "datetime_col1":{"column_size":74,
"value_count":2, "null_value_count":0, "n [...]
+{"bigint_col":{"column_size":74, "value_count":2, "null_value_count":0,
"nan_value_count":null, "lower_bound":111, "upper_bound":222},
"boolean_col":{"column_size":33, "value_count":2, "null_value_count":0,
"nan_value_count":null, "lower_bound":0, "upper_bound":1},
"date_col":{"column_size":66, "value_count":2, "null_value_count":0,
"nan_value_count":null, "lower_bound":"2023-01-01",
"upper_bound":"2023-06-15"}, "datetime_col1":{"column_size":74,
"value_count":2, "null_value_count":0, "n [...]
-- !sql_3 --
false 22 222 2.2 2.2 2222 8765.4321 8765.432100
987654321098765432.987654321099 bbb 2023-06-15 2023-06-15T23:45:01
@@ -17,7 +17,7 @@ true 11 111 1.1 1.1 1111 1234.5678
1234.567890 123456789012345678.123456789012 a
0 ORC 2 {1:2, 2:2, 3:2, 4:2, 5:2, 6:2, 7:2, 8:2, 9:2, 10:2,
11:2, 12:2} {} {1:0x00, 2:0x0B00000000000000, 3:0x6F00000000000000,
4:0xCDCC8C3F, 5:0x9A9999999999F13F, 6:0x0457, 7:0x00BC614E, 8:0x075BCD15,
9:0x018EE90FF6C373E0393713FA14, 10:0x616161, 11:0x9E4B0000,
12:0x005CE70F33F10500} {1:0x01, 2:0x1600000000000000,
3:0xDE00000000000000, 4:0xCDCC0C40, 5:0x9A99999999990140, 6:0x08AE,
7:0x05397FB1, 8:0x05397FB1, 9:0x0C7748819DFFB62505316873CB, 10:0x626262,
11:0x434C0000, 12:0x40D91FA833FE0500}
-- !sql_5 --
-{"bigint_col":{"column_size":null, "value_count":2, "null_value_count":null,
"nan_value_count":null, "lower_bound":111, "upper_bound":222},
"boolean_col":{"column_size":null, "value_count":2, "null_value_count":null,
"nan_value_count":null, "lower_bound":0, "upper_bound":1},
"date_col":{"column_size":null, "value_count":2, "null_value_count":null,
"nan_value_count":null, "lower_bound":"2023-01-01",
"upper_bound":"2023-06-15"}, "datetime_col1":{"column_size":null,
"value_count":2, "null_v [...]
+{"bigint_col":{"column_size":null, "value_count":2, "null_value_count":null,
"nan_value_count":null, "lower_bound":111, "upper_bound":222},
"boolean_col":{"column_size":null, "value_count":2, "null_value_count":null,
"nan_value_count":null, "lower_bound":0, "upper_bound":1},
"date_col":{"column_size":null, "value_count":2, "null_value_count":null,
"nan_value_count":null, "lower_bound":"2023-01-01",
"upper_bound":"2023-06-15"}, "datetime_col1":{"column_size":null,
"value_count":2, "null_v [...]
-- !sql_6 --
{1:0x00, 2:0x0B000000, 3:0x6F00000000000000, 4:0xCDCC8C3F,
5:0x9A9999999999F13F, 6:0x0457, 7:0x00BC614E, 8:0x499602D2,
9:0x018EE90FF6C373E0393713FA14, 10:0x616161, 11:0x9E4B0000,
12:0x005CE70F33F10500}
diff --git
a/regression-test/suites/external_table_p0/iceberg/test_iceberg_files_timestamp_partition.groovy
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_files_timestamp_partition.groovy
new file mode 100644
index 00000000000..4ec5d37c41c
--- /dev/null
+++
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_files_timestamp_partition.groovy
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_iceberg_files_timestamp_partition", "p0,external,iceberg") {
+ if
(!context.config.otherConfigs.get("enableIcebergTest")?.toString()?.equalsIgnoreCase("true"))
{
+ return
+ }
+ String host = context.config.otherConfigs.get("externalEnvIp")
+ String restPort = context.config.otherConfigs.get("iceberg_rest_uri_port")
+ String minioPort = context.config.otherConfigs.get("iceberg_minio_port")
+ def previousZone = sql("SELECT @@time_zone")[0][0]
+ // Parquet supports both pre-epoch fractions and NTZ. Use identity
partitions so the
+ // metadata rows retain the original microseconds, not a day/hour
transform.
+ spark_iceberg_multi """
+ SET spark.sql.session.timeZone=UTC;
+ CREATE DATABASE IF NOT EXISTS demo.files_timestamp_partition_db;
+ DROP TABLE IF EXISTS
demo.files_timestamp_partition_db.metadata_timestamp_precision;
+ CREATE TABLE
demo.files_timestamp_partition_db.metadata_timestamp_precision (
+ id INT, ts TIMESTAMP_NTZ, tz TIMESTAMP
+ ) USING iceberg PARTITIONED BY (ts, tz)
+ TBLPROPERTIES ('write.format.default'='parquet');
+ INSERT INTO
demo.files_timestamp_partition_db.metadata_timestamp_precision
+ SELECT id, CAST(value AS TIMESTAMP_NTZ), CAST(value AS TIMESTAMP)
+ FROM VALUES
+ (0, '1969-12-31 23:59:58.999999'),
+ (1, '1969-12-31 23:59:59.999999'),
+ (2, '1970-01-01 00:00:00.000001'),
+ (3, '1970-01-01 00:00:00.000999'),
+ (4, '1970-01-01 00:00:00.001000'),
+ (5, '1970-01-01 00:00:01.234567'),
+ (6, CAST(NULL AS STRING)) AS input(id, value);
+ """
+ try {
+ for (boolean mapping : [false, true]) {
+ sql "DROP CATALOG IF EXISTS test_iceberg_files_timestamp_partition"
+ sql """CREATE CATALOG test_iceberg_files_timestamp_partition
PROPERTIES (
+ 'type'='iceberg', 'iceberg.catalog.type'='rest',
+ 'uri'='http://${host}:${restPort}',
+ 's3.endpoint'='http://${host}:${minioPort}',
+ 's3.access_key'='admin', 's3.secret_key'='password',
's3.region'='us-east-1',
+ 'enable.mapping.timestamp_tz'='${mapping}')"""
+ sql "SWITCH test_iceberg_files_timestamp_partition"
+ sql "USE files_timestamp_partition_db"
+ for (String zone : ["UTC", "Asia/Shanghai"]) {
+ sql "SET time_zone = '${zone}'"
+ String tag = "${mapping}_${zone.replace('/', '_')}"
+ "order_qt_${tag}" """SELECT `partition`
+ FROM metadata_timestamp_precision\$files ORDER BY
file_path"""
+ // Exercise a pruned struct child as well as the entire
partition above.
+ "order_qt_${tag}_ntz" """SELECT `partition`.ts
+ FROM metadata_timestamp_precision\$files ORDER BY
file_path"""
+ }
+ }
+ } finally {
+ sql "SET time_zone = '${previousZone}'"
+ sql "SWITCH internal"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]