Skip to content

Conversation

@tpetry
Copy link
Owner

@tpetry tpetry commented Jun 20, 2023

For quite some time, many developers have requested to get SQL queries with merged bindings from the query builder (laravel#38027, laravel#39053, laravel#39551, laravel#45705, laravel#45189).

They want to have something like this:

User::where('email', '[email protected]')->ddRawSql();
// "SELECT * FROM users WHERE email = '[email protected]'"

Instead of:

User::where('email', '[email protected]')->dd();
// "SELECT * FROM users WHERE email = ?"
// [
//  0 => "[email protected]"
// ]

Prior Implementations

All prior implementations had the same issues that prevented them from being merged:

  1. The bindings had been inserted into the query as strings without any further processing. Each of those generated queries was vulnerable to SQL injection attacks.
  2. They replaced all question marks with values that would break on raw calls, e.g. ->whereRaw("description = 'foo?'")

Improved Implementation

These new ways of generating SQL queries with embedded bindings are available:

$sql = User::where('email', '[email protected]')
  ->toRawSql();// $sql = "SELECT * FROM users WHERE email = '[email protected]'"

User::where('email', '[email protected]')
  ->dumpRawSql(); // "SELECT * FROM users WHERE email = '[email protected]'"

User::where('email', '[email protected]')
  ->ddRawSql(); // "SELECT * FROM users WHERE email = '[email protected]'"

$sql = DB::connection()->getQueryGrammar()->makeRawSql(
  'SELECT * FROM users WHERE email = ?',
  '[email protected]',
); // $sql = "SELECT * FROM users WHERE email = '[email protected]'"

1. SQL Injections

I've built an extension for the database layer that can escape any values for safe embedding into SQL queries (laravel#46558) that is already merged into Laravel 10.x. Based on that code, any binding is escaped before being injected into the SQL query:

User::where('name', "Robert'; drop table users; --")->dd();
// "SELECT * FROM users WHERE email = 'Robert\'; drop table users; --'"

2. Ambiguous Question Marks

Simple search-and-replace operations are not enough to reliably generate a raw SQL statement. With raw expressions anyone can embed more question marks into a SQL query that are clearly no placeholders:

User::whereRaw("abc = 'Hello World?'")->where('name', 'Robert')->dd();
// "SELECT * FROM users WHERE abc = 'Hello WorldRobert' name = ?"

But this can also be solved relatively easily. The generated SQL string with placeholders is parsed by a very simple LL(1) parser (just 20 lines) to watch for string escape sequences and only replace question not being escaped in string literals:

  • The occurrence of ' starts a string literal -> no question marks will be replaced
  • The occurrence of '' and \' marks escaped quotes -> they are copied
  • The occurrence of ' ends a string literal -> question marks will be replaced again.

That way the generated raw SQL string have no problem with question marks in string literals:

User::whereRaw("abc = 'Hello World?'")->where('name', 'Robert')->dd();
// "SELECT * FROM users WHERE abc = 'Hello World?' name = 'Robert'"

3. Executability of Raw SQL Queries

A generated query by these new functions should be able to be copied and pasted into any query tool and execute without problems. This is guaranteed for any database except PostgreSQL. Because PostgreSQL has special operators involving a question that needs to be doubled because of some PDO behaviour:

User::where('json', '?', 'abc')->dd(); // json object contains key "abc"
// "SELECT * FROM users WHERE json ?? 'abc'"

The query can not be executed as Laravel (correctly!) doubles the question mark (double ones are exempt from replacement 😉). To also make these special queries copy-able any (1) PostgreSQL operator containing a question mark that is (2) included in Laravel's operator information is decoded again:

User::where('json', '?', 'abc')->dd(); // json object contains key "abc"
// "SELECT * FROM users WHERE json ? 'abc'"

Final

This implementation solves any known problems of generating raw SQL string known until today (including mine added for PostgreSQL).

I am open to a different naming for these new methods.

@tpetry tpetry force-pushed the feature/to-raw-sql branch from e9689e2 to bee3dda Compare June 20, 2023 11:57
@tpetry tpetry force-pushed the feature/to-raw-sql branch from bee3dda to 42942f9 Compare June 20, 2023 12:03
@tpetry tpetry changed the title [10.x] Add toRawSql for query builders [10.x] Add toRawSql, dumpRawSql() and ddRawSql() to Query Builders Jun 20, 2023
tpetry pushed a commit that referenced this pull request Jan 30, 2024
…laravel#49474)

* test: validateJson should return false when value is null

Fails with Laravel Framework 10.38.2 in PHP < 8.3, introduced in laravel#49413

* fix: validateJson should return false when value is null

Return false when $value is null.

Avoid TypeError: json_validate(): Argument #1 ($json) must be of type string, null given, when using symfony/polyfill-php83 in PHP < 8.3.

Avoid deprecation warning: json_validate(): Passing null to parameter #1 ($json) of type string is deprecated, when using PHP 8.3.

---------

Co-authored-by: Rogelio Jacinto <[email protected]>
tpetry pushed a commit that referenced this pull request Oct 6, 2025
…ets (laravel#55191)

* [12.x] introduce `Rule::oneOf()` (#laravel#54880)

* chore: apply styleCI

* feat: add nested oneOf validation test

* chore: apply styleCI

* refactor: rename `oneof` into `anyof` to fit implementation

* fix: wrong failure message

* feat: update base tests

* feat: add test case

* chore: apply styleCI

* formatting

* feat: allow string fields

* feat: add test and clean nested rules

* chore: apply styleCI

* failing test

* Validation tests (#1)

* feat: add more validation tests

* wip: add failing test

* wip: add basic string rule validations

* chore: rename object fields for better debugging

* refactor: rename ruleSets to rules

* fix: respect array rule validation

---------

Co-authored-by: Christian Ascone <[email protected]>

* fix: this should be passing because AnyOf has no type relevance and 'required' only checks to see if the field has something in it

---------

Co-authored-by: Christian Ascone <[email protected]>

---------

Co-authored-by: Christian Ascone <[email protected]>

* chore: correspond with recent changes https://github.com/brianferri/framework/pull/1/commits/de3b902a950b8f5ba8edaafa273f91d7c6ade295

* chore: remove unused private property

* feat: attribute mapping in favor of potentially indexed mapping

* feat: add more tests

* refactor(tests): remove unnecessary amount of tests, rename parameter properties to be more descriptive/analogous to use cases

* chore: apply styleCI

* feat: add tests to verify compliance with dot notation nesting validator

* formatting

* fix: remove messages

* fix(wip): regression introduced in 14598f6

laravel#55191 (comment)

* feat: implement star rule counter tests for simple and nested rules

Co-authored-by: Christian Ascone <[email protected]>

* chore: apply styleCI

---------

Co-authored-by: Taylor Otwell <[email protected]>
Co-authored-by: Christian Ascone <[email protected]>
tpetry pushed a commit that referenced this pull request Oct 6, 2025
* Fix incorrect docblock for custom builder resolvers

Fixes laravel#56152 which was caused by the incorrect parameters being used for a more specific type hint docblock merged in laravel#55687. 

This currently causes issues with projects that use static analysis and make use of the $builder->blueprintResolver() method to resolve custom blueprints.

Example below:

 ------ ---------------------------------------------------------------------------------------------------------- 
  Line   Database/DatabaseServiceProvider.php                                                                      
 ------ ---------------------------------------------------------------------------------------------------------- 
  :109   Parameter #1 $connection of class Winter\Storm\Database\Schema\Blueprint constructor expects              
         Illuminate\Database\Connection, string given.                                                             
         🪪  argument.type                                                                                         
  :109   Parameter laravel#2 $table of class Winter\Storm\Database\Schema\Blueprint constructor expects string, Closure   
         given.                                                                                                    
         🪪  argument.type                                                                                         
  :109   Parameter laravel#3 $callback of class Winter\Storm\Database\Schema\Blueprint constructor expects Closure|null,  
         string given.                                                                                             
         🪪  argument.type                                                                                         
 ------ ----------------------------------------------------------------------------------------------------------

* Update src/Illuminate/Database/Schema/Builder.php

* Apply suggestions from code review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants