drizzle-types 0.3.1

A type-safe SQL query builder for Rust
Documentation
//! The [`Dialect`] enum, shared by every drizzle crate that needs to name a database.

/// A supported SQL database: `SQLite`, `PostgreSQL` or `MySQL`.
///
/// Parse one from a name with [`Dialect::parse`] or [`str::parse`]; print it
/// with `Display` (`"sqlite"`, `"postgresql"`, `"mysql"`).
///
/// # Examples
///
/// ```
/// use drizzle_types::Dialect;
///
/// let dialect = Dialect::PostgreSQL;
/// assert!(dialect.uses_numbered_placeholders());
///
/// let sqlite = Dialect::SQLite;
/// assert!(!sqlite.uses_numbered_placeholders());
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Dialect {
    /// `SQLite`, with `?` positional placeholders.
    ///
    /// Compatible with: rusqlite, libsql, turso
    #[default]
    SQLite,

    /// `PostgreSQL`, with `$1, $2, ...` numbered placeholders.
    ///
    /// Compatible with: tokio-postgres, postgres
    PostgreSQL,

    /// `MySQL`, with `?` positional placeholders.
    ///
    /// Compatible with: mysql
    MySQL,
}

impl Dialect {
    /// Returns `true` if the dialect uses numbered placeholders (`$1, $2, ...`).
    ///
    /// Only `PostgreSQL` does; `SQLite` and `MySQL` use `?`.
    #[inline]
    #[must_use]
    pub const fn uses_numbered_placeholders(&self) -> bool {
        matches!(self, Self::PostgreSQL)
    }

    /// Parses a dialect name, ignoring case. Returns `None` for an unknown name.
    ///
    /// Accepted names:
    /// - `SQLite`: `"sqlite"`, `"turso"`, `"libsql"`
    /// - `PostgreSQL`: `"postgresql"`, `"postgres"`, `"pg"`
    /// - `MySQL`: `"mysql"`
    ///
    /// # Examples
    ///
    /// ```
    /// use drizzle_types::Dialect;
    ///
    /// assert_eq!(Dialect::parse("sqlite"), Some(Dialect::SQLite));
    /// assert_eq!(Dialect::parse("postgres"), Some(Dialect::PostgreSQL));
    /// assert_eq!(Dialect::parse("pg"), Some(Dialect::PostgreSQL));
    /// assert_eq!(Dialect::parse("unknown"), None);
    /// ```
    #[must_use]
    pub const fn parse(s: &str) -> Option<Self> {
        // Use eq_ignore_ascii_case for no_std compatibility (no allocation)
        if s.eq_ignore_ascii_case("sqlite")
            || s.eq_ignore_ascii_case("turso")
            || s.eq_ignore_ascii_case("libsql")
        {
            Some(Self::SQLite)
        } else if s.eq_ignore_ascii_case("postgresql")
            || s.eq_ignore_ascii_case("postgres")
            || s.eq_ignore_ascii_case("pg")
        {
            Some(Self::PostgreSQL)
        } else if s.eq_ignore_ascii_case("mysql") {
            Some(Self::MySQL)
        } else {
            None
        }
    }

    /// Returns the start of the table attribute used in generated code, such
    /// as `"#[PostgresTable"`.
    #[must_use]
    pub const fn table_prefix(&self) -> &'static str {
        match self {
            Self::SQLite => "#[SQLiteTable",
            Self::PostgreSQL => "#[PostgresTable",
            Self::MySQL => "#[MySQLTable",
        }
    }

    /// Returns the start of the index attribute used in generated code, such
    /// as `"#[PostgresIndex"`.
    #[must_use]
    pub const fn index_prefix(&self) -> &'static str {
        match self {
            Self::SQLite => "#[SQLiteIndex",
            Self::PostgreSQL => "#[PostgresIndex",
            Self::MySQL => "#[MySQLIndex",
        }
    }

    /// Returns the schema derive attribute used in generated code, such as
    /// `"#[derive(PostgresSchema)]"`.
    #[must_use]
    pub const fn schema_derive(&self) -> &'static str {
        match self {
            Self::SQLite => "#[derive(SQLiteSchema)]",
            Self::PostgreSQL => "#[derive(PostgresSchema)]",
            Self::MySQL => "#[derive(MySQLSchema)]",
        }
    }

    /// Returns the lowercase name: `"sqlite"`, `"postgresql"` or `"mysql"`.
    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::SQLite => "sqlite",
            Self::PostgreSQL => "postgresql",
            Self::MySQL => "mysql",
        }
    }
}

impl core::fmt::Display for Dialect {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(self.as_str())
    }
}

impl core::str::FromStr for Dialect {
    type Err = DialectParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::parse(s).ok_or(DialectParseError)
    }
}

/// Error from parsing an unknown dialect name with [`str::parse`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DialectParseError;

impl core::fmt::Display for DialectParseError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("unknown dialect")
    }
}

#[cfg(feature = "std")]
impl std::error::Error for DialectParseError {}

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

    #[test]
    fn test_dialect_parse() {
        assert_eq!(Dialect::parse("sqlite"), Some(Dialect::SQLite));
        assert_eq!(Dialect::parse("SQLite"), Some(Dialect::SQLite));
        assert_eq!(Dialect::parse("turso"), Some(Dialect::SQLite));
        assert_eq!(Dialect::parse("libsql"), Some(Dialect::SQLite));

        assert_eq!(Dialect::parse("postgresql"), Some(Dialect::PostgreSQL));
        assert_eq!(Dialect::parse("postgres"), Some(Dialect::PostgreSQL));
        assert_eq!(Dialect::parse("pg"), Some(Dialect::PostgreSQL));
        assert_eq!(Dialect::parse("PG"), Some(Dialect::PostgreSQL));

        assert_eq!(Dialect::parse("mysql"), Some(Dialect::MySQL));
        assert_eq!(Dialect::parse("MySQL"), Some(Dialect::MySQL));

        assert_eq!(Dialect::parse("unknown"), None);
        assert_eq!(Dialect::parse(""), None);
    }

    #[test]
    fn test_dialect_placeholders() {
        assert!(!Dialect::SQLite.uses_numbered_placeholders());
        assert!(Dialect::PostgreSQL.uses_numbered_placeholders());
        assert!(!Dialect::MySQL.uses_numbered_placeholders());
    }

    #[test]
    fn test_dialect_display() {
        assert_eq!(format!("{}", Dialect::SQLite), "sqlite");
        assert_eq!(format!("{}", Dialect::PostgreSQL), "postgresql");
        assert_eq!(format!("{}", Dialect::MySQL), "mysql");
    }
}