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)]
113pub enum Value {
114 String(String),
116 Number(Decimal),
118 Integer(i64),
120 Date(NaiveDate),
122 Boolean(bool),
124 Amount(Amount),
126 Position(Position),
128 Inventory(Inventory),
130 StringSet(Vec<String>),
132 Metadata(Metadata),
134 Interval(Interval),
136 Object(BTreeMap<String, Self>),
138 Null,
140}
141
142impl Value {
143 pub(crate) fn hash_value<H: Hasher>(&self, state: &mut H) {
149 std::mem::discriminant(self).hash(state);
150 match self {
151 Self::String(s) => s.hash(state),
152 Self::Number(d) => d.serialize().hash(state),
153 Self::Integer(i) => i.hash(state),
154 Self::Date(d) => {
155 d.year().hash(state);
156 d.month().hash(state);
157 d.day().hash(state);
158 }
159 Self::Boolean(b) => b.hash(state),
160 Self::Amount(a) => {
161 a.number.serialize().hash(state);
162 a.currency.as_str().hash(state);
163 }
164 Self::Position(p) => {
165 p.units.number.serialize().hash(state);
166 p.units.currency.as_str().hash(state);
167 if let Some(cost) = &p.cost {
168 cost.number.serialize().hash(state);
169 cost.currency.as_str().hash(state);
170 }
171 }
172 Self::Inventory(inv) => {
173 for pos in inv.positions() {
174 pos.units.number.serialize().hash(state);
175 pos.units.currency.as_str().hash(state);
176 if let Some(cost) = &pos.cost {
177 cost.number.serialize().hash(state);
178 cost.currency.as_str().hash(state);
179 }
180 }
181 }
182 Self::StringSet(ss) => {
183 let mut sorted = ss.clone();
185 sorted.sort();
186 for s in &sorted {
187 s.hash(state);
188 }
189 }
190 Self::Metadata(meta) => {
191 let mut keys: Vec<_> = meta.keys().collect();
193 keys.sort();
194 for key in keys {
195 key.hash(state);
196 format!("{:?}", meta.get(key)).hash(state);
198 }
199 }
200 Self::Interval(interval) => {
201 interval.count.hash(state);
202 interval.unit.hash(state);
203 }
204 Self::Object(obj) => {
205 for (k, v) in obj {
207 k.hash(state);
208 v.hash_value(state);
209 }
210 }
211 Self::Null => {}
212 }
213 }
214}
215
216pub type Row = Vec<Value>;
218
219pub fn hash_row(row: &Row) -> u64 {
221 use std::collections::hash_map::DefaultHasher;
222 let mut hasher = DefaultHasher::new();
223 for value in row {
224 value.hash_value(&mut hasher);
225 }
226 hasher.finish()
227}
228
229pub fn hash_single_value(value: &Value) -> u64 {
231 use std::collections::hash_map::DefaultHasher;
232 let mut hasher = DefaultHasher::new();
233 value.hash_value(&mut hasher);
234 hasher.finish()
235}
236
237#[derive(Debug, Clone)]
239pub struct QueryResult {
240 pub columns: Vec<String>,
242 pub rows: Vec<Row>,
244}
245
246impl QueryResult {
247 pub const fn new(columns: Vec<String>) -> Self {
249 Self {
250 columns,
251 rows: Vec::new(),
252 }
253 }
254
255 pub fn add_row(&mut self, row: Row) {
257 self.rows.push(row);
258 }
259
260 pub fn len(&self) -> usize {
262 self.rows.len()
263 }
264
265 pub fn is_empty(&self) -> bool {
267 self.rows.is_empty()
268 }
269}
270
271#[derive(Debug)]
273pub struct PostingContext<'a> {
274 pub transaction: &'a Transaction,
276 pub posting_index: usize,
278 pub balance: Option<Inventory>,
280 pub directive_index: Option<usize>,
282}
283
284#[derive(Debug, Clone)]
286pub struct WindowContext {
287 pub row_number: usize,
289 pub rank: usize,
291 pub dense_rank: usize,
293}
294
295#[derive(Debug, Clone)]
297pub struct AccountInfo {
298 pub open_date: Option<NaiveDate>,
300 pub close_date: Option<NaiveDate>,
302 pub open_meta: Metadata,
304}
305
306#[derive(Debug, Clone)]
308pub struct Table {
309 pub columns: Vec<String>,
311 pub rows: Vec<Vec<Value>>,
313}
314
315impl Table {
316 #[allow(clippy::missing_const_for_fn)] pub fn new(columns: Vec<String>) -> Self {
319 Self {
320 columns,
321 rows: Vec::new(),
322 }
323 }
324
325 pub fn add_row(&mut self, row: Vec<Value>) {
327 self.rows.push(row);
328 }
329}