Skip to content

Commit 98a9954

Browse files
[9.x] InteractsWithDatabase::castAsJson($value) incorrectly handles SQLite Database (#44196)
* Implement compileJsonCast method in database query grammar * Use compileJsonCast to retrieve queryable json value * Add castAsJson tests * CS fixes * Fix tests * Use single quotes with concatenation for consistency * formatting Co-authored-by: Taylor Otwell <[email protected]>
1 parent f40fe7d commit 98a9954

File tree

5 files changed

+181
-1
lines changed

5 files changed

+181
-1
lines changed

src/Illuminate/Database/Query/Grammars/Grammar.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,17 @@ protected function compileJsonLength($column, $operator, $value)
682682
throw new RuntimeException('This database engine does not support JSON length operations.');
683683
}
684684

685+
/**
686+
* Compile a "JSON value cast" statement into SQL.
687+
*
688+
* @param string $value
689+
* @return string
690+
*/
691+
public function compileJsonValueCast($value)
692+
{
693+
return $value;
694+
}
695+
685696
/**
686697
* Compile a "where fulltext" clause.
687698
*

src/Illuminate/Database/Query/Grammars/MySqlGrammar.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ protected function compileJsonLength($column, $operator, $value)
128128
return 'json_length('.$field.$path.') '.$operator.' '.$value;
129129
}
130130

131+
/**
132+
* Compile a "JSON value cast" statement into SQL.
133+
*
134+
* @param string $value
135+
* @return string
136+
*/
137+
public function compileJsonValueCast($value)
138+
{
139+
return 'cast('.$value.' as json)';
140+
}
141+
131142
/**
132143
* Compile the random statement into SQL.
133144
*

src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ protected function compileJsonLength($column, $operator, $value)
205205
return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value;
206206
}
207207

208+
/**
209+
* Compile a "JSON value cast" statement into SQL.
210+
*
211+
* @param string $value
212+
* @return string
213+
*/
214+
public function compileJsonValueCast($value)
215+
{
216+
return 'json_query('.$value.')';
217+
}
218+
208219
/**
209220
* Compile a single having clause.
210221
*

src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ public function castAsJson($value)
180180

181181
$value = DB::connection()->getPdo()->quote($value);
182182

183-
return DB::raw("CAST($value AS JSON)");
183+
return DB::raw(
184+
DB::connection()->getQueryGrammar()->compileJsonValueCast($value)
185+
);
184186
}
185187

186188
/**
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Testing\Concerns;
4+
5+
use Illuminate\Database\ConnectionInterface;
6+
use Illuminate\Database\Query\Expression;
7+
use Illuminate\Database\Query\Grammars\MySqlGrammar;
8+
use Illuminate\Database\Query\Grammars\PostgresGrammar;
9+
use Illuminate\Database\Query\Grammars\SQLiteGrammar;
10+
use Illuminate\Database\Query\Grammars\SqlServerGrammar;
11+
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
12+
use Illuminate\Support\Facades\DB;
13+
use Illuminate\Support\Facades\Facade;
14+
use Mockery as m;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class InteractsWithDatabaseTest extends TestCase
18+
{
19+
protected function setUp(): void
20+
{
21+
Facade::clearResolvedInstances();
22+
Facade::setFacadeApplication(null);
23+
}
24+
25+
protected function tearDown(): void
26+
{
27+
m::close();
28+
}
29+
30+
public function testCastToJsonSqlite()
31+
{
32+
$grammar = new SQLiteGrammar();
33+
34+
$this->assertEquals(<<<'TEXT'
35+
'["foo","bar"]'
36+
TEXT,
37+
$this->castAsJson(['foo', 'bar'], $grammar)
38+
);
39+
40+
$this->assertEquals(<<<'TEXT'
41+
'["foo","bar"]'
42+
TEXT,
43+
$this->castAsJson(collect(['foo', 'bar']), $grammar)
44+
);
45+
46+
$this->assertEquals(<<<'TEXT'
47+
'{"foo":"bar"}'
48+
TEXT,
49+
$this->castAsJson((object) ['foo' => 'bar'], $grammar)
50+
);
51+
}
52+
53+
public function testCastToJsonPostgres()
54+
{
55+
$grammar = new PostgresGrammar();
56+
57+
$this->assertEquals(<<<'TEXT'
58+
'["foo","bar"]'
59+
TEXT,
60+
$this->castAsJson(['foo', 'bar'], $grammar)
61+
);
62+
63+
$this->assertEquals(<<<'TEXT'
64+
'["foo","bar"]'
65+
TEXT,
66+
$this->castAsJson(collect(['foo', 'bar']), $grammar)
67+
);
68+
69+
$this->assertEquals(<<<'TEXT'
70+
'{"foo":"bar"}'
71+
TEXT,
72+
$this->castAsJson((object) ['foo' => 'bar'], $grammar)
73+
);
74+
}
75+
76+
public function testCastToJsonSqlServer()
77+
{
78+
$grammar = new SqlServerGrammar();
79+
80+
$this->assertEquals(<<<'TEXT'
81+
json_query('["foo","bar"]')
82+
TEXT,
83+
$this->castAsJson(['foo', 'bar'], $grammar)
84+
);
85+
86+
$this->assertEquals(<<<'TEXT'
87+
json_query('["foo","bar"]')
88+
TEXT,
89+
$this->castAsJson(collect(['foo', 'bar']), $grammar)
90+
);
91+
92+
$this->assertEquals(<<<'TEXT'
93+
json_query('{"foo":"bar"}')
94+
TEXT,
95+
$this->castAsJson((object) ['foo' => 'bar'], $grammar)
96+
);
97+
}
98+
99+
public function testCastToJsonMySql()
100+
{
101+
$grammar = new MySqlGrammar();
102+
103+
$this->assertEquals(<<<'TEXT'
104+
cast('["foo","bar"]' as json)
105+
TEXT,
106+
$this->castAsJson(['foo', 'bar'], $grammar)
107+
);
108+
109+
$this->assertEquals(<<<'TEXT'
110+
cast('["foo","bar"]' as json)
111+
TEXT,
112+
$this->castAsJson(collect(['foo', 'bar']), $grammar)
113+
);
114+
115+
$this->assertEquals(<<<'TEXT'
116+
cast('{"foo":"bar"}' as json)
117+
TEXT,
118+
$this->castAsJson((object) ['foo' => 'bar'], $grammar)
119+
);
120+
}
121+
122+
protected function castAsJson($value, $grammar)
123+
{
124+
$connection = m::mock(ConnectionInterface::class);
125+
126+
$connection->shouldReceive('getQueryGrammar')->andReturn($grammar);
127+
128+
$connection->shouldReceive('getPdo->quote')->andReturnUsing(function ($value) {
129+
return "'".$value."'";
130+
});
131+
132+
DB::shouldReceive('connection')->andReturn($connection);
133+
134+
DB::shouldReceive('raw')->andReturnUsing(function ($value) {
135+
return new Expression($value);
136+
});
137+
138+
$instance = new class
139+
{
140+
use InteractsWithDatabase;
141+
};
142+
143+
return $instance->castAsJson($value)->getValue();
144+
}
145+
}

0 commit comments

Comments
 (0)