use parking_lot::RwLock;
use std::sync::OnceLock;
use crate::database::Database;
use crate::error::{Error, Result};
use crate::internal::{Backend, EntityTrait, Schema, SchemaBuilder};
use crate::{tide_debug, tide_info, tide_warn};
mod registry;
mod schema;
#[doc(hidden)]
pub use registry::CompiledModelRegistration;
pub use registry::{RegisterModels, SyncModel};
use registry::{get_entity_registry, get_model_schemas, register_compiled_models_matching};
use schema::sync_model_schemas;
pub use schema::{ColumnDef, ModelSchema, normalize_rust_type};
pub type EntityRegistrationFn = Box<dyn Fn(SchemaBuilder) -> SchemaBuilder + Send + Sync>;
pub struct SyncRegistry;
impl SyncRegistry {
pub fn register_entity<E: EntityTrait + Default + 'static>() {
let registry = get_entity_registry();
let mut fns = registry.write();
let register_fn: EntityRegistrationFn =
Box::new(|builder: SchemaBuilder| builder.register(E::default()));
fns.push(register_fn);
}
pub fn build_schema_builder(backend: Backend) -> SchemaBuilder {
let registry = get_entity_registry();
let fns = registry.read();
let schema = Schema::new(backend.into());
let mut builder = schema.builder();
for register_fn in fns.iter() {
builder = register_fn(builder);
}
builder
}
pub fn entity_count() -> usize {
let registry = get_entity_registry();
let fns = registry.read();
fns.len()
}
pub fn schema_count() -> usize {
let direct = get_model_schemas();
let schemas = direct.read();
schemas.len()
}
pub fn clear() {
let registry = get_entity_registry();
let mut fns = registry.write();
fns.clear();
let direct = get_model_schemas();
let mut schemas = direct.write();
schemas.clear();
}
pub fn register_schema(schema: ModelSchema) {
let direct = get_model_schemas();
let mut schemas = direct.write();
if !schemas.iter().any(|s| s.table_name == schema.table_name) {
schemas.push(schema);
}
}
pub fn register_models_matching(pattern: &str) -> usize {
register_compiled_models_matching(pattern)
}
pub fn get_all_schemas() -> Vec<ModelSchema> {
let direct = get_model_schemas();
let schemas = direct.read();
schemas.clone()
}
}
pub async fn sync_database(db: &Database) -> Result<()> {
sync_database_with_options(db, false).await
}
pub async fn sync_database_with_options(db: &Database, force_sync: bool) -> Result<()> {
if force_sync {
tide_warn!("Database FORCE sync mode is ENABLED - using schema apply mode!");
} else {
tide_warn!("Database sync mode is ENABLED - DO NOT use in production!");
}
let conn = db.__internal_connection()?;
let backend = conn.get_database_backend();
let entity_count = SyncRegistry::entity_count();
let schema_count = SyncRegistry::schema_count();
let total_count = entity_count + schema_count;
if total_count == 0 {
tide_info!("No models registered for sync");
return Ok(());
}
tide_info!(
"Syncing {} model(s) using the ORM schema builder...",
total_count
);
tide_debug!(" - {} entity-based models", entity_count);
tide_debug!(" - {} TideORM schema models", schema_count);
if entity_count > 0 {
let schema_builder = SyncRegistry::build_schema_builder(Backend::from(backend));
#[cfg(any(feature = "postgres", feature = "mysql", feature = "sqlite"))]
if force_sync {
tide_debug!(" Using SchemaBuilder.apply() - fresh schema creation");
schema_builder
.apply(&conn)
.await
.map_err(|e| Error::query(format!("Schema apply failed: {}", e)))?;
} else {
tide_debug!(" Using SchemaBuilder.sync() - incremental sync");
schema_builder
.sync(&conn)
.await
.map_err(|e| Error::query(format!("Schema sync failed: {}", e)))?;
}
#[cfg(not(any(feature = "postgres", feature = "mysql", feature = "sqlite")))]
{
let _ = schema_builder;
return Err(Error::configuration(
"database sync requires at least one backend feature: postgres, mysql, or sqlite",
));
}
}
if schema_count > 0 {
tide_debug!(" Processing {} TideORM schema(s)...", schema_count);
sync_model_schemas(db, force_sync).await?;
}
tide_info!("Database sync completed");
Ok(())
}