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 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, 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() const { return m_expression; } + const String& column_alias() const { return m_column_alias; } + +private: + ResultType m_type { ResultType::All }; + + String m_table_name {}; + + RefPtr m_expression {}; + String m_column_alias {}; +}; + +class GroupByClause : public ASTNode { +public: + GroupByClause(NonnullRefPtrVector group_by_list, RefPtr 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& group_by_list() const { return m_group_by_list; } + const RefPtr& having_clause() const { return m_having_clause; } + +private: + NonnullRefPtrVector m_group_by_list; + RefPtr 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 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& 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 m_subqueries {}; +}; + +enum class Order { + Ascending, + Descending, +}; + +enum class Nulls { + First, + Last, +}; + +class OrderingTerm : public ASTNode { +public: + OrderingTerm(NonnullRefPtr 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() 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 m_expression; + String m_collation_name; + Order m_order; + Nulls m_nulls; +}; + +class LimitClause : public ASTNode { +public: + LimitClause(NonnullRefPtr limit_expression, RefPtr offset_expression) + : m_limit_expression(move(limit_expression)) + , m_offset_expression(move(offset_expression)) + { + } + + const NonnullRefPtr& limit_expression() const { return m_limit_expression; } + const RefPtr& offset_expression() const { return m_offset_expression; } + +private: + NonnullRefPtr m_limit_expression; + RefPtr m_offset_expression; +}; + //================================================================================================== // Expressions //================================================================================================== @@ -570,4 +712,38 @@ private: RefPtr m_returning_clause; }; +class Select : public Statement { +public: + Select(RefPtr common_table_expression_list, bool select_all, NonnullRefPtrVector result_column_list, NonnullRefPtrVector table_or_subquery_list, RefPtr where_clause, RefPtr group_by_clause, NonnullRefPtrVector ordering_term_list, RefPtr 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& common_table_expression_list() const { return m_common_table_expression_list; } + bool select_all() const { return m_select_all; } + const NonnullRefPtrVector& result_column_list() const { return m_result_column_list; } + const NonnullRefPtrVector& table_or_subquery_list() const { return m_table_or_subquery_list; } + const RefPtr& where_clause() const { return m_where_clause; } + const RefPtr& group_by_clause() const { return m_group_by_clause; } + const NonnullRefPtrVector& ordering_term_list() const { return m_ordering_term_list; } + const RefPtr& limit_clause() const { return m_limit_clause; } + +private: + RefPtr m_common_table_expression_list; + bool m_select_all; + NonnullRefPtrVector m_result_column_list; + NonnullRefPtrVector m_table_or_subquery_list; + RefPtr m_where_clause; + RefPtr m_group_by_clause; + NonnullRefPtrVector m_ordering_term_list; + RefPtr 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 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(); } } @@ -44,8 +46,10 @@ NonnullRefPtr Parser::parse_statement_with_expression_list(RefPtr(); } } @@ -148,6 +152,94 @@ NonnullRefPtr Parser::parse_delete_statement(RefPtr(move(common_table_expression_list), move(qualified_table_name), move(where_clause), move(returning_clause)); } +NonnullRefPtr(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 Parser::parse_common_table_expression_list() { consume(TokenType::With); @@ -297,17 +389,22 @@ Optional> Parser::parse_literal_value_expression() return {}; } -Optional> Parser::parse_column_name_expression() +Optional> 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 Parser::parse_returning_clause() return create_ast_node(move(columns)); } +NonnullRefPtr Parser::parse_result_column() +{ + // https://sqlite.org/syntax/result-column.html + if (consume_if(TokenType::Asterisk)) + return create_ast_node(); + + // 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(move(table_name)); + } + + auto expression = table_name.is_null() + ? parse_expression() + : static_cast>(*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(move(expression), move(column_alias)); +} + +NonnullRefPtr 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(move(schema_name), move(table_name), move(table_alias)); + } + + consume(TokenType::ParenOpen); + // FIXME: Parse join-clause. + + NonnullRefPtrVector 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(move(subqueries)); +} + +NonnullRefPtr Parser::parse_ordering_term() +{ + // https://sqlite.org/syntax/ordering-term.html + auto expression = parse_expression(); + + String collation_name; + if (is(*expression)) { + const auto& collate = static_cast(*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(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 parse_create_table_statement(); NonnullRefPtr parse_drop_table_statement(); NonnullRefPtr parse_delete_statement(RefPtr); + NonnullRefPtr