From 3c98fe1bae224dde700328559b1d73444fe4be11 Mon Sep 17 00:00:00 2001 From: Pedro Date: Thu, 18 Dec 2014 12:11:26 +0100 Subject: [PATCH] added having --- Makefile | 4 ++-- src/lib/Expr.h | 1 + src/lib/Table.h | 9 +++---- src/lib/statements/SelectStatement.h | 19 ++++++++++++++- src/lib/statements/destruct.cpp | 16 +++++++++++++ src/parser/bison_parser.y | 15 +++++++++--- src/sql_grammar_test.cpp | 1 - src/sql_tests.cpp | 13 +++------- src/tests/helper.h | 20 ++++++---------- src/tests/select.cpp | 36 +++++++++++++++++++++++----- test/valid_queries.sql | 1 + 11 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 src/lib/statements/destruct.cpp diff --git a/Makefile b/Makefile index 3257350..c3492b5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -test: +test: FORCE @echo "Compiling..." @make clean -C src/ >/dev/null || exit 1 @make tests -C src/ >/dev/null || exit 1 @@ -14,4 +14,4 @@ docs: FORCE doxygen docs/doxy.conf -FORCE: \ No newline at end of file +FORCE: diff --git a/src/lib/Expr.h b/src/lib/Expr.h index 2cd2e17..709521b 100644 --- a/src/lib/Expr.h +++ b/src/lib/Expr.h @@ -85,6 +85,7 @@ struct Expr { * Convenience accessor methods */ inline bool isType(ExprType e_type) { return e_type == type; } + inline bool isLiteral() { return isType(kExprLiteralInt) || isType(kExprLiteralFloat) || isType(kExprLiteralString) || isType(kExprPlaceholder); } inline bool hasAlias() { return alias != NULL; } inline bool hasTable() { return table != NULL; } inline char* getName() { diff --git a/src/lib/Table.h b/src/lib/Table.h index 2e7e66b..c34d3b9 100644 --- a/src/lib/Table.h +++ b/src/lib/Table.h @@ -1,6 +1,8 @@ #ifndef __TABLEREF_H__ #define __TABLEREF_H__ +#include "List.h" +#include "Expr.h" #include namespace hsql { @@ -36,12 +38,7 @@ struct TableRef { list(NULL), join(NULL) {} - virtual ~TableRef() { - delete name; - delete alias; - delete select; - delete list; - } + virtual ~TableRef(); TableRefType type; diff --git a/src/lib/statements/SelectStatement.h b/src/lib/statements/SelectStatement.h index bb6e011..008618d 100644 --- a/src/lib/statements/SelectStatement.h +++ b/src/lib/statements/SelectStatement.h @@ -49,6 +49,23 @@ struct LimitDescription { int64_t offset; }; +/** + * @struct GroupByDescription + */ +struct GroupByDescription { + GroupByDescription() : + columns(NULL), + having(NULL) {} + + ~GroupByDescription() { + delete columns; + delete having; + } + + List* columns; + Expr* having; +}; + /** * @struct SelectStatement * @brief Representation of a full select statement. @@ -78,7 +95,7 @@ struct SelectStatement : SQLStatement { TableRef* from_table; List* select_list; Expr* where_clause; - List* group_by; + GroupByDescription* group_by; SelectStatement* union_select; OrderDescription* order; diff --git a/src/lib/statements/destruct.cpp b/src/lib/statements/destruct.cpp new file mode 100644 index 0000000..73d1a7e --- /dev/null +++ b/src/lib/statements/destruct.cpp @@ -0,0 +1,16 @@ + +#include "Table.h" +#include "SelectStatement.h" + +namespace hsql { + + +TableRef::~TableRef() { + delete name; + delete alias; + delete select; + delete list; +} + + +} // namespace hsql \ No newline at end of file diff --git a/src/parser/bison_parser.y b/src/parser/bison_parser.y index c845575..ffb58f7 100644 --- a/src/parser/bison_parser.y +++ b/src/parser/bison_parser.y @@ -118,6 +118,7 @@ int yyerror(YYLTYPE* llocp, SQLStatementList** result, yyscan_t scanner, const c hsql::OrderType order_type; hsql::LimitDescription* limit; hsql::ColumnDefinition* column_t; + hsql::GroupByDescription* group_t; hsql::UpdateClause* update_t; hsql::SQLStatementList* stmt_list; @@ -176,8 +177,8 @@ int yyerror(YYLTYPE* llocp, SQLStatementList** result, yyscan_t scanner, const c %type join_clause join_table table_ref_name_no_alias %type expr scalar_expr unary_expr binary_expr function_expr star_expr expr_alias placeholder_expr %type column_name literal int_literal num_literal string_literal -%type comp_expr opt_where join_condition -%type expr_list opt_group select_list literal_list +%type comp_expr opt_where join_condition opt_having +%type expr_list select_list literal_list %type table_ref_commalist %type opt_order %type opt_limit @@ -187,6 +188,7 @@ int yyerror(YYLTYPE* llocp, SQLStatementList** result, yyscan_t scanner, const c %type column_def_commalist %type update_clause %type update_clause_commalist +%type opt_group /****************************** ** Token Precedence and Associativity @@ -480,10 +482,17 @@ opt_where: // TODO: having opt_group: - GROUP BY expr_list { $$ = $3; } + GROUP BY expr_list opt_having { + $$ = new GroupByDescription(); + $$->columns = $3; + $$->having = $4; + } | /* empty */ { $$ = NULL; } ; +opt_having: + HAVING expr { $$ = $2; } + | /* empty */ { $$ = NULL; } opt_order: ORDER BY expr opt_order_type { $$ = new OrderDescription($4, $3); } diff --git a/src/sql_grammar_test.cpp b/src/sql_grammar_test.cpp index bfc4b24..63acdd8 100644 --- a/src/sql_grammar_test.cpp +++ b/src/sql_grammar_test.cpp @@ -25,7 +25,6 @@ std::vector readlines(std::string path) { return lines; } - #define STREQ(a, b) (std::string(a).compare(std::string(b)) == 0) int main(int argc, char *argv[]) { diff --git a/src/sql_tests.cpp b/src/sql_tests.cpp index 3645aa5..52885b2 100644 --- a/src/sql_tests.cpp +++ b/src/sql_tests.cpp @@ -3,19 +3,12 @@ */ #include "tests/test.h" +#include "tests/helper.h" #include "SQLParser.h" #include "sqlhelper.h" using namespace hsql; -#define PARSE_SINGLE_SQL(query, stmt_type, stmt_class, output_var) \ - SQLStatementList* stmt_list = SQLParser::parseSQLString(query); \ - ASSERT(stmt_list->isValid); \ - ASSERT_EQ(stmt_list->size(), 1); \ - ASSERT_EQ(stmt_list->at(0)->type(), stmt_type); \ - stmt_class* output_var = (stmt_class*) stmt_list->at(0); - - TEST(DeleteStatementTest) { SQLStatementList* stmt_list = SQLParser::parseSQLString("DELETE FROM students WHERE grade > 2.0;"); @@ -105,7 +98,7 @@ TEST(DropTableStatementTest) { TEST(PrepareStatementTest) { - PARSE_SINGLE_SQL("PREPARE test: SELECT ?, test FROM t2 WHERE c1 = ?;", kStmtPrepare, PrepareStatement, prep_stmt); + TEST_PARSE_SINGLE_SQL("PREPARE test: SELECT ?, test FROM t2 WHERE c1 = ?;", kStmtPrepare, PrepareStatement, prep_stmt); ASSERT_EQ(prep_stmt->stmt->type(), kStmtSelect); ASSERT_STREQ(prep_stmt->name, "test"); @@ -120,7 +113,7 @@ TEST(PrepareStatementTest) { TEST(ExecuteStatementTest) { - PARSE_SINGLE_SQL("EXECUTE test(1, 2);", kStmtExecute, ExecuteStatement, stmt); + TEST_PARSE_SINGLE_SQL("EXECUTE test(1, 2);", kStmtExecute, ExecuteStatement, stmt); ASSERT_STREQ(stmt->name, "test"); ASSERT_EQ(stmt->parameters->size(), 2); diff --git a/src/tests/helper.h b/src/tests/helper.h index 89e3bca..cc56a50 100644 --- a/src/tests/helper.h +++ b/src/tests/helper.h @@ -2,20 +2,14 @@ #define __HELPER_H__ -std::vector readlines(std::string path) { - std::ifstream infile(path); - std::vector lines; - std::string line; - while (std::getline(infile, line)) { - std::istringstream iss(line); - // Skip comments - if (line[0] != '#') { - lines.push_back(line); - } - } - return lines; -} +#define TEST_PARSE_SINGLE_SQL(query, stmt_type, stmt_class, output_var) \ + SQLStatementList* stmt_list = SQLParser::parseSQLString(query); \ + ASSERT(stmt_list->isValid); \ + ASSERT_EQ(stmt_list->size(), 1); \ + ASSERT_EQ(stmt_list->at(0)->type(), stmt_type); \ + stmt_class* output_var = (stmt_class*) stmt_list->at(0); + #endif \ No newline at end of file diff --git a/src/tests/select.cpp b/src/tests/select.cpp index 35b4cd2..74a71bb 100644 --- a/src/tests/select.cpp +++ b/src/tests/select.cpp @@ -1,15 +1,39 @@ + #include "test.h" +#include "helper.h" #include "SQLParser.h" using namespace hsql; -TEST(Select) { - SQLStatementList* stmt_list = SQLParser::parseSQLString("SELECT * FROM students;"); - ASSERT(stmt_list->isValid); - ASSERT_EQ(stmt_list->size(), 1); - ASSERT_EQ(stmt_list->at(0)->type(), kStmtSelect); +TEST(SelectTest) { + TEST_PARSE_SINGLE_SQL("SELECT * FROM students;", kStmtSelect, SelectStatement, stmt); - SelectStatement* stmt = (SelectStatement*) stmt_list->at(0); + ASSERT_NULL(stmt->where_clause); + ASSERT_NULL(stmt->group_by); +} + + +TEST(SelectHavingTest) { + TEST_PARSE_SINGLE_SQL("SELECT city, AVG(grade) AS avg_grade FROM students GROUP BY city HAVING AVG(grade) < 2.0", kStmtSelect, SelectStatement, stmt); + + GroupByDescription* group = stmt->group_by; + ASSERT_NOTNULL(group); + ASSERT_EQ(group->columns->size(), 1); + ASSERT(group->having->isSimpleOp('<')); + ASSERT(group->having->expr->isType(kExprFunctionRef)); + ASSERT(group->having->expr2->isType(kExprLiteralFloat)); +} + + +TEST(SelectDistinctTest) { + TEST_PARSE_SINGLE_SQL("SELECT DISTINCT grade, city FROM students;", kStmtSelect, SelectStatement, stmt); ASSERT_NULL(stmt->where_clause); } + +TEST(SelectGroupDistinctTest) { + TEST_PARSE_SINGLE_SQL("SELECT city, COUNT(DISTINCT name), SUM(DISTINCT grade) FROM students GROUP BY city;", kStmtSelect, SelectStatement, stmt); + ASSERT_NULL(stmt->where_clause); +} + + diff --git a/test/valid_queries.sql b/test/valid_queries.sql index 156ae5b..2ffbacd 100644 --- a/test/valid_queries.sql +++ b/test/valid_queries.sql @@ -10,6 +10,7 @@ SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY col1; # JOIN SELECT t1.a, t1.b, t2.c FROM "table" AS t1 JOIN (SELECT * FROM foo JOIN bar ON foo.id = bar.id) t2 ON t1.a = t2.b WHERE (t1.b OR NOT t1.a) AND t2.c = 12.5 SELECT * FROM t1 JOIN t2 ON c1 = c2; +SELECT a, SUM(b) FROM t2 GROUP BY a HAVING SUM(b) > 100; # CREATE statement CREATE TABLE "table" FROM TBL FILE 'students.tbl' CREATE TABLE IF NOT EXISTS "table" FROM TBL FILE 'students.tbl'