sqlite.rs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. #![allow(missing_docs)]
  2. #![cfg(feature = "sqlite")]
  3. use kuatia_storage::store::{AccountStore, PostingStore};
  4. use kuatia_storage_sql::SqlStore;
  5. use kuatia_types::*;
  6. use sqlx::{Any, Pool, Row};
  7. async fn new_pool() -> Pool<Any> {
  8. sqlx::any::install_default_drivers();
  9. sqlx::any::AnyPoolOptions::new()
  10. .max_connections(1)
  11. .connect("sqlite::memory:")
  12. .await
  13. .unwrap()
  14. }
  15. async fn new_store() -> SqlStore {
  16. let store = SqlStore::new(new_pool().await);
  17. store.migrate().await.unwrap();
  18. store
  19. }
  20. kuatia_storage::store_tests!(new_store);
  21. /// The point of the schema: no column holds opaque binary. A content-addressed
  22. /// id is stored as lower-case hex text, and a structured payload as readable
  23. /// JSON text, so a row can be audited in a plain SQL client.
  24. #[tokio::test]
  25. async fn columns_store_hex_ids_and_json_text() {
  26. let pool = new_pool().await;
  27. let store = SqlStore::new(pool.clone());
  28. store.migrate().await.unwrap();
  29. let account = Account {
  30. id: AccountId::new(1),
  31. version: 1,
  32. policy: AccountPolicy::NoOverdraft,
  33. flags: AccountFlags::empty(),
  34. book: BookId(0),
  35. user_data: UserData::default(),
  36. metadata: std::collections::BTreeMap::new(),
  37. };
  38. store.create_account(account).await.unwrap();
  39. let tid = EnvelopeId([0xab; 32]);
  40. let posting = Posting::new(
  41. PostingId {
  42. transfer: tid,
  43. index: 0,
  44. },
  45. AccountId::new(1),
  46. AssetId::new(1),
  47. Cent::from(100),
  48. );
  49. store.insert_postings(&[posting]).await.unwrap();
  50. // The 32-byte transfer id is stored as its 64-char lower-case hex form.
  51. let row = sqlx::query("SELECT transfer_id FROM postings")
  52. .fetch_one(&pool)
  53. .await
  54. .unwrap();
  55. let transfer_id: String = row.try_get("transfer_id").unwrap();
  56. assert_eq!(transfer_id, "ab".repeat(32));
  57. // The account payload is readable JSON text, not a blob.
  58. let row = sqlx::query("SELECT user_data FROM accounts")
  59. .fetch_one(&pool)
  60. .await
  61. .unwrap();
  62. let user_data: String = row.try_get("user_data").unwrap();
  63. assert!(
  64. serde_json::from_str::<serde_json::Value>(&user_data).is_ok(),
  65. "user_data should be JSON text, got: {user_data}"
  66. );
  67. }
  68. /// migrate() is idempotent: running it repeatedly on the same DB is a no-op.
  69. #[tokio::test]
  70. async fn migrate_is_idempotent() {
  71. sqlx::any::install_default_drivers();
  72. let pool = sqlx::any::AnyPoolOptions::new()
  73. .max_connections(1)
  74. .connect("sqlite::memory:")
  75. .await
  76. .unwrap();
  77. let store = SqlStore::new(pool);
  78. store.migrate().await.unwrap();
  79. store.migrate().await.unwrap();
  80. store.migrate().await.unwrap();
  81. }