use std::fmt::Write;
use crate::Connection;
use crate::sql::db::database::Database;
use crate::sql::db::table::{DataType, Table};
pub fn dump_schema_for_connection(conn: &Connection) -> String {
let db = conn.database();
dump_schema_for_database(&db)
}
pub fn dump_schema_for_database(db: &Database) -> String {
let mut names: Vec<&str> = db
.tables
.keys()
.filter(|k| k.as_str() != "sqlrite_master")
.map(String::as_str)
.collect();
names.sort_unstable();
let mut out = String::new();
for (i, name) in names.iter().enumerate() {
if i > 0 {
out.push('\n');
}
let table = match db.tables.get(*name) {
Some(t) => t,
None => continue,
};
format_create_table(table, &mut out);
}
out
}
fn format_create_table(table: &Table, out: &mut String) {
let _ = writeln!(out, "CREATE TABLE {} (", table.tb_name);
for (i, col) in table.columns.iter().enumerate() {
let datatype = render_datatype(&col.datatype);
let mut clauses: Vec<&'static str> = Vec::new();
if col.is_pk {
clauses.push("PRIMARY KEY");
}
if col.is_unique && !col.is_pk {
clauses.push("UNIQUE");
}
if col.not_null && !col.is_pk {
clauses.push("NOT NULL");
}
let trailing = if i + 1 == table.columns.len() {
""
} else {
","
};
if clauses.is_empty() {
let _ = writeln!(out, " {} {}{}", col.column_name, datatype, trailing);
} else {
let _ = writeln!(
out,
" {} {} {}{}",
col.column_name,
datatype,
clauses.join(" "),
trailing
);
}
}
out.push_str(");\n");
}
fn render_datatype(dt: &DataType) -> String {
match dt {
DataType::Integer => "INTEGER".to_string(),
DataType::Text => "TEXT".to_string(),
DataType::Real => "REAL".to_string(),
DataType::Bool => "BOOLEAN".to_string(),
DataType::Vector(dim) => format!("VECTOR({dim})"),
DataType::Json => "JSON".to_string(),
DataType::None => "TEXT".to_string(),
DataType::Invalid => "TEXT".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Connection;
fn open() -> Connection {
Connection::open_in_memory().expect("open in-memory db")
}
#[test]
fn empty_schema_returns_empty_string() {
let conn = open();
assert_eq!(dump_schema_for_connection(&conn), "");
}
#[test]
fn single_table_round_trips() {
let mut conn = open();
conn.execute(
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)",
)
.unwrap();
let dump = dump_schema_for_connection(&conn);
assert!(dump.contains("CREATE TABLE users ("), "got: {dump}");
assert!(dump.contains("id INTEGER PRIMARY KEY"));
assert!(dump.contains("name TEXT NOT NULL"));
assert!(dump.contains("email TEXT UNIQUE"));
}
#[test]
fn vector_and_json_columns_render_with_canonical_keywords() {
let mut conn = open();
conn.execute(
"CREATE TABLE docs (id INTEGER PRIMARY KEY, embedding VECTOR(384), payload JSON)",
)
.unwrap();
let dump = dump_schema_for_connection(&conn);
assert!(dump.contains("embedding VECTOR(384)"), "got: {dump}");
assert!(dump.contains("payload JSON"), "got: {dump}");
}
#[test]
fn tables_emitted_in_alphabetical_order() {
let mut conn = open();
conn.execute("CREATE TABLE zebra (id INTEGER PRIMARY KEY)")
.unwrap();
conn.execute("CREATE TABLE alpha (id INTEGER PRIMARY KEY)")
.unwrap();
conn.execute("CREATE TABLE mango (id INTEGER PRIMARY KEY)")
.unwrap();
let dump = dump_schema_for_connection(&conn);
let alpha = dump.find("CREATE TABLE alpha").unwrap();
let mango = dump.find("CREATE TABLE mango").unwrap();
let zebra = dump.find("CREATE TABLE zebra").unwrap();
assert!(
alpha < mango && mango < zebra,
"non-deterministic order: {dump}"
);
}
#[test]
fn sqlrite_master_is_excluded() {
let mut conn = open();
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)")
.unwrap();
let dump = dump_schema_for_connection(&conn);
assert!(
!dump.contains("sqlrite_master"),
"internal catalog leaked: {dump}"
);
}
#[test]
fn dump_is_byte_stable_across_calls() {
let mut conn = open();
conn.execute("CREATE TABLE a (id INTEGER PRIMARY KEY, x TEXT)")
.unwrap();
conn.execute("CREATE TABLE b (id INTEGER PRIMARY KEY, y REAL)")
.unwrap();
conn.execute("CREATE TABLE c (id INTEGER PRIMARY KEY, z BOOLEAN)")
.unwrap();
let first = dump_schema_for_connection(&conn);
for _ in 0..20 {
assert_eq!(dump_schema_for_connection(&conn), first);
}
}
}