fletch-orm 1.0.0

Lightweight database toolkit built on SQLx
Documentation
//! SQL dialect abstraction for multi-database support.
//!
//! Each database has different syntax for bind parameters, identifier quoting,
//! and RETURNING clauses. The [`Dialect`] trait abstracts these differences so
//! the query builder can generate correct SQL for any supported backend.

/// Describes how a specific database handles SQL syntax differences.
pub trait Dialect: Send + Sync {
    /// Returns the dialect name (e.g. "sqlite", "postgres", "mysql").
    fn name(&self) -> &'static str;

    /// Quote an identifier (table name, column name).
    ///
    /// - Postgres/SQLite: `"identifier"`
    /// - MySQL: `` `identifier` ``
    fn quote_identifier(&self, ident: &str) -> String;

    /// Return the bind parameter placeholder for the given 1-based index.
    ///
    /// - Postgres: `$1`, `$2`, ...
    /// - SQLite/MySQL: `?`
    fn bind_parameter(&self, index: usize) -> String;

    /// Whether this dialect supports the `RETURNING` clause on INSERT/UPDATE/DELETE.
    fn supports_returning(&self) -> bool;
}

/// SQLite dialect.
#[derive(Debug, Clone, Copy, Default)]
pub struct SqliteDialect;

impl Dialect for SqliteDialect {
    fn name(&self) -> &'static str {
        "sqlite"
    }

    fn quote_identifier(&self, ident: &str) -> String {
        format!("\"{}\"", ident.replace('"', "\"\""))
    }

    fn bind_parameter(&self, _index: usize) -> String {
        "?".to_owned()
    }

    fn supports_returning(&self) -> bool {
        true
    }
}

/// PostgreSQL dialect.
#[derive(Debug, Clone, Copy, Default)]
pub struct PostgresDialect;

impl Dialect for PostgresDialect {
    fn name(&self) -> &'static str {
        "postgres"
    }

    fn quote_identifier(&self, ident: &str) -> String {
        format!("\"{}\"", ident.replace('"', "\"\""))
    }

    fn bind_parameter(&self, index: usize) -> String {
        format!("${index}")
    }

    fn supports_returning(&self) -> bool {
        true
    }
}

/// MySQL dialect.
#[derive(Debug, Clone, Copy, Default)]
pub struct MySqlDialect;

impl Dialect for MySqlDialect {
    fn name(&self) -> &'static str {
        "mysql"
    }

    fn quote_identifier(&self, ident: &str) -> String {
        format!("`{}`", ident.replace('`', "``"))
    }

    fn bind_parameter(&self, _index: usize) -> String {
        "?".to_owned()
    }

    fn supports_returning(&self) -> bool {
        false
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sqlite_quote_identifier() {
        let d = SqliteDialect;
        assert_eq!(d.quote_identifier("users"), "\"users\"");
        assert_eq!(d.quote_identifier("user\"name"), "\"user\"\"name\"");
    }

    #[test]
    fn sqlite_bind_parameter() {
        let d = SqliteDialect;
        assert_eq!(d.bind_parameter(1), "?");
        assert_eq!(d.bind_parameter(5), "?");
    }

    #[test]
    fn sqlite_supports_returning() {
        assert!(SqliteDialect.supports_returning());
    }

    #[test]
    fn postgres_quote_identifier() {
        let d = PostgresDialect;
        assert_eq!(d.quote_identifier("users"), "\"users\"");
    }

    #[test]
    fn postgres_bind_parameter() {
        let d = PostgresDialect;
        assert_eq!(d.bind_parameter(1), "$1");
        assert_eq!(d.bind_parameter(3), "$3");
    }

    #[test]
    fn postgres_supports_returning() {
        assert!(PostgresDialect.supports_returning());
    }

    #[test]
    fn mysql_quote_identifier() {
        let d = MySqlDialect;
        assert_eq!(d.quote_identifier("users"), "`users`");
        assert_eq!(d.quote_identifier("user`name"), "`user``name`");
    }

    #[test]
    fn mysql_bind_parameter() {
        let d = MySqlDialect;
        assert_eq!(d.bind_parameter(1), "?");
    }

    #[test]
    fn mysql_does_not_support_returning() {
        assert!(!MySqlDialect.supports_returning());
    }
}