1use std::path::Path;
90use std::sync::{Arc, Mutex, PoisonError};
91
92use async_trait::async_trait;
93use eventsdb_core::store::EventStore as EventsdbStore;
94use eventsdb_core::upcast::Current as Upcasted;
95use eventsdb_sqlite::SqliteEventLog;
96use serde_json::{Map, Value};
97
98use super::event::{validate_event, FIELD_EPOCH_MS, FIELD_SEQ};
99use super::event_store::{
100 stamp_schema_version, ChildScan, ChildrenDecision, Committed, Decision, EventStore, Split,
101 SplitDecision,
102};
103use super::logs::Logs;
104use super::query::{QueryPlan, QueryRows};
105use super::{KnlError, KnlResult};
106
107pub const EVENTS_TABLE: &str = "events";
110
111pub(super) const CHILD_INDEX_DDL: &str = "CREATE INDEX IF NOT EXISTS \
135 events_session_opened_parent \
136 ON events (json_extract(data, '$.parent')) \
137 WHERE kind = 'session_opened';";
138
139#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct SchemaColumn {
147 pub name: String,
149 pub declared_type: String,
151 pub pk: bool,
153}
154
155const EVENTS_COLUMNS: [(&str, &str, bool); 8] = [
172 ("position", "INTEGER", true),
173 ("stream", "TEXT", false),
174 ("seq", "INTEGER", false),
175 ("epoch_ms", "INTEGER", false),
176 ("kind", "TEXT", false),
177 ("schema_version", "INTEGER", false),
178 ("meta", "TEXT", false),
179 ("data", "TEXT", false),
180];
181
182pub fn events_schema() -> KnlResult<Vec<SchemaColumn>> {
189 Ok(EVENTS_COLUMNS
190 .iter()
191 .map(|(name, declared_type, pk)| SchemaColumn {
192 name: (*name).to_string(),
193 declared_type: (*declared_type).to_string(),
194 pk: *pk,
195 })
196 .collect())
197}
198
199pub struct SqliteEventStore {
205 log: Arc<SqliteEventLog>,
209 handle: eventsdb_sqlite::SqliteEventStore,
212 stream: String,
214}
215
216impl SqliteEventStore {
217 pub async fn open(path: &Path, stream: impl Into<String>, logs: &Logs) -> KnlResult<Self> {
223 Ok(Self::on(logs.file(path).await?, stream))
224 }
225
226 pub async fn open_memory(stream: impl Into<String>, logs: &Logs) -> KnlResult<Self> {
233 Ok(Self::on(logs.memory().await?, stream))
234 }
235
236 pub fn on(log: Arc<SqliteEventLog>, stream: impl Into<String>) -> Self {
242 let stream = stream.into();
243 let handle = log.stream_handle(&stream);
244 Self {
245 log,
246 handle,
247 stream,
248 }
249 }
250}
251
252fn owned_kinds(kinds: Option<&[&str]>) -> Option<Vec<String>> {
255 kinds.map(|kinds| kinds.iter().map(|kind| (*kind).to_string()).collect())
256}
257
258fn borrowed_kinds(kinds: &Option<Vec<String>>) -> Option<Vec<&str>> {
260 kinds
261 .as_ref()
262 .map(|kinds| kinds.iter().map(String::as_str).collect())
263}
264
265fn prepared(mut event: Map<String, Value>) -> Map<String, Value> {
275 event.remove(FIELD_SEQ);
276 event.remove(FIELD_EPOCH_MS);
277 stamp_schema_version(&mut event);
278 event
279}
280
281fn committed_of(committed: eventsdb_core::position::Committed) -> Committed {
286 Committed {
287 seq: committed.seq,
288 epoch_ms: committed.epoch_ms,
289 }
290}
291
292fn values_of(events: Vec<Upcasted>) -> Vec<Value> {
298 events
299 .into_iter()
300 .map(|event| Value::Object(event.into_inner()))
301 .collect()
302}
303
304fn values_of_ref(events: &[Upcasted]) -> Vec<Value> {
306 events
307 .iter()
308 .map(|event| Value::Object((**event).clone()))
309 .collect()
310}
311
312type Parked = Arc<Mutex<Option<KnlError>>>;
322
323fn taken(parked: &Parked) -> Option<KnlError> {
325 parked.lock().unwrap_or_else(PoisonError::into_inner).take()
326}
327
328fn park(parked: &Parked, error: KnlError) {
330 *parked.lock().unwrap_or_else(PoisonError::into_inner) = Some(error);
331}
332
333fn rolled_back() -> eventsdb_core::Error {
336 eventsdb_core::Error::validation("the kernel refused an event this write was to record")
337}
338
339#[async_trait]
340impl EventStore for SqliteEventStore {
341 async fn append(&mut self, event: Map<String, Value>) -> KnlResult<Committed> {
342 validate_event(&event)?;
344 self.handle
345 .append(prepared(event))
346 .await
347 .map(committed_of)
348 .map_err(KnlError::from)
349 }
350
351 async fn append_many(&mut self, events: Vec<Map<String, Value>>) -> KnlResult<Vec<Committed>> {
352 for event in &events {
355 validate_event(event)?;
356 }
357 let events: Vec<Map<String, Value>> = events.into_iter().map(prepared).collect();
358 self.handle
359 .append_many(events)
360 .await
361 .map(|committed| committed.into_iter().map(committed_of).collect())
362 .map_err(KnlError::from)
363 }
364
365 async fn append_if(
366 &mut self,
367 kinds: Option<&[&str]>,
368 decide: Decision,
369 ) -> KnlResult<Option<Committed>> {
370 let parked: Parked = Arc::default();
376 let sink = Arc::clone(&parked);
377 let answer: eventsdb_core::store::Decision = Box::new(move |seen: &[Upcasted]| {
378 let event = decide(values_of_ref(seen))?;
379 match validate_event(&event) {
384 Ok(()) => Some(prepared(event)),
385 Err(refusal) => {
386 park(&sink, refusal);
387 None
388 }
389 }
390 });
391 let committed = self.handle.append_if(kinds, answer).await;
392 match taken(&parked) {
393 Some(refusal) => Err(refusal),
394 None => committed
395 .map(|committed| committed.map(committed_of))
396 .map_err(KnlError::from),
397 }
398 }
399
400 async fn append_if_many(
401 &mut self,
402 other: &str,
403 kinds: Option<&[&str]>,
404 decide: SplitDecision,
405 ) -> KnlResult<Option<Split<Committed>>> {
406 let stream = self.stream.clone();
411 let other = other.to_string();
412 let kinds = owned_kinds(kinds);
413 let parked: Parked = Arc::default();
414 let sink = Arc::clone(&parked);
415
416 let committed = self
417 .log
418 .with_transaction(move |tx| {
419 let selection = borrowed_kinds(&kinds);
420 let seen = Split {
421 own: values_of(tx.read(&stream, selection.as_deref(), 0, usize::MAX)?),
422 other: values_of(tx.read(&other, None, 0, 1)?),
426 };
427 let Some(split) = decide(seen) else {
428 return Ok(None);
430 };
431 for event in split.own.iter().chain(split.other.iter()) {
432 if let Err(refusal) = validate_event(event) {
433 park(&sink, refusal);
434 return Err(rolled_back());
435 }
436 }
437 let own = tx.append_many(
438 &stream,
439 split.own.into_iter().map(prepared).collect::<Vec<_>>(),
440 )?;
441 let elsewhere = tx.append_many(
442 &other,
443 split.other.into_iter().map(prepared).collect::<Vec<_>>(),
444 )?;
445 Ok(Some(Split {
446 own: own.into_iter().map(committed_of).collect(),
447 other: elsewhere.into_iter().map(committed_of).collect(),
448 }))
449 })
450 .await;
451
452 match taken(&parked) {
453 Some(refusal) => Err(refusal),
454 None => committed.map_err(KnlError::from),
455 }
456 }
457
458 async fn append_with_open_children(
459 &mut self,
460 scan: &ChildScan,
461 decide: ChildrenDecision,
462 ) -> KnlResult<Committed> {
463 let stream = self.stream.clone();
467 let scan = scan.clone();
468 let parked: Parked = Arc::default();
469 let sink = Arc::clone(&parked);
470
471 let committed = self
472 .log
473 .with_transaction(move |tx| {
474 let conn: &rusqlite::Connection = tx;
478 let children = open_children_in(conn, &stream, &scan)
479 .map_err(|e| eventsdb_core::Error::storage(e.to_string()))?;
480 let event = decide(children);
481 if let Err(refusal) = validate_event(&event) {
482 park(&sink, refusal);
483 return Err(rolled_back());
484 }
485 tx.append(&stream, prepared(event)).map(committed_of)
486 })
487 .await;
488
489 match taken(&parked) {
490 Some(refusal) => Err(refusal),
491 None => committed.map_err(KnlError::from),
492 }
493 }
494
495 fn database(&self) -> Option<&str> {
496 Some(self.log.database())
497 }
498
499 async fn read_kinds(
500 &self,
501 kinds: Option<&[&str]>,
502 from_seq: u64,
503 limit: usize,
504 ) -> KnlResult<Vec<Value>> {
505 self.handle
506 .read_kinds(kinds, from_seq, limit)
507 .await
508 .map(values_of)
509 .map_err(KnlError::from)
510 }
511
512 async fn read_last(&self, n: usize) -> KnlResult<Vec<Value>> {
513 self.handle
514 .read_last(n)
515 .await
516 .map(values_of)
517 .map_err(KnlError::from)
518 }
519
520 async fn head(&self) -> KnlResult<Option<u64>> {
521 self.handle.head().await.map_err(KnlError::from)
522 }
523
524 async fn len(&self) -> KnlResult<usize> {
525 self.handle.len().await.map_err(KnlError::from)
526 }
527
528 async fn query(&self, plan: &QueryPlan) -> KnlResult<QueryRows> {
540 let cap = i64::try_from(plan.limit)
546 .unwrap_or(i64::MAX)
547 .saturating_add(1);
548 let sql = format!("SELECT * FROM ({}) LIMIT {cap}", plan.sql);
549 let rows = self
550 .log
551 .query_timeout(&sql, plan.values.clone(), plan.timeout)
552 .await
553 .map_err(query_error)?;
554
555 let truncated = rows.len() > plan.limit;
556 Ok(QueryRows {
557 rows: rows
558 .into_iter()
559 .take(plan.limit)
560 .map(|row| {
564 row.into_iter()
565 .filter(|(_, value)| !value.is_null())
566 .collect()
567 })
568 .collect(),
569 truncated,
570 })
571 }
572
573 fn detach_append(&self, event: Map<String, Value>) {
574 if let Err(e) = validate_event(&event) {
580 tracing::warn!(error = %e, "knl: a detached append was refused before it was submitted");
581 return;
582 }
583 if let Err(e) = self.log.detach_append(&self.stream, prepared(event)) {
584 tracing::warn!(error = %e, "knl: a detached append was not accepted by the log");
585 }
586 }
587}
588
589fn sql_literal(text: &str) -> String {
598 format!("'{}'", text.replace('\'', "''"))
599}
600
601fn child_scan_sql(scan: &ChildScan) -> String {
612 let opened = sql_literal(&scan.opened);
613 let closed = sql_literal(&scan.closed);
614 let path = sql_literal(&format!("$.{}", scan.parent_field));
615 format!(
616 "SELECT opened.stream \
617 FROM events AS opened \
618 WHERE opened.kind = {opened} \
619 AND json_extract(opened.data, {path}) = ?1 \
620 AND NOT EXISTS ( \
621 SELECT 1 FROM events AS ending \
622 WHERE ending.stream = opened.stream AND ending.kind = {closed} \
623 ) \
624 ORDER BY opened.epoch_ms, opened.stream"
625 )
626}
627
628fn open_children_in(
639 conn: &rusqlite::Connection,
640 stream: &str,
641 scan: &ChildScan,
642) -> rusqlite::Result<Vec<String>> {
643 let mut stmt = conn.prepare(&child_scan_sql(scan))?;
644 let rows = stmt.query_map(rusqlite::params![stream], |row| row.get::<_, String>(0))?;
645 rows.collect()
646}
647
648fn is_retryable(error: &rusqlite::Error) -> bool {
651 matches!(
652 error,
653 rusqlite::Error::SqliteFailure(inner, _)
654 if matches!(
655 inner.code,
656 rusqlite::ErrorCode::DatabaseBusy | rusqlite::ErrorCode::DatabaseLocked
657 )
658 )
659}
660
661impl From<rusqlite::Error> for KnlError {
673 fn from(error: rusqlite::Error) -> Self {
674 if is_retryable(&error) {
675 return KnlError::Busy(format!("sqlite: busy/locked: {error}"));
676 }
677 KnlError::Storage(format!("sqlite: {error}"))
678 }
679}
680
681impl From<eventsdb_core::Error> for KnlError {
696 fn from(error: eventsdb_core::Error) -> Self {
697 use eventsdb_core::Error as Failure;
698 match error {
699 Failure::Validation(reason) => KnlError::Validation(reason),
700 Failure::Busy(reason) => KnlError::Busy(reason),
701 Failure::Timeout(reason) => KnlError::Timeout(reason),
702 Failure::Storage(reason) => KnlError::Storage(reason),
703 Failure::Corruption(reason) => KnlError::Corruption(reason),
704 Failure::Unsupported(reason) => KnlError::Unsupported(reason),
705 other => KnlError::Storage(other.to_string()),
706 }
707 }
708}
709
710const SQLITE_STATEMENT_ERRORS: [&str; 7] = [
738 "no such column",
739 "no such table",
740 "no such function",
741 "syntax error",
742 "near \"",
743 "wrong number of arguments",
744 "ambiguous column name",
745];
746
747fn is_statement_error(message: &str) -> bool {
749 SQLITE_STATEMENT_ERRORS
750 .iter()
751 .any(|phrase| message.contains(phrase))
752}
753
754fn query_error(error: eventsdb_core::Error) -> KnlError {
762 match KnlError::from(error) {
763 KnlError::Storage(reason) if is_statement_error(&reason) => {
764 KnlError::Validation(format!("sql: {reason}"))
765 }
766 other => other,
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773 use crate::knl::event::{kind_of, seq_of, FIELD_DATA, FIELD_META};
774 use crate::knl::query::{self, QueryOpts, QueryParams};
775 use crate::knl::CURRENT_SCHEMA_VERSION;
776 use serde_json::json;
777
778 fn obj(value: Value) -> Map<String, Value> {
780 match value {
781 Value::Object(map) => map,
782 other => panic!("test fixture must be an object, got {other}"),
783 }
784 }
785
786 fn ev(i: usize) -> Map<String, Value> {
788 obj(json!({ "kind": format!("e{i}") }))
789 }
790
791 fn budget(kind: &str, amount: i64) -> Map<String, Value> {
793 obj(json!({ "kind": kind, "data": { "amount": amount } }))
794 }
795
796 async fn mem_store() -> (SqliteEventStore, Logs) {
803 let logs = Logs::new();
804 let store = SqliteEventStore::open_memory(uuid::Uuid::new_v4().to_string(), &logs)
805 .await
806 .expect("open");
807 (store, logs)
808 }
809
810 fn decide(
813 f: impl FnOnce(Vec<Value>) -> Option<Map<String, Value>> + Send + 'static,
814 ) -> Decision {
815 Box::new(f)
816 }
817
818 #[tokio::test]
819 async fn append_assigns_gap_free_monotonic_seq_from_one() {
820 let (mut store, _logs) = mem_store().await;
821 assert!(store.is_empty().await.expect("is_empty"));
822 assert_eq!(store.len().await.expect("len"), 0);
823
824 let a = store.append(ev(1)).await.expect("append e1");
825 let b = store.append(ev(2)).await.expect("append e2");
826 let c = store.append(ev(3)).await.expect("append e3");
827
828 assert_eq!((a.seq, b.seq, c.seq), (1, 2, 3));
829 assert_eq!(store.len().await.expect("len"), 3);
830 assert!(!store.is_empty().await.expect("is_empty"));
831
832 let stored = store.read(0, usize::MAX).await.expect("read");
834 let stored_epoch = stored[0]
835 .get("epoch_ms")
836 .and_then(Value::as_u64)
837 .expect("epoch is on the stored event");
838 assert_eq!(stored_epoch, a.epoch_ms);
839 }
840
841 #[tokio::test]
842 async fn a_rejected_append_records_nothing_and_burns_no_seq() {
843 let (mut store, _logs) = mem_store().await;
844 store
845 .append(obj(json!({ "text": "no kind" })))
846 .await
847 .expect_err("kind is required");
848 assert_eq!(store.len().await.expect("len"), 0);
849 assert_eq!(store.append(ev(1)).await.expect("append").seq, 1);
850 }
851
852 #[tokio::test]
856 async fn a_caller_supplied_coordinate_is_overwritten() {
857 let (mut store, _logs) = mem_store().await;
858 store.append(ev(1)).await.expect("seed");
859
860 let committed = store
861 .append(obj(json!({ "kind": "e2", "seq": 99, "epoch_ms": 7 })))
862 .await
863 .expect("append");
864 assert_eq!(committed.seq, 2, "the store numbers the stream");
865 assert_ne!(committed.epoch_ms, 7, "the store reads the clock");
866
867 let stored = store.read(0, usize::MAX).await.expect("read");
868 assert_eq!(seq_of(&stored[1]), 2);
869 assert_eq!(
870 stored[1].get("_schema_version").and_then(Value::as_u64),
871 Some(CURRENT_SCHEMA_VERSION),
872 "every append is stamped with the kernel's version"
873 );
874 }
875
876 #[tokio::test]
880 async fn append_if_decides_inside_the_transaction_and_writes_only_a_some() {
881 let (mut store, _logs) = mem_store().await;
882 store.append(ev(1)).await.expect("seed");
883
884 let seen_kinds: Arc<Mutex<Vec<String>>> = Arc::default();
887 let recorded = Arc::clone(&seen_kinds);
888 let committed = store
889 .append_if(
890 None,
891 decide(move |events| {
892 *recorded.lock().expect("not poisoned") =
893 events.iter().map(|e| kind_of(e).to_string()).collect();
894 Some(ev(2))
895 }),
896 )
897 .await
898 .expect("append_if");
899 assert_eq!(
900 *seen_kinds.lock().expect("not poisoned"),
901 ["e1"],
902 "decide saw the durable stream"
903 );
904 assert_eq!(committed.map(|c| c.seq), Some(2));
905
906 let nothing = store
907 .append_if(None, decide(|_| None))
908 .await
909 .expect("append_if");
910 assert_eq!(nothing, None);
911 assert_eq!(store.len().await.expect("len"), 2, "a None commits nothing");
912 assert_eq!(store.append(ev(3)).await.expect("append").seq, 3);
913 }
914
915 #[tokio::test]
919 async fn append_if_validates_the_event_the_decision_returns() {
920 let (mut store, _logs) = mem_store().await;
921 let err = store
922 .append_if(None, decide(|_| Some(obj(json!({ "text": "no kind" })))))
923 .await
924 .expect_err("kind is required");
925 assert_eq!(err.kind(), KnlError::VALIDATION, "{err}");
926 assert_eq!(store.len().await.expect("len"), 0);
927 }
928
929 #[tokio::test]
933 async fn append_if_holds_a_kernel_kind_to_its_data() {
934 let (mut store, _logs) = mem_store().await;
935 let err = store
936 .append_if(
937 None,
938 decide(|_| Some(obj(json!({ "kind": "budget_spent", "data": {} })))),
939 )
940 .await
941 .expect_err("a kernel kind needs its data");
942 assert_eq!(err.kind(), KnlError::VALIDATION, "{err}");
943 assert!(err.reason().contains("amount"), "{}", err.reason());
944 assert_eq!(store.len().await.expect("len"), 0);
945 }
946
947 #[tokio::test]
951 async fn append_many_is_one_transaction_that_lands_whole_or_not_at_all() {
952 let (mut store, _logs) = mem_store().await;
953 store.append(ev(1)).await.expect("seed");
954
955 let committed = store
956 .append_many(vec![ev(2), ev(3)])
957 .await
958 .expect("the batch");
959 assert_eq!(
960 committed.iter().map(|c| c.seq).collect::<Vec<_>>(),
961 [2, 3],
962 "numbered on from the head that was there"
963 );
964 let stored = store.read(0, usize::MAX).await.expect("read");
965 let kinds: Vec<&str> = stored.iter().map(kind_of).collect();
966 assert_eq!(kinds, ["e1", "e2", "e3"]);
967
968 store
971 .append_many(vec![ev(4), obj(json!({ "text": "no kind" }))])
972 .await
973 .expect_err("kind is required");
974 assert_eq!(
975 store.len().await.expect("len"),
976 3,
977 "a batch that fails lands nothing"
978 );
979 }
980
981 #[tokio::test]
985 async fn append_if_many_writes_both_streams_or_neither() {
986 let logs = Logs::new();
987 let parent = uuid::Uuid::new_v4().to_string();
988 let child = uuid::Uuid::new_v4().to_string();
989 let log = logs.memory().await.expect("the log");
990 let mut ledger = SqliteEventStore::on(Arc::clone(&log), parent.clone());
991 let opened = SqliteEventStore::on(Arc::clone(&log), child.clone());
992 assert_eq!(
993 ledger.database(),
994 opened.database(),
995 "both streams are in one database"
996 );
997
998 ledger
999 .append(budget("budget_granted", 100))
1000 .await
1001 .expect("the grant");
1002
1003 let seen: Arc<Mutex<(usize, usize)>> = Arc::default();
1006 let recorded = Arc::clone(&seen);
1007 let child_stream = child.clone();
1008 let committed = ledger
1009 .append_if_many(
1010 &child,
1011 Some(&["budget_granted"]),
1012 Box::new(move |split: Split<Value>| {
1013 *recorded.lock().expect("not poisoned") = (split.own.len(), split.other.len());
1014 Some(Split {
1015 own: vec![obj(json!({
1016 "kind": "budget_reserved",
1017 "data": { "amount": 10, "child": child_stream },
1018 }))],
1019 other: vec![
1020 obj(json!({
1021 "kind": "session_opened",
1022 "data": { "scope_id": "sc-1", "owner": "o", "parent": "p" },
1023 })),
1024 budget("budget_granted", 10),
1025 ],
1026 })
1027 }),
1028 )
1029 .await
1030 .expect("append_if_many")
1031 .expect("a Some writes");
1032 assert_eq!(
1033 *seen.lock().expect("not poisoned"),
1034 (1, 0),
1035 "its own kinds, and an empty other stream"
1036 );
1037 assert_eq!(committed.own.iter().map(|c| c.seq).collect::<Vec<_>>(), [2]);
1038 assert_eq!(
1039 committed.other.iter().map(|c| c.seq).collect::<Vec<_>>(),
1040 [1, 2],
1041 "the other stream is numbered from its own head"
1042 );
1043
1044 let nothing = ledger
1046 .append_if_many(&child, None, Box::new(|_| None))
1047 .await
1048 .expect("append_if_many");
1049 assert_eq!(nothing, None);
1050 assert_eq!(ledger.len().await.expect("len"), 2);
1051 assert_eq!(opened.len().await.expect("len"), 2);
1052
1053 let err = ledger
1055 .append_if_many(
1056 &child,
1057 None,
1058 Box::new(|_| {
1059 Some(Split {
1060 own: vec![budget("budget_spent", 1)],
1061 other: vec![obj(json!({ "text": "no kind" }))],
1062 })
1063 }),
1064 )
1065 .await
1066 .expect_err("kind is required");
1067 assert_eq!(err.kind(), KnlError::VALIDATION, "{err}");
1068 assert_eq!(ledger.len().await.expect("len"), 2);
1069 assert_eq!(opened.len().await.expect("len"), 2);
1070 }
1071
1072 #[tokio::test]
1081 async fn the_child_scan_reads_by_the_parent_index() {
1082 let logs = Logs::new();
1083 let log = logs.memory().await.expect("the log");
1084 let scan = ChildScan {
1085 opened: "session_opened".to_string(),
1086 closed: "session_closed".to_string(),
1087 parent_field: "parent".to_string(),
1088 };
1089 let rows = log
1090 .query(
1091 &format!("EXPLAIN QUERY PLAN {}", child_scan_sql(&scan)),
1092 vec![Value::from("p-1")],
1093 )
1094 .await
1095 .expect("the plan");
1096 let plan: String = rows
1097 .iter()
1098 .filter_map(|row| row.get("detail").and_then(Value::as_str))
1099 .collect::<Vec<_>>()
1100 .join(" | ");
1101 assert!(
1102 plan.contains("events_session_opened_parent"),
1103 "the scan must read by the parent index: {plan}"
1104 );
1105 }
1106
1107 #[test]
1116 fn a_word_written_into_the_scan_stays_one_word() {
1117 let sql = child_scan_sql(&ChildScan {
1118 opened: "it's opened".to_string(),
1119 closed: "it's closed".to_string(),
1120 parent_field: "it's parent".to_string(),
1121 });
1122 assert!(sql.contains("'it''s opened'"), "{sql}");
1123 assert!(sql.contains("'it''s closed'"), "{sql}");
1124 assert!(sql.contains("'$.it''s parent'"), "{sql}");
1125 assert_eq!(
1126 sql.matches('\'').count() % 2,
1127 0,
1128 "every literal is closed: {sql}"
1129 );
1130 }
1131
1132 #[tokio::test]
1137 async fn database_is_the_same_for_two_streams_of_one_database() {
1138 let logs = Logs::new();
1139 let dir = tempfile::tempdir().expect("tempdir");
1140 let here = dir.path().join("knl.db");
1141 let there = dir.path().join("other.db");
1142
1143 let a = SqliteEventStore::open(&here, "s-1", &logs)
1144 .await
1145 .expect("open a");
1146 let b = SqliteEventStore::open(&here, "s-2", &logs)
1147 .await
1148 .expect("open b");
1149 let elsewhere = SqliteEventStore::open(&there, "s-1", &logs)
1150 .await
1151 .expect("open elsewhere");
1152
1153 assert_eq!(a.database(), b.database());
1154 assert_ne!(a.database(), elsewhere.database());
1155
1156 let (mem, _mem_logs) = mem_store().await;
1158 assert_ne!(mem.database(), a.database());
1159 assert!(mem.database().is_some());
1160 }
1161
1162 #[tokio::test]
1166 async fn open_children_are_the_unended_streams_that_name_this_one() {
1167 let logs = Logs::new();
1168 let log = logs.memory().await.expect("the log");
1169 let scan = ChildScan {
1170 opened: "session_opened".to_string(),
1171 closed: "session_closed".to_string(),
1172 parent_field: "parent".to_string(),
1173 };
1174
1175 async fn opened(log: &Arc<SqliteEventLog>, id: &str, parent: &str) -> SqliteEventStore {
1177 let mut store = SqliteEventStore::on(Arc::clone(log), id);
1178 store
1179 .append(obj(json!({
1180 "kind": "session_opened",
1181 "data": { "scope_id": "sc", "owner": "o", "parent": parent },
1182 })))
1183 .await
1184 .expect("the opening");
1185 store
1186 }
1187
1188 let mut parent = SqliteEventStore::on(Arc::clone(&log), "parent");
1189 let _open_child = opened(&log, "child-open", "parent").await;
1190 let mut ended = opened(&log, "child-ended", "parent").await;
1191 let _elsewhere = opened(&log, "child-of-other", "another").await;
1192 ended
1193 .append(obj(json!({
1194 "kind": "session_closed",
1195 "data": { "reason": "done" },
1196 })))
1197 .await
1198 .expect("the ending");
1199
1200 let recorded: Arc<Mutex<Vec<String>>> = Arc::default();
1201 let seen = Arc::clone(&recorded);
1202 let committed = parent
1203 .append_with_open_children(
1204 &scan,
1205 Box::new(move |children| {
1206 *seen.lock().expect("not poisoned") = children.clone();
1207 obj(json!({
1208 "kind": "session_closed",
1209 "data": { "reason": "done", "open_children": children },
1210 }))
1211 }),
1212 )
1213 .await
1214 .expect("the close");
1215 assert_eq!(committed.seq, 1, "the close is the parent's first event");
1216 assert_eq!(
1217 *recorded.lock().expect("not poisoned"),
1218 ["child-open"],
1219 "only this stream's children, and only the open ones"
1220 );
1221 }
1222
1223 #[tokio::test]
1227 async fn read_kinds_selects_by_kind_and_keeps_the_streams_order() {
1228 let (mut store, _logs) = mem_store().await;
1229 store
1230 .append(budget("budget_granted", 100))
1231 .await
1232 .expect("grant");
1233 store.append(ev(1)).await.expect("noise");
1234 store
1235 .append(budget("budget_spent", 10))
1236 .await
1237 .expect("spend");
1238
1239 let all = store.read(0, usize::MAX).await.expect("read");
1240 assert_eq!(
1241 all.iter().map(kind_of).collect::<Vec<_>>(),
1242 ["budget_granted", "e1", "budget_spent"]
1243 );
1244
1245 let ledger = store
1246 .read_kinds(Some(&["budget_granted", "budget_spent"]), 0, usize::MAX)
1247 .await
1248 .expect("read_kinds");
1249 assert_eq!(
1250 ledger.iter().map(seq_of).collect::<Vec<_>>(),
1251 [1, 3],
1252 "the seq the stream gave them, not a fresh numbering"
1253 );
1254
1255 let nothing = store
1256 .read_kinds(Some(&[]), 0, usize::MAX)
1257 .await
1258 .expect("read_kinds");
1259 assert!(nothing.is_empty(), "an empty selection selects nothing");
1260 }
1261
1262 #[tokio::test]
1266 async fn append_if_filters_the_decisions_input_and_numbers_against_the_stream() {
1267 let (mut store, _logs) = mem_store().await;
1268 store
1269 .append(budget("budget_granted", 100))
1270 .await
1271 .expect("grant");
1272 store.append(ev(1)).await.expect("noise");
1273
1274 let seen: Arc<Mutex<Vec<String>>> = Arc::default();
1275 let recorded = Arc::clone(&seen);
1276 let committed = store
1277 .append_if(
1278 Some(&["budget_granted", "budget_spent"]),
1279 decide(move |events| {
1280 *recorded.lock().expect("not poisoned") =
1281 events.iter().map(|e| kind_of(e).to_string()).collect();
1282 Some(budget("budget_spent", 10))
1283 }),
1284 )
1285 .await
1286 .expect("append_if");
1287 assert_eq!(
1288 *seen.lock().expect("not poisoned"),
1289 ["budget_granted"],
1290 "only the kinds asked for"
1291 );
1292 assert_eq!(
1293 committed.map(|c| c.seq),
1294 Some(3),
1295 "numbered against the whole stream"
1296 );
1297 }
1298
1299 #[tokio::test]
1303 async fn append_if_across_two_handles_decides_on_the_other_handles_write() {
1304 let logs = Logs::new();
1305 let log = logs.memory().await.expect("the log");
1306 let stream = uuid::Uuid::new_v4().to_string();
1307 let mut a = SqliteEventStore::on(Arc::clone(&log), stream.clone());
1308 let mut b = SqliteEventStore::on(Arc::clone(&log), stream);
1309
1310 fn claim() -> Decision {
1312 decide(|events: Vec<Value>| {
1313 if events.is_empty() {
1314 Some(obj(json!({ "kind": "claim" })))
1315 } else {
1316 None
1317 }
1318 })
1319 }
1320 assert!(a
1321 .append_if(Some(&["claim"]), claim())
1322 .await
1323 .expect("a")
1324 .is_some());
1325 assert!(
1326 b.append_if(Some(&["claim"]), claim())
1327 .await
1328 .expect("b")
1329 .is_none(),
1330 "the second handle decided against what the first wrote"
1331 );
1332 assert_eq!(a.len().await.expect("len"), 1);
1333 }
1334
1335 #[tokio::test]
1336 async fn read_pages_by_from_seq_and_limit() {
1337 let (mut store, _logs) = mem_store().await;
1338 for i in 1..=5 {
1339 store.append(ev(i)).await.expect("append");
1340 }
1341 let page = store.read(2, 2).await.expect("read");
1342 assert_eq!(page.iter().map(seq_of).collect::<Vec<_>>(), [2, 3]);
1343 let rest = store.read(4, usize::MAX).await.expect("read");
1344 assert_eq!(rest.iter().map(seq_of).collect::<Vec<_>>(), [4, 5]);
1345 assert!(store.read(6, 10).await.expect("read").is_empty());
1346 }
1347
1348 #[tokio::test]
1350 async fn read_last_takes_the_end_of_the_stream_in_seq_order() {
1351 let (mut store, _logs) = mem_store().await;
1352 for i in 1..=5 {
1353 store.append(ev(i)).await.expect("append");
1354 }
1355 let tail = store.read_last(2).await.expect("read_last");
1356 assert_eq!(tail.iter().map(seq_of).collect::<Vec<_>>(), [4, 5]);
1357 assert!(store.read_last(0).await.expect("read_last").is_empty());
1358 assert_eq!(store.read_last(50).await.expect("read_last").len(), 5);
1359 }
1360
1361 #[tokio::test]
1362 async fn head_is_none_when_empty_then_tracks_the_max() {
1363 let (mut store, _logs) = mem_store().await;
1364 assert_eq!(store.head().await.expect("head"), None);
1365 store.append(ev(1)).await.expect("append");
1366 assert_eq!(store.head().await.expect("head"), Some(1));
1367 store.append(ev(2)).await.expect("append");
1368 assert_eq!(store.head().await.expect("head"), Some(2));
1369 }
1370
1371 #[tokio::test]
1375 async fn read_reconstructs_the_written_event_out_of_its_columns() {
1376 let (mut store, _logs) = mem_store().await;
1377 let committed = store
1378 .append(obj(json!({
1379 "kind": "llm_response",
1380 "meta": { "beat": "b-1", "attempt": 2, "final": true },
1381 "data": { "content": { "text": "hi" }, "usage": { "input_tokens": 3 } },
1382 })))
1383 .await
1384 .expect("append");
1385
1386 let stored = store.read(0, usize::MAX).await.expect("read");
1387 let event = &stored[0];
1388 assert_eq!(kind_of(event), "llm_response");
1389 assert_eq!(seq_of(event), committed.seq);
1390 assert_eq!(
1391 event.get(FIELD_META),
1392 Some(&json!({ "beat": "b-1", "attempt": 2, "final": true }))
1393 );
1394 assert_eq!(
1395 event.get(FIELD_DATA),
1396 Some(&json!({ "content": { "text": "hi" }, "usage": { "input_tokens": 3 } }))
1397 );
1398 assert_eq!(
1399 event.get("_schema_version").and_then(Value::as_u64),
1400 Some(CURRENT_SCHEMA_VERSION)
1401 );
1402 }
1403
1404 #[tokio::test]
1408 async fn the_beat_is_a_meta_label_with_an_index() {
1409 let (mut store, _logs) = mem_store().await;
1410 store
1411 .append(obj(json!({ "kind": "e1", "meta": { "beat": "b-1" } })))
1412 .await
1413 .expect("append");
1414 store.append(ev(2)).await.expect("append");
1415
1416 let rows = ask(
1417 &store,
1418 "SELECT json_extract(meta, '$.beat') AS beat FROM events \
1419 WHERE stream = $stream ORDER BY seq",
1420 )
1421 .await
1422 .expect("query");
1423 assert_eq!(rows.rows[0].get("beat"), Some(&json!("b-1")));
1424 assert!(
1425 !rows.rows[1].contains_key("beat"),
1426 "an undeclared beat reads as nil: {:?}",
1427 rows.rows[1]
1428 );
1429
1430 let indexes = ask(
1431 &store,
1432 "SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'events'",
1433 )
1434 .await
1435 .expect("query");
1436 let names: Vec<&str> = indexes
1437 .rows
1438 .iter()
1439 .filter_map(|row| row.get("name").and_then(Value::as_str))
1440 .collect();
1441 assert!(
1442 names.contains(&"events_meta_beat"),
1443 "the beat label is indexed: {names:?}"
1444 );
1445 assert!(
1446 !names.contains(&"events_stream_beat_seq"),
1447 "and the column's old index is gone: {names:?}"
1448 );
1449 }
1450
1451 #[tokio::test]
1452 async fn events_persist_across_a_reopen_of_the_same_path_and_stream() {
1453 let dir = tempfile::tempdir().expect("tempdir");
1454 let path = dir.path().join("knl.db");
1455 let stream = "s-1";
1456
1457 {
1458 let logs = Logs::new();
1459 let mut store = SqliteEventStore::open(&path, stream, &logs)
1460 .await
1461 .expect("open");
1462 store.append(ev(1)).await.expect("append");
1463 store.append(ev(2)).await.expect("append");
1464 assert!(logs.shutdown().await.is_empty(), "the log closed cleanly");
1465 }
1466
1467 let logs = Logs::new();
1468 let reopened = SqliteEventStore::open(&path, stream, &logs)
1469 .await
1470 .expect("reopen");
1471 let stored = reopened.read(0, usize::MAX).await.expect("read");
1472 assert_eq!(stored.iter().map(kind_of).collect::<Vec<_>>(), ["e1", "e2"]);
1473 assert_eq!(reopened.head().await.expect("head"), Some(2));
1474 }
1475
1476 #[tokio::test]
1477 async fn two_streams_in_one_db_file_do_not_see_each_others_events() {
1478 let logs = Logs::new();
1479 let dir = tempfile::tempdir().expect("tempdir");
1480 let path = dir.path().join("knl.db");
1481
1482 let mut a = SqliteEventStore::open(&path, "s-a", &logs)
1483 .await
1484 .expect("open a");
1485 let mut b = SqliteEventStore::open(&path, "s-b", &logs)
1486 .await
1487 .expect("open b");
1488 a.append(ev(1)).await.expect("a");
1489 b.append(ev(2)).await.expect("b");
1490
1491 assert_eq!(
1492 a.read(0, usize::MAX)
1493 .await
1494 .expect("read a")
1495 .iter()
1496 .map(kind_of)
1497 .collect::<Vec<_>>(),
1498 ["e1"]
1499 );
1500 assert_eq!(
1501 b.read(0, usize::MAX)
1502 .await
1503 .expect("read b")
1504 .iter()
1505 .map(kind_of)
1506 .collect::<Vec<_>>(),
1507 ["e2"]
1508 );
1509 assert_eq!(a.head().await.expect("head"), Some(1));
1510 assert_eq!(b.head().await.expect("head"), Some(1));
1511 }
1512
1513 #[tokio::test]
1522 async fn read_errors_on_a_corrupt_row_instead_of_dropping_it() {
1523 let dir = tempfile::tempdir().expect("tempdir");
1524 let path = dir.path().join("knl.db");
1525
1526 {
1527 let logs = Logs::new();
1528 let mut store = SqliteEventStore::open(&path, "s-1", &logs)
1529 .await
1530 .expect("open");
1531 store.append(ev(1)).await.expect("append");
1532 assert!(logs.shutdown().await.is_empty(), "the log closed cleanly");
1533 }
1534
1535 let conn = rusqlite::Connection::open(&path).expect("open the file");
1536 conn.execute(
1537 "INSERT INTO events (stream, seq, epoch_ms, kind, schema_version, meta, data) \
1538 VALUES ('s-1', 2, 0, 'e2', 2, '{}', 'not json')",
1539 [],
1540 )
1541 .expect("write a bad row");
1542 drop(conn);
1543
1544 let logs = Logs::new();
1545 let store = SqliteEventStore::open(&path, "s-1", &logs)
1546 .await
1547 .expect("reopen");
1548 let err = store
1549 .read(0, usize::MAX)
1550 .await
1551 .expect_err("a row that will not decode must surface");
1552 assert_eq!(err.kind(), KnlError::CORRUPTION, "{err}");
1553 }
1554
1555 #[test]
1558 fn every_store_error_has_a_kernel_class() {
1559 use eventsdb_core::Error as Failure;
1560 let cases = [
1561 (Failure::Validation("v".into()), KnlError::VALIDATION),
1562 (Failure::Busy("b".into()), KnlError::BUSY),
1563 (Failure::Timeout("t".into()), KnlError::TIMEOUT),
1564 (Failure::Storage("s".into()), KnlError::STORAGE),
1565 (Failure::Corruption("c".into()), KnlError::CORRUPTION),
1566 (Failure::Unsupported("u".into()), KnlError::UNSUPPORTED),
1567 (
1571 Failure::Truncated {
1572 requested: 1,
1573 removed_up_to: 2,
1574 },
1575 KnlError::STORAGE,
1576 ),
1577 ];
1578 for (failure, expected) in cases {
1579 let translated = KnlError::from(failure);
1580 assert_eq!(translated.kind(), expected, "{translated}");
1581 }
1582 assert!(
1583 !KnlError::from(Failure::Timeout("t".into())).is_retryable(),
1584 "a deadline is not contention"
1585 );
1586 assert!(KnlError::from(Failure::Busy("b".into())).is_retryable());
1587 }
1588
1589 #[tokio::test]
1597 async fn a_write_that_stays_contended_surfaces_as_busy() {
1598 let dir = tempfile::tempdir().expect("tempdir");
1599 let path = dir.path().join("knl.db");
1600 let log = SqliteEventLog::open_with(
1601 &path,
1602 eventsdb_sqlite::OpenOptions::default()
1603 .busy_timeout(std::time::Duration::from_millis(50))
1604 .upcasters(crate::knl::kernel_upcasters()),
1605 )
1606 .await
1607 .expect("open");
1608 let mut store = SqliteEventStore::on(Arc::new(log), "s-1");
1609 store.append(ev(1)).await.expect("the first append");
1610
1611 let blocker = rusqlite::Connection::open(&path).expect("open the file");
1613 blocker
1614 .execute_batch("BEGIN IMMEDIATE; CREATE TABLE IF NOT EXISTS held (x)")
1615 .expect("hold the lock");
1616
1617 let err = store
1618 .append(ev(2))
1619 .await
1620 .expect_err("a write that stays contended must surface");
1621 assert_eq!(err.kind(), KnlError::BUSY, "{err}");
1622 assert!(err.is_retryable(), "busy is the class that says ask again");
1623
1624 blocker.execute_batch("ROLLBACK").expect("release");
1625 store.append(ev(3)).await.expect("the lock is free again");
1626 }
1627
1628 #[tokio::test]
1632 async fn two_handles_on_one_stream_both_append_in_arrival_order() {
1633 let logs = Logs::new();
1634 let dir = tempfile::tempdir().expect("tempdir");
1635 let path = dir.path().join("knl.db");
1636 let mut a = SqliteEventStore::open(&path, "s-1", &logs)
1637 .await
1638 .expect("open a");
1639 let mut b = SqliteEventStore::open(&path, "s-1", &logs)
1640 .await
1641 .expect("open b");
1642
1643 assert_eq!(a.append(ev(1)).await.expect("a").seq, 1);
1644 assert_eq!(b.append(ev(2)).await.expect("b").seq, 2);
1645 assert_eq!(a.append(ev(3)).await.expect("a").seq, 3);
1646
1647 let stored = b.read(0, usize::MAX).await.expect("read");
1648 assert_eq!(
1649 stored.iter().map(kind_of).collect::<Vec<_>>(),
1650 ["e1", "e2", "e3"]
1651 );
1652 }
1653
1654 async fn ask(store: &SqliteEventStore, sql: &str) -> KnlResult<QueryRows> {
1658 ask_with(store, sql, QueryParams::None, &QueryOpts::default()).await
1659 }
1660
1661 async fn ask_with(
1663 store: &SqliteEventStore,
1664 sql: &str,
1665 params: QueryParams,
1666 opts: &QueryOpts,
1667 ) -> KnlResult<QueryRows> {
1668 let plan = query::plan(sql, params, opts, &store.stream)?;
1669 store.query(&plan).await
1670 }
1671
1672 fn kinds_of(rows: &QueryRows) -> Vec<&str> {
1674 rows.rows
1675 .iter()
1676 .filter_map(|row| row.get("kind").and_then(Value::as_str))
1677 .collect()
1678 }
1679
1680 #[tokio::test]
1683 async fn a_query_reads_what_the_writer_wrote() {
1684 let (mut store, _logs) = mem_store().await;
1685 store.append(ev(1)).await.expect("append");
1686 store.append(ev(2)).await.expect("append");
1687
1688 let rows = ask(
1689 &store,
1690 "SELECT kind, seq FROM events WHERE stream = $stream ORDER BY seq",
1691 )
1692 .await
1693 .expect("query");
1694 assert_eq!(kinds_of(&rows), ["e1", "e2"]);
1695 assert!(!rows.truncated);
1696 assert_eq!(rows.rows[0].get("seq"), Some(&json!(1)));
1697 }
1698
1699 #[tokio::test]
1702 async fn stream_binds_to_this_stores_own_stream() {
1703 let logs = Logs::new();
1704 let log = logs.memory().await.expect("the log");
1705 let mut mine = SqliteEventStore::on(Arc::clone(&log), "s-mine");
1706 let mut theirs = SqliteEventStore::on(Arc::clone(&log), "s-theirs");
1707 mine.append(ev(1)).await.expect("mine");
1708 theirs.append(ev(2)).await.expect("theirs");
1709
1710 let rows = ask(&mine, "SELECT kind FROM events WHERE stream = $stream")
1711 .await
1712 .expect("query");
1713 assert_eq!(kinds_of(&rows), ["e1"]);
1714 }
1715
1716 #[tokio::test]
1719 async fn sessions_reads_across_the_set_it_was_given() {
1720 let logs = Logs::new();
1721 let log = logs.memory().await.expect("the log");
1722 let mut one = SqliteEventStore::on(Arc::clone(&log), "s-one");
1723 let mut two = SqliteEventStore::on(Arc::clone(&log), "s-two");
1724 let mut three = SqliteEventStore::on(Arc::clone(&log), "s-three");
1725 one.append(ev(1)).await.expect("one");
1726 two.append(ev(2)).await.expect("two");
1727 three.append(ev(3)).await.expect("three");
1728
1729 let opts = QueryOpts {
1730 sessions: Some(vec!["s-one".to_string(), "s-two".to_string()]),
1731 ..QueryOpts::default()
1732 };
1733 let rows = ask_with(
1734 &one,
1735 "SELECT kind FROM events WHERE stream IN $sessions ORDER BY position",
1736 QueryParams::None,
1737 &opts,
1738 )
1739 .await
1740 .expect("query");
1741 assert_eq!(kinds_of(&rows), ["e1", "e2"]);
1742 }
1743
1744 #[tokio::test]
1747 async fn a_bound_value_with_a_quote_in_it_is_a_value() {
1748 let (mut store, _logs) = mem_store().await;
1749 store
1750 .append(obj(json!({ "kind": "it's fine" })))
1751 .await
1752 .expect("append");
1753
1754 let rows = ask_with(
1755 &store,
1756 "SELECT kind FROM events WHERE stream = $stream AND kind = :kind",
1757 QueryParams::Named(obj(json!({ "kind": "it's fine" }))),
1758 &QueryOpts::default(),
1759 )
1760 .await
1761 .expect("query");
1762 assert_eq!(kinds_of(&rows), ["it's fine"]);
1763 }
1764
1765 #[tokio::test]
1768 async fn the_row_cap_is_reported_when_it_cuts() {
1769 let (mut store, _logs) = mem_store().await;
1770 for i in 1..=5 {
1771 store.append(ev(i)).await.expect("append");
1772 }
1773
1774 let capped = QueryOpts {
1775 limit: 2,
1776 ..QueryOpts::default()
1777 };
1778 let rows = ask_with(
1779 &store,
1780 "SELECT kind FROM events WHERE stream = $stream ORDER BY seq",
1781 QueryParams::None,
1782 &capped,
1783 )
1784 .await
1785 .expect("query");
1786 assert_eq!(kinds_of(&rows), ["e1", "e2"]);
1787 assert!(rows.truncated, "the answer was cut");
1788
1789 let exact = QueryOpts {
1790 limit: 5,
1791 ..QueryOpts::default()
1792 };
1793 let rows = ask_with(
1794 &store,
1795 "SELECT kind FROM events WHERE stream = $stream ORDER BY seq",
1796 QueryParams::None,
1797 &exact,
1798 )
1799 .await
1800 .expect("query");
1801 assert_eq!(rows.rows.len(), 5);
1802 assert!(!rows.truncated, "nothing was cut off");
1803 }
1804
1805 #[tokio::test]
1808 async fn a_query_that_runs_too_long_is_a_timeout() {
1809 let (store, _logs) = mem_store().await;
1810 let hurried = QueryOpts {
1811 timeout_ms: 50,
1812 ..QueryOpts::default()
1813 };
1814 let err = ask_with(
1815 &store,
1816 "WITH RECURSIVE forever(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM forever) \
1818 SELECT COUNT(*) FROM forever",
1819 QueryParams::None,
1820 &hurried,
1821 )
1822 .await
1823 .expect_err("an endless query must be cut short");
1824 assert_eq!(err.kind(), KnlError::TIMEOUT, "{err}");
1825 assert!(!err.is_retryable(), "a slow query is not a retry: {err}");
1826
1827 assert!(ask(&store, "SELECT 1 AS one").await.is_ok());
1830 }
1831
1832 #[tokio::test]
1836 async fn a_write_or_a_second_statement_is_refused_before_the_connection() {
1837 let (store, _logs) = mem_store().await;
1838 for sql in [
1839 "INSERT INTO events (stream) VALUES ('x')",
1840 "UPDATE events SET kind = 'x'",
1841 "PRAGMA table_info(events)",
1842 "ATTACH DATABASE '/tmp/other.db' AS other",
1843 "SELECT 1; DROP TABLE events",
1844 ] {
1845 let err = ask(&store, sql).await.expect_err("must be refused");
1846 assert_eq!(err.kind(), KnlError::VALIDATION, "{sql:?}: {err}");
1847 }
1848 }
1849
1850 #[tokio::test]
1858 async fn a_statement_that_does_not_compile_is_the_callers() {
1859 let (store, _logs) = mem_store().await;
1860 for (sql, phrase) in [
1861 ("SELECT beat FROM events", "no such column"),
1863 ("SELECT * FROM chronicle", "no such table"),
1864 ("SELECT nonesuch(1) AS x", "no such function"),
1865 ("SELECT 1 + FROM events", "syntax error"),
1866 ("SELECT * FROM events ORDER seq", "near \""),
1867 ("SELECT abs(1, 2) AS x", "wrong number of arguments"),
1868 (
1869 "SELECT seq FROM events AS a, events AS b",
1870 "ambiguous column name",
1871 ),
1872 ] {
1873 let err = ask(&store, sql).await.expect_err("must not compile");
1874 assert_eq!(err.kind(), KnlError::VALIDATION, "{sql:?}: {err}");
1875 assert!(
1876 err.reason().contains(phrase),
1877 "{sql:?}: expected SQLite to say {phrase:?}, got {err}"
1878 );
1879 assert!(
1880 err.reason().starts_with("sql: "),
1881 "{sql:?}: the reason names which half was wrong: {err}"
1882 );
1883 }
1884 }
1885
1886 #[tokio::test]
1890 async fn a_real_store_fault_is_still_storage() {
1891 let (store, _logs) = mem_store().await;
1892 let err = ask(&store, "SELECT zeroblob(1000000001) AS huge")
1896 .await
1897 .expect_err("over SQLITE_MAX_LENGTH");
1898 assert_eq!(err.kind(), KnlError::STORAGE, "{err}");
1899 }
1900
1901 #[tokio::test]
1904 async fn the_sqlite_types_map_onto_values_and_null_is_absence() {
1905 let (store, _logs) = mem_store().await;
1906 let rows = ask(
1907 &store,
1908 "SELECT 1 AS whole, 1.5 AS fraction, 'text' AS words, NULL AS absent",
1910 )
1911 .await
1912 .expect("query");
1913 let row = &rows.rows[0];
1914 assert_eq!(row["whole"], Value::from(1));
1915 assert_eq!(row["fraction"], Value::from(1.5));
1916 assert_eq!(row["words"], Value::from("text"));
1917 assert!(
1918 !row.contains_key("absent"),
1919 "a NULL column is absent, so it reads as nil: {row:?}"
1920 );
1921 }
1922
1923 #[tokio::test]
1932 async fn a_cell_with_no_json_value_is_refused_and_the_refusal_names_it() {
1933 let (store, _logs) = mem_store().await;
1934 for (sql, column, advice) in [
1935 ("SELECT CAST('bytes' AS BLOB) AS raw", "raw", "hex(raw)"),
1936 ("SELECT 9e999 AS boundless", "boundless", "CAST(boundless"),
1940 ("SELECT CAST(x'ff' AS TEXT) AS garbled", "garbled", "UTF-8"),
1941 ] {
1942 let err = ask(&store, sql).await.expect_err("must be refused");
1943 assert_eq!(err.kind(), KnlError::UNSUPPORTED, "{sql:?}: {err}");
1944 assert!(
1945 err.reason().contains(column),
1946 "{sql:?}: the refusal names the column: {err}"
1947 );
1948 assert!(
1949 err.reason().contains(advice),
1950 "{sql:?}: expected {advice:?} in the refusal: {err}"
1951 );
1952 }
1953 }
1954
1955 #[tokio::test]
1958 async fn the_published_schema_is_the_events_table() {
1959 let columns = events_schema().expect("schema");
1960 let names: Vec<&str> = columns.iter().map(|c| c.name.as_str()).collect();
1961 assert_eq!(
1962 names,
1963 [
1964 "position",
1965 "stream",
1966 "seq",
1967 "epoch_ms",
1968 "kind",
1969 "schema_version",
1970 "meta",
1971 "data"
1972 ]
1973 );
1974
1975 let pk: Vec<&str> = columns
1976 .iter()
1977 .filter(|c| c.pk)
1978 .map(|c| c.name.as_str())
1979 .collect();
1980 assert_eq!(pk, ["position"], "the log is keyed by its global order");
1981
1982 let (store, _logs) = mem_store().await;
1984 let sql = format!("SELECT {} FROM {EVENTS_TABLE}", names.join(", "));
1985 ask(&store, &sql)
1986 .await
1987 .expect("the published columns are the real ones");
1988 }
1989
1990 #[tokio::test]
2001 async fn the_published_schema_is_the_live_one() {
2002 let dir = tempfile::tempdir().expect("tempdir");
2003 let path = dir.path().join("knl.db");
2004 let logs = Logs::new();
2005 let log = logs.file(&path).await.expect("open");
2006
2007 let rows = log
2008 .query(&format!("PRAGMA table_info({EVENTS_TABLE})"), vec![])
2009 .await
2010 .expect("table_info");
2011 let live: Vec<SchemaColumn> = rows
2012 .iter()
2013 .map(|row| SchemaColumn {
2014 name: row["name"].as_str().expect("a column name").to_string(),
2015 declared_type: row["type"].as_str().expect("a declared type").to_string(),
2016 pk: row["pk"].as_i64().expect("a pk flag") > 0,
2017 })
2018 .collect();
2019
2020 assert_eq!(live, events_schema().expect("published schema"));
2021 drop(log);
2022 assert!(logs.shutdown().await.is_empty(), "the log closed cleanly");
2023 }
2024}