Skip to content

Commit 7b8910b

Browse files
authored
test: Add failing test for aggregate with parent() + select() + limit() (#644)
Reproduces a bug where combining select() + limit() with an aggregate that uses parent() in its filter causes SQL error. This bug was found in ash_graphql where GraphQL list queries would automatically trigger this scenario.
1 parent 277c055 commit 7b8910b

File tree

4 files changed

+178
-1
lines changed

4 files changed

+178
-1
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"attributes": [
3+
{
4+
"allow_nil?": false,
5+
"default": "fragment(\"gen_random_uuid()\")",
6+
"generated?": false,
7+
"precision": null,
8+
"primary_key?": true,
9+
"references": null,
10+
"scale": null,
11+
"size": null,
12+
"source": "id",
13+
"type": "uuid"
14+
},
15+
{
16+
"allow_nil?": true,
17+
"default": "nil",
18+
"generated?": false,
19+
"precision": null,
20+
"primary_key?": false,
21+
"references": null,
22+
"scale": null,
23+
"size": null,
24+
"source": "name",
25+
"type": "text"
26+
},
27+
{
28+
"allow_nil?": true,
29+
"default": "nil",
30+
"generated?": false,
31+
"precision": null,
32+
"primary_key?": false,
33+
"references": {
34+
"deferrable": false,
35+
"destination_attribute": "id",
36+
"destination_attribute_default": null,
37+
"destination_attribute_generated": null,
38+
"index?": false,
39+
"match_type": null,
40+
"match_with": null,
41+
"multitenancy": {
42+
"attribute": null,
43+
"global": null,
44+
"strategy": null
45+
},
46+
"name": "chats_last_read_message_id_fkey",
47+
"on_delete": null,
48+
"on_update": null,
49+
"primary_key?": true,
50+
"schema": "public",
51+
"table": "messages"
52+
},
53+
"scale": null,
54+
"size": null,
55+
"source": "last_read_message_id",
56+
"type": "uuid"
57+
}
58+
],
59+
"base_filter": null,
60+
"check_constraints": [],
61+
"custom_indexes": [],
62+
"custom_statements": [],
63+
"has_create_action": true,
64+
"hash": "78989357AB74584B9A18249E307C842D17AA96B7DD790EAADC9FA1C6422388B5",
65+
"identities": [],
66+
"multitenancy": {
67+
"attribute": null,
68+
"global": null,
69+
"strategy": null
70+
},
71+
"repo": "Elixir.AshPostgres.TestRepo",
72+
"schema": null,
73+
"table": "chats"
74+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
defmodule AshPostgres.TestRepo.Migrations.AddLastReadMessageToChats do
2+
@moduledoc """
3+
Updates resources based on their most recent snapshots.
4+
5+
This file was autogenerated with `mix ash_postgres.generate_migrations`
6+
"""
7+
8+
use Ecto.Migration
9+
10+
def up do
11+
alter table(:chats) do
12+
add(
13+
:last_read_message_id,
14+
references(:messages,
15+
column: :id,
16+
name: "chats_last_read_message_id_fkey",
17+
type: :uuid,
18+
prefix: "public"
19+
)
20+
)
21+
end
22+
end
23+
24+
def down do
25+
drop(constraint(:chats, "chats_last_read_message_id_fkey"))
26+
27+
alter table(:chats) do
28+
remove(:last_read_message_id)
29+
end
30+
end
31+
end

test/aggregate_test.exs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
defmodule AshSql.AggregateTest do
66
use AshPostgres.RepoCase, async: false
77
import ExUnit.CaptureIO
8-
alias AshPostgres.Test.{Author, Comment, Organization, Post, Rating, User}
8+
alias AshPostgres.Test.{Author, Chat, Comment, Organization, Post, Rating, User}
99

1010
require Ash.Query
1111
import Ash.Expr
@@ -1860,4 +1860,63 @@ defmodule AshSql.AggregateTest do
18601860
assert Enum.at(results, 0).count_of_comments == 3
18611861
assert Enum.at(results, 1).count_of_comments == 2
18621862
end
1863+
1864+
describe "aggregate with parent filter and limited select" do
1865+
test "FAILS when combining select() + limit() with aggregate using parent() in filter" do
1866+
# BUG: When using select() + limit() with an aggregate that uses parent()
1867+
# in its filter, the query generation creates a subquery that's missing the parent
1868+
# fields, causing a SQL error.
1869+
#
1870+
# This bug was found in ash_graphql where GraphQL list queries with pagination
1871+
# would fail when loading aggregates that use parent() in filters.
1872+
#
1873+
# The bug requires BOTH conditions:
1874+
# 1. select() limits which fields are included (e.g., only :id)
1875+
# 2. limit() causes a subquery to be generated
1876+
# 3. An aggregate filter references parent() fields that aren't in select()
1877+
#
1878+
# Without BOTH select() and limit(), the query works fine (see tests below).
1879+
#
1880+
# Current error:
1881+
# ERROR 42703 (undefined_column) column s0.last_read_message_id does not exist
1882+
#
1883+
# Generated query:
1884+
# SELECT s0."id", coalesce(s1."unread_message_count"::bigint, ...)
1885+
# FROM (SELECT sc0."id" AS "id" FROM "chats" AS sc0 LIMIT 10) AS s0
1886+
# LEFT OUTER JOIN LATERAL (
1887+
# SELECT ... FROM "messages" WHERE ... s0."last_read_message_id" ... # <- field not in subquery!
1888+
# ) AS s1 ON TRUE
1889+
#
1890+
# Expected fix: Ash should automatically include parent() referenced fields
1891+
# (like last_read_message_id) in the subquery even if not explicitly selected.
1892+
1893+
Chat
1894+
|> Ash.Query.select(:id)
1895+
|> Ash.Query.load(:unread_message_count)
1896+
|> Ash.Query.limit(10)
1897+
|> Ash.read!()
1898+
end
1899+
1900+
test "works WITHOUT select() - limit alone doesn't cause the bug" do
1901+
Chat
1902+
|> Ash.Query.load(:unread_message_count)
1903+
|> Ash.Query.limit(10)
1904+
|> Ash.read!()
1905+
end
1906+
1907+
test "works WITHOUT limit() - select alone doesn't cause the bug" do
1908+
Chat
1909+
|> Ash.Query.select(:id)
1910+
|> Ash.Query.load(:unread_message_count)
1911+
|> Ash.read!()
1912+
end
1913+
1914+
test "works when selecting the parent() referenced field explicitly (workaround)" do
1915+
Chat
1916+
|> Ash.Query.select([:id, :last_read_message_id])
1917+
|> Ash.Query.load(:unread_message_count)
1918+
|> Ash.Query.limit(10)
1919+
|> Ash.read!()
1920+
end
1921+
end
18631922
end

test/support/resources/chat.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ defmodule AshPostgres.Test.Chat do
2424
end
2525

2626
relationships do
27+
belongs_to :last_read_message, AshPostgres.Test.Message do
28+
allow_nil?(true)
29+
public?(true)
30+
attribute_writable?(true)
31+
end
32+
2733
has_many :messages, AshPostgres.Test.Message do
2834
public?(true)
2935
end
@@ -41,4 +47,11 @@ defmodule AshPostgres.Test.Chat do
4147
sort(sent_at: :desc)
4248
end
4349
end
50+
51+
aggregates do
52+
count :unread_message_count, :messages do
53+
public?(true)
54+
filter(expr(is_nil(parent(last_read_message_id)) or id > parent(last_read_message_id)))
55+
end
56+
end
4457
end

0 commit comments

Comments
 (0)