forked from SerenityOS/serenity
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
230118c
commit d074a60
Showing
12 changed files
with
329 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* | ||
* Copyright (c) 2021, Jan de Visser <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <unistd.h> | ||
|
||
#include <AK/ScopeGuard.h> | ||
#include <LibSQL/AST/Parser.h> | ||
#include <LibSQL/Database.h> | ||
#include <LibSQL/Row.h> | ||
#include <LibSQL/SQLResult.h> | ||
#include <LibSQL/Value.h> | ||
#include <LibTest/TestCase.h> | ||
|
||
namespace { | ||
|
||
constexpr const char* db_name = "/tmp/test.db"; | ||
|
||
RefPtr<SQL::SQLResult> execute(NonnullRefPtr<SQL::Database> 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<SQL::Database> database) | ||
{ | ||
auto result = execute(database, "CREATE SCHEMA TestSchema;"); | ||
EXPECT(result->inserted() == 1); | ||
} | ||
|
||
void create_table(NonnullRefPtr<SQL::Database> 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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* Copyright (c) 2021, Jan de Visser <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <LibSQL/AST/AST.h> | ||
#include <LibSQL/Database.h> | ||
|
||
namespace SQL::AST { | ||
|
||
Value Expression::evaluate(ExecutionContext&) const | ||
{ | ||
return Value::null(); | ||
} | ||
|
||
Value NumericLiteral::evaluate(ExecutionContext&) const | ||
{ | ||
Value ret(SQLType::Float); | ||
ret = value(); | ||
return ret; | ||
} | ||
|
||
Value StringLiteral::evaluate(ExecutionContext&) const | ||
{ | ||
Value ret(SQLType::Text); | ||
ret = value(); | ||
return ret; | ||
} | ||
|
||
Value NullLiteral::evaluate(ExecutionContext&) const | ||
{ | ||
return Value::null(); | ||
} | ||
|
||
Value NestedExpression::evaluate(ExecutionContext& context) const | ||
{ | ||
return expression()->evaluate(context); | ||
} | ||
|
||
Value ChainedExpression::evaluate(ExecutionContext& context) const | ||
{ | ||
Value ret(SQLType::Tuple); | ||
Vector<Value> values; | ||
for (auto& expression : expressions()) { | ||
values.append(expression.evaluate(context)); | ||
} | ||
ret = values; | ||
return ret; | ||
} | ||
|
||
Value UnaryOperatorExpression::evaluate(ExecutionContext& context) const | ||
{ | ||
Value expression_value = NestedExpression::evaluate(context); | ||
switch (type()) { | ||
case UnaryOperator::Plus: | ||
if (expression_value.type() == SQLType::Integer || expression_value.type() == SQLType::Float) | ||
return expression_value; | ||
// TODO: Error handling. | ||
VERIFY_NOT_REACHED(); | ||
case UnaryOperator::Minus: | ||
if (expression_value.type() == SQLType::Integer) { | ||
expression_value = -int(expression_value); | ||
return expression_value; | ||
} | ||
if (expression_value.type() == SQLType::Float) { | ||
expression_value = -double(expression_value); | ||
return expression_value; | ||
} | ||
// TODO: Error handling. | ||
VERIFY_NOT_REACHED(); | ||
case UnaryOperator::Not: | ||
if (expression_value.type() == SQLType::Boolean) { | ||
expression_value = !bool(expression_value); | ||
return expression_value; | ||
} | ||
// TODO: Error handling. | ||
VERIFY_NOT_REACHED(); | ||
case UnaryOperator::BitwiseNot: | ||
if (expression_value.type() == SQLType::Integer) { | ||
expression_value = ~u32(expression_value); | ||
return expression_value; | ||
} | ||
// TODO: Error handling. | ||
VERIFY_NOT_REACHED(); | ||
} | ||
VERIFY_NOT_REACHED(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright (c) 2021, Jan de Visser <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <LibSQL/AST/AST.h> | ||
#include <LibSQL/Database.h> | ||
#include <LibSQL/Meta.h> | ||
#include <LibSQL/Row.h> | ||
|
||
namespace SQL::AST { | ||
|
||
RefPtr<SQLResult> Insert::execute(ExecutionContext& context) const | ||
{ | ||
auto table_def = context.database->get_table(m_schema_name, m_table_name); | ||
if (!table_def) { | ||
auto schema_name = m_schema_name; | ||
if (schema_name.is_null() || schema_name.is_empty()) | ||
schema_name = "default"; | ||
return SQLResult::construct(SQLCommand::Insert, SQLErrorCode::TableDoesNotExist, String::formatted("{}.{}", schema_name, m_table_name)); | ||
} | ||
|
||
Row row(table_def); | ||
for (auto& column : m_column_names) { | ||
if (!row.has(column)) { | ||
return SQLResult::construct(SQLCommand::Insert, SQLErrorCode::ColumnDoesNotExist, column); | ||
} | ||
} | ||
|
||
for (auto& row_expr : m_chained_expressions) { | ||
for (auto& column_def : table_def->columns()) { | ||
if (!m_column_names.contains_slow(column_def.name())) { | ||
row[column_def.name()] = column_def.default_value(); | ||
} | ||
} | ||
auto row_value = row_expr.evaluate(context); | ||
VERIFY(row_value.type() == SQLType::Tuple); | ||
auto values = row_value.to_vector().value(); | ||
for (auto ix = 0u; ix < values.size(); ix++) { | ||
auto& column_name = m_column_names[ix]; | ||
row[column_name] = values[ix]; | ||
} | ||
context.database->insert(row); | ||
} | ||
return SQLResult::construct(SQLCommand::Insert, 0, m_chained_expressions.size(), 0); | ||
} | ||
|
||
} |
Oops, something went wrong.