use crate::identifier::QualifiedName;
use crate::ir::statistic::{Statistic, StatisticColumn, StatisticKinds};
#[must_use]
pub fn create_statistic(s: &Statistic) -> String {
let mut out = format!("CREATE STATISTICS {}", s.qname.render_sql());
if !is_default_all(s.kinds) {
out.push_str(&render_kinds(s.kinds));
}
out.push_str(" ON ");
out.push_str(&render_columns(&s.columns));
out.push_str(" FROM ");
out.push_str(&s.target.render_sql());
out.push(';');
out
}
#[must_use]
pub fn drop_statistic(qname: &QualifiedName) -> String {
format!("DROP STATISTICS {};", qname.render_sql())
}
#[must_use]
pub fn replace_statistic(from: &Statistic, to: &Statistic) -> [String; 2] {
[drop_statistic(&from.qname), create_statistic(to)]
}
#[must_use]
pub fn alter_statistic_set_target(qname: &QualifiedName, value: i32) -> String {
format!(
"ALTER STATISTICS {} SET STATISTICS {value};",
qname.render_sql()
)
}
#[must_use]
pub fn comment_on_statistic(qname: &QualifiedName, comment: Option<&str>) -> String {
let body = comment.map_or_else(
|| "NULL".to_owned(),
|c| format!("'{}'", c.replace('\'', "''")),
);
format!("COMMENT ON STATISTICS {} IS {body};", qname.render_sql())
}
const fn is_default_all(k: StatisticKinds) -> bool {
k.ndistinct && k.dependencies && k.mcv
}
fn render_kinds(k: StatisticKinds) -> String {
let mut parts: Vec<&str> = Vec::new();
if k.ndistinct {
parts.push("ndistinct");
}
if k.dependencies {
parts.push("dependencies");
}
if k.mcv {
parts.push("mcv");
}
debug_assert!(
!parts.is_empty(),
"empty StatisticKinds must be caught by canon"
);
format!(" ({})", parts.join(", "))
}
fn render_columns(cols: &[StatisticColumn]) -> String {
let parts: Vec<String> = cols
.iter()
.map(|c| match c {
StatisticColumn::Column(id) => id.render_sql(),
StatisticColumn::Expression(expr) => format!("({})", expr.canonical_text),
})
.collect();
parts.join(", ")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::default_expr::NormalizedExpr;
use crate::ir::statistic::StatisticKinds;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(schema: &str, name: &str) -> QualifiedName {
QualifiedName::new(id(schema), id(name))
}
fn stat_all_kinds(cols: Vec<StatisticColumn>) -> Statistic {
Statistic {
qname: qn("app", "s"),
target: qn("app", "t"),
kinds: StatisticKinds::pg_default(),
columns: cols,
statistics_target: None,
owner: None,
comment: None,
}
}
fn stat_with_kinds(kinds: StatisticKinds, cols: Vec<StatisticColumn>) -> Statistic {
Statistic {
qname: qn("app", "s"),
target: qn("app", "t"),
kinds,
columns: cols,
statistics_target: None,
owner: None,
comment: None,
}
}
#[test]
fn create_statistic_omits_kinds_clause_when_all_enabled() {
let s = stat_all_kinds(vec![
StatisticColumn::Column(id("a")),
StatisticColumn::Column(id("b")),
]);
let sql = create_statistic(&s);
assert!(!sql.contains('('), "unexpected kinds clause in: {sql}");
assert_eq!(sql, "CREATE STATISTICS app.s ON a, b FROM app.t;");
}
#[test]
fn create_statistic_includes_kinds_clause_when_partial() {
let s = stat_with_kinds(
StatisticKinds {
ndistinct: true,
dependencies: false,
mcv: false,
},
vec![
StatisticColumn::Column(id("a")),
StatisticColumn::Column(id("b")),
],
);
let sql = create_statistic(&s);
assert!(
sql.contains("(ndistinct)"),
"expected kinds clause in: {sql}"
);
assert_eq!(
sql,
"CREATE STATISTICS app.s (ndistinct) ON a, b FROM app.t;"
);
}
#[test]
fn create_statistic_renders_expression_columns_in_parens() {
let s = stat_all_kinds(vec![
StatisticColumn::Column(id("a")),
StatisticColumn::Expression(NormalizedExpr::from_text("lower(name)")),
]);
let sql = create_statistic(&s);
assert!(
sql.contains("(lower(name))"),
"expected expr in parens: {sql}"
);
assert_eq!(
sql,
"CREATE STATISTICS app.s ON a, (lower(name)) FROM app.t;"
);
}
#[test]
fn drop_statistic_renders_correctly() {
let sql = drop_statistic(&qn("app", "s"));
assert_eq!(sql, "DROP STATISTICS app.s;");
}
#[test]
fn replace_statistic_returns_drop_then_create() {
let from = stat_all_kinds(vec![StatisticColumn::Column(id("a"))]);
let to = stat_with_kinds(
StatisticKinds {
ndistinct: true,
dependencies: false,
mcv: false,
},
vec![
StatisticColumn::Column(id("a")),
StatisticColumn::Column(id("b")),
],
);
let [drop_sql, create_sql] = replace_statistic(&from, &to);
assert_eq!(drop_sql, "DROP STATISTICS app.s;");
assert_eq!(
create_sql,
"CREATE STATISTICS app.s (ndistinct) ON a, b FROM app.t;"
);
}
#[test]
fn alter_statistic_set_target_renders_correctly() {
let sql = alter_statistic_set_target(&qn("app", "s"), 100);
assert_eq!(sql, "ALTER STATISTICS app.s SET STATISTICS 100;");
}
#[test]
fn comment_on_statistic_with_text() {
let sql = comment_on_statistic(&qn("app", "s"), Some("my comment"));
assert_eq!(sql, "COMMENT ON STATISTICS app.s IS 'my comment';");
}
#[test]
fn comment_on_statistic_null_clears() {
let sql = comment_on_statistic(&qn("app", "s"), None);
assert_eq!(sql, "COMMENT ON STATISTICS app.s IS NULL;");
}
#[test]
fn comment_on_statistic_escapes_single_quotes() {
let sql = comment_on_statistic(&qn("app", "s"), Some("it's a stat"));
assert_eq!(sql, "COMMENT ON STATISTICS app.s IS 'it''s a stat';");
}
#[test]
fn render_kinds_two_enabled() {
let k = StatisticKinds {
ndistinct: true,
dependencies: true,
mcv: false,
};
assert!(!is_default_all(k));
let s = render_kinds(k);
assert_eq!(s, " (ndistinct, dependencies)");
}
#[test]
fn is_default_all_true_when_all_enabled() {
assert!(is_default_all(StatisticKinds::pg_default()));
}
#[test]
fn is_default_all_false_when_any_disabled() {
assert!(!is_default_all(StatisticKinds {
ndistinct: true,
dependencies: true,
mcv: false
}));
assert!(!is_default_all(StatisticKinds {
ndistinct: false,
dependencies: true,
mcv: true
}));
}
}