Skip to content

[9.x] Database queries containing JSON paths support array index braces, part 2 - #41767

Merged
taylorotwell merged 3 commits into
laravel:9.xfrom
derekmd:fix-postgres-json-paths-with-array-index
Apr 1, 2022
Merged

[9.x] Database queries containing JSON paths support array index braces, part 2#41767
taylorotwell merged 3 commits into
laravel:9.xfrom
derekmd:fix-postgres-json-paths-with-array-index

Conversation

@derekmd

@derekmd derekmd commented Mar 31, 2022

Copy link
Copy Markdown
Contributor

Follow-up to #38391 meant to fix #26415
Related to today's PR #41756

  1. DB::table('json_table')
        ->where('column->json_option[0]', 'foo')
        ->update(['column->json_option[0]', => 'bar']);

    The previous PR allowed the above query to match by JSON array index. But the fixes didn't apply to the Postgres database driver that overrides PostgresGrammer@wrapJsonSelector().

    Postgres does already support this:

    DB::table('json_table')
       ->where('column->json_option->0', 'foo')
       ->update(['column->json_option->0', => 'bar']);

    This fix allows the first PHP snippet to also work, bringing it in line with the other database drivers. SQL:

    -- before
    update "json_table"
    set "column" = jsonb_set("column"::jsonb, '{"json_option[0]"}', '"bar"'::jsonb)
    where ("column"->'json_option[0]')::jsonb = '"foo"'::jsonb;
    
    -- after, array index is extracted and braces are stripped
    update "json_table"
    set "column" = jsonb_set("column"::jsonb, '{"json_option",0}', '"bar"'::jsonb)
    where ("column"->'json_option'->0)::jsonb = '"foo"'::jsonb;

    Now the GitHub Actions config has a Postgres container I've added some integration tests.

  2. Illuminate\Database\Schema\Grammars\Grammar@wrapJsonFieldAndPath() was a copy and paste duplicate from Illuminate\Database\Query\Grammars\Grammar and didn't receive the same fixes from the previous PR. It's a far edge case for column methods virtualAsJson() and storedAsJson() to use an array index but it probably makes sense for these classes to share a trait for compiling SQL containing JSON paths.

Illuminate\Database\Query\Grammars\Grammar@wrapJsonFieldAndPath() and
Illuminate\Database\Schema\Grammars\Grammar@wrapJsonFieldAndPath() were
copy and pasted duplicates but the query version had JSON path fixes
applied that the schema version doesn't have.

Instead add a trait to make the two classes share these methods.
protected function wrapJsonPathAttributes($path)
{
return array_map(function ($attribute) {
$quote = func_num_args() === 2 ? func_get_arg(1) : "'";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    protected function wrapJsonPathAttributes($path, $quote = "'")
    {

I forget if Laravel considers protected method argument changes as breaking. I guess userland may extend PostgresGrammar and override this method.

Similar to MySQL, SQL Server, and SQLite, make Postgres support
`$query->where('column->json_key[0]', 'foo')`. Postgres also allows
equivalent call `$query->where('column->json_key->0', 'foo')`.

Unlike the other database drivers, the SQL doesn't compile to a JSON
path expression. The array indices must be parsed from the string,
separating them into new segments. e.g.,

$query->where('column->json_key[0]', 'foo')
@derekmd
derekmd force-pushed the fix-postgres-json-paths-with-array-index branch from fb5179c to ab59bf5 Compare April 1, 2022 14:39
@taylorotwell
taylorotwell merged commit d80471d into laravel:9.x Apr 1, 2022
@derekmd
derekmd deleted the fix-postgres-json-paths-with-array-index branch April 1, 2022 15:11
@taylorotwell

Copy link
Copy Markdown
Member

Thanks @derekmd! We would be pretty lost without you 😅

@robsontenorio

robsontenorio commented Jul 20, 2025

Copy link
Copy Markdown

@derekmd

I notice that ->update() from Eloquent does not use jsonb_set syntax, but a default SQL SET "a" = '{b: 12}'

$person->update([
  'a->b' => 12
]);

Is that expected, can we have in any way the same jsonb_set syntax for Eloquent?

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.

mysql json field cannot update nested array item through nested array's index

3 participants