1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 12:05:00 +00:00

LibSQL: Parse and execute sequential placeholder values

This partially implements SQLite's bind-parameter expression to support
indicating placeholder values in a SQL statement. For example:

    INSERT INTO table VALUES (42, ?);

In the above statement, the '?' identifier is a placeholder. This will
allow clients to compile statements a single time while running those
statements any number of times with different placeholder values.

Further, this will help mitigate SQL injection attacks.
This commit is contained in:
Timothy Flynn 2022-12-01 22:20:55 -05:00 committed by Andreas Kling
parent 53f8d62ea4
commit b2b9ae27fd
10 changed files with 154 additions and 30 deletions

View file

@ -21,19 +21,19 @@ namespace {
constexpr char const* db_name = "/tmp/test.db";
SQL::ResultOr<SQL::ResultSet> try_execute(NonnullRefPtr<SQL::Database> database, DeprecatedString const& sql)
SQL::ResultOr<SQL::ResultSet> try_execute(NonnullRefPtr<SQL::Database> database, DeprecatedString const& sql, Vector<SQL::Value> placeholder_values = {})
{
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_deprecated_string());
return statement->execute(move(database));
return statement->execute(move(database), placeholder_values);
}
SQL::ResultSet execute(NonnullRefPtr<SQL::Database> database, DeprecatedString const& sql)
SQL::ResultSet execute(NonnullRefPtr<SQL::Database> database, DeprecatedString const& sql, Vector<SQL::Value> placeholder_values = {})
{
auto result = try_execute(move(database), sql);
auto result = try_execute(move(database), sql, move(placeholder_values));
if (result.is_error()) {
outln("{}", result.release_error().error_string());
VERIFY_NOT_REACHED();
@ -41,6 +41,12 @@ SQL::ResultSet execute(NonnullRefPtr<SQL::Database> database, DeprecatedString c
return result.release_value();
}
template<typename... Args>
Vector<SQL::Value> placeholders(Args&&... args)
{
return { SQL::Value(forward<Args>(args))... };
}
void create_schema(NonnullRefPtr<SQL::Database> database)
{
auto result = execute(database, "CREATE SCHEMA TestSchema;");
@ -175,6 +181,59 @@ TEST_CASE(insert_without_column_names)
EXPECT_EQ(rows_or_error.value().size(), 2u);
}
TEST_CASE(insert_with_placeholders)
{
ScopeGuard guard([]() { unlink(db_name); });
auto database = SQL::Database::construct(db_name);
EXPECT(!database->open().is_error());
create_table(database);
{
auto result = try_execute(database, "INSERT INTO TestSchema.TestTable VALUES (?, ?);");
EXPECT(result.is_error());
EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::InvalidNumberOfPlaceholderValues);
result = try_execute(database, "INSERT INTO TestSchema.TestTable VALUES (?, ?);", placeholders("Test_1"sv));
EXPECT(result.is_error());
EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::InvalidNumberOfPlaceholderValues);
result = try_execute(database, "INSERT INTO TestSchema.TestTable VALUES (?, ?);", placeholders(42, 42));
EXPECT(result.is_error());
EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::InvalidValueType);
result = try_execute(database, "INSERT INTO TestSchema.TestTable VALUES (?, ?);", placeholders("Test_1"sv, "Test_2"sv));
EXPECT(result.is_error());
EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::InvalidValueType);
}
{
auto result = execute(database, "INSERT INTO TestSchema.TestTable VALUES (?, ?);", placeholders("Test_1"sv, 42));
EXPECT_EQ(result.size(), 1u);
result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY TextColumn;");
EXPECT_EQ(result.size(), 1u);
EXPECT_EQ(result[0].row[0], "Test_1"sv);
EXPECT_EQ(result[0].row[1], 42);
}
{
auto result = execute(database, "INSERT INTO TestSchema.TestTable VALUES (?, ?), (?, ?);", placeholders("Test_2"sv, 43, "Test_3"sv, 44));
EXPECT_EQ(result.size(), 2u);
result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY TextColumn;");
EXPECT_EQ(result.size(), 3u);
EXPECT_EQ(result[0].row[0], "Test_1"sv);
EXPECT_EQ(result[0].row[1], 42);
EXPECT_EQ(result[1].row[0], "Test_2"sv);
EXPECT_EQ(result[1].row[1], 43);
EXPECT_EQ(result[2].row[0], "Test_3"sv);
EXPECT_EQ(result[2].row[1], 44);
}
}
TEST_CASE(select_from_empty_table)
{
ScopeGuard guard([]() { unlink(db_name); });