1use crate::FieldType;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Dialect {
13 Postgres,
14 Sqlite,
15}
16
17impl Dialect {
18 pub fn from_database(database: &str) -> Self {
21 if database.eq_ignore_ascii_case("sqlite") {
22 Dialect::Sqlite
23 } else {
24 Dialect::Postgres
25 }
26 }
27
28 pub fn id_pk_ddl(self) -> &'static str {
31 match self {
32 Dialect::Postgres => "id UUID PRIMARY KEY DEFAULT gen_random_uuid()",
33 Dialect::Sqlite => "id TEXT PRIMARY KEY",
34 }
35 }
36
37 pub fn app_generates_id(self) -> bool {
39 matches!(self, Dialect::Sqlite)
40 }
41
42 pub fn column_type(self, ty: FieldType) -> &'static str {
44 match self {
45 Dialect::Postgres => ty.sql_type(),
47 Dialect::Sqlite => match ty {
50 FieldType::String | FieldType::Uuid | FieldType::DateTime => "TEXT",
51 FieldType::Bool | FieldType::I32 | FieldType::I64 => "INTEGER",
52 FieldType::F64 => "REAL",
53 },
54 }
55 }
56
57 pub fn timestamp_type_default(self) -> &'static str {
59 match self {
60 Dialect::Postgres => "TIMESTAMPTZ NOT NULL DEFAULT now()",
61 Dialect::Sqlite => "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))",
63 }
64 }
65
66 pub fn now_expr(self) -> &'static str {
68 match self {
69 Dialect::Postgres => "now()",
70 Dialect::Sqlite => "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
71 }
72 }
73
74 pub fn placeholder(self, n: usize) -> String {
76 match self {
77 Dialect::Postgres => format!("${n}"),
78 Dialect::Sqlite => format!("?{n}"),
79 }
80 }
81
82 pub fn pool_type(self) -> &'static str {
84 match self {
85 Dialect::Postgres => "PgPool",
86 Dialect::Sqlite => "SqlitePool",
87 }
88 }
89
90 pub fn pool_options(self) -> &'static str {
92 match self {
93 Dialect::Postgres => "PgPoolOptions",
94 Dialect::Sqlite => "SqlitePoolOptions",
95 }
96 }
97
98 pub fn sqlx_module(self) -> &'static str {
100 match self {
101 Dialect::Postgres => "postgres",
102 Dialect::Sqlite => "sqlite",
103 }
104 }
105
106 pub fn sqlx_feature(self) -> &'static str {
108 match self {
109 Dialect::Postgres => "postgres",
110 Dialect::Sqlite => "sqlite",
111 }
112 }
113
114 pub fn unique_violation_code(self) -> &'static str {
117 match self {
118 Dialect::Postgres => "23505",
119 Dialect::Sqlite => "2067",
120 }
121 }
122
123 pub fn foreign_key_violation_code(self) -> &'static str {
126 match self {
127 Dialect::Postgres => "23503",
128 Dialect::Sqlite => "787",
129 }
130 }
131
132 pub fn example_url(self, project: &str) -> String {
134 match self {
135 Dialect::Postgres => {
136 format!("postgres://postgres:postgres@localhost:5432/{project}")
137 }
138 Dialect::Sqlite => format!("sqlite://{project}.db?mode=rwc"),
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn postgres_output_is_the_canonical_mapping() {
149 let d = Dialect::from_database("postgres");
150 assert_eq!(
151 d.id_pk_ddl(),
152 "id UUID PRIMARY KEY DEFAULT gen_random_uuid()"
153 );
154 assert_eq!(d.column_type(FieldType::String), "TEXT");
155 assert_eq!(d.column_type(FieldType::DateTime), "TIMESTAMPTZ");
156 assert_eq!(d.placeholder(3), "$3");
157 assert!(!d.app_generates_id());
158 assert_eq!(d.pool_type(), "PgPool");
159 }
160
161 #[test]
162 fn sqlite_maps_types_and_placeholders() {
163 let d = Dialect::from_database("sqlite");
164 assert_eq!(d.id_pk_ddl(), "id TEXT PRIMARY KEY");
165 assert_eq!(d.column_type(FieldType::Uuid), "TEXT");
166 assert_eq!(d.column_type(FieldType::Bool), "INTEGER");
167 assert_eq!(d.column_type(FieldType::F64), "REAL");
168 assert_eq!(d.placeholder(2), "?2");
169 assert!(d.app_generates_id());
170 assert_eq!(d.pool_type(), "SqlitePool");
171 assert_eq!(d.sqlx_feature(), "sqlite");
172 }
173
174 #[test]
175 fn defaults_to_postgres() {
176 assert_eq!(Dialect::from_database("mysql"), Dialect::Postgres);
177 assert_eq!(Dialect::from_database(""), Dialect::Postgres);
178 }
179}