mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
refactor(database/gdb): simplify order and group by alias quoting (bu… (#4555)
## What this PR does Revert the auto table prefix behavior in `Order()` and `Group()` introduced by #4521. ## Why PR #4521 attempted to resolve column ambiguity in GROUP BY/ORDER BY with MySQL JOIN by automatically adding table prefixes to unqualified columns. However, this approach has issues: 1. When using `.As()` to set table alias, it uses the original table name instead of the alias, causing errors in PostgreSQL and other databases 2. The framework cannot reliably determine which table the user intends when multiple tables have the same column 3. Adds hidden behavior that users may not expect ## Example of the issue (#4554) ```go db.Model("demo_a").As("a"). LeftJoin("demo_b", "b", "a.id=b.data_id"). Order("sort").All() Expected (v2.9.5): ORDER BY "sort" Actual (v2.9.6): ORDER BY "demo_a".sort -- Wrong! Should use alias "a" or no prefix Solution Revert to v2.9.5 behavior: Order("sort") generates ORDER BY "sort" without auto-prefixing. Users should explicitly specify table prefix when needed: Order("a.sort"). Closes #4554 ``` --------- Co-authored-by: hailaz <739476267@qq.com>
This commit is contained in:
@ -7,7 +7,7 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@ -30,7 +30,6 @@ func (m *Model) Order(orderBy ...any) *Model {
|
||||
core = m.db.GetCore()
|
||||
model = m.getModel()
|
||||
)
|
||||
|
||||
for _, v := range orderBy {
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
@ -41,47 +40,13 @@ func (m *Model) Order(orderBy ...any) *Model {
|
||||
default:
|
||||
orderByStr := gconv.String(v)
|
||||
if gstr.Contains(orderByStr, " ") {
|
||||
// Handle "column asc/desc" format
|
||||
parts := gstr.SplitAndTrim(orderByStr, " ")
|
||||
if len(parts) >= 2 {
|
||||
columnPart := parts[0]
|
||||
orderPart := gstr.Join(parts[1:], " ")
|
||||
|
||||
// Check if column part is qualified
|
||||
if gstr.Contains(columnPart, ".") {
|
||||
model.orderBy += core.QuoteString(columnPart) + " " + orderPart
|
||||
} else {
|
||||
// Try to get the correct prefix for this field
|
||||
prefix := m.getPrefixByField(columnPart)
|
||||
if prefix != "" {
|
||||
model.orderBy += core.QuoteString(fmt.Sprintf("%s.%s", prefix, columnPart)) + " " + orderPart
|
||||
} else {
|
||||
// If we can't determine the table, just quote the field
|
||||
model.orderBy += core.QuoteWord(columnPart) + " " + orderPart
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback for complex expressions
|
||||
model.orderBy += core.QuoteString(orderByStr)
|
||||
}
|
||||
model.orderBy += core.QuoteString(orderByStr)
|
||||
} else {
|
||||
if gstr.Equal(orderByStr, "ASC") || gstr.Equal(orderByStr, "DESC") {
|
||||
model.orderBy = gstr.TrimRight(model.orderBy, ",")
|
||||
model.orderBy += " " + orderByStr
|
||||
} else {
|
||||
// Check if column is already qualified
|
||||
if gstr.Contains(orderByStr, ".") {
|
||||
model.orderBy += core.QuoteString(orderByStr)
|
||||
} else {
|
||||
// Try to get the correct prefix for this field
|
||||
prefix := m.getPrefixByField(orderByStr)
|
||||
if prefix != "" {
|
||||
model.orderBy += core.QuoteString(fmt.Sprintf("%s.%s", prefix, orderByStr))
|
||||
} else {
|
||||
// If we can't determine the table, just quote the field
|
||||
model.orderBy += core.QuoteWord(orderByStr)
|
||||
}
|
||||
}
|
||||
model.orderBy += core.QuoteWord(orderByStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -113,7 +78,7 @@ func (m *Model) OrderRandom() *Model {
|
||||
}
|
||||
|
||||
// Group sets the "GROUP BY" statement for the model.
|
||||
func (m *Model) Group(groupBy ...any) *Model {
|
||||
func (m *Model) Group(groupBy ...string) *Model {
|
||||
if len(groupBy) == 0 {
|
||||
return m
|
||||
}
|
||||
@ -122,29 +87,9 @@ func (m *Model) Group(groupBy ...any) *Model {
|
||||
model = m.getModel()
|
||||
)
|
||||
|
||||
for _, v := range groupBy {
|
||||
if model.groupBy != "" {
|
||||
model.groupBy += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case Raw, *Raw:
|
||||
model.groupBy += gconv.String(v)
|
||||
default:
|
||||
groupByStr := gconv.String(v)
|
||||
if gstr.Contains(groupByStr, ".") {
|
||||
// Already qualified (e.g., "table.column")
|
||||
model.groupBy += core.QuoteString(groupByStr)
|
||||
} else {
|
||||
// Try to get the correct prefix for this field
|
||||
prefix := m.getPrefixByField(groupByStr)
|
||||
if prefix != "" {
|
||||
model.groupBy += core.QuoteString(fmt.Sprintf("%s.%s", prefix, groupByStr))
|
||||
} else {
|
||||
// If we can't determine the table, just quote the field
|
||||
model.groupBy += core.QuoteWord(groupByStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if model.groupBy != "" {
|
||||
model.groupBy += ","
|
||||
}
|
||||
model.groupBy += core.QuoteString(strings.Join(groupBy, ","))
|
||||
return model
|
||||
}
|
||||
|
||||
@ -749,110 +749,16 @@ func (m *Model) getHolderAndArgsAsSubModel(ctx context.Context) (holder string,
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) parseTableAlias(tableInitStr string) (alias string, tableName string) {
|
||||
// Split by space to separate table from alias
|
||||
// Format can be: `table` alias or `table` AS `alias` or just table alias
|
||||
parts := gstr.SplitAndTrim(tableInitStr, " ")
|
||||
charL, charR := m.db.GetCore().GetChars()
|
||||
|
||||
if len(parts) >= 2 {
|
||||
// Check if second part is "AS" keyword
|
||||
if gstr.Equal(parts[1], "AS") && len(parts) >= 3 {
|
||||
// Format: table AS alias
|
||||
alias = gstr.Trim(parts[2], charL+charR)
|
||||
tableName = gstr.Trim(parts[0], charL+charR)
|
||||
} else if !gstr.Equal(parts[1], "AS") {
|
||||
// Format: table alias (without AS keyword)
|
||||
alias = gstr.Trim(parts[1], charL+charR)
|
||||
tableName = gstr.Trim(parts[0], charL+charR)
|
||||
} else {
|
||||
// Only table name with "AS" keyword but no alias
|
||||
tableName = gstr.Trim(parts[0], charL+charR)
|
||||
}
|
||||
} else if len(parts) == 1 {
|
||||
// No alias, use table name directly
|
||||
tableName = gstr.Trim(parts[0], charL+charR)
|
||||
}
|
||||
|
||||
return alias, tableName
|
||||
}
|
||||
|
||||
func (m *Model) getAutoPrefix() string {
|
||||
autoPrefix := ""
|
||||
if gstr.Contains(m.tables, " JOIN ") {
|
||||
// Try to get alias from tablesInit
|
||||
alias, _ := m.parseTableAlias(m.tablesInit)
|
||||
if alias != "" {
|
||||
autoPrefix = m.QuoteWord(alias)
|
||||
}
|
||||
|
||||
// Fallback to table name if alias not found
|
||||
if autoPrefix == "" {
|
||||
autoPrefix = m.QuoteWord(
|
||||
m.db.GetCore().guessPrimaryTableName(m.tablesInit),
|
||||
)
|
||||
}
|
||||
autoPrefix = m.QuoteWord(
|
||||
m.db.GetCore().guessPrimaryTableName(m.tablesInit),
|
||||
)
|
||||
}
|
||||
return autoPrefix
|
||||
}
|
||||
|
||||
// getPrefixByField attempts to find the correct table prefix for a given field name
|
||||
// by checking which table in the JOIN contains this field. It returns:
|
||||
// - The quoted table prefix/alias if the field belongs to a specific joined table
|
||||
// - An empty string if the field cannot be determined or belongs to multiple tables
|
||||
//
|
||||
// This function searches through all tables involved in the query (main table and joined tables)
|
||||
// and checks their schema to find which table contains the specified field.
|
||||
func (m *Model) getPrefixByField(fieldName string) string {
|
||||
// If there's no JOIN, no need to add prefix
|
||||
if !gstr.Contains(m.tables, " JOIN ") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get all table names and aliases involved in the query
|
||||
var tables = make(map[string]string) // map[alias/tableName]realTableName
|
||||
|
||||
// Add main table
|
||||
alias, tableName := m.parseTableAlias(m.tablesInit)
|
||||
if alias != "" {
|
||||
tables[alias] = tableName
|
||||
} else if tableName != "" {
|
||||
tables[tableName] = tableName
|
||||
}
|
||||
|
||||
// Add joined tables from tableAliasMap
|
||||
for alias, tableName := range m.tableAliasMap {
|
||||
tables[alias] = tableName
|
||||
}
|
||||
|
||||
// Check which table contains this field
|
||||
var matchedPrefix string
|
||||
var matchCount int
|
||||
|
||||
for aliasOrTable, realTable := range tables {
|
||||
tableFields, err := m.TableFields(realTable)
|
||||
if err != nil {
|
||||
// If we can't get table fields, skip this table
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this table contains the field
|
||||
if _, exists := tableFields[fieldName]; exists {
|
||||
matchedPrefix = aliasOrTable
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Only return prefix if field uniquely belongs to one table
|
||||
if matchCount == 1 {
|
||||
return m.QuoteWord(matchedPrefix)
|
||||
}
|
||||
|
||||
// If field exists in multiple tables or not found, return empty
|
||||
// to avoid ambiguity - user should specify the table explicitly
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Model) getFieldsAsStr() string {
|
||||
var (
|
||||
fieldsStr string
|
||||
|
||||
Reference in New Issue
Block a user