use crate::client::ClickHouse;
use crate::config::ConfigError;
use crate::error::ClickHouseError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Migration {
pub name: String,
pub ddl: String,
}
impl Migration {
pub fn new(name: impl Into<String>, ddl: impl Into<String>) -> Self {
Self {
name: name.into(),
ddl: ddl.into(),
}
}
pub fn create_table(name: &str, columns: &str) -> Result<Self, ConfigError> {
if !is_table_name(name) {
return Err(ConfigError(format!(
"migration `{name}`: the table name is not an identifier"
)));
}
Ok(Self {
name: format!("create table {name}"),
ddl: format!(
"CREATE TABLE IF NOT EXISTS {name} ({columns}) ENGINE = MergeTree() ORDER BY tuple()"
),
})
}
}
fn is_table_name(name: &str) -> bool {
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
if !(first.is_ascii_alphabetic() || first == '_') {
return false;
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
}
pub async fn migrate(db: &ClickHouse, migrations: &[Migration]) -> Result<(), ClickHouseError> {
for migration in migrations {
db.execute(&migration.ddl).await.map_err(|err| {
let detail = err.detail().map_or_else(|| err.to_string(), str::to_owned);
ClickHouseError::Migration {
name: migration.name.clone(),
detail,
}
})?;
}
Ok(())
}
#[cfg(test)]
mod tests;