Git Product home page Git Product logo

ash_postgres's Introduction

ash_postgres's People

Contributors

ahey avatar andrewcallahan avatar barnabasj avatar bcksl avatar brettkolodny avatar dependabot[bot] avatar frankdugan3 avatar jechol avatar jimsynz avatar joshprice avatar kernel-io avatar lifeofdan avatar michaelst avatar moissela avatar mrdotb avatar nallwhy avatar rapidfsub avatar rbino avatar regularfellow avatar rgraff avatar ryanrborn avatar sevenseacat avatar sezaru avatar simonmcconnell avatar tommasop avatar totaltrash avatar vonagam avatar wolfdan avatar zachdaniel avatar zimt28 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ash_postgres's Issues

Custom schema does not get carried over to underlying SQL queries

Describe the bug

When an Ash resource is defined using a custom schema (via schema("blah")), when doing queries, the underlying queries don't seem to take the custom schema into account.

To Reproduce

Add schema("anything_but_public") to a resource.

Expected behavior

Queries such as:

MyAshResource
|> Ash.Query.filter(user_id: user.id)
|> Api.read_one!()

should work, but it doesn't. With the following error:

** (Ash.Error.Unknown)

* ** (Postgrex.Error) ERROR 42P01 (undefined_table) relation "blah" does not exist

    query: SELECT f0."id", f0."source", f0."money", f0."status", f0."metadata", f0."inserted_at", f0."updated_at", f0."user_id" FROM "blah" AS f0 WHERE (f0."user_id" = $1::uuid)

** Runtime

  • Elixir version 1.13.4
  • Erlang version 24.3.4
  • OS macOS 12.4
  • Ash version 1.52.0-rc.4
  • Ash Posgres 610e388

Additional context

Everything worked fine before changing the schema. After running mix ash_postgres.generate_migrations, the migration file correctly contains prefix: "blah". Resetting the DB was also successful, can see the new schema and its tables.

The current experiment for runtime configuration does not work

We attempted to hijack the repo to embed configuration there, but its not really working out. The configuration isn't set on application start for some reason. That pattern isn't sustainable in terms of consistency with the rest of the ecosystem anyway.

Migration generator doesn't consider generated?-flag on integer values

Describe the bug
I created a migration for a resource with the following attribute

attribute :id, :integer, primary_key?: true, generated?: true

But the migration didn't consider the genrated?-flag and created the field like this

add(:id, :integer, null: true, default: nil, primary_key: true)

Unique DB migration names

Is your feature request related to a problem? Please describe.

At the moment, every time we run mix ash_postgres.generate_migrations, if there are changes, a migration file gets generated with names like 20220619142531_migrate_resources1.exs.

The problem with this is that when people work on different branches, they end up generating different migration files with the same module name, thus causing conflict.

Describe the solution you'd like

Instead of using 1, 2, 3... to increment the file and module names, a timestamp or UUID will prevent duplicated migration files far better.

So, instead of 20220619142531_migrate_resources1.exs, we could have either 20220619142531_migrate_ash_resources_20220619142531.exs or 20220619142531_migrate_ash_resources_863c8c09-a422-47e4-bb31-34a877825f85.exs.

What do you think?

integer_primary_key migration issue

Describe the bug

The migrator generates a non-incrementing, nullable field for integer_primary_key.

To Reproduce

An attribute defined as:

integer_primary_key :id

generates:

add(:id, :integer, null: true, default: nil, primary_key: true)

Expected behavior

add(:id, :serial, null: false, primary_key: true)

`uuid_primary_key` should allow autogenerated IDs

When defining UUID primary keys there doesn't seem to be a way to specify the keys to autogenerate. For example:

  attributes do
    uuid_primary_key :id
  end

generates the following migration

  def up do
    create table(:account, primary_key: false) do
      add :id, :uuid, null: false, primary_key: true
      ...

It'd be great to be able to opt into (or even default to) autogeneration:

  def up do
    create table(:account, primary_key: false) do
      add :id, :uuid, null: false, primary_key: true, autogenerate: true
      ...

If we opt in to auto generation:

  attributes do
    uuid_primary_key :id, autogenerate: true
  end

Or if we default to auto generation, this would allow opt-out

  attributes do
    uuid_primary_key :id, autogenerate: false
  end

Obviously opt-in is more backward compatible but I suspect that autogenerate should probably be the default?

Generated migration for new join tables causes error when running migration

Describe the bug
When creating a new join table through a resource declaration, running the generated migration file causes an error message such as:
** (Postgrex.Error) ERROR 42P16 (invalid_table_definition) multiple primary keys for table "resource1_resource2" are not allowed
SOLUTION: After having generated a new join table call,
when generating an "alter table(resource1_resource2)" call for adding id -> resource references,
sample_migration

DO: remove id property modifications (notably: "primary_key: true")

To Reproduce

Module definition, use Ash.Resource...

postgres do
table "resource1_resource2"
repo MyApp.Repo
end

relationships do
belongs_to :resource1, Resource1, primary_key?: true
belongs_to :resource2, Resource2, primary_key?: true
end

Expected behavior
Migrations should run without error

** Runtime

  • Elixir version: 1.10.4
  • Erlang version: 23
  • OS: OSX Catalina
  • Ash version: 1.22
  • any related extension versions

Additional context

Sort doesn't work with join table filter

When adding the filter on a many to many relationship where the Database resource has a :name field and the many to many users also has a :name field, the sort does not work.

Database
|> Ash.Query.sort(:name)
|> Ash.Query.filter(users.id == ^user.id or restrict_access == false)

Missing Jason dependency when generating migrations

Describe the bug
When generating migrations with mix ash_postgres.generate_migrations --apis MyApp.Api the following error occurs:

** (UndefinedFunctionError) function Jason.encode!/2 is undefined (module Jason is not available)
    Jason.encode!(%{attributes: [%{allow_nil?: false, default: "nil", name: :body, primary_key?: false, references: nil, type: :text}, %{allow_nil?: false, default: "nil", name: :id, primary_key?: true, references: nil, type: :binary_id}, %{allow_nil?: false, default: "false", name: :public, primary_key?: false, references: nil, type: :boolean}, %{allow_nil?: true, default: "nil", name: :user_id, primary_key?: false, references: %{destination_field: :id, table: "users"}, type: :binary_id}], base_filter: nil, hash: "3C7AC4CCC43983208605B89DC1C5BFBBCFB5B47AD02FC5E2A7EE90A865A6020A", identities: [], repo: AshTest.Repo, table: "tweets"}, [pretty: true])
    lib/migration_generator/migration_generator.ex:273: anonymous fn/3 in AshPostgres.MigrationGenerator.write_migration/4
    (elixir 1.10.2) lib/enum.ex:783: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir 1.10.2) lib/enum.ex:783: Enum.each/2
    lib/migration_generator/migration_generator.ex:272: AshPostgres.MigrationGenerator.write_migration/4
    (elixir 1.10.2) lib/enum.ex:789: anonymous fn/3 in Enum.each/2
    (stdlib 3.12.1) maps.erl:232: :maps.fold_1/3
    (elixir 1.10.2) lib/enum.ex:2127: Enum.each/2

Expected behavior
migration should be created

** Runtime

  • Elixir version
  • Erlang version
  • OS
  • Ash version
  • any related extension versions

Additional context
adding the jason dependency to mix solves the issue

Regression w/ multiple sorts

Describe the bug
There seems to be a regression in sorting, where sorting on multiple columns crashes the query engine. It still does it even when sorting on multiple "normal" attributes and not loading any aggs/calcs. Seems to be a problem w/ any multiple-sorts.

All Ash libs are at the lastest as of this post:

* ash 1.46.13 (Hex package) (mix)
  locked at 1.46.13 (ash) 776a44f1
  ok
* ash_graphql 0.16.21 (Hex package) (mix)
  locked at 0.16.21 (ash_graphql) 23ef9737
  ok
* ash_phoenix 0.5.5 (Hex package) (mix)
  locked at 0.5.5 (ash_phoenix) 4a43ac09
  ok
* ash_policy_authorizer 0.16.2 (Hex package) (mix)
  locked at 0.16.2 (ash_policy_authorizer) 508a0c17
  ok
* ash_postgres 0.40.8 (Hex package) (mix)
  locked at 0.40.8 (ash_postgres) 91a832bb
  ok

Additional context
Here is the error output (the query is also paginated):

iex(4)> {:EXIT, #PID<0.1169.0>, :normal}
#Ash.Query<
  resource: Hsm.Attendance.Record,
  sort: [in: :asc, out: :asc],
  select: [:out, :in, :duration_hours, :notes]
>
[error] Task #PID<0.1172.0> started from #PID<0.1144.0> terminating
** (FunctionClauseError) no function clause matching in :lists.mapfoldl/3
    (stdlib 3.15.1) lists.erl:1357: :lists.mapfoldl(#Function<7.20389857/2 in Macro.do_traverse_args/4>, :ok, {:asc, {{:., [], [{:&, [], [0]}, :out]}, [], []}})
    (stdlib 3.15.1) lists.erl:1359: :lists.mapfoldl/3
    (elixir 1.12.2) lib/macro.ex:468: Macro.do_traverse/4
    (stdlib 3.15.1) lists.erl:1358: :lists.mapfoldl/3
    (elixir 1.12.2) lib/macro.ex:468: Macro.do_traverse/4
    (elixir 1.12.2) lib/macro.ex:463: Macro.do_traverse/4
    (stdlib 3.15.1) lists.erl:1358: :lists.mapfoldl/3
    (elixir 1.12.2) lib/macro.ex:468: Macro.do_traverse/4
    (ecto 3.6.2) lib/ecto/query/inspect.ex:230: Inspect.Ecto.Query.expr/3
    (ecto 3.6.2) lib/ecto/query/inspect.ex:195: Inspect.Ecto.Query.window/2
    (elixir 1.12.2) lib/enum.ex:1582: Enum."-map/2-lists^map/1-0-"/2
    (ecto 3.6.2) lib/ecto/query/inspect.ex:96: Inspect.Ecto.Query.to_list/1
    (ecto 3.6.2) lib/ecto/query/inspect.ex:75: Inspect.Ecto.Query.to_string/1
    (ecto 3.6.2) lib/ecto/query/inspect.ex:138: Inspect.Ecto.Query.inspect_source/1
    (ecto 3.6.2) lib/ecto/query/inspect.ex:134: Inspect.Ecto.Query.bound_from/2
    (ecto 3.6.2) lib/ecto/query/inspect.ex:92: Inspect.Ecto.Query.to_list/1
    (ecto 3.6.2) lib/ecto/query/inspect.ex:75: Inspect.Ecto.Query.to_string/1
    (ecto 3.6.2) lib/ecto/exceptions.ex:77: Ecto.SubQueryError.exception/1
Function: #Function<14.23005548/0 in Ash.Actions.Read.fetch_count/7>
    Args: []
[error] GenServer #PID<0.1144.0> terminating
** (FunctionClauseError) no function clause matching in Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir 1.12.2) lib/enum.ex:1704: Enum."-map_reduce/3-lists^mapfoldl/2-0-"(#Function<24.10667392/2 in Ecto.Query.Planner.prewalk/6>, 0, {:asc, {{:., [], [{:&, [], [0]}, :out]}, [], []}})
    (elixir 1.12.2) lib/enum.ex:1704: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir 1.12.2) lib/enum.ex:1704: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir 1.12.2) lib/enum.ex:1704: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto 3.6.2) lib/ecto/repo/queryable.ex:208: Ecto.Repo.Queryable.execute/4
    (ecto 3.6.2) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
    (ash_postgres 0.40.8) lib/data_layer.ex:435: AshPostgres.DataLayer.run_query/2
    (ash 1.46.13) lib/ash/actions/read.ex:780: Ash.Actions.Read.run_query/2
    (ash 1.46.13) lib/ash/actions/read.ex:482: anonymous fn/5 in Ash.Actions.Read.data_field/4

Belongs to relation does not generate a foreign key

Describe the bug
belongs_to required relationship on a model does not generate a foreign key migration.

To Reproduce

defmodule App.Resources.User.ReferralReward do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer

  postgres do
    repo(App.Repo)
    table "user_referral_rewards"
  end

  relationships do
    belongs_to :referral, App.Resources.User do
      required?(true)
      source_field(:referral_id)
    end

    belongs_to :referred_by, App.Resources.User do
      required?(true)
      source_field(:referred_by_id)
    end
  end

  attributes do
    uuid_primary_key :id

    attribute :reward_fulfilled, :boolean, allow_nil?: false, default: false

    timestamps()
  end

  actions do
    defaults [:read, :create, :update]
  end

generates the following migration:

defmodule App.Repo.Migrations.MigrateResources15 do
  @moduledoc """
  Updates resources based on their most recent snapshots.

  This file was autogenerated with `mix ash_postgres.generate_migrations`
  """

  use Ecto.Migration

  def up do
    create table(:user_referral_rewards, primary_key: false) do
      add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true
      add :reward_fulfilled, :boolean, null: false, default: false
      add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()")
      add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()")
      add :referral_id, :uuid, null: false
      add :referred_by_id, :uuid, null: false
    end
  end

  def down do
    drop table(:user_referral_rewards)
  end
end

Expected behavior
I expect a foreign key to be a part of the migration.

** Runtime

elixir ฮป ag '"ash' mix.lock
8:  "ash": {:hex, :ash, "1.52.0-rc.11", "3bfd3a883b4deaf7e05736d43a347cfcf937f679064c05ab9f6f4b62e5c5738c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.9", [hex: :sourceror, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "1f674fe260d2c415ee9a3fd1202fa82562253a95539d19a2e102179aabf49a7d"},
9:  "ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "9bb12460d4408f67d2460b64f4196b698e8c60f7", []},
10:  "ash_json_api_wrapper": {:git, "https://github.com/ash-project/ash_json_api_wrapper.git", "f42756c3426d9746acf71a53e05094f23d05240e", []},
11:  "ash_postgres": {:git, "https://github.com/ash-project/ash_postgres.git", "5d435d92b664aae9ae868a69907d8c1793f82a4a", []},
elixir ฮป asdf current
elixir          1.13.4          [...]
[...]
erlang          24.3.4          [...]
[...]

Feature: Introspect DB when generating snapshots/migrations

Is your feature request related to a problem? Please describe.
When adding Ash to an existing project, the DB table for a resource may already exist. It also may or may not exactly match the resource definition (changed column properties, different index names, etc.). Currently, this scenario means the generated migrations fail because they assume a blank slate for the first snapshot for each resource.

Describe the solution you'd like
It would be ideal if the snapshot/migration generator introspected the DB to see what already exists in order to determine what can be skipped/altered or created. This would make for an amazing experience when adding Ash to existing projects.

Describe alternatives you've considered
For now, manually altering the generated migrations to be successful is a sufficient workaround.

Additional context
PostGraphile generates a GraphQL API by introspecting the Postgres schema. Its introspection query may prove informative for this purpose: https://github.com/graphile/graphile-engine/blob/v4/packages/graphile-build-pg/src/plugins/introspectionQuery.js

Migration generator shows wrong summary

Example, the generator actually created 5 files:

* creating priv/repo/tenant_migrations/20210727125616_migrate_resources2.exs
No changes detected, so no migrations or snapshots have been created.

Invalid snapshots state

Describe the bug

After upgrading Ash from 1.52.0-rc.11 to 15cd3fb7bcc6968ff1ae40a7589b068ea686e328:

diff --git a/elixir/mix.lock b/elixir/mix.lock
index 83259136..588e4a2c 100644
--- a/elixir/mix.lock
+++ b/elixir/mix.lock
@@ -5,7 +5,7 @@
   "appsignal": {:hex, :appsignal, "2.2.14", "f2747cb051eb9e93c02896ce19f95ebbdf1b25c3facafeb8e81d8b0f44b49289", [:make, :mix], [{:decorator, "~> 1.2.3 or ~> 1.3", [hex: :decorator, repo: "hexpm", optional: false]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, ">= 1.3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f25a293e063a4213d9314eb15f2a31a95767d094836c51b73febd816e762b058"},
   "appsignal_phoenix": {:hex, :appsignal_phoenix, "2.0.14", "c2e7ee802a5ee037d399ec9cbe3c3cf12f8cf0dfcc2bbfa595214d14a3ff99ea", [:mix], [{:appsignal_plug, ">= 2.0.
9 and < 3.0.0", [hex: :appsignal_plug, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.11 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.9", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}], "hexpm", "7e987756dc15f0b86b5c10671c9706db98663c0ec4a1448e58eaf01acd5bbeec"},
   "appsignal_plug": {:hex, :appsignal_plug, "2.0.11", "96ab6656a6a2450e7a32c2686bb4f8955d4b25124c0f867293bfa0bdfd6c1c34", [:mix], [{:appsignal, ">= 2.2.13 and < 3.0.0", [hex: :appsignal, repo: "hexpm", optional: false]}, {:plug, ">= 1.1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b16cffc492f276e883f7e5430f28a55a49c50dcd1e7f09a770eb4ae26a3da717"},
-  "ash": {:hex, :ash, "1.52.0-rc.11", "3bfd3a883b4deaf7e05736d43a347cfcf937f679064c05ab9f6f4b62e5c5738c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.9", [hex: :sourceror, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "1f674fe260d2c415ee9a3fd1202fa82562253a95539d19a2e102179aabf49a7d"},
+  "ash": {:git, "https://github.com/ash-project/ash.git", "15cd3fb7bcc6968ff1ae40a7589b068ea686e328", []},
   "ash_admin": {:hex, :ash_admin, "0.4.5-rc.0", "ca955436208301ab18b934859f1c845090af491134bcdc968cd68a1c54217624", [:mix], [{:ash, "~> 1.52.0-rc.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 0.7.2-rc.1", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:surface, "~> 0.7", [hex: :surface, repo: "hexpm", optional: false]}], "hexpm", "fa1c6f5b67cf8de3d4d3824ec0746e78ac6b6aa6fd323fd7e3aee105c471260e"},
   "ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "9bb12460d4408f67d2460b64f4196b698e8c60f7", []},
   "ash_json_api_wrapper": {:git, "https://github.com/ash-project/ash_json_api_wrapper.git", "f42756c3426d9746acf71a53e05094f23d05240e", []},

The migration generator breaks the snapshots of already migrated resources (see the output in "expected behavior").

To Reproduce
Not sure.

Expected behavior
It should not (1) try to rename the timestamps, and definitely (2) should not do that if I type n while generating the migration (note the source: null in the snapshot):

elixir ฮป cat priv/repo/migrations/20220705093537_migrate_resources23.exs
defmodule [filtered].Repo.Migrations.MigrateResources23 do
  @moduledoc """
  Updates resources based on their most recent snapshots.

  This file was autogenerated with `mix ash_postgres.generate_migrations`
  """

  use Ecto.Migration

  def up do
    alter table(:users) do
      # Attribute removal has been commented out to avoid data loss. See the migration generator documentation for more
      # If you uncomment this, be sure to also uncomment the corresponding attribute *addition* in the `down` migration
      # remove :totp_verified

      add :totp_enabled, :boolean, null: false, default: false
      add :totp_secret, :text
    end

    alter table(:user_referral_rewards) do
      # Attribute removal has been commented out to avoid data loss. See the migration generator documentation for more
      # If you uncomment this, be sure to also uncomment the corresponding attribute *addition* in the `down` migration
      # remove :updated_at

      # Attribute removal has been commented out to avoid data loss. See the migration generator documentation for more
      # If you uncomment this, be sure to also uncomment the corresponding attribute *addition* in the `down` migration
      # remove :inserted_at

      add nil, :utc_datetime_usec, null: false, default: fragment("now()")
    end
  end

  def down do
    alter table(:user_referral_rewards) do
      remove nil
      # This is the `down` migration of the statement:
      #
      #     remove :inserted_at
      #

      # add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()")
      # This is the `down` migration of the statement:
      #
      #     remove :updated_at
      #

      # add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()")
    end

    alter table(:users) do
      remove :totp_secret
      remove :totp_enabled
      # This is the `down` migration of the statement:
      #
      #     remove :totp_verified
      #

      # add :totp_verified, :boolean, null: false, default: false
    end
  end
endโŽ

elixir ฮป g
## sandbox...origin/sandbox
?? priv/repo/migrations/20220705093537_migrate_resources23.exs
?? priv/resource_snapshots/repo/user_referral_rewards/20220705093537.json
?? priv/resource_snapshots/repo/users/20220705093537.json

elixir ฮป cat priv/resource_snapshots/repo/user_referral_rewards/20220705093537.json
{
  "attributes": [
    {
      "allow_nil?": false,
      "default": "fragment(\"now()\")",
      "generated?": false,
      "primary_key?": false,
      "references": null,
      "size": null,
      "source": null,
      "type": "utc_datetime_usec"
    },
    {
      "allow_nil?": false,
      "default": "fragment(\"uuid_generate_v4()\")",
      "generated?": false,
      "primary_key?": true,
      "references": null,
      "size": null,
      "source": "id",
      "type": "uuid"
    },
    {
      "allow_nil?": false,
      "default": "false",
      "generated?": false,
      "primary_key?": false,
      "references": null,
      "size": null,
      "source": "fulfilled",
      "type": "boolean"
    },
    {
      "allow_nil?": false,
      "default": "nil",
      "generated?": false,
      "primary_key?": false,
      "references": {
        "destination_field": "id",
        "destination_field_default": null,
        "destination_field_generated": null,
        "multitenancy": {
          "attribute": null,
          "global": null,
          "strategy": null
        },
        "name": "user_referral_rewards_referred_id_fkey",
        "on_delete": null,
        "on_update": null,
        "schema": "public",
        "table": "users"
      },
      "size": null,
      "source": "referred_id",
      "type": "text"
    },
    {
      "allow_nil?": false,
      "default": "nil",
      "generated?": false,
      "primary_key?": false,
      "references": {
        "destination_field": "id",
        "destination_field_default": null,
        "destination_field_generated": null,
        "multitenancy": {
          "attribute": null,
          "global": null,
          "strategy": null
        },
        "name": "user_referral_rewards_referrer_id_fkey",
        "on_delete": null,
        "on_update": null,
        "schema": "public",
        "table": "users"
      },
      "size": null,
      "source": "referrer_id",
      "type": "text"
    }
  ],
  "base_filter": null,
  "check_constraints": [],
  "custom_indexes": [],
  "has_create_action": true,
  "hash": "CF755B6DFB9427D6B62485F8B1D70304D8B554FE6D5E70776FC5B122F61F3B56",
  "identities": [
    {
      "base_filter": null,
      "index_name": "user_referral_rewards_referred_index",
      "keys": [
        "referred_id",
        "referrer_id"
      ],
      "name": "referred"
    }
  ],
  "multitenancy": {
    "attribute": null,
    "global": null,
    "strategy": null
  },
  "repo": "[filtered]",
  "schema": null,
  "table": "user_referral_rewards"
}

** Runtime

  • Elixir version 1.13.4
  • Erlang version 24.3.4
  "ash": {:git, "https://github.com/ash-project/ash.git", "15cd3fb7bcc6968ff1ae40a7589b068ea686e328", []},
  "ash_admin": {:hex, :ash_admin, "0.4.5-rc.0", "ca955436208301ab18b934859f1c845090af491134bcdc968cd68a1c54217624", [:mix], [{:ash, "~> 1.52.0-rc.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 0.7.2-rc.1", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:surface, "~> 0.7", [hex: :surface, repo: "hexpm", optional: false]}], "hexpm", "fa1c6f5b67cf8de3d4d3824ec0746e78ac6b6aa6fd323fd7e3aee105c471260e"},
  "ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "9bb12460d4408f67d2460b64f4196b698e8c60f7", []},
  "ash_json_api_wrapper": {:git, "https://github.com/ash-project/ash_json_api_wrapper.git", "f42756c3426d9746acf71a53e05094f23d05240e", []},
  "ash_phoenix": {:hex, :ash_phoenix, "0.7.2-rc.1", "90572ecaeb5a957e8eb444d258c5903693fab867258fc265ed3834522937de61", [:mix], [{:ash, "~> 1.52.0-rc.2", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "dd8b44ccd9bfe001336d3f9cd83571fe606afb9322d5e43c60ee797f1050a49b"},
  "ash_postgres": {:git, "https://github.com/ash-project/ash_postgres.git", "5d435d92b664aae9ae868a69907d8c1793f82a4a", []},

Additional context
ash-project/ash#344
#102

Figure out exactly if/what our long term plan for migrations is

In the short term, it makes the most sense to just have users make ecto migrations. We've talked utilities that would help them generate migrations, and I think that, given how well defined our resources are, we should be able to adopt an approach very similar to django's approach for migrations, e.g automatically generating them based on changes in the resource.

More info on django: https://docs.djangoproject.com/en/3.0/topics/migrations/

Error when running migrations generated by the migration generator

Describe the bug
When running migrations with mix ecto.migrate the following error occurs:

17:35:31.457 [info]  == Running 20200930163220 AshTest.Resources.Repo.Migrations.MigrateResources1.up/0 forward
17:35:31.459 [info]  create table users
17:35:31.466 [info]  create table tweets
** (FunctionClauseError) no function clause matching in Ecto.Adapters.Postgres.Connection.column_definition/2    
    The following arguments were given to Ecto.Adapters.Postgres.Connection.column_definition/2:
        # 1
        %Ecto.Migration.Table{comment: nil, engine: nil, name: "tweets", options: nil, prefix: nil, primary_key: false}
        # 2
        {:modify, :user_id, %Ecto.Migration.Reference{column: :id, name: nil, on_delete: :nothing, on_update: :nothing, prefix: nil, table: "users", type: :binary_id}, [default: nil, primary_key: false]}
    Attempted function clauses (showing 2 out of 2):
        defp column_definition(table, {:add, name, %Ecto.Migration.Reference{} = ref, opts})
        defp column_definition(_table, {:add, name, type, opts})
    (ecto_sql 3.4.5) Ecto.Adapters.Postgres.Connection.column_definition/2
    (ecto_sql 3.4.5) lib/ecto/adapters/postgres/connection.ex:1151: Ecto.Adapters.Postgres.Connection.intersperse_map/4
    (ecto_sql 3.4.5) lib/ecto/adapters/postgres/connection.ex:755: Ecto.Adapters.Postgres.Connection.execute_ddl/1
    (ecto_sql 3.4.5) lib/ecto/adapters/sql.ex:684: Ecto.Adapters.SQL.execute_ddl/4
    (ecto_sql 3.4.5) lib/ecto/migration/runner.ex:343: Ecto.Migration.Runner.log_and_execute_ddl/3
    (ecto_sql 3.4.5) lib/ecto/migration/runner.ex:117: anonymous fn/6 in Ecto.Migration.Runner.flush/0
    (elixir 1.10.2) lib/enum.ex:2111: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto_sql 3.4.5) lib/ecto/migration/runner.ex:116: Ecto.Migration.Runner.flush/0

To Reproduce
A minimal set of resource definitions and calls that can reproduce the bug.

Expected behavior
migration should run successfully.

** Runtime

  • Elixir version - 1.10.2
  • Erlang version - OTP 22
  • OS - 10.15.6
  • Ash version - 1.13.3

Additional context
Changing the modify table to alter table and put it outside the create table solves this issue.

Default values need a protocol

As noted in this issue: ash-project/ash#310

Our default value handling is rudimentary. What we do currently:

defp default(%{default: value, type: type}, _) do
    case Ash.Type.dump_to_native(type, value) do
      {:ok, value} when is_struct(value) ->
        to_string(value)

      {:ok, value} ->
        inspect(value)

      _ ->
        "nil"
    end
  rescue
    _ ->
      "nil"
  end

But what we need to do is add a protocol like AshPostgres.ToSql that can be implemented for various types and structs.

Default values

The migration generator uses inspect/1 to generate default values. In case of :decimal types (and maybe others) this leads to invalid defaults:

iex(2)> Decimal.new(0) |> to_string()
"0"
iex(3)> Decimal.new(0) |> inspect()
"#Decimal<0>"

Figure out when to left/inner join

We may need to use heuristics for when to left join/right join

For instance, if a relationship has a non-nil predicate applied to it in all ors or a single and then we should be able to inner join.

My comment in code from when I first wrote joining logic.
I have learned from experience that no single approach here will be a one-size-fits-all. We need to either use complexity metrics, hints from the interface, or some other heuristic to do our best to make queries perform well. For now, I'm just choosing the most naive approach possible: left join to relationships that appear in or conditions, inner join to conditions that are constant the query (don't do this yet, but it will be a good optimization). Realistically, in my experience, joins don't actually scale very well, especially when calculated attributes are added.

Changing constraints that don't need to be changed

This migration was generated when only a single attribute should have been added:

defmodule Demo.Repo.Migrations.MigrateResources3 do
  @moduledoc """
  Updates resources based on their most recent snapshots.

  This file was autogenerated with `mix ash_postgres.generate_migrations`
  """

  use Ecto.Migration

  def up() do
    drop(constraint(:tickets, "tickets_representative_id_fkey"))

    alter table(:tickets) do
      add(:deleted_at, :utc_datetime, null: true, default: nil, primary_key: false)
      modify(:representative_id, references("users", type: :binary_id, column: :id))
    end
  end

  def down() do
    drop(constraint(:tickets, "tickets_representative_id_fkey"))

    alter table(:tickets) do
      modify(:representative_id, references("users", type: :binary_id, column: :id))
      remove(:deleted_at)
    end
  end
end

For some reason we are dropping/recreating that constraint when we don't need to.

Use gen_random_uuid() for generated uuids which is native to postgres 13+

We made this change, but then reverted it here: db3166f realizing that all users who didn't have the uuid-ossp extension currently installed would upgrade to this version and then get defaults added to all of their versions. So we'll want to save this for a major release and/or make it opt-in in some way. For now we'll leave it reverted and figure out how we can work it back in later, because it is much nicer to not need an extension to have AshPostgres automatically do this behavior of setting uuid defaults.

`Rename` in generated migration has incorrect table `:prefix`

Describe the bug

When you rename a field for a resource that has a table prefix, the generated migration file contains incorrect rename syntax.

To Reproduce

Rename a resource that has a table prefix.

Expected behavior

Expected outcome:

rename table(:finance_orders, prefix: "v2"), :source, to: :provider

Actual / incorrect outcome:

rename table(:finance_orders), :source, to: :provider, prefix: "v2"

** Runtime

  • Elixir version 1.13.4
  • Erlang version 24.3.4
  • OS MacOS 12.5
  • Ash version 1.52.0-rc.21
  • Ash Postgres 0.42.0-rc.7

error generating migration

Describe the bug
I got an error when I tried to generate a migration after I modified an existing resource attributes

MIX_ENV=test mix ash_postgres.generate_migrations --apis AshPostgres.Test.Api

I get this error:

** (SyntaxError) nofile:18: unexpected token: ":" (column 17, code point U+003A)
    (elixir 1.10.4) lib/code.ex:654: Code.format_string!/2
    lib/migration_generator/migration_generator.ex:332: AshPostgres.MigrationGenerator.write_migration/4
    (elixir 1.10.4) lib/enum.ex:789: anonymous fn/3 in Enum.each/2
    (stdlib 3.12.1) maps.erl:232: :maps.fold_1/3
    (elixir 1.10.4) lib/enum.ex:2127: Enum.each/2
    (mix 1.10.4) lib/mix/task.ex:330: Mix.Task.run_task/3
    (mix 1.10.4) lib/mix/cli.ex:82: Mix.CLI.run_task/2.

To Reproduce

  1. add a new attribute new_field to an existing resource Post in test/support/resources/posts.ex
  2. generate a migration using ash_postgres.generate_migrations task

Expected behavior
it should create a new migration file to alter table :posts, adding column :new_field correctly

** Runtime

  • Elixir 1.10.4
  • Erlang/OTP 22
  • Ash version 1.19
  • postgres (PostgreSQL) 12.2

Some fkeys not generating references in migration

create table(:transactions, primary_key: false) do
  add :id, :bigserial, null: false, primary_key: true
  add :amount, :decimal, null: false
  add :date, :date, null: false
  add :name, :text, null: false
  add :note, :text
  add :reviewed, :boolean, null: false
  add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()")
  add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()")
  add :bank_transaction_id, :bigint
  add :user_id, :bigint, null: false
end
relationships do
  belongs_to :bank_transaction, Spendable.BankTransaction, field_type: :integer
  belongs_to :user, Spendable.User, required?: true, field_type: :integer

  has_many :budget_allocations, Spendable.BudgetAllocation
end

Index in migration generated by Resource identity does not honour column order

Describe the bug

The index generated by mix ash_postgres.generate_migrations contains incorrect index column order.

To Reproduce

In an Ash resource we have:

identities do
  identity :user_new_order, [:user_id, :money, :executed_at]
end

Expected behavior

Expected index:

create unique_index(:test, [:user_id, :money, :executed_at],
         name: "test_user_new_order_index"
       )

Actual index:

create unique_index(:test, [:executed_at, :money, :user_id],
         name: "test_user_new_order_index"
       )

** Runtime

  • Elixir version 1.14.0
  • Erlang version 25.0.4
  • OS macOS 12.5.1
  • Ash version 2.0.0-rc.0
  • Ash postgres 1.0.0-rc.0

Loading a calculation on an existing resource does not loads its dependent loads

Describe the bug
When Api.load! is called on an existing resource to load a calculation on that resource, and that calculation itself has dependent loads specified, those dependent loads are not automatically loaded.

To Reproduce
Please see this test case that reproduces the issue

Expected behavior
The dependent load should be loaded automatically.

** Runtime

  • Elixir version 1.14.2
  • Erlang version 25.1.1
  • OS MacOS 13
  • Ash Postgres version 1.2.3

rename/3 is used inside alter/2

Describe the bug
I tried creating a migration where I rename one field, but the resulting migration created the following code.

  def up() do
    alter table(:streaming_profile) do
      rename(table(:streaming_profile), :url, to: :profile_id)
    end
  end

  def down() do
    alter table(:streaming_profile) do
      rename(table(:streaming_profile), :profile_id, to: :url)
    end
  end

To Reproduce

  1. Create a resource
  2. Create migrations
  3. rename one field
  4. Create migrations again

Expected behavior
The rename statement should have been defined outside of the alter block

  def up() do
    rename(table(:streaming_profile), :url, to: :profile_id)
  end

  def down() do
    rename(table(:streaming_profile), :profile_id, to: :url)
  end

** Runtime

  • Ash Version: 1.26.13
  • Ash Postgres Version: 0.28.1

Aggregates on `{:array, ...}` types fail when arrays are empty

Describe the bug
Looks like postgres doesn't like aggregates on arrays, where there are empty arrays

To Reproduce
A user resource contains:

attribute :roles, {:array, :atom}

A related resource with an aggregate on the user resource:

first :roles, :user, :roles

When you load the aggregate, if there are any users with no roles, the following error is raised:

ERROR 2202E (array_subscript_error) cannot accumulate empty arrays

Expected behavior
No error

Runtime

  • Elixir version 1.14
  • Erlang version 25
  • Ash version 2.4.15
  • Ash postgres version 1.1.2
  • Postgres version 13

Bug: Sort order not preserved in distinct query

Describe the bug

When sorting with distinct, the query builder seems to add an extraneous ORDER BY that conflicts with the specified sort direction.

To Reproduce

query
|> Ash.Query.sort(job_no: :desc)
|> Ash.Query.distinct(:job_no)
|> Ash.Query.limit(20)
SELECT
  DISTINCT ON (e0."job_no") e0."job_no",
  -- ...
  e0."id"
FROM
  "e2_routing" AS e0 WINDOW "order" AS (
    ORDER BY
      e0."job_no" DESC
  )
ORDER BY
  e0."job_no",
  e0."job_no" DESC
LIMIT
  $ 1 [ 20 ]

The query generated without distinct is as expected:

query
|> Ash.Query.sort(job_no: :desc)
|> Ash.Query.limit(20)
SELECT
  e0."job_no",
  -- ...
  e0."id"
FROM
  "e2_routing" AS e0 WINDOW "order" AS (
    ORDER BY
      e0."job_no" DESC
  )
ORDER BY
  e0."job_no" DESC
LIMIT
  $ 1 [ 20 ]

Expected behavior

Sort/direction should be preserved through a distinct query.

** Runtime

  • Elixir 1.13.4
  • Erlang OTP 24
  • OS 5.4.188-1-MANJARO (Linux)
  • Ash 1.52.0-rc.1
  • ash_postgres 0.42.0-rc.0

Extensions missing when generating migrations

Describe the bug
I recently updated ash_postgres to the latest, and now I seem to be getting no extensions added to the install_extensions migration. Previously I would get:

  def up do
    execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")
    execute("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\"")
    execute("CREATE EXTENSION IF NOT EXISTS \"citext\"")
  end

Now I'm getting

  def up do
  end

** Runtime

  • ash 1.53.3
  • ash_postgres 0.43.0

Implement polymorphic_embeds_one in ash_postgres

Is your feature request related to a problem? Please describe.
I'm trying to use EmbeddedResources as polymorphic into an Ash.Resource.
The situation I'm modeling is similar to this:

defmodule MyApp.Report do
  use Ecto.Schema
  import Ecto.Changeset
  import PolymorphicEmbed

  schema "reports" do
    field :date, :utc_datetime
    field :name, :string

    polymorphic_embeds_many :report_data,
      types: [
        category: MyApp.ReportTypes.Category,
        product: MyApp.ReportTypes.Product
      ],
      on_type_not_found: :raise,
      on_replace: :update
  end

  def changeset(struct, values) do
    struct
    |> cast(values, [:date, :text])
    |> cast_polymorphic_embed(:report_data, required: true)
    |> validate_required(:date)
  end
end
defmodule MyApp.ReportType.Category do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key false

  embedded_schema do
    field :name, :string
    field :parent_id, :int
  end

  def changeset(email, params) do
    email
    |> cast(params, ~w(address confirmed)a)
    |> validate_required(:name)
    |> validate_length(:name, min: 3)
  end
end
defmodule MyApp.ReportTypes.Product do
  use Ecto.Schema

  @primary_key false

  embedded_schema do
    field :number, :string
    field :number, :string
    field :price, :int
  end
end

Describe the solution you'd like
I'd like to be able to model this situation through EmbeddedResources and resource attributes not relationships.

Express the feature either with a change to resource syntax, or with a change to the resource interface

For example

defmodule MyApp.ReportTypes.Categories do
  use Ash.Resource,
    data_layer: :embedded # Use the atom `:embedded` as the data layer.

  postgres do
    polymorphic?(true)
    repo AshPostgres.TestRepo
  end

  attributes do
    attribute :name, :string
    attribute :parent_id, :int
  end
end
 defmodule MyApp.Support.Report do
  # This turns this module into a resource
  use Ash.Resource

  actions do
    # Add a set of simple actions. You'll customize these later.
    defaults [:create, :read, :update, :destroy]
  end

  # Attributes are the simple pieces of data that exist on your resource
  attributes do
    ...

    # Add the report data as a map type --> JSONB column in PostgreSQL
    attribute :report_data,  {:array, types: [ category: MyApp.ReportTypes.Category, product: MyApp.ReportTypes.Product]} do
      allow_nil? false
    end

  end
end

I don't know if this is already achievable somehow. It would be great because it will allow to define new reports (with corresponding api actions) only defining a new EmbeddedResource with dynamic api results based on report type.

Respond with a more useful error message if required Postgres extensions are missing

Is your feature request related to a problem? Please describe.
Yes - I was recently experimenting with how to do partial text matches, and came across the contains predicate. I finally got the syntax how I think it's supposed to work, but it raised an error because I didn't have the citext extension enabled.

iex(2)> MyApp.Element |> Ash.Query.filter(contains(description, "oil")) |> MyApp.Api.read()
[debug] QUERY ERROR source="elements" db=0.0ms queue=0.4ms idle=808.3ms
SELECT e0."id", e0."description", FROM "elements" AS e0 WHERE (strpos(e0."description"::citext, $1::citext) > 0) ["oil"]
[error] Task #PID<0.668.0> started from #PID<0.665.0> terminating
** (Postgrex.Error) ERROR 42704 (undefined_object) type "citext" does not exist

    query: SELECT e0."id", e0."description" FROM "elements" AS e0 WHERE (strpos(e0."description"::citext, $1::citext) > 0)
    ...

Describe the solution you'd like
Ideally there would be a nicer error message, something like "The citext extension needs to be installed before the contains/2 predicate can be used. Please add citext to the list of installed_extensions in your Repo module."

Add handling of DB-level constraints

I leverage Postgres constraints quite a bit, e.g:

create constraint(:attendance_record_types, :shortcut_must_be_lowercase,
  check: "lower(shortcut) = shortcut"
)

In Ecto, I'd just use check_constraint so that if the DB kicks back, I can cleanly handle the error rather than throwing an exception:

|> check_constraint(:shortcut,
  name: :shortcut_must_be_lowercase,
  message: "Shortcut keys must be lower-case."
)

I would like to be able to specify such constraints and the error messages they should produce.

This may make sense in the attributes section:

attributes do
  # Tuple of constraint name and message. Could also use keyword syntax for message in case other options are added.
  attribute :shortcut, :string, pg_constraints: [{:shortcut_must_be_lowercase, "Shortcut keys must be lower-case."}]
end

More complex constraints may apply to more than one field, the above syntax would allow the same constraint to have different messages on other attributes.

Alternatively, since this would be Postgres-specific, it may be better suited in the postgres section of the resource.

`repo` is not formatting correctly

I've setup formatters for ash and ash_postgres

  import_deps: [:ecto, :phoenix, :surface, :ash, :ash_postgres],

but don't see repo formatting correctly

  postgres do
    repo(MyApp.Repo)
    table "account"
  end

Should see:

  postgres do
    repo MyApp.Repo
    table "account"
  end

Runtime

erlang 24.3.3
elixir 1.13.3-otp-24
ash master, 1.51
ash_postgres 0.41.7

Additional context

Have checked the formatters and ash contains table but not repo:

https://github.com/ash-project/ash/blob/master/.formatter.exs#L108

ash_postgres formatter has table and repo:

https://github.com/ash-project/ash_postgres/blob/master/.formatter.exs#L31
https://github.com/ash-project/ash_postgres/blob/master/.formatter.exs#L29

outstanding issues with dynamic refactor

As discussed on Discord here are the outstanding issues I am encountering in my application while using the dynamic refactor of ash postgres.

  1. Inability to use DB enums as Ash explicitly casts the bindings to a varchar instead of just leaving it uncast - i.e: SELECT * FROM burgers WHERE status = $1::varchar (instead of just = $1) - also the storage_type override on Ash.Type has no effect on this.

  2. Ash gets confused while trying to load an aggregate that has a filter on it
    count :total_leased_count, :assets, filter: expr(is_nil(active_order_service.id) == false)

In my case I have AssetType which has many Assets, which in turn has one active OrderService (has_one :active_order_service, OrderService, filter: expr(fragment("? @> now()", period))), I have a count aggregate defined on AssetType which counts the number of Assets with an active OrderService, OrderService has a range type column called period.

error is unknown error: {%Ecto.SubQueryError{exception: %Ecto.QueryError{message: \"could not find named binding 'as(nil)') while trying to load the AssetType with the count

  1. I have previously needed to define the filter on the aggregate and relationship for it to work correctly, I can't tell if this has been fixed as point 2 is blowing up first.

  2. There was an issue with GROUP BY on aggregates, which I think has been resolved, but I can't fully test because of 2.

Embedding a resource with an array attribute is not faithful

Describe the bug
A clear and concise description of what the bug is. If you are not sure if the bug is related to ash or an extension, log it with ash and we will move it.

Suppose I have an embedded resource with a nullable array attribute. Then if this resource is embedded in a parent resource, then when it is persisted in the database with this value set to nil, then when it is extracted it changes to an empty array [].

To Reproduce

  defmodule EmbeddedResource do
    use Ash.Resource,
      data_layer: :embedded

    attributes do
      attribute :list_att, {:array, :string} do
        allow_nil? true
        default nil
      end
    end
  end

  defmodule MyResource do
      use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: []

  postgres do
    repo(MyRepo)
    table "my_resources"
  end

    actions do
      defaults [:read, :destroy, :create, :update]
    end

    attributes do
      uuid_primary_key :id

      attribute :embedded_resource, EmbeddedResource
    end
  end

then I would expect the following to hold:

test "embeds and extracts faithfully" do
    embedded = %EmbeddedResource{list_att: nil}
    data = %{embedded_resource: embedded}

    %MyResource{embedded_resource: extracted} =
      MyResource
      |> Ash.Changeset.for_create(:create, data)
      |> MyApi.create!()

    assert embedded.list_att == extracted.list_att
  end

Expected behavior
A clear and concise description of what you expected to happen.

I expect the value to be nil once extracting from the db.

** Runtime

  • Elixir version
    0.14
    OTP 25
  • OS
    macOS
  • Ash version
    2.4.26
    ash_postgres 1.2

Change index name

Ash currently generates index names called #{table}_#{name}_unique_index which can lead to ugly names like table_unique_title_unique_index โ€“ the _unique part should be removed.

This is a breaking change, so there needs to be an update guide or if possible the migration generator should convert it.

tenant_schema callback

Proposal: Add a tenant_schema/1 callback to AshPostgres.Repo which takes any input from a tenant: param and returns a schema string.

Create extensions in the migration generator

Right now, we create a snapshot for each resource. In order to create (and rollback) extensions in the migration generator, we will need to track another snapshot just for the repo, and we would currently only store the list of extensions. Then, when migrations are generated, we'd go through each resource in each API, grab their repo, unique them, and create snapshot for them w/ their extensions. Any extensions in the new list that aren't in the old list should be added in the generated migration.

Redesign "snapshots" to be "operations"

Right now, collaborating with multiple developers using the migration generator can be cumbersome. You have to remove any generated migrations locally, pull, and then regenerate migrations. This is because snapshots essentially are a linear process.

If we rearchitect this to use a single json file with an array of operations that have been generated, and then have the snapshots derived as a projection of those operations, then there will no longer be issues w/ multiple developers working together with the migration generator (e.g on different branches).

Add --exit option to migration generator

Would make sense to add an --exit option to Mix.Tasks.AshPostgres.GenerateMigrations? It could then be used in CIs and fail if resources and migrations don't fit together. The big issue I see here is that the CI would have to be changed every time there's a new api.

The changeset does not define a constraint for a custom unique index

Describe the bug

The following custom unique index:

postgres do
  # ...

  custom_indexes do
    index(["provider", "provider_id"], unique: true, where: "archived_at IS NULL and provider_id IS NOT NULL")
  end
end

Does not add a constraint on a changeset which results in the exception when inserting a duplicated record:

** (Ecto.ConstraintError) constraint error when attempting to insert struct:

    * table_provider_provider_id_index (unique_constraint)

If you would like to stop this constraint violation from raising an
exception and instead add it as an error to your changeset, please
call `unique_constraint/3` on your changeset with the constraint
`:name` as an option.

The changeset defined the following constraints:

    * table_user_id_fkey (foreign_key_constraint)
    * table_pkey (unique_constraint)

    (ecto 3.8.4) lib/ecto/repo/schema.ex:783: anonymous fn/4 in Ecto.Repo.Schema.constraints_to_errors/3
    (elixir 1.14.0) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ecto 3.8.4) lib/ecto/repo/schema.ex:768: Ecto.Repo.Schema.constraints_to_errors/3
    (ecto 3.8.4) lib/ecto/repo/schema.ex:749: Ecto.Repo.Schema.apply/4
    (ecto 3.8.4) lib/ecto/repo/schema.ex:367: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
    (ash_postgres 1.0.0-rc.5) lib/data_layer.ex:934: AshPostgres.DataLayer.create/2
    (ash 2.0.0-rc.7) lib/ash/actions/create.ex:378: anonymous fn/8 in Ash.Actions.Create.as_requests/5
    (ash 2.0.0-rc.7) lib/ash/changeset/changeset.ex:1463: Ash.Changeset.run_around_actions/2
    (ash 2.0.0-rc.7) lib/ash/actions/create.ex:313: anonymous fn/8 in Ash.Actions.Create.as_requests/5
    (ash 2.0.0-rc.7) lib/ash/engine/request.ex:1041: Ash.Engine.Request.do_try_resolve_local/4
    (ash 2.0.0-rc.7) lib/ash/engine/request.ex:281: Ash.Engine.Request.do_next/1
    (ash 2.0.0-rc.7) lib/ash/engine/request.ex:210: Ash.Engine.Request.next/1
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:610: Ash.Engine.advance_request/2
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:535: Ash.Engine.fully_advance_request/2
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:469: Ash.Engine.do_run_iteration/2
    (elixir 1.14.0) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:218: Ash.Engine.run_to_completion/1
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:167: Ash.Engine.do_run/2
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:88: anonymous fn/2 in Ash.Engine.run/2
    (ecto_sql 3.8.3) lib/ecto/adapters/sql.ex:1222: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4

Expected behavior
All custom unique indexes should add a constraint to the changeset.

** Runtime

  • Elixir version 1.14.0
  • Erlang version 25.0.4
  • ash 2.0.0-rc.7
  • ash_admin 0.6.0-rc.1
  • ash_graphql 0.20.0-rc.1
  • ash_phoenix 1.0.0-rc.0
  • ash_postgres 1.0.0-rc.5

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.