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

Add support for comparing jsonb @> with boolean values #399

Merged

Conversation

leandrocp
Copy link
Contributor

@leandrocp leandrocp commented May 2, 2022

The commit 96f70b5 may have introduced a breaking change when dealing with boolean values.

Given the schema

defmodule Schema do
use Ecto.Schema
schema "schema" do
field :x, :integer
field :y, :integer
field :z, :integer
field :w, {:array, :integer}
field :meta, :map
has_many :comments, Ecto.Adapters.PostgresTest.Schema2,
references: :x,
foreign_key: :z
has_one :permalink, Ecto.Adapters.PostgresTest.Schema3,
references: :y,
foreign_key: :id
end
end
and considering a record with meta stored as {"enabled": true} on v3.7.2 one could write the following query:

     query = Schema |> where([s], s.meta["enabled"] == "true") |> select(true) |> plan()
     assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"#>'{"enabled"}') = 'true')|

Note that you can't write s.meta["enabled"] == true as that would generate the following invalid query, either on v3.7.2 or v3.8.1:

     query = Schema |> where([s], s.meta["enabled"] == true) |> select(true) |> plan()
     assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"#>'{"enabled"}') = TRUE)|

So far, so good.

On v3.8.1 the same query s.meta["enabled"] == "true" is now generated as:

     query = Schema |> where([s], s.meta["enabled"] == "true") |> select(true) |> plan()
     assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"@>'{"enabled": "true"}'))|

And turns out that query won't return the expected records, see a live example at https://dbfiddle.uk/?rdbms=postgres_13&fiddle=9987baf6c8972988319559cb0fafb655

In order to make it work the query should be "meta"@>'{"enabled": true}' (an actual boolean instead of a string) or using the #> operator as before.

Proposed solution

AFAIK boolean values are expected to be transformed to their text representation

defp expr(true, _sources, _query), do: "TRUE"
defp expr(false, _sources, _query), do: "FALSE"
so using the @> wouldn't work or would introduce a new behavior, right? So the proposed solution is to revert to the old query for bool expr and also deal with true/false to make s.meta["enabled"] == true work.

I'm not sure if we should check for right in ["TRUE", "true", "FALSE", "false"] but I'm concerned because without such guard it will generate the query using @> which would fail to return the expected results silently.

@josevalim
Copy link
Member

This is tricky. 🤔 Do we have this issue with integers?

@josevalim
Copy link
Member

@leandrocp I think may preferred solution to address this is to actually make == true work. Otherwise == "true" is ambiguous because someone may actually be expecting the string "true".

@leandrocp
Copy link
Contributor Author

leandrocp commented May 3, 2022

@josevalim seems like the problem goes beyond boolean values:

  1. The operator @> works with jsonb fields only
    AFAIK Ecto makes no distinction if the field is json or jsonb, so to support both then that @> operator can't be used.

  2. The operator #> returns either json or jsonb
    Comparing with scalar values fails as pointed out by @greg-rychlewski

See this example: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=e55dda0ca5e9d25842d10a1f9a2294a6

I don't know the context of 96f70b5 but a possible solution is to just use the #>> operator by default and fix the comparison with boolean.

@josevalim
Copy link
Member

I think that operator is not optimizable, which defeats the purpose of the PR. :( So we have two options:

  1. revert
  2. provide an option (perhaps true by default) to use optimized jsonb operator. so people have an option to disable. then we fix the boolean handling

@leandrocp
Copy link
Contributor Author

  1. "Revert"
    Even if we revert, I believe we should fix the comparison with boolean and change the default json operator to #>>

  2. "Option"
    How the API would like to pass a json operator as an option in a clause like where: t.column["key"] == "value"?

@josevalim
Copy link
Member

Wait. I think we documented when we added this syntax that it only works for jsonb. And I think that’s fair. Jsonb is much more efficient and I don’t think it is worth making everything slower for json. Then you can use fragments.

@leandrocp
Copy link
Contributor Author

leandrocp commented May 3, 2022

Cool, so two places I found a mention on jsonb type are https://hexdocs.pm/ecto/Ecto.Query.API.html#json_extract_path/2-warning-indexes-on-postgresql and https://hexdocs.pm/ecto/embedded-schemas.html

Wdyt about the following plan?

ecto

ecto_sql

  • Fix jsonb comparison with boolean on this PR

@josevalim
Copy link
Member

I don't think we need to update where. :map/:array already defaults to :jsonb and you will get a query error if you try to use json anyway. :) The other two tasks look great!

@josevalim josevalim merged commit 65b1c65 into elixir-ecto:master May 3, 2022
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

leandrocp added a commit to leandrocp/ecto that referenced this pull request May 3, 2022
josevalim pushed a commit to elixir-ecto/ecto that referenced this pull request May 3, 2022
@leandrocp leandrocp changed the title Fix possible breaking change on json_extract_path for boolean values Add support for comparing jsonb @> with boolean values May 3, 2022
@s3cur3 s3cur3 mentioned this pull request May 18, 2022
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.

2 participants