mod sealed {
pub trait Sealed {}
impl Sealed for super::Postgres {}
impl Sealed for super::Sqlite {}
impl Sealed for super::Mysql {}
impl Sealed for super::Mssql {}
impl Sealed for super::Cql {}
impl Sealed for super::NotSql {}
}
pub trait SqlDialect: Send + Sync + sealed::Sealed {
fn placeholder(&self, i: usize) -> String;
fn returning_clause(&self, cols: &str) -> String;
fn quote_ident(&self, ident: &str) -> String;
fn supports_distinct_on(&self) -> bool {
false
}
fn insert_has_returning(&self) -> bool {
true
}
fn upsert_clause(&self, conflict_cols: &[&str], update_set: &str) -> String;
fn begin_sql(&self) -> &'static str {
"BEGIN"
}
fn commit_sql(&self) -> &'static str {
"COMMIT"
}
fn rollback_sql(&self) -> &'static str {
"ROLLBACK"
}
}
pub struct Postgres;
pub struct Sqlite;
pub struct Mysql;
pub struct Mssql;
pub struct Cql;
pub struct NotSql;
impl SqlDialect for Postgres {
fn placeholder(&self, i: usize) -> String {
format!("${}", i)
}
fn returning_clause(&self, cols: &str) -> String {
format!(" RETURNING {}", cols)
}
fn quote_ident(&self, i: &str) -> String {
format!("\"{}\"", i.replace('"', "\"\""))
}
fn supports_distinct_on(&self) -> bool {
true
}
fn upsert_clause(&self, c: &[&str], s: &str) -> String {
format!(" ON CONFLICT ({}) DO UPDATE SET {}", c.join(", "), s)
}
}
impl SqlDialect for Sqlite {
fn placeholder(&self, i: usize) -> String {
format!("?{}", i)
}
fn returning_clause(&self, cols: &str) -> String {
format!(" RETURNING {}", cols)
}
fn quote_ident(&self, i: &str) -> String {
format!("\"{}\"", i.replace('"', "\"\""))
}
fn upsert_clause(&self, c: &[&str], s: &str) -> String {
format!(" ON CONFLICT ({}) DO UPDATE SET {}", c.join(", "), s)
}
}
impl SqlDialect for Mysql {
fn placeholder(&self, _i: usize) -> String {
"?".into()
}
fn returning_clause(&self, _cols: &str) -> String {
String::new()
}
fn insert_has_returning(&self) -> bool {
false
}
fn quote_ident(&self, i: &str) -> String {
format!("`{}`", i.replace('`', "``"))
}
fn upsert_clause(&self, _c: &[&str], s: &str) -> String {
format!(" ON DUPLICATE KEY UPDATE {}", s)
}
}
impl SqlDialect for Mssql {
fn placeholder(&self, i: usize) -> String {
format!("@P{}", i)
}
fn returning_clause(&self, cols: &str) -> String {
if cols == "*" {
return " OUTPUT INSERTED.*".into();
}
let prefixed: Vec<String> = cols
.split(',')
.map(|c| format!("INSERTED.{}", c.trim()))
.collect();
format!(" OUTPUT {}", prefixed.join(", "))
}
fn quote_ident(&self, i: &str) -> String {
format!("[{}]", i.replace(']', "]]"))
}
fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
String::new()
}
fn begin_sql(&self) -> &'static str {
"BEGIN TRANSACTION"
}
fn commit_sql(&self) -> &'static str {
"COMMIT TRANSACTION"
}
fn rollback_sql(&self) -> &'static str {
"ROLLBACK TRANSACTION"
}
}
impl SqlDialect for Cql {
fn placeholder(&self, _i: usize) -> String {
"?".into()
}
fn returning_clause(&self, _cols: &str) -> String {
String::new()
}
fn insert_has_returning(&self) -> bool {
false
}
fn quote_ident(&self, i: &str) -> String {
format!("\"{}\"", i.replace('"', "\"\""))
}
fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
String::new()
}
}
impl SqlDialect for NotSql {
fn placeholder(&self, _i: usize) -> String {
unimplemented!(
"NotSql dialect does not emit SQL; engines that return NotSql from \
QueryEngine::dialect() must not route requests through the SQL \
operation builders (FindManyOperation, CreateOperation, etc.). \
Use a SQL-capable dialect (Postgres/Mysql/Sqlite/Mssql) or build \
queries natively (e.g. BSON for MongoDB)."
)
}
fn returning_clause(&self, _cols: &str) -> String {
unimplemented!("NotSql::returning_clause — see NotSql::placeholder for details")
}
fn quote_ident(&self, _ident: &str) -> String {
unimplemented!("NotSql::quote_ident — see NotSql::placeholder for details")
}
fn upsert_clause(&self, _c: &[&str], _s: &str) -> String {
unimplemented!("NotSql::upsert_clause — see NotSql::placeholder for details")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn placeholders_per_dialect() {
assert_eq!(Postgres.placeholder(3), "$3");
assert_eq!(Sqlite.placeholder(3), "?3");
assert_eq!(Mysql.placeholder(3), "?");
assert_eq!(Mssql.placeholder(3), "@P3");
}
#[test]
fn returning_mssql_is_output_inserted() {
assert_eq!(Mssql.returning_clause("*"), " OUTPUT INSERTED.*");
assert_eq!(Mssql.returning_clause("id"), " OUTPUT INSERTED.id");
assert_eq!(
Mssql.returning_clause("id, email"),
" OUTPUT INSERTED.id, INSERTED.email"
);
assert_eq!(
Mssql.returning_clause("id,email,name"),
" OUTPUT INSERTED.id, INSERTED.email, INSERTED.name"
);
}
#[test]
fn upsert_mysql_is_on_duplicate_key() {
assert_eq!(
Mysql.upsert_clause(&[], "x = 1"),
" ON DUPLICATE KEY UPDATE x = 1"
);
}
#[test]
fn upsert_postgres_is_on_conflict() {
assert_eq!(
Postgres.upsert_clause(&["email"], "name = EXCLUDED.name"),
" ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name"
);
}
#[test]
fn quote_ident_backends_escape_the_embedded_quote() {
assert_eq!(
Postgres.quote_ident(r#"col"with"quote"#),
r#""col""with""quote""#
);
assert_eq!(
Sqlite.quote_ident(r#"col"with"quote"#),
r#""col""with""quote""#
);
assert_eq!(Mysql.quote_ident("co`l"), "`co``l`");
assert_eq!(Mssql.quote_ident("col]ident"), "[col]]ident]");
}
#[test]
#[should_panic(expected = "NotSql dialect does not emit SQL")]
fn not_sql_placeholder_panics() {
let _ = NotSql.placeholder(1);
}
#[test]
#[should_panic]
fn not_sql_quote_ident_panics() {
let _ = NotSql.quote_ident("col");
}
#[test]
#[should_panic]
fn not_sql_returning_clause_panics() {
let _ = NotSql.returning_clause("*");
}
#[test]
#[should_panic]
fn not_sql_upsert_clause_panics() {
let _ = NotSql.upsert_clause(&[], "x = 1");
}
#[test]
fn mssql_transaction_keywords_are_distinct() {
assert_eq!(Mssql.begin_sql(), "BEGIN TRANSACTION");
assert_eq!(Mssql.commit_sql(), "COMMIT TRANSACTION");
assert_eq!(Mssql.rollback_sql(), "ROLLBACK TRANSACTION");
}
#[test]
fn distinct_on_support() {
assert!(Postgres.supports_distinct_on());
assert!(!Sqlite.supports_distinct_on());
assert!(!Mysql.supports_distinct_on());
assert!(!Mssql.supports_distinct_on());
assert!(!NotSql.supports_distinct_on());
}
#[test]
fn sealed_pattern_prevents_external_impl() {
use crate::dialect::{Postgres, SqlDialect};
let _p: &dyn SqlDialect = &Postgres;
}
}