Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pgdog/src/admin/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
39 changes: 34 additions & 5 deletions pgdog/src/admin/reset_prepared.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! RESET PREPARED.
use crate::config::config;
use crate::frontend::prepared_statements::PreparedStatements;

use super::prelude::*;
Expand All @@ -17,10 +16,40 @@ impl Command for ResetPrepared {
}

async fn execute(&self) -> Result<Vec<Message>, 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");
}
}
31 changes: 23 additions & 8 deletions pgdog/src/frontend/prepared_statements/global_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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::<Vec<_>>();

Expand Down Expand Up @@ -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();
Expand Down
Loading