Merge branch 'db-conn-init' of https://github.com/jjlin/vaultwarden into jjlin-db-conn-init

This commit is contained in:
Daniel García 2022-05-11 21:36:00 +02:00
commit 451ad47327
No known key found for this signature in database
GPG Key ID: FC8A7D14C3CD543A
3 changed files with 57 additions and 3 deletions

View File

@ -29,6 +29,15 @@
## Define the size of the connection pool used for connecting to the database. ## Define the size of the connection pool used for connecting to the database.
# DATABASE_MAX_CONNS=10 # DATABASE_MAX_CONNS=10
## Database connection initialization
## Allows SQL statements to be run whenever a new database connection is created.
## This is mainly useful for connection-scoped pragmas.
## If empty, a database-specific default is used:
## - SQLite: "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;"
## - MySQL: ""
## - PostgreSQL: ""
# DATABASE_CONN_INIT=""
## Individual folders, these override %DATA_FOLDER% ## Individual folders, these override %DATA_FOLDER%
# RSA_KEY_FILENAME=data/rsa_key # RSA_KEY_FILENAME=data/rsa_key
# ICON_CACHE_FOLDER=data/icon_cache # ICON_CACHE_FOLDER=data/icon_cache

View File

@ -515,11 +515,14 @@ make_config! {
db_connection_retries: u32, false, def, 15; db_connection_retries: u32, false, def, 15;
/// Timeout when aquiring database connection /// Timeout when aquiring database connection
database_timeout: u64, false, def, 30; database_timeout: u64, false, def, 30;
/// Database connection pool size /// Database connection pool size
database_max_conns: u32, false, def, 10; database_max_conns: u32, false, def, 10;
/// Database connection init |> SQL statements to run when creating a new database connection, mainly useful for connection-scoped pragmas. If empty, a database-specific default is used.
database_conn_init: String, false, def, "".to_string();
/// Bypass admin page security (Know the risks!) |> Disables the Admin Token for the admin page so you may use your own auth in-front /// Bypass admin page security (Know the risks!) |> Disables the Admin Token for the admin page so you may use your own auth in-front
disable_admin_token: bool, true, def, false; disable_admin_token: bool, true, def, false;

View File

@ -1,6 +1,10 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use diesel::{
connection::SimpleConnection,
r2d2::{ConnectionManager, CustomizeConnection, Pool, PooledConnection},
};
use rocket::{ use rocket::{
http::Status, http::Status,
outcome::IntoOutcome, outcome::IntoOutcome,
@ -62,6 +66,23 @@ macro_rules! generate_connections {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum DbConnInner { $( #[cfg($name)] $name(PooledConnection<ConnectionManager< $ty >>), )+ } pub enum DbConnInner { $( #[cfg($name)] $name(PooledConnection<ConnectionManager< $ty >>), )+ }
#[derive(Debug)]
pub struct DbConnOptions {
pub init_stmts: String,
}
$( // Based on <https://stackoverflow.com/a/57717533>.
#[cfg($name)]
impl CustomizeConnection<$ty, diesel::r2d2::Error> for DbConnOptions {
fn on_acquire(&self, conn: &mut $ty) -> Result<(), diesel::r2d2::Error> {
(|| {
if !self.init_stmts.is_empty() {
conn.batch_execute(&self.init_stmts)?;
}
Ok(())
})().map_err(diesel::r2d2::Error::QueryError)
}
})+
#[derive(Clone)] #[derive(Clone)]
pub struct DbPool { pub struct DbPool {
@ -103,7 +124,8 @@ macro_rules! generate_connections {
} }
impl DbPool { impl DbPool {
// For the given database URL, guess it's type, run migrations create pool and return it // For the given database URL, guess its type, run migrations, create pool, and return it
#[allow(clippy::diverging_sub_expression)]
pub fn from_config() -> Result<Self, Error> { pub fn from_config() -> Result<Self, Error> {
let url = CONFIG.database_url(); let url = CONFIG.database_url();
let conn_type = DbConnType::from_url(&url)?; let conn_type = DbConnType::from_url(&url)?;
@ -117,6 +139,9 @@ macro_rules! generate_connections {
let pool = Pool::builder() let pool = Pool::builder()
.max_size(CONFIG.database_max_conns()) .max_size(CONFIG.database_max_conns())
.connection_timeout(Duration::from_secs(CONFIG.database_timeout())) .connection_timeout(Duration::from_secs(CONFIG.database_timeout()))
.connection_customizer(Box::new(DbConnOptions{
init_stmts: conn_type.get_init_stmts()
}))
.build(manager) .build(manager)
.map_res("Failed to create pool")?; .map_res("Failed to create pool")?;
return Ok(DbPool { return Ok(DbPool {
@ -190,6 +215,23 @@ impl DbConnType {
err!("`DATABASE_URL` looks like a SQLite URL, but 'sqlite' feature is not enabled") err!("`DATABASE_URL` looks like a SQLite URL, but 'sqlite' feature is not enabled")
} }
} }
pub fn get_init_stmts(&self) -> String {
let init_stmts = CONFIG.database_conn_init();
if !init_stmts.is_empty() {
init_stmts
} else {
self.default_init_stmts()
}
}
pub fn default_init_stmts(&self) -> String {
match self {
Self::sqlite => "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;".to_string(),
Self::mysql => "".to_string(),
Self::postgresql => "".to_string(),
}
}
} }
#[macro_export] #[macro_export]