diff --git a/pgdog/src/admin/parser.rs b/pgdog/src/admin/parser.rs index 0b17000cf..15e004a97 100644 --- a/pgdog/src/admin/parser.rs +++ b/pgdog/src/admin/parser.rs @@ -255,6 +255,12 @@ mod tests { assert!(matches!(result, Ok(ParseResult::ResetQueryCache(_)))); } + #[test] + fn parses_reset_prepared_command() { + let result = Parser::parse("RESET PREPARED"); + assert!(matches!(result, Ok(ParseResult::ResetPrepared(_)))); + } + #[test] fn rejects_unknown_admin_command() { let result = Parser::parse("FOO BAR"); diff --git a/pgdog/src/admin/reset_prepared.rs b/pgdog/src/admin/reset_prepared.rs index a04707626..559b96f89 100644 --- a/pgdog/src/admin/reset_prepared.rs +++ b/pgdog/src/admin/reset_prepared.rs @@ -1,5 +1,4 @@ //! RESET PREPARED. -use crate::config::config; use crate::frontend::prepared_statements::PreparedStatements; use super::prelude::*; @@ -17,10 +16,40 @@ impl Command for ResetPrepared { } async fn execute(&self) -> Result, Error> { - let config = config(); - PreparedStatements::global() - .write() - .close_unused(config.config.general.prepared_statements_limit); + // Deliberately not the configured limit: RESET clears everything + // not in use regardless of it. + PreparedStatements::global().write().close_unused(0); Ok(vec![]) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::net::messages::Parse; + + #[tokio::test] + async fn reset_prepared_clears_released_statements() { + let held; + let released; + { + let global = PreparedStatements::global(); + let mut cache = global.write(); + held = cache.insert(&Parse::named("s", "SELECT 'rp_held'")).1; + released = cache.insert(&Parse::named("s", "SELECT 'rp_released'")).1; + cache.close(&released); + } + + // Default prepared_statements_limit is unlimited; + // the command must clear released statements anyway. + ResetPrepared.execute().await.unwrap(); + + let global = PreparedStatements::global(); + let cache = global.read(); + assert!( + cache.parse(&released).is_none(), + "released statement cleared" + ); + assert!(cache.parse(&held).is_some(), "statement in use survives"); + } +} diff --git a/pgdog/src/frontend/prepared_statements/global_cache.rs b/pgdog/src/frontend/prepared_statements/global_cache.rs index 50eed73ba..471ad4b03 100644 --- a/pgdog/src/frontend/prepared_statements/global_cache.rs +++ b/pgdog/src/frontend/prepared_statements/global_cache.rs @@ -232,7 +232,9 @@ impl GlobalCache { } } - /// Clear the global cache. + /// Clear the global cache. Test-only: rolling the name counter back + /// would reuse global statement names. + #[cfg(test)] pub fn reset(&mut self) { self.statements.clear(); self.names.clear(); @@ -310,14 +312,10 @@ impl GlobalCache { } } - /// Close all unused statements exceeding capacity. + /// Close unused statements until the cache is down to `capacity` entries; + /// `0` removes everything not in use. Statements in use stay, and global + /// names are never reused. pub fn close_unused(&mut self, capacity: usize) -> usize { - if capacity == 0 { - let removed = self.len(); - self.reset(); - return removed; - } - let over = self.len().saturating_sub(capacity); let remove = self.unused.iter().take(over).copied().collect::>(); @@ -362,6 +360,23 @@ impl GlobalCache { mod test { use super::*; + #[test] + fn test_close_unused_zero_keeps_in_use_and_counter() { + let mut cache = GlobalCache::default(); + + let (_, held) = cache.insert(&Parse::named("s", "SELECT 'held'")); + let (_, released) = cache.insert(&Parse::named("s", "SELECT 'released'")); + cache.close(&released); + + assert_eq!(cache.close_unused(0), 1, "only the released statement goes"); + assert!(cache.parse(&held).is_some(), "statements in use survive"); + assert!(cache.parse(&released).is_none()); + + // A reused name could hand a server connection a different query. + let (_, next) = cache.insert(&Parse::named("s", "SELECT 'next'")); + assert_eq!(next, "__pgdog_3", "global names are never reused"); + } + #[test] fn test_prep_stmt_cache_close() { let mut cache = GlobalCache::default();