1use crate::prelude::{Box, String, ToString, Vec, format};
4use compact_str::CompactString;
5use thiserror::Error;
6
7const MAX_CONTEXT_PARAMS: usize = 32;
8const MAX_CONTEXT_PARAM_CHARS: usize = 128;
9
10#[derive(Debug, Clone)]
12pub struct QueryContext {
13 pub sql: CompactString,
15 pub params: Box<[CompactString]>,
17 pub param_count: usize,
19}
20
21impl QueryContext {
22 pub fn new<V: core::fmt::Debug>(sql: &str, params: &[&V]) -> Self {
24 let rendered = params
25 .iter()
26 .take(MAX_CONTEXT_PARAMS)
27 .map(|param| truncate_param(format!("{param:?}")))
28 .collect::<Vec<_>>()
29 .into_boxed_slice();
30
31 Self {
32 sql: sql.into(),
33 params: rendered,
34 param_count: params.len(),
35 }
36 }
37
38 fn params_display(&self) -> String {
39 if self.param_count == 0 {
40 return "[]".to_string();
41 }
42
43 let mut rendered = String::from("[");
44 for (index, param) in self.params.iter().enumerate() {
45 if index > 0 {
46 rendered.push_str(", ");
47 }
48 rendered.push_str(param.as_str());
49 }
50 if self.param_count > self.params.len() {
51 if !self.params.is_empty() {
52 rendered.push_str(", ");
53 }
54 rendered.push_str("...");
55 rendered.push_str(&format!("(+{} more)", self.param_count - self.params.len()));
56 }
57 rendered.push(']');
58 rendered
59 }
60}
61
62fn truncate_param(mut value: String) -> CompactString {
63 if value.chars().count() <= MAX_CONTEXT_PARAM_CHARS {
64 return value.into();
65 }
66
67 let mut truncated = String::new();
68 for ch in value.drain(..).take(MAX_CONTEXT_PARAM_CHARS) {
69 truncated.push(ch);
70 }
71 truncated.push_str("...");
72 truncated.into()
73}
74
75#[derive(Debug, Error)]
77pub enum DrizzleError {
78 #[error("Execution error: {0}")]
80 ExecutionError(compact_str::CompactString),
81
82 #[error("Prepare error: {0}")]
84 PrepareError(compact_str::CompactString),
85
86 #[error("No rows found")]
88 NotFound,
89
90 #[error("Transaction error: {0}")]
92 TransactionError(compact_str::CompactString),
93
94 #[error("Mapping error: {0}")]
96 Mapping(compact_str::CompactString),
97
98 #[error("Statement error: {0}")]
100 Statement(compact_str::CompactString),
101
102 #[error("Query error: {0}")]
104 Query(CompactString),
105
106 #[error("{source}\n sql: {sql}\n params: {params}", sql = .ctx.sql, params = .ctx.params_display())]
108 QueryFailed {
109 ctx: Box<QueryContext>,
111 #[source]
113 source: Box<DrizzleError>,
114 },
115
116 #[error("Parameter conversion error: {0}")]
118 ParameterError(compact_str::CompactString),
119
120 #[error("Integer conversion error: {0}")]
122 TryFromInt(#[from] core::num::TryFromIntError),
123
124 #[error("Parse int error: {0}")]
126 ParseInt(#[from] core::num::ParseIntError),
127
128 #[error("Parse float error: {0}")]
130 ParseFloat(#[from] core::num::ParseFloatError),
131
132 #[error("Parse bool error: {0}")]
134 ParseBool(#[from] core::str::ParseBoolError),
135
136 #[error("Type conversion error: {0}")]
138 ConversionError(compact_str::CompactString),
139
140 #[error("Schema error: {0}")]
142 Schema(compact_str::CompactString),
143
144 #[error("Database error: {0}")]
146 Other(compact_str::CompactString),
147
148 #[cfg(feature = "rusqlite")]
150 #[error("Rusqlite error: {0}")]
151 Rusqlite(#[from] rusqlite::Error),
152
153 #[cfg(feature = "turso")]
155 #[error("Turso error: {0}")]
156 Turso(#[from] turso::Error),
157
158 #[cfg(feature = "libsql")]
160 #[error("LibSQL error: {0}")]
161 LibSQL(#[from] libsql::Error),
162
163 #[cfg(feature = "tokio-postgres")]
165 #[error("Postgres error: {0}")]
166 Postgres(#[from] tokio_postgres::Error),
167
168 #[cfg(all(feature = "postgres-sync", not(feature = "tokio-postgres")))]
169 #[error("Postgres error: {0}")]
170 Postgres(#[from] postgres::Error),
171
172 #[cfg(feature = "uuid")]
174 #[error("UUID error: {0}")]
175 UuidError(#[from] uuid::Error),
176
177 #[cfg(feature = "serde")]
179 #[error("JSON error: {0}")]
180 JsonError(#[from] serde_json::Error),
181
182 #[error("Infallible conversion error")]
184 Infallible(#[from] core::convert::Infallible),
185}
186
187pub type Result<T> = core::result::Result<T, DrizzleError>;
189
190pub trait ResultExt<T> {
192 fn with_query<F>(self, ctx: F) -> Result<T>
194 where
195 F: FnOnce() -> QueryContext;
196}
197
198impl<T, E> ResultExt<T> for core::result::Result<T, E>
199where
200 E: Into<DrizzleError>,
201{
202 fn with_query<F>(self, ctx: F) -> Result<T>
203 where
204 F: FnOnce() -> QueryContext,
205 {
206 self.map_err(|error| {
207 let source = error.into();
208 match source {
209 DrizzleError::QueryFailed { .. } => source,
210 other => DrizzleError::QueryFailed {
211 ctx: Box::new(ctx()),
212 source: Box::new(other),
213 },
214 }
215 })
216 }
217}