diff options
author | Timothy Flynn <trflynn89@pm.me> | 2021-04-22 10:11:17 -0400 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-04-22 18:08:15 +0200 |
commit | ac0e387bebf8ac3c4eb23ad52ce3b2f9aeb715ed (patch) | |
tree | 379591ccb2c069dcca7eb3d920cff0f00a2ebc8c | |
parent | 9331293e4417e1dc3813396defa2c51793f19570 (diff) | |
download | serenity-ac0e387bebf8ac3c4eb23ad52ce3b2f9aeb715ed.zip |
LibSQL: Parse (most of) SELECT statement
This doesn't yet parse join clauses, windowing functions, or compound
SELECT statements.
-rw-r--r-- | Userland/Libraries/LibSQL/AST.h | 176 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Forward.h | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Parser.cpp | 210 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Parser.h | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp | 164 |
5 files changed, 555 insertions, 7 deletions
diff --git a/Userland/Libraries/LibSQL/AST.h b/Userland/Libraries/LibSQL/AST.h index 677d94d874..fed6eaf0c0 100644 --- a/Userland/Libraries/LibSQL/AST.h +++ b/Userland/Libraries/LibSQL/AST.h @@ -154,6 +154,148 @@ private: Vector<ColumnClause> m_columns; }; +enum class ResultType { + All, + Table, + Expression, +}; + +class ResultColumn : public ASTNode { +public: + ResultColumn() = default; + + explicit ResultColumn(String table_name) + : m_type(ResultType::Table) + , m_table_name(move(table_name)) + { + } + + ResultColumn(NonnullRefPtr<Expression> expression, String column_alias) + : m_type(ResultType::Expression) + , m_expression(move(expression)) + , m_column_alias(move(column_alias)) + { + } + + ResultType type() const { return m_type; } + + bool select_from_table() const { return !m_table_name.is_null(); } + const String& table_name() const { return m_table_name; } + + bool select_from_expression() const { return !m_expression.is_null(); } + const RefPtr<Expression>& expression() const { return m_expression; } + const String& column_alias() const { return m_column_alias; } + +private: + ResultType m_type { ResultType::All }; + + String m_table_name {}; + + RefPtr<Expression> m_expression {}; + String m_column_alias {}; +}; + +class GroupByClause : public ASTNode { +public: + GroupByClause(NonnullRefPtrVector<Expression> group_by_list, RefPtr<Expression> having_clause) + : m_group_by_list(move(group_by_list)) + , m_having_clause(move(having_clause)) + { + VERIFY(!m_group_by_list.is_empty()); + } + + const NonnullRefPtrVector<Expression>& group_by_list() const { return m_group_by_list; } + const RefPtr<Expression>& having_clause() const { return m_having_clause; } + +private: + NonnullRefPtrVector<Expression> m_group_by_list; + RefPtr<Expression> m_having_clause; +}; + +class TableOrSubquery : public ASTNode { +public: + TableOrSubquery() = default; + + TableOrSubquery(String schema_name, String table_name, String table_alias) + : m_is_table(true) + , m_schema_name(move(schema_name)) + , m_table_name(move(table_name)) + , m_table_alias(move(table_alias)) + { + } + + explicit TableOrSubquery(NonnullRefPtrVector<TableOrSubquery> subqueries) + : m_is_subquery(!subqueries.is_empty()) + , m_subqueries(move(subqueries)) + { + } + + bool is_table() const { return m_is_table; } + const String& schema_name() const { return m_schema_name; } + const String& table_name() const { return m_table_name; } + const String& table_alias() const { return m_table_alias; } + + bool is_subquery() const { return m_is_subquery; } + const NonnullRefPtrVector<TableOrSubquery>& subqueries() const { return m_subqueries; } + +private: + bool m_is_table { false }; + String m_schema_name {}; + String m_table_name {}; + String m_table_alias {}; + + bool m_is_subquery { false }; + NonnullRefPtrVector<TableOrSubquery> m_subqueries {}; +}; + +enum class Order { + Ascending, + Descending, +}; + +enum class Nulls { + First, + Last, +}; + +class OrderingTerm : public ASTNode { +public: + OrderingTerm(NonnullRefPtr<Expression> expression, String collation_name, Order order, Nulls nulls) + : m_expression(move(expression)) + , m_collation_name(move(collation_name)) + , m_order(order) + , m_nulls(nulls) + { + } + + const NonnullRefPtr<Expression>& expression() const { return m_expression; } + const String& collation_name() const { return m_collation_name; } + Order order() const { return m_order; } + Nulls nulls() const { return m_nulls; } + +private: + NonnullRefPtr<Expression> m_expression; + String m_collation_name; + Order m_order; + Nulls m_nulls; +}; + +class LimitClause : public ASTNode { +public: + LimitClause(NonnullRefPtr<Expression> limit_expression, RefPtr<Expression> offset_expression) + : m_limit_expression(move(limit_expression)) + , m_offset_expression(move(offset_expression)) + { + } + + const NonnullRefPtr<Expression>& limit_expression() const { return m_limit_expression; } + const RefPtr<Expression>& offset_expression() const { return m_offset_expression; } + +private: + NonnullRefPtr<Expression> m_limit_expression; + RefPtr<Expression> m_offset_expression; +}; + //================================================================================================== // Expressions //================================================================================================== @@ -570,4 +712,38 @@ private: RefPtr<ReturningClause> m_returning_clause; }; +class Select : public Statement { +public: + Select(RefPtr<CommonTableExpressionList> common_table_expression_list, bool select_all, NonnullRefPtrVector<ResultColumn> result_column_list, NonnullRefPtrVector<TableOrSubquery> table_or_subquery_list, RefPtr<Expression> where_clause, RefPtr<GroupByClause> group_by_clause, NonnullRefPtrVector<OrderingTerm> ordering_term_list, RefPtr<LimitClause> limit_clause) + : m_common_table_expression_list(move(common_table_expression_list)) + , m_select_all(move(select_all)) + , m_result_column_list(move(result_column_list)) + , m_table_or_subquery_list(move(table_or_subquery_list)) + , m_where_clause(move(where_clause)) + , m_group_by_clause(move(group_by_clause)) + , m_ordering_term_list(move(ordering_term_list)) + , m_limit_clause(move(limit_clause)) + { + } + + const RefPtr<CommonTableExpressionList>& common_table_expression_list() const { return m_common_table_expression_list; } + bool select_all() const { return m_select_all; } + const NonnullRefPtrVector<ResultColumn>& result_column_list() const { return m_result_column_list; } + const NonnullRefPtrVector<TableOrSubquery>& table_or_subquery_list() const { return m_table_or_subquery_list; } + const RefPtr<Expression>& where_clause() const { return m_where_clause; } + const RefPtr<GroupByClause>& group_by_clause() const { return m_group_by_clause; } + const NonnullRefPtrVector<OrderingTerm>& ordering_term_list() const { return m_ordering_term_list; } + const RefPtr<LimitClause>& limit_clause() const { return m_limit_clause; } + +private: + RefPtr<CommonTableExpressionList> m_common_table_expression_list; + bool m_select_all; + NonnullRefPtrVector<ResultColumn> m_result_column_list; + NonnullRefPtrVector<TableOrSubquery> m_table_or_subquery_list; + RefPtr<Expression> m_where_clause; + RefPtr<GroupByClause> m_group_by_clause; + NonnullRefPtrVector<OrderingTerm> m_ordering_term_list; + RefPtr<LimitClause> m_limit_clause; +}; + } diff --git a/Userland/Libraries/LibSQL/Forward.h b/Userland/Libraries/LibSQL/Forward.h index d0949a5ddc..41104c72b5 100644 --- a/Userland/Libraries/LibSQL/Forward.h +++ b/Userland/Libraries/LibSQL/Forward.h @@ -25,24 +25,30 @@ class DropTable; class ErrorExpression; class ErrorStatement; class Expression; +class GroupByClause; class InChainedExpression; class InTableExpression; class InvertibleNestedDoubleExpression; class InvertibleNestedExpression; class IsExpression; class Lexer; +class LimitClause; class MatchExpression; class NestedDoubleExpression; class NestedExpression; class NullExpression; class NullLiteral; class NumericLiteral; +class OrderingTerm; class Parser; class QualifiedTableName; +class ResultColumn; class ReturningClause; +class Select; class SignedNumber; class Statement; class StringLiteral; +class TableOrSubquery; class Token; class TypeName; class UnaryOperatorExpression; diff --git a/Userland/Libraries/LibSQL/Parser.cpp b/Userland/Libraries/LibSQL/Parser.cpp index 3208ef9f78..79eadfb285 100644 --- a/Userland/Libraries/LibSQL/Parser.cpp +++ b/Userland/Libraries/LibSQL/Parser.cpp @@ -33,8 +33,10 @@ NonnullRefPtr<Statement> Parser::parse_statement() return parse_drop_table_statement(); case TokenType::Delete: return parse_delete_statement({}); + case TokenType::Select: + return parse_select_statement({}); default: - expected("CREATE, DROP, or DELETE"); + expected("CREATE, DROP, DELETE, or SELECT"); return create_ast_node<ErrorStatement>(); } } @@ -44,8 +46,10 @@ NonnullRefPtr<Statement> Parser::parse_statement_with_expression_list(RefPtr<Com switch (m_parser_state.m_token.type()) { case TokenType::Delete: return parse_delete_statement(move(common_table_expression_list)); + case TokenType::Select: + return parse_select_statement(move(common_table_expression_list)); default: - expected("DELETE"); + expected("DELETE or SELECT"); return create_ast_node<ErrorStatement>(); } } @@ -148,6 +152,94 @@ NonnullRefPtr<Delete> Parser::parse_delete_statement(RefPtr<CommonTableExpressio return create_ast_node<Delete>(move(common_table_expression_list), move(qualified_table_name), move(where_clause), move(returning_clause)); } +NonnullRefPtr<Select> Parser::parse_select_statement(RefPtr<CommonTableExpressionList> common_table_expression_list) +{ + // https://sqlite.org/lang_select.html + consume(TokenType::Select); + + bool select_all = !consume_if(TokenType::Distinct); + consume_if(TokenType::All); // ALL is the default, so ignore it if specified. + + NonnullRefPtrVector<ResultColumn> result_column_list; + do { + result_column_list.append(parse_result_column()); + if (!match(TokenType::Comma)) + break; + consume(TokenType::Comma); + } while (!match(TokenType::Eof)); + + NonnullRefPtrVector<TableOrSubquery> table_or_subquery_list; + if (consume_if(TokenType::From)) { + // FIXME: Parse join-clause. + + do { + table_or_subquery_list.append(parse_table_or_subquery()); + if (!match(TokenType::Comma)) + break; + consume(TokenType::Comma); + } while (!match(TokenType::Eof)); + } + + RefPtr<Expression> where_clause; + if (consume_if(TokenType::Where)) + where_clause = parse_expression(); + + RefPtr<GroupByClause> group_by_clause; + if (consume_if(TokenType::Group)) { + consume(TokenType::By); + + NonnullRefPtrVector<Expression> group_by_list; + do { + group_by_list.append(parse_expression()); + if (!match(TokenType::Comma)) + break; + consume(TokenType::Comma); + } while (!match(TokenType::Eof)); + + RefPtr<Expression> having_clause; + if (consume_if(TokenType::Having)) + having_clause = parse_expression(); + + group_by_clause = create_ast_node<GroupByClause>(move(group_by_list), move(having_clause)); + } + + // FIXME: Parse 'WINDOW window-name AS window-defn'. + // FIXME: Parse 'compound-operator'. + + NonnullRefPtrVector<OrderingTerm> ordering_term_list; + if (consume_if(TokenType::Order)) { + consume(TokenType::By); + + do { + ordering_term_list.append(parse_ordering_term()); + if (!match(TokenType::Comma)) + break; + consume(TokenType::Comma); + } while (!match(TokenType::Eof)); + } + + RefPtr<LimitClause> limit_clause; + if (consume_if(TokenType::Limit)) { + auto limit_expression = parse_expression(); + + RefPtr<Expression> offset_expression; + if (consume_if(TokenType::Offset)) { + offset_expression = parse_expression(); + } else { + // Note: The limit clause may instead be definied as "offset-expression, limit-expression", effectively reversing the + // order of the expressions. SQLite notes "this is counter-intuitive" and "to avoid confusion, programmers are strongly + // encouraged to ... avoid using a LIMIT clause with a comma-separated offset." + VERIFY(!consume_if(TokenType::Comma)); + } + + limit_clause = create_ast_node<LimitClause>(move(limit_expression), move(offset_expression)); + } + + consume(TokenType::SemiColon); + + return create_ast_node<Select>(move(common_table_expression_list), select_all, move(result_column_list), move(table_or_subquery_list), move(where_clause), move(group_by_clause), move(ordering_term_list), move(limit_clause)); +} + NonnullRefPtr<CommonTableExpressionList> Parser::parse_common_table_expression_list() { consume(TokenType::With); @@ -297,17 +389,22 @@ Optional<NonnullRefPtr<Expression>> Parser::parse_literal_value_expression() return {}; } -Optional<NonnullRefPtr<Expression>> Parser::parse_column_name_expression() +Optional<NonnullRefPtr<Expression>> Parser::parse_column_name_expression(String with_parsed_identifier, bool with_parsed_period) { - if (!match(TokenType::Identifier)) + if (with_parsed_identifier.is_null() && !match(TokenType::Identifier)) return {}; - String first_identifier = consume(TokenType::Identifier).value(); + String first_identifier; + if (with_parsed_identifier.is_null()) + first_identifier = consume(TokenType::Identifier).value(); + else + first_identifier = move(with_parsed_identifier); + String schema_name; String table_name; String column_name; - if (consume_if(TokenType::Period)) { + if (with_parsed_period || consume_if(TokenType::Period)) { String second_identifier = consume(TokenType::Identifier).value(); if (consume_if(TokenType::Period)) { @@ -741,6 +838,107 @@ NonnullRefPtr<ReturningClause> Parser::parse_returning_clause() return create_ast_node<ReturningClause>(move(columns)); } +NonnullRefPtr<ResultColumn> Parser::parse_result_column() +{ + // https://sqlite.org/syntax/result-column.html + if (consume_if(TokenType::Asterisk)) + return create_ast_node<ResultColumn>(); + + // If we match an identifier now, we don't know whether it is a table-name of the form "table-name.*", or if it is the start of a + // column-name-expression, until we try to parse the asterisk. So if we consume an indentifier and a period, but don't find an + // asterisk, hold onto that information to form a column-name-expression later. + String table_name; + bool parsed_period = false; + + if (match(TokenType::Identifier)) { + table_name = consume().value(); + parsed_period = consume_if(TokenType::Period); + if (parsed_period && consume_if(TokenType::Asterisk)) + return create_ast_node<ResultColumn>(move(table_name)); + } + + auto expression = table_name.is_null() + ? parse_expression() + : static_cast<NonnullRefPtr<Expression>>(*parse_column_name_expression(move(table_name), parsed_period)); + consume_if(TokenType::As); // 'AS' is optional. + + String column_alias; + if (match(TokenType::Identifier)) + column_alias = consume().value(); + + return create_ast_node<ResultColumn>(move(expression), move(column_alias)); +} + +NonnullRefPtr<TableOrSubquery> Parser::parse_table_or_subquery() +{ + // https://sqlite.org/syntax/table-or-subquery.html + if (match(TokenType::Identifier)) { + String schema_or_table_name = consume().value(); + String schema_name; + String table_name; + + if (consume_if(TokenType::Period)) { + schema_name = move(schema_or_table_name); + table_name = consume(TokenType::Identifier).value(); + } else { + table_name = move(schema_or_table_name); + } + + consume_if(TokenType::As); // 'AS' is optional. + + String table_alias; + if (match(TokenType::Identifier)) + table_alias = consume().value(); + + return create_ast_node<TableOrSubquery>(move(schema_name), move(table_name), move(table_alias)); + } + + consume(TokenType::ParenOpen); + // FIXME: Parse join-clause. + + NonnullRefPtrVector<TableOrSubquery> subqueries; + while (!has_errors() && !match(TokenType::Eof)) { + subqueries.append(parse_table_or_subquery()); + if (!match(TokenType::Comma)) + break; + consume(TokenType::Comma); + } + + consume(TokenType::ParenClose); + + return create_ast_node<TableOrSubquery>(move(subqueries)); +} + +NonnullRefPtr<OrderingTerm> Parser::parse_ordering_term() +{ + // https://sqlite.org/syntax/ordering-term.html + auto expression = parse_expression(); + + String collation_name; + if (is<CollateExpression>(*expression)) { + const auto& collate = static_cast<const CollateExpression&>(*expression); + collation_name = collate.collation_name(); + expression = collate.expression(); + } else if (consume_if(TokenType::Collate)) { + collation_name = consume(TokenType::Identifier).value(); + } + + Order order = consume_if(TokenType::Desc) ? Order::Descending : Order::Ascending; + consume_if(TokenType::Asc); // ASC is the default, so ignore it if specified. + + Nulls nulls = order == Order::Ascending ? Nulls::First : Nulls::Last; + if (consume_if(TokenType::Nulls)) { + if (consume_if(TokenType::First)) + nulls = Nulls::First; + else if (consume_if(TokenType::Last)) + nulls = Nulls::Last; + else + expected("FIRST or LAST"); + } + + return create_ast_node<OrderingTerm>(move(expression), move(collation_name), order, nulls); +} + Token Parser::consume() { auto old_token = m_parser_state.m_token; diff --git a/Userland/Libraries/LibSQL/Parser.h b/Userland/Libraries/LibSQL/Parser.h index 1a4796171a..8e0419371c 100644 --- a/Userland/Libraries/LibSQL/Parser.h +++ b/Userland/Libraries/LibSQL/Parser.h @@ -55,13 +55,14 @@ private: NonnullRefPtr<CreateTable> parse_create_table_statement(); NonnullRefPtr<DropTable> parse_drop_table_statement(); NonnullRefPtr<Delete> parse_delete_statement(RefPtr<CommonTableExpressionList>); + NonnullRefPtr<Select> parse_select_statement(RefPtr<CommonTableExpressionList>); NonnullRefPtr<CommonTableExpressionList> parse_common_table_expression_list(); NonnullRefPtr<Expression> parse_primary_expression(); NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression> primary); bool match_secondary_expression() const; Optional<NonnullRefPtr<Expression>> parse_literal_value_expression(); - Optional<NonnullRefPtr<Expression>> parse_column_name_expression(); + Optional<NonnullRefPtr<Expression>> parse_column_name_expression(String with_parsed_identifier = {}, bool with_parsed_period = false); Optional<NonnullRefPtr<Expression>> parse_unary_operator_expression(); Optional<NonnullRefPtr<Expression>> parse_binary_operator_expression(NonnullRefPtr<Expression> lhs); Optional<NonnullRefPtr<Expression>> parse_chained_expression(); @@ -80,6 +81,9 @@ private: NonnullRefPtr<CommonTableExpression> parse_common_table_expression(); NonnullRefPtr<QualifiedTableName> parse_qualified_table_name(); NonnullRefPtr<ReturningClause> parse_returning_clause(); + NonnullRefPtr<ResultColumn> parse_result_column(); + NonnullRefPtr<TableOrSubquery> parse_table_or_subquery(); + NonnullRefPtr<OrderingTerm> parse_ordering_term(); Token consume(); Token consume(TokenType type); diff --git a/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp b/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp index 5dc40b8311..c4c0fe36cb 100644 --- a/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp +++ b/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp @@ -234,4 +234,168 @@ TEST_CASE(delete_) validate("WITH RECURSIVE table AS () DELETE FROM table;", { true, { { "table", {} } } }, {}, "table", {}, false, false, {}); } +TEST_CASE(select) +{ + EXPECT(parse("SELECT").is_error()); + EXPECT(parse("SELECT;").is_error()); + EXPECT(parse("SELECT DISTINCT;").is_error()); + EXPECT(parse("SELECT ALL;").is_error()); + EXPECT(parse("SELECT *").is_error()); + EXPECT(parse("SELECT * FROM;").is_error()); + EXPECT(parse("SELECT table. FROM table;").is_error()); + EXPECT(parse("SELECT * FROM (").is_error()); + EXPECT(parse("SELECT * FROM ()").is_error()); + EXPECT(parse("SELECT * FROM ();").is_error()); + EXPECT(parse("SELECT * FROM (table1)").is_error()); + EXPECT(parse("SELECT * FROM (table1, )").is_error()); + EXPECT(parse("SELECT * FROM (table1, table2)").is_error()); + EXPECT(parse("SELECT * FROM table").is_error()); + EXPECT(parse("SELECT * FROM table WHERE;").is_error()); + EXPECT(parse("SELECT * FROM table WHERE 1 ==1").is_error()); + EXPECT(parse("SELECT * FROM table GROUP;").is_error()); + EXPECT(parse("SELECT * FROM table GROUP BY;").is_error()); + EXPECT(parse("SELECT * FROM table GROUP BY column").is_error()); + EXPECT(parse("SELECT * FROM table ORDER:").is_error()); + EXPECT(parse("SELECT * FROM table ORDER BY column").is_error()); + EXPECT(parse("SELECT * FROM table ORDER BY column COLLATE:").is_error()); + EXPECT(parse("SELECT * FROM table ORDER BY column COLLATE collation").is_error()); + EXPECT(parse("SELECT * FROM table ORDER BY column NULLS;").is_error()); + EXPECT(parse("SELECT * FROM table ORDER BY column NULLS SECOND;").is_error()); + EXPECT(parse("SELECT * FROM table LIMIT;").is_error()); + EXPECT(parse("SELECT * FROM table LIMIT 12").is_error()); + EXPECT(parse("SELECT * FROM table LIMIT 12 OFFSET;").is_error()); + EXPECT(parse("SELECT * FROM table LIMIT 12 OFFSET 15").is_error()); + + struct Type { + SQL::ResultType type; + StringView table_name_or_column_alias {}; + }; + + struct From { + StringView schema_name; + StringView table_name; + StringView table_alias; + }; + + struct Ordering { + String collation_name; + SQL::Order order; + SQL::Nulls nulls; + }; + + auto validate = [](StringView sql, Vector<Type> expected_columns, Vector<From> expected_from_list, bool expect_where_clause, size_t expected_group_by_size, bool expect_having_clause, Vector<Ordering> expected_ordering, bool expect_limit_clause, bool expect_offset_clause) { + auto result = parse(sql); + EXPECT(!result.is_error()); + + auto statement = result.release_value(); + EXPECT(is<SQL::Select>(*statement)); + + const auto& select = static_cast<const SQL::Select&>(*statement); + + const auto& result_column_list = select.result_column_list(); + EXPECT_EQ(result_column_list.size(), expected_columns.size()); + for (size_t i = 0; i < result_column_list.size(); ++i) { + const auto& result_column = result_column_list[i]; + const auto& expected_column = expected_columns[i]; + EXPECT_EQ(result_column.type(), expected_column.type); + + switch (result_column.type()) { + case SQL::ResultType::All: + EXPECT(expected_column.table_name_or_column_alias.is_null()); + break; + case SQL::ResultType::Table: + EXPECT_EQ(result_column.table_name(), expected_column.table_name_or_column_alias); + break; + case SQL::ResultType::Expression: + EXPECT_EQ(result_column.column_alias(), expected_column.table_name_or_column_alias); + break; + } + } + + const auto& table_or_subquery_list = select.table_or_subquery_list(); + EXPECT_EQ(table_or_subquery_list.size(), expected_from_list.size()); + for (size_t i = 0; i < table_or_subquery_list.size(); ++i) { + const auto& result_from = table_or_subquery_list[i]; + const auto& expected_from = expected_from_list[i]; + EXPECT_EQ(result_from.schema_name(), expected_from.schema_name); + EXPECT_EQ(result_from.table_name(), expected_from.table_name); + EXPECT_EQ(result_from.table_alias(), expected_from.table_alias); + } + + const auto& where_clause = select.where_clause(); + EXPECT_EQ(where_clause.is_null(), !expect_where_clause); + if (where_clause) + EXPECT(!is<SQL::ErrorExpression>(*where_clause)); + + const auto& group_by_clause = select.group_by_clause(); + EXPECT_EQ(group_by_clause.is_null(), (expected_group_by_size == 0)); + if (group_by_clause) { + const auto& group_by_list = group_by_clause->group_by_list(); + EXPECT_EQ(group_by_list.size(), expected_group_by_size); + for (size_t i = 0; i < group_by_list.size(); ++i) + EXPECT(!is<SQL::ErrorExpression>(group_by_list[i])); + + const auto& having_clause = group_by_clause->having_clause(); + EXPECT_EQ(having_clause.is_null(), !expect_having_clause); + if (having_clause) + EXPECT(!is<SQL::ErrorExpression>(*having_clause)); + } + + const auto& ordering_term_list = select.ordering_term_list(); + EXPECT_EQ(ordering_term_list.size(), expected_ordering.size()); + for (size_t i = 0; i < ordering_term_list.size(); ++i) { + const auto& result_order = ordering_term_list[i]; + const auto& expected_order = expected_ordering[i]; + EXPECT(!is<SQL::ErrorExpression>(*result_order.expression())); + EXPECT_EQ(result_order.collation_name(), expected_order.collation_name); + EXPECT_EQ(result_order.order(), expected_order.order); + EXPECT_EQ(result_order.nulls(), expected_order.nulls); + } + + const auto& limit_clause = select.limit_clause(); + EXPECT_EQ(limit_clause.is_null(), !expect_limit_clause); + if (limit_clause) { + const auto& limit_expression = limit_clause->limit_expression(); + EXPECT(!is<SQL::ErrorExpression>(*limit_expression)); + + const auto& offset_expression = limit_clause->offset_expression(); + EXPECT_EQ(offset_expression.is_null(), !expect_offset_clause); + if (offset_expression) + EXPECT(!is<SQL::ErrorExpression>(*offset_expression)); + } + }; + + Vector<Type> all { { SQL::ResultType::All } }; + Vector<From> from { { {}, "table", {} } }; + + validate("SELECT * FROM table;", { { SQL::ResultType::All } }, from, false, 0, false, {}, false, false); + validate("SELECT table.* FROM table;", { { SQL::ResultType::Table, "table" } }, from, false, 0, false, {}, false, false); + validate("SELECT column AS alias FROM table;", { { SQL::ResultType::Expression, "alias" } }, from, false, 0, false, {}, false, false); + validate("SELECT table.column AS alias FROM table;", { { SQL::ResultType::Expression, "alias" } }, from, false, 0, false, {}, false, false); + validate("SELECT schema.table.column AS alias FROM table;", { { SQL::ResultType::Expression, "alias" } }, from, false, 0, false, {}, false, false); + validate("SELECT column AS alias, *, table.* FROM table;", { { SQL::ResultType::Expression, "alias" }, { SQL::ResultType::All }, { SQL::ResultType::Table, "table" } }, from, false, 0, false, {}, false, false); + + validate("SELECT * FROM table;", all, { { {}, "table", {} } }, false, 0, false, {}, false, false); + validate("SELECT * FROM schema.table;", all, { { "schema", "table", {} } }, false, 0, false, {}, false, false); + validate("SELECT * FROM schema.table AS alias;", all, { { "schema", "table", "alias" } }, false, 0, false, {}, false, false); + validate("SELECT * FROM schema.table AS alias, table2, table3 AS table4;", all, { { "schema", "table", "alias" }, { {}, "table2", {} }, { {}, "table3", "table4" } }, false, 0, false, {}, false, false); + + validate("SELECT * FROM table WHERE column IS NOT NULL;", all, from, true, 0, false, {}, false, false); + + validate("SELECT * FROM table GROUP BY column;", all, from, false, 1, false, {}, false, false); + validate("SELECT * FROM table GROUP BY column1, column2, column3;", all, from, false, 3, false, {}, false, false); + validate("SELECT * FROM table GROUP BY column HAVING 'abc';", all, from, false, 1, true, {}, false, false); + + validate("SELECT * FROM table ORDER BY column;", all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::First } }, false, false); + validate("SELECT * FROM table ORDER BY column COLLATE collation;", all, from, false, 0, false, { { "collation", SQL::Order::Ascending, SQL::Nulls::First } }, false, false); + validate("SELECT * FROM table ORDER BY column ASC;", all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::First } }, false, false); + validate("SELECT * FROM table ORDER BY column DESC;", all, from, false, 0, false, { { {}, SQL::Order::Descending, SQL::Nulls::Last } }, false, false); + validate("SELECT * FROM table ORDER BY column ASC NULLS LAST;", all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::Last } }, false, false); + validate("SELECT * FROM table ORDER BY column DESC NULLS FIRST;", all, from, false, 0, false, { { {}, SQL::Order::Descending, SQL::Nulls::First } }, false, false); + validate("SELECT * FROM table ORDER BY column1, column2 DESC, column3 NULLS LAST;", all, from, false, 0, false, { { {}, SQL::Order::Ascending, SQL::Nulls::First }, { {}, SQL::Order::Descending, SQL::Nulls::Last }, { {}, SQL::Order::Ascending, SQL::Nulls::Last } }, false, false); + + validate("SELECT * FROM table LIMIT 15;", all, from, false, 0, false, {}, true, false); + validate("SELECT * FROM table LIMIT 15 OFFSET 16;", all, from, false, 0, false, {}, true, true); +} + TEST_MAIN(SqlStatementParser) |