datafusion_quality/rules/
schema.rs1use crate::{SchemaRule, ValidationError};
2use datafusion::{arrow::datatypes::DataType, common::DFSchema};
3use std::sync::Arc;
4
5#[derive(Debug, Clone, Default)]
7pub struct ColumnExistsRule {
8 column_name: String,
9}
10
11impl ColumnExistsRule {
12 pub fn new(column_name: String) -> Self {
18 Self { column_name }
19 }
20}
21
22impl SchemaRule for ColumnExistsRule {
23 fn validate_schema(&self, schema: &DFSchema) -> Result<bool, ValidationError> {
24 match schema.field_with_name(None, &self.column_name) {
25 Ok(_) => Ok(true),
26 Err(_) => Err(ValidationError::ColumnNotFound {
27 column_name: self.column_name.clone(),
28 }),
29 }
30 }
31
32 fn name(&self) -> &str {
33 "column_exists"
34 }
35
36 fn description(&self) -> &str {
37 "Checks if a column exists in the schema"
38 }
39}
40
41pub fn dfq_column_exists(column_name: impl AsRef<str>) -> Arc<ColumnExistsRule> {
55 Arc::new(ColumnExistsRule::new(column_name.as_ref().to_string()))
56}
57
58#[derive(Debug, Clone)]
60pub struct ColumnTypeRule {
61 column_name: String,
62 expected_type: DataType,
63}
64
65impl ColumnTypeRule {
66 pub fn new(column_name: String, expected_type: DataType) -> Self {
73 Self {
74 column_name,
75 expected_type,
76 }
77 }
78}
79
80impl SchemaRule for ColumnTypeRule {
81 fn validate_schema(&self, schema: &DFSchema) -> Result<bool, ValidationError> {
82 match schema.field_with_name(None, &self.column_name) {
83 Ok(field) => {
84 if field.data_type() == &self.expected_type {
85 Ok(true)
86 } else {
87 Err(ValidationError::TypeMismatch {
88 message: format!(
89 "Column: {}, Expected type {:?} but got {:?}",
90 self.column_name,
91 self.expected_type,
92 field.data_type()
93 ),
94 })
95 }
96 }
97 Err(_) => Err(ValidationError::ColumnNotFound {
98 column_name: self.column_name.clone(),
99 }),
100 }
101 }
102
103 fn name(&self) -> &str {
104 "column_type"
105 }
106
107 fn description(&self) -> &str {
108 "Checks if a column has a specific data type"
109 }
110}
111
112pub fn dfq_column_type(
127 column_name: impl AsRef<str>,
128 expected_type: DataType,
129) -> Arc<ColumnTypeRule> {
130 Arc::new(ColumnTypeRule::new(
131 column_name.as_ref().to_string(),
132 expected_type,
133 ))
134}
135
136#[derive(Debug, Clone)]
138pub struct ColumnNullableRule {
139 column_name: String,
140 expected_nullable: bool,
141}
142
143impl ColumnNullableRule {
144 pub fn new(column_name: String, expected_nullable: bool) -> Self {
151 Self {
152 column_name,
153 expected_nullable,
154 }
155 }
156}
157
158impl SchemaRule for ColumnNullableRule {
159 fn validate_schema(&self, schema: &DFSchema) -> Result<bool, ValidationError> {
160 match schema.field_with_name(None, &self.column_name) {
161 Ok(field) => {
162 if field.is_nullable() == self.expected_nullable {
163 Ok(true)
164 } else {
165 Err(ValidationError::ColumnNullabilityMismatch {
166 column_name: self.column_name.clone(),
167 expected: self.expected_nullable,
168 })
169 }
170 }
171 Err(_) => Err(ValidationError::ColumnNotFound {
172 column_name: self.column_name.clone(),
173 }),
174 }
175 }
176
177 fn name(&self) -> &str {
178 "column_nullable"
179 }
180
181 fn description(&self) -> &str {
182 "Checks if a column is nullable"
183 }
184}
185
186pub fn dfq_column_nullable(column_name: impl AsRef<str>) -> Arc<ColumnNullableRule> {
200 Arc::new(ColumnNullableRule::new(
201 column_name.as_ref().to_string(),
202 true,
203 ))
204}
205
206pub fn dfq_column_not_nullable(column_name: impl AsRef<str>) -> Arc<ColumnNullableRule> {
220 Arc::new(ColumnNullableRule::new(
221 column_name.as_ref().to_string(),
222 false,
223 ))
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use datafusion::arrow::datatypes::{Field, Schema};
230 use datafusion::common::DFSchema;
231
232 #[test]
233 fn test_column_exists_rule() {
234 let arrow_schema = Schema::new(vec![
235 Field::new("id", DataType::Int32, false),
236 Field::new("name", DataType::Utf8, true),
237 ]);
238 let schema = DFSchema::try_from(arrow_schema).unwrap();
239
240 let rule = dfq_column_exists("id");
241 assert!(rule.validate_schema(&schema).unwrap());
242
243 let rule = dfq_column_exists("nonexistent");
244 assert!(rule.validate_schema(&schema).is_err());
245 }
246
247 #[test]
248 fn test_column_type_rule() {
249 let arrow_schema = Schema::new(vec![
250 Field::new("id", DataType::Int32, false),
251 Field::new("name", DataType::Utf8, true),
252 ]);
253 let schema = DFSchema::try_from(arrow_schema).unwrap();
254
255 let rule = dfq_column_type("id", DataType::Int32);
256 assert!(rule.validate_schema(&schema).unwrap());
257
258 let rule = dfq_column_type("id", DataType::Utf8);
259 assert!(rule.validate_schema(&schema).is_err());
260
261 let rule = dfq_column_type("nonexistent", DataType::Int32);
262 assert!(rule.validate_schema(&schema).is_err());
263 }
264
265 #[test]
266 fn test_column_nullable_rule() {
267 let arrow_schema = Schema::new(vec![
268 Field::new("id", DataType::Int32, false),
269 Field::new("name", DataType::Utf8, true),
270 ]);
271 let schema = DFSchema::try_from(arrow_schema).unwrap();
272
273 let rule = dfq_column_not_nullable("id");
274 assert!(rule.validate_schema(&schema).unwrap());
275
276 let rule = dfq_column_nullable("name");
277 assert!(rule.validate_schema(&schema).unwrap());
278
279 let rule = dfq_column_nullable("id");
280 assert!(rule.validate_schema(&schema).is_err());
281
282 let rule = dfq_column_nullable("nonexistent");
283 assert!(rule.validate_schema(&schema).is_err());
284 }
285}