Skip to content

Commit 93a95b6

Browse files
committed
feat: support readable time values in config
1 parent 9b27309 commit 93a95b6

13 files changed

Lines changed: 836 additions & 322 deletions

File tree

.schema/pgdog.schema.json

Lines changed: 173 additions & 203 deletions
Large diffs are not rendered by default.

.schema/users.schema.json

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,21 @@
9898
}
9999
]
100100
},
101+
"TimeValue": {
102+
"description": "Time value: a number, in the unit documented on the field, or a duration\nstring built from `ms`, `s`, `m`, `h` and `d` components, e.g. `\"1h5m15s\"`.",
103+
"anyOf": [
104+
{
105+
"description": "Number of milliseconds, or seconds for fields documented in seconds.",
106+
"type": "integer",
107+
"format": "uint64",
108+
"minimum": 0
109+
},
110+
{
111+
"description": "Duration string, e.g. `\"250ms\"`, `\"5s\"`, `\"1h5m15s\"`.",
112+
"type": "string"
113+
}
114+
]
115+
},
101116
"User": {
102117
"description": "User allowed to connect to pgDog.\nA user entry in `users.toml`, controlling which users are allowed to connect to PgDog.\n\n<https://docs.pgdog.dev/configuration/users.toml/users/>",
103118
"type": "object",
@@ -136,21 +151,27 @@
136151
},
137152
"idle_timeout": {
138153
"description": "Overrides [`idle_timeout`](https://docs.pgdog.dev/configuration/pgdog.toml/general/#idle_timeout) for this user. Server connections that have been idle for this long, without affecting [`min_pool_size`](https://docs.pgdog.dev/configuration/pgdog.toml/general/#min_pool_size), will be closed.\n\n<https://docs.pgdog.dev/configuration/users.toml/users/#idle_timeout>",
139-
"type": [
140-
"integer",
141-
"null"
154+
"anyOf": [
155+
{
156+
"$ref": "#/$defs/TimeValue"
157+
},
158+
{
159+
"type": "null"
160+
}
142161
],
143-
"format": "uint64",
144-
"minimum": 0
162+
"default": null
145163
},
146164
"lock_timeout": {
147165
"description": "Lock timeout.\n\nSets the `lock_timeout` on all server connections at connection creation.\nAborts any statement that waits longer than the specified duration to acquire a lock.\nUnlike `statement_timeout`, this only counts time spent waiting for locks, not execution time.\nRecommended for replication destination connections to prevent cross-shard deadlocks\nfrom hanging indefinitely.\n\n**Note:** Nothing is preventing the user from manually changing this setting at runtime,\ne.g., by running `SET lock_timeout TO 0`;\n\n<https://docs.pgdog.dev/configuration/users.toml/users/#lock_timeout>",
148-
"type": [
149-
"integer",
150-
"null"
166+
"anyOf": [
167+
{
168+
"$ref": "#/$defs/TimeValue"
169+
},
170+
{
171+
"type": "null"
172+
}
151173
],
152-
"format": "uint64",
153-
"minimum": 0
174+
"default": null
154175
},
155176
"min_pool_size": {
156177
"description": "Overrides [`min_pool_size`](https://docs.pgdog.dev/configuration/pgdog.toml/general/#min_pool_size) for this user. Opens at least this many connections on pooler startup and keeps them open despite [`idle_timeout`](https://docs.pgdog.dev/configuration/pgdog.toml/general/#idle_timeout).\n\n<https://docs.pgdog.dev/configuration/users.toml/users/#min_pool_size>",
@@ -245,21 +266,27 @@
245266
},
246267
"server_lifetime": {
247268
"description": "Server connections older than this (in milliseconds) will be closed when returned to the pool.",
248-
"type": [
249-
"integer",
250-
"null"
269+
"anyOf": [
270+
{
271+
"$ref": "#/$defs/TimeValue"
272+
},
273+
{
274+
"type": "null"
275+
}
251276
],
252-
"format": "uint64",
253-
"minimum": 0
277+
"default": null
254278
},
255279
"server_lifetime_jitter": {
256280
"description": "Maximum random adjustment applied to `server_lifetime` per backend connection (milliseconds).\nOverrides the database-level and general-level `server_lifetime_jitter` setting for this user.",
257-
"type": [
258-
"integer",
259-
"null"
281+
"anyOf": [
282+
{
283+
"$ref": "#/$defs/TimeValue"
284+
},
285+
{
286+
"type": "null"
287+
}
260288
],
261-
"format": "uint64",
262-
"minimum": 0
289+
"default": null
263290
},
264291
"server_password": {
265292
"description": "Which password to connect with when creating backend connections from PgDog to PostgreSQL. By default, the password configured in `password` is used. This setting allows you to override this configuration and use a different password, decoupling server passwords from user passwords given to clients.\n\n**Note:** Values specified in `pgdog.toml` take priority over this configuration.\n\n<https://docs.pgdog.dev/configuration/users.toml/users/#server_password>",
@@ -284,12 +311,15 @@
284311
},
285312
"statement_timeout": {
286313
"description": "Statement timeout.\n\nSets the `statement_timeout` on all server connections at connection creation. This allows you to set a reasonable default for each user without modifying `postgresql.conf` or using `ALTER USER`.\n\n**Note:** Nothing is preventing the user from manually changing this setting at runtime, e.g., by running `SET statement_timeout TO 0`;\n\n<https://docs.pgdog.dev/configuration/users.toml/users/#statement_timeout>",
287-
"type": [
288-
"integer",
289-
"null"
314+
"anyOf": [
315+
{
316+
"$ref": "#/$defs/TimeValue"
317+
},
318+
{
319+
"type": "null"
320+
}
290321
],
291-
"format": "uint64",
292-
"minimum": 0
322+
"default": null
293323
},
294324
"tls_client_certificate_required": {
295325
"description": "Require this user to present a client TLS certificate. Defaults to `true`.\n\nOnly enforced when `tls_client_ca_certificate` is configured and the client\nconnected over TLS. Set to `false` to let password and mTLS users share a listener.",

example.pgdog.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#
55
# Most settings have reasonable defaults.
66
#
7+
# Time settings accept a number, in the unit documented for the setting, or a
8+
# duration string built from "ms", "s", "m", "h" and "d", e.g. "5s", "1h5m15s".
9+
#
710

811
# General settings.
912
#

pgdog-config/src/core.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,106 @@ mod tests {
700700
use std::time::Duration;
701701
use tempfile::NamedTempFile;
702702

703+
#[test]
704+
fn test_human_durations() {
705+
let source = r#"
706+
[general]
707+
ban_timeout = "5m"
708+
checkout_timeout = "2s500ms"
709+
query_timeout = "1h5m15s"
710+
shutdown_termination_timeout = "30s"
711+
idle_timeout = 60000
712+
two_phase_commit_wal_checkpoint_interval = "2m"
713+
714+
[[databases]]
715+
name = "production"
716+
host = "127.0.0.1"
717+
database_name = "postgres"
718+
statement_timeout = "1m30s"
719+
720+
[tcp]
721+
time = "1s"
722+
interval = "500ms"
723+
user_timeout = 1000
724+
725+
[replica_lag]
726+
check_interval = "1s"
727+
max_age = "25ms"
728+
729+
[otel]
730+
push_interval = "10s"
731+
732+
[vault]
733+
url = "http://127.0.0.1:8200"
734+
auth_method = "kubernetes"
735+
client_token_ttl = "5m"
736+
"#;
737+
738+
let config: Config = toml::from_str(source).unwrap();
739+
let general = &config.general;
740+
741+
assert_eq!(general.ban_timeout, 300_000);
742+
assert_eq!(general.checkout_timeout, 2_500);
743+
assert_eq!(general.query_timeout, 3_915_000);
744+
assert_eq!(general.shutdown_termination_timeout, Some(30_000));
745+
assert_eq!(general.idle_timeout, 60_000);
746+
assert_eq!(general.two_phase_commit_wal_checkpoint_interval, 120_000);
747+
748+
assert_eq!(config.databases[0].statement_timeout, Some(90_000));
749+
assert_eq!(config.tcp.time(), Some(Duration::from_secs(1)));
750+
assert_eq!(config.tcp.interval(), Some(Duration::from_millis(500)));
751+
assert_eq!(config.tcp.user_timeout(), Some(Duration::from_secs(1)));
752+
753+
let replica_lag = config.replica_lag.unwrap();
754+
assert_eq!(replica_lag.check_interval, Duration::from_secs(1));
755+
assert_eq!(replica_lag.max_age, Duration::from_millis(25));
756+
757+
assert_eq!(config.otel.push_interval, 10_000);
758+
assert_eq!(config.vault.unwrap().client_token_ttl, Some(300));
759+
}
760+
761+
#[test]
762+
fn test_human_durations_users() {
763+
let source = r#"
764+
[[users]]
765+
name = "alice"
766+
database = "production"
767+
statement_timeout = "10s"
768+
idle_timeout = "1h"
769+
server_lifetime = "1d"
770+
server_lifetime_jitter = 5000
771+
"#;
772+
773+
let users: Users = toml::from_str(source).unwrap();
774+
let user = &users.users[0];
775+
776+
assert_eq!(user.statement_timeout, Some(10_000));
777+
assert_eq!(user.idle_timeout, Some(3_600_000));
778+
assert_eq!(user.server_lifetime, Some(86_400_000));
779+
assert_eq!(user.server_lifetime_jitter, Some(5_000));
780+
}
781+
782+
#[test]
783+
fn test_human_durations_invalid() {
784+
let source = r#"
785+
[general]
786+
ban_timeout = "5 minutes"
787+
"#;
788+
789+
let err = toml::from_str::<Config>(source).unwrap_err().to_string();
790+
assert!(err.contains("is not a valid duration"), "{}", err);
791+
792+
let source = r#"
793+
[vault]
794+
url = "http://127.0.0.1:8200"
795+
auth_method = "kubernetes"
796+
client_token_ttl = "1500ms"
797+
"#;
798+
799+
let err = toml::from_str::<Config>(source).unwrap_err().to_string();
800+
assert!(err.contains("whole number of seconds"), "{}", err);
801+
}
802+
703803
#[test]
704804
fn test_basic() {
705805
let pgdog_source = r#"

pgdog-config/src/database.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,22 @@ pub struct Database {
178178
/// This setting configures the `statement_timeout` connection parameter on all connections to Postgres for this database.
179179
///
180180
/// <https://docs.pgdog.dev/configuration/pgdog.toml/databases/#statement_timeout>
181+
#[serde(default, deserialize_with = "crate::duration::millis_optional")]
182+
#[schemars(with = "Option<crate::duration::TimeValue>")]
181183
pub statement_timeout: Option<u64>,
182184
/// This setting configures the `lock_timeout` connection parameter on all connections to Postgres for this database.
183185
/// Aborts any statement that waits longer than the specified duration to acquire a lock.
184186
/// Unlike `statement_timeout`, this only counts time spent waiting for locks, not execution time.
185187
///
186188
/// <https://docs.pgdog.dev/configuration/pgdog.toml/databases/#lock_timeout>
189+
#[serde(default, deserialize_with = "crate::duration::millis_optional")]
190+
#[schemars(with = "Option<crate::duration::TimeValue>")]
187191
pub lock_timeout: Option<u64>,
188192
/// Overrides the `idle_timeout` setting. Idle server connections exceeding this timeout will be closed automatically.
189193
///
190194
/// <https://docs.pgdog.dev/configuration/pgdog.toml/databases/#idle_timeout>
195+
#[serde(default, deserialize_with = "crate::duration::millis_optional")]
196+
#[schemars(with = "Option<crate::duration::TimeValue>")]
191197
pub idle_timeout: Option<u64>,
192198
/// Sets the `default_transaction_read_only` connection parameter to `on` on all server connections to this database. Clients can still override it with `SET`.
193199
///
@@ -196,10 +202,14 @@ pub struct Database {
196202
/// Overrides the `server_lifetime` setting. Server connections older than this will be closed when returned to the pool.
197203
///
198204
/// <https://docs.pgdog.dev/configuration/pgdog.toml/databases/#server_lifetime>
205+
#[serde(default, deserialize_with = "crate::duration::millis_optional")]
206+
#[schemars(with = "Option<crate::duration::TimeValue>")]
199207
pub server_lifetime: Option<u64>,
200208
/// Overrides the `server_lifetime_jitter` setting for this database.
201209
///
202210
/// <https://docs.pgdog.dev/configuration/pgdog.toml/databases/#server_lifetime_jitter>
211+
#[serde(default, deserialize_with = "crate::duration::millis_optional")]
212+
#[schemars(with = "Option<crate::duration::TimeValue>")]
203213
pub server_lifetime_jitter: Option<u64>,
204214
/// Used for resharding only; this database will not serve regular traffic.
205215
#[serde(default)]

0 commit comments

Comments
 (0)