Skip to content

Commit

Permalink
Added the ability to use individual date range filters in BI queries
Browse files Browse the repository at this point in the history
  • Loading branch information
Gilbert09 committed Jul 26, 2024
1 parent eedd19a commit da96776
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 18 deletions.
8 changes: 4 additions & 4 deletions hogql_parser/HogQLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ void hogqlparserParserInitialize() {
1265,5,105,0,0,1262,1265,3,152,76,0,1263,1265,3,154,77,0,1264,1261,1,
0,0,0,1264,1262,1,0,0,0,1264,1263,1,0,0,0,1265,161,1,0,0,0,1266,1267,
3,166,83,0,1267,1268,5,122,0,0,1268,1269,3,148,74,0,1269,163,1,0,0,0,
1270,1271,5,128,0,0,1271,1272,3,160,80,0,1272,1273,5,147,0,0,1273,165,
1270,1271,5,128,0,0,1271,1272,3,134,67,0,1272,1273,5,147,0,0,1273,165,
1,0,0,0,1274,1277,5,110,0,0,1275,1277,3,168,84,0,1276,1274,1,0,0,0,1276,
1275,1,0,0,0,1277,167,1,0,0,0,1278,1282,5,142,0,0,1279,1281,3,170,85,
0,1280,1279,1,0,0,0,1281,1284,1,0,0,0,1282,1280,1,0,0,0,1282,1283,1,0,
Expand Down Expand Up @@ -11733,8 +11733,8 @@ tree::TerminalNode* HogQLParser::PlaceholderContext::LBRACE() {
return getToken(HogQLParser::LBRACE, 0);
}

HogQLParser::IdentifierContext* HogQLParser::PlaceholderContext::identifier() {
return getRuleContext<HogQLParser::IdentifierContext>(0);
HogQLParser::NestedIdentifierContext* HogQLParser::PlaceholderContext::nestedIdentifier() {
return getRuleContext<HogQLParser::NestedIdentifierContext>(0);
}

tree::TerminalNode* HogQLParser::PlaceholderContext::RBRACE() {
Expand Down Expand Up @@ -11770,7 +11770,7 @@ HogQLParser::PlaceholderContext* HogQLParser::placeholder() {
setState(1270);
match(HogQLParser::LBRACE);
setState(1271);
identifier();
nestedIdentifier();
setState(1272);
match(HogQLParser::RBRACE);

Expand Down
2 changes: 1 addition & 1 deletion hogql_parser/HogQLParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -2411,7 +2411,7 @@ class HogQLParser : public antlr4::Parser {
PlaceholderContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
antlr4::tree::TerminalNode *LBRACE();
IdentifierContext *identifier();
NestedIdentifierContext *nestedIdentifier();
antlr4::tree::TerminalNode *RBRACE();


Expand Down
2 changes: 1 addition & 1 deletion hogql_parser/HogQLParser.interp

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion hogql_parser/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include <Python.h>
#include <boost/algorithm/string.hpp>
#include <string>
#include <sstream>
#include <iostream>
#include <iterator>

#include "HogQLLexer.h"
#include "HogQLParser.h"
Expand Down Expand Up @@ -2554,7 +2557,15 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
}

VISIT(Placeholder) {
string name = visitAsString(ctx->identifier());
auto nested_identifier_ctx = ctx->nestedIdentifier();
vector<string> nested =
nested_identifier_ctx ? any_cast<vector<string>>(visit(nested_identifier_ctx)) : vector<string>();

std::ostringstream os;
std::copy(nested.begin(), nested.end() - 1, std::ostream_iterator<std::string>(os, "."));
os << nested.back();
string name = os.str();

RETURN_NEW_AST_NODE("Placeholder", "{s:s#}", "field", name.data(), name.size());
}

Expand Down
2 changes: 1 addition & 1 deletion hogql_parser/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

setup(
name="hogql_parser",
version="1.0.32",
version="1.0.33",
url="https://github.com/PostHog/posthog/tree/master/hogql_parser",
author="PostHog Inc.",
author_email="[email protected]",
Expand Down
56 changes: 56 additions & 0 deletions posthog/hogql/filters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Optional, TypeVar

import dataclasses
from dateutil.parser import isoparse

from posthog.hogql import ast
Expand All @@ -14,6 +15,12 @@
T = TypeVar("T", bound=ast.Expr)


@dataclasses.dataclass
class CompareOperationWrapper:
compare_operation: ast.CompareOperation
skip: bool = False


def replace_filters(node: T, filters: Optional[HogQLFilters], team: Team) -> T:
return ReplaceFilters(filters, team).visit(node)

Expand All @@ -24,13 +31,26 @@ def __init__(self, filters: Optional[HogQLFilters], team: Team = None):
self.filters = filters
self.team = team
self.selects: list[ast.SelectQuery] = []
self.compare_operations: list[CompareOperationWrapper] = []

def visit_select_query(self, node):
self.selects.append(node)
node = super().visit_select_query(node)
self.selects.pop()
return node

def visit_compare_operation(self, node):
self.compare_operations.append(CompareOperationWrapper(compare_operation=node, skip=False))
node = super().visit_compare_operation(node)
compare_wrapper = self.compare_operations.pop()
if compare_wrapper.skip:
return ast.CompareOperation(
left=ast.Constant(value=True),
op=ast.CompareOperationOp.Eq,
right=ast.Constant(value=True),
)
return node

def visit_placeholder(self, node):
if node.field == "filters":
if self.filters is None:
Expand Down Expand Up @@ -111,4 +131,40 @@ def visit_placeholder(self, node):
if len(exprs) == 1:
return exprs[0]
return ast.And(exprs=exprs)
if node.field == "filters.dateRange.dateFrom":
compare_op_wrapper = self.compare_operations[-1]

if self.filters is None:
compare_op_wrapper.skip = True
return ast.Constant(value=True)

dateFrom = self.filters.dateRange.date_from if self.filters.dateRange else None
if dateFrom is not None and dateFrom != "all":
try:
parsed_date = isoparse(dateFrom).replace(tzinfo=self.team.timezone_info)
except ValueError:
parsed_date = relative_date_parse(dateFrom, self.team.timezone_info)

return ast.Constant(value=parsed_date)
else:
compare_op_wrapper.skip = True
return ast.Constant(value=True)
if node.field == "filters.dateRange.dateTo":
compare_op_wrapper = self.compare_operations[-1]

if self.filters is None:
compare_op_wrapper.skip = True
return ast.Constant(value=True)

dateTo = self.filters.dateRange.date_to if self.filters.dateRange else None
if dateTo is not None:
try:
parsed_date = isoparse(dateTo).replace(tzinfo=self.team.timezone_info)
except ValueError:
parsed_date = relative_date_parse(dateTo, self.team.timezone_info)
return ast.Constant(value=parsed_date)
else:
compare_op_wrapper.skip = True
return ast.Constant(value=True)

return super().visit_placeholder(node)
2 changes: 1 addition & 1 deletion posthog/hogql/grammar/HogQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ keywordForAlias
alias: IDENTIFIER | keywordForAlias; // |interval| can't be an alias, otherwise 'INTERVAL 1 SOMETHING' becomes ambiguous.
identifier: IDENTIFIER | interval | keyword;
enumValue: string EQ_SINGLE numberLiteral;
placeholder: LBRACE identifier RBRACE;
placeholder: LBRACE nestedIdentifier RBRACE;

string: STRING_LITERAL | templateString;
templateString : QUOTE_SINGLE_TEMPLATE stringContents* QUOTE_SINGLE ;
Expand Down
2 changes: 1 addition & 1 deletion posthog/hogql/grammar/HogQLParser.interp

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions posthog/hogql/grammar/HogQLParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ def serializedATN():
76,0,1263,1265,3,154,77,0,1264,1261,1,0,0,0,1264,1262,1,0,0,0,1264,
1263,1,0,0,0,1265,161,1,0,0,0,1266,1267,3,166,83,0,1267,1268,5,122,
0,0,1268,1269,3,148,74,0,1269,163,1,0,0,0,1270,1271,5,128,0,0,1271,
1272,3,160,80,0,1272,1273,5,147,0,0,1273,165,1,0,0,0,1274,1277,5,
1272,3,134,67,0,1272,1273,5,147,0,0,1273,165,1,0,0,0,1274,1277,5,
110,0,0,1275,1277,3,168,84,0,1276,1274,1,0,0,0,1276,1275,1,0,0,0,
1277,167,1,0,0,0,1278,1282,5,142,0,0,1279,1281,3,170,85,0,1280,1279,
1,0,0,0,1281,1284,1,0,0,0,1282,1280,1,0,0,0,1282,1283,1,0,0,0,1283,
Expand Down Expand Up @@ -9725,8 +9725,8 @@ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
def LBRACE(self):
return self.getToken(HogQLParser.LBRACE, 0)

def identifier(self):
return self.getTypedRuleContext(HogQLParser.IdentifierContext,0)
def nestedIdentifier(self):
return self.getTypedRuleContext(HogQLParser.NestedIdentifierContext,0)


def RBRACE(self):
Expand All @@ -9753,7 +9753,7 @@ def placeholder(self):
self.state = 1270
self.match(HogQLParser.LBRACE)
self.state = 1271
self.identifier()
self.nestedIdentifier()
self.state = 1272
self.match(HogQLParser.RBRACE)
except RecognitionException as re:
Expand Down
4 changes: 2 additions & 2 deletions posthog/hogql/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,8 +1120,8 @@ def visitHogqlxTagAttribute(self, ctx: HogQLParser.HogqlxTagAttributeContext):
return ast.HogQLXAttribute(name=name, value=ast.Constant(value=True))

def visitPlaceholder(self, ctx: HogQLParser.PlaceholderContext):
name = self.visit(ctx.identifier())
return ast.Placeholder(field=name)
nested = self.visit(ctx.nestedIdentifier()) if ctx.nestedIdentifier() else []
return ast.Placeholder(field=".".join(identifier for identifier in nested))

def visitColumnExprTemplateString(self, ctx: HogQLParser.ColumnExprTemplateStringContext):
return self.visit(ctx.templateString())
Expand Down
14 changes: 12 additions & 2 deletions posthog/hogql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,19 @@ def execute_hogql_query(
raise ValueError(
f"Query contains 'filters' placeholder, yet filters are also provided as a standalone query parameter."
)
if "filters" in placeholders_in_query:
if "filters" in placeholders_in_query or any(
placeholder.startswith("filters.") for placeholder in placeholders_in_query
):
select_query = replace_filters(select_query, filters, team)
placeholders_in_query.remove("filters")

removed_placeholders: list[str] = []
for placeholder in placeholders_in_query:
if placeholder == "filters":
removed_placeholders.append(placeholder)
elif placeholder.startswith("filters."):
removed_placeholders.append(placeholder)

placeholders_in_query = [n for n in placeholders_in_query if n not in removed_placeholders]

if len(placeholders_in_query) > 0:
if len(placeholders) == 0:
Expand Down

0 comments on commit da96776

Please sign in to comment.