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