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, BoxDynError> {
64				buf.extend(&self.0.to_be_bytes());
65				Ok(IsNull::No)
66			}
67		}
68
69		impl From<$NAME> for $DOMAIN {
70			fn from(value: $NAME) -> Self {
71				Self(value.0)
72			}
73		}
74
75		impl From<$DOMAIN> for $NAME {
76			fn from(value: $DOMAIN) -> Self {
77				Self(value.0)
78			}
79		}
80	};
81}
82
83/// Cardano block number
84#[derive(Debug, Copy, Ord, PartialOrd, Clone, PartialEq, Eq)]
85pub struct BlockNumber(pub u32);
86sqlx_implementations_for_wrapper!(i32, "INT4", BlockNumber, McBlockNumber);
87
88/// Cardano epoch number
89#[derive(Debug, Copy, Clone, PartialEq)]
90pub struct EpochNumber(pub u32);
91sqlx_implementations_for_wrapper!(i32, "INT4", EpochNumber, McEpochNumber);
92
93/// Cardano slot number
94#[derive(Debug, Copy, Clone, PartialEq)]
95pub struct SlotNumber(pub u64);
96sqlx_implementations_for_wrapper!(i64, "INT8", SlotNumber, McSlotNumber);
97
98/// Index of a Cardano transaction output
99///
100/// This type corresponds to the following SQL type defined by Db-Sync.
101/// ```sql
102/// CREATE DOMAIN txindex AS smallint CONSTRAINT txindex_check CHECK ((VALUE >= 0));
103/// ```
104#[derive(Debug, Clone, PartialEq)]
105pub struct TxIndex(pub u16);
106sqlx_implementations_for_wrapper!(i16, "INT2", TxIndex, UtxoIndex);
107
108/// Index of a Cardano transaction with its block
109#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
110pub struct TxIndexInBlock(pub u32);
111sqlx_implementations_for_wrapper!(i32, "INT4", TxIndexInBlock, McTxIndexInBlock);
112
113/// Number of ADA expressed in Lovelace (1 million-th of ADA)
114///
115/// This type corresponds to the following SQL type defined by Db-Sync.
116/// ```sql
117/// CREATE DOMAIN int65type AS numeric (20, 0) CHECK (VALUE >= -18446744073709551615 AND VALUE <= 18446744073709551615);
118/// ```
119#[derive(Debug, Clone, PartialEq)]
120pub struct TxValue(pub i128);
121
122impl sqlx::Type<Postgres> for TxValue {
123	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
124		PgTypeInfo::with_name("NUMERIC")
125	}
126}
127
128impl<'r> Decode<'r, Postgres> for TxValue {
129	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
130		let decoded = <sqlx::types::BigDecimal as Decode<Postgres>>::decode(value)?;
131		let i = decoded.to_i128().ok_or("TxValue is always an integer".to_string())?;
132		Ok(Self(i))
133	}
134}
135
136/// Number of ADA delegated by a Cardano delegator to a single SPO, expressed in Lovelace (1 million-th of ADA)
137///
138/// This type corresponds to the following SQL type defined by Db-Sync.
139/// ```sql
140/// CREATE DOMAIN "lovelace" AS numeric(20,0) CONSTRAINT flyway_needs_this CHECK (VALUE >= 0::numeric AND VALUE <= '18446744073709551615'::numeric);
141/// ```
142#[derive(Debug, Clone, PartialEq)]
143pub struct StakeDelegation(pub u64);
144
145impl sqlx::Type<Postgres> for StakeDelegation {
146	fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
147		PgTypeInfo::with_name("NUMERIC")
148	}
149}
150
151impl<'r> Decode<'r, Postgres> for StakeDelegation {
152	fn decode(value: <Postgres as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
153		let decoded = <sqlx::types::BigDecimal as Decode<Postgres>>::decode(value)?;
154		let i = decoded.to_u64().ok_or("StakeDelegation is always a u64".to_string())?;
155		Ok(Self(i))
156	}
157}
158
159/// Cardano native asset name, typically UTF-8 encoding of a human-readable name
160#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
161pub struct AssetName(pub Vec<u8>);
162
163impl From<sidechain_domain::AssetName> for AssetName {
164	fn from(name: sidechain_domain::AssetName) -> Self {
165		Self(name.0.to_vec())
166	}
167}
168
169/// Cardano minting policy ID. This value is obtained by hashing the Plutus script of the policy.
170#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
171pub struct PolicyId(pub Vec<u8>);
172
173impl From<sidechain_domain::PolicyId> for PolicyId {
174	fn from(id: sidechain_domain::PolicyId) -> Self {
175		Self(id.0.to_vec())
176	}
177}
178
179/// Full identifier of a Cardano native asset
180#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
181pub struct Asset {
182	/// Minting policy ID of the asset
183	pub policy_id: PolicyId,
184	/// Asset name
185	pub asset_name: AssetName,
186}
187
188impl Asset {
189	/// Creates an Asset with empty asset_name.
190	pub fn new(policy_id: sidechain_domain::PolicyId) -> Self {
191		Self { policy_id: PolicyId(policy_id.0.to_vec()), asset_name: AssetName(vec![]) }
192	}
193}
194
195impl From<sidechain_domain::AssetId> for Asset {
196	fn from(asset: AssetId) -> Self {
197		Self { policy_id: asset.policy_id.into(), asset_name: asset.asset_name.into() }
198	}
199}
200
201/// Helper row type for querying just epoch number
202#[allow(deprecated)]
203mod epoch_number_row {
204	use super::*;
205	#[deprecated(
206		since = "1.7.0",
207		note = "Deprecated due to not being either a primitive type or a complete Db-Sync table row."
208	)]
209	#[derive(Debug, Copy, Clone, sqlx::FromRow, PartialEq)]
210	pub struct EpochNumberRow(pub EpochNumber);
211
212	#[allow(deprecated)]
213	impl From<EpochNumberRow> for EpochNumber {
214		fn from(r: EpochNumberRow) -> Self {
215			r.0
216		}
217	}
218}
219pub use epoch_number_row::*;
220
221/// Cardano address in human-readable form. Either Base58 for Byron addresses and Bech32 for Shelley.
222#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
223pub struct Address(pub String);
224
225impl From<MainchainAddress> for Address {
226	fn from(addr: MainchainAddress) -> Self {
227		Self(addr.to_string())
228	}
229}
230
231/// Cardano epoch nonce, ie. random 32 bytes generated by Cardano every epoch.
232///
233/// This value can be used as a tamper-proof randomness seed.
234#[derive(Debug, Clone, sqlx::FromRow, PartialEq)]
235pub struct MainchainEpochNonce(pub Vec<u8>);