1use crate::error::record_not_inserted;
9use crate::pool::{Engine, Exec, SqlxRow};
10use crate::sql::{Sql, select};
11use crate::value::{ColumnKind, SqlValue};
12use alibi_core::error::{AuthError, AuthResult};
13
14#[derive(Clone, Debug, PartialEq)]
16pub enum ActiveValue {
17 Set(SqlValue),
19 Unchanged(SqlValue),
21 NotSet,
23}
24
25impl ActiveValue {
26 #[must_use]
27 pub const fn is_set(&self) -> bool {
28 matches!(self, Self::Set(_))
29 }
30
31 #[must_use]
32 pub const fn is_not_set(&self) -> bool {
33 matches!(self, Self::NotSet)
34 }
35
36 #[must_use]
38 pub const fn value(&self) -> Option<&SqlValue> {
39 match self {
40 Self::Set(value) | Self::Unchanged(value) => Some(value),
41 Self::NotSet => None,
42 }
43 }
44
45 #[must_use]
46 pub fn into_value(self) -> Option<SqlValue> {
47 match self {
48 Self::Set(value) | Self::Unchanged(value) => Some(value),
49 Self::NotSet => None,
50 }
51 }
52}
53
54#[derive(Clone, Debug, Default, PartialEq)]
56pub struct ActiveRow {
57 columns: Vec<(&'static str, ActiveValue)>,
58}
59
60impl ActiveRow {
61 #[must_use]
62 pub const fn new() -> Self {
63 Self {
64 columns: Vec::new(),
65 }
66 }
67
68 fn slot(&mut self, column: &'static str) -> &mut ActiveValue {
69 let index = self
70 .columns
71 .iter()
72 .position(|(name, _)| *name == column)
73 .unwrap_or_else(|| {
74 self.columns.push((column, ActiveValue::NotSet));
75 self.columns.len() - 1
76 });
77 #[expect(
79 clippy::indexing_slicing,
80 reason = "the slot index was located or appended immediately above"
81 )]
82 &mut self.columns[index].1
83 }
84
85 pub fn set(&mut self, column: &'static str, value: impl Into<SqlValue>) {
87 *self.slot(column) = ActiveValue::Set(value.into());
88 }
89
90 pub fn unchanged(&mut self, column: &'static str, value: impl Into<SqlValue>) {
92 *self.slot(column) = ActiveValue::Unchanged(value.into());
93 }
94
95 pub fn not_set(&mut self, column: &'static str) {
97 *self.slot(column) = ActiveValue::NotSet;
98 }
99
100 #[must_use]
101 pub fn get(&self, column: &str) -> Option<&ActiveValue> {
102 self.columns
103 .iter()
104 .find(|(name, _)| *name == column)
105 .map(|(_, value)| value)
106 }
107
108 pub fn take(&mut self, column: &str) -> ActiveValue {
110 self.columns
111 .iter_mut()
112 .find(|(name, _)| *name == column)
113 .map_or(ActiveValue::NotSet, |(_, value)| {
114 std::mem::replace(value, ActiveValue::NotSet)
115 })
116 }
117
118 pub(crate) fn present(&self) -> impl Iterator<Item = (&'static str, &SqlValue)> {
119 self.columns
120 .iter()
121 .filter_map(|(name, value)| value.value().map(|value| (*name, value)))
122 }
123
124 pub(crate) fn changed(&self) -> impl Iterator<Item = (&'static str, &SqlValue)> {
125 self.columns.iter().filter_map(|(name, value)| match value {
126 ActiveValue::Set(value) => Some((*name, value)),
127 ActiveValue::Unchanged(_) | ActiveValue::NotSet => None,
128 })
129 }
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
134pub struct ColumnDef {
135 pub name: &'static str,
136 pub kind: ColumnKind,
137}
138
139pub trait SqlxModel: SqlxRow + Clone + Send + Sync + 'static {
141 const TABLE: &'static str;
143 const COLUMNS: &'static [ColumnDef];
145 const COLUMN_NAMES: &'static [&'static str];
147 const FIELD_COLUMNS: &'static [(&'static str, &'static str)] = &[];
149 const PRIMARY_KEY: &'static str;
151 const PROVIDER_VERIFICATION_COLUMN: Option<&'static str> = None;
154
155 fn into_active(self) -> ActiveRow;
157
158 fn from_active(active: ActiveRow) -> AuthResult<Self>;
165
166 fn column_value(column: &str, value: SqlValue) -> SqlValue {
169 match (Self::column_kind(column), value) {
170 (ColumnKind::BpChar, SqlValue::Text(value)) => SqlValue::BpChar(value),
171 (ColumnKind::Text, SqlValue::Json(value)) => {
172 SqlValue::Text(value.map(|value| value.to_string()))
173 }
174 (_, value) => value,
175 }
176 }
177
178 fn timestamp_value(column: &str, value: chrono::DateTime<chrono::Utc>) -> SqlValue {
180 match Self::column_kind(column) {
181 ColumnKind::NaiveTimestamp => value.naive_utc().into(),
182 _ => value.into(),
183 }
184 }
185
186 #[must_use]
187 fn column_kind(column: &str) -> ColumnKind {
188 Self::COLUMNS
189 .iter()
190 .find(|definition| definition.name == column)
191 .map_or(ColumnKind::Other, |definition| definition.kind)
192 }
193}
194
195fn projection<M: SqlxModel>(sql: &mut Sql, qualified: bool) {
196 for (index, column) in M::COLUMN_NAMES.iter().enumerate() {
197 if index > 0 {
198 sql.push(", ");
199 }
200 let raw_verification =
201 sql.engine() == Engine::Sqlite && M::PROVIDER_VERIFICATION_COLUMN == Some(*column);
202 let emit_column = |sql: &mut Sql| {
203 if qualified {
204 sql.column(M::TABLE, column);
205 } else {
206 sql.ident(column);
207 }
208 };
209 if raw_verification {
210 sql.push("CASE WHEN typeof(");
214 emit_column(sql);
215 sql.push(") IN ('integer', 'real') THEN ");
216 emit_column(sql);
217 sql.push(" = 1 WHEN ");
218 emit_column(sql);
219 sql.push(" IS NULL THEN NULL ELSE ");
220 emit_column(sql);
221 sql.push(" <> '' END AS ");
222 sql.ident(column);
223 } else if sql.engine() == Engine::Postgres && M::column_kind(column) == ColumnKind::BigInt {
224 sql.push("CAST(");
225 emit_column(sql);
226 sql.push(" AS BIGINT) AS ");
227 sql.ident(column);
228 } else {
229 emit_column(sql);
230 }
231 }
232}
233
234pub(crate) fn select_model<M: SqlxModel>(exec: Exec<'_>) -> Sql {
235 if exec.engine() == Engine::Sqlite && M::PROVIDER_VERIFICATION_COLUMN.is_none() {
236 return select(exec.engine(), M::TABLE, M::COLUMN_NAMES);
237 }
238 let mut sql = Sql::new(exec.engine());
239 sql.push("SELECT ");
240 projection::<M>(&mut sql, true);
241 sql.push(" FROM ");
242 sql.ident(M::TABLE);
243 sql
244}
245
246pub(crate) fn returning<M: SqlxModel>(sql: &mut Sql) {
247 sql.push(" RETURNING ");
248 projection::<M>(sql, false);
249}
250
251pub(crate) fn bind_model_value<M: SqlxModel>(sql: &mut Sql, column: &str, value: SqlValue) {
252 if sql.engine() == Engine::Postgres && M::column_kind(column) == ColumnKind::Number {
253 let text = match value {
254 SqlValue::Double(value) => SqlValue::Text(value.map(|value| value.to_string())),
255 SqlValue::BigInt(value) => SqlValue::Text(value.map(|value| value.to_string())),
256 SqlValue::Int(value) => SqlValue::Text(value.map(|value| value.to_string())),
257 value => value,
258 };
259 sql.push("CAST(");
260 sql.bind(text);
261 sql.push(" AS INTEGER)");
262 return;
263 }
264 let cast = sql.engine() == Engine::Postgres
265 && M::PROVIDER_VERIFICATION_COLUMN == Some(column)
266 && matches!(&value, SqlValue::Text(_));
267 if cast {
268 sql.push("CAST(");
269 }
270 sql.bind(M::column_value(column, value));
271 if cast {
272 sql.push(" AS BOOLEAN)");
273 }
274}
275
276pub(crate) async fn insert<M: SqlxModel>(exec: Exec<'_>, active: &ActiveRow) -> AuthResult<M> {
278 let mut sql = Sql::new(exec.engine());
279 sql.push("INSERT INTO ");
280 sql.ident(M::TABLE);
281 sql.push(" (");
282 let present = active.present().collect::<Vec<_>>();
283 sql.column_list(&present.iter().map(|(name, _)| *name).collect::<Vec<_>>());
284 sql.push(") VALUES (");
285 for (index, (column, value)) in present.into_iter().enumerate() {
286 if index > 0 {
287 sql.push(", ");
288 }
289 bind_model_value::<M>(&mut sql, column, value.clone());
290 }
291 sql.push(")");
292 returning::<M>(&mut sql);
293 exec.fetch_optional::<M>(sql)
294 .await?
295 .ok_or_else(record_not_inserted)
296}
297
298pub(crate) async fn update<M: SqlxModel>(
301 exec: Exec<'_>,
302 active: &ActiveRow,
303) -> AuthResult<Option<M>> {
304 let key = active
305 .get(M::PRIMARY_KEY)
306 .and_then(ActiveValue::value)
307 .cloned()
308 .ok_or_else(|| AuthError::internal("model update requires its primary key"))?;
309 if active.changed().next().is_none() {
310 let mut sql = by_id::<M>(exec, key);
312 limit_one(&mut sql);
313 return exec.fetch_optional::<M>(sql).await;
314 }
315 let mut sql = Sql::new(exec.engine());
316 sql.push("UPDATE ");
317 sql.ident(M::TABLE);
318 sql.push(" SET ");
319 for (index, (column, value)) in active.changed().enumerate() {
320 if index > 0 {
321 sql.push(", ");
322 }
323 sql.ident(column);
324 sql.push(" = ");
325 bind_model_value::<M>(&mut sql, column, value.clone());
326 }
327 sql.push(" WHERE ");
328 sql.compare_model::<M>(M::TABLE, M::PRIMARY_KEY, " = ", key);
329 returning::<M>(&mut sql);
330 exec.fetch_optional::<M>(sql).await
331}
332
333pub(crate) fn by_id<M: SqlxModel>(exec: Exec<'_>, id: impl Into<SqlValue>) -> Sql {
335 let mut sql = select_model::<M>(exec);
336 sql.push(" WHERE ");
337 sql.compare_model::<M>(M::TABLE, M::PRIMARY_KEY, " = ", id);
338 sql
339}
340
341pub(crate) fn limit_one(sql: &mut Sql) {
343 sql.push(" LIMIT 1");
344}
345
346pub(crate) fn delete_by_id<M: SqlxModel>(exec: Exec<'_>, id: impl Into<SqlValue>) -> Sql {
348 let mut sql = Sql::new(exec.engine());
349 sql.push("DELETE FROM ");
350 sql.ident(M::TABLE);
351 sql.push(" WHERE ");
352 sql.compare_model::<M>(M::TABLE, M::PRIMARY_KEY, " = ", id);
353 sql
354}