use serde::{Deserialize, Serialize};
use crate::identifier::QualifiedName;
use crate::ir::difference::Difference;
use crate::ir::eq::Equiv;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DefaultExpr {
Literal(LiteralValue),
Sequence(QualifiedName),
Expr(NormalizedExpr),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LiteralValue {
Bool(bool),
Integer(i64),
Float(f64),
Text(String),
Bytea(Vec<u8>),
Null,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NormalizedExpr {
pub canonical_text: String,
pub ast_hash: [u8; 32],
}
impl PartialOrd for NormalizedExpr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NormalizedExpr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.canonical_text.cmp(&other.canonical_text)
}
}
impl NormalizedExpr {
pub fn from_text(canonical_text: impl Into<String>) -> Self {
let canonical_text = canonical_text.into();
let ast_hash = blake3::hash(canonical_text.as_bytes()).into();
Self {
canonical_text,
ast_hash,
}
}
pub fn from_canonical_text(text: impl Into<String>) -> Self {
Self::from_text(text)
}
}
impl Equiv for DefaultExpr {
fn differences(&self, other: &Self) -> Vec<Difference> {
if self == other {
Vec::new()
} else {
vec![Difference::new("", display(self), display(other))]
}
}
}
fn display(d: &DefaultExpr) -> String {
match d {
DefaultExpr::Literal(LiteralValue::Bool(b)) => b.to_string(),
DefaultExpr::Literal(LiteralValue::Integer(i)) => i.to_string(),
DefaultExpr::Literal(LiteralValue::Float(f)) => f.to_string(),
DefaultExpr::Literal(LiteralValue::Text(t)) => format!("'{}'", t.replace('\'', "''")),
DefaultExpr::Literal(LiteralValue::Bytea(b)) => format!("\\x{}", hex(b)),
DefaultExpr::Literal(LiteralValue::Null) => "NULL".into(),
DefaultExpr::Sequence(q) => format!("nextval('{}')", q.render_sql()),
DefaultExpr::Expr(e) => e.canonical_text.clone(),
}
}
fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
use std::fmt::Write as _;
let _ = write!(s, "{b:02x}");
}
s
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identifier::Identifier;
#[test]
fn equal_text_literals_canonical_eq() {
let a = DefaultExpr::Literal(LiteralValue::Text("foo".into()));
let b = DefaultExpr::Literal(LiteralValue::Text("foo".into()));
assert!(a.canonical_eq(&b));
}
#[test]
fn different_text_literals_diff() {
let a = DefaultExpr::Literal(LiteralValue::Text("foo".into()));
let b = DefaultExpr::Literal(LiteralValue::Text("bar".into()));
let d = a.differences(&b);
assert_eq!(d.len(), 1);
}
#[test]
fn sequence_differs_from_literal() {
let q = QualifiedName::new(
Identifier::from_unquoted("app").unwrap(),
Identifier::from_unquoted("seq1").unwrap(),
);
let a = DefaultExpr::Sequence(q);
let b = DefaultExpr::Literal(LiteralValue::Integer(1));
assert!(!a.canonical_eq(&b));
}
#[test]
fn integer_and_text_literals_distinct() {
let a = DefaultExpr::Literal(LiteralValue::Integer(1));
let b = DefaultExpr::Literal(LiteralValue::Text("1".into()));
assert!(!a.canonical_eq(&b));
}
#[test]
fn null_literals_equal() {
let a = DefaultExpr::Literal(LiteralValue::Null);
let b = DefaultExpr::Literal(LiteralValue::Null);
assert!(a.canonical_eq(&b));
}
#[test]
fn normalized_expr_round_trips() {
let e = NormalizedExpr::from_text("now()");
let json = serde_json::to_string(&e).unwrap();
let back: NormalizedExpr = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
}
}