Skip to main content

drizzle_core/sql/
chunk.rs

1use crate::SQLConstraintKind;
2use crate::prelude::*;
3use crate::{Param, Placeholder, SQLParam, sql::tokens::Token};
4
5// ==================== Dialect enums ====================
6
7/// Dialect-specific column metadata. Const-compatible enum grouping
8/// fields that only apply to one dialect.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum ColumnDialect {
11    SQLite {
12        autoincrement: bool,
13    },
14    PostgreSQL {
15        postgres_type: &'static str,
16        is_serial: bool,
17        is_bigserial: bool,
18        is_generated_identity: bool,
19        is_identity_always: bool,
20        generated_expression: Option<&'static str>,
21        generated_stored: bool,
22        collate: Option<&'static str>,
23    },
24}
25
26/// Dialect-specific table metadata.
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28pub enum TableDialect {
29    #[default]
30    PostgreSQL,
31    SQLite {
32        without_rowid: bool,
33        strict: bool,
34    },
35}
36
37// ==================== Ref structs ====================
38
39/// Foreign key reference as a const Copy struct.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct ForeignKeyRef {
42    pub target_table: &'static str,
43    pub source_columns: &'static [&'static str],
44    pub target_columns: &'static [&'static str],
45}
46
47/// Primary key reference as a const Copy struct.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct PrimaryKeyRef {
50    pub columns: &'static [&'static str],
51}
52
53/// Constraint reference as a const Copy struct.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub struct ConstraintRef {
56    pub name: Option<&'static str>,
57    pub kind: SQLConstraintKind,
58    pub columns: &'static [&'static str],
59    pub check_expression: Option<&'static str>,
60}
61
62// ==================== Enhanced TableRef and ColumnRef ====================
63
64/// Table reference with full schema metadata.
65///
66/// Carries both the SQL rendering fields (`name`, `column_names`) and
67/// complete schema metadata (columns, keys, constraints). SQL rendering
68/// code only uses `name`/`column_names` and ignores extra fields.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct TableRef {
71    // SQL rendering fields
72    pub name: &'static str,
73    pub column_names: &'static [&'static str],
74
75    // Schema metadata
76    pub schema: Option<&'static str>,
77    pub qualified_name: &'static str,
78    pub columns: &'static [ColumnRef],
79    pub primary_key: Option<PrimaryKeyRef>,
80    pub foreign_keys: &'static [ForeignKeyRef],
81    pub constraints: &'static [ConstraintRef],
82    pub dependency_names: &'static [&'static str],
83
84    // Dialect-specific
85    pub dialect: TableDialect,
86}
87
88impl TableRef {
89    /// Creates a lightweight `TableRef` for SQL rendering only.
90    ///
91    /// Only `name` and `column_names` are populated; metadata fields use
92    /// empty defaults. Use a full struct literal for metadata-carrying refs.
93    #[must_use]
94    pub const fn sql(name: &'static str, column_names: &'static [&'static str]) -> Self {
95        Self {
96            name,
97            column_names,
98            schema: None,
99            qualified_name: "",
100            columns: &[],
101            primary_key: None,
102            foreign_keys: &[],
103            constraints: &[],
104            dependency_names: &[],
105            dialect: TableDialect::PostgreSQL,
106        }
107    }
108}
109
110/// Packed column metadata flags for [`ColumnRef`].
111///
112/// Encodes the nullability, primary-key, unique, and has-default bits in a
113/// single byte so that [`ColumnRef`] stays below the "too many bools" threshold
114/// while keeping each bit independently addressable.
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
116pub struct ColumnFlags(u8);
117
118impl ColumnFlags {
119    /// Column is declared `NOT NULL`.
120    pub const NOT_NULL: Self = Self(1 << 0);
121    /// Column participates in the table's primary key.
122    pub const PRIMARY_KEY: Self = Self(1 << 1);
123    /// Column has a `UNIQUE` constraint.
124    pub const UNIQUE: Self = Self(1 << 2);
125    /// Column has a `DEFAULT` clause.
126    pub const HAS_DEFAULT: Self = Self(1 << 3);
127
128    /// Returns a flag set with no bits set.
129    #[must_use]
130    pub const fn empty() -> Self {
131        Self(0)
132    }
133
134    /// Reconstructs a flag set from its raw byte representation.
135    #[must_use]
136    pub const fn from_bits(bits: u8) -> Self {
137        Self(bits)
138    }
139
140    /// Returns the raw byte representation.
141    #[must_use]
142    pub const fn bits(self) -> u8 {
143        self.0
144    }
145
146    /// Returns `true` when every bit in `other` is set in `self`.
147    #[must_use]
148    pub const fn contains(self, other: Self) -> bool {
149        (self.0 & other.0) == other.0
150    }
151
152    /// Returns the union of two flag sets.
153    #[must_use]
154    pub const fn union(self, other: Self) -> Self {
155        Self(self.0 | other.0)
156    }
157}
158
159impl core::ops::BitOr for ColumnFlags {
160    type Output = Self;
161    fn bitor(self, rhs: Self) -> Self {
162        self.union(rhs)
163    }
164}
165
166impl core::ops::BitOrAssign for ColumnFlags {
167    fn bitor_assign(&mut self, rhs: Self) {
168        *self = self.union(rhs);
169    }
170}
171
172/// Column reference with full schema metadata.
173///
174/// Carries both the SQL rendering fields (`table`, `name`) and
175/// complete column metadata. SQL rendering code only uses the name fields
176/// and ignores extra metadata.
177#[derive(Clone, Copy, Debug, PartialEq, Eq)]
178pub struct ColumnRef {
179    // SQL rendering fields
180    pub table: &'static str,
181    pub name: &'static str,
182
183    // Schema metadata
184    pub sql_type: &'static str,
185    pub flags: ColumnFlags,
186
187    // Dialect-specific
188    pub dialect: ColumnDialect,
189}
190
191impl ColumnRef {
192    /// Creates a lightweight `ColumnRef` for SQL rendering only.
193    ///
194    /// Only `table` and `name` are populated; metadata fields
195    /// use empty defaults. Use a full struct literal for metadata-carrying refs.
196    #[must_use]
197    pub const fn sql(table: &'static str, name: &'static str) -> Self {
198        Self {
199            table,
200            name,
201            sql_type: "",
202            flags: ColumnFlags::empty(),
203            dialect: ColumnDialect::SQLite {
204                autoincrement: false,
205            },
206        }
207    }
208
209    /// Returns `true` if this column is declared `NOT NULL`.
210    #[must_use]
211    pub const fn not_null(&self) -> bool {
212        self.flags.contains(ColumnFlags::NOT_NULL)
213    }
214
215    /// Returns `true` if this column participates in the primary key.
216    #[must_use]
217    pub const fn primary_key(&self) -> bool {
218        self.flags.contains(ColumnFlags::PRIMARY_KEY)
219    }
220
221    /// Returns `true` if this column has a `UNIQUE` constraint.
222    #[must_use]
223    pub const fn unique(&self) -> bool {
224        self.flags.contains(ColumnFlags::UNIQUE)
225    }
226
227    /// Returns `true` if this column has a `DEFAULT` clause.
228    #[must_use]
229    pub const fn has_default(&self) -> bool {
230        self.flags.contains(ColumnFlags::HAS_DEFAULT)
231    }
232}
233
234// ==================== Identifier quoting ====================
235
236/// Writes a SQL identifier enclosed in double quotes.
237///
238/// Any embedded `"` characters are doubled to prevent identifier-injection
239/// (CWE-89). Both `PostgreSQL` and `SQLite` accept `"..."` as a delimited
240/// identifier and treat `""` as an escaped double-quote character inside
241/// such an identifier.
242///
243/// Fast path: identifiers with no embedded `"` are written with three calls
244/// (open quote, name, close quote). Only identifiers containing a `"` take
245/// the character-by-character escaping path.
246#[inline]
247pub fn write_quoted_ident(buf: &mut impl core::fmt::Write, name: &str) {
248    let _ = buf.write_char('"');
249    if name.contains('"') {
250        for ch in name.chars() {
251            if ch == '"' {
252                let _ = buf.write_str("\"\"");
253            } else {
254                let _ = buf.write_char(ch);
255            }
256        }
257    } else {
258        let _ = buf.write_str(name);
259    }
260    let _ = buf.write_char('"');
261}
262
263// ==================== SQLChunk ====================
264
265/// A SQL chunk represents a part of an SQL statement.
266///
267/// Each variant has a clear semantic purpose:
268/// - `Token` - SQL keywords and operators (SELECT, FROM, =, etc.)
269/// - `Ident` - Quoted identifiers ("`table_name`", "`column_name`")
270/// - `Raw` - Unquoted raw SQL text (function names, expressions)
271/// - `Param` - Parameter placeholders with values
272/// - `Table` - Table reference via `TableRef`
273/// - `Column` - Column reference via `ColumnRef`
274#[derive(Clone)]
275pub enum SQLChunk<'a, V: SQLParam> {
276    /// SQL keywords and operators: SELECT, FROM, WHERE, =, AND, etc.
277    /// Renders as: keyword with automatic spacing rules
278    Token(Token),
279
280    /// Quoted identifier for user-provided names
281    /// Renders as: "name" (with quotes)
282    /// Use for: table names, column names, alias names
283    Ident(Cow<'a, str>),
284
285    /// Raw SQL text (unquoted) for expressions, function names
286    /// Renders as: text (no quotes, as-is)
287    /// Use for: function names like COUNT, expressions, numeric literals
288    Raw(Cow<'a, str>),
289
290    /// Unsigned integer SQL literal rendered directly without heap allocation.
291    ///
292    /// Primarily used for clauses like LIMIT/OFFSET where numeric literals are
293    /// embedded directly in SQL text rather than parameterized.
294    Number(usize),
295
296    /// Parameter with value and placeholder
297    /// Renders as: ? or $1 or :name depending on placeholder style
298    Param(Param<'a, V>),
299
300    /// Table reference with static name and column names.
301    /// Renders as: "`table_name`"
302    /// Column names used for SELECT * expansion.
303    Table(TableRef),
304
305    /// Column reference with static table and column names.
306    /// Renders as: "`table_name"."column_name`"
307    Column(ColumnRef),
308}
309
310impl<'a, V: SQLParam> SQLChunk<'a, V> {
311    // ==================== const constructors ====================
312
313    /// Creates a token chunk - const
314    #[inline]
315    #[must_use]
316    pub const fn token(t: Token) -> Self {
317        Self::Token(t)
318    }
319
320    /// Creates a quoted identifier from a static string - const
321    #[inline]
322    #[must_use]
323    pub const fn ident_static(name: &'static str) -> Self {
324        Self::Ident(Cow::Borrowed(name))
325    }
326
327    /// Creates raw SQL text from a static string - const
328    #[inline]
329    #[must_use]
330    pub const fn raw_static(text: &'static str) -> Self {
331        Self::Raw(Cow::Borrowed(text))
332    }
333
334    /// Creates a table chunk - const
335    #[inline]
336    #[must_use]
337    pub const fn table(table: TableRef) -> Self {
338        Self::Table(table)
339    }
340
341    /// Creates a column chunk - const
342    #[inline]
343    #[must_use]
344    pub const fn column(column: ColumnRef) -> Self {
345        Self::Column(column)
346    }
347
348    /// Creates a parameter chunk with borrowed value - const
349    #[inline]
350    pub const fn param_borrowed(value: &'a V, placeholder: Placeholder) -> Self {
351        Self::Param(Param {
352            value: Some(Cow::Borrowed(value)),
353            placeholder,
354        })
355    }
356
357    // ==================== non-const constructors ====================
358
359    /// Creates a quoted identifier from a runtime string
360    #[inline]
361    pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
362        Self::Ident(name.into())
363    }
364
365    /// Creates raw SQL text from a runtime string
366    #[inline]
367    pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
368        Self::Raw(text.into())
369    }
370
371    /// Creates an unsigned integer SQL literal chunk.
372    #[inline]
373    #[must_use]
374    pub const fn number(value: usize) -> Self {
375        Self::Number(value)
376    }
377
378    /// Creates a parameter chunk with owned value
379    #[inline]
380    pub fn param(value: impl Into<Cow<'a, V>>, placeholder: Placeholder) -> Self {
381        Self::Param(Param {
382            value: Some(value.into()),
383            placeholder,
384        })
385    }
386
387    // ==================== write implementation ====================
388
389    /// Write chunk content to buffer
390    pub(crate) fn write(&self, buf: &mut impl core::fmt::Write) {
391        match self {
392            SQLChunk::Token(token) => {
393                let _ = buf.write_str(token.as_str());
394            }
395            SQLChunk::Ident(name) => {
396                write_quoted_ident(buf, name);
397            }
398            SQLChunk::Raw(text) => {
399                let _ = buf.write_str(text);
400            }
401            SQLChunk::Number(value) => {
402                let _ = write!(buf, "{value}");
403            }
404            SQLChunk::Param(Param { placeholder, .. }) => {
405                let _ = write!(buf, "{placeholder}");
406            }
407            SQLChunk::Table(t) => {
408                write_quoted_ident(buf, t.name);
409            }
410            SQLChunk::Column(c) => {
411                write_quoted_ident(buf, c.table);
412                let _ = buf.write_char('.');
413                write_quoted_ident(buf, c.name);
414            }
415        }
416    }
417
418    /// Check if this chunk is "word-like" (needs space separation from other word-like chunks)
419    #[inline]
420    pub(crate) const fn is_word_like(&self) -> bool {
421        match self {
422            SQLChunk::Token(t) => !t.is_punctuation() && !t.is_operator(),
423            SQLChunk::Ident(_)
424            | SQLChunk::Raw(_)
425            | SQLChunk::Number(_)
426            | SQLChunk::Param(_)
427            | SQLChunk::Table(_)
428            | SQLChunk::Column(_) => true,
429        }
430    }
431}
432
433impl<V: SQLParam + core::fmt::Debug> core::fmt::Debug for SQLChunk<'_, V> {
434    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
435        match self {
436            SQLChunk::Token(token) => f.debug_tuple("Token").field(token).finish(),
437            SQLChunk::Ident(name) => f.debug_tuple("Ident").field(name).finish(),
438            SQLChunk::Raw(text) => f.debug_tuple("Raw").field(text).finish(),
439            SQLChunk::Number(value) => f.debug_tuple("Number").field(value).finish(),
440            SQLChunk::Param(param) => f.debug_tuple("Param").field(param).finish(),
441            SQLChunk::Table(t) => f.debug_tuple("Table").field(&t.name).finish(),
442            SQLChunk::Column(c) => f
443                .debug_tuple("Column")
444                .field(&format!("{}.{}", c.table, c.name))
445                .finish(),
446        }
447    }
448}
449
450// ==================== From implementations ====================
451
452impl<V: SQLParam> From<Token> for SQLChunk<'_, V> {
453    #[inline]
454    fn from(value: Token) -> Self {
455        Self::Token(value)
456    }
457}
458
459impl<V: SQLParam> From<TableRef> for SQLChunk<'_, V> {
460    #[inline]
461    fn from(value: TableRef) -> Self {
462        Self::Table(value)
463    }
464}
465
466impl<V: SQLParam> From<ColumnRef> for SQLChunk<'_, V> {
467    #[inline]
468    fn from(value: ColumnRef) -> Self {
469        Self::Column(value)
470    }
471}
472
473impl<'a, V: SQLParam> From<Param<'a, V>> for SQLChunk<'a, V> {
474    #[inline]
475    fn from(value: Param<'a, V>) -> Self {
476        Self::Param(value)
477    }
478}