test: add failing test to demonstrate potential bug (#164)

1) test complex calculation (AshPostgres.Test.ComplexCalculationsTest)
     test/complex_calculations_test.exs:5
     ** (RuntimeError) Error while building reference: latest_documentation_status
     code: |> AshPostgres.Test.ComplexCalculations.Api.load!([
     stacktrace:
       (ash_postgres 1.3.41) lib/expr.ex:846: AshPostgres.Expr.do_dynamic_expr/5
       (ash_postgres 1.3.41) lib/expr.ex:109: AshPostgres.Expr.do_dynamic_expr/5
       (ash_postgres 1.3.41) lib/expr.ex:356: AshPostgres.Expr.do_dynamic_expr/5
       (ash_postgres 1.3.41) lib/expr.ex:968: anonymous fn/6 in AshPostgres.Expr.do_dynamic_expr/5
       (ecto 3.10.3) lib/ecto/query/builder/dynamic.ex:76: Ecto.Query.Builder.Dynamic.expand/3
       (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3
       (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4
       (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3
       (stdlib 5.0.2) lists.erl:1707: :lists.mapfoldl_1/3
       (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4
       (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3
       (stdlib 5.0.2) lists.erl:1707: :lists.mapfoldl_1/3
       (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4
       (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3
       (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4
       (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3
       (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4
       (ecto 3.10.3) lib/ecto/query/builder/dynamic.ex:59: Ecto.Query.Builder.Dynamic.partially_expand/6
       (ecto 3.10.3) lib/ecto/query/builder/select.ex:235: Ecto.Query.Builder.Select.expand_nested/3
       (ecto 3.10.3) lib/ecto/query/builder/select.ex:274: Ecto.Query.Builder.Select.expand_nested_pair/3
       (elixir 1.15.4) lib/enum.ex:1825: anonymous fn/3 in Enum.map_reduce/3
       (stdlib 5.0.2) maps.erl:416: :maps.fold_1/4
       (elixir 1.15.4) lib/enum.ex:2522: Enum.map_reduce/3
       (ecto 3.10.3) lib/ecto/query/builder/select.ex:257: Ecto.Query.Builder.Select.expand_nested/3
       (ecto 3.10.3) lib/ecto/query/builder/select.ex:205: Ecto.Query.Builder.Select.select!/5
       (elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
       (ash_postgres 1.3.41) lib/aggregate.ex:121: anonymous fn/6 in AshPostgres.Aggregate.add_aggregates/6
       (elixir 1.15.4) lib/enum.ex:4830: Enumerable.List.reduce/3
       (elixir 1.15.4) lib/enum.ex:2564: Enum.reduce_while/3
       (ash_postgres 1.3.41) lib/aggregate.ex:53: AshPostgres.Aggregate.add_aggregates/6
       (ash 2.13.3) lib/ash/query/aggregate.ex:570: anonymous fn/6 in Ash.Query.Aggregate.value_request/9
       (ash 2.13.3) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
       (ash 2.13.3) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
       (elixir 1.15.4) lib/task/supervised.ex:101: Task.Supervised.invoke_mfa/2
       (elixir 1.15.4) lib/task/supervised.ex:36: Task.Supervised.reply/4
       (ash 2.13.3) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
       (elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
       (elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
       (ash 2.13.3) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1
       (ash 2.13.3) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1
       (ash 2.13.3) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
       (ash 2.13.3) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
       (ash 2.13.3) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
       (ash 2.13.3) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
       (ash 2.13.3) lib/ash/api/api.ex:1733: Ash.Api.load/4
       (ash 2.13.3) lib/ash/api/api.ex:1707: Ash.Api.load!/4
       test/complex_calculations_test.exs:55: (test)
This commit is contained in:
Alan Heywood 2023-08-17 10:49:18 +10:00 committed by GitHub
parent c570d5f60e
commit bac2e01b54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 487 additions and 1 deletions

View file

@ -44,7 +44,11 @@ if Mix.env() == :test do
config :ash_postgres,
ecto_repos: [AshPostgres.TestRepo, AshPostgres.TestNoSandboxRepo],
ash_apis: [AshPostgres.Test.Api, AshPostgres.MultitenancyTest.Api]
ash_apis: [
AshPostgres.Test.Api,
AshPostgres.MultitenancyTest.Api,
AshPostgres.Test.ComplexCalculations.Api
]
config :logger, level: :warning
end

View file

@ -0,0 +1,29 @@
{
"attributes": [
{
"default": "fragment(\"uuid_generate_v4()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"primary_key?": true,
"generated?": false
}
],
"table": "complex_calculations_certifications",
"hash": "A4D8129693BDC95C72E91842B17BB1B44951D91A87DC166A3371D052E4D27C1F",
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"check_constraints": [],
"identities": [],
"custom_indexes": [],
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"base_filter": null,
"custom_statements": [],
"has_create_action": true
}

View file

@ -0,0 +1,95 @@
{
"attributes": [
{
"default": "fragment(\"uuid_generate_v4()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"primary_key?": true,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "status",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "utc_datetime_usec",
"source": "documented_at",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "inserted_at",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "updated_at",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "skill_id",
"references": {
"name": "complex_calculations_documentations_skill_id_fkey",
"table": "complex_calculations_skills",
"primary_key?": true,
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"destination_attribute": "id",
"on_delete": null,
"on_update": null,
"deferrable": false,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": true,
"primary_key?": false,
"generated?": false
}
],
"table": "complex_calculations_documentations",
"hash": "26FCE27CAB6B3B67C7E099831352AF89AFD352CBD6E30043C4354B0400F09FBD",
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"check_constraints": [],
"identities": [],
"custom_indexes": [],
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"base_filter": null,
"custom_statements": [],
"has_create_action": true
}

View file

@ -0,0 +1,65 @@
{
"attributes": [
{
"default": "fragment(\"uuid_generate_v4()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"primary_key?": true,
"generated?": false
},
{
"default": "false",
"size": null,
"type": "boolean",
"source": "removed",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "certification_id",
"references": {
"name": "complex_calculations_skills_certification_id_fkey",
"table": "complex_calculations_certifications",
"primary_key?": true,
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"destination_attribute": "id",
"on_delete": null,
"on_update": null,
"deferrable": false,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": true,
"primary_key?": false,
"generated?": false
}
],
"table": "complex_calculations_skills",
"hash": "D2569EE39E2C9C58AAA3EB9E03A5EE726C5CD2F43D7387DA213F2C2D539F7606",
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"check_constraints": [],
"identities": [],
"custom_indexes": [],
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"base_filter": null,
"custom_statements": [],
"has_create_action": true
}

View file

@ -0,0 +1,69 @@
defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationTables 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(:complex_calculations_skills, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true
add :removed, :boolean, null: false, default: false
add :certification_id, :uuid
end
create table(:complex_calculations_documentations, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true
add :status, :text, null: false
add :documented_at, :utc_datetime_usec
add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()")
add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()")
add :skill_id,
references(:complex_calculations_skills,
column: :id,
name: "complex_calculations_documentations_skill_id_fkey",
type: :uuid,
prefix: "public"
)
end
create table(:complex_calculations_certifications, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true
end
alter table(:complex_calculations_skills) do
modify :certification_id,
references(:complex_calculations_certifications,
column: :id,
name: "complex_calculations_skills_certification_id_fkey",
type: :uuid,
prefix: "public"
)
end
end
def down do
drop constraint(
:complex_calculations_skills,
"complex_calculations_skills_certification_id_fkey"
)
alter table(:complex_calculations_skills) do
modify :certification_id, :uuid
end
drop table(:complex_calculations_certifications)
drop constraint(
:complex_calculations_documentations,
"complex_calculations_documentations_skill_id_fkey"
)
drop table(:complex_calculations_documentations)
drop table(:complex_calculations_skills)
end
end

View file

@ -0,0 +1,60 @@
defmodule AshPostgres.Test.ComplexCalculationsTest do
use AshPostgres.RepoCase, async: false
test "complex calculation" do
certification =
AshPostgres.Test.ComplexCalculations.Certification
|> Ash.Changeset.new()
|> AshPostgres.Test.ComplexCalculations.Api.create!()
skill =
AshPostgres.Test.ComplexCalculations.Skill
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:certification, certification, type: :append)
|> AshPostgres.Test.ComplexCalculations.Api.create!()
_documentation =
AshPostgres.Test.ComplexCalculations.Documentation
|> Ash.Changeset.new(%{status: :demonstrated})
|> Ash.Changeset.manage_relationship(:skill, skill, type: :append)
|> AshPostgres.Test.ComplexCalculations.Api.create!()
skill =
skill
|> AshPostgres.Test.ComplexCalculations.Api.load!([:latest_documentation_status])
assert skill.latest_documentation_status == :demonstrated
certification =
certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([
:count_of_skills
])
assert certification.count_of_skills == 1
certification =
certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([
:count_of_approved_skills
])
assert certification.count_of_approved_skills == 0
certification =
certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([
:count_of_documented_skills
])
assert certification.count_of_documented_skills == 1
certification =
certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([
:some_documentation_created
])
refute certification.some_documentation_created
end
end

View file

@ -0,0 +1,8 @@
defmodule AshPostgres.Test.ComplexCalculations.Api do
@moduledoc false
use Ash.Api
resources do
registry(AshPostgres.Test.ComplexCalculations.Registry)
end
end

View file

@ -0,0 +1,10 @@
defmodule AshPostgres.Test.ComplexCalculations.Registry do
@moduledoc false
use Ash.Registry
entries do
entry(AshPostgres.Test.ComplexCalculations.Certification)
entry(AshPostgres.Test.ComplexCalculations.Skill)
entry(AshPostgres.Test.ComplexCalculations.Documentation)
end
end

View file

@ -0,0 +1,48 @@
defmodule AshPostgres.Test.ComplexCalculations.Certification do
@moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer
actions do
defaults([:create, :read, :update, :destroy])
end
aggregates do
count :count_of_documented_skills, :skills do
filter(expr(removed == false and status != :pending))
end
count :count_of_approved_skills, :skills do
filter(expr(removed == false and status == :approved))
end
count :count_of_skills, :skills do
filter(expr(removed == false))
end
end
attributes do
uuid_primary_key(:id)
end
calculations do
calculate :all_documentation_approved, :boolean do
calculation(expr(count_of_skills == count_of_approved_skills))
load([:count_of_skills, :count_of_approved_skills])
end
calculate :some_documentation_created, :boolean do
calculation(expr(count_of_documented_skills > 0 && all_documentation_approved == false))
load([:count_of_documented_skills, :all_documentation_approved])
end
end
postgres do
table "complex_calculations_certifications"
repo(AshPostgres.TestRepo)
end
relationships do
has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill)
end
end

View file

@ -0,0 +1,48 @@
defmodule AshPostgres.Test.ComplexCalculations.Documentation do
@moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(
:status,
:atom,
constraints: [
one_of: [:demonstrated, :performed, :approved, :reopened]
],
allow_nil?: false
)
attribute(:documented_at, :utc_datetime_usec)
create_timestamp(:inserted_at, private?: false)
update_timestamp(:updated_at, private?: false)
end
calculations do
calculate(
:timestamp,
:utc_datetime_usec,
expr(
if is_nil(documented_at) do
inserted_at
else
documented_at
end
)
)
end
postgres do
table "complex_calculations_documentations"
repo(AshPostgres.TestRepo)
end
relationships do
belongs_to(:skill, AshPostgres.Test.ComplexCalculations.Skill)
end
end

View file

@ -0,0 +1,50 @@
defmodule AshPostgres.Test.ComplexCalculations.Skill do
@moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer
actions do
defaults([:create, :read, :update, :destroy])
end
aggregates do
first :latest_documentation_status, [:documentations], :status do
sort(timestamp: :desc)
end
end
attributes do
uuid_primary_key(:id)
attribute(:removed, :boolean, default: false, allow_nil?: false)
end
calculations do
calculate :status, :atom do
calculation(
expr(
if is_nil(latest_documentation_status) do
:pending
else
latest_documentation_status
end
)
)
end
end
postgres do
table "complex_calculations_skills"
repo(AshPostgres.TestRepo)
end
relationships do
belongs_to(:certification, AshPostgres.Test.ComplexCalculations.Certification)
has_many :documentations, AshPostgres.Test.ComplexCalculations.Documentation do
sort(timestamp: :desc, inserted_at: :desc)
end
has_one :latest_documentation, AshPostgres.Test.ComplexCalculations.Documentation do
sort(timestamp: :desc, inserted_at: :desc)
end
end
end