summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibSQL
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2021-04-23 15:29:28 -0400
committerAndreas Kling <kling@serenityos.org>2021-04-23 22:36:07 +0200
commit99b38aa3fab3ee545d8467aff3b64565092d497e (patch)
treeaf471c020f19d36c6e5316cc2d39fe6c682e82e4 /Userland/Libraries/LibSQL
parente62e76ca1a346d6910cda96c3b585bffae7d0958 (diff)
downloadserenity-99b38aa3fab3ee545d8467aff3b64565092d497e.zip
LibSQL: Parse EXISTS expressions
The EXISTS expression is a bit of an odd-man-out because it can appear as any of the following forms: EXISTS (select-stmt) NOT EXISTS (select-stmt) (select-stmt) Which makes it the only keyword expression that doesn't require its keyword to actually be used. The consequence is that we might come across an EXISTS expression while parsing another expression type; NOT would have triggered a unary operator expression, and an opening parentheses would have triggered an expression chain.
Diffstat (limited to 'Userland/Libraries/LibSQL')
-rw-r--r--Userland/Libraries/LibSQL/AST.h16
-rw-r--r--Userland/Libraries/LibSQL/Parser.cpp35
-rw-r--r--Userland/Libraries/LibSQL/Parser.h1
-rw-r--r--Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp33
4 files changed, 80 insertions, 5 deletions
diff --git a/Userland/Libraries/LibSQL/AST.h b/Userland/Libraries/LibSQL/AST.h
index 922432b48a..96f0e970dc 100644
--- a/Userland/Libraries/LibSQL/AST.h
+++ b/Userland/Libraries/LibSQL/AST.h
@@ -537,6 +537,22 @@ private:
RefPtr<Expression> m_else_expression;
};
+class ExistsExpression : public Expression {
+public:
+ ExistsExpression(NonnullRefPtr<Select> select_statement, bool invert_expression)
+ : m_select_statement(move(select_statement))
+ , m_invert_expression(invert_expression)
+ {
+ }
+
+ const NonnullRefPtr<Select>& select_statement() const { return m_select_statement; }
+ bool invert_expression() const { return m_invert_expression; }
+
+private:
+ NonnullRefPtr<Select> m_select_statement;
+ bool m_invert_expression;
+};
+
class CollateExpression : public NestedExpression {
public:
CollateExpression(NonnullRefPtr<Expression> expression, String collation_name)
diff --git a/Userland/Libraries/LibSQL/Parser.cpp b/Userland/Libraries/LibSQL/Parser.cpp
index 69b30e37ad..8775c2d3b3 100644
--- a/Userland/Libraries/LibSQL/Parser.cpp
+++ b/Userland/Libraries/LibSQL/Parser.cpp
@@ -215,7 +215,6 @@ NonnullRefPtr<Expression> Parser::parse_expression()
// FIXME: Parse 'bind-parameter'.
// FIXME: Parse 'function-name'.
- // FIXME: Parse 'exists'.
// FIXME: Parse 'raise-function'.
return expression;
@@ -241,6 +240,9 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
if (auto expression = parse_case_expression(); expression.has_value())
return move(expression.value());
+ if (auto expression = parse_exists_expression(false); expression.has_value())
+ return move(expression.value());
+
expected("Primary Expression");
consume();
@@ -381,8 +383,12 @@ Optional<NonnullRefPtr<Expression>> Parser::parse_unary_operator_expression()
if (consume_if(TokenType::Tilde))
return create_ast_node<UnaryOperatorExpression>(UnaryOperator::BitwiseNot, parse_expression());
- if (consume_if(TokenType::Not))
- return create_ast_node<UnaryOperatorExpression>(UnaryOperator::Not, parse_expression());
+ if (consume_if(TokenType::Not)) {
+ if (match(TokenType::Exists))
+ return parse_exists_expression(true);
+ else
+ return create_ast_node<UnaryOperatorExpression>(UnaryOperator::Not, parse_expression());
+ }
return {};
}
@@ -448,11 +454,15 @@ Optional<NonnullRefPtr<Expression>> Parser::parse_binary_operator_expression(Non
Optional<NonnullRefPtr<Expression>> Parser::parse_chained_expression()
{
- if (!match(TokenType::ParenOpen))
+ if (!consume_if(TokenType::ParenOpen))
return {};
+ if (match(TokenType::Select))
+ return parse_exists_expression(false, TokenType::Select);
+
NonnullRefPtrVector<Expression> expressions;
- parse_comma_separated_list(true, [&]() { expressions.append(parse_expression()); });
+ parse_comma_separated_list(false, [&]() { expressions.append(parse_expression()); });
+ consume(TokenType::ParenClose);
return create_ast_node<ChainedExpression>(move(expressions));
}
@@ -506,6 +516,21 @@ Optional<NonnullRefPtr<Expression>> Parser::parse_case_expression()
return create_ast_node<CaseExpression>(move(case_expression), move(when_then_clauses), move(else_expression));
}
+Optional<NonnullRefPtr<Expression>> Parser::parse_exists_expression(bool invert_expression, TokenType opening_token)
+{
+ VERIFY((opening_token == TokenType::Exists) || (opening_token == TokenType::Select));
+
+ if ((opening_token == TokenType::Exists) && !consume_if(TokenType::Exists))
+ return {};
+
+ if (opening_token == TokenType::Exists)
+ consume(TokenType::ParenOpen);
+ auto select_statement = parse_select_statement({});
+ consume(TokenType::ParenClose);
+
+ return create_ast_node<ExistsExpression>(move(select_statement), invert_expression);
+}
+
Optional<NonnullRefPtr<Expression>> Parser::parse_collate_expression(NonnullRefPtr<Expression> expression)
{
if (!match(TokenType::Collate))
diff --git a/Userland/Libraries/LibSQL/Parser.h b/Userland/Libraries/LibSQL/Parser.h
index 6d6cf91591..14b03bbf52 100644
--- a/Userland/Libraries/LibSQL/Parser.h
+++ b/Userland/Libraries/LibSQL/Parser.h
@@ -68,6 +68,7 @@ private:
Optional<NonnullRefPtr<Expression>> parse_chained_expression();
Optional<NonnullRefPtr<Expression>> parse_cast_expression();
Optional<NonnullRefPtr<Expression>> parse_case_expression();
+ Optional<NonnullRefPtr<Expression>> parse_exists_expression(bool invert_expression, TokenType opening_token = TokenType::Exists);
Optional<NonnullRefPtr<Expression>> parse_collate_expression(NonnullRefPtr<Expression> expression);
Optional<NonnullRefPtr<Expression>> parse_is_expression(NonnullRefPtr<Expression> expression);
Optional<NonnullRefPtr<Expression>> parse_match_expression(NonnullRefPtr<Expression> lhs, bool invert_expression);
diff --git a/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp b/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp
index 4d8952dc14..1e7a2ef187 100644
--- a/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp
+++ b/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp
@@ -338,6 +338,39 @@ TEST_CASE(case_expression)
validate("CASE 15 WHEN 16 THEN 17 WHEN 18 THEN 19 ELSE 20 END", true, 2, true);
}
+TEST_CASE(exists_expression)
+{
+ EXPECT(parse("EXISTS").is_error());
+ EXPECT(parse("EXISTS (").is_error());
+ EXPECT(parse("EXISTS (SELECT").is_error());
+ EXPECT(parse("EXISTS (SELECT)").is_error());
+ EXPECT(parse("EXISTS (SELECT * FROM table").is_error());
+ EXPECT(parse("NOT EXISTS").is_error());
+ EXPECT(parse("NOT EXISTS (").is_error());
+ EXPECT(parse("NOT EXISTS (SELECT").is_error());
+ EXPECT(parse("NOT EXISTS (SELECT)").is_error());
+ EXPECT(parse("NOT EXISTS (SELECT * FROM table").is_error());
+ EXPECT(parse("(").is_error());
+ EXPECT(parse("(SELECT").is_error());
+ EXPECT(parse("(SELECT)").is_error());
+ EXPECT(parse("(SELECT * FROM table").is_error());
+
+ auto validate = [](StringView sql, bool expected_invert_expression) {
+ auto result = parse(sql);
+ EXPECT(!result.is_error());
+
+ auto expression = result.release_value();
+ EXPECT(is<SQL::ExistsExpression>(*expression));
+
+ const auto& exists = static_cast<const SQL::ExistsExpression&>(*expression);
+ EXPECT_EQ(exists.invert_expression(), expected_invert_expression);
+ };
+
+ validate("EXISTS (SELECT * FROM table)", false);
+ validate("NOT EXISTS (SELECT * FROM table)", true);
+ validate("(SELECT * FROM table)", false);
+}
+
TEST_CASE(collate_expression)
{
EXPECT(parse("COLLATE").is_error());