uqa-execution 0.5.0

Volcano physical operators with row-batch pipelines
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

//! The enforced keys of the tables a session writes, kept while the definitions they were read from are the same objects.

use std::{collections::BTreeMap, sync::Arc};

use parking_lot::Mutex;
use uqa_core::RelationIdentity;
use uqa_sql::{ast::TableKeyConstraint, catalog::index::EnforcedKey};
use uqa_storage::{CatalogIndexRow, StorageBackendResult};

type IndexRows = BTreeMap<RelationIdentity, CatalogIndexRow>;

/// Tables whose keys are kept. A statement writes its target and the tables its referential actions reach.
const TABLES: usize = 8;

struct Entry {
    table: String,
    constraints: Vec<TableKeyConstraint>,
    indexes: Arc<IndexRows>,
    keys: Vec<EnforcedKey>,
}

/// Every written row asks for its table's enforced keys twice, and finding them decodes each index definition of the catalog. The keys of a table depend on its declared constraints and on the index rows, so they are kept for the same constraints and the same index rows. Arc identity follows copy-on-write catalog publication, refresh and rollback; no independent invalidation counter can omit a mutation path. The keys of a partition's index also name its ancestors, which other tables' definitions decide, and are not kept.
#[derive(Default)]
pub struct EnforcedKeyCache {
    entries: Mutex<Vec<Entry>>,
}

impl EnforcedKeyCache {
    pub fn keys(
        &self,
        table: &str,
        constraints: Vec<TableKeyConstraint>,
        indexes: &Arc<IndexRows>,
        find: impl FnOnce(Vec<TableKeyConstraint>) -> StorageBackendResult<Vec<EnforcedKey>>,
    ) -> StorageBackendResult<Vec<EnforcedKey>> {
        if let Some(entry) = self.entries.lock().iter().find(|entry| {
            entry.table == table
                && Arc::ptr_eq(&entry.indexes, indexes)
                && entry.constraints == constraints
        }) {
            return Ok(entry.keys.clone());
        }
        let keys = find(constraints.clone())?;
        if keys.iter().all(|key| key.index_ancestors.is_empty()) {
            let mut entries = self.entries.lock();
            entries.retain(|entry| entry.table != table);
            if entries.len() == TABLES {
                entries.remove(0);
            }
            entries.push(Entry {
                table: table.to_owned(),
                constraints,
                indexes: Arc::clone(indexes),
                keys: keys.clone(),
            });
        }
        Ok(keys)
    }
}

#[cfg(test)]
mod tests;