diff --git a/mix.exs b/mix.exs index a5f32925..9858e475 100644 --- a/mix.exs +++ b/mix.exs @@ -188,7 +188,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.32")}, {:spark, "~> 2.3 and >= 2.3.4"}, - {:ash_sql, ash_sql_version("~> 0.6 and >= 0.6.8")}, + {:ash_sql, ash_sql_version("~> 0.7")}, {:igniter, "~> 0.6 and >= 0.6.29", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index 10ffd9e2..88f4b130 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.32.0", "046ec1b817b026aefb0a04013d340fec86ec4ceabb3f11517332edaaa7f89dca", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee80eeacaf5fa8995cc875741bc3f248ccd77a3e68a0927a086d9c7ed562e926"}, - "ash_sql": {:hex, :ash_sql, "0.6.8", "d59f7be3e749db6b258da50a7d564e270038c9335a16da2c1d1c5bf14a08e944", [:mix], [{:ash, ">= 3.24.5 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, ">= 3.13.4 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "80dfa363f3f69a1cd9a5bcc1e8c6213fbe7755a25d6885d78df7b88ce5230118"}, + "ash_sql": {:hex, :ash_sql, "0.7.0", "88b9aef905c44dbd8230e2f8f1be6f6e212d8af689533666f30589129a582016", [:mix], [{:ash, "~> 3.32", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, ">= 3.13.4 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ada6217edba5c60f99502528a03241664c5e7b56036278258cc5c3a3c9cf1e12"}, "benchee": {:hex, :benchee, "1.5.1", "b95cbc36c4b98969a5c592a246e171041eb683c56bad1cb4f49a3b081ba66087", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a539301f8dfd4efc5c5123bfb9d47ebde20092a863a5b5b16c2a60d2243dfce7"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"}, diff --git a/test/range_expr_test.exs b/test/range_expr_test.exs new file mode 100644 index 00000000..e31fd349 --- /dev/null +++ b/test/range_expr_test.exs @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.RangeExprTest do + @moduledoc false + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.Booking + + require Ash.Query + + @march ~U[2026-03-01 00:00:00.000000Z] + @april ~U[2026-04-01 00:00:00.000000Z] + @may ~U[2026-05-01 00:00:00.000000Z] + @june ~U[2026-06-01 00:00:00.000000Z] + + defp create(attrs) do + Booking |> Ash.Changeset.for_create(:create, attrs) |> Ash.create!() + end + + setup do + march_quarter = + create(%{ + stay: %Ash.Range{lower: @march, upper: @april}, + guests: %Ash.Range{lower: 1, upper: 5} + }) + + may_onwards = + create(%{ + stay: %Ash.Range{lower: @may, upper: @june}, + guests: %Ash.Range{lower: 4, upper: 9} + }) + + %{march_quarter: march_quarter, may_onwards: may_onwards} + end + + test "range_overlaps/2 filters on the && operator", %{march_quarter: march_quarter} do + window = %Ash.Range{lower: @march, upper: @may} + + assert [found] = + Booking + |> Ash.Query.filter(range_overlaps(stay, ^window)) + |> Ash.read!() + + assert found.id == march_quarter.id + end + + test "range_overlaps/2 sees a shared point", %{march_quarter: mq, may_onwards: mo} do + assert [_, _] = + Booking + |> Ash.Query.filter(range_overlaps(guests, ^%Ash.Range{lower: 4, upper: 6})) + |> Ash.read!() + + refute mq.id == mo.id + end + + test "range_lower/1 and range_upper/1 read the endpoints", %{march_quarter: march_quarter} do + assert [found] = + Booking + |> Ash.Query.filter(range_lower(stay) == type(^@march, :utc_datetime_usec)) + |> Ash.read!() + + assert found.id == march_quarter.id + + assert [^found] = + Booking + |> Ash.Query.filter(range_upper(stay) == type(^@april, :utc_datetime_usec)) + |> Ash.read!() + end + + test "range_contains/2 holds a point", %{march_quarter: march_quarter} do + assert [found] = + Booking + |> Ash.Query.filter(range_contains(guests, type(3, :integer))) + |> Ash.read!() + + assert found.id == march_quarter.id + end + + test "range_contains/2 holds a range", %{march_quarter: march_quarter} do + inner = %Ash.Range{lower: 2, upper: 4} + + assert [found] = + Booking + |> Ash.Query.filter(range_contains(guests, ^inner)) + |> Ash.read!() + + assert found.id == march_quarter.id + end + + test "range_adjacent/2 finds the abutting range", %{may_onwards: may_onwards} do + abutting = %Ash.Range{lower: @june, upper: ~U[2026-07-01 00:00:00.000000Z]} + + assert [found] = + Booking + |> Ash.Query.filter(range_adjacent(stay, ^abutting)) + |> Ash.read!() + + assert found.id == may_onwards.id + end + + test "range_lower/1 sorts by where a range begins" do + assert [%{stay: %Ash.Range{lower: @march}}, %{stay: %Ash.Range{lower: @may}}] = + Booking + |> Ash.Query.sort(stay: :asc) + |> Ash.read!() + end + + test "range_upper/1 is expressible over a discrete inner type", %{march_quarter: march_quarter} do + assert [found] = + Booking + |> Ash.Query.filter(range_upper(guests) == type(5, :integer)) + |> Ash.read!() + + assert found.id == march_quarter.id + end +end