Skip to content

Commit 3454863

Browse files
committed
Add support for returning table alias on Columns().
Currently the driver discards the column alias and the original table name. This change adds a new configuration parameter to include the table alias with the column name. This is useful because when mapping columns to structs, it is impossible to distinguish between duplicate column names when doing JOIN queries.
1 parent 9543750 commit 3454863

7 files changed

+59
-18
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Aaron Hopkins <go-sql-driver at die.net>
1515
Arne Hormann <arnehormann at gmail.com>
1616
Carlos Nieto <jose.carlos at menteslibres.net>
17+
Chris Moos <chris at tech9computers.com>
1718
DisposaBoy <disposaboy at dby.me>
1819
Frederick Mayle <frederickmayle at gmail.com>
1920
Gustavo Kristic <gkristic at gmail.com>

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,21 @@ Default: false
166166

167167
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
168168

169+
##### `columnsWithAlias`
170+
171+
```
172+
Type: bool
173+
Valid Values: true, false
174+
Default: false
175+
```
176+
177+
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
178+
179+
```
180+
SELECT u.id FROM users as u
181+
```
182+
183+
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
169184

170185
##### `loc`
171186

connection.go

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type config struct {
4545
allowAllFiles bool
4646
allowOldPasswords bool
4747
clientFoundRows bool
48+
columnsWithAlias bool
4849
}
4950

5051
// Handles parameters set in DSN after the connection is established

packets.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -530,11 +530,20 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
530530
pos += n
531531

532532
// Table [len coded string]
533-
n, err = skipLengthEncodedString(data[pos:])
534-
if err != nil {
535-
return nil, err
533+
if mc.cfg.columnsWithAlias {
534+
tableName, _, n, err := readLengthEncodedString(data[pos:])
535+
if err != nil {
536+
return nil, err
537+
}
538+
pos += n
539+
columns[i].tableName = string(tableName)
540+
} else {
541+
n, err = skipLengthEncodedString(data[pos:])
542+
if err != nil {
543+
return nil, err
544+
}
545+
pos += n
536546
}
537-
pos += n
538547

539548
// Original table [len coded string]
540549
n, err = skipLengthEncodedString(data[pos:])

rows.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
)
1515

1616
type mysqlField struct {
17+
tableName string
1718
name string
1819
flags fieldFlag
1920
fieldType byte
@@ -37,8 +38,14 @@ type emptyRows struct{}
3738

3839
func (rows *mysqlRows) Columns() []string {
3940
columns := make([]string, len(rows.columns))
40-
for i := range columns {
41-
columns[i] = rows.columns[i].name
41+
if rows.mc.cfg.columnsWithAlias {
42+
for i := range columns {
43+
columns[i] = rows.columns[i].tableName + "." + rows.columns[i].name
44+
}
45+
} else {
46+
for i := range columns {
47+
columns[i] = rows.columns[i].name
48+
}
4249
}
4350
return columns
4451
}

utils.go

+7
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ func parseDSNParams(cfg *config, params string) (err error) {
215215
}
216216
cfg.collation = collation
217217
break
218+
219+
case "columnsWithAlias":
220+
var isBool bool
221+
cfg.columnsWithAlias, isBool = readBool(value)
222+
if !isBool {
223+
return fmt.Errorf("Invalid Bool value: %s", value)
224+
}
218225

219226
// Time Location
220227
case "loc":

utils_test.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ var testDSNs = []struct {
2121
out string
2222
loc *time.Location
2323
}{
24-
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
25-
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
26-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
27-
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
28-
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true}", time.UTC},
29-
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.Local},
30-
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
31-
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
32-
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
33-
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
34-
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
35-
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false}", time.UTC},
24+
{"username:password@protocol(address)/dbname?param=value", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
25+
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{user:username passwd:password net:protocol addr:address dbname:dbname params:map[param:value] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:true}", time.UTC},
26+
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{user:user passwd: net:unix addr:/path/to/socket dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
27+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
28+
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{user:user passwd:password net:tcp addr:localhost:5555 dbname:dbname params:map[charset:utf8mb4,utf8] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
29+
{"user:password@/dbname?loc=UTC&timeout=30s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{user:user passwd:password net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:30000000000 collation:224 allowAllFiles:true allowOldPasswords:true clientFoundRows:true columnsWithAlias:false}", time.UTC},
30+
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{user:user passwd:p@ss(word) net:tcp addr:[de:ad:be:ef::ca:fe]:80 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.Local},
31+
{"/dbname", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname:dbname params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
32+
{"@/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
33+
{"/", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
34+
{"", "&{user: passwd: net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
35+
{"user:p@/ssword@/", "&{user:user passwd:p@/ssword net:tcp addr:127.0.0.1:3306 dbname: params:map[] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
36+
{"unix/?arg=%2Fsome%2Fpath.ext", "&{user: passwd: net:unix addr:/tmp/mysql.sock dbname: params:map[arg:/some/path.ext] loc:%p tls:<nil> timeout:0 collation:33 allowAllFiles:false allowOldPasswords:false clientFoundRows:false columnsWithAlias:false}", time.UTC},
3637
}
3738

3839
func TestDSNParser(t *testing.T) {

0 commit comments

Comments
 (0)