ash-project / ash_postgres Goto Github PK
View Code? Open in Web Editor NEWThe PostgreSQL data layer for Ash Framework
Home Page: https://hexdocs.pm/ash_postgres
License: MIT License
The PostgreSQL data layer for Ash Framework
Home Page: https://hexdocs.pm/ash_postgres
License: MIT License
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
"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
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
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
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)
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/
As discussed on Discord here are the outstanding issues I am encountering in my application while using the dynamic refactor of ash postgres.
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.
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
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.
There was an issue with GROUP BY on aggregates, which I think has been resolved, but I can't fully test because of 2
.
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
new_field
to an existing resource Post
in test/support/resources/posts.ex
ash_postgres.generate_migrations
taskExpected behavior
it should create a new migration file to alter table :posts
, adding column :new_field
correctly
** Runtime
Right now it will silently ignore references
that don't match a relationship, which is obviously not ideal.
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
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.
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.
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,
DO: remove id property modifications (notably: "primary_key: true")
To Reproduce
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
Additional context
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
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.
Right now we overwrite snapshots, which causes a few problems. The most important is that if you haven't committed your changes between the last time you generated migrations, its very easy to get into an annoying/undesirable state.
Add an example of a resource using ash_postgres
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
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
Additional context
adding the jason dependency to mix solves the issue
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
Additional context
Changing the modify table to alter table and put it outside the create table solves this issue.
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."
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.
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.
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?
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
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.
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
Right now, every time we add an expression we count the bindings, see filter_value_to_expr
in data_layer.ex
. We should just keep track of the count alongside the list.
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.
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.
We want to separate use
ing ash components from their configuration, (good idea @andrewcallahan ), so its configuration should use a DSL block. We can do this after ash-project/ash#25
If an attribute is allow_nil?: false
but there are multiple resources pointing at the same table and they don't all have that same attribute with null: false
, then we want to make sure the attribute is not null: false
in the migration.
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
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
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.
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.
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 [...]
[...]
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
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
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.
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)
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).
When generating ecto migrations we should consider also generating the indexes for foreign keys
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
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
Proposal: Add a tenant_schema/1
callback to AshPostgres.Repo
which takes any input from a tenant: param
and returns a schema string.
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>"
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.
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)
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
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?
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.