use alloc::format;
use alloc::string::String;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Identifier {
inner: String,
}
impl Identifier {
#[must_use]
pub fn new(raw: &str) -> Self {
Self {
inner: String::from(raw),
}
}
#[must_use]
pub fn to_str(&self) -> &str {
&self.inner
}
#[must_use]
pub fn quoted(&self) -> String {
format!("\"{}\"", self.inner.replace('"', "\"\""))
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.quoted())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SchemaId {
name: Identifier,
}
impl SchemaId {
#[must_use]
pub const fn new(name: Identifier) -> Self {
Self { name }
}
#[must_use]
pub const fn name(&self) -> &Identifier {
&self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TableId {
name: Identifier,
}
impl TableId {
#[must_use]
pub const fn new(name: Identifier) -> Self {
Self { name }
}
#[must_use]
pub const fn name(&self) -> &Identifier {
&self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ColumnId {
name: Identifier,
}
impl ColumnId {
#[must_use]
pub const fn new(name: Identifier) -> Self {
Self { name }
}
#[must_use]
pub const fn name(&self) -> &Identifier {
&self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DbTableId {
schema: SchemaId,
table: TableId,
}
impl DbTableId {
#[must_use]
pub const fn new(schema: SchemaId, table: TableId) -> Self {
Self { schema, table }
}
#[must_use]
pub const fn schema(&self) -> &SchemaId {
&self.schema
}
#[must_use]
pub const fn table(&self) -> &TableId {
&self.table
}
#[must_use]
pub fn qualified(&self) -> String {
format!(
"{}.{}",
self.schema.name().quoted(),
self.table.name().quoted()
)
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::{ColumnId, DbTableId, Identifier, SchemaId, TableId};
#[test]
fn display_renders_the_quoted_name() {
let id = Identifier::new("we\"ird");
assert_eq!(format!("{id}"), id.quoted());
}
#[test]
fn column_names_are_case_sensitive() {
let upper = ColumnId::new(Identifier::new("SUBJECT"));
let lower = ColumnId::new(Identifier::new("subject"));
assert_ne!(upper, lower);
assert_eq!(upper.name().quoted(), "\"SUBJECT\"");
}
#[test]
fn a_loading_mirror_keeps_the_schema_and_suffixes_the_table() {
let target = DbTableId::new(
SchemaId::new(Identifier::new("ZOHO_DESK")),
TableId::new(Identifier::new("TICKETS")),
);
let loading = DbTableId::new(
target.schema().clone(),
TableId::new(Identifier::new(&format!(
"{}_LOADING",
target.table().name().to_str()
))),
);
assert_eq!(loading.qualified(), "\"ZOHO_DESK\".\"TICKETS_LOADING\"");
}
#[test]
fn identifier_escapes_double_quotes() {
let id = Identifier::new("we\"ird");
assert_eq!(id.to_str(), "we\"ird");
assert_eq!(id.quoted(), "\"we\"\"ird\"");
}
#[test]
fn identifier_keeps_percent_literal() {
let id = Identifier::new("a%b");
assert_eq!(id.quoted(), "\"a%b\"");
}
#[test]
fn db_table_id_renders_qualified_name() {
let table = DbTableId::new(
SchemaId::new(Identifier::new("observes")),
TableId::new(Identifier::new("sca_advisories")),
);
assert_eq!(table.qualified(), "\"observes\".\"sca_advisories\"");
}
#[test]
fn db_table_id_qualified_escapes_each_part() {
let table = DbTableId::new(
SchemaId::new(Identifier::new("we\"ird")),
TableId::new(Identifier::new("t")),
);
assert_eq!(table.qualified(), "\"we\"\"ird\".\"t\"");
}
}