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 braces, part 2 #41767

Merged

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.
*
* @param array $path
* @return array
*/
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 😅

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
2 participants