Lazy stores

pub fn store_bind_lazy(local: reference, source: text) -> boolean fs#read

Bind a COLLECTION to a lazy source. After this, a lookup that MISSES fetches that one entry and inserts it, so the next lookup is an ordinary resident hit; a lookup that hits never leaves the process. The collection is therefore automatically the cached data set — there is no separate cache.

Per COLLECTION, not per store: persons and companies are different sources, and two collections of one type can bind differently. Binding replaces, and may be done before the collection holds anything.

source is either an IMAGE — what store_load_key accepts: a local .store file or an http(s):// URL served with Range — or a DATABASE, named by a driver prefix. Returns false for a null collection. persons: hash<Person[id]> = [] store_bind_lazy(persons, "people.store") p = persons[42] // fetches exactly entry 42, then holds it

sqlite:<path> binds to a table instead, and the query is DERIVED from the collection's own type: the table is the element type's name lowercased, the columns are its fields, and the WHERE is the collection's key. Nothing is written down twice. persons: hash<Person[id]> = [] // struct Person { id: integer, name: text } store_bind_lazy(persons, "sqlite:people.db") p = persons[42] // SELECT "id","name" FROM "person" WHERE "id" = 42

Read-only, and the connection enforces it. A binding that cannot be served — a field that is not a column, a collection whose KIND the source cannot read — is REFUSED rather than served wrongly, and says so through store_lazy_error. sqlite is opened on the first fault, so a program that binds no database loads nothing.

FALSE means the binding was not made, and it is worth checking. A .store IMAGE is read a page at a time, which only a hash, a trie or a spatial supports: a sorted or index bound to one is refused HERE, at the call that is wrong, rather than answering null at every later lookup. Those kinds load whole — store_load / store_load_url_trusted carry all of them. A DATABASE source judges its own schema on the first fault instead, since what it can serve is a fact about the other end. if !store_bind_lazy(tiles, "tiles.store") { store_load(tiles, "tiles.store"); // whole-image, every kind }

pub fn store_lazy_query(local: reference, condition: text) -> integer fs#read

Run an explicit condition against this collection's bound DATABASE source and pull every matching row into the collection. Answers how many records the collection gained.

The escape hatch for what the collection's KEY cannot express — a predicate on another column, a pattern, a range nobody declared an index for. A keyed lookup derives its own query and needs no call; this one cannot be derived, so it is written down and visible rather than happening behind a lookup.

found = store_lazy_query(persons, "name LIKE 'Ada%'"); p = persons[42] // hits what the query already brought in

The rows land IN the collection, not in a separate result: a person reached by this query and the same person reached by a later lookup are ONE record, and a row already resident is left alone rather than fetched twice. So len and iteration keep answering "what have I got" — after this, more.

condition is SQL, sent as written (the connection is read-only). Answers 0 both when nothing matched and when the query could not run; store_lazy_error tells those apart.

pub fn store_lazy_range(local: reference, lo: integer, hi: integer) -> integer fs#read

Pull a whole KEY RANGE from this collection's bound DATABASE source in ONE query. Answers how many records the collection gained.

This is what keeps lazy reading usable rather than merely correct. Fetching 500 records one lookup at a time is 500 round trips; the same 500 as a range is one. So when you know the span you want, ask for the span:

store_lazy_range(events, 100, 199); // one query, up to 100 records for e in events { .. } // all resident, no further fetching

The collection must be ORDERED (sorted or index) — a hash has no order to range over — and keyed on ONE column, since two numbers cannot say which value pins a composite key's leading column; use store_lazy_query for that. Both bounds are inclusive, in the collection's own key order. A record already resident is left alone. Answers 0 when nothing matched AND when the query could not run; store_lazy_error tells those apart.

pub fn store_lazy_error(local: const reference) -> text

Why a lazy fetch for this collection could not REACH its source, or "" when it is healthy.

A lookup cannot tell you this. C80 says a value read never raises, so a miss answers null whether the key is genuinely absent or the source is unreachable — and those are different facts, one stable and one not. Ask this after a null to tell them apart:

p = persons[42]; if p == null { why = store_lazy_error(persons); if why == "" { /* really no such person */ } else { /* could not reach: {why} */ } }

The FIRST failure's reason, kept — not the last: it names the original cause, and later ones are usually the same failure repeating. Nothing clears it but store_lazy_clear. An absence does NOT, and neither does a later success: reaching the source now says nothing about what an earlier failure already lost, and answering "healthy" over a traversal that missed data is the silent wrong answer this channel exists to prevent.

pub fn store_lazy_faults(local: const reference) -> integer

How many fetches could not REACH this collection's source. 0 is healthy. The magnitude behind store_lazy_error: after a traversal it answers "how incomplete am I".

pub fn store_lazy_clear(local: reference) -> boolean

Acknowledge this collection's fetch failures, returning whether there was anything to acknowledge.

The ONLY thing that clears them. A later fetch happening to succeed does NOT: a traversal whose first lookup could not reach the source and whose second could is MISSING data, and answering "healthy" afterwards would be exactly the silent wrong answer this channel exists to prevent. Clearing is a caller saying "I have seen this", which is a different event entirely.

pub fn store_lazy_fail(local: reference, why: text) fs#read

A loft DRIVER reporting that it could not reach its source.

The writing end of the channel store_lazy_error reads. A driver written in loft (fn lazy_fetch(...)) has the same three answers a Rust source has, and two of them are an integer: 1 inserted, 0 absent. The third is not — "the source is down" carries a REASON, and answering 0 for it would be a silent wrong answer, because a caller cannot tell it from "no such person".

fn lazy_fetch(coll: hash<Person[id]>, source: text, key_int: integer, key_text: text) -> integer { if !db.db_open(source) { store_lazy_fail(coll, "cannot open {source}: {db.db_last_error()}"); return 0; } .. }

Sticky and counted exactly like a Rust source's failure: the FIRST reason is kept, every failure is counted, and only store_lazy_clear clears them.