Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

misc improvements

  • Loading branch information...
commit 9211f07efaad479e817ae69ef0d91a4e424a743d 1 parent 6eef300
@Philio authored
View
6 README.markdown
@@ -7,7 +7,7 @@ Revision History
0.3.x series [testing]
-* 0.3.0-RC - Fixed SimpleTest unit test.
+* 0.3.0-RC - Fixed SimpleTest unit test. Fixed and variable length strings for normal queries now return string types not []byte, text/blobs are indistinguishable so are left in []byte format. All integer values in prepared statements are stored as either int64 or uint64 depending on the unsigned flag, this simplifies conversion greatly when binding the result. Added ParamCount() and RowCount() methods to statements. The built in Date, Time and DateTime types can now be bound as strings in statements.
* 0.3.0-beta-1 - Added full statement and functions. Refactored packet handlers into generic functions. Added new BindResult/Fetch method to get result data from prepared statements. Added type conversions for similar types to populate the result pointers with values from the row data. Added simple type conversion to standard queries. Added automatic reconnect for a select number of operations. Added greater number of client errors from the MySQL manual. Added date/time types to allow date/time elements to be stored as integers and ints, making them more useful.
* 0.3.0-alpha-3 - Added new error structs ClientError and ServerError. Replaced majority of os.Error/os.NewError functionality with MySQL specific ClientError objects. Server error responses now return a ServerError. Removed Client.Errno and Client.Error. Added deferred error processing to reader, writer and packets to catch and errors and always return a ClientError. Rewrote auto reconnect to check for specific MySQL error codes.
* 0.3.0-alpha-2 - Added transaction wrappers, Added auto-reconnect functionality to repeatable methods. Removed mutex lock/unlocking, as it is now more appropriate that the application decides when thread safe functions are required and it's considerably safer to have a sequence such as Client.Lock(), Client.Query(...), Client.Unlock(). Added a new test which performs create, drop, select, insert and update queries on a simple demo table to test the majority of the library functionality. Added additional error messages to places where an error could be returned but there was no error number/string set. Many small changes and general improvements.
@@ -236,6 +236,8 @@ Statement methods
**Statement.Prepare(sql string) (err os.Error)** - Prepare a new statement using the supplied query.
+**Statement.ParamCount() uint16** - Get the number of parameters.
+
**Statement.BindParams(params ...interface{}) (err os.Error)** - Bind parameters to the statement.
**Statement.SendLongData(num int, data []byte) (err os.Error)** - Send a parameter as long data. The data can be > than the maximum packet size and will be split automatically.
@@ -250,6 +252,8 @@ Statement methods
**Statement.BindResult(params ...interface{}) (err os.Error)** - Bind the result, parameters passed to this functions should be pointers to variables which will be populated with the data from the fetched row. If a column value is not needed a nil can be used. Parameters should be of a "similar" type to the actual column value in the MySQL table, e.g. for an INT field, the parameter can be any integer type or a string and the relevant conversion is performed. Using integer sizes smaller than the size in the table is not recommended. The number of parameters bound can be equal or less than the number of fields in the table, providing more parameters than actual columns will result in a crash.
+**Statement.RowCount() uint64** - Get the number of rows in the result set, works for stored results only, otherwise returns 0.
+
**Statement.Fetch() (eof bool, err os.Error)** - Fetch the next row in the result, values are populated into parameters bound using BindResult.
**Statement.StoreResult() (err os.Error)** - Store all rows for a result set,
View
30 convert.go
@@ -221,24 +221,8 @@ func lcbtob(n uint64) (b []byte) {
}
// any to uint64
-func atou64(i interface{}) (n uint64) {
+func atoui64(i interface{}) (n uint64) {
switch t := i.(type) {
- case int:
- n = uint64(t)
- case uint:
- n = uint64(t)
- case int8:
- n = uint64(t)
- case uint8:
- n = uint64(t)
- case int16:
- n = uint64(t)
- case uint16:
- n = uint64(t)
- case int32:
- n = uint64(t)
- case uint32:
- n = uint64(t)
case int64:
n = uint64(t)
case uint64:
@@ -278,14 +262,22 @@ func atof64(i interface{}) (f float64) {
// any to string
func atos(i interface{}) (s string) {
switch t := i.(type) {
- case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
- s = strconv.Uitoa64(atou64(i))
+ case int64:
+ s = strconv.Itoa64(t)
+ case uint64:
+ s = strconv.Uitoa64(t)
case float32:
s = strconv.Ftoa32(t, 'f', -1)
case float64:
s = strconv.Ftoa64(t, 'f', -1)
case []byte:
s = string(t)
+ case Date:
+ return t.String()
+ case Time:
+ return t.String()
+ case DateTime:
+ return t.String()
case string:
return t
default:
View
14 handler.go
@@ -145,7 +145,7 @@ func handleRow(p *packetRowData, c *Client, r *Result) (err os.Error) {
return
}
// Strings
- case FIELD_TYPE_DECIMAL, FIELD_TYPE_NEWDECIMAL, FIELD_TYPE_VARCHAR:
+ case FIELD_TYPE_DECIMAL, FIELD_TYPE_NEWDECIMAL, FIELD_TYPE_VARCHAR, FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING:
field = string(p.row[i].([]byte))
// Anything else
default:
@@ -230,25 +230,25 @@ func handleBinaryRow(p *packetRowBinary, c *Client, r *Result) (err os.Error) {
// Tiny int (8 bit int unsigned or signed)
case FIELD_TYPE_TINY:
if f.Flags&FLAG_UNSIGNED > 0 {
- field = p.data[pos]
+ field = uint64(p.data[pos])
} else {
- field = int8(p.data[pos])
+ field = int64(p.data[pos])
}
pos++
// Small int (16 bit int unsigned or signed)
case FIELD_TYPE_SHORT, FIELD_TYPE_YEAR:
if f.Flags&FLAG_UNSIGNED > 0 {
- field = btoui16(p.data[pos : pos+2])
+ field = uint64(btoui16(p.data[pos : pos+2]))
} else {
- field = btoi16(p.data[pos : pos+2])
+ field = int64(btoi16(p.data[pos : pos+2]))
}
pos += 2
// Int (32 bit int unsigned or signed) and medium int which is actually in int32 format
case FIELD_TYPE_LONG, FIELD_TYPE_INT24:
if f.Flags&FLAG_UNSIGNED > 0 {
- field = btoui32(p.data[pos : pos+4])
+ field = uint64(btoui32(p.data[pos : pos+4]))
} else {
- field = btoi32(p.data[pos : pos+4])
+ field = int64(btoi32(p.data[pos : pos+4]))
}
pos += 4
// Big int (64 bit int unsigned or signed)
View
75 mysql_test.go
@@ -35,11 +35,13 @@ const (
TEST_DBNAMEBAD = "gomysql_bad" // This is a nonexistant database
// Simple table queries
- CREATE_SIMPLE = "CREATE TABLE `simple` (`id` SERIAL NOT NULL, `number` BIGINT NOT NULL, `string` VARCHAR(32) NOT NULL, `text` TEXT NOT NULL, `datetime` DATETIME NOT NULL) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT = 'GoMySQL Test Suite Simple Table';"
- SELECT_SIMPLE = "SELECT * FROM simple"
- INSERT_SIMPLE = "INSERT INTO simple VALUES (null, %d, '%s', '%s', NOW())"
- UPDATE_SIMPLE = "UPDATE simple SET `text` = '%s', `datetime` = NOW() WHERE id = %d"
- DROP_SIMPLE = "DROP TABLE `simple`"
+ CREATE_SIMPLE = "CREATE TABLE `simple` (`id` SERIAL NOT NULL, `number` BIGINT NOT NULL, `string` VARCHAR(32) NOT NULL, `text` TEXT NOT NULL, `datetime` DATETIME NOT NULL) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT = 'GoMySQL Test Suite Simple Table';"
+ SELECT_SIMPLE = "SELECT * FROM simple"
+ INSERT_SIMPLE = "INSERT INTO simple VALUES (null, %d, '%s', '%s', NOW())"
+ INSERT_SIMPLE_STMT = "INSERT INTO simple VALUES (null, ?, ?, ?, NOW())"
+ UPDATE_SIMPLE = "UPDATE simple SET `text` = '%s', `datetime` = NOW() WHERE id = %d"
+ UPDATE_SIMPLE_STMT = "UPDATE simple SET `text` = ?, `datetime` = NOW() WHERE id = ?"
+ DROP_SIMPLE = "DROP TABLE `simple`"
// All types table queries
CREATE_ALLTYPES = "CREATE TABLE `all_types` (`id` SERIAL NOT NULL, `tiny_int` TINYINT NOT NULL, `tiny_uint` TINYINT UNSIGNED NOT NULL, `small_int` SMALLINT NOT NULL, `small_uint` SMALLINT UNSIGNED NOT NULL, `medium_int` MEDIUMINT NOT NULL, `medium_uint` MEDIUMINT UNSIGNED NOT NULL, `int` INT NOT NULL, `uint` INT UNSIGNED NOT NULL, `big_int` BIGINT NOT NULL, `big_uint` BIGINT UNSIGNED NOT NULL, `decimal` DECIMAL(10,4) NOT NULL, `float` FLOAT NOT NULL, `double` DOUBLE NOT NULL, `real` REAL NOT NULL, `bit` BIT(32) NOT NULL, `boolean` BOOLEAN NOT NULL, `date` DATE NOT NULL, `datetime` DATETIME NOT NULL, `timestamp` TIMESTAMP NOT NULL, `time` TIME NOT NULL, `year` YEAR NOT NULL, `char` CHAR(32) NOT NULL, `varchar` VARCHAR(32) NOT NULL, `tiny_text` TINYTEXT NOT NULL, `text` TEXT NOT NULL, `medium_text` MEDIUMTEXT NOT NULL, `long_text` LONGTEXT NOT NULL, `binary` BINARY(32) NOT NULL, `var_binary` VARBINARY(32) NOT NULL, `tiny_blob` TINYBLOB NOT NULL, `medium_blob` MEDIUMBLOB NOT NULL, `blob` BLOB NOT NULL, `long_blob` LONGBLOB NOT NULL, `enum` ENUM('a','b','c','d','e') NOT NULL, `set` SET('a','b','c','d','e') NOT NULL, `geometry` GEOMETRY NOT NULL) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci COMMENT = 'GoMySQL Test Suite All Types Table'"
@@ -51,6 +53,14 @@ var (
err os.Error
)
+type SimpleRow struct {
+ Id uint64
+ Number int64
+ String string
+ Text string
+ Date Date
+}
+
// Test connect to server via TCP
func TestDialTCP(t *testing.T) {
t.Logf("Running DialTCP test to %s:%s", TEST_HOST, TEST_PORT)
@@ -171,7 +181,7 @@ func TestSimple(t *testing.T) {
break
}
id := row[0].(uint64)
- num, str1, str2 := strconv.Itoa64(row[1].(int64)), string(row[2].([]byte)), string(row[3].([]byte))
+ num, str1, str2 := strconv.Itoa64(row[1].(int64)), row[2].(string), string(row[3].([]byte))
if rowMap[id][0] != num || rowMap[id][1] != str1 || rowMap[id][2] != str2 {
t.Logf("String from database doesn't match local string")
t.Fail()
@@ -215,7 +225,7 @@ func TestSimple(t *testing.T) {
break
}
id := row[0].(uint64)
- num, str1, str2 := strconv.Itoa64(row[1].(int64)), string(row[2].([]byte)), string(row[3].([]byte))
+ num, str1, str2 := strconv.Itoa64(row[1].(int64)), row[2].(string), string(row[3].([]byte))
if rowMap[id][0] != num || rowMap[id][1] != str1 || rowMap[id][2] != str2 {
t.Logf("%#v %#v", rowMap[id], row)
t.Logf("String from database doesn't match local string")
@@ -242,6 +252,57 @@ func TestSimple(t *testing.T) {
}
}
+// Test queries on a simple table (create database, select, insert, update, drop database) using a statement
+func TestSimpleStatement(t *testing.T) {
+ t.Logf("Running simple table statement tests")
+ db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAME)
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ t.Logf("Init statement")
+ stmt, err := db.InitStmt()
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ t.Logf("Prepare create table")
+ err = stmt.Prepare(CREATE_SIMPLE)
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ t.Logf("Execute create table")
+ err = stmt.Execute()
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ t.Logf("Prepare insert")
+ err = stmt.Prepare(INSERT_SIMPLE_STMT)
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ t.Logf("Insert 1000 records")
+ rowMap := make(map[uint64][]string)
+ for i := 0; i < 1000; i++ {
+ num, str1, str2 := rand.Int(), randString(32), randString(128)
+ err = stmt.BindParams(num, str1, str2)
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ err = stmt.Execute()
+ if err != nil {
+ t.Logf("Error %s", err)
+ t.Fail()
+ }
+ row := []string{fmt.Sprintf("%d", num), str1, str2}
+ rowMap[db.LastInsertId] = row
+ }
+}
+
// Benchmark connect/handshake via TCP
func BenchmarkDialTCP(b *testing.B) {
for i := 0; i < b.N; i++ {
View
34 statement.go
@@ -86,6 +86,11 @@ func (s *Statement) Prepare(sql string) (err os.Error) {
return
}
+// Get number of params
+func (s *Statement) ParamCount() uint16 {
+ return s.paramCount
+}
+
// Bind params
func (s *Statement) BindParams(params ...interface{}) (err os.Error) {
// Check prepared
@@ -331,6 +336,15 @@ func (s *Statement) BindResult(params ...interface{}) (err os.Error) {
return
}
+// Get row count
+func (s *Statement) RowCount() uint64 {
+ // Stored mode
+ if s.checkResult() && s.result.mode == RESULT_STORED {
+ return uint64(len(s.result.rows))
+ }
+ return 0
+}
+
// Fetch next row
func (s *Statement) Fetch() (eof bool, err os.Error) {
// Log fetch
@@ -381,25 +395,25 @@ func (s *Statement) Fetch() (eof bool, err os.Error) {
switch t := v.(type) {
// Integer types
case *int:
- *t = int(atou64(row[k]))
+ *t = int(atoui64(row[k]))
case *uint:
- *t = uint(atou64(row[k]))
+ *t = uint(atoui64(row[k]))
case *int8:
- *t = int8(atou64(row[k]))
+ *t = int8(atoui64(row[k]))
case *uint8:
- *t = uint8(atou64(row[k]))
+ *t = uint8(atoui64(row[k]))
case *int16:
- *t = int16(atou64(row[k]))
+ *t = int16(atoui64(row[k]))
case *uint16:
- *t = uint16(atou64(row[k]))
+ *t = uint16(atoui64(row[k]))
case *int32:
- *t = int32(atou64(row[k]))
+ *t = int32(atoui64(row[k]))
case *uint32:
- *t = uint32(atou64(row[k]))
+ *t = uint32(atoui64(row[k]))
case *int64:
- *t = int64(atou64(row[k]))
+ *t = int64(atoui64(row[k]))
case *uint64:
- *t = atou64(row[k])
+ *t = atoui64(row[k])
// Floating point types
case *float32:
*t = float32(atof64(row[k]))
Please sign in to comment.
Something went wrong with that request. Please try again.