mod column_filter;
use std::borrow::Cow;
use column_filter::StatsColumnFilter;
pub(crate) use column_filter::StatsConfig;
use crate::actions::{MAX_VALUES, MIN_VALUES, NULL_COUNT, NUM_RECORDS, TIGHT_BOUNDS};
use crate::schema::{
ArrayType, ColumnName, DataType, MapType, PrimitiveType, Schema, StructField, StructType,
};
use crate::transforms::{transform_output_type, SchemaTransform};
use crate::DeltaResult;
#[allow(unused)]
pub(crate) fn expected_stats_schema(
data_schema: &Schema,
config: &StatsConfig<'_>,
required_columns: Option<&[ColumnName]>,
requested_columns: Option<&[ColumnName]>,
) -> DeltaResult<Schema> {
let mut fields = Vec::with_capacity(5);
fields.push(StructField::nullable(NUM_RECORDS, DataType::LONG));
let mut base_transform = BaseStatsTransform::new(config, required_columns, requested_columns);
if let Some(base_schema) = base_transform.transform_struct(data_schema) {
let base_schema = base_schema.into_owned();
let mut null_count_transform = NullCountStatsTransform;
let null_count_schema = null_count_transform.transform_struct(&base_schema);
fields.push(StructField::nullable(
NULL_COUNT,
null_count_schema.into_owned(),
));
let mut min_max_transform = MinMaxStatsTransform;
if let Some(min_max_schema) = min_max_transform.transform_struct(&base_schema) {
let min_max_schema = min_max_schema.into_owned();
fields.push(StructField::nullable(MIN_VALUES, min_max_schema.clone()));
fields.push(StructField::nullable(MAX_VALUES, min_max_schema));
}
}
fields.push(StructField::nullable(TIGHT_BOUNDS, DataType::BOOLEAN));
StructType::try_new(fields)
}
#[allow(unused)]
pub(crate) fn stats_column_names(
data_schema: &Schema,
config: &StatsConfig<'_>,
required_columns: Option<&[ColumnName]>,
) -> Vec<ColumnName> {
let mut filter = StatsColumnFilter::new(config, required_columns, None);
let mut columns = Vec::new();
filter.collect_columns(data_schema, &mut columns);
columns
}
pub(crate) struct StripFieldMetadataTransform;
impl<'a> SchemaTransform<'a> for StripFieldMetadataTransform {
transform_output_type!(|'a, T| Cow<'a, T>);
fn transform_struct_field(&mut self, field: &'a StructField) -> Cow<'a, StructField> {
match self.transform(&field.data_type) {
Cow::Borrowed(_) if field.metadata.is_empty() => Cow::Borrowed(field),
data_type => Cow::Owned(StructField {
name: field.name.clone(),
data_type: data_type.into_owned(),
nullable: field.is_nullable(),
metadata: Default::default(),
}),
}
}
}
pub(crate) fn schema_with_all_fields_nullable(schema: &Schema) -> Schema {
NullableStatsTransform.transform_struct(schema).into_owned()
}
pub(crate) struct NullableStatsTransform;
impl<'a> SchemaTransform<'a> for NullableStatsTransform {
transform_output_type!(|'a, T| Cow<'a, T>);
fn transform_struct_field(&mut self, field: &'a StructField) -> Cow<'a, StructField> {
let data_type = self.transform(&field.data_type);
make_nullable_field(field, data_type)
}
}
fn make_nullable_field<'a>(
field: &'a StructField,
data_type: Cow<'a, DataType>,
) -> Cow<'a, StructField> {
match data_type {
Cow::Borrowed(_) if field.is_nullable() => Cow::Borrowed(field),
data_type => Cow::Owned(StructField {
name: field.name.clone(),
data_type: data_type.into_owned(),
nullable: true,
metadata: field.metadata.clone(),
}),
}
}
pub(crate) struct NullCountStatsTransform;
impl<'a> SchemaTransform<'a> for NullCountStatsTransform {
transform_output_type!(|'a, T| Cow<'a, T>);
fn transform_struct_field(&mut self, field: &'a StructField) -> Cow<'a, StructField> {
match &field.data_type {
DataType::Struct(_) => self.recurse_into_struct_field(field),
_ => Cow::Owned(StructField {
name: field.name.clone(),
data_type: DataType::LONG,
nullable: true,
metadata: field.metadata.clone(),
}),
}
}
}
#[allow(unused)]
struct BaseStatsTransform<'col> {
filter: StatsColumnFilter<'col>,
}
impl<'col> BaseStatsTransform<'col> {
#[allow(unused)]
fn new(
config: &StatsConfig<'col>,
required_columns: Option<&'col [ColumnName]>,
requested_columns: Option<&'col [ColumnName]>,
) -> Self {
Self {
filter: StatsColumnFilter::new(config, required_columns, requested_columns),
}
}
fn include_leaf(&mut self) -> bool {
if !self.filter.should_include_for_table() {
return false;
}
self.filter.record_included();
self.filter.should_include_for_requested()
}
}
impl<'a> SchemaTransform<'a> for BaseStatsTransform<'_> {
transform_output_type!(|'a, T| Option<Cow<'a, T>>);
fn transform_struct_field(&mut self, field: &'a StructField) -> Option<Cow<'a, StructField>> {
self.filter.enter_field(field.name());
let data_type = self.transform(&field.data_type);
self.filter.exit_field();
Some(make_nullable_field(field, data_type?))
}
fn transform_primitive(&mut self, ptype: &'a PrimitiveType) -> Option<Cow<'a, PrimitiveType>> {
self.include_leaf().then_some(Cow::Borrowed(ptype))
}
fn transform_array(&mut self, atype: &'a ArrayType) -> Option<Cow<'a, ArrayType>> {
self.include_leaf().then_some(Cow::Borrowed(atype))
}
fn transform_map(&mut self, mtype: &'a MapType) -> Option<Cow<'a, MapType>> {
self.include_leaf().then_some(Cow::Borrowed(mtype))
}
fn transform_variant(&mut self, vtype: &'a StructType) -> Option<Cow<'a, StructType>> {
self.include_leaf().then_some(Cow::Borrowed(vtype))
}
}
#[allow(unused)]
struct MinMaxStatsTransform;
impl<'a> SchemaTransform<'a> for MinMaxStatsTransform {
transform_output_type!(|'a, T| Option<Cow<'a, T>>);
fn transform_array(&mut self, _: &'a ArrayType) -> Option<Cow<'a, ArrayType>> {
None
}
fn transform_map(&mut self, _: &'a MapType) -> Option<Cow<'a, MapType>> {
None
}
fn transform_variant(&mut self, _: &'a StructType) -> Option<Cow<'a, StructType>> {
None
}
fn transform_primitive(&mut self, ptype: &'a PrimitiveType) -> Option<Cow<'a, PrimitiveType>> {
is_skipping_eligible_datatype(ptype).then_some(Cow::Borrowed(ptype))
}
}
pub(crate) fn is_skipping_eligible_datatype(data_type: &PrimitiveType) -> bool {
matches!(
data_type,
&PrimitiveType::Byte
| &PrimitiveType::Short
| &PrimitiveType::Integer
| &PrimitiveType::Long
| &PrimitiveType::Float
| &PrimitiveType::Double
| &PrimitiveType::Date
| &PrimitiveType::Timestamp
| &PrimitiveType::TimestampNtz
| &PrimitiveType::String
| PrimitiveType::Decimal(_)
)
}
#[cfg(test)]
mod tests {
#[cfg(feature = "geo-type-in-dev")]
use rstest::rstest;
use super::*;
use crate::expressions::column_name;
use crate::schema::schema;
#[cfg(feature = "geo-type-in-dev")]
use crate::schema::{EdgeInterpolationAlgorithm, GeographyType, GeometryType};
use crate::table_properties::TableProperties;
#[cfg(feature = "geo-type-in-dev")]
#[rstest]
#[case(PrimitiveType::Geometry(Box::new(GeometryType::try_new("EPSG:4326").unwrap())))]
#[case(PrimitiveType::Geography(Box::new(
GeographyType::try_new("EPSG:4326", EdgeInterpolationAlgorithm::Spherical).unwrap()
)))]
fn test_geo_types_are_not_skipping_eligible(#[case] ptype: PrimitiveType) {
assert!(!is_skipping_eligible_datatype(&ptype));
}
fn stats_config_from_table_properties(properties: &TableProperties) -> StatsConfig<'_> {
StatsConfig {
data_skipping_stats_columns: properties.data_skipping_stats_columns.as_deref(),
data_skipping_num_indexed_cols: properties.data_skipping_num_indexed_cols,
}
}
fn expected_stats(null_count: StructType, min_max: StructType) -> StructType {
schema! {
nullable NUM_RECORDS: LONG,
nullable NULL_COUNT: (null_count),
nullable MIN_VALUES: (min_max.clone()),
nullable MAX_VALUES: (min_max),
nullable TIGHT_BOUNDS: BOOLEAN,
}
}
#[test]
fn test_stats_schema_simple() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! { nullable "id": LONG };
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected = expected_stats(file_schema.clone(), file_schema);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_nested() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
not_null "id": LONG,
not_null "user": {
not_null "name": STRING,
nullable "age": INTEGER,
},
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_min_max = NullableStatsTransform
.transform_struct(&file_schema)
.into_owned();
let null_count = NullCountStatsTransform
.transform_struct(&expected_min_max)
.into_owned();
let expected = expected_stats(null_count, expected_min_max);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_with_non_eligible_field() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "metadata": {
nullable "name": STRING,
nullable "tags": [ not_null STRING ],
nullable "score": DOUBLE,
},
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null = schema! {
nullable "id": LONG,
nullable "metadata": {
nullable "name": LONG,
nullable "tags": LONG,
nullable "score": LONG,
},
};
let expected_fields = schema! {
nullable "id": LONG,
nullable "metadata": {
nullable "name": STRING,
nullable "score": DOUBLE,
},
};
let expected = expected_stats(expected_null, expected_fields);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_col_names() {
let properties: TableProperties = [(
"delta.dataSkippingStatsColumns".to_string(),
"`user.info`.name".to_string(),
)]
.into();
let file_schema = schema! {
nullable "id": LONG,
nullable "user.info": {
nullable "name": STRING,
nullable "age": INTEGER,
},
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_fields = schema! {
nullable "user.info": {
nullable "name": STRING,
},
};
let null_count = NullCountStatsTransform
.transform_struct(&expected_fields)
.into_owned();
let expected = expected_stats(null_count, expected_fields);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_stats_columns_with_complex_types() {
let properties: TableProperties = [(
"delta.dataSkippingStatsColumns".to_string(),
"id,tags".to_string(),
)]
.into();
let file_schema = schema! {
nullable "id": LONG,
nullable "tags": [ not_null STRING ],
nullable "metadata": { STRING => nullable STRING },
nullable "name": STRING,
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "id": LONG,
nullable "tags": LONG,
};
let expected_min_max = schema! { nullable "id": LONG };
let expected = expected_stats(expected_null_count, expected_min_max);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_n_cols() {
let properties: TableProperties = [(
"delta.dataSkippingNumIndexedCols".to_string(),
"1".to_string(),
)]
.into();
let logical_schema = schema! {
nullable "name": STRING,
nullable "age": INTEGER,
};
let stats_schema = expected_stats_schema(
&logical_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_fields = schema! { nullable "name": STRING };
let null_count = NullCountStatsTransform
.transform_struct(&expected_fields)
.into_owned();
let expected = expected_stats(null_count, expected_fields);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_different_fields_in_null_vs_minmax() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "is_active": BOOLEAN,
nullable "metadata": BINARY,
nullable "duration": INTERVAL_DAY_TIME,
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "id": LONG,
nullable "is_active": LONG,
nullable "metadata": LONG,
nullable "duration": LONG,
};
let expected_min_max = schema! { nullable "id": LONG };
let expected = expected_stats(expected_null_count, expected_min_max);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_nested_different_fields_in_null_vs_minmax() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": STRING,
nullable "is_admin": BOOLEAN,
nullable "age": INTEGER,
nullable "profile_pic": BINARY,
},
nullable "is_deleted": BOOLEAN,
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": LONG,
nullable "is_admin": LONG,
nullable "age": LONG,
nullable "profile_pic": LONG,
},
nullable "is_deleted": LONG,
};
let expected_min_max = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": STRING,
nullable "age": INTEGER,
},
};
let expected = expected_stats(expected_null_count, expected_min_max);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_only_non_eligible_fields() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "is_active": BOOLEAN,
nullable "metadata": BINARY,
nullable "duration": INTERVAL_DAY_TIME,
nullable "tags": [ not_null STRING ],
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "is_active": LONG,
nullable "metadata": LONG,
nullable "duration": LONG,
nullable "tags": LONG,
};
let expected = schema! {
nullable NUM_RECORDS: LONG,
nullable NULL_COUNT: (expected_null_count),
nullable TIGHT_BOUNDS: BOOLEAN,
};
assert_eq!(&expected, &stats_schema);
}
#[rstest::rstest]
#[case::num_indexed_cols("delta.dataSkippingNumIndexedCols", "1")]
#[case::stats_columns("delta.dataSkippingStatsColumns", "iv")]
fn test_interval_stats_respect_column_selection(
#[values(DataType::INTERVAL_YEAR_MONTH, DataType::INTERVAL_DAY_TIME)] interval: DataType,
#[case] property_name: &str,
#[case] property_value: &str,
) {
let properties: TableProperties = [(property_name, property_value)].into();
let file_schema = schema! {
nullable "iv": (interval),
nullable "value": LONG,
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected = schema! {
nullable NUM_RECORDS: LONG,
nullable NULL_COUNT: {
nullable "iv": LONG,
},
nullable TIGHT_BOUNDS: BOOLEAN,
};
assert_eq!(expected, stats_schema);
}
#[test]
fn test_stats_schema_complex_types_count_against_limit() {
let properties: TableProperties = [(
"delta.dataSkippingNumIndexedCols".to_string(),
"3".to_string(),
)]
.into();
let file_schema = schema! {
nullable "tags": [ not_null STRING ],
nullable "metadata": { STRING => nullable STRING },
nullable "v": unshredded_variant(),
nullable "col1": LONG,
nullable "col2": STRING,
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "tags": LONG,
nullable "metadata": LONG,
nullable "v": LONG,
};
let expected = schema! {
nullable NUM_RECORDS: LONG,
nullable NULL_COUNT: (expected_null_count),
nullable TIGHT_BOUNDS: BOOLEAN,
};
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_schema_complex_type_consumes_slot_before_primitive() {
let properties: TableProperties = [(
"delta.dataSkippingNumIndexedCols".to_string(),
"2".to_string(),
)]
.into();
let file_schema = schema! {
nullable "id": LONG,
nullable "tags": [ not_null STRING ],
nullable "name": STRING,
};
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "id": LONG,
nullable "tags": LONG,
};
let expected_min_max = schema! { nullable "id": LONG };
let expected = expected_stats(expected_null_count, expected_min_max);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_stats_column_names_default() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": STRING,
nullable "age": INTEGER,
},
};
let config = StatsConfig {
data_skipping_stats_columns: properties.data_skipping_stats_columns.as_deref(),
data_skipping_num_indexed_cols: properties.data_skipping_num_indexed_cols,
};
let columns = stats_column_names(&file_schema, &config, None);
assert_eq!(
columns,
vec![
column_name!("id"),
column_name!("user.name"),
column_name!("user.age"),
]
);
}
#[test]
fn test_stats_column_names_with_num_indexed_cols() {
let properties: TableProperties = [(
"delta.dataSkippingNumIndexedCols".to_string(),
"2".to_string(),
)]
.into();
let file_schema = schema! {
nullable "a": LONG,
nullable "b": STRING,
nullable "c": INTEGER,
nullable "d": DOUBLE,
};
let config = StatsConfig {
data_skipping_stats_columns: properties.data_skipping_stats_columns.as_deref(),
data_skipping_num_indexed_cols: properties.data_skipping_num_indexed_cols,
};
let columns = stats_column_names(&file_schema, &config, None);
assert_eq!(columns, vec![column_name!("a"), column_name!("b"),]);
}
#[test]
fn test_stats_column_names_with_stats_columns() {
let properties: TableProperties = [(
"delta.dataSkippingStatsColumns".to_string(),
"id,user.age".to_string(),
)]
.into();
let file_schema = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": STRING,
nullable "age": INTEGER,
},
nullable "extra": STRING,
};
let config = StatsConfig {
data_skipping_stats_columns: properties.data_skipping_stats_columns.as_deref(),
data_skipping_num_indexed_cols: properties.data_skipping_num_indexed_cols,
};
let columns = stats_column_names(&file_schema, &config, None);
assert_eq!(columns, vec![column_name!("id"), column_name!("user.age"),]);
}
#[test]
fn test_stats_column_names_includes_complex_types() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "tags": [ not_null STRING ],
nullable "metadata": { STRING => nullable STRING },
nullable "v": unshredded_variant(),
nullable "name": STRING,
};
let config = StatsConfig {
data_skipping_stats_columns: properties.data_skipping_stats_columns.as_deref(),
data_skipping_num_indexed_cols: properties.data_skipping_num_indexed_cols,
};
let columns = stats_column_names(&file_schema, &config, None);
assert_eq!(
columns,
vec![
column_name!("id"),
column_name!("tags"),
column_name!("metadata"),
column_name!("v"),
column_name!("name"),
]
);
}
#[test]
fn test_stats_schema_with_clustering_past_limit() {
let properties: TableProperties = [(
"delta.dataSkippingNumIndexedCols".to_string(),
"1".to_string(),
)]
.into();
let file_schema = schema! {
nullable "a": LONG,
nullable "b": STRING,
nullable "c": INTEGER,
};
let clustering_columns = vec![column_name!("c")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
Some(&clustering_columns),
None,
)
.unwrap();
let expected_null_count = schema! {
nullable "a": LONG,
nullable "c": LONG,
};
let expected_min_max = schema! {
nullable "a": LONG,
nullable "c": INTEGER,
};
let expected = expected_stats(expected_null_count, expected_min_max);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_requested_filters_to_single_column() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
nullable "value": INTEGER,
};
let columns = [column_name!("id")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let expected_nested = schema! { nullable "id": LONG };
let expected = expected_stats(expected_nested.clone(), expected_nested);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_none_requested_returns_full_schema() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
};
let with_none = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
let min_values = with_none.field(MIN_VALUES).expect("should have minValues");
if let DataType::Struct(inner) = min_values.data_type() {
assert!(inner.field("id").is_some());
assert!(inner.field("name").is_some());
} else {
panic!("minValues should be a struct");
}
}
#[test]
fn test_requested_column_outside_limit_excluded() {
let properties: TableProperties = [("delta.dataSkippingNumIndexedCols", "1")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
};
let columns = [column_name!("name")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let expected = schema! {
nullable NUM_RECORDS: LONG,
nullable TIGHT_BOUNDS: BOOLEAN,
};
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_required_bypasses_limit_with_requested_filter() {
let properties: TableProperties = [("delta.dataSkippingNumIndexedCols", "1")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
};
let columns = [column_name!("name")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
Some(&columns),
Some(&columns),
)
.unwrap();
let expected_nested = schema! { nullable "name": STRING };
let expected_null = schema! { nullable "name": LONG };
let expected = expected_stats(expected_null, expected_nested);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_requested_does_not_affect_column_counting() {
let properties: TableProperties = [("delta.dataSkippingNumIndexedCols", "2")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
nullable "value": INTEGER,
};
let columns = [column_name!("name")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let expected_nested = schema! { nullable "name": STRING };
let expected_null = schema! { nullable "name": LONG };
let expected = expected_stats(expected_null, expected_nested);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_multiple_requested_columns() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
nullable "value": INTEGER,
};
let columns = [column_name!("id"), column_name!("name")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let expected_nested = schema! {
nullable "id": LONG,
nullable "name": STRING,
};
let expected_null = schema! {
nullable "id": LONG,
nullable "name": LONG,
};
let expected = expected_stats(expected_null, expected_nested);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_nested_requested_column() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": STRING,
nullable "age": INTEGER,
},
};
let columns = [column_name!("user.name")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let expected_nested = schema! {
nullable "user": {
nullable "name": STRING,
},
};
let expected_null = schema! {
nullable "user": {
nullable "name": LONG,
},
};
let expected = expected_stats(expected_null, expected_nested);
assert_eq!(&expected, &stats_schema);
}
#[test]
fn test_empty_requested_columns() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "name": STRING,
};
let columns: [ColumnName; 0] = [];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let full_stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
None,
)
.unwrap();
assert_eq!(&full_stats_schema, &stats_schema);
}
#[test]
fn test_mixed_nested_and_top_requested() {
let properties: TableProperties = [("key", "value")].into();
let file_schema = schema! {
nullable "id": LONG,
nullable "user": {
nullable "name": STRING,
nullable "age": INTEGER,
},
nullable "value": DOUBLE,
};
let columns = [column_name!("id"), column_name!("user.age")];
let stats_schema = expected_stats_schema(
&file_schema,
&stats_config_from_table_properties(&properties),
None,
Some(&columns),
)
.unwrap();
let expected_nested = schema! {
nullable "id": LONG,
nullable "user": {
nullable "age": INTEGER,
},
};
let expected_null = schema! {
nullable "id": LONG,
nullable "user": {
nullable "age": LONG,
},
};
let expected = expected_stats(expected_null, expected_nested);
assert_eq!(&expected, &stats_schema);
}
}