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:
parent
53f8d62ea4
commit
b2b9ae27fd
10 changed files with 154 additions and 30 deletions
|
@ -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); });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue