@@ -111,6 +111,34 @@ defmodule AshPostgres.CalculationTest do
111111 |> Ash . read! ( )
112112 end
113113
114+ test "runtime loading calculation with fragment referencing aggregate works correctly" do
115+
116+ post =
117+ Post
118+ |> Ash.Changeset . for_create ( :create , % { title: "test post" } )
119+ |> Ash . create! ( )
120+
121+ Comment
122+ |> Ash.Changeset . for_create ( :create , % { title: "comment1" , likes: 5 } )
123+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
124+ |> Ash . create! ( )
125+
126+ Comment
127+ |> Ash.Changeset . for_create ( :create , % { title: "comment2" , likes: 15 } )
128+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
129+ |> Ash . create! ( )
130+
131+ result =
132+ Post
133+ |> Ash.Query . load ( [ :comment_metric , :complex_comment_metric , :multi_agg_calc ] )
134+ |> Ash . read! ( )
135+
136+ assert [ post ] = result
137+ assert is_integer ( post . comment_metric )
138+ assert is_integer ( post . complex_comment_metric )
139+ assert is_integer ( post . multi_agg_calc )
140+ end
141+
114142 test "expression calculations don't load when `reuse_values?` is true" do
115143 post =
116144 Post
@@ -1241,4 +1269,219 @@ defmodule AshPostgres.CalculationTest do
12411269
12421270 assert [ ] == Ash . read! ( query )
12431271 end
1272+
1273+ test "expression calculation referencing aggregates loaded via code_interface with load option" do
1274+ post =
1275+ Post
1276+ |> Ash.Changeset . for_create ( :create , % { title: "test post" } )
1277+ |> Ash . create! ( )
1278+
1279+ Comment
1280+ |> Ash.Changeset . for_create ( :create , % { title: "comment1" , likes: 5 } )
1281+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1282+ |> Ash . create! ( )
1283+
1284+ Comment
1285+ |> Ash.Changeset . for_create ( :create , % { title: "comment2" , likes: 15 } )
1286+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1287+ |> Ash . create! ( )
1288+
1289+ result = Post . get_by_id! ( post . id , load: [ :comment_metric ] )
1290+
1291+ assert result . comment_metric == 200
1292+ end
1293+
1294+ test "complex SQL fragment calculation with multiple aggregates" do
1295+ post =
1296+ Post
1297+ |> Ash.Changeset . for_create ( :create , % {
1298+ title: "test post" ,
1299+ base_reading_time: 500
1300+ } )
1301+ |> Ash . create! ( )
1302+
1303+ Comment
1304+ |> Ash.Changeset . for_create ( :create , % {
1305+ title: "comment1" ,
1306+ edited_duration: 100 ,
1307+ planned_duration: 80 ,
1308+ reading_time: 30 ,
1309+ version: :edited
1310+ } )
1311+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1312+ |> Ash . create! ( )
1313+
1314+ Comment
1315+ |> Ash.Changeset . for_create ( :create , % {
1316+ title: "comment2" ,
1317+ edited_duration: 0 ,
1318+ planned_duration: 120 ,
1319+ reading_time: 45 ,
1320+ version: :planned
1321+ } )
1322+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1323+ |> Ash . create! ( )
1324+
1325+ result = Post . get_by_id! ( post . id , load: [ :estimated_reading_time ] )
1326+
1327+ assert result . estimated_reading_time == 175
1328+ end
1329+
1330+ test "calculation with missing aggregate dependencies" do
1331+ post =
1332+ Post
1333+ |> Ash.Changeset . for_create ( :create , % {
1334+ title: "test post" ,
1335+ base_reading_time: 500
1336+ } )
1337+ |> Ash . create! ( )
1338+
1339+ Comment
1340+ |> Ash.Changeset . for_create ( :create , % {
1341+ title: "modified comment" ,
1342+ edited_duration: 100 ,
1343+ planned_duration: 0 ,
1344+ reading_time: 30 ,
1345+ version: :edited
1346+ } )
1347+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1348+ |> Ash . create! ( )
1349+
1350+ Comment
1351+ |> Ash.Changeset . for_create ( :create , % {
1352+ title: "planned comment" ,
1353+ edited_duration: 0 ,
1354+ planned_duration: 80 ,
1355+ reading_time: 20 ,
1356+ version: :planned
1357+ } )
1358+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1359+ |> Ash . create! ( )
1360+
1361+ result = Post . get_by_id! ( post . id , load: [ :estimated_reading_time ] )
1362+
1363+ refute match? ( % Ash.NotLoaded { } , result . estimated_reading_time ) ,
1364+ "Expected calculated value, got: #{ inspect ( result . estimated_reading_time ) } "
1365+ end
1366+
1367+ test "calculation with filtered aggregates and keyset pagination" do
1368+ post =
1369+ Post
1370+ |> Ash.Changeset . for_create ( :create , % {
1371+ title: "test post" ,
1372+ base_reading_time: 500
1373+ } )
1374+ |> Ash . create! ( )
1375+
1376+ Comment
1377+ |> Ash.Changeset . for_create ( :create , % {
1378+ title: "completed comment" ,
1379+ edited_duration: 100 ,
1380+ reading_time: 30 ,
1381+ version: :edited ,
1382+ status: :published
1383+ } )
1384+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1385+ |> Ash . create! ( )
1386+
1387+ Comment
1388+ |> Ash.Changeset . for_create ( :create , % {
1389+ title: "pending comment" ,
1390+ planned_duration: 80 ,
1391+ reading_time: 20 ,
1392+ version: :planned ,
1393+ status: :pending
1394+ } )
1395+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1396+ |> Ash . create! ( )
1397+
1398+
1399+ result_calc_only = Post . get_by_id! ( post . id , load: [ :estimated_reading_time ] )
1400+
1401+ debug_result =
1402+ Post . get_by_id! ( post . id ,
1403+ load: [
1404+ :total_edited_time ,
1405+ :total_planned_time ,
1406+ :total_comment_time ,
1407+ :published_comments ,
1408+ :base_reading_time
1409+ ]
1410+ )
1411+
1412+ result_count_only = Post . get_by_id! ( post . id , load: [ :published_comments ] )
1413+
1414+
1415+ result_both = Post . get_by_id! ( post . id , load: [ :published_comments , :estimated_reading_time ] )
1416+
1417+
1418+ assert result_both . estimated_reading_time == 150 ,
1419+ "Should calculate correctly with both loaded"
1420+
1421+ assert result_both . published_comments == 1 , "Should count correctly with both loaded"
1422+ end
1423+
1424+ test "calculation with keyset pagination works correctly (previously returned NotLoaded)" do
1425+ _posts =
1426+ Enum . map ( 1 .. 5 , fn i ->
1427+ post =
1428+ Post
1429+ |> Ash.Changeset . for_create ( :create , % {
1430+ title: "test post #{ i } " ,
1431+ base_reading_time: 100 * i
1432+ } )
1433+ |> Ash . create! ( )
1434+
1435+ Comment
1436+ |> Ash.Changeset . for_create ( :create , % {
1437+ title: "comment#{ i } " ,
1438+ edited_duration: 50 * i ,
1439+ planned_duration: 40 * i ,
1440+ reading_time: 10 * i ,
1441+ version: :edited ,
1442+ status: :published
1443+ } )
1444+ |> Ash.Changeset . manage_relationship ( :post , post , type: :append_and_remove )
1445+ |> Ash . create! ( )
1446+
1447+ post
1448+ end )
1449+
1450+ first_page =
1451+ Post
1452+ |> Ash.Query . load ( [ :published_comments , :estimated_reading_time ] )
1453+ |> Ash . read! ( action: :read_with_related_list_agg_filter , page: [ limit: 2 , count: true ] )
1454+
1455+ Enum . each ( first_page . results , fn post ->
1456+ refute match? ( % Ash.NotLoaded { } , post . estimated_reading_time ) ,
1457+ "First page post #{ post . id } should have loaded estimated_reading_time, got: #{ inspect ( post . estimated_reading_time ) } "
1458+ end )
1459+
1460+ if first_page . more? do
1461+ second_page =
1462+ Post
1463+ |> Ash.Query . load ( [ :published_comments , :estimated_reading_time ] )
1464+ |> Ash . read! (
1465+ action: :read_with_related_list_agg_filter ,
1466+ page: [
1467+ limit: 2 ,
1468+ after: first_page . results |> List . last ( ) |> Map . get ( :__metadata__ ) |> Map . get ( :keyset )
1469+ ]
1470+ )
1471+
1472+
1473+ assert length ( second_page . results ) > 0 , "Second page should have results"
1474+
1475+ Enum . each ( second_page . results , fn post ->
1476+ refute match? ( % Ash.NotLoaded { } , post . estimated_reading_time ) ,
1477+ "estimated_reading_time should be calculated, not NotLoaded"
1478+
1479+ refute match? ( % Ash.NotLoaded { } , post . published_comments ) ,
1480+ "published_comments should be calculated, not NotLoaded"
1481+
1482+ assert post . estimated_reading_time > 0 , "estimated_reading_time should be positive"
1483+ assert post . published_comments == 1 , "Each post has exactly 1 completed comment"
1484+ end )
1485+ end
1486+ end
12441487end
0 commit comments