lib.rs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. //! SQL-backed Store implementation for SQLite and PostgreSQL.
  2. //!
  3. //! Uses `sqlx::Any` for database-agnostic queries. Enable features
  4. //! `sqlite` or `postgres` to select the backend.
  5. //!
  6. //! ```text
  7. //! let pool = sqlx::any::Pool<Any>Options::new()
  8. //! .connect("sqlite::memory:").await?;
  9. //! let store = SqlStore::new(pool);
  10. //! store.migrate().await?;
  11. //! ```
  12. use std::collections::HashSet;
  13. use async_trait::async_trait;
  14. use sqlx::{Any, Pool, Row};
  15. use kuatia_storage::error::StoreError;
  16. use kuatia_storage::events::{EventStore, LedgerEvent};
  17. use kuatia_storage::store::*;
  18. use kuatia_types::*;
  19. /// SQL-backed [`Store`] implementation.
  20. pub struct SqlStore {
  21. pool: Pool<Any>,
  22. autoid: kuatia_types::autoid::AutoId,
  23. }
  24. impl SqlStore {
  25. /// Create a new SQL store wrapping an existing connection pool.
  26. pub fn new(pool: Pool<Any>) -> Self {
  27. Self {
  28. pool,
  29. autoid: kuatia_types::autoid::AutoId::new(),
  30. }
  31. }
  32. /// Run database migrations.
  33. pub async fn migrate(&self) -> Result<(), StoreError> {
  34. for sql in [
  35. include_str!("migrations/001_init.sql"),
  36. include_str!("migrations/002_timestamps_and_columns.sql"),
  37. include_str!("migrations/003_events.sql"),
  38. include_str!("migrations/004_books.sql"),
  39. ] {
  40. for statement in sql.split(';') {
  41. let trimmed = statement.trim();
  42. if !trimmed.is_empty() {
  43. sqlx::query(trimmed)
  44. .execute(&self.pool)
  45. .await
  46. .map_err(|e| StoreError::Internal(e.to_string()))?;
  47. }
  48. }
  49. }
  50. Ok(())
  51. }
  52. }
  53. // ---------------------------------------------------------------------------
  54. // Serialization helpers
  55. // ---------------------------------------------------------------------------
  56. fn serialize_policy(policy: &AccountPolicy) -> Result<String, StoreError> {
  57. serde_json::to_string(policy)
  58. .map_err(|e| StoreError::Internal(format!("policy serialization: {e}")))
  59. }
  60. fn deserialize_policy(s: &str) -> Result<AccountPolicy, StoreError> {
  61. serde_json::from_str(s).map_err(|e| StoreError::Internal(format!("bad policy: {e}")))
  62. }
  63. fn serialize_blob<T: serde::Serialize>(val: &T) -> Result<Vec<u8>, StoreError> {
  64. serde_json::to_vec(val).map_err(|e| StoreError::Internal(format!("blob serialization: {e}")))
  65. }
  66. fn deserialize_blob<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, StoreError> {
  67. serde_json::from_slice(bytes).map_err(|e| StoreError::Internal(format!("bad blob: {e}")))
  68. }
  69. fn status_to_i16(s: PostingStatus) -> i16 {
  70. match s {
  71. PostingStatus::Active => 0,
  72. PostingStatus::PendingInactive => 1,
  73. PostingStatus::Inactive => 2,
  74. }
  75. }
  76. fn status_from_i16(v: i16) -> Result<PostingStatus, StoreError> {
  77. match v {
  78. 0 => Ok(PostingStatus::Active),
  79. 1 => Ok(PostingStatus::PendingInactive),
  80. 2 => Ok(PostingStatus::Inactive),
  81. _ => Err(StoreError::Internal(format!("bad posting status: {v}"))),
  82. }
  83. }
  84. fn row_to_account(row: &sqlx::any::AnyRow) -> Result<Account, StoreError> {
  85. let id: i64 = row
  86. .try_get("id")
  87. .map_err(|e| StoreError::Internal(e.to_string()))?;
  88. let version: i64 = row
  89. .try_get("version")
  90. .map_err(|e| StoreError::Internal(e.to_string()))?;
  91. let policy_str: String = row
  92. .try_get("policy")
  93. .map_err(|e| StoreError::Internal(e.to_string()))?;
  94. let flags_bits: i32 = row
  95. .try_get("flags")
  96. .map_err(|e| StoreError::Internal(e.to_string()))?;
  97. let book: i64 = row
  98. .try_get("book")
  99. .map_err(|e| StoreError::Internal(e.to_string()))?;
  100. let user_data_bytes: Vec<u8> = row
  101. .try_get("user_data")
  102. .map_err(|e| StoreError::Internal(e.to_string()))?;
  103. let metadata_bytes: Vec<u8> = row
  104. .try_get("metadata")
  105. .map_err(|e| StoreError::Internal(e.to_string()))?;
  106. Ok(Account {
  107. id: AccountId::new(id),
  108. version: version as u64,
  109. policy: deserialize_policy(&policy_str)?,
  110. flags: AccountFlags::from_bits_truncate(flags_bits as u32),
  111. book: BookId::new(book),
  112. user_data: deserialize_blob(&user_data_bytes)?,
  113. metadata: deserialize_blob(&metadata_bytes)?,
  114. })
  115. }
  116. fn row_to_posting(row: &sqlx::any::AnyRow) -> Result<Posting, StoreError> {
  117. let transfer_id: Vec<u8> = row
  118. .try_get("transfer_id")
  119. .map_err(|e| StoreError::Internal(e.to_string()))?;
  120. let idx: i16 = row
  121. .try_get("idx")
  122. .map_err(|e| StoreError::Internal(e.to_string()))?;
  123. let owner: i64 = row
  124. .try_get("owner")
  125. .map_err(|e| StoreError::Internal(e.to_string()))?;
  126. let asset: i32 = row
  127. .try_get("asset")
  128. .map_err(|e| StoreError::Internal(e.to_string()))?;
  129. let value: i64 = row
  130. .try_get("value")
  131. .map_err(|e| StoreError::Internal(e.to_string()))?;
  132. let status: i16 = row
  133. .try_get("status")
  134. .map_err(|e| StoreError::Internal(e.to_string()))?;
  135. let mut tid = [0u8; 32];
  136. tid.copy_from_slice(&transfer_id);
  137. Ok(Posting {
  138. id: PostingId {
  139. transfer: EnvelopeId(tid),
  140. index: idx as u16,
  141. },
  142. owner: AccountId::new(owner),
  143. asset: AssetId::new(asset as u32),
  144. value: Cent::from(value),
  145. status: status_from_i16(status)?,
  146. })
  147. }
  148. // ---------------------------------------------------------------------------
  149. // AccountStore
  150. // ---------------------------------------------------------------------------
  151. #[async_trait]
  152. impl AccountStore for SqlStore {
  153. async fn get_account(&self, id: &AccountId) -> Result<Account, StoreError> {
  154. let row = sqlx::query("SELECT * FROM accounts WHERE id = $1 ORDER BY version DESC LIMIT 1")
  155. .bind(id.0)
  156. .fetch_optional(&self.pool)
  157. .await
  158. .map_err(|e| StoreError::Internal(e.to_string()))?
  159. .ok_or_else(|| StoreError::NotFound(format!("account {id:?}")))?;
  160. row_to_account(&row)
  161. }
  162. async fn get_accounts(&self, ids: &[AccountId]) -> Result<Vec<Account>, StoreError> {
  163. let mut result = Vec::with_capacity(ids.len());
  164. for id in ids {
  165. result.push(self.get_account(id).await?);
  166. }
  167. Ok(result)
  168. }
  169. async fn create_account(&self, account: Account) -> Result<(), StoreError> {
  170. let exists = sqlx::query("SELECT 1 FROM accounts WHERE id = $1 LIMIT 1")
  171. .bind(account.id.0)
  172. .fetch_optional(&self.pool)
  173. .await
  174. .map_err(|e| StoreError::Internal(e.to_string()))?;
  175. if exists.is_some() {
  176. return Err(StoreError::AlreadyExists(format!(
  177. "account {:?}",
  178. account.id
  179. )));
  180. }
  181. sqlx::query(
  182. "INSERT INTO accounts (id, version, policy, flags, book, user_data, metadata) VALUES ($1, $2, $3, $4, $5, $6, $7)"
  183. )
  184. .bind(account.id.0)
  185. .bind(account.version as i64)
  186. .bind(serialize_policy(&account.policy)?)
  187. .bind(account.flags.bits() as i32)
  188. .bind(account.book.0)
  189. .bind(serialize_blob(&account.user_data)?)
  190. .bind(serialize_blob(&account.metadata)?)
  191. .execute(&self.pool)
  192. .await
  193. .map_err(|e| StoreError::Internal(e.to_string()))?;
  194. Ok(())
  195. }
  196. async fn append_account_version(&self, account: Account) -> Result<(), StoreError> {
  197. let current =
  198. sqlx::query("SELECT version FROM accounts WHERE id = $1 ORDER BY version DESC LIMIT 1")
  199. .bind(account.id.0)
  200. .fetch_optional(&self.pool)
  201. .await
  202. .map_err(|e| StoreError::Internal(e.to_string()))?
  203. .ok_or_else(|| StoreError::NotFound(format!("account {:?}", account.id)))?;
  204. let current_version: i64 = current
  205. .try_get("version")
  206. .map_err(|e| StoreError::Internal(e.to_string()))?;
  207. let expected = current_version
  208. .checked_add(1)
  209. .ok_or_else(|| StoreError::Internal("account version overflow".to_string()))?;
  210. if account.version as i64 != expected {
  211. return Err(StoreError::VersionConflict {
  212. account: account.id,
  213. expected: expected as u64,
  214. actual: account.version,
  215. });
  216. }
  217. sqlx::query(
  218. "INSERT INTO accounts (id, version, policy, flags, book, user_data, metadata) VALUES ($1, $2, $3, $4, $5, $6, $7)"
  219. )
  220. .bind(account.id.0)
  221. .bind(account.version as i64)
  222. .bind(serialize_policy(&account.policy)?)
  223. .bind(account.flags.bits() as i32)
  224. .bind(account.book.0)
  225. .bind(serialize_blob(&account.user_data)?)
  226. .bind(serialize_blob(&account.metadata)?)
  227. .execute(&self.pool)
  228. .await
  229. .map_err(|e| StoreError::Internal(e.to_string()))?;
  230. Ok(())
  231. }
  232. async fn get_account_history(&self, id: &AccountId) -> Result<Vec<Account>, StoreError> {
  233. let rows = sqlx::query("SELECT * FROM accounts WHERE id = $1 ORDER BY version ASC")
  234. .bind(id.0)
  235. .fetch_all(&self.pool)
  236. .await
  237. .map_err(|e| StoreError::Internal(e.to_string()))?;
  238. if rows.is_empty() {
  239. return Err(StoreError::NotFound(format!("account {id:?}")));
  240. }
  241. rows.iter().map(row_to_account).collect()
  242. }
  243. async fn list_accounts(&self) -> Result<Vec<Account>, StoreError> {
  244. let rows = sqlx::query("SELECT * FROM accounts ORDER BY id, version DESC")
  245. .fetch_all(&self.pool)
  246. .await
  247. .map_err(|e| StoreError::Internal(e.to_string()))?;
  248. let mut accounts: Vec<Account> = rows.iter().map(row_to_account).collect::<Result<_, _>>()?;
  249. accounts.dedup_by_key(|a| a.id);
  250. Ok(accounts)
  251. }
  252. }
  253. // ---------------------------------------------------------------------------
  254. // PostingStore
  255. // ---------------------------------------------------------------------------
  256. #[async_trait]
  257. impl PostingStore for SqlStore {
  258. async fn get_postings(&self, ids: &[PostingId]) -> Result<Vec<Posting>, StoreError> {
  259. let mut result = Vec::with_capacity(ids.len());
  260. for id in ids {
  261. let row = sqlx::query("SELECT * FROM postings WHERE transfer_id = $1 AND idx = $2")
  262. .bind(id.transfer.0.as_slice())
  263. .bind(id.index as i16)
  264. .fetch_optional(&self.pool)
  265. .await
  266. .map_err(|e| StoreError::Internal(e.to_string()))?
  267. .ok_or_else(|| StoreError::NotFound(format!("posting {id:?}")))?;
  268. result.push(row_to_posting(&row)?);
  269. }
  270. Ok(result)
  271. }
  272. async fn get_postings_by_account(
  273. &self,
  274. account: &AccountId,
  275. asset: Option<&AssetId>,
  276. status: Option<PostingStatus>,
  277. ) -> Result<Vec<Posting>, StoreError> {
  278. let rows = match (asset, status) {
  279. (Some(a), Some(s)) => {
  280. sqlx::query(
  281. "SELECT * FROM postings WHERE owner = $1 AND asset = $2 AND status = $3",
  282. )
  283. .bind(account.0)
  284. .bind(a.0 as i32)
  285. .bind(status_to_i16(s))
  286. .fetch_all(&self.pool)
  287. .await
  288. }
  289. (Some(a), None) => {
  290. sqlx::query("SELECT * FROM postings WHERE owner = $1 AND asset = $2")
  291. .bind(account.0)
  292. .bind(a.0 as i32)
  293. .fetch_all(&self.pool)
  294. .await
  295. }
  296. (None, Some(s)) => {
  297. sqlx::query("SELECT * FROM postings WHERE owner = $1 AND status = $2")
  298. .bind(account.0)
  299. .bind(status_to_i16(s))
  300. .fetch_all(&self.pool)
  301. .await
  302. }
  303. (None, None) => {
  304. sqlx::query("SELECT * FROM postings WHERE owner = $1")
  305. .bind(account.0)
  306. .fetch_all(&self.pool)
  307. .await
  308. }
  309. }
  310. .map_err(|e| StoreError::Internal(e.to_string()))?;
  311. rows.iter().map(row_to_posting).collect()
  312. }
  313. async fn query_postings(&self, query: &PostingQuery) -> Result<Page<Posting>, StoreError> {
  314. let (where_clause, count_clause) = {
  315. let mut w = String::from("WHERE owner = $1");
  316. let mut idx = 2u32;
  317. if query.asset.is_some() {
  318. w.push_str(&format!(" AND asset = ${idx}"));
  319. idx += 1;
  320. }
  321. if query.status.is_some() {
  322. w.push_str(&format!(" AND status = ${idx}"));
  323. }
  324. let c = format!("SELECT COUNT(*) as cnt FROM postings {w}");
  325. let limit = query.limit.unwrap_or(u32::MAX);
  326. let offset = query.offset.unwrap_or(0);
  327. w.push_str(&format!(" LIMIT {limit} OFFSET {offset}"));
  328. (format!("SELECT * FROM postings {w}"), c)
  329. };
  330. // Build count query
  331. let mut count_q = sqlx::query(&count_clause).bind(query.account.0);
  332. if let Some(ref a) = query.asset {
  333. count_q = count_q.bind(a.0 as i32);
  334. }
  335. if let Some(s) = query.status {
  336. count_q = count_q.bind(status_to_i16(s));
  337. }
  338. let count_row = count_q
  339. .fetch_one(&self.pool)
  340. .await
  341. .map_err(|e| StoreError::Internal(e.to_string()))?;
  342. let total: i64 = count_row
  343. .try_get("cnt")
  344. .map_err(|e| StoreError::Internal(e.to_string()))?;
  345. // Build data query
  346. let mut data_q = sqlx::query(&where_clause).bind(query.account.0);
  347. if let Some(ref a) = query.asset {
  348. data_q = data_q.bind(a.0 as i32);
  349. }
  350. if let Some(s) = query.status {
  351. data_q = data_q.bind(status_to_i16(s));
  352. }
  353. let rows = data_q
  354. .fetch_all(&self.pool)
  355. .await
  356. .map_err(|e| StoreError::Internal(e.to_string()))?;
  357. let items: Vec<Posting> = rows.iter().map(row_to_posting).collect::<Result<_, _>>()?;
  358. Ok(Page {
  359. items,
  360. total: total as u64,
  361. })
  362. }
  363. async fn reserve_postings(&self, ids: &[PostingId]) -> Result<(), StoreError> {
  364. // Validate all Active first, then update in a transaction.
  365. let mut tx = self
  366. .pool
  367. .begin()
  368. .await
  369. .map_err(|e| StoreError::Internal(e.to_string()))?;
  370. for id in ids {
  371. let row =
  372. sqlx::query("SELECT status FROM postings WHERE transfer_id = $1 AND idx = $2")
  373. .bind(id.transfer.0.as_slice())
  374. .bind(id.index as i16)
  375. .fetch_optional(&mut *tx)
  376. .await
  377. .map_err(|e| StoreError::Internal(e.to_string()))?
  378. .ok_or_else(|| StoreError::NotFound(format!("posting {id:?}")))?;
  379. let status: i16 = row
  380. .try_get("status")
  381. .map_err(|e| StoreError::Internal(e.to_string()))?;
  382. if status != 0 {
  383. return Err(StoreError::PostingNotActive(*id));
  384. }
  385. }
  386. for id in ids {
  387. sqlx::query("UPDATE postings SET status = $1 WHERE transfer_id = $2 AND idx = $3")
  388. .bind(status_to_i16(PostingStatus::PendingInactive))
  389. .bind(id.transfer.0.as_slice())
  390. .bind(id.index as i16)
  391. .execute(&mut *tx)
  392. .await
  393. .map_err(|e| StoreError::Internal(e.to_string()))?;
  394. }
  395. tx.commit()
  396. .await
  397. .map_err(|e| StoreError::Internal(e.to_string()))?;
  398. Ok(())
  399. }
  400. async fn release_postings(&self, ids: &[PostingId]) -> Result<(), StoreError> {
  401. let mut tx = self
  402. .pool
  403. .begin()
  404. .await
  405. .map_err(|e| StoreError::Internal(e.to_string()))?;
  406. for id in ids {
  407. let row =
  408. sqlx::query("SELECT status FROM postings WHERE transfer_id = $1 AND idx = $2")
  409. .bind(id.transfer.0.as_slice())
  410. .bind(id.index as i16)
  411. .fetch_optional(&mut *tx)
  412. .await
  413. .map_err(|e| StoreError::Internal(e.to_string()))?
  414. .ok_or_else(|| StoreError::NotFound(format!("posting {id:?}")))?;
  415. let status: i16 = row
  416. .try_get("status")
  417. .map_err(|e| StoreError::Internal(e.to_string()))?;
  418. if status == 2 {
  419. return Err(StoreError::PostingInactive(*id));
  420. }
  421. }
  422. for id in ids {
  423. sqlx::query("UPDATE postings SET status = $1 WHERE transfer_id = $2 AND idx = $3 AND status = $4")
  424. .bind(status_to_i16(PostingStatus::Active))
  425. .bind(id.transfer.0.as_slice())
  426. .bind(id.index as i16)
  427. .bind(status_to_i16(PostingStatus::PendingInactive))
  428. .execute(&mut *tx)
  429. .await
  430. .map_err(|e| StoreError::Internal(e.to_string()))?;
  431. }
  432. tx.commit()
  433. .await
  434. .map_err(|e| StoreError::Internal(e.to_string()))?;
  435. Ok(())
  436. }
  437. async fn finalize_postings(
  438. &self,
  439. deactivate: &[PostingId],
  440. create: &[Posting],
  441. ) -> Result<(), StoreError> {
  442. let mut tx = self
  443. .pool
  444. .begin()
  445. .await
  446. .map_err(|e| StoreError::Internal(e.to_string()))?;
  447. for id in deactivate {
  448. sqlx::query("UPDATE postings SET status = $1 WHERE transfer_id = $2 AND idx = $3")
  449. .bind(status_to_i16(PostingStatus::Inactive))
  450. .bind(id.transfer.0.as_slice())
  451. .bind(id.index as i16)
  452. .execute(&mut *tx)
  453. .await
  454. .map_err(|e| StoreError::Internal(e.to_string()))?;
  455. }
  456. for posting in create {
  457. sqlx::query(
  458. "INSERT INTO postings (transfer_id, idx, owner, asset, value, status) VALUES ($1, $2, $3, $4, $5, $6)"
  459. )
  460. .bind(posting.id.transfer.0.as_slice())
  461. .bind(posting.id.index as i16)
  462. .bind(posting.owner.0)
  463. .bind(posting.asset.0 as i32)
  464. .bind(posting.value.value())
  465. .bind(status_to_i16(posting.status))
  466. .execute(&mut *tx)
  467. .await
  468. .map_err(|e| StoreError::Internal(e.to_string()))?;
  469. }
  470. tx.commit()
  471. .await
  472. .map_err(|e| StoreError::Internal(e.to_string()))?;
  473. Ok(())
  474. }
  475. }
  476. // ---------------------------------------------------------------------------
  477. // TransferStore
  478. // ---------------------------------------------------------------------------
  479. #[async_trait]
  480. impl TransferStore for SqlStore {
  481. async fn get_transfer(&self, id: &EnvelopeId) -> Result<Option<EnvelopeRecord>, StoreError> {
  482. let row = sqlx::query("SELECT transfer, receipt, created_at FROM transfers WHERE id = $1")
  483. .bind(id.0.as_slice())
  484. .fetch_optional(&self.pool)
  485. .await
  486. .map_err(|e| StoreError::Internal(e.to_string()))?;
  487. match row {
  488. None => Ok(None),
  489. Some(row) => {
  490. let transfer_bytes: Vec<u8> = row
  491. .try_get("transfer")
  492. .map_err(|e| StoreError::Internal(e.to_string()))?;
  493. let receipt_bytes: Vec<u8> = row
  494. .try_get("receipt")
  495. .map_err(|e| StoreError::Internal(e.to_string()))?;
  496. let created_at: i64 = row
  497. .try_get("created_at")
  498. .map_err(|e| StoreError::Internal(e.to_string()))?;
  499. Ok(Some(EnvelopeRecord {
  500. envelope: deserialize_blob(&transfer_bytes)?,
  501. receipt: deserialize_blob(&receipt_bytes)?,
  502. created_at,
  503. }))
  504. }
  505. }
  506. }
  507. async fn store_transfer(&self, record: EnvelopeRecord) -> Result<(), StoreError> {
  508. let tid = record.receipt.transfer_id;
  509. let transfer_bytes = serialize_blob(&record.envelope)?;
  510. let receipt_bytes = serialize_blob(&record.receipt)?;
  511. let mut tx = self
  512. .pool
  513. .begin()
  514. .await
  515. .map_err(|e| StoreError::Internal(e.to_string()))?;
  516. sqlx::query("INSERT INTO transfers (id, transfer, receipt, created_at, book) VALUES ($1, $2, $3, $4, $5)")
  517. .bind(tid.0.as_slice())
  518. .bind(&transfer_bytes)
  519. .bind(&receipt_bytes)
  520. .bind(record.created_at)
  521. .bind(record.envelope.book().0)
  522. .execute(&mut *tx)
  523. .await
  524. .map_err(|e| StoreError::Internal(e.to_string()))?;
  525. // Populate transfer_accounts join table
  526. let mut account_ids: HashSet<i64> = HashSet::new();
  527. for np in record.envelope.creates() {
  528. account_ids.insert(np.owner.0);
  529. }
  530. for aid in &account_ids {
  531. sqlx::query("INSERT INTO transfer_accounts (transfer_id, account_id) VALUES ($1, $2)")
  532. .bind(tid.0.as_slice())
  533. .bind(*aid)
  534. .execute(&mut *tx)
  535. .await
  536. .map_err(|e| StoreError::Internal(e.to_string()))?;
  537. }
  538. tx.commit()
  539. .await
  540. .map_err(|e| StoreError::Internal(e.to_string()))?;
  541. Ok(())
  542. }
  543. async fn get_transfers_for_account(
  544. &self,
  545. account: &AccountId,
  546. ) -> Result<Vec<EnvelopeRecord>, StoreError> {
  547. let rows = sqlx::query(
  548. "SELECT t.id, t.transfer, t.receipt, t.created_at FROM transfers t INNER JOIN transfer_accounts ta ON t.id = ta.transfer_id WHERE ta.account_id = $1 ORDER BY t.created_at"
  549. )
  550. .bind(account.0)
  551. .fetch_all(&self.pool)
  552. .await
  553. .map_err(|e| StoreError::Internal(e.to_string()))?;
  554. let mut result = Vec::with_capacity(rows.len());
  555. for row in &rows {
  556. let transfer_bytes: Vec<u8> = row
  557. .try_get("transfer")
  558. .map_err(|e| StoreError::Internal(e.to_string()))?;
  559. let receipt_bytes: Vec<u8> = row
  560. .try_get("receipt")
  561. .map_err(|e| StoreError::Internal(e.to_string()))?;
  562. let created_at: i64 = row
  563. .try_get("created_at")
  564. .map_err(|e| StoreError::Internal(e.to_string()))?;
  565. result.push(EnvelopeRecord {
  566. envelope: deserialize_blob(&transfer_bytes)?,
  567. receipt: deserialize_blob(&receipt_bytes)?,
  568. created_at,
  569. });
  570. }
  571. Ok(result)
  572. }
  573. async fn query_transfers(
  574. &self,
  575. query: &TransferQuery,
  576. ) -> Result<Page<EnvelopeRecord>, StoreError> {
  577. // Load base records, using the account join when available.
  578. let base_records = if let Some(ref account) = query.account {
  579. self.get_transfers_for_account(account).await?
  580. } else {
  581. let rows = sqlx::query(
  582. "SELECT transfer, receipt, created_at FROM transfers ORDER BY created_at",
  583. )
  584. .fetch_all(&self.pool)
  585. .await
  586. .map_err(|e| StoreError::Internal(e.to_string()))?;
  587. let mut records = Vec::with_capacity(rows.len());
  588. for row in &rows {
  589. let transfer_bytes: Vec<u8> = row
  590. .try_get("transfer")
  591. .map_err(|e| StoreError::Internal(e.to_string()))?;
  592. let receipt_bytes: Vec<u8> = row
  593. .try_get("receipt")
  594. .map_err(|e| StoreError::Internal(e.to_string()))?;
  595. let created_at: i64 = row
  596. .try_get("created_at")
  597. .map_err(|e| StoreError::Internal(e.to_string()))?;
  598. records.push(EnvelopeRecord {
  599. envelope: deserialize_blob(&transfer_bytes)?,
  600. receipt: deserialize_blob(&receipt_bytes)?,
  601. created_at,
  602. });
  603. }
  604. records
  605. };
  606. // Filter in memory for remaining conditions.
  607. let filtered: Vec<EnvelopeRecord> = base_records
  608. .into_iter()
  609. .filter(|r| {
  610. if let Some(from) = query.from_ts
  611. && r.created_at < from
  612. {
  613. return false;
  614. }
  615. if let Some(to) = query.to_ts
  616. && r.created_at >= to
  617. {
  618. return false;
  619. }
  620. if let Some(book) = query.book
  621. && r.envelope.book() != book
  622. {
  623. return false;
  624. }
  625. true
  626. })
  627. .collect();
  628. let total = filtered.len() as u64;
  629. let offset = query.offset.unwrap_or(0) as usize;
  630. let limit = query.limit.unwrap_or(u32::MAX) as usize;
  631. let items = filtered.into_iter().skip(offset).take(limit).collect();
  632. Ok(Page { items, total })
  633. }
  634. }
  635. // ---------------------------------------------------------------------------
  636. // SagaStore
  637. // ---------------------------------------------------------------------------
  638. #[async_trait]
  639. impl SagaStore for SqlStore {
  640. async fn save_saga(&self, id: &i64, data: Vec<u8>) -> Result<(), StoreError> {
  641. sqlx::query("INSERT OR REPLACE INTO sagas (id, data) VALUES ($1, $2)")
  642. .bind(*id)
  643. .bind(&data)
  644. .execute(&self.pool)
  645. .await
  646. .map_err(|e| StoreError::Internal(e.to_string()))?;
  647. Ok(())
  648. }
  649. async fn list_pending_sagas(&self) -> Result<Vec<(i64, Vec<u8>)>, StoreError> {
  650. let rows = sqlx::query("SELECT id, data FROM sagas")
  651. .fetch_all(&self.pool)
  652. .await
  653. .map_err(|e| StoreError::Internal(e.to_string()))?;
  654. let mut result = Vec::with_capacity(rows.len());
  655. for row in &rows {
  656. let id: i64 = row
  657. .try_get("id")
  658. .map_err(|e| StoreError::Internal(e.to_string()))?;
  659. let data: Vec<u8> = row
  660. .try_get("data")
  661. .map_err(|e| StoreError::Internal(e.to_string()))?;
  662. result.push((id, data));
  663. }
  664. Ok(result)
  665. }
  666. async fn delete_saga(&self, id: &i64) -> Result<(), StoreError> {
  667. sqlx::query("DELETE FROM sagas WHERE id = $1")
  668. .bind(*id)
  669. .execute(&self.pool)
  670. .await
  671. .map_err(|e| StoreError::Internal(e.to_string()))?;
  672. Ok(())
  673. }
  674. }
  675. // ---------------------------------------------------------------------------
  676. // EventStore
  677. // ---------------------------------------------------------------------------
  678. #[async_trait]
  679. impl EventStore for SqlStore {
  680. async fn append_event(&self, event: &LedgerEvent) -> Result<u64, StoreError> {
  681. let kind_str =
  682. serde_json::to_string(&event.kind).map_err(|e| StoreError::Internal(e.to_string()))?;
  683. let data = serialize_blob(event)?;
  684. let seq = self.autoid.next() as u64;
  685. sqlx::query("INSERT INTO events (seq, timestamp, kind, data) VALUES ($1, $2, $3, $4)")
  686. .bind(seq as i64)
  687. .bind(event.timestamp)
  688. .bind(&kind_str)
  689. .bind(&data)
  690. .execute(&self.pool)
  691. .await
  692. .map_err(|e| StoreError::Internal(e.to_string()))?;
  693. Ok(seq)
  694. }
  695. async fn get_events_since(
  696. &self,
  697. after_seq: u64,
  698. limit: u32,
  699. ) -> Result<Vec<LedgerEvent>, StoreError> {
  700. let rows = sqlx::query("SELECT seq, data FROM events WHERE seq > $1 ORDER BY seq LIMIT $2")
  701. .bind(after_seq as i64)
  702. .bind(limit as i32)
  703. .fetch_all(&self.pool)
  704. .await
  705. .map_err(|e| StoreError::Internal(e.to_string()))?;
  706. let mut events = Vec::with_capacity(rows.len());
  707. for row in &rows {
  708. let seq: i64 = row
  709. .try_get("seq")
  710. .map_err(|e| StoreError::Internal(e.to_string()))?;
  711. let data: Vec<u8> = row
  712. .try_get("data")
  713. .map_err(|e| StoreError::Internal(e.to_string()))?;
  714. let mut event: LedgerEvent = deserialize_blob(&data)?;
  715. event.seq = seq as u64;
  716. events.push(event);
  717. }
  718. Ok(events)
  719. }
  720. }
  721. // ---------------------------------------------------------------------------
  722. // BookStore
  723. // ---------------------------------------------------------------------------
  724. #[async_trait]
  725. impl BookStore for SqlStore {
  726. async fn create_book(&self, book: Book) -> Result<(), StoreError> {
  727. let exists = sqlx::query("SELECT 1 FROM books WHERE id = $1 LIMIT 1")
  728. .bind(book.id.0)
  729. .fetch_optional(&self.pool)
  730. .await
  731. .map_err(|e| StoreError::Internal(e.to_string()))?;
  732. if exists.is_some() {
  733. return Err(StoreError::AlreadyExists(format!("book {:?}", book.id)));
  734. }
  735. let data = serialize_blob(&book)?;
  736. sqlx::query("INSERT INTO books (id, name, data) VALUES ($1, $2, $3)")
  737. .bind(book.id.0)
  738. .bind(&book.name)
  739. .bind(&data)
  740. .execute(&self.pool)
  741. .await
  742. .map_err(|e| StoreError::Internal(e.to_string()))?;
  743. Ok(())
  744. }
  745. async fn get_book(&self, id: &BookId) -> Result<Book, StoreError> {
  746. let row = sqlx::query("SELECT data FROM books WHERE id = $1")
  747. .bind(id.0)
  748. .fetch_optional(&self.pool)
  749. .await
  750. .map_err(|e| StoreError::Internal(e.to_string()))?
  751. .ok_or_else(|| StoreError::NotFound(format!("book {id:?}")))?;
  752. let data: Vec<u8> = row
  753. .try_get("data")
  754. .map_err(|e| StoreError::Internal(e.to_string()))?;
  755. deserialize_blob(&data)
  756. }
  757. async fn list_books(&self) -> Result<Vec<Book>, StoreError> {
  758. let rows = sqlx::query("SELECT data FROM books")
  759. .fetch_all(&self.pool)
  760. .await
  761. .map_err(|e| StoreError::Internal(e.to_string()))?;
  762. rows.iter()
  763. .map(|row| {
  764. let data: Vec<u8> = row
  765. .try_get("data")
  766. .map_err(|e| StoreError::Internal(e.to_string()))?;
  767. deserialize_blob(&data)
  768. })
  769. .collect()
  770. }
  771. }