Skip to content

reproduce a bug with a complex calculation #593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "level",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "complex_calculations_folder_items_folder_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "complex_calculations_folders"
},
"scale": null,
"size": null,
"source": "folder_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": false,
"hash": "C7BC97676F202A744482B4095560986B265E6BCB74F6E69C1330034FBC4981E5",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"table": "complex_calculations_folder_items"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "some_integer_setting",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "level",
"type": "ltree"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": false,
"hash": "1982E0DAF0B5B22502A5747ED8533C6F1D58E882E1132FAB0939EC13F2CFC5AF",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"table": "complex_calculations_folders"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationsFolderAndItems 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_folder_items, primary_key: false) do
add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true)
add(:name, :text)
add(:level, :bigint)
add(:folder_id, :uuid)
end

create table(:complex_calculations_folders, primary_key: false) do
add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true)
end

alter table(:complex_calculations_folder_items) do
modify(
:folder_id,
references(:complex_calculations_folders,
column: :id,
name: "complex_calculations_folder_items_folder_id_fkey",
type: :uuid,
prefix: "public"
)
)
end

alter table(:complex_calculations_folders) do
add(:some_integer_setting, :bigint)
add(:level, :ltree)
end
end

def down do
alter table(:complex_calculations_folders) do
remove(:level)
remove(:some_integer_setting)
end

drop(
constraint(
:complex_calculations_folder_items,
"complex_calculations_folder_items_folder_id_fkey"
)
)

alter table(:complex_calculations_folder_items) do
modify(:folder_id, :uuid)
end

drop(table(:complex_calculations_folders))

drop(table(:complex_calculations_folder_items))
end
end
30 changes: 30 additions & 0 deletions test/complex_calculations_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -373,4 +373,34 @@
assert doc_before.is_active_with_timezone == false
assert doc_after.is_active_with_timezone == true
end

test "gnarly parent bug with some weird ltree setup" do

Check failure on line 377 in test/complex_calculations_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix test

test gnarly parent bug with some weird ltree setup (AshPostgres.Test.ComplexCalculationsTest)

Check failure on line 377 in test/complex_calculations_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix test

test gnarly parent bug with some weird ltree setup (AshPostgres.Test.ComplexCalculationsTest)

Check failure on line 377 in test/complex_calculations_test.exs

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix test

test gnarly parent bug with some weird ltree setup (AshPostgres.Test.ComplexCalculationsTest)
_folder_a =
Ash.Seed.seed!(
AshPostgres.Test.Support.ComplexCalculations.Folder,
%{some_integer_setting: 1, level: "a"}
)

_folder_b =
Ash.Seed.seed!(
AshPostgres.Test.Support.ComplexCalculations.Folder,
%{some_integer_setting: nil, level: "a.b"}
)

folder_c =
Ash.Seed.seed!(
AshPostgres.Test.Support.ComplexCalculations.Folder,
%{some_integer_setting: nil, level: "a.b.c"}
)

folder_c_item =
Ash.Seed.seed!(
AshPostgres.Test.Support.ComplexCalculations.FolderItem,
%{name: "Item in C", level: 1, folder_id: folder_c.id}
)

# reproduction: this raises Unknown Error
# * ** (Postgrex.Error) ERROR 42846 (cannot_coerce) cannot cast type bigint to ltree
assert Ash.calculate!(folder_c_item, :folder_setting) == 1
end
end
2 changes: 2 additions & 0 deletions test/support/complex_calculations/domain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Domain do
resource(AshPostgres.Test.ComplexCalculations.Channel)
resource(AshPostgres.Test.ComplexCalculations.DMChannel)
resource(AshPostgres.Test.ComplexCalculations.ChannelMember)
resource(AshPostgres.Test.Support.ComplexCalculations.Folder)
resource(AshPostgres.Test.Support.ComplexCalculations.FolderItem)
end

authorization do
Expand Down
75 changes: 75 additions & 0 deletions test/support/complex_calculations/resources/folder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
defmodule AshPostgres.Test.Support.ComplexCalculations.Folder do
@moduledoc """
A tree structure using the ltree type.
"""

alias AshPostgres.Test.Support.ComplexCalculations.Folder

use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer

@default_integer_setting 5

postgres do
table "complex_calculations_folders"
repo(AshPostgres.TestRepo)
end

attributes do
uuid_primary_key(:id)

attribute(:some_integer_setting, :integer,
public?: true,
description: "Some setting that can be inherited. No real semantic meaning, just for demo"
)

attribute(:level, AshPostgres.Ltree, public?: true)
end

actions do
defaults([:read])
end

relationships do
has_many :ancestors, Folder do
public?(true)
no_attributes?(true)

# use ltree @> operator to get all ancestors
filter(expr(fragment("? @> ? AND ? < ?", level, parent(level), nlevel, parent(nlevel))))
end

has_many :items, AshPostgres.Test.Support.ComplexCalculations.FolderItem do
public?(true)
end
end

calculations do
calculate(:nlevel, :integer, expr(fragment("nlevel(?)", level)))

calculate(
:effective_integer_setting,
:integer,
expr(
if is_nil(some_integer_setting) do
# closest ancestor with a non-nil setting, or the default
first(
ancestors,
query: [
sort: [nlevel: :desc],
filter: expr(not is_nil(some_integer_setting))
],
field: :some_integer_setting
) || @default_integer_setting
else
some_integer_setting
end
),
description: """
The effective integer setting, inheriting from ancestors if not explicitly set,
or defaulting to #{@default_integer_setting} if none are set. No real semantic meaning, just for demo
"""
)
end
end
38 changes: 38 additions & 0 deletions test/support/complex_calculations/resources/folder_item.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule AshPostgres.Test.Support.ComplexCalculations.FolderItem do
@moduledoc false
use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer

postgres do
table "complex_calculations_folder_items"
repo(AshPostgres.TestRepo)
end

attributes do
uuid_primary_key(:id)
attribute(:name, :string, public?: true)

attribute(:level, :integer,
public?: true,
description: """
No real semantic meaning here, just for demo, this *deliberately* is named the same as the ltree level column in Folder,
but is NOT related to it in any way
"""
)
end

actions do
defaults([:read])
end

calculations do
calculate(:folder_setting, :integer, expr(folder.effective_integer_setting))
end

relationships do
belongs_to(:folder, AshPostgres.Test.Support.ComplexCalculations.Folder) do
public?(true)
end
end
end
Loading