use reinhardt_query::prelude::{
Alias, ColumnDef, CreateTableStatement, ForeignKey, ForeignKeyAction, Query,
};
use uuid::Uuid;
pub struct DclTracker {
objects: Vec<String>,
}
impl DclTracker {
pub fn new() -> Self {
Self {
objects: Vec::new(),
}
}
pub fn track(&mut self, object_name: String) {
self.objects.push(object_name);
}
pub fn cleanup_list(&mut self) -> Vec<String> {
std::mem::take(&mut self.objects)
}
}
impl Default for DclTracker {
fn default() -> Self {
Self::new()
}
}
#[rstest::fixture]
pub fn dcl_tracker() -> DclTracker {
DclTracker::new()
}
fn unique_suffix() -> String {
let s = Uuid::now_v7().simple().to_string();
s[s.len() - 12..].to_string()
}
pub fn dcl_test_table() -> String {
format!("dcl_test_{}", unique_suffix())
}
pub fn test_role() -> String {
format!("test_role_{}", unique_suffix())
}
pub fn test_role_with_attrs() -> (String, String) {
let role_name = format!("test_role_attrs_{}", unique_suffix());
let attributes = "LOGIN,CREATEDB".to_string();
(role_name, attributes)
}
pub fn test_user() -> String {
format!("test_user_{}", unique_suffix())
}
pub fn test_user_with_password() -> (String, String) {
let suffix = unique_suffix();
let user_name = format!("test_user_pass_{}", suffix);
let password = format!("test_password_{}", suffix);
(user_name, password)
}
pub fn test_database() -> String {
format!("test_db_{}", unique_suffix())
}
pub fn test_schema() -> String {
format!("test_schema_{}", unique_suffix())
}
pub fn dcl_test_table_stmt() -> CreateTableStatement {
let table_name = dcl_test_table();
let mut stmt = Query::create_table();
stmt.table(Alias::new(&table_name))
.col(
ColumnDef::new(Alias::new("id"))
.big_integer()
.not_null(true)
.primary_key(true),
)
.col(
ColumnDef::new(Alias::new("name"))
.string_len(100)
.not_null(true),
)
.col(ColumnDef::new(Alias::new("value")).text())
.col(ColumnDef::new(Alias::new("created_at")).timestamp());
stmt.take()
}
pub fn dcl_test_table_with_fk() -> (CreateTableStatement, CreateTableStatement, String, String) {
let suffix = unique_suffix();
let parent_name = format!("dcl_test_parent_{}", suffix);
let child_name = format!("dcl_test_child_{}", suffix);
let mut parent_stmt = Query::create_table();
parent_stmt
.table(Alias::new(&parent_name))
.col(
ColumnDef::new(Alias::new("id"))
.big_integer()
.not_null(true)
.primary_key(true),
)
.col(
ColumnDef::new(Alias::new("name"))
.string_len(100)
.not_null(true),
);
let mut fk = ForeignKey::create();
fk.name(Alias::new(format!("fk_{}_parent", child_name)))
.from_tbl(Alias::new(&child_name))
.from_col(Alias::new("parent_id"))
.to_tbl(Alias::new(&parent_name))
.to_col(Alias::new("id"))
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade);
let mut child_stmt = Query::create_table();
child_stmt
.table(Alias::new(&child_name))
.col(
ColumnDef::new(Alias::new("id"))
.big_integer()
.not_null(true)
.primary_key(true),
)
.col(
ColumnDef::new(Alias::new("parent_id"))
.big_integer()
.not_null(true),
)
.col(ColumnDef::new(Alias::new("value")).text())
.foreign_key_from_builder(&mut fk);
(
parent_stmt.take(),
child_stmt.take(),
parent_name,
child_name,
)
}
#[cfg(test)]
mod tests {
use super::*;
use reinhardt_query::prelude::{
MySqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder,
};
use rstest::rstest;
#[rstest]
fn test_dcl_test_table_format() {
let table = dcl_test_table();
assert!(table.starts_with("dcl_test_"));
}
#[rstest]
fn test_dcl_test_table_uniqueness() {
let table1 = dcl_test_table();
let table2 = dcl_test_table();
assert_ne!(table1, table2, "Each call must generate a unique name");
}
#[rstest]
fn test_test_role_format() {
let role = test_role();
assert!(role.starts_with("test_role_"));
}
#[rstest]
fn test_test_role_with_attrs_format() {
let (role, attrs) = test_role_with_attrs();
assert!(role.starts_with("test_role_attrs_"));
assert_eq!(attrs, "LOGIN,CREATEDB");
}
#[rstest]
fn test_test_user_format() {
let user = test_user();
assert!(user.starts_with("test_user_"));
}
#[rstest]
fn test_test_user_with_password_format() {
let (user, pass) = test_user_with_password();
assert!(user.starts_with("test_user_pass_"));
assert!(pass.starts_with("test_password_"));
}
#[rstest]
fn test_test_database_format() {
let db = test_database();
assert!(db.starts_with("test_db_"));
}
#[rstest]
fn test_test_schema_format() {
let schema = test_schema();
assert!(schema.starts_with("test_schema_"));
}
#[rstest]
fn test_dcl_tracker_tracks_objects() {
let mut tracker = DclTracker::new();
let role = test_role();
let user = test_user();
let table = dcl_test_table();
tracker.track(format!("ROLE:{}", role));
tracker.track(format!("USER:{}", user));
tracker.track(format!("TABLE:{}", table));
let cleanup = tracker.cleanup_list();
assert_eq!(cleanup.len(), 3);
assert!(cleanup.iter().any(|o| o.starts_with("ROLE:")));
assert!(cleanup.iter().any(|o| o.starts_with("USER:")));
assert!(cleanup.iter().any(|o| o.starts_with("TABLE:")));
}
#[rstest]
fn test_dcl_tracker_clears_after_cleanup() {
let mut tracker = DclTracker::new();
tracker.track(format!("ROLE:{}", test_role()));
tracker.track(format!("USER:{}", test_user()));
let cleanup1 = tracker.cleanup_list();
let cleanup2 = tracker.cleanup_list();
assert_eq!(cleanup1.len(), 2);
assert_eq!(cleanup2.len(), 0);
}
#[rstest]
fn test_dcl_test_table_stmt_generates_valid_sql() {
let stmt = dcl_test_table_stmt();
assert!(
stmt.to_string(PostgresQueryBuilder::new())
.contains("CREATE TABLE")
);
assert!(
stmt.to_string(MySqlQueryBuilder::new())
.contains("CREATE TABLE")
);
}
#[rstest]
fn test_dcl_test_table_with_fk_format() {
let (parent_stmt, child_stmt, parent_name, child_name) = dcl_test_table_with_fk();
assert!(parent_name.starts_with("dcl_test_parent_"));
assert!(child_name.starts_with("dcl_test_child_"));
let parent_sql = parent_stmt.to_string(PostgresQueryBuilder::new());
assert!(parent_sql.contains("CREATE TABLE"));
assert!(parent_sql.contains(&parent_name));
let child_sql = child_stmt.to_string(PostgresQueryBuilder::new());
assert!(child_sql.contains("CREATE TABLE"));
assert!(child_sql.contains(&child_name));
assert!(child_sql.contains("FOREIGN KEY"));
}
#[rstest]
fn test_dcl_test_table_with_fk_uses_same_suffix() {
let (_parent_stmt, _child_stmt, parent_name, child_name) = dcl_test_table_with_fk();
let parent_suffix = parent_name.strip_prefix("dcl_test_parent_").unwrap();
let child_suffix = child_name.strip_prefix("dcl_test_child_").unwrap();
assert_eq!(parent_suffix, child_suffix);
}
}