Skip to content

Commit bc99639

Browse files
committed
Fix: Long column identifiers in deeply nested joins cause fields to be omitted #7513
1 parent 751a6dd commit bc99639

File tree

5 files changed

+155
-2
lines changed

5 files changed

+155
-2
lines changed

callbacks/query.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func BuildQuerySQL(db *gorm.DB) {
4040
}
4141
}
4242

43+
if db.Statement.TruncatedAliases == nil {
44+
db.Statement.TruncatedAliases = make(map[string]string)
45+
}
4346
if db.Statement.SQL.Len() == 0 {
4447
db.Statement.SQL.Grow(100)
4548
clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
@@ -158,11 +161,17 @@ func BuildQuerySQL(db *gorm.DB) {
158161
selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
159162
for _, s := range relation.FieldSchema.DBNames {
160163
if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
164+
aliasName := db.NamingStrategy.JoinNestedRelationNames([]string{tableAliasName, s})
161165
clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
162166
Table: tableAliasName,
163167
Name: s,
164-
Alias: utils.NestedRelationName(tableAliasName, s),
168+
Alias: aliasName,
165169
})
170+
origTableAliasName := tableAliasName
171+
if alias, ok := db.Statement.TruncatedAliases[tableAliasName]; ok {
172+
origTableAliasName = alias
173+
}
174+
db.Statement.TruncatedAliases[aliasName] = utils.NestedRelationName(origTableAliasName, s)
166175
}
167176
}
168177

@@ -235,9 +244,19 @@ func BuildQuerySQL(db *gorm.DB) {
235244
for idx, rel := range relations {
236245
// joins table alias like "Manager, Company, Manager__Company"
237246
curAliasName := rel.Name
247+
248+
var nameParts []string
249+
var fullName string
238250
if parentTableName != clause.CurrentTable {
239-
curAliasName = utils.NestedRelationName(parentTableName, curAliasName)
251+
nameParts = []string{parentTableName, curAliasName}
252+
fullName = utils.NestedRelationName(parentTableName, curAliasName)
253+
} else {
254+
nameParts = []string{curAliasName}
255+
fullName = curAliasName
240256
}
257+
aliasName := db.NamingStrategy.JoinNestedRelationNames(nameParts)
258+
db.Statement.TruncatedAliases[aliasName] = fullName
259+
curAliasName = aliasName
241260

242261
if _, ok := specifiedRelationsName[curAliasName]; !ok {
243262
aliasName := curAliasName

scan.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ func Scan(rows Rows, db *DB, mode ScanMode) {
227227
if sch != nil {
228228
matchedFieldCount := make(map[string]int, len(columns))
229229
for idx, column := range columns {
230+
if origName, ok := db.Statement.TruncatedAliases[column]; ok {
231+
column = origName
232+
}
230233
if field := sch.LookUpField(column); field != nil && field.Readable {
231234
fields[idx] = field
232235
if count, ok := matchedFieldCount[column]; ok {

schema/naming.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package schema
22

33
import (
44
"crypto/sha1"
5+
"crypto/sha256"
56
"encoding/hex"
67
"regexp"
78
"strings"
89
"unicode/utf8"
910

11+
"gorm.io/gorm/utils"
12+
1013
"github.com/jinzhu/inflection"
1114
"golang.org/x/text/cases"
1215
"golang.org/x/text/language"
@@ -22,6 +25,7 @@ type Namer interface {
2225
CheckerName(table, column string) string
2326
IndexName(table, column string) string
2427
UniqueName(table, column string) string
28+
JoinNestedRelationNames(relationNames []string) string
2529
}
2630

2731
// Replacer replacer interface like strings.Replacer
@@ -95,6 +99,58 @@ func (ns NamingStrategy) UniqueName(table, column string) string {
9599
return ns.formatName("uni", table, ns.toDBName(column))
96100
}
97101

102+
// JoinNestedRelationNames nested relationships like `Manager__Company` with enforcing IdentifierMaxLength
103+
func (ns NamingStrategy) JoinNestedRelationNames(relationNames []string) string {
104+
tableAlias := utils.JoinNestedRelationNames(relationNames)
105+
return ns.truncateName(tableAlias)
106+
}
107+
108+
// TruncatedName generate truncated name
109+
func (ns NamingStrategy) truncateName(ident string) string {
110+
formattedName := ident
111+
if ns.IdentifierMaxLength == 0 {
112+
ns.IdentifierMaxLength = 64
113+
}
114+
115+
if len(formattedName) > ns.IdentifierMaxLength {
116+
h := sha256.New224()
117+
h.Write([]byte(formattedName))
118+
bs := h.Sum(nil)
119+
formattedName = truncate(formattedName, ns.IdentifierMaxLength-8) + hex.EncodeToString(bs)[:8]
120+
}
121+
return formattedName
122+
}
123+
124+
func truncate(s string, size int) string {
125+
if len(s) <= size {
126+
return s
127+
}
128+
s = s[0:size]
129+
num := brokenTailSize(s)
130+
s = s[0 : len(s)-num]
131+
return s
132+
}
133+
134+
func brokenTailSize(s string) int {
135+
if len(s) == 0 {
136+
return 0
137+
}
138+
res := 1
139+
140+
for i := len(s) - 1; i >= 0; i-- {
141+
char := s[i] & 0b11000000
142+
if char != 0b10000000 {
143+
break
144+
}
145+
res++
146+
}
147+
148+
if utf8.Valid([]byte(s[len(s)-res:])) {
149+
res = 0
150+
}
151+
return res
152+
}
153+
98154
func (ns NamingStrategy) formatName(prefix, table, name string) string {
99155
formattedName := strings.ReplaceAll(strings.Join([]string{
100156
prefix, table, name,

statement.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type Statement struct {
4747
attrs []interface{}
4848
assigns []interface{}
4949
scopes []func(*DB) *DB
50+
TruncatedAliases map[string]string
5051
Result *result
5152
}
5253

tests/joins_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,77 @@ func TestJoinsPreload_Issue7013_NoEntries(t *testing.T) {
476476

477477
AssertEqual(t, len(entries), 0)
478478
}
479+
480+
func TestJoinsLongName_Issue7513(t *testing.T) {
481+
type (
482+
Owner struct {
483+
gorm.Model
484+
Name string
485+
}
486+
487+
Land struct {
488+
gorm.Model
489+
Address string
490+
OwneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerID uint `gorm:"column:owner_id"`
491+
Owneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer *Owner
492+
}
493+
494+
Design struct {
495+
gorm.Model
496+
Name𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x string
497+
}
498+
499+
Building struct {
500+
gorm.Model
501+
Name string
502+
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃xID uint `gorm:"column:land_id"`
503+
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x *Land
504+
505+
DesignID uint `gorm:"column:design_id"`
506+
Design *Design
507+
}
508+
)
509+
510+
DB.Migrator().DropTable(&Building{}, &Owner{}, &Land{})
511+
DB.Migrator().AutoMigrate(&Building{}, &Owner{}, &Land{})
512+
513+
home := &Building{
514+
Model: gorm.Model{
515+
ID: 1,
516+
},
517+
Name: "Awesome Building",
518+
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃xID: 2,
519+
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x: &Land{
520+
Model: gorm.Model{
521+
ID: 2,
522+
},
523+
Address: "Awesome Street",
524+
OwneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerID: 3,
525+
Owneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer: &Owner{
526+
Model: gorm.Model{
527+
ID: 3,
528+
},
529+
Name: "Awesome Person",
530+
},
531+
},
532+
DesignID: 4,
533+
Design: &Design{
534+
Model: gorm.Model{
535+
ID: 4,
536+
},
537+
Name𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x: "Awesome Design",
538+
},
539+
}
540+
DB.Create(home)
541+
542+
var entries []Building
543+
assert.NotPanics(t, func() {
544+
assert.NoError(t,
545+
DB.Joins("Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x").
546+
Joins("Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x.Owneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer").
547+
Joins("Design").
548+
Find(&entries).Error)
549+
})
550+
551+
AssertEqual(t, entries, []Building{*home})
552+
}

0 commit comments

Comments
 (0)