#[cfg(feature = "sqlite")]
use std::{
env,
ffi::{CStr, CString},
fs,
path::{Path, PathBuf},
};
#[cfg(feature = "sqlite")]
fn migration_files(directory: &Path) -> Result<Vec<PathBuf>, String> {
let entries = fs::read_dir(directory)
.map_err(|error| format!("could not read {}: {error}", directory.display()))?;
let mut files = Vec::new();
for entry in entries {
let path = entry
.map_err(|error| format!("could not read migration entry: {error}"))?
.path();
if path.extension().and_then(|extension| extension.to_str()) == Some("sql") {
files.push(path);
}
}
files.sort();
Ok(files)
}
fn main() {
if let Err(error) = generate_schema_snapshots() {
panic!("could not generate schema snapshots: {error}");
}
}
#[cfg(not(feature = "sqlite"))]
fn generate_schema_snapshots() -> Result<(), String> {
Ok(())
}
#[cfg(feature = "sqlite")]
fn generate_schema_snapshots() -> Result<(), String> {
let manifest_dir = PathBuf::from(
env::var("CARGO_MANIFEST_DIR")
.map_err(|error| format!("missing manifest directory: {error}"))?,
);
let sqlite_migrations = manifest_dir.join("migrations/sqlite");
let sqlite_snapshot = manifest_dir.join("schema.sqlite");
if sqlite_snapshot.exists() {
fs::remove_file(&sqlite_snapshot)
.map_err(|error| format!("could not replace {}: {error}", sqlite_snapshot.display()))?;
}
let sqlite_path = CString::new(sqlite_snapshot.to_string_lossy().as_bytes())
.map_err(|error| format!("invalid SQLite snapshot path: {error}"))?;
let mut connection = std::ptr::null_mut();
let open_status =
unsafe { libsqlite3_sys::sqlite3_open(sqlite_path.as_ptr(), &mut connection) };
if open_status != libsqlite3_sys::SQLITE_OK {
return Err(format!("could not create {}", sqlite_snapshot.display()));
}
for path in migration_files(&sqlite_migrations)? {
println!("cargo:rerun-if-changed={}", path.display());
let migration = fs::read_to_string(&path)
.map_err(|error| format!("could not read {}: {error}", path.display()))?;
let migration = CString::new(migration)
.map_err(|error| format!("invalid SQL in {}: {error}", path.display()))?;
let mut error_message = std::ptr::null_mut();
let status = unsafe {
libsqlite3_sys::sqlite3_exec(
connection,
migration.as_ptr(),
None,
std::ptr::null_mut(),
&mut error_message,
)
};
if status != libsqlite3_sys::SQLITE_OK {
let message = if error_message.is_null() {
"unknown SQLite error".to_owned()
} else {
unsafe { CStr::from_ptr(error_message) }
.to_string_lossy()
.into_owned()
};
if !error_message.is_null() {
unsafe { libsqlite3_sys::sqlite3_free(error_message.cast()) };
}
unsafe { libsqlite3_sys::sqlite3_close(connection) };
return Err(format!("could not apply {}: {message}", path.display()));
}
}
unsafe { libsqlite3_sys::sqlite3_close(connection) };
Ok(())
}