diff options
author | Timothy Flynn <trflynn89@pm.me> | 2022-12-01 22:20:55 -0500 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-12-07 13:09:00 +0100 |
commit | b2b9ae27fd247c98caf311a5c5583c466648d83c (patch) | |
tree | 50f348d6c9dc9a5d238925d9c5f84a755d6f107d /Userland | |
parent | 53f8d62ea4442f31add38a06bbdd2bfa3037a0a5 (diff) | |
download | serenity-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.h | 20 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/AST/Expression.cpp | 7 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/AST/Parser.cpp | 19 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/AST/Parser.h | 3 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/AST/Statement.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/AST/Token.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Result.h | 43 |
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 { |