Skip to main content

datumesh_store/
pg.rs

1//! `PgStore`: the store on PostgreSQL.
2
3use std::borrow::Cow;
4
5use crate::error::{Db, Error, Result};
6use crate::pg_unstable::Connector;
7use crate::pool::{Conn, Pool, PoolOptions, Pooled};
8use crate::slices::{check_entry, cmp_keys, is_hash, is_instant, version_slice, Constraint, Kind, Pattern};
9use crate::sql::Q;
10use crate::{Binding, Namespace, SliceMember, SliceValue, Store, Version};
11use datumesh_core::lexical::{check_dns_name, check_function_name, check_hex, check_instant};
12use datumesh_core::{
13    json, keys_from_response, sha256_hex, verify, Cell, ErrorCode, FunctionDescriptor, Keys, Manifest, Receipt,
14    Resolver, Scalar, Slice, Type,
15};
16
17/// The store on PostgreSQL, with a connection pool. `Send + Sync`: share one per process
18/// (behind an `Arc`); each call checks a connection out for its duration.
19///
20/// Every `put_*` is one transaction. [`Store`] methods return errors: a malformed argument
21/// is [`Error::Invalid`] (no query is made), a database failure [`Error::Database`]. The
22/// [`Resolver`] methods answer both with `None`; a database failure is logged with
23/// `tracing` (target `datumesh_store`, level `error`).
24pub struct PgStore {
25    pool: Pool,
26}
27
28impl std::fmt::Debug for PgStore {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("PgStore").finish_non_exhaustive()
31    }
32}
33
34impl PgStore {
35    /// Connects to a `postgres://` URL or `key=value` connection string with the default
36    /// [`PoolOptions`]: [`connect_with(url, PoolOptions::new())`](Self::connect_with).
37    pub fn connect(url: &str) -> Result<PgStore> {
38        PgStore::connect_with(url, PoolOptions::new())
39    }
40
41    /// Connects to a `postgres://` URL or `key=value` connection string, with a pool and TLS
42    /// roots as `options` say. One connection is opened at once, so a wrong URL, an
43    /// unreachable server or a refused certificate fails here; nothing else is done on it
44    /// (no statement is prepared, the schema is not looked at), so an empty database is fine
45    /// (create the schema with [`migrate::apply`](crate::migrate::apply)).
46    ///
47    /// TLS (rustls) is used as the URL's `sslmode` says: `disable` never, `prefer` (the
48    /// default) when the server offers it, `require` always. Whenever it is used, the
49    /// server's certificate and host name are verified, against the Mozilla roots plus the
50    /// roots added with [`PoolOptions::tls_root_certificates_pem`]; there is no way to
51    /// accept any certificate. So a server with `ssl=on` and a self-signed certificate (or
52    /// one from a private CA) fails even under the default `prefer`: add its CA, or connect
53    /// with `sslmode=disable`.
54    pub fn connect_with(url: &str, options: PoolOptions) -> Result<PgStore> {
55        let config = url.parse::<postgres::Config>().db()?;
56        let connector = Connector::new(config, &options)?;
57        PgStore::from_pool(Pool::new(Box::new(move || connector.connect()), options))
58    }
59
60    /// A store on `pool`, after opening one connection.
61    pub(crate) fn from_pool(pool: Pool) -> Result<PgStore> {
62        let store = PgStore { pool };
63        drop(store.pool.get()?);
64        Ok(store)
65    }
66
67    /// A pooled connection, held until dropped (migrations hold one throughout).
68    pub(crate) fn conn(&self) -> Result<Pooled<'_>> {
69        self.pool.get()
70    }
71
72    /// Prepares every SQL statement the store uses against the live schema, on a fresh
73    /// connection's behalf (no cached statement is reused), and returns how many there are.
74    /// Fails with the first statement the schema cannot serve.
75    pub fn check_statements(&self) -> Result<usize> {
76        let mut c = self.pool.get()?;
77        for q in Q::ALL {
78            c.client.prepare_typed(q.sql(), q.types()).db()?;
79        }
80        Ok(Q::ALL.len())
81    }
82
83    /// A [`Resolver`] lookup: a failure (logged by `run`) becomes `None`.
84    fn resolve<T>(&self, op: &'static str, f: impl FnOnce(&mut Conn) -> Result<Option<T>>) -> Option<T> {
85        self.run(op, f).ok().flatten()
86    }
87
88    /// Runs `f` on a pooled connection; a database or pool failure is logged.
89    fn run<T>(&self, op: &'static str, f: impl FnOnce(&mut Conn) -> Result<T>) -> Result<T> {
90        let result = self.pool.get().and_then(|mut c| f(&mut c));
91        if let Err(e @ (Error::Database(_) | Error::PoolTimeout)) = &result {
92            tracing::error!(target: "datumesh_store", op, error = %e, "database failure");
93        }
94        result
95    }
96
97    /// Versions from rows of (tx, receipt_hash, asof).
98    fn version_rows(rows: &[postgres::Row], cell_id: &str) -> Result<Vec<Version>> {
99        rows.iter().map(|r| version_row(r, cell_id)).collect()
100    }
101
102    /// A version (and its receipt's bytes) from a query answering
103    /// (tx, receipt_hash, asof, bytes) for (cell_id, asof).
104    fn asof_with_receipt(
105        &self,
106        op: &'static str,
107        q: Q,
108        cell_id: &str,
109        asof: &str,
110    ) -> Result<Option<(Version, Vec<u8>)>> {
111        check_hex(cell_id, 64, "cell_id")?;
112        check_instant(asof, "asof")?;
113        self.run(op, |c| {
114            let st = c.stmt(q)?;
115            let Some(row) = c.client.query_opt(&st, &[&cell_id, &asof]).db()? else { return Ok(None) };
116            Ok(Some((version_row(&row, cell_id)?, row.try_get(3).db()?)))
117        })
118    }
119
120    /// A version from a query answering (tx, receipt_hash, asof) for (cell_id, asof).
121    fn asof_version(&self, op: &'static str, q: Q, cell_id: &str, asof: &str) -> Result<Option<Version>> {
122        check_hex(cell_id, 64, "cell_id")?;
123        check_instant(asof, "asof")?;
124        self.run(op, |c| {
125            let st = c.stmt(q)?;
126            let rows = c.client.query(&st, &[&cell_id, &asof]).db()?;
127            Ok(PgStore::version_rows(&rows, cell_id)?.pop())
128        })
129    }
130}
131
132fn version_row(r: &postgres::Row, cell_id: &str) -> Result<Version> {
133    Ok(Version {
134        cell_id: cell_id.to_string(),
135        tx: r.try_get(0).db()?,
136        receipt_hash: r.try_get(1).db()?,
137        asof: r.try_get(2).db()?,
138    })
139}
140
141/// A member from a row of (tx, receipt_hash, asof, bytes, ...).
142fn member_row(r: &postgres::Row, cell_id: &str) -> Result<SliceMember> {
143    Ok(SliceMember { version: version_row(r, cell_id)?, receipt: r.try_get(3).db()? })
144}
145
146/// A member's value and cell from a row of (tx, receipt_hash, asof, value_type, value, ns,
147/// fn, fn_hash, "name:type:value" of each argument, ...): the cell is rebuilt from the
148/// typed-argument index (`cell_args` holds every argument's type and lexical form).
149fn value_row(r: &postgres::Row, cell_id: &str) -> Result<SliceValue> {
150    let bad =
151        |what: &str| Error::Invalid(datumesh_core::Error::new(ErrorCode::CanonType, "$", format!("a stored {what}")));
152    let scalar = |ty: &str, value: &str| -> Result<Scalar> {
153        let ty = Type::from_name(ty).ok_or_else(|| bad("type"))?;
154        Ok(Scalar::from_lexical(ty, value)?)
155    };
156    let (ty, value): (String, String) = (r.try_get(3).db()?, r.try_get(4).db()?);
157    let mut args = datumesh_core::Args::new();
158    for a in r.try_get::<_, Vec<String>>(8).db()? {
159        let mut parts = a.splitn(3, ':');
160        let (Some(name), Some(t), Some(v)) = (parts.next(), parts.next(), parts.next()) else {
161            return Err(bad("argument"));
162        };
163        args.insert(name.to_string(), datumesh_core::Arg::Value(scalar(t, v)?));
164    }
165    let cell = Cell { args, fn_hash: r.try_get(7).db()?, fn_name: r.try_get(6).db()?, ns: r.try_get(5).db()? };
166    Ok(SliceValue { version: version_row(r, cell_id)?, value: scalar(&ty, &value)?, cell })
167}
168
169fn get_bytes(c: &mut Conn, q: Q, key: &str) -> Result<Option<Vec<u8>>> {
170    let st = c.stmt(q)?;
171    let row = c.client.query_opt(&st, &[&key]).db()?;
172    Ok(match row {
173        Some(r) => Some(r.try_get(0).db()?),
174        None => None,
175    })
176}
177
178fn get_strings(c: &mut Conn, q: Q, key: &str) -> Result<Vec<String>> {
179    let st = c.stmt(q)?;
180    let rows = c.client.query(&st, &[&key]).db()?;
181    rows.iter().map(|r| r.try_get(0).db()).collect()
182}
183
184impl Resolver for PgStore {
185    fn descriptor(&self, fn_hash: &str) -> Option<Cow<'_, [u8]>> {
186        if !is_hash(fn_hash) {
187            return None;
188        }
189        self.resolve("descriptor", |c| get_bytes(c, Q::DescriptorGet, fn_hash)).map(Cow::Owned)
190    }
191
192    fn receipt(&self, hash: &str) -> Option<Cow<'_, [u8]>> {
193        if !is_hash(hash) {
194            return None;
195        }
196        self.resolve("receipt", |c| get_bytes(c, Q::ReceiptGet, hash)).map(Cow::Owned)
197    }
198
199    /// The stored manifest of slice entry `entry` ([`Store::put_manifest`]), its canonical
200    /// form; `None` when there is none, or `entry` is not an entry (since 0.3.1).
201    fn manifest(&self, entry: &str) -> Option<Cow<'_, [u8]>> {
202        if check_entry(entry, "$").is_err() {
203            return None;
204        }
205        self.resolve("manifest", |c| get_bytes(c, Q::ManifestGet, entry)).map(Cow::Owned)
206    }
207
208    /// `Some` only for a `stored` function (descriptor `code.lang` is `stored`, and the store
209    /// has the descriptor): the value of the receipt of the version that answers as of
210    /// `asof`, [`stored_asof`](Store::stored_asof)'s: the latest version (by tx) with `tx <=
211    /// asof` whose receipt's asof is also `<= asof` (the core's testkit
212    /// `AnswerRule::TxAndAsof`). `None` for every other function, and when no version
213    /// answers. (Until 0.2.0 it looked at `tx` only, as [`version_asof`](Store::version_asof)
214    /// still does; the same when every receipt's asof is its tx.)
215    fn evaluate(&self, cell: &Cell, asof: &str) -> Option<Scalar> {
216        if !is_instant(asof) {
217            return None;
218        }
219        self.resolve("evaluate", |c| {
220            let Some(desc) = get_bytes(c, Q::DescriptorGet, &cell.fn_hash)? else { return Ok(None) };
221            match FunctionDescriptor::from_json(&desc) {
222                Ok(d) if d.lang == "stored" => {}
223                Ok(_) => return Ok(None),
224                Err(e) => {
225                    tracing::error!(target: "datumesh_store", fn_hash = %cell.fn_hash, error = %e, "stored descriptor does not parse");
226                    return Ok(None);
227                }
228            }
229            let st = c.stmt(Q::ValueAsof)?;
230            let Some(row) = c.client.query_opt(&st, &[&cell.id(), &asof]).db()? else { return Ok(None) };
231            let bytes: Vec<u8> = row.try_get(0).db()?;
232            match Receipt::from_json(&bytes) {
233                Ok(r) => Ok(Some(r.value().clone())),
234                Err(e) => {
235                    tracing::error!(target: "datumesh_store", cell_id = %cell.id(), error = %e, "stored receipt does not parse");
236                    Ok(None)
237                }
238            }
239        })
240    }
241}
242
243impl Store for PgStore {
244    fn put_descriptor(&self, bytes: &[u8]) -> Result<String> {
245        // spec-0.4.0's descriptor: `signature.order` and argument `extent` allowed.
246        let d = FunctionDescriptor::from_json(bytes)?;
247        let hash = d.hash();
248        let canonical = d.canonical();
249        self.run("put_descriptor", |c| {
250            let st = c.stmt(Q::DescriptorPut)?;
251            c.client.execute(&st, &[&hash, &canonical]).db()?;
252            Ok(())
253        })?;
254        tracing::debug!(target: "datumesh_store", fn_hash = %hash, "put_descriptor");
255        Ok(hash)
256    }
257
258    fn put_receipt(&self, bytes: &[u8]) -> Result<String> {
259        // Steps 1-4 first: a receipt that is not one is refused without a query.
260        let r = Receipt::from_json(bytes)?;
261        // Steps 1-4 passed, so the text parses; its JCS is the canonical form.
262        let canonical = json::parse(bytes)?.to_jcs();
263        let hash = r.hash().to_string();
264        let cell_id = r.cell().id();
265        let host = r.signer_host().to_string();
266        // Receipt hashes are edges; slice entries (spec-0.4.0) slice edges.
267        let (entries, inputs): (Vec<String>, Vec<String>) =
268            r.inputs().iter().cloned().partition(|e| e.starts_with("slice:"));
269        self.run("put_receipt", |c| {
270            let ns = c.stmt(Q::NamespaceGet)?;
271            let put = c.stmt(Q::ReceiptPut)?;
272            let get = c.stmt(Q::ReceiptGet)?;
273            let edges = c.stmt(Q::EdgesPut)?;
274            let slice_edges = c.stmt(Q::SliceEdgesPut)?;
275            let mut t = c.client.transaction().db()?;
276            // Idempotent: the very receipt already stored verified when it was stored, and
277            // the stored copy is unchanged, so it is not checked again (even if its key has
278            // since been revoked).
279            if let Some(row) = t.query_opt(&get, &[&hash]).db()? {
280                let stored: Vec<u8> = row.try_get(0).db()?;
281                if stored == canonical {
282                    return Ok(());
283                }
284            }
285            // Steps 5-6 against the stored key document of the signer host; none stored is
286            // a host without keys (E_KEY_UNAVAILABLE).
287            let mut keys = Keys::new();
288            if let Some(row) = t.query_opt(&ns, &[&host]).db()? {
289                let doc: Vec<u8> = row.try_get(0).db()?;
290                keys.insert_response(&host, 200, Some(&doc));
291            }
292            let out = verify(bytes, &keys, None, None);
293            if !out.ok {
294                return Err(Error::Invalid(out.error().unwrap_or_else(|| {
295                    datumesh_core::Error::new(ErrorCode::Signature, "$", "the receipt did not verify")
296                })));
297            }
298            let n = t
299                .execute(&put, &[&hash, &canonical, &cell_id, &r.tx(), &r.asof(), &r.cell().fn_hash, &r.publisher()])
300                .db()?;
301            if n == 0 {
302                let row = t.query_one(&get, &[&hash]).db()?;
303                let stored: Vec<u8> = row.try_get(0).db()?;
304                if stored != canonical {
305                    return Err(Error::Conflict { hash: hash.clone() });
306                }
307            }
308            if !inputs.is_empty() {
309                t.execute(&edges, &[&hash, &inputs]).db()?;
310            }
311            if !entries.is_empty() {
312                t.execute(&slice_edges, &[&hash, &entries]).db()?;
313            }
314            t.commit().db()?;
315            Ok(())
316        })?;
317        tracing::debug!(target: "datumesh_store", hash = %hash, cell_id = %cell_id, "put_receipt");
318        Ok(hash)
319    }
320
321    fn put_blob(&self, bytes: &[u8]) -> Result<String> {
322        let sha = sha256_hex(bytes);
323        let length = i64::try_from(bytes.len()).unwrap_or(i64::MAX);
324        self.run("put_blob", |c| {
325            let st = c.stmt(Q::BlobPut)?;
326            c.client.execute(&st, &[&sha, &bytes, &length]).db()?;
327            Ok(())
328        })?;
329        tracing::debug!(target: "datumesh_store", sha256 = %sha, length, "put_blob");
330        Ok(sha)
331    }
332
333    fn blob(&self, sha256: &str) -> Result<Option<Cow<'_, [u8]>>> {
334        check_hex(sha256, 64, "sha256")?;
335        Ok(self.run("blob", |c| get_bytes(c, Q::BlobGet, sha256))?.map(Cow::Owned))
336    }
337
338    fn put_version(&self, cell_id: &str, tx: &str, receipt_hash: &str) -> Result<()> {
339        check_hex(cell_id, 64, "cell_id")?;
340        check_instant(tx, "tx")?;
341        check_hex(receipt_hash, 64, "receipt_hash")?;
342        self.run("put_version", |c| {
343            let lock = c.stmt(Q::Lock)?;
344            let meta = c.stmt(Q::ReceiptMeta)?;
345            let latest = c.stmt(Q::VersionLatestTx)?;
346            let put = c.stmt(Q::VersionPut)?;
347            let mut t = c.client.transaction().db()?;
348            t.execute(&lock, &[&"versions", &cell_id]).db()?;
349            let Some(row) = t.query_opt(&meta, &[&receipt_hash]).db()? else {
350                return Err(Error::NotFound { what: "receipt", key: receipt_hash.to_string() });
351            };
352            let (r_cell, r_tx): (String, String) = (row.try_get(0).db()?, row.try_get(1).db()?);
353            if r_cell != cell_id {
354                return Err(Error::Mismatch { what: "cell_id", expected: cell_id.to_string(), found: r_cell });
355            }
356            if r_tx != tx {
357                return Err(Error::Mismatch { what: "tx", expected: tx.to_string(), found: r_tx });
358            }
359            if let Some(row) = t.query_opt(&latest, &[&cell_id]).db()? {
360                let l: String = row.try_get(0).db()?;
361                if tx <= l.as_str() {
362                    return Err(Error::NotLater {
363                        what: "version",
364                        key: cell_id.to_string(),
365                        given: tx.to_string(),
366                        latest: l,
367                    });
368                }
369            }
370            t.execute(&put, &[&cell_id, &tx, &receipt_hash]).db()?;
371            t.commit().db()?;
372            Ok(())
373        })?;
374        tracing::debug!(target: "datumesh_store", cell_id, tx, receipt_hash, "put_version");
375        Ok(())
376    }
377
378    fn version_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>> {
379        self.asof_version("version_asof", Q::VersionAsof, cell_id, asof)
380    }
381
382    fn versions(&self, cell_id: &str, from: &str, to: &str) -> Result<Vec<Version>> {
383        check_hex(cell_id, 64, "cell_id")?;
384        check_instant(from, "from")?;
385        check_instant(to, "to")?;
386        self.run("versions", |c| {
387            let st = c.stmt(Q::VersionRange)?;
388            let rows = c.client.query(&st, &[&cell_id, &from, &to]).db()?;
389            PgStore::version_rows(&rows, cell_id)
390        })
391    }
392
393    fn latest(&self, cell_id: &str) -> Result<Option<Version>> {
394        check_hex(cell_id, 64, "cell_id")?;
395        self.run("latest", |c| {
396            let st = c.stmt(Q::VersionLatest)?;
397            let rows = c.client.query(&st, &[&cell_id]).db()?;
398            Ok(PgStore::version_rows(&rows, cell_id)?.pop())
399        })
400    }
401
402    fn dependents(&self, receipt_hash: &str) -> Result<Vec<String>> {
403        check_hex(receipt_hash, 64, "receipt_hash")?;
404        self.run("dependents", |c| get_strings(c, Q::Dependents, receipt_hash))
405    }
406
407    fn inputs(&self, receipt_hash: &str) -> Result<Vec<String>> {
408        check_hex(receipt_hash, 64, "receipt_hash")?;
409        self.run("inputs", |c| get_strings(c, Q::Inputs, receipt_hash))
410    }
411
412    fn put_namespace(&self, host: &str, keydoc_bytes: &[u8], validated_at: &str) -> Result<()> {
413        // SPEC 9.3: a 200 with this body, for this host (a DNS name equal to the document's).
414        keys_from_response(host, 200, Some(keydoc_bytes))?;
415        check_instant(validated_at, "validated_at")?;
416        let canonical = json::parse(keydoc_bytes)?.to_jcs();
417        self.run("put_namespace", |c| {
418            let lock = c.stmt(Q::Lock)?;
419            let get = c.stmt(Q::NamespaceGet)?;
420            let put = c.stmt(Q::NamespacePut)?;
421            let mut t = c.client.transaction().db()?;
422            t.execute(&lock, &[&"namespaces", &host]).db()?;
423            if let Some(row) = t.query_opt(&get, &[&host]).db()? {
424                let at: String = row.try_get(1).db()?;
425                if validated_at < at.as_str() {
426                    return Err(Error::NotLater {
427                        what: "namespace",
428                        key: host.to_string(),
429                        given: validated_at.to_string(),
430                        latest: at,
431                    });
432                }
433            }
434            t.execute(&put, &[&host, &canonical, &validated_at]).db()?;
435            t.commit().db()?;
436            Ok(())
437        })?;
438        tracing::debug!(target: "datumesh_store", host, validated_at, "put_namespace");
439        Ok(())
440    }
441
442    fn namespace(&self, host: &str) -> Result<Option<Namespace>> {
443        check_dns_name(host, "host")?;
444        self.run("namespace", |c| {
445            let st = c.stmt(Q::NamespaceGet)?;
446            let Some(row) = c.client.query_opt(&st, &[&host]).db()? else { return Ok(None) };
447            Ok(Some(Namespace {
448                host: host.to_string(),
449                keydoc: row.try_get(0).db()?,
450                validated_at: row.try_get(1).db()?,
451                suspended_at: row.try_get(2).db()?,
452            }))
453        })
454    }
455
456    fn bind_name(&self, fn_name: &str, fn_hash: &str, tx: &str) -> Result<()> {
457        check_function_name(fn_name, "fn_name")?;
458        check_hex(fn_hash, 64, "fn_hash")?;
459        check_instant(tx, "tx")?;
460        self.run("bind_name", |c| {
461            let lock = c.stmt(Q::Lock)?;
462            let exists = c.stmt(Q::DescriptorExists)?;
463            let latest = c.stmt(Q::NameLatest)?;
464            let put = c.stmt(Q::NamePut)?;
465            let mut t = c.client.transaction().db()?;
466            t.execute(&lock, &[&"names", &fn_name]).db()?;
467            let found: bool = t.query_one(&exists, &[&fn_hash]).db()?.try_get(0).db()?;
468            if !found {
469                return Err(Error::NotFound { what: "descriptor", key: fn_hash.to_string() });
470            }
471            if let Some(row) = t.query_opt(&latest, &[&fn_name]).db()? {
472                let l: String = row.try_get(0).db()?;
473                if tx <= l.as_str() {
474                    return Err(Error::NotLater {
475                        what: "name",
476                        key: fn_name.to_string(),
477                        given: tx.to_string(),
478                        latest: l,
479                    });
480                }
481            }
482            t.execute(&put, &[&fn_name, &tx, &fn_hash]).db()?;
483            t.commit().db()?;
484            Ok(())
485        })?;
486        tracing::debug!(target: "datumesh_store", fn_name, fn_hash, tx, "bind_name");
487        Ok(())
488    }
489
490    fn put_computed(&self, cell_id: &str, tx: &str, receipt_hash: &str) -> Result<()> {
491        check_hex(cell_id, 64, "cell_id")?;
492        check_instant(tx, "tx")?;
493        check_hex(receipt_hash, 64, "receipt_hash")?;
494        // No lock: the primary key serialises writers of one (cell_id, tx, asof). An insert
495        // that meets a concurrent one waits for it; if it committed, nothing is inserted and
496        // the row it wrote (visible to the next statement under READ COMMITTED) decides.
497        let asof = self.run("put_computed", |c| {
498            let meta = c.stmt(Q::ReceiptMeta)?;
499            let put = c.stmt(Q::ComputedPut)?;
500            let get = c.stmt(Q::ComputedGet)?;
501            let mut t = c.client.transaction().db()?;
502            let Some(row) = t.query_opt(&meta, &[&receipt_hash]).db()? else {
503                return Err(Error::NotFound { what: "receipt", key: receipt_hash.to_string() });
504            };
505            let (r_cell, r_tx, asof): (String, String, String) =
506                (row.try_get(0).db()?, row.try_get(1).db()?, row.try_get(2).db()?);
507            if r_cell != cell_id {
508                return Err(Error::Mismatch { what: "cell_id", expected: cell_id.to_string(), found: r_cell });
509            }
510            if r_tx != tx {
511                return Err(Error::Mismatch { what: "tx", expected: tx.to_string(), found: r_tx });
512            }
513            if t.execute(&put, &[&cell_id, &tx, &asof, &receipt_hash]).db()? == 0 {
514                let stored: String = t.query_one(&get, &[&cell_id, &tx, &asof]).db()?.try_get(0).db()?;
515                if stored != receipt_hash {
516                    return Err(Error::Conflict { hash: receipt_hash.to_string() });
517                }
518            }
519            t.commit().db()?;
520            Ok(asof)
521        })?;
522        tracing::debug!(target: "datumesh_store", cell_id, tx, asof, receipt_hash, "put_computed");
523        Ok(())
524    }
525
526    fn computed_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>> {
527        self.asof_version("computed_asof", Q::ComputedAsof, cell_id, asof)
528    }
529
530    fn stored_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>> {
531        self.asof_version("stored_asof", Q::StoredAsof, cell_id, asof)
532    }
533
534    fn cell_dependents(&self, cell_id: &str) -> Result<Vec<String>> {
535        check_hex(cell_id, 64, "cell_id")?;
536        self.run("cell_dependents", |c| get_strings(c, Q::CellDependents, cell_id))
537    }
538
539    /// One query: the version and its receipt's bytes.
540    fn computed_asof_with_receipt(&self, cell_id: &str, asof: &str) -> Result<Option<(Version, Cow<'_, [u8]>)>> {
541        Ok(self
542            .asof_with_receipt("computed_asof_with_receipt", Q::ComputedAsofReceipt, cell_id, asof)?
543            .map(|(v, b)| (v, Cow::Owned(b))))
544    }
545
546    /// One query: the version and its receipt's bytes.
547    fn stored_asof_with_receipt(&self, cell_id: &str, asof: &str) -> Result<Option<(Version, Cow<'_, [u8]>)>> {
548        Ok(self
549            .asof_with_receipt("stored_asof_with_receipt", Q::StoredAsofReceipt, cell_id, asof)?
550            .map(|(v, b)| (v, Cow::Owned(b))))
551    }
552
553    fn put_manifest(&self, bytes: &[u8]) -> Result<String> {
554        let m = Manifest::from_json(bytes)?;
555        let entry = m.entry();
556        let canonical = m.canonical();
557        let members = m.members();
558        let n = i32::try_from(members.len()).unwrap_or(i32::MAX);
559        self.run("put_manifest", |c| {
560            let put = c.stmt(Q::ManifestPut)?;
561            let put_members = c.stmt(Q::ManifestMembersPut)?;
562            let mut t = c.client.transaction().db()?;
563            // The entry hashes the content: a stored entry has these very bytes. A concurrent
564            // insert of it waits for the other; if that one committed, nothing is inserted
565            // here, members included (it inserted them).
566            let inserted = t
567                .execute(&put, &[&entry, &m.slice().id(), &m.members_hash(), &m.slice().fn_hash(), &n, &canonical])
568                .db()?;
569            if inserted == 1 && !members.is_empty() {
570                t.execute(&put_members, &[&entry, &members]).db()?;
571            }
572            t.commit().db()?;
573            Ok(())
574        })?;
575        tracing::debug!(target: "datumesh_store", entry = %entry, members = n, "put_manifest");
576        Ok(entry)
577    }
578
579    fn member_manifests(&self, receipt_hash: &str) -> Result<Vec<String>> {
580        check_hex(receipt_hash, 64, "receipt_hash")?;
581        self.run("member_manifests", |c| get_strings(c, Q::MemberManifests, receipt_hash))
582    }
583
584    fn slice_entries(&self, receipt_hash: &str) -> Result<Vec<String>> {
585        check_hex(receipt_hash, 64, "receipt_hash")?;
586        self.run("slice_entries", |c| get_strings(c, Q::SliceEntries, receipt_hash))
587    }
588
589    fn slice_dependents(&self, entry: &str) -> Result<Vec<String>> {
590        check_entry(entry, "entry")?;
591        self.run("slice_dependents", |c| get_strings(c, Q::SliceDependents, entry))
592    }
593
594    fn slice_members(&self, slice: &Slice, asof: &str) -> Result<Vec<SliceMember>> {
595        check_instant(asof, "asof")?;
596        let v = slice.to_value();
597        if let Some((cell_id, from, to)) = version_slice(slice, &v)? {
598            return self.versions_between(&cell_id, &from, &to, asof);
599        }
600        let pattern = Pattern::of(&v)?;
601        self.run("slice_members", |c| pattern.members(c, asof))
602    }
603
604    fn versions_between(&self, cell_id: &str, from: &str, to: &str, asof: &str) -> Result<Vec<SliceMember>> {
605        check_hex(cell_id, 64, "cell_id")?;
606        check_instant(from, "from")?;
607        check_instant(to, "to")?;
608        check_instant(asof, "asof")?;
609        self.run("versions_between", |c| {
610            let st = c.stmt(Q::VersionsBetween)?;
611            let rows = c.client.query(&st, &[&cell_id, &from, &to, &asof]).db()?;
612            rows.iter().map(|r| member_row(r, cell_id)).collect()
613        })
614    }
615
616    fn slice_values(&self, slice: &Slice, asof: &str) -> Result<Vec<SliceValue>> {
617        check_instant(asof, "asof")?;
618        let v = slice.to_value();
619        if let Some((cell_id, from, to)) = version_slice(slice, &v)? {
620            return self.run("slice_values", |c| {
621                let st = c.stmt(Q::VersionsBetweenValues)?;
622                let rows = c.client.query(&st, &[&cell_id, &from, &to, &asof]).db()?;
623                rows.iter().map(|r| value_row(r, &cell_id)).collect()
624            });
625        }
626        let pattern = Pattern::of(&v)?;
627        self.run("slice_values", |c| pattern.values(c, asof))
628    }
629
630    fn slice_changed_after(&self, slice: &Slice, asof: &str) -> Result<bool> {
631        check_instant(asof, "asof")?;
632        let v = slice.to_value();
633        if let Some((cell_id, from, to)) = version_slice(slice, &v)? {
634            return self.run("slice_changed_after", |c| {
635                let st = c.stmt(Q::VersionsChangedAfter)?;
636                c.client.query_one(&st, &[&cell_id, &from, &to, &asof]).db()?.try_get(0).db()
637            });
638        }
639        let pattern = Pattern::of(&v)?;
640        self.run("slice_changed_after", |c| pattern.changed_after(c, asof))
641    }
642
643    fn resolve_binding(&self, fn_name: &str, asof: &str) -> Result<Option<Binding>> {
644        check_function_name(fn_name, "fn_name")?;
645        check_instant(asof, "asof")?;
646        self.run("resolve_binding", |c| {
647            let st = c.stmt(Q::BindingAsof)?;
648            let Some(row) = c.client.query_opt(&st, &[&fn_name, &asof]).db()? else { return Ok(None) };
649            Ok(Some(Binding { fn_hash: row.try_get(0).db()?, tx: row.try_get(1).db()? }))
650        })
651    }
652
653    fn resolve_name(&self, fn_name: &str, asof: &str) -> Result<Option<String>> {
654        check_function_name(fn_name, "fn_name")?;
655        check_instant(asof, "asof")?;
656        self.run("resolve_name", |c| {
657            let st = c.stmt(Q::NameAsof)?;
658            let row = c.client.query_opt(&st, &[&fn_name, &asof]).db()?;
659            Ok(match row {
660                Some(r) => Some(r.try_get(0).db()?),
661                None => None,
662            })
663        })
664    }
665}
666
667impl Pattern {
668    /// The members as of `asof`, in key order.
669    fn members(&self, c: &mut Conn, asof: &str) -> Result<Vec<SliceMember>> {
670        let qs = [Q::SliceMembersAll, Q::SliceMembersText, Q::SliceMembersNum];
671        self.rows(c, asof, qs, 4, member_row, |m| &m.version.cell_id)
672    }
673
674    /// The members' values as of `asof`, in key order.
675    fn values(&self, c: &mut Conn, asof: &str) -> Result<Vec<SliceValue>> {
676        let qs = [Q::SliceValuesAll, Q::SliceValuesText, Q::SliceValuesNum];
677        self.rows(c, asof, qs, 9, value_row, |m| &m.version.cell_id)
678    }
679
680    /// The constraint columns of the member and change queries ($6-$10).
681    fn columns(&self) -> [Vec<String>; 5] {
682        let col = |f: fn(&Constraint) -> &str| -> Vec<String> { self.args.iter().map(|a| f(a).to_string()).collect() };
683        [
684            col(|a| a.name.as_str()),
685            col(|a| a.kind.as_str()),
686            col(|a| a.ty.as_str()),
687            col(|a| a.lo.as_str()),
688            col(|a| a.hi.as_str()),
689        ]
690    }
691
692    /// Whether a matching cell has a stored version answering from after `asof`: from
693    /// whichever side is smaller (both probed, up to 10,001), the function's receipts with an
694    /// asof after `asof` (then each one's cell checked against the slice), or the slice's
695    /// candidate cells (then each one's receipts by cell and asof).
696    fn changed_after(&self, c: &mut Conn, asof: &str) -> Result<bool> {
697        let (drive, candidates) = self.drive(c, true)?;
698        let st = c.stmt(Q::ProbeRecent)?;
699        let recent: i64 = c.client.query_one(&st, &[&self.fn_hash, &asof]).db()?.try_get(0).db()?;
700        let qs = if recent <= candidates.unwrap_or(i64::MAX) {
701            [Q::ChangedRecent; 3]
702        } else {
703            [Q::ChangedCandAll, Q::ChangedCandText, Q::ChangedCandNum]
704        };
705        let rows = self.query(c, asof, qs, drive)?;
706        match rows.first() {
707            Some(r) => r.try_get(0).db(),
708            None => Ok(false),
709        }
710    }
711
712    /// The bound or ranged argument holding the fewest cells (probed, up to 10,001, when
713    /// there are several, or when `count` asks), else none (every cell of the function); and
714    /// the number of candidates when probed.
715    fn drive(&self, c: &mut Conn, count: bool) -> Result<(Option<&Constraint>, Option<i64>)> {
716        let mut probe = |a: Option<&Constraint>| -> Result<i64> {
717            match a {
718                None => {
719                    let st = c.stmt(Q::ProbeCells)?;
720                    c.client.query_one(&st, &[&self.fn_hash]).db()?.try_get(0).db()
721                }
722                Some(a) => {
723                    let st = c.stmt(if a.ty == "number" { Q::SliceProbeNum } else { Q::SliceProbeText })?;
724                    c.client.query_one(&st, &[&self.fn_hash, &a.name, &a.ty, &a.lo, &a.hi]).db()?.try_get(0).db()
725                }
726            }
727        };
728        let constrained: Vec<&Constraint> = self.args.iter().filter(|a| a.kind != Kind::Open).collect();
729        Ok(match constrained.as_slice() {
730            [] => (None, if count { Some(probe(None)?) } else { None }),
731            [one] => (Some(*one), if count { Some(probe(Some(one))?) } else { None }),
732            several => {
733                let mut best: Option<(i64, &Constraint)> = None;
734                for a in several {
735                    let n = probe(Some(a))?;
736                    if best.is_none_or(|(m, _)| n < m) {
737                        best = Some((n, a));
738                    }
739                }
740                (best.map(|(_, a)| a), best.map(|(n, _)| n))
741            }
742        })
743    }
744
745    /// Runs one of a family of slice queries `qs` (all cells, a text range, a number range)
746    /// with the slice's parameters and the candidates narrowed by `drive`.
747    fn query(&self, c: &mut Conn, asof: &str, qs: [Q; 3], drive: Option<&Constraint>) -> Result<Vec<postgres::Row>> {
748        let [names, kinds, types, los, his] = self.columns();
749        let keys: Vec<String> = self.args.iter().filter(|a| a.kind != Kind::Value).map(|a| a.name.clone()).collect();
750        let nargs = i32::try_from(self.args.len()).unwrap_or(i32::MAX);
751        let (q, d_name, d_ty, d_lo, d_hi) = match drive {
752            None => (qs[0], "", "", "", ""),
753            Some(a) => {
754                let q = if a.ty == "number" { qs[2] } else { qs[1] };
755                (q, a.name.as_str(), a.ty.as_str(), a.lo.as_str(), a.hi.as_str())
756            }
757        };
758        let st = c.stmt(q)?;
759        c.client
760            .query(
761                &st,
762                &[
763                    &self.fn_hash,
764                    &self.ns,
765                    &self.fn_name,
766                    &nargs,
767                    &asof,
768                    &names,
769                    &kinds,
770                    &types,
771                    &los,
772                    &his,
773                    &keys,
774                    &d_name,
775                    &d_ty,
776                    &d_lo,
777                    &d_hi,
778                ],
779            )
780            .db()
781    }
782
783    /// The rows of one of the member queries `qs` (all cells, a text range, a number range),
784    /// each made into a `T` (the cell id in column `at`, the key after it), in key order.
785    fn rows<T>(
786        &self,
787        c: &mut Conn,
788        asof: &str,
789        qs: [Q; 3],
790        at: usize,
791        make: impl Fn(&postgres::Row, &str) -> Result<T>,
792        cell_of: impl Fn(&T) -> &String,
793    ) -> Result<Vec<T>> {
794        let (drive, _) = self.drive(c, false)?;
795        let rows = self.query(c, asof, qs, drive)?;
796        let mut out: Vec<(Vec<String>, T)> = Vec::with_capacity(rows.len());
797        for r in &rows {
798            let cell_id: String = r.try_get(at).db()?;
799            let key: Vec<String> = r.try_get(at + 1).db()?;
800            out.push((key, make(r, &cell_id)?));
801        }
802        out.sort_by(|(a, ma), (b, mb)| cmp_keys(a, b).then_with(|| cell_of(ma).cmp(cell_of(mb))));
803        Ok(out.into_iter().map(|(_, m)| m).collect())
804    }
805}