use crate::error::Error;
pub trait CreateIndex<'until_build> {
fn unique(self) -> Self;
fn if_not_exists(self) -> Self;
fn add_column(self, column: &'until_build str) -> Self;
fn set_condition(self, condition: String) -> Self;
fn build(self) -> Result<String, Error>;
}
pub struct CreateIndexData<'until_build> {
pub(crate) name: &'until_build str,
pub(crate) table_name: &'until_build str,
pub(crate) unique: bool,
pub(crate) if_not_exists: bool,
pub(crate) columns: Vec<&'until_build str>,
pub(crate) condition: Option<String>,
}
pub enum CreateIndexImpl<'until_build> {
#[cfg(feature = "sqlite")]
Sqlite(CreateIndexData<'until_build>),
#[cfg(feature = "postgres")]
Postgres(CreateIndexData<'until_build>),
}
impl<'until_build> CreateIndex<'until_build> for CreateIndexImpl<'until_build> {
fn unique(mut self) -> Self {
match self {
#[cfg(feature = "sqlite")]
CreateIndexImpl::Sqlite(ref mut d) => d.unique = true,
#[cfg(feature = "postgres")]
CreateIndexImpl::Postgres(ref mut d) => d.unique = true,
};
self
}
fn if_not_exists(mut self) -> Self {
match self {
#[cfg(feature = "sqlite")]
CreateIndexImpl::Sqlite(ref mut d) => d.if_not_exists = true,
#[cfg(feature = "postgres")]
CreateIndexImpl::Postgres(ref mut d) => d.if_not_exists = true,
};
self
}
fn add_column(mut self, column: &'until_build str) -> Self {
match self {
#[cfg(feature = "sqlite")]
CreateIndexImpl::Sqlite(ref mut d) => d.columns.push(column),
#[cfg(feature = "postgres")]
CreateIndexImpl::Postgres(ref mut d) => d.columns.push(column),
}
self
}
fn set_condition(mut self, condition: String) -> Self {
match self {
#[cfg(feature = "sqlite")]
CreateIndexImpl::Sqlite(ref mut d) => d.condition = Some(condition),
#[cfg(feature = "postgres")]
CreateIndexImpl::Postgres(ref mut d) => d.condition = Some(condition),
}
self
}
fn build(self) -> Result<String, Error> {
match self {
#[cfg(feature = "sqlite")]
CreateIndexImpl::Sqlite(d) => {
if d.columns.is_empty() {
return Err(Error::SQLBuildError(format!(
"Couldn't create index on {}: Missing column(s) to create the index on",
d.table_name
)));
}
Ok(format!(
"CREATE{} INDEX{} \"{}\" ON \"{}\" ({}){};",
if d.unique { " UNIQUE" } else { "" },
if d.if_not_exists {
" IF NOT EXISTS"
} else {
""
},
d.name,
d.table_name,
quote_columns(&d.columns),
match d.condition {
None => String::from(""),
Some(cond) => format!(" WHERE {}", cond.as_str()),
}
))
}
#[cfg(feature = "postgres")]
CreateIndexImpl::Postgres(d) => {
if d.columns.is_empty() {
return Err(Error::SQLBuildError(format!(
"Couldn't create index on {}: Missing column(s) to create the index on",
d.table_name
)));
}
Ok(format!(
"CREATE{} INDEX{} \"{}\" ON \"{}\" ({}){};",
if d.unique { " UNIQUE" } else { "" },
if d.if_not_exists {
" IF NOT EXISTS"
} else {
""
},
d.name,
d.table_name,
quote_columns(&d.columns),
match d.condition {
None => String::from(""),
Some(cond) => format!(" WHERE {}", cond.as_str()),
}
))
}
}
}
}
fn quote_columns(columns: &[&str]) -> String {
columns
.iter()
.map(|column| format!("\"{column}\""))
.collect::<Vec<_>>()
.join(", ")
}
#[cfg(test)]
mod test {
use crate::create_index::CreateIndex;
use crate::DBImpl;
#[cfg(feature = "sqlite")]
#[test]
fn single_column_sqlite() {
assert_eq!(
DBImpl::SQLite
.create_index("user_login_idx", "user")
.add_column("login")
.build()
.unwrap(),
r#"CREATE INDEX "user_login_idx" ON "user" ("login");"#
);
}
#[cfg(feature = "postgres")]
#[test]
fn single_column_postgres() {
assert_eq!(
DBImpl::Postgres
.create_index("user_login_idx", "user")
.add_column("login")
.build()
.unwrap(),
r#"CREATE INDEX "user_login_idx" ON "user" ("login");"#
);
}
#[cfg(feature = "sqlite")]
#[test]
fn multiple_columns_sqlite() {
assert_eq!(
DBImpl::SQLite
.create_index("user_full_name_idx", "user")
.add_column("last_name")
.add_column("first_name")
.build()
.unwrap(),
r#"CREATE INDEX "user_full_name_idx" ON "user" ("last_name", "first_name");"#
);
}
#[cfg(feature = "postgres")]
#[test]
fn multiple_columns_postgres() {
assert_eq!(
DBImpl::Postgres
.create_index("user_full_name_idx", "user")
.add_column("last_name")
.add_column("first_name")
.build()
.unwrap(),
r#"CREATE INDEX "user_full_name_idx" ON "user" ("last_name", "first_name");"#
);
}
#[cfg(feature = "sqlite")]
#[test]
fn unique_and_if_not_exists_sqlite() {
assert_eq!(
DBImpl::SQLite
.create_index("user_login_idx", "user")
.add_column("login")
.unique()
.if_not_exists()
.build()
.unwrap(),
r#"CREATE UNIQUE INDEX IF NOT EXISTS "user_login_idx" ON "user" ("login");"#
);
}
#[cfg(feature = "sqlite")]
#[test]
fn condition_sqlite() {
assert_eq!(
DBImpl::SQLite
.create_index("user_login_idx", "user")
.add_column("login")
.set_condition("\"login\" IS NOT NULL".to_string())
.build()
.unwrap(),
r#"CREATE INDEX "user_login_idx" ON "user" ("login") WHERE "login" IS NOT NULL;"#
);
}
#[cfg(feature = "sqlite")]
#[test]
fn missing_columns_is_an_error() {
assert!(DBImpl::SQLite
.create_index("user_login_idx", "user")
.build()
.is_err());
}
}