-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify filter handling in adapters #320
Comments
@demmer Long read but I’d appreciate if you could have a look. Consider the proposal as a starting point for a discussion, not necessarily the final design. |
Updated the description based on the results of a discussion with @demmer:
|
Current status:
|
There are three prototype implementations which use the API now:
Lessons learned:
Overall, the transformation removed some boilerplate code and error handling, but the benefits are small. This made me wonder whether the abstraction is worth its weight. I originally wanted to avoid AST modification, but what if we do a small pass on the filter before it is handed over to an adapter, which would:
This, together with already introduced I’m currently leaning to this solution, but I welcome thoughts (@demmer). |
I haven't looked at the code at all but from what you wrote here I agree that the AST rewrite makes sense. |
OK, going ahead with that approach. |
Right now, adapters which translate static filter expressions into native queries have to walk a non-trivial AST, typically using
ASTVisitor
. This is not a great API, for several reasons:FilterLiteral
).visitXxx
functions throwing exceptions). This is tedious and prone to errors and divergence across adapters.visitBinaryExpression
needs to handle expression-level operatorsAND
andOR
as well as term-level comparison operators like<
.The goal of this issue is to solve these problems by providing adapters a nicer filter processing API.
Proposal
The key insight is that filter expressions coming into the adapter consist of these concepts, building on top of each other:
AND
,OR
andNOT
operatorsFor most adapters, it probably makes sense to handle each of these concepts separately when building a native query.
One way to implement this idea is to provide a class (named e.g.
SimpleFilterCompilerBase
) which the adapters would inherit from and implement methods responsible for translating concepts they support. For unsupported concepts the adapter simply wouldn’t implement the relevant method, which means a default base class implementation which throws a suitable exception would be used.The
SimpleFilterCompilerBase
class can have an interface like this (in TypeScript):Technically, the class would be implemented using a visitor which would call its methods. But this would be invisible to the adapters.
Discusssion
Why not just offer some kind of specialized
ASTVisitor
?Offering a visitor which would provide default implementation of most methods (which would throw an exception or do an obvious thing like descending into a subexpression in cases like
visitExpressionFilterTerm
) is definitely a viable alternative. However this solution would retain two problems solved bySimpleFilterCompilerBase
:AND
/OR
expressions and other binary operators invisitBinaryExpression
visitSimpleFilterTerm
What about preventing conflation by using different AST node types for
AND
/OR
and other operators?If done across all ASTs it would multiply the number of AST node types, which I’d like to avoid. And doing it only for filter ASTs would mean having two different kinds of ASTs, which I’d also like to avoid if possible.
What about getting rid of some of the complexity by simplifying the AST?
It is possible to some degree (e.g. eliminating
FilterLiteral
andExpressionFilterTerm
nodes before passing the AST to adapters) but not everywhere (e.g. we still needSimpleFilterTerm
or something similar to designate FTS). And again, it would mean having two different kinds of ASTs, which I’d like to avoid if possible.Wouldn’t it make more sense to avoid AST nodes in the API?
It is true that
compileField
could just take a string with a name, similarly forcompileFulltextTerm
. AndcompileLiteral
could just take a Javascript representation of the value. In case ofcompileExpressionTerm
,compileAndExpression
,compileOrExpression
, andcompileNotExpression
it would make sense to compile the operands and pass the methods the results.The approach would probably work and it would save some complexity and explicit recursive calls. It would mean AST nodes wouldn’t have to be part of the external API. However, there are quite severe disadvantages:
compileExpressionTerm
would presumably also want to use location info covering some parts of the expression, e.g. when reporting invalid type of one of the operands).expressionFilterTerm
we need to distinguish between field and value operands. In compiled form, these may not be distinguishable (e.g. they can both compile to strings).For these reasons I think the node-based approach makes more sense.
Do we really want to use
switch
statements incompileLiteral
andcompileExpressionTerm
?We can either use
switch
statements, split the handling into separate methods, or follow the AST (like we do in visitors). This is mostly a style choice. The described solution roughly maps the methods to filter expression concepts as described above.One advantage of fine-grained
visitXxx
approach is that the default implementations can just throw an exception and no code is required to make a construct unsupported. But if adapter authors usedefault
clauses in theirswitch
statements, it is easy to achieve a similar result.The text was updated successfully, but these errors were encountered: