On 21/05/24 09:34, Stefano Zacchiroli wrote:
> On Mon, May 20, 2024 at 11:22:33PM +0200, Daniele Nicolodi wrote:
>> From the perspective of the maintainer of bean-query, I wonder whether a
>> tool like this could have been implemented as a front-end that interprets
>> the command line options and translates them into a query for bean-query in
>> the form 'PRINT FROM ...'
>>
>> If you thought about this approach, I would like to know which shortcomings
>> of bean-query didn't allow to implement bean-grep this way.
>
> Probably a disappointment to you (sorry!), but I didn't consider that
> approach. I'm familiar with the Python API of Beancount, so that was my
> go-to design choice. Hence I don't think I've useful feedback to give
> you on the applicability of the approach you propose.
>
> Just a gut feeling, though: if the translation you suggest requires
> generating queries as textual strings, that would make me feel itchy,
> due to the usual SQL-style problems of generating invalid syntax,
> possibly involuntary SQL-injections, etc. If OTOH there is an abstract
> (AST-based?) API to do the same, it would be less of a problem.
beanquery parses a query into an AST representation which is then
"compiled" into an tree of evaluator nodes (for lack of a better name,
if someone has a better idea of how these should be called, please let
me know) that are then executed. An example of both can be obtained
running the '.explain' command in the shell:
beanquery> .explain select date + 1 from #postings
parsed statement
----------------
(select
targets: (
(target
expression: (add
left: (column
name: 'date')
right: (constant
value: 1))))
from-clause: (table
name: 'postings'))
compiled query
--------------
EvalQuery(table=<beanquery.query_env.PostingsTable object at
0x1064cdb50>, c_targets=[EvalTarget(c_expr=Add[date,int](date(<class
'datetime.date'>), EvalConstant(1)), name='date + 1',
is_aggregate=False)], c_where=None, group_indexes=None,
having_index=None, order_spec=None, limit=None, distinct=None)
The parser AST is displayed in s-expression-like format, inspired to the
one produced by tree-sitter, but it is just Python classes:
>>> beanquery.parser.parse('SELECT 1+1 FROM #')
Select(
targets=[
Target(
expression=Add(
left=Constant(value=1),
right=Constant(value=1)),
name=None)
],
from_clause=Table(name=''),
where_clause=None,
group_by=None,
order_by=None,
pivot_by=None,
limit=None,
distinct=None
)
(reformatted for readability)
The public API exposes the possibility to directly pass the parser AST.
The evaluator nodes are not public API, and writing the evaluation nodes
by hand becomes tedious for anything non-trivial.
On the other hand, beanquery exposed an DB-API 2.0 compatible API with
parameters placeholders and parameters substitutions:
>>> from datetime import date
>>> import beanquery
>>> conn = beanquery.connect('beancount:tests/test01.beancount')
>>> curs = conn.execute(
... 'SELECT date WHERE date > %s',
... (date.today(),)
... )
>>> curs.fetchall()
[]
>>> curs = conn.execute(
... 'SELECT date WHERE date > %(today)s',
... {'today': date.today()})
>>> curs.fetchall()
[]
Query parameters are not interpolated in the query, but the AST has
direct support for them:
>>> beanquery.parser.parse(
... 'SELECT date FROM #postings WHERE date > %(today)s')
Select(
targets=[
Target(
expression=Column(name='date'), name=None)
],
from_clause=Table(name='postings'),
where_clause=Greater(
left=Column(name='date'),
right=Placeholder(name='today')
^^^^^^^^^^^^^^^^^^^^^^^^^
),
group_by=None,
order_by=None,
pivot_by=None,
limit=None,
distinct=None
)
Some day I need to document all this...
Cheers,
Dan