mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 21:47:43 +00:00
LibSQL: Parse DELETE statement
This commit is contained in:
parent
ce6c7ae18a
commit
733806b6a1
5 changed files with 284 additions and 1 deletions
|
@ -101,6 +101,62 @@ private:
|
|||
NonnullRefPtr<TypeName> m_type_name;
|
||||
};
|
||||
|
||||
class CommonTableExpression : public ASTNode {
|
||||
public:
|
||||
CommonTableExpression(String table_name, Vector<String> column_names)
|
||||
: m_table_name(move(table_name))
|
||||
, m_column_names(move(column_names))
|
||||
{
|
||||
}
|
||||
|
||||
const String& table_name() const { return m_table_name; }
|
||||
const Vector<String>& column_names() const { return m_column_names; }
|
||||
|
||||
private:
|
||||
String m_table_name;
|
||||
Vector<String> m_column_names;
|
||||
};
|
||||
|
||||
class QualifiedTableName : public ASTNode {
|
||||
public:
|
||||
QualifiedTableName(String schema_name, String table_name, String alias)
|
||||
: m_schema_name(move(schema_name))
|
||||
, m_table_name(move(table_name))
|
||||
, m_alias(move(alias))
|
||||
{
|
||||
}
|
||||
|
||||
const String& schema_name() const { return m_schema_name; }
|
||||
const String& table_name() const { return m_table_name; }
|
||||
const String& alias() const { return m_alias; }
|
||||
|
||||
private:
|
||||
String m_schema_name;
|
||||
String m_table_name;
|
||||
String m_alias;
|
||||
};
|
||||
|
||||
class ReturningClause : public ASTNode {
|
||||
public:
|
||||
struct ColumnClause {
|
||||
NonnullRefPtr<Expression> expression;
|
||||
String column_alias;
|
||||
};
|
||||
|
||||
ReturningClause() = default;
|
||||
|
||||
explicit ReturningClause(Vector<ColumnClause> columns)
|
||||
: m_columns(move(columns))
|
||||
{
|
||||
}
|
||||
|
||||
bool return_all_columns() const { return m_columns.is_empty(); };
|
||||
const Vector<ColumnClause>& columns() const { return m_columns; }
|
||||
|
||||
private:
|
||||
Vector<ColumnClause> m_columns;
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
// Expressions
|
||||
//==================================================================================================
|
||||
|
@ -495,4 +551,29 @@ private:
|
|||
bool m_is_error_if_table_does_not_exist;
|
||||
};
|
||||
|
||||
class Delete : public Statement {
|
||||
public:
|
||||
Delete(bool recursive, RefPtr<CommonTableExpression> common_table_expression, NonnullRefPtr<QualifiedTableName> qualified_table_name, RefPtr<Expression> where_clause, RefPtr<ReturningClause> returning_clause)
|
||||
: m_recursive(recursive)
|
||||
, m_common_table_expression(move(common_table_expression))
|
||||
, m_qualified_table_name(move(qualified_table_name))
|
||||
, m_where_clause(move(where_clause))
|
||||
, m_returning_clause(move(returning_clause))
|
||||
{
|
||||
}
|
||||
|
||||
bool recursive() const { return m_recursive; }
|
||||
const RefPtr<CommonTableExpression>& common_table_expression() const { return m_common_table_expression; }
|
||||
const NonnullRefPtr<QualifiedTableName>& qualified_table_name() const { return m_qualified_table_name; }
|
||||
const RefPtr<Expression>& where_clause() const { return m_where_clause; }
|
||||
const RefPtr<ReturningClause>& returning_clause() const { return m_returning_clause; }
|
||||
|
||||
private:
|
||||
bool m_recursive;
|
||||
RefPtr<CommonTableExpression> m_common_table_expression;
|
||||
NonnullRefPtr<QualifiedTableName> m_qualified_table_name;
|
||||
RefPtr<Expression> m_where_clause;
|
||||
RefPtr<ReturningClause> m_returning_clause;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -37,7 +37,9 @@ class ChainedExpression;
|
|||
class CollateExpression;
|
||||
class ColumnDefinition;
|
||||
class ColumnNameExpression;
|
||||
class CommonTableExpression;
|
||||
class CreateTable;
|
||||
class Delete;
|
||||
class DropTable;
|
||||
class ErrorExpression;
|
||||
class ErrorStatement;
|
||||
|
@ -55,6 +57,8 @@ class NullExpression;
|
|||
class NullLiteral;
|
||||
class NumericLiteral;
|
||||
class Parser;
|
||||
class QualifiedTableName;
|
||||
class ReturningClause;
|
||||
class SignedNumber;
|
||||
class Statement;
|
||||
class StringLiteral;
|
||||
|
|
|
@ -41,8 +41,11 @@ NonnullRefPtr<Statement> Parser::next_statement()
|
|||
return parse_create_table_statement();
|
||||
case TokenType::Drop:
|
||||
return parse_drop_table_statement();
|
||||
case TokenType::Delete:
|
||||
case TokenType::With:
|
||||
return parse_delete_statement();
|
||||
default:
|
||||
expected("CREATE or DROP");
|
||||
expected("CREATE, DROP, or DELETE");
|
||||
return create_ast_node<ErrorStatement>();
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +128,34 @@ NonnullRefPtr<DropTable> Parser::parse_drop_table_statement()
|
|||
return create_ast_node<DropTable>(move(schema_name), move(table_name), is_error_if_table_does_not_exist);
|
||||
}
|
||||
|
||||
NonnullRefPtr<Delete> Parser::parse_delete_statement()
|
||||
{
|
||||
// https://sqlite.org/lang_delete.html
|
||||
|
||||
bool recursive = false;
|
||||
RefPtr<CommonTableExpression> common_table_expression;
|
||||
if (consume_if(TokenType::With)) {
|
||||
recursive = consume_if(TokenType::Recursive);
|
||||
common_table_expression = parse_common_table_expression();
|
||||
}
|
||||
|
||||
consume(TokenType::Delete);
|
||||
consume(TokenType::From);
|
||||
auto qualified_table_name = parse_qualified_table_name();
|
||||
|
||||
RefPtr<Expression> where_clause;
|
||||
if (consume_if(TokenType::Where))
|
||||
where_clause = parse_expression();
|
||||
|
||||
RefPtr<ReturningClause> returning_clause;
|
||||
if (match(TokenType::Returning))
|
||||
returning_clause = parse_returning_clause();
|
||||
|
||||
consume(TokenType::SemiColon);
|
||||
|
||||
return create_ast_node<Delete>(recursive, move(common_table_expression), move(qualified_table_name), move(where_clause), move(returning_clause));
|
||||
}
|
||||
|
||||
NonnullRefPtr<Expression> Parser::parse_expression()
|
||||
{
|
||||
// https://sqlite.org/lang_expr.html
|
||||
|
@ -620,6 +651,87 @@ NonnullRefPtr<SignedNumber> Parser::parse_signed_number()
|
|||
return create_ast_node<SignedNumber>(0);
|
||||
}
|
||||
|
||||
NonnullRefPtr<CommonTableExpression> Parser::parse_common_table_expression()
|
||||
{
|
||||
// https://sqlite.org/syntax/common-table-expression.html
|
||||
auto table_name = consume(TokenType::Identifier).value();
|
||||
|
||||
Vector<String> column_names;
|
||||
if (consume_if(TokenType::ParenOpen)) {
|
||||
do {
|
||||
column_names.append(consume(TokenType::Identifier).value());
|
||||
if (match(TokenType::ParenClose))
|
||||
break;
|
||||
|
||||
consume(TokenType::Comma);
|
||||
} while (!match(TokenType::Eof));
|
||||
|
||||
consume(TokenType::ParenClose);
|
||||
}
|
||||
|
||||
consume(TokenType::As);
|
||||
consume(TokenType::ParenOpen);
|
||||
// FIXME: Parse "select-stmt".
|
||||
consume(TokenType::ParenClose);
|
||||
|
||||
return create_ast_node<CommonTableExpression>(move(table_name), move(column_names));
|
||||
}
|
||||
|
||||
NonnullRefPtr<QualifiedTableName> Parser::parse_qualified_table_name()
|
||||
{
|
||||
// https://sqlite.org/syntax/qualified-table-name.html
|
||||
String schema_or_table_name = consume(TokenType::Identifier).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);
|
||||
}
|
||||
|
||||
String alias;
|
||||
if (consume_if(TokenType::As))
|
||||
alias = consume(TokenType::Identifier).value();
|
||||
|
||||
// Note: The qualified-table-name spec may include an "INDEXED BY index-name" or "NOT INDEXED" clause. This is a SQLite extension
|
||||
// "designed to help detect undesirable query plan changes during regression testing", and "application developers are admonished
|
||||
// to omit all use of INDEXED BY during application design, implementation, testing, and tuning". Our implementation purposefully
|
||||
// omits parsing INDEXED BY for now until there is good reason to add support.
|
||||
|
||||
return create_ast_node<QualifiedTableName>(move(schema_name), move(table_name), move(alias));
|
||||
}
|
||||
|
||||
NonnullRefPtr<ReturningClause> Parser::parse_returning_clause()
|
||||
{
|
||||
// https://sqlite.org/syntax/returning-clause.html
|
||||
consume(TokenType::Returning);
|
||||
|
||||
if (consume_if(TokenType::Asterisk))
|
||||
return create_ast_node<ReturningClause>();
|
||||
|
||||
Vector<ReturningClause::ColumnClause> columns;
|
||||
|
||||
do {
|
||||
auto expression = parse_expression();
|
||||
|
||||
consume_if(TokenType::As); // 'AS' is optional.
|
||||
|
||||
String column_alias;
|
||||
if (match(TokenType::Identifier))
|
||||
column_alias = consume().value();
|
||||
|
||||
columns.append({ move(expression), move(column_alias) });
|
||||
if (!match(TokenType::Comma))
|
||||
break;
|
||||
|
||||
consume(TokenType::Comma);
|
||||
} while (!match(TokenType::Eof));
|
||||
|
||||
return create_ast_node<ReturningClause>(move(columns));
|
||||
}
|
||||
|
||||
Token Parser::consume()
|
||||
{
|
||||
auto old_token = m_parser_state.m_token;
|
||||
|
|
|
@ -72,6 +72,7 @@ private:
|
|||
|
||||
NonnullRefPtr<CreateTable> parse_create_table_statement();
|
||||
NonnullRefPtr<DropTable> parse_drop_table_statement();
|
||||
NonnullRefPtr<Delete> parse_delete_statement();
|
||||
|
||||
NonnullRefPtr<Expression> parse_primary_expression();
|
||||
NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression> primary);
|
||||
|
@ -93,6 +94,9 @@ private:
|
|||
NonnullRefPtr<ColumnDefinition> parse_column_definition();
|
||||
NonnullRefPtr<TypeName> parse_type_name();
|
||||
NonnullRefPtr<SignedNumber> parse_signed_number();
|
||||
NonnullRefPtr<CommonTableExpression> parse_common_table_expression();
|
||||
NonnullRefPtr<QualifiedTableName> parse_qualified_table_name();
|
||||
NonnullRefPtr<ReturningClause> parse_returning_clause();
|
||||
|
||||
Token consume();
|
||||
Token consume(TokenType type);
|
||||
|
|
|
@ -157,4 +157,86 @@ TEST_CASE(drop_table)
|
|||
validate("DROP TABLE IF EXISTS test;", {}, "test", false);
|
||||
}
|
||||
|
||||
TEST_CASE(delete_)
|
||||
{
|
||||
EXPECT(parse("DELETE").is_error());
|
||||
EXPECT(parse("DELETE FROM").is_error());
|
||||
EXPECT(parse("DELETE FROM table").is_error());
|
||||
EXPECT(parse("DELETE FROM table WHERE").is_error());
|
||||
EXPECT(parse("DELETE FROM table WHERE 15").is_error());
|
||||
EXPECT(parse("DELETE FROM table WHERE 15 RETURNING").is_error());
|
||||
EXPECT(parse("DELETE FROM table WHERE 15 RETURNING *").is_error());
|
||||
EXPECT(parse("DELETE FROM table WHERE (');").is_error());
|
||||
EXPECT(parse("WITH DELETE FROM table;").is_error());
|
||||
EXPECT(parse("WITH table DELETE FROM table;").is_error());
|
||||
EXPECT(parse("WITH table AS DELETE FROM table;").is_error());
|
||||
EXPECT(parse("WITH RECURSIVE table DELETE FROM table;").is_error());
|
||||
EXPECT(parse("WITH RECURSIVE table AS DELETE FROM table;").is_error());
|
||||
|
||||
struct SelectedTable {
|
||||
bool recursive { false };
|
||||
StringView table_name {};
|
||||
Vector<StringView> column_names {};
|
||||
};
|
||||
|
||||
auto validate = [](StringView sql, SelectedTable expected_selected_table, StringView expected_schema, StringView expected_table, StringView expected_alias, bool expect_where_clause, bool expect_returning_clause, Vector<StringView> expected_returned_column_aliases) {
|
||||
auto result = parse(sql);
|
||||
EXPECT(!result.is_error());
|
||||
|
||||
auto statement = result.release_value();
|
||||
EXPECT(is<SQL::Delete>(*statement));
|
||||
|
||||
const auto& delete_ = static_cast<const SQL::Delete&>(*statement);
|
||||
EXPECT_EQ(delete_.recursive(), expected_selected_table.recursive);
|
||||
|
||||
const auto& common_table_expression = delete_.common_table_expression();
|
||||
EXPECT_EQ(common_table_expression.is_null(), expected_selected_table.table_name.is_empty());
|
||||
if (common_table_expression) {
|
||||
EXPECT_EQ(common_table_expression->table_name(), expected_selected_table.table_name);
|
||||
EXPECT_EQ(common_table_expression->column_names().size(), expected_selected_table.column_names.size());
|
||||
for (size_t i = 0; i < common_table_expression->column_names().size(); ++i)
|
||||
EXPECT_EQ(common_table_expression->column_names()[i], expected_selected_table.column_names[i]);
|
||||
}
|
||||
|
||||
const auto& qualified_table_name = delete_.qualified_table_name();
|
||||
EXPECT_EQ(qualified_table_name->schema_name(), expected_schema);
|
||||
EXPECT_EQ(qualified_table_name->table_name(), expected_table);
|
||||
EXPECT_EQ(qualified_table_name->alias(), expected_alias);
|
||||
|
||||
const auto& where_clause = delete_.where_clause();
|
||||
EXPECT_EQ(where_clause.is_null(), !expect_where_clause);
|
||||
if (where_clause)
|
||||
EXPECT(!is<SQL::ErrorExpression>(*where_clause));
|
||||
|
||||
const auto& returning_clause = delete_.returning_clause();
|
||||
EXPECT_EQ(returning_clause.is_null(), !expect_returning_clause);
|
||||
if (returning_clause) {
|
||||
EXPECT_EQ(returning_clause->columns().size(), expected_returned_column_aliases.size());
|
||||
|
||||
for (size_t i = 0; i < returning_clause->columns().size(); ++i) {
|
||||
const auto& column = returning_clause->columns()[i];
|
||||
const auto& expected_column_alias = expected_returned_column_aliases[i];
|
||||
|
||||
EXPECT(!is<SQL::ErrorExpression>(*column.expression));
|
||||
EXPECT_EQ(column.column_alias, expected_column_alias);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
validate("DELETE FROM table;", {}, {}, "table", {}, false, false, {});
|
||||
validate("DELETE FROM schema.table;", {}, "schema", "table", {}, false, false, {});
|
||||
validate("DELETE FROM schema.table AS alias;", {}, "schema", "table", "alias", false, false, {});
|
||||
validate("DELETE FROM table WHERE (1 == 1);", {}, {}, "table", {}, true, false, {});
|
||||
validate("DELETE FROM table RETURNING *;", {}, {}, "table", {}, false, true, {});
|
||||
validate("DELETE FROM table RETURNING column;", {}, {}, "table", {}, false, true, { {} });
|
||||
validate("DELETE FROM table RETURNING column AS alias;", {}, {}, "table", {}, false, true, { "alias" });
|
||||
validate("DELETE FROM table RETURNING column1 AS alias1, column2 AS alias2;", {}, {}, "table", {}, false, true, { "alias1", "alias2" });
|
||||
|
||||
// FIXME: When parsing of SELECT statements are supported, the common-table-expressions below will become invalid due to the empty "AS ()" clause.
|
||||
validate("WITH table AS () DELETE FROM table;", { false, "table", {} }, {}, "table", {}, false, false, {});
|
||||
validate("WITH table (column) AS () DELETE FROM table;", { false, "table", { "column" } }, {}, "table", {}, false, false, {});
|
||||
validate("WITH table (column1, column2) AS () DELETE FROM table;", { false, "table", { "column1", "column2" } }, {}, "table", {}, false, false, {});
|
||||
validate("WITH RECURSIVE table AS () DELETE FROM table;", { true, "table", {} }, {}, "table", {}, false, false, {});
|
||||
}
|
||||
|
||||
TEST_MAIN(SqlParser)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue