Databases
Add saferis and the one adapter module for your database. Each adapter brings saferis-jdbc (or the Node driver) and its own driver, and its layer turns your pool (javax.sql.DataSource) into a SqlSession. Everything above the session, sql"...", Table, the query builder, and transact, is the same on every database.
| Database | Module | Layer | Dialect |
|---|---|---|---|
| PostgreSQL | saferis-postgres-jdbc | PostgresJdbc.layer() | default |
| PostgreSQL on Node | saferis-postgres-node | NodeSession.layer | default |
| MySQL | saferis-mysql-jdbc | MySqlJdbc.layer() | import saferis.mysql.given |
| SQLite | saferis-sqlite-jdbc | SqliteJdbc.layer() | import saferis.sqlite.given |
| H2 | saferis-h2-jdbc | H2Jdbc.layer() | import saferis.h2.given |
Each adapter names a SqlCondition from a code the server actually sent. The SQLSTATE and the message stay as the server sent them, so UniqueViolation means a unique violation on every database without inventing a Postgres code for MySQL or SQLite. Every shipped adapter runs one conformance suite, provided as a layer, and so does a database you bring (see Getting Started).
Portable code does not continue after a statement error. Postgres will not let it. MySQL, SQLite, and H2 will.
This page says what each adapter does that the others do not.
PostgreSQL
Streams read through a server-side cursor. Over JDBC a pool stream opens a read transaction with a fetch size of 256, because pgjdbc only honors a fetch size with autocommit off, so a long stream holds that transaction open. On Node the stream pulls 256 rows per
pg-cursorread.Arrays, enums,
jsonb,RETURNING, and upsert are available. The query builder's.inListbinds one array parameter,= ANY($1).Schema.verifyreadsinformation_schemaandpg_catalog.Node always sets
DateStyle=ISO, which its decoding depends on; aDateStyleinPgConnectionConfig.parametersis replaced. The pool turns on TCP keepalive, so a peer that vanished fails aCOMMITinstead of hanging it. npmpg@8.16.3andpg-cursor@2.22.0are required at run time.A statement error aborts the transaction. The next command is
Abortedwith SQLSTATE25P02, andCOMMITis rejected. Catching the error does not clear that.Node transport failures (
ECONNRESET,EPIPE) areSaferisError.ConnectionLost. They are not a SQLSTATE, sosqlStateis empty, and the message keeps the code. A JDBCIOExceptionis the same:ConnectionLostwith no SQLSTATE.
MySQL
The session time zone is UTC. Every checkout runs
SET time_zone = '+00:00'before yourconfigure, so atimestampcolumn stores and returns UTC and anInstantround-trips whatever zone the JVM or server is in. That also meansNOW()andCURRENT_TIMESTAMPin your own SQL return UTC. Aconfigurethat sets another time zone runs after it and wins, at the cost of that round trip.Streams read row by row (Connector/J's streaming mode, fetch size
Integer.MIN_VALUE). While a stream is open its connection cannot run another statement, so insidetransactfinish the stream before the next statement.DDL:
Numericisdecimal(65, 30),Timestampisdatetime(6),TimestampTzistimestamp(6), andTimeistime(6).import saferis.mysql.givenmakes aUUIDachar(36).Tables created by Saferis 0.19 used
decimal(which isdecimal(10,0)) andtimestampforLocalDateTimetoo. ALocalDateTimefield on such atimestampcolumn now reads as an instant and fails to decode; change the column todatetime(6)or the field toInstant.Errors: the errno picks the condition and the Connector/J SQLSTATE stays on the error. 1062 is unique, 1451 and 1452 foreign key, 1048 not null, 3819 check, 1213 deadlock, and 3024 canceled. The message is MySQL's sentence.
A statement error leaves the transaction open. A caught unique violation can be followed by another statement, and commit persists.
No array parameters: the query builder binds
IN (...), and binding an array fails withSaferisError.Unsupported. MySQL has noRETURNINGor upsert capability.Schema.verifyreadsinformation_schema, with names folded to lower case as MySQL compares them.
SQLite
Every checkout turns on
foreign_keys,journal_mode = WAL, and a 5 secondbusy_timeout, before yourconfigure. WAL persists on the database file and creates-waland-shmfiles next to it, and it does not work on network file systems. With WAL a reader never blocks a writer, and a writer that meets another waits instead of failing; one that still cannot get the lock fails with a retryableSerializationFailure.Checkouts are not serialized. A single-permit session would deadlock a stream that writes each row through another connection. SQLite's own locking and the busy timeout serialize writers instead.
Types: columns read by the type they were declared with (see Dialect System). Dates, times, and timestamps are ISO-8601 text, every integer is 64 bits, and a decimal uses SQLite's
numericaffinity, which stores it as a number with SQLite's precision. Ajsoncolumn has numeric affinity too, so a document that is a bare number, such as1e5, is stored as a number and reads back as SQLite prints it.Tables created by Saferis 0.19 declared booleans as
integer, every float and decimal asreal, and dates and timestamps astext. On those columns a boolean or date/time field fails to decode; recreate those tables from the current DDL. Floats and doubles still read correctly, because arealcolumn reads as the 8-byte double SQLite stores.ddl.addColumnnames the column. It used to emitalter table t add column <type>with no name, which SQLite rejects.Errors: the result code names the condition. Unique and primary key, foreign key, not null, and check are those violations. Busy and locked are
SerializationFailure. Interrupt isTimeout. Any other result code, includingSQLITE_ERROR, isQueryError. The message is SQLite's sentence. A SQLSTATE is not invented.A statement error leaves the transaction open. A caught unique violation can be followed by another statement, and commit persists.
Schema.verifyreads the pragma table functions. SQLite does not keep the names of unique or foreign-key constraints, so those match by columns andstrictNameMatchingcannot match their names.No array parameters: binding one fails with
SaferisError.Unsupported.
H2
H2 is here for fast in-memory tests of code that runs on another database in production. H2Jdbc.memory("name") is a DataSource for a named in-memory database that lives until the JVM exits, with DATABASE_TO_LOWER set so unquoted names fold to lower case as they do on PostgreSQL. H2Dialect renders H2's own SQL and claims no optional capability, so the query builder binds IN (...), and there is no RETURNING, upsert, or Schema.verify. JSON binds as UTF-8 bytes, which H2 reads as JSON text.
A statement error leaves the transaction open. A caught unique violation can be followed by another statement, and commit persists.