Merge 654aecbe295cbdad729fdfa731a2d752f5ce9248 into 4e34a6d21b63e9a9b701a70be9759e5539bf26e9
This commit is contained in:
commit
11663fccf9
@ -40,6 +40,12 @@ func BuildQuerySQL(db *gorm.DB) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
truncatedTableAliases := make(map[string]string)
|
||||||
|
|
||||||
|
if db.Statement.ColumnMapping == nil {
|
||||||
|
db.Statement.ColumnMapping = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
if db.Statement.SQL.Len() == 0 {
|
if db.Statement.SQL.Len() == 0 {
|
||||||
db.Statement.SQL.Grow(100)
|
db.Statement.SQL.Grow(100)
|
||||||
clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
|
clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
|
||||||
@ -158,11 +164,17 @@ func BuildQuerySQL(db *gorm.DB) {
|
|||||||
selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
|
selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
|
||||||
for _, s := range relation.FieldSchema.DBNames {
|
for _, s := range relation.FieldSchema.DBNames {
|
||||||
if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
|
if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
|
||||||
|
aliasName := db.NamingStrategy.JoinNestedRelationNames([]string{tableAliasName, s})
|
||||||
clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
|
clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
|
||||||
Table: tableAliasName,
|
Table: tableAliasName,
|
||||||
Name: s,
|
Name: s,
|
||||||
Alias: utils.NestedRelationName(tableAliasName, s),
|
Alias: aliasName,
|
||||||
})
|
})
|
||||||
|
origTableAliasName := tableAliasName
|
||||||
|
if alias, ok := truncatedTableAliases[tableAliasName]; ok {
|
||||||
|
origTableAliasName = alias
|
||||||
|
}
|
||||||
|
db.Statement.ColumnMapping[aliasName] = utils.NestedRelationName(origTableAliasName, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,12 +244,23 @@ func BuildQuerySQL(db *gorm.DB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parentTableName := clause.CurrentTable
|
parentTableName := clause.CurrentTable
|
||||||
|
parentFullTableName := clause.CurrentTable
|
||||||
for idx, rel := range relations {
|
for idx, rel := range relations {
|
||||||
// joins table alias like "Manager, Company, Manager__Company"
|
// joins table alias like "Manager, Company, Manager__Company"
|
||||||
curAliasName := rel.Name
|
curAliasName := rel.Name
|
||||||
|
|
||||||
|
var nameParts []string
|
||||||
|
var fullName string
|
||||||
if parentTableName != clause.CurrentTable {
|
if parentTableName != clause.CurrentTable {
|
||||||
curAliasName = utils.NestedRelationName(parentTableName, curAliasName)
|
nameParts = []string{parentFullTableName, curAliasName}
|
||||||
|
fullName = utils.NestedRelationName(parentFullTableName, curAliasName)
|
||||||
|
} else {
|
||||||
|
nameParts = []string{curAliasName}
|
||||||
|
fullName = curAliasName
|
||||||
}
|
}
|
||||||
|
aliasName := db.NamingStrategy.JoinNestedRelationNames(nameParts)
|
||||||
|
truncatedTableAliases[aliasName] = fullName
|
||||||
|
curAliasName = aliasName
|
||||||
|
|
||||||
if _, ok := specifiedRelationsName[curAliasName]; !ok {
|
if _, ok := specifiedRelationsName[curAliasName]; !ok {
|
||||||
aliasName := curAliasName
|
aliasName := curAliasName
|
||||||
@ -250,6 +273,7 @@ func BuildQuerySQL(db *gorm.DB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parentTableName = curAliasName
|
parentTableName = curAliasName
|
||||||
|
parentFullTableName = fullName
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fromClause.Joins = append(fromClause.Joins, clause.Join{
|
fromClause.Joins = append(fromClause.Joins, clause.Join{
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"gorm.io/gorm/utils"
|
||||||
|
|
||||||
"github.com/jinzhu/inflection"
|
"github.com/jinzhu/inflection"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@ -22,6 +24,7 @@ type Namer interface {
|
|||||||
CheckerName(table, column string) string
|
CheckerName(table, column string) string
|
||||||
IndexName(table, column string) string
|
IndexName(table, column string) string
|
||||||
UniqueName(table, column string) string
|
UniqueName(table, column string) string
|
||||||
|
JoinNestedRelationNames(relationNames []string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replacer replacer interface like strings.Replacer
|
// Replacer replacer interface like strings.Replacer
|
||||||
@ -95,23 +98,64 @@ func (ns NamingStrategy) UniqueName(table, column string) string {
|
|||||||
return ns.formatName("uni", table, ns.toDBName(column))
|
return ns.formatName("uni", table, ns.toDBName(column))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JoinNestedRelationNames nested relationships like `Manager__Company` with enforcing IdentifierMaxLength
|
||||||
|
func (ns NamingStrategy) JoinNestedRelationNames(relationNames []string) string {
|
||||||
|
tableAlias := utils.JoinNestedRelationNames(relationNames)
|
||||||
|
return ns.truncateName(tableAlias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TruncatedName generate truncated name
|
||||||
|
func (ns NamingStrategy) truncateName(ident string) string {
|
||||||
|
formattedName := ident
|
||||||
|
if ns.IdentifierMaxLength == 0 {
|
||||||
|
ns.IdentifierMaxLength = 64
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(formattedName) > ns.IdentifierMaxLength {
|
||||||
|
h := sha256.New224()
|
||||||
|
h.Write([]byte(formattedName))
|
||||||
|
bs := h.Sum(nil)
|
||||||
|
formattedName = truncate(formattedName, ns.IdentifierMaxLength-8) + hex.EncodeToString(bs)[:8]
|
||||||
|
}
|
||||||
|
return formattedName
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(s string, size int) string {
|
||||||
|
if len(s) <= size {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s = s[0:size]
|
||||||
|
num := brokenTailSize(s)
|
||||||
|
s = s[0 : len(s)-num]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func brokenTailSize(s string) int {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
res := 1
|
||||||
|
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
char := s[i] & 0b11000000
|
||||||
|
if char != 0b10000000 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
res++
|
||||||
|
}
|
||||||
|
|
||||||
|
if utf8.Valid([]byte(s[len(s)-res:])) {
|
||||||
|
res = 0
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (ns NamingStrategy) formatName(prefix, table, name string) string {
|
func (ns NamingStrategy) formatName(prefix, table, name string) string {
|
||||||
formattedName := strings.ReplaceAll(strings.Join([]string{
|
formattedName := strings.ReplaceAll(strings.Join([]string{
|
||||||
prefix, table, name,
|
prefix, table, name,
|
||||||
}, "_"), ".", "_")
|
}, "_"), ".", "_")
|
||||||
|
|
||||||
if ns.IdentifierMaxLength == 0 {
|
return ns.truncateName(formattedName)
|
||||||
ns.IdentifierMaxLength = 64
|
|
||||||
}
|
|
||||||
|
|
||||||
if utf8.RuneCountInString(formattedName) > ns.IdentifierMaxLength {
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write([]byte(formattedName))
|
|
||||||
bs := h.Sum(nil)
|
|
||||||
|
|
||||||
formattedName = formattedName[0:ns.IdentifierMaxLength-8] + hex.EncodeToString(bs)[:8]
|
|
||||||
}
|
|
||||||
return formattedName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -193,7 +193,7 @@ func TestFormatNameWithStringLongerThan63Characters(t *testing.T) {
|
|||||||
ns := NamingStrategy{IdentifierMaxLength: 63}
|
ns := NamingStrategy{IdentifierMaxLength: 63}
|
||||||
|
|
||||||
formattedName := ns.formatName("prefix", "table", "thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString")
|
formattedName := ns.formatName("prefix", "table", "thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString")
|
||||||
if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVer180f2c67" {
|
if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVerb463f8ff" {
|
||||||
t.Errorf("invalid formatted name generated, got %v", formattedName)
|
t.Errorf("invalid formatted name generated, got %v", formattedName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ func TestFormatNameWithStringLongerThan64Characters(t *testing.T) {
|
|||||||
ns := NamingStrategy{IdentifierMaxLength: 64}
|
ns := NamingStrategy{IdentifierMaxLength: 64}
|
||||||
|
|
||||||
formattedName := ns.formatName("prefix", "table", "thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString")
|
formattedName := ns.formatName("prefix", "table", "thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString")
|
||||||
if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVery180f2c67" {
|
if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryb463f8ff" {
|
||||||
t.Errorf("invalid formatted name generated, got %v", formattedName)
|
t.Errorf("invalid formatted name generated, got %v", formattedName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -987,7 +987,7 @@ func TestParseConstraintNameWithSchemaQualifiedLongTableName(t *testing.T) {
|
|||||||
t.Fatalf("Failed to parse schema")
|
t.Fatalf("Failed to parse schema")
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_l4db13eec"
|
expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_l46bfd72a"
|
||||||
constraint := s.Relationships.Relations["Author"].ParseConstraint()
|
constraint := s.Relationships.Relations["Author"].ParseConstraint()
|
||||||
|
|
||||||
if constraint.Name != expectedConstraintName {
|
if constraint.Name != expectedConstraintName {
|
||||||
|
@ -2,6 +2,7 @@ package tests_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@ -476,3 +477,81 @@ func TestJoinsPreload_Issue7013_NoEntries(t *testing.T) {
|
|||||||
|
|
||||||
AssertEqual(t, len(entries), 0)
|
AssertEqual(t, len(entries), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJoinsLongName_Issue7513(t *testing.T) {
|
||||||
|
if os.Getenv("GORM_DIALECT") != "postgres" {
|
||||||
|
// Another DB may not support UTF-8 characters in identifiers
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type (
|
||||||
|
Owner struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
Land struct {
|
||||||
|
gorm.Model
|
||||||
|
Address string
|
||||||
|
OwneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerID uint `gorm:"column:owner_id"`
|
||||||
|
Owneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer *Owner
|
||||||
|
}
|
||||||
|
|
||||||
|
Design struct {
|
||||||
|
gorm.Model
|
||||||
|
Name𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x string
|
||||||
|
}
|
||||||
|
|
||||||
|
Building struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃xID uint `gorm:"column:land_id"`
|
||||||
|
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x *Land
|
||||||
|
|
||||||
|
DesignID uint `gorm:"column:design_id"`
|
||||||
|
Design *Design
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
DB.Migrator().DropTable(&Building{}, &Owner{}, &Land{}, Design{})
|
||||||
|
DB.Migrator().AutoMigrate(&Building{}, &Owner{}, &Land{}, Design{})
|
||||||
|
|
||||||
|
home := &Building{
|
||||||
|
Model: gorm.Model{
|
||||||
|
ID: 1,
|
||||||
|
},
|
||||||
|
Name: "Awesome Building",
|
||||||
|
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃xID: 2,
|
||||||
|
Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x: &Land{
|
||||||
|
Model: gorm.Model{
|
||||||
|
ID: 2,
|
||||||
|
},
|
||||||
|
Address: "Awesome Street",
|
||||||
|
OwneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerID: 3,
|
||||||
|
Owneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer: &Owner{
|
||||||
|
Model: gorm.Model{
|
||||||
|
ID: 3,
|
||||||
|
},
|
||||||
|
Name: "Awesome Person",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DesignID: 4,
|
||||||
|
Design: &Design{
|
||||||
|
Model: gorm.Model{
|
||||||
|
ID: 4,
|
||||||
|
},
|
||||||
|
Name𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x: "Awesome Design",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
DB.Create(home)
|
||||||
|
|
||||||
|
var entries []Building
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
assert.NoError(t,
|
||||||
|
DB.Joins("Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x").
|
||||||
|
Joins("Laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaand𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃𒀃x.Owneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer").
|
||||||
|
Joins("Design").
|
||||||
|
Find(&entries).Error)
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertEqual(t, entries, []Building{*home})
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user