use crate::timing::elapsed_nanos;
use crate::{prepare_postgres_sql, PreparedPostgresStatement, SqlStatement};
use hawdb_core::time::Instant;
use hawdb_core::Result;
use hawdb_plan_cache::{LfuCache, PlanCacheStats};
use std::sync::{Arc, Mutex, MutexGuard};
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct PreparedRelationalSql {
pub template: Arc<PreparedPostgresStatement>,
pub parse_nanos: u64,
}
impl PreparedRelationalSql {
pub fn statement(&self) -> &SqlStatement {
&self.template.statement
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct RelationalPlanTemplateCache {
entries: Mutex<LfuCache<String, Arc<PreparedPostgresStatement>>>,
}
impl RelationalPlanTemplateCache {
pub fn new(max_entries: Option<usize>) -> Self {
Self {
entries: Mutex::new(LfuCache::new(max_entries)),
}
}
pub fn prepare(&self, sql: &str) -> Result<PreparedRelationalSql> {
let key = sql.to_string();
if let Some(template) = self.entries().get(&key) {
return Ok(PreparedRelationalSql {
template,
parse_nanos: 0,
});
}
let started = Instant::now();
let template = Arc::new(prepare_postgres_sql(sql)?);
let parse_nanos = elapsed_nanos(started);
let mut entries = self.entries();
if is_cacheable_query(&template.statement) {
entries.insert(key, Arc::clone(&template));
} else {
entries.record_bypass();
}
Ok(PreparedRelationalSql {
template,
parse_nanos,
})
}
pub fn stats(&self) -> PlanCacheStats {
self.entries().stats()
}
fn entries(&self) -> MutexGuard<'_, LfuCache<String, Arc<PreparedPostgresStatement>>> {
self.entries
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
fn is_cacheable_query(statement: &SqlStatement) -> bool {
match statement {
SqlStatement::Select(_) | SqlStatement::Branch(_) => true,
SqlStatement::Explain(explain) => {
matches!(explain.statement.as_ref(), SqlStatement::Select(_))
}
SqlStatement::Insert(_)
| SqlStatement::Update(_)
| SqlStatement::Delete(_)
| SqlStatement::CreateTable(_)
| SqlStatement::CreateIndex(_)
| SqlStatement::AlterTableAddColumn(_) => false,
}
}