From d26cebdbe131a0f0f7f490623dd16524e3c4f806 Mon Sep 17 00:00:00 2001 From: Shiyan Xu <2701446+xushiyan@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:41:19 -0600 Subject: [PATCH] feat: support incremental read MOR tables --- crates/core/src/file_group/builder.rs | 51 ++++-- crates/core/src/table/mod.rs | 167 +++++++++--------- crates/core/src/timeline/selector.rs | 23 +++ ...mplekeygen_nonhivestyle_overwritetable.sql | 101 +++++++++++ ...mplekeygen_nonhivestyle_overwritetable.zip | Bin 0 -> 48409 bytes 5 files changed, 245 insertions(+), 97 deletions(-) create mode 100644 crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.sql create mode 100644 crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.zip diff --git a/crates/core/src/file_group/builder.rs b/crates/core/src/file_group/builder.rs index 4f73aa1..035c681 100644 --- a/crates/core/src/file_group/builder.rs +++ b/crates/core/src/file_group/builder.rs @@ -44,21 +44,42 @@ pub fn build_file_groups(commit_metadata: &Map) -> Result, ) -> Result> { // If the end timestamp is not provided, use the latest commit timestamp. - let Some(as_of_timestamp) = + let Some(end_timestamp) = end_timestamp.or_else(|| self.timeline.get_latest_commit_timestamp()) else { return Ok(Vec::new()); @@ -336,10 +336,10 @@ impl Table { let mut file_slices: Vec = Vec::new(); let file_groups = self .timeline - .get_incremental_file_groups(Some(start_timestamp), Some(as_of_timestamp)) + .get_incremental_file_groups(Some(start_timestamp), Some(end_timestamp)) .await?; for file_group in file_groups { - if let Some(file_slice) = file_group.get_file_slice_as_of(as_of_timestamp) { + if let Some(file_slice) = file_group.get_file_slice_as_of(end_timestamp) { file_slices.push(file_slice.clone()); } } @@ -347,13 +347,14 @@ impl Table { // Read incremental records from the file slices. let filters = &[ FilterField::new(MetaField::CommitTime.as_ref()).gt(start_timestamp), - FilterField::new(MetaField::CommitTime.as_ref()).lte(as_of_timestamp), + FilterField::new(MetaField::CommitTime.as_ref()).lte(end_timestamp), ]; let fg_reader = self.create_file_group_reader_with_filters(filters, MetaField::schema().as_ref())?; let base_file_only = self.get_table_type() == TableTypeValue::CopyOnWrite; let timezone = self.timezone(); - let instant_range = InstantRange::up_to(as_of_timestamp, &timezone); + let instant_range = + InstantRange::within_open_closed(start_timestamp, end_timestamp, &timezone); let batches = futures::future::try_join_all( file_slices .iter() @@ -992,93 +993,95 @@ mod tests { mod test_incremental_queries { use super::super::*; - use arrow_array::{Array, StringArray}; + use arrow_select::concat::concat_batches; use hudi_tests::SampleTable; - use std::collections::HashSet; #[tokio::test] async fn test_empty() -> Result<()> { - let base_url = SampleTable::V6Empty.url_to_cow(); - let hudi_table = Table::new(base_url.path()).await?; - - let records = hudi_table.read_incremental_records("0", None).await?; - assert!(records.is_empty()); - + for base_url in SampleTable::V6Empty.urls() { + let hudi_table = Table::new(base_url.path()).await?; + let records = hudi_table.read_incremental_records("0", None).await?; + assert!(records.is_empty()) + } Ok(()) } #[tokio::test] async fn test_simplekeygen_nonhivestyle_overwritetable() -> Result<()> { - let base_url = SampleTable::V6SimplekeygenNonhivestyleOverwritetable.url_to_cow(); - let hudi_table = Table::new(base_url.path()).await?; - - // read records changed from the first commit (exclusive) to the second commit (inclusive) - let records = hudi_table - .read_incremental_records("20240707001301554", Some("20240707001302376")) - .await?; - assert_eq!(records.len(), 2); - assert_eq!(records[0].num_rows(), 1); - assert_eq!(records[1].num_rows(), 1); - - // verify the partition paths - let partition_paths = StringArray::from( - arrow::compute::concat(&[ - records[0].column_by_name("_hoodie_partition_path").unwrap(), - records[1].column_by_name("_hoodie_partition_path").unwrap(), - ])? - .to_data(), - ); - let actual_partition_paths = - HashSet::<&str>::from_iter(partition_paths.iter().map(|s| s.unwrap())); - let expected_partition_paths = HashSet::from_iter(vec!["10", "30"]); - assert_eq!(actual_partition_paths, expected_partition_paths); - - // verify the file names - let file_names = StringArray::from( - arrow::compute::concat(&[ - records[0].column_by_name("_hoodie_file_name").unwrap(), - records[1].column_by_name("_hoodie_file_name").unwrap(), - ])? - .to_data(), - ); - let actual_file_names = - HashSet::<&str>::from_iter(file_names.iter().map(|s| s.unwrap())); - let expected_file_names = HashSet::from_iter(vec![ - "d398fae1-c0e6-4098-8124-f55f7098bdba-0_1-95-136_20240707001302376.parquet", - "4f2685a3-614f-49ca-9b2b-e1cb9fb61f27-0_0-95-135_20240707001302376.parquet", - ]); - assert_eq!(actual_file_names, expected_file_names); - - // read records changed from the first commit (exclusive) to - // the latest (an insert overwrite table's replacecommit) - let records = hudi_table - .read_incremental_records("20240707001301554", None) - .await?; - assert_eq!(records.len(), 1); - assert_eq!(records[0].num_rows(), 1); - - // verify the partition paths - let actual_partition_paths = StringArray::from( - records[0] - .column_by_name("_hoodie_partition_path") - .unwrap() - .to_data(), - ); - let expected_partition_paths = StringArray::from(vec!["30"]); - assert_eq!(actual_partition_paths, expected_partition_paths); - - // verify the file names - let actual_file_names = StringArray::from( - records[0] - .column_by_name("_hoodie_file_name") - .unwrap() - .to_data(), - ); - let expected_file_names = StringArray::from(vec![ - "ebcb261d-62d3-4895-90ec-5b3c9622dff4-0_0-111-154_20240707001303088.parquet", - ]); - assert_eq!(actual_file_names, expected_file_names); + for base_url in SampleTable::V6SimplekeygenNonhivestyleOverwritetable.urls() { + let hudi_table = Table::new(base_url.path()).await?; + let commit_timestamps = hudi_table + .timeline + .completed_commits + .iter() + .map(|i| i.timestamp.as_str()) + .collect::>(); + assert_eq!(commit_timestamps.len(), 3); + let first_commit = commit_timestamps[0]; + let second_commit = commit_timestamps[1]; + let third_commit = commit_timestamps[2]; + + // read records changed from the beginning to the 1st commit + let records = hudi_table + .read_incremental_records("19700101000000", Some(first_commit)) + .await?; + let schema = &records[0].schema(); + let records = concat_batches(schema, &records)?; + let sample_data = SampleTable::sample_data_order_by_id(&records); + assert_eq!( + sample_data, + vec![(1, "Alice", true), (2, "Bob", false), (3, "Carol", true),], + "Should return 3 records inserted in the 1st commit" + ); + // read records changed from the 1st to the 2nd commit + let records = hudi_table + .read_incremental_records(first_commit, Some(second_commit)) + .await?; + let schema = &records[0].schema(); + let records = concat_batches(schema, &records)?; + let sample_data = SampleTable::sample_data_order_by_id(&records); + assert_eq!( + sample_data, + vec![(1, "Alice", false), (4, "Diana", true),], + "Should return 2 records inserted or updated in the 2nd commit" + ); + + // read records changed from the 2nd to the 3rd commit + let records = hudi_table + .read_incremental_records(second_commit, Some(third_commit)) + .await?; + let schema = &records[0].schema(); + let records = concat_batches(schema, &records)?; + let sample_data = SampleTable::sample_data_order_by_id(&records); + assert_eq!( + sample_data, + vec![(4, "Diana", false),], + "Should return 1 record insert-overwritten in the 3rd commit" + ); + + // read records changed from the 1st commit + let records = hudi_table + .read_incremental_records(first_commit, None) + .await?; + let schema = &records[0].schema(); + let records = concat_batches(schema, &records)?; + let sample_data = SampleTable::sample_data_order_by_id(&records); + assert_eq!( + sample_data, + vec![(4, "Diana", false),], + "Should return 1 record insert-overwritten in the 3rd commit" + ); + + // read records changed from the 3rd commit + let records = hudi_table + .read_incremental_records(third_commit, None) + .await?; + assert!( + records.is_empty(), + "Should return 0 record as it's the latest commit" + ); + } Ok(()) } } diff --git a/crates/core/src/timeline/selector.rs b/crates/core/src/timeline/selector.rs index a6fa2e9..c5c1785 100644 --- a/crates/core/src/timeline/selector.rs +++ b/crates/core/src/timeline/selector.rs @@ -25,6 +25,7 @@ use crate::Result; use chrono::{DateTime, Utc}; use std::sync::Arc; +#[allow(dead_code)] #[derive(Debug, Clone)] pub struct InstantRange { timezone: String, @@ -62,6 +63,28 @@ impl InstantRange { ) } + /// Create a new [InstantRange] with an open timestamp range. + pub fn within(start_timestamp: &str, end_timestamp: &str, timezone: &str) -> Self { + Self::new( + timezone.to_string(), + Some(start_timestamp.to_string()), + Some(end_timestamp.to_string()), + false, + false, + ) + } + + /// Create a new [InstantRange] with an open start and closed end timestamp range. + pub fn within_open_closed(start_timestamp: &str, end_timestamp: &str, timezone: &str) -> Self { + Self::new( + timezone.to_string(), + Some(start_timestamp.to_string()), + Some(end_timestamp.to_string()), + false, + true, + ) + } + pub fn timezone(&self) -> &str { &self.timezone } diff --git a/crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.sql b/crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.sql new file mode 100644 index 0000000..d86e5df --- /dev/null +++ b/crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.sql @@ -0,0 +1,101 @@ +/* + * 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. + */ + +CREATE TABLE v6_simplekeygen_nonhivestyle_overwritetable ( + id INT, + name STRING, + isActive BOOLEAN, + shortField SHORT, + intField INT, + longField LONG, + floatField FLOAT, + doubleField DOUBLE, + decimalField DECIMAL(10,5), + dateField DATE, + timestampField TIMESTAMP, + binaryField BINARY, + arrayField ARRAY>, -- Array of structs + mapField MAP>, -- Map with struct values + structField STRUCT< + field1: STRING, + field2: INT, + child_struct: STRUCT< + child_field1: DOUBLE, + child_field2: BOOLEAN + > + >, + byteField BYTE +) + USING HUDI + LOCATION '/opt/data/external_tables/v6_simplekeygen_nonhivestyle_overwritetable' +TBLPROPERTIES ( + type = 'mor', + primaryKey = 'id', + preCombineField = 'longField', + 'hoodie.metadata.enable' = 'false', + 'hoodie.datasource.write.hive_style_partitioning' = 'false', + 'hoodie.datasource.write.drop.partition.columns' = 'false', + 'hoodie.table.log.file.format' = 'PARQUET', + 'hoodie.logfile.data.block.format' = 'parquet', + 'hoodie.datasource.write.record.merger.impls' = 'org.apache.hudi.HoodieSparkRecordMerger', + 'hoodie.parquet.small.file.limit' = '0' +) +PARTITIONED BY (byteField); + +INSERT INTO v6_simplekeygen_nonhivestyle_overwritetable VALUES + (1, 'Alice', true, 300, 15000, 1234567890, 1.0, 3.14159, 12345.67890, CAST('2023-04-01' AS DATE), CAST('2023-04-01 12:01:00' AS TIMESTAMP), CAST('binary data' AS BINARY), + ARRAY(STRUCT('red', 100), STRUCT('blue', 200), STRUCT('green', 300)), + MAP('key1', STRUCT(123.456, true), 'key2', STRUCT(789.012, false)), + STRUCT('Alice', 30, STRUCT(123.456, true)), + 10 + ), + (2, 'Bob', false, 100, 25000, 9876543210, 2.0, 2.71828, 67890.12345, CAST('2023-04-02' AS DATE), CAST('2023-04-02 13:02:00' AS TIMESTAMP), CAST('more binary data' AS BINARY), + ARRAY(STRUCT('yellow', 400), STRUCT('purple', 500)), + MAP('key3', STRUCT(234.567, true), 'key4', STRUCT(567.890, false)), + STRUCT('Bob', 40, STRUCT(789.012, false)), + 20 + ), + (3, 'Carol', true, 200, 35000, 1928374650, 3.0, 1.41421, 11111.22222, CAST('2023-04-03' AS DATE), CAST('2023-04-03 14:03:00' AS TIMESTAMP), CAST('even more binary data' AS BINARY), + ARRAY(STRUCT('black', 600), STRUCT('white', 700), STRUCT('pink', 800)), + MAP('key5', STRUCT(345.678, true), 'key6', STRUCT(654.321, false)), + STRUCT('Carol', 25, STRUCT(456.789, true)), + 10 + ); + +INSERT INTO v6_simplekeygen_nonhivestyle_overwritetable VALUES + (1, 'Alice', false, 300, 15000, 1234567890, 1.0, 3.14159, 12345.67890, CAST('2023-04-01' AS DATE), CAST('2023-04-01 12:01:00' AS TIMESTAMP), CAST('binary data' AS BINARY), + ARRAY(STRUCT('red', 100), STRUCT('blue', 200), STRUCT('green', 300)), + MAP('key1', STRUCT(123.456, true), 'key2', STRUCT(789.012, false)), + STRUCT('Alice', 30, STRUCT(123.456, true)), + 10 + ), + (4, 'Diana', true, 500, 45000, 987654321, 4.0, 2.468, 65432.12345, CAST('2023-04-04' AS DATE), CAST('2023-04-04 15:04:00' AS TIMESTAMP), CAST('new binary data' AS BINARY), + ARRAY(STRUCT('orange', 900), STRUCT('gray', 1000)), + MAP('key7', STRUCT(456.789, true), 'key8', STRUCT(123.456, false)), + STRUCT('Diana', 50, STRUCT(987.654, true)), + 30 + ); + +INSERT OVERWRITE TABLE v6_simplekeygen_nonhivestyle_overwritetable SELECT + 4, 'Diana', false, 500, 45000, 987654321, 4.0, 2.468, 65432.12345, CAST('2023-04-04' AS DATE), CAST('2023-04-04 15:04:00' AS TIMESTAMP), CAST('new binary data' AS BINARY), + ARRAY(STRUCT('orange', 900), STRUCT('gray', 1000)), + MAP('key7', STRUCT(456.789, true), 'key8', STRUCT(123.456, false)), + STRUCT('Diana', 50, STRUCT(987.654, true)), + 30 + ; diff --git a/crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.zip b/crates/tests/data/tables/mor/v6_simplekeygen_nonhivestyle_overwritetable.zip new file mode 100644 index 0000000000000000000000000000000000000000..afd3208c3a385d6f945274b8577d2f4bc08ef4de GIT binary patch literal 48409 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

UXQK~0ng_|OUmmm&$r}sxU`(O#AQV6Xw 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 literal 0 HcmV?d00001