nodedb_sql/catalog.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! `SqlCatalog` trait + descriptor-resolution error type.
4
5use nodedb_types::DatabaseId;
6use thiserror::Error;
7
8use crate::types::CollectionInfo;
9use crate::types_array::{ArrayAttrAst, ArrayDimAst};
10
11/// Errors surfaced by `SqlCatalog` implementations.
12///
13/// Only one variant today — callers pattern-match directly and
14/// map the retryable case to `SqlError::RetryableSchemaChanged`
15/// via the `From` impl in `error.rs`. The enum shape is kept
16/// despite having a single variant so future variants can be
17/// added without a breaking change.
18#[derive(Debug, Clone, Error)]
19pub enum SqlCatalogError {
20 /// A DDL drain is in progress on the descriptor at the
21 /// version the planner wanted to acquire a lease on. Callers
22 /// should retry the whole plan after a short backoff — by
23 /// then either the drain has completed (new descriptor
24 /// version available in the cache) or the retry budget is
25 /// exhausted and a typed error surfaces to the client.
26 #[error("retryable schema change on {descriptor}")]
27 RetryableSchemaChanged {
28 /// Human-readable identifier for the descriptor, e.g.
29 /// `"collection orders"`. Used in log / trace output.
30 descriptor: String,
31 },
32
33 /// Collection is soft-deleted (`DROP COLLECTION` run, retention
34 /// window still active). Distinct from `Ok(None)` = absent so the
35 /// planner can surface an actionable error with an `UNDROP`
36 /// hint rather than a generic "unknown table".
37 #[error(
38 "collection '{name}' was dropped and is within its retention window; \
39 restore with UNDROP COLLECTION before {retention_expires_at_ns} ns"
40 )]
41 CollectionDeactivated {
42 name: String,
43 /// Wall-clock nanoseconds when retention elapses and the
44 /// collection is hard-deleted by the GC sweeper.
45 retention_expires_at_ns: u64,
46 },
47}
48
49/// Trait for looking up collection metadata during planning.
50///
51/// Both Origin (via CredentialStore) and Lite (via the embedded
52/// redb catalog) implement this trait.
53///
54/// The return type is `Result<Option<CollectionInfo>, _>` with
55/// a three-way semantics:
56///
57/// - `Ok(Some(info))` — the collection exists and is usable.
58/// An Origin implementation will have acquired a descriptor
59/// lease at the current version before returning; subsequent
60/// planning against the same collection within the lease
61/// window is drain-safe.
62/// - `Ok(None)` — the collection does not exist. Callers should
63/// surface this as `SqlError::UnknownTable`.
64/// - `Err(SqlCatalogError::RetryableSchemaChanged { .. })` —
65/// the collection exists but a DDL drain is in progress.
66/// Callers propagate this up so the pgwire layer can retry
67/// the whole statement.
68pub trait SqlCatalog {
69 fn get_collection(
70 &self,
71 database_id: DatabaseId,
72 name: &str,
73 ) -> Result<Option<CollectionInfo>, SqlCatalogError>;
74
75 /// Look up an array by name. Returns `None` if no array with that
76 /// name is registered. The default implementation returns `None` so
77 /// that catalog adapters predating array support compile without
78 /// change — the array DML planner falls back to "array not found"
79 /// in that case.
80 fn lookup_array(&self, _name: &str) -> Option<ArrayCatalogView> {
81 None
82 }
83
84 /// Cheap existence check; the default delegates to `lookup_array`.
85 fn array_exists(&self, name: &str) -> bool {
86 self.lookup_array(name).is_some()
87 }
88}
89
90/// View of a registered array, surfaced to the SQL planner. Decoded by
91/// the runtime catalog adapter from its persisted msgpack schema blob;
92/// keeps `nodedb-sql` free of any dependency on `nodedb-array`.
93#[derive(Debug, Clone)]
94pub struct ArrayCatalogView {
95 pub name: String,
96 pub dims: Vec<ArrayDimAst>,
97 pub attrs: Vec<ArrayAttrAst>,
98 pub tile_extents: Vec<i64>,
99}