use nodedb_types::DatabaseId;
use thiserror::Error;
use crate::types::CollectionInfo;
use crate::types_array::{ArrayAttrAst, ArrayDimAst};
pub fn normalize_regclass_name(input: &str) -> Option<String> {
fn parse_part(raw: &str) -> Option<String> {
let raw = raw.trim();
if raw.is_empty() {
return None;
}
if !raw.starts_with('"') {
return (!raw.contains('"')).then(|| raw.to_ascii_lowercase());
}
let mut chars = raw.char_indices().peekable();
chars.next();
let mut out = String::new();
let mut closed_at = None;
while let Some((idx, ch)) = chars.next() {
if ch != '"' {
out.push(ch);
continue;
}
if chars.peek().is_some_and(|(_, next)| *next == '"') {
chars.next();
out.push('"');
} else {
closed_at = Some(idx + ch.len_utf8());
break;
}
}
let end = closed_at?;
raw[end..].trim().is_empty().then_some(out)
}
let mut parts = Vec::new();
let mut quoted = false;
let mut start = 0;
let chars: Vec<(usize, char)> = input.char_indices().collect();
let mut i = 0;
while i < chars.len() {
let (idx, ch) = chars[i];
if ch == '"' {
if quoted && i + 1 < chars.len() && chars[i + 1].1 == '"' {
i += 2;
continue;
}
quoted = !quoted;
} else if ch == '.' && !quoted {
parts.push(parse_part(&input[start..idx])?);
start = idx + 1;
}
i += 1;
}
if quoted {
return None;
}
parts.push(parse_part(&input[start..])?);
match parts.as_slice() {
[name] => Some(name.clone()),
[schema, name] if schema == "public" || schema == "pg_catalog" => Some(name.clone()),
_ => None,
}
}
#[derive(Debug, Clone, Error)]
pub enum SqlCatalogError {
#[error("retryable schema change on {descriptor}")]
RetryableSchemaChanged {
descriptor: String,
},
#[error(
"collection '{name}' was dropped and is within its retention window; \
restore with UNDROP COLLECTION before {retention_expires_at_ns} ns"
)]
CollectionDeactivated {
name: String,
retention_expires_at_ns: u64,
},
}
pub trait TableProvider {
fn schema(&self) -> CollectionInfo;
fn rel_oid(&self) -> Option<i64> {
None
}
}
pub trait SqlCatalog {
fn get_collection(
&self,
database_id: DatabaseId,
name: &str,
) -> Result<Option<CollectionInfo>, SqlCatalogError>;
fn resolve_relation(
&self,
database_id: DatabaseId,
name: &str,
) -> Result<Option<CollectionInfo>, SqlCatalogError> {
self.get_collection(database_id, name)
}
fn lookup_array(&self, _name: &str) -> Option<ArrayCatalogView> {
None
}
fn array_exists(&self, name: &str) -> bool {
self.lookup_array(name).is_some()
}
fn resolve_regclass(
&self,
_database_id: nodedb_types::DatabaseId,
_tenant_id: u64,
_name: &str,
) -> Option<i64> {
None
}
fn resolve_regtype(&self, _name: &str) -> Option<i64> {
None
}
}
#[derive(Debug, Clone)]
pub struct ArrayCatalogView {
pub name: String,
pub dims: Vec<ArrayDimAst>,
pub attrs: Vec<ArrayAttrAst>,
pub tile_extents: Vec<i64>,
}