use std::collections::BTreeSet;
use uqa_sql::prepared::{dependencies::PreparedAnalysisDependencies, entry::PreparedStatementPlan};
pub fn invalidate_execution_plans<'a>(
entries: impl IntoIterator<Item = &'a mut PreparedStatementPlan>,
) {
for entry in entries {
if !entry.has_tracked_executable_dependencies() {
entry.plan = None;
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CatalogRegistryChange {
Definitions,
BuiltinRoutinePrivileges,
}
impl CatalogRegistryChange {
pub fn invalidate<'a>(self, entries: impl IntoIterator<Item = &'a mut PreparedStatementPlan>) {
if self == Self::Definitions {
invalidate_execution_plans(entries);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum PreparedCatalogChange {
Relation(u32),
Routine([u8; 16]),
GlobalCatalog,
}
impl PreparedCatalogChange {
pub fn invalidate_with_routines<'a>(
self,
entries: impl IntoIterator<Item = &'a mut PreparedStatementPlan>,
routines: &crate::routines::invocation::bodies::SessionRoutineBodies,
) {
self.invalidate(entries);
routines.invalidate(|dependencies| self.affects(dependencies));
}
fn affects(self, dependencies: &PreparedAnalysisDependencies) -> bool {
match self {
Self::Relation(oid) => dependencies.relations.contains(&oid),
Self::Routine(identity) => dependencies.routines.contains(&identity),
Self::GlobalCatalog => true,
}
}
pub fn invalidate<'a>(self, entries: impl IntoIterator<Item = &'a mut PreparedStatementPlan>) {
for entry in entries {
if self.affects(&entry.dependencies) {
entry.invalidate();
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PreparedInvalidationMark(usize);
#[derive(Default)]
pub struct PreparedInvalidationLog {
levels: Vec<BTreeSet<PreparedCatalogChange>>,
}
impl PreparedInvalidationLog {
pub fn invalidate_with_routines<'a>(
&self,
entries: impl IntoIterator<Item = &'a mut PreparedStatementPlan>,
routines: &crate::routines::invocation::bodies::SessionRoutineBodies,
) {
self.invalidate(entries);
routines.invalidate(|dependencies| self.affects(dependencies));
}
fn current(&mut self) -> &mut BTreeSet<PreparedCatalogChange> {
if self.levels.is_empty() {
self.levels.push(BTreeSet::new());
}
self.levels
.last_mut()
.expect("invalidation log has a current level")
}
pub fn record(&mut self, change: PreparedCatalogChange) {
let level = self.current();
if level.contains(&PreparedCatalogChange::GlobalCatalog) {
return;
}
if change == PreparedCatalogChange::GlobalCatalog {
level.clear();
}
level.insert(change);
}
pub fn mark(&mut self) -> PreparedInvalidationMark {
self.current();
let mark = PreparedInvalidationMark(self.levels.len());
self.levels.push(BTreeSet::new());
mark
}
pub fn release(&mut self, mark: PreparedInvalidationMark) {
let released = Self {
levels: self.levels.split_off(mark.0),
};
self.append(released);
}
pub fn rollback_to(&mut self, mark: PreparedInvalidationMark) -> Self {
let undone = Self {
levels: self.levels.split_off(mark.0),
};
self.levels.push(BTreeSet::new());
undone
}
pub fn append(&mut self, other: Self) {
for change in other.levels.into_iter().flatten() {
self.record(change);
}
}
pub fn invalidate<'a>(&self, entries: impl IntoIterator<Item = &'a mut PreparedStatementPlan>) {
for entry in entries {
if self.affects(&entry.dependencies) {
entry.invalidate();
}
}
}
fn affects(&self, dependencies: &PreparedAnalysisDependencies) -> bool {
self.levels
.iter()
.flatten()
.any(|change| change.affects(dependencies))
}
}
#[cfg(test)]
mod tests;