rustledger_query/executor/
types.rs1use std::collections::BTreeMap;
4use std::hash::{Hash, Hasher};
5
6use rust_decimal::Decimal;
7use rustledger_core::{Amount, Inventory, Metadata, NaiveDate, Position, Transaction};
8
9#[derive(Debug, Clone)]
11pub struct SourceLocation {
12 pub filename: String,
14 pub lineno: usize,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum IntervalUnit {
21 Day,
23 Week,
25 Month,
27 Quarter,
29 Year,
31}
32
33impl IntervalUnit {
34 pub fn parse_unit(s: &str) -> Option<Self> {
36 match s.to_uppercase().as_str() {
37 "DAY" | "DAYS" | "D" => Some(Self::Day),
38 "WEEK" | "WEEKS" | "W" => Some(Self::Week),
39 "MONTH" | "MONTHS" | "M" => Some(Self::Month),
40 "QUARTER" | "QUARTERS" | "Q" => Some(Self::Quarter),
41 "YEAR" | "YEARS" | "Y" => Some(Self::Year),
42 _ => None,
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub struct Interval {
50 pub count: i64,
52 pub unit: IntervalUnit,
54}
55
56impl Interval {
57 pub const fn new(count: i64, unit: IntervalUnit) -> Self {
59 Self { count, unit }
60 }
61
62 pub(crate) const fn to_approx_days(&self) -> i64 {
65 let days_per_unit = match self.unit {
66 IntervalUnit::Day => 1,
67 IntervalUnit::Week => 7,
68 IntervalUnit::Month => 30,
69 IntervalUnit::Quarter => 91,
70 IntervalUnit::Year => 365,
71 };
72 self.count.saturating_mul(days_per_unit)
73 }
74
75 pub fn add_to_date(&self, date: NaiveDate) -> Option<NaiveDate> {
77 use jiff::ToSpan;
78
79 let span = match self.unit {
80 IntervalUnit::Day => self.count.days(),
81 IntervalUnit::Week => self.count.weeks(),
82 IntervalUnit::Month => self.count.months(),
83 IntervalUnit::Quarter => (self.count * 3).months(),
84 IntervalUnit::Year => self.count.years(),
85 };
86 date.checked_add(span).ok()
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum Value {
97 String(String),
99 Number(Decimal),
101 Integer(i64),
103 Date(NaiveDate),
105 Boolean(bool),
107 Amount(Amount),
109 Position(Box<Position>),
111 Inventory(Box<Inventory>),
113 StringSet(Vec<String>),
115 Set(Vec<Self>),
117 Metadata(Box<Metadata>),
119 Interval(Interval),
121 Object(Box<BTreeMap<String, Self>>),
123 Null,
125}
126
127impl Value {
128 pub(crate) fn hash_value<H: Hasher>(&self, state: &mut H) {
134 std::mem::discriminant(self).hash(state);
135 match self {
136 Self::String(s) => s.hash(state),
137 Self::Number(d) => d.serialize().hash(state),
138 Self::Integer(i) => i.hash(state),
139 Self::Date(d) => {
140 d.year().hash(state);
141 d.month().hash(state);
142 d.day().hash(state);
143 }
144 Self::Boolean(b) => b.hash(state),
145 Self::Amount(a) => {
146 a.number.serialize().hash(state);
147 a.currency.as_str().hash(state);
148 }
149 Self::Position(p) => {
150 p.units.number.serialize().hash(state);
152 p.units.currency.as_str().hash(state);
153 if let Some(cost) = &p.cost {
154 cost.number.serialize().hash(state);
155 cost.currency.as_str().hash(state);
156 }
157 }
158 Self::Inventory(inv) => {
159 for pos in inv.positions() {
161 pos.units.number.serialize().hash(state);
162 pos.units.currency.as_str().hash(state);
163 if let Some(cost) = &pos.cost {
164 cost.number.serialize().hash(state);
165 cost.currency.as_str().hash(state);
166 }
167 }
168 }
169 Self::StringSet(ss) => {
170 let mut sorted = ss.clone();
172 sorted.sort();
173 for s in &sorted {
174 s.hash(state);
175 }
176 }
177 Self::Set(values) => {
178 for v in values {
180 v.hash_value(state);
181 }
182 }
183 Self::Metadata(meta) => {
184 let mut keys: Vec<_> = meta.keys().collect();
186 keys.sort();
187 for key in keys {
188 key.hash(state);
189 format!("{:?}", meta.get(key)).hash(state);
191 }
192 }
193 Self::Interval(interval) => {
194 interval.count.hash(state);
195 interval.unit.hash(state);
196 }
197 Self::Object(obj) => {
198 for (k, v) in obj.as_ref() {
200 k.hash(state);
201 v.hash_value(state);
202 }
203 }
204 Self::Null => {}
205 }
206 }
207}
208
209pub type Row = Vec<Value>;
211
212pub fn hash_row(row: &Row) -> u64 {
214 use std::collections::hash_map::DefaultHasher;
215 let mut hasher = DefaultHasher::new();
216 for value in row {
217 value.hash_value(&mut hasher);
218 }
219 hasher.finish()
220}
221
222pub fn hash_single_value(value: &Value) -> u64 {
224 use std::collections::hash_map::DefaultHasher;
225 let mut hasher = DefaultHasher::new();
226 value.hash_value(&mut hasher);
227 hasher.finish()
228}
229
230#[derive(Debug, Clone)]
232pub struct QueryResult {
233 pub columns: Vec<String>,
235 pub rows: Vec<Row>,
237}
238
239impl QueryResult {
240 pub const fn new(columns: Vec<String>) -> Self {
242 Self {
243 columns,
244 rows: Vec::new(),
245 }
246 }
247
248 pub fn add_row(&mut self, row: Row) {
250 self.rows.push(row);
251 }
252
253 pub const fn len(&self) -> usize {
255 self.rows.len()
256 }
257
258 pub const fn is_empty(&self) -> bool {
260 self.rows.is_empty()
261 }
262}
263
264#[derive(Debug)]
266pub struct PostingContext<'a> {
267 pub transaction: &'a Transaction,
269 pub posting_index: usize,
271 pub balance: Option<Inventory>,
273 pub directive_index: Option<usize>,
275}
276
277#[derive(Debug, Clone)]
279pub struct WindowContext {
280 pub row_number: usize,
282 pub rank: usize,
284 pub dense_rank: usize,
286}
287
288#[derive(Debug, Clone)]
290pub struct AccountInfo {
291 pub open_date: Option<NaiveDate>,
293 pub close_date: Option<NaiveDate>,
295 pub open_meta: Metadata,
297}
298
299#[derive(Debug, Clone)]
301pub struct Table {
302 pub columns: Vec<String>,
304 pub rows: Vec<Vec<Value>>,
306}
307
308impl Table {
309 #[allow(clippy::missing_const_for_fn)] pub fn new(columns: Vec<String>) -> Self {
312 Self {
313 columns,
314 rows: Vec::new(),
315 }
316 }
317
318 pub fn add_row(&mut self, row: Vec<Value>) {
320 self.rows.push(row);
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
331 fn test_value_size() {
332 use std::mem::size_of;
333 assert!(
335 size_of::<Value>() <= 48,
336 "Value enum too large: {} bytes",
337 size_of::<Value>()
338 );
339 }
340}