pub(crate) mod record_key;
pub mod timestamp_based;
use crate::Result;
use crate::config::HudiConfigs;
use crate::config::table::HudiTableConfig;
use crate::config::table::HudiTableConfig::{
KeyGeneratorClass, KeyGeneratorType, PartitionFields, TableVersion,
};
use crate::error::CoreError;
use crate::expr::filter::Filter;
use crate::keygen::record_key::configured_record_key_fields;
const COMPLEX_KEYGEN_NEW_ENCODING: &str = "hoodie.write.complex.keygen.new.encoding";
pub(crate) const DEFAULT_PARTITION_PATH: &str = "__HIVE_DEFAULT_PARTITION__";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum KeyGeneratorKind {
Simple,
NonPartitioned,
Complex,
SparkSql,
TimestampBased,
Other,
}
impl KeyGeneratorKind {
fn from_class(class: &str) -> Self {
match class {
"org.apache.hudi.keygen.SimpleKeyGenerator"
| "org.apache.hudi.keygen.SimpleAvroKeyGenerator" => Self::Simple,
"org.apache.hudi.keygen.NonpartitionedKeyGenerator"
| "org.apache.hudi.keygen.NonpartitionedAvroKeyGenerator" => Self::NonPartitioned,
"org.apache.hudi.keygen.ComplexKeyGenerator"
| "org.apache.hudi.keygen.ComplexAvroKeyGenerator" => Self::Complex,
"org.apache.spark.sql.hudi.command.SqlKeyGenerator" => Self::SparkSql,
"org.apache.hudi.keygen.TimestampBasedKeyGenerator"
| "org.apache.hudi.keygen.TimestampBasedAvroKeyGenerator" => Self::TimestampBased,
_ => Self::Other,
}
}
fn from_type(key_generator_type: &str) -> Self {
match key_generator_type.to_ascii_uppercase().as_str() {
"SIMPLE" | "SIMPLE_AVRO" => Self::Simple,
"NON_PARTITION" | "NON_PARTITION_AVRO" => Self::NonPartitioned,
"COMPLEX" | "COMPLEX_AVRO" => Self::Complex,
"SPARK_SQL" => Self::SparkSql,
"TIMESTAMP" | "TIMESTAMP_AVRO" => Self::TimestampBased,
_ => Self::Other,
}
}
}
fn declared<'a>(hudi_configs: &'a HudiConfigs, config: &HudiTableConfig) -> Option<&'a str> {
hudi_configs
.get_raw(config.as_ref())
.map(str::trim)
.filter(|v| !v.is_empty())
}
fn declared_key_generator(hudi_configs: &HudiConfigs) -> Option<KeyGeneratorKind> {
declared(hudi_configs, &KeyGeneratorClass)
.map(KeyGeneratorKind::from_class)
.or_else(|| declared(hudi_configs, &KeyGeneratorType).map(KeyGeneratorKind::from_type))
}
pub(crate) fn key_generator_kind(hudi_configs: &HudiConfigs) -> Result<KeyGeneratorKind> {
let kind = match declared_key_generator(hudi_configs) {
Some(kind) => kind,
None => {
let partition_fields: Vec<String> = hudi_configs.get_or_default(PartitionFields).into();
if partition_fields.first().is_some_and(|f| f.contains(':')) {
KeyGeneratorKind::Other
} else {
match partition_fields.len() {
0 => KeyGeneratorKind::NonPartitioned,
1 if configured_record_key_fields(hudi_configs)?.len() > 1 => {
KeyGeneratorKind::Complex
}
1 => KeyGeneratorKind::Simple,
_ => KeyGeneratorKind::Complex,
}
}
}
};
if matches!(
kind,
KeyGeneratorKind::TimestampBased | KeyGeneratorKind::Other
) {
let setting = [KeyGeneratorClass, KeyGeneratorType, PartitionFields]
.iter()
.find_map(|config| {
declared(hudi_configs, config).map(|value| format!("{}={value}", config.as_ref()))
})
.unwrap_or_default();
return Err(CoreError::Unsupported(format!(
"writes do not support {setting}; supported key generators are SIMPLE, COMPLEX, \
NON_PARTITION, and SPARK_SQL"
)));
}
Ok(kind)
}
pub(crate) fn encodes_single_key_field_name(hudi_configs: &HudiConfigs) -> Result<bool> {
if !matches!(
key_generator_kind(hudi_configs)?,
KeyGeneratorKind::Complex | KeyGeneratorKind::SparkSql
) {
return Ok(false);
}
let table_version: Option<isize> = hudi_configs.try_get(TableVersion)?.map(Into::into);
let new_encoding = hudi_configs
.get_raw(COMPLEX_KEYGEN_NEW_ENCODING)
.is_some_and(|v| v.trim().eq_ignore_ascii_case("true"));
Ok(table_version.is_some_and(|v| v >= 9) || !new_encoding)
}
pub(crate) fn declares_complex_key_generator(hudi_configs: &HudiConfigs) -> bool {
let kind = match hudi_configs.get_raw(KeyGeneratorType.as_ref()) {
Some(key_generator_type) => KeyGeneratorKind::from_type(key_generator_type.trim()),
None => declared(hudi_configs, &KeyGeneratorClass)
.map_or(KeyGeneratorKind::Other, KeyGeneratorKind::from_class),
};
kind == KeyGeneratorKind::Complex
}
pub fn is_timestamp_based_keygen(hudi_configs: &HudiConfigs) -> Result<bool> {
Ok(declared_key_generator(hudi_configs) == Some(KeyGeneratorKind::TimestampBased))
}
pub trait KeyGeneratorFilterTransformer {
fn source_field(&self) -> &str;
fn transform_filter(&self, filter: &Filter) -> Result<Vec<Filter>>;
}
#[cfg(test)]
mod tests {
use super::*;
fn configs(pairs: &[(&str, &str)]) -> HudiConfigs {
HudiConfigs::new(pairs.iter().map(|(k, v)| (*k, v.to_string())))
}
#[test]
fn test_declared_key_generator_kinds() {
let timestamp = configs(&[(
"hoodie.table.keygenerator.class",
"org.apache.hudi.keygen.TimestampBasedAvroKeyGenerator",
)]);
assert!(is_timestamp_based_keygen(×tamp).unwrap());
assert!(
is_timestamp_based_keygen(&configs(&[("hoodie.table.keygenerator.type", "timestamp")]))
.unwrap()
);
assert!(!is_timestamp_based_keygen(&configs(&[])).unwrap());
assert!(
!is_timestamp_based_keygen(&configs(&[
("hoodie.table.keygenerator.type", "TIMESTAMP"),
(
"hoodie.table.keygenerator.class",
"org.apache.hudi.keygen.SimpleKeyGenerator",
),
]))
.unwrap()
);
let complex_type = configs(&[
("hoodie.table.keygenerator.type", "COMPLEX"),
(
"hoodie.table.keygenerator.class",
"org.apache.hudi.keygen.SimpleKeyGenerator",
),
]);
assert!(declares_complex_key_generator(&complex_type));
assert!(!declares_complex_key_generator(&configs(&[(
"hoodie.table.keygenerator.type",
"SPARK_SQL"
)])));
assert!(!declares_complex_key_generator(&configs(&[(
"hoodie.table.partition.fields",
"p,q"
)])));
}
}