|
17 | 17 |
|
18 | 18 | package org.apache.fluss.lake.paimon.tiering; |
19 | 19 |
|
| 20 | +import org.apache.fluss.client.table.getter.PartitionGetter; |
20 | 21 | import org.apache.fluss.config.AutoPartitionTimeUnit; |
21 | 22 | import org.apache.fluss.config.ConfigOptions; |
22 | 23 | import org.apache.fluss.lake.paimon.testutils.FlinkPaimonTieringTestBase; |
| 24 | +import org.apache.fluss.metadata.PartitionInfo; |
23 | 25 | import org.apache.fluss.metadata.Schema; |
24 | 26 | import org.apache.fluss.metadata.TableBucket; |
25 | 27 | import org.apache.fluss.metadata.TableChange; |
26 | 28 | import org.apache.fluss.metadata.TableDescriptor; |
| 29 | +import org.apache.fluss.metadata.TableInfo; |
27 | 30 | import org.apache.fluss.metadata.TablePath; |
| 31 | +import org.apache.fluss.row.BinaryString; |
| 32 | +import org.apache.fluss.row.Decimal; |
28 | 33 | import org.apache.fluss.row.InternalRow; |
| 34 | +import org.apache.fluss.row.TimestampLtz; |
| 35 | +import org.apache.fluss.row.TimestampNtz; |
29 | 36 | import org.apache.fluss.server.testutils.FlussClusterExtension; |
30 | 37 | import org.apache.fluss.types.DataTypes; |
31 | 38 | import org.apache.fluss.utils.types.Tuple2; |
|
40 | 47 | import org.junit.jupiter.api.BeforeAll; |
41 | 48 | import org.junit.jupiter.api.Test; |
42 | 49 | import org.junit.jupiter.api.extension.RegisterExtension; |
| 50 | +import org.junit.jupiter.params.ParameterizedTest; |
| 51 | +import org.junit.jupiter.params.provider.Arguments; |
| 52 | +import org.junit.jupiter.params.provider.MethodSource; |
43 | 53 |
|
| 54 | +import java.math.BigDecimal; |
| 55 | +import java.nio.charset.StandardCharsets; |
44 | 56 | import java.time.Duration; |
| 57 | +import java.time.Instant; |
| 58 | +import java.time.LocalDate; |
| 59 | +import java.time.LocalDateTime; |
| 60 | +import java.time.LocalTime; |
45 | 61 | import java.util.ArrayList; |
46 | 62 | import java.util.Arrays; |
47 | 63 | import java.util.Collections; |
48 | 64 | import java.util.HashMap; |
49 | 65 | import java.util.Iterator; |
50 | 66 | import java.util.List; |
51 | 67 | import java.util.Map; |
| 68 | +import java.util.stream.Stream; |
52 | 69 |
|
53 | 70 | import static org.apache.fluss.lake.committer.BucketOffset.FLUSS_LAKE_SNAP_BUCKET_OFFSET_PROPERTY; |
54 | 71 | import static org.apache.fluss.testutils.DataTestUtils.row; |
@@ -185,6 +202,150 @@ void testTiering() throws Exception { |
185 | 202 | } |
186 | 203 | } |
187 | 204 |
|
| 205 | + private static Stream<Arguments> tieringAllTypesWriteArgs() { |
| 206 | + return Stream.of(Arguments.of(true), Arguments.of(false)); |
| 207 | + } |
| 208 | + |
| 209 | + @ParameterizedTest |
| 210 | + @MethodSource("tieringAllTypesWriteArgs") |
| 211 | + void testTieringForAllTypes(boolean isPrimaryKeyTable) throws Exception { |
| 212 | + // create a table, write some records and wait until snapshot finished |
| 213 | + TablePath t1 = |
| 214 | + TablePath.of( |
| 215 | + DEFAULT_DB, |
| 216 | + isPrimaryKeyTable ? "pkTableForAllTypes" : "logTableForAllTypes"); |
| 217 | + Schema.Builder builder = |
| 218 | + Schema.newBuilder() |
| 219 | + .column("c0", DataTypes.STRING()) |
| 220 | + .column("c1", DataTypes.BOOLEAN()) |
| 221 | + .column("c2", DataTypes.TINYINT()) |
| 222 | + .column("c3", DataTypes.SMALLINT()) |
| 223 | + .column("c4", DataTypes.INT()) |
| 224 | + .column("c5", DataTypes.BIGINT()) |
| 225 | + .column("c6", DataTypes.FLOAT()) |
| 226 | + .column("c7", DataTypes.DOUBLE()) |
| 227 | + // decimal not support for partition key |
| 228 | + .column("c8", DataTypes.DECIMAL(10, 2)) |
| 229 | + .column("c9", DataTypes.CHAR(10)) |
| 230 | + .column("c10", DataTypes.STRING()) |
| 231 | + .column("c11", DataTypes.BYTES()) |
| 232 | + .column("c12", DataTypes.BINARY(5)) |
| 233 | + .column("c13", DataTypes.DATE()) |
| 234 | + .column("c14", DataTypes.TIME(6)) |
| 235 | + .column("c15", DataTypes.TIMESTAMP(6)) |
| 236 | + .column("c16", DataTypes.TIMESTAMP_LTZ(6)); |
| 237 | + if (isPrimaryKeyTable) { |
| 238 | + builder.primaryKey( |
| 239 | + "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c9", "c10", "c11", "c12", |
| 240 | + "c13", "c14", "c15", "c16"); |
| 241 | + } |
| 242 | + List<String> partitionKeys = |
| 243 | + Arrays.asList( |
| 244 | + "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c9", "c10", "c11", "c12", "c13", |
| 245 | + "c14", "c15", "c16"); |
| 246 | + TableDescriptor.Builder tableDescriptor = |
| 247 | + TableDescriptor.builder() |
| 248 | + .schema(builder.build()) |
| 249 | + .distributedBy(1, "c0") |
| 250 | + .property(ConfigOptions.TABLE_DATALAKE_ENABLED.key(), "true") |
| 251 | + .property(ConfigOptions.TABLE_DATALAKE_FRESHNESS, Duration.ofMillis(500)); |
| 252 | + tableDescriptor.partitionedBy(partitionKeys); |
| 253 | + tableDescriptor.customProperties(Collections.emptyMap()); |
| 254 | + tableDescriptor.properties(Collections.emptyMap()); |
| 255 | + long t1Id = createTable(t1, tableDescriptor.build()); |
| 256 | + |
| 257 | + // write records |
| 258 | + List<InternalRow> rows = |
| 259 | + Collections.singletonList( |
| 260 | + row( |
| 261 | + BinaryString.fromString("v0"), |
| 262 | + true, |
| 263 | + (byte) 1, |
| 264 | + (short) 2, |
| 265 | + 3, |
| 266 | + 4L, |
| 267 | + 5.0f, |
| 268 | + 6.0, |
| 269 | + Decimal.fromBigDecimal(new BigDecimal("0.09"), 10, 2), |
| 270 | + BinaryString.fromString("v1"), |
| 271 | + BinaryString.fromString("v2"), |
| 272 | + "v3".getBytes(StandardCharsets.UTF_8), |
| 273 | + new byte[] {1, 2, 3, 4, 5}, |
| 274 | + (int) LocalDate.of(2025, 10, 16).toEpochDay(), |
| 275 | + (int) |
| 276 | + (LocalTime.of(10, 10, 10, 123000000).toNanoOfDay() |
| 277 | + / 1_000_000), |
| 278 | + TimestampNtz.fromLocalDateTime( |
| 279 | + LocalDateTime.of(2025, 10, 16, 10, 10, 10, 123000000)), |
| 280 | + TimestampLtz.fromInstant( |
| 281 | + Instant.parse("2025-10-16T10:10:10.123Z")))); |
| 282 | + writeRows(t1, rows, !isPrimaryKeyTable); |
| 283 | + |
| 284 | + TableInfo tableInfo = admin.getTableInfo(t1).get(); |
| 285 | + List<PartitionInfo> partitionInfos = admin.listPartitionInfos(t1).get(); |
| 286 | + assertThat(partitionInfos.size()).isEqualTo(1); |
| 287 | + PartitionGetter partitionGetter = |
| 288 | + new PartitionGetter(tableInfo.getRowType(), partitionKeys); |
| 289 | + String partition = partitionGetter.getPartition(rows.get(0)); |
| 290 | + assertThat(partitionInfos.get(0).getPartitionName()).isEqualTo(partition); |
| 291 | + |
| 292 | + long partitionId = partitionInfos.get(0).getPartitionId(); |
| 293 | + TableBucket t1Bucket = new TableBucket(t1Id, partitionId, 0); |
| 294 | + |
| 295 | + // then start tiering job |
| 296 | + JobClient jobClient = buildTieringJob(execEnv); |
| 297 | + |
| 298 | + try { |
| 299 | + // check the status of replica after synced |
| 300 | + assertReplicaStatus(t1Bucket, 1); |
| 301 | + |
| 302 | + // check data in paimon |
| 303 | + Iterator<org.apache.paimon.data.InternalRow> paimonRowIterator = |
| 304 | + getPaimonRowCloseableIterator(t1); |
| 305 | + for (InternalRow expectedRow : rows) { |
| 306 | + org.apache.paimon.data.InternalRow row = paimonRowIterator.next(); |
| 307 | + assertThat(row.getString(0).toString()) |
| 308 | + .isEqualTo(expectedRow.getString(0).toString()); |
| 309 | + assertThat(row.getBoolean(1)).isEqualTo(expectedRow.getBoolean(1)); |
| 310 | + assertThat(row.getByte(2)).isEqualTo(expectedRow.getByte(2)); |
| 311 | + assertThat(row.getShort(3)).isEqualTo(expectedRow.getShort(3)); |
| 312 | + assertThat(row.getInt(4)).isEqualTo(expectedRow.getInt(4)); |
| 313 | + assertThat(row.getLong(5)).isEqualTo(expectedRow.getLong(5)); |
| 314 | + assertThat(row.getFloat(6)).isEqualTo(expectedRow.getFloat(6)); |
| 315 | + assertThat(row.getDouble(7)).isEqualTo(expectedRow.getDouble(7)); |
| 316 | + assertThat(row.getDecimal(8, 10, 2).toBigDecimal()) |
| 317 | + .isEqualTo(expectedRow.getDecimal(8, 10, 2).toBigDecimal()); |
| 318 | + assertThat(row.getString(9).toString()) |
| 319 | + .isEqualTo(expectedRow.getString(9).toString()); |
| 320 | + assertThat(row.getString(10).toString()) |
| 321 | + .isEqualTo(expectedRow.getString(10).toString()); |
| 322 | + assertThat(row.getBinary(11)).isEqualTo(expectedRow.getBytes(11)); |
| 323 | + assertThat(row.getBinary(12)).isEqualTo(expectedRow.getBinary(12, 5)); |
| 324 | + assertThat(row.getInt(13)).isEqualTo(expectedRow.getInt(13)); |
| 325 | + assertThat(row.getInt(14)).isEqualTo(expectedRow.getInt(14)); |
| 326 | + assertThat(row.getTimestamp(15, 6).getMillisecond()) |
| 327 | + .isEqualTo(expectedRow.getTimestampNtz(15, 6).getMillisecond()); |
| 328 | + assertThat(row.getTimestamp(16, 6).getMillisecond()) |
| 329 | + .isEqualTo(expectedRow.getTimestampLtz(16, 6).getEpochMillisecond()); |
| 330 | + |
| 331 | + // check snapshot in paimon |
| 332 | + Map<String, String> properties = |
| 333 | + new HashMap<String, String>() { |
| 334 | + { |
| 335 | + put( |
| 336 | + FLUSS_LAKE_SNAP_BUCKET_OFFSET_PROPERTY, |
| 337 | + String.format( |
| 338 | + "[{\"partition_id\":%d,\"bucket\":0,\"partition_name\":\"c1=true/c2=1/c3=2/c4=3/c5=4/c6=5_0/c7=6_0/c9=v1/c10=v2/c11=7633/c12=0102030405/c13=2025-10-16/c14=10-10-10_123/c15=2025-10-16-10-10-10_123/c16=2025-10-16-10-10-10_123\",\"offset\":1}]", |
| 339 | + partitionId)); |
| 340 | + } |
| 341 | + }; |
| 342 | + checkSnapshotPropertyInPaimon(t1, properties); |
| 343 | + } |
| 344 | + } finally { |
| 345 | + jobClient.cancel().get(); |
| 346 | + } |
| 347 | + } |
| 348 | + |
188 | 349 | @Test |
189 | 350 | void testTieringForAlterTable() throws Exception { |
190 | 351 | TablePath t1 = TablePath.of(DEFAULT_DB, "pkTableAlter"); |
|
0 commit comments