LCOV - code coverage report
Current view: top level - dashql-core/src/catalog - mod.rs Coverage Total Hit
Test: DashQL d805a4b Lines: 96.3 % 765 737
Test Date: 2026-11-09 20:22:13

            Line data    Source code
       1              : //! The catalog publishes catalog entries (the declarations of analyzed scripts and descriptor pools)
       2              : //! atomically and resolves table names across them. Entries are immutable once published and shared
       3              : //! through [`CatalogEntryRef`]. A script entry holds only declarations and names, never the analysis,
       4              : //! so publishing does not keep ASTs or earlier analyses alive (see `docs/design/core_analysis_catalog.md`).
       5              : 
       6              : pub mod clfn;
       7              : pub mod descriptor;
       8              : pub mod entry;
       9              : pub mod ids;
      10              : pub mod names;
      11              : 
      12              : pub use clfn::ClientFunctionSpec;
      13              : pub use descriptor::DescriptorEntry;
      14              : pub use entry::*;
      15              : pub use ids::*;
      16              : pub use names::{IndexedName, NameSearchIndex};
      17              : 
      18              : use crate::error::{Error, Result};
      19              : use dashql_buffers::catalog::{
      20              :     FlatCatalogEntryT, FlatCatalogT, IndexedFlatDatabaseEntryT, IndexedFlatSchemaEntryT, IndexedFlatTableEntryT,
      21              : };
      22              : use dashql_buffers::status::StatusCode;
      23              : use std::cell::RefCell;
      24              : use crate::hash::HashMap;
      25              : use std::collections::{BTreeMap, BTreeSet};
      26              : use std::sync::atomic::{AtomicU32, Ordering};
      27              : use std::sync::Arc;
      28              : 
      29              : /// A published script: its declarations and the names that completion searches.
      30              : ///
      31              : /// The catalog keeps only this part of an analysis. Neither field refers to other entries, so
      32              : /// published scripts never keep earlier versions of each other alive.
      33              : #[derive(Clone, Debug)]
      34              : pub struct PublishedScript {
      35              :     /// The id of the catalog the script was analyzed against (`Catalog::id`)
      36              :     pub catalog_id: u64,
      37              :     /// The declarations
      38              :     pub entry: Arc<CatalogEntryData>,
      39              :     /// The names of the script
      40              :     pub names: Arc<NameSearchIndex>,
      41              : }
      42              : 
      43              : /// A published catalog entry
      44              : #[derive(Clone, Debug)]
      45              : pub enum CatalogEntryRef {
      46              :     Script(PublishedScript),
      47              :     Descriptor(Arc<DescriptorEntry>),
      48              : }
      49              : 
      50              : impl CatalogEntryRef {
      51        13905 :     pub fn data(&self) -> &CatalogEntryData {
      52        13905 :         self.shared_data()
      53        13905 :     }
      54              : 
      55              :     /// The declarations of the entry, shared with the analyzed scripts that depend on it
      56        13906 :     pub fn shared_data(&self) -> &Arc<CatalogEntryData> {
      57        13906 :         match self {
      58        13698 :             CatalogEntryRef::Script(s) => &s.entry,
      59          208 :             CatalogEntryRef::Descriptor(d) => &d.entry,
      60              :         }
      61        13906 :     }
      62              : 
      63              :     /// The name search index of the entry, built on first use
      64          122 :     pub fn name_search_index(&self) -> &NameSearchIndex {
      65          122 :         match self {
      66          119 :             CatalogEntryRef::Script(s) => &s.names,
      67            3 :             CatalogEntryRef::Descriptor(d) => d.name_search_index(),
      68              :         }
      69          122 :     }
      70              : }
      71              : 
      72              : /// A database declared by the catalog
      73              : #[derive(Clone, Debug)]
      74              : pub struct DatabaseDeclaration {
      75              :     /// The qualified object id of the database
      76              :     pub object_id: QualifiedCatalogObjectId,
      77              :     /// The database name
      78              :     pub database_name: Arc<str>,
      79              :     /// The database alias, empty when not aliased
      80              :     pub database_alias: Arc<str>,
      81              : }
      82              : 
      83              : /// A schema declared by the catalog
      84              : #[derive(Clone, Debug)]
      85              : pub struct SchemaDeclaration {
      86              :     /// The qualified object id, packs the database and schema ids
      87              :     pub object_id: QualifiedCatalogObjectId,
      88              :     /// The database name
      89              :     pub database_name: Arc<str>,
      90              :     /// The schema name
      91              :     pub schema_name: Arc<str>,
      92              : }
      93              : 
      94              : /// A catalog entry that populates a schema
      95              : #[derive(Clone, Copy, Debug)]
      96              : pub struct SchemaEntryInfo {
      97              :     /// The catalog entry
      98              :     pub catalog_entry_id: CatalogEntryId,
      99              :     /// The qualified object id of the schema
     100              :     pub catalog_schema_id: QualifiedCatalogObjectId,
     101              : }
     102              : 
     103              : /// Canonical ids, reserved during analysis and never reused
     104              : #[derive(Debug)]
     105              : struct IdReservations {
     106              :     /// The reserved database ids by database name
     107              :     database_ids_by_name: HashMap<String, CatalogDatabaseId>,
     108              :     /// The reserved schema ids by (database, schema)
     109              :     schema_ids_by_name: HashMap<(String, String), QualifiedCatalogObjectId>,
     110              :     /// The next database id to reserve
     111              :     next_database_id: CatalogDatabaseId,
     112              :     /// The next schema id to reserve
     113              :     next_schema_id: CatalogSchemaId,
     114              :     /// The reserved table and function ids of the entries, by (entry, kind, qualified name, occurrence)
     115              :     object_ids: HashMap<(CatalogEntryId, ObjectKind, QualifiedKey, u32), u32>,
     116              :     /// The next table and function id to reserve in an entry
     117              :     next_object_ids: HashMap<(CatalogEntryId, ObjectKind), u32>,
     118              :     /// The reserved column ids of the tables, by (table, column name, occurrence)
     119              :     column_ids: HashMap<(CatalogTableId, Arc<str>, u32), u32>,
     120              :     /// The next column id to reserve in a table
     121              :     next_column_ids: HashMap<CatalogTableId, u32>,
     122              : }
     123              : 
     124              : /// The kind of an object that an entry declares
     125              : #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
     126              : enum ObjectKind {
     127              :     Table,
     128              :     Function,
     129              : }
     130              : 
     131              : impl IdReservations {
     132              :     /// Reserve the id of an object of an entry
     133         2050 :     fn object_id(&mut self, entry: CatalogEntryId, kind: ObjectKind, name: &QualifiedKey, occurrence: u32) -> u32 {
     134         2050 :         let key = (entry, kind, name.clone(), occurrence);
     135         2050 :         if let Some(&id) = self.object_ids.get(&key) {
     136          410 :             return id;
     137         1640 :         }
     138         1640 :         let next = self.next_object_ids.entry((entry, kind)).or_insert(0);
     139         1640 :         let id = *next;
     140         1640 :         *next += 1;
     141         1640 :         self.object_ids.insert(key, id);
     142         1640 :         id
     143         2050 :     }
     144              : 
     145              :     /// Release the object and column ids of an entry
     146           68 :     fn release_entry(&mut self, entry: CatalogEntryId) {
     147           73 :         self.object_ids.retain(|(e, ..), _| *e != entry);
     148           70 :         self.next_object_ids.retain(|(e, _), _| *e != entry);
     149           72 :         self.column_ids.retain(|(table, ..), _| table.origin != entry);
     150           71 :         self.next_column_ids.retain(|table, _| table.origin != entry);
     151           68 :     }
     152              : }
     153              : 
     154              : /// The source of catalog entry ids.
     155              : ///
     156              : /// Catalogs that share a source never hand out the same entry id, so entry ids identify scripts and
     157              : /// descriptor pools across all of these catalogs. A catalog created with [`Catalog::new`] has a
     158              : /// source of its own.
     159              : #[derive(Clone, Debug)]
     160              : pub struct EntryIdSource {
     161              :     /// The next entry id to allocate
     162              :     next: Arc<AtomicU32>,
     163              : }
     164              : 
     165              : impl Default for EntryIdSource {
     166            0 :     fn default() -> Self {
     167            0 :         Self::new()
     168            0 :     }
     169              : }
     170              : 
     171              : impl EntryIdSource {
     172              :     /// A source that starts at the initial entry id
     173         1771 :     pub fn new() -> Self {
     174         1771 :         Self { next: Arc::new(AtomicU32::new(INITIAL_ENTRY_ID)) }
     175         1771 :     }
     176              : 
     177              :     /// Allocate an entry id
     178        13958 :     pub fn allocate(&self) -> CatalogEntryId {
     179        13958 :         let id = self.next.fetch_add(1, Ordering::Relaxed);
     180        13958 :         assert!(id != PROTO_NULL_U32, "catalog entry ID namespace exhausted");
     181        13958 :         id
     182        13958 :     }
     183              : 
     184              :     /// Was the entry id allocated by this source?
     185           65 :     pub fn allocated(&self, id: CatalogEntryId) -> bool {
     186           65 :         id >= INITIAL_ENTRY_ID && id < self.next.load(Ordering::Relaxed)
     187           65 :     }
     188              : }
     189              : 
     190              : /// Fail for a value that is not a layer, the FlatBuffers enum admits every byte
     191         1095 : fn check_layer(layer: CatalogLayer) -> Result<()> {
     192         1095 :     match layer.variant_name() {
     193         1094 :         Some(_) => Ok(()),
     194            1 :         None => Err(Error::invalid_argument("invalid catalog layer")),
     195              :     }
     196         1095 : }
     197              : 
     198              : /// The source of unique catalog ids
     199              : static NEXT_CATALOG_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
     200              : 
     201              : /// The published state of a catalog at one point in time.
     202              : ///
     203              : /// Immutable once published: analyses keep the snapshot they resolved against, so their catalog
     204              : /// locations stay valid however the catalog changes later. A snapshot holds declarations and names,
     205              : /// never analyses, so keeping one alive keeps no earlier analysis alive.
     206              : #[derive(Clone, Debug)]
     207              : pub struct CatalogSnapshot {
     208              :     /// The process-unique id of the catalog
     209              :     id: u64,
     210              :     /// The version, bumped by every modification
     211              :     version: CatalogVersion,
     212              :     /// The published entries
     213              :     entries: HashMap<CatalogEntryId, CatalogEntryRef>,
     214              :     /// The published scripts and their layers
     215              :     script_entries: HashMap<CatalogEntryId, (PublishedScript, CatalogLayer)>,
     216              :     /// The published descriptor pools
     217              :     descriptor_entries: HashMap<CatalogEntryId, Arc<DescriptorEntry>>,
     218              :     /// The entries ordered by (layer, id)
     219              :     entries_ranked: BTreeSet<(CatalogLayer, CatalogEntryId)>,
     220              :     /// The entries by (database, schema, layer, id)
     221              :     entries_by_qualified_schema: BTreeMap<(Arc<str>, Arc<str>, CatalogLayer, CatalogEntryId), SchemaEntryInfo>,
     222              :     /// The entries by (schema, layer, id)
     223              :     entries_by_schema: BTreeMap<(Arc<str>, CatalogLayer, CatalogEntryId), SchemaEntryInfo>,
     224              :     /// The databases referenced by entries
     225              :     databases: BTreeMap<Arc<str>, Arc<DatabaseDeclaration>>,
     226              :     /// The schemas referenced by entries
     227              :     schemas: BTreeMap<(Arc<str>, Arc<str>), Arc<SchemaDeclaration>>,
     228              :     /// The client function specs by namespace, a spec shadows its namespace for `CALL`
     229              :     client_function_specs: BTreeMap<Arc<str>, Arc<ClientFunctionSpec>>,
     230              :     /// The relations that the connection reported as gone, their declarations in scripts are hidden until the
     231              :     /// connection has them again. An empty part of a name matches any database or schema.
     232              :     absent_relations: Vec<QualifiedKey>,
     233              : }
     234              : 
     235              : /// The catalog: the current snapshot and the ids that outlive snapshots.
     236              : ///
     237              : /// Reads go to the current snapshot (`Deref`). A modification copies the snapshot if an analysis
     238              : /// still holds it (`DerefMut` is `Arc::make_mut`), so published snapshots never change.
     239              : #[derive(Debug)]
     240              : pub struct Catalog {
     241              :     /// The current snapshot
     242              :     current: Arc<CatalogSnapshot>,
     243              :     /// The canonical ids, reserved during analysis and never reused
     244              :     ids: RefCell<IdReservations>,
     245              :     /// The source of the entry ids
     246              :     entry_ids: EntryIdSource,
     247              : }
     248              : 
     249              : impl std::ops::Deref for Catalog {
     250              :     type Target = CatalogSnapshot;
     251              : 
     252        23766 :     fn deref(&self) -> &CatalogSnapshot {
     253        23766 :         &self.current
     254        23766 :     }
     255              : }
     256              : 
     257              : impl std::ops::DerefMut for Catalog {
     258         8584 :     fn deref_mut(&mut self) -> &mut CatalogSnapshot {
     259         8584 :         Arc::make_mut(&mut self.current)
     260         8584 :     }
     261              : }
     262              : 
     263              : impl Default for Catalog {
     264           18 :     fn default() -> Self {
     265           18 :         Self::new()
     266           18 :     }
     267              : }
     268              : 
     269              : impl Catalog {
     270         1770 :     pub fn new() -> Self {
     271         1770 :         Self::with_entry_ids(EntryIdSource::new())
     272         1770 :     }
     273              : 
     274              :     /// A catalog that allocates its entry ids from a shared source
     275         1772 :     pub fn with_entry_ids(entry_ids: EntryIdSource) -> Self {
     276         1772 :         Self {
     277         1772 :             current: Arc::new(CatalogSnapshot {
     278         1772 :                 id: NEXT_CATALOG_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
     279         1772 :                 version: 1,
     280         1772 :                 entries: HashMap::default(),
     281         1772 :                 script_entries: HashMap::default(),
     282         1772 :                 descriptor_entries: HashMap::default(),
     283         1772 :                 entries_ranked: BTreeSet::new(),
     284         1772 :                 entries_by_qualified_schema: BTreeMap::new(),
     285         1772 :                 entries_by_schema: BTreeMap::new(),
     286         1772 :                 databases: BTreeMap::new(),
     287         1772 :                 schemas: BTreeMap::new(),
     288         1772 :                 client_function_specs: BTreeMap::new(),
     289         1772 :                 absent_relations: Vec::new(),
     290         1772 :             }),
     291         1772 :             ids: RefCell::new(IdReservations {
     292         1772 :                 database_ids_by_name: HashMap::default(),
     293         1772 :                 schema_ids_by_name: HashMap::default(),
     294         1772 :                 next_database_id: INITIAL_DATABASE_ID,
     295         1772 :                 next_schema_id: INITIAL_SCHEMA_ID,
     296         1772 :                 object_ids: HashMap::default(),
     297         1772 :                 next_object_ids: HashMap::default(),
     298         1772 :                 column_ids: HashMap::default(),
     299         1772 :                 next_column_ids: HashMap::default(),
     300         1772 :             }),
     301         1772 :             entry_ids,
     302         1772 :         }
     303         1772 :     }
     304              : 
     305              :     /// The current snapshot
     306         4003 :     pub fn snapshot(&self) -> &Arc<CatalogSnapshot> {
     307         4003 :         &self.current
     308         4003 :     }
     309              : 
     310              :     /// Is a snapshot of this catalog current? A snapshot stays current while the version stays,
     311              :     /// i.e. while the declarations that analyses resolve against stay the same.
     312         1129 :     pub fn is_current(&self, snapshot: &CatalogSnapshot) -> bool {
     313         1129 :         snapshot.id == self.current.id && snapshot.version == self.current.version
     314         1129 :     }
     315              : }
     316              : 
     317              : impl CatalogSnapshot {
     318              :     /// The process-unique id of the catalog
     319         3874 :     pub fn id(&self) -> u64 {
     320         3874 :         self.id
     321         3874 :     }
     322              : 
     323         5585 :     pub fn version(&self) -> CatalogVersion {
     324         5585 :         self.version
     325         5585 :     }
     326              : 
     327           30 :     pub fn contains(&self, id: CatalogEntryId) -> bool {
     328           30 :         self.entries.contains_key(&id)
     329           30 :     }
     330              : 
     331         1136 :     pub fn entry(&self, id: CatalogEntryId) -> Option<&CatalogEntryRef> {
     332         1136 :         self.entries.get(&id)
     333         1136 :     }
     334              : 
     335              :     /// The layer of an entry
     336         3668 :     pub fn layer(&self, id: CatalogEntryId) -> Option<CatalogLayer> {
     337         3668 :         match self.script_entries.get(&id) {
     338         3624 :             Some((_, layer)) => Some(*layer),
     339           44 :             None => self.descriptor_entries.get(&id).map(|d| d.layer),
     340              :         }
     341         3668 :     }
     342              : 
     343              :     /// The entries in resolution order: by layer, then by entry id
     344         1232 :     pub fn entries_ranked(&self) -> impl Iterator<Item = (CatalogLayer, &CatalogEntryRef)> {
     345         1241 :         self.entries_ranked.iter().map(|(layer, id)| (*layer, &self.entries[id]))
     346         1232 :     }
     347              : 
     348            8 :     pub fn databases(&self) -> &BTreeMap<Arc<str>, Arc<DatabaseDeclaration>> {
     349            8 :         &self.databases
     350            8 :     }
     351              : 
     352           44 :     pub fn schemas(&self) -> &BTreeMap<(Arc<str>, Arc<str>), Arc<SchemaDeclaration>> {
     353           44 :         &self.schemas
     354           44 :     }
     355              : 
     356              :     /// The entries by (database, schema, layer, id)
     357           18 :     pub fn entries_by_qualified_schema(&self) -> &BTreeMap<(Arc<str>, Arc<str>, CatalogLayer, CatalogEntryId), SchemaEntryInfo> {
     358           18 :         &self.entries_by_qualified_schema
     359           18 :     }
     360              : 
     361              :     /// The entries by (schema, layer, id)
     362           37 :     pub fn entries_by_schema(&self) -> &BTreeMap<(Arc<str>, CatalogLayer, CatalogEntryId), SchemaEntryInfo> {
     363           37 :         &self.entries_by_schema
     364           37 :     }
     365              : }
     366              : 
     367              : impl Catalog {
     368              :     /// Allocate an entry id
     369        13958 :     pub fn allocate_entry_id(&self) -> CatalogEntryId {
     370        13958 :         self.entry_ids.allocate()
     371        13958 :     }
     372              : 
     373              :     /// Was the entry id allocated by the id source of the catalog?
     374           65 :     pub fn entry_id_allocated(&self, id: CatalogEntryId) -> bool {
     375           65 :         self.entry_ids.allocated(id)
     376           65 :     }
     377              : 
     378              :     /// Reserve the canonical id of a database
     379         1359 :     pub fn reserve_database_id(&self, database: &str) -> QualifiedCatalogObjectId {
     380         1359 :         let mut ids = self.ids.borrow_mut();
     381         1359 :         if let Some(&id) = ids.database_ids_by_name.get(database) {
     382          480 :             return QualifiedCatalogObjectId::database(id);
     383          879 :         }
     384          879 :         let id = ids.next_database_id;
     385          879 :         ids.next_database_id += 1;
     386          879 :         ids.database_ids_by_name.insert(database.to_string(), id);
     387          879 :         QualifiedCatalogObjectId::database(id)
     388         1359 :     }
     389              : 
     390              :     /// Reserve the id of a table of an entry.
     391              :     ///
     392              :     /// The id is keyed by the qualified name and the occurrence of that name among the tables of the
     393              :     /// entry, so a table keeps its id across versions of the entry however its declarations move. A
     394              :     /// fresh entry gets the ids in declaration order. Dropping the entry releases its ids.
     395         2011 :     pub fn reserve_table_id(&self, entry: CatalogEntryId, name: &QualifiedKey, occurrence: u32) -> CatalogTableId {
     396         2011 :         CatalogTableId::new(entry, self.ids.borrow_mut().object_id(entry, ObjectKind::Table, name, occurrence))
     397         2011 :     }
     398              : 
     399              :     /// Reserve the id of a function of an entry, like [`Catalog::reserve_table_id`]
     400           39 :     pub fn reserve_function_id(&self, entry: CatalogEntryId, name: &QualifiedKey, occurrence: u32) -> CatalogFunctionId {
     401           39 :         CatalogFunctionId::new(entry, self.ids.borrow_mut().object_id(entry, ObjectKind::Function, name, occurrence))
     402           39 :     }
     403              : 
     404              :     /// Reserve the id of a column of a table, keyed by the column name and its occurrence among the
     405              :     /// columns of the table
     406         4585 :     pub fn reserve_column_id(&self, table: CatalogTableId, name: &Arc<str>, occurrence: u32) -> u32 {
     407         4585 :         let mut ids = self.ids.borrow_mut();
     408         4585 :         let key = (table, name.clone(), occurrence);
     409         4585 :         if let Some(&id) = ids.column_ids.get(&key) {
     410          378 :             return id;
     411         4207 :         }
     412         4207 :         let next = ids.next_column_ids.entry(table).or_insert(0);
     413         4207 :         let id = *next;
     414         4207 :         *next += 1;
     415         4207 :         ids.column_ids.insert(key, id);
     416         4207 :         id
     417         4585 :     }
     418              : 
     419              :     /// Reserve the canonical id of a schema
     420         1382 :     pub fn reserve_schema_id(&self, database: &str, schema: &str, db_id: QualifiedCatalogObjectId) -> QualifiedCatalogObjectId {
     421         1382 :         let mut ids = self.ids.borrow_mut();
     422         1382 :         let key = (database.to_string(), schema.to_string());
     423         1382 :         if let Some(&id) = ids.schema_ids_by_name.get(&key) {
     424          476 :             return id;
     425          906 :         }
     426          906 :         let schema_id = ids.next_schema_id;
     427          906 :         ids.next_schema_id += 1;
     428          906 :         let id = QualifiedCatalogObjectId::schema(db_id.unpack_database_id(), schema_id);
     429          906 :         ids.schema_ids_by_name.insert(key, id);
     430          906 :         id
     431         1382 :     }
     432              : 
     433         1035 :     fn check_canonical_ids(&self, entry: &CatalogEntryData) -> Result<()> {
     434         1035 :         let ids = self.ids.borrow();
     435         1052 :         for (name, &i) in &entry.databases_by_name {
     436         1049 :             let object_id = entry.database_references[i].object_id;
     437         1049 :             match ids.database_ids_by_name.get(&**name) {
     438         1049 :                 Some(&id) if object_id == QualifiedCatalogObjectId::database(id) => {}
     439            0 :                 _ => return Err(Error::status(StatusCode::CATALOG_ID_OUT_OF_SYNC)),
     440              :             }
     441              :         }
     442         1063 :         for ((db, schema), &i) in &entry.schemas_by_qualified_name {
     443         1060 :             let object_id = entry.schema_references[i].object_id;
     444         1060 :             match ids.schema_ids_by_name.get(&(db.to_string(), schema.to_string())) {
     445         1060 :                 Some(&id) if id == object_id => {}
     446            0 :                 _ => return Err(Error::status(StatusCode::CATALOG_ID_OUT_OF_SYNC)),
     447              :             }
     448              :         }
     449         1035 :         Ok(())
     450         1035 :     }
     451              : 
     452              :     /// Load an analyzed script
     453          882 :     pub fn load_script(&mut self, script: impl Into<PublishedScript>, layer: CatalogLayer) -> Result<()> {
     454          882 :         self.load_scripts(&[(script.into(), layer)])
     455          882 :     }
     456              : 
     457              :     /// Atomically add or replace scripts
     458         1455 :     pub fn load_scripts<S: Clone + Into<PublishedScript>>(&mut self, scripts: &[(S, CatalogLayer)]) -> Result<()> {
     459         1455 :         if scripts.is_empty() {
     460          450 :             return Ok(());
     461         1005 :         }
     462         1005 :         let scripts: Vec<(PublishedScript, CatalogLayer)> =
     463         1037 :             scripts.iter().map(|(script, layer)| (script.clone().into(), *layer)).collect();
     464         1005 :         let mut batch_ids = BTreeSet::new();
     465         1037 :         for (_, layer) in &scripts {
     466         1037 :             check_layer(*layer)?;
     467              :         }
     468         1037 :         for (script, _) in &scripts {
     469         1037 :             if script.catalog_id != self.id {
     470            1 :                 return Err(Error::status(StatusCode::CATALOG_MISMATCH));
     471         1036 :             }
     472         1036 :             let id = script.entry.catalog_entry_id;
     473         1036 :             if !batch_ids.insert(id) {
     474            1 :                 return Err(Error::status(StatusCode::EXTERNAL_ID_COLLISION));
     475         1035 :             }
     476         1035 :             if self.entries.contains_key(&id) && !self.script_entries.contains_key(&id) {
     477            0 :                 return Err(Error::status(StatusCode::EXTERNAL_ID_COLLISION));
     478         1035 :             }
     479         1035 :             self.check_canonical_ids(&script.entry)?;
     480              :         }
     481              :         // Scripts that declare what their published versions declare replace them in place: the
     482              :         // indexes stay valid and the catalog version stays, so the analyses of other scripts remain
     483              :         // current
     484         1003 :         let unchanged = scripts.iter().all(|(script, layer)| {
     485         1003 :             self.script_entries.get(&script.entry.catalog_entry_id).is_some_and(|(published, published_layer)| {
     486          216 :                 published_layer == layer && published.entry.same_declarations(&script.entry)
     487          216 :             })
     488         1003 :         });
     489         1003 :         if unchanged {
     490          204 :             for (script, layer) in &scripts {
     491          204 :                 let id = script.entry.catalog_entry_id;
     492          204 :                 self.script_entries.insert(id, (script.clone(), *layer));
     493          204 :                 self.entries.insert(id, CatalogEntryRef::Script(script.clone()));
     494          204 :             }
     495          204 :             return Ok(());
     496          799 :         }
     497          799 :         let mut staged = self.script_entries.clone();
     498          829 :         for (script, layer) in &scripts {
     499          829 :             staged.insert(script.entry.catalog_entry_id, (script.clone(), *layer));
     500          829 :         }
     501          799 :         let descriptors = self.descriptor_entries.clone();
     502          799 :         self.publish_entries(staged, descriptors)
     503         1455 :     }
     504              : 
     505              :     /// Build all indexes before committing
     506          852 :     fn publish_entries(
     507          852 :         &mut self,
     508          852 :         script_entries: HashMap<CatalogEntryId, (PublishedScript, CatalogLayer)>,
     509          852 :         descriptor_entries: HashMap<CatalogEntryId, Arc<DescriptorEntry>>,
     510          852 :     ) -> Result<()> {
     511          852 :         let mut entries = HashMap::default();
     512          852 :         let mut entries_ranked = BTreeSet::new();
     513          852 :         let mut entries_by_qualified_schema = BTreeMap::new();
     514          852 :         let mut entries_by_schema = BTreeMap::new();
     515          852 :         let mut databases: BTreeMap<Arc<str>, Arc<DatabaseDeclaration>> = BTreeMap::new();
     516          852 :         let mut schemas: BTreeMap<(Arc<str>, Arc<str>), Arc<SchemaDeclaration>> = BTreeMap::new();
     517              : 
     518          949 :         let mut stage = |entry: CatalogEntryRef, layer: CatalogLayer| -> Result<()> {
     519          949 :             let data = entry.data();
     520          949 :             let entry_id = data.catalog_entry_id;
     521          949 :             entries_ranked.insert((layer, entry_id));
     522          971 :             for (name, &i) in &data.databases_by_name {
     523          968 :                 let db = &data.database_references[i];
     524          968 :                 if let Some(existing) = databases.get(name) {
     525           79 :                     if existing.object_id != db.object_id {
     526            0 :                         return Err(Error::status(StatusCode::CATALOG_ID_OUT_OF_SYNC));
     527           79 :                     }
     528           79 :                     continue;
     529          889 :                 }
     530          889 :                 databases.insert(
     531          889 :                     db.database_name.clone(),
     532          889 :                     Arc::new(DatabaseDeclaration {
     533          889 :                         object_id: db.object_id,
     534          889 :                         database_name: db.database_name.clone(),
     535          889 :                         database_alias: db.database_alias.clone(),
     536          889 :                     }),
     537              :                 );
     538              :             }
     539          989 :             for ((db_name, schema_name), &i) in &data.schemas_by_qualified_name {
     540          986 :                 let schema = &data.schema_references[i];
     541          986 :                 let key = (db_name.clone(), schema_name.clone());
     542          986 :                 if let Some(existing) = schemas.get(&key) {
     543           75 :                     if existing.object_id != schema.object_id {
     544            0 :                         return Err(Error::status(StatusCode::CATALOG_ID_OUT_OF_SYNC));
     545           75 :                     }
     546           75 :                     continue;
     547          911 :                 }
     548          911 :                 if !databases.contains_key(db_name) {
     549            0 :                     return Err(Error::status(StatusCode::CATALOG_ID_OUT_OF_SYNC));
     550          911 :                 }
     551          911 :                 schemas.insert(
     552          911 :                     key,
     553          911 :                     Arc::new(SchemaDeclaration {
     554          911 :                         object_id: schema.object_id,
     555          911 :                         database_name: db_name.clone(),
     556          911 :                         schema_name: schema.schema_name.clone(),
     557          911 :                     }),
     558              :                 );
     559              :             }
     560          989 :             for ((db_name, schema_name), &i) in &data.schemas_by_qualified_name {
     561          986 :                 let info = SchemaEntryInfo {
     562          986 :                     catalog_entry_id: entry_id,
     563          986 :                     catalog_schema_id: data.schema_references[i].object_id,
     564          986 :                 };
     565          986 :                 entries_by_qualified_schema.insert((db_name.clone(), schema_name.clone(), layer, entry_id), info);
     566          986 :                 entries_by_schema.insert((schema_name.clone(), layer, entry_id), info);
     567          986 :             }
     568          949 :             if entries.insert(entry_id, entry.clone()).is_some() {
     569            0 :                 return Err(Error::status(StatusCode::EXTERNAL_ID_COLLISION));
     570          949 :             }
     571          949 :             Ok(())
     572          949 :         };
     573          933 :         for (script, layer) in script_entries.values() {
     574          885 :             stage(CatalogEntryRef::Script(script.clone()), *layer)?;
     575              :         }
     576          854 :         for descriptor in descriptor_entries.values() {
     577           64 :             stage(CatalogEntryRef::Descriptor(descriptor.clone()), descriptor.layer)?;
     578              :         }
     579          852 :         self.script_entries = script_entries;
     580          852 :         self.descriptor_entries = descriptor_entries;
     581          852 :         self.entries = entries;
     582          852 :         self.entries_ranked = entries_ranked;
     583          852 :         self.entries_by_qualified_schema = entries_by_qualified_schema;
     584          852 :         self.entries_by_schema = entries_by_schema;
     585          852 :         self.databases = databases;
     586          852 :         self.schemas = schemas;
     587          852 :         self.version += 1;
     588          852 :         Ok(())
     589          852 :     }
     590              : 
     591              :     /// Replace a descriptor pool atomically
     592           62 :     pub fn replace_descriptor(&mut self, id: CatalogEntryId, layer: CatalogLayer, descriptor: &[u8]) -> Result<()> {
     593           62 :         if !self.entry_id_allocated(id)
     594           59 :             || (self.entries.contains_key(&id) && !self.descriptor_entries.contains_key(&id))
     595              :         {
     596            4 :             return Err(Error::invalid_argument("invalid or colliding receiver catalog entry ID"));
     597           58 :         }
     598           58 :         check_layer(layer)?;
     599              :         // Identical bytes declare the same, the catalog version stays and the analyses stay current
     600           57 :         if self.descriptor_entries.get(&id).is_some_and(|e| e.layer == layer && e.bytes == descriptor) {
     601            1 :             return Ok(());
     602           56 :         }
     603           56 :         let entry = DescriptorEntry::new(self, id, layer, descriptor)?;
     604           68 :         let reported: Vec<QualifiedKey> = entry.entry.table_declarations.iter().map(|t| t.table_name.key()).collect();
     605           52 :         let mut staged = self.descriptor_entries.clone();
     606           52 :         staged.insert(id, Arc::new(entry));
     607           52 :         let scripts = self.script_entries.clone();
     608           52 :         self.publish_entries(scripts, staged)?;
     609              :         // The connection has these relations again
     610           52 :         self.absent_relations.retain(|absent| !reported.iter().any(|table| same_relation(absent, table)));
     611           52 :         Ok(())
     612           62 :     }
     613              : 
     614              :     /// Update the tables of a descriptor pool with a packed `CatalogTableUpdate`, an unknown pool is created in the
     615              :     /// layer. Tables that the update leaves as they are keep their position, an update that changes nothing keeps
     616              :     /// the catalog version. A relation that has no table anymore is gone: scripts that declare it no longer provide it.
     617           27 :     pub fn replace_tables(&mut self, id: CatalogEntryId, layer: CatalogLayer, update: &[u8]) -> Result<()> {
     618              :         use dashql_buffers::analyzer::{QualifiedTableNameT, TableT};
     619              :         use dashql_buffers::catalog::{CatalogDescriptor, CatalogDescriptorT, CatalogTableUpdate};
     620           27 :         let update = flatbuffers::root::<CatalogTableUpdate>(update)
     621           27 :             .map_err(|_| Error::invalid_argument("invalid CatalogTableUpdate FlatBuffer"))?
     622           26 :             .unpack();
     623           26 :         let (mut descriptor, layer) = match self.descriptor_entries.get(&id) {
     624           15 :             Some(entry) => {
     625           15 :                 let descriptor = flatbuffers::root::<CatalogDescriptor>(&entry.bytes)
     626           15 :                     .map_err(|_| Error::invalid_argument("invalid CatalogDescriptor FlatBuffer"))?
     627           15 :                     .unpack();
     628           15 :                 (descriptor, entry.layer)
     629              :             }
     630           11 :             None => (CatalogDescriptorT::default(), layer),
     631              :         };
     632           62 :         let key_of = |name: &QualifiedTableNameT| -> QualifiedKey {
     633           62 :             (
     634           62 :                 Arc::from(name.database_name.as_deref().unwrap_or("")),
     635           62 :                 Arc::from(name.schema_name.as_deref().unwrap_or("")),
     636           62 :                 Arc::from(name.table_name.as_deref().unwrap_or("")),
     637           62 :             )
     638           62 :         };
     639           48 :         let key_of_table = |table: &TableT| key_of(table.table_name.as_deref().unwrap_or(&QualifiedTableNameT::default()));
     640           26 :         let before = descriptor.tables.take().unwrap_or_default();
     641           26 :         let new_tables = update.tables.unwrap_or_default();
     642           26 :         let new_keys: Vec<QualifiedKey> = new_tables.iter().map(key_of_table).collect();
     643           26 :         let relations: Vec<QualifiedKey> = update.relations.unwrap_or_default().iter().map(key_of).collect();
     644           26 :         let databases = update.databases.unwrap_or_default();
     645           26 :         let mut tables: Vec<TableT> = Vec::with_capacity(before.len() + new_tables.len());
     646           26 :         let mut before_keys: Vec<QualifiedKey> = Vec::with_capacity(before.len());
     647           26 :         for table in &before {
     648           24 :             let key = key_of_table(table);
     649           24 :             if let Some(next) = new_keys.iter().position(|k| *k == key) {
     650            3 :                 tables.push(new_tables[next].clone());
     651           21 :             } else if !relations.iter().any(|r| same_relation(r, &key)) && !databases.iter().any(|d| d.as_str() == &*key.0) {
     652           14 :                 tables.push(table.clone());
     653           14 :             }
     654           24 :             before_keys.push(key);
     655              :         }
     656           26 :         for (table, key) in new_tables.iter().zip(&new_keys) {
     657           24 :             if !before_keys.contains(key) {
     658           21 :                 tables.push(table.clone());
     659           21 :             }
     660              :         }
     661              :         // The relations that the connection has no table of are gone, the others are not
     662           26 :         let mut absent = self.absent_relations.clone();
     663           26 :         absent.retain(|a| !new_keys.iter().any(|k| same_relation(a, k)));
     664           26 :         for relation in &relations {
     665           14 :             if !new_keys.iter().any(|k| same_relation(relation, k)) && !absent.contains(relation) {
     666            7 :                 absent.push(relation.clone());
     667            7 :             }
     668              :         }
     669           26 :         let absent_changed = absent != self.absent_relations;
     670           26 :         if tables == before && self.descriptor_entries.contains_key(&id) {
     671            5 :             if absent_changed {
     672            3 :                 self.absent_relations = absent;
     673            3 :                 self.version += 1;
     674            3 :             }
     675            5 :             return Ok(());
     676           21 :         }
     677           21 :         descriptor.tables = Some(tables);
     678           21 :         let mut builder = flatbuffers::FlatBufferBuilder::new();
     679           21 :         let packed = descriptor.pack(&mut builder);
     680           21 :         builder.finish(packed, None);
     681           21 :         self.replace_descriptor(id, layer, builder.finished_data())?;
     682           20 :         self.absent_relations = absent;
     683           20 :         Ok(())
     684           27 :     }
     685              : 
     686              :     /// The relations that the connection reported as gone
     687            0 :     pub fn absent_relations(&self) -> &[QualifiedKey] {
     688            0 :         &self.absent_relations
     689            0 :     }
     690              : 
     691              :     /// The bytes of a descriptor pool
     692            0 :     pub fn descriptor_bytes(&self, id: CatalogEntryId) -> Option<&[u8]> {
     693            0 :         self.descriptor_entries.get(&id).map(|entry| entry.bytes.as_slice())
     694            0 :     }
     695              : 
     696              :     /// Drop a descriptor pool
     697            3 :     pub fn drop_descriptor(&mut self, id: CatalogEntryId) -> Result<()> {
     698            3 :         if !self.descriptor_entries.contains_key(&id) {
     699            2 :             return Ok(());
     700            1 :         }
     701            1 :         self.ids.borrow_mut().release_entry(id);
     702            1 :         let mut staged = self.descriptor_entries.clone();
     703            1 :         staged.remove(&id);
     704            1 :         let scripts = self.script_entries.clone();
     705            1 :         self.publish_entries(scripts, staged)
     706            3 :     }
     707              : 
     708              :     /// Drop a script
     709           68 :     pub fn drop_script(&mut self, id: CatalogEntryId) {
     710              :         // Also for scripts that were analyzed but never published. A descriptor pool published under the id of the
     711              :         // script owns the ids, they stay until the pool is dropped.
     712           68 :         if !self.descriptor_entries.contains_key(&id) {
     713           67 :             self.ids.borrow_mut().release_entry(id);
     714           68 :         }
     715           68 :         let Some((analyzed, layer)) = self.script_entries.remove(&id) else {
     716           54 :             return;
     717              :         };
     718           15 :         for (db_name, schema_name) in analyzed.entry.schemas_by_qualified_name.keys() {
     719           13 :             self.entries_by_qualified_schema.remove(&(db_name.clone(), schema_name.clone(), layer, id));
     720           13 :             self.entries_by_schema.remove(&(schema_name.clone(), layer, id));
     721           13 :         }
     722           14 :         self.entries_ranked.remove(&(layer, id));
     723           14 :         self.entries.remove(&id);
     724           15 :         for (db_name, schema_name) in analyzed.entry.schemas_by_qualified_name.keys() {
     725           13 :             let lower = (db_name.clone(), schema_name.clone(), MIN_LAYER, 0);
     726           13 :             let remaining = self.entries_by_qualified_schema.range(lower..).next();
     727            1 :             match remaining {
     728            1 :                 Some(((d, s, _, _), _)) if d == db_name && s == schema_name => {}
     729           12 :                 _ => {
     730           12 :                     self.schemas.remove(&(db_name.clone(), schema_name.clone()));
     731           12 :                 }
     732              :             }
     733              :         }
     734           15 :         for name in analyzed.entry.databases_by_name.keys() {
     735           13 :             let referenced = self.entries.values().any(|e| e.data().databases_by_name.contains_key(name));
     736           13 :             if !referenced {
     737           12 :                 self.databases.remove(name);
     738           12 :             }
     739              :         }
     740           14 :         self.version += 1;
     741           68 :     }
     742              : 
     743              :     /// Register a client function spec. Its namespace must be free, drop the spec first to replace it.
     744          125 :     pub fn register_client_functions(&mut self, json: &str) -> Result<Arc<str>> {
     745          125 :         let spec = ClientFunctionSpec::parse(json)?;
     746          125 :         if self.client_function_specs.contains_key(spec.namespace.as_str()) {
     747            0 :             return Err(Error::invalid_argument(format!("the namespace {} is already registered", spec.namespace)));
     748          125 :         }
     749          125 :         let namespace: Arc<str> = Arc::from(spec.namespace.as_str());
     750          125 :         self.client_function_specs.insert(namespace.clone(), Arc::new(spec));
     751          125 :         self.version += 1;
     752          125 :         Ok(namespace)
     753          125 :     }
     754              : 
     755              :     /// Drop a client function spec
     756            0 :     pub fn drop_client_functions(&mut self, namespace: &str) {
     757            0 :         if self.client_function_specs.remove(namespace).is_some() {
     758            0 :             self.version += 1;
     759            0 :         }
     760            0 :     }
     761              : 
     762              :     /// Clear the catalog. The client function specs stay, they come with the connector and not from the backend.
     763            2 :     pub fn clear(&mut self) {
     764            2 :         self.entries_by_qualified_schema.clear();
     765            2 :         self.entries_by_schema.clear();
     766            2 :         self.entries_ranked.clear();
     767            2 :         self.entries.clear();
     768            2 :         self.script_entries.clear();
     769            2 :         self.descriptor_entries.clear();
     770            2 :         self.schemas.clear();
     771            2 :         self.databases.clear();
     772            2 :         self.absent_relations.clear();
     773            2 :         self.version += 1;
     774            2 :     }
     775              : 
     776              : }
     777              : 
     778              : impl CatalogSnapshot {
     779              :     /// The client function spec of a namespace
     780          126 :     pub fn client_function_spec(&self, namespace: &str) -> Option<&Arc<ClientFunctionSpec>> {
     781          126 :         self.client_function_specs.get(namespace)
     782          126 :     }
     783              : 
     784              :     /// The client function specs by namespace
     785         5034 :     pub fn client_function_specs(&self) -> &BTreeMap<Arc<str>, Arc<ClientFunctionSpec>> {
     786         5034 :         &self.client_function_specs
     787         5034 :     }
     788              : 
     789              :     /// Flatten the catalog.
     790              :     ///
     791              :     /// Databases, schemas and tables are merged across entries and ordered by name. A table name that
     792              :     /// occurs in several entries is taken from the first entry in resolution order. Names go through a
     793              :     /// dictionary, built in resolution order, so that JS only decodes the names it renders.
     794           10 :     pub fn flatten(&self) -> FlatCatalogT {
     795              :         /// A table of the flattened catalog
     796              :         struct TableNode {
     797              :             /// The table id
     798              :             table_id: CatalogTableId,
     799              :             /// The index of the table name in the name dictionary
     800              :             name_id: usize,
     801              :             /// The index of the first column in the column nodes
     802              :             columns_begin: usize,
     803              :             /// The number of columns
     804              :             column_count: usize,
     805              :         }
     806              :         /// A schema of the flattened catalog
     807              :         struct SchemaNode {
     808              :             /// The schema id
     809              :             schema_id: CatalogSchemaId,
     810              :             /// The index of the schema name in the name dictionary
     811              :             name_id: usize,
     812              :             /// The table node indexes by table name
     813              :             children: BTreeMap<Arc<str>, usize>,
     814              :         }
     815              :         /// A database of the flattened catalog
     816              :         struct DatabaseNode {
     817              :             /// The database id
     818              :             database_id: CatalogDatabaseId,
     819              :             /// The index of the database name in the name dictionary
     820              :             name_id: usize,
     821              :             /// The schema node indexes by schema name
     822              :             children: BTreeMap<Arc<str>, usize>,
     823              :         }
     824              : 
     825           10 :         let mut name_dictionary: Vec<String> = Vec::new();
     826           10 :         let mut name_dictionary_index: HashMap<Arc<str>, usize> = HashMap::default();
     827          111 :         let mut add_name = |name: &Arc<str>| -> usize {
     828          111 :             *name_dictionary_index.entry(name.clone()).or_insert_with(|| {
     829          103 :                 name_dictionary.push(name.to_string());
     830          103 :                 name_dictionary.len() - 1
     831          103 :             })
     832          111 :         };
     833              : 
     834           10 :         let mut database_nodes: Vec<DatabaseNode> = Vec::new();
     835           10 :         let mut schema_nodes: Vec<SchemaNode> = Vec::new();
     836           10 :         let mut table_nodes: Vec<TableNode> = Vec::new();
     837              :         // (column id, name id)
     838           10 :         let mut column_nodes: Vec<(u32, usize)> = Vec::new();
     839           10 :         let mut root: BTreeMap<Arc<str>, usize> = BTreeMap::new();
     840           10 :         let mut database_node_map: HashMap<QualifiedCatalogObjectId, usize> = HashMap::default();
     841           10 :         let mut schema_node_map: HashMap<QualifiedCatalogObjectId, usize> = HashMap::default();
     842              : 
     843              :         // Visit entries in resolution order, the name dictionary must not depend on the hash implementation
     844           13 :         for (_, entry) in self.entries_ranked() {
     845           11 :             let data = entry.data();
     846           13 :             for db_ref in &data.database_references {
     847           13 :                 if database_node_map.contains_key(&db_ref.object_id) {
     848            3 :                     continue;
     849           10 :                 }
     850           10 :                 let name_id = add_name(&db_ref.database_name);
     851           10 :                 database_nodes.push(DatabaseNode {
     852           10 :                     database_id: db_ref.database_id(),
     853           10 :                     name_id,
     854           10 :                     children: BTreeMap::new(),
     855           10 :                 });
     856           10 :                 database_node_map.insert(db_ref.object_id, database_nodes.len() - 1);
     857           10 :                 root.entry(db_ref.database_name.clone()).or_insert(database_nodes.len() - 1);
     858              :             }
     859           13 :             for &i in data.schemas_by_qualified_name.values() {
     860           13 :                 let schema_ref = &data.schema_references[i];
     861           13 :                 if schema_node_map.contains_key(&schema_ref.object_id) {
     862            3 :                     continue;
     863           10 :                 }
     864           10 :                 let name_id = add_name(&schema_ref.schema_name);
     865           10 :                 schema_nodes.push(SchemaNode {
     866           10 :                     schema_id: schema_ref.schema_id(),
     867           10 :                     name_id,
     868           10 :                     children: BTreeMap::new(),
     869           10 :                 });
     870           10 :                 let schema_node = schema_nodes.len() - 1;
     871           10 :                 schema_node_map.insert(schema_ref.object_id, schema_node);
     872           10 :                 let db_node = database_node_map[&QualifiedCatalogObjectId::database(schema_ref.database_id())];
     873           10 :                 database_nodes[db_node].children.entry(schema_ref.schema_name.clone()).or_insert(schema_node);
     874              :             }
     875              :         }
     876              : 
     877              :         // Tables are not deduplicated among entries, the first entry in resolution order wins
     878           13 :         for (_, entry) in self.entries_ranked() {
     879           20 :             for (index, table) in entry.data().table_declarations.iter().enumerate() {
     880           20 :                 if self.is_absent(entry.data().catalog_entry_id, table) || entry.data().tables_by_qualified_name.get(&table.table_name.key()) != Some(&index) {
     881            1 :                     continue;
     882           19 :                 }
     883           19 :                 let schema_node = schema_node_map[&table.catalog_schema_id];
     884           19 :                 let table_name = &table.table_name.table_name.text;
     885           19 :                 if schema_nodes[schema_node].children.contains_key(table_name) {
     886            0 :                     continue;
     887           19 :                 }
     888           19 :                 let columns_begin = column_nodes.len();
     889           72 :                 for column in &table.table_columns {
     890           72 :                     let name_id = add_name(&column.column_name.text);
     891           72 :                     column_nodes.push((column.column_id(), name_id));
     892           72 :                 }
     893           19 :                 let name_id = add_name(table_name);
     894           19 :                 table_nodes.push(TableNode {
     895           19 :                     table_id: table.table_id(),
     896           19 :                     name_id,
     897           19 :                     columns_begin,
     898           19 :                     column_count: table.table_columns.len(),
     899           19 :                 });
     900           19 :                 schema_nodes[schema_node].children.insert(table_name.clone(), table_nodes.len() - 1);
     901              :             }
     902              :         }
     903              : 
     904              :         // (entry index, parent index, object id, name id, child begin, child count)
     905          111 :         let entry = |idx: usize, parent: usize, object_id: u64, name_id: usize, begin: usize, count: usize| {
     906          111 :             FlatCatalogEntryT {
     907          111 :                 flat_entry_idx: idx as u32,
     908          111 :                 flat_parent_idx: parent as u32,
     909          111 :                 catalog_object_id: object_id,
     910          111 :                 name_id: name_id as u32,
     911          111 :                 child_begin: begin as u32,
     912          111 :                 child_count: count as u32,
     913          111 :             }
     914          111 :         };
     915           10 :         let mut databases = Vec::new();
     916           10 :         let mut schemas = Vec::new();
     917           10 :         let mut tables = Vec::new();
     918           10 :         let mut columns = Vec::new();
     919           10 :         let mut databases_by_id = Vec::new();
     920           10 :         let mut schemas_by_id = Vec::new();
     921           10 :         let mut tables_by_id = Vec::new();
     922           12 :         for &db_node in root.values() {
     923           10 :             let db = &database_nodes[db_node];
     924           10 :             let db_idx = databases.len();
     925           10 :             databases.push(entry(db_idx, 0, db.database_id as u64, db.name_id, schemas.len(), db.children.len()));
     926           10 :             databases_by_id.push(IndexedFlatDatabaseEntryT {
     927           10 :                 database_id: db.database_id,
     928           10 :                 flat_entry_idx: db_idx as u32,
     929           10 :             });
     930           10 :             for &schema_node in db.children.values() {
     931           10 :                 let schema = &schema_nodes[schema_node];
     932           10 :                 let schema_idx = schemas.len();
     933           10 :                 let schema_id = schema.schema_id as u64;
     934           10 :                 schemas.push(entry(schema_idx, db_idx, schema_id, schema.name_id, tables.len(), schema.children.len()));
     935           10 :                 schemas_by_id.push(IndexedFlatSchemaEntryT {
     936           10 :                     schema_id: schema.schema_id,
     937           10 :                     flat_entry_idx: schema_idx as u32,
     938           10 :                 });
     939           20 :                 for &table_node in schema.children.values() {
     940           19 :                     let table = &table_nodes[table_node];
     941           19 :                     let table_idx = tables.len();
     942           19 :                     let table_id = table.table_id.pack();
     943           19 :                     let column_count = table.column_count;
     944           19 :                     tables.push(entry(table_idx, schema_idx, table_id, table.name_id, columns.len(), column_count));
     945           19 :                     tables_by_id.push(IndexedFlatTableEntryT {
     946           19 :                         table_id,
     947           19 :                         flat_entry_idx: table_idx as u32,
     948           19 :                     });
     949           19 :                     let table_columns = table.columns_begin..table.columns_begin + table.column_count;
     950           72 :                     for &(column_id, name_id) in &column_nodes[table_columns] {
     951           72 :                         columns.push(entry(columns.len(), table_idx, column_id as u64, name_id, 0, 0));
     952           72 :                     }
     953              :                 }
     954              :             }
     955              :         }
     956           10 :         databases_by_id.sort_by_key(|e| e.database_id);
     957           10 :         schemas_by_id.sort_by_key(|e| e.schema_id);
     958           10 :         tables_by_id.sort_by_key(|e| e.table_id);
     959              : 
     960           10 :         let mut out = FlatCatalogT::default();
     961           10 :         out.catalog_version = self.version as u64;
     962           10 :         out.name_dictionary = Some(name_dictionary);
     963           10 :         out.databases = Some(databases);
     964           10 :         out.schemas = Some(schemas);
     965           10 :         out.tables = Some(tables);
     966           10 :         out.columns = Some(columns);
     967           10 :         out.databases_by_id = Some(databases_by_id);
     968           10 :         out.schemas_by_id = Some(schemas_by_id);
     969           10 :         out.tables_by_id = Some(tables_by_id);
     970           10 :         out
     971           10 :     }
     972              : 
     973              :     /// A table by id
     974          194 :     pub fn table_by_id(&self, table_id: CatalogTableId) -> Option<&TableDeclaration> {
     975          194 :         self.entries.get(&table_id.origin)?.data().table_by_id(table_id)
     976          194 :     }
     977              : 
     978              :     /// A table by location
     979         7780 :     pub fn table(&self, loc: TableLoc) -> Option<&TableDeclaration> {
     980         7780 :         self.entries.get(&loc.entry)?.data().table_declarations.get(loc.index as usize)
     981         7780 :     }
     982              : 
     983              :     /// Did the connection report a relation that this script declaration names as gone?
     984           28 :     fn is_absent(&self, entry: CatalogEntryId, table: &TableDeclaration) -> bool {
     985           28 :         !self.absent_relations.is_empty()
     986            6 :             && self.script_entries.contains_key(&entry)
     987            5 :             && self.absent_relations.iter().any(|absent| same_relation(absent, &table.table_name.key()))
     988           28 :     }
     989              : 
     990              :     /// Is a table the declaration of a relation that is gone?
     991         2233 :     pub fn is_absent_table(&self, loc: TableLoc) -> bool {
     992         2233 :         !self.absent_relations.is_empty() && self.table(loc).is_some_and(|table| self.is_absent(loc.entry, table))
     993         2233 :     }
     994              : 
     995              :     /// Is an object of a name index part of a relation that is gone?
     996          428 :     pub fn is_absent_object(&self, object: CatalogObjectRef) -> bool {
     997          428 :         match object {
     998          132 :             CatalogObjectRef::Table(table) => self.is_absent_table(table),
     999          229 :             CatalogObjectRef::Column(column) => self.is_absent_table(column.table),
    1000           67 :             _ => false,
    1001              :         }
    1002          428 :     }
    1003              : 
    1004              :     /// Resolve a table across all entries except `ignore_entry`. The matches of the first layer that has one are
    1005              :     /// the candidates: what the connection reports doesn't compete with what a script declares, and a script doesn't
    1006              :     /// provide a relation that the connection reported as gone.
    1007         2224 :     pub fn resolve_table(&self, name: &QualifiedTableName, ignore_entry: CatalogEntryId, out: &mut Vec<TableLoc>, limit: usize) {
    1008         2224 :         self.collect_table_matches(name, ignore_entry, out, limit);
    1009         2227 :         out.retain(|loc| !self.is_absent_table(*loc));
    1010         2224 :         if let Some(first) = out.first().and_then(|loc| self.layer(loc.entry)) {
    1011         1843 :             out.retain(|loc| self.layer(loc.entry) == Some(first));
    1012          401 :         }
    1013         2224 :     }
    1014              : 
    1015              :     /// Collect the matches of a table in resolution order
    1016         2224 :     fn collect_table_matches(&self, name: &QualifiedTableName, ignore_entry: CatalogEntryId, out: &mut Vec<TableLoc>, limit: usize) {
    1017         2224 :         let db = name.database_name.text.clone();
    1018         2224 :         let schema = name.schema_name.text.clone();
    1019              :         // Direct lookups of the qualified name
    1020         2224 :         let lower = (db.clone(), schema.clone(), MIN_LAYER, 0);
    1021         2224 :         let upper = (db.clone(), schema.clone(), MAX_LAYER, CatalogEntryId::MAX);
    1022         2224 :         let key = name.key();
    1023              :         // A name with a database is exact. Any other name reads the same in every layer: `t` of a script
    1024              :         // has no qualification and would hit itself directly, ahead of the `db.schema.t` of the connection.
    1025         2224 :         if !name.database_name.text.is_empty() {
    1026          103 :             for ((_, _, _, candidate), _) in self.entries_by_qualified_schema.range(lower..=upper) {
    1027           71 :                 if *candidate == ignore_entry {
    1028            0 :                     continue;
    1029           71 :                 }
    1030           71 :                 let entry = self.entries[candidate].data();
    1031           71 :                 if let Some(&i) = entry.tables_by_qualified_name.get(&key) {
    1032           57 :                     out.push(entry.table_loc(i));
    1033           57 :                     if out.len() >= limit {
    1034            0 :                         break;
    1035           57 :                     }
    1036           14 :                 }
    1037              :             }
    1038          103 :             return;
    1039         2121 :         }
    1040         2121 :         if !name.schema_name.text.is_empty() {
    1041              :             // Table and schema name: search the entries that populate the schema in any database
    1042           42 :             let lower = (schema.clone(), MIN_LAYER, 0);
    1043           42 :             let upper = (schema.clone(), MAX_LAYER, CatalogEntryId::MAX);
    1044           42 :             for ((schema_name, _, candidate), _) in self.entries_by_schema.range(lower..=upper) {
    1045            6 :                 if *candidate == ignore_entry {
    1046            0 :                     continue;
    1047            6 :                 }
    1048            6 :                 self.entries[candidate].data().resolve_table_in_schema(schema_name, &name.table_name.text, out, limit);
    1049            6 :                 if out.len() >= limit {
    1050            0 :                     break;
    1051            6 :                 }
    1052              :             }
    1053              :         } else {
    1054              :             // Only the table name: search all entries in resolution order
    1055         2374 :             for (_, entry_id) in &self.entries_ranked {
    1056         2257 :                 if *entry_id == ignore_entry {
    1057          251 :                     continue;
    1058         2006 :                 }
    1059         2006 :                 self.entries[entry_id].data().resolve_table_everywhere(&name.table_name.text, out, limit);
    1060         2006 :                 if out.len() >= limit {
    1061            0 :                     break;
    1062         2006 :                 }
    1063              :             }
    1064              :         }
    1065         2224 :     }
    1066              : }
    1067              : 
    1068              : /// Do two relation names name the same relation? An empty database or schema matches any. An empty table names a whole
    1069              : /// schema, which only a relation with that schema is in.
    1070           33 : fn same_relation(a: &QualifiedKey, b: &QualifiedKey) -> bool {
    1071           36 :     let part = |x: &Arc<str>, y: &Arc<str>| x.is_empty() || y.is_empty() || x == y;
    1072           33 :     if a.2.is_empty() || b.2.is_empty() {
    1073            5 :         return a.1 == b.1 && part(&a.0, &b.0);
    1074           28 :     }
    1075           28 :     a.2 == b.2 && part(&a.0, &b.0) && part(&a.1, &b.1)
    1076           33 : }
    1077              : 
    1078              : /// Describe a catalog entry of a layer
    1079            4 : fn describe_entry(snapshot: &CatalogSnapshot, layer: CatalogLayer, entry: &CatalogEntryRef) -> dashql_buffers::catalog::CatalogEntryT {
    1080              :     use dashql_buffers::catalog::{CatalogEntryT, CatalogEntryType, SchemaDescriptorT, SchemaTableColumnT, SchemaTableT};
    1081            4 :     let data = entry.data();
    1082            4 :     let mut out = CatalogEntryT::default();
    1083            4 :     out.catalog_entry_id = data.catalog_entry_id;
    1084            4 :     out.catalog_entry_type = CatalogEntryType::DESCRIPTOR_POOL;
    1085            4 :     out.layer = layer;
    1086            4 :     match entry {
    1087              :         // Scripts are described as one anonymous schema with all tables
    1088              :         CatalogEntryRef::Script(_) => {
    1089            3 :             let tables = data
    1090            3 :                 .table_declarations
    1091            3 :                 .iter()
    1092            3 :                 .enumerate()
    1093            4 :                 .filter(|(_, table)| !snapshot.is_absent(data.catalog_entry_id, table))
    1094            4 :                 .map(|(i, table)| {
    1095            4 :                     let mut t = SchemaTableT::default();
    1096            4 :                     t.table_id = i as u32;
    1097            4 :                     t.table_name = Some(table.table_name.table_name.text.to_string());
    1098            4 :                     t.columns = Some(
    1099            4 :                         table
    1100            4 :                             .table_columns
    1101            4 :                             .iter()
    1102            5 :                             .map(|c| {
    1103            5 :                                 let mut column = SchemaTableColumnT::default();
    1104            5 :                                 column.column_name = Some(c.column_name.text.to_string());
    1105            5 :                                 column
    1106            5 :                             })
    1107            4 :                             .collect(),
    1108              :                     );
    1109            4 :                     t
    1110            4 :                 })
    1111            3 :                 .collect();
    1112            3 :             let mut schema = SchemaDescriptorT::default();
    1113            3 :             schema.tables = Some(tables);
    1114            3 :             out.schemas = Some(vec![schema]);
    1115              :         }
    1116              :         CatalogEntryRef::Descriptor(_) => {
    1117            1 :             let schemas = data
    1118            1 :                 .schemas_by_qualified_name
    1119            1 :                 .iter()
    1120            2 :                 .map(|((db, name), &i)| {
    1121            2 :                     let schema_id = data.schema_references[i].object_id;
    1122            2 :                     let tables = data
    1123            2 :                         .table_declarations
    1124            2 :                         .iter()
    1125            4 :                         .filter(|t| t.catalog_schema_id == schema_id)
    1126            2 :                         .map(|table| {
    1127            2 :                             let mut t = SchemaTableT::default();
    1128            2 :                             t.table_id = table.table_id().object;
    1129            2 :                             t.table_name = Some(table.table_name.table_name.text.to_string());
    1130            2 :                             t.columns = Some(
    1131            2 :                                 table
    1132            2 :                                     .table_columns
    1133            2 :                                     .iter()
    1134            2 :                                     .enumerate()
    1135            3 :                                     .map(|(i, c)| {
    1136            3 :                                         let mut column = SchemaTableColumnT::default();
    1137            3 :                                         column.column_name = Some(c.column_name.text.to_string());
    1138            3 :                                         column.ordinal_position = i as u32;
    1139            3 :                                         column
    1140            3 :                                     })
    1141            2 :                                     .collect(),
    1142              :                             );
    1143            2 :                             t
    1144            2 :                         })
    1145            2 :                         .collect();
    1146            2 :                     let mut schema = SchemaDescriptorT::default();
    1147            2 :                     schema.database_name = Some(db.to_string());
    1148            2 :                     schema.schema_name = Some(name.to_string());
    1149            2 :                     schema.tables = Some(tables);
    1150            2 :                     schema
    1151            2 :                 })
    1152            1 :                 .collect();
    1153            1 :             out.schemas = Some(schemas);
    1154              :         }
    1155              :     }
    1156            4 :     out
    1157            4 : }
    1158              : 
    1159              : impl CatalogSnapshot {
    1160              :     /// Describe all entries in resolution order
    1161            2 :     pub fn describe_entries(&self) -> dashql_buffers::catalog::CatalogEntriesT {
    1162            2 :         let mut out = dashql_buffers::catalog::CatalogEntriesT::default();
    1163            2 :         out.entries = Some(self.entries_ranked().map(|(layer, e)| describe_entry(self, layer, e)).collect());
    1164            2 :         out
    1165            2 :     }
    1166              : 
    1167              :     /// Describe one entry, None if the entry is unknown
    1168            3 :     pub fn describe_entries_of(&self, id: CatalogEntryId) -> Option<dashql_buffers::catalog::CatalogEntriesT> {
    1169            3 :         let entry = self.entries.get(&id)?;
    1170            2 :         let layer = self.layer(id)?;
    1171            2 :         let mut out = dashql_buffers::catalog::CatalogEntriesT::default();
    1172            2 :         out.entries = Some(vec![describe_entry(self, layer, entry)]);
    1173            2 :         Some(out)
    1174            3 :     }
    1175              : 
    1176              :     /// The statistics of the catalog content
    1177            3 :     pub fn statistics(&self) -> dashql_buffers::catalog::CatalogStatisticsT {
    1178            3 :         let mut content = dashql_buffers::catalog::CatalogContentStatisticsT::default();
    1179            3 :         content.database_count = self.databases.len() as u32;
    1180            3 :         content.schema_count = self.schemas.len() as u32;
    1181            3 :         content.table_count = self.entries.values().map(|e| e.data().table_declarations.len() as u32).sum();
    1182            3 :         content.table_column_count = self
    1183            3 :             .entries
    1184            3 :             .values()
    1185            3 :             .flat_map(|e| e.data().table_declarations.iter())
    1186            5 :             .map(|t| t.table_columns.len() as u32)
    1187            3 :             .sum();
    1188            3 :         let mut out = dashql_buffers::catalog::CatalogStatisticsT::default();
    1189            3 :         out.content = Some(content);
    1190            3 :         out
    1191            3 :     }
    1192              : }
        

Generated by: LCOV version 2.0-1