pub mod types;
#[cfg(feature = "surrealdb")]
pub mod surql;
#[cfg(feature = "sql")]
pub mod sql;
use async_trait::async_trait;
use std::collections::BTreeMap;
use crate::error::Result;
use crate::schemasync::{EdgeConfig, TableConfig};
use crate::types::StructField;
pub use types::*;
pub use crate::schemasync::compare::SchemaComparator;
#[cfg(feature = "sql")]
pub use crate::schemasync::compare::sql::SqlSchemaComparator;
#[cfg(feature = "surrealdb")]
pub use self::surql::SurrealdbProvider;
#[async_trait]
pub trait DatabaseProvider: Send + Sync {
fn name(&self) -> &'static str;
fn supports_graph_queries(&self) -> bool;
fn supports_embedded_mode(&self) -> bool;
async fn connect(&mut self, config: &DatabaseConfig) -> Result<()>;
async fn disconnect(&mut self) -> Result<()>;
fn is_connected(&self) -> bool;
async fn export_schema(&self) -> Result<SchemaExport>;
async fn apply_schema(&self, statements: &[String]) -> Result<()>;
async fn get_table_info(&self, table_name: &str) -> Result<Option<TableInfo>>;
async fn list_tables(&self) -> Result<Vec<String>>;
async fn execute(&self, query: &str) -> Result<Vec<serde_json::Value>>;
async fn execute_batch(&self, queries: &[String]) -> Result<Vec<Vec<serde_json::Value>>>;
async fn insert(&self, table: &str, records: &[serde_json::Value]) -> Result<Vec<String>>;
async fn upsert(&self, table: &str, records: &[serde_json::Value]) -> Result<Vec<String>>;
async fn select(&self, table: &str, filter: Option<&str>) -> Result<Vec<serde_json::Value>>;
async fn count(&self, table: &str, filter: Option<&str>) -> Result<u64>;
async fn delete(&self, table: &str, ids: &[String]) -> Result<()>;
fn generate_create_table(
&self,
table_name: &str,
config: &TableConfig,
all_tables: &BTreeMap<String, TableConfig>,
objects: &BTreeMap<String, crate::types::StructConfig>,
enums: &BTreeMap<String, crate::types::TaggedUnion>,
) -> String;
fn generate_create_field(
&self,
table_name: &str,
field: &StructField,
objects: &BTreeMap<String, crate::types::StructConfig>,
enums: &BTreeMap<String, crate::types::TaggedUnion>,
) -> String;
fn map_field_type(&self, field_type: &crate::types::FieldType) -> String;
fn format_value(
&self,
field_type: &crate::types::FieldType,
value: &serde_json::Value,
) -> String;
fn generate_relationship_table(&self, edge: &EdgeConfig) -> Vec<String>;
async fn create_relationship(
&self,
edge_table: &str,
from_id: &str,
to_id: &str,
data: Option<&serde_json::Value>,
) -> Result<String>;
async fn delete_relationship(&self, edge_table: &str, from_id: &str, to_id: &str)
-> Result<()>;
async fn get_relationships(
&self,
edge_table: &str,
record_id: &str,
direction: RelationshipDirection,
) -> Result<Vec<Relationship>>;
async fn begin_transaction(&self) -> Result<Box<dyn Transaction>>;
async fn create_embedded_instance(&self) -> Result<Option<Box<dyn DatabaseProvider>>>;
}
#[async_trait]
pub trait Transaction: Send + Sync {
async fn commit(self: Box<Self>) -> Result<()>;
async fn rollback(self: Box<Self>) -> Result<()>;
async fn execute(&self, query: &str) -> Result<Vec<serde_json::Value>>;
}
#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub provider: ProviderType,
pub url: String,
pub namespace: Option<String>,
pub database: Option<String>,
pub username: Option<String>,
pub password: Option<String>,
pub timeout_secs: u64,
pub max_connections: Option<u32>,
pub min_connections: Option<u32>,
pub schema: Option<String>,
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
provider: ProviderType::SurrealDb,
url: String::new(),
namespace: None,
database: None,
username: None,
password: None,
timeout_secs: 30,
max_connections: Some(10),
min_connections: Some(1),
schema: Some("public".to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProviderType {
#[default]
SurrealDb,
Postgres,
MySql,
Sqlite,
}
impl std::fmt::Display for ProviderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProviderType::SurrealDb => write!(f, "surrealdb"),
ProviderType::Postgres => write!(f, "postgres"),
ProviderType::MySql => write!(f, "mysql"),
ProviderType::Sqlite => write!(f, "sqlite"),
}
}
}
impl std::str::FromStr for ProviderType {
type Err = crate::error::EvenframeError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"surrealdb" | "surreal" => Ok(ProviderType::SurrealDb),
"postgres" | "postgresql" | "pg" => Ok(ProviderType::Postgres),
"mysql" | "mariadb" => Ok(ProviderType::MySql),
"sqlite" | "sqlite3" => Ok(ProviderType::Sqlite),
_ => Err(crate::error::EvenframeError::config(format!(
"Unknown database provider: {}. Supported: surrealdb, postgres, mysql, sqlite",
s
))),
}
}
}
pub fn create_provider(config: &DatabaseConfig) -> Result<Box<dyn DatabaseProvider>> {
match config.provider {
#[cfg(feature = "surrealdb")]
ProviderType::SurrealDb => Ok(Box::new(surql::SurrealdbProvider::new())),
#[cfg(not(feature = "surrealdb"))]
ProviderType::SurrealDb => Err(crate::error::EvenframeError::config(
"SurrealDB support not enabled. Enable the 'surrealdb' feature flag.",
)),
#[cfg(feature = "postgres")]
ProviderType::Postgres => Ok(Box::new(sql::postgres::PostgresProvider::new())),
#[cfg(not(feature = "postgres"))]
ProviderType::Postgres => Err(crate::error::EvenframeError::config(
"PostgreSQL support not enabled. Enable the 'postgres' feature flag.",
)),
#[cfg(feature = "mysql")]
ProviderType::MySql => Ok(Box::new(sql::mysql::MysqlProvider::new())),
#[cfg(not(feature = "mysql"))]
ProviderType::MySql => Err(crate::error::EvenframeError::config(
"MySQL support not enabled. Enable the 'mysql' feature flag.",
)),
#[cfg(feature = "sqlite")]
ProviderType::Sqlite => Ok(Box::new(sql::sqlite::SqliteProvider::new())),
#[cfg(not(feature = "sqlite"))]
ProviderType::Sqlite => Err(crate::error::EvenframeError::config(
"SQLite support not enabled. Enable the 'sqlite' feature flag.",
)),
}
}
pub async fn connect(config: &DatabaseConfig) -> Result<Box<dyn DatabaseProvider>> {
let mut provider = create_provider(config)?;
provider.connect(config).await?;
Ok(provider)
}