33 Aggregate as AggregateExpr ,
44 CollectionRef ,
55 Func as FuncExpr ,
6+ INCLUDES_SCALAR_FIELD ,
67 IncludesSubquery ,
78 PropRef ,
89 QueryRef ,
@@ -24,11 +25,12 @@ import {
2425 isRefProxy ,
2526 toExpression ,
2627} from './ref-proxy.js'
27- import { ToArrayWrapper } from './functions.js'
28+ import { ConcatToArrayWrapper , ToArrayWrapper } from './functions.js'
2829import type { NamespacedRow , SingleResult } from '../../types.js'
2930import type {
3031 Aggregate ,
3132 BasicExpression ,
33+ IncludesMaterialization ,
3234 JoinClause ,
3335 OrderBy ,
3436 OrderByDirection ,
@@ -44,10 +46,13 @@ import type {
4446 JoinOnCallback ,
4547 MergeContextForJoinCallback ,
4648 MergeContextWithJoinType ,
49+ NonScalarSelectObject ,
4750 OrderByCallback ,
4851 OrderByOptions ,
4952 RefsForContext ,
5053 ResultTypeFromSelect ,
54+ ResultTypeFromSelectValue ,
55+ ScalarSelectValue ,
5156 SchemaFromSource ,
5257 SelectObject ,
5358 Source ,
@@ -489,11 +494,29 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
489494 * ```
490495 */
491496 select < TSelectObject extends SelectObject > (
492- callback : ( refs : RefsForContext < TContext > ) => TSelectObject ,
493- ) : QueryBuilder < WithResult < TContext , ResultTypeFromSelect < TSelectObject > > > {
497+ callback : (
498+ refs : RefsForContext < TContext > ,
499+ ) => NonScalarSelectObject < TSelectObject > ,
500+ ) : QueryBuilder < WithResult < TContext , ResultTypeFromSelect < TSelectObject > > >
501+ select < TSelectValue extends ScalarSelectValue > (
502+ callback : ( refs : RefsForContext < TContext > ) => TSelectValue ,
503+ ) : QueryBuilder < WithResult < TContext , ResultTypeFromSelectValue < TSelectValue > > >
504+ select (
505+ callback : (
506+ refs : RefsForContext < TContext > ,
507+ ) => SelectObject | ScalarSelectValue ,
508+ ) {
494509 const aliases = this . _getCurrentAliases ( )
495510 const refProxy = createRefProxy ( aliases ) as RefsForContext < TContext >
496- const selectObject = callback ( refProxy )
511+ let selectObject = callback ( refProxy )
512+
513+ // Returning a top-level alias directly is equivalent to spreading it.
514+ // Leaf refs like `row.name` must remain scalar selections.
515+ if ( isRefProxy ( selectObject ) && selectObject . __path . length === 1 ) {
516+ const sentinelKey = `__SPREAD_SENTINEL__${ selectObject . __path [ 0 ] } __0`
517+ selectObject = { [ sentinelKey ] : true }
518+ }
519+
497520 const select = buildNestedSelect ( selectObject , aliases )
498521
499522 return new BaseQueryBuilder ( {
@@ -679,7 +702,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
679702 * // Get countries our users are from
680703 * query
681704 * .from({ users: usersCollection })
682- * .select(({users}) => users.country)
705+ * .select(({users}) => ({ country: users.country }) )
683706 * .distinct()
684707 * ```
685708 */
@@ -709,7 +732,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
709732 // TODO: enforcing return only one result with also a default orderBy if none is specified
710733 // limit: 1,
711734 singleResult : true ,
712- } )
735+ } ) as any
713736 }
714737
715738 // Helper methods
@@ -772,7 +795,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
772795 ...builder . query ,
773796 select : undefined , // remove the select clause if it exists
774797 fnSelect : callback ,
775- } )
798+ } ) as any
776799 } ,
777800 /**
778801 * Filter rows using a function that operates on each row
@@ -880,14 +903,21 @@ function buildNestedSelect(obj: any, parentAliases: Array<string> = []): any {
880903 continue
881904 }
882905 if ( v instanceof BaseQueryBuilder ) {
883- out [ k ] = buildIncludesSubquery ( v , k , parentAliases , false )
906+ out [ k ] = buildIncludesSubquery ( v , k , parentAliases , `collection` )
884907 continue
885908 }
886909 if ( v instanceof ToArrayWrapper ) {
887910 if ( ! ( v . query instanceof BaseQueryBuilder ) ) {
888911 throw new Error ( `toArray() must wrap a subquery builder` )
889912 }
890- out [ k ] = buildIncludesSubquery ( v . query , k , parentAliases , true )
913+ out [ k ] = buildIncludesSubquery ( v . query , k , parentAliases , `array` )
914+ continue
915+ }
916+ if ( v instanceof ConcatToArrayWrapper ) {
917+ if ( ! ( v . query instanceof BaseQueryBuilder ) ) {
918+ throw new Error ( `concat(toArray(...)) must wrap a subquery builder` )
919+ }
920+ out [ k ] = buildIncludesSubquery ( v . query , k , parentAliases , `concat` )
891921 continue
892922 }
893923 out [ k ] = buildNestedSelect ( v , parentAliases )
@@ -937,7 +967,7 @@ function buildIncludesSubquery(
937967 childBuilder : BaseQueryBuilder ,
938968 fieldName : string ,
939969 parentAliases : Array < string > ,
940- materializeAsArray : boolean ,
970+ materialization : IncludesMaterialization ,
941971) : IncludesSubquery {
942972 const childQuery = childBuilder . _getQuery ( )
943973
@@ -1093,14 +1123,45 @@ function buildIncludesSubquery(
10931123 where : pureChildWhere . length > 0 ? pureChildWhere : undefined ,
10941124 }
10951125
1126+ const rawChildSelect = modifiedQuery . select as any
1127+ const hasObjectSelect =
1128+ rawChildSelect === undefined || isPlainObject ( rawChildSelect )
1129+ let includesQuery = modifiedQuery
1130+ let scalarField : string | undefined
1131+
1132+ if ( materialization === `concat` ) {
1133+ if ( rawChildSelect === undefined || hasObjectSelect ) {
1134+ throw new Error (
1135+ `concat(toArray(...)) for "${ fieldName } " requires the subquery to select a scalar value` ,
1136+ )
1137+ }
1138+ }
1139+
1140+ if ( ! hasObjectSelect ) {
1141+ if ( materialization === `collection` ) {
1142+ throw new Error (
1143+ `Includes subquery for "${ fieldName } " must select an object when materializing as a Collection` ,
1144+ )
1145+ }
1146+
1147+ scalarField = INCLUDES_SCALAR_FIELD
1148+ includesQuery = {
1149+ ...modifiedQuery ,
1150+ select : {
1151+ [ scalarField ] : rawChildSelect ,
1152+ } ,
1153+ }
1154+ }
1155+
10961156 return new IncludesSubquery (
1097- modifiedQuery ,
1157+ includesQuery ,
10981158 parentRef ,
10991159 childRef ,
11001160 fieldName ,
11011161 parentFilters . length > 0 ? parentFilters : undefined ,
11021162 parentProjection ,
1103- materializeAsArray ,
1163+ materialization ,
1164+ scalarField ,
11041165 )
11051166}
11061167
0 commit comments