From 4ce505f3c8af8857d5ca5dbe7cbb2a9d1c9e1a19 Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Mon, 2 May 2022 12:19:13 -0400 Subject: [PATCH 1/2] Fix json_extract_path for boolean values --- lib/ecto/adapters/postgres/connection.ex | 8 ++++++++ test/ecto/adapters/postgres_test.exs | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/ecto/adapters/postgres/connection.ex b/lib/ecto/adapters/postgres/connection.ex index 200d8f4e..3fc5d6ce 100644 --- a/lib/ecto/adapters/postgres/connection.ex +++ b/lib/ecto/adapters/postgres/connection.ex @@ -709,6 +709,12 @@ if Code.ensure_loaded?(Postgrex) do defp expr({:count, _, []}, _sources, _query), do: "count(*)" + defp expr({:==, _, [{:json_extract_path, _, [expr, path]} = left, right]}, sources, query) + when is_boolean(right) or right in ["TRUE", "true", "FALSE", "false"] do + right = quote_boolean(right) + [maybe_paren(left, sources, query), " = " | maybe_paren(right, sources, query)] + end + defp expr({:==, _, [{:json_extract_path, _, [expr, path]} = left, right]}, sources, query) when is_binary(right) or is_integer(right) do case Enum.split(path, -1) do @@ -1304,7 +1310,9 @@ if Code.ensure_loaded?(Postgrex) do # TRUE, ON, or 1 to enable the option, and FALSE, OFF, or 0 to disable it defp quote_boolean(nil), do: nil defp quote_boolean(true), do: "TRUE" + defp quote_boolean(value) when value in ["TRUE", "true"], do: "TRUE" defp quote_boolean(false), do: "FALSE" + defp quote_boolean(value) when value in ["FALSE", "false"], do: "FALSE" defp quote_boolean(value), do: error!(nil, "bad boolean value #{value}") defp format_to_sql(:text), do: "FORMAT TEXT" diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index a31096bb..a962d5b6 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -607,18 +607,29 @@ defmodule Ecto.Adapters.PostgresTest do test "optimized json_extract_path" do query = Schema |> where([s], s.meta["id"] == 123) |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM \"schema\" AS s0 WHERE ((s0."meta"@>'{"id": 123}'))| + assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"@>'{"id": 123}'))| query = Schema |> where([s], s.meta["id"] == "123") |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM \"schema\" AS s0 WHERE ((s0."meta"@>'{"id": "123"}'))| + assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"@>'{"id": "123"}'))| query = Schema |> where([s], s.meta["tags"][0]["name"] == "123") |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM \"schema\" AS s0 WHERE (((s0."meta"#>'{"tags",0}')@>'{"name": "123"}'))| + assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE (((s0."meta"#>'{"tags",0}')@>'{"name": "123"}'))| query = Schema |> where([s], s.meta[0] == "123") |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM \"schema\" AS s0 WHERE ((s0.\"meta\"#>'{0}') = '123')| + assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0.\"meta\"#>'{0}') = '123')| end + test "json_extract_path for boolean values" do + 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')| + + 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')| + + query = Schema |> where([s], s.meta["audit"]["enabled"] == "true") |> select(true) |> plan() + assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"#>'{"audit","enabled"}') = 'TRUE')| + end + test "nested expressions" do z = 123 query = from(r in Schema, []) |> select([r], r.x > 0 and (r.y > ^(-z)) or true) |> plan() From db3357741e77a38791aa6c70efc161d105add29a Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Tue, 3 May 2022 10:10:25 -0400 Subject: [PATCH 2/2] Fix jsonb @> comparison with boolean values --- lib/ecto/adapters/postgres/connection.ex | 13 ++++--------- test/ecto/adapters/postgres_test.exs | 15 +++++---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/ecto/adapters/postgres/connection.ex b/lib/ecto/adapters/postgres/connection.ex index 3fc5d6ce..a04dd1f3 100644 --- a/lib/ecto/adapters/postgres/connection.ex +++ b/lib/ecto/adapters/postgres/connection.ex @@ -710,13 +710,7 @@ if Code.ensure_loaded?(Postgrex) do defp expr({:count, _, []}, _sources, _query), do: "count(*)" defp expr({:==, _, [{:json_extract_path, _, [expr, path]} = left, right]}, sources, query) - when is_boolean(right) or right in ["TRUE", "true", "FALSE", "false"] do - right = quote_boolean(right) - [maybe_paren(left, sources, query), " = " | maybe_paren(right, sources, query)] - end - - defp expr({:==, _, [{:json_extract_path, _, [expr, path]} = left, right]}, sources, query) - when is_binary(right) or is_integer(right) do + when is_binary(right) or is_integer(right) or is_boolean(right) do case Enum.split(path, -1) do {path, [last]} when is_binary(last) -> extracted = json_extract_path(expr, path, sources, query) @@ -1310,9 +1304,7 @@ if Code.ensure_loaded?(Postgrex) do # TRUE, ON, or 1 to enable the option, and FALSE, OFF, or 0 to disable it defp quote_boolean(nil), do: nil defp quote_boolean(true), do: "TRUE" - defp quote_boolean(value) when value in ["TRUE", "true"], do: "TRUE" defp quote_boolean(false), do: "FALSE" - defp quote_boolean(value) when value in ["FALSE", "false"], do: "FALSE" defp quote_boolean(value), do: error!(nil, "bad boolean value #{value}") defp format_to_sql(:text), do: "FORMAT TEXT" @@ -1362,6 +1354,9 @@ if Code.ensure_loaded?(Postgrex) do Integer.to_string(value) end + defp escape_json(true), do: ["true"] + defp escape_json(false), do: ["false"] + defp ecto_to_db({:array, t}), do: [ecto_to_db(t), ?[, ?]] defp ecto_to_db(:id), do: "integer" defp ecto_to_db(:identity), do: "bigint" diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index a962d5b6..6ea79ec0 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -617,18 +617,13 @@ defmodule Ecto.Adapters.PostgresTest do query = Schema |> where([s], s.meta[0] == "123") |> select(true) |> plan() assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0.\"meta\"#>'{0}') = '123')| - end - - test "json_extract_path for boolean values" do - 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')| - 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')| + 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}'))| - query = Schema |> where([s], s.meta["audit"]["enabled"] == "true") |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"#>'{"audit","enabled"}') = 'TRUE')| - end + query = Schema |> where([s], s.meta["extra"][0]["enabled"] == false) |> select(true) |> plan() + assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE (((s0."meta"#>'{"extra",0}')@>'{"enabled": false}'))| + end test "nested expressions" do z = 123