The first cut of this adapter since 0.16.0; it sat 0.17.0 through 0.18.1 out. No table or column moves, and a database written under 0.16.0 opens unchanged.
A row written under an earlier core reads back as the current record. A frame is the
whole record nippy froze, so a row carries every field the record type had when it was
written. Core 0.19.0 drops a sentex's :polarity, a rule's :sentence and a
justification's :out, and a rule firing's justification no longer lists the rule
handle among its :antecedents. A fetch now passes the thawed record through the
engine's codec/decode-sentex / codec/decode-justification, which drop those fields
and take the rule out of :antecedents, so a fetch from an upgraded database returns
the record core 0.19.0 would have written. Class: Fix. Migration: none; requires core 0.19.0.
The dependencies move to Clojure 1.12.6, nippy 3.9.0 and sqlite-jdbc 3.53.4.0. nippy matches the engine's pinned 3.9.0, so the reflection check compiles the engine against the nippy it ships with. Class: Fix. Migration: none.
The backend suite declares its rule forward. Core 0.18.0 made a bare implies rule
backward-only, so the persist and recover tests' rule stopped materializing its
conclusion on assert and the tests read empty. populate! asserts the rule with a
forward direction. Class: Fix — a test; no table, column or call moves.
Migration: none.
The prose check's P4 refuses a copula with are straight after it. A tree-wide
substitution that rewrites a phrase without fixing its verb leaves the old copula
standing before are, and no sentence needs the pair. A clojure.test require that
refers both the is and the are macros, and a hyphenated compound ending in a
copula, are excluded. Class: Additive — lint only. Migration: none.
The number. This adapter ships at the engine's version and pins core 0.19.0, which
carries five Breaking entries and four Refusals. Three of the Breaking entries
change the fields of the records this store freezes: a sentex drops :polarity, a rule
drops :sentence, and a justification drops :out and the rule handle from
:antecedents. The first entry above is how those three reach this store. The other two
Breaking entries and the four Refusals read a KB rather than a store and reach nothing
here.
No behaviour change, no schema change and nothing a caller can observe. A database written under 0.15.0 opens unchanged and the tables do not move.
scripts/check-prose.py — the engine's check, adapted to this tree's layout — runs
as the prose row in scripts/lint.sh with a lein lint-prose alias. The two
metaphors this repo carried are replaced by what each one named: the engine's storage
protocols (RecordStore, SnapshotSink / SnapshotSource) and the public entry point
a caller reaches them through. The evaluative phrasings become statements of what the
assertion checks. Class: Fix — docstrings and test names only; no table, column or
call moves. Migration: none.The number. The engine, the plugin and both adapters ship one version string, checked
at the cut. Requires core 0.16.0, which carries three Breaking entries and one
Refusal: a :missing-adapter refusal split out of :unknown-backend, a CLI operand
count moved from :unknown-option to :bad-args, different no longer provable against
an unpinned indeterminate_term, and a rule concluding an indeterminacy from a
different antecedent refused as :not-stratified. The first reaches this repo in one
direction only: with this adapter on the classpath, a :sqlite records axis resolves and no
refusal is raised. The other three read a KB rather than a store and reach nothing
here.
The first cut of this adapter since 0.13.0 — it sat 0.14.0 out — and it carries no behaviour change, no schema change and nothing a caller can observe. A database written under 0.13.0 opens unchanged and the tables do not move.
:polarity. The engine
renamed the sentex map's :truth slot to :polarity with :positive / :negative
values, and renamed the AtomicSentex record to LiteralSentex. Neither reaches this
store, and the reason is the property the store is built on: it freezes the whole
record with nippy and thaws it back type-identical, so what a field is called is not
something it has an opinion about, and a record frozen under 0.13.0 thaws back under
the new class without a migration. The only places the old spellings appeared were the
hand-built test fixtures and the schema docstring that promises the round-trip.
Class: Fix — a test and a docstring; no table, column or call moves.The number. The engine, the plugin and both adapters ship one version string, checked at the cut. Requires core 0.15.0.
A source over a database no sink has written answers absent, and cleanup is
the no-op it claims. read-manifest on a file with no image tables answers
nil — the protocol's absent case — so a first-run load-index! reports
{:index :rebuild :reason :absent} instead of throwing no such table, and
drop-image! skips a table that is not there, as its docstring says.
Class: Fix. Migration: none.
The four quiet entry points are quiet. put-provenance, delete-provenance!,
mark-premise and unmark-premise! gate on id-ok? the way every fetch
does, so a handle this store could never have issued — an informant keyword —
is a no-op rather than a driver error or a ClassCastException out of the
cache key. Class: Fix. Migration: none.
A failed open releases its connection. sqlite-record-store and
sqlite-sink close the connection they borrowed when construction throws
past the borrow, so a retried open of a corrupt or locked file no longer
leaks a handle and its WAL per attempt. Class: Fix. Migration: none.
First release of the SQLite sibling (com.vaelii/sqlite) — an
Apache-2.0 adapter on the SSPL engine. It depends on core; core never depends
on it. Requires core 0.12.0: the record store answers protocols that land there — the
bulk sink, the tallies, the two bare BulkAnnotating writes — and reads
vaelii.impl.profile/record-fetch, which lands in 0.11.0. The family releases in
lockstep, one version string across the engine, the plugin and the two adapters, which
is why a first release is numbered 0.12.0.
A bulk load is a transaction per batch, and import! takes it. A put here is a
transaction of its own — a commit and a WAL write per record — which is the whole of
what a corpus load pays. The store answers core's BulkLoading protocol
(open-sentex-sink / open-justification-sink) with a sink that lands :batch rows
per transaction in one execute-batch!, so an import! writes its records through it
without naming this namespace. It is a load and not an upsert — a plain INSERT,
so a handle the store already holds raises on the primary key rather than being
replaced silently, which is the contract the Postgres adapter's COPY has. Through
import! on a 30k-record dump, {:belief? false}: 7,444 → 16,572 records/s,
against core's :disk at 6,790. Class: Additive — a new optional capability; a
caller that does not open a sink writes exactly as it did.
The premise marks and the provenance batch too. A statement here is a transaction
of its own, so core's BulkAnnotating lands as one execute-batch! inside one
transaction for each — the same UPDATE and the same upsert the one-row ops run, with
the same WHERE guarding against marking a handle that has no sentex. It is what an
import! at {:belief? true} or {:belief? :stored} writes its marks and its
provenance through. Class: Additive — a caller that does not ask for a batch writes
exactly as it did.
The roster is compressed, and the tallies leave it alone. The three enumerations
answer core's vaelii.impl.roster rather than a PersistentHashSet<Long> — at 48–75
bytes a handle the roster of a large store is the caller's heap, and handles arrive in
the near-contiguous run next-id mints, which a bitmap holds at a fraction of a byte
apiece and probes faster. The walk goes through jdbc/plan rather than execute!, so
it never builds a result map per row on the way to discarding it. Beside it, core's
Tallying: open-kb asks how many records and is there one at all twice before the
KB has answered anything, and each is now a count(*) or a LIMIT 1 instead of a table
scan and a roster built out of it. Class: Additive — the enumerations answer the same
handles, and the protocol's contract is what says a set may be either shape.
A SQLite snapshot sink and source. vaelii.sqlite.snapshot provides
sqlite-sink (a SnapshotSink writing a KB image to a single-file database) and
sqlite-source (a read-only SnapshotSource reading it back), over the engine's
snapshot protocol (vaelii.impl.io.snapshot). It holds the index projection today
and any of the protocol's named sections as they land. A section written through this
sink reads back frame-identical through any source — file, memory, or SQLite —
and a mismatched image is discarded and rebuilt, never trusted (the engine's
shared snapshot/decision). Class: Additive — a new snapshot target; nothing in
core changes.
A live embedded-SQLite record store, wired as the :sqlite backend.
vaelii.sqlite.record_store provides a RecordStore over a single-file
<dir>/records.sqlite — the durable ground truth a KB reads and writes. Core
resolves it lazily under {:records :sqlite} (the sugar :sqlite), so the engine
carries no JDBC dependency and a KB that never asks for it loads none; off the
classpath the backend refuses by name with the coordinate to add. A durable
:disk index over :sqlite records is refused exactly as it is over :memory.
Class: Additive — a new records backend; :memory and :disk are unchanged.
Verified end to end through core (test/vaelii/sqlite/backend_test.clj).
Three properties the fetch LRU in front of it owes, each with a test:
Integer 5 and
Long 5 are equal numbers and different java.util.Map keys, so a delete whose
handle arrives as one must still evict an entry stored under the other — otherwise a
record the store no longer holds goes on answering get-sentex for the life of the
store. Clojure's own maps normalize integer hashing, which is why the RAM and disk
backends have nothing to do here.RecordStore requires,
so vaelii.impl.profile's :fetches counts what a caller asked for and is
comparable with what the other backends report.close! still lets go of the file and its WAL.
fsync is a no-op with a reason: SQLite's synchronous=NORMAL decides when a write
lands, and there is no client-side buffer for the engine to force.Pick by shape: a snapshot is a frozen image you re-export (backup, shipping a corpus); a record store is an always-current backend the KB reads and writes.
Docs: this repo's README.md; the protocol is core's storage.md.
Can you improve this documentation?Edit on GitHub
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 |