use std::collections::{BTreeSet, HashMap};
use super::embedding_graph::reads_cte;
use crate::{
db::{DatabaseAdapter, WhereClause, WhereOperator},
error::Result,
schema::CompiledSchema,
};
const TYPENAME_PROBE: &str = "SELECT EXISTS (SELECT 1 FROM pg_attribute \
WHERE attrelid = to_regclass('tviews.registry') AND attname = 'typename' \
AND NOT attisdropped) AS has_typename";
pub async fn read_by_id_sources<A: DatabaseAdapter + ?Sized>(
adapter: &A,
schema: &CompiledSchema,
) -> Result<HashMap<String, String>> {
let names: BTreeSet<String> = super::swept_view_names(schema);
if names.is_empty() {
return Ok(HashMap::new());
}
let has_typename = adapter
.execute_raw_query(TYPENAME_PROBE)
.await?
.first()
.and_then(|row| row.get("has_typename"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if !has_typename {
return Ok(HashMap::new());
}
let rows = adapter
.execute_raw_query(&format!(
"{reads}, \
tv AS (SELECT to_regclass(quote_ident(g.schema) || '.' || quote_ident(g.name))::oid \
AS rel, g.typename FROM tviews.registry g) \
SELECT r.name, tv.typename FROM roots r JOIN tv ON tv.rel = r.rel \
JOIN pg_class c ON c.oid = r.rel WHERE NOT c.relrowsecurity",
reads = reads_cte(&names),
))
.await?;
Ok(rows
.iter()
.filter_map(|row| {
let text = |key: &str| row.get(key).and_then(serde_json::Value::as_str);
Some((text("name")?.to_string(), text("typename")?.to_string()))
})
.collect())
}
pub(super) fn by_id_tag(
sources: &HashMap<String, String>,
view: &str,
where_clause: Option<&WhereClause>,
accessed_views: &[String],
) -> Option<(String, String)> {
let type_name = sources.get(view)?;
if accessed_views.len() != 1 {
return None;
}
let Some(WhereClause::NativeField {
column,
pg_cast,
operator: WhereOperator::Eq,
value,
}) = where_clause
else {
return None;
};
if column != "id" {
return None;
}
Some((type_name.clone(), id_text(pg_cast, value)?))
}
fn id_text(pg_cast: &str, value: &serde_json::Value) -> Option<String> {
let text = match value {
serde_json::Value::String(text) => text.clone(),
serde_json::Value::Number(number) => number.to_string(),
_ => return None,
};
match pg_cast {
"uuid" => uuid::Uuid::parse_str(&text).ok().map(|id| id.hyphenated().to_string()),
"int2" | "int4" | "int8" => text.trim().parse::<i64>().ok().map(|id| id.to_string()),
_ => Some(text),
}
}
#[cfg(test)]
#[path = "by_id_tests.rs"]
mod by_id_tests;