diff --git a/configuration.md b/configuration.md
index 77e6b322..c5911799 100644
--- a/configuration.md
+++ b/configuration.md
@@ -15,8 +15,8 @@ Here are the available configuration options and their default values:
| `unix_socket` | | Path to a UNIX socket to listen on instead of the TCP port. If specified, SQLPage will accept HTTP connections only on this socket and not on any TCP port. This option is mutually exclusive with `listen_on` and `port`.
| `host` | | The web address where your application is accessible (e.g., "myapp.example.com"). Used for login redirects with OIDC. |
| `max_database_pool_connections` | PostgreSQL: 50 MySql: 75 SQLite: 16 MSSQL: 100 | How many simultaneous database connections to open at most |
-| `database_connection_idle_timeout_seconds` | SQLite: None All other: 30 minutes | Automatically close database connections after this period of inactivity |
-| `database_connection_max_lifetime_seconds` | SQLite: None All other: 60 minutes | Always close database connections after this amount of time |
+| `database_connection_idle_timeout_seconds` | SQLite: None All other: 30 minutes | Automatically close database connections after this period of inactivity. Set to 0 to disable. |
+| `database_connection_max_lifetime_seconds` | SQLite: None All other: 60 minutes | Always close database connections after this amount of time. Set to 0 to disable. |
| `database_connection_retries` | 6 | Database connection attempts before giving up. Retries will happen every 5 seconds. |
| `database_connection_acquire_timeout_seconds` | 10 | How long to wait when acquiring a database connection from the pool before giving up and returning an error. |
| `sqlite_extensions` | | An array of SQLite extensions to load, such as `mod_spatialite` |
diff --git a/src/app_config.rs b/src/app_config.rs
index 5ebcab63..9e601ade 100644
--- a/src/app_config.rs
+++ b/src/app_config.rs
@@ -9,6 +9,7 @@ use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};
+use std::time::Duration;
#[cfg(not(feature = "lambda-web"))]
const DEFAULT_DATABASE_FILE: &str = "sqlpage.db";
@@ -73,6 +74,8 @@ impl AppConfig {
.validate()
.context("The provided configuration is invalid")?;
+ config.resolve_timeouts();
+
log::debug!("Loaded configuration: {config:#?}");
log::info!(
"Configuration loaded from {}",
@@ -82,6 +85,26 @@ impl AppConfig {
Ok(config)
}
+ fn resolve_timeouts(&mut self) {
+ let is_sqlite = self.database_url.starts_with("sqlite:");
+ self.database_connection_idle_timeout = resolve_timeout(
+ self.database_connection_idle_timeout,
+ if is_sqlite {
+ None
+ } else {
+ Some(Duration::from_secs(30 * 60))
+ },
+ );
+ self.database_connection_max_lifetime = resolve_timeout(
+ self.database_connection_max_lifetime,
+ if is_sqlite {
+ None
+ } else {
+ Some(Duration::from_secs(60 * 60))
+ },
+ );
+ }
+
fn validate(&self) -> anyhow::Result<()> {
if !self.web_root.is_dir() {
return Err(anyhow::anyhow!(
@@ -107,20 +130,6 @@ impl AppConfig {
));
}
}
- if let Some(idle_timeout) = self.database_connection_idle_timeout_seconds {
- if idle_timeout < 0.0 {
- return Err(anyhow::anyhow!(
- "Database connection idle timeout must be non-negative"
- ));
- }
- }
- if let Some(max_lifetime) = self.database_connection_max_lifetime_seconds {
- if max_lifetime < 0.0 {
- return Err(anyhow::anyhow!(
- "Database connection max lifetime must be non-negative"
- ));
- }
- }
anyhow::ensure!(self.max_pending_rows > 0, "max_pending_rows cannot be null");
Ok(())
}
@@ -146,8 +155,18 @@ pub struct AppConfig {
#[serde(default)]
pub database_password: Option,
pub max_database_pool_connections: Option,
- pub database_connection_idle_timeout_seconds: Option,
- pub database_connection_max_lifetime_seconds: Option,
+ #[serde(
+ default,
+ deserialize_with = "deserialize_duration_seconds",
+ rename = "database_connection_idle_timeout_seconds"
+ )]
+ pub database_connection_idle_timeout: Option,
+ #[serde(
+ default,
+ deserialize_with = "deserialize_duration_seconds",
+ rename = "database_connection_max_lifetime_seconds"
+ )]
+ pub database_connection_max_lifetime: Option,
#[serde(default)]
pub sqlite_extensions: Vec,
@@ -611,6 +630,26 @@ impl DevOrProd {
}
}
+fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result