1use crate::{AuthError, AuthResult};
3use std::sync::Arc;
4
5pub trait DatabaseIdGenerator: Send + Sync {
7 fn generate(&self, model: &str, size: Option<usize>) -> AuthResult<Option<String>>;
8}
9impl<F> DatabaseIdGenerator for F
10where
11 F: Fn(&str, Option<usize>) -> AuthResult<Option<String>> + Send + Sync,
12{
13 fn generate(&self, model: &str, size: Option<usize>) -> AuthResult<Option<String>> {
14 self(model, size)
15 }
16}
17
18#[derive(Clone)]
19pub enum DatabaseIdStrategy {
20 Uuid,
21 Serial,
22 Custom(Arc<dyn DatabaseIdGenerator>),
23}
24impl std::fmt::Debug for DatabaseIdStrategy {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.write_str(match self {
27 Self::Uuid => "Uuid",
28 Self::Serial => "Serial",
29 Self::Custom(_) => "Custom(..)",
30 })
31 }
32}
33impl super::AdvancedDatabaseConfig {
34 #[must_use]
35 pub fn serial_ids(&self) -> bool {
36 matches!(self.generate_id, Some(DatabaseIdStrategy::Serial))
37 || (self.generate_id.is_none() && self.use_number_id)
38 }
39 pub fn generated_id(&self, model: &str) -> AuthResult<Option<String>> {
41 match &self.generate_id {
42 Some(DatabaseIdStrategy::Uuid) => Ok(Some(uuid::Uuid::new_v4().to_string())),
43 Some(DatabaseIdStrategy::Custom(generator)) => generator
44 .generate(model, None)
45 .map_err(crate::store::adapter::callback_error)?
46 .map(Some)
47 .ok_or_else(|| {
48 AuthError::CallbackFailure(Box::new(AuthError::internal(
49 "Application ID generator returned no identifier",
50 )))
51 }),
52 _ => Ok(None),
53 }
54 }
55}
56
57#[doc(hidden)]
60pub fn serial_id_statements(table: &str, column: &str, postgres: bool) -> [String; 2] {
61 let quote = |value: &str| format!("\"{}\"", value.replace('"', "\"\""));
62 let table_sql = quote(table);
63 let column_sql = quote(column);
64 let key = format!("'{table}'", table = table.replace('\'', "''"));
65 let numeric = if postgres {
66 format!(" WHERE CAST({column_sql} AS TEXT) ~ '^[0-9]+$'")
67 } else {
68 " WHERE 1 = 1".into()
69 };
70 let next = if postgres {
71 "GREATEST(alibi_id_sequences.value + 1, EXCLUDED.value)"
72 } else {
73 "MAX(alibi_id_sequences.value + 1, EXCLUDED.value)"
74 };
75 [
76 "CREATE TABLE IF NOT EXISTS alibi_id_sequences (model TEXT PRIMARY KEY, value BIGINT NOT NULL)".into(),
77 format!("INSERT INTO alibi_id_sequences (model, value) SELECT {key}, COALESCE(MAX(CAST(CAST({column_sql} AS TEXT) AS BIGINT)), 0) + 1 FROM {table_sql}{numeric} ON CONFLICT (model) DO UPDATE SET value = {next} RETURNING value"),
78 ]
79}