use serde::{Deserialize, Serialize};
use super::{Attribute, Field, SourceSpan};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct View {
pub docs: Vec<String>,
pub name: String,
pub name_span: SourceSpan,
pub sources: Vec<ViewSource>,
pub fields: Vec<Field>,
pub attributes: Vec<Attribute>,
pub span: SourceSpan,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ViewSource {
pub name: String,
pub name_span: SourceSpan,
}
impl View {
pub fn server_sql(&self) -> Option<&str> {
self.body_attribute("@@server_sql")
.or_else(|| self.body_attribute("@@sql"))
}
pub fn embedded_sql(&self) -> Option<&str> {
self.body_attribute("@@embedded_sql")
.or_else(|| self.body_attribute("@@sql"))
}
pub fn is_materialized(&self) -> bool {
self.has_bare_attribute("@@materialized")
}
pub fn no_unique(&self) -> bool {
self.has_bare_attribute("@@no_unique")
}
fn body_attribute(&self, prefix: &str) -> Option<&str> {
self.attributes
.iter()
.filter(|attr| attr.raw.starts_with(prefix))
.find_map(|attr| extract_sql_body(&attr.raw, prefix))
}
fn has_bare_attribute(&self, name: &str) -> bool {
self.attributes.iter().any(|attr| {
let trimmed = attr.raw.trim();
trimmed == name || trimmed.starts_with(&format!("{name}(")) || trimmed == name
})
}
}
fn extract_sql_body<'a>(raw: &'a str, prefix: &str) -> Option<&'a str> {
let after_prefix = raw.strip_prefix(prefix)?.trim_start();
let inside_parens = after_prefix
.strip_prefix('(')?
.rsplit_once(')')
.map(|(body, _tail)| body)?
.trim();
if let Some(rest) = inside_parens.strip_prefix("\"\"\"") {
rest.strip_suffix("\"\"\"")
} else if let Some(rest) = inside_parens.strip_prefix('"') {
rest.strip_suffix('"')
} else {
None
}
}