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
	 IgorStepanov
						IgorStepanov