Skip to content

Alternatives

Scythe is a SQL-first code generator. This page compares it to the tool it is most similar to (sqlc), to the wider SQL-first landscape (SQLDelight, PgTyped, sqlx, aiosql, PugSQL, HugSQL, go-jet, SQLBoiler, Kysely, MyBatis and others), and to ORMs (Hibernate, SQLAlchemy, ActiveRecord, and more).

Claims about scythe on this page are checked against this repository’s source. Claims about other tools reflect their public documentation and behavior as of 2026-08 and are not re-verified against their source on every scythe release – they may drift out of date. If something here looks wrong for a competitor tool, check that tool’s own docs.

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, MariaDB, SQLite, DuckDB, CockroachDB, Redshift, SQL Server, Oracle, Snowflake PostgreSQL, MySQL, SQLite SQLite, PostgreSQL, MySQL, H2 25+ databases Varies
Backends 56 ~5 4 N/A (JDBC) Varies
SQL linting 58 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

Scythe is one point in a crowded space. The tools below all reject “let an ORM write your SQL”, but they disagree on where the types come from and whether you write SQL or build queries in your host language — usually the two decisions that actually separate them.

The same category as scythe: .sql files are the source of truth and a build step emits typed functions.

Tool Languages Types derived from Notes
scythe 10 Static analysis of schema + query No database needed to generate
sqlc Go, plus Python/Kotlin/TypeScript plugins Static analysis The direct inspiration; strongest in Go
SQLDelight Kotlin (JVM/Native/JS) Static analysis of .sq files Reactive queries via Flow, IntelliJ plugin
PgTyped TypeScript A running Postgres Types come from the server, not a parser
aiosql Python Not typed SQL files become callable functions
PugSQL Python Not typed Python port of HugSQL
HugSQL / Yesql Clojure Not typed Where this pattern started

aiosql, PugSQL, HugSQL and Yesql give you SQL as the source of truth without types. scythe, sqlc, SQLDelight and PgTyped add the type layer. If all you want is your SQL out of string literals, the untyped tools are smaller and simpler.

These check queries against a real server, so a parser that has drifted from the engine cannot fool them — at the cost of needing that server available at build time.

Tool Languages How
sqlx Rust query! macros check against DATABASE_URL at compile time; cargo sqlx prepare caches metadata into .sqlx/*.json for offline and CI builds
PgTyped TypeScript Generates types from a running Postgres
SafeQL TypeScript ESLint rule validating inline SQL against a live database

This is the sharpest trade-off against scythe, and worth being honest about. Live-database checking is authoritative about column types; static analysis is not, and a parser lagging a dialect can be confidently wrong. What the database does not hand you is nullability for arbitrary query columns — Postgres describes result columns as type OIDs with no nullability — so outer-join and aggregate nullability still has to be inferred either way. Scythe takes the static route so generation needs no database at all. Cross-checking that inference against a live server is open work, not a shipped feature.

Here the schema is introspected and a typed query builder is generated; you compose queries in the host language rather than writing SQL.

Tool Languages Notes
jOOQ Java, Kotlin See below; commercial licence for Oracle/SQL Server/DB2
go-jet Go Type-safe builder plus result mapping; PostgreSQL, MySQL, MariaDB, SQLite
SQLBoiler Go Database-first ORM generated from your schema
xo Go Idiomatic Go for PostgreSQL, MySQL, SQLite, Oracle, SQL Server
Kysely + kysely-codegen TypeScript Builder with types generated from the schema
Prisma, Drizzle TypeScript Schema-first, closer to the ORM end
Exposed, Ktorm Kotlin DSL over a typed schema
QueryDSL Java Generated metamodel, fluent queries

The difference is what you write. These generate the vocabulary for building queries; scythe generates the queries themselves from SQL you already wrote. Builders win on dynamic composition, which scythe deliberately does not do. Scythe does ship typescript-kysely and kotlin-exposed backends, if you want generated code that lands inside those ecosystems rather than replacing them.

Tool Languages Notes
MyBatis Java SQL in XML or annotations, mapped to objects; no generated types
Dapper C# Micro-ORM; you write SQL, it maps rows onto types you declare
JDBI Java Fluent and declarative SQL object APIs

These keep SQL first-class but leave the result types to you — you declare them by hand and they are checked at runtime, if at all. A hand-written type drifting from its query is the specific failure scythe exists to prevent.


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 58 built-in rules (23 lint + 35 audit). 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 @optional annotation rewrites SQL conditions to skip filters when NULL is passed. sqlc uses sqlc.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 MariaDB, DuckDB, CockroachDB, Redshift, SQL Server, Oracle, and Snowflake. Scythe’s engine-aware backends generate optimized code per database.
  • Configuration. sqlc uses sqlc.yaml, scythe uses scythe.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.


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, i.e. a live subscription. Scythe’s java-r2dbc/kotlin-r2dbc backends generate one-shot reactive types (Flux<T>/Flow<T> for :many, Mono<T>/suspend fun for :one) but do not re-emit on data change – see the Overview table above.
  • 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 58 built-in 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.


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.


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.

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.

  • 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, Arel is undocumented, no compile-time type safety. Scythe generates module-wrapped Data.define types 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/1 escape hatches. Scythe generates Postgrex query functions with typespecs. Choose Ecto when your stack is Elixir/Phoenix and queries are straightforward.
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