use sqlx::FromRow;
use crate::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Driver {
#[cfg(feature = "mysql")]
MySql,
#[cfg(feature = "postgres")]
Postgres,
#[cfg(feature = "sqlite")]
Sqlite,
}
impl Driver {
pub fn name(self) -> &'static str {
match self {
#[cfg(feature = "mysql")]
Self::MySql => "MySQL",
#[cfg(feature = "postgres")]
Self::Postgres => "PostgreSQL",
#[cfg(feature = "sqlite")]
Self::Sqlite => "SQLite",
}
}
pub(crate) fn quote(self, name: &str) -> String {
match self {
#[cfg(feature = "mysql")]
Self::MySql => format!("`{name}`"),
#[cfg(any(feature = "postgres", feature = "sqlite"))]
_ => format!("\"{name}\""),
}
}
#[cfg_attr(not(feature = "postgres"), allow(unused_variables))]
pub(crate) fn placeholder(self, n: usize) -> String {
match self {
#[cfg(feature = "mysql")]
Self::MySql => "?".to_owned(),
#[cfg(feature = "postgres")]
Self::Postgres => format!("${n}"),
#[cfg(feature = "sqlite")]
Self::Sqlite => "?".to_owned(),
}
}
pub(crate) fn lock_clause(self) -> &'static str {
match self {
#[cfg(feature = "mysql")]
Self::MySql => " FOR UPDATE",
#[cfg(feature = "postgres")]
Self::Postgres => " FOR UPDATE",
#[cfg(feature = "sqlite")]
Self::Sqlite => "",
}
}
pub(crate) fn offset_only_limit(self) -> Option<&'static str> {
match self {
#[cfg(feature = "mysql")]
Self::MySql => Some("18446744073709551615"),
#[cfg(feature = "postgres")]
Self::Postgres => None,
#[cfg(feature = "sqlite")]
Self::Sqlite => Some("-1"),
}
}
pub(crate) fn empty_insert(self) -> &'static str {
match self {
#[cfg(feature = "mysql")]
Self::MySql => "() VALUES ()",
#[cfg(any(feature = "postgres", feature = "sqlite"))]
_ => "DEFAULT VALUES",
}
}
pub(crate) fn bigint(self) -> &'static str {
match self {
#[cfg(feature = "mysql")]
Self::MySql => "SIGNED",
#[cfg(feature = "postgres")]
Self::Postgres => "BIGINT",
#[cfg(feature = "sqlite")]
Self::Sqlite => "INTEGER",
}
}
pub(crate) fn supports_replace(self) -> bool {
match self {
#[cfg(feature = "mysql")]
Self::MySql => true,
#[cfg(feature = "postgres")]
Self::Postgres => false,
#[cfg(feature = "sqlite")]
Self::Sqlite => true,
}
}
pub(crate) fn inlines_null(self) -> bool {
match self {
#[cfg(feature = "mysql")]
Self::MySql => false,
#[cfg(feature = "postgres")]
Self::Postgres => true,
#[cfg(feature = "sqlite")]
Self::Sqlite => false,
}
}
pub(crate) fn insert_returns_id(self) -> bool {
match self {
#[cfg(feature = "mysql")]
Self::MySql => false,
#[cfg(feature = "postgres")]
Self::Postgres => true,
#[cfg(feature = "sqlite")]
Self::Sqlite => false,
}
}
pub(crate) fn prepare(self, sql: &str, binds: &[Value]) -> (String, Vec<Value>) {
match self {
#[cfg(feature = "mysql")]
Self::MySql => (sql.to_owned(), binds.to_vec()),
#[cfg(feature = "postgres")]
Self::Postgres => prepare_postgres(sql, binds),
#[cfg(feature = "sqlite")]
Self::Sqlite => (sql.to_owned(), binds.to_vec()),
}
}
}
#[cfg(feature = "postgres")]
fn prepare_postgres(sql: &str, binds: &[Value]) -> (String, Vec<Value>) {
let mut out = String::with_capacity(sql.len() + 8);
let mut kept = Vec::new();
let mut seen = 0usize;
let mut chars = sql.chars().peekable();
let mut quote: Option<char> = None;
while let Some(c) = chars.next() {
match quote {
Some(q) => {
out.push(c);
if c == q {
quote = None;
}
}
None => match c {
'\'' | '"' => {
quote = Some(c);
out.push(c);
}
'-' if chars.peek() == Some(&'-') => {
out.push(c);
for next in chars.by_ref() {
out.push(next);
if next == '\n' {
break;
}
}
}
'?' => {
let value = binds.get(seen).cloned().unwrap_or(Value::Null);
seen += 1;
if matches!(value, Value::Null) {
out.push_str("NULL");
} else {
kept.push(value);
out.push_str(&format!("${}", kept.len()));
}
}
_ => out.push(c),
},
}
}
(out, kept)
}
#[doc(hidden)]
pub mod rows {
#[cfg(feature = "mysql")]
pub trait RowMySql: for<'r> super::FromRow<'r, sqlx::mysql::MySqlRow> {}
#[cfg(feature = "mysql")]
impl<T: for<'r> super::FromRow<'r, sqlx::mysql::MySqlRow>> RowMySql for T {}
#[cfg(not(feature = "mysql"))]
pub trait RowMySql {}
#[cfg(not(feature = "mysql"))]
impl<T> RowMySql for T {}
#[cfg(feature = "postgres")]
pub trait RowPostgres: for<'r> super::FromRow<'r, sqlx::postgres::PgRow> {}
#[cfg(feature = "postgres")]
impl<T: for<'r> super::FromRow<'r, sqlx::postgres::PgRow>> RowPostgres for T {}
#[cfg(not(feature = "postgres"))]
pub trait RowPostgres {}
#[cfg(not(feature = "postgres"))]
impl<T> RowPostgres for T {}
#[cfg(feature = "sqlite")]
pub trait RowSqlite: for<'r> super::FromRow<'r, sqlx::sqlite::SqliteRow> {}
#[cfg(feature = "sqlite")]
impl<T: for<'r> super::FromRow<'r, sqlx::sqlite::SqliteRow>> RowSqlite for T {}
#[cfg(not(feature = "sqlite"))]
pub trait RowSqlite {}
#[cfg(not(feature = "sqlite"))]
impl<T> RowSqlite for T {}
pub trait DriverRows: RowMySql + RowPostgres + RowSqlite {}
impl<T: RowMySql + RowPostgres + RowSqlite> DriverRows for T {}
}
#[cfg(not(any(feature = "mysql", feature = "postgres", feature = "sqlite")))]
compile_error!("orbit-orm needs at least one database feature: mysql, postgres or sqlite");