-
-
Notifications
You must be signed in to change notification settings - Fork 54
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
feat(devexp): add ability to parse simple storage queries #5776
Changes from 17 commits
7633006
2689bb2
78db72a
024875f
bb3c8c3
60e1295
c72dd2e
3c0598a
5f5c346
c40becd
5cda379
edc5448
1bc0459
e4c59da
64cf7d1
9349313
559e698
422b133
56d1148
477348c
975c298
81aa0dc
5120b4e
6deda59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Optional, Sequence | ||
|
||
from snuba.query.expressions import ( | ||
|
@@ -13,6 +15,28 @@ | |
# verbose to build. | ||
|
||
|
||
class NestedColumn: | ||
"""Usage: | ||
|
||
tags = NestedColumn("tags") | ||
assert tags["some_key"] == SubscriptableReference( | ||
"_snuba_tags[some_key]", | ||
Column("_snuba_tags"), None, "tags"), | ||
Literal(None, "some_key") | ||
) | ||
""" | ||
|
||
def __init__(self, column_name: str) -> None: | ||
self.column_name = column_name | ||
|
||
def __getitem__(self, key: str) -> SubscriptableReference: | ||
return SubscriptableReference( | ||
f"_snuba_{self.column_name}[{key}]", | ||
Column(f"_snuba_{self.column_name}", None, self.column_name), | ||
Literal(None, key), | ||
) | ||
|
||
|
||
def column( | ||
column_name: str, table_name: str | None = None, alias: str | None = None | ||
) -> Column: | ||
|
@@ -23,18 +47,6 @@ def literal(value: OptionalScalarType, alias: str | None = None) -> Literal: | |
return Literal(alias, value) | ||
|
||
|
||
def snuba_tags_raw(indexer_mapping: int) -> SubscriptableReference: | ||
return SubscriptableReference( | ||
f"_snuba_tags_raw[{indexer_mapping}]", | ||
Column( | ||
"_snuba_tags_raw", | ||
None, | ||
"tags_raw", | ||
), | ||
Literal(None, str(indexer_mapping)), | ||
) | ||
|
||
|
||
def literals_tuple(alias: Optional[str], literals: Sequence[Literal]) -> FunctionCall: | ||
return FunctionCall(alias, "tuple", tuple(literals)) | ||
|
||
|
@@ -91,8 +103,12 @@ def binary_condition( | |
return FunctionCall(None, function_name, (lhs, rhs)) | ||
|
||
|
||
def equals(lhs: Expression, rhs: Expression) -> FunctionCall: | ||
return binary_condition("equals", lhs, rhs) | ||
def equals( | ||
lhs: Expression | OptionalScalarType, rhs: Expression | OptionalScalarType | ||
) -> FunctionCall: | ||
Comment on lines
+106
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. happy to change this but I found it useful to not have to specify literals There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i like it |
||
left = lhs if isinstance(lhs, Expression) else Literal(None, lhs) | ||
right = rhs if isinstance(rhs, Expression) else Literal(None, rhs) | ||
return binary_condition("equals", left, right) | ||
|
||
|
||
def and_cond(lhs: FunctionCall, rhs: FunctionCall, *args: FunctionCall) -> FunctionCall: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,14 +2,12 @@ | |
|
||
from typing import Callable, Iterable, Optional, Sequence | ||
|
||
from snuba.query import LimitBy, OrderBy | ||
from snuba.query import ProcessableQuery as AbstractQuery | ||
from snuba.query import SelectedExpression | ||
from snuba.query.data_source.simple import Entity | ||
from snuba.query import LimitBy, OrderBy, ProcessableQuery, SelectedExpression | ||
from snuba.query.data_source.simple import Entity, Storage | ||
from snuba.query.expressions import Expression, ExpressionVisitor | ||
|
||
|
||
class Query(AbstractQuery[Entity]): | ||
class Query(ProcessableQuery[Entity]): | ||
""" | ||
Represents the logical query during query processing. | ||
This means the query class used between parsing and query translation. | ||
|
@@ -81,3 +79,73 @@ def _transform_expressions_impl( | |
|
||
def _transform_impl(self, visitor: ExpressionVisitor[Expression]) -> None: | ||
pass | ||
|
||
|
||
class StorageQuery(ProcessableQuery[Storage]): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: I think this type can be generalized There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you decide to parse into a StorageQuery rather than a ClickhouseQuery? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. The main thing is that the Ideally what I would do is remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does it matter that Table doesn't resemble Entity? |
||
""" | ||
Represents the logical query during query processing. | ||
This means the query class used between parsing and query translation. | ||
|
||
TODO: This query is supposed to rely on entities as data source. | ||
This will happen in a following step. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
from_clause: Optional[Storage], | ||
selected_columns: Optional[Sequence[SelectedExpression]] = None, | ||
array_join: Optional[Sequence[Expression]] = None, | ||
condition: Optional[Expression] = None, | ||
prewhere: Optional[Expression] = None, | ||
groupby: Optional[Sequence[Expression]] = None, | ||
having: Optional[Expression] = None, | ||
order_by: Optional[Sequence[OrderBy]] = None, | ||
limitby: Optional[LimitBy] = None, | ||
sample: Optional[float] = None, | ||
limit: Optional[int] = None, | ||
offset: int = 0, | ||
totals: bool = False, | ||
granularity: Optional[int] = None, | ||
): | ||
""" | ||
Expects an already parsed query body. | ||
""" | ||
self.__final = False | ||
self.__sample = sample | ||
super().__init__( | ||
from_clause=from_clause, | ||
selected_columns=selected_columns, | ||
array_join=array_join, | ||
condition=condition, | ||
groupby=groupby, | ||
having=having, | ||
order_by=order_by, | ||
limitby=limitby, | ||
limit=limit, | ||
offset=offset, | ||
totals=totals, | ||
granularity=granularity, | ||
) | ||
|
||
def get_final(self) -> bool: | ||
return self.__final | ||
|
||
def set_final(self, final: bool) -> None: | ||
self.__final = final | ||
|
||
def get_sample(self) -> Optional[float]: | ||
return self.__sample | ||
|
||
def _eq_functions(self) -> Sequence[str]: | ||
return tuple(super()._eq_functions()) + ("get_final", "get_sample") | ||
|
||
def _get_expressions_impl(self) -> Iterable[Expression]: | ||
return [] | ||
|
||
def _transform_expressions_impl( | ||
self, func: Callable[[Expression], Expression] | ||
) -> None: | ||
pass | ||
|
||
def _transform_impl(self, visitor: ExpressionVisitor[Expression]) -> None: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was not used