gatekeep_sqlx/
fragment.rs1use std::marker::PhantomData;
2
3use sqlx::{
4 QueryBuilder,
5 types::{
6 Uuid,
7 time::{Date, OffsetDateTime, PrimitiveDateTime, Time},
8 },
9};
10
11mod backend;
12mod driver;
13mod tenant;
14pub use backend::GatekeepSqlxBackend;
15#[cfg(feature = "mysql")]
16pub use backend::MySqlBackend;
17#[cfg(feature = "postgres")]
18pub use backend::PostgresBackend;
19#[cfg(feature = "sqlite")]
20pub use backend::SqliteBackend;
21pub use driver::{
22 SqlxDriver, SqlxDriverError, infer_enabled_driver_from_url, validate_database_url_for_backend,
23};
24pub use tenant::{
25 MAX_TENANT_IDENTIFIER_BYTES, TenantColumn, TenantColumnError, TenantIdentifierPart,
26};
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum SqlxValue {
32 Bool(bool),
34 I16(i16),
36 I32(i32),
38 I64(i64),
40 Text(String),
42 Bytes(Vec<u8>),
44 Uuid(Uuid),
46 Date(Date),
48 Time(Time),
50 Timestamp(PrimitiveDateTime),
52 TimestampTz(OffsetDateTime),
54}
55
56macro_rules! impl_sqlx_value_from {
57 ($ty:ty, $variant:ident) => {
58 impl From<$ty> for SqlxValue {
59 fn from(value: $ty) -> Self {
60 Self::$variant(value)
61 }
62 }
63 };
64}
65
66impl_sqlx_value_from!(bool, Bool);
67impl_sqlx_value_from!(i16, I16);
68impl_sqlx_value_from!(i32, I32);
69impl_sqlx_value_from!(i64, I64);
70impl_sqlx_value_from!(String, Text);
71impl_sqlx_value_from!(Vec<u8>, Bytes);
72impl_sqlx_value_from!(Uuid, Uuid);
73impl_sqlx_value_from!(Date, Date);
74impl_sqlx_value_from!(Time, Time);
75impl_sqlx_value_from!(PrimitiveDateTime, Timestamp);
76impl_sqlx_value_from!(OffsetDateTime, TimestampTz);
77
78impl From<&str> for SqlxValue {
79 fn from(value: &str) -> Self {
80 Self::Text(value.to_owned())
81 }
82}
83
84impl From<&[u8]> for SqlxValue {
85 fn from(value: &[u8]) -> Self {
86 Self::Bytes(value.to_vec())
87 }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91enum SqlPart {
92 Text(String),
93 Bind(SqlxValue),
94}
95
96#[derive(Debug, PartialEq, Eq)]
98pub struct SqlxFragment<B> {
99 parts: Vec<SqlPart>,
100 backend: PhantomData<fn() -> B>,
101}
102
103impl<B> Clone for SqlxFragment<B> {
104 fn clone(&self) -> Self {
105 Self {
106 parts: self.parts.clone(),
107 backend: PhantomData,
108 }
109 }
110}
111
112impl<B> Default for SqlxFragment<B> {
113 fn default() -> Self {
114 Self {
115 parts: Vec::new(),
116 backend: PhantomData,
117 }
118 }
119}
120
121impl<B> SqlxFragment<B> {
122 #[must_use]
127 pub fn trusted(sql: impl Into<String>) -> Self {
128 let sql = sql.into();
129 if sql.is_empty() {
130 Self::default()
131 } else {
132 Self {
133 parts: vec![SqlPart::Text(sql)],
134 backend: PhantomData,
135 }
136 }
137 }
138
139 #[must_use]
141 pub fn bind(value: impl Into<SqlxValue>) -> Self {
142 Self {
143 parts: vec![SqlPart::Bind(value.into())],
144 backend: PhantomData,
145 }
146 }
147
148 pub fn binds(&self) -> impl Iterator<Item = &SqlxValue> {
150 self.parts.iter().filter_map(|part| match part {
151 SqlPart::Text(_) => None,
152 SqlPart::Bind(value) => Some(value),
153 })
154 }
155
156 pub fn push_fragment(&mut self, fragment: Self) {
158 self.parts.extend(fragment.parts);
159 }
160
161 pub(crate) fn push_sql(&mut self, sql: impl Into<String>) {
162 let sql = sql.into();
163 if !sql.is_empty() {
164 self.parts.push(SqlPart::Text(sql));
165 }
166 }
167
168 #[must_use]
169 pub(crate) fn wrapped(self) -> Self {
170 let mut fragment = Self::trusted("(");
171 fragment.push_fragment(self);
172 fragment.push_sql(")");
173 fragment
174 }
175
176 #[must_use]
177 pub(crate) fn unary(prefix: &str, inner: Self) -> Self {
178 let mut fragment = Self::trusted(prefix);
179 fragment.push_fragment(inner.wrapped());
180 fragment
181 }
182
183 #[must_use]
184 pub(crate) fn binary(separator: &str, fragments: impl IntoIterator<Item = Self>) -> Self {
185 let mut iter = fragments.into_iter();
186 let Some(first) = iter.next() else {
187 return Self::trusted("FALSE");
188 };
189
190 let mut fragment = first.wrapped();
191 for next in iter {
192 fragment.push_sql(separator);
193 fragment.push_fragment(next.wrapped());
194 }
195 fragment
196 }
197
198 #[must_use]
199 pub(crate) fn function(name: &str, fragments: impl IntoIterator<Item = Self>) -> Self {
200 let mut fragment = Self::trusted(name);
201 fragment.push_sql("(");
202
203 let mut iter = fragments.into_iter();
204 if let Some(first) = iter.next() {
205 fragment.push_fragment(first);
206 for next in iter {
207 fragment.push_sql(", ");
208 fragment.push_fragment(next);
209 }
210 }
211
212 fragment.push_sql(")");
213 fragment
214 }
215}
216
217impl<B> SqlxFragment<B>
218where
219 B: GatekeepSqlxBackend,
220{
221 #[must_use]
223 pub fn to_sql(&self) -> String {
224 let mut sql = String::new();
225 let mut placeholders = 0usize;
226
227 for part in &self.parts {
228 match part {
229 SqlPart::Text(text) => sql.push_str(text),
230 SqlPart::Bind(_) => {
231 placeholders = placeholders.saturating_add(1);
233 B::push_placeholder(&mut sql, placeholders);
234 }
235 }
236 }
237 sql
238 }
239
240 pub fn push_to(&self, builder: &mut QueryBuilder<B::Database>) {
242 for part in &self.parts {
243 match part {
244 SqlPart::Text(text) => {
245 builder.push(text);
246 }
247 SqlPart::Bind(value) => B::push_bind(builder, value),
248 }
249 }
250 }
251}
252
253#[cfg(feature = "postgres")]
255pub type PgValue = SqlxValue;
256
257#[cfg(feature = "postgres")]
259pub type PgFragment = SqlxFragment<PostgresBackend>;
260
261#[cfg(feature = "postgres")]
262impl SqlxFragment<PostgresBackend> {
263 #[must_use]
265 pub fn to_postgres_sql(&self) -> String {
266 self.to_sql()
267 }
268}