@@ -20,13 +20,14 @@ type QueryBuilder interface {
2020// Join containts information to create a join relationship.
2121// It is composed while building the select clause.
2222type Join struct {
23- name string // join name, crafted to be unique
24- fields string // fields to be selected in the related table (eg. a, b::text, c AS C)
25- nested string // nested joins
26- inner bool // is a inner join
27- rel * Relationship // base relationship
28- relLabel string // label for the relationship, taken from the select clause
29- relName string // relation/function name as it appears in the query
23+ name string // join name, crafted to be unique
24+ fields string // fields to be selected in the related table (eg. a, b::text, c AS C)
25+ nested string // nested joins
26+ inner bool // is a inner join
27+ rel * Relationship // base relationship
28+ relLabel string // label for the relationship, taken from the select clause
29+ relName string // relation/function name as it appears in the query
30+ selectFields []SelectField // inner select fields (for related order validation)
3031}
3132
3233type BuildError struct {
@@ -120,7 +121,7 @@ func labelWithNumber(table string, num int) string {
120121 return table + "_" + strconv .Itoa (num )
121122}
122123
123- func selectForJoinClause (join Join , label string , parts * QueryParts , stack BuildStack ) (sel string ) {
124+ func selectForJoinClause (join Join , label string , parts * QueryParts , stack BuildStack ) (sel string , err error ) {
124125 rel := join .rel
125126 sel = " SELECT " + join .fields
126127 _ , t1 := splitTableName (rel .RelatedTable )
@@ -166,7 +167,10 @@ func selectForJoinClause(join Join, label string, parts *QueryParts, stack Build
166167 if wc != "" {
167168 sel += " WHERE " + wc
168169 }
169- oc := orderClause (table , schema , label1 , parts .orderFields , stack .info )
170+ oc , err := orderClause (table , schema , label1 , stack .level + 1 , parts .orderFields , join .selectFields , stack .info )
171+ if err != nil {
172+ return "" , err
173+ }
170174 if oc != "" {
171175 sel += " ORDER BY " + oc
172176 }
@@ -231,13 +235,16 @@ func selectForJoinClause(join Join, label string, parts *QueryParts, stack Build
231235 if whereClause != "" {
232236 sel += " AND " + whereClause
233237 }
234- orderClause := orderClause (table , schema , label1 , parts .orderFields , stack .info )
235- if orderClause != "" {
236- sel += " ORDER BY " + orderClause
238+ oc , err := orderClause (table , schema , label1 , stack .level + 1 , parts .orderFields , join .selectFields , stack .info )
239+ if err != nil {
240+ return "" , err
241+ }
242+ if oc != "" {
243+ sel += " ORDER BY " + oc
237244 }
238245 }
239246 }
240- return sel
247+ return sel , nil
241248}
242249
243250func findRelationship (table , relation , fk , schema string , info * SchemaInfo ) (rel * Relationship , err error ) {
@@ -360,7 +367,7 @@ func selectClause(table, schema, label string, parts *QueryParts, stack BuildSta
360367 if err != nil {
361368 return "" , "" , nil , err
362369 }
363- joinSeq = append (joinSeq , Join {joinName , sc , j , sfield .relation .inner , frel , sfield .label , sfield .relation .name })
370+ joinSeq = append (joinSeq , Join {joinName , sc , j , sfield .relation .inner , frel , sfield .label , sfield .relation .name , sfield . relation . fields })
364371 } else {
365372 if i != 0 {
366373 selClause += ", "
@@ -388,7 +395,10 @@ func selectClause(table, schema, label string, parts *QueryParts, stack BuildSta
388395 if len (joinSeq ) > 0 {
389396 for _ , join := range joinSeq {
390397 relName := join .name
391- selectForJoin := selectForJoinClause (join , label , parts , stack )
398+ selectForJoin , err := selectForJoinClause (join , label , parts , stack )
399+ if err != nil {
400+ return "" , "" , nil , err
401+ }
392402 if join .inner {
393403 joins += " INNER"
394404 } else {
@@ -466,25 +476,41 @@ func groupByClause(table, schema string, parts *QueryParts, info *SchemaInfo) st
466476 return ""
467477}
468478
469- func orderClause (table , schema , label string , orderFields []OrderField , info * SchemaInfo ) string {
479+ func orderClause (table , schema , label string , level int , orderFields []OrderField ,
480+ selectFields []SelectField , info * SchemaInfo ) (string , error ) {
470481 var order string
471482 for _ , o := range orderFields {
472483 if o .field .tablename != table {
473- // skip where filters for other tables
484+ // skip order fields for other tables
474485 continue
475486 }
476487 if order != "" {
477488 order += ", "
478489 }
479490 var fieldname string
480- if label == "" {
491+ if o .relation != "" {
492+ // Related order: order by a column in a to-one related table
493+ matchedLabel , err := validateRelatedOrder (table , schema , o , selectFields , info )
494+ if err != nil {
495+ return "" , err
496+ }
497+ joinAlias := quote (labelWithNumber (table + "_" + matchedLabel , level + 1 ))
498+ fieldname = joinAlias + "." + quote (o .field .name )
499+ if o .field .jsonPath != "" {
500+ fieldname = "(" + fieldname + o .field .jsonPath + ")"
501+ }
502+ } else if label == "" {
481503 fieldname = _stq (o .field .name , schema , table )
504+ if o .field .jsonPath != "" {
505+ fieldname = "(" + toJson (table , schema , o .field .name , fieldname , info ) +
506+ o .field .jsonPath + ")"
507+ }
482508 } else {
483509 fieldname = label + "." + quote (o .field .name )
484- }
485- if o .field .jsonPath != "" {
486- fieldname = "(" + toJson ( table , schema , o .field .name , fieldname , info ) +
487- o . field . jsonPath + ")"
510+ if o . field . jsonPath != "" {
511+ fieldname = "(" + toJson ( table , schema , o .field .name , fieldname , info ) +
512+ o .field .jsonPath + ")"
513+ }
488514 }
489515 order += fieldname
490516 if o .descending {
@@ -498,7 +524,44 @@ func orderClause(table, schema, label string, orderFields []OrderField, info *Sc
498524 }
499525 }
500526 }
501- return order
527+ return order , nil
528+ }
529+
530+ // validateRelatedOrder checks that a related order references an embedded to-one relationship.
531+ // Returns the matched label (used for join alias construction) or an error.
532+ func validateRelatedOrder (table , schema string , o OrderField , selectFields []SelectField , info * SchemaInfo ) (string , error ) {
533+ for _ , sf := range selectFields {
534+ if sf .relation == nil {
535+ continue
536+ }
537+ sfLabel := sf .label
538+ if sfLabel == "" {
539+ sfLabel = sf .relation .name
540+ }
541+ if sfLabel == o .relation || sf .relation .name == o .relation {
542+ // Found matching embedded resource - check relationship type
543+ parentTable := sf .relation .parent
544+ if parentTable == "" {
545+ parentTable = table
546+ }
547+ rel , err := findRelationship (parentTable , sf .relation .name , sf .relation .fk , schema , info )
548+ if err != nil {
549+ return "" , err
550+ }
551+ switch rel .Type {
552+ case M2O , O2O :
553+ // OK
554+ case O2M , M2M :
555+ return "" , & BuildError {"A related order on '" + o .relation + "' is not possible" }
556+ case Computed :
557+ if rel .ReturnIsSet {
558+ return "" , & BuildError {"A related order on '" + o .relation + "' is not possible" }
559+ }
560+ }
561+ return sfLabel , nil
562+ }
563+ }
564+ return "" , & BuildError {"'" + o .relation + "' is not an embedded resource in this request" }
502565}
503566
504567func appendValue (where , value string , valueList []any , nmarker int ) (string , []any , int ) {
@@ -968,7 +1031,10 @@ func (CommonBuilder) BuildExecute(name string, record Record, parts *QueryParts,
9681031 for i := range parts .orderFields {
9691032 parts .orderFields [i ].field .tablename = "t"
9701033 }
971- orderClause := orderClause ("t" , "" , "" , parts .orderFields , info )
1034+ orderClause , err := orderClause ("t" , "" , "" , 0 , parts .orderFields , parts .selectFields , info )
1035+ if err != nil {
1036+ return "" , nil , err
1037+ }
9721038 query = "SELECT " + selectClause
9731039 from := "FROM " + _sq (name , schema ) + "(" + pairs + ") t "
9741040
@@ -988,7 +1054,10 @@ func (DirectQueryBuilder) BuildSelect(table string, parts *QueryParts, options *
9881054 }
9891055 whereClause , valueList := whereClause (table , schema , "" , parts .whereConditionsTree , 0 , stack )
9901056 groupByClause := groupByClause (table , schema , parts , info )
991- orderClause := orderClause (table , schema , "" , parts .orderFields , info )
1057+ orderClause , err := orderClause (table , schema , "" , 0 , parts .orderFields , parts .selectFields , info )
1058+ if err != nil {
1059+ return "" , nil , err
1060+ }
9921061
9931062 query := "SELECT " + selectClause
9941063 from := "FROM " + _sq (table , schema )
@@ -1013,7 +1082,10 @@ func (QueryWithJSON) BuildSelect(table string, parts *QueryParts, options *Query
10131082 }
10141083 whereClause , valueList := whereClause (table , schema , "" , parts .whereConditionsTree , 0 , stack )
10151084 groupByClause := groupByClause (table , schema , parts , info )
1016- orderClause := orderClause (table , schema , "" , parts .orderFields , info )
1085+ orderClause , err := orderClause (table , schema , "" , 0 , parts .orderFields , parts .selectFields , info )
1086+ if err != nil {
1087+ return "" , nil , err
1088+ }
10171089
10181090 query := "SELECT "
10191091 if selectClause == "*" {
0 commit comments