use super::TableFeature;
use crate::schema::{PrimitiveType, Schema};
use crate::table_configuration::TableConfiguration;
use crate::transforms::{transform_output_type, SchemaTransform};
use crate::utils::require;
use crate::{DeltaResult, Error};
fn schema_contains_geospatial(schema: &Schema) -> bool {
UsesGeo.transform_struct(schema).is_err()
}
struct UsesGeo;
impl<'a> SchemaTransform<'a> for UsesGeo {
transform_output_type!(|'a, T| Result<(), ()>);
fn transform_primitive(&mut self, ptype: &'a PrimitiveType) -> Result<(), ()> {
match ptype {
PrimitiveType::Geometry(_) | PrimitiveType::Geography(_) => Err(()),
_ => Ok(()),
}
}
}
pub(crate) fn validate_geospatial_feature_support(tc: &TableConfiguration) -> DeltaResult<()> {
if schema_contains_geospatial(&tc.logical_schema()) {
require!(
tc.protocol().has_table_feature(&TableFeature::GeospatialType),
Error::unsupported(
"Table contains geometry or geography columns but does not have the required 'geospatial' feature in reader and writer features"
)
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::actions::Protocol;
use crate::schema::{
schema, ArrayType, DataType, EdgeInterpolationAlgorithm, MapType, StructType,
};
use crate::unit_test_utils::{assert_schema_feature_validation, geography_type, geometry_type};
fn schema_with_field(dt: DataType) -> StructType {
schema! {
not_null "id": INTEGER,
nullable "g": (dt),
}
}
#[rstest]
#[case::top_level_geometry(schema_with_field(geometry_type("EPSG:4326")), true)]
#[case::nested_struct(
schema_with_field(DataType::from(schema! {
nullable "inner_geo": (geography_type(
"EPSG:4326",
EdgeInterpolationAlgorithm::Spherical,
)),
})),
true
)]
#[case::in_array(schema_with_field(ArrayType::new(geometry_type("EPSG:4326"), true).into()), true)]
#[case::in_map(
schema_with_field(MapType::new(DataType::STRING, geography_type("EPSG:4326", EdgeInterpolationAlgorithm::Spherical), true).into()),
true
)]
#[case::no_geo(schema_with_field(DataType::STRING), false)]
fn test_schema_contains_geospatial(#[case] schema: StructType, #[case] expected: bool) {
assert_eq!(schema_contains_geospatial(&schema), expected);
}
#[test]
fn test_geospatial_feature_validation() {
let schema_with_geotype = schema! {
not_null "id": INTEGER,
nullable "geom": (geometry_type("EPSG:4326")),
};
let schema_without_geotype = schema! {
not_null "id": INTEGER,
nullable "name": STRING,
};
let nested_schema_with_geotype = schema! {
not_null "id": INTEGER,
nullable "nested": {
nullable "inner_geo": (geography_type(
"EPSG:4326",
EdgeInterpolationAlgorithm::Spherical,
)),
},
};
let protocol_with_geotype = Protocol::try_new_modern(
[TableFeature::GeospatialType],
[TableFeature::GeospatialType],
)
.unwrap();
let protocol_without_geotype =
Protocol::try_new_modern(TableFeature::EMPTY_LIST, TableFeature::EMPTY_LIST).unwrap();
assert_schema_feature_validation(
&schema_with_geotype,
&schema_without_geotype,
&protocol_with_geotype,
&protocol_without_geotype,
&[&nested_schema_with_geotype],
"Table contains geometry or geography columns but does not have the required 'geospatial' feature in reader and writer features",
);
}
}