use razeri_plugin_contract::DialectFamily;
use razeri_plugin_contract::{
DdlRenderer, DialectCapabilities, DmlRenderer, ExpressionRenderer, FormulaRenderer,
ProcedureRenderer, ReplicationRenderer, SqlRenderer, StorageRenderer, TaskRenderer,
};
use std::collections::HashMap;
use std::sync::Arc;
pub use razeri_plugin_contract;
#[derive(Debug, Clone, serde::Serialize)]
pub struct DialectInfo {
pub name: &'static str,
pub display_name: &'static str,
pub dialect_family: DialectFamily,
pub family: &'static str,
pub is_cloud_native: bool,
pub is_olap: bool,
pub is_postgres_compatible: bool,
}
pub trait PlatformDialect:
SqlRenderer
+ ExpressionRenderer
+ DdlRenderer
+ DmlRenderer
+ ProcedureRenderer
+ FormulaRenderer
+ TaskRenderer
+ DialectCapabilities
+ ReplicationRenderer
+ StorageRenderer
{
fn info(&self) -> DialectInfo;
}
pub struct PlatformRegistry {
dialects: HashMap<String, Arc<dyn PlatformDialect>>,
}
impl PlatformRegistry {
#[must_use]
pub fn new() -> Self {
Self { dialects: HashMap::new() }
}
pub fn register(&mut self, name: String, dialect: Arc<dyn PlatformDialect>) {
self.dialects.insert(name, dialect);
}
#[must_use]
pub fn get_by_name(&self, name: &str) -> Option<&Arc<dyn PlatformDialect>> {
self.dialects.get(name)
}
#[must_use]
pub fn resolve(&self, target: &str) -> Option<Arc<dyn PlatformDialect>> {
self.dialects.get(target).cloned()
}
#[must_use]
pub fn list(&self) -> Vec<DialectInfo> {
self.dialects.values().map(|d| d.info()).collect()
}
#[must_use]
pub fn registered_names(&self) -> Vec<String> {
let mut names: Vec<String> = self.dialects.keys().cloned().collect();
names.sort();
names
}
#[must_use]
pub fn with_auto_discovered() -> Self {
use crate::builder::DialectRegistration;
let mut registry = Self::new();
for reg in inventory::iter::<DialectRegistration> {
let dialect = (reg.factory)();
let name = dialect.info().name.to_string();
registry.register(name, dialect);
}
registry
}
}
impl Default for PlatformRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use razeri_plugin_contract::DataType;
struct TestDialect {
info: DialectInfo,
}
impl TestDialect {
fn postgres() -> Self {
Self {
info: DialectInfo {
name: "postgres",
display_name: "PostgreSQL",
dialect_family: DialectFamily::PostgreSql,
family: "PostgreSQL",
is_cloud_native: false,
is_olap: false,
is_postgres_compatible: true,
},
}
}
fn redshift() -> Self {
Self {
info: DialectInfo {
name: "redshift",
display_name: "Amazon Redshift",
dialect_family: DialectFamily::PostgreSql,
family: "PostgreSQL",
is_cloud_native: true,
is_olap: true,
is_postgres_compatible: true,
},
}
}
fn snowflake() -> Self {
Self {
info: DialectInfo {
name: "snowflake",
display_name: "Snowflake",
dialect_family: DialectFamily::Snowflake,
family: "Snowflake",
is_cloud_native: true,
is_olap: true,
is_postgres_compatible: false,
},
}
}
}
impl SqlRenderer for TestDialect {
fn render_data_type(&self, _dt: &DataType) -> String {
"TEXT".to_string()
}
fn quote_identifier(&self, name: &str) -> String {
format!("\"{name}\"")
}
fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
match schema {
Some(s) => format!("{}.{}", self.quote_identifier(s), self.quote_identifier(name)),
None => self.quote_identifier(name),
}
}
fn render_current_timestamp(&self) -> String {
"CURRENT_TIMESTAMP".to_string()
}
}
impl ExpressionRenderer for TestDialect {
fn render_concat(&self, parts: &[String]) -> String {
parts.join(" || ")
}
}
impl DdlRenderer for TestDialect {}
impl DmlRenderer for TestDialect {}
impl ProcedureRenderer for TestDialect {}
impl FormulaRenderer for TestDialect {}
impl TaskRenderer for TestDialect {}
impl ReplicationRenderer for TestDialect {}
impl StorageRenderer for TestDialect {}
impl DialectCapabilities for TestDialect {
fn supports_identity(&self) -> bool {
true
}
fn supports_sequences(&self) -> bool {
true
}
fn supports_stored_procedures(&self) -> bool {
true
}
}
impl PlatformDialect for TestDialect {
fn info(&self) -> DialectInfo {
self.info.clone()
}
}
#[test]
fn register_dialect_resolves_by_name() {
let mut registry = PlatformRegistry::new();
registry.register("postgres".to_string(), Arc::new(TestDialect::postgres()));
assert!(registry.get_by_name("postgres").is_some());
}
#[test]
fn register_extension_resolves_by_name() {
let mut registry = PlatformRegistry::new();
registry.register("redshift".to_string(), Arc::new(TestDialect::redshift()));
assert!(registry.get_by_name("redshift").is_some());
}
#[test]
fn resolve_tries_name_lookup() {
let mut registry = PlatformRegistry::new();
registry.register("postgres".to_string(), Arc::new(TestDialect::postgres()));
registry.register("redshift".to_string(), Arc::new(TestDialect::redshift()));
assert!(registry.resolve("postgres").is_some());
assert!(registry.resolve("redshift").is_some());
assert!(registry.resolve("nonexistent").is_none());
}
#[test]
fn list_returns_all_registered_dialects() {
let mut registry = PlatformRegistry::new();
registry.register("postgres".to_string(), Arc::new(TestDialect::postgres()));
registry.register("snowflake".to_string(), Arc::new(TestDialect::snowflake()));
registry.register("redshift".to_string(), Arc::new(TestDialect::redshift()));
let list = registry.list();
assert_eq!(list.len(), 3);
}
#[test]
fn duplicate_registration_overwrites() {
let mut registry = PlatformRegistry::new();
registry.register("postgres".to_string(), Arc::new(TestDialect::postgres()));
registry.register("postgres".to_string(), Arc::new(TestDialect::postgres()));
assert!(registry.get_by_name("postgres").is_some());
let list = registry.list();
assert_eq!(list.len(), 1);
}
#[test]
fn dialect_info_contains_expected_metadata() {
let dialect = TestDialect::postgres();
let info = dialect.info();
assert_eq!(info.name, "postgres");
assert_eq!(info.display_name, "PostgreSQL");
assert_eq!(info.dialect_family, DialectFamily::PostgreSql);
}
#[test]
fn classification_fields_reflect_dialect_characteristics() {
let pg = TestDialect::postgres();
let pg_info = pg.info();
assert_eq!(pg_info.family, "PostgreSQL");
assert!(pg_info.is_postgres_compatible);
assert!(!pg_info.is_cloud_native);
assert!(!pg_info.is_olap);
let sf = TestDialect::snowflake();
let sf_info = sf.info();
assert_eq!(sf_info.family, "Snowflake");
assert!(sf_info.is_cloud_native);
assert!(sf_info.is_olap);
assert!(!sf_info.is_postgres_compatible);
let rd = TestDialect::redshift();
let rd_info = rd.info();
assert!(rd_info.is_cloud_native);
assert!(rd_info.is_olap);
assert!(rd_info.is_postgres_compatible);
}
#[test]
fn empty_registry_returns_none() {
let registry = PlatformRegistry::new();
assert!(registry.get_by_name("postgres").is_none());
assert!(registry.resolve("postgres").is_none());
assert!(registry.list().is_empty());
}
#[test]
fn registered_names_are_sorted() {
let mut registry = PlatformRegistry::new();
registry.register("snowflake".to_string(), Arc::new(TestDialect::snowflake()));
registry.register("postgres".to_string(), Arc::new(TestDialect::postgres()));
registry.register("redshift".to_string(), Arc::new(TestDialect::redshift()));
assert_eq!(registry.registered_names(), vec!["postgres", "redshift", "snowflake"]);
}
#[test]
fn dialect_info_serializes_for_discovery() {
let info = TestDialect::snowflake().info();
let json = serde_json::to_value(&info).expect("DialectInfo serializes");
assert_eq!(json["name"], "snowflake");
assert_eq!(json["display_name"], "Snowflake");
assert_eq!(json["dialect_family"], "snowflake");
assert_eq!(json["is_cloud_native"], true);
assert_eq!(json["is_postgres_compatible"], false);
}
}