@@ -58,127 +58,151 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
5858 parentAlias : string ,
5959 payload : true | FindArgs < Schema , GetModels < Schema > , true > ,
6060 ) : SelectQueryBuilder < any , any , any > {
61- const joinedQuery = this . buildRelationJSON ( model , query , relationField , parentAlias , payload ) ;
62-
63- return joinedQuery . select ( `${ parentAlias } $${ relationField } .$t as ${ relationField } ` ) ;
61+ const relationResultName = `${ parentAlias } $${ relationField } ` ;
62+ const joinedQuery = this . buildRelationJSON (
63+ model ,
64+ query ,
65+ relationField ,
66+ parentAlias ,
67+ payload ,
68+ relationResultName ,
69+ ) ;
70+ return joinedQuery . select ( `${ relationResultName } .$data as ${ relationField } ` ) ;
6471 }
6572
6673 private buildRelationJSON (
6774 model : string ,
6875 qb : SelectQueryBuilder < any , any , any > ,
6976 relationField : string ,
70- parentName : string ,
77+ parentAlias : string ,
7178 payload : true | FindArgs < Schema , GetModels < Schema > , true > ,
79+ resultName : string ,
7280 ) {
7381 const relationFieldDef = requireField ( this . schema , model , relationField ) ;
7482 const relationModel = relationFieldDef . type as GetModels < Schema > ;
7583
7684 return qb . leftJoinLateral (
7785 ( eb ) => {
78- const joinTableName = `${ parentName } $${ relationField } ` ;
79-
80- // simple select by default
81- let result = eb . selectFrom ( `${ relationModel } as ${ joinTableName } ` ) ;
86+ const relationSelectName = `${ resultName } $sub` ;
87+ const relationModelDef = requireModel ( this . schema , relationModel ) ;
8288
83- // however if there're filter/orderBy/take/skip,
84- // we need to build a subquery to handle them before aggregation
89+ let tbl : SelectQueryBuilder < any , any , any > ;
8590
86- // give sub query an alias to avoid conflict with parent scope
87- // (e.g., for cases like self-relation)
88- const subQueryAlias = ` ${ relationModel } $ ${ relationField } $sub` ;
91+ if ( this . canJoinWithoutNestedSelect ( relationModelDef , payload ) ) {
92+ // build join directly
93+ tbl = this . buildModelSelect ( eb , relationModel , relationSelectName , payload , false ) ;
8994
90- result = eb . selectFrom ( ( ) => {
91- let subQuery = this . buildSelectModel ( eb , relationModel , subQueryAlias ) ;
92- subQuery = this . buildSelectAllFields (
95+ // parent join filter
96+ tbl = this . buildRelationJoinFilter (
97+ tbl ,
98+ model ,
99+ relationField ,
93100 relationModel ,
94- subQuery ,
95- typeof payload === 'object' ? payload ?. omit : undefined ,
96- subQueryAlias ,
101+ relationSelectName ,
102+ parentAlias ,
97103 ) ;
98-
99- if ( payload && typeof payload === 'object' ) {
100- subQuery = this . buildFilterSortTake ( relationModel , payload , subQuery , subQueryAlias ) ;
101- }
102-
103- // add join conditions
104-
105- const m2m = getManyToManyRelation ( this . schema , model , relationField ) ;
106-
107- if ( m2m ) {
108- // many-to-many relation
109- const parentIds = getIdFields ( this . schema , model ) ;
110- const relationIds = getIdFields ( this . schema , relationModel ) ;
111- invariant ( parentIds . length === 1 , 'many-to-many relation must have exactly one id field' ) ;
112- invariant ( relationIds . length === 1 , 'many-to-many relation must have exactly one id field' ) ;
113- subQuery = subQuery . where (
114- eb (
115- eb . ref ( `${ subQueryAlias } .${ relationIds [ 0 ] } ` ) ,
116- 'in' ,
117- eb
118- . selectFrom ( m2m . joinTable )
119- . select ( `${ m2m . joinTable } .${ m2m . otherFkName } ` )
120- . whereRef (
121- `${ parentName } .${ parentIds [ 0 ] } ` ,
122- '=' ,
123- `${ m2m . joinTable } .${ m2m . parentFkName } ` ,
124- ) ,
125- ) ,
104+ } else {
105+ // join with a nested query
106+ tbl = eb . selectFrom ( ( ) => {
107+ let subQuery = this . buildModelSelect (
108+ eb ,
109+ relationModel ,
110+ `${ relationSelectName } $t` ,
111+ payload ,
112+ true ,
126113 ) ;
127- } else {
128- const joinPairs = buildJoinPairs ( this . schema , model , parentName , relationField , subQueryAlias ) ;
129- subQuery = subQuery . where ( ( eb ) =>
130- this . and ( eb , ...joinPairs . map ( ( [ left , right ] ) => eb ( sql . ref ( left ) , '=' , sql . ref ( right ) ) ) ) ,
114+
115+ // parent join filter
116+ subQuery = this . buildRelationJoinFilter (
117+ subQuery ,
118+ model ,
119+ relationField ,
120+ relationModel ,
121+ `${ relationSelectName } $t` ,
122+ parentAlias ,
131123 ) ;
132- }
133124
134- return subQuery . as ( joinTableName ) ;
135- } ) ;
125+ return subQuery . as ( relationSelectName ) ;
126+ } ) ;
127+ }
136128
137- result = this . buildRelationObjectSelect (
129+ // select relation result
130+ tbl = this . buildRelationObjectSelect (
138131 relationModel ,
139- joinTableName ,
140- relationField ,
132+ relationSelectName ,
141133 relationFieldDef ,
142- result ,
134+ tbl ,
143135 payload ,
144- parentName ,
136+ resultName ,
145137 ) ;
146138
147139 // add nested joins for each relation
148- result = this . buildRelationJoins ( relationModel , relationField , result , payload , parentName ) ;
140+ tbl = this . buildRelationJoins ( tbl , relationModel , relationSelectName , payload , resultName ) ;
149141
150142 // alias the join table
151- return result . as ( joinTableName ) ;
143+ return tbl . as ( resultName ) ;
152144 } ,
153145 ( join ) => join . onTrue ( ) ,
154146 ) ;
155147 }
156148
149+ private buildRelationJoinFilter (
150+ query : SelectQueryBuilder < any , any , { } > ,
151+ model : string ,
152+ relationField : string ,
153+ relationModel : GetModels < Schema > ,
154+ relationModelAlias : string ,
155+ parentAlias : string ,
156+ ) {
157+ const m2m = getManyToManyRelation ( this . schema , model , relationField ) ;
158+ if ( m2m ) {
159+ // many-to-many relation
160+ const parentIds = getIdFields ( this . schema , model ) ;
161+ const relationIds = getIdFields ( this . schema , relationModel ) ;
162+ invariant ( parentIds . length === 1 , 'many-to-many relation must have exactly one id field' ) ;
163+ invariant ( relationIds . length === 1 , 'many-to-many relation must have exactly one id field' ) ;
164+ query = query . where ( ( eb ) =>
165+ eb (
166+ eb . ref ( `${ relationModelAlias } .${ relationIds [ 0 ] } ` ) ,
167+ 'in' ,
168+ eb
169+ . selectFrom ( m2m . joinTable )
170+ . select ( `${ m2m . joinTable } .${ m2m . otherFkName } ` )
171+ . whereRef ( `${ parentAlias } .${ parentIds [ 0 ] } ` , '=' , `${ m2m . joinTable } .${ m2m . parentFkName } ` ) ,
172+ ) ,
173+ ) ;
174+ } else {
175+ const joinPairs = buildJoinPairs ( this . schema , model , parentAlias , relationField , relationModelAlias ) ;
176+ query = query . where ( ( eb ) =>
177+ this . and ( eb , ...joinPairs . map ( ( [ left , right ] ) => eb ( sql . ref ( left ) , '=' , sql . ref ( right ) ) ) ) ,
178+ ) ;
179+ }
180+ return query ;
181+ }
182+
157183 private buildRelationObjectSelect (
158184 relationModel : string ,
159185 relationModelAlias : string ,
160- relationField : string ,
161186 relationFieldDef : FieldDef ,
162187 qb : SelectQueryBuilder < any , any , any > ,
163188 payload : true | FindArgs < Schema , GetModels < Schema > , true > ,
164- parentName : string ,
189+ parentResultName : string ,
165190 ) {
166191 qb = qb . select ( ( eb ) => {
167192 const objArgs = this . buildRelationObjectArgs (
168193 relationModel ,
169194 relationModelAlias ,
170- relationField ,
171195 eb ,
172196 payload ,
173- parentName ,
197+ parentResultName ,
174198 ) ;
175199
176200 if ( relationFieldDef . array ) {
177201 return eb . fn
178202 . coalesce ( sql `jsonb_agg(jsonb_build_object(${ sql . join ( objArgs ) } ))` , sql `'[]'::jsonb` )
179- . as ( '$t ' ) ;
203+ . as ( '$data ' ) ;
180204 } else {
181- return sql `jsonb_build_object(${ sql . join ( objArgs ) } )` . as ( '$t ' ) ;
205+ return sql `jsonb_build_object(${ sql . join ( objArgs ) } )` . as ( '$data ' ) ;
182206 }
183207 } ) ;
184208
@@ -188,17 +212,15 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
188212 private buildRelationObjectArgs (
189213 relationModel : string ,
190214 relationModelAlias : string ,
191- relationField : string ,
192215 eb : ExpressionBuilder < any , any > ,
193216 payload : true | FindArgs < Schema , GetModels < Schema > , true > ,
194- parentAlias : string ,
217+ parentResultName : string ,
195218 ) {
196219 const relationModelDef = requireModel ( this . schema , relationModel ) ;
197220 const objArgs : Array <
198221 string | ExpressionWrapper < any , any , any > | SelectQueryBuilder < any , any , any > | RawBuilder < any >
199222 > = [ ] ;
200223
201- // TODO: descendant JSON shouldn't be joined and selected if none of its fields are selected
202224 const descendantModels = getDelegateDescendantModels ( this . schema , relationModel ) ;
203225 if ( descendantModels . length > 0 ) {
204226 // select all JSONs built from delegate descendants
@@ -234,15 +256,15 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
234256 const subJson = this . buildCountJson (
235257 relationModel as GetModels < Schema > ,
236258 eb ,
237- ` ${ parentAlias } $ ${ relationField } ` ,
259+ relationModelAlias ,
238260 value ,
239261 ) ;
240262 return [ sql . lit ( field ) , subJson ] ;
241263 } else {
242264 const fieldDef = requireField ( this . schema , relationModel , field ) ;
243265 const fieldValue = fieldDef . relation
244266 ? // reference the synthesized JSON field
245- eb . ref ( `${ parentAlias } $${ relationField } $ ${ field } .$t ` )
267+ eb . ref ( `${ parentResultName } $${ field } .$data ` )
246268 : // reference a plain field
247269 this . fieldRef ( relationModel , field , eb , undefined , false ) ;
248270 return [ sql . lit ( field ) , fieldValue ] ;
@@ -260,7 +282,7 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
260282 . map ( ( [ field ] ) => [
261283 sql . lit ( field ) ,
262284 // reference the synthesized JSON field
263- eb . ref ( `${ parentAlias } $${ relationField } $ ${ field } .$t ` ) ,
285+ eb . ref ( `${ parentResultName } $${ field } .$data ` ) ,
264286 ] )
265287 . flatMap ( ( v ) => v ) ,
266288 ) ;
@@ -269,13 +291,13 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
269291 }
270292
271293 private buildRelationJoins (
294+ query : SelectQueryBuilder < any , any , any > ,
272295 relationModel : string ,
273- relationField : string ,
274- qb : SelectQueryBuilder < any , any , any > ,
296+ relationModelAlias : string ,
275297 payload : true | FindArgs < Schema , GetModels < Schema > , true > ,
276- parentName : string ,
298+ parentResultName : string ,
277299 ) {
278- let result = qb ;
300+ let result = query ;
279301 if ( typeof payload === 'object' ) {
280302 const selectInclude = payload . include ?? payload . select ;
281303 if ( selectInclude && typeof selectInclude === 'object' ) {
@@ -287,8 +309,9 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
287309 relationModel ,
288310 result ,
289311 field ,
290- ` ${ parentName } $ ${ relationField } ` ,
312+ relationModelAlias ,
291313 value ,
314+ `${ parentResultName } $${ field } ` ,
292315 ) ;
293316 } ) ;
294317 }
0 commit comments