Address globally aggregated immutable key-value stores(s).
Address globally aggregated immutable key-value stores(s).
(bulk-delete-statement db-type table store-keys)(bulk-insert-statement db-type table store-key-values)(bulk-select-statement db-type table store-keys)Generate SELECT statement for multiple keys. Returns a vector with [sql-string & parameters].
Generate SELECT statement for multiple keys. Returns a vector with [sql-string & parameters].
(change-row-id connection table from to)(conditional-write-domain {:keys [dbtype dbname]})How far this store's fence reaches: :global, :machine, :process, or nil.
For the servers it follows the dbtype. For the two embedded databases it cannot, because one dbtype spells three different deployments and the difference is the whole answer:
h2 with a mem: name, and sqlite with :memory:, live in one JVM heap.
A second process on the same host opens an entirely DIFFERENT database, so
the honest domain is :process. Reporting :machine here would be an
OVER-claim — a caller asking (conditional-write? store :machine) would be
told yes about writers that cannot even see this data.h2 reached over tcp:// or ssl:// is a server like any other, and
orders writers anywhere on the network: :global.A database this does not recognise gets NO domain and :expected-revision is
refused. The statement would work on any SQL database; what cannot be guessed
is how far its answer reaches, and guessing generously is how a deployment
comes to believe it is fenced across hosts when it is not.
How far this store's fence reaches: `:global`, `:machine`, `:process`, or nil.
For the servers it follows the dbtype. For the two embedded databases it
cannot, because one dbtype spells three different deployments and the
difference is the whole answer:
- `h2` with a `mem:` name, and sqlite with `:memory:`, live in one JVM heap.
A second process on the same host opens an entirely DIFFERENT database, so
the honest domain is `:process`. Reporting `:machine` here would be an
OVER-claim — a caller asking `(conditional-write? store :machine)` would be
told yes about writers that cannot even see this data.
- `h2` reached over `tcp://` or `ssl://` is a server like any other, and
orders writers anywhere on the network: `:global`.
- otherwise both are a file, ordering processes on the host that holds it and
no further — and not even that on a network filesystem, where SQLite's own
documentation calls locking unreliable.
A database this does not recognise gets NO domain and `:expected-revision` is
refused. The statement would work on any SQL database; what cannot be guessed
is how far its answer reaches, and guessing generously is how a deployment
comes to believe it is fenced across hosts when it is not.(connect-store db-spec & {:keys [table opts] :as params})(copy-row-statement db-type table to from)(create-statement db-type table)(delete-statement db-type table)(delete-store db-spec & {:keys [table opts]})Drop the store's table. Uses a connection of its own rather than the shared pool, so deleting one tenant's store leaves every other store on the database untouched.
Drop the store's table. Uses a connection of its own rather than the shared pool, so deleting one tenant's store leaves every other store on the database untouched.
(extract-bytes obj dbtype)(fenced-insert-statement db-type table id header meta value)Insert the row for id, relying on the PRIMARY KEY to refuse it if the row
already exists — which is create-if-absent, evaluated by the database.
A plain INSERT rather than a dialect-specific upsert precisely because it must NOT overwrite. The refusal arrives as an integrity-constraint violation, which is SQLSTATE class 23 in the standard and in every driver here.
Insert the row for `id`, relying on the PRIMARY KEY to refuse it if the row already exists — which is create-if-absent, evaluated by the database. A plain INSERT rather than a dialect-specific upsert precisely because it must NOT overwrite. The refusal arrives as an integrity-constraint violation, which is SQLSTATE class 23 in the standard and in every driver here.
(fenced-read? env)(fenced-update-statement db-type table id header meta value expected-meta)Replace the row for id, but only while its meta column still holds
expected-meta. Row count 1 means the write happened, 0 that it was refused.
ONE statement, so the comparison and the write are the same step — the database evaluates it, which is what makes this backing's guarantee reach as far as the database does. No transaction is needed for that; an UPDATE is atomic on its own.
The comparison is on the META column rather than a separate version column. konserve's revision lives inside the serialized metadata, so for this row the meta bytes ARE the revision, and comparing them needs no schema change and no migration for existing tables.
SQL Server needs the length too. Its varbinary comparison treats trailing zero bytes as insignificant, so two values of different lengths can compare EQUAL — which for a fence means a stale write passing. Microsoft's own guidance is to test the length alongside the data, and DATALENGTH is how. The other dialects compare binary exactly: bytea, longblob and SQLite BLOBs are all memcmp.
Replace the row for `id`, but only while its `meta` column still holds `expected-meta`. Row count 1 means the write happened, 0 that it was refused. ONE statement, so the comparison and the write are the same step — the database evaluates it, which is what makes this backing's guarantee reach as far as the database does. No transaction is needed for that; an UPDATE is atomic on its own. The comparison is on the META column rather than a separate version column. konserve's revision lives inside the serialized metadata, so for this row the meta bytes ARE the revision, and comparing them needs no schema change and no migration for existing tables. SQL Server needs the length too. Its varbinary comparison treats trailing zero bytes as insignificant, so two values of different lengths can compare EQUAL — which for a fence means a stale write passing. Microsoft's own guidance is to test the length alongside the data, and DATALENGTH is how. The other dialects compare binary exactly: bytea, longblob and SQLite BLOBs are all memcmp.
The :operation values konserve puts in the env of a CONDITIONAL write. The
read it takes under the lock to evaluate that write carries the same one, which
is what lets the read path tell itself apart from every other read.
The `:operation` values konserve puts in the env of a CONDITIONAL write. The read it takes under the lock to evaluate that write carries the same one, which is what lets the read path tell itself apart from every other read.
(get-connection db-spec)Return the pooled DataSource for db-spec, opening it if needed, and take a
reference to it. Every call must be paired with a release (or
release-pool!), or the pool is never closed.
With :validate-pool? true in the spec, an already-open pool is probed
before it is handed out and rebuilt if it has been closed out of band. The
probe costs a connection checkout per connect, so it is off by default.
Return the pooled DataSource for `db-spec`, opening it if needed, and take a reference to it. Every call must be paired with a `release` (or `release-pool!`), or the pool is never closed. With `:validate-pool? true` in the spec, an already-open pool is probed before it is handed out and rebuilt if it has been closed out of band. The probe costs a connection checkout per connect, so it is off by default.
(integrity-violation? e)Is this the database refusing a duplicate primary key?
SQLSTATE class 23 is the standard's integrity-constraint violation. Measured
against every database this backend supports: postgres 23505, mysql 23000,
h2 23505, sqlserver 23000 — and sqlite, which reports NO SQLSTATE at all
(nil) and carries the refusal in the vendor code instead, 19 for
SQLITE_CONSTRAINT. Missing that case would turn create-if-absent on sqlite
from a clean rejection into a raw driver exception the caller cannot classify,
so the vendor code is matched too — narrowly, by driver class name, because
the number 19 means nothing in particular anywhere else.
Is this the database refusing a duplicate primary key? SQLSTATE class 23 is the standard's integrity-constraint violation. Measured against every database this backend supports: postgres 23505, mysql 23000, h2 23505, sqlserver 23000 — and sqlite, which reports NO SQLSTATE at all (`nil`) and carries the refusal in the vendor code instead, 19 for SQLITE_CONSTRAINT. Missing that case would turn create-if-absent on sqlite from a clean rejection into a raw driver exception the caller cannot classify, so the vendor code is matched too — narrowly, by driver class name, because the number 19 means nothing in particular anywhere else.
(offset-query db-type table offset)(pool-status)Registry snapshot for diagnostics and tests: pool key -> {:refs n :open? bool :db-spec spec}, with credentials stripped. Never returns the DataSource itself.
Registry snapshot for diagnostics and tests: pool key -> {:refs n :open? bool
:db-spec spec}, with credentials stripped. Never returns the DataSource
itself.(read-all db-type connection table id)Maximum keys per SELECT IN clause, by database type. Based on SQL parameter limits (1 param per key) and practical result set sizes.
Maximum keys per SELECT IN clause, by database type. Based on SQL parameter limits (1 param per key) and practical result set sizes.
(read-field db-type
connection
table
id
column
&
{:keys [binary? locked-cb] :or {binary? false}})(read-meta db-type connection table id)(read-operation env db-type connection table id)(release store env)Hand back this store's reference to its connection pool. Must be called when work on the store has finished.
The pool is shared with every other store on the same database, so it is only
closed once the last store using it has been released. Returns :closed,
:retained, :absent, :already-released or :stale (the pool this store
was opened against has since been closed out of band and rebuilt; the
store's reference no longer counts and nothing is closed).
{:force? true} in env closes the shared pool regardless of who else is
using it -- the pre-refcount behaviour, appropriate at process shutdown and
nowhere else.
Hand back this store's reference to its connection pool. Must be called when
work on the store has finished.
The pool is shared with every other store on the same database, so it is only
closed once the last store using it has been released. Returns `:closed`,
`:retained`, `:absent`, `:already-released` or `:stale` (the pool this store
was opened against has since been closed out of band and rebuilt; the
store's reference no longer counts and nothing is closed).
`{:force? true}` in `env` closes the shared pool regardless of who else is
using it -- the pre-refcount behaviour, appropriate at process shutdown and
nowhere else.(release-pool! db-spec & {:keys [force? token]})Give back one reference to the pool for db-spec. Closes and forgets the
pool when the last reference goes. Returns :closed, :retained or
:absent.
:force? true closes the pool no matter how many stores still hold it. That
is the old, unconditional behaviour -- a footgun on a shared database, and
only appropriate when shutting the whole process down.
:token is the token acquire-pool handed out with the reference. It makes
releases generation-safe: a holder of a retired pool decrements that retired
generation, never the replacement active under the same key. :stale means
the referenced generation has already been forcibly closed.
Give back one reference to the pool for `db-spec`. Closes and forgets the pool when the last reference goes. Returns `:closed`, `:retained` or `:absent`. `:force? true` closes the pool no matter how many stores still hold it. That is the old, unconditional behaviour -- a footgun on a shared database, and only appropriate when shutting the whole process down. `:token` is the token `acquire-pool` handed out with the reference. It makes releases generation-safe: a holder of a retired pool decrements that retired generation, never the replacement active under the same key. `:stale` means the referenced generation has already been forcibly closed.
(remove-from-pool db-spec)Retire the active pool for db-spec without interrupting its holders, so the
next connect-store builds a fresh generation. The retired pool remains
reference counted and closes when its final original holder releases it.
Retire the active pool for `db-spec` without interrupting its holders, so the next `connect-store` builds a fresh generation. The retired pool remains reference counted and closes when its final original holder releases it.
(retired-pool-status)Credential-free snapshot of pool generations retired by remove-from-pool.
Credential-free snapshot of pool generations retired by `remove-from-pool`.
How far a fenced write reaches, for the databases that are SERVERS. The
mechanism is the same everywhere — one UPDATE ... WHERE meta = ?, evaluated
by the database — but the reach is a property of where that database runs, and
a server on the network is reachable from any host, so its comparison orders
every writer anywhere.
How far a fenced write reaches, for the databases that are SERVERS. The mechanism is the same everywhere — one `UPDATE ... WHERE meta = ?`, evaluated by the database — but the reach is a property of where that database runs, and a server on the network is reachable from any host, so its comparison orders every writer anywhere.
(table-exists-query db-type table)(update-statement db-type table id header meta value)cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |