1use std::sync::Arc;
4
5use rorm_declaration::config::DatabaseDriver;
6use rorm_sql::conditional;
7use rorm_sql::delete::Delete;
8use rorm_sql::insert::Insert;
9use rorm_sql::join_table::JoinTableData;
10use rorm_sql::ordering::OrderByEntry;
11#[cfg(feature = "postgres-only")]
12use rorm_sql::select::LockingClause;
13use rorm_sql::select_column::SelectColumnData;
14use rorm_sql::update::Update;
15use rorm_sql::value::Value;
16use tracing::warn;
17
18use crate::error::Error;
19use crate::executor::AffectedRows;
20use crate::executor::All;
21use crate::executor::Executor;
22use crate::executor::Nothing;
23use crate::executor::One;
24use crate::executor::QueryStrategy;
25use crate::internal::any::AnyPool;
26use crate::query_type::GetLimitClause;
27use crate::row::Row;
28use crate::transaction::Transaction;
29use crate::transaction::TransactionError;
30
31pub type ColumnSelector<'a> = SelectColumnData<'a>;
37
38pub type JoinTable<'until_build, 'post_build> = JoinTableData<'until_build, 'post_build>;
44
45#[derive(Debug)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct DatabaseConfiguration {
49 pub driver: DatabaseDriver,
51
52 pub min_connections: u32,
56
57 pub max_connections: u32,
61}
62
63impl DatabaseConfiguration {
64 pub fn new(driver: DatabaseDriver) -> Self {
75 DatabaseConfiguration {
76 driver,
77 min_connections: 1,
78 max_connections: 10,
79 }
80 }
81}
82
83#[derive(Clone)]
92pub struct Database(pub(crate) AnyPool, Arc<()>);
93
94impl Database {
95 pub async fn connect(configuration: DatabaseConfiguration) -> Result<Self, Error> {
97 Ok(Self(AnyPool::connect(configuration).await?, Arc::new(())))
98 }
99
100 pub async fn start_transaction(&self) -> Result<Transaction, Error> {
106 Ok(Transaction::new(self.0.begin().await?))
107 }
108
109 pub async fn close(self) {
119 self.0.close().await;
120 }
121}
122
123#[cfg(feature = "postgres-only")]
124impl Database {
125 pub fn as_pool(&self) -> &sqlx::postgres::PgPool {
130 match &self.0 {
131 AnyPool::Postgres(x) => x,
132 }
133 }
134}
135
136impl Drop for Database {
137 fn drop(&mut self) {
139 if Arc::strong_count(&self.1) == 1 && !self.0.is_closed() {
143 warn!("Database has been dropped without calling close. This might cause the last queries to not be flushed properly");
144 }
145 }
146}
147
148#[allow(clippy::too_many_arguments)] pub fn query<'exe, 'data, Q: QueryStrategy + GetLimitClause>(
164 executor: impl Executor<'exe>,
165 model: &str,
166 columns: &[ColumnSelector<'data>],
167 joins: &[JoinTable<'data, 'data>],
168 conditions: Option<&conditional::Condition<'data>>,
169 order_by_clause: &[OrderByEntry<'_>],
170 limit: Option<Q::LimitOrOffset>,
171 distinct: bool,
172 #[cfg(feature = "postgres-only")] locking_clause: Option<LockingClause>,
173) -> Q::Result<'exe> {
174 let columns: Vec<_> = columns
175 .iter()
176 .map(|c| {
177 executor.dialect().select_column(
178 c.table_name,
179 c.column_name,
180 c.select_alias,
181 c.aggregation,
182 )
183 })
184 .collect();
185 let joins: Vec<_> = joins
186 .iter()
187 .map(|j| {
188 executor.dialect().join_table(
189 j.join_type,
190 j.table_name,
191 j.join_alias,
192 j.join_condition.clone(),
193 )
194 })
195 .collect();
196 let mut q = executor
197 .dialect()
198 .select(&columns, model, &joins, order_by_clause);
199
200 if let Some(condition) = conditions {
201 q = q.where_clause(condition);
202 }
203
204 if let Some(limit) = Q::get_limit_clause(limit) {
205 q = q.limit_clause(limit);
206 }
207
208 if distinct {
209 q = q.distinct();
210 }
211
212 #[cfg(feature = "postgres-only")]
213 if let Some(x) = locking_clause {
214 q = q.locking_clause(x);
215 }
216
217 let (query_string, bind_params) = q.build();
218
219 executor.execute::<Q>(query_string, bind_params)
220}
221
222pub async fn insert_returning(
230 executor: impl Executor<'_>,
231 model: &str,
232 columns: &[&str],
233 values: &[Value<'_>],
234 returning: &[&str],
235) -> Result<Row, Error> {
236 generic_insert::<One>(executor, model, columns, values, Some(returning)).await
237}
238
239pub async fn insert(
246 executor: impl Executor<'_>,
247 model: &str,
248 columns: &[&str],
249 values: &[Value<'_>],
250) -> Result<(), Error> {
251 generic_insert::<Nothing>(executor, model, columns, values, None).await
252}
253
254pub(crate) fn generic_insert<'exe, Q: QueryStrategy>(
258 executor: impl Executor<'exe>,
259 model: &str,
260 columns: &[&str],
261 values: &[Value<'_>],
262 returning: Option<&[&str]>,
263) -> Q::Result<'exe> {
264 let values = &[values];
265 let q = executor.dialect().insert(model, columns, values, returning);
266
267 let (query_string, bind_params) = q.build();
268
269 executor.execute::<Q>(query_string, bind_params)
270}
271
272pub async fn insert_bulk(
282 executor: impl Executor<'_>,
283 model: &str,
284 columns: &[&str],
285 rows: &[&[Value<'_>]],
286) -> Result<(), Error> {
287 let mut tr = executor.ensure_transaction().await?;
288
289 for chunk in rows.chunks(25) {
290 let mut insert = tr.dialect().insert(model, columns, chunk, None);
291 insert = insert.rollback_transaction();
292 let (insert_query, insert_params) = insert.build();
293
294 tr.execute::<Nothing>(insert_query, insert_params).await?;
295 }
296
297 tr.commit_if_owned().await.map_err(|x| match x {
298 TransactionError::Database(x) => x,
299 TransactionError::Hook(_) => {
300 unreachable!("Potentially create transaction does not use hooks")
301 }
302 })?;
303
304 Ok(())
305}
306
307pub async fn insert_bulk_returning(
317 executor: impl Executor<'_>,
318 model: &str,
319 columns: &[&str],
320 rows: &[&[Value<'_>]],
321 returning: &[&str],
322) -> Result<Vec<Row>, Error> {
323 let mut tr = executor.ensure_transaction().await?;
324
325 let mut inserted = Vec::with_capacity(rows.len());
326 for chunk in rows.chunks(25) {
327 let mut insert = tr.dialect().insert(model, columns, chunk, Some(returning));
328 insert = insert.rollback_transaction();
329 let (insert_query, insert_params) = insert.build();
330
331 inserted.extend(tr.execute::<All>(insert_query, insert_params).await?);
332 }
333
334 tr.commit_if_owned().await.map_err(|x| match x {
335 TransactionError::Database(x) => x,
336 TransactionError::Hook(_) => {
337 unreachable!("Potentially create transaction does not use hooks")
338 }
339 })?;
340
341 Ok(inserted)
342}
343
344pub async fn delete<'post_build>(
354 executor: impl Executor<'_>,
355 model: &str,
356 condition: Option<&conditional::Condition<'post_build>>,
357) -> Result<u64, Error> {
358 let mut q = executor.dialect().delete(model);
359 if let Some(condition) = condition {
360 q = q.where_clause(condition);
361 }
362
363 let (query_string, bind_params) = q.build();
364
365 executor
366 .execute::<AffectedRows>(query_string, bind_params)
367 .await
368}
369
370pub async fn update<'post_build>(
382 executor: impl Executor<'_>,
383 model: &str,
384 updates: &[(&str, Value<'post_build>)],
385 condition: Option<&conditional::Condition<'post_build>>,
386) -> Result<u64, Error> {
387 let mut stmt = executor.dialect().update(model);
388
389 for (column, value) in updates {
390 stmt = stmt.add_update(column, *value);
391 }
392
393 if let Some(cond) = condition {
394 stmt = stmt.where_clause(cond);
395 }
396
397 let (query_string, bind_params) = stmt.build()?;
398
399 executor
400 .execute::<AffectedRows>(query_string, bind_params)
401 .await
402}