1use chrono::{NaiveDate, NaiveDateTime};
2
3use super::Dialect;
4use crate::{Driver, Error, Value};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub(crate) enum ColumnType {
8 Id,
9 String(u32),
10 Text,
11 Integer,
12 BigInteger,
13 Boolean,
14 DateTime(u32),
16 Timestamp(u32),
18 Binary,
19}
20
21#[derive(Debug, Clone)]
24pub struct Column {
25 pub(crate) name: String,
26 pub(crate) ty: ColumnType,
27 nullable: bool,
28 default: Option<Value>,
29 use_current: bool,
30 pub(crate) primary: bool,
31 pub(crate) change: bool,
32}
33
34impl Column {
35 pub(crate) fn new(name: &str, ty: ColumnType) -> Self {
36 Self {
37 name: name.to_owned(),
38 ty,
39 nullable: false,
40 default: None,
41 use_current: false,
42 primary: false,
43 change: false,
44 }
45 }
46
47 pub fn nullable(&mut self) -> &mut Self {
49 self.nullable = true;
50 self
51 }
52
53 pub fn default(&mut self, value: impl Into<Value>) -> &mut Self {
55 self.default = Some(value.into());
56 self
57 }
58
59 pub fn length(&mut self, length: u32) -> &mut Self {
61 if let ColumnType::String(n) = &mut self.ty {
62 *n = length;
63 }
64 self
65 }
66
67 pub fn precision(&mut self, precision: u32) -> &mut Self {
75 if let ColumnType::DateTime(p) | ColumnType::Timestamp(p) = &mut self.ty {
76 *p = precision;
77 }
78 self
79 }
80
81 pub fn use_current(&mut self) -> &mut Self {
86 self.use_current = true;
87 self
88 }
89
90 pub fn primary(&mut self) -> &mut Self {
97 self.primary = true;
98 self
99 }
100
101 pub fn change(&mut self) -> &mut Self {
107 self.change = true;
108 self
109 }
110
111 pub(crate) fn render(&self, driver: Driver, dialect: Dialect) -> Result<String, Error> {
113 crate::query::validate(&self.name)?;
114 let name = driver.quote(&self.name);
115 if self.ty == ColumnType::Id {
116 let id = match dialect {
117 Dialect::MySql => "BIGINT AUTO_INCREMENT PRIMARY KEY",
118 Dialect::Postgres => "BIGSERIAL PRIMARY KEY",
120 Dialect::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT",
121 };
122 return Ok(format!("{name} {id}"));
123 }
124 let mut sql = format!("{name} {}", self.sql_type(dialect));
125 sql.push_str(if self.nullable { " NULL" } else { " NOT NULL" });
126 if let Some(default) = self.default_sql(driver, dialect)? {
127 sql.push_str(" DEFAULT ");
128 sql.push_str(&default);
129 }
130 if self.primary {
131 sql.push_str(" PRIMARY KEY");
132 }
133 Ok(sql)
134 }
135
136 pub(crate) fn render_change_postgres(&self, driver: Driver) -> Result<String, Error> {
139 crate::query::validate(&self.name)?;
140 let name = driver.quote(&self.name);
141 let ty = self.sql_type(Dialect::Postgres);
142 let null = if self.nullable {
143 "DROP NOT NULL"
144 } else {
145 "SET NOT NULL"
146 };
147 let default = match self.default_sql(driver, Dialect::Postgres)? {
148 Some(default) => format!("SET DEFAULT {default}"),
149 None => "DROP DEFAULT".to_owned(),
150 };
151 Ok(format!(
152 "ALTER COLUMN {name} TYPE {ty}, ALTER COLUMN {name} {null}, \
153 ALTER COLUMN {name} {default}"
154 ))
155 }
156
157 fn default_sql(&self, driver: Driver, dialect: Dialect) -> Result<Option<String>, Error> {
160 if self.use_current {
161 match (self.ty, dialect) {
162 (ColumnType::DateTime(p) | ColumnType::Timestamp(p), Dialect::MySql) if p > 0 => {
163 return Ok(Some(format!("CURRENT_TIMESTAMP({p})")));
164 }
165 (ColumnType::DateTime(_) | ColumnType::Timestamp(_), _) => {
166 return Ok(Some("CURRENT_TIMESTAMP".to_owned()));
167 }
168 _ => {}
169 }
170 }
171 self.default
172 .as_ref()
173 .map(|value| literal(value, driver, dialect))
174 .transpose()
175 }
176
177 fn sql_type(&self, dialect: Dialect) -> String {
178 match (self.ty, dialect) {
179 (ColumnType::Id, _) => unreachable!("an id column is rendered whole"),
180 (ColumnType::String(_), Dialect::Sqlite) => "TEXT".to_owned(),
181 (ColumnType::String(n), _) => format!("VARCHAR({n})"),
182 (ColumnType::Text, _) => "TEXT".to_owned(),
183 (ColumnType::Integer, _) => "INTEGER".to_owned(),
184 (ColumnType::BigInteger, Dialect::Sqlite) => "INTEGER".to_owned(),
185 (ColumnType::BigInteger, _) => "BIGINT".to_owned(),
186 (ColumnType::Boolean, Dialect::Postgres) => "BOOLEAN".to_owned(),
187 (ColumnType::Boolean, _) => "TINYINT(1)".to_owned(),
188 (ColumnType::DateTime(p) | ColumnType::Timestamp(p), Dialect::Postgres) => {
189 format!("TIMESTAMP({p}) WITHOUT TIME ZONE")
190 }
191 (ColumnType::DateTime(_) | ColumnType::Timestamp(_), Dialect::Sqlite) => {
192 "DATETIME".to_owned()
193 }
194 (ColumnType::DateTime(0), _) => "DATETIME".to_owned(),
195 (ColumnType::DateTime(p), _) => format!("DATETIME({p})"),
196 (ColumnType::Timestamp(0), _) => "TIMESTAMP".to_owned(),
197 (ColumnType::Timestamp(p), _) => format!("TIMESTAMP({p})"),
198 (ColumnType::Binary, Dialect::Postgres) => "BYTEA".to_owned(),
199 (ColumnType::Binary, _) => "BLOB".to_owned(),
200 }
201 }
202}
203
204fn literal(value: &Value, driver: Driver, dialect: Dialect) -> Result<String, Error> {
206 let unsupported = || Error::Unsupported {
207 operation: "a column default of this type",
208 driver: driver.name(),
209 };
210 Ok(match value {
211 Value::Null => "NULL".to_owned(),
212 Value::Bool(b) => match (dialect, b) {
213 (Dialect::Postgres, true) => "true".to_owned(),
214 (Dialect::Postgres, false) => "false".to_owned(),
215 (_, true) => "1".to_owned(),
216 (_, false) => "0".to_owned(),
217 },
218 Value::Int(n) => n.to_string(),
219 Value::UInt(n) => n.to_string(),
220 Value::Float(f) if f.is_finite() => format!("{f:?}"),
221 Value::Float(_) => return Err(unsupported()),
222 Value::Text(s) => quote_text(s, dialect),
223 Value::DateTime(d) => quote_text(&format_datetime(d), dialect),
224 Value::Date(d) => quote_text(&format_date(d), dialect),
225 Value::Bytes(_) => return Err(unsupported()),
226 })
227}
228
229fn format_datetime(d: &NaiveDateTime) -> String {
230 d.format("%Y-%m-%d %H:%M:%S").to_string()
231}
232
233fn format_date(d: &NaiveDate) -> String {
234 d.format("%Y-%m-%d").to_string()
235}
236
237fn quote_text(s: &str, dialect: Dialect) -> String {
240 let mut out = s.replace('\'', "''");
241 if dialect == Dialect::MySql {
242 out = out.replace('\\', "\\\\");
243 }
244 format!("'{out}'")
245}