From d074a601dfdb3cdbb50b83c142db85d079da475f Mon Sep 17 00:00:00 2001 From: Jan de Visser Date: Mon, 19 Jul 2021 19:48:46 -0400 Subject: LibSQL+SQLServer: Bare bones INSERT and SELECT statements This patch provides very basic, bare bones implementations of the INSERT and SELECT statements. They are *very* limited: - The only variant of the INSERT statement that currently works is SELECT INTO schema.table (column1, column2, ....) VALUES (value11, value21, ...), (value12, value22, ...), ... where the values are literals. - The SELECT statement is even more limited, and is only provided to allow verification of the INSERT statement. The only form implemented is: SELECT * FROM schema.table These statements required a bit of change in the Statement::execute API. Originally execute only received a Database object as parameter. This is not enough; we now pass an ExecutionContext object which contains the Database, the current result set, and the last Tuple read from the database. This object will undoubtedly evolve over time. This API change dragged SQLServer::SQLStatement into the patch. Another API addition is Expression::evaluate. This method is, unsurprisingly, used to evaluate expressions, like the values in the INSERT statement. Finally, a new test file is added: TestSqlStatementExecution, which tests the currently implemented statements. As the number and flavour of implemented statements grows, this test file will probably have to be restructured. --- Tests/LibSQL/TestSqlStatementExecution.cpp | 101 +++++++++++++++++++++++++ Userland/Libraries/LibSQL/AST/AST.h | 24 +++++- Userland/Libraries/LibSQL/AST/CreateSchema.cpp | 6 +- Userland/Libraries/LibSQL/AST/CreateTable.cpp | 8 +- Userland/Libraries/LibSQL/AST/Expression.cpp | 90 ++++++++++++++++++++++ Userland/Libraries/LibSQL/AST/Insert.cpp | 49 ++++++++++++ Userland/Libraries/LibSQL/AST/Parser.cpp | 12 ++- Userland/Libraries/LibSQL/AST/Select.cpp | 37 +++++++++ Userland/Libraries/LibSQL/CMakeLists.txt | 3 + Userland/Libraries/LibSQL/Database.cpp | 10 ++- Userland/Libraries/LibSQL/SQLResult.h | 1 + Userland/Services/SQLServer/SQLStatement.cpp | 4 +- 12 files changed, 329 insertions(+), 16 deletions(-) create mode 100644 Tests/LibSQL/TestSqlStatementExecution.cpp create mode 100644 Userland/Libraries/LibSQL/AST/Expression.cpp create mode 100644 Userland/Libraries/LibSQL/AST/Insert.cpp create mode 100644 Userland/Libraries/LibSQL/AST/Select.cpp diff --git a/Tests/LibSQL/TestSqlStatementExecution.cpp b/Tests/LibSQL/TestSqlStatementExecution.cpp new file mode 100644 index 0000000000..cd9a35f2d9 --- /dev/null +++ b/Tests/LibSQL/TestSqlStatementExecution.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr const char* db_name = "/tmp/test.db"; + +RefPtr execute(NonnullRefPtr database, String const& sql) +{ + auto parser = SQL::AST::Parser(SQL::AST::Lexer(sql)); + auto statement = parser.next_statement(); + EXPECT(!parser.has_errors()); + if (parser.has_errors()) { + outln(parser.errors()[0].to_string()); + } + SQL::AST::ExecutionContext context { database }; + auto result = statement->execute(context); + EXPECT(result->error().code == SQL::SQLErrorCode::NoError); + return result; +} + +void create_schema(NonnullRefPtr database) +{ + auto result = execute(database, "CREATE SCHEMA TestSchema;"); + EXPECT(result->inserted() == 1); +} + +void create_table(NonnullRefPtr database) +{ + create_schema(database); + auto result = execute(database, "CREATE TABLE TestSchema.TestTable ( TextColumn text, IntColumn integer );"); + EXPECT(result->inserted() == 1); +} + +TEST_CASE(create_schema) +{ + ScopeGuard guard([]() { unlink(db_name); }); + auto database = SQL::Database::construct(db_name); + create_schema(database); + auto schema = database->get_schema("TESTSCHEMA"); + EXPECT(schema); +} + +TEST_CASE(create_table) +{ + ScopeGuard guard([]() { unlink(db_name); }); + auto database = SQL::Database::construct(db_name); + create_table(database); + auto table = database->get_table("TESTSCHEMA", "TESTTABLE"); + EXPECT(table); +} + +TEST_CASE(insert_into_table) +{ + ScopeGuard guard([]() { unlink(db_name); }); + auto database = SQL::Database::construct(db_name); + create_table(database); + auto result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test', 42 );"); + EXPECT(result->inserted() == 1); + + auto table = database->get_table("TESTSCHEMA", "TESTTABLE"); + + int count = 0; + for (auto& row : database->select_all(*table)) { + EXPECT_EQ(row["TEXTCOLUMN"].to_string(), "Test"); + EXPECT_EQ(row["INTCOLUMN"].to_int().value(), 42); + count++; + } + EXPECT_EQ(count, 1); +} + +TEST_CASE(select_from_table) +{ + ScopeGuard guard([]() { unlink(db_name); }); + auto database = SQL::Database::construct(db_name); + create_table(database); + auto result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_1', 42 ), ( 'Test_2', 43 );"); + EXPECT(result->inserted() == 2); + result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_3', 44 ), ( 'Test_4', 45 );"); + EXPECT(result->inserted() == 2); + result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_5', 46 );"); + EXPECT(result->inserted() == 1); + result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT(result->has_results()); + EXPECT_EQ(result->results().size(), 5u); +} + +} diff --git a/Userland/Libraries/LibSQL/AST/AST.h b/Userland/Libraries/LibSQL/AST/AST.h index cd9001cf31..7a8ca428eb 100644 --- a/Userland/Libraries/LibSQL/AST/AST.h +++ b/Userland/Libraries/LibSQL/AST/AST.h @@ -295,7 +295,15 @@ private: // Expressions //================================================================================================== +struct ExecutionContext { + NonnullRefPtr database; + RefPtr result { nullptr }; + Tuple current_row {}; +}; + class Expression : public ASTNode { +public: + virtual Value evaluate(ExecutionContext&) const; }; class ErrorExpression final : public Expression { @@ -309,6 +317,7 @@ public: } double value() const { return m_value; } + virtual Value evaluate(ExecutionContext&) const override; private: double m_value; @@ -322,6 +331,7 @@ public: } const String& value() const { return m_value; } + virtual Value evaluate(ExecutionContext&) const override; private: String m_value; @@ -341,11 +351,14 @@ private: }; class NullLiteral : public Expression { +public: + virtual Value evaluate(ExecutionContext&) const override; }; class NestedExpression : public Expression { public: const NonnullRefPtr& expression() const { return m_expression; } + virtual Value evaluate(ExecutionContext&) const override; protected: explicit NestedExpression(NonnullRefPtr expression) @@ -439,6 +452,7 @@ public: } UnaryOperator type() const { return m_type; } + virtual Value evaluate(ExecutionContext&) const override; private: UnaryOperator m_type; @@ -488,6 +502,7 @@ public: } const NonnullRefPtrVector& expressions() const { return m_expressions; } + virtual Value evaluate(ExecutionContext&) const override; private: NonnullRefPtrVector m_expressions; @@ -667,7 +682,7 @@ private: class Statement : public ASTNode { public: - virtual RefPtr execute(NonnullRefPtr) const { return nullptr; } + virtual RefPtr execute(ExecutionContext&) const { return nullptr; } }; class ErrorStatement final : public Statement { @@ -684,7 +699,7 @@ public: const String& schema_name() const { return m_schema_name; } bool is_error_if_schema_exists() const { return m_is_error_if_schema_exists; } - RefPtr execute(NonnullRefPtr) const override; + RefPtr execute(ExecutionContext&) const override; private: String m_schema_name; @@ -723,7 +738,7 @@ public: bool is_temporary() const { return m_is_temporary; } bool is_error_if_table_exists() const { return m_is_error_if_table_exists; } - RefPtr execute(NonnullRefPtr) const override; + RefPtr execute(ExecutionContext&) const override; private: String m_schema_name; @@ -886,6 +901,8 @@ public: bool has_selection() const { return !m_select_statement.is_null(); } const RefPtr