bark/persist/adaptor/
sort.rs

1use bitcoin::Amount;
2use chrono::{DateTime, Local};
3
4use crate::vtxo::VtxoStateKind;
5
6/// Opaque sort key encoded as bytes for lexicographic comparison.
7///
8/// The encoding ensures that database-level `ORDER BY sort_key ASC` produces
9/// the correct logical ordering. Use [`SortKeyBuilder`] to construct composite
10/// keys with mixed ascending/descending fields.
11///
12/// # Database Storage
13///
14/// Store as `BLOB` (SQLite), `BYTEA` (Postgres), or raw bytes in NoSQL stores.
15/// Create an index on `(partition, sort_key)` for efficient range scans.
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17pub struct SortKey(Vec<u8>);
18
19impl SortKey {
20	/// Returns the raw bytes for database storage.
21	pub fn as_bytes(&self) -> &[u8] {
22		&self.0
23	}
24
25	/// Constructs a sort key from raw bytes (for database retrieval).
26	pub fn from_bytes(bytes: Vec<u8>) -> Self {
27		Self(bytes)
28	}
29
30	/// Creates a builder for composite sort keys.
31	pub fn builder() -> SortKeyBuilder {
32		SortKeyBuilder(Vec::new())
33	}
34
35	/// Creates a sort key from a single u32, ascending order.
36	pub fn u32_asc(n: u32) -> Self {
37		Self::builder().u32_asc(n).build()
38	}
39
40	/// Creates a sort key from a single u64, descending order.
41	pub fn u64_desc(n: u64) -> Self {
42		Self::builder().u64_desc(n).build()
43	}
44}
45
46/// Builder for constructing composite sort keys with multiple fields.
47///
48/// # Example
49///
50/// ```rust
51/// # use bark::persist::adaptor::SortKey;
52///
53/// let height = 100;
54/// let amount = 1000;
55///
56/// // Sort by height ascending, then amount descending
57/// let key = SortKey::builder()
58///     .u32_asc(height)
59///     .u64_desc(amount)
60///     .build();
61/// ```
62#[derive(Debug, Clone, Default)]
63pub struct SortKeyBuilder(Vec<u8>);
64
65impl SortKeyBuilder {
66	pub fn u8_asc(mut self, n: u8) -> Self {
67		self.0.push(n);
68		self
69	}
70
71	/// Appends a u64 in ascending order.
72	pub fn u32_asc(mut self, n: u32) -> Self {
73		self.0.extend_from_slice(&n.to_be_bytes());
74		self
75	}
76
77	/// Appends a u64 in descending order.
78	pub fn u64_desc(mut self, n: u64) -> Self {
79		self.0.extend_from_slice(&(!n).to_be_bytes());
80		self
81	}
82
83	/// Builds the final sort key.
84	pub fn build(self) -> SortKey {
85		SortKey(self.0)
86	}
87}
88
89pub(crate) fn vtxo_sort_key(vtxo_state: VtxoStateKind, expiry_height: u32, amount: Amount) -> SortKey {
90	// Sort by state ASC, then expiry_height ASC, then amount DESC
91	// This prioritizes VTXOs that expire sooner and are larger
92	SortKey::builder()
93		.u8_asc(vtxo_state.as_byte())
94		.u32_asc(expiry_height)
95		.u64_desc(amount.to_sat())
96		.build()
97}
98
99pub(crate) fn movement_sort_key(created_at: &DateTime<Local>) -> SortKey {
100	// Sort by created_at DESC (most recent first)
101	SortKey::u64_desc(created_at.timestamp_millis() as u64)
102}
103
104#[cfg(test)]
105mod tests {
106	use super::*;
107
108	#[test]
109	fn sort_key_u64() {
110		let k1 = SortKey::builder().u32_asc(1).build();
111		let k2 = SortKey::builder().u32_asc(2).build();
112		let k3 = SortKey::builder().u32_asc(3).build();
113
114		assert!(k1 < k2);
115		assert!(k2 < k3);
116		assert!(k1 < k3);
117	}
118
119	#[test]
120	fn sort_key_u64_desc() {
121		let k1 = SortKey::builder().u64_desc(1).build();
122		let k2 = SortKey::builder().u64_desc(2).build();
123		let k3 = SortKey::builder().u64_desc(3).build();
124
125		assert!(k1 > k2);
126		assert!(k2 > k3);
127		assert!(k1 > k3);
128	}
129
130	#[test]
131	fn sort_key_composite() {
132		// Sort by height ASC, then amount DESC
133		let make_key = |height: u32, amount: u64| {
134			SortKey::builder()
135				.u32_asc(height)
136				.u64_desc(amount)
137				.build()
138		};
139
140		// Same height, higher amount should come first
141		let k1 = make_key(100, 1000);
142		let k2 = make_key(100, 500);
143		assert!(k1 < k2); // 1000 DESC < 500 DESC
144
145		// Lower height comes first regardless of amount
146		let k3 = make_key(50, 100);
147		assert!(k3 < k1);
148		assert!(k3 < k2);
149
150		// Higher height comes first regardless of amount
151		let k4 = make_key(150, 100);
152		assert!(k4 > k1);
153		assert!(k4 > k2);
154		assert!(k4 > k3);
155	}
156}