rustledger_query/executor/
types.rs1use std::collections::BTreeMap;
4use std::hash::{Hash, Hasher};
5
6use chrono::Datelike;
7use rust_decimal::Decimal;
8use rustledger_core::{Amount, Inventory, Metadata, NaiveDate, Position, Transaction};
9
10#[derive(Debug, Clone)]
12pub struct SourceLocation {
13 pub filename: String,
15 pub lineno: usize,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum IntervalUnit {
22 Day,
24 Week,
26 Month,
28 Quarter,
30 Year,
32}
33
34impl IntervalUnit {
35 pub fn parse_unit(s: &str) -> Option<Self> {
37 match s.to_uppercase().as_str() {
38 "DAY" | "DAYS" | "D" => Some(Self::Day),
39 "WEEK" | "WEEKS" | "W" => Some(Self::Week),
40 "MONTH" | "MONTHS" | "M" => Some(Self::Month),
41 "QUARTER" | "QUARTERS" | "Q" => Some(Self::Quarter),
42 "YEAR" | "YEARS" | "Y" => Some(Self::Year),
43 _ => None,
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50pub struct Interval {
51 pub count: i64,
53 pub unit: IntervalUnit,
55}
56
57impl Interval {
58 pub const fn new(count: i64, unit: IntervalUnit) -> Self {
60 Self { count, unit }
61 }
62
63 pub(crate) const fn to_approx_days(&self) -> i64 {
66 let days_per_unit = match self.unit {
67 IntervalUnit::Day => 1,
68 IntervalUnit::Week => 7,
69 IntervalUnit::Month => 30,
70 IntervalUnit::Quarter => 91,
71 IntervalUnit::Year => 365,
72 };
73 self.count.saturating_mul(days_per_unit)
74 }
75
76 #[allow(clippy::missing_const_for_fn)] pub fn add_to_date(&self, date: NaiveDate) -> Option<NaiveDate> {
79 use chrono::Months;
80
81 match self.unit {
82 IntervalUnit::Day => date.checked_add_signed(chrono::Duration::days(self.count)),
83 IntervalUnit::Week => date.checked_add_signed(chrono::Duration::weeks(self.count)),
84 IntervalUnit::Month => {
85 if self.count >= 0 {
86 date.checked_add_months(Months::new(self.count as u32))
87 } else {
88 date.checked_sub_months(Months::new((-self.count) as u32))
89 }
90 }
91 IntervalUnit::Quarter => {
92 let months = self.count * 3;
93 if months >= 0 {
94 date.checked_add_months(Months::new(months as u32))
95 } else {
96 date.checked_sub_months(Months::new((-months) as u32))
97 }
98 }
99 IntervalUnit::Year => {
100 let months = self.count * 12;
101 if months >= 0 {
102 date.checked_add_months(Months::new(months as u32))
103 } else {
104 date.checked_sub_months(Months::new((-months) as u32))
105 }
106 }
107 }
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum Value {
118 String(String),
120 Number(Decimal),
122 Integer(i64),
124 Date(NaiveDate),
126 Boolean(bool),
128 Amount(Amount),
130 Position(Box<Position>),
132 Inventory(Box<Inventory>),
134 StringSet(Vec<String>),
136 Metadata(Box<Metadata>),
138 Interval(Interval),
140 Object(Box<BTreeMap<String, Self>>),
142 Null,
144}
145
146impl Value {
147 pub(crate) fn hash_value<H: Hasher>(&self, state: &mut H) {
153 std::mem::discriminant(self).hash(state);
154 match self {
155 Self::String(s) => s.hash(state),
156 Self::Number(d) => d.serialize().hash(state),
157 Self::Integer(i) => i.hash(state),
158 Self::Date(d) => {
159 d.year().hash(state);
160 d.month().hash(state);
161 d.day().hash(state);
162 }
163 Self::Boolean(b) => b.hash(state),
164 Self::Amount(a) => {
165 a.number.serialize().hash(state);
166 a.currency.as_str().hash(state);
167 }
168 Self::Position(p) => {
169 p.units.number.serialize().hash(state);
171 p.units.currency.as_str().hash(state);
172 if let Some(cost) = &p.cost {
173 cost.number.serialize().hash(state);
174 cost.currency.as_str().hash(state);
175 }
176 }
177 Self::Inventory(inv) => {
178 for pos in inv.positions() {
180 pos.units.number.serialize().hash(state);
181 pos.units.currency.as_str().hash(state);
182 if let Some(cost) = &pos.cost {
183 cost.number.serialize().hash(state);
184 cost.currency.as_str().hash(state);
185 }
186 }
187 }
188 Self::StringSet(ss) => {
189 let mut sorted = ss.clone();
191 sorted.sort();
192 for s in &sorted {
193 s.hash(state);
194 }
195 }
196 Self::Metadata(meta) => {
197 let mut keys: Vec<_> = meta.keys().collect();
199 keys.sort();
200 for key in keys {
201 key.hash(state);
202 format!("{:?}", meta.get(key)).hash(state);
204 }
205 }
206 Self::Interval(interval) => {
207 interval.count.hash(state);
208 interval.unit.hash(state);
209 }
210 Self::Object(obj) => {
211 for (k, v) in obj.as_ref() {
213 k.hash(state);
214 v.hash_value(state);
215 }
216 }
217 Self::Null => {}
218 }
219 }
220}
221
222pub type Row = Vec<Value>;
224
225pub fn hash_row(row: &Row) -> u64 {
227 use std::collections::hash_map::DefaultHasher;
228 let mut hasher = DefaultHasher::new();
229 for value in row {
230 value.hash_value(&mut hasher);
231 }
232 hasher.finish()
233}
234
235pub fn hash_single_value(value: &Value) -> u64 {
237 use std::collections::hash_map::DefaultHasher;
238 let mut hasher = DefaultHasher::new();
239 value.hash_value(&mut hasher);
240 hasher.finish()
241}
242
243#[derive(Debug, Clone)]
245pub struct QueryResult {
246 pub columns: Vec<String>,
248 pub rows: Vec<Row>,
250}
251
252impl QueryResult {
253 pub const fn new(columns: Vec<String>) -> Self {
255 Self {
256 columns,
257 rows: Vec::new(),
258 }
259 }
260
261 pub fn add_row(&mut self, row: Row) {
263 self.rows.push(row);
264 }
265
266 pub fn len(&self) -> usize {
268 self.rows.len()
269 }
270
271 pub fn is_empty(&self) -> bool {
273 self.rows.is_empty()
274 }
275}
276
277#[derive(Debug)]
279pub struct PostingContext<'a> {
280 pub transaction: &'a Transaction,
282 pub posting_index: usize,
284 pub balance: Option<Inventory>,
286 pub directive_index: Option<usize>,
288}
289
290#[derive(Debug, Clone)]
292pub struct WindowContext {
293 pub row_number: usize,
295 pub rank: usize,
297 pub dense_rank: usize,
299}
300
301#[derive(Debug, Clone)]
303pub struct AccountInfo {
304 pub open_date: Option<NaiveDate>,
306 pub close_date: Option<NaiveDate>,
308 pub open_meta: Metadata,
310}
311
312#[derive(Debug, Clone)]
314pub struct Table {
315 pub columns: Vec<String>,
317 pub rows: Vec<Vec<Value>>,
319}
320
321impl Table {
322 #[allow(clippy::missing_const_for_fn)] pub fn new(columns: Vec<String>) -> Self {
325 Self {
326 columns,
327 rows: Vec::new(),
328 }
329 }
330
331 pub fn add_row(&mut self, row: Vec<Value>) {
333 self.rows.push(row);
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
344 fn test_value_size() {
345 use std::mem::size_of;
346 assert!(
348 size_of::<Value>() <= 48,
349 "Value enum too large: {} bytes",
350 size_of::<Value>()
351 );
352 }
353}