1pub mod base64;
9pub mod builder;
10pub mod config;
11pub mod connections;
12pub mod credentials;
13pub mod dialect;
14pub mod driver;
15pub mod migration;
16pub mod model;
17pub mod mysql;
18pub mod pagination;
19pub mod pool;
20pub mod postgres;
21pub mod random;
22pub mod row;
23pub mod schema;
24pub mod sqlserver;
25pub mod tls;
26pub mod value;
27
28pub use builder::{Direction, QueryBuilder};
29pub use config::DatabaseConfig;
30pub use dialect::{ColumnType, Dialect, ReturningStyle};
31pub use driver::{Driver, DriverConnection, QueryResult};
32pub use connections::{Connections, DEFAULT_BUDGET};
33pub use migration::{Faker, Migration, Migrator, Seeder};
34pub use model::{Model, ModelExt, belongs_to, has_many};
35pub use mysql::{MySqlConnection, MySqlDriver};
36pub use pagination::{CursorPage, Page};
37pub use postgres::connection::{log_bindings, set_log_bindings};
38pub use pool::{Pool, PooledConnection};
39pub use schema::{Schema, Table};
40pub use sqlserver::{SqlServerConnection, SqlServerDriver};
41pub use row::{Row, rows_to_json};
42pub use value::{FromValue, Value};
43
44pub use rustlavel_core::{Error, Result};
45use std::sync::Arc;
46
47fn driver_for(config: DatabaseConfig) -> Result<Arc<dyn Driver>> {
52 match config.driver.as_str() {
53 "postgres" => Ok(Arc::new(postgres::PostgresDriver::new(config))),
54 "mysql" => Ok(Arc::new(mysql::MySqlDriver::new(config))),
55 "sqlserver" => Ok(Arc::new(sqlserver::SqlServerDriver::new(config))),
56 other => Err(Error::msg(format!(
57 "the `{other}` driver is not available in this build. \
58 Point DATABASE_URL at a database this build supports."
59 ))),
60 }
61}
62
63pub use rustlavel_macros::Model;
65
66pub mod prelude {
68 pub use crate::connections::Connections;
69 pub use crate::migration::{Faker, Migrator, Seeder};
70 pub use crate::model::{ModelExt, belongs_to, has_many};
71 pub use crate::schema::{Schema, Table};
72 pub use crate::{CursorPage, Database, Model, Page, QueryBuilder, Row, Value};
73 pub use rustlavel_core::{Error, Json, Result};
74}
75
76#[derive(Clone)]
81pub struct Database {
82 pool: Pool,
83 dialect: Arc<dyn Dialect>,
84}
85
86impl Database {
87 pub async fn connect(url: &str) -> Result<Database> {
89 Database::with_config(DatabaseConfig::from_url(url)?).await
90 }
91
92 pub async fn with_config(config: DatabaseConfig) -> Result<Database> {
94 let database = Database::lazy(config)?;
95 database.pool.verify().await?;
96 Ok(database)
97 }
98
99 pub fn lazy(config: DatabaseConfig) -> Result<Database> {
102 Ok(Database::with_driver(driver_for(config)?))
103 }
104
105 pub fn with_driver(driver: Arc<dyn Driver>) -> Database {
108 let dialect = driver.dialect();
109 Database { pool: Pool::new(driver), dialect }
110 }
111
112 pub fn dialect(&self) -> &dyn Dialect {
117 self.dialect.as_ref()
118 }
119
120 pub fn pool(&self) -> &Pool {
121 &self.pool
122 }
123
124 pub fn table(&self, name: &str) -> QueryBuilder {
126 QueryBuilder::new(name)
127 }
128
129 pub async fn select(&self, sql: &str, params: &[Value]) -> Result<Vec<Row>> {
131 let mut connection = self.pool.acquire().await?;
132 Ok(connection.query(sql, params).await?.rows)
133 }
134
135 pub async fn select_one(&self, sql: &str, params: &[Value]) -> Result<Option<Row>> {
137 Ok(self.select(sql, params).await?.into_iter().next())
138 }
139
140 pub async fn execute(&self, sql: &str, params: &[Value]) -> Result<u64> {
142 let mut connection = self.pool.acquire().await?;
143 Ok(connection.query(sql, params).await?.affected)
144 }
145
146 pub async fn run(&self, sql: &str) -> Result<u64> {
148 let mut connection = self.pool.acquire().await?;
149 Ok(connection.simple_query(sql).await?.affected)
150 }
151
152 pub async fn insert_returning_key(
158 &self,
159 sql: &str,
160 params: &[Value],
161 column: &str,
162 ) -> Result<Option<Value>> {
163 let mut connection = self.pool.acquire().await?;
164 let result = connection.query(sql, params).await?;
165
166 if let Some(row) = result.rows.first() {
167 return Ok(Some(row.value(column).or_else(|_| row.value_at(0))?.clone()));
170 }
171 Ok(result.last_insert_id.map(Value::Int))
172 }
173
174 pub async fn scalar<T: FromValue>(&self, sql: &str, params: &[Value]) -> Result<Option<T>> {
176 match self.select_one(sql, params).await? {
177 Some(row) => row.get_at::<T>(0).map(Some),
178 None => Ok(None),
179 }
180 }
181
182 pub async fn begin(&self) -> Result<Transaction> {
197 let mut connection = self.pool.acquire().await?;
198 connection.simple_query(self.dialect.begin_sql()).await?;
199 Ok(Transaction {
200 connection: Some(connection),
201 dialect: Arc::clone(&self.dialect),
202 finished: false,
203 })
204 }
205
206 pub async fn close(&self) {
208 self.pool.close().await;
209 }
210}
211
212pub struct Transaction {
214 connection: Option<PooledConnection>,
215 dialect: Arc<dyn Dialect>,
217 finished: bool,
218}
219
220impl Transaction {
221 fn connection(&mut self) -> Result<&mut PooledConnection> {
222 self.connection
223 .as_mut()
224 .ok_or_else(|| Error::msg("this transaction has already finished"))
225 }
226
227 pub fn dialect(&self) -> &dyn Dialect {
230 self.dialect.as_ref()
231 }
232
233 pub async fn select(&mut self, sql: &str, params: &[Value]) -> Result<Vec<Row>> {
234 Ok(self.connection()?.query(sql, params).await?.rows)
235 }
236
237 pub async fn select_one(&mut self, sql: &str, params: &[Value]) -> Result<Option<Row>> {
238 Ok(self.select(sql, params).await?.into_iter().next())
239 }
240
241 pub async fn execute(&mut self, sql: &str, params: &[Value]) -> Result<u64> {
242 Ok(self.connection()?.query(sql, params).await?.affected)
243 }
244
245 pub async fn run(&mut self, sql: &str) -> Result<u64> {
246 Ok(self.connection()?.simple_query(sql).await?.affected)
247 }
248
249 pub async fn scalar<T: FromValue>(&mut self, sql: &str, params: &[Value]) -> Result<Option<T>> {
250 match self.select_one(sql, params).await? {
251 Some(row) => row.get_at::<T>(0).map(Some),
252 None => Ok(None),
253 }
254 }
255
256 pub async fn savepoint(&mut self, name: &str) -> Result<()> {
258 validate_identifier(name)?;
259 let sql = self.dialect.savepoint_sql(name);
260 self.connection()?.simple_query(&sql).await?;
261 Ok(())
262 }
263
264 pub async fn rollback_to(&mut self, name: &str) -> Result<()> {
265 validate_identifier(name)?;
266 let sql = self.dialect.rollback_to_savepoint_sql(name);
267 self.connection()?.simple_query(&sql).await?;
268 Ok(())
269 }
270
271 pub async fn commit(mut self) -> Result<()> {
273 let sql = self.dialect.commit_sql();
274 self.connection()?.simple_query(sql).await?;
275 self.finished = true;
276 Ok(())
277 }
278
279 pub async fn rollback(mut self) -> Result<()> {
281 let sql = self.dialect.rollback_sql();
282 self.connection()?.simple_query(sql).await?;
283 self.finished = true;
284 Ok(())
285 }
286}
287
288impl Drop for Transaction {
289 fn drop(&mut self) {
290 if self.finished {
291 return;
292 }
293 if let Some(mut connection) = self.connection.take() {
297 let sql = self.dialect.rollback_sql();
298 tokio::spawn(async move {
299 let _ = connection.simple_query(sql).await;
300 });
301 }
302 }
303}
304
305pub fn validate_identifier(name: &str) -> Result<()> {
310 let valid = !name.is_empty()
311 && name.len() <= 63
312 && name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
313 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
314
315 if valid {
316 Ok(())
317 } else {
318 Err(Error::msg(format!(
319 "`{name}` is not a valid SQL identifier. Identifiers may contain letters, digits and \
320 underscores, and must not start with a digit."
321 )))
322 }
323}
324
325pub fn quote_identifier(name: &str) -> Result<String> {
327 let quoted: Result<Vec<String>> = name
329 .split('.')
330 .map(|part| validate_identifier(part).map(|_| format!("\"{part}\"")))
331 .collect();
332 Ok(quoted?.join("."))
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn accepts_ordinary_identifiers() {
341 for name in ["users", "user_profiles", "_private", "t1"] {
342 assert!(validate_identifier(name).is_ok(), "{name} should be valid");
343 }
344 }
345
346 #[test]
347 fn rejects_anything_that_could_alter_a_statement() {
348 for name in ["users; drop table users", "user\"s", "1abc", "", "a b", "users--"] {
349 assert!(validate_identifier(name).is_err(), "{name:?} should be rejected");
350 }
351 }
352
353 #[test]
354 fn quotes_qualified_names_part_by_part() {
355 assert_eq!(quote_identifier("public.users").unwrap(), "\"public\".\"users\"");
356 assert!(quote_identifier("public.users; drop table x").is_err());
357 }
358}