Skip to main content

drizzle_core/
dialect.rs

1//! Dialect markers, per-dialect type mappings, and dialect-only features.
2//!
3//! - [`Dialect`]: the runtime dialect enum.
4//! - [`SQLiteDialect`], [`PostgresDialect`], [`MySQLDialect`]: type-level
5//!   markers, chosen by a value type's [`SQLParam::DialectMarker`](crate::SQLParam::DialectMarker).
6//! - [`DialectTypes`]: maps generic SQL types (`Int`, `Text`, ...) to each
7//!   dialect's native types.
8//! - [`DialectSupports`] and [`feature`]: gate functions that only some
9//!   dialects have.
10//! - [`ParamStyle`]: how placeholders are written.
11
12/// The SQL dialect a value type or schema targets, known at runtime.
13pub use drizzle_types::Dialect;
14
15// =============================================================================
16// Type-level dialect markers
17// =============================================================================
18
19/// Type-level marker for SQLite.
20///
21/// Selects SQLite type mappings ([`DialectTypes`],
22/// [`SQLTypeToRust`](crate::row::SQLTypeToRust)). SQLite stores UUIDs as
23/// BLOB and date/time and JSON values as TEXT.
24#[derive(Debug, Clone, Copy)]
25pub struct SQLiteDialect;
26
27/// Type-level marker for PostgreSQL.
28///
29/// Selects PostgreSQL type mappings ([`DialectTypes`],
30/// [`SQLTypeToRust`](crate::row::SQLTypeToRust)). PostgreSQL has native
31/// date/time, UUID and JSON types, so selecting them needs a matching
32/// feature (`chrono`, `time` or `jiff`; `uuid`; `serde`).
33#[derive(Debug, Clone, Copy)]
34pub struct PostgresDialect;
35
36/// Type-level marker for MySQL.
37///
38/// Selects MySQL type mappings ([`DialectTypes`],
39/// [`SQLTypeToRust`](crate::row::SQLTypeToRust)). MySQL keeps signed and
40/// unsigned integer types apart, quotes identifiers with backticks, and has
41/// one native JSON type. It has no native UUID or time-zone-aware datetime:
42/// UUIDs use `BINARY(16)`, and `TimestampTz` maps to `TIMESTAMP`, which
43/// follows the session time zone.
44#[derive(Debug, Clone, Copy)]
45pub struct MySQLDialect;
46
47// =============================================================================
48// DialectTypes — maps conceptual SQL types to dialect-native markers
49// =============================================================================
50
51use crate::types::{Binary, BooleanLike, DataType, Floating, Integral, Temporal, Textual};
52
53/// Maps generic SQL types (`Int`, `Text`, `Bool`, ...) to each dialect's
54/// native type markers.
55///
56/// Implemented for [`SQLiteDialect`], [`PostgresDialect`] and
57/// [`MySQLDialect`]. Dialect-neutral expressions use it to pick a result
58/// type: `Int` is `sqlite::types::Integer` for SQLite and
59/// `postgres::types::Int4` for PostgreSQL.
60pub trait DialectTypes {
61    /// 16-bit integer.
62    type SmallInt: DataType + Integral;
63    /// 32-bit integer.
64    type Int: DataType + Integral;
65    /// 64-bit integer.
66    type BigInt: DataType + Integral;
67    /// Single-precision float.
68    type Float: DataType + Floating;
69    /// Double-precision float.
70    type Double: DataType + Floating;
71    /// Text.
72    type Text: DataType + Textual;
73    /// Boolean (an integer on SQLite).
74    type Bool: DataType + BooleanLike;
75    /// Binary data.
76    type Bytes: DataType + Binary;
77    /// Calendar date.
78    type Date: DataType + Temporal;
79    /// Time of day.
80    type Time: DataType + Temporal;
81    /// Date and time without a time zone.
82    type Timestamp: DataType + Temporal;
83    /// Date and time with a time zone.
84    type TimestampTz: DataType + Temporal;
85    /// UUID.
86    type Uuid: DataType;
87    /// JSON.
88    type Json: DataType;
89    /// Binary JSON (`jsonb`; plain JSON where the dialect has no `jsonb`).
90    type Jsonb: DataType;
91    /// A value of unknown type.
92    type Any: DataType;
93
94    /// Result type of `RANDOM()`.
95    type Random: DataType;
96    /// Result type of `SIGN(x)`.
97    type Sign: DataType;
98    /// Nullability of a math function whose domain is narrower than its
99    /// input type (`SQRT`, `LN`, `LOG`, ...) given input nullability `Input`:
100    /// SQLite and MySQL answer NULL outside the domain, PostgreSQL raises.
101    type DomainNullable<Input: crate::expr::Nullability>: crate::expr::Nullability;
102    /// Name of the character-count function (`LENGTH` on SQLite).
103    const CHAR_LENGTH_FN: &'static str;
104}
105
106impl DialectTypes for SQLiteDialect {
107    type SmallInt = drizzle_types::sqlite::types::Integer;
108    type Int = drizzle_types::sqlite::types::Integer;
109    type BigInt = drizzle_types::sqlite::types::Integer;
110    type Float = drizzle_types::sqlite::types::Real;
111    type Double = drizzle_types::sqlite::types::Real;
112    type Text = drizzle_types::sqlite::types::Text;
113    type Bool = drizzle_types::sqlite::types::Integer;
114    type Bytes = drizzle_types::sqlite::types::Blob;
115    type Date = drizzle_types::sqlite::types::Text;
116    type Time = drizzle_types::sqlite::types::Text;
117    type Timestamp = drizzle_types::sqlite::types::Text;
118    type TimestampTz = drizzle_types::sqlite::types::Text;
119    type Uuid = drizzle_types::sqlite::types::Blob;
120    type Json = drizzle_types::sqlite::types::Text;
121    type Jsonb = drizzle_types::sqlite::types::Text;
122    type Any = drizzle_types::sqlite::types::Any;
123
124    type Random = drizzle_types::sqlite::types::Integer;
125    type Sign = drizzle_types::sqlite::types::Integer;
126    type DomainNullable<Input: crate::expr::Nullability> = crate::expr::Null;
127    const CHAR_LENGTH_FN: &'static str = "LENGTH";
128}
129
130impl DialectTypes for PostgresDialect {
131    type SmallInt = drizzle_types::postgres::types::Int2;
132    type Int = drizzle_types::postgres::types::Int4;
133    type BigInt = drizzle_types::postgres::types::Int8;
134    type Float = drizzle_types::postgres::types::Float4;
135    type Double = drizzle_types::postgres::types::Float8;
136    type Text = drizzle_types::postgres::types::Text;
137    type Bool = drizzle_types::postgres::types::Boolean;
138    type Bytes = drizzle_types::postgres::types::Bytea;
139    type Date = drizzle_types::postgres::types::Date;
140    type Time = drizzle_types::postgres::types::Time;
141    type Timestamp = drizzle_types::postgres::types::Timestamp;
142    type TimestampTz = drizzle_types::postgres::types::Timestamptz;
143    type Uuid = drizzle_types::postgres::types::Uuid;
144    type Json = drizzle_types::postgres::types::Json;
145    type Jsonb = drizzle_types::postgres::types::Jsonb;
146    type Any = drizzle_types::postgres::types::Any;
147
148    type Random = drizzle_types::postgres::types::Float8;
149    type Sign = drizzle_types::postgres::types::Float8;
150    type DomainNullable<Input: crate::expr::Nullability> = Input;
151    const CHAR_LENGTH_FN: &'static str = "CHAR_LENGTH";
152}
153
154impl DialectTypes for MySQLDialect {
155    type SmallInt = drizzle_types::mysql::types::SmallInt;
156    type Int = drizzle_types::mysql::types::Int;
157    type BigInt = drizzle_types::mysql::types::BigInt;
158    type Float = drizzle_types::mysql::types::Float;
159    type Double = drizzle_types::mysql::types::Double;
160    type Text = drizzle_types::mysql::types::Text;
161    type Bool = drizzle_types::mysql::types::Boolean;
162    type Bytes = drizzle_types::mysql::types::Blob;
163    type Date = drizzle_types::mysql::types::Date;
164    type Time = drizzle_types::mysql::types::Time;
165    type Timestamp = drizzle_types::mysql::types::DateTime;
166    type TimestampTz = drizzle_types::mysql::types::Timestamp;
167    type Uuid = drizzle_types::mysql::types::Binary;
168    type Json = drizzle_types::mysql::types::Json;
169    type Jsonb = drizzle_types::mysql::types::Json;
170    type Any = drizzle_types::mysql::types::Any;
171
172    type Random = drizzle_types::mysql::types::Double;
173    type Sign = drizzle_types::mysql::types::BigInt;
174    type DomainNullable<Input: crate::expr::Nullability> = crate::expr::Null;
175    const CHAR_LENGTH_FN: &'static str = "CHAR_LENGTH";
176}
177
178/// How parameter placeholders are written.
179///
180/// Each [`Dialect`] has a default style ([`ParamStyle::for_dialect`]). A
181/// driver that speaks a dialect but binds parameters differently, such as
182/// the AWS Aurora Data API (PostgreSQL SQL with `:1, :2` parameters), can
183/// pick another style.
184///
185/// # Examples
186///
187/// ```
188/// use drizzle_core::dialect::{Dialect, ParamStyle};
189///
190/// let mut sql = String::new();
191/// ParamStyle::for_dialect(Dialect::PostgreSQL).write(2, &mut sql);
192/// ParamStyle::ColonNumbered.write(3, &mut sql);
193/// assert_eq!(sql, "$2:3");
194/// ```
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum ParamStyle {
197    /// `$1, $2, ...`: PostgreSQL.
198    DollarNumbered,
199    /// `?`: SQLite and MySQL, positional.
200    Question,
201    /// `:1, :2, ...`: AWS Aurora Data API.
202    ///
203    /// The names are 1-based positions, matching the
204    /// `SqlParameter { name: "1", ... }` encoding the Data API expects.
205    ColonNumbered,
206}
207
208impl ParamStyle {
209    /// The default style for `dialect`: `DollarNumbered` for PostgreSQL,
210    /// `Question` for SQLite and MySQL.
211    #[inline]
212    #[must_use]
213    pub const fn for_dialect(dialect: Dialect) -> Self {
214        match dialect {
215            Dialect::PostgreSQL => Self::DollarNumbered,
216            Dialect::SQLite | Dialect::MySQL => Self::Question,
217        }
218    }
219
220    /// Writes the placeholder for the parameter at 1-based position `index`.
221    #[inline]
222    pub fn write(self, index: usize, buf: &mut impl core::fmt::Write) {
223        match self {
224            Self::DollarNumbered => {
225                let _ = buf.write_char('$');
226                let _ = write!(buf, "{index}");
227            }
228            Self::ColonNumbered => {
229                let _ = buf.write_char(':');
230                let _ = write!(buf, "{index}");
231            }
232            Self::Question => {
233                let _ = buf.write_char('?');
234            }
235        }
236    }
237}
238
239/// Writes the default placeholder of `dialect` for the parameter at 1-based
240/// position `index`.
241///
242/// Same as `ParamStyle::for_dialect(dialect).write(index, buf)`.
243#[inline]
244pub fn write_placeholder(dialect: Dialect, index: usize, buf: &mut impl core::fmt::Write) {
245    ParamStyle::for_dialect(dialect).write(index, buf);
246}
247
248/// Feature markers for [`DialectSupports`]: SQL functions that only some
249/// dialects have.
250pub mod feature {
251    /// SQLite date/time functions (`unixepoch`, `strftime`, ...).
252    #[derive(Debug, Clone, Copy, Default)]
253    pub struct SQLiteDateTime;
254    /// PostgreSQL date/time functions (`date_trunc`, `age`, ...).
255    #[derive(Debug, Clone, Copy, Default)]
256    pub struct PostgresDateTime;
257    /// Sequence functions (`nextval`, `currval`, `setval`).
258    #[derive(Debug, Clone, Copy, Default)]
259    pub struct Sequence;
260    /// `TYPEOF`.
261    #[derive(Debug, Clone, Copy, Default)]
262    pub struct Typeof;
263    /// The `EXCLUDED` row in an upsert.
264    #[derive(Debug, Clone, Copy, Default)]
265    pub struct Excluded;
266    /// Aggregate `FILTER (WHERE ...)`.
267    #[derive(Debug, Clone, Copy, Default)]
268    pub struct AggregateFilter;
269    /// PostgreSQL aggregates (`array_agg`, `bool_and`, `json_agg`, ...).
270    #[derive(Debug, Clone, Copy, Default)]
271    pub struct PostgresAggregate;
272    /// SQLite-only aggregates (`total`, ...).
273    #[derive(Debug, Clone, Copy, Default)]
274    pub struct SQLiteAggregate;
275    /// `GROUP_CONCAT`.
276    #[derive(Debug, Clone, Copy, Default)]
277    pub struct GroupConcat;
278    /// PostgreSQL string functions (`initcap`, `split_part`, ...).
279    #[derive(Debug, Clone, Copy, Default)]
280    pub struct PostgresString;
281    /// `LEFT` / `RIGHT`.
282    #[derive(Debug, Clone, Copy, Default)]
283    pub struct LeftRight;
284    /// `LPAD` / `RPAD`.
285    #[derive(Debug, Clone, Copy, Default)]
286    pub struct Pad;
287    /// `REVERSE`.
288    #[derive(Debug, Clone, Copy, Default)]
289    pub struct Reverse;
290    /// `REPEAT`.
291    #[derive(Debug, Clone, Copy, Default)]
292    pub struct Repeat;
293}
294
295/// The dialect `Self` has the SQL feature `Feature`.
296///
297/// Dialect-specific functions require
298/// `V::DialectMarker: DialectSupports<feature::X>`, where `V` is the value
299/// type. Calling one on a dialect that lacks it is a compile error rather
300/// than a database error:
301///
302/// ```text
303/// error[E0277]: `Sequence` is not available for `SQLiteDialect`
304///   = note: use a dialect-specific alternative
305/// ```
306///
307/// # Examples
308///
309/// ```
310/// use drizzle_core::{DialectSupports, PostgresDialect, feature};
311///
312/// fn nextval<D: DialectSupports<feature::Sequence>>() {}
313///
314/// nextval::<PostgresDialect>();
315/// ```
316///
317/// ```compile_fail
318/// use drizzle_core::{DialectSupports, SQLiteDialect, feature};
319///
320/// fn nextval<D: DialectSupports<feature::Sequence>>() {}
321///
322/// // error: `Sequence` is not available for `SQLiteDialect`
323/// nextval::<SQLiteDialect>();
324/// ```
325#[diagnostic::on_unimplemented(
326    message = "`{Feature}` is not available for `{Self}`",
327    label = "this function is not supported by this dialect",
328    note = "use a dialect-specific alternative"
329)]
330pub trait DialectSupports<Feature> {}