Skip to content

Commit

Permalink
[5.8] Support JSON queries on MariaDB (#25517)
Browse files Browse the repository at this point in the history
* Simplify SQLiteGrammar and SqlServerGrammar

* Support JSON queries on MariaDB
  • Loading branch information
staudenmeir authored and taylorotwell committed Sep 13, 2018
1 parent d5f3e80 commit e4bd21f
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 36 deletions.
12 changes: 7 additions & 5 deletions src/Illuminate/Database/Query/Grammars/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -966,15 +966,16 @@ protected function wrapJsonSelector($value)
* Split the given JSON selector into the field and the optional path and wrap them separately.
*
* @param string $column
* @param string $delimiter
* @return array
*/
protected function wrapJsonFieldAndPath($column)
protected function wrapJsonFieldAndPath($column, $delimiter = '->')
{
$parts = explode('->', $column, 2);
$parts = explode($delimiter, $column, 2);

$field = $this->wrap($parts[0]);

$path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1]) : '';
$path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1], $delimiter) : '';

return [$field, $path];
}
Expand All @@ -983,11 +984,12 @@ protected function wrapJsonFieldAndPath($column)
* Wrap the given JSON path.
*
* @param string $value
* @param string $delimiter
* @return string
*/
protected function wrapJsonPath($value)
protected function wrapJsonPath($value, $delimiter = '->')
{
return '\'$."'.str_replace('->', '"."', $value).'"\'';
return '\'$."'.str_replace($delimiter, '"."', $value).'"\'';
}

/**
Expand Down
20 changes: 11 additions & 9 deletions src/Illuminate/Database/Query/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public function compileSelect(Builder $query)
*/
protected function compileJsonContains($column, $value)
{
return 'json_contains('.$this->wrap($column).', '.$value.')';
list($field, $path) = $this->wrapJsonFieldAndPath($column);

return 'json_contains('.$field.', '.$value.$path.')';
}

/**
Expand Down Expand Up @@ -317,16 +319,16 @@ protected function wrapValue($value)
*/
protected function wrapJsonSelector($value)
{
$delimiter = Str::contains($value, '->>')
? '->>'
: '->';
$delimiter = Str::contains($value, '->>') ? '->>' : '->';

$path = explode($delimiter, $value);
list($field, $path) = $this->wrapJsonFieldAndPath($value, $delimiter);

$field = $this->wrapSegments(explode('.', array_shift($path)));
$selector = 'json_extract('.$field.$path.')';

if ($delimiter === '->>') {
$selector = 'json_unquote('.$selector.')';
}

return sprintf('%s'.$delimiter.'\'$.%s\'', $field, collect($path)->map(function ($part) {
return '"'.$part.'"';
})->implode('.'));
return $selector;
}
}
6 changes: 1 addition & 5 deletions src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,7 @@ public function compileTruncate(Builder $query)
*/
protected function wrapJsonSelector($value)
{
$parts = explode('->', $value, 2);

$field = $this->wrap($parts[0]);

$path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1]) : '';
list($field, $path) = $this->wrapJsonFieldAndPath($value);

$selector = 'json_extract('.$field.$path.')';

Expand Down
6 changes: 2 additions & 4 deletions src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -476,11 +476,9 @@ protected function wrapValue($value)
*/
protected function wrapJsonSelector($value)
{
$parts = explode('->', $value, 2);
list($field, $path) = $this->wrapJsonFieldAndPath($value);

$field = $this->wrapSegments(explode('.', array_shift($parts)));

return 'json_value('.$field.', '.$this->wrapJsonPath($parts[0]).')';
return 'json_value('.$field.$path.')';
}

/**
Expand Down
32 changes: 19 additions & 13 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2003,7 +2003,7 @@ public function testMySqlWrappingJsonWithString()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->sku', '=', 'foo-bar');
$this->assertEquals('select * from `users` where `items`->\'$."sku"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."sku"\') = ?', $builder->toSql());
$this->assertCount(1, $builder->getRawBindings()['where']);
$this->assertEquals('foo-bar', $builder->getRawBindings()['where'][0]);
}
Expand All @@ -2012,35 +2012,41 @@ public function testMySqlWrappingJsonWithInteger()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->price', '=', 1);
$this->assertEquals('select * from `users` where `items`->\'$."price"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."price"\') = ?', $builder->toSql());
}

public function testMySqlWrappingJsonWithDouble()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->price', '=', 1.5);
$this->assertEquals('select * from `users` where `items`->\'$."price"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."price"\') = ?', $builder->toSql());
}

public function testMySqlWrappingJsonWithBoolean()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->available', '=', true);
$this->assertEquals('select * from `users` where `items`->\'$."available"\' = true', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."available"\') = true', $builder->toSql());
}

public function testMySqlWrappingJsonWithBooleanAndIntegerThatLooksLikeOne()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->available', '=', true)->where('items->active', '=', false)->where('items->number_available', '=', 0);
$this->assertEquals('select * from `users` where `items`->\'$."available"\' = true and `items`->\'$."active"\' = false and `items`->\'$."number_available"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."available"\') = true and json_extract(`items`, \'$."active"\') = false and json_extract(`items`, \'$."number_available"\') = ?', $builder->toSql());
}

public function testMySqlWrappingJsonWithoutQuote()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->>sku', '=', 'foo-bar');
$this->assertEquals('select * from `users` where `items`->>\'$."sku"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_unquote(json_extract(`items`, \'$."sku"\')) = ?', $builder->toSql());
$this->assertCount(1, $builder->getRawBindings()['where']);
$this->assertEquals('foo-bar', $builder->getRawBindings()['where'][0]);

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->>price->>in_usd', '=', 'foo-bar');
$this->assertEquals('select * from `users` where json_unquote(json_extract(`items`, \'$."price"."in_usd"\')) = ?', $builder->toSql());
$this->assertCount(1, $builder->getRawBindings()['where']);
$this->assertEquals('foo-bar', $builder->getRawBindings()['where'][0]);
}
Expand All @@ -2053,15 +2059,15 @@ public function testMySqlWrappingJson()

$builder = $this->getMySqlBuilder();
$builder->select('items->price')->from('users')->where('users.items->price', '=', 1)->orderBy('items->price');
$this->assertEquals('select `items`->\'$."price"\' from `users` where `users`.`items`->\'$."price"\' = ? order by `items`->\'$."price"\' asc', $builder->toSql());
$this->assertEquals('select json_extract(`items`, \'$."price"\') from `users` where json_extract(`users`.`items`, \'$."price"\') = ? order by json_extract(`items`, \'$."price"\') asc', $builder->toSql());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->price->in_usd', '=', 1);
$this->assertEquals('select * from `users` where `items`->\'$."price"."in_usd"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."price"."in_usd"\') = ?', $builder->toSql());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('items->price->in_usd', '=', 1)->where('items->age', '=', 2);
$this->assertEquals('select * from `users` where `items`->\'$."price"."in_usd"\' = ? and `items`->\'$."age"\' = ?', $builder->toSql());
$this->assertEquals('select * from `users` where json_extract(`items`, \'$."price"."in_usd"\') = ? and json_extract(`items`, \'$."age"\') = ?', $builder->toSql());
}

public function testPostgresWrappingJson()
Expand Down Expand Up @@ -2689,12 +2695,12 @@ public function testWhereJsonContainsMySql()

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonContains('users.options->languages', ['en']);
$this->assertEquals('select * from `users` where json_contains(`users`.`options`->\'$."languages"\', ?)', $builder->toSql());
$this->assertEquals('select * from `users` where json_contains(`users`.`options`, ?, \'$."languages"\')', $builder->toSql());
$this->assertEquals(['["en"]'], $builder->getBindings());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonContains('options->languages', new Raw("'[\"en\"]'"));
$this->assertEquals('select * from `users` where `id` = ? or json_contains(`options`->\'$."languages"\', \'["en"]\')', $builder->toSql());
$this->assertEquals('select * from `users` where `id` = ? or json_contains(`options`, \'["en"]\', \'$."languages"\')', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());
}

Expand Down Expand Up @@ -2747,12 +2753,12 @@ public function testWhereJsonDoesntContainMySql()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonDoesntContain('options->languages', ['en']);
$this->assertEquals('select * from `users` where not json_contains(`options`->\'$."languages"\', ?)', $builder->toSql());
$this->assertEquals('select * from `users` where not json_contains(`options`, ?, \'$."languages"\')', $builder->toSql());
$this->assertEquals(['["en"]'], $builder->getBindings());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonDoesntContain('options->languages', new Raw("'[\"en\"]'"));
$this->assertEquals('select * from `users` where `id` = ? or not json_contains(`options`->\'$."languages"\', \'["en"]\')', $builder->toSql());
$this->assertEquals('select * from `users` where `id` = ? or not json_contains(`options`, \'["en"]\', \'$."languages"\')', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());
}

Expand Down

0 comments on commit e4bd21f

Please sign in to comment.