From b7828e698e1db7a05bbf6d5e1eb04e7b7dd546de Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 7 Mar 2017 14:55:51 +0100 Subject: [PATCH] implement CASE WHEN expressions --- src/parser/bison_parser.y | 19 ++++++++++++------- src/parser/flex_lexer.l | 12 +++++------- src/parser/sql_keywords.txt | 5 +++++ src/sql/Expr.cpp | 10 ++++++++++ src/sql/Expr.h | 3 +++ test/queries/tpc-h-08.sql | 2 +- test/select_tests.cpp | 30 ++++++++++++++++++++++++++++-- 7 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/parser/bison_parser.y b/src/parser/bison_parser.y index edd8132..68bbedd 100644 --- a/src/parser/bison_parser.y +++ b/src/parser/bison_parser.y @@ -169,12 +169,11 @@ int yyerror(YYLTYPE* llocp, SQLParserResult** result, yyscan_t scanner, const ch %token INSERT ISNULL OFFSET RENAME SCHEMA SELECT SORTED %token TABLES UNIQUE UNLOAD UPDATE VALUES AFTER ALTER CROSS %token DELTA GROUP INDEX INNER LIMIT LOCAL MERGE MINUS ORDER -%token OUTER RIGHT TABLE UNION USING WHERE CALL DATE DESC -%token DROP FILE FROM FULL HASH HINT INTO JOIN LEFT LIKE -%token LOAD NULL PART PLAN SHOW TEXT TIME VIEW WITH ADD ALL -%token AND ASC CSV FOR INT KEY NOT OFF SET TBL TOP AS BY IF -%token IN IS OF ON OR TO - +%token OUTER RIGHT TABLE UNION USING WHERE CALL CASE DATE +%token DESC DROP ELSE FILE FROM FULL HASH HINT INTO JOIN +%token LEFT LIKE LOAD NULL PART PLAN SHOW TEXT THEN TIME +%token VIEW WHEN WITH ADD ALL AND ASC CSV END FOR INT KEY +%token NOT OFF SET TBL TOP AS BY IF IN IS OF ON OR TO /********************************* ** Non-Terminal types (http://www.gnu.org/software/bison/manual/html_node/Type-Decl.html) @@ -198,7 +197,7 @@ int yyerror(YYLTYPE* llocp, SQLParserResult** result, yyscan_t scanner, const ch %type expr operand scalar_expr unary_expr binary_expr logic_expr exists_expr %type function_expr between_expr star_expr expr_alias placeholder_expr %type column_name literal int_literal num_literal string_literal -%type comp_expr opt_where join_condition opt_having +%type comp_expr opt_where join_condition opt_having case_expr %type opt_limit opt_top %type order_desc %type opt_order_type @@ -606,6 +605,7 @@ expr: | between_expr | logic_expr | exists_expr + | case_expr ; operand: @@ -645,6 +645,11 @@ logic_expr: | expr OR expr { $$ = Expr::makeOpBinary($1, Expr::OR, $3); } ; +// TODO: allow no else specified +case_expr: + CASE WHEN operand THEN operand ELSE operand END { $$ = Expr::makeCase($3, $5, $7); } + ; + exists_expr: EXISTS '(' select_no_paren ')' { $$ = Expr::makeExists($3); } ; diff --git a/src/parser/flex_lexer.l b/src/parser/flex_lexer.l index 13e1054..cde49ac 100644 --- a/src/parser/flex_lexer.l +++ b/src/parser/flex_lexer.l @@ -54,11 +54,8 @@ [^\n]* /* skipping comment content until a end of line is read */; \n BEGIN(INITIAL); - [ \t\n]+ /* skip whitespace */; - - DEALLOCATE TOKEN(DEALLOCATE) PARAMETERS TOKEN(PARAMETERS) INTERSECT TOKEN(INTERSECT) @@ -127,9 +124,11 @@ UNION TOKEN(UNION) USING TOKEN(USING) WHERE TOKEN(WHERE) CALL TOKEN(CALL) +CASE TOKEN(CASE) DATE TOKEN(DATE) DESC TOKEN(DESC) DROP TOKEN(DROP) +ELSE TOKEN(ELSE) FILE TOKEN(FILE) FROM TOKEN(FROM) FULL TOKEN(FULL) @@ -145,14 +144,17 @@ PART TOKEN(PART) PLAN TOKEN(PLAN) SHOW TOKEN(SHOW) TEXT TOKEN(TEXT) +THEN TOKEN(THEN) TIME TOKEN(TIME) VIEW TOKEN(VIEW) +WHEN TOKEN(WHEN) WITH TOKEN(WITH) ADD TOKEN(ADD) ALL TOKEN(ALL) AND TOKEN(AND) ASC TOKEN(ASC) CSV TOKEN(CSV) +END TOKEN(END) FOR TOKEN(FOR) INT TOKEN(INT) KEY TOKEN(KEY) @@ -171,15 +173,12 @@ ON TOKEN(ON) OR TOKEN(OR) TO TOKEN(TO) - "<>" TOKEN(NOTEQUALS) "<=" TOKEN(LESSEQ) ">=" TOKEN(GREATEREQ) - [-+*/(){},.;<>=^%:?] { return yytext[0]; } - [0-9]+"."[0-9]* | "."[0-9]* { yylval->fval = atof(yytext); @@ -202,7 +201,6 @@ TO TOKEN(TO) return SQL_IDENTIFIER; } - '[^'\n]*' { // Crop the leading and trailing quote char yylval->sval = hsql::substr(yytext, 1, strlen(yytext)-1); diff --git a/src/parser/sql_keywords.txt b/src/parser/sql_keywords.txt index 2209fcf..092b4c2 100644 --- a/src/parser/sql_keywords.txt +++ b/src/parser/sql_keywords.txt @@ -140,6 +140,11 @@ IS ISNULL BETWEEN ESCAPE +CASE +WHEN +THEN +ELSE +END // With WITH diff --git a/src/sql/Expr.cpp b/src/sql/Expr.cpp index bd2f265..3557a61 100644 --- a/src/sql/Expr.cpp +++ b/src/sql/Expr.cpp @@ -57,6 +57,16 @@ namespace hsql { return e; } + Expr* Expr::makeCase(Expr* expr, Expr* then, Expr* other) { + Expr* e = new Expr(kExprOperator); + e->expr = expr; + e->opType = CASE; + e->exprList = new std::vector(); + e->exprList->push_back(then); + e->exprList->push_back(other); + return e; + } + Expr* Expr::makeLiteral(int64_t val) { Expr* e = new Expr(kExprLiteralInt); e->ival = val; diff --git a/src/sql/Expr.h b/src/sql/Expr.h index d9dac70..48cbdbd 100644 --- a/src/sql/Expr.h +++ b/src/sql/Expr.h @@ -38,6 +38,7 @@ namespace hsql { // Ternary operators BETWEEN, + CASE, // Binary operators. SIMPLE_OP, @@ -111,6 +112,8 @@ namespace hsql { static Expr* makeBetween(Expr* expr, Expr* left, Expr* right); + static Expr* makeCase(Expr* expr, Expr* then, Expr* other); + static Expr* makeLiteral(int64_t val); static Expr* makeLiteral(double val); diff --git a/test/queries/tpc-h-08.sql b/test/queries/tpc-h-08.sql index 7c0ff24..77f8c64 100644 --- a/test/queries/tpc-h-08.sql +++ b/test/queries/tpc-h-08.sql @@ -1,7 +1,7 @@ -- http://www.sqlserver-dba.com/2011/09/this-is-a-followup-on-my-earlier-post-of-sql-server-test-data-generation-testing-tools-i-had-some-requests-for-my-set-up-pr.html SELECT O_YEAR, SUM(CASE WHEN NATION = 'BRAZIL' THEN VOLUME ELSE 0 END)/SUM(VOLUME) AS MKT_SHARE FROM (SELECT datepart(yy,O_ORDERDATE) AS O_YEAR, L_EXTENDEDPRICE*(1-L_DISCOUNT) AS VOLUME, N2.N_NAME AS NATION - FROM PART, SUPPLIER, LINEITEM, ORDERS, CUSTOMER, NATION N1, NATION N2, REGION + FROM "PART", SUPPLIER, LINEITEM, ORDERS, CUSTOMER, NATION N1, NATION N2, REGION WHERE P_PARTKEY = L_PARTKEY AND S_SUPPKEY = L_SUPPKEY AND L_ORDERKEY = O_ORDERKEY AND O_CUSTKEY = C_CUSTKEY AND C_NATIONKEY = N1.N_NATIONKEY AND N1.N_REGIONKEY = R_REGIONKEY AND R_NAME = 'AMERICA' AND S_NATIONKEY = N2.N_NATIONKEY diff --git a/test/select_tests.cpp b/test/select_tests.cpp index f6a9e10..7a0ddd3 100644 --- a/test/select_tests.cpp +++ b/test/select_tests.cpp @@ -184,7 +184,7 @@ TEST(SelectConditionalSelectTest) { SelectStatement* select2 = cond1->expr2->select; ASSERT_NOTNULL(select2); - ASSERT_STREQ(select2->fromTable->getName(), "tt") + ASSERT_STREQ(select2->fromTable->getName(), "tt"); // EXISTS (SELECT ...) @@ -193,7 +193,33 @@ TEST(SelectConditionalSelectTest) { ASSERT_NOTNULL(cond2->select); SelectStatement* ex_select = cond2->select; - ASSERT_STREQ(ex_select->fromTable->getName(), "test") + ASSERT_STREQ(ex_select->fromTable->getName(), "test"); + + delete result; +} + +TEST(SelectCaseWhen) { + TEST_PARSE_SINGLE_SQL( + "SELECT MAX(CASE WHEN a = 'foo' THEN x ELSE 0 END) FROM test;", + kStmtSelect, + SelectStatement, + result, + stmt); + + ASSERT_EQ(stmt->selectList->size(), 1); + Expr* func = stmt->selectList->at(0); + + ASSERT_NOTNULL(func); + ASSERT(func->isType(kExprFunctionRef)); + ASSERT_EQ(func->exprList->size(), 1); + + Expr* caseExpr = func->exprList->at(0); + ASSERT_NOTNULL(caseExpr); + ASSERT(caseExpr->isType(kExprOperator)); + ASSERT_EQ(caseExpr->opType, Expr::CASE); + ASSERT(caseExpr->expr->isType(kExprOperator)); + ASSERT(caseExpr->expr->isSimpleOp('=')); + ASSERT_EQ(caseExpr->exprList->size(), 2); delete result; }