From 53c86cf72c1326539fe2bfce523c1dce1bdb81c0 Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Mon, 3 Feb 2025 23:35:52 +0100 Subject: [PATCH 1/2] Support GROUP BY for links and tags Currently, `[...] GROUP BY links` throws the following exception: beanquery.compiler.CompilationError: GROUP-BY a non-hashable type is not supported: "Column(name='links')" because `set` is not hashable. `frozenset` is hashable, and can be used for the `links` and `tags` which don't need to be mutable. --- beanquery/query_execute_test.py | 28 ++++++++++++++++++++++++++++ beanquery/sources/beancount.py | 8 ++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/beanquery/query_execute_test.py b/beanquery/query_execute_test.py index 0358d632..facdd4c3 100644 --- a/beanquery/query_execute_test.py +++ b/beanquery/query_execute_test.py @@ -1086,6 +1086,34 @@ def test_aggregated_group_by_invisible_order_by_aggregate_invisible(self): (2,), ]) + def test_aggregated_group_by_links(self): + self.check_query( + """ + 2010-02-21 * "doctor" "appointment" ^2010-reimbursement-doc1 + Assets:Bank:Checking -1000.00 USD + Assets:AccountsReceivable:Pending 1000.00 USD + + 2010-02-22 * "insurance" "partial reimbursement" ^2010-reimbursement-doc1 + Assets:Bank:Checking 100.00 USD + Assets:AccountsReceivable:Pending -100.00 USD + """, + """ + SELECT FIRST(date) as date, FIRST(payee) AS payee, FIRST(narration) AS narration, links, SUM(position) AS balance + WHERE account ~ 'Assets:AccountsReceivable:Pending' + GROUP BY links + ORDER BY balance DESC + """, + [ + ('date', datetime.date), + ('payee', str), + ('narration', str), + ('links', frozenset), + ('balance', inventory.Inventory), + ], + [ + (datetime.date(2010, 2, 21), 'doctor', 'appointment', {'2010-reimbursement-doc1'}, I('900.00 USD')), + ]) + def test_aggregated_group_by_with_having(self): self.check_query( """ diff --git a/beanquery/sources/beancount.py b/beanquery/sources/beancount.py index e388b6c6..b4d131ef 100644 --- a/beanquery/sources/beancount.py +++ b/beanquery/sources/beancount.py @@ -357,12 +357,12 @@ def description(entry): return None return ' | '.join(filter(None, [entry.payee, entry.narration])) - @columns.register(set) + @columns.register(frozenset) def tags(entry): """The set of tags of the transaction.""" return getattr(entry, 'tags', None) - @columns.register(set) + @columns.register(frozenset) def links(entry): """The set of links of the transaction.""" return getattr(entry, 'links', None) @@ -493,12 +493,12 @@ def description(context): """A combination of the payee + narration for the transaction of this posting.""" return ' | '.join(filter(None, [context.entry.payee, context.entry.narration])) - @columns.register(set) + @columns.register(frozenset) def tags(context): """The set of tags of the parent transaction for this posting.""" return context.entry.tags - @columns.register(set) + @columns.register(frozenset) def links(context): """The set of links of the parent transaction for this posting.""" return context.entry.links From 2adaf1409bb8d4c80c5b36665b0d3b7080f82ece Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Sun, 9 Feb 2025 15:51:35 +0100 Subject: [PATCH 2/2] add renderer for frozenset --- beanquery/query_render.py | 4 ++++ beanquery/query_render_test.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/beanquery/query_render.py b/beanquery/query_render.py index ebcfbcae..d1063f02 100644 --- a/beanquery/query_render.py +++ b/beanquery/query_render.py @@ -147,6 +147,10 @@ def format(self, value): return self.sep.join(str(x) for x in sorted(value)) +class FrozensetRenderer(SetRenderer): + dtype = frozenset + + class DateRenderer(ColumnRenderer): dtype = datetime.date diff --git a/beanquery/query_render_test.py b/beanquery/query_render_test.py index 4f55b42a..11a9375c 100644 --- a/beanquery/query_render_test.py +++ b/beanquery/query_render_test.py @@ -100,6 +100,13 @@ def test_set_str(self): 'bb ccc', ]) + def test_frozenset_str(self): + self.assertEqual(self.render(frozenset, [frozenset({}), frozenset({'aaaa'}), frozenset({'bb', 'ccc'})]), [ + ' ', + 'aaaa ', + 'bb ccc', + ]) + def test_date(self): self.assertEqual(self.render(datetime.date, [datetime.date(2014, 10, 3)]), [ '2014-10-03'