Skip to main content

datasources/converters/
deterministic_type_converter.rs

1use super::decimal_rule::DecimalRule;
2use super::mapping::Mapping;
3use super::spec_field::{FieldSize, SpecField};
4use super::spec_field_rules::SpecFieldRules;
5use crate::datasource_error::DatasourceError;
6use crate::result::Result;
7use std::collections::HashMap;
8
9pub struct DeterministicTypeConverter {
10    pub dictionary: HashMap<String, String>,
11    reverse: HashMap<String, Vec<String>>,
12    rules: SpecFieldRules,
13}
14
15impl DeterministicTypeConverter {
16    pub fn new(dictionary: HashMap<String, String>, rules: SpecFieldRules) -> Self {
17        let mut reverse = HashMap::<String, Vec<String>>::new();
18        for (spec_type, native) in &dictionary {
19            reverse.entry(native.clone()).or_default().push(spec_type.clone());
20        }
21        Self { dictionary, reverse, rules }
22    }
23
24    pub fn convert(&self, field: &SpecField) -> Result<String> {
25        self.convert_field(field)
26    }
27
28    fn convert_field(&self, field: &SpecField) -> Result<String> {
29        let row = &self.rules;
30        if field.type_name == "string" {
31            return Ok(sized(&row.string_unlimited, row.string_sized, field));
32        }
33        if field.type_name == "character" {
34            if let Some(character) = &row.character {
35                return Ok(format!("{character}({})", char_len(field)));
36            }
37        }
38        if field.type_name == "decimal" {
39            if let Some(rule) = &row.decimal {
40            let ps = decimal_precision_scale(field)?;
41            return match rule {
42                DecimalRule::Required { type_name } => {
43                    let (p, s) = ps.ok_or_else(|| {
44                        DatasourceError::message(format!(
45                            "decimal field \"{}\" requires size: [precision, scale] for {}",
46                            field.name.as_deref().unwrap_or(""),
47                            row.dialect_name.as_deref().unwrap_or("sql")
48                        ))
49                    })?;
50                    Ok(format!("{type_name}({p}, {s})"))
51                }
52                DecimalRule::Optional { type_name, fallback } => Ok(match ps {
53                    Some((p, s)) => format!("{type_name}({p}, {s})"),
54                    None => fallback.clone(),
55                }),
56            };
57            }
58        }
59        if field.type_name == "binary" {
60            if let (Some(unlimited), Some(render)) = (&row.binary_unlimited, row.binary_sized) {
61                return Ok(sized(unlimited, render, field));
62            }
63        }
64        self.dictionary
65            .get(&field.type_name)
66            .cloned()
67            .ok_or_else(|| {
68                DatasourceError::message(format!("Unknown spec field type: {}", field.type_name))
69            })
70    }
71
72    pub fn reverse_convert(&self, native: &str) -> Result<String> {
73        Mapping::reverse_convert(self, &native.to_string())
74    }
75}
76
77impl Mapping<String, String> for DeterministicTypeConverter {
78    fn convert(&self, from: &String) -> Result<String> {
79        self.dictionary.get(from).cloned().ok_or_else(|| {
80            DatasourceError::message(format!("Unknown spec field type: {from}"))
81        })
82    }
83
84    fn reverse_convert(&self, native: &String) -> Result<String> {
85        let spec_types = self.reverse.get(native).ok_or_else(|| {
86            DatasourceError::message(format!("Unknown native field type: {native}"))
87        })?;
88        if spec_types.len() > 1 {
89            return Err(DatasourceError::message(format!(
90                "Native field type {native} maps to multiple spec types: {}",
91                spec_types.join(", ")
92            )));
93        }
94        Ok(spec_types[0].clone())
95    }
96}
97
98fn sized(unlimited: &str, render: fn(i32) -> String, field: &SpecField) -> String {
99    match field.size {
100        None | Some(FieldSize::Unlimited) => unlimited.to_string(),
101        Some(FieldSize::Length(n)) | Some(FieldSize::Decimal(n, _)) => render(n),
102    }
103}
104
105fn char_len(field: &SpecField) -> i32 {
106    match field.size {
107        None | Some(FieldSize::Unlimited) => 1,
108        Some(FieldSize::Length(n)) | Some(FieldSize::Decimal(n, _)) => n,
109    }
110}
111
112fn decimal_precision_scale(field: &SpecField) -> Result<Option<(i32, i32)>> {
113    match &field.size {
114        None | Some(FieldSize::Unlimited) => Ok(None),
115        Some(FieldSize::Decimal(p, s)) => Ok(Some((*p, *s))),
116        Some(FieldSize::Length(_)) => Err(DatasourceError::message(format!(
117            "decimal field \"{}\" size must be [precision, scale] tuple; got {:?}",
118            field.name.as_deref().unwrap_or(""),
119            field.size
120        ))),
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn maps_sized_string() {
130        let converter = DeterministicTypeConverter::new(
131            HashMap::from([("string".into(), "TEXT".into())]),
132            SpecFieldRules {
133                dialect_name: Some("sqlite".into()),
134                string_unlimited: "TEXT".into(),
135                string_sized: |n| format!("VARCHAR({n})"),
136                character: None,
137                decimal: None,
138                binary_unlimited: None,
139                binary_sized: None,
140            },
141        );
142        let field = SpecField {
143            type_name: "string".into(),
144            size: Some(FieldSize::Length(64)),
145            ..SpecField::default()
146        };
147        assert_eq!(converter.convert(&field).expect("type"), "VARCHAR(64)");
148    }
149}