datasources/converters/
deterministic_type_converter.rs1use 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}