drizzle_core/sql/
chunk.rs

1use crate::prelude::*;
2use crate::{Param, Placeholder, SQLColumnInfo, SQLParam, SQLTableInfo, sql::tokens::Token};
3
4/// A SQL chunk represents a part of an SQL statement.
5///
6/// This enum has 7 variants, each with a clear semantic purpose:
7/// - `Token` - SQL keywords and operators (SELECT, FROM, =, etc.)
8/// - `Ident` - Quoted identifiers ("table_name", "column_name")
9/// - `Raw` - Unquoted raw SQL text (function names, expressions)
10/// - `Param` - Parameter placeholders with values
11/// - `Table` - Table reference with metadata access
12/// - `Column` - Column reference with metadata access
13/// - `Alias` - Alias wrapper (expr AS "name")
14#[derive(Clone)]
15pub enum SQLChunk<'a, V: SQLParam> {
16    /// SQL keywords and operators: SELECT, FROM, WHERE, =, AND, etc.
17    /// Renders as: keyword with automatic spacing rules
18    Token(Token),
19
20    /// Quoted identifier for user-provided names
21    /// Renders as: "name" (with quotes)
22    /// Use for: table names, column names, alias names
23    Ident(Cow<'a, str>),
24
25    /// Raw SQL text (unquoted) for expressions, function names
26    /// Renders as: text (no quotes, as-is)
27    /// Use for: function names like COUNT, expressions, numeric literals
28    Raw(Cow<'a, str>),
29
30    /// Parameter with value and placeholder
31    /// Renders as: ? or $1 or :name depending on placeholder style
32    Param(Param<'a, V>),
33
34    /// Table reference with full metadata access
35    /// Renders as: "table_name"
36    /// Provides: columns() for SELECT *, dependencies() for FK tracking
37    Table(&'static dyn SQLTableInfo),
38
39    /// Column reference with full metadata access
40    /// Renders as: "table"."column"
41    /// Provides: table(), is_primary_key(), foreign_key(), etc.
42    Column(&'static dyn SQLColumnInfo),
43
44    /// Alias wrapper: renders inner chunk followed by AS "alias"
45    /// Renders as: {inner} AS "alias"
46    Alias {
47        inner: Box<SQLChunk<'a, V>>,
48        alias: Cow<'a, str>,
49    },
50}
51
52impl<'a, V: SQLParam> SQLChunk<'a, V> {
53    // ==================== const constructors ====================
54
55    /// Creates a token chunk - const
56    #[inline]
57    pub const fn token(t: Token) -> Self {
58        Self::Token(t)
59    }
60
61    /// Creates a quoted identifier from a static string - const
62    #[inline]
63    pub const fn ident_static(name: &'static str) -> Self {
64        Self::Ident(Cow::Borrowed(name))
65    }
66
67    /// Creates raw SQL text from a static string - const
68    #[inline]
69    pub const fn raw_static(text: &'static str) -> Self {
70        Self::Raw(Cow::Borrowed(text))
71    }
72
73    /// Creates a table chunk - const
74    #[inline]
75    pub const fn table(table: &'static dyn SQLTableInfo) -> Self {
76        Self::Table(table)
77    }
78
79    /// Creates a column chunk - const
80    #[inline]
81    pub const fn column(column: &'static dyn SQLColumnInfo) -> Self {
82        Self::Column(column)
83    }
84
85    /// Creates a parameter chunk with borrowed value - const
86    #[inline]
87    pub const fn param_borrowed(value: &'a V, placeholder: Placeholder) -> Self {
88        Self::Param(Param {
89            value: Some(Cow::Borrowed(value)),
90            placeholder,
91        })
92    }
93
94    // ==================== non-const constructors ====================
95
96    /// Creates a quoted identifier from a runtime string
97    #[inline]
98    pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
99        Self::Ident(name.into())
100    }
101
102    /// Creates raw SQL text from a runtime string
103    #[inline]
104    pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
105        Self::Raw(text.into())
106    }
107
108    /// Creates a parameter chunk with owned value
109    #[inline]
110    pub fn param(value: impl Into<Cow<'a, V>>, placeholder: Placeholder) -> Self {
111        Self::Param(Param {
112            value: Some(value.into()),
113            placeholder,
114        })
115    }
116
117    /// Creates an alias chunk wrapping any SQLChunk
118    #[inline]
119    pub fn alias(inner: SQLChunk<'a, V>, alias: impl Into<Cow<'a, str>>) -> Self {
120        Self::Alias {
121            inner: Box::new(inner),
122            alias: alias.into(),
123        }
124    }
125
126    // ==================== write implementation ====================
127
128    /// Write chunk content to buffer
129    pub(crate) fn write(&self, buf: &mut impl core::fmt::Write) {
130        match self {
131            SQLChunk::Token(token) => {
132                let _ = buf.write_str(token.as_str());
133            }
134            SQLChunk::Ident(name) => {
135                let _ = buf.write_char('"');
136                let _ = buf.write_str(name);
137                let _ = buf.write_char('"');
138            }
139            SQLChunk::Raw(text) => {
140                let _ = buf.write_str(text);
141            }
142            SQLChunk::Param(Param { placeholder, .. }) => {
143                let _ = write!(buf, "{}", placeholder);
144            }
145            SQLChunk::Table(table) => {
146                let _ = buf.write_char('"');
147                let _ = buf.write_str(table.name());
148                let _ = buf.write_char('"');
149            }
150            SQLChunk::Column(column) => {
151                let _ = buf.write_char('"');
152                let _ = buf.write_str(column.table().name());
153                let _ = buf.write_str("\".\"");
154                let _ = buf.write_str(column.name());
155                let _ = buf.write_char('"');
156            }
157            SQLChunk::Alias { inner, alias } => {
158                inner.write(buf);
159                let _ = buf.write_str(" AS \"");
160                let _ = buf.write_str(alias);
161                let _ = buf.write_char('"');
162            }
163        }
164    }
165
166    /// Check if this chunk is "word-like" (needs space separation from other word-like chunks)
167    #[inline]
168    pub(crate) const fn is_word_like(&self) -> bool {
169        match self {
170            SQLChunk::Token(t) => !t.is_punctuation() && !t.is_operator(),
171            SQLChunk::Ident(_)
172            | SQLChunk::Raw(_)
173            | SQLChunk::Param(_)
174            | SQLChunk::Table(_)
175            | SQLChunk::Column(_)
176            | SQLChunk::Alias { .. } => true,
177        }
178    }
179}
180
181impl<'a, V: SQLParam + core::fmt::Debug> core::fmt::Debug for SQLChunk<'a, V> {
182    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
183        match self {
184            SQLChunk::Token(token) => f.debug_tuple("Token").field(token).finish(),
185            SQLChunk::Ident(name) => f.debug_tuple("Ident").field(name).finish(),
186            SQLChunk::Raw(text) => f.debug_tuple("Raw").field(text).finish(),
187            SQLChunk::Param(param) => f.debug_tuple("Param").field(param).finish(),
188            SQLChunk::Table(table) => f.debug_tuple("Table").field(&table.name()).finish(),
189            SQLChunk::Column(column) => f
190                .debug_tuple("Column")
191                .field(&format!("{}.{}", column.table().name(), column.name()))
192                .finish(),
193            SQLChunk::Alias { inner, alias } => f
194                .debug_struct("Alias")
195                .field("inner", inner)
196                .field("alias", alias)
197                .finish(),
198        }
199    }
200}
201
202// ==================== From implementations ====================
203
204impl<'a, V: SQLParam> From<Token> for SQLChunk<'a, V> {
205    #[inline]
206    fn from(value: Token) -> Self {
207        Self::Token(value)
208    }
209}
210
211impl<'a, V: SQLParam> From<&'static dyn SQLColumnInfo> for SQLChunk<'a, V> {
212    #[inline]
213    fn from(value: &'static dyn SQLColumnInfo) -> Self {
214        Self::Column(value)
215    }
216}
217
218impl<'a, V: SQLParam> From<&'static dyn SQLTableInfo> for SQLChunk<'a, V> {
219    #[inline]
220    fn from(value: &'static dyn SQLTableInfo) -> Self {
221        Self::Table(value)
222    }
223}
224
225impl<'a, V: SQLParam> From<Param<'a, V>> for SQLChunk<'a, V> {
226    #[inline]
227    fn from(value: Param<'a, V>) -> Self {
228        Self::Param(value)
229    }
230}