1use super::{
2 SqliteStore, lineage,
3 snapshot::{column, decode, load_cached_blocking, revision_from_sql},
4};
5use crate::{
6 HistoryEntry, HistoryPage, RecordedOperation, StoreError, StoredRecord, error::database_error,
7};
8use dpm_engine::Operation;
9use dpm_model::OperationId;
10use rusqlite::{Connection, OptionalExtension, params};
11use std::path::Path;
12
13impl SqliteStore {
14 pub fn history_blocking(
16 &self,
17 after_sequence: u64,
18 limit: u16,
19 ) -> Result<HistoryPage, StoreError> {
20 let transaction = self
21 .connection
22 .unchecked_transaction()
23 .map_err(database_error(&self.path, "begin history query"))?;
24 let plan = load_cached_blocking(&transaction, &self.path, &self.cache)?
27 .ok_or_else(|| StoreError::NotInitialized(self.path.clone()))?;
28 let lineage = lineage::read_blocking(&transaction, &self.path)?;
29 let mut entries = Vec::new();
30 if let Ok(cursor) = i64::try_from(after_sequence) {
31 let mut statement = transaction
32 .prepare(&format!(
33 "{OPERATION_COLUMNS} WHERE sequence > ?1 ORDER BY sequence LIMIT ?2"
34 ))
35 .map_err(database_error(&self.path, "prepare history query"))?;
36 let mut rows = statement
37 .query(params![cursor, limit.min(1000)])
38 .map_err(database_error(&self.path, "query history"))?;
39 while let Some(row) = rows
40 .next()
41 .map_err(database_error(&self.path, "read history"))?
42 {
43 entries.push(read_entry(row, &self.path)?);
44 }
45 }
46 transaction
47 .commit()
48 .map_err(database_error(&self.path, "finish history query"))?;
49 Ok(HistoryPage {
50 revision: plan.revision,
51 lineage_id: Some(lineage.lineage_id),
52 next_after_sequence: entries
53 .last()
54 .map_or(after_sequence, |entry| entry.sequence),
55 entries,
56 })
57 }
58
59 pub fn recorded_operation_blocking(
61 &self,
62 id: OperationId,
63 ) -> Result<Option<HistoryEntry>, StoreError> {
64 recorded_blocking(&self.connection, &self.path, id)
65 }
66}
67
68pub(super) fn recorded_blocking(
70 connection: &Connection,
71 path: &Path,
72 id: OperationId,
73) -> Result<Option<HistoryEntry>, StoreError> {
74 let mut statement = connection
75 .prepare(&format!("{OPERATION_COLUMNS} WHERE operation_id = ?1"))
76 .map_err(database_error(path, "prepare operation lookup"))?;
77 statement
78 .query_row(params![id.to_string()], |row| Ok(read_entry(row, path)))
79 .optional()
80 .map_err(database_error(path, "look up operation"))?
81 .transpose()
82}
83
84pub(crate) const OPERATION_COLUMNS: &str = "SELECT sequence, operation_id, base_revision, \
86 resulting_revision, actor_json, timestamp, command_json, workspace_id, lineage_id \
87 FROM operations";
88
89pub(crate) fn read_entry(row: &rusqlite::Row<'_>, path: &Path) -> Result<HistoryEntry, StoreError> {
91 let sequence: u64 = row
92 .get(0)
93 .map_err(database_error(path, "read history cursor"))?;
94 let record = StoredRecord::Operation { sequence };
95 let text = |index, field| column::<String>(row, index, path, record, field);
96 let revision =
97 |index, field| column::<i64>(row, index, path, record, field).map(revision_from_sql);
98 let scalar =
101 |index, field| text(index, field).map(|value| serde_json::Value::String(value).to_string());
102 Ok(HistoryEntry {
103 sequence,
104 operation: RecordedOperation {
105 operation: Operation {
106 id: decode(&scalar(1, "operation_id")?, path, record, "operation_id")?,
107 base_revision: revision(2, "base_revision")?,
108 resulting_revision: revision(3, "resulting_revision")?,
109 actor: decode(&text(4, "actor_json")?, path, record, "actor_json")?,
110 timestamp: decode(&scalar(5, "timestamp")?, path, record, "timestamp")?,
111 command: decode(&text(6, "command_json")?, path, record, "command_json")?,
112 },
113 workspace_id: decode(&scalar(7, "workspace_id")?, path, record, "workspace_id")?,
114 lineage_id: decode(&scalar(8, "lineage_id")?, path, record, "lineage_id")?,
115 },
116 })
117}
118
119#[cfg(test)]
120mod tests;