Skip to content

SQL & ORM Abstraction

A breadth-first survey of database access across languages — from the thinnest layer that keeps SQL injection-proof (safe string templates, raw drivers), through typed query builders and functional data mappers, up to full object-relational mappers with change tracking and unit-of-work. The goal is a grounded map of how ~30 production systems in a dozen ecosystems structure the four hard problems — safe query construction, connection & transaction lifetime, schema & migration, and result mapping — to inform the design of an algebraic effects-first D data-access library (working name sparkles:sql). Because the design centre is effects-first, the survey weights the effect-system and functional data-mapper families most heavily: Effect TS (TypeScript), Quill, doobie, skunk, Slick (Scala), and Ecto (Elixir).

This survey answers seven questions:

  1. What are the levels of abstraction, and what does each rung trade? The driver → safe-SQL → typed-builder → functional-data-mapper → full-ORM ladder. See concepts: the abstraction ladder and the master catalog.
  2. How is SQL made injection-safe, and how do the mechanisms differ? Runtime tagged-templates vs typed builders vs compile-time quotation vs macro-checked raw SQL. See concepts: statements, parameters & injection, the by-query-model taxonomy, and the safe-interpolation case study (which features D's IES).
  3. Compile-time or runtime query construction — who checks the query, and when? Type-level schemas and macros (Quill, sqlx, Squeal, jOOQ) vs runtime AST builders. See the comparison.
  4. How is database access modelled as an effect, and how do transactions compose? Blocking vs async vs effect-value (IO/ZIO/Effect/ConnectionIO); withTransaction combinators, nesting, and savepoints. See the by-effect-model taxonomy.
  5. How is error handling typed? A single typed error channel with a reason union (Effect TS) vs a narrowed exception channel (Quill) vs thrown SQLException/DbException. See concepts: effects, transactions & errors.
  6. What are the schema & migration strategies? Code-first, schema-first, and db-first (introspection + codegen). See the by-schema-stance taxonomy.
  7. What should an effects-first sparkles:sql borrow, across the thin→ORM spectrum? The synthesis and delta. See the comparison.

NOTE

Scope: complete — all five waves + synthesis published. This survey was built in waves. Wave 1 establishes the shared vocabulary (concepts) and the effect-system / functional-data-mapper core: Effect TS, Quill, doobie, skunk, Slick, Ecto. Wave 2 adds the Haskell typed cluster (hasql, Squeal, Opaleye, Beam, persistent + esqueleto). Wave 3 adds the typed query builders & thin safe-SQL (Diesel, sqlx, SeaORM, Kysely, Drizzle, jOOQ, sqlc, linq2db, Dapper, Exposed). Wave 4 adds the full ORMs (EF Core, Hibernate, SQLAlchemy, Django ORM, Prisma, TypeORM, GORM, ent, ActiveRecord). Wave 5 adds the raw / tagged-template baseline (Go database/sql, postgres.js, JDBI), and the capstone comparison & synthesis reads the whole corpus against itself and distils the design brief for an effects-first sparkles:sql.

Last reviewed: July 12, 2026


Master catalog

One row per surveyed system. Category places it on the abstraction ladder. Query model is how a query is expressed (concepts). Effect/async model is how a result is returned. Schema stance is the library's relationship to the schema (concepts). The Link column points at the deep-dive (or the wave that publishes it).

SystemLanguageCategoryQuery modelEffect / async modelSchema stanceLink
Effect TS sqlTypeScriptFunctional data mapperTagged templateEffect value (typed SqlError)Code-agnosticeffect-ts
QuillScalaFunctional data mapperQuoted DSL → AST (compile-time)Pluggable; ZIO in JDBC-ZIOCode-agnosticquill
doobieScalaFunctional data mapperTagged template + FragmentConnectionIO (cats-effect)None (SQL only)doobie
skunkScalaFunctional data mapperTyped statement + Codeccats-effect + fs2None (SQL only)skunk
SlickScalaFunctional-relationalTyped relational algebraDBIO → effect-poly F (IO/Future/ZIO)Schema + codegenslick
EctoElixirFunctional data mapperComposable query macrosImmutable / blockingCode-first + migrationsecto
hasqlHaskellSafe-SQL / micro-mapperRaw SQL + Encoders/DecodersSession over IO (blocking)None (SQL only)hasql
SquealHaskellTyped query builderTyped relational algebraIndexed monad PQ over IOCode-first (type-level)squeal
OpaleyeHaskellTyped query builderTyped relational algebra (arrows)IO (postgresql-simple)Db-firstopaleye
BeamHaskellFunctional data mapperTyped relational algebra (Q monad)MonadBeam over IOCode-first / db-firstbeam
persistent + esqueletoHaskellFull ORM + typed joinsTH entities + type-safe EDSLSqlPersistT over IO (blocking)Code-firstpersistent-esqueleto
DieselRustTyped query builderFluent typed builderBlocking (+ async fork)Db-first (schema.rs)diesel
sqlxRustSafe-SQL / micro-mapperMacro-checked raw SQLAsyncDb-first (compile check)sqlx
SeaORMRustFull ORM (data mapper)Fluent builder over sqlxAsyncCode/db-first (entities)sea-orm
KyselyTypeScriptTyped query builderFluent typed builderAsyncDb-first (types)kysely
DrizzleTypeScriptTyped query builderFluent SQL-like builderAsyncCode-first (schema)drizzle
jOOQJavaTyped query builderFluent typed builderBlocking (+ R2DBC)Db-first codegenjooq
sqlcGoSafe-SQL / micro-mapperRaw SQL → generated codeBlocking (database/sql)Db-first (codegen)sqlc
linq2db.NETTyped query builderLINQ → SQLAsyncDb-first / POCOlinq2db
Dapper.NETSafe-SQL / micro-mapperRaw SQL + auto-mapBlocking / asyncNonedapper
ExposedKotlinTyped query builder + DAOFluent DSL / DAOBlocking (+ suspend)Code-firstexposed
EF Core.NETFull ORM (data mapper)LINQAsyncCode-first + migrationsef-core
Hibernate / JPAJavaFull ORM (data mapper)JPQL / CriteriaBlocking (+ reactive)Code-first / db-firsthibernate
SQLAlchemyPythonFull ORM (data mapper) + CoreCore expression + ORMBlocking (+ asyncio)Code-firstsqlalchemy
Django ORMPythonFull ORM (active-record-ish)QuerySet method chainsBlocking (+ async)Code-first + migrationsdjango-orm
PrismaTypeScriptFull ORM (data mapper)Schema-first + generated clientAsyncSchema-first (.prisma)prisma
TypeORMTypeScriptFull ORM (AR + data mapper)Decorators + query builderAsyncCode-firsttypeorm
GORMGoFull ORM (active-record-ish)Chainable methods + struct tagsBlockingCode-first (automigrate)gorm
entGoFull ORM (data mapper)Schema-as-code + generated buildersBlockingCode-first (codegen)ent
ActiveRecordRubyFull ORM (active record)Model + query methodsBlockingDb-first + migrationsactiverecord
Go database/sqlGoDriver (+ sqlx)Raw stringBlockingNonego-database-sql
postgres.jsJS / TSDriver + safe-SQLTagged templateAsyncNonepostgres-js
JDBIJavaSafe-SQL / micro-mapperRaw SQL (fluent + SQL objects)BlockingNonejdbi

Taxonomy

Each table re-cuts the same set by one axis. Forward-dated systems are named but not yet linked; their deep-dives arrive in the marked wave.

By abstraction level

The primary axis (see concepts: the abstraction ladder).

RungSystems
DriverGo database/sql, postgres.js, JDBC, ADO.NET
Safe-SQL / micro-mapperdoobie-adjacent, hasql, Dapper, JDBI, sqlc, sqlx, postgres.js
Typed query builderSlick, Squeal, Opaleye, Diesel, Kysely, Drizzle, jOOQ, linq2db, Exposed
Functional data mapperEffect TS, Quill, doobie, skunk, Ecto, Beam
Full ORM (data mapper)EF Core, Hibernate, SQLAlchemy, Prisma, ent, SeaORM, persistent
Full ORM (active record)ActiveRecord, Django ORM, GORM, TypeORM

By query construction model

How the query is expressed (concepts: query construction models).

ModelCheckedSystems
Raw stringneverGo database/sql, Dapper, JDBI (SQL objects)
Tagged templateruntimeEffect TS, doobie (sql"..."), postgres.js
Fluent typed buildercompilejOOQ, Kysely, Diesel, Drizzle, Exposed, SeaORM
Quoted DSL → ASTcompileQuill, EF Core / linq2db (LINQ)
Typed relational algebracompile (types)Slick, Squeal, Opaleye, Beam, esqueleto
Macro-checked raw SQLcompilesqlx, sqlc, cornucopia
Criteria / method chainsruntime/partialHibernate, SQLAlchemy, Django ORM, GORM, ActiveRecord, Ecto macros

By effect / async model

How a result is returned (concepts: effects, transactions & errors).

ModelSystems
BlockingDiesel, jOOQ, Hibernate, JDBI, Go database/sql, sqlc, GORM, ent, ActiveRecord, Django ORM
Async (future / promise / Task)sqlx, EF Core, Prisma, Kysely, Drizzle, TypeORM, postgres.js, SeaORM, linq2db, SQLAlchemy (asyncio)
Effect value (IO/ZIO/Effect)Effect TS, Quill (ZIO), doobie, skunk (cats-effect), Slick (DBIO run to an effect-poly F), Squeal (indexed PQ), Beam
Blocking IO (Haskell)hasql (Session), Opaleye (postgresql-simple), persistent + esqueleto (SqlPersistT)
Functional/immutable, blockingEcto (eager tagged-tuple Repo calls)

By schema stance

The library's relationship to the schema (concepts: schema, migrations & codegen).

StanceSystems
Code-first (models → schema)EF Core, Django ORM, TypeORM, Beam, Drizzle, Exposed, Ecto, GORM, ent, SeaORM
Schema-first (declaration)Prisma (.prisma), Slick (codegen input), Squeal (type-level)
Db-first (introspect + gen)jOOQ, sqlc, sqlx, Diesel, Kysely, Opaleye, ActiveRecord (schema.rb), linq2db
None (raw SQL, no schema)doobie, skunk, hasql, Dapper, JDBI, Go database/sql, postgres.js

Milestones

A high-confidence timeline of when the field's ideas and tools landed. Per-library provenance (and exact release dates) live in each deep-dive's Ecosystem & maturity and Sources; forward-dated entries are marked.

YearMilestone
1970Codd — the relational model (CACM)
1986–1992SQL standardized (SQL-86 … SQL-92); JDBC-style call-level interfaces emerge
2001Hibernate (Gavin King) — the archetypal JVM ORM; JDBI's ancestor era
2003–2004SQLAlchemy begins (Bayer); Ruby on Rails / ActiveRecord (DHH) popularizes active record
2005Django ORM ships with Django; Hibernate informs the JPA standard (JSR 220, 2006)
2007–2008LINQ + LINQ to SQL / Entity Framework (Microsoft) — language-integrated query on .NET
2009jOOQ (Lukas Eder) — typed SQL DSL generated from the schema
2012–2013Slick (Typesafe) — functional-relational mapping for Scala; Ecto work begins for Elixir
2015Ecto 1.0 (Elixir); doobie matures (Rob Norris); HugSQL/Yesod persistent era
2016Quill (Li Haoyi/Flavio Brasil) — compile-time QDSL; Diesel 1.0 (Rust); Opaleye/Beam (Haskell typed SQL)
2017–2019skunk (Norris) — pure-FP Postgres, no JDBC; hasql matures; ent (Facebook, Go); sqlc (Go)
2019Prisma 2 rewrite (schema-first + Rust query engine); EF Core matures as the .NET default
2020sqlx (Rust) — compile-time-checked raw SQL; SeaORM begins; GORM v2
2022Kysely + Drizzle (TypeScript) — type-safe query builders challenge the ORM default
2023–2026*Effect TS sql — DB-access-as-effect (typed SqlError, scoped acquirer); ProtoQuill (Scala 3); Drizzle/Kysely adoption surge

* 2023–2026 entries are current-as-of-review; exact dates are in the per-library deep-dives (some forward-dated pending their wave).


Quick navigation

Suggested reading paths

Library deep-dives

Grouped by category; see the master catalog for the one-line summary.


Sources

  • Each deep-dive's Sources section carries its primary sources (repository files + official docs), pinned in the survey's grounding ledger.
  • Shared vocabulary and the canonical pattern references (Codd; Fowler, Patterns of Enterprise Application Architecture; OWASP on SQL injection) are cited in concepts.