The name is derived from 'safe' and 'eris' (Greek for 'strife' or 'discord')
Saferis mitigates the discord of unsafe SQL. A type-safe, resource-safe SQL client library for Scala 3 and ZIO.
- SQL Injection Protection - Safe SQL interpolator with compile-time validation
- Multi-Database Support - Works with PostgreSQL, MySQL, SQLite, and any JDBC database
- Type-Safe Capabilities - Operations only available when your database supports them
- Resource Safety - Guaranteed connection and transaction management with ZIO
- Label-Based Decoding - Column-to-field mapping by name, not position
- Compile-Time Validation - Table schemas, column names, and SQL verified at compile time
- Unified Query Builder - Type-safe joins, subqueries, and pagination in one fluent API
- Streaming -
queryStreamfor real-time row iteration;pagedStream/seekingStreamfor cursor-based pagination with connection release between pages and checkpoint support for resumable processing
Add to your build.sbt:
// JVM. Add saferis and the one adapter for your database. Each adapter brings
// saferis-jdbc (java.sql only) and its own JDBC driver.
libraryDependencies ++= Seq(
"rocks.earlyeffect" %% "saferis" % "<version>",
"rocks.earlyeffect" %% "saferis-postgres-jdbc" % "<version>", // or one of the adapters below
)| Database | Adapter module | Layer | Dialect import |
|---|---|---|---|
| PostgreSQL | saferis-postgres-jdbc (pgjdbc) |
PostgresJdbc.layer() |
default |
| MySQL | saferis-mysql-jdbc (Connector/J) |
MySqlJdbc.layer() |
import saferis.mysql.given |
| SQLite | saferis-sqlite-jdbc (sqlite-jdbc) |
SqliteJdbc.layer() |
import saferis.sqlite.given |
| H2, for fast in-memory tests | saferis-h2-jdbc |
H2Jdbc.memory("db") >>> H2Jdbc.layer() |
import saferis.h2.given |
Each layer needs a javax.sql.DataSource (your pool) and provides a SqlSession.
A database Saferis does not ship needs a Dialect for its SQL and a JdbcAdapter for its driver. StandardJdbcAdapter covers what java.sql specifies, so an adapter overrides only what its driver does differently:
object MyDialect extends Dialect:
val name = DialectName("MyDb")
def columnType(tpe: SqlType): ColumnType = ...
def generatedKey(key: GeneratedKey): SqlText = ...
object MyAdapter extends StandardJdbcAdapter:
// Name the condition from a code this driver reports. Leave the SQLSTATE and the message as the server sent them.
override def serverError(e: SQLException): ServerError = ...
val session = JdbcSession.layer(MyAdapter)Every shipped database runs one conformance suite by providing its SqlSession and a description of itself as layers, and a database brought from outside the library runs the same suite the same way.
Node (Scala.js) uses saferis-postgres-node. Compile does not download pg. Install the same versions this repository links against, or require fails when the bundle loads:
libraryDependencies ++= Seq(
"rocks.earlyeffect" %%% "saferis" % "<version>",
"rocks.earlyeffect" %%% "saferis-postgres-node" % "<version>",
)npm install pg@8.16.3 pg-cursor@2.22.0Saferis provides compile-time guarantees that operations are only available when your database supports them:
| Feature | PostgreSQL | MySQL | SQLite |
|---|---|---|---|
| RETURNING clause | Yes | No | Yes |
| JSON operations | Yes | Yes | No |
| Array types | Yes | No | No |
| UPSERT | Yes | No | No |
Schema.verify |
Yes | Yes | Yes |
The query builder's .in / .inList binds one array parameter on PostgreSQL and one parameter per value elsewhere. In a raw sql string, in(...) works on every database.
Switch databases by changing one import - your code adapts automatically:
import saferis.* // PostgreSQL (default)
import saferis.mysql.{given} // Override with MySQL
import saferis.sqlite.{given} // Override with SQLiteSee the full documentation for:
- Complete API reference
- Running examples with real database output
- Dialect system details
- DDL and DML operations
- Advanced pagination patterns
The documentation is built with Specular: every example is a DocSpec that asserts under zio-test and runs against a live PostgreSQL database, so the output shown is real and the build fails if an example breaks.
./scripts/install-git-hooks # once per clone: pre-commit runs scalafmtCheckAll