1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ColumnInfo {
11 pub name: String,
13
14 pub col_type: ColumnType,
16
17 pub nullable: bool,
19
20 pub description: Option<String>,
22}
23
24impl ColumnInfo {
25 pub fn new(name: impl Into<String>, col_type: ColumnType) -> Self {
27 Self {
28 name: name.into(),
29 col_type,
30 nullable: true,
31 description: None,
32 }
33 }
34
35 pub fn with_nullable(mut self, nullable: bool) -> Self {
37 self.nullable = nullable;
38 self
39 }
40
41 pub fn with_description(mut self, description: impl Into<String>) -> Self {
43 self.description = Some(description.into());
44 self
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
50#[serde(rename_all = "lowercase")]
51pub enum ColumnType {
52 #[default]
54 String,
55
56 Integer,
58
59 Integer64,
61
62 Decimal,
64
65 Boolean,
67
68 Date,
70
71 DateTime,
73
74 Instant,
76
77 Time,
79
80 Base64Binary,
82
83 Json,
85}
86
87impl ColumnType {
88 pub fn from_fhir_type(type_str: &str) -> Self {
92 match type_str.to_lowercase().as_str() {
93 "string" | "code" | "uri" | "url" | "canonical" | "id" | "oid" | "uuid"
94 | "markdown" => Self::String,
95 "integer" | "positiveint" | "unsignedint" => Self::Integer,
96 "integer64" => Self::Integer64,
97 "decimal" => Self::Decimal,
98 "boolean" => Self::Boolean,
99 "date" => Self::Date,
100 "datetime" => Self::DateTime,
101 "instant" => Self::Instant,
102 "time" => Self::Time,
103 "base64binary" => Self::Base64Binary,
104 _ => Self::String, }
106 }
107
108 pub fn from_ansi_type(type_str: &str) -> Self {
112 let t = type_str.trim().to_lowercase();
113 match t.as_str() {
114 "int" | "integer" | "smallint" => Self::Integer,
115 "bigint" => Self::Integer64,
116 "decimal" | "numeric" | "real" | "double precision" | "float" => Self::Decimal,
117 "boolean" | "bool" => Self::Boolean,
118 "timestamp with time zone" | "timestamptz" => Self::Instant,
119 "binary" | "varbinary" | "bytea" => Self::Base64Binary,
120 "json" | "jsonb" => Self::Json,
121 _ => Self::String,
122 }
123 }
124
125 pub fn sql_type(&self) -> &'static str {
128 match self {
129 Self::String => "TEXT",
130 Self::Integer => "INTEGER",
131 Self::Integer64 => "BIGINT",
132 Self::Decimal => "NUMERIC",
133 Self::Boolean => "BOOLEAN",
134 Self::Date => "DATE",
135 Self::DateTime | Self::Instant => "TIMESTAMPTZ",
136 Self::Time => "TIME",
137 Self::Base64Binary => "BYTEA",
138 Self::Json => "JSONB",
139 }
140 }
141
142 pub fn ansi_type(&self) -> &'static str {
147 match self {
148 Self::String | Self::Decimal | Self::Date | Self::DateTime | Self::Time => {
151 "CHARACTER VARYING"
152 }
153 Self::Integer => "INT",
154 Self::Integer64 => "BIGINT",
155 Self::Boolean => "BOOLEAN",
156 Self::Instant => "TIMESTAMP WITH TIME ZONE",
157 Self::Base64Binary => "BINARY",
158 Self::Json => "JSON",
160 }
161 }
162
163 pub fn null_representation(&self) -> &'static str {
165 "NULL"
166 }
167}
168
169impl std::fmt::Display for ColumnType {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 match self {
172 Self::String => write!(f, "string"),
173 Self::Integer => write!(f, "integer"),
174 Self::Integer64 => write!(f, "integer64"),
175 Self::Decimal => write!(f, "decimal"),
176 Self::Boolean => write!(f, "boolean"),
177 Self::Date => write!(f, "date"),
178 Self::DateTime => write!(f, "dateTime"),
179 Self::Instant => write!(f, "instant"),
180 Self::Time => write!(f, "time"),
181 Self::Base64Binary => write!(f, "base64Binary"),
182 Self::Json => write!(f, "json"),
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_column_type_from_fhir_type() {
193 assert_eq!(ColumnType::from_fhir_type("string"), ColumnType::String);
194 assert_eq!(ColumnType::from_fhir_type("code"), ColumnType::String);
195 assert_eq!(ColumnType::from_fhir_type("integer"), ColumnType::Integer);
196 assert_eq!(
197 ColumnType::from_fhir_type("positiveInt"),
198 ColumnType::Integer
199 );
200 assert_eq!(ColumnType::from_fhir_type("decimal"), ColumnType::Decimal);
201 assert_eq!(ColumnType::from_fhir_type("boolean"), ColumnType::Boolean);
202 assert_eq!(ColumnType::from_fhir_type("date"), ColumnType::Date);
203 assert_eq!(ColumnType::from_fhir_type("dateTime"), ColumnType::DateTime);
204 assert_eq!(ColumnType::from_fhir_type("instant"), ColumnType::Instant);
205 assert_eq!(ColumnType::from_fhir_type("time"), ColumnType::Time);
206 assert_eq!(
207 ColumnType::from_fhir_type("base64Binary"),
208 ColumnType::Base64Binary
209 );
210 assert_eq!(
212 ColumnType::from_fhir_type("UnknownType"),
213 ColumnType::String
214 );
215 }
216
217 #[test]
218 fn test_column_type_sql_type() {
219 assert_eq!(ColumnType::String.sql_type(), "TEXT");
220 assert_eq!(ColumnType::Integer.sql_type(), "INTEGER");
221 assert_eq!(ColumnType::Integer64.sql_type(), "BIGINT");
222 assert_eq!(ColumnType::Decimal.sql_type(), "NUMERIC");
223 assert_eq!(ColumnType::Boolean.sql_type(), "BOOLEAN");
224 assert_eq!(ColumnType::Date.sql_type(), "DATE");
225 assert_eq!(ColumnType::DateTime.sql_type(), "TIMESTAMPTZ");
226 assert_eq!(ColumnType::Json.sql_type(), "JSONB");
227 }
228
229 #[test]
230 fn test_column_type_ansi_type() {
231 assert_eq!(ColumnType::String.ansi_type(), "CHARACTER VARYING");
233 assert_eq!(ColumnType::Decimal.ansi_type(), "CHARACTER VARYING");
234 assert_eq!(ColumnType::Date.ansi_type(), "CHARACTER VARYING");
235 assert_eq!(ColumnType::DateTime.ansi_type(), "CHARACTER VARYING");
236 assert_eq!(ColumnType::Time.ansi_type(), "CHARACTER VARYING");
237 assert_eq!(ColumnType::Integer.ansi_type(), "INT");
238 assert_eq!(ColumnType::Integer64.ansi_type(), "BIGINT");
239 assert_eq!(ColumnType::Boolean.ansi_type(), "BOOLEAN");
240 assert_eq!(ColumnType::Instant.ansi_type(), "TIMESTAMP WITH TIME ZONE");
241 assert_eq!(ColumnType::Base64Binary.ansi_type(), "BINARY");
242 }
243
244 #[test]
245 fn test_integer64_maps_to_bigint() {
246 assert_eq!(
247 ColumnType::from_fhir_type("integer64"),
248 ColumnType::Integer64
249 );
250 assert_eq!(ColumnType::from_fhir_type("integer"), ColumnType::Integer);
251 assert_eq!(ColumnType::from_ansi_type("BIGINT"), ColumnType::Integer64);
252 assert_eq!(ColumnType::from_ansi_type("INTEGER"), ColumnType::Integer);
253 }
254
255 #[test]
256 fn test_column_info_builder() {
257 let col = ColumnInfo::new("test_col", ColumnType::String)
258 .with_nullable(false)
259 .with_description("A test column");
260
261 assert_eq!(col.name, "test_col");
262 assert_eq!(col.col_type, ColumnType::String);
263 assert!(!col.nullable);
264 assert_eq!(col.description, Some("A test column".to_string()));
265 }
266}