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 (_, value) => value,
172 }
173 }
174
175 fn timestamp_value(column: &str, value: chrono::DateTime<chrono::Utc>) -> SqlValue {
177 match Self::column_kind(column) {
178 ColumnKind::NaiveTimestamp => value.naive_utc().into(),
179 _ => value.into(),
180 }
181 }
182
183 #[must_use]
184 fn column_kind(column: &str) -> ColumnKind {
185 Self::COLUMNS
186 .iter()
187 .find(|definition| definition.name == column)
188 .map_or(ColumnKind::Other, |definition| definition.kind)
189 }
190}
191
192fn projection<M: SqlxModel>(sql: &mut Sql, qualified: bool) {
193 for (index, column) in M::COLUMN_NAMES.iter().enumerate() {
194 if index > 0 {
195 sql.push(", ");
196 }
197 let raw_verification =
198 sql.engine() == Engine::Sqlite && M::PROVIDER_VERIFICATION_COLUMN == Some(*column);
199 let emit_column = |sql: &mut Sql| {
200 if qualified {
201 sql.column(M::TABLE, column);
202 } else {
203 sql.ident(column);
204 }
205 };
206 if raw_verification {
207 sql.push("CASE WHEN typeof(");
211 emit_column(sql);
212 sql.push(") IN ('integer', 'real') THEN ");
213 emit_column(sql);
214 sql.push(" = 1 WHEN ");
215 emit_column(sql);
216 sql.push(" IS NULL THEN NULL ELSE ");
217 emit_column(sql);
218 sql.push(" <> '' END AS ");
219 sql.ident(column);
220 } else {
221 emit_column(sql);
222 }
223 }
224}
225
226pub(crate) fn select_model<M: SqlxModel>(exec: Exec<'_>) -> Sql {
227 if exec.engine() != Engine::Sqlite || M::PROVIDER_VERIFICATION_COLUMN.is_none() {
228 return select(exec.engine(), M::TABLE, M::COLUMN_NAMES);
229 }
230 let mut sql = Sql::new(exec.engine());
231 sql.push("SELECT ");
232 projection::<M>(&mut sql, true);
233 sql.push(" FROM ");
234 sql.ident(M::TABLE);
235 sql
236}
237
238pub(crate) fn returning<M: SqlxModel>(sql: &mut Sql) {
239 sql.push(" RETURNING ");
240 projection::<M>(sql, false);
241}
242
243fn bind_model_value<M: SqlxModel>(sql: &mut Sql, column: &str, value: SqlValue) {
244 let cast = sql.engine() == Engine::Postgres
245 && M::PROVIDER_VERIFICATION_COLUMN == Some(column)
246 && matches!(&value, SqlValue::Text(_));
247 if cast {
248 sql.push("CAST(");
249 }
250 sql.bind(M::column_value(column, value));
251 if cast {
252 sql.push(" AS BOOLEAN)");
253 }
254}
255
256pub(crate) async fn insert<M: SqlxModel>(exec: Exec<'_>, active: &ActiveRow) -> AuthResult<M> {
258 let mut sql = Sql::new(exec.engine());
259 sql.push("INSERT INTO ");
260 sql.ident(M::TABLE);
261 sql.push(" (");
262 let present = active.present().collect::<Vec<_>>();
263 sql.column_list(&present.iter().map(|(name, _)| *name).collect::<Vec<_>>());
264 sql.push(") VALUES (");
265 for (index, (column, value)) in present.into_iter().enumerate() {
266 if index > 0 {
267 sql.push(", ");
268 }
269 bind_model_value::<M>(&mut sql, column, value.clone());
270 }
271 sql.push(")");
272 returning::<M>(&mut sql);
273 exec.fetch_optional::<M>(sql)
274 .await?
275 .ok_or_else(record_not_inserted)
276}
277
278pub(crate) async fn update<M: SqlxModel>(
281 exec: Exec<'_>,
282 active: &ActiveRow,
283) -> AuthResult<Option<M>> {
284 let key = active
285 .get(M::PRIMARY_KEY)
286 .and_then(ActiveValue::value)
287 .cloned()
288 .ok_or_else(|| AuthError::internal("model update requires its primary key"))?;
289 if active.changed().next().is_none() {
290 let mut sql = by_id::<M>(exec, key);
292 limit_one(&mut sql);
293 return exec.fetch_optional::<M>(sql).await;
294 }
295 let mut sql = Sql::new(exec.engine());
296 sql.push("UPDATE ");
297 sql.ident(M::TABLE);
298 sql.push(" SET ");
299 for (index, (column, value)) in active.changed().enumerate() {
300 if index > 0 {
301 sql.push(", ");
302 }
303 sql.ident(column);
304 sql.push(" = ");
305 bind_model_value::<M>(&mut sql, column, value.clone());
306 }
307 sql.push(" WHERE ");
308 sql.compare_model::<M>(M::TABLE, M::PRIMARY_KEY, " = ", key);
309 returning::<M>(&mut sql);
310 exec.fetch_optional::<M>(sql).await
311}
312
313pub(crate) fn by_id<M: SqlxModel>(exec: Exec<'_>, id: impl Into<SqlValue>) -> Sql {
315 let mut sql = select_model::<M>(exec);
316 sql.push(" WHERE ");
317 sql.compare_model::<M>(M::TABLE, M::PRIMARY_KEY, " = ", id);
318 sql
319}
320
321pub(crate) fn limit_one(sql: &mut Sql) {
323 sql.push(" LIMIT 1");
324}
325
326pub(crate) fn delete_by_id<M: SqlxModel>(exec: Exec<'_>, id: impl Into<SqlValue>) -> Sql {
328 let mut sql = Sql::new(exec.engine());
329 sql.push("DELETE FROM ");
330 sql.ident(M::TABLE);
331 sql.push(" WHERE ");
332 sql.compare_model::<M>(M::TABLE, M::PRIMARY_KEY, " = ", id);
333 sql
334}