LCOV - code coverage report
Current view: top level - dashql-core/src/catalog - changes.rs Coverage Total Hit
Test: DashQL 915828b Lines: 98.8 % 80 79
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : //! Catalog changes applied to descriptor entries.
       2              : //!
       3              : //! A full catalog refresh can be expensive, so the catalog follows the DDL statements that the user runs: their
       4              : //! changes (`dashql_core_session::catalog_changes`) are applied to the tables of a descriptor entry.
       5              : 
       6              : use super::{Catalog, CatalogEntryId, CatalogLayer};
       7              : use crate::error::{Error, Result};
       8              : use dashql_buffers::analyzer::{QualifiedTableNameT, TableColumnT, TableT};
       9              : use dashql_buffers::catalog::{CatalogChangeT, CatalogChangeType, CatalogDescriptor, CatalogDescriptorT};
      10              : 
      11              : /// What a statement did to a relation
      12              : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
      13              : pub enum CatalogChangeKind {
      14              :     /// The statement created or replaced the relation
      15              :     CreateRelation,
      16              :     /// The statement dropped the relation
      17              :     DropRelation,
      18              : }
      19              : 
      20              : /// A change of a relation of the catalog, implied by a successful statement
      21              : #[derive(Clone, Debug, PartialEq, Eq)]
      22              : pub struct CatalogChange {
      23              :     /// What the statement did
      24              :     pub kind: CatalogChangeKind,
      25              :     /// The database name, empty if neither the statement nor the defaults name one
      26              :     pub database_name: String,
      27              :     /// The schema name, empty if neither the statement nor the defaults name one
      28              :     pub schema_name: String,
      29              :     /// The relation name
      30              :     pub relation_name: String,
      31              :     /// Is the relation only created if it does not exist yet?
      32              :     pub if_not_exists: bool,
      33              :     /// The column names of a created relation, in declaration order
      34              :     pub column_names: Vec<String>,
      35              : }
      36              : 
      37              : impl CatalogChange {
      38              :     /// The lookup key: (database, schema, relation)
      39           55 :     pub fn key(&self) -> (&str, &str, &str) {
      40           55 :         (&self.database_name, &self.schema_name, &self.relation_name)
      41           55 :     }
      42              : 
      43              :     /// Read a change of the boundary, None for a change without effect
      44            2 :     pub fn unpack(change: &CatalogChangeT) -> Option<Self> {
      45              :         Some(Self {
      46            2 :             kind: match change.change_type {
      47            1 :                 CatalogChangeType::CREATE_RELATION => CatalogChangeKind::CreateRelation,
      48            1 :                 CatalogChangeType::DROP_RELATION => CatalogChangeKind::DropRelation,
      49            0 :                 _ => return None,
      50              :             },
      51            2 :             database_name: change.database_name.clone().unwrap_or_default(),
      52            2 :             schema_name: change.schema_name.clone().unwrap_or_default(),
      53            2 :             relation_name: change.relation_name.clone().unwrap_or_default(),
      54            2 :             if_not_exists: change.if_not_exists,
      55            2 :             column_names: change.column_names.clone().unwrap_or_default(),
      56              :         })
      57            2 :     }
      58              : 
      59              :     /// Write the change for the boundary
      60          189 :     pub fn pack(&self) -> CatalogChangeT {
      61          189 :         let mut change = CatalogChangeT::default();
      62          189 :         change.change_type = match self.kind {
      63          143 :             CatalogChangeKind::CreateRelation => CatalogChangeType::CREATE_RELATION,
      64           46 :             CatalogChangeKind::DropRelation => CatalogChangeType::DROP_RELATION,
      65              :         };
      66          189 :         change.database_name = Some(self.database_name.clone());
      67          189 :         change.schema_name = Some(self.schema_name.clone());
      68          189 :         change.relation_name = Some(self.relation_name.clone());
      69          189 :         change.if_not_exists = self.if_not_exists;
      70          189 :         if self.kind == CatalogChangeKind::CreateRelation {
      71          143 :             change.column_names = Some(self.column_names.clone());
      72          143 :         }
      73          189 :         change
      74          189 :     }
      75              : }
      76              : 
      77              : /// Apply changes to the tables of a descriptor
      78           15 : fn apply_to_descriptor(descriptor: &mut CatalogDescriptorT, changes: &[CatalogChange]) {
      79           15 :     let tables = descriptor.tables.get_or_insert_with(Vec::new);
      80           19 :     for change in changes {
      81           19 :         let (database, schema, relation) = change.key();
      82           22 :         let position = tables.iter().position(|t| {
      83           22 :             let name = t.table_name.as_deref();
      84           52 :             let part = |f: fn(&QualifiedTableNameT) -> &Option<String>| name.and_then(|n| f(n).as_deref()).unwrap_or("");
      85           22 :             part(|n| &n.database_name) == database && part(|n| &n.schema_name) == schema && part(|n| &n.table_name) == relation
      86           22 :         });
      87           19 :         match change.kind {
      88              :             CatalogChangeKind::CreateRelation => {
      89           14 :                 if change.if_not_exists && position.is_some() {
      90            1 :                     continue;
      91           13 :                 }
      92           13 :                 let mut name = QualifiedTableNameT::default();
      93           13 :                 name.database_name = Some(database.to_string());
      94           13 :                 name.schema_name = Some(schema.to_string());
      95           13 :                 name.table_name = Some(relation.to_string());
      96           13 :                 let mut table = TableT::default();
      97           13 :                 table.table_name = Some(Box::new(name));
      98              :                 // A descriptor declares every column name once
      99           13 :                 let mut seen = std::collections::HashSet::new();
     100           13 :                 table.table_columns = Some(
     101           13 :                     change
     102           13 :                         .column_names
     103           13 :                         .iter()
     104           20 :                         .filter(|c| !c.is_empty() && seen.insert(c.as_str()))
     105           19 :                         .map(|c| {
     106           19 :                             let mut column = TableColumnT::default();
     107           19 :                             column.column_name = Some(c.clone());
     108           19 :                             column
     109           19 :                         })
     110           13 :                         .collect(),
     111              :                 );
     112           13 :                 match position {
     113            2 :                     Some(position) => tables[position] = table,
     114           11 :                     None => tables.push(table),
     115              :                 }
     116              :             }
     117              :             CatalogChangeKind::DropRelation => {
     118            5 :                 if let Some(position) = position {
     119            3 :                     tables.remove(position);
     120            4 :                 }
     121              :             }
     122              :         }
     123              :     }
     124           15 : }
     125              : 
     126              : impl Catalog {
     127              :     /// Apply catalog changes to a descriptor entry.
     128              :     /// An unknown entry is created with the given layer, a known entry keeps its layer.
     129           15 :     pub fn apply_catalog_changes(&mut self, id: CatalogEntryId, layer: CatalogLayer, changes: &[CatalogChange]) -> Result<()> {
     130           15 :         let (mut descriptor, layer) = match self.descriptor_entries.get(&id) {
     131           10 :             Some(entry) => {
     132           10 :                 let descriptor = flatbuffers::root::<CatalogDescriptor>(&entry.bytes)
     133           10 :                     .map_err(|_| Error::invalid_argument("invalid CatalogDescriptor FlatBuffer"))?
     134           10 :                     .unpack();
     135           10 :                 (descriptor, entry.layer)
     136              :             }
     137            5 :             None => (CatalogDescriptorT::default(), layer),
     138              :         };
     139           15 :         apply_to_descriptor(&mut descriptor, changes);
     140           15 :         let mut builder = flatbuffers::FlatBufferBuilder::new();
     141           15 :         let packed = descriptor.pack(&mut builder);
     142           15 :         builder.finish(packed, None);
     143           15 :         self.replace_descriptor(id, layer, builder.finished_data())
     144           15 :     }
     145              : }
        

Generated by: LCOV version 2.0-1