sqlx-firebirdsql 0.1.0

Firebird SQL driver for SQLx
use std::fmt::{self, Display, Formatter};

pub(crate) use sqlx_core::type_info::*;

/// Firebird SQL column types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub enum FirebirdSqlType {
    /// CHAR (fixed-length string)
    Text,
    /// VARCHAR (variable-length string)
    Varying,
    /// SMALLINT (16-bit signed integer)
    Short,
    /// INTEGER (32-bit signed integer)
    Long,
    /// FLOAT (32-bit IEEE 754)
    Float,
    /// DOUBLE PRECISION (64-bit IEEE 754)
    Double,
    /// TIMESTAMP (date and time, no timezone)
    Timestamp,
    /// BLOB (binary large object)
    Blob,
    /// DATE
    Date,
    /// TIME (no timezone)
    Time,
    /// BIGINT (64-bit signed integer)
    Int64,
    /// INT128 (128-bit signed integer)
    Int128,
    /// BOOLEAN
    Boolean,
    /// TIMESTAMP WITH TIME ZONE
    TimestampTz,
    /// TIME WITH TIME ZONE
    TimeTz,
    /// NUMERIC / DECIMAL (fixed-point)
    DecFixed,
    /// DECFLOAT(16)
    Dec64,
    /// DECFLOAT(34)
    Dec128,
    /// NULL
    Null,
}

impl FirebirdSqlType {
    pub fn name(&self) -> &'static str {
        match self {
            Self::Text => "CHAR",
            Self::Varying => "VARCHAR",
            Self::Short => "SMALLINT",
            Self::Long => "INTEGER",
            Self::Float => "FLOAT",
            Self::Double => "DOUBLE PRECISION",
            Self::Timestamp => "TIMESTAMP",
            Self::Blob => "BLOB",
            Self::Date => "DATE",
            Self::Time => "TIME",
            Self::Int64 => "BIGINT",
            Self::Int128 => "INT128",
            Self::Boolean => "BOOLEAN",
            Self::TimestampTz => "TIMESTAMP WITH TIME ZONE",
            Self::TimeTz => "TIME WITH TIME ZONE",
            Self::DecFixed => "NUMERIC",
            Self::Dec64 => "DECFLOAT(16)",
            Self::Dec128 => "DECFLOAT(34)",
            Self::Null => "NULL",
        }
    }

    /// Convert from a firebirust SQL type constant (`SQL_TYPE_*`).
    pub fn from_sqltype(sqltype: u32) -> Option<Self> {
        match sqltype {
            452 => Some(Self::Text),
            448 => Some(Self::Varying),
            500 => Some(Self::Short),
            496 => Some(Self::Long),
            482 => Some(Self::Float),
            480 => Some(Self::Double),
            510 => Some(Self::Timestamp),
            520 => Some(Self::Blob),
            570 => Some(Self::Date),
            560 => Some(Self::Time),
            580 => Some(Self::Int64),
            32752 => Some(Self::Int128),
            32764 => Some(Self::Boolean),
            32754 => Some(Self::TimestampTz),
            32756 => Some(Self::TimeTz),
            32758 => Some(Self::DecFixed),
            32760 => Some(Self::Dec64),
            32762 => Some(Self::Dec128),
            32766 => Some(Self::Null),
            _ => None,
        }
    }
}

/// Type information for a Firebird SQL type.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub struct FirebirdTypeInfo {
    pub(crate) r#type: FirebirdSqlType,
    pub(crate) sqlscale: i32,
    pub(crate) sqlsubtype: i32,
}

impl FirebirdTypeInfo {
    pub(crate) const fn new(r#type: FirebirdSqlType) -> Self {
        Self {
            r#type,
            sqlscale: 0,
            sqlsubtype: 0,
        }
    }

    pub(crate) const fn with_scale(r#type: FirebirdSqlType, sqlscale: i32, sqlsubtype: i32) -> Self {
        Self {
            r#type,
            sqlscale,
            sqlsubtype,
        }
    }

    #[doc(hidden)]
    pub fn __type_feature_gate(&self) -> Option<&'static str> {
        match self.r#type {
            FirebirdSqlType::Date
            | FirebirdSqlType::Time
            | FirebirdSqlType::Timestamp
            | FirebirdSqlType::TimestampTz
            | FirebirdSqlType::TimeTz => Some("chrono"),

            FirebirdSqlType::DecFixed
            | FirebirdSqlType::Dec64
            | FirebirdSqlType::Dec128 => Some("rust_decimal"),

            _ => None,
        }
    }
}

impl Display for FirebirdTypeInfo {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.pad(self.name())
    }
}

impl TypeInfo for FirebirdTypeInfo {
    fn is_null(&self) -> bool {
        matches!(self.r#type, FirebirdSqlType::Null)
    }

    fn name(&self) -> &str {
        self.r#type.name()
    }
}

impl PartialEq<FirebirdTypeInfo> for FirebirdTypeInfo {
    fn eq(&self, other: &FirebirdTypeInfo) -> bool {
        if self.r#type != other.r#type {
            return false;
        }

        match self.r#type {
            // For integer types, scale matters (non-zero scale means NUMERIC/DECIMAL)
            FirebirdSqlType::Short
            | FirebirdSqlType::Long
            | FirebirdSqlType::Int64
            | FirebirdSqlType::Int128 => {
                self.sqlscale == other.sqlscale
            }

            // For BLOB, subtype matters (0 = binary, 1 = text)
            FirebirdSqlType::Blob => {
                self.sqlsubtype == other.sqlsubtype
            }

            _ => true,
        }
    }
}

impl Eq for FirebirdTypeInfo {}