summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2022-12-01 22:20:55 -0500
committerAndreas Kling <kling@serenityos.org>2022-12-07 13:09:00 +0100
commitb2b9ae27fd247c98caf311a5c5583c466648d83c (patch)
tree50f348d6c9dc9a5d238925d9c5f84a755d6f107d /Userland
parent53f8d62ea4442f31add38a06bbdd2bfa3037a0a5 (diff)
downloadserenity-b2b9ae27fd247c98caf311a5c5583c466648d83c.zip
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.
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibSQL/AST/AST.h20
-rw-r--r--Userland/Libraries/LibSQL/AST/Expression.cpp7
-rw-r--r--Userland/Libraries/LibSQL/AST/Parser.cpp19
-rw-r--r--Userland/Libraries/LibSQL/AST/Parser.h3
-rw-r--r--Userland/Libraries/LibSQL/AST/Statement.cpp4
-rw-r--r--Userland/Libraries/LibSQL/AST/Token.h1
-rw-r--r--Userland/Libraries/LibSQL/Result.h43
7 files changed, 71 insertions, 26 deletions
diff --git a/Userland/Libraries/LibSQL/AST/AST.h b/Userland/Libraries/LibSQL/AST/AST.h
index 4d64c9d48a..fd319289de 100644
--- a/Userland/Libraries/LibSQL/AST/AST.h
+++ b/Userland/Libraries/LibSQL/AST/AST.h
@@ -300,7 +300,8 @@ private:
struct ExecutionContext {
NonnullRefPtr<Database> database;
- class Statement const* statement;
+ Statement const* statement { nullptr };
+ Span<Value const> placeholder_values {};
Tuple* current_row { nullptr };
};
@@ -361,6 +362,21 @@ public:
virtual ResultOr<Value> evaluate(ExecutionContext&) const override;
};
+class Placeholder : public Expression {
+public:
+ explicit Placeholder(size_t parameter_index)
+ : m_parameter_index(parameter_index)
+ {
+ }
+
+ size_t parameter_index() const { return m_parameter_index; }
+
+ virtual ResultOr<Value> evaluate(ExecutionContext&) const override;
+
+private:
+ size_t m_parameter_index { 0 };
+};
+
class NestedExpression : public Expression {
public:
NonnullRefPtr<Expression> const& expression() const { return m_expression; }
@@ -729,7 +745,7 @@ private:
class Statement : public ASTNode {
public:
- ResultOr<ResultSet> execute(AK::NonnullRefPtr<Database> database) const;
+ ResultOr<ResultSet> execute(AK::NonnullRefPtr<Database> database, Span<Value const> placeholder_values = {}) const;
virtual ResultOr<ResultSet> execute(ExecutionContext&) const
{
diff --git a/Userland/Libraries/LibSQL/AST/Expression.cpp b/Userland/Libraries/LibSQL/AST/Expression.cpp
index 043670b5b4..0a39d4a30b 100644
--- a/Userland/Libraries/LibSQL/AST/Expression.cpp
+++ b/Userland/Libraries/LibSQL/AST/Expression.cpp
@@ -29,6 +29,13 @@ ResultOr<Value> NullLiteral::evaluate(ExecutionContext&) const
return Value {};
}
+ResultOr<Value> Placeholder::evaluate(ExecutionContext& context) const
+{
+ if (parameter_index() >= context.placeholder_values.size())
+ return Result { SQLCommand::Unknown, SQLErrorCode::InvalidNumberOfPlaceholderValues };
+ return context.placeholder_values[parameter_index()];
+}
+
ResultOr<Value> NestedExpression::evaluate(ExecutionContext& context) const
{
return expression()->evaluate(context);
diff --git a/Userland/Libraries/LibSQL/AST/Parser.cpp b/Userland/Libraries/LibSQL/AST/Parser.cpp
index bccea887aa..81ef007519 100644
--- a/Userland/Libraries/LibSQL/AST/Parser.cpp
+++ b/Userland/Libraries/LibSQL/AST/Parser.cpp
@@ -401,7 +401,6 @@ NonnullRefPtr<Expression> Parser::parse_expression()
if (match_secondary_expression())
expression = parse_secondary_expression(move(expression));
- // FIXME: Parse 'bind-parameter'.
// FIXME: Parse 'function-name'.
// FIXME: Parse 'raise-function'.
@@ -414,6 +413,9 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
if (auto expression = parse_literal_value_expression())
return expression.release_nonnull();
+ if (auto expression = parse_bind_parameter_expression())
+ return expression.release_nonnull();
+
if (auto expression = parse_column_name_expression())
return expression.release_nonnull();
@@ -528,6 +530,21 @@ RefPtr<Expression> Parser::parse_literal_value_expression()
return {};
}
+// https://sqlite.org/lang_expr.html#varparam
+RefPtr<Expression> Parser::parse_bind_parameter_expression()
+{
+ // FIXME: Support ?NNN, :AAAA, @AAAA, and $AAAA forms.
+ if (consume_if(TokenType::Placeholder)) {
+ auto parameter = m_parser_state.m_bound_parameters;
+ if (++m_parser_state.m_bound_parameters > Limits::maximum_bound_parameters)
+ syntax_error(DeprecatedString::formatted("Exceeded maximum number of bound parameters {}", Limits::maximum_bound_parameters));
+
+ return create_ast_node<Placeholder>(parameter);
+ }
+
+ return {};
+}
+
RefPtr<Expression> Parser::parse_column_name_expression(DeprecatedString with_parsed_identifier, bool with_parsed_period)
{
if (with_parsed_identifier.is_null() && !match(TokenType::Identifier))
diff --git a/Userland/Libraries/LibSQL/AST/Parser.h b/Userland/Libraries/LibSQL/AST/Parser.h
index 64e0d9f6ad..430280f9f2 100644
--- a/Userland/Libraries/LibSQL/AST/Parser.h
+++ b/Userland/Libraries/LibSQL/AST/Parser.h
@@ -19,6 +19,7 @@ namespace Limits {
// https://www.sqlite.org/limits.html
constexpr size_t maximum_expression_tree_depth = 1000;
constexpr size_t maximum_subquery_depth = 100;
+constexpr size_t maximum_bound_parameters = 1000;
}
class Parser {
@@ -52,6 +53,7 @@ private:
Vector<Error> m_errors;
size_t m_current_expression_depth { 0 };
size_t m_current_subquery_depth { 0 };
+ size_t m_bound_parameters { 0 };
};
NonnullRefPtr<Statement> parse_statement();
@@ -71,6 +73,7 @@ private:
NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression> primary);
bool match_secondary_expression() const;
RefPtr<Expression> parse_literal_value_expression();
+ RefPtr<Expression> parse_bind_parameter_expression();
RefPtr<Expression> parse_column_name_expression(DeprecatedString with_parsed_identifier = {}, bool with_parsed_period = false);
RefPtr<Expression> parse_unary_operator_expression();
RefPtr<Expression> parse_binary_operator_expression(NonnullRefPtr<Expression> lhs);
diff --git a/Userland/Libraries/LibSQL/AST/Statement.cpp b/Userland/Libraries/LibSQL/AST/Statement.cpp
index 7bc718d3ac..97a1fdb622 100644
--- a/Userland/Libraries/LibSQL/AST/Statement.cpp
+++ b/Userland/Libraries/LibSQL/AST/Statement.cpp
@@ -11,9 +11,9 @@
namespace SQL::AST {
-ResultOr<ResultSet> Statement::execute(AK::NonnullRefPtr<Database> database) const
+ResultOr<ResultSet> Statement::execute(AK::NonnullRefPtr<Database> database, Span<Value const> placeholder_values) const
{
- ExecutionContext context { move(database), this, nullptr };
+ ExecutionContext context { move(database), this, placeholder_values, nullptr };
auto result = TRY(execute(context));
// FIXME: When transactional sessions are supported, don't auto-commit modifications.
diff --git a/Userland/Libraries/LibSQL/AST/Token.h b/Userland/Libraries/LibSQL/AST/Token.h
index 86a55d722f..354f1bdcec 100644
--- a/Userland/Libraries/LibSQL/AST/Token.h
+++ b/Userland/Libraries/LibSQL/AST/Token.h
@@ -171,6 +171,7 @@ namespace SQL::AST {
__ENUMERATE_SQL_TOKEN("_blob_", BlobLiteral, Blob) \
__ENUMERATE_SQL_TOKEN("_eof_", Eof, Invalid) \
__ENUMERATE_SQL_TOKEN("_invalid_", Invalid, Invalid) \
+ __ENUMERATE_SQL_TOKEN("?", Placeholder, Operator) \
__ENUMERATE_SQL_TOKEN("&", Ampersand, Operator) \
__ENUMERATE_SQL_TOKEN("*", Asterisk, Operator) \
__ENUMERATE_SQL_TOKEN(",", Comma, Punctuation) \
diff --git a/Userland/Libraries/LibSQL/Result.h b/Userland/Libraries/LibSQL/Result.h
index 5f65653f7c..594c2200e3 100644
--- a/Userland/Libraries/LibSQL/Result.h
+++ b/Userland/Libraries/LibSQL/Result.h
@@ -41,27 +41,28 @@ constexpr char const* command_tag(SQLCommand command)
}
}
-#define ENUMERATE_SQL_ERRORS(S) \
- S(NoError, "No error") \
- S(InternalError, "{}") \
- S(NotYetImplemented, "{}") \
- S(DatabaseUnavailable, "Database Unavailable") \
- S(StatementUnavailable, "Statement with id '{}' Unavailable") \
- S(SyntaxError, "Syntax Error") \
- S(DatabaseDoesNotExist, "Database '{}' does not exist") \
- S(SchemaDoesNotExist, "Schema '{}' does not exist") \
- S(SchemaExists, "Schema '{}' already exist") \
- S(TableDoesNotExist, "Table '{}' does not exist") \
- S(ColumnDoesNotExist, "Column '{}' does not exist") \
- S(AmbiguousColumnName, "Column name '{}' is ambiguous") \
- S(TableExists, "Table '{}' already exist") \
- S(InvalidType, "Invalid type '{}'") \
- S(InvalidDatabaseName, "Invalid database name '{}'") \
- S(InvalidValueType, "Invalid type for attribute '{}'") \
- S(InvalidNumberOfValues, "Number of values does not match number of columns") \
- S(BooleanOperatorTypeMismatch, "Cannot apply '{}' operator to non-boolean operands") \
- S(NumericOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \
- S(IntegerOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \
+#define ENUMERATE_SQL_ERRORS(S) \
+ S(NoError, "No error") \
+ S(InternalError, "{}") \
+ S(NotYetImplemented, "{}") \
+ S(DatabaseUnavailable, "Database Unavailable") \
+ S(StatementUnavailable, "Statement with id '{}' Unavailable") \
+ S(SyntaxError, "Syntax Error") \
+ S(DatabaseDoesNotExist, "Database '{}' does not exist") \
+ S(SchemaDoesNotExist, "Schema '{}' does not exist") \
+ S(SchemaExists, "Schema '{}' already exist") \
+ S(TableDoesNotExist, "Table '{}' does not exist") \
+ S(ColumnDoesNotExist, "Column '{}' does not exist") \
+ S(AmbiguousColumnName, "Column name '{}' is ambiguous") \
+ S(TableExists, "Table '{}' already exist") \
+ S(InvalidType, "Invalid type '{}'") \
+ S(InvalidDatabaseName, "Invalid database name '{}'") \
+ S(InvalidValueType, "Invalid type for attribute '{}'") \
+ S(InvalidNumberOfPlaceholderValues, "Number of values does not match number of placeholders") \
+ S(InvalidNumberOfValues, "Number of values does not match number of columns") \
+ S(BooleanOperatorTypeMismatch, "Cannot apply '{}' operator to non-boolean operands") \
+ S(NumericOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \
+ S(IntegerOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \
S(InvalidOperator, "Invalid operator '{}'")
enum class SQLErrorCode {