Skip to content

Commit e9a3f83

Browse files
committed
LONG DATA handling
prerequisite for issue #33
1 parent de6141b commit e9a3f83

File tree

8 files changed

+302
-54
lines changed

8 files changed

+302
-54
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
3030
* Connections over TCP/IPv4, TCP/IPv6 or Unix Sockets
3131
* Automatic handling of broken connections
3232
* Automatic Connection-Pooling *(by database/sql package)*
33+
* Supports queries larger than 16MB
34+
* Intelligent `LONG DATA` handling in prepared statements
3335

3436
## Requirements
3537
* Go 1.0.3 or higher

connection.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ import (
1717
)
1818

1919
type mysqlConn struct {
20-
cfg *config
21-
flags clientFlag
22-
charset byte
23-
cipher []byte
24-
netConn net.Conn
25-
buf *buffer
26-
protocol uint8
27-
sequence uint8
28-
affectedRows uint64
29-
insertId uint64
20+
cfg *config
21+
flags clientFlag
22+
charset byte
23+
cipher []byte
24+
netConn net.Conn
25+
buf *buffer
26+
protocol uint8
27+
sequence uint8
28+
affectedRows uint64
29+
insertId uint64
30+
maxPacketAllowed int
31+
maxWriteSize int
3032
}
3133

3234
type config struct {
@@ -192,3 +194,31 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
192194
// with args, must use prepared stmt
193195
return nil, driver.ErrSkip
194196
}
197+
198+
// Gets the value of the given MySQL System Variable
199+
func (mc *mysqlConn) getSystemVar(name string) (val []byte, err error) {
200+
// Send command
201+
err = mc.writeCommandPacketStr(comQuery, "SELECT @@"+name)
202+
if err == nil {
203+
// Read Result
204+
var resLen int
205+
resLen, err = mc.readResultSetHeaderPacket()
206+
if err == nil {
207+
rows := &mysqlRows{mc, false, nil, false}
208+
209+
if resLen > 0 {
210+
// Columns
211+
rows.columns, err = mc.readColumns(resLen)
212+
}
213+
214+
dest := make([]driver.Value, resLen)
215+
err = rows.readRow(dest)
216+
if err == nil {
217+
val = dest[0].([]byte)
218+
err = mc.readUntilEOF()
219+
}
220+
}
221+
}
222+
223+
return
224+
}

const.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ package mysql
1111

1212
const (
1313
minProtocolVersion byte = 10
14-
//maxPacketSize = 1<<24 - 1
15-
timeFormat = "2006-01-02 15:04:05"
14+
maxPacketSize = 1<<24 - 1
15+
timeFormat = "2006-01-02 15:04:05"
1616
)
1717

1818
// MySQL constants documentation:
@@ -47,10 +47,8 @@ const (
4747
clientMultiResults
4848
)
4949

50-
type commandType byte
51-
5250
const (
53-
comQuit commandType = iota + 1
51+
comQuit byte = iota + 1
5452
comInitDB
5553
comQuery
5654
comFieldList

driver.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ func (d *mysqlDriver) Open(dsn string) (driver.Conn, error) {
2424
var err error
2525

2626
// New mysqlConn
27-
mc := new(mysqlConn)
28-
mc.cfg = parseDSN(dsn)
27+
mc := &mysqlConn{
28+
cfg: parseDSN(dsn),
29+
maxPacketAllowed: maxPacketSize,
30+
maxWriteSize: maxPacketSize - 1,
31+
}
2932

3033
// Connect to Server
3134
if _, ok := mc.cfg.params["timeout"]; ok { // with timeout
@@ -60,6 +63,16 @@ func (d *mysqlDriver) Open(dsn string) (driver.Conn, error) {
6063
return nil, err
6164
}
6265

66+
// Get max allowed packet size
67+
maxap, err := mc.getSystemVar("max_allowed_packet")
68+
if err != nil {
69+
return nil, err
70+
}
71+
mc.maxPacketAllowed = stringToInt(maxap) - 1
72+
if mc.maxPacketAllowed < maxPacketSize {
73+
mc.maxWriteSize = mc.maxPacketAllowed
74+
}
75+
6376
// Handle DSN Params
6477
err = mc.handleParams()
6578
if err != nil {

driver_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net"
77
"os"
8+
"strings"
89
"sync"
910
"testing"
1011
)
@@ -57,6 +58,9 @@ func getEnv() bool {
5758
func mustExec(t *testing.T, db *sql.DB, query string, args ...interface{}) (res sql.Result) {
5859
res, err := db.Exec(query, args...)
5960
if err != nil {
61+
if len(query) > 300 {
62+
query = "[query too large to print]"
63+
}
6064
t.Fatalf("Error on Exec %q: %v", query, err)
6165
}
6266
return
@@ -65,6 +69,9 @@ func mustExec(t *testing.T, db *sql.DB, query string, args ...interface{}) (res
6569
func mustQuery(t *testing.T, db *sql.DB, query string, args ...interface{}) (rows *sql.Rows) {
6670
rows, err := db.Query(query, args...)
6771
if err != nil {
72+
if len(query) > 300 {
73+
query = "[query too large to print]"
74+
}
6875
t.Fatalf("Error on Query %q: %v", query, err)
6976
}
7077
return
@@ -501,6 +508,76 @@ func TestNULL(t *testing.T) {
501508
mustExec(t, db, "DROP TABLE IF EXISTS test")
502509
}
503510

511+
func TestLongData(t *testing.T) {
512+
if !getEnv() {
513+
t.Logf("MySQL-Server not running on %s. Skipping TestLongData", netAddr)
514+
return
515+
}
516+
517+
db, err := sql.Open("mysql", dsn)
518+
if err != nil {
519+
t.Fatalf("Error connecting: %v", err)
520+
}
521+
defer db.Close()
522+
523+
var maxAllowedPacketSize int
524+
err = db.QueryRow("select @@max_allowed_packet").Scan(&maxAllowedPacketSize)
525+
if err != nil {
526+
t.Fatal(err)
527+
}
528+
maxAllowedPacketSize--
529+
530+
// don't get too ambitious
531+
if maxAllowedPacketSize > 1<<25 {
532+
maxAllowedPacketSize = 1 << 25
533+
}
534+
535+
mustExec(t, db, "DROP TABLE IF EXISTS test")
536+
mustExec(t, db, "CREATE TABLE test (value LONGBLOB) CHARACTER SET utf8 COLLATE utf8_unicode_ci")
537+
538+
in := strings.Repeat(`0`, maxAllowedPacketSize+1)
539+
var out string
540+
var rows *sql.Rows
541+
542+
// Long text data
543+
const nonDataQueryLen = 28 // length query w/o value
544+
inS := in[:maxAllowedPacketSize-nonDataQueryLen]
545+
mustExec(t, db, "INSERT INTO test VALUES('"+inS+"')")
546+
rows = mustQuery(t, db, "SELECT value FROM test")
547+
if rows.Next() {
548+
rows.Scan(&out)
549+
if inS != out {
550+
t.Fatalf("LONGBLOB: length in: %d, length out: %d", len(inS), len(out))
551+
}
552+
if rows.Next() {
553+
t.Error("LONGBLOB: unexpexted row")
554+
}
555+
} else {
556+
t.Fatalf("LONGBLOB: no data")
557+
}
558+
559+
// Empty table
560+
mustExec(t, db, "TRUNCATE TABLE test")
561+
562+
// Long binary data
563+
mustExec(t, db, "INSERT INTO test VALUES(?)", in)
564+
rows = mustQuery(t, db, "SELECT value FROM test WHERE 1=?", 1)
565+
if rows.Next() {
566+
rows.Scan(&out)
567+
if in != out {
568+
t.Fatalf("LONGBLOB: length in: %d, length out: %d", len(in), len(out))
569+
}
570+
if rows.Next() {
571+
t.Error("LONGBLOB: unexpexted row")
572+
}
573+
//t.Fatalf("%d %d %d", len(in)+nonDataQueryLen, len(out)+nonDataQueryLen, maxAllowedPacketSize)
574+
} else {
575+
t.Fatalf("LONGBLOB: no data")
576+
}
577+
578+
mustExec(t, db, "DROP TABLE IF EXISTS test")
579+
}
580+
504581
// Special cases
505582

506583
func TestRowsClose(t *testing.T) {

errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ var (
1616
errPktSync = errors.New("Commands out of sync. You can't run this command now")
1717
errPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
1818
errOldPassword = errors.New("It seems like you are using old_passwords, which is unsupported. See https://github.com/Go-SQL-Driver/MySQL/wiki/old_passwords")
19+
errPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
1920
)

0 commit comments

Comments
 (0)