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