db_sync_sqlx/
lib.rs

1//! helpers and primitive types for querying Cardano [Db-Sync] data using [sqlx]
2//!
3//! # About
4//!
5//! This crate is meant to provide help when writing queries for data produced by [Db-Sync],
6//! a Cardano blockchain indexer. Db-Sync keeps the indexed data in a Postgre database that
7//! can be queried using SQL. This crate defines primitive types that correspond to column
8//! types used in the [Db-Sync schema], along with some other helpers and casts to types
9//! defined in [sidechain_domain].
10//!
11//! [sqlx]: https://github.com/launchbadge/sqlx
12//! [Db-Sync]: https://github.com/IntersectMBO/cardano-db-sync
13//! [Db-Sync schema]: https://github.com/IntersectMBO/cardano-db-sync/blob/master/doc/schema.md
14use num_traits::ToPrimitive;
15use sidechain_domain::*;
16use sqlx::database::Database;
17use sqlx::encode::IsNull;
18use sqlx::error::BoxDynError;
19use sqlx::postgres::PgTypeInfo;
20use sqlx::*;
21
22/// Macro to handle numeric types that are non-negative but are stored by Db-Sync using
23/// signed SQL types.
24///
25/// This macro generates an unsigned numeric domain type and sqlx trait implementation for
26/// decoding it from signed data coming from Db-Sync database. It expects that values will
27/// always have 0 as the most significant bit.
28///
29/// For example because [TxIndex] is in range of \[0, 2^15-1\], it will be [u16] in domain,
30/// but it requires encoding and decoding as i16.
31///
32/// See `txindex`, `word31` `and` word63 types in Db-Sync schema definition.
33#[macro_export]
34macro_rules! sqlx_implementations_for_wrapper {
35	($WRAPPED:ty, $DBTYPE:expr, $NAME:ty, $DOMAIN:ty) => {
36		impl sqlx::Type<Postgres> for $NAME {
37			fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
38				PgTypeInfo::with_name($DBTYPE)
39			}
40		}
41
42		impl<'r> Decode<'r, Postgres> for $NAME
43		where
44			$WRAPPED: Decode<'r, Postgres>,
45		{
46			fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
47				let decoded: $WRAPPED = <$WRAPPED as Decode<Postgres>>::decode(value)?;
48				Ok(Self(decoded.try_into()?))
49			}
50		}
51
52		#[cfg(test)]
53		impl From<$WRAPPED> for $NAME {
54			fn from(value: $WRAPPED) -> Self {
55				Self(value.try_into().expect("value from domain fits in type db type"))
56			}
57		}
58
59		impl<'q> Encode<'q, Postgres> for $NAME {
60			fn encode_by_ref(
61				&self,
62				buf: &mut <Postgres as Database>::ArgumentBuffer<'q>,
63			) -> std::result::Result<IsNull, Box<(dyn std::error::Error + Send + Sync + 'static)>>
64			{
65				buf.extend(&self.0.to_be_bytes());
66				Ok(IsNull::No)
67			}
68		}
69
70		impl From<$NAME> for $DOMAIN {
71			fn from(value: $NAME) -> Self {
72				Self(value.0)
73			}
74		}
75
76		impl From<$DOMAIN> for $NAME {
77			fn from(value: $DOMAIN) -> Self {
78				Self(value.0)
79			}
80		}
81	};
82}
83
84/// Cardano block number
85#[derive(Debug, Copy, Ord, PartialOrd, Clone, PartialEq, Eq)]
86pub struct BlockNumber(pub u32);
87sqlx_implementations_for_wrapper!(i32, "INT4", BlockNumber, McBlockNumber);
88
89/// Cardano epoch number
90#[derive(Debug, Copy, Clone, PartialEq)]
91pub struct EpochNumber(pub u32);
92sqlx_implementations_for_wrapper!(i32, "INT4", EpochNumber, McEpochNumber);
93
94/// Cardano slot number
95#[derive(Debug, Copy, Clone, PartialEq)]
96pub struct SlotNumber(pub u64);
97sqlx_implementations_for_wrapper!(i64, "INT8", SlotNumber, McSlotNumber);
98
99/// Index of a Cardano transaction output
100///
101/// This type corresponds to the following SQL type defined by Db-Sync.
102/// ```sql
103/// CREATE DOMAIN txindex AS smallint CONSTRAINT txindex_check CHECK ((VALUE >= 0));
104/// ```
105#[derive(Debug, Clone, PartialEq)]
106pub struct TxIndex(pub u16);
107sqlx_implementations_for_wrapper!(i16, "INT2", TxIndex, UtxoIndex);
108
109/// Index of a Cardano transaction with its block
110#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
111pub struct TxIndexInBlock(pub u32);
112sqlx_implementations_for_wrapper!(i32, "INT4", TxIndexInBlock, McTxIndexInBlock);
113
114/// Number of ADA expressed in Lovelace (1 million-th of ADA)
115///
116/// This type corresponds to the following SQL type defined by Db-Sync.
117/// ```sql
118/// CREATE DOMAIN int65type AS numeric (20, 0) CHECK (VALUE >= -18446744073709551615 AND VALUE <= 18446744073709551615);
119/// ```
120#[derive(Debug, Clone, PartialEq)]
121pub struct TxValue(pub i128);
122
123impl sqlx::Type<Postgres> for TxValue {
124	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
125		PgTypeInfo::with_name("NUMERIC")
126	}
127}
128
129impl<'r> Decode<'r, Postgres> for TxValue {
130	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
131		let decoded = <sqlx::types::BigDecimal as Decode<Postgres>>::decode(value)?;
132		let i = decoded.to_i128().ok_or("TxValue is always an integer".to_string())?;
133		Ok(Self(i))
134	}
135}
136
137/// Number of ADA delegated by a Cardano delegator to a single SPO, expressed in Lovelace (1 million-th of ADA)
138///
139/// This type corresponds to the following SQL type defined by Db-Sync.
140/// ```sql
141/// CREATE DOMAIN "lovelace" AS numeric(20,0) CONSTRAINT flyway_needs_this CHECK (VALUE >= 0::numeric AND VALUE <= '18446744073709551615'::numeric);
142/// ```
143#[derive(Debug, Clone, PartialEq)]
144pub struct StakeDelegation(pub u64);
145
146impl sqlx::Type<Postgres> for StakeDelegation {
147	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
148		PgTypeInfo::with_name("NUMERIC")
149	}
150}
151
152impl<'r> Decode<'r, Postgres> for StakeDelegation {
153	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
154		let decoded = <sqlx::types::BigDecimal as Decode<Postgres>>::decode(value)?;
155		let i = decoded.to_u64().ok_or("StakeDelegation is always a u64".to_string())?;
156		Ok(Self(i))
157	}
158}
159
160/// Cardano native asset name, typically UTF-8 encoding of a human-readable name
161#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
162pub struct AssetName(pub Vec<u8>);
163
164impl From<sidechain_domain::AssetName> for AssetName {
165	fn from(name: sidechain_domain::AssetName) -> Self {
166		Self(name.0.to_vec())
167	}
168}
169
170/// Cardano minting policy ID. This value is obtained by hashing the Plutus script of the policy.
171#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
172pub struct PolicyId(pub Vec<u8>);
173
174impl From<sidechain_domain::PolicyId> for PolicyId {
175	fn from(id: sidechain_domain::PolicyId) -> Self {
176		Self(id.0.to_vec())
177	}
178}
179
180/// Full identifier of a Cardano native asset
181#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
182pub struct Asset {
183	/// Minting policy ID of the asset
184	pub policy_id: PolicyId,
185	/// Asset name
186	pub asset_name: AssetName,
187}
188
189impl Asset {
190	/// Creates an Asset with empty asset_name.
191	pub fn new(policy_id: sidechain_domain::PolicyId) -> Self {
192		Self { policy_id: PolicyId(policy_id.0.to_vec()), asset_name: AssetName(vec![]) }
193	}
194}
195
196impl From<sidechain_domain::AssetId> for Asset {
197	fn from(asset: AssetId) -> Self {
198		Self { policy_id: asset.policy_id.into(), asset_name: asset.asset_name.into() }
199	}
200}
201
202/// Helper row type for querying just epoch number
203#[allow(deprecated)]
204mod epoch_number_row {
205	use super::*;
206	#[deprecated(
207		since = "1.7.0",
208		note = "Deprecated due to not being either a primitive type or a complete Db-Sync table row."
209	)]
210	#[derive(Debug, Copy, Clone, sqlx::FromRow, PartialEq)]
211	pub struct EpochNumberRow(pub EpochNumber);
212
213	#[allow(deprecated)]
214	impl From<EpochNumberRow> for EpochNumber {
215		fn from(r: EpochNumberRow) -> Self {
216			r.0
217		}
218	}
219}
220pub use epoch_number_row::*;
221
222/// Cardano address in human-readable form. Either Base58 for Byron addresses and Bech32 for Shelley.
223#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
224pub struct Address(pub String);
225
226impl From<MainchainAddress> for Address {
227	fn from(addr: MainchainAddress) -> Self {
228		Self(addr.to_string())
229	}
230}
231
232/// Cardano epoch nonce, ie. random 32 bytes generated by Cardano every epoch.
233///
234/// This value can be used as a tamper-proof randomness seed.
235#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
236pub struct MainchainEpochNonce(pub Vec<u8>);