use fraiseql_core::{
db::{DatabaseAdapter, quote_postgres_identifier},
schema::{CompiledSchema, SourceKind, SourceProbe, sql_source_probes},
};
fn existence_sql(probe: &SourceProbe) -> String {
match probe.kind {
SourceKind::Relation => {
let ident = match &probe.schema {
Some(s) => format!(
"{}.{}",
quote_postgres_identifier(s),
quote_postgres_identifier(&probe.name)
),
None => quote_postgres_identifier(&probe.name),
};
let literal = ident.replace('\'', "''");
format!("SELECT to_regclass('{literal}') IS NOT NULL AS source_exists")
},
SourceKind::Function => {
let name = probe.name.replace('\'', "''");
match &probe.schema {
Some(s) => {
let schema = s.replace('\'', "''");
format!(
"SELECT EXISTS(SELECT 1 FROM pg_proc p \
JOIN pg_namespace n ON n.oid = p.pronamespace \
WHERE n.nspname = '{schema}' AND p.proname = '{name}' \
AND p.prokind IN ('f','p')) AS source_exists"
)
},
None => format!(
"SELECT EXISTS(SELECT 1 FROM pg_proc p \
JOIN pg_namespace n ON n.oid = p.pronamespace \
WHERE p.proname = '{name}' \
AND n.nspname = ANY(current_schemas(false)) \
AND p.prokind IN ('f','p')) AS source_exists"
),
}
},
}
}
pub async fn find_unbacked_sources<A: DatabaseAdapter>(
schema: &CompiledSchema,
adapter: &A,
) -> fraiseql_core::Result<Vec<SourceProbe>> {
let mut unbacked = Vec::new();
for probe in sql_source_probes(schema) {
let rows = adapter.execute_raw_query(&existence_sql(&probe)).await?;
let exists = rows
.first()
.and_then(|r| r.get("source_exists"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if !exists {
unbacked.push(probe);
}
}
Ok(unbacked)
}
#[must_use]
pub fn format_unbacked(unbacked: &[SourceProbe]) -> String {
use std::fmt::Write as _;
let mut out = String::from(
"fail-fast sql_source validation failed — declared sources are not backed by the database:",
);
for probe in unbacked {
let kind = match probe.kind {
SourceKind::Relation => "relation",
SourceKind::Function => "function",
};
let _ = write!(out, "\n - {} ({kind}) does not exist", probe.display_name());
}
out
}
#[cfg(test)]
#[path = "sql_source_check_tests.rs"]
mod sql_source_check_tests;