@@ -3262,6 +3262,8 @@ defmodule AshPostgres.DataLayer do
3262
3262
3263
3263
@ impl true
3264
3264
def sort ( query , sort , _resource ) do
3265
+ query = maybe_subquery_upgrade ( query , { :sort , sort } )
3266
+
3265
3267
{ :ok ,
3266
3268
Map . update! (
3267
3269
query ,
@@ -3272,12 +3274,18 @@ defmodule AshPostgres.DataLayer do
3272
3274
3273
3275
@ impl true
3274
3276
def select ( query , select , _resource ) do
3277
+ query = maybe_subquery_upgrade ( query , { :select , select } )
3278
+
3275
3279
if query . __ash_bindings__ . context [ :data_layer ] [ :combination_query? ] ||
3276
3280
query . __ash_bindings__ . context [ :data_layer ] [ :combination_of_queries? ] do
3277
3281
binding = query . __ash_bindings__ . root_binding
3278
3282
3279
- query =
3280
- from ( row in Ecto.Query . exclude ( query , :select ) , select: % { } )
3283
+ { query , select } =
3284
+ if field_set = query . __ash_bindings__ [ :already_selected ] do
3285
+ { query , select -- field_set }
3286
+ else
3287
+ { from ( row in Ecto.Query . exclude ( query , :select ) , select: % { } ) , select }
3288
+ end
3281
3289
3282
3290
Enum . reduce ( select , query , fn field , query ->
3283
3291
from ( row in query , select_merge: % { ^ field => field ( as ( ^ binding ) , ^ field ) } )
@@ -3294,6 +3302,7 @@ defmodule AshPostgres.DataLayer do
3294
3302
end
3295
3303
3296
3304
def distinct_sort ( query , sort , _ ) do
3305
+ query = maybe_subquery_upgrade ( query , { :distinct_sort , sort } )
3297
3306
{ :ok , Map . update! ( query , :__ash_bindings__ , & Map . put ( & 1 , :distinct_sort , sort ) ) }
3298
3307
end
3299
3308
@@ -3302,11 +3311,13 @@ defmodule AshPostgres.DataLayer do
3302
3311
# to come up with alternatives here.
3303
3312
@ impl true
3304
3313
def distinct ( query , distinct , resource ) do
3314
+ query = maybe_subquery_upgrade ( query , { :distinct , distinct } )
3305
3315
AshSql.Distinct . distinct ( query , distinct , resource )
3306
3316
end
3307
3317
3308
3318
@ impl true
3309
3319
def filter ( query , filter , resource , opts \\ [ ] ) do
3320
+ query = maybe_subquery_upgrade ( query , { :filter , filter } )
3310
3321
used_aggregates = Ash.Filter . used_aggregates ( filter , [ ] )
3311
3322
3312
3323
query =
@@ -3336,6 +3347,112 @@ defmodule AshPostgres.DataLayer do
3336
3347
end
3337
3348
end
3338
3349
3350
+ defp maybe_subquery_upgrade (
3351
+ % { __ash_bindings__: % { subquery_upgrade?: true } } = query ,
3352
+ _
3353
+ ) do
3354
+ query
3355
+ end
3356
+
3357
+ defp maybe_subquery_upgrade ( query , type ) do
3358
+ fieldset = query . __ash_bindings__ . context [ :data_layer ] [ :combination_fieldset ]
3359
+
3360
+ if query . __ash_bindings__ . context [ :data_layer ] [ :combination_of_queries? ] && fieldset do
3361
+ requires_join? =
3362
+ case type do
3363
+ { :filter , contents } ->
3364
+ Enum . any? (
3365
+ Ash.Filter . list_refs ( contents ) ,
3366
+ & ( & 1 . relationship_path != [ ] || & 1 . attribute . name not in fieldset )
3367
+ )
3368
+
3369
+ { :calculations , calculations } ->
3370
+ Enum . any? ( calculations , fn { _ , expr } ->
3371
+ Enum . any? (
3372
+ Ash.Filter . list_refs ( expr ) ,
3373
+ & ( & 1 . relationship_path != [ ] || & 1 . attribute . name not in fieldset )
3374
+ )
3375
+ end )
3376
+
3377
+ { sort , sorts } when sort in [ :sort , :distinct , :distinct_sort ] ->
3378
+ Enum . any? ( sorts , fn
3379
+ { atom , _ } when is_atom ( atom ) ->
3380
+ atom not in fieldset
3381
+
3382
+ { % Ash.Query.Calculation { } = calc , _ } ->
3383
+ calc . opts
3384
+ |> calc . module . expression ( calc . context )
3385
+ |> Ash.Filter . hydrate_refs ( % {
3386
+ resource: query . __ash_bindings__ . resource ,
3387
+ parent_stack: query . __ash_bindings__ [ :parent_resources ] || [ ] ,
3388
+ public?: false
3389
+ } )
3390
+ |> Ash.Filter . list_refs ( )
3391
+ |> Enum . any? ( & ( & 1 . relationship_path != [ ] || & 1 . attribute . name not in fieldset ) )
3392
+
3393
+ _ ->
3394
+ true
3395
+ end )
3396
+
3397
+ { :select , select } ->
3398
+ Enum . any? ( select , & ( & 1 not in fieldset ) )
3399
+ end
3400
+
3401
+ resource = query . __ash_bindings__ . resource
3402
+
3403
+ if requires_join? do
3404
+ primary_key = Ash.Resource.Info . primary_key ( query . __ash_bindings__ . resource )
3405
+
3406
+ if primary_key != [ ] && primary_key -- fieldset == [ ] do
3407
+ dynamic =
3408
+ Enum . reduce ( primary_key , nil , fn key , expr ->
3409
+ if is_nil ( expr ) do
3410
+ Ecto.Query . dynamic ( [ l , r ] , field ( l , ^ key ) == field ( r , ^ key ) )
3411
+ else
3412
+ Ecto.Query . dynamic ( [ l , r ] , field ( l , ^ key ) == field ( r , ^ key ) and ^ expr )
3413
+ end
3414
+ end )
3415
+
3416
+ default_select =
3417
+ MapSet . to_list (
3418
+ Ash.Resource.Info . selected_by_default_attribute_names (
3419
+ query . __ash_bindings__ . resource
3420
+ )
3421
+ )
3422
+
3423
+ query_with_select =
3424
+ from ( sub in query ,
3425
+ join: row in ^ query . __ash_bindings__ . resource ,
3426
+ # why doesn't `.root_binding` work the way I expect it to here?
3427
+ on: ^ dynamic ,
3428
+ select: map ( row , ^ default_select ) ,
3429
+ select_merge: map ( sub , ^ fieldset )
3430
+ )
3431
+
3432
+ from ( row in subquery ( query_with_select ) , as: ^ 0 )
3433
+ |> AshSql.Bindings . default_bindings ( resource , AshPostgres.SqlImplementation )
3434
+ |> Map . update! (
3435
+ :__ash_bindings__ ,
3436
+ & Map . merge ( & 1 , % {
3437
+ already_selected: fieldset ,
3438
+ subquery_upgrade?: true ,
3439
+ context: query . __ash_bindings__ . context
3440
+ } )
3441
+ )
3442
+ else
3443
+ raise """
3444
+ Unsupported combination query. Combinations must select the primary key if referencing
3445
+ any fields that are *not* selected by the combinations in filter, sort & distinct.
3446
+ """
3447
+ end
3448
+ else
3449
+ query
3450
+ end
3451
+ else
3452
+ query
3453
+ end
3454
+ end
3455
+
3339
3456
@ impl true
3340
3457
def add_aggregates ( query , aggregates , resource ) do
3341
3458
AshSql.Aggregate . add_aggregates (
@@ -3349,6 +3466,8 @@ defmodule AshPostgres.DataLayer do
3349
3466
3350
3467
@ impl true
3351
3468
def add_calculations ( query , calculations , resource , select? \\ true ) do
3469
+ query = maybe_subquery_upgrade ( query , { :calculations , calculations } )
3470
+
3352
3471
AshSql.Calculation . add_calculations (
3353
3472
query ,
3354
3473
calculations ,
0 commit comments