Skip to main content

Database

Struct Database 

Source
pub struct Database { /* private fields */ }
Expand description

A directory of tables: the JSONL logs, their index snapshots, the metadata catalog (hayson.json) describing them, and one writer lock (lock::DirLock, ADR-hjxp/0006 — one writer per database directory).

Implementations§

Source§

impl Database

Source

pub fn open(dir: &Path) -> Result<Database, DatabaseError>

Source

pub fn open_with( dir: &Path, options: OpenOptions, ) -> Result<Database, DatabaseError>

Opens (and creates when missing) a Hayson database directory:

  1. locks the directory for writing, unless options.readonly or options.lock is LockMode::Skip (ADR-hjxp/0006) — at the path the arrangement on disk puts the lock, settled under the lock itself (see Database::hold_for_open);
  2. reads hayson.json, or starts an empty catalog when the directory is new;
  3. for each declared table, opens its log and restores its registry from <table>.idx via checkpoint::restore_or_rebuild rather than always scanning the whole log;
  4. finishes an unfinished .hayson.txn journal, if one is on disk, before returning — so no write reaches a table until whatever a prior crash left half-committed is done.

A refusal at any step after the lock was taken releases it again before returning, so a directory this call rejected is left no more locked than it was found.

Source

pub fn resolve_layout( dir: &Path, options: &LayoutOptions, ) -> Result<Layout, DatabaseError>

Works out where this database’s files are before anything is opened or locked: the metadata document name the options ask for (refusing metadataFile and manifest naming two different files), then the arrangement — whatever the document on disk records, or the requested one when there is no document yet.

Runs before create_dir_all, so a refusal here leaves the directory exactly as it found it. Public for that same reason: a caller that only needs to know where the writer lock or the metadata document of a directory sits gets the paths without opening the database, taking the lock, or creating anything.

@param dir the database directory @param options the layout/metadataFile/manifest triple of OpenOptions, unresolved @returns where this database’s files are, or a refusal to name the metadata document twice

Source

pub fn resolve_layout_sealed( dir: &Path, options: &LayoutOptions, sealer: Option<&Sealer>, ) -> Result<Layout, DatabaseError>

Database::resolve_layout for a database whose metadata document is sealed with sealer.

Source

pub fn dir(&self) -> &Path

Source

pub fn layout(&self) -> &Layout

Where this handle’s files are on disk — the arrangement and the metadata document name it resolved at open, the read side of the TS Database.layout property.

Source

pub fn follow(&self) -> Option<FollowPolicy>

The follow policy every table of this database was opened with — None when reads never catch up unprompted, which is what a writable open resolves to.

Source

pub fn table_names(&self) -> Vec<&str>

Source

pub fn table(&self, name: &str) -> Option<&Table>

Source

pub fn table_mut(&mut self, name: &str) -> Option<&mut Table>

Handing out a writable table is the write path this type cannot see the end of, so the lease is renewed here as well — a host that never calls DirLock::tick still keeps its lock as long as it keeps writing. A lock already lost cannot be reported through an Option; the next call that returns a Result — commit and every schema change — raises DatabaseError::Lock instead.

Source

pub fn put_checked( &mut self, table: &str, document: &Value, force: bool, ) -> Result<HaysonRecord, DatabaseError>

Table::put, refusing first if document breaks an enforce: true relation declared on table. table_mut(name).put(...) skips this — every caller that wants enforcement honoured has to come in through here, because Table itself cannot see another table’s registry to check against (only Database holds every table at once).

Source

pub fn patch_checked( &mut self, table: &str, key: &str, changes: &Value, force: bool, ) -> Result<HaysonRecord, DatabaseError>

Table::patch, refusing first if changes breaks an enforce: true relation declared on table. See Database::put_checked for why this exists alongside table_mut(...).patch.

Source

pub fn stage_put_checked( &mut self, table: &str, document: &Value, force: bool, ) -> Result<HaysonRecord, DatabaseError>

Table::stage_put, refusing first if document breaks an enforce: true relation declared on table. Same rationale as Database::put_checked; the napi/wasm bindings call this instead of table_mut(name).stage_put(...) because their staged writes still run synchronously with full Database access at the call site, same as a direct put.

Source

pub fn stage_patch_checked( &mut self, table: &str, key: &str, changes: &Value, force: bool, ) -> Result<HaysonRecord, DatabaseError>

Table::stage_patch, refusing first if changes breaks an enforce: true relation declared on table. See Database::stage_put_checked.

Source

pub fn delete_checked( &mut self, table: &str, key: &str, force: bool, ) -> Result<bool, DatabaseError>

Deletes key from table, honouring every on_delete relation naming table as the to side — restrict refuses if a referencing record still exists, cascade deletes every referencing record (recursively, watching for a cycle in the relation graph), set-null clears the referencing field instead of deleting the row, and no-action/undeclared leaves referencing records untouched exactly as Table::delete always has (checked, not fixed — Database::check_integrity still finds what this deliberately leaves behind).

A table nothing points at (no relation names it as to) skips the plan and goes straight through Table::delete — the common case pays nothing extra, and force there behaves as it always has. Every effect a cascade needs lands in one UnitOfWork (Database::commit), so a restrict found three tables deep aborts the whole delete with nothing written, not a half-cascaded one. force has no equivalent inside that unit — a fresh unit has no pending per-key conflict to force past the way a plain Table::delete does — so it is honoured only on the no-relation fast path.

Source

pub fn tick(&self) -> Result<(), DatabaseError>

Renews the writer lease from outside — DirLock::tick reached through the lock this handle owns privately. A host without background threads (wasm32-wasip1) has no heartbeat of its own, so this is what a timer on its side drives; call it at least twice per LockOptions::heartbeat_ms. Does nothing on a readonly handle, which holds no lock.

Source

pub fn restore_outcome(&self, name: &str) -> Option<RestoreOutcome>

How the table named name was brought up at open — Restored from <table>.idx or Rebuilt by a full scan of its log. None when the database holds no such table.

Source

pub fn catalog_unchecked(&self) -> &Catalog

The metadata catalog as this handle last read it, without the drift gate — every table it describes may be one this handle does not serve. catalog is what a caller outside the engine should ask for.

Source

pub fn catalog(&self) -> Result<&Catalog, DatabaseError>

The metadata catalog as this handle last read it — the parsed form of hayson.json, which catalog::catalog_document turns back into the document a caller can serialize.

Refuses when any table open here has drifted from what the catalog now declares, because handing the document over would describe tables this handle does not serve. Refusing is all that happens: the handle is never reloaded behind the caller’s back, so the schema and the index set every open table was opened with stay exactly as they were.

Source

pub fn describe_table(&self, name: &str) -> Result<Value, DatabaseError>

The schema of one table as a standalone document, the shape create_table accepts — what the TS describeTable answers with.

Read off the live handle (table::describe_table_document), not off the catalog entry, so it describes the schema and the index set the table is actually being served with — every table the catalog declares is open here, both after open and after Database::refresh, so there is no catalog-only table to fall back to.

The drift gate here is the one table named, unlike Database::catalog, whose document covers every table and so has to answer for every table: a table of its own that moved on says nothing about this one.

Source

pub fn relations(&self, table: Option<&str>) -> Vec<Relation>

The relations the catalog declares.

@param table When given, keeps only the relations touching this table, on either end.

Source

pub fn check_integrity(&self) -> Result<IntegrityReport>

Walks every declared relation and reports the records whose reference has no counterpart — a diagnosis, not a gate; nothing is repaired, removed or written.

Source

pub fn refresh(&mut self) -> Result<bool, DatabaseError>

Catches up with everything other processes did to this directory since this handle last looked — the port of the TS Database.refresh(), in the same two steps and the same order:

  1. hayson.json is read again, and every table it now declares that this handle does not hold is opened, through the very code path open uses per table;
  2. every table already open here is caught up through Table::refresh.

Answers whether anything at all changed. A catalog that lost a table on disk does not close the one already open here: a live table is not dropped out from under a caller holding it.

Source

pub fn unit_of_work(&mut self) -> UnitOfWork

Starts a unit of work: stage put/patch/delete across several tables, then pass it to Database::commit, which lands it whole or not at all.

Source

pub fn commit( &mut self, unit: &mut UnitOfWork, ) -> Result<UnitOfWorkResult, DatabaseError>

Runs every operation staged in unit, across the tables it names, through the six-step commit txn::UnitOfWork::commit documents (resolve, barrier, prepare, journal, write, cleanup) — refused when the database is readonly.

The unit is borrowed, not consumed: a commit refused before anything was journalled leaves it staged and retriable, exactly as the TS engine leaves it.

Source

pub fn prepare_commit( &mut self, unit: &mut UnitOfWork, ) -> Result<PreparedUnit, DatabaseError>

The first phase of Database::commit — the readonly refusal, the lock renewal and UnitOfWork::prepare. Nothing is appended and no file is written, so a refusal from here leaves unit staged and retriable exactly as a refused commit does.

Split out for a caller that keeps the database behind a lock and wants to release it across the journal write, which touches no table: prepare here, UnitOfWork::journal with the database let go, then Database::apply_commit once it is held again. The caller must stay the only writer across all three, or the burned _seq numbers would land out of order.

Source

pub fn apply_commit( &mut self, prepared: PreparedUnit, ) -> Result<UnitOfWorkResult, DatabaseError>

The last phase of Database::commit: every prepared entry appended and the journal removed. See Database::prepare_commit for the contract a caller running the phases apart has to keep.

Source

pub fn create_table( &mut self, name: &str, schema_spec: Value, ) -> Result<(), DatabaseError>

Creates a table, or brings an existing one up to a newer (additive) schema — catalog::build does not check revision compatibility yet (see its own doc comment), so a genuinely breaking change is not refused here either; that check is still on the TODO list this method inherits.

Disk first: the catalog is written before self.catalog/self.tables change, so a failure past that point is the only way this call can leave the two out of step with each other.

The handle comes from open_catalog_table — the same helper open()/refresh() use — so a table migrated here carries exactly what it would carry after a restart, triggers included.

name is the key the table lands under in the catalog, and the schema’s own table is carried into every log line and index snapshot written for it. The two disagreeing is refused rather than resolved: the catalog key wins wherever they are read back, so accepting the spec would silently drop the name its author wrote.

Source

pub fn drop_field( &mut self, table: &str, field: &str, force: bool, ) -> Result<(), DatabaseError>

Takes a field off the schema of a table without touching its log — records written before the drop keep carrying the field on disk, reads just stop handing it back.

Refuses (leaving disk and self untouched) when the field is the primary key, is what the table partitions by, or an index/check/relation of the catalog still depends on it (catalog::build::drop_field, unconditional on force) — and, unless force is true, when a live record still holds a value for it (checked here, against the open table, per the TODO catalog::build::drop_field leaves for this layer).

The declaration check runs first on purpose: with both refusals due the caller is told about the one force cannot get them past, and the error class matches what the field’s dependents are — see packages/hayson/test/drop-pola-kolejnosc-odmowy.test.ts.

Source

pub fn declare_index( &mut self, table: &str, index_spec: Value, ) -> Result<(), DatabaseError>

Adds a named index to a table of the catalog — a unique index is refused whole if a live record already shares a value on it (catalog::declare_index, checked against the open table). The reopened table’s registry is built from the catalog’s full expanded index list (catalog::expand_indexes), so a composite or where-scoped index is enforced/served right away, same as the single-field unconditional case catalog::expand_indexes derives as sugar.

Reopened through open_catalog_table — the same helper open()/refresh() use — so the handle keeps its snapshot pairing, and the stale .idx is rewritten before this returns.

Source

pub fn declare_relation( &mut self, relation_spec: Value, force: bool, ) -> Result<(), DatabaseError>

Adds a named relation to the catalog (catalog::declare_relation). With enforce: true, refuses whole if a live record already breaks it — unless force is true, which declares it anyway and lets enforcement start from here on, existing orphans left as they are.

Unlike declare_index, nothing here reopens a table: a relation is not read by a table’s own registry the way an index is, so no live handle changes shape. from/to are read straight out of relation_spec first, ahead of the real parse inside catalog::declare_relation, only to know which two tables to flush and hand it — a malformed spec still gets the full, authoritative error from the parser, this is not validation.

Source

pub fn declare_trigger( &mut self, trigger_spec: Value, ) -> Result<(), DatabaseError>

Declares an AFTER trigger (catalog::build::declare_trigger, which refuses a spec naming a table the catalog does not declare) and refreshes every open table’s live trigger set from the result — both the source table trigger_spec.table names and the effect table trigger_spec.effect.table names, since either could be a live table this call did not itself just open. Disk first, same discipline as create_table/declare_index.

Source

pub fn flush(&mut self) -> Result<(), DatabaseError>

Appends every write staged against any table of this database and not yet committed — the TS Database.flush(). A no-op for a table with nothing in flight, which is every table of a database whose writes all went through put/patch/delete.

Source

pub fn close(self) -> Result<()>

Appends whatever every table still holds staged, snapshots the ones with writes since their last snapshot (see Table::needs_snapshot), then releases the writer lock, if this handle holds one — mirrors the TS Database.close(). The catalog needs no flush of its own: it is already durable (fsync) by the time the call that wrote it returns.

The lock is released whether or not a table refused its flush or snapshot: the handle is consumed either way, and a lock file left behind names a live pid that no staleness rule reclaims, so keeping it would lock this very process out of its own directory. The first refusal is what the caller gets, after every other table had its turn.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.