Skip to content

SQLite whereJsonContains grammar not working correctly #50246

@dmtar

Description

@dmtar

Laravel Version

10.44

PHP Version

8.1

Database Driver & Version

SQLite 3.42

Description

I have existing queries with whereJsonContains and some tests using it started failing after I migrated the framework from 9.x to 10.44. Up until now we had a patch that uses PDO sqliteCreateFunction in order to fake the exact same behaviour of the MySQL json_contains into SQLite.

However I saw native support in Laravel 10 added with this PR and tried to use it instead of our internal one. The query is built like bellow:

$query->whereJsonContains('value', ['enabled' => true, 'some_other_flag' => false]);

In MySQL grammar the above compiles to

select * from `settings` where json_contains(`value`, '{\"enabled\":true,\"some_other_flag\":false}');

In SQLite while running tests the same query compiles to

select * from "settings" where exists (select 1 from json_each("value") where "json_each"."value" is 1);

The above has couple of issues

  1. it does not take into consideration enabled and some_other_flag json properties and their values
  2. it will always return 0 rows because of json_each("value"), however if you prefix it with the table name json_each("settings"."value") it works, but of course returns all rows containing any kind of json value that is treated as "truthy"

What I'm expecting to get is something like

select * from "settings" where exists (select 1 from json_each(settings.value, '$."enabled"') where "json_each"."value" is 1) and exists (select 1 from json_each(settings.value, '$."some_other_flag"') where "json_each"."value" is 0)

That would have exactly the same behaviour like in MySQL.
If I change
$query->whereJsonContains('value', ['enabled' => true, 'some_other_flag' => false]);
to

$query->whereJsonContains('value->enabled', true)->whereJsonContains('value-> some_other_flag', false);

I can bypass issue 1, but still I can't get around issue 2. I thought it's because "value" is double quoted, but I tried with json_each('value') and json_each(value) without luck, so its something with the internals of the json_each function and the value column name.

Steps To Reproduce

  1. Create model and table with json column named value
  2. Insert 3 records with the following values {"enabled":true,"some_other_flag":true}, {"enabled":false,"some_other_flag":false}, {"enabled":true,"some_other_flag":false}
  3. With a query builder try to fetch $query->whereJsonContains('value', ['enabled' => true, 'some_other_flag' => false]);
  4. Write unit test that checks the result

In MySQL this will return 1 record, In SQLite they will be 3.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions