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::sql::Q;
9use crate::{Binding, Namespace, SliceMember, SliceValue, Store, Version};
10use datumesh_core::json::Value;
11use datumesh_core::lexical::{check_dns_name, check_function_name, check_hex, check_input_entry, 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
34fn is_hash(s: &str) -> bool {
35 check_hex(s, 64, "$").is_ok()
36}
37
38fn is_instant(s: &str) -> bool {
39 check_instant(s, "$").is_ok()
40}
41
42fn check_entry(entry: &str, path: &str) -> Result<()> {
45 check_input_entry(entry, path)?;
46 if !entry.starts_with("slice:") {
47 return Err(Error::Invalid(datumesh_core::Error::new(ErrorCode::CanonHex, path, "not a slice entry")));
48 }
49 Ok(())
50}
51
52impl PgStore {
53 pub fn connect(url: &str) -> Result<PgStore> {
56 PgStore::connect_with(url, PoolOptions::new())
57 }
58
59 pub fn connect_with(url: &str, options: PoolOptions) -> Result<PgStore> {
73 let config: postgres::Config = url.parse().db()?;
74 let connector = Connector::new(config, &options)?;
75 PgStore::from_pool(Pool::new(Box::new(move || connector.connect()), options))
76 }
77
78 pub(crate) fn from_pool(pool: Pool) -> Result<PgStore> {
80 let store = PgStore { pool };
81 drop(store.pool.get()?);
82 Ok(store)
83 }
84
85 pub(crate) fn conn(&self) -> Result<Pooled<'_>> {
87 self.pool.get()
88 }
89
90 pub fn check_statements(&self) -> Result<usize> {
94 let mut c = self.pool.get()?;
95 for q in Q::ALL {
96 c.client.prepare_typed(q.sql(), q.types()).db()?;
97 }
98 Ok(Q::ALL.len())
99 }
100
101 fn resolve<T>(&self, op: &'static str, f: impl FnOnce(&mut Conn) -> Result<Option<T>>) -> Option<T> {
103 self.run(op, f).ok().flatten()
104 }
105
106 fn run<T>(&self, op: &'static str, f: impl FnOnce(&mut Conn) -> Result<T>) -> Result<T> {
108 let result = self.pool.get().and_then(|mut c| f(&mut c));
109 if let Err(e @ (Error::Database(_) | Error::PoolTimeout)) = &result {
110 tracing::error!(target: "datumesh_store", op, error = %e, "database failure");
111 }
112 result
113 }
114
115 fn version_rows(rows: &[postgres::Row], cell_id: &str) -> Result<Vec<Version>> {
117 rows.iter().map(|r| version_row(r, cell_id)).collect()
118 }
119
120 fn asof_with_receipt(
123 &self,
124 op: &'static str,
125 q: Q,
126 cell_id: &str,
127 asof: &str,
128 ) -> Result<Option<(Version, Vec<u8>)>> {
129 check_hex(cell_id, 64, "cell_id")?;
130 check_instant(asof, "asof")?;
131 self.run(op, |c| {
132 let st = c.stmt(q)?;
133 let Some(row) = c.client.query_opt(&st, &[&cell_id, &asof]).db()? else { return Ok(None) };
134 Ok(Some((version_row(&row, cell_id)?, row.try_get(3).db()?)))
135 })
136 }
137
138 fn asof_version(&self, op: &'static str, q: Q, cell_id: &str, asof: &str) -> Result<Option<Version>> {
140 check_hex(cell_id, 64, "cell_id")?;
141 check_instant(asof, "asof")?;
142 self.run(op, |c| {
143 let st = c.stmt(q)?;
144 let rows = c.client.query(&st, &[&cell_id, &asof]).db()?;
145 Ok(PgStore::version_rows(&rows, cell_id)?.pop())
146 })
147 }
148}
149
150fn version_row(r: &postgres::Row, cell_id: &str) -> Result<Version> {
151 Ok(Version {
152 cell_id: cell_id.to_string(),
153 tx: r.try_get(0).db()?,
154 receipt_hash: r.try_get(1).db()?,
155 asof: r.try_get(2).db()?,
156 })
157}
158
159fn member_row(r: &postgres::Row, cell_id: &str) -> Result<SliceMember> {
161 Ok(SliceMember { version: version_row(r, cell_id)?, receipt: r.try_get(3).db()? })
162}
163
164fn value_row(r: &postgres::Row, cell_id: &str) -> Result<SliceValue> {
168 let bad =
169 |what: &str| Error::Invalid(datumesh_core::Error::new(ErrorCode::CanonType, "$", format!("a stored {what}")));
170 let scalar = |ty: &str, value: &str| -> Result<Scalar> {
171 let ty = Type::from_name(ty).ok_or_else(|| bad("type"))?;
172 Ok(Scalar::from_lexical(ty, value)?)
173 };
174 let (ty, value): (String, String) = (r.try_get(3).db()?, r.try_get(4).db()?);
175 let mut args = datumesh_core::Args::new();
176 for a in r.try_get::<_, Vec<String>>(8).db()? {
177 let mut parts = a.splitn(3, ':');
178 let (Some(name), Some(t), Some(v)) = (parts.next(), parts.next(), parts.next()) else {
179 return Err(bad("argument"));
180 };
181 args.insert(name.to_string(), datumesh_core::Arg::Value(scalar(t, v)?));
182 }
183 let cell = Cell { args, fn_hash: r.try_get(7).db()?, fn_name: r.try_get(6).db()?, ns: r.try_get(5).db()? };
184 Ok(SliceValue { version: version_row(r, cell_id)?, value: scalar(&ty, &value)?, cell })
185}
186
187fn get_bytes(c: &mut Conn, q: Q, key: &str) -> Result<Option<Vec<u8>>> {
188 let st = c.stmt(q)?;
189 let row = c.client.query_opt(&st, &[&key]).db()?;
190 Ok(match row {
191 Some(r) => Some(r.try_get(0).db()?),
192 None => None,
193 })
194}
195
196fn get_strings(c: &mut Conn, q: Q, key: &str) -> Result<Vec<String>> {
197 let st = c.stmt(q)?;
198 let rows = c.client.query(&st, &[&key]).db()?;
199 rows.iter().map(|r| r.try_get(0).db()).collect()
200}
201
202impl Resolver for PgStore {
203 fn descriptor(&self, fn_hash: &str) -> Option<Cow<'_, [u8]>> {
204 if !is_hash(fn_hash) {
205 return None;
206 }
207 self.resolve("descriptor", |c| get_bytes(c, Q::DescriptorGet, fn_hash)).map(Cow::Owned)
208 }
209
210 fn receipt(&self, hash: &str) -> Option<Cow<'_, [u8]>> {
211 if !is_hash(hash) {
212 return None;
213 }
214 self.resolve("receipt", |c| get_bytes(c, Q::ReceiptGet, hash)).map(Cow::Owned)
215 }
216
217 fn manifest(&self, entry: &str) -> Option<Cow<'_, [u8]>> {
220 if check_entry(entry, "$").is_err() {
221 return None;
222 }
223 self.resolve("manifest", |c| get_bytes(c, Q::ManifestGet, entry)).map(Cow::Owned)
224 }
225
226 fn evaluate(&self, cell: &Cell, asof: &str) -> Option<Scalar> {
234 if !is_instant(asof) {
235 return None;
236 }
237 self.resolve("evaluate", |c| {
238 let Some(desc) = get_bytes(c, Q::DescriptorGet, &cell.fn_hash)? else { return Ok(None) };
239 match FunctionDescriptor::from_json(&desc) {
240 Ok(d) if d.lang == "stored" => {}
241 Ok(_) => return Ok(None),
242 Err(e) => {
243 tracing::error!(target: "datumesh_store", fn_hash = %cell.fn_hash, error = %e, "stored descriptor does not parse");
244 return Ok(None);
245 }
246 }
247 let st = c.stmt(Q::ValueAsof)?;
248 let Some(row) = c.client.query_opt(&st, &[&cell.id(), &asof]).db()? else { return Ok(None) };
249 let bytes: Vec<u8> = row.try_get(0).db()?;
250 match Receipt::from_json(&bytes) {
251 Ok(r) => Ok(Some(r.value().clone())),
252 Err(e) => {
253 tracing::error!(target: "datumesh_store", cell_id = %cell.id(), error = %e, "stored receipt does not parse");
254 Ok(None)
255 }
256 }
257 })
258 }
259}
260
261impl Store for PgStore {
262 fn put_descriptor(&self, bytes: &[u8]) -> Result<String> {
263 let d = FunctionDescriptor::from_json(bytes)?;
265 let hash = d.hash();
266 let canonical = d.canonical();
267 self.run("put_descriptor", |c| {
268 let st = c.stmt(Q::DescriptorPut)?;
269 c.client.execute(&st, &[&hash, &canonical]).db()?;
270 Ok(())
271 })?;
272 tracing::debug!(target: "datumesh_store", fn_hash = %hash, "put_descriptor");
273 Ok(hash)
274 }
275
276 fn put_receipt(&self, bytes: &[u8]) -> Result<String> {
277 let r = Receipt::from_json(bytes)?;
279 let canonical = json::parse(bytes)?.to_jcs();
281 let hash = r.hash().to_string();
282 let cell_id = r.cell().id();
283 let host = r.signer_host().to_string();
284 let (entries, inputs): (Vec<String>, Vec<String>) =
286 r.inputs().iter().cloned().partition(|e| e.starts_with("slice:"));
287 self.run("put_receipt", |c| {
288 let ns = c.stmt(Q::NamespaceGet)?;
289 let put = c.stmt(Q::ReceiptPut)?;
290 let get = c.stmt(Q::ReceiptGet)?;
291 let edges = c.stmt(Q::EdgesPut)?;
292 let slice_edges = c.stmt(Q::SliceEdgesPut)?;
293 let mut t = c.client.transaction().db()?;
294 if let Some(row) = t.query_opt(&get, &[&hash]).db()? {
298 let stored: Vec<u8> = row.try_get(0).db()?;
299 if stored == canonical {
300 return Ok(());
301 }
302 }
303 let mut keys = Keys::new();
306 if let Some(row) = t.query_opt(&ns, &[&host]).db()? {
307 let doc: Vec<u8> = row.try_get(0).db()?;
308 keys.insert_response(&host, 200, Some(&doc));
309 }
310 let out = verify(bytes, &keys, None, None);
311 if !out.ok {
312 return Err(Error::Invalid(out.error().unwrap_or_else(|| {
313 datumesh_core::Error::new(ErrorCode::Signature, "$", "the receipt did not verify")
314 })));
315 }
316 let n = t
317 .execute(&put, &[&hash, &canonical, &cell_id, &r.tx(), &r.asof(), &r.cell().fn_hash, &r.publisher()])
318 .db()?;
319 if n == 0 {
320 let row = t.query_one(&get, &[&hash]).db()?;
321 let stored: Vec<u8> = row.try_get(0).db()?;
322 if stored != canonical {
323 return Err(Error::Conflict { hash: hash.clone() });
324 }
325 }
326 if !inputs.is_empty() {
327 t.execute(&edges, &[&hash, &inputs]).db()?;
328 }
329 if !entries.is_empty() {
330 t.execute(&slice_edges, &[&hash, &entries]).db()?;
331 }
332 t.commit().db()?;
333 Ok(())
334 })?;
335 tracing::debug!(target: "datumesh_store", hash = %hash, cell_id = %cell_id, "put_receipt");
336 Ok(hash)
337 }
338
339 fn put_blob(&self, bytes: &[u8]) -> Result<String> {
340 let sha = sha256_hex(bytes);
341 let length = i64::try_from(bytes.len()).unwrap_or(i64::MAX);
342 self.run("put_blob", |c| {
343 let st = c.stmt(Q::BlobPut)?;
344 c.client.execute(&st, &[&sha, &bytes, &length]).db()?;
345 Ok(())
346 })?;
347 tracing::debug!(target: "datumesh_store", sha256 = %sha, length, "put_blob");
348 Ok(sha)
349 }
350
351 fn blob(&self, sha256: &str) -> Result<Option<Cow<'_, [u8]>>> {
352 check_hex(sha256, 64, "sha256")?;
353 Ok(self.run("blob", |c| get_bytes(c, Q::BlobGet, sha256))?.map(Cow::Owned))
354 }
355
356 fn put_version(&self, cell_id: &str, tx: &str, receipt_hash: &str) -> Result<()> {
357 check_hex(cell_id, 64, "cell_id")?;
358 check_instant(tx, "tx")?;
359 check_hex(receipt_hash, 64, "receipt_hash")?;
360 self.run("put_version", |c| {
361 let lock = c.stmt(Q::Lock)?;
362 let meta = c.stmt(Q::ReceiptMeta)?;
363 let latest = c.stmt(Q::VersionLatestTx)?;
364 let put = c.stmt(Q::VersionPut)?;
365 let mut t = c.client.transaction().db()?;
366 t.execute(&lock, &[&"versions", &cell_id]).db()?;
367 let Some(row) = t.query_opt(&meta, &[&receipt_hash]).db()? else {
368 return Err(Error::NotFound { what: "receipt", key: receipt_hash.to_string() });
369 };
370 let (r_cell, r_tx): (String, String) = (row.try_get(0).db()?, row.try_get(1).db()?);
371 if r_cell != cell_id {
372 return Err(Error::Mismatch { what: "cell_id", expected: cell_id.to_string(), found: r_cell });
373 }
374 if r_tx != tx {
375 return Err(Error::Mismatch { what: "tx", expected: tx.to_string(), found: r_tx });
376 }
377 if let Some(row) = t.query_opt(&latest, &[&cell_id]).db()? {
378 let l: String = row.try_get(0).db()?;
379 if tx <= l.as_str() {
380 return Err(Error::NotLater {
381 what: "version",
382 key: cell_id.to_string(),
383 given: tx.to_string(),
384 latest: l,
385 });
386 }
387 }
388 t.execute(&put, &[&cell_id, &tx, &receipt_hash]).db()?;
389 t.commit().db()?;
390 Ok(())
391 })?;
392 tracing::debug!(target: "datumesh_store", cell_id, tx, receipt_hash, "put_version");
393 Ok(())
394 }
395
396 fn version_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>> {
397 self.asof_version("version_asof", Q::VersionAsof, cell_id, asof)
398 }
399
400 fn versions(&self, cell_id: &str, from: &str, to: &str) -> Result<Vec<Version>> {
401 check_hex(cell_id, 64, "cell_id")?;
402 check_instant(from, "from")?;
403 check_instant(to, "to")?;
404 self.run("versions", |c| {
405 let st = c.stmt(Q::VersionRange)?;
406 let rows = c.client.query(&st, &[&cell_id, &from, &to]).db()?;
407 PgStore::version_rows(&rows, cell_id)
408 })
409 }
410
411 fn latest(&self, cell_id: &str) -> Result<Option<Version>> {
412 check_hex(cell_id, 64, "cell_id")?;
413 self.run("latest", |c| {
414 let st = c.stmt(Q::VersionLatest)?;
415 let rows = c.client.query(&st, &[&cell_id]).db()?;
416 Ok(PgStore::version_rows(&rows, cell_id)?.pop())
417 })
418 }
419
420 fn dependents(&self, receipt_hash: &str) -> Result<Vec<String>> {
421 check_hex(receipt_hash, 64, "receipt_hash")?;
422 self.run("dependents", |c| get_strings(c, Q::Dependents, receipt_hash))
423 }
424
425 fn inputs(&self, receipt_hash: &str) -> Result<Vec<String>> {
426 check_hex(receipt_hash, 64, "receipt_hash")?;
427 self.run("inputs", |c| get_strings(c, Q::Inputs, receipt_hash))
428 }
429
430 fn put_namespace(&self, host: &str, keydoc_bytes: &[u8], validated_at: &str) -> Result<()> {
431 keys_from_response(host, 200, Some(keydoc_bytes))?;
433 check_instant(validated_at, "validated_at")?;
434 let canonical = json::parse(keydoc_bytes)?.to_jcs();
435 self.run("put_namespace", |c| {
436 let lock = c.stmt(Q::Lock)?;
437 let get = c.stmt(Q::NamespaceGet)?;
438 let put = c.stmt(Q::NamespacePut)?;
439 let mut t = c.client.transaction().db()?;
440 t.execute(&lock, &[&"namespaces", &host]).db()?;
441 if let Some(row) = t.query_opt(&get, &[&host]).db()? {
442 let at: String = row.try_get(1).db()?;
443 if validated_at < at.as_str() {
444 return Err(Error::NotLater {
445 what: "namespace",
446 key: host.to_string(),
447 given: validated_at.to_string(),
448 latest: at,
449 });
450 }
451 }
452 t.execute(&put, &[&host, &canonical, &validated_at]).db()?;
453 t.commit().db()?;
454 Ok(())
455 })?;
456 tracing::debug!(target: "datumesh_store", host, validated_at, "put_namespace");
457 Ok(())
458 }
459
460 fn namespace(&self, host: &str) -> Result<Option<Namespace>> {
461 check_dns_name(host, "host")?;
462 self.run("namespace", |c| {
463 let st = c.stmt(Q::NamespaceGet)?;
464 let Some(row) = c.client.query_opt(&st, &[&host]).db()? else { return Ok(None) };
465 Ok(Some(Namespace {
466 host: host.to_string(),
467 keydoc: row.try_get(0).db()?,
468 validated_at: row.try_get(1).db()?,
469 suspended_at: row.try_get(2).db()?,
470 }))
471 })
472 }
473
474 fn bind_name(&self, fn_name: &str, fn_hash: &str, tx: &str) -> Result<()> {
475 check_function_name(fn_name, "fn_name")?;
476 check_hex(fn_hash, 64, "fn_hash")?;
477 check_instant(tx, "tx")?;
478 self.run("bind_name", |c| {
479 let lock = c.stmt(Q::Lock)?;
480 let exists = c.stmt(Q::DescriptorExists)?;
481 let latest = c.stmt(Q::NameLatest)?;
482 let put = c.stmt(Q::NamePut)?;
483 let mut t = c.client.transaction().db()?;
484 t.execute(&lock, &[&"names", &fn_name]).db()?;
485 let found: bool = t.query_one(&exists, &[&fn_hash]).db()?.try_get(0).db()?;
486 if !found {
487 return Err(Error::NotFound { what: "descriptor", key: fn_hash.to_string() });
488 }
489 if let Some(row) = t.query_opt(&latest, &[&fn_name]).db()? {
490 let l: String = row.try_get(0).db()?;
491 if tx <= l.as_str() {
492 return Err(Error::NotLater {
493 what: "name",
494 key: fn_name.to_string(),
495 given: tx.to_string(),
496 latest: l,
497 });
498 }
499 }
500 t.execute(&put, &[&fn_name, &tx, &fn_hash]).db()?;
501 t.commit().db()?;
502 Ok(())
503 })?;
504 tracing::debug!(target: "datumesh_store", fn_name, fn_hash, tx, "bind_name");
505 Ok(())
506 }
507
508 fn put_computed(&self, cell_id: &str, tx: &str, receipt_hash: &str) -> Result<()> {
509 check_hex(cell_id, 64, "cell_id")?;
510 check_instant(tx, "tx")?;
511 check_hex(receipt_hash, 64, "receipt_hash")?;
512 let asof = self.run("put_computed", |c| {
516 let meta = c.stmt(Q::ReceiptMeta)?;
517 let put = c.stmt(Q::ComputedPut)?;
518 let get = c.stmt(Q::ComputedGet)?;
519 let mut t = c.client.transaction().db()?;
520 let Some(row) = t.query_opt(&meta, &[&receipt_hash]).db()? else {
521 return Err(Error::NotFound { what: "receipt", key: receipt_hash.to_string() });
522 };
523 let (r_cell, r_tx, asof): (String, String, String) =
524 (row.try_get(0).db()?, row.try_get(1).db()?, row.try_get(2).db()?);
525 if r_cell != cell_id {
526 return Err(Error::Mismatch { what: "cell_id", expected: cell_id.to_string(), found: r_cell });
527 }
528 if r_tx != tx {
529 return Err(Error::Mismatch { what: "tx", expected: tx.to_string(), found: r_tx });
530 }
531 if t.execute(&put, &[&cell_id, &tx, &asof, &receipt_hash]).db()? == 0 {
532 let stored: String = t.query_one(&get, &[&cell_id, &tx, &asof]).db()?.try_get(0).db()?;
533 if stored != receipt_hash {
534 return Err(Error::Conflict { hash: receipt_hash.to_string() });
535 }
536 }
537 t.commit().db()?;
538 Ok(asof)
539 })?;
540 tracing::debug!(target: "datumesh_store", cell_id, tx, asof, receipt_hash, "put_computed");
541 Ok(())
542 }
543
544 fn computed_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>> {
545 self.asof_version("computed_asof", Q::ComputedAsof, cell_id, asof)
546 }
547
548 fn stored_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>> {
549 self.asof_version("stored_asof", Q::StoredAsof, cell_id, asof)
550 }
551
552 fn cell_dependents(&self, cell_id: &str) -> Result<Vec<String>> {
553 check_hex(cell_id, 64, "cell_id")?;
554 self.run("cell_dependents", |c| get_strings(c, Q::CellDependents, cell_id))
555 }
556
557 fn computed_asof_with_receipt(&self, cell_id: &str, asof: &str) -> Result<Option<(Version, Cow<'_, [u8]>)>> {
559 Ok(self
560 .asof_with_receipt("computed_asof_with_receipt", Q::ComputedAsofReceipt, cell_id, asof)?
561 .map(|(v, b)| (v, Cow::Owned(b))))
562 }
563
564 fn stored_asof_with_receipt(&self, cell_id: &str, asof: &str) -> Result<Option<(Version, Cow<'_, [u8]>)>> {
566 Ok(self
567 .asof_with_receipt("stored_asof_with_receipt", Q::StoredAsofReceipt, cell_id, asof)?
568 .map(|(v, b)| (v, Cow::Owned(b))))
569 }
570
571 fn put_manifest(&self, bytes: &[u8]) -> Result<String> {
572 let m = Manifest::from_json(bytes)?;
573 let entry = m.entry();
574 let canonical = m.canonical();
575 let members = m.members();
576 let n = i32::try_from(members.len()).unwrap_or(i32::MAX);
577 self.run("put_manifest", |c| {
578 let put = c.stmt(Q::ManifestPut)?;
579 let put_members = c.stmt(Q::ManifestMembersPut)?;
580 let mut t = c.client.transaction().db()?;
581 let inserted = t
585 .execute(&put, &[&entry, &m.slice().id(), &m.members_hash(), &m.slice().fn_hash(), &n, &canonical])
586 .db()?;
587 if inserted == 1 && !members.is_empty() {
588 t.execute(&put_members, &[&entry, &members]).db()?;
589 }
590 t.commit().db()?;
591 Ok(())
592 })?;
593 tracing::debug!(target: "datumesh_store", entry = %entry, members = n, "put_manifest");
594 Ok(entry)
595 }
596
597 fn member_manifests(&self, receipt_hash: &str) -> Result<Vec<String>> {
598 check_hex(receipt_hash, 64, "receipt_hash")?;
599 self.run("member_manifests", |c| get_strings(c, Q::MemberManifests, receipt_hash))
600 }
601
602 fn slice_entries(&self, receipt_hash: &str) -> Result<Vec<String>> {
603 check_hex(receipt_hash, 64, "receipt_hash")?;
604 self.run("slice_entries", |c| get_strings(c, Q::SliceEntries, receipt_hash))
605 }
606
607 fn slice_dependents(&self, entry: &str) -> Result<Vec<String>> {
608 check_entry(entry, "entry")?;
609 self.run("slice_dependents", |c| get_strings(c, Q::SliceDependents, entry))
610 }
611
612 fn slice_members(&self, slice: &Slice, asof: &str) -> Result<Vec<SliceMember>> {
613 check_instant(asof, "asof")?;
614 let v = slice.to_value();
615 if let Some((cell_id, from, to)) = version_slice(slice, &v)? {
616 return self.versions_between(&cell_id, &from, &to, asof);
617 }
618 let pattern = Pattern::of(&v)?;
619 self.run("slice_members", |c| pattern.members(c, asof))
620 }
621
622 fn versions_between(&self, cell_id: &str, from: &str, to: &str, asof: &str) -> Result<Vec<SliceMember>> {
623 check_hex(cell_id, 64, "cell_id")?;
624 check_instant(from, "from")?;
625 check_instant(to, "to")?;
626 check_instant(asof, "asof")?;
627 self.run("versions_between", |c| {
628 let st = c.stmt(Q::VersionsBetween)?;
629 let rows = c.client.query(&st, &[&cell_id, &from, &to, &asof]).db()?;
630 rows.iter().map(|r| member_row(r, cell_id)).collect()
631 })
632 }
633
634 fn slice_values(&self, slice: &Slice, asof: &str) -> Result<Vec<SliceValue>> {
635 check_instant(asof, "asof")?;
636 let v = slice.to_value();
637 if let Some((cell_id, from, to)) = version_slice(slice, &v)? {
638 return self.run("slice_values", |c| {
639 let st = c.stmt(Q::VersionsBetweenValues)?;
640 let rows = c.client.query(&st, &[&cell_id, &from, &to, &asof]).db()?;
641 rows.iter().map(|r| value_row(r, &cell_id)).collect()
642 });
643 }
644 let pattern = Pattern::of(&v)?;
645 self.run("slice_values", |c| pattern.values(c, asof))
646 }
647
648 fn slice_changed_after(&self, slice: &Slice, asof: &str) -> Result<bool> {
649 check_instant(asof, "asof")?;
650 let v = slice.to_value();
651 if let Some((cell_id, from, to)) = version_slice(slice, &v)? {
652 return self.run("slice_changed_after", |c| {
653 let st = c.stmt(Q::VersionsChangedAfter)?;
654 c.client.query_one(&st, &[&cell_id, &from, &to, &asof]).db()?.try_get(0).db()
655 });
656 }
657 let pattern = Pattern::of(&v)?;
658 self.run("slice_changed_after", |c| pattern.changed_after(c, asof))
659 }
660
661 fn resolve_binding(&self, fn_name: &str, asof: &str) -> Result<Option<Binding>> {
662 check_function_name(fn_name, "fn_name")?;
663 check_instant(asof, "asof")?;
664 self.run("resolve_binding", |c| {
665 let st = c.stmt(Q::BindingAsof)?;
666 let Some(row) = c.client.query_opt(&st, &[&fn_name, &asof]).db()? else { return Ok(None) };
667 Ok(Some(Binding { fn_hash: row.try_get(0).db()?, tx: row.try_get(1).db()? }))
668 })
669 }
670
671 fn resolve_name(&self, fn_name: &str, asof: &str) -> Result<Option<String>> {
672 check_function_name(fn_name, "fn_name")?;
673 check_instant(asof, "asof")?;
674 self.run("resolve_name", |c| {
675 let st = c.stmt(Q::NameAsof)?;
676 let row = c.client.query_opt(&st, &[&fn_name, &asof]).db()?;
677 Ok(match row {
678 Some(r) => Some(r.try_get(0).db()?),
679 None => None,
680 })
681 })
682 }
683}
684
685fn version_slice(slice: &Slice, v: &Value) -> Result<Option<(String, String, String)>> {
687 if !slice.is_version_slice() {
688 return Ok(None);
689 }
690 match (v.get("cell"), v.get("from").and_then(Value::as_str), v.get("to").and_then(Value::as_str)) {
691 (Some(cell), Some(from), Some(to)) => Ok(Some((sha256_hex(&cell.to_jcs()), from.to_string(), to.to_string()))),
692 _ => Err(Error::Invalid(datumesh_core::Error::new(ErrorCode::Schema, "$", "not a version slice"))),
693 }
694}
695
696#[derive(Clone, Copy, Debug, PartialEq, Eq)]
698enum Kind {
699 Open,
700 Value,
701 Range,
702}
703
704impl Kind {
705 fn as_str(self) -> &'static str {
706 match self {
707 Kind::Open => "open",
708 Kind::Value => "value",
709 Kind::Range => "range",
710 }
711 }
712}
713
714struct Constraint {
717 name: String,
718 kind: Kind,
719 ty: String,
720 lo: String,
721 hi: String,
722}
723
724struct Pattern {
726 fn_hash: String,
727 ns: String,
728 fn_name: String,
729 args: Vec<Constraint>,
730}
731
732fn malformed(what: &str) -> Error {
733 Error::Invalid(datumesh_core::Error::new(ErrorCode::Schema, "$", format!("not a slice object: {what}")))
734}
735
736impl Pattern {
737 fn of(v: &Value) -> Result<Pattern> {
739 let s = |k: &str| v.get(k).and_then(Value::as_str).map(str::to_string).ok_or_else(|| malformed(k));
740 let mut args = Vec::new();
741 for (name, a) in v.get("args").and_then(Value::as_object).ok_or_else(|| malformed("args"))? {
742 let text = |k: &str| match a.get(k) {
743 Some(Value::String(x)) => Ok(x.clone()),
744 Some(Value::Bool(b)) => Ok(b.to_string()),
745 _ => Err(malformed(k)),
746 };
747 let c = match a {
748 Value::String(x) if x == "*" => Constraint {
749 name: name.clone(),
750 kind: Kind::Open,
751 ty: String::new(),
752 lo: String::new(),
753 hi: String::new(),
754 },
755 Value::Object(m) if m.iter().any(|(k, _)| k == "lo") => Constraint {
756 name: name.clone(),
757 kind: Kind::Range,
758 ty: text("type")?,
759 lo: text("lo")?,
760 hi: text("hi")?,
761 },
762 _ => {
763 let value = text("value")?;
764 Constraint {
765 name: name.clone(),
766 kind: Kind::Value,
767 ty: text("type")?,
768 lo: value.clone(),
769 hi: value,
770 }
771 }
772 };
773 args.push(c);
774 }
775 args.sort_by(|a, b| a.name.cmp(&b.name));
776 Ok(Pattern { fn_hash: s("fn_hash")?, ns: s("ns")?, fn_name: s("fn")?, args })
777 }
778
779 fn members(&self, c: &mut Conn, asof: &str) -> Result<Vec<SliceMember>> {
781 let qs = [Q::SliceMembersAll, Q::SliceMembersText, Q::SliceMembersNum];
782 self.rows(c, asof, qs, 4, member_row, |m| &m.version.cell_id)
783 }
784
785 fn values(&self, c: &mut Conn, asof: &str) -> Result<Vec<SliceValue>> {
787 let qs = [Q::SliceValuesAll, Q::SliceValuesText, Q::SliceValuesNum];
788 self.rows(c, asof, qs, 9, value_row, |m| &m.version.cell_id)
789 }
790
791 fn columns(&self) -> [Vec<String>; 5] {
793 let col = |f: fn(&Constraint) -> &str| -> Vec<String> { self.args.iter().map(|a| f(a).to_string()).collect() };
794 [
795 col(|a| a.name.as_str()),
796 col(|a| a.kind.as_str()),
797 col(|a| a.ty.as_str()),
798 col(|a| a.lo.as_str()),
799 col(|a| a.hi.as_str()),
800 ]
801 }
802
803 fn changed_after(&self, c: &mut Conn, asof: &str) -> Result<bool> {
808 let (drive, candidates) = self.drive(c, true)?;
809 let st = c.stmt(Q::ProbeRecent)?;
810 let recent: i64 = c.client.query_one(&st, &[&self.fn_hash, &asof]).db()?.try_get(0).db()?;
811 let qs = if recent <= candidates.unwrap_or(i64::MAX) {
812 [Q::ChangedRecent; 3]
813 } else {
814 [Q::ChangedCandAll, Q::ChangedCandText, Q::ChangedCandNum]
815 };
816 let rows = self.query(c, asof, qs, drive)?;
817 match rows.first() {
818 Some(r) => r.try_get(0).db(),
819 None => Ok(false),
820 }
821 }
822
823 fn drive(&self, c: &mut Conn, count: bool) -> Result<(Option<&Constraint>, Option<i64>)> {
827 let mut probe = |a: Option<&Constraint>| -> Result<i64> {
828 match a {
829 None => {
830 let st = c.stmt(Q::ProbeCells)?;
831 c.client.query_one(&st, &[&self.fn_hash]).db()?.try_get(0).db()
832 }
833 Some(a) => {
834 let st = c.stmt(if a.ty == "number" { Q::SliceProbeNum } else { Q::SliceProbeText })?;
835 c.client.query_one(&st, &[&self.fn_hash, &a.name, &a.ty, &a.lo, &a.hi]).db()?.try_get(0).db()
836 }
837 }
838 };
839 let constrained: Vec<&Constraint> = self.args.iter().filter(|a| a.kind != Kind::Open).collect();
840 Ok(match constrained.as_slice() {
841 [] => (None, if count { Some(probe(None)?) } else { None }),
842 [one] => (Some(*one), if count { Some(probe(Some(one))?) } else { None }),
843 several => {
844 let mut best: Option<(i64, &Constraint)> = None;
845 for a in several {
846 let n = probe(Some(a))?;
847 if best.is_none_or(|(m, _)| n < m) {
848 best = Some((n, a));
849 }
850 }
851 (best.map(|(_, a)| a), best.map(|(n, _)| n))
852 }
853 })
854 }
855
856 fn query(&self, c: &mut Conn, asof: &str, qs: [Q; 3], drive: Option<&Constraint>) -> Result<Vec<postgres::Row>> {
859 let [names, kinds, types, los, his] = self.columns();
860 let keys: Vec<String> = self.args.iter().filter(|a| a.kind != Kind::Value).map(|a| a.name.clone()).collect();
861 let nargs = i32::try_from(self.args.len()).unwrap_or(i32::MAX);
862 let (q, d_name, d_ty, d_lo, d_hi) = match drive {
863 None => (qs[0], "", "", "", ""),
864 Some(a) => {
865 let q = if a.ty == "number" { qs[2] } else { qs[1] };
866 (q, a.name.as_str(), a.ty.as_str(), a.lo.as_str(), a.hi.as_str())
867 }
868 };
869 let st = c.stmt(q)?;
870 c.client
871 .query(
872 &st,
873 &[
874 &self.fn_hash,
875 &self.ns,
876 &self.fn_name,
877 &nargs,
878 &asof,
879 &names,
880 &kinds,
881 &types,
882 &los,
883 &his,
884 &keys,
885 &d_name,
886 &d_ty,
887 &d_lo,
888 &d_hi,
889 ],
890 )
891 .db()
892 }
893
894 fn rows<T>(
897 &self,
898 c: &mut Conn,
899 asof: &str,
900 qs: [Q; 3],
901 at: usize,
902 make: impl Fn(&postgres::Row, &str) -> Result<T>,
903 cell_of: impl Fn(&T) -> &String,
904 ) -> Result<Vec<T>> {
905 let (drive, _) = self.drive(c, false)?;
906 let rows = self.query(c, asof, qs, drive)?;
907 let mut out: Vec<(Vec<String>, T)> = Vec::with_capacity(rows.len());
908 for r in &rows {
909 let cell_id: String = r.try_get(at).db()?;
910 let key: Vec<String> = r.try_get(at + 1).db()?;
911 out.push((key, make(r, &cell_id)?));
912 }
913 out.sort_by(|(a, ma), (b, mb)| cmp_keys(a, b).then_with(|| cell_of(ma).cmp(cell_of(mb))));
914 Ok(out.into_iter().map(|(_, m)| m).collect())
915 }
916}
917
918fn cmp_keys(a: &[String], b: &[String]) -> std::cmp::Ordering {
924 for (x, y) in a.iter().zip(b) {
925 let (tx, vx) = x.split_once(':').unwrap_or(("", x));
926 let (ty, vy) = y.split_once(':').unwrap_or(("", y));
927 let o = tx.cmp(ty).then_with(|| if tx == "number" { cmp_decimal(vx, vy) } else { vx.cmp(vy) });
928 if o.is_ne() {
929 return o;
930 }
931 }
932 a.len().cmp(&b.len())
933}
934
935pub(crate) fn cmp_decimal(a: &str, b: &str) -> std::cmp::Ordering {
937 let (na, ma) = a.strip_prefix('-').map_or((false, a), |m| (true, m));
938 let (nb, mb) = b.strip_prefix('-').map_or((false, b), |m| (true, m));
939 let magnitude = || {
940 let (ia, fa) = ma.split_once('.').unwrap_or((ma, ""));
941 let (ib, fb) = mb.split_once('.').unwrap_or((mb, ""));
942 ia.len().cmp(&ib.len()).then_with(|| ia.cmp(ib)).then_with(|| fa.cmp(fb))
945 };
946 match (na, nb) {
947 (false, true) => std::cmp::Ordering::Greater,
948 (true, false) => std::cmp::Ordering::Less,
949 (false, false) => magnitude(),
950 (true, true) => magnitude().reverse(),
951 }
952}
953
954#[cfg(test)]
955mod tests {
956 use super::cmp_decimal;
957 use std::cmp::Ordering::{Equal, Greater, Less};
958
959 #[test]
960 fn decimals_compare_numerically() {
961 for (a, b, o) in [
962 ("9", "10", Less),
963 ("-1.5", "-1", Less),
964 ("-1", "-1.5", Greater),
965 ("0", "-0.001", Greater),
966 ("0.05", "0.5", Less),
967 ("0.5", "0.51", Less),
968 ("10.5", "9.75", Greater),
969 ("-10", "-9", Less),
970 ("123", "123", Equal),
971 ("-0.1", "0", Less),
972 ("1", "0.999999", Greater),
973 ] {
974 assert_eq!(cmp_decimal(a, b), o, "{a} {b}");
975 }
976 }
977}