use std::time::Duration;
pub type Cell = Option<String>;
#[derive(Debug, Clone, Default)]
pub struct QueryResult {
pub columns: Vec<String>,
pub column_types: Vec<String>,
pub rows: Vec<Vec<Cell>>,
pub elapsed: Duration,
pub affected: Option<u64>,
}
impl QueryResult {
pub fn row_count(&self) -> usize {
self.rows.len()
}
pub fn summary(&self) -> String {
let milliseconds = self.elapsed.as_millis();
if self.rows.is_empty()
&& let Some(affected) = self.affected
{
let unit = if affected == 1 { "row" } else { "rows" };
return format!("{affected} {unit} affected in {milliseconds} ms");
}
let rows = self.row_count();
let unit = if rows == 1 { "row" } else { "rows" };
format!("{rows} {unit} in {milliseconds} ms")
}
}
pub fn unsupported(type_name: &str) -> Cell {
Some(format!("<{type_name}>"))
}
pub fn boolean(value: bool) -> String {
if value { "1" } else { "0" }.to_string()
}
pub fn blob(bytes: &[u8]) -> Cell {
Some(format!("<{} bytes>", bytes.len()))
}
pub fn is_placeholder(cell: &Cell) -> bool {
let Some(value) = cell else {
return false;
};
let Some(inner) = value.strip_prefix('<').and_then(|v| v.strip_suffix('>')) else {
return false;
};
if inner.contains(['<', '>']) {
return false;
}
match inner.split_once(' ') {
Some((count, "bytes")) => count.chars().all(|c| c.is_ascii_digit()),
Some(_) | None => !inner.is_empty(),
}
}
pub fn is_binary_type(type_name: &str) -> bool {
let name = type_name.trim_matches('"').to_ascii_uppercase();
let name = name.strip_suffix("[]").unwrap_or(&name);
name == "BYTEA"
|| name.ends_with("BLOB")
|| name == "BINARY"
|| name == "VARBINARY"
|| name == "GEOMETRY"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stand_ins_are_recognized() {
assert!(is_placeholder(&blob(b"abc")));
assert!(is_placeholder(&unsupported("XML")));
assert!(is_placeholder(&unsupported("INT4[]")));
assert!(!is_placeholder(&Some("plain".into())));
assert!(!is_placeholder(&None));
assert!(!is_placeholder(&Some("<a>hi</a>".into())));
assert!(!is_placeholder(&Some("<>".into())));
}
#[test]
fn booleans_are_digits() {
assert_eq!(boolean(true), "1");
assert_eq!(boolean(false), "0");
}
#[test]
fn binary_types_are_recognized() {
for name in ["BLOB", "bytea", "LONGBLOB", "VarBinary", "BYTEA[]"] {
assert!(is_binary_type(name), "{name} should be binary");
}
for name in ["TEXT", "INT4", "VARCHAR"] {
assert!(!is_binary_type(name), "{name} should not be binary");
}
}
}