Skip to content

Commit 2c38d61

Browse files
committed
backport: [10.x] Add toRawSql, dumpRawSql() and ddRawSql() to Query Builders
1 parent 14399d8 commit 2c38d61

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

src/Backports/GrammarBackport.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
/**
1010
* To support some features these commits from laravel needed to be backported for older versions:
1111
* - [10.x] Escaping functionality within the Grammar (https://github.com/laravel/framework/commit/e953137280cdf6e0fe3c3e4c49d7209ad86c92c0).
12+
* - [10.x] Add toRawSql, dumpRawSql() and ddRawSql() to Query Builders (https://github.com/laravel/framework/commit/830efbeeb815a5f1558433b673a58b0ddcbdc750).
13+
* - [11.x] Allow for custom Postgres operators to be added (https://github.com/laravel/framework/commit/029e993cb976e76a8d34d6b175eed8c8af1c3ed8)
1214
*/
1315
trait GrammarBackport
1416
{
@@ -18,6 +20,22 @@ trait GrammarBackport
1820
* @var \Illuminate\Database\Connection
1921
*/
2022
protected $connection;
23+
/**
24+
* The Postgres grammar specific custom operators.
25+
*
26+
* @var array
27+
*/
28+
protected static $customOperators = [];
29+
30+
/**
31+
* Set any Postgres grammar specific custom operators.
32+
*/
33+
public static function customOperators(array $operators): void
34+
{
35+
static::$customOperators = array_values(
36+
array_merge(static::$customOperators, array_filter(array_filter($operators, 'is_string')))
37+
);
38+
}
2139

2240
/**
2341
* Escapes a value for safe SQL embedding.
@@ -34,6 +52,14 @@ public function escape($value, $binary = false): string
3452
return $this->connection->escape($value, $binary);
3553
}
3654

55+
/**
56+
* Get the Postgres grammar specific operators.
57+
*/
58+
public function getOperators(): array
59+
{
60+
return array_values(array_unique(array_merge($this->operators, static::$customOperators)));
61+
}
62+
3763
/**
3864
* Set the grammar's database connection.
3965
*
@@ -45,4 +71,49 @@ public function setConnection($connection): static
4571

4672
return $this;
4773
}
74+
75+
/**
76+
* Substitute the given bindings into the given raw SQL query.
77+
*
78+
* @param string $sql
79+
* @param array $bindings
80+
*/
81+
public function substituteBindingsIntoRawSql($sql, $bindings): string
82+
{
83+
$bindings = array_map(fn ($value) => $this->escape($value), $bindings);
84+
85+
$query = '';
86+
87+
$isStringLiteral = false;
88+
89+
for ($i = 0; $i < \strlen($sql); ++$i) {
90+
$char = $sql[$i];
91+
$nextChar = $sql[$i + 1] ?? null;
92+
93+
// Single quotes can be escaped as '' according to the SQL standard while
94+
// MySQL uses \'. Postgres has operators like ?| that must get encoded
95+
// in PHP like ??|. We should skip over the escaped characters here.
96+
if (\in_array($char.$nextChar, ["\'", "''", '??'])) {
97+
$query .= $char.$nextChar;
98+
++$i;
99+
} elseif ("'" === $char) { // Starting / leaving string literal...
100+
$query .= $char;
101+
$isStringLiteral = !$isStringLiteral;
102+
} elseif ('?' === $char && !$isStringLiteral) { // Substitutable binding...
103+
$query .= array_shift($bindings) ?? '?';
104+
} else { // Normal character...
105+
$query .= $char;
106+
}
107+
}
108+
109+
foreach ($this->getOperators() as $operator) {
110+
if (!str_contains($operator, '?')) {
111+
continue;
112+
}
113+
114+
$query = str_replace(str_replace('?', '??', $operator), $operator, $query);
115+
}
116+
117+
return $query;
118+
}
48119
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tpetry\PostgresqlEnhanced\Tests\Connection;
6+
7+
use Tpetry\PostgresqlEnhanced\Tests\TestCase;
8+
9+
class QueryGrammarSubstituteBindingsTest extends TestCase
10+
{
11+
public function testToRawSql(): void
12+
{
13+
$query = $this->getConnection()->getQueryGrammar()->substituteBindingsIntoRawSql(
14+
'select * from "users" where \'{}\' ?? \'Hello\\\'\\\'World?\' AND "email" = ?',
15+
['foo'],
16+
);
17+
18+
$this->assertSame('select * from "users" where \'{}\' ? \'Hello\\\'\\\'World?\' AND "email" = \'foo\'', $query);
19+
}
20+
}

0 commit comments

Comments
 (0)