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> {}