Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Database queries containing JSON paths support array index references #38391

Merged
merged 1 commit into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/Illuminate/Database/Query/Grammars/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use RuntimeException;

class Grammar extends BaseGrammar
Expand Down Expand Up @@ -1223,7 +1224,34 @@ protected function wrapJsonPath($value, $delimiter = '->')
{
$value = preg_replace("/([\\\\]+)?\\'/", "''", $value);

return '\'$."'.str_replace($delimiter, '"."', $value).'"\'';
$jsonPath = collect(explode($delimiter, $value))
->map(function ($segment) {
return $this->wrapJsonPathSegment($segment);
})
->join('.');

return "'$".(str_starts_with($jsonPath, '[') ? '' : '.').$jsonPath."'";
}

/**
* Wrap the given JSON path segment.
*
* @param string $segment
* @return string
*/
protected function wrapJsonPathSegment($segment)
{
if (preg_match('/(\[[^\]]+\])+$/', $segment, $parts)) {
$key = Str::beforeLast($segment, $parts[0]);

if (! empty($key)) {
return '"'.$key.'"'.$parts[0];
}

return $parts[0];
}

return '"'.$segment.'"';
}

/**
Expand Down
23 changes: 23 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2883,6 +2883,29 @@ public function testMySqlUpdateWrappingJsonArray()
]);
}

public function testMySqlUpdateWrappingJsonPathArrayIndex()
{
$grammar = new MySqlGrammar;
$processor = m::mock(Processor::class);

$connection = $this->createMock(ConnectionInterface::class);
$connection->expects($this->once())
->method('update')
->with(
'update `users` set `options` = json_set(`options`, \'$[1]."2fa"\', false), `meta` = json_set(`meta`, \'$."tags"[0][2]\', ?) where `active` = ?',
[
'large',
1,
]
);

$builder = new Builder($connection, $grammar, $processor);
$builder->from('users')->where('active', 1)->update([
'options->[1]->2fa' => false,
'meta->tags[0][2]' => 'large',
]);
}

public function testMySqlUpdateWithJsonPreparesBindingsCorrectly()
{
$grammar = new MySqlGrammar;
Expand Down
20 changes: 20 additions & 0 deletions tests/Integration/Database/DatabaseMySqlConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ public function jsonWhereNullDataProvider()
'nested key exists and null' => [true, 'nested->value', ['nested' => ['value' => null]]],
'nested key exists and "null"' => [false, 'nested->value', ['nested' => ['value' => 'null']]],
'nested key exists and not null' => [false, 'nested->value', ['nested' => ['value' => false]]],
'array index not exists' => [false, '[0]', [1 => 'invalid']],
'array index exists and null' => [true, '[0]', [null]],
'array index exists and "null"' => [false, '[0]', ['null']],
'array index exists and not null' => [false, '[0]', [false]],
'nested array index not exists' => [false, 'nested[0]', ['nested' => [1 => 'nested->invalid']]],
'nested array index exists and null' => [true, 'nested->value[1]', ['nested' => ['value' => [0, null]]]],
'nested array index exists and "null"' => [false, 'nested->value[1]', ['nested' => ['value' => [0, 'null']]]],
'nested array index exists and not null' => [false, 'nested->value[1]', ['nested' => ['value' => [0, false]]]],
];
}

public function testJsonPathUpdate()
{
DB::table(self::TABLE)->insert([
[self::JSON_COL => '{"foo":["bar"]}'],
[self::JSON_COL => '{"foo":["baz"]}'],
]);
$updatedCount = DB::table(self::TABLE)->where(self::JSON_COL.'->foo[0]', 'baz')->update([
self::JSON_COL.'->foo[0]' => 'updated',
]);
$this->assertSame(1, $updatedCount);
}
}