use super::record_context::RecordContext;
use super::schema_handler::FileGroupReaderSchemaHandler;
use crate::config::table::HudiTableConfig;
use crate::storage::{RowFilterBuilder, RowGroupSelector};
use crate::timeline::selector::InstantRange;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct CompletionGateInputs {
pub completed_instants: HashSet<String>,
pub inflight_instants: HashSet<String>,
pub archived_boundary: Option<String>,
}
pub const CONFIG_MERGE_TYPE: &str = "hoodie.datasource.merge.type";
#[derive(Clone)]
pub struct ReaderContext {
pub table_path: String,
pub latest_commit_time: String,
pub base_file_format: String,
pub has_log_files: bool,
pub has_bootstrap_base_file: bool,
pub needs_bootstrap_merge: bool,
pub should_merge_use_record_position: bool,
pub enable_logical_timestamp_field_repair: bool,
pub iterator_mode: String,
pub merge_mode: String,
pub merge_strategy_id: String,
pub instant_range: Option<InstantRange>,
pub record_context: RecordContext,
pub schema_handler: FileGroupReaderSchemaHandler,
pub table_config: HashMap<String, String>,
pub hoodie_reader_config: HashMap<String, String>,
pub row_filter_builder: Option<RowFilterBuilder>,
pub row_group_selector: Option<RowGroupSelector>,
pub key_predicate: Option<crate::file_group::base_file::reader::KeyPredicate>,
pub mor_pk_safe: bool,
pub repair_risk_columns: Vec<String>,
pub completion_gate_inputs: Option<Arc<CompletionGateInputs>>,
}
impl std::fmt::Debug for ReaderContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReaderContext")
.field("table_path", &self.table_path)
.field("latest_commit_time", &self.latest_commit_time)
.field("base_file_format", &self.base_file_format)
.field("has_log_files", &self.has_log_files)
.field("has_bootstrap_base_file", &self.has_bootstrap_base_file)
.field("needs_bootstrap_merge", &self.needs_bootstrap_merge)
.field(
"should_merge_use_record_position",
&self.should_merge_use_record_position,
)
.field(
"enable_logical_timestamp_field_repair",
&self.enable_logical_timestamp_field_repair,
)
.field("iterator_mode", &self.iterator_mode)
.field("merge_mode", &self.merge_mode)
.field("merge_strategy_id", &self.merge_strategy_id)
.field("instant_range", &self.instant_range)
.field("record_context", &self.record_context)
.field("schema_handler", &self.schema_handler)
.field("table_config", &self.table_config)
.field("hoodie_reader_config", &self.hoodie_reader_config)
.field(
"row_filter_builder",
&self.row_filter_builder.as_ref().map(|_| "<closure>"),
)
.field(
"row_group_selector",
&self.row_group_selector.as_ref().map(|_| "<closure>"),
)
.field("mor_pk_safe", &self.mor_pk_safe)
.field("repair_risk_columns", &self.repair_risk_columns)
.field("completion_gate_inputs", &self.completion_gate_inputs)
.finish()
}
}
impl ReaderContext {
pub fn get_record_context(&self) -> &RecordContext {
&self.record_context
}
pub fn record_key_field(&self) -> &str {
&self.record_context.record_key_field
}
pub fn record_key_fields(&self) -> Vec<String> {
Self::record_key_fields_from(&self.table_config)
}
pub fn record_key_fields_from(table_config: &HashMap<String, String>) -> Vec<String> {
RecordContext::record_key_fields_from(table_config)
}
pub fn ordering_field_names(&self) -> &[String] {
&self.record_context.ordering_field_names
}
#[allow(dead_code)]
pub fn partition_path(&self) -> &str {
&self.record_context.partition_path
}
pub fn timezone(&self) -> String {
self.table_config
.get(HudiTableConfig::TimelineTimezone.as_ref())
.cloned()
.unwrap_or_else(|| "utc".to_string())
}
pub fn rebuild_record_context(&mut self, partition_path: String) {
self.record_context = RecordContext::new(&self.table_config, partition_path);
}
#[allow(dead_code)]
pub fn empty() -> Self {
Self {
table_path: String::new(),
latest_commit_time: String::new(),
base_file_format: String::new(),
has_log_files: false,
has_bootstrap_base_file: false,
needs_bootstrap_merge: false,
should_merge_use_record_position: false,
enable_logical_timestamp_field_repair: false,
iterator_mode: String::new(),
merge_mode: String::new(),
merge_strategy_id: String::new(),
instant_range: None,
record_context: RecordContext::default(),
schema_handler: FileGroupReaderSchemaHandler::new(),
table_config: HashMap::new(),
hoodie_reader_config: HashMap::new(),
row_filter_builder: None,
row_group_selector: None,
key_predicate: None,
mor_pk_safe: false,
repair_risk_columns: Vec::new(),
completion_gate_inputs: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_key_fields_from_default_meta_field_mode() {
let mut tc = HashMap::new();
tc.insert(
"hoodie.table.recordkey.fields".to_string(),
"id".to_string(),
);
let fields = ReaderContext::record_key_fields_from(&tc);
assert_eq!(fields, vec!["_hoodie_record_key".to_string()]);
}
#[test]
fn record_key_fields_from_virtual_key_mode_single() {
let mut tc = HashMap::new();
tc.insert(
"hoodie.populate.meta.fields".to_string(),
"false".to_string(),
);
tc.insert(
"hoodie.table.recordkey.fields".to_string(),
"id".to_string(),
);
let fields = ReaderContext::record_key_fields_from(&tc);
assert_eq!(fields, vec!["id".to_string()]);
}
#[test]
fn record_key_fields_from_virtual_key_mode_composite() {
let mut tc = HashMap::new();
tc.insert(
"hoodie.populate.meta.fields".to_string(),
"false".to_string(),
);
tc.insert(
"hoodie.table.recordkey.fields".to_string(),
"id, ts".to_string(),
);
let fields = ReaderContext::record_key_fields_from(&tc);
assert_eq!(fields, vec!["id".to_string(), "ts".to_string()]);
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum MergeMode {
CommitTimeOrdering,
EventTimeOrdering,
Custom,
}
impl AsRef<str> for MergeMode {
fn as_ref(&self) -> &str {
match self {
MergeMode::CommitTimeOrdering => "COMMIT_TIME_ORDERING",
MergeMode::EventTimeOrdering => "EVENT_TIME_ORDERING",
MergeMode::Custom => "CUSTOM",
}
}
}