use anyhow::{Context, Result};
use sqlx::PgPool;
#[allow(dead_code)]
fn sanitize_identifier(identifier: &str) -> Result<String> {
if !identifier
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
{
anyhow::bail!(
"Invalid identifier \"{}\": contains illegal characters",
identifier
);
}
let escaped = identifier.replace('"', "\"\"");
Ok(format!("\"{}\"", escaped))
}
#[allow(dead_code)]
pub async fn database_exists(pool: &PgPool, database_name: &str) -> Result<bool> {
let exists: bool =
sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)")
.bind(database_name)
.fetch_one(pool)
.await
.context("Failed to check if database exists")?;
Ok(exists)
}
#[allow(dead_code)]
pub async fn create_database(pool: &PgPool, database_name: &str, owner: &str) -> Result<()> {
let sanitized_db = sanitize_identifier(database_name)?;
let sanitized_owner = sanitize_identifier(owner)?;
let create_sql = format!("CREATE DATABASE {} OWNER {}", sanitized_db, sanitized_owner);
sqlx::query(&create_sql)
.execute(pool)
.await
.context("Failed to create database")?;
Ok(())
}
#[allow(dead_code)]
pub async fn user_exists(pool: &PgPool, username: &str) -> Result<bool> {
let exists: bool =
sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname = $1)")
.bind(username)
.fetch_one(pool)
.await
.context("Failed to check if user exists")?;
Ok(exists)
}
#[allow(dead_code)]
pub async fn create_user(pool: &PgPool, username: &str, password: &str) -> Result<()> {
let sanitized_username = sanitize_identifier(username)?;
let escaped_password = password.replace('\'', "''");
let create_sql = format!(
"CREATE USER {} WITH PASSWORD '{}'",
sanitized_username, escaped_password
);
sqlx::query(&create_sql)
.execute(pool)
.await
.context("Failed to create user")?;
Ok(())
}
#[allow(dead_code)]
pub async fn update_user_password(pool: &PgPool, username: &str, password: &str) -> Result<()> {
let sanitized_username = sanitize_identifier(username)?;
let escaped_password = password.replace('\'', "''");
let alter_sql = format!(
"ALTER USER {} WITH PASSWORD '{}'",
sanitized_username, escaped_password
);
sqlx::query(&alter_sql)
.execute(pool)
.await
.context("Failed to update user password")?;
Ok(())
}
#[allow(dead_code)]
pub async fn change_database_owner(
pool: &PgPool,
database_name: &str,
new_owner: &str,
) -> Result<()> {
let sanitized_db = sanitize_identifier(database_name)?;
let sanitized_owner = sanitize_identifier(new_owner)?;
let alter_sql = format!(
"ALTER DATABASE {} OWNER TO {}",
sanitized_db, sanitized_owner
);
sqlx::query(&alter_sql)
.execute(pool)
.await
.context("Failed to change database owner")?;
Ok(())
}
#[allow(dead_code)]
pub async fn drop_database(pool: &PgPool, database_name: &str) -> Result<()> {
let sanitized_db = sanitize_identifier(database_name)?;
let drop_sql = format!("DROP DATABASE {}", sanitized_db);
sqlx::query(&drop_sql)
.execute(pool)
.await
.context("Failed to drop database")?;
Ok(())
}
#[allow(dead_code)]
pub async fn drop_user(pool: &PgPool, username: &str) -> Result<()> {
let sanitized_username = sanitize_identifier(username)?;
let drop_sql = format!("DROP USER {}", sanitized_username);
sqlx::query(&drop_sql)
.execute(pool)
.await
.context("Failed to drop user")?;
Ok(())
}