0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-24 09:14:46 +01:00

feat(hog): remove semicolons, require "as" for column aliases (#22868)

This commit is contained in:
Marius Andra 2024-06-12 11:46:11 +02:00 committed by GitHub
parent f324ec6fbc
commit 779b17f46e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 3226 additions and 3254 deletions

File diff suppressed because it is too large Load Diff

View File

@ -45,8 +45,8 @@ public:
enum {
RuleProgram = 0, RuleDeclaration = 1, RuleExpression = 2, RuleVarDecl = 3,
RuleVarAssignment = 4, RuleIdentifierList = 5, RuleStatement = 6, RuleExprStmt = 7,
RuleIfStmt = 8, RuleWhileStmt = 9, RuleReturnStmt = 10, RuleFuncStmt = 11,
RuleIdentifierList = 4, RuleStatement = 5, RuleReturnStmt = 6, RuleIfStmt = 7,
RuleWhileStmt = 8, RuleFuncStmt = 9, RuleVarAssignment = 10, RuleExprStmt = 11,
RuleEmptyStmt = 12, RuleBlock = 13, RuleKvPair = 14, RuleKvPairList = 15,
RuleSelect = 16, RuleSelectUnionStmt = 17, RuleSelectStmtWithParens = 18,
RuleSelectStmt = 19, RuleWithClause = 20, RuleTopClause = 21, RuleFromClause = 22,
@ -92,14 +92,14 @@ public:
class DeclarationContext;
class ExpressionContext;
class VarDeclContext;
class VarAssignmentContext;
class IdentifierListContext;
class StatementContext;
class ExprStmtContext;
class ReturnStmtContext;
class IfStmtContext;
class WhileStmtContext;
class ReturnStmtContext;
class FuncStmtContext;
class VarAssignmentContext;
class ExprStmtContext;
class EmptyStmtContext;
class BlockContext;
class KvPairContext;
@ -220,7 +220,6 @@ public:
virtual size_t getRuleIndex() const override;
antlr4::tree::TerminalNode *LET();
IdentifierContext *identifier();
antlr4::tree::TerminalNode *SEMICOLON();
antlr4::tree::TerminalNode *COLON();
antlr4::tree::TerminalNode *EQ_SINGLE();
ExpressionContext *expression();
@ -232,23 +231,6 @@ public:
VarDeclContext* varDecl();
class VarAssignmentContext : public antlr4::ParserRuleContext {
public:
VarAssignmentContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
std::vector<ExpressionContext *> expression();
ExpressionContext* expression(size_t i);
antlr4::tree::TerminalNode *COLON();
antlr4::tree::TerminalNode *EQ_SINGLE();
antlr4::tree::TerminalNode *SEMICOLON();
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
};
VarAssignmentContext* varAssignment();
class IdentifierListContext : public antlr4::ParserRuleContext {
public:
IdentifierListContext(antlr4::ParserRuleContext *parent, size_t invokingState);
@ -270,12 +252,12 @@ public:
StatementContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
ReturnStmtContext *returnStmt();
EmptyStmtContext *emptyStmt();
ExprStmtContext *exprStmt();
IfStmtContext *ifStmt();
WhileStmtContext *whileStmt();
FuncStmtContext *funcStmt();
VarAssignmentContext *varAssignment();
ExprStmtContext *exprStmt();
EmptyStmtContext *emptyStmt();
BlockContext *block();
@ -285,10 +267,11 @@ public:
StatementContext* statement();
class ExprStmtContext : public antlr4::ParserRuleContext {
class ReturnStmtContext : public antlr4::ParserRuleContext {
public:
ExprStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
ReturnStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
antlr4::tree::TerminalNode *RETURN();
ExpressionContext *expression();
antlr4::tree::TerminalNode *SEMICOLON();
@ -297,7 +280,7 @@ public:
};
ExprStmtContext* exprStmt();
ReturnStmtContext* returnStmt();
class IfStmtContext : public antlr4::ParserRuleContext {
public:
@ -327,20 +310,6 @@ public:
ExpressionContext *expression();
antlr4::tree::TerminalNode *RPAREN();
StatementContext *statement();
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
};
WhileStmtContext* whileStmt();
class ReturnStmtContext : public antlr4::ParserRuleContext {
public:
ReturnStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
antlr4::tree::TerminalNode *RETURN();
ExpressionContext *expression();
antlr4::tree::TerminalNode *SEMICOLON();
@ -348,7 +317,7 @@ public:
};
ReturnStmtContext* returnStmt();
WhileStmtContext* whileStmt();
class FuncStmtContext : public antlr4::ParserRuleContext {
public:
@ -368,6 +337,36 @@ public:
FuncStmtContext* funcStmt();
class VarAssignmentContext : public antlr4::ParserRuleContext {
public:
VarAssignmentContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
std::vector<ExpressionContext *> expression();
ExpressionContext* expression(size_t i);
antlr4::tree::TerminalNode *COLON();
antlr4::tree::TerminalNode *EQ_SINGLE();
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
};
VarAssignmentContext* varAssignment();
class ExprStmtContext : public antlr4::ParserRuleContext {
public:
ExprStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
ExpressionContext *expression();
antlr4::tree::TerminalNode *SEMICOLON();
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
};
ExprStmtContext* exprStmt();
class EmptyStmtContext : public antlr4::ParserRuleContext {
public:
EmptyStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
@ -1246,7 +1245,6 @@ public:
ColumnExprAliasContext(ColumnExprContext *ctx);
ColumnExprContext *columnExpr();
AliasContext *alias();
antlr4::tree::TerminalNode *AS();
IdentifierContext *identifier();
antlr4::tree::TerminalNode *STRING_LITERAL();

File diff suppressed because one or more lines are too long

View File

@ -31,10 +31,6 @@ public:
return visitChildren(ctx);
}
virtual std::any visitVarAssignment(HogQLParser::VarAssignmentContext *ctx) override {
return visitChildren(ctx);
}
virtual std::any visitIdentifierList(HogQLParser::IdentifierListContext *ctx) override {
return visitChildren(ctx);
}
@ -43,7 +39,7 @@ public:
return visitChildren(ctx);
}
virtual std::any visitExprStmt(HogQLParser::ExprStmtContext *ctx) override {
virtual std::any visitReturnStmt(HogQLParser::ReturnStmtContext *ctx) override {
return visitChildren(ctx);
}
@ -55,11 +51,15 @@ public:
return visitChildren(ctx);
}
virtual std::any visitReturnStmt(HogQLParser::ReturnStmtContext *ctx) override {
virtual std::any visitFuncStmt(HogQLParser::FuncStmtContext *ctx) override {
return visitChildren(ctx);
}
virtual std::any visitFuncStmt(HogQLParser::FuncStmtContext *ctx) override {
virtual std::any visitVarAssignment(HogQLParser::VarAssignmentContext *ctx) override {
return visitChildren(ctx);
}
virtual std::any visitExprStmt(HogQLParser::ExprStmtContext *ctx) override {
return visitChildren(ctx);
}

View File

@ -27,22 +27,22 @@ public:
virtual std::any visitVarDecl(HogQLParser::VarDeclContext *context) = 0;
virtual std::any visitVarAssignment(HogQLParser::VarAssignmentContext *context) = 0;
virtual std::any visitIdentifierList(HogQLParser::IdentifierListContext *context) = 0;
virtual std::any visitStatement(HogQLParser::StatementContext *context) = 0;
virtual std::any visitExprStmt(HogQLParser::ExprStmtContext *context) = 0;
virtual std::any visitReturnStmt(HogQLParser::ReturnStmtContext *context) = 0;
virtual std::any visitIfStmt(HogQLParser::IfStmtContext *context) = 0;
virtual std::any visitWhileStmt(HogQLParser::WhileStmtContext *context) = 0;
virtual std::any visitReturnStmt(HogQLParser::ReturnStmtContext *context) = 0;
virtual std::any visitFuncStmt(HogQLParser::FuncStmtContext *context) = 0;
virtual std::any visitVarAssignment(HogQLParser::VarAssignmentContext *context) = 0;
virtual std::any visitExprStmt(HogQLParser::ExprStmtContext *context) = 0;
virtual std::any visitEmptyStmt(HogQLParser::EmptyStmtContext *context) = 0;
virtual std::any visitBlock(HogQLParser::BlockContext *context) = 0;

View File

@ -1059,9 +1059,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
VISIT(ColumnExprAlias) {
string alias;
if (ctx->alias()) {
alias = visitAsString(ctx->alias());
} else if (ctx->identifier()) {
if (ctx->identifier()) {
alias = visitAsString(ctx->identifier());
} else if (ctx->STRING_LITERAL()) {
alias = parse_string_literal_ctx(ctx->STRING_LITERAL());

View File

@ -1,10 +1,9 @@
["_h", 32, "-- test functions --", 2, "print", 1, 35, 41, "add", 2, 6, 36, 0, 36, 1, 6, 38, 41, "add2", 2, 9, 36, 0, 36,
1, 6, 36, 2, 38, 35, 41, "mult", 2, 6, 36, 0, 36, 1, 8, 38, 41, "noArgs", 0, 12, 32, "basdfasdf", 33, 3, 33, 2, 6, 36,
1, 38, 35, 35, 41, "empty", 0, 2, 31, 38, 41, "empty2", 0, 4, 29, 35, 31, 38, 41, "empty3", 0, 8, 29, 35, 29, 35, 29,
35, 31, 38, 41, "noReturn", 0, 14, 33, 1, 33, 2, 36, 1, 36, 0, 6, 31, 38, 35, 35, 35, 33, 4, 33, 3, 2, "add", 2, 2,
"print", 1, 35, 33, 1, 33, 1, 2, "add", 2, 33, 100, 33, 4, 33, 3, 2, "add", 2, 6, 6, 2, "print", 1, 35, 33, -1, 2,
"noArgs", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, -1, 2, "empty", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, -1, 2,
"empty2", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, -1, 2, "empty3", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, -1, 2,
"noReturn", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, 2, 33, 1, 33, 2, 2, "add", 2, 33, 100, 33, 4, 33, 3, 2, "add", 2,
6, 6, 2, "mult", 2, 2, "print", 1, 35, 33, 10, 33, 1, 33, 2, 2, "add2", 2, 33, 100, 33, 4, 33, 3, 2, "add2", 2, 6, 6, 2,
"mult", 2, 2, "print", 1, 35]
1, 38, 35, 35, 41, "empty", 0, 2, 31, 38, 41, "empty2", 0, 2, 31, 38, 41, "empty3", 0, 2, 31, 38, 41, "noReturn", 0, 14,
33, 1, 33, 2, 36, 1, 36, 0, 6, 31, 38, 35, 35, 35, 33, 4, 33, 3, 2, "add", 2, 2, "print", 1, 35, 33, 1, 33, 1, 2, "add",
2, 33, 100, 33, 4, 33, 3, 2, "add", 2, 6, 6, 2, "print", 1, 35, 33, -1, 2, "noArgs", 0, 2, "ifNull", 2, 2, "print", 1,
35, 33, -1, 2, "empty", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, -1, 2, "empty2", 0, 2, "ifNull", 2, 2, "print", 1, 35,
33, -1, 2, "empty3", 0, 2, "ifNull", 2, 2, "print", 1, 35, 33, -1, 2, "noReturn", 0, 2, "ifNull", 2, 2, "print", 1, 35,
33, 2, 33, 1, 33, 2, 2, "add", 2, 33, 100, 33, 4, 33, 3, 2, "add", 2, 6, 6, 2, "mult", 2, 2, "print", 1, 35, 33, 10, 33,
1, 33, 2, 2, "add2", 2, 33, 100, 33, 4, 33, 3, 2, "add2", 2, 6, 6, 2, "mult", 2, 2, "print", 1, 35]

View File

@ -1,12 +1,12 @@
print([]);
print([1, 2, 3]);
print([1, '2', 3]);
print([1, [2, 3], 4]);
print([1, [2, [3, 4]], 5]);
print([])
print([1, 2, 3])
print([1, '2', 3])
print([1, [2, 3], 4])
print([1, [2, [3, 4]], 5])
let a := [1, 2, 3];
print(a[1]);
print([1, 2, 3][1]);
print([1, [2, [3, 4]], 5][1][1][1]);
print([1, [2, [3, 4]], 5][1][1][1] + 1);
print([1, [2, [3, 4]], 5].1.1.1);
let a := [1, 2, 3]
print(a[1])
print([1, 2, 3][1])
print([1, [2, [3, 4]], 5][1][1][1])
print([1, [2, [3, 4]], 5][1][1][1] + 1)
print([1, [2, [3, 4]], 5].1.1.1)

View File

@ -1,13 +1,13 @@
print({});
print({'key': 'value'});
print({'key': 'value', 'other': 'thing'});
print({'key': {'otherKey': 'value'}});
print({key: 'value'});
print({})
print({'key': 'value'})
print({'key': 'value', 'other': 'thing'})
print({'key': {'otherKey': 'value'}})
print({key: 'value'})
let key := 3;
print({key: 'value'});
let key := 3
print({key: 'value'})
print({'key': 'value'}.key);
print({'key': 'value'}['key']);
print({'key': {'otherKey': 'value'}}.key.otherKey);
print({'key': {'otherKey': 'value'}}['key'].otherKey);
print({'key': 'value'}.key)
print({'key': 'value'}['key'])
print({'key': {'otherKey': 'value'}}.key.otherKey)
print({'key': {'otherKey': 'value'}}['key'].otherKey)

View File

@ -1,35 +1,35 @@
print('-- test functions --');
print('-- test functions --')
fn add(a, b) {
return a + b;
return a + b
}
fn add2(a, b) {
let c := a + b;
return c;
let c := a + b
return c
}
fn mult(a, b) {
return a * b;
return a * b
}
fn noArgs() {
let url := 'basdfasdf';
let second := 2 + 3;
return second;
let url := 'basdfasdf'
let second := 2 + 3
return second
}
fn empty() {}
fn empty2() {;}
fn empty3() {;;;}
fn empty2() {}
fn empty3() {}
fn noReturn() {
let a := 1;
let b := 2;
let c := a + b;
let a := 1
let b := 2
let c := a + b
}
print(add(3, 4));
print(add(3, 4) + 100 + add(1, 1));
print(noArgs() ?? -1);
print(empty() ?? -1);
print(empty2() ?? -1);
print(empty3() ?? -1);
print(noReturn() ?? -1);
print(mult(add(3, 4) + 100 + add(2, 1), 2));
print(mult(add2(3, 4) + 100 + add2(2, 1), 10));
print(add(3, 4))
print(add(3, 4) + 100 + add(1, 1))
print(noArgs() ?? -1)
print(empty() ?? -1)
print(empty2() ?? -1)
print(empty3() ?? -1)
print(noReturn() ?? -1)
print(mult(add(3, 4) + 100 + add(2, 1), 2))
print(mult(add2(3, 4) + 100 + add2(2, 1), 10))

View File

@ -1,15 +1,15 @@
print('-- test if else --');
print('-- test if else --')
{
if (true) print(1); else print(2);
if (true) print(1); else print(2);
if (false) print(1); else print(2);
if (true) { print(1); } else { print(2); }
if (true) print(1) else print(2)
if (true) print(1) else print(2)
if (false) print(1) else print(2)
if (true) { print(1) } else { print(2) }
let a := true;
let a := true
if (a) {
let a := 3;
print(a + 2);
let a := 3
print(a + 2)
} else {
print(2);
print(2)
}
}

View File

@ -1,8 +1,8 @@
---- Commented out because python and JS add different spaces to their JSON output
-- print(jsonStringify({'$browser': 'Chrome', '$os': 'Windows' }));
-- print(jsonStringify({'$browser': 'Chrome', '$os': 'Windows' }, 3));
-- print(jsonStringify({'$browser': 'Chrome', '$os': 'Windows' }))
-- print(jsonStringify({'$browser': 'Chrome', '$os': 'Windows' }, 3))
print(jsonParse('[1,2,3]'));
print(jsonParse('[1,2,3]'))
let event := {
'event': '$pageview',
@ -10,6 +10,6 @@ let event := {
'$browser': 'Chrome',
'$os': 'Windows'
}
};
let json := jsonStringify(event);
print(jsonParse(json));
}
let json := jsonStringify(event)
print(jsonParse(json))

View File

@ -1,9 +1,9 @@
print('-- test while loop --');
print('-- test while loop --')
{
let i := 0;
let i := 0
while (i < 3) {
i := i + 1;
print(i);
i := i + 1
print(i)
}
print(i);
print(i)
}

View File

@ -1,44 +1,44 @@
fn mandelbrot(re, im, max_iter) {
let z_re := 0.0;
let z_im := 0.0;
let n := 0;
let z_re := 0.0
let z_im := 0.0
let n := 0
while (z_re*z_re + z_im*z_im <= 4 and n < max_iter) {
let temp_re := z_re * z_re - z_im * z_im + re;
let temp_im := 2 * z_re * z_im + im;
z_re := temp_re;
z_im := temp_im;
n := n + 1;
let temp_re := z_re * z_re - z_im * z_im + re
let temp_im := 2 * z_re * z_im + im
z_re := temp_re
z_im := temp_im
n := n + 1
}
if (n == max_iter) {
return ' ';
return ' '
} else {
return '#';
return '#'
}
}
fn main() {
let width := 80;
let height := 24;
let xmin := -2.0;
let xmax := 1.0;
let ymin := -1.0;
let ymax := 1.0;
let max_iter := 30;
let width := 80
let height := 24
let xmin := -2.0
let xmax := 1.0
let ymin := -1.0
let ymax := 1.0
let max_iter := 30
let y := 0;
let y := 0
while(y < height) {
let row := '';
let x := 0;
let row := ''
let x := 0
while (x < width) {
let re := x / width * (xmax - xmin) + xmin;
let im := y / height * (ymax - ymin) + ymin;
let letter := mandelbrot(re, im, max_iter);
row := concat(row, letter);
x := x + 1;
let re := x / width * (xmax - xmin) + xmin
let im := y / height * (ymax - ymin) + ymin
let letter := mandelbrot(re, im, max_iter)
row := concat(row, letter)
x := x + 1
}
print(row);
y := y + 1;
print(row)
y := y + 1
}
}
main();
main()

View File

@ -1,8 +0,0 @@
["_h", 41, "mandelbrot", 3, 93, 34, 0.0, 34, 0.0, 33, 0, 36, 0, 36, 5, 15, 33, 4, 36, 4, 36, 4, 8, 36, 3, 36, 3, 8, 6,
16, 3, 2, 40, 44, 36, 2, 36, 4, 36, 4, 8, 36, 3, 36, 3, 8, 7, 6, 36, 1, 36, 4, 36, 3, 33, 2, 8, 8, 6, 36, 6, 37, 3, 36,
7, 37, 4, 33, 1, 36, 5, 6, 37, 5, 35, 35, 39, -67, 36, 0, 36, 5, 11, 40, 5, 32, " ", 38, 39, 3, 32, "#", 38, 31, 38, 35,
35, 35, 41, "main", 0, 119, 33, 80, 33, 24, 34, -2.0, 34, 1.0, 34, -1.0, 34, 1.0, 33, 30, 33, 0, 36, 1, 36, 7, 15, 40,
86, 32, "", 33, 0, 36, 0, 36, 9, 15, 40, 58, 36, 2, 36, 2, 36, 3, 7, 36, 0, 36, 9, 9, 8, 6, 36, 4, 36, 4, 36, 5, 7, 36,
1, 36, 7, 9, 8, 6, 36, 6, 36, 11, 36, 10, 2, "mandelbrot", 3, 36, 12, 36, 8, 2, "concat", 2, 37, 8, 33, 1, 36, 9, 6, 37,
9, 35, 35, 35, 39, -65, 36, 8, 2, "print", 1, 35, 33, 1, 36, 7, 6, 37, 7, 35, 35, 39, -93, 31, 38, 35, 35, 35, 35, 35,
35, 35, 35, 2, "main", 0, 35]

View File

@ -1,70 +1,70 @@
fn test(val) {
print(jsonStringify(val));
print(jsonStringify(val))
}
print('-- test the most common expressions --');
test(1 + 2); -- 3
test(1 - 2); -- -1
test(3 * 2); -- 6
test(3 / 2); -- 1.5
test(3 % 2); -- 1
test(1 and 2); -- true
test(1 or 0); -- true
test(1 and 0); -- false
test(1 or (0 and 1) or 2); -- true
test((1 and 0) and 1); -- false
test((1 or 2) and (1 or 2)); -- true
test(true); -- true
test(not true); -- false
test(false); -- false
test(null); -- null
test(3.14); -- 3.14
test(1 = 2); -- false
test(1 == 2); -- false
test(1 != 2); -- true
test(1 < 2); -- true
test(1 <= 2); -- true
test(1 > 2); -- false
test(1 >= 2); -- false
test('a' like 'b'); -- false
test('baa' like '%a%'); -- true
test('baa' like '%x%'); -- false
test('baa' ilike '%A%'); -- true
test('baa' ilike '%C%'); -- false
test('a' ilike 'b'); -- false
test('a' not like 'b'); -- true
test('a' not ilike 'b'); -- true
test('a' in 'car'); -- true
test('a' in 'foo'); -- false
test('a' not in 'car'); -- false
test(properties.bla); -- null
test(properties.foo); -- "bar"
test(ifNull(properties.foo, false)); -- "bar"
test(ifNull(properties.nullValue, false)); -- false
test(concat('arg', 'another')); -- 'arganother'
test(concat(1, NULL)); -- '1'
test(concat(true, false)); -- 'truefalse'
test(match('test', 'e.*')); -- true
test(match('test', '^e.*')); -- false
test(match('test', 'x.*')); -- false
test('test' =~ 'e.*'); -- true
test('test' !~ 'e.*'); -- false
test('test' =~ '^e.*'); -- false
test('test' !~ '^e.*'); -- true
test('test' =~ 'x.*'); -- false
test('test' !~ 'x.*'); -- true
test('test' ~* 'EST'); -- true
test('test' =~* 'EST'); -- true
test('test' !~* 'EST'); -- false
test(toString(1)); -- '1'
test(toString(1.5)); -- '1.5'
test(toString(true)); -- 'true'
test(toString(null)); -- 'null'
test(toString('string')); -- 'string'
test(toInt('1')); -- 1
test(toInt('bla')); -- null
test(toFloat('1.2')); -- 1.2
test(toFloat('bla')); -- null
test(toUUID('asd')); -- 'asd'
test(1 == null); -- false
test(1 != null); -- true
print('-- test the most common expressions --')
test(1 + 2) -- 3
test(1 - 2) -- -1
test(3 * 2) -- 6
test(3 / 2) -- 1.5
test(3 % 2) -- 1
test(1 and 2) -- true
test(1 or 0) -- true
test(1 and 0) -- false
test(1 or (0 and 1) or 2) -- true
test((1 and 0) and 1) -- false
test((1 or 2) and (1 or 2)) -- true
test(true) -- true
test(not true) -- false
test(false) -- false
test(null) -- null
test(3.14) -- 3.14
test(1 = 2) -- false
test(1 == 2) -- false
test(1 != 2) -- true
test(1 < 2) -- true
test(1 <= 2) -- true
test(1 > 2) -- false
test(1 >= 2) -- false
test('a' like 'b') -- false
test('baa' like '%a%') -- true
test('baa' like '%x%') -- false
test('baa' ilike '%A%') -- true
test('baa' ilike '%C%') -- false
test('a' ilike 'b') -- false
test('a' not like 'b') -- true
test('a' not ilike 'b') -- true
test('a' in 'car') -- true
test('a' in 'foo') -- false
test('a' not in 'car') -- false
test(properties.bla) -- null
test(properties.foo) -- "bar"
test(ifNull(properties.foo, false)) -- "bar"
test(ifNull(properties.nullValue, false)) -- false
test(concat('arg', 'another')) -- 'arganother'
test(concat(1, NULL)) -- '1'
test(concat(true, false)) -- 'truefalse'
test(match('test', 'e.*')) -- true
test(match('test', '^e.*')) -- false
test(match('test', 'x.*')) -- false
test('test' =~ 'e.*') -- true
test('test' !~ 'e.*') -- false
test('test' =~ '^e.*') -- false
test('test' !~ '^e.*') -- true
test('test' =~ 'x.*') -- false
test('test' !~ 'x.*') -- true
test('test' ~* 'EST') -- true
test('test' =~* 'EST') -- true
test('test' !~* 'EST') -- false
test(toString(1)) -- '1'
test(toString(1.5)) -- '1.5'
test(toString(true)) -- 'true'
test(toString(null)) -- 'null'
test(toString('string')) -- 'string'
test(toInt('1')) -- 1
test(toInt('bla')) -- null
test(toFloat('1.2')) -- 1.2
test(toFloat('bla')) -- null
test(toUUID('asd')) -- 'asd'
test(1 == null) -- false
test(1 != null) -- true

View File

@ -1,49 +1,49 @@
{
let r := [1, 2, {'d': (1, 3, 42, 6)}];
print(r.2.d.1);
let r := [1, 2, {'d': (1, 3, 42, 6)}]
print(r.2.d.1)
}
{
let r := [1, 2, {'d': (1, 3, 42, 6)}];
print(r[2].d[2]);
let r := [1, 2, {'d': (1, 3, 42, 6)}]
print(r[2].d[2])
}
{
let r := [1, 2, {'d': (1, 3, 42, 6)}];
print(r.2['d'][3]);
let r := [1, 2, {'d': (1, 3, 42, 6)}]
print(r.2['d'][3])
}
{
let r := {'d': (1, 3, 42, 6)};
print(r.d.1);
let r := {'d': (1, 3, 42, 6)}
print(r.d.1)
}
{
let r := [1, 2, {'d': [1, 3, 42, 3]}];
r.2.d.2 := 3;
print(r.2.d.2);
let r := [1, 2, {'d': [1, 3, 42, 3]}]
r.2.d.2 := 3
print(r.2.d.2)
}
{
let r := [1, 2, {'d': [1, 3, 42, 3]}];
r[2].d[2] := 3;
print(r[2].d[2]);
let r := [1, 2, {'d': [1, 3, 42, 3]}]
r[2].d[2] := 3
print(r[2].d[2])
}
{
let r := [1, 2, {'d': [1, 3, 42, 3]}];
r[2].c := [666];
print(r[2]);
let r := [1, 2, {'d': [1, 3, 42, 3]}]
r[2].c := [666]
print(r[2])
}
{
let r := [1, 2, {'d': [1, 3, 42, 3]}];
r[2].d[2] := 3;
print(r[2].d);
let r := [1, 2, {'d': [1, 3, 42, 3]}]
r[2].d[2] := 3
print(r[2].d)
}
{
let r := [1, 2, {'d': [1, 3, 42, 3]}];
r.2['d'] := ['a', 'b', 'c', 'd'];
print(r[2].d[2]);
let r := [1, 2, {'d': [1, 3, 42, 3]}]
r.2['d'] := ['a', 'b', 'c', 'd']
print(r[2].d[2])
}
{
let r := [1, 2, {'d': [1, 3, 42, 3]}];
let g := 'd';
r.2[g] := ['a', 'b', 'c', 'd'];
print(r[2].d[2]);
let r := [1, 2, {'d': [1, 3, 42, 3]}]
let g := 'd'
r.2[g] := ['a', 'b', 'c', 'd']
print(r[2].d[2])
}
{
let event := {
@ -52,9 +52,9 @@
'$browser': 'Chrome',
'$os': 'Windows'
}
};
event['properties']['$browser'] := 'Firefox';
print(event);
}
event['properties']['$browser'] := 'Firefox'
print(event)
}
{
let event := {
@ -63,9 +63,9 @@
'$browser': 'Chrome',
'$os': 'Windows'
}
};
event.properties.$browser := 'Firefox';
print(event);
}
event.properties.$browser := 'Firefox'
print(event)
}
{
let event := {
@ -74,7 +74,7 @@
'$browser': 'Chrome',
'$os': 'Windows'
}
};
let config := {};
print(event);
}
let config := {}
print(event)
}

View File

@ -1,4 +1,4 @@
print('-- empty, notEmpty, length, lower, upper, reverse --');
if (empty('') and notEmpty('234')) print(length('123'));
if (lower('Tdd4gh') == 'tdd4gh') print(upper('test'));
print(reverse('spinner'));
print('-- empty, notEmpty, length, lower, upper, reverse --')
if (empty('') and notEmpty('234')) print(length('123'))
if (lower('Tdd4gh') == 'tdd4gh') print(upper('test'))
print(reverse('spinner'))

View File

@ -1,9 +1,9 @@
print((1, 2, 3));
print((1, '2', 3));
print((1, (2, 3), 4));
print((1, (2, (3, 4)), 5));
let a := (1, 2, 3);
print(a[1]);
print((1, (2, (3, 4)), 5)[1][1][1]);
print((1, (2, (3, 4)), 5).1.1.1);
print((1, (2, (3, 4)), 5)[1][1][1] + 1);
print((1, 2, 3))
print((1, '2', 3))
print((1, (2, 3), 4))
print((1, (2, (3, 4)), 5))
let a := (1, 2, 3)
print(a[1])
print((1, (2, (3, 4)), 5)[1][1][1])
print((1, (2, (3, 4)), 5).1.1.1)
print((1, (2, (3, 4)), 5)[1][1][1] + 1)

View File

@ -1,16 +1,16 @@
print('-- test variables --');
print('-- test variables --')
{
let a := 1 + 2;
print(a);
let a := 1 + 2
print(a)
let b := a + 4;
print(b);
let b := a + 4
print(b)
}
print('-- test variable reassignment --');
print('-- test variable reassignment --')
{
let a := 1;
a := a + 3;
a := a * 2;
print(a);
let a := 1
a := a + 3
a := a * 2
print(a)
}

View File

@ -56,6 +56,9 @@ def execute_bytecode(
if next_token() != HOGQL_BYTECODE_IDENTIFIER:
raise HogVMException(f"Invalid bytecode. Must start with '{HOGQL_BYTECODE_IDENTIFIER}'")
if len(bytecode) == 1:
return BytecodeResult(result=None, stdout=stdout, bytecode=bytecode)
def check_timeout():
if time.time() - start_time > timeout and not debug:
raise HogVMException(f"Execution timed out after {timeout} seconds. Performed {ops} ops.")

View File

@ -30,47 +30,14 @@ posthog/hogql/database/schema/numbers.py:0: error: Incompatible types in assignm
posthog/hogql/database/schema/numbers.py:0: note: "Dict" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
posthog/hogql/database/schema/numbers.py:0: note: Consider using "Mapping" instead, which is covariant in the value type
posthog/hogql/ast.py:0: error: Incompatible return value type (got "bool | None", expected "bool") [return-value]
posthog/hogql/visitor.py:0: error: Statement is unreachable [unreachable]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Type | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Type | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Type | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "RatioExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Constant | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "JoinConstraint | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "JoinExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "JoinExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "CTE") [assignment]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "CTE") [assignment]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "CTE") [assignment]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "OrderExpr", variable has type "CTE") [assignment]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "CTE") [assignment]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "WindowExpr", variable has type "CTE") [assignment]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "FieldAliasType", variable has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType") [assignment]
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Type", variable has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType") [assignment]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "WindowFrameExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "WindowFrameExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "WindowExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Constant | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "RatioExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "JoinExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "JoinConstraint | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "SampleExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "JoinExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument "select" to "SelectQuery" has incompatible type "list[Expr] | None"; expected "list[Expr]" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "Expr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "WindowFrameExpr | None"; expected "AST" [arg-type]
posthog/hogql/visitor.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "WindowFrameExpr | None"; expected "AST" [arg-type]
posthog/hogql/resolver_utils.py:0: error: Argument 1 to "lookup_field_by_name" has incompatible type "SelectQueryType | SelectUnionQueryType"; expected "SelectQueryType" [arg-type]
posthog/hogql/parser.py:0: error: Item "None" of "list[Expr] | None" has no attribute "__iter__" (not iterable) [union-attr]
posthog/hogql/parser.py:0: error: "None" has no attribute "text" [attr-defined]
@ -198,21 +165,15 @@ posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "select" [union-attr]
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "group_by" [union-attr]
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "None" of "list[Expr] | Any | None" has no attribute "append" [union-attr]
posthog/hogql/resolver.py:0: error: Argument 1 of "visit" is incompatible with supertype "Visitor"; supertype defines the argument type as "AST" [override]
posthog/hogql/resolver.py:0: error: Argument 1 of "visit" is incompatible with supertype "Visitor"; supertype defines the argument type as "AST | None" [override]
posthog/hogql/resolver.py:0: note: This violates the Liskov substitution principle
posthog/hogql/resolver.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
posthog/hogql/resolver.py:0: error: List comprehension has incompatible type List[SelectQueryType | None]; expected List[SelectQueryType] [misc]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "JoinExpr | None") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "JoinExpr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Need type annotation for "columns_with_visible_alias" (hint: "columns_with_visible_alias: dict[<type>, <type>] = ...") [var-annotated]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Type | None", target has type "Type") [assignment]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Type | None", target has type "Type") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "Expr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "Expr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "Expr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: List comprehension has incompatible type List[Expr]; expected List[OrderExpr] [misc]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "Expr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "Expr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Value expression in dictionary comprehension has incompatible type "Expr"; expected type "WindowExpr" [misc]
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
posthog/hogql/resolver.py:0: error: Argument 2 to "lookup_cte_by_name" has incompatible type "str | int"; expected "str" [arg-type]
@ -229,12 +190,9 @@ posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression
posthog/hogql/resolver.py:0: error: Invalid index type "str | int" for "dict[str, BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType]"; expected type "str" [index]
posthog/hogql/resolver.py:0: error: Argument 1 to "clone_expr" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "JoinExpr | None") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "JoinExpr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
posthog/hogql/resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "join_type" [union-attr]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SampleExpr | None") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "SampleExpr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Visitor" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/resolver.py:0: error: Argument "select_query_type" to "SelectViewType" has incompatible type "SelectQueryType | None"; expected "SelectQueryType | SelectUnionQueryType" [arg-type]
posthog/hogql/resolver.py:0: error: Item "None" of "SelectQuery | SelectUnionQuery | Field | None" has no attribute "type" [union-attr]
posthog/hogql/resolver.py:0: error: Argument "select_query_type" to "SelectQueryAliasType" has incompatible type "Type | Any | None"; expected "SelectQueryType | SelectUnionQueryType" [arg-type]
@ -242,9 +200,7 @@ posthog/hogql/resolver.py:0: error: Item "None" of "SelectQuery | SelectUnionQue
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Type | Any | None", variable has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "append" of "list" has incompatible type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None"; expected "SelectQueryType | SelectUnionQueryType" [arg-type]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "JoinExpr | None") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "JoinExpr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SampleExpr | None") [assignment]
posthog/hogql/resolver.py:0: error: Argument 1 to "visit" of "Resolver" has incompatible type "SampleExpr | None"; expected "Expr" [arg-type]
posthog/hogql/resolver.py:0: error: Argument 2 to "convert_hogqlx_tag" has incompatible type "int | None"; expected "int" [arg-type]
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
posthog/hogql/resolver.py:0: error: Item "None" of "Type | None" has no attribute "resolve_constant_type" [union-attr]
@ -293,10 +249,6 @@ posthog/hogql/printer.py:0: error: Argument 1 to "len" has incompatible type "li
posthog/hogql/printer.py:0: error: Item "None" of "list[Expr] | None" has no attribute "__iter__" (not iterable) [union-attr]
posthog/hogql/printer.py:0: error: Right operand of "and" is never evaluated [unreachable]
posthog/hogql/printer.py:0: error: Subclass of "TableType" and "LazyTableType" cannot exist: would have incompatible method signatures [unreachable]
posthog/hogql/printer.py:0: error: Argument 1 to "visit" of "_Printer" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/printer.py:0: error: Argument 1 to "visit" of "_Printer" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/printer.py:0: error: Argument 1 to "visit" of "_Printer" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/printer.py:0: error: Argument 1 to "visit" of "_Printer" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "AST" [arg-type]
posthog/hogql/printer.py:0: error: Argument 1 to "_print_escaped_string" of "_Printer" has incompatible type "int | float | UUID | date | None"; expected "float | int | str | list[Any] | tuple[Any, ...] | datetime | date" [arg-type]
posthog/hogql/printer.py:0: error: Argument 1 to "join" of "str" has incompatible type "list[str | int]"; expected "Iterable[str]" [arg-type]
posthog/hogql/printer.py:0: error: Name "args" already defined on line 0 [no-redef]

View File

@ -160,7 +160,7 @@ class ClickhouseClientTestCase(TestCase, ClickhouseTestMixin):
result = client.get_query_status(self.team.id, query_id)
self.assertTrue(result.error)
assert result.error_message
self.assertRegex(result.error_message, "Unknown table")
self.assertRegex(result.error_message, "no viable alternative at input")
def test_async_query_client_uuid(self):
query = build_query("SELECT toUUID('00000000-0000-0000-0000-000000000000')")

View File

@ -47,7 +47,7 @@ class Statement(Declaration):
@dataclass(kw_only=True)
class ExprStatement(Statement):
expr: Expr
expr: Optional[Expr]
@dataclass(kw_only=True)

View File

@ -51,7 +51,7 @@ class GetNodeAtPositionTraverser(TraversingVisitor):
self.end = end
super().visit(expr)
def visit(self, node: AST):
def visit(self, node: AST | None):
if node is not None and node.start is not None and node.end is not None:
if self.start >= node.start and self.end <= node.end:
self.node = node

View File

@ -227,6 +227,8 @@ class BytecodeBuilder(Visitor):
return response
def visit_expr_statement(self, node: ast.ExprStatement):
if node.expr is None:
return []
response = self.visit(node.expr)
response.append(Operation.POP)
return response

View File

@ -6,32 +6,29 @@ options {
program: declaration* EOF;
declaration
: varDecl
| statement ;
declaration: varDecl | statement ;
expression: columnExpr;
varDecl: LET identifier ( COLON EQ_SINGLE expression )? SEMICOLON ;
varAssignment: expression COLON EQ_SINGLE expression SEMICOLON ;
varDecl: LET identifier ( COLON EQ_SINGLE expression )? ;
identifierList: identifier (COMMA identifier)*;
statement : returnStmt
| emptyStmt
| exprStmt
| ifStmt
| whileStmt
| funcStmt
| varAssignment
| returnStmt
| exprStmt
| emptyStmt
| block ;
exprStmt : expression SEMICOLON ;
ifStmt : IF LPAREN expression RPAREN statement
( ELSE statement )? ;
whileStmt : WHILE LPAREN expression RPAREN statement;
returnStmt : RETURN expression SEMICOLON ;
returnStmt : RETURN expression? SEMICOLON?;
ifStmt : IF LPAREN expression RPAREN statement ( ELSE statement )? ;
whileStmt : WHILE LPAREN expression RPAREN statement SEMICOLON?;
funcStmt : FN identifier LPAREN identifierList? RPAREN block;
varAssignment : expression COLON EQ_SINGLE expression ;
exprStmt : expression SEMICOLON?;
emptyStmt : SEMICOLON ;
block : LBRACE declaration* RBRACE ;
@ -184,8 +181,7 @@ columnExpr
// TODO(ilezhankin): `BETWEEN a AND b AND c` is parsed in a wrong way: `BETWEEN (a AND b) AND c`
| columnExpr NOT? BETWEEN columnExpr AND columnExpr # ColumnExprBetween
| <assoc=right> columnExpr QUERY columnExpr COLON columnExpr # ColumnExprTernaryOp
// Note: difference with ClickHouse: we also support "AS string" as a shortcut for naming columns
| columnExpr (alias | AS identifier | AS STRING_LITERAL) # ColumnExprAlias
| columnExpr (AS identifier | AS STRING_LITERAL) # ColumnExprAlias
| (tableIdentifier DOT)? ASTERISK # ColumnExprAsterisk // single-column only
| LPAREN selectUnionStmt RPAREN # ColumnExprSubquery // single-column only

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -29,11 +29,6 @@ class HogQLParserVisitor(ParseTreeVisitor):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#varAssignment.
def visitVarAssignment(self, ctx:HogQLParser.VarAssignmentContext):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#identifierList.
def visitIdentifierList(self, ctx:HogQLParser.IdentifierListContext):
return self.visitChildren(ctx)
@ -44,8 +39,8 @@ class HogQLParserVisitor(ParseTreeVisitor):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#exprStmt.
def visitExprStmt(self, ctx:HogQLParser.ExprStmtContext):
# Visit a parse tree produced by HogQLParser#returnStmt.
def visitReturnStmt(self, ctx:HogQLParser.ReturnStmtContext):
return self.visitChildren(ctx)
@ -59,13 +54,18 @@ class HogQLParserVisitor(ParseTreeVisitor):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#returnStmt.
def visitReturnStmt(self, ctx:HogQLParser.ReturnStmtContext):
# Visit a parse tree produced by HogQLParser#funcStmt.
def visitFuncStmt(self, ctx:HogQLParser.FuncStmtContext):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#funcStmt.
def visitFuncStmt(self, ctx:HogQLParser.FuncStmtContext):
# Visit a parse tree produced by HogQLParser#varAssignment.
def visitVarAssignment(self, ctx:HogQLParser.VarAssignmentContext):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#exprStmt.
def visitExprStmt(self, ctx:HogQLParser.ExprStmtContext):
return self.visitChildren(ctx)

View File

@ -185,7 +185,12 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
raise e
def visitProgram(self, ctx: HogQLParser.ProgramContext):
return ast.Program(declarations=[self.visit(declaration) for declaration in ctx.declaration()])
declarations: list[ast.Declaration] = []
for declaration in ctx.declaration():
if not declaration.statement() or not declaration.statement().emptyStmt():
statement = self.visit(declaration)
declarations.append(cast(ast.Declaration, statement))
return ast.Program(declarations=declarations)
def visitDeclaration(self, ctx: HogQLParser.DeclarationContext):
return self.visitChildren(ctx)
@ -245,10 +250,15 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
return [ident.getText() for ident in ctx.identifier()]
def visitEmptyStmt(self, ctx: HogQLParser.EmptyStmtContext):
return ast.ExprStatement(expr=ast.Constant(value=True))
return ast.ExprStatement(expr=None)
def visitBlock(self, ctx: HogQLParser.BlockContext):
return ast.Block(declarations=[self.visit(declaration) for declaration in ctx.declaration()])
declarations: list[ast.Declaration] = []
for declaration in ctx.declaration():
if not declaration.statement() or not declaration.statement().emptyStmt():
statement = self.visit(declaration)
declarations.append(cast(ast.Declaration, statement))
return ast.Block(declarations=declarations)
def visitSelect(self, ctx: HogQLParser.SelectContext):
return self.visit(ctx.selectUnionStmt() or ctx.selectStmt() or ctx.hogqlxTagElement())
@ -573,9 +583,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
def visitColumnExprAlias(self, ctx: HogQLParser.ColumnExprAliasContext):
alias: str
if ctx.alias():
alias = self.visit(ctx.alias())
elif ctx.identifier():
if ctx.identifier():
alias = self.visit(ctx.identifier())
elif ctx.STRING_LITERAL():
alias = parse_string_literal_ctx(ctx.STRING_LITERAL())

View File

@ -173,7 +173,9 @@ class _Printer(Visitor):
def indent(self, extra: int = 0):
return " " * self.tab_size * (self._indent + extra)
def visit(self, node: AST):
def visit(self, node: AST | None):
if node is None:
return ""
self.stack.append(node)
self._indent += 1
response = super().visit(node)

View File

@ -110,7 +110,7 @@ class Resolver(CloningVisitor):
self.database = context.database
self.cte_counter = 0
def visit(self, node: ast.Expr) -> ast.Expr:
def visit(self, node: ast.Expr | None) -> ast.Expr:
if isinstance(node, ast.Expr) and node.type is not None:
raise ResolutionError(
f"Type already resolved for {type(node).__name__} ({type(node.type).__name__}). Can't run again."

View File

@ -957,7 +957,7 @@ def parser_test_factory(backend: Literal["python", "cpp"]):
def test_select_array_join(self):
self.assertEqual(
self._select("select a from events ARRAY JOIN [1,2,3] a"),
self._select("select a from events ARRAY JOIN [1,2,3] as a"),
ast.SelectQuery(
select=[ast.Field(chain=["a"])],
select_from=ast.JoinExpr(table=ast.Field(chain=["events"])),
@ -977,7 +977,7 @@ def parser_test_factory(backend: Literal["python", "cpp"]):
),
)
self.assertEqual(
self._select("select a from events INNER ARRAY JOIN [1,2,3] a"),
self._select("select a from events INNER ARRAY JOIN [1,2,3] as a"),
ast.SelectQuery(
select=[ast.Field(chain=["a"])],
select_from=ast.JoinExpr(table=ast.Field(chain=["events"])),
@ -997,7 +997,7 @@ def parser_test_factory(backend: Literal["python", "cpp"]):
),
)
self.assertEqual(
self._select("select 1, b from events LEFT ARRAY JOIN [1,2,3] a, [4,5,6] AS b"),
self._select("select 1, b from events LEFT ARRAY JOIN [1,2,3] as a, [4,5,6] AS b"),
ast.SelectQuery(
select=[ast.Constant(value=1), ast.Field(chain=["b"])],
select_from=ast.JoinExpr(table=ast.Field(chain=["events"])),

View File

@ -243,7 +243,7 @@ class TestPrinter(BaseTest):
)
self._assert_expr_error(
"properties.'no strings'",
"no viable alternative at input '.'no strings'",
"mismatched input",
"hogql",
)
@ -393,10 +393,10 @@ class TestPrinter(BaseTest):
self._assert_expr_error("(", "no viable alternative at input '('")
self._assert_expr_error("())", "no viable alternative at input '()'")
self._assert_expr_error("(3 57", "no viable alternative at input '(3 57'")
self._assert_expr_error("select query from events", "mismatched input 'from' expecting <EOF>")
self._assert_expr_error("this makes little sense", "Unable to resolve field: this")
self._assert_expr_error("select query from events", "mismatched input 'query' expecting <EOF>")
self._assert_expr_error("this makes little sense", "mismatched input 'makes' expecting <EOF>")
self._assert_expr_error("1;2", "mismatched input ';' expecting <EOF>")
self._assert_expr_error("b.a(bla)", "mismatched input '(' expecting '.'")
self._assert_expr_error("b.a(bla)", "mismatched input '(' expecting <EOF>")
def test_logic(self):
self.assertEqual(
@ -504,11 +504,6 @@ class TestPrinter(BaseTest):
self._select("select 1 as `-- select team_id` from events"),
f"SELECT 1 AS `-- select team_id` FROM events WHERE equals(events.team_id, {self.team.pk}) LIMIT {MAX_SELECT_RETURNED_ROWS}",
)
# Some aliases are funny, but that's what the antlr syntax permits, and ClickHouse doesn't complain either
self.assertEqual(
self._expr("event makes little sense"),
"((events.event AS makes) AS little) AS sense",
)
def test_case_when(self):
self.assertEqual(self._expr("case when 1 then 2 else 3 end"), "if(1, 2, 3)")

View File

@ -624,7 +624,7 @@ class TestQuery(ClickhouseTestMixin, APIBaseTest):
self._create_random_events()
# sample pivot table, testing tuple access
query = """
select col_a, arrayZip( (sumMap( g.1, g.2 ) as x).1, x.2) r from (
select col_a, arrayZip( (sumMap( g.1, g.2 ) as x).1, x.2) as r from (
select col_a, groupArray( (col_b, col_c) ) as g from
(
SELECT properties.index as col_a,
@ -894,7 +894,7 @@ class TestQuery(ClickhouseTestMixin, APIBaseTest):
group by col_a
),
PIVOT_FUNCTION_2 AS (
select col_a, arrayZip( (sumMap( g.1, g.2 ) as x).1, x.2) r from
select col_a, arrayZip( (sumMap( g.1, g.2 ) as x).1, x.2) as r from
PIVOT_FUNCTION_1
group by col_a
)
@ -933,7 +933,7 @@ class TestQuery(ClickhouseTestMixin, APIBaseTest):
group by col_a
),
PIVOT_FUNCTION_2 AS (
select col_a, arrayZip( (sumMap( g.1, g.2 ) as x).1, x.2) r from
select col_a, arrayZip( (sumMap( g.1, g.2 ) as x).1, x.2) as r from
PIVOT_FUNCTION_1
group by col_a
),

View File

@ -18,9 +18,9 @@ T = TypeVar("T")
class Visitor(Generic[T]):
def visit(self, node: AST) -> T:
def visit(self, node: AST | None) -> T:
if node is None:
return node
return node # type: ignore
try:
return node.accept(self)
@ -509,7 +509,7 @@ class CloningVisitor(Visitor[Any]):
type=None if self.clear_types else node.type,
ctes={key: self.visit(expr) for key, expr in node.ctes.items()} if node.ctes else None, # to not traverse
select_from=self.visit(node.select_from), # keep "select_from" before "select" to resolve tables first
select=[self.visit(expr) for expr in node.select] if node.select else None,
select=[self.visit(expr) for expr in node.select] if node.select else [],
array_join_op=node.array_join_op,
array_join_list=[self.visit(expr) for expr in node.array_join_list] if node.array_join_list else None,
where=self.visit(node.where),

View File

@ -645,7 +645,7 @@ class FunnelBase(ABC):
exprs: list[ast.Expr] = []
for i in range(max_steps):
exprs.append(parse_expr(f"countIf(steps = {i + 1}) step_{i + 1}"))
exprs.append(parse_expr(f"countIf(steps = {i + 1}) as step_{i + 1}"))
return exprs
@ -686,7 +686,7 @@ class FunnelBase(ABC):
for i in range(0, max_steps):
event_fields = ["latest", *self.extra_event_fields_and_properties]
event_fields_with_step = ", ".join([f"{field}_{i}" for field in event_fields])
event_clause = f"({event_fields_with_step}) as step_{i}_matching_event"
event_clause = f"({event_fields_with_step}) AS step_{i}_matching_event"
events.append(parse_expr(event_clause))
return [*events, *self._get_final_matching_event(max_steps)]
@ -700,8 +700,8 @@ class FunnelBase(ABC):
and self.context.actorsQuery.includeRecordings
):
for i in range(0, max_steps):
exprs.append(parse_expr(f"groupArray(10)(step_{i}_matching_event) as step_{i}_matching_events"))
exprs.append(parse_expr(f"groupArray(10)(final_matching_event) as final_matching_events"))
exprs.append(parse_expr(f"groupArray(10)(step_{i}_matching_event) AS step_{i}_matching_events"))
exprs.append(parse_expr(f"groupArray(10)(final_matching_event) AS final_matching_events"))
return exprs
def _get_step_time_avgs(self, max_steps: int, inner_query: bool = False) -> list[ast.Expr]:
@ -709,9 +709,9 @@ class FunnelBase(ABC):
for i in range(1, max_steps):
exprs.append(
parse_expr(f"avg(step_{i}_conversion_time) step_{i}_average_conversion_time_inner")
parse_expr(f"avg(step_{i}_conversion_time) as step_{i}_average_conversion_time_inner")
if inner_query
else parse_expr(f"avg(step_{i}_average_conversion_time_inner) step_{i}_average_conversion_time")
else parse_expr(f"avg(step_{i}_average_conversion_time_inner) as step_{i}_average_conversion_time")
)
return exprs
@ -721,9 +721,9 @@ class FunnelBase(ABC):
for i in range(1, max_steps):
exprs.append(
parse_expr(f"median(step_{i}_conversion_time) step_{i}_median_conversion_time_inner")
parse_expr(f"median(step_{i}_conversion_time) as step_{i}_median_conversion_time_inner")
if inner_query
else parse_expr(f"median(step_{i}_median_conversion_time_inner) step_{i}_median_conversion_time")
else parse_expr(f"median(step_{i}_median_conversion_time_inner) as step_{i}_median_conversion_time")
)
return exprs
@ -732,7 +732,7 @@ class FunnelBase(ABC):
exprs: list[ast.Expr] = []
for i in range(1, max_steps):
exprs.append(parse_expr(f"groupArray(step_{i}_conversion_time) step_{i}_conversion_time_array"))
exprs.append(parse_expr(f"groupArray(step_{i}_conversion_time) as step_{i}_conversion_time_array"))
return exprs
@ -742,7 +742,7 @@ class FunnelBase(ABC):
for i in range(1, max_steps):
exprs.append(
parse_expr(
f"if(isNaN(avgArray(step_{i}_conversion_time_array) as inter_{i}_conversion), NULL, inter_{i}_conversion) step_{i}_average_conversion_time"
f"if(isNaN(avgArray(step_{i}_conversion_time_array) as inter_{i}_conversion), NULL, inter_{i}_conversion) as step_{i}_average_conversion_time"
)
)
@ -754,7 +754,7 @@ class FunnelBase(ABC):
for i in range(1, max_steps):
exprs.append(
parse_expr(
f"if(isNaN(medianArray(step_{i}_conversion_time_array) as inter_{i}_median), NULL, inter_{i}_median) step_{i}_median_conversion_time"
f"if(isNaN(medianArray(step_{i}_conversion_time_array) as inter_{i}_median), NULL, inter_{i}_median) as step_{i}_median_conversion_time"
)
)
@ -796,8 +796,8 @@ class FunnelBase(ABC):
return (
[ast.Field(chain=[f"latest_{target_step}"]), ast.Field(chain=[f"latest_{target_step - 1}"])],
[
parse_expr(f"argMax(latest_{target_step}, steps) as max_timestamp"),
parse_expr(f"argMax(latest_{target_step - 1}, steps) as min_timestamp"),
parse_expr(f"argMax(latest_{target_step}, steps) AS max_timestamp"),
parse_expr(f"argMax(latest_{target_step - 1}, steps) AS min_timestamp"),
],
)
elif self.context.includeTimestamp:
@ -808,9 +808,9 @@ class FunnelBase(ABC):
ast.Field(chain=[f"latest_{first_step}"]),
],
[
parse_expr(f"argMax(latest_{target_step}, steps) as timestamp"),
parse_expr(f"argMax(latest_{final_step}, steps) as final_timestamp"),
parse_expr(f"argMax(latest_{first_step}, steps) as first_timestamp"),
parse_expr(f"argMax(latest_{target_step}, steps) AS timestamp"),
parse_expr(f"argMax(latest_{final_step}, steps) AS final_timestamp"),
parse_expr(f"argMax(latest_{first_step}, steps) AS first_timestamp"),
],
)
else:
@ -825,7 +825,7 @@ class FunnelBase(ABC):
for i in range(1, max_steps):
exprs.append(
parse_expr(
f"if(isNotNull(latest_{i}) AND latest_{i} <= toTimeZone(latest_{i-1}, 'UTC') + INTERVAL {windowInterval} {windowIntervalUnit}, dateDiff('second', latest_{i - 1}, latest_{i}), NULL) step_{i}_conversion_time"
f"if(isNotNull(latest_{i}) AND latest_{i} <= toTimeZone(latest_{i-1}, 'UTC') + INTERVAL {windowInterval} {windowIntervalUnit}, dateDiff('second', latest_{i - 1}, latest_{i}), NULL) as step_{i}_conversion_time"
),
)
@ -859,14 +859,14 @@ class FunnelBase(ABC):
exprs.append(
parse_expr(
f"min(latest_{i}) over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND {duplicate_event} PRECEDING) latest_{i}"
f"min(latest_{i}) over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND {duplicate_event} PRECEDING) as latest_{i}"
)
)
for field in self.extra_event_fields_and_properties:
exprs.append(
parse_expr(
f'last_value("{field}_{i}") over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND {duplicate_event} PRECEDING) "{field}_{i}"'
f'last_value("{field}_{i}") over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND {duplicate_event} PRECEDING) as "{field}_{i}"'
)
)
@ -875,7 +875,7 @@ class FunnelBase(ABC):
if cast(int, exclusion.funnelFromStep) + 1 == i:
exprs.append(
parse_expr(
f"min(exclusion_{exclusion_id}_latest_{exclusion.funnelFromStep}) over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING) exclusion_{exclusion_id}_latest_{exclusion.funnelFromStep}"
f"min(exclusion_{exclusion_id}_latest_{exclusion.funnelFromStep}) over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING) as exclusion_{exclusion_id}_latest_{exclusion.funnelFromStep}"
)
)

View File

@ -81,14 +81,14 @@ class FunnelStrict(FunnelBase):
else:
exprs.append(
parse_expr(
f"min(latest_{i}) over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN {i} PRECEDING AND {i} PRECEDING) latest_{i}"
f"min(latest_{i}) over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN {i} PRECEDING AND {i} PRECEDING) as latest_{i}"
)
)
for field in self.extra_event_fields_and_properties:
exprs.append(
parse_expr(
f'min("{field}_{i}") over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN {i} PRECEDING AND {i} PRECEDING) "{field}_{i}"'
f'min("{field}_{i}") over (PARTITION by aggregation_target {self._get_breakdown_prop()} ORDER BY timestamp DESC ROWS BETWEEN {i} PRECEDING AND {i} PRECEDING) as "{field}_{i}"'
)
)

View File

@ -132,7 +132,7 @@ class FunnelUnordered(FunnelBase):
for i in range(1, max_steps):
exprs.append(
parse_expr(
f"if(isNotNull(conversion_times[{i+1}]) AND conversion_times[{i+1}] <= toTimeZone(conversion_times[{i}], 'UTC') + INTERVAL {windowInterval} {windowIntervalUnit}, dateDiff('second', conversion_times[{i}], conversion_times[{i+1}]), NULL) step_{i}_conversion_time"
f"if(isNotNull(conversion_times[{i+1}]) AND conversion_times[{i+1}] <= toTimeZone(conversion_times[{i}], 'UTC') + INTERVAL {windowInterval} {windowIntervalUnit}, dateDiff('second', conversion_times[{i}], conversion_times[{i+1}]), NULL) as step_{i}_conversion_time"
)
)
# array indices in ClickHouse are 1-based :shrug: