use crate::FieldType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dialect {
Postgres,
Sqlite,
}
impl Dialect {
pub fn from_database(database: &str) -> Self {
if database.eq_ignore_ascii_case("sqlite") {
Dialect::Sqlite
} else {
Dialect::Postgres
}
}
pub fn id_pk_ddl(self) -> &'static str {
match self {
Dialect::Postgres => "id UUID PRIMARY KEY DEFAULT gen_random_uuid()",
Dialect::Sqlite => "id TEXT PRIMARY KEY",
}
}
pub fn app_generates_id(self) -> bool {
matches!(self, Dialect::Sqlite)
}
pub fn column_type(self, ty: FieldType) -> &'static str {
match self {
Dialect::Postgres => ty.sql_type(),
Dialect::Sqlite => match ty {
FieldType::String | FieldType::Uuid | FieldType::DateTime => "TEXT",
FieldType::Bool | FieldType::I32 | FieldType::I64 => "INTEGER",
FieldType::F64 => "REAL",
},
}
}
pub fn timestamp_type_default(self) -> &'static str {
match self {
Dialect::Postgres => "TIMESTAMPTZ NOT NULL DEFAULT now()",
Dialect::Sqlite => "TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))",
}
}
pub fn now_expr(self) -> &'static str {
match self {
Dialect::Postgres => "now()",
Dialect::Sqlite => "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
}
}
pub fn placeholder(self, n: usize) -> String {
match self {
Dialect::Postgres => format!("${n}"),
Dialect::Sqlite => format!("?{n}"),
}
}
pub fn pool_type(self) -> &'static str {
match self {
Dialect::Postgres => "PgPool",
Dialect::Sqlite => "SqlitePool",
}
}
pub fn pool_options(self) -> &'static str {
match self {
Dialect::Postgres => "PgPoolOptions",
Dialect::Sqlite => "SqlitePoolOptions",
}
}
pub fn sqlx_module(self) -> &'static str {
match self {
Dialect::Postgres => "postgres",
Dialect::Sqlite => "sqlite",
}
}
pub fn sqlx_feature(self) -> &'static str {
match self {
Dialect::Postgres => "postgres",
Dialect::Sqlite => "sqlite",
}
}
pub fn unique_violation_code(self) -> &'static str {
match self {
Dialect::Postgres => "23505",
Dialect::Sqlite => "2067",
}
}
pub fn foreign_key_violation_code(self) -> &'static str {
match self {
Dialect::Postgres => "23503",
Dialect::Sqlite => "787",
}
}
pub fn example_url(self, project: &str) -> String {
match self {
Dialect::Postgres => {
format!("postgres://postgres:postgres@localhost:5432/{project}")
}
Dialect::Sqlite => format!("sqlite://{project}.db?mode=rwc"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn postgres_output_is_the_canonical_mapping() {
let d = Dialect::from_database("postgres");
assert_eq!(
d.id_pk_ddl(),
"id UUID PRIMARY KEY DEFAULT gen_random_uuid()"
);
assert_eq!(d.column_type(FieldType::String), "TEXT");
assert_eq!(d.column_type(FieldType::DateTime), "TIMESTAMPTZ");
assert_eq!(d.placeholder(3), "$3");
assert!(!d.app_generates_id());
assert_eq!(d.pool_type(), "PgPool");
}
#[test]
fn sqlite_maps_types_and_placeholders() {
let d = Dialect::from_database("sqlite");
assert_eq!(d.id_pk_ddl(), "id TEXT PRIMARY KEY");
assert_eq!(d.column_type(FieldType::Uuid), "TEXT");
assert_eq!(d.column_type(FieldType::Bool), "INTEGER");
assert_eq!(d.column_type(FieldType::F64), "REAL");
assert_eq!(d.placeholder(2), "?2");
assert!(d.app_generates_id());
assert_eq!(d.pool_type(), "SqlitePool");
assert_eq!(d.sqlx_feature(), "sqlite");
}
#[test]
fn defaults_to_postgres() {
assert_eq!(Dialect::from_database("mysql"), Dialect::Postgres);
assert_eq!(Dialect::from_database(""), Dialect::Postgres);
}
}