1use crate::SQLConstraintKind;
2use crate::prelude::*;
3use crate::{Param, Placeholder, SQLParam, sql::tokens::Token};
4
5#[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#[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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub struct PrimaryKeyRef {
83 pub columns: &'static [&'static str],
84}
85
86#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub struct TableRef {
107 pub name: &'static str,
109 pub column_names: &'static [&'static str],
110
111 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 pub dialect: TableDialect,
122}
123
124impl TableRef {
125 #[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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
200pub struct ColumnFlags(u8);
201
202impl ColumnFlags {
203 pub const NOT_NULL: Self = Self(1 << 0);
205 pub const PRIMARY_KEY: Self = Self(1 << 1);
207 pub const UNIQUE: Self = Self(1 << 2);
209 pub const HAS_DEFAULT: Self = Self(1 << 3);
211
212 #[must_use]
214 pub const fn empty() -> Self {
215 Self(0)
216 }
217
218 #[must_use]
220 pub const fn from_bits(bits: u8) -> Self {
221 Self(bits)
222 }
223
224 #[must_use]
226 pub const fn bits(self) -> u8 {
227 self.0
228 }
229
230 #[must_use]
232 pub const fn contains(self, other: Self) -> bool {
233 (self.0 & other.0) == other.0
234 }
235
236 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
262pub struct ColumnRef {
263 pub table: &'static str,
265 pub name: &'static str,
266
267 pub sql_type: &'static str,
269 pub flags: ColumnFlags,
270
271 pub dialect: ColumnDialect,
273}
274
275impl ColumnRef {
276 #[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 #[must_use]
299 pub const fn not_null(&self) -> bool {
300 self.flags.contains(ColumnFlags::NOT_NULL)
301 }
302
303 #[must_use]
305 pub const fn primary_key(&self) -> bool {
306 self.flags.contains(ColumnFlags::PRIMARY_KEY)
307 }
308
309 #[must_use]
311 pub const fn unique(&self) -> bool {
312 self.flags.contains(ColumnFlags::UNIQUE)
313 }
314
315 #[must_use]
317 pub const fn has_default(&self) -> bool {
318 self.flags.contains(ColumnFlags::HAS_DEFAULT)
319 }
320}
321
322#[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#[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#[derive(Clone)]
404pub enum SQLChunk<'a, V: SQLParam> {
405 Token(Token),
408
409 Ident(Cow<'a, str>),
413
414 Raw(Cow<'a, str>),
418
419 Number(usize),
424
425 Param(Param<'a, V>),
428
429 Table(TableSqlRef),
433
434 Column(ColumnSqlRef),
437}
438
439impl<'a, V: SQLParam> SQLChunk<'a, V> {
440 #[inline]
444 #[must_use]
445 pub const fn token(t: Token) -> Self {
446 Self::Token(t)
447 }
448
449 #[inline]
451 #[must_use]
452 pub const fn ident_static(name: &'static str) -> Self {
453 Self::Ident(Cow::Borrowed(name))
454 }
455
456 #[inline]
458 #[must_use]
459 pub const fn raw_static(text: &'static str) -> Self {
460 Self::Raw(Cow::Borrowed(text))
461 }
462
463 #[inline]
465 #[must_use]
466 pub const fn table(table: TableRef) -> Self {
467 Self::Table(TableSqlRef::from_table_ref(table))
468 }
469
470 #[inline]
472 #[must_use]
473 pub const fn column(column: ColumnRef) -> Self {
474 Self::Column(ColumnSqlRef::from_column_ref(column))
475 }
476
477 #[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 #[inline]
490 pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
491 Self::Ident(name.into())
492 }
493
494 #[inline]
496 pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
497 Self::Raw(text.into())
498 }
499
500 #[inline]
502 #[must_use]
503 pub const fn number(value: usize) -> Self {
504 Self::Number(value)
505 }
506
507 #[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 #[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 #[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
580impl<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 assert!(size_of::<SQLChunk<'static, TestParam>>() <= 64);
629 }
630}