mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:52:45 +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
				
			
		|  | @ -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 | ||||
|     { | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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)) | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -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)                        \ | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Flynn
						Timothy Flynn