1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::schema::{
5 foreign_key::ForeignKeySyntax, names::ColumnName, primary_key::PrimaryKeySyntax,
6 str_or_bool::StrOrBoolOrArray,
7};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
10#[serde(rename_all = "snake_case")]
11pub struct ColumnDef {
12 pub name: ColumnName,
13 pub r#type: ColumnType,
14 pub nullable: bool,
15 pub default: Option<String>,
16 pub comment: Option<String>,
17 pub primary_key: Option<PrimaryKeySyntax>,
18 pub unique: Option<StrOrBoolOrArray>,
19 pub index: Option<StrOrBoolOrArray>,
20 pub foreign_key: Option<ForeignKeySyntax>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24#[serde(rename_all = "snake_case", untagged)]
25pub enum ColumnType {
26 Simple(SimpleColumnType),
27 Complex(ComplexColumnType),
28}
29
30impl ColumnType {
31 pub fn to_sql(&self) -> String {
33 match self {
34 ColumnType::Simple(ty) => match ty {
35 SimpleColumnType::SmallInt => "SMALLINT".into(),
36 SimpleColumnType::Integer => "INTEGER".into(),
37 SimpleColumnType::BigInt => "BIGINT".into(),
38 SimpleColumnType::Real => "REAL".into(),
39 SimpleColumnType::DoublePrecision => "DOUBLE PRECISION".into(),
40 SimpleColumnType::Text => "TEXT".into(),
41 SimpleColumnType::Boolean => "BOOLEAN".into(),
42 SimpleColumnType::Date => "DATE".into(),
43 SimpleColumnType::Time => "TIME".into(),
44 SimpleColumnType::Timestamp => "TIMESTAMP".into(),
45 SimpleColumnType::Timestamptz => "TIMESTAMPTZ".into(),
46 SimpleColumnType::Interval => "INTERVAL".into(),
47 SimpleColumnType::Bytea => "BYTEA".into(),
48 SimpleColumnType::Uuid => "UUID".into(),
49 SimpleColumnType::Json => "JSON".into(),
50 SimpleColumnType::Jsonb => "JSONB".into(),
51 SimpleColumnType::Inet => "INET".into(),
52 SimpleColumnType::Cidr => "CIDR".into(),
53 SimpleColumnType::Macaddr => "MACADDR".into(),
54 SimpleColumnType::Xml => "XML".into(),
55 },
56 ColumnType::Complex(ty) => match ty {
57 ComplexColumnType::Varchar { length } => format!("VARCHAR({})", length),
58 ComplexColumnType::Numeric { precision, scale } => {
59 format!("NUMERIC({}, {})", precision, scale)
60 }
61 ComplexColumnType::Char { length } => format!("CHAR({})", length),
62 ComplexColumnType::Custom { custom_type } => custom_type.clone(),
63 },
64 }
65 }
66
67 pub fn to_rust_type(&self, nullable: bool) -> String {
69 let base = match self {
70 ColumnType::Simple(ty) => match ty {
71 SimpleColumnType::SmallInt => "i16".to_string(),
72 SimpleColumnType::Integer => "i32".to_string(),
73 SimpleColumnType::BigInt => "i64".to_string(),
74 SimpleColumnType::Real => "f32".to_string(),
75 SimpleColumnType::DoublePrecision => "f64".to_string(),
76 SimpleColumnType::Text => "String".to_string(),
77 SimpleColumnType::Boolean => "bool".to_string(),
78 SimpleColumnType::Date => "Date".to_string(),
79 SimpleColumnType::Time => "Time".to_string(),
80 SimpleColumnType::Timestamp => "DateTime".to_string(),
81 SimpleColumnType::Timestamptz => "DateTimeWithTimeZone".to_string(),
82 SimpleColumnType::Interval => "String".to_string(),
83 SimpleColumnType::Bytea => "Vec<u8>".to_string(),
84 SimpleColumnType::Uuid => "Uuid".to_string(),
85 SimpleColumnType::Json | SimpleColumnType::Jsonb => "Json".to_string(),
86 SimpleColumnType::Inet | SimpleColumnType::Cidr => "String".to_string(),
87 SimpleColumnType::Macaddr => "String".to_string(),
88 SimpleColumnType::Xml => "String".to_string(),
89 },
90 ColumnType::Complex(ty) => match ty {
91 ComplexColumnType::Varchar { .. } => "String".to_string(),
92 ComplexColumnType::Numeric { .. } => "Decimal".to_string(),
93 ComplexColumnType::Char { .. } => "String".to_string(),
94 ComplexColumnType::Custom { .. } => "String".to_string(), },
96 };
97
98 if nullable {
99 format!("Option<{}>", base)
100 } else {
101 base
102 }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
107#[serde(rename_all = "snake_case")]
108pub enum SimpleColumnType {
109 SmallInt,
110 Integer,
111 BigInt,
112 Real,
113 DoublePrecision,
114
115 Text,
117
118 Boolean,
120
121 Date,
123 Time,
124 Timestamp,
125 Timestamptz,
126 Interval,
127
128 Bytea,
130
131 Uuid,
133
134 Json,
136 Jsonb,
137
138 Inet,
140 Cidr,
141 Macaddr,
142
143 Xml,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
148#[serde(rename_all = "snake_case", tag = "kind")]
149pub enum ComplexColumnType {
150 Varchar { length: u32 },
151 Numeric { precision: u32, scale: u32 },
152 Char { length: u32 },
153 Custom { custom_type: String },
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use rstest::rstest;
160
161 #[rstest]
162 #[case(SimpleColumnType::SmallInt, "SMALLINT")]
163 #[case(SimpleColumnType::Integer, "INTEGER")]
164 #[case(SimpleColumnType::BigInt, "BIGINT")]
165 #[case(SimpleColumnType::Real, "REAL")]
166 #[case(SimpleColumnType::DoublePrecision, "DOUBLE PRECISION")]
167 #[case(SimpleColumnType::Text, "TEXT")]
168 #[case(SimpleColumnType::Boolean, "BOOLEAN")]
169 #[case(SimpleColumnType::Date, "DATE")]
170 #[case(SimpleColumnType::Time, "TIME")]
171 #[case(SimpleColumnType::Timestamp, "TIMESTAMP")]
172 #[case(SimpleColumnType::Timestamptz, "TIMESTAMPTZ")]
173 #[case(SimpleColumnType::Interval, "INTERVAL")]
174 #[case(SimpleColumnType::Bytea, "BYTEA")]
175 #[case(SimpleColumnType::Uuid, "UUID")]
176 #[case(SimpleColumnType::Json, "JSON")]
177 #[case(SimpleColumnType::Jsonb, "JSONB")]
178 #[case(SimpleColumnType::Inet, "INET")]
179 #[case(SimpleColumnType::Cidr, "CIDR")]
180 #[case(SimpleColumnType::Macaddr, "MACADDR")]
181 #[case(SimpleColumnType::Xml, "XML")]
182 fn test_simple_column_type_to_sql(
183 #[case] column_type: SimpleColumnType,
184 #[case] expected: &str,
185 ) {
186 assert_eq!(ColumnType::Simple(column_type).to_sql(), expected);
187 }
188
189 #[rstest]
190 #[case(ComplexColumnType::Varchar { length: 255 }, "VARCHAR(255)")]
191 #[case(ComplexColumnType::Varchar { length: 50 }, "VARCHAR(50)")]
192 #[case(ComplexColumnType::Varchar { length: 1 }, "VARCHAR(1)")]
193 #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "NUMERIC(10, 2)")]
194 #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, "NUMERIC(5, 0)")]
195 #[case(ComplexColumnType::Numeric { precision: 18, scale: 4 }, "NUMERIC(18, 4)")]
196 #[case(ComplexColumnType::Char { length: 10 }, "CHAR(10)")]
197 #[case(ComplexColumnType::Char { length: 1 }, "CHAR(1)")]
198 #[case(ComplexColumnType::Char { length: 255 }, "CHAR(255)")]
199 #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "MONEY")]
200 #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, "JSONB")]
201 #[case(ComplexColumnType::Custom { custom_type: "CUSTOM_TYPE".into() }, "CUSTOM_TYPE")]
202 fn test_complex_column_type_to_sql(
203 #[case] column_type: ComplexColumnType,
204 #[case] expected: &str,
205 ) {
206 assert_eq!(ColumnType::Complex(column_type).to_sql(), expected);
207 }
208
209 #[rstest]
210 #[case(SimpleColumnType::SmallInt, "i16")]
211 #[case(SimpleColumnType::Integer, "i32")]
212 #[case(SimpleColumnType::BigInt, "i64")]
213 #[case(SimpleColumnType::Real, "f32")]
214 #[case(SimpleColumnType::DoublePrecision, "f64")]
215 #[case(SimpleColumnType::Text, "String")]
216 #[case(SimpleColumnType::Boolean, "bool")]
217 #[case(SimpleColumnType::Date, "Date")]
218 #[case(SimpleColumnType::Time, "Time")]
219 #[case(SimpleColumnType::Timestamp, "DateTime")]
220 #[case(SimpleColumnType::Timestamptz, "DateTimeWithTimeZone")]
221 #[case(SimpleColumnType::Interval, "String")]
222 #[case(SimpleColumnType::Bytea, "Vec<u8>")]
223 #[case(SimpleColumnType::Uuid, "Uuid")]
224 #[case(SimpleColumnType::Json, "Json")]
225 #[case(SimpleColumnType::Jsonb, "Json")]
226 #[case(SimpleColumnType::Inet, "String")]
227 #[case(SimpleColumnType::Cidr, "String")]
228 #[case(SimpleColumnType::Macaddr, "String")]
229 #[case(SimpleColumnType::Xml, "String")]
230 fn test_simple_column_type_to_rust_type_not_nullable(
231 #[case] column_type: SimpleColumnType,
232 #[case] expected: &str,
233 ) {
234 assert_eq!(
235 ColumnType::Simple(column_type).to_rust_type(false),
236 expected
237 );
238 }
239
240 #[rstest]
241 #[case(SimpleColumnType::SmallInt, "Option<i16>")]
242 #[case(SimpleColumnType::Integer, "Option<i32>")]
243 #[case(SimpleColumnType::BigInt, "Option<i64>")]
244 #[case(SimpleColumnType::Real, "Option<f32>")]
245 #[case(SimpleColumnType::DoublePrecision, "Option<f64>")]
246 #[case(SimpleColumnType::Text, "Option<String>")]
247 #[case(SimpleColumnType::Boolean, "Option<bool>")]
248 #[case(SimpleColumnType::Date, "Option<Date>")]
249 #[case(SimpleColumnType::Time, "Option<Time>")]
250 #[case(SimpleColumnType::Timestamp, "Option<DateTime>")]
251 #[case(SimpleColumnType::Timestamptz, "Option<DateTimeWithTimeZone>")]
252 #[case(SimpleColumnType::Interval, "Option<String>")]
253 #[case(SimpleColumnType::Bytea, "Option<Vec<u8>>")]
254 #[case(SimpleColumnType::Uuid, "Option<Uuid>")]
255 #[case(SimpleColumnType::Json, "Option<Json>")]
256 #[case(SimpleColumnType::Jsonb, "Option<Json>")]
257 #[case(SimpleColumnType::Inet, "Option<String>")]
258 #[case(SimpleColumnType::Cidr, "Option<String>")]
259 #[case(SimpleColumnType::Macaddr, "Option<String>")]
260 #[case(SimpleColumnType::Xml, "Option<String>")]
261 fn test_simple_column_type_to_rust_type_nullable(
262 #[case] column_type: SimpleColumnType,
263 #[case] expected: &str,
264 ) {
265 assert_eq!(ColumnType::Simple(column_type).to_rust_type(true), expected);
266 }
267
268 #[rstest]
269 #[case(ComplexColumnType::Varchar { length: 255 }, false, "String")]
270 #[case(ComplexColumnType::Varchar { length: 50 }, false, "String")]
271 #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, false, "Decimal")]
272 #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, false, "Decimal")]
273 #[case(ComplexColumnType::Char { length: 10 }, false, "String")]
274 #[case(ComplexColumnType::Char { length: 1 }, false, "String")]
275 #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, false, "String")]
276 #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, false, "String")]
277 fn test_complex_column_type_to_rust_type_not_nullable(
278 #[case] column_type: ComplexColumnType,
279 #[case] nullable: bool,
280 #[case] expected: &str,
281 ) {
282 assert_eq!(
283 ColumnType::Complex(column_type).to_rust_type(nullable),
284 expected
285 );
286 }
287
288 #[rstest]
289 #[case(ComplexColumnType::Varchar { length: 255 }, "Option<String>")]
290 #[case(ComplexColumnType::Varchar { length: 50 }, "Option<String>")]
291 #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "Option<Decimal>")]
292 #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, "Option<Decimal>")]
293 #[case(ComplexColumnType::Char { length: 10 }, "Option<String>")]
294 #[case(ComplexColumnType::Char { length: 1 }, "Option<String>")]
295 #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "Option<String>")]
296 #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, "Option<String>")]
297 fn test_complex_column_type_to_rust_type_nullable(
298 #[case] column_type: ComplexColumnType,
299 #[case] expected: &str,
300 ) {
301 assert_eq!(
302 ColumnType::Complex(column_type).to_rust_type(true),
303 expected
304 );
305 }
306}