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 Set(Vec<Self>),
138 Metadata(Box<Metadata>),
140 Interval(Interval),
142 Object(Box<BTreeMap<String, Self>>),
144 Null,
146}
147
148impl Value {
149 pub(crate) fn hash_value<H: Hasher>(&self, state: &mut H) {
155 std::mem::discriminant(self).hash(state);
156 match self {
157 Self::String(s) => s.hash(state),
158 Self::Number(d) => d.serialize().hash(state),
159 Self::Integer(i) => i.hash(state),
160 Self::Date(d) => {
161 d.year().hash(state);
162 d.month().hash(state);
163 d.day().hash(state);
164 }
165 Self::Boolean(b) => b.hash(state),
166 Self::Amount(a) => {
167 a.number.serialize().hash(state);
168 a.currency.as_str().hash(state);
169 }
170 Self::Position(p) => {
171 p.units.number.serialize().hash(state);
173 p.units.currency.as_str().hash(state);
174 if let Some(cost) = &p.cost {
175 cost.number.serialize().hash(state);
176 cost.currency.as_str().hash(state);
177 }
178 }
179 Self::Inventory(inv) => {
180 for pos in inv.positions() {
182 pos.units.number.serialize().hash(state);
183 pos.units.currency.as_str().hash(state);
184 if let Some(cost) = &pos.cost {
185 cost.number.serialize().hash(state);
186 cost.currency.as_str().hash(state);
187 }
188 }
189 }
190 Self::StringSet(ss) => {
191 let mut sorted = ss.clone();
193 sorted.sort();
194 for s in &sorted {
195 s.hash(state);
196 }
197 }
198 Self::Set(values) => {
199 for v in values {
201 v.hash_value(state);
202 }
203 }
204 Self::Metadata(meta) => {
205 let mut keys: Vec<_> = meta.keys().collect();
207 keys.sort();
208 for key in keys {
209 key.hash(state);
210 format!("{:?}", meta.get(key)).hash(state);
212 }
213 }
214 Self::Interval(interval) => {
215 interval.count.hash(state);
216 interval.unit.hash(state);
217 }
218 Self::Object(obj) => {
219 for (k, v) in obj.as_ref() {
221 k.hash(state);
222 v.hash_value(state);
223 }
224 }
225 Self::Null => {}
226 }
227 }
228}
229
230pub type Row = Vec<Value>;
232
233pub fn hash_row(row: &Row) -> u64 {
235 use std::collections::hash_map::DefaultHasher;
236 let mut hasher = DefaultHasher::new();
237 for value in row {
238 value.hash_value(&mut hasher);
239 }
240 hasher.finish()
241}
242
243pub fn hash_single_value(value: &Value) -> u64 {
245 use std::collections::hash_map::DefaultHasher;
246 let mut hasher = DefaultHasher::new();
247 value.hash_value(&mut hasher);
248 hasher.finish()
249}
250
251#[derive(Debug, Clone)]
253pub struct QueryResult {
254 pub columns: Vec<String>,
256 pub rows: Vec<Row>,
258}
259
260impl QueryResult {
261 pub const fn new(columns: Vec<String>) -> Self {
263 Self {
264 columns,
265 rows: Vec::new(),
266 }
267 }
268
269 pub fn add_row(&mut self, row: Row) {
271 self.rows.push(row);
272 }
273
274 pub const fn len(&self) -> usize {
276 self.rows.len()
277 }
278
279 pub const fn is_empty(&self) -> bool {
281 self.rows.is_empty()
282 }
283}
284
285#[derive(Debug)]
287pub struct PostingContext<'a> {
288 pub transaction: &'a Transaction,
290 pub posting_index: usize,
292 pub balance: Option<Inventory>,
294 pub directive_index: Option<usize>,
296}
297
298#[derive(Debug, Clone)]
300pub struct WindowContext {
301 pub row_number: usize,
303 pub rank: usize,
305 pub dense_rank: usize,
307}
308
309#[derive(Debug, Clone)]
311pub struct AccountInfo {
312 pub open_date: Option<NaiveDate>,
314 pub close_date: Option<NaiveDate>,
316 pub open_meta: Metadata,
318}
319
320#[derive(Debug, Clone)]
322pub struct Table {
323 pub columns: Vec<String>,
325 pub rows: Vec<Vec<Value>>,
327}
328
329impl Table {
330 #[allow(clippy::missing_const_for_fn)] pub fn new(columns: Vec<String>) -> Self {
333 Self {
334 columns,
335 rows: Vec::new(),
336 }
337 }
338
339 pub fn add_row(&mut self, row: Vec<Value>) {
341 self.rows.push(row);
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
352 fn test_value_size() {
353 use std::mem::size_of;
354 assert!(
356 size_of::<Value>() <= 48,
357 "Value enum too large: {} bytes",
358 size_of::<Value>()
359 );
360 }
361}