use std::sync::Arc;
use fraiseql_core::{
db::traits::DatabaseAdapter,
runtime::Executor,
schema::{CompiledSchema, TenancyMode},
};
use fraiseql_error::{FraiseQLError, Result};
use serde::Deserialize;
use tracing::info;
use super::schema_isolation;
#[derive(Debug, Clone, Deserialize)]
pub struct TenantPoolConfig {
pub connection_string: String,
#[serde(default = "default_max_connections")]
pub max_connections: u32,
#[serde(default = "default_connect_timeout")]
pub connect_timeout_secs: u64,
#[serde(default = "default_idle_timeout")]
pub idle_timeout_secs: u64,
}
const fn default_max_connections() -> u32 {
10
}
const fn default_connect_timeout() -> u64 {
5
}
const fn default_idle_timeout() -> u64 {
300
}
#[async_trait::async_trait]
pub trait FromPoolConfig: DatabaseAdapter + Sized {
async fn from_pool_config(config: &TenantPoolConfig) -> Result<Self>;
}
#[doc(hidden)] pub async fn create_tenant_executor<A: FromPoolConfig>(
tenant_key: &str,
schema_json: &str,
pool_config: &TenantPoolConfig,
) -> Result<Arc<Executor<A>>> {
let schema =
CompiledSchema::from_json(schema_json, false).map_err(|e| FraiseQLError::Parse {
message: format!("Invalid compiled schema JSON: {e}"),
location: String::new(),
})?;
schema
.validate_format_version()
.map_err(|msg| FraiseQLError::validation(format!("Incompatible compiled schema: {msg}")))?;
let tenancy_mode = schema.tenancy_mode();
let adapter = A::from_pool_config(pool_config).await?;
if tenancy_mode == TenancyMode::Schema {
info!(tenant_key, "provisioning schema for tenant (schema isolation mode)");
schema_isolation::provision_tenant_schema(tenant_key, &adapter).await?;
schema_isolation::configure_search_path(tenant_key, &adapter).await?;
}
Ok(Arc::new(Executor::new(schema, Arc::new(adapter))))
}
pub async fn destroy_tenant_schema(tenant_key: &str, adapter: &dyn DatabaseAdapter) -> Result<()> {
schema_isolation::drop_tenant_schema(tenant_key, adapter).await
}