1use 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
17pub 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 pub fn connect(url: &str) -> Result<PgStore> {
38 PgStore::connect_with(url, PoolOptions::new())
39 }
40
41 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 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 pub(crate) fn conn(&self) -> Result<Pooled<'_>> {
69 self.pool.get()
70 }
71
72 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 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 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 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 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 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
141fn 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
146fn 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 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 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 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 let r = Receipt::from_json(bytes)?;
261 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}