use std::fmt::Write as _;
use std::ops::Deref;
use uni_locy::{CompiledProgram, DerivedFactSet};
use uni_query::QueryMetrics;
#[derive(Debug, Clone)]
pub struct LocyResult {
inner: uni_locy::LocyResult,
metrics: QueryMetrics,
}
impl LocyResult {
pub(crate) fn new(inner: uni_locy::LocyResult, metrics: QueryMetrics) -> Self {
Self { inner, metrics }
}
pub fn metrics(&self) -> &QueryMetrics {
&self.metrics
}
pub fn derived(&self) -> Option<&DerivedFactSet> {
self.inner.derived_fact_set.as_ref()
}
pub fn into_inner(self) -> uni_locy::LocyResult {
self.inner
}
pub fn into_parts(self) -> (uni_locy::LocyResult, QueryMetrics) {
(self.inner, self.metrics)
}
}
impl Deref for LocyResult {
type Target = uni_locy::LocyResult;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug, Clone)]
pub struct LocyExplainOutput {
pub plan_text: String,
pub strata_count: usize,
pub rule_names: Vec<String>,
pub has_recursive_strata: bool,
pub warnings: Vec<String>,
pub command_count: usize,
}
impl LocyExplainOutput {
pub(crate) fn from_compiled(compiled: &CompiledProgram) -> Self {
let strata_count = compiled.strata.len();
let rule_names: Vec<String> = compiled.rule_catalog.keys().cloned().collect();
let has_recursive_strata = compiled.strata.iter().any(|s| s.is_recursive);
let warnings: Vec<String> = compiled
.warnings
.iter()
.map(|w| w.message.clone())
.collect();
let command_count = compiled.commands.len();
let plan_text = format_locy_plan(compiled);
Self {
plan_text,
strata_count,
rule_names,
has_recursive_strata,
warnings,
command_count,
}
}
}
fn format_locy_plan(compiled: &CompiledProgram) -> String {
let mut out = String::new();
for stratum in &compiled.strata {
let kind = if stratum.is_recursive {
"recursive"
} else {
"non-recursive"
};
let deps = if stratum.depends_on.is_empty() {
String::new()
} else {
let ids: Vec<String> = stratum.depends_on.iter().map(|d| d.to_string()).collect();
format!(", depends on [{}]", ids.join(", "))
};
let _ = writeln!(out, "Stratum {} ({kind}{deps}):", stratum.id);
for rule in &stratum.rules {
let clause_count = rule.clauses.len();
let plural = if clause_count == 1 { "" } else { "s" };
let _ = writeln!(out, " rule: {} ({clause_count} clause{plural})", rule.name);
}
}
let cmd_count = compiled.commands.len();
if cmd_count > 0 {
let plural = if cmd_count == 1 { "" } else { "s" };
let _ = writeln!(out, "Commands: {cmd_count} command{plural}");
}
if !compiled.warnings.is_empty() {
let _ = writeln!(out, "Warnings:");
for w in &compiled.warnings {
let _ = writeln!(out, " - [{}] {}", w.rule_name, w.message);
}
}
out
}