use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Table {
name: String,
schema: Option<String>,
}
impl Table {
pub fn new(name: impl Into<String>, schema: Option<impl Into<String>>) -> Self {
Self {
name: name.into(),
schema: schema.map(|s| s.into()),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn schema(&self) -> Option<&str> {
self.schema.as_deref()
}
pub fn qualified_name(&self) -> String {
if let Some(schema) = &self.schema {
format!("{}.{}", schema, self.name)
} else {
self.name.clone()
}
}
}
impl fmt::Display for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.qualified_name())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Columns {
table: String,
columns: Vec<String>,
}
impl Columns {
pub fn new(table: impl Into<String>, columns: &[impl AsRef<str>]) -> Self {
Self {
table: table.into(),
columns: columns.iter().map(|c| c.as_ref().to_string()).collect(),
}
}
pub fn table(&self) -> &str {
&self.table
}
pub fn columns(&self) -> &[String] {
&self.columns
}
pub fn columns_str(&self) -> String {
self.columns.join(", ")
}
}
impl fmt::Display for Columns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.columns_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IndexName {
table: String,
columns: Vec<String>,
suffix: String,
}
impl IndexName {
pub fn new(
table: impl Into<String>,
columns: &[impl AsRef<str>],
suffix: impl Into<String>,
) -> Self {
Self {
table: table.into(),
columns: columns.iter().map(|c| c.as_ref().to_string()).collect(),
suffix: suffix.into(),
}
}
pub fn table(&self) -> &str {
&self.table
}
pub fn columns(&self) -> &[String] {
&self.columns
}
pub fn suffix(&self) -> &str {
&self.suffix
}
pub fn generate_name(&self) -> String {
let cols = self.columns.join("_");
format!(
"{}_{}_{}_{}",
self.table,
cols,
self.suffix,
hash_name(&cols)
)
}
}
impl fmt::Display for IndexName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.generate_name())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ForeignKeyName {
from_table: String,
from_columns: Vec<String>,
to_table: String,
to_columns: Vec<String>,
suffix: String,
}
impl ForeignKeyName {
pub fn new(
from_table: impl Into<String>,
from_columns: &[impl AsRef<str>],
to_table: impl Into<String>,
to_columns: &[impl AsRef<str>],
suffix: impl Into<String>,
) -> Self {
Self {
from_table: from_table.into(),
from_columns: from_columns
.iter()
.map(|c| c.as_ref().to_string())
.collect(),
to_table: to_table.into(),
to_columns: to_columns.iter().map(|c| c.as_ref().to_string()).collect(),
suffix: suffix.into(),
}
}
pub fn from_table(&self) -> &str {
&self.from_table
}
pub fn to_table(&self) -> &str {
&self.to_table
}
pub fn generate_name(&self) -> String {
let from_cols = self.from_columns.join("_");
let to_cols = self.to_columns.join("_");
format!(
"{}_{}_{}_{}_{}_{}",
self.from_table,
from_cols,
self.to_table,
to_cols,
self.suffix,
hash_name(&format!("{}_{}", from_cols, to_cols))
)
}
}
impl fmt::Display for ForeignKeyName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.generate_name())
}
}
#[derive(Debug, Clone)]
pub struct Statement {
template: String,
params: Vec<(String, String)>,
}
impl Statement {
pub fn new(template: impl Into<String>) -> Self {
Self {
template: template.into(),
params: Vec::new(),
}
}
pub fn template(&self) -> &str {
&self.template
}
pub fn set_param(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.params.push((key.into(), value.into()));
}
pub fn render(&self) -> String {
let mut result = self.template.clone();
for (key, value) in &self.params {
let placeholder = format!("%({})s", key);
result = result.replace(&placeholder, value);
}
result
}
}
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.render())
}
}
fn hash_name(input: &str) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
input.hash(&mut hasher);
let hash = hasher.finish();
format!("{:x}", hash & 0xFFFF) }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_table_reference() {
let table = Table::new("users", None::<String>);
assert_eq!(table.name(), "users");
assert_eq!(table.schema(), None);
assert_eq!(table.qualified_name(), "users");
let schema_table = Table::new("posts", Some("blog"));
assert_eq!(schema_table.schema(), Some("blog"));
assert_eq!(schema_table.qualified_name(), "blog.posts");
}
#[test]
fn test_columns_reference() {
let columns = Columns::new("users", &["id", "name", "email"]);
assert_eq!(columns.table(), "users");
assert_eq!(columns.columns(), &["id", "name", "email"]);
assert_eq!(columns.columns_str(), "id, name, email");
}
#[test]
fn test_index_name() {
let idx = IndexName::new("users", &["email"], "idx");
let name = idx.generate_name();
assert!(name.starts_with("users"));
assert!(name.contains("email"));
assert!(name.contains("idx"));
}
#[test]
fn test_foreign_key_name() {
let fk = ForeignKeyName::new("posts", &["user_id"], "users", &["id"], "fk");
assert_eq!(fk.from_table(), "posts");
assert_eq!(fk.to_table(), "users");
let name = fk.generate_name();
assert!(name.contains("posts"));
assert!(name.contains("user_id"));
assert!(name.contains("users"));
}
#[test]
fn test_statement() {
let mut stmt = Statement::new("CREATE TABLE %(table)s (%(definition)s)");
stmt.set_param("table", "users");
stmt.set_param("definition", "id INTEGER");
let rendered = stmt.render();
assert_eq!(rendered, "CREATE TABLE users (id INTEGER)");
}
}