Skip to main content

scythe_codegen/backends/
mod.rs

1pub mod csharp_microsoft_sqlite;
2pub mod csharp_mysqlconnector;
3pub mod csharp_npgsql;
4pub mod elixir_exqlite;
5pub mod elixir_myxql;
6pub mod elixir_postgrex;
7pub mod go_database_sql;
8pub mod go_pgx;
9pub mod java_jdbc;
10pub mod kotlin_jdbc;
11pub mod php_pdo;
12pub mod python_aiomysql;
13pub mod python_aiosqlite;
14pub mod python_asyncpg;
15pub mod python_psycopg3;
16pub mod ruby_mysql2;
17pub mod ruby_pg;
18pub mod ruby_sqlite3;
19pub mod sqlx;
20pub mod tokio_postgres;
21pub mod typescript_better_sqlite3;
22pub mod typescript_mysql2;
23pub mod typescript_pg;
24pub mod typescript_postgres;
25
26use scythe_core::errors::{ErrorCode, ScytheError};
27
28use crate::backend_trait::CodegenBackend;
29
30/// Strip SQL comments, trailing semicolons, and excess whitespace.
31/// Preserves newlines between lines.
32pub(crate) fn clean_sql(sql: &str) -> String {
33    sql.lines()
34        .filter(|line| !line.trim_start().starts_with("--"))
35        .collect::<Vec<_>>()
36        .join("\n")
37        .trim()
38        .trim_end_matches(';')
39        .trim()
40        .to_string()
41}
42
43/// Like clean_sql but joins lines with spaces (for languages that embed SQL inline).
44pub(crate) fn clean_sql_oneline(sql: &str) -> String {
45    sql.lines()
46        .filter(|line| !line.trim_start().starts_with("--"))
47        .collect::<Vec<_>>()
48        .join(" ")
49        .trim()
50        .trim_end_matches(';')
51        .trim()
52        .to_string()
53}
54
55/// Get a backend by name and database engine.
56///
57/// The `engine` parameter (e.g., "postgresql", "mysql", "sqlite") determines
58/// which manifest is loaded for type mappings. PG-only backends reject non-PG engines.
59pub fn get_backend(name: &str, engine: &str) -> Result<Box<dyn CodegenBackend>, ScytheError> {
60    let backend: Box<dyn CodegenBackend> = match name {
61        "rust-sqlx" | "sqlx" | "rust" => Box::new(sqlx::SqlxBackend::new(engine)?),
62        "rust-tokio-postgres" | "tokio-postgres" => {
63            Box::new(tokio_postgres::TokioPostgresBackend::new(engine)?)
64        }
65        "python-psycopg3" | "python" => {
66            Box::new(python_psycopg3::PythonPsycopg3Backend::new(engine)?)
67        }
68        "python-asyncpg" => Box::new(python_asyncpg::PythonAsyncpgBackend::new(engine)?),
69        "python-aiomysql" => Box::new(python_aiomysql::PythonAiomysqlBackend::new(engine)?),
70        "python-aiosqlite" => Box::new(python_aiosqlite::PythonAiosqliteBackend::new(engine)?),
71        "typescript-postgres" | "ts" | "typescript" => {
72            Box::new(typescript_postgres::TypescriptPostgresBackend::new(engine)?)
73        }
74        "typescript-pg" => Box::new(typescript_pg::TypescriptPgBackend::new(engine)?),
75        "typescript-mysql2" => Box::new(typescript_mysql2::TypescriptMysql2Backend::new(engine)?),
76        "typescript-better-sqlite3" => {
77            Box::new(typescript_better_sqlite3::TypescriptBetterSqlite3Backend::new(engine)?)
78        }
79        "go-database-sql" => Box::new(go_database_sql::GoDatabaseSqlBackend::new(engine)?),
80        "go-pgx" | "go" => Box::new(go_pgx::GoPgxBackend::new(engine)?),
81        "java-jdbc" | "java" => Box::new(java_jdbc::JavaJdbcBackend::new(engine)?),
82        "kotlin-jdbc" | "kotlin" | "kt" => Box::new(kotlin_jdbc::KotlinJdbcBackend::new(engine)?),
83        "csharp-npgsql" | "csharp" | "c#" | "dotnet" => {
84            Box::new(csharp_npgsql::CsharpNpgsqlBackend::new(engine)?)
85        }
86        "csharp-mysqlconnector" => Box::new(
87            csharp_mysqlconnector::CsharpMysqlConnectorBackend::new(engine)?,
88        ),
89        "csharp-microsoft-sqlite" => Box::new(
90            csharp_microsoft_sqlite::CsharpMicrosoftSqliteBackend::new(engine)?,
91        ),
92        "elixir-postgrex" | "elixir" | "ex" => {
93            Box::new(elixir_postgrex::ElixirPostgrexBackend::new(engine)?)
94        }
95        "elixir-myxql" => Box::new(elixir_myxql::ElixirMyxqlBackend::new(engine)?),
96        "elixir-exqlite" => Box::new(elixir_exqlite::ElixirExqliteBackend::new(engine)?),
97        "ruby-pg" | "ruby" | "rb" => Box::new(ruby_pg::RubyPgBackend::new(engine)?),
98        "ruby-mysql2" => Box::new(ruby_mysql2::RubyMysql2Backend::new(engine)?),
99        "ruby-sqlite3" => Box::new(ruby_sqlite3::RubySqlite3Backend::new(engine)?),
100        "php-pdo" | "php" => Box::new(php_pdo::PhpPdoBackend::new(engine)?),
101        _ => {
102            return Err(ScytheError::new(
103                ErrorCode::InternalError,
104                format!("unknown backend: {}", name),
105            ));
106        }
107    };
108
109    // Validate engine is supported by this backend
110    let normalized_engine = normalize_engine(engine);
111    if !backend
112        .supported_engines()
113        .iter()
114        .any(|e| normalize_engine(e) == normalized_engine)
115    {
116        return Err(ScytheError::new(
117            ErrorCode::InternalError,
118            format!(
119                "backend '{}' does not support engine '{}'. Supported: {:?}",
120                name,
121                engine,
122                backend.supported_engines()
123            ),
124        ));
125    }
126
127    Ok(backend)
128}
129
130/// Normalize engine name to canonical form.
131fn normalize_engine(engine: &str) -> &str {
132    match engine {
133        "postgresql" | "postgres" | "pg" => "postgresql",
134        "mysql" | "mariadb" => "mysql",
135        "sqlite" | "sqlite3" => "sqlite",
136        other => other,
137    }
138}