Skip to main content

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