Skip to content

Conversation

derekmd
Copy link
Contributor

@derekmd derekmd commented Mar 31, 2022

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
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
Member

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

@robsontenorio
Copy link

robsontenorio commented Jul 20, 2025

@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