pub mod create;
pub mod ddl;
pub mod diff;
pub mod expected;
pub mod introspect;
pub mod migrate;
pub use create::{CreateTable, SchemaBuilder};
pub use ddl::{
DdlGenerator, MysqlDdlGenerator, PostgresDdlGenerator, SqliteDdlGenerator,
generator_for_dialect,
};
pub use expected::{
ModelSchema, ModelTuple, expected_schema, normalize_sql_type, table_schema_from_fields,
table_schema_from_model,
};
pub use introspect::{
CheckConstraintInfo, ColumnInfo, DatabaseSchema, Dialect, ForeignKeyInfo, IndexInfo,
Introspector, ParsedSqlType, TableInfo, UniqueConstraintInfo,
};
pub use migrate::{Migration, MigrationFormat, MigrationRunner, MigrationStatus, MigrationWriter};
use asupersync::{Cx, Outcome};
use sqlmodel_core::{Connection, Model, quote_ident};
pub fn create_table<M: Model>() -> CreateTable<M> {
CreateTable::new()
}
pub async fn create_all<C: Connection>(
cx: &Cx,
conn: &C,
schemas: &[&str],
) -> Outcome<(), sqlmodel_core::Error> {
for sql in schemas {
match conn.execute(cx, sql, &[]).await {
Outcome::Ok(_) => continue,
Outcome::Err(e) => return Outcome::Err(e),
Outcome::Cancelled(r) => return Outcome::Cancelled(r),
Outcome::Panicked(p) => return Outcome::Panicked(p),
}
}
Outcome::Ok(())
}
pub async fn drop_table<C: Connection>(
cx: &Cx,
conn: &C,
table_name: &str,
if_exists: bool,
) -> Outcome<(), sqlmodel_core::Error> {
let sql = if if_exists {
format!("DROP TABLE IF EXISTS {}", quote_ident(table_name))
} else {
format!("DROP TABLE {}", quote_ident(table_name))
};
conn.execute(cx, &sql, &[]).await.map(|_| ())
}
pub fn drop_table_sql(table_name: &str, if_exists: bool) -> String {
if if_exists {
format!("DROP TABLE IF EXISTS {}", quote_ident(table_name))
} else {
format!("DROP TABLE {}", quote_ident(table_name))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drop_table_sql_simple() {
let sql = drop_table_sql("users", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
let sql = drop_table_sql("heroes", false);
assert_eq!(sql, "DROP TABLE \"heroes\"");
}
#[test]
fn test_drop_table_sql_with_keyword_name() {
let sql = drop_table_sql("order", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"order\"");
let sql = drop_table_sql("select", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"select\"");
let sql = drop_table_sql("user", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"user\"");
}
#[test]
fn test_drop_table_sql_with_embedded_quotes() {
let sql = drop_table_sql("my\"table", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"my\"\"table\"");
let sql = drop_table_sql("test\"\"name", false);
assert_eq!(sql, "DROP TABLE \"test\"\"\"\"name\"");
let sql = drop_table_sql("\"", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"\"\"\"");
}
#[test]
fn test_drop_table_sql_with_spaces() {
let sql = drop_table_sql("my table", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"my table\"");
}
#[test]
fn test_drop_table_sql_with_unicode() {
let sql = drop_table_sql("用户", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"用户\"");
let sql = drop_table_sql("tâble_émoji_🦀", false);
assert_eq!(sql, "DROP TABLE \"tâble_émoji_🦀\"");
}
#[test]
fn test_drop_table_sql_edge_cases() {
let sql = drop_table_sql("", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"\"");
let sql = drop_table_sql("x", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"x\"");
let sql = drop_table_sql("123table", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"123table\"");
let sql = drop_table_sql("table-with-dashes", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"table-with-dashes\"");
let sql = drop_table_sql("table.with.dots", true);
assert_eq!(sql, "DROP TABLE IF EXISTS \"table.with.dots\"");
}
#[test]
fn test_drop_table_sql_sql_injection_attempt_neutralized() {
let malicious = "users\"; DROP TABLE secrets; --";
let sql = drop_table_sql(malicious, true);
assert_eq!(
sql,
"DROP TABLE IF EXISTS \"users\"\"; DROP TABLE secrets; --\""
);
assert!(sql.starts_with("DROP TABLE IF EXISTS \""));
assert!(sql.ends_with('"'));
let quote_count = sql.matches('"').count();
assert_eq!(quote_count, 4);
}
}