Alternatives¶
Scythe is a SQL-first code generator. This page compares it to the tool it is most similar to (sqlc), other SQL-first tools (SQLDelight, jOOQ), and ORMs (Hibernate, SQLAlchemy, ActiveRecord, and others).
Overview¶
| Scythe | sqlc | SQLDelight | jOOQ | ORMs | |
|---|---|---|---|---|---|
| Approach | SQL files to code | SQL files to code | .sq files to code | Java DSL to SQL | Code to SQL |
| Languages | 10 (Rust, Python, TS, Go, Java, Kotlin, C#, Elixir, Ruby, PHP) | Go (primary), Python, Kotlin, TypeScript (plugin) | Kotlin (JVM, Native, JS via KMP) | Java, Kotlin | Language-specific |
| Databases | PostgreSQL, MySQL, SQLite, DuckDB, CockroachDB | PostgreSQL, MySQL, SQLite | SQLite, PostgreSQL, MySQL, H2 | 25+ databases | Varies |
| Backend drivers | 34 | ~5 | 4 | N/A (JDBC) | Varies |
| SQL linting | 93 rules | None | None | None | None |
| SQL formatting | sqruff integration | None | None | None | None |
| Nullability inference | JOINs, COALESCE, CASE, window functions, aggregates | Basic (column constraints) | Column constraints | From DB metadata | Varies |
| Optional params | @optional annotation (SQL rewriting) |
sqlc.narg() |
None | Runtime DSL | Varies |
| Batch execution | :batch return type |
sqlc.slice() |
None | Batch API | Varies |
| Row type options | Pydantic, msgspec, Zod, dataclass, interface | None | None | N/A | Language-native |
| IDE plugin | None (planned) | None | IntelliJ | IntelliJ | Varies |
| Reactive queries | R2DBC (Java, Kotlin) | Not supported | Kotlin Flow, RxJava | Not supported | Varies |
| Migration support | External | External | Built-in (.sqm validation) | External | Usually built-in |
| Custom types | type_overrides config | Override config | ColumnAdapter interface | Binding/Converter | Language-native |
| Build integration | CLI (any build system) | CLI | Gradle plugin | Maven/Gradle | Language-specific |
| Licensing | MIT | MIT (core), BSD | Apache 2.0 | Apache 2.0 (open DBs), Commercial (Oracle, MSSQL, etc.) | Varies |
| When SQL runs | Compiled at build time | Compiled at build time | Compiled at build time | DSL builds SQL at runtime | Generated at runtime |
vs sqlc¶
sqlc is the tool scythe is most directly inspired by. Both compile SQL files into typed code.
Key differences:
- Language support. sqlc primarily targets Go, with community plugins for Python/Kotlin/TypeScript. Scythe generates native, idiomatic code for 10 languages from a single codebase.
- SQL linting. Scythe includes 93 lint rules. sqlc has none -- you need a separate tool.
- Type inference. Scythe infers nullability from JOINs, COALESCE, CASE, window functions. sqlc infers from column constraints and has
sqlc.narg()for nullable params. - Optional parameters. Scythe's
@optionalannotation rewrites SQL conditions to skip filters when NULL is passed. sqlc usessqlc.narg()for a similar effect. - Row types. Scythe supports configurable row types -- Pydantic/msgspec for Python, Zod for TypeScript. sqlc generates fixed types per language.
- Multi-database. Both support PostgreSQL, MySQL, SQLite. Scythe also supports DuckDB and CockroachDB. Scythe's engine-aware backends generate optimized code per database.
- Configuration. sqlc uses
sqlc.yaml, scythe usesscythe.toml. Both are CLI tools.
When to choose sqlc: Go-only teams, existing sqlc investment, need sqlc.narg() for dynamic queries.
When to choose scythe: Polyglot teams, want SQL linting/formatting, need better nullability inference.
vs SQLDelight¶
SQLDelight is the closest tool to scythe in philosophy -- both generate code from SQL files at compile time. SQLDelight targets the Kotlin ecosystem exclusively.
Key differences:
- Languages. SQLDelight generates Kotlin only (JVM, Native, JS via KMP). Scythe generates 10 languages.
- Build system. SQLDelight requires Gradle. Scythe is a CLI that works with any build system.
- Reactive queries. SQLDelight integrates with Kotlin Flow and RxJava -- queries re-emit when data changes. Scythe does not support reactive queries.
- IDE support. SQLDelight has an IntelliJ plugin with autocomplete and refactoring. Scythe does not (planned).
- Migrations. SQLDelight validates .sqm migration files at build time. Scythe delegates migrations to external tools.
- SQL linting. Scythe has 93 rules. SQLDelight has none.
When to choose SQLDelight: Kotlin/KMP stack, need reactive queries (Flow), need IDE plugin, building cross-platform mobile.
When to choose scythe: Polyglot teams, want SQL linting/formatting, not using Gradle, need non-JVM languages.
vs jOOQ¶
jOOQ takes a different approach -- instead of SQL files, you write queries using a Java DSL that generates SQL at runtime.
Key differences:
- SQL authoring. jOOQ uses a Java/Kotlin fluent API. Scythe uses plain SQL files.
- When SQL runs. jOOQ builds SQL strings at runtime. Scythe compiles SQL at build time -- zero runtime overhead.
- Schema source. jOOQ requires a live database connection for code generation. Scythe reads .sql files.
- Dynamic queries. jOOQ excels at runtime query composition (conditional WHERE, dynamic columns). Scythe queries are static.
- Languages. jOOQ targets Java/Kotlin. Scythe targets 10 languages.
- Licensing. jOOQ is free for open-source databases, commercial license required for Oracle/SQL Server/DB2. Scythe is MIT for everything.
jOOQ's runtime DSL allows conditional query construction:
var query = ctx.select(USERS.ID, USERS.NAME).from(USERS);
if (filterByStatus) {
query = query.where(USERS.STATUS.eq(status));
}
if (filterByDate) {
query = query.and(USERS.CREATED_AT.gt(minDate));
}
Scythe does not support dynamic query composition. Queries are fixed at compile time. If you need conditional logic, you write separate queries or use SQL-level conditionals (WHERE (:filter_status IS NULL OR status = :filter_status)). This is a deliberate trade-off: dynamic queries are powerful but harder to analyze statically, harder to lint, and harder to optimize.
When to choose jOOQ: Java/Kotlin only, need dynamic query composition, prefer query builder over SQL files.
When to choose scythe: Want plain SQL files, polyglot teams, need all databases without commercial licensing, want SQL linting.
vs ORMs¶
ORMs (Hibernate, SQLAlchemy, ActiveRecord, Entity Framework, GORM, Diesel, Ecto) map database tables to objects in application code. They generate SQL at runtime based on your object model.
Common ORM pain points scythe solves¶
| Problem | ORMs | Scythe |
|---|---|---|
| N+1 queries | Lazy loading fires individual SELECTs per row | You write the JOIN -- one query, one round trip |
| Opaque SQL | Generated SQL is unpredictable and hard to debug | The SQL you write is the SQL that runs |
| Migration drift | Model != schema divergence is common | Schema is SQL -- scythe reads it directly |
| Limited SQL | Window functions, CTEs, lateral joins require raw SQL escape hatches | Write any SQL your database supports |
| Type safety | Struggles with aggregations, conditional expressions, nullable joins | Static analysis with precise nullability inference |
When to choose an ORM: Database portability matters (BYOD — your users pick the database), simple CRUD, rapid prototyping, team prefers not to write SQL.
When to choose scythe: You control the database, complex queries, performance-sensitive, want full SQL control with type safety.
Per-framework notes¶
- Hibernate/JPA (Java) --
LazyInitializationException, N+1 queries, HQL limitations for window functions and CTEs. Scythe gives you full SQL with JDBC types. Choose Hibernate when you need JPA portability or are invested in the JPA ecosystem. - SQLAlchemy (Python) -- Powerful but ORM mode obscures queries. Session complexity causes stale reads and detached instance errors. Async lazy loading does not work. Scythe generates dataclasses with async support. Choose SQLAlchemy Core when you need runtime query composition.
- ActiveRecord (Ruby) -- Convention-over-configuration works until queries get complex. N+1 by default,
Arelis undocumented, no compile-time type safety. Scythe generates module-wrappedData.definetypes with RBS annotations. Choose ActiveRecord for standard Rails CRUD. - Entity Framework (C#) -- LINQ is expressive but not all expressions translate to SQL. Migration conflicts across branches are common. Change tracker adds overhead for bulk operations. Scythe generates async record types. Choose EF when you need tight Visual Studio integration.
- GORM (Go) -- Auto-migration is dangerous in production. Runtime reflection loses compile-time guarantees. Silent failures on missing rows. Scythe generates structs with explicit error returns for pgx/database-sql. Choose GORM for quick data access layers with simple CRUD.
- Diesel (Rust) -- Compile-time safety but the DSL has a steep learning curve. Compiler errors for malformed queries can be hundreds of lines. Window functions and CTEs require raw SQL. Sync only without
diesel-async. Scythe uses plain SQL with sqlx/tokio-postgres. Choose Diesel when you want compile-time SQL verification within Rust's type system. - Ecto (Elixir) -- One of the better-designed ORMs. Composable query DSL, clean changesets, tight Phoenix integration. Complex queries still require
fragment/1escape hatches. Scythe generates Postgrex query functions with typespecs. Choose Ecto when your stack is Elixir/Phoenix and queries are straightforward.
Summary table¶
| Source of truth | Query language | Type safety | SQL feature coverage | Runtime cost | |
|---|---|---|---|---|---|
| Scythe | SQL files | SQL | Generated, precise | Full (CTEs, window functions, etc.) | Zero (static code) |
| Hibernate | Java annotations | JPQL / Criteria | Partial | Limited | Session management, dirty checking |
| SQLAlchemy | Python classes | Python DSL / Core | Partial (Any leaks) |
Moderate | Session, identity map |
| ActiveRecord | Ruby conventions | Ruby DSL | None (dynamic typing) | Limited | Lazy loading, callbacks |
| Entity Framework | C# classes | LINQ | Good (LINQ-level) | Moderate | Change tracking |
| GORM | Go struct tags | Go DSL | None (reflection) | Limited | Reflection |
| Diesel | Rust macros | Rust DSL | Strong (type-level) | Moderate | Minimal |
| Ecto | Elixir modules | Elixir DSL | Moderate (compile-time) | Moderate | Minimal |