use anyhow::Result;
use std::fs::{self, File, OpenOptions};
use std::io::Write;
use std::path::Path;
pub fn quote_identifier(name: &str, quote: &str) -> String {
if name.contains('.') {
name.split('.')
.map(|part| format!("{}{}{}", quote, part, quote))
.collect::<Vec<_>>()
.join(".")
} else {
format!("{}{}{}", quote, name, quote)
}
}
pub async fn write_to_schema_file(
content: String,
migrations_folder: String,
schema_file: String,
) -> Result<()> {
let schema_path = format!("{}/{}", migrations_folder, schema_file);
let path = Path::new(schema_path.as_str());
if File::open(path.to_str().unwrap()).is_err() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
File::create(&schema_path)?;
};
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(schema_path.as_str())?;
file.write_all(content.as_bytes())?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn test_write_to_schema_file_new_file() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir.path().to_str().unwrap().to_string();
let schema_file = "test_schema.sql".to_string();
let content = "CREATE TABLE users (id INTEGER PRIMARY KEY);".to_string();
let result = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result.is_ok());
let schema_path = format!("{}/{}", migrations_folder, schema_file);
let file_content = std::fs::read_to_string(&schema_path).unwrap();
assert_eq!(file_content, content);
}
#[tokio::test]
async fn test_write_to_schema_file_overwrite() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir.path().to_str().unwrap().to_string();
let schema_file = "overwrite_schema.sql".to_string();
let initial_content = "CREATE TABLE test1 (id INTEGER);".to_string();
let result1 = write_to_schema_file(
initial_content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result1.is_ok());
let new_content = "CREATE TABLE test2 (id INTEGER, name TEXT);".to_string();
let result2 = write_to_schema_file(
new_content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result2.is_ok());
let schema_path = format!("{}/{}", migrations_folder, schema_file);
let file_content = std::fs::read_to_string(&schema_path).unwrap();
assert_eq!(file_content, new_content);
assert_ne!(file_content, initial_content);
}
#[tokio::test]
async fn test_write_to_schema_file_nested_directory() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir
.path()
.join("nested")
.join("path")
.to_str()
.unwrap()
.to_string();
let schema_file = "nested_schema.sql".to_string();
let content = "CREATE INDEX idx_users_email ON users(email);".to_string();
let result = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result.is_ok());
let schema_path = format!("{}/{}", migrations_folder, schema_file);
assert!(Path::new(&schema_path).exists());
let file_content = std::fs::read_to_string(&schema_path).unwrap();
assert_eq!(file_content, content);
}
#[tokio::test]
async fn test_write_to_schema_file_empty_content() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir.path().to_str().unwrap().to_string();
let schema_file = "empty_schema.sql".to_string();
let content = "".to_string();
let result = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result.is_ok());
let schema_path = format!("{}/{}", migrations_folder, schema_file);
let file_content = std::fs::read_to_string(&schema_path).unwrap();
assert_eq!(file_content, "");
}
#[tokio::test]
async fn test_write_to_schema_file_large_content() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir.path().to_str().unwrap().to_string();
let schema_file = "large_schema.sql".to_string();
let mut content = String::new();
for i in 0..1000 {
content.push_str(&format!(
"CREATE TABLE table_{} (id INTEGER PRIMARY KEY);\n",
i
));
}
let result = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result.is_ok());
let schema_path = format!("{}/{}", migrations_folder, schema_file);
let file_content = std::fs::read_to_string(&schema_path).unwrap();
assert_eq!(file_content, content);
assert!(file_content.len() > 10000); }
#[tokio::test]
async fn test_write_to_schema_file_special_characters() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir.path().to_str().unwrap().to_string();
let schema_file = "special_schema.sql".to_string();
let content =
"CREATE TABLE test (id INTEGER, data TEXT DEFAULT 'special chars: éñ中文🚀');"
.to_string();
let result = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result.is_ok());
let schema_path = format!("{}/{}", migrations_folder, schema_file);
let file_content = std::fs::read_to_string(&schema_path).unwrap();
assert_eq!(file_content, content);
}
#[tokio::test]
async fn test_write_to_schema_file_path_formatting() {
let tmp_dir = tempdir().unwrap();
let migrations_folder = tmp_dir.path().to_str().unwrap().to_string();
let schema_file = "path_test.sql".to_string();
let content = "SELECT 1;".to_string();
let result = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file.clone(),
)
.await;
assert!(result.is_ok());
let expected_path = format!("{}/{}", migrations_folder, schema_file);
assert!(Path::new(&expected_path).exists());
let schema_file_with_subdir = "subdir/schema.sql".to_string();
let result2 = write_to_schema_file(
content.clone(),
migrations_folder.clone(),
schema_file_with_subdir.clone(),
)
.await;
assert!(result2.is_ok());
let expected_path2 = format!("{}/{}", migrations_folder, schema_file_with_subdir);
assert!(Path::new(&expected_path2).exists());
}
#[test]
fn test_quote_identifier_simple() {
assert_eq!(quote_identifier("users", "\""), "\"users\"");
assert_eq!(
quote_identifier("schema_migrations", "`"),
"`schema_migrations`"
);
}
#[test]
fn test_quote_identifier_schema_qualified() {
assert_eq!(
quote_identifier("migrations.migrations", "\""),
"\"migrations\".\"migrations\""
);
assert_eq!(
quote_identifier("my_schema.my_table", "`"),
"`my_schema`.`my_table`"
);
}
#[test]
fn test_quote_identifier_multiple_dots() {
assert_eq!(quote_identifier("a.b.c", "\""), "\"a\".\"b\".\"c\"");
}
#[test]
fn test_quote_identifier_empty() {
assert_eq!(quote_identifier("", "\""), "\"\"");
}
}