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
14
15use num_traits::ToPrimitive;
16use sidechain_domain::*;
17use sqlx::database::Database;
18use sqlx::encode::IsNull;
19use sqlx::error::BoxDynError;
20use sqlx::postgres::PgTypeInfo;
21use sqlx::*;
22
23/// Macro to handle numeric types that are non-negative but are stored by Db-Sync using
24/// signed SQL types.
25///
26/// This macro generates an unsigned numeric domain type and sqlx trait implementation for
27/// decoding it from signed data coming from Db-Sync database. It expects that values will
28/// always have 0 as the most significant bit.
29///
30/// For example because [TxIndex] is in range of \[0, 2^15-1\], it will be [u16] in domain,
31/// but it requires encoding and decoding as i16.
32///
33/// See `txindex`, `word31` `and` word63 types in Db-Sync schema definition.
34#[macro_export]
35macro_rules! sqlx_implementations_for_wrapper {
36	($WRAPPED:ty, $DBTYPE:expr, $NAME:ty, $DOMAIN:ty) => {
37		impl sqlx::Type<Postgres> for $NAME {
38			fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
39				PgTypeInfo::with_name($DBTYPE)
40			}
41		}
42
43		impl<'r> Decode<'r, Postgres> for $NAME
44		where
45			$WRAPPED: Decode<'r, Postgres>,
46		{
47			fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
48				let decoded: $WRAPPED = <$WRAPPED as Decode<Postgres>>::decode(value)?;
49				Ok(Self(decoded.try_into()?))
50			}
51		}
52
53		#[cfg(test)]
54		impl From<$WRAPPED> for $NAME {
55			fn from(value: $WRAPPED) -> Self {
56				Self(value.try_into().expect("value from domain fits in type db type"))
57			}
58		}
59
60		impl<'q> Encode<'q, Postgres> for $NAME {
61			fn encode_by_ref(
62				&self,
63				buf: &mut <Postgres as Database>::ArgumentBuffer<'q>,
64			) -> std::result::Result<IsNull, BoxDynError> {
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#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
115pub struct TxHash(pub [u8; 32]);
116
117impl sqlx::Type<Postgres> for TxHash {
118	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
119		PgTypeInfo::with_name("bytea")
120	}
121}
122
123impl<'r> Decode<'r, Postgres> for TxHash {
124	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
125		let decoded = <[u8; 32]>::decode(value)?;
126		Ok(Self(decoded))
127	}
128}
129
130impl<'r> Encode<'r, Postgres> for TxHash {
131	fn encode_by_ref(
132		&self,
133		buf: &mut <Postgres as Database>::ArgumentBuffer<'r>,
134	) -> std::result::Result<IsNull, BoxDynError> {
135		buf.extend(self.0.iter());
136		Ok(IsNull::No)
137	}
138}
139
140impl From<TxHash> for McTxHash {
141	fn from(hash: TxHash) -> Self {
142		Self(hash.0)
143	}
144}
145impl From<McTxHash> for TxHash {
146	fn from(hash: McTxHash) -> Self {
147		Self(hash.0)
148	}
149}
150
151/// Number of ADA expressed in Lovelace (1 million-th of ADA)
152///
153/// This type corresponds to the following SQL type defined by Db-Sync.
154/// ```sql
155/// CREATE DOMAIN int65type AS numeric (20, 0) CHECK (VALUE >= -18446744073709551615 AND VALUE <= 18446744073709551615);
156/// ```
157#[derive(Debug, Clone, PartialEq)]
158pub struct TxValue(pub i128);
159
160impl sqlx::Type<Postgres> for TxValue {
161	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
162		PgTypeInfo::with_name("NUMERIC")
163	}
164}
165
166impl<'r> Decode<'r, Postgres> for TxValue {
167	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
168		let decoded = <sqlx::types::BigDecimal as Decode<Postgres>>::decode(value)?;
169		let i = decoded.to_i128().ok_or("TxValue is always an integer".to_string())?;
170		Ok(Self(i))
171	}
172}
173
174/// Number of ADA delegated by a Cardano delegator to a single SPO, expressed in Lovelace (1 million-th of ADA)
175///
176/// This type corresponds to the following SQL type defined by Db-Sync.
177/// ```sql
178/// CREATE DOMAIN "lovelace" AS numeric(20,0) CONSTRAINT flyway_needs_this CHECK (VALUE >= 0::numeric AND VALUE <= '18446744073709551615'::numeric);
179/// ```
180#[derive(Debug, Clone, PartialEq)]
181pub struct StakeDelegation(pub u64);
182
183impl sqlx::Type<Postgres> for StakeDelegation {
184	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
185		PgTypeInfo::with_name("NUMERIC")
186	}
187}
188
189impl<'r> Decode<'r, Postgres> for StakeDelegation {
190	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
191		let decoded = <sqlx::types::BigDecimal as Decode<Postgres>>::decode(value)?;
192		let i = decoded.to_u64().ok_or("StakeDelegation is always a u64".to_string())?;
193		Ok(Self(i))
194	}
195}
196
197/// Cardano native asset name, typically UTF-8 encoding of a human-readable name
198#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
199pub struct AssetName(pub Vec<u8>);
200
201impl From<sidechain_domain::AssetName> for AssetName {
202	fn from(name: sidechain_domain::AssetName) -> Self {
203		Self(name.0.to_vec())
204	}
205}
206
207/// Cardano minting policy ID. This value is obtained by hashing the Plutus script of the policy.
208#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
209pub struct PolicyId(pub Vec<u8>);
210
211impl From<sidechain_domain::PolicyId> for PolicyId {
212	fn from(id: sidechain_domain::PolicyId) -> Self {
213		Self(id.0.to_vec())
214	}
215}
216
217/// Full identifier of a Cardano native asset
218#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
219pub struct Asset {
220	/// Minting policy ID of the asset
221	pub policy_id: PolicyId,
222	/// Asset name
223	pub asset_name: AssetName,
224}
225
226impl Asset {
227	/// Creates an Asset with empty asset_name.
228	pub fn new(policy_id: sidechain_domain::PolicyId) -> Self {
229		Self { policy_id: PolicyId(policy_id.0.to_vec()), asset_name: AssetName(vec![]) }
230	}
231}
232
233impl From<sidechain_domain::AssetId> for Asset {
234	fn from(asset: AssetId) -> Self {
235		Self { policy_id: asset.policy_id.into(), asset_name: asset.asset_name.into() }
236	}
237}
238
239/// Helper row type for querying just epoch number
240#[allow(deprecated)]
241mod epoch_number_row {
242	use super::*;
243	#[deprecated(
244		since = "1.7.0",
245		note = "Deprecated due to not being either a primitive type or a complete Db-Sync table row."
246	)]
247	#[derive(Debug, Copy, Clone, sqlx::FromRow, PartialEq)]
248	pub struct EpochNumberRow(pub EpochNumber);
249
250	#[allow(deprecated)]
251	impl From<EpochNumberRow> for EpochNumber {
252		fn from(r: EpochNumberRow) -> Self {
253			r.0
254		}
255	}
256}
257pub use epoch_number_row::*;
258
259/// Cardano address in human-readable form. Either Base58 for Byron addresses and Bech32 for Shelley.
260#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
261pub struct Address(pub String);
262
263impl From<MainchainAddress> for Address {
264	fn from(addr: MainchainAddress) -> Self {
265		Self(addr.to_string())
266	}
267}
268
269/// Cardano epoch nonce, ie. random 32 bytes generated by Cardano every epoch.
270///
271/// This value can be used as a tamper-proof randomness seed.
272#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
273pub struct MainchainEpochNonce(pub Vec<u8>);