1use std::fmt;
2use std::num::NonZeroUsize;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::time::Duration;
6
7use sofie_core::{ColumnReader, Error, Result, SqlType, SqlValue};
8
9use crate::{GuardCheck, MessageSql, Migrations};
10
11pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
12
13const DEFAULT_POOL_WAIT: Duration = Duration::from_secs(5);
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct PoolSettings {
18 pub connections: Option<NonZeroUsize>,
20 pub wait: Duration,
21}
22
23impl Default for PoolSettings {
24 fn default() -> Self {
25 Self {
26 connections: None,
27 wait: DEFAULT_POOL_WAIT,
28 }
29 }
30}
31
32#[derive(Debug)]
33pub enum Step {
34 Statement {
36 sql: String,
37 params: Vec<SqlValue>,
38 expect_rows: Option<ExpectedRows>,
39 },
40 Script(String),
42}
43
44impl Step {
45 #[must_use]
47 pub fn statement(
48 sql: impl Into<String>,
49 params: Vec<SqlValue>,
50 expect_rows: Option<u64>,
51 ) -> Self {
52 Self::Statement {
53 sql: sql.into(),
54 params,
55 expect_rows: expect_rows.map(ExpectedRows::or_conflict),
56 }
57 }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct ExpectedRows {
62 pub rows: u64,
63 pub otherwise: Unmet,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
68pub enum Unmet {
69 Conflict,
70 NotFound,
71 Refused(Arc<GuardCheck>),
72}
73
74impl ExpectedRows {
75 #[must_use]
76 pub const fn or_conflict(rows: u64) -> Self {
77 Self {
78 rows,
79 otherwise: Unmet::Conflict,
80 }
81 }
82
83 #[must_use]
84 pub const fn or_not_found(rows: u64) -> Self {
85 Self {
86 rows,
87 otherwise: Unmet::NotFound,
88 }
89 }
90
91 pub fn check(&self, affected: u64) -> Result<()> {
92 let met = affected == self.rows;
93 match &self.otherwise {
94 Unmet::Refused(check) => check.checked(!met),
95 _ if met => Ok(()),
96 Unmet::Conflict => Err(Error::Conflict),
97 Unmet::NotFound => Err(Error::NotFound),
98 }
99 }
100}
101
102pub trait RowSink: Send {
103 fn expects(&self) -> &[Option<SqlType>];
105
106 fn row(&mut self, row: &mut dyn ColumnReader) -> Result<()>;
107}
108
109pub trait Driver: Send + Sync + 'static {
110 fn dialect(&self) -> &dyn Dialect;
111
112 fn query<'a>(
113 &'a self,
114 sql: &'a str,
115 params: &'a [SqlValue],
116 rows: &'a mut dyn RowSink,
117 ) -> BoxFuture<'a, Result<()>>;
118
119 #[cfg(feature = "raw-sql")]
121 fn query_read_only<'a>(
122 &'a self,
123 role: Option<&'a str>,
124 sql: &'a str,
125 params: &'a [SqlValue],
126 rows: &'a mut dyn RowSink,
127 ) -> BoxFuture<'a, Result<()>> {
128 let _ = (role, params, rows);
129 crate::raw::refuse_read_only(sql)
130 }
131
132 fn transaction(&self, steps: Vec<Step>) -> BoxFuture<'_, Result<()>>;
135
136 fn lock_migrations(&self) -> Option<BoxFuture<'_, Result<Box<dyn MigrationsLock>>>> {
138 None
139 }
140
141 #[cfg(feature = "concurrent-indexes")]
143 fn migrate_alone<'a>(
144 &'a self,
145 schema: &'a str,
146 sql: &'a str,
147 record: Step,
148 ) -> std::result::Result<BoxFuture<'a, Result<()>>, Step> {
149 let _ = (schema, sql);
150 Err(record)
151 }
152
153 fn catalog(&self) -> Option<&dyn crate::Catalog> {
154 None
155 }
156
157 fn wakeups(&self) -> Option<BoxFuture<'_, Result<Box<dyn Wakeups>>>> {
158 None
159 }
160
161 #[cfg(feature = "shield")]
162 fn shield_wakeups(&self) -> Option<BoxFuture<'_, Result<Box<dyn Wakeups>>>> {
163 None
164 }
165
166 #[cfg(feature = "cache")]
168 fn cache_notifications(&self) -> Option<BoxFuture<'_, Result<Box<dyn Notifications>>>> {
169 None
170 }
171
172 #[cfg(feature = "live")]
174 fn change_notifications(&self) -> Option<BoxFuture<'_, Result<Box<dyn Notifications>>>> {
175 None
176 }
177}
178
179#[derive(Debug)]
180pub struct UniqueViolation {
181 pub index: String,
182}
183
184impl fmt::Display for UniqueViolation {
185 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(formatter, "unique index {} refused the write", self.index)
187 }
188}
189
190impl std::error::Error for UniqueViolation {}
191
192#[must_use]
193pub fn check_refusal(constraint: &str) -> String {
194 format!("a value breaks the rule `{constraint}`")
195}
196
197pub const REFERENCE_REFUSAL: &str =
199 "the write names a record that does not exist, or removes one another record still names";
200
201#[must_use]
202pub fn exclusion_refusal(constraint: &str) -> String {
203 format!("a value overlaps one another record holds, under the rule `{constraint}`")
204}
205
206#[derive(Clone, Copy, Debug, PartialEq, Eq)]
207pub enum Engine {
208 Postgres,
209 Sqlite,
210}
211
212pub trait Dialect: Send + Sync + std::any::Any {
214 fn push_placeholder(&self, sql: &mut String, index: usize);
216
217 fn push_quoted(&self, sql: &mut String, identifier: &str) {
218 sql.push('"');
219 for character in identifier.chars() {
220 if character == '"' {
221 sql.push('"');
222 }
223 sql.push(character);
224 }
225 sql.push('"');
226 }
227
228 fn placeholder(&self, index: usize) -> String {
229 let mut sql = String::new();
230 self.push_placeholder(&mut sql, index);
231 sql
232 }
233
234 fn quote(&self, identifier: &str) -> String {
235 let mut sql = String::with_capacity(identifier.len() + 2);
236 self.push_quoted(&mut sql, identifier);
237 sql
238 }
239
240 fn engine(&self) -> Engine {
242 Engine::Postgres
243 }
244
245 fn key_lock(&self) -> Option<&'static str> {
247 None
248 }
249
250 fn shared_key_lock(&self) -> Option<&'static str> {
251 None
252 }
253
254 fn qualified(&self, schema: &str, name: &str) -> String {
256 format!("{}.{}", self.quote(schema), self.quote(name))
257 }
258
259 fn bytes_in_json(&self, column: &str) -> String {
261 column.to_owned()
262 }
263
264 fn nested_rows(&self, columns: &str, from: &str) -> String {
265 panic!("this database cannot load child rows, so `{columns}` of `{from}` cannot be read")
266 }
267
268 fn binds_arrays(&self) -> bool {
269 false
270 }
271
272 fn migrations_setup(&self) -> &'static str;
273
274 fn migrations_lock(&self) -> &'static str;
275
276 fn enter_schema(&self, schema: &str) -> String;
277
278 fn drop_every_table(&self) -> Option<&'static str> {
280 None
281 }
282
283 #[cfg(feature = "search")]
285 fn text_search(&self, column: &str, words: &str) -> String {
286 let (column, words) = (self.lowercase(column), self.lowercase(words));
287 let escaped =
288 format!(r"replace(replace(replace({words}, '\', '\\'), '%', '\%'), '_', '\_')");
289 format!(r"({column} LIKE '%' || {escaped} || '%' ESCAPE '\')")
290 }
291
292 fn lowercase(&self, expression: &str) -> String {
293 format!("lower({expression})")
294 }
295
296 fn empty_order(&self, descending: bool) -> &'static str {
298 let _ = descending;
299 ""
300 }
301
302 #[cfg(feature = "module-roles")]
304 fn enter_role(&self, module: Option<&str>) -> Option<String> {
305 let _ = module;
306 None
307 }
308
309 fn framework_migrations(&self) -> Migrations;
310
311 fn messages(&self) -> &'static MessageSql;
312
313 #[cfg(feature = "shield")]
314 fn shield(&self) -> Option<&'static crate::ShieldSql> {
315 None
316 }
317
318 #[cfg(feature = "remote-contracts")]
319 fn feed(&self) -> Option<&'static crate::FeedSql> {
320 None
321 }
322
323 #[cfg(feature = "product-reports")]
324 fn product_events(&self) -> Option<&'static crate::ProductSql> {
325 None
326 }
327
328 #[cfg(feature = "live")]
330 fn live_notify(&self) -> Option<&'static str> {
331 None
332 }
333}
334
335pub trait MigrationsLock: Send {}
336
337pub trait Wakeups: Send {
338 fn next(&mut self) -> BoxFuture<'_, Result<()>>;
339}
340
341#[cfg(any(feature = "cache", feature = "live"))]
342pub trait Notifications: Send {
343 fn next(&mut self) -> BoxFuture<'_, Result<Vec<String>>>;
345}