├── LICENSE ├── Makefile ├── README.markdown ├── const.go ├── convert.go ├── error.go ├── handler.go ├── mysql.go ├── mysql_test.go ├── packet.go ├── password.go ├── reader.go ├── result.go ├── statement.go ├── types.go └── writer.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010/2011, Phil Bayfield "Philio" 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Phil Bayfield nor the names of contributors may be 15 | used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=mysql 4 | GOFILES=mysql.go\ 5 | types.go\ 6 | const.go\ 7 | error.go\ 8 | password.go\ 9 | reader.go\ 10 | writer.go\ 11 | packet.go\ 12 | convert.go\ 13 | handler.go\ 14 | result.go\ 15 | statement.go 16 | 17 | include $(GOROOT)/src/Make.pkg 18 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | GoMySQL Version 0.3.2 2 | ===================== 3 | 4 | 5 | Revision History 6 | ---------------- 7 | 8 | 0.3.x series [current] 9 | 10 | * 0.3.2 - Updated to support release.r57.1 Go build, change to usage of reflect. 11 | * 0.3.1 - Updated to support weekly.2011-04-04 Go build, change to usage of net.Dial. 12 | * 0.3.0 - No changes since RC2. 13 | * 0.3.0-RC-2 - Convert additional string types (issue 47). Added a check for NULL fields in the row packet handler to prevent a crash in strconv (issue 48). 14 | * 0.3.0-RC-1 - Fixed TestSimple unit test and added TestSimpleStatement which performs the same tests as TestSimple but uses a prepared statement throughout. Fixed and variable length strings for normal queries now return string types not []byte, text/blobs are indistinguishable so are left in []byte format which is more efficient. 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. Added auto-reconnect to all methods using the network and added reconnect/recovery support to Prepare and Execute functions. Statement.Reset now frees any remaining rows or complete result sets from the connection before sending the reset command so no longer requires a call to FreeResult prior to calling. 15 | * 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. 16 | * 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. 17 | * 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. 18 | * 0.3.0-alpha-1 - First test release of new library, completely rewritten from scratch. Fully compatible with all versions of MySQL using the 4.1+ protocol and 4.0 protocol (which supports earlier versions). Fully supports old and new passwords, including old passwords using the 4.1 protocol. Includes new Go style constructors 'NewClient', 'DialTCP', 'DialUnix' replacing 'New' from the 0.2 branch. All structs have been renamed to be more user friendly, MySQL has also now been replaced with Client. Removed many dependencies on external packages such as bufio. New reader that reads the entire packet completely to a slice then processes afterwards. New writer that constructs the entire packet completely to a slice and writes in a single operation. The Client.Query function no longer returns a result set and now uses the tradition store/use result mechanism for retrieving the result and processing it's contents. The 'MultiQuery' function has been removed as this is now supported by the Client.Query function. Currently all result sets must be freed before another query can be executed either using the Result.Free() method or Client.FreeResult() method, a check for additional result sets can be made using Client.MoreResults() and the next result can be retrieved using Client.NextResult(). Client.FreeResult() is capable of reading and discarding an entire result set (provided the first result set packet has been read), a partially read result set (e.g. from Client.UseResult) or a fully stored result. Transaction support and prepared statements are NOT available in this alpha release. 19 | 20 | 0.2.x series [deprecated] 21 | 22 | * 0.2.12 - Fix a bug in getPrepareResult() causing queries returning no fields (e.g. DROP TABLE ...) to hang. 23 | * 0.2.11 - Skipped 24 | * 0.2.10 - Compatibility update for Go release.2011-01-20 25 | * 0.2.9 - Added support for MySQL 5.5 26 | * 0.2.8 - Fixes issue #38. 27 | * 0.2.7 - Added additional binary type support: medium int (int32/uint32), decimal (string), new decimal (string), bit ([]byte), year (uint16), set ([]byte), enum/set use string type. 28 | * 0.2.6 - Replaced buffer checks in prepared statements, similar to change in 0.2.5, more robust method to handle end of packets. 29 | * 0.2.5 - Fixes issue #10, removed buffer check from query function as no longer needed. 30 | * 0.2.4 - Fixes issue #7 and related issues with prepared statement - thanks to Tom Lee [[thomaslee]](/thomaslee). New faster Escape function - thanks to [[jteeuwen]](/jteeuwen). Updated/fixed examples - thanks to [[jteeuwen]](/jteeuwen). Fixes issues (#10, #21) with reading full packet, due to some delay e.g. network lag - thanks to Michał Derkacz [[ziutek]](/ziutek) and Damian Reeves for submitting fixes for this. 31 | * 0.2.3 - Fixes issue #6 - thanks to Tom Lee [[thomaslee]](/thomaslee). 32 | * 0.2.2 - Resolves issue #16. 33 | * 0.2.1 - Updated to work with latest release of Go plus 1 or 2 minor tweaks. 34 | * 0.2.0 - Functions have been reworked and now always return os.Error to provide a generic and consistent design. Improved logging output. Improved client stability. Removed length vs buffered length checks as they don't work with packets > 4096 bytes. Added new Escape function, although this is currently only suitable for short strings. Tested library with much larger databases such as multi-gigabyte tables and multi-megabyte blogs. Many minor bug fixes. Resolved issue #3, #4 and #5. 35 | 36 | 0.1.x series [obsolete] 37 | 38 | * 0.1.14 - Added support for long data packets. 39 | * 0.1.13 - Added proper support for NULL bit map in binary row data packets. 40 | * 0.1.12 - Added auth struct to store authentication data. Removed logging param from New() in favour of just setting the public var. Added Reconnect() function. Bug fix in Query() causing panic for error packet responses. Added a number of examples. 41 | * 0.1.11 - Added support for binary time fields, fixed missing zeros in time for datetime/timestamp fields. 42 | * 0.1.10 - Changed row data to use interface{} instead of string, so rows contain data of the correct type. 43 | * 0.1.9 - Small code tweaks, change to execute packets to allow params to contain up to 4096 bytes of data. [not released] 44 | * 0.1.8 - Added internal mutex to make client operations thread safe. 45 | * 0.1.7 - Added prepared statement support. 46 | * 0.1.6 - Added Ping function. 47 | * 0.1.5 - Clean up packet visibility all should have been private, add packet handlers for prepare/execute and related packets. 48 | * 0.1.4 - Connect now uses ...interface{} for parameters to remove (or reduce) 'junk' required params to call the function. [not released] 49 | * 0.1.3 - Added ChangeDb function to change the active database. [not released] 50 | * 0.1.2 - Added MultiQuery function to return multiple result sets as an array. [not released] 51 | * 0.1.1 - Added support for multiple queries in a single command. [not released] 52 | * 0.1.0 - Initial release, supporting connect, close and query functions. [not released] 53 | 54 | 55 | About 56 | ----- 57 | 58 | The most complete and stable MySQL client library written completely in Go. The aim of this project is to provide a library with a high level of usability, good internal error handling and to emulate similar libraries available for other languages to provide an easy migration of MySQL based systems into the Go language. 59 | 60 | For discussions, ideas, suggestions, comments, please visit the Google Group: [https://groups.google.com/group/gomysql](https://groups.google.com/group/gomysql) 61 | 62 | Please report bugs via the GitHub issue tracker: [https://github.com/Philio/GoMySQL/issues](https://github.com/Philio/GoMySQL/issues) 63 | 64 | 65 | License 66 | ------- 67 | 68 | GoMySQL 0.3 is governed by a BSD-style license that can be found in the LICENSE file. 69 | 70 | GoMySQL 0.1.x and 0.2.x is licensed under a Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License. 71 | 72 | 73 | Compatibility 74 | ------------- 75 | 76 | Implements the MySQL protocol version 4.0- and 4.1+ 77 | 78 | Tested on versions of MySQL 4.x, 5.x (including 5.5), MariaDB and Percona. 79 | 80 | 81 | Thread Safety 82 | ------------- 83 | 84 | As of version 0.3, the thread safe functionality was removed from the library, but the inherited functions from sync.Mutex were retained. The reasons for this is that the inclusions of locking/unlocking within the client itself conflicted with the new functionality that had been added and it was clear that locking should be performed within the calling program and not the library. For convenience to the programmer, the mutex functions were retained allowing for Client.Lock() and Client.Unlock() to be used for thread safe operations. 85 | 86 | In older versions of the client from 0.1.8 - 0.2.x internal locking remains, however it is not recommended to use these versions as version 0.3.x is a much better implementation. 87 | 88 | Installation 89 | ------------ 90 | 91 | There are 2 methods to install GoMySQL 92 | 93 | 1. Via goinstall: 94 | 95 | `goinstall github.com/Philio/GoMySQL` 96 | 97 | The library will be installed in the same path locally so the import must be the same: 98 | 99 | `import "github.com/Philio/GoMySQL"` 100 | 101 | 2. Via make: 102 | 103 | Clone the git repository: 104 | 105 | `git clone git://github.com/Philio/GoMySQL.git` 106 | 107 | Build / install: 108 | 109 | `cd GoMySQL` 110 | `make` 111 | `make install` 112 | 113 | This installs the package as 'mysql' so can be imported as so: 114 | 115 | `import "mysql"` 116 | 117 | 118 | A note about 0.3 methods and functionality 119 | ------------------------------------------ 120 | 121 | Version 0.3 is a complete rewrite of the library and a vast improvement on the 0.2 branch, because of this there are considerable changes to available methods and usage. Please see the migration guide below for more information. 122 | 123 | 124 | Client constants 125 | ---------------- 126 | 127 | **Client.VERSION** - The current library version. 128 | 129 | **Client.DEFAULT_PORT** - The default port for MySQL (3306). 130 | 131 | **Client.DEFAULT_SOCKET** - The default socket for MySQL, valid for Debian/Ubuntu systems. 132 | 133 | **Client.MAX_PACKET_SIZE** - The maximum size of packets that will be used. 134 | 135 | **Client.PROTOCOL_41** - Used to indicate that the 4.1+ protocol should be used to connect to the server. 136 | 137 | **Client.PROTOCOL_40** - Used to indicate that the 4.0- protocol should be used to connect to the server. 138 | 139 | **Client.DEFAULT_PROTOCOL** - An alias for Client.PROTOCOL_41 140 | 141 | **Client.TCP** - Used to indicate that a TCP connection should be used. 142 | 143 | **Client.UNIX** - Used to indicate that a unix socket connection should be used (this is faster when connecting to localhost). 144 | 145 | **Client.LOG_SCREEN** - Send log messages to stdout. 146 | 147 | **Client.LOG_FILE** - Send log messages to a provided file pointer. 148 | 149 | 150 | Client properties 151 | ----------------- 152 | 153 | **Client.LogLevel** - The level of logging to provide to stdout, valid values are 0 (none), 1 (essential information), 2 (extended information), 3 (all information). 154 | 155 | **Client.LogType** - The type of logging to use, should be one of mysql.LOG_SCREEN or mysql.LOG_FILE, the default is mysql.LOG_SCREEN. 156 | 157 | **Client.LogFile** - A pointer for logging to file, can be used to log to any source that implements os.File. 158 | 159 | **Client.AffectedRows** - The number of affected rows for the last operation (if applicable). 160 | 161 | **Client.LastInsertId** - The insert id of the last operation (if applicable). 162 | 163 | **Client.Warnings** - The number of warnings generated by the last operation (if applicable). 164 | 165 | **Client.Reconnect** - Set to true to enable automatic reconnect for dropped connections. 166 | 167 | 168 | Client methods 169 | -------------- 170 | 171 | **mysql.NewClient(protocol ...uint8) (c *Client)** - Get a new client instance, optionally specifying the protocol. 172 | 173 | **mysql.DialTCP(raddr, user, passwd string, dbname ...string) (c *Client, err os.Error)** - Connect to the server using TCP. 174 | 175 | **mysql.DialUnix(raddr, user, passwd string, dbname ...string) (c *Client, err os.Error)** - Connect to the server using unix socket. 176 | 177 | **Client.Connect(network, raddr, user, passwd string, dbname ...string) (err os.Error)** - Connect to the server using the provided details. 178 | 179 | **Client.Close() (err os.Error)** - Close the connection to the server. 180 | 181 | **Client.ChangeDb(dbname string) (err os.Error)** - Change database. 182 | 183 | **Client.Query(sql string) (err os.Error)** - Perform an SQL query. 184 | 185 | **Client.StoreResult() (result *Result, err os.Error)** - Store the complete result set and return a pointer to the result. 186 | 187 | **Client.UseResult() (result *Result, err os.Error)** - Use the result set but do not store the result, data is read from the server one row at a time via Result.Fetch functions (see below). 188 | 189 | **Client.FreeResult() (err os.Error)** - Traditionally this function would free the memory used by the result set, in GoMySQL this removes the reference to allow the GC to clean up the memory. All results must be freed before more queries can be performed at present. FreeResult also reads and discards any remaining row packets received for the result set. 190 | 191 | **Client.MoreResults() bool** - Check if more results are available. 192 | 193 | **Client.NextResult() (more bool, err os.Error)** - Get the next result set from the server. 194 | 195 | **Client.SetAutoCommit(state bool) (err os.Error)** - Set the auto commit state of the connection. 196 | 197 | **Client.Start() (err os.Error)** - Start a new transaction. 198 | 199 | **Client.Commit() (err os.Error)** - Commit the current transaction. 200 | 201 | **Client.Rollback() (err os.Error)** - Rollback the current transaction. 202 | 203 | **Client.Escape(s string) (esc string)** - Escape a string. 204 | 205 | **Client.InitStmt() (stmt *Statement, err os.Error)** - Initialise a new statement. 206 | 207 | **Client.Prepare(sql string) (stmt *Statement, err os.Error)** - Initialise and prepare a new statement using the supplied query. 208 | 209 | 210 | Result methods 211 | -------------- 212 | 213 | **Result.FieldCount() uint64** - Get the number of fields in the result set. 214 | 215 | **Result.FetchField() *Field** - Get the next field in the result set. 216 | 217 | **Result.FetchFields() []*Field** - Get all fields in the result set. 218 | 219 | **Result.RowCount() uint64** - Get the number of rows in the result set, **works for stored results only**, used result always return 0. 220 | 221 | **Result.FetchRow() Row** - Get the next row in the result set. 222 | 223 | **Result.FetchMap() Map** - Get the next row in the result set and convert to a map with field names as keys. 224 | 225 | **Result.FetchRows() []Row** - Get all rows in the result set, works for stored results only, used results always return nil. 226 | 227 | 228 | Statement properties 229 | -------------------- 230 | 231 | **Statement.AffectedRows** - The number of affected rows for the last statement operation (if applicable). 232 | 233 | **Statement.LastInsertId** - The insert id of the last statement operation (if applicable). 234 | 235 | **Statement.Warnings** - The number of warnings generated by the last statement operation (if applicable). 236 | 237 | 238 | Statement methods 239 | ----------------- 240 | 241 | **Statement.Prepare(sql string) (err os.Error)** - Prepare a new statement using the supplied query. 242 | 243 | **Statement.ParamCount() uint16** - Get the number of parameters. 244 | 245 | **Statement.BindParams(params ...interface{}) (err os.Error)** - Bind parameters to the statement. 246 | 247 | **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. 248 | 249 | **Statement.Execute() (err os.Error)** - Execute the statement. 250 | 251 | **Statement.FieldCount() uint64** - Get the number of fields in the statement result set. 252 | 253 | **Statement.FetchColumn() *Field** - Get the next field in the statement result set. 254 | 255 | **Statement.FetchColumns() []*Field** - Get all fields in the statement result set. 256 | 257 | **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. 258 | 259 | **Statement.RowCount() uint64** - Get the number of rows in the result set, **works for stored results only**, otherwise returns 0. 260 | 261 | **Statement.Fetch() (eof bool, err os.Error)** - Fetch the next row in the result, values are populated into parameters bound using BindResult. 262 | 263 | **Statement.StoreResult() (err os.Error)** - Store all rows for a result set, 264 | 265 | **Statement.FreeResult() (err os.Error)** - Remove the result pointer, allowing the memory used for the result to be garbage collected. 266 | 267 | **Statement.MoreResults() bool** - Check if more results are available. 268 | 269 | **Statement.NextResult() (more bool, err os.Error)** - Get the next result set from the server. 270 | 271 | **Statement.Reset() (err os.Error)** - Reset the statement. 272 | 273 | **Statement.Close() (err os.Error)** - Close the statement. 274 | 275 | 276 | Usage examples 277 | -------------- 278 | 279 | 280 | 1. A simple query 281 | 282 | // Connect to database 283 | db, err := mysql.DialUnix(mysql.DEFAULT_SOCKET, "user", "password", "database") 284 | if err != nil { 285 | os.Exit(1) 286 | } 287 | // Perform query 288 | err = db.Query("select * from my_table") 289 | if err != nil { 290 | os.Exit(1) 291 | } 292 | // Get result set 293 | result, err := db.UseResult() 294 | if err != nil { 295 | os.Exit(1) 296 | } 297 | // Get each row from the result and perform some processing 298 | for { 299 | row := result.FetchRow() 300 | if row == nil { 301 | break 302 | } 303 | // ADD SOME ROW PROCESSING HERE 304 | } 305 | 306 | 307 | 2. Prepared statement 308 | 309 | 310 | // Define a struct to hold row data 311 | type MyRow struct { 312 | Id uint64 313 | Name string 314 | Description string 315 | } 316 | 317 | // Connect to database 318 | db, err := mysql.DialUnix(mysql.DEFAULT_SOCKET, "user", "password", "database") 319 | if err != nil { 320 | os.Exit(1) 321 | } 322 | // Prepare statement 323 | stmt, err := db.Prepare("select * from my_table where name = ?") 324 | if err != nil { 325 | os.Exit(1) 326 | } 327 | // Bind params 328 | err = stmt.BindParams("param") 329 | if err != nil { 330 | os.Exit(1) 331 | } 332 | // Execute statement 333 | err = stmt.Execute() 334 | if err != nil { 335 | os.Exit(1) 336 | } 337 | // Define a new row to hold result 338 | var myrow MyRow 339 | // Bind result 340 | stmt.BindResult(&myrow.Id, &myrow.Name, &myrow.Description) 341 | // Get each row from the result and perform some processing 342 | for { 343 | eof, err := stmt.Fetch() 344 | if err != nil { 345 | os.Exit(1) 346 | } 347 | if eof { 348 | break 349 | } 350 | // ADD SOME ROW PROCESSING HERE 351 | } 352 | 353 | 354 | Auto-reconnect functionality 355 | ---------------------------- 356 | 357 | As of version 0.3.0 the library can detect network failures and try and reconnect automatically. Any methods that use the network connection support reconnect but may still return a network error (as the process is too complicated to recover) while a number of core methods are able to attempt to reconnect and recover the operation. The default setting for the feature is OFF. 358 | 359 | Methods supporting recovery: 360 | 361 | * Client.ChangeDb - Will attempt to reconnect and rerun the changedb command. 362 | * Client.Query - Will attempt to reconnect and rerun the query. 363 | * Statement.Prepare - Will attempt to reconnect and prepare the statement again. 364 | * Statement.Execute - Will attempt to reconnect, prepare and execute the statement again. **Long data packets are not resent!** 365 | 366 | 367 | Prepared statement notes (previously limitations) 368 | ------------------------------------------------- 369 | 370 | This section is less relevant to the 0.3 client as it has full binary support and excellent type support but has been retained for reference. 371 | 372 | When using prepared statements the data packets sent to/from the server are in binary format (normal queries send results as text). 373 | 374 | Prior to version 0.2.7 there were a number of unsupported data types in the library which limited the use of prepared statement selects to the most common field types. 375 | 376 | As of version 0.2.7 all currently supported MySQL data types are fully supported, as well as a wide range of support of Go types for binding parameters. There are some minor limitations in the usage of unsigned numeric types, as Go does not natively support unsigned floating point numbers unsigned floats and doubles are limited to the maximum value of a signed float or double. 377 | 378 | **Supported parameter types:** 379 | 380 | Integer types: int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64 381 | 382 | Float types: float, float32, float64 383 | 384 | Strings/other tyes: string 385 | 386 | **Go row data formats:** 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 |
MySQL data typeNative Go type
TINYINTint8
TINYINT (unsigned)uint8
SMALLINTint16
SMALLINT (unsigned)uint16
MEDIUMINTint32
MEDIUMINT (unsigned)uint32
INTint32
INT (unsigned)uint32
BIGINTint64
BIGINT (unsigned)uint64
TIMESTAMPstring
DATEstring
TIMEstring
DATETIMEstring
YEARstring
VARCHARstring
TINYTEXTstring
MEDIUMTEXTstring
LONGTEXTstring
TEXTstring
TINYBLOBstring
MEDIUMBLOBstring
LONGBLOBstring
BLOBstring
BIT[]byte
GEOMETRY[]byte
498 | 499 | 500 | Error handling 501 | -------------- 502 | 503 | As of version 0.3.0 all functions return a ClientError or ServerError struct which contains a MySQL error code and description. The original Errno and Error public properties are deprecated. 504 | 505 | As of version 0.2.0 all functions return os.Error. If the command succeeded the return value will be nil, otherwise it will contain the error. 506 | If returned value is not nil then MySQL error code and description can then be retrieved from Errno and Error properties for additional info/debugging. 507 | Prepared statements have their own copy of the Errno and Error properties. 508 | Generated errors attempt to follow MySQL protocol/specifications as closely as possible. 509 | 510 | 511 | Migration guide from 0.2 - 0.3 512 | ------------------------------ 513 | 514 | 1. Constructors 515 | 516 | The original 'New()' method has gone and in it's place are a number of Go-style constructors: NewClient, DialTCP and DialUnix. This offers greater flexibility (such as the ability to connect to localhost via TCP) and simplified usage. If features such as logging are required or the 4.0 protocol, the NewClient constructor can be used to set options before connect, then Connect can be called as with previous versions. 517 | 518 | 2. Queries 519 | 520 | As the Query method no longer returns a result set an extra step is needed here, either UseResult or StoreResult. UseResult allows you to read rows 1 at a time from the buffer which can reduce memory requirements for large result sets considerably. It is currently also a requirement to call FreeResult once you have finished with a result set, this may change with a later release. The MultiQuery method has been removed as the Query method can now support multiple queries, when using this feature you should check MoreResults and use NextResult to move to the next result set. You must free the previous result before calling NextResult, although again this may change later as we feel it would be more intuitive to free the result automatically. 521 | 522 | 3. Statements 523 | 524 | The main changes for statements are that you must now bind the result parameters before fetching a row, this means type conversion is automated and there is no longer a need for type assertions on the rows. The bound result parameters can be individual vars or struct properties or really anything that can be passed as a pointer. 525 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | type command byte 9 | 10 | const ( 11 | COM_QUIT command = iota + 1 12 | COM_INIT_DB 13 | COM_QUERY 14 | COM_FIELD_LIST 15 | COM_CREATE_DB 16 | COM_DROP_DB 17 | COM_REFRESH 18 | COM_SHUTDOWN 19 | COM_STATISTICS 20 | COM_PROCESS_INFO 21 | COM_CONNECT 22 | COM_PROCESS_KILL 23 | COM_DEBUG 24 | COM_PING 25 | COM_TIME 26 | COM_DELAYED_INSERT 27 | COM_CHANGE_USER 28 | COM_BINLOG_DUMP 29 | COM_TABLE_DUMP 30 | COM_CONNECT_OUT 31 | COM_REGISTER_SLAVE 32 | COM_STMT_PREPARE 33 | COM_STMT_EXECUTE 34 | COM_STMT_SEND_LONG_DATA 35 | COM_STMT_CLOSE 36 | COM_STMT_RESET 37 | COM_SET_OPTION 38 | COM_STMT_FETCH 39 | ) 40 | 41 | type ClientFlag uint32 42 | 43 | const ( 44 | CLIENT_LONG_PASSWORD ClientFlag = 1 << iota 45 | CLIENT_FOUND_ROWS 46 | CLIENT_LONG_FLAG 47 | CLIENT_CONNECT_WITH_DB 48 | CLIENT_NO_SCHEMA 49 | CLIENT_COMPRESS 50 | CLIENT_ODBC 51 | CLIENT_LOCAL_FILES 52 | CLIENT_IGNORE_SPACE 53 | CLIENT_PROTOCOL_41 54 | CLIENT_INTERACTIVE 55 | CLIENT_SSL 56 | CLIENT_IGNORE_SIGPIPE 57 | CLIENT_TRANSACTIONS 58 | CLIENT_RESERVED 59 | CLIENT_SECURE_CONN 60 | CLIENT_MULTI_STATEMENTS 61 | CLIENT_MULTI_RESULTS 62 | ) 63 | 64 | type ServerStatus uint16 65 | 66 | const ( 67 | SERVER_STATUS_IN_TRANS ServerStatus = 1 << iota 68 | SERVER_STATUS_AUTOCOMMIT 69 | ) 70 | 71 | const ( 72 | SERVER_MORE_RESULTS_EXISTS ServerStatus = 1 << (iota + 3) 73 | SERVER_QUERY_NO_GOOD_INDEX_USED 74 | SERVER_QUERY_NO_INDEX_USED 75 | SERVER_STATUS_CURSOR_EXISTS 76 | SERVER_STATUS_LAST_ROW_SENT 77 | SERVER_STATUS_DB_DROPPED 78 | SERVER_STATUS_NO_BACKSLASH_ESCAPES 79 | SERVER_STATUS_METADATA_CHANGED 80 | ) 81 | 82 | type FieldType byte 83 | 84 | const ( 85 | FIELD_TYPE_DECIMAL FieldType = iota 86 | FIELD_TYPE_TINY 87 | FIELD_TYPE_SHORT 88 | FIELD_TYPE_LONG 89 | FIELD_TYPE_FLOAT 90 | FIELD_TYPE_DOUBLE 91 | FIELD_TYPE_NULL 92 | FIELD_TYPE_TIMESTAMP 93 | FIELD_TYPE_LONGLONG 94 | FIELD_TYPE_INT24 95 | FIELD_TYPE_DATE 96 | FIELD_TYPE_TIME 97 | FIELD_TYPE_DATETIME 98 | FIELD_TYPE_YEAR 99 | FIELD_TYPE_NEWDATE 100 | FIELD_TYPE_VARCHAR 101 | FIELD_TYPE_BIT 102 | ) 103 | 104 | const ( 105 | FIELD_TYPE_NEWDECIMAL FieldType = iota + 0xf6 106 | FIELD_TYPE_ENUM 107 | FIELD_TYPE_SET 108 | FIELD_TYPE_TINY_BLOB 109 | FIELD_TYPE_MEDIUM_BLOB 110 | FIELD_TYPE_LONG_BLOB 111 | FIELD_TYPE_BLOB 112 | FIELD_TYPE_VAR_STRING 113 | FIELD_TYPE_STRING 114 | FIELD_TYPE_GEOMETRY 115 | ) 116 | 117 | type FieldFlag uint16 118 | 119 | const ( 120 | FLAG_NOT_NULL FieldFlag = 1 << iota 121 | FLAG_PRI_KEY 122 | FLAG_UNIQUE_KEY 123 | FLAG_MULTIPLE_KEY 124 | FLAG_BLOB 125 | FLAG_UNSIGNED 126 | FLAG_ZEROFILL 127 | FLAG_BINARY 128 | FLAG_ENUM 129 | FLAG_AUTO_INCREMENT 130 | FLAG_TIMESTAMP 131 | FLAG_SET 132 | FLAG_UNKNOWN_1 133 | FLAG_UNKNOWN_2 134 | FLAG_UNKNOWN_3 135 | FLAG_UNKNOWN_4 136 | ) 137 | 138 | type ExecuteFlag byte 139 | 140 | const ( 141 | CURSOR_TYPE_NO_CURSOR ExecuteFlag = 0 142 | CURSOR_TYPE_READ_ONLY ExecuteFlag = 1 << iota 143 | CURSOR_TYPE_FOR_UPDATE 144 | CURSOR_TYPE_SCROLLABLE 145 | ) 146 | 147 | type Refresh byte 148 | 149 | const ( 150 | REFRESH_GRANT Refresh = 1 << iota 151 | REFRESH_LOG 152 | REFRESH_TABLES 153 | REFRESH_HOSTS 154 | REFRESH_STATUS 155 | REFRESH_THREADS 156 | REFRESH_SLAVE 157 | REFRESH_MASTER 158 | ) 159 | 160 | type Shutdown byte 161 | 162 | const ( 163 | SHUTDOWN_DEFAULT Shutdown = iota 164 | SHUTDOWN_WAIT_CONNECTIONS 165 | SHUTDOWN_WAIT_TRANSACTIONS 166 | SHUTDOWN_WAIT_UPDATES Shutdown = 0x08 167 | SHUTDOWN_WAIT_ALL_BUFFERS Shutdown = 0x10 168 | SHUTDOWN_WAIT_CRITICAL_BUFFERS Shutdown = 0x11 169 | KILL_QUERY Shutdown = 0xfe 170 | KILL_CONNECTION Shutdown = 0xff 171 | ) 172 | -------------------------------------------------------------------------------- /convert.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "math" 10 | "os" 11 | "strconv" 12 | ) 13 | 14 | // bytes to int 15 | func btoi(b []byte) int { 16 | return int(btoui(b)) 17 | } 18 | 19 | // int to bytes 20 | func itob(n int) []byte { 21 | return uitob(uint(n)) 22 | } 23 | 24 | // bytes to uint 25 | func btoui(b []byte) (n uint) { 26 | for i := uint8(0); i < uint8(strconv.IntSize)/8; i++ { 27 | n |= uint(b[i]) << (i * 8) 28 | } 29 | return 30 | } 31 | 32 | // uint to bytes 33 | func uitob(n uint) (b []byte) { 34 | b = make([]byte, strconv.IntSize/8) 35 | for i := uint8(0); i < uint8(strconv.IntSize)/8; i++ { 36 | b[i] = byte(n >> (i * 8)) 37 | } 38 | return 39 | } 40 | 41 | // bytes to int16 42 | func btoi16(b []byte) int16 { 43 | return int16(btoui16(b)) 44 | } 45 | 46 | // int16 to bytes 47 | func i16tob(n int16) []byte { 48 | return ui16tob(uint16(n)) 49 | } 50 | 51 | // bytes to uint16 52 | func btoui16(b []byte) (n uint16) { 53 | n |= uint16(b[0]) 54 | n |= uint16(b[1]) << 8 55 | return 56 | } 57 | 58 | // uint16 to bytes 59 | func ui16tob(n uint16) (b []byte) { 60 | b = make([]byte, 2) 61 | b[0] = byte(n) 62 | b[1] = byte(n >> 8) 63 | return 64 | } 65 | 66 | // bytes to int24 67 | func btoi24(b []byte) (n int32) { 68 | u := btoui24(b) 69 | if u&0x800000 != 0 { 70 | u |= 0xff000000 71 | } 72 | n = int32(u) 73 | return 74 | } 75 | 76 | // int24 to bytes 77 | func i24tob(n int32) []byte { 78 | return ui24tob(uint32(n)) 79 | } 80 | 81 | // bytes to uint24 82 | func btoui24(b []byte) (n uint32) { 83 | for i := uint8(0); i < 3; i++ { 84 | n |= uint32(b[i]) << (i * 8) 85 | } 86 | return 87 | } 88 | 89 | // uint24 to bytes 90 | func ui24tob(n uint32) (b []byte) { 91 | b = make([]byte, 3) 92 | for i := uint8(0); i < 3; i++ { 93 | b[i] = byte(n >> (i * 8)) 94 | } 95 | return 96 | } 97 | 98 | // bytes to int32 99 | func btoi32(b []byte) int32 { 100 | return int32(btoui32(b)) 101 | } 102 | 103 | // int32 to bytes 104 | func i32tob(n int32) []byte { 105 | return ui32tob(uint32(n)) 106 | } 107 | 108 | // bytes to uint32 109 | func btoui32(b []byte) (n uint32) { 110 | for i := uint8(0); i < 4; i++ { 111 | n |= uint32(b[i]) << (i * 8) 112 | } 113 | return 114 | } 115 | 116 | // uint32 to bytes 117 | func ui32tob(n uint32) (b []byte) { 118 | b = make([]byte, 4) 119 | for i := uint8(0); i < 4; i++ { 120 | b[i] = byte(n >> (i * 8)) 121 | } 122 | return 123 | } 124 | // bytes to int64 125 | func btoi64(b []byte) int64 { 126 | return int64(btoui64(b)) 127 | } 128 | 129 | // int64 to bytes 130 | func i64tob(n int64) []byte { 131 | return ui64tob(uint64(n)) 132 | } 133 | 134 | // bytes to uint64 135 | func btoui64(b []byte) (n uint64) { 136 | for i := uint8(0); i < 8; i++ { 137 | n |= uint64(b[i]) << (i * 8) 138 | } 139 | return 140 | } 141 | 142 | // uint64 to bytes 143 | func ui64tob(n uint64) (b []byte) { 144 | b = make([]byte, 8) 145 | for i := uint8(0); i < 8; i++ { 146 | b[i] = byte(n >> (i * 8)) 147 | } 148 | return 149 | } 150 | 151 | // bytes to float32 152 | func btof32(b []byte) float32 { 153 | return math.Float32frombits(btoui32(b)) 154 | } 155 | 156 | // float32 to bytes 157 | func f32tob(f float32) []byte { 158 | return ui32tob(math.Float32bits(f)) 159 | } 160 | 161 | // bytes to float64 162 | func btof64(b []byte) float64 { 163 | return math.Float64frombits(btoui64(b)) 164 | } 165 | 166 | // float64 to bytes 167 | func f64tob(f float64) []byte { 168 | return ui64tob(math.Float64bits(f)) 169 | } 170 | 171 | // bytes to length 172 | func btolcb(b []byte) (num uint64, n int, err os.Error) { 173 | switch { 174 | // 0-250 = value of first byte 175 | case b[0] <= 250: 176 | num = uint64(b[0]) 177 | n = 1 178 | return 179 | // 251 column value = NULL 180 | case b[0] == 251: 181 | num = 0 182 | n = 1 183 | return 184 | // 252 following 2 = value of following 16-bit word 185 | case b[0] == 252: 186 | n = 3 187 | // 253 following 3 = value of following 24-bit word 188 | case b[0] == 253: 189 | n = 4 190 | // 254 following 8 = value of following 64-bit word 191 | case b[0] == 254: 192 | n = 9 193 | } 194 | // Check there are enough bytes 195 | if len(b) < n { 196 | err = os.EOF 197 | return 198 | } 199 | // Get uint64 200 | t := make([]byte, 8) 201 | copy(t, b[1:n]) 202 | num = btoui64(t) 203 | return 204 | } 205 | 206 | // length to bytes 207 | func lcbtob(n uint64) (b []byte) { 208 | switch { 209 | // <= 250 = 1 byte 210 | case n <= 250: 211 | b = []byte{byte(n)} 212 | // <= 0xffff = 252 + 2 bytes 213 | case n <= 0xffff: 214 | b = []byte{0xfc, byte(n), byte(n >> 8)} 215 | // <= 0xffffff = 253 + 3 bytes 216 | case n <= 0xffffff: 217 | b = []byte{0xfd, byte(n), byte(n >> 8), byte(n >> 16)} 218 | // Due to max packet size the 8 byte version is never actually used so is ommited 219 | } 220 | return 221 | } 222 | 223 | // any to uint64 224 | func atoui64(i interface{}) (n uint64) { 225 | switch t := i.(type) { 226 | case int64: 227 | n = uint64(t) 228 | case uint64: 229 | return t 230 | case string: 231 | // Convert to int64 first for signing bit 232 | in, err := strconv.Atoi64(t) 233 | if err != nil { 234 | panic("Invalid string for integer conversion") 235 | } 236 | n = uint64(in) 237 | default: 238 | panic("Not a numeric type") 239 | } 240 | return 241 | } 242 | 243 | // any to float64 244 | func atof64(i interface{}) (f float64) { 245 | switch t := i.(type) { 246 | case float32: 247 | f = float64(t) 248 | case float64: 249 | return t 250 | case string: 251 | var err os.Error 252 | f, err = strconv.Atof64(t) 253 | if err != nil { 254 | panic("Invalid string for floating point conversion") 255 | } 256 | default: 257 | panic("Not a floating point type") 258 | } 259 | return 260 | } 261 | 262 | // any to string 263 | func atos(i interface{}) (s string) { 264 | switch t := i.(type) { 265 | case int64: 266 | s = strconv.Itoa64(t) 267 | case uint64: 268 | s = strconv.Uitoa64(t) 269 | case float32: 270 | s = strconv.Ftoa32(t, 'f', -1) 271 | case float64: 272 | s = strconv.Ftoa64(t, 'f', -1) 273 | case []byte: 274 | s = string(t) 275 | case Date: 276 | return t.String() 277 | case Time: 278 | return t.String() 279 | case DateTime: 280 | return t.String() 281 | case string: 282 | return t 283 | default: 284 | panic("Not a string or compatible type") 285 | } 286 | return 287 | } 288 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import "fmt" 9 | 10 | // Client error types 11 | type Errno int 12 | type Error string 13 | 14 | const ( 15 | CR_UNKNOWN_ERROR Errno = 2000 16 | CR_UNKNOWN_ERROR_STR Error = "Unknown MySQL error" 17 | CR_SOCKET_CREATE_ERROR Errno = 2001 18 | CR_SOCKET_CREATE_ERROR_STR Error = "Can't create UNIX socket (%d)" 19 | CR_CONNECTION_ERROR Errno = 2002 20 | CR_CONNECTION_ERROR_STR Error = "Can't connect to local MySQL server through socket '%s'" 21 | CR_CONN_HOST_ERROR Errno = 2003 22 | CR_CONN_HOST_ERROR_STR Error = "Can't connect to MySQL server on '%s'" 23 | CR_IPSOCK_ERROR Errno = 2004 24 | CR_IPSOCK_ERROR_STR Error = "Can't create TCP/IP socket (%d)" 25 | CR_UNKNOWN_HOST Errno = 2005 26 | CR_UNKNOWN_HOST_STR Error = "Uknown MySQL server host '%s' (%d)" 27 | CR_SERVER_GONE_ERROR Errno = 2006 28 | CR_SERVER_GONE_ERROR_STR Error = "MySQL server has gone away" 29 | CR_VERSION_ERROR Errno = 2007 30 | CR_VERSION_ERROR_STR Error = "Protocol mismatch; server version = %d, client version = %d" 31 | CR_OUT_OF_MEMORY Errno = 2008 32 | CR_OUT_OF_MEMORY_STR Error = "MySQL client ran out of memory" 33 | CR_WRONG_HOST_INFO Errno = 2009 34 | CR_WRONG_HOST_INFO_STR Error = "Wrong host info" 35 | CR_LOCALHOST_CONNECTION Errno = 2010 36 | CR_LOCALHOST_CONNECTION_STR Error = "Localhost via UNIX socket" 37 | CR_TCP_CONNECTION Errno = 2011 38 | CR_TCP_CONNECTION_STR Error = "%s via TCP/IP" 39 | CR_SERVER_HANDSHAKE_ERR Errno = 2012 40 | CR_SERVER_HANDSHAKE_ERR_STR Error = "Error in server handshake" 41 | CR_SERVER_LOST Errno = 2013 42 | CR_SERVER_LOST_STR Error = "Lost connection to MySQL server during query" 43 | CR_COMMANDS_OUT_OF_SYNC Errno = 2014 44 | CR_COMMANDS_OUT_OF_SYNC_STR Error = "Commands out of sync; you can't run this command now" 45 | CR_NAMEDPIPE_CONNECTION Errno = 2015 46 | CR_NAMEDPIPE_CONNECTION_STR Error = "Named pipe: %s" 47 | CR_NAMEDPIPEWAIT_ERROR Errno = 2016 48 | CR_NAMEDPIPEWAIT_ERROR_STR Error = "Can't wait for named pipe to host: %s pipe: %s (%lu)" 49 | CR_NAMEDPIPEOPEN_ERROR Errno = 2017 50 | CR_NAMEDPIPEOPEN_ERROR_STR Error = "Can't open named pipe to host: %s pipe: %s (%lu)" 51 | CR_NAMEDPIPESETSTATE_ERROR Errno = 2018 52 | CR_NAMEDPIPESETSTATE_ERROR_STR Error = "Can't set state of named pipe to host: %s pipe: %s (%lu)" 53 | CR_CANT_READ_CHARSET Errno = 2019 54 | CR_CANT_READ_CHARSET_STR Error = "Can't initialize character set %s (path: %s)" 55 | CR_NET_PACKET_TOO_LARGE Errno = 2020 56 | CR_NET_PACKET_TOO_LARGE_STR Error = "Got packet bigger than 'max_allowed_packet' bytes" 57 | CR_EMBEDDED_CONNECTION Errno = 2021 58 | CR_EMBEDDED_CONNECTION_STR Error = "Embedded server" 59 | CR_PROBE_SLAVE_STATUS Errno = 2022 60 | CR_PROBE_SLAVE_STATUS_STR Error = "Error on SHOW SLAVE STATUS:" 61 | CR_PROBE_SLAVE_HOSTS Errno = 2023 62 | CR_PROBE_SLAVE_HOSTS_STR Error = "Error on SHOW SLAVE HOSTS:" 63 | CR_PROBE_SLAVE_CONNECT Errno = 2024 64 | CR_PROBE_SLAVE_CONNECT_STR Error = "Error connecting to slave:" 65 | CR_PROBE_MASTER_CONNECT Errno = 2025 66 | CR_PROBE_MASTER_CONNECT_STR Error = "Error connecting to master:" 67 | CR_SSL_CONNECTION_ERROR Errno = 2026 68 | CR_SSL_CONNECTION_ERROR_STR Error = "SSL connection error" 69 | CR_MALFORMED_PACKET Errno = 2027 70 | CR_MALFORMED_PACKET_STR Error = "Malformed Packet" 71 | CR_WRONG_LICENSE Errno = 2028 72 | CR_WRONG_LICENSE_STR Error = "This client library is licensed only for use with MySQL servers having '%s' license" 73 | CR_NULL_POINTER Errno = 2029 74 | CR_NULL_POINTER_STR Error = "Invalid use of null pointer" 75 | CR_NO_PREPARE_STMT Errno = 2030 76 | CR_NO_PREPARE_STMT_STR Error = "Statement not prepared" 77 | CR_PARAMS_NOT_BOUND Errno = 2031 78 | CR_PARAMS_NOT_BOUND_STR Error = "No data supplied for parameters in prepared statement" 79 | CR_DATA_TRUNCATED Errno = 2032 80 | CR_DATA_TRUNCATED_STR Error = "Data truncated" 81 | CR_NO_PARAMETERS_EXISTS Errno = 2033 82 | CR_NO_PARAMETERS_EXISTS_STR Error = "No parameters exist in the statement" 83 | CR_INVALID_PARAMETER_NO Errno = 2034 84 | CR_INVALID_PARAMETER_NO_STR Error = "Invalid parameter number" 85 | CR_INVALID_BUFFER_USE Errno = 2035 86 | CR_INVALID_BUFFER_USE_STR Error = "Can't send long data for non-string/non-binary data types (parameter: %d)" 87 | CR_UNSUPPORTED_PARAM_TYPE Errno = 2036 88 | CR_UNSUPPORTED_PARAM_TYPE_STR Error = "Using unsupported parameter type: %s (parameter: %d)" 89 | CR_CONN_UNKNOW_PROTOCOL Errno = 2047 90 | CR_CONN_UNKNOW_PROTOCOL_STR Error = "Wrong or unknown protocol" 91 | CR_SECURE_AUTH Errno = 2049 92 | CR_SECURE_AUTH_STR Error = "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" 93 | CR_FETCH_CANCELED Errno = 2050 94 | CR_FETCH_CANCELED_STR Error = "Row retrieval was canceled by mysql_stmt_close() call" 95 | CR_NO_DATA Errno = 2051 96 | CR_NO_DATA_STR Error = "Attempt to read column without prior row fetch" 97 | CR_NO_STMT_METADATA Errno = 2052 98 | CR_NO_STMT_METADATA_STR Error = "Prepared statement contains no metadata" 99 | CR_NO_RESULT_SET Errno = 2053 100 | CR_NO_RESULT_SET_STR Error = "Attempt to read a row while there is no result set associated with the statement" 101 | CR_NOT_IMPLEMENTED Errno = 2054 102 | CR_NOT_IMPLEMENTED_STR Error = "This feature is not implemented yet" 103 | CR_SERVER_LOST_EXTENDED Errno = 2055 104 | CR_SERVER_LOST_EXTENDED_STR Error = "Lost connection to MySQL server at '%s', system error: %d" 105 | CR_STMT_CLOSED Errno = 2056 106 | CR_STMT_CLOSED_STR Error = "Statement closed indirectly because of a preceeding %s() call" 107 | CR_NEW_STMT_METADATA Errno = 2057 108 | CR_NEW_STMT_METADATA_STR Error = "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again" 109 | CR_ALREADY_CONNECTED Errno = 2058 110 | CR_ALREADY_CONNECTED_STR Error = "This handle is already connected" 111 | CR_AUTH_PLUGIN_CANNOT_LOAD Errno = 2059 112 | CR_AUTH_PLUGIN_CANNOT_LOAD_STR Error = "Authentication plugin '%s' cannot be loaded: %s" 113 | ) 114 | 115 | // Client error struct 116 | type ClientError struct { 117 | Errno Errno 118 | Error Error 119 | } 120 | 121 | // Convert to string 122 | func (e *ClientError) String() string { 123 | return fmt.Sprintf("#%d %s", e.Errno, e.Error) 124 | } 125 | 126 | // Server error struct 127 | type ServerError struct { 128 | Errno Errno 129 | Error Error 130 | } 131 | 132 | // Convert to string 133 | func (e *ServerError) String() string { 134 | return fmt.Sprintf("#%d %s", e.Errno, e.Error) 135 | } 136 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "os" 10 | "strconv" 11 | ) 12 | 13 | // OK packet handler 14 | func handleOK(p *packetOK, c *Client, a, i *uint64, w *uint16) (err os.Error) { 15 | // Log OK result 16 | c.log(1, "[%d] Received OK packet", p.sequence) 17 | // Check sequence 18 | err = c.checkSequence(p.sequence) 19 | if err != nil { 20 | return 21 | } 22 | // Store packet data 23 | *a = p.affectedRows 24 | *i = p.insertId 25 | *w = p.warningCount 26 | c.serverStatus = ServerStatus(p.serverStatus) 27 | // Full logging [level 3] 28 | if c.LogLevel > 2 { 29 | c.logStatus() 30 | } 31 | return 32 | } 33 | 34 | // Error packet handler 35 | func handleError(p *packetError, c *Client) (err os.Error) { 36 | // Log error result 37 | c.log(1, "[%d] Received error packet", p.sequence) 38 | // Check sequence 39 | err = c.checkSequence(p.sequence) 40 | if err != nil { 41 | return 42 | } 43 | // Check and unset more results flag 44 | // @todo maybe serverStatus should just be zeroed? 45 | if c.MoreResults() { 46 | c.serverStatus ^= SERVER_MORE_RESULTS_EXISTS 47 | } 48 | // Return error 49 | return &ServerError{Errno(p.errno), Error(p.error)} 50 | } 51 | 52 | // EOF packet handler 53 | func handleEOF(p *packetEOF, c *Client) (err os.Error) { 54 | // Log EOF result 55 | c.log(1, "[%d] Received EOF packet", p.sequence) 56 | // Check sequence 57 | err = c.checkSequence(p.sequence) 58 | if err != nil { 59 | return 60 | } 61 | // Store packet data 62 | if p.useStatus { 63 | c.serverStatus = ServerStatus(p.serverStatus) 64 | // Full logging [level 3] 65 | if c.LogLevel > 2 { 66 | c.logStatus() 67 | } 68 | } 69 | return 70 | } 71 | 72 | // Result set packet handler 73 | func handleResultSet(p *packetResultSet, c *Client, r *Result) (err os.Error) { 74 | // Log error result 75 | c.log(1, "[%d] Received result set packet", p.sequence) 76 | // Check sequence 77 | err = c.checkSequence(p.sequence) 78 | if err != nil { 79 | return 80 | } 81 | // Assign field count 82 | r.fieldCount = p.fieldCount 83 | return 84 | } 85 | 86 | // Field packet handler 87 | func handleField(p *packetField, c *Client, r *Result) (err os.Error) { 88 | // Log field result 89 | c.log(1, "[%d] Received field packet", p.sequence) 90 | // Check sequence 91 | err = c.checkSequence(p.sequence) 92 | if err != nil { 93 | return 94 | } 95 | // Check if packet needs to be stored 96 | if r == nil || r.mode == RESULT_FREE { 97 | return 98 | } 99 | // Apppend new field 100 | r.fields = append(r.fields, &Field{ 101 | Database: p.database, 102 | Table: p.table, 103 | Name: p.name, 104 | Length: p.length, 105 | Type: FieldType(p.fieldType), 106 | Flags: FieldFlag(p.flags), 107 | Decimals: p.decimals, 108 | }) 109 | return 110 | } 111 | 112 | // Row packet hander 113 | func handleRow(p *packetRowData, c *Client, r *Result) (err os.Error) { 114 | // Log field result 115 | c.log(1, "[%d] Received row packet", p.sequence) 116 | // Check sequence 117 | err = c.checkSequence(p.sequence) 118 | if err != nil { 119 | return 120 | } 121 | // Check if there is a result set 122 | if r == nil || r.mode == RESULT_FREE { 123 | return 124 | } 125 | // Basic type conversion 126 | var row []interface{} 127 | var field interface{} 128 | // Iterate fields to get types 129 | for i, f := range r.fields { 130 | // Check null 131 | if len(p.row[i].([]byte)) ==0 { 132 | field = nil 133 | } else { 134 | switch f.Type { 135 | // Signed/unsigned ints 136 | case FIELD_TYPE_TINY, FIELD_TYPE_SHORT, FIELD_TYPE_YEAR, FIELD_TYPE_INT24, FIELD_TYPE_LONG, FIELD_TYPE_LONGLONG: 137 | if f.Flags&FLAG_UNSIGNED > 0 { 138 | field, err = strconv.Atoui64(string(p.row[i].([]byte))) 139 | } else { 140 | field, err = strconv.Atoi64(string(p.row[i].([]byte))) 141 | } 142 | if err != nil { 143 | return 144 | } 145 | // Floats and doubles 146 | case FIELD_TYPE_FLOAT, FIELD_TYPE_DOUBLE: 147 | field, err = strconv.Atof64(string(p.row[i].([]byte))) 148 | if err != nil { 149 | return 150 | } 151 | // Strings 152 | case FIELD_TYPE_DECIMAL, FIELD_TYPE_NEWDECIMAL, FIELD_TYPE_VARCHAR, FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING: 153 | field = string(p.row[i].([]byte)) 154 | // Anything else 155 | default: 156 | field = p.row[i] 157 | } 158 | } 159 | // Add to row 160 | row = append(row, field) 161 | } 162 | // Stored result 163 | if r.mode == RESULT_STORED { 164 | // Cast and append the row 165 | r.rows = append(r.rows, Row(row)) 166 | } 167 | // Used result 168 | if r.mode == RESULT_USED { 169 | // Only save 1 row, overwrite previous 170 | r.rows = []Row{Row(row)} 171 | } 172 | return 173 | } 174 | 175 | // Prepare OK packet handler 176 | func handlePrepareOK(p *packetPrepareOK, c *Client, s *Statement) (err os.Error) { 177 | // Log result 178 | c.log(1, "[%d] Received prepare OK packet", p.sequence) 179 | // Check sequence 180 | err = c.checkSequence(p.sequence) 181 | if err != nil { 182 | return 183 | } 184 | // Store packet data 185 | s.statementId = p.statementId 186 | s.paramCount = p.paramCount 187 | s.columnCount = uint64(p.columnCount) 188 | s.Warnings = p.warningCount 189 | return 190 | } 191 | 192 | // Parameter packet handler 193 | func handleParam(p *packetParameter, c *Client) (err os.Error) { 194 | // Log result 195 | c.log(1, "[%d] Received parameter packet", p.sequence) 196 | // Check sequence 197 | err = c.checkSequence(p.sequence) 198 | if err != nil { 199 | return 200 | } 201 | // @todo at some point implement this properly if any versions of MySQL are doing so 202 | return 203 | } 204 | 205 | // Binary row packet handler 206 | func handleBinaryRow(p *packetRowBinary, c *Client, r *Result) (err os.Error) { 207 | // Log binary row result 208 | c.log(1, "[%d] Received binary row packet", p.sequence) 209 | // Check sequence 210 | err = c.checkSequence(p.sequence) 211 | if err != nil { 212 | return 213 | } 214 | // Check if there is a result set 215 | if r == nil || r.mode == RESULT_FREE { 216 | return 217 | } 218 | // Read data into fields 219 | var row []interface{} 220 | var field interface{} 221 | // Get null bit map 222 | nc := (r.fieldCount + 9) / 8 223 | nbm := p.data[1 : nc+1] 224 | pos := nc + 1 225 | for i, f := range r.fields { 226 | // Check if field is null 227 | posByte := (i + 2) / 8 228 | posBit := i - (posByte * 8) + 2 229 | if nbm[posByte]&(1< 0 { 238 | field = uint64(p.data[pos]) 239 | } else { 240 | field = int64(p.data[pos]) 241 | } 242 | pos++ 243 | // Small int (16 bit int unsigned or signed) 244 | case FIELD_TYPE_SHORT, FIELD_TYPE_YEAR: 245 | if f.Flags&FLAG_UNSIGNED > 0 { 246 | field = uint64(btoui16(p.data[pos : pos+2])) 247 | } else { 248 | field = int64(btoi16(p.data[pos : pos+2])) 249 | } 250 | pos += 2 251 | // Int (32 bit int unsigned or signed) and medium int which is actually in int32 format 252 | case FIELD_TYPE_LONG, FIELD_TYPE_INT24: 253 | if f.Flags&FLAG_UNSIGNED > 0 { 254 | field = uint64(btoui32(p.data[pos : pos+4])) 255 | } else { 256 | field = int64(btoi32(p.data[pos : pos+4])) 257 | } 258 | pos += 4 259 | // Big int (64 bit int unsigned or signed) 260 | case FIELD_TYPE_LONGLONG: 261 | if f.Flags&FLAG_UNSIGNED > 0 { 262 | field = btoui64(p.data[pos : pos+8]) 263 | } else { 264 | field = btoi64(p.data[pos : pos+8]) 265 | } 266 | pos += 8 267 | // Floats (Single precision floating point, 32 bit signed) 268 | case FIELD_TYPE_FLOAT: 269 | field = btof32(p.data[pos : pos+4]) 270 | pos += 4 271 | // Double (Double precision floating point, 64 bit signed) 272 | case FIELD_TYPE_DOUBLE: 273 | field = btof64(p.data[pos : pos+8]) 274 | pos += 8 275 | // Bit, decimal, strings, blobs etc, all length coded binary strings 276 | case FIELD_TYPE_BIT, FIELD_TYPE_DECIMAL, FIELD_TYPE_NEWDECIMAL, FIELD_TYPE_VARCHAR, 277 | FIELD_TYPE_TINY_BLOB, FIELD_TYPE_MEDIUM_BLOB, FIELD_TYPE_LONG_BLOB, FIELD_TYPE_BLOB, 278 | FIELD_TYPE_VAR_STRING, FIELD_TYPE_STRING, FIELD_TYPE_GEOMETRY: 279 | num, n, err := btolcb(p.data[pos:]) 280 | if err != nil { 281 | return 282 | } 283 | field = p.data[pos+uint64(n) : pos+uint64(n)+num] 284 | pos += uint64(n) + num 285 | // Date (From libmysql/libmysql.c read_binary_datetime) 286 | case FIELD_TYPE_DATE: 287 | num, n, err := btolcb(p.data[pos:]) 288 | if err != nil { 289 | return 290 | } 291 | // New date 292 | d := Date{} 293 | // Check zero 294 | if num == 0 { 295 | field = d 296 | pos++ 297 | break 298 | } 299 | // Year 2 bytes 300 | d.Year = btoui16(p.data[pos+uint64(n) : pos+uint64(n)+2]) 301 | // Month 1 byte 302 | d.Month = p.data[pos+uint64(n)+2] 303 | // Day 1 byte 304 | d.Day = p.data[pos+uint64(n)+3] 305 | field = d 306 | pos += uint64(n) + num 307 | // Time (From libmysql/libmysql.c read_binary_time) 308 | case FIELD_TYPE_TIME: 309 | num, n, err := btolcb(p.data[pos:]) 310 | if err != nil { 311 | return 312 | } 313 | // New time 314 | t := Time{} 315 | // Default zero values 316 | if num == 0 { 317 | field = t 318 | pos++ 319 | break 320 | } 321 | // Hour 1 byte 322 | t.Hour = p.data[pos+6] 323 | // Minute 1 byte 324 | t.Minute = p.data[pos+7] 325 | // Second 1 byte 326 | t.Second = p.data[pos+8] 327 | field = t 328 | pos += uint64(n) + num 329 | // Datetime/Timestamp (From libmysql/libmysql.c read_binary_datetime) 330 | case FIELD_TYPE_TIMESTAMP, FIELD_TYPE_DATETIME: 331 | num, n, err := btolcb(p.data[pos:]) 332 | if err != nil { 333 | return 334 | } 335 | // New datetime 336 | d := DateTime{} 337 | // Check zero 338 | if num == 0 { 339 | field = d 340 | pos++ 341 | break 342 | } 343 | // Year 2 bytes 344 | d.Year = btoui16(p.data[pos+uint64(n) : pos+uint64(n)+2]) 345 | // Month 1 byte 346 | d.Month = p.data[pos+uint64(n)+2] 347 | // Day 1 byte 348 | d.Day = p.data[pos+uint64(n)+3] 349 | // Hour 1 byte 350 | d.Hour = p.data[pos+uint64(n)+4] 351 | // Minute 1 byte 352 | d.Minute = p.data[pos+uint64(n)+5] 353 | // Second 1 byte 354 | d.Second = p.data[pos+uint64(n)+6] 355 | field = d 356 | pos += uint64(n) + num 357 | } 358 | // Add to row 359 | row = append(row, field) 360 | } 361 | // Stored result 362 | if r.mode == RESULT_STORED { 363 | // Cast and append the row 364 | r.rows = append(r.rows, Row(row)) 365 | } 366 | // Used result 367 | if r.mode == RESULT_USED { 368 | // Only save 1 row, overwrite previous 369 | r.rows = []Row{Row(row)} 370 | } 371 | return 372 | } 373 | -------------------------------------------------------------------------------- /mysql.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | // Imports 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "io" 13 | "log" 14 | "os" 15 | "net" 16 | "strings" 17 | "sync" 18 | "time" 19 | ) 20 | 21 | // Constants 22 | const ( 23 | // General 24 | VERSION = "0.3.2" 25 | DEFAULT_PORT = "3306" 26 | DEFAULT_SOCKET = "/var/run/mysqld/mysqld.sock" 27 | MAX_PACKET_SIZE = 1<<24 - 1 28 | PROTOCOL_41 = 41 29 | PROTOCOL_40 = 40 30 | DEFAULT_PROTOCOL = PROTOCOL_41 31 | 32 | // Connection types 33 | TCP = "tcp" 34 | UNIX = "unix" 35 | 36 | // Log methods 37 | LOG_SCREEN = 0x0 38 | LOG_FILE = 0x1 39 | 40 | // Result storage methods 41 | RESULT_UNUSED = 0x0 42 | RESULT_STORED = 0x1 43 | RESULT_USED = 0x2 44 | RESULT_FREE = 0x3 45 | ) 46 | 47 | // Client struct 48 | type Client struct { 49 | // Mutex for thread safety 50 | sync.Mutex 51 | 52 | // Logging 53 | LogLevel uint8 54 | LogType uint8 55 | LogFile *os.File 56 | 57 | // Credentials 58 | network string 59 | raddr string 60 | user string 61 | passwd string 62 | dbname string 63 | 64 | // Connection 65 | conn io.ReadWriteCloser 66 | r *reader 67 | w *writer 68 | connected bool 69 | Reconnect bool 70 | 71 | // Sequence 72 | protocol uint8 73 | sequence uint8 74 | 75 | // Server settings 76 | serverVersion string 77 | serverProtocol uint8 78 | serverFlags ClientFlag 79 | serverCharset uint8 80 | serverStatus ServerStatus 81 | scrambleBuff []byte 82 | 83 | // Result 84 | AffectedRows uint64 85 | LastInsertId uint64 86 | Warnings uint16 87 | result *Result 88 | } 89 | 90 | // Create new client 91 | func NewClient(protocol ...uint8) (c *Client) { 92 | if len(protocol) == 0 { 93 | protocol = make([]uint8, 1) 94 | protocol[0] = DEFAULT_PROTOCOL 95 | } 96 | c = &Client{ 97 | protocol: protocol[0], 98 | } 99 | return 100 | } 101 | 102 | // Connect to server via TCP 103 | func DialTCP(raddr, user, passwd string, dbname ...string) (c *Client, err os.Error) { 104 | c = NewClient(DEFAULT_PROTOCOL) 105 | // Add port if not set 106 | if strings.Index(raddr, ":") == -1 { 107 | raddr += ":" + DEFAULT_PORT 108 | } 109 | // Connect to server 110 | err = c.Connect(TCP, raddr, user, passwd, dbname...) 111 | return 112 | } 113 | 114 | // Connect to server via unix socket 115 | func DialUnix(raddr, user, passwd string, dbname ...string) (c *Client, err os.Error) { 116 | c = NewClient(DEFAULT_PROTOCOL) 117 | // Use default socket if socket is empty 118 | if raddr == "" { 119 | raddr = DEFAULT_SOCKET 120 | } 121 | // Connect to server 122 | err = c.Connect(UNIX, raddr, user, passwd, dbname...) 123 | return 124 | } 125 | 126 | // Connect to the server 127 | func (c *Client) Connect(network, raddr, user, passwd string, dbname ...string) (err os.Error) { 128 | // Log connect 129 | c.log(1, "=== Begin connect ===") 130 | // Check not already connected 131 | if c.checkConn() { 132 | return &ClientError{CR_ALREADY_CONNECTED, CR_ALREADY_CONNECTED_STR} 133 | } 134 | // Reset client 135 | c.reset() 136 | // Store connection credentials 137 | c.network = network 138 | c.raddr = raddr 139 | c.user = user 140 | c.passwd = passwd 141 | if len(dbname) > 0 { 142 | c.dbname = dbname[0] 143 | } 144 | // Call connect 145 | err = c.connect() 146 | if err != nil { 147 | return 148 | } 149 | // Set connected 150 | c.connected = true 151 | return 152 | } 153 | 154 | // Close connection to server 155 | func (c *Client) Close() (err os.Error) { 156 | // Log close 157 | c.log(1, "=== Begin close ===") 158 | // Check connection 159 | if !c.checkConn() { 160 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 161 | } 162 | // Reset client 163 | c.reset() 164 | // Send close command 165 | c.command(COM_QUIT) 166 | // Close connection 167 | c.conn.Close() 168 | // Log disconnect 169 | c.log(1, "Disconnected") 170 | // Set connected 171 | c.connected = false 172 | return 173 | } 174 | 175 | // Change the current database 176 | func (c *Client) ChangeDb(dbname string) (err os.Error) { 177 | // Auto reconnect 178 | defer func() { 179 | if err != nil && c.checkNet(err) && c.Reconnect { 180 | c.log(1, "!!! Lost connection to server !!!") 181 | c.connected = false 182 | err = c.reconnect() 183 | if err == nil { 184 | err = c.ChangeDb(dbname) 185 | } 186 | } 187 | }() 188 | // Log changeDb 189 | c.log(1, "=== Begin change db to '%s' ===", dbname) 190 | // Pre-run checks 191 | if !c.checkConn() || c.checkResult() { 192 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 193 | } 194 | // Reset client 195 | c.reset() 196 | // Send close command 197 | err = c.command(COM_INIT_DB, dbname) 198 | if err != nil { 199 | return 200 | } 201 | // Read result from server 202 | c.sequence++ 203 | _, err = c.getResult(PACKET_OK | PACKET_ERROR) 204 | return 205 | } 206 | 207 | // Send a query/queries to the server 208 | func (c *Client) Query(sql string) (err os.Error) { 209 | // Auto reconnect 210 | defer func() { 211 | if err != nil && c.checkNet(err) && c.Reconnect { 212 | c.log(1, "!!! Lost connection to server !!!") 213 | c.connected = false 214 | err = c.reconnect() 215 | if err == nil { 216 | err = c.Query(sql) 217 | } 218 | } 219 | }() 220 | // Log query 221 | c.log(1, "=== Begin query '%s' ===", sql) 222 | // Pre-run checks 223 | if !c.checkConn() || c.checkResult() { 224 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 225 | } 226 | // Reset client 227 | c.reset() 228 | // Send query command 229 | err = c.command(COM_QUERY, sql) 230 | if err != nil { 231 | return 232 | } 233 | // Read result from server 234 | c.sequence++ 235 | _, err = c.getResult(PACKET_OK | PACKET_ERROR | PACKET_RESULT) 236 | if err != nil || c.result == nil { 237 | return 238 | } 239 | // Store fields 240 | err = c.getFields() 241 | return 242 | } 243 | 244 | // Fetch all rows for a result and store it, returning the result set 245 | func (c *Client) StoreResult() (result *Result, err os.Error) { 246 | // Auto reconnect 247 | defer func() { 248 | err = c.simpleReconnect(err) 249 | }() 250 | // Log store result 251 | c.log(1, "=== Begin store result ===") 252 | // Check result 253 | if !c.checkResult() { 254 | return nil, &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 255 | } 256 | // Check if result already used/stored 257 | if c.result.mode != RESULT_UNUSED { 258 | return nil, &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 259 | } 260 | // Set storage mode 261 | c.result.mode = RESULT_STORED 262 | // Store all rows 263 | err = c.getAllRows() 264 | if err != nil { 265 | return 266 | } 267 | c.result.allRead = true 268 | return c.result, nil 269 | } 270 | 271 | // Use a result set, does not store rows 272 | func (c *Client) UseResult() (result *Result, err os.Error) { 273 | // Auto reconnect 274 | defer func() { 275 | err = c.simpleReconnect(err) 276 | }() 277 | // Log use result 278 | c.log(1, "=== Begin use result ===") 279 | // Check result 280 | if !c.checkResult() { 281 | return nil, &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 282 | } 283 | // Check if result already used/stored 284 | if c.result.mode != RESULT_UNUSED { 285 | return nil, &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 286 | } 287 | // Set storage mode 288 | c.result.mode = RESULT_USED 289 | return c.result, nil 290 | } 291 | 292 | // Free the current result 293 | func (c *Client) FreeResult() (err os.Error) { 294 | // Auto reconnect 295 | defer func() { 296 | err = c.simpleReconnect(err) 297 | }() 298 | // Log free result 299 | c.log(1, "=== Begin free result ===") 300 | // Check result 301 | if !c.checkResult() { 302 | return &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 303 | } 304 | // Check for unread rows 305 | if !c.result.allRead { 306 | // Read all rows 307 | err = c.getAllRows() 308 | if err != nil { 309 | return 310 | } 311 | } 312 | // Reset some of the properties to ensure any pointers are "destroyed" 313 | c.result.c = nil 314 | c.result.fieldCount = 0 315 | c.result.fieldPos = 0 316 | c.result.fields = nil 317 | c.result.rowPos = 0 318 | c.result.rows = nil 319 | c.result.mode = RESULT_UNUSED 320 | c.result.allRead = false 321 | // Unset the result 322 | c.result = nil 323 | return 324 | } 325 | 326 | // Check if more results are available 327 | func (c *Client) MoreResults() bool { 328 | return c.serverStatus&SERVER_MORE_RESULTS_EXISTS > 0 329 | } 330 | 331 | // Move to the next available result 332 | func (c *Client) NextResult() (more bool, err os.Error) { 333 | // Auto reconnect 334 | defer func() { 335 | err = c.simpleReconnect(err) 336 | }() 337 | // Log next result 338 | c.log(1, "=== Begin next result ===") 339 | // Pre-run checks 340 | if !c.checkConn() || c.checkResult() { 341 | return false, &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 342 | } 343 | // Check for more results 344 | more = c.MoreResults() 345 | if !more { 346 | return 347 | } 348 | // Read result from server 349 | c.sequence++ 350 | _, err = c.getResult(PACKET_OK | PACKET_ERROR | PACKET_RESULT) 351 | // Store fields 352 | err = c.getFields() 353 | return 354 | } 355 | 356 | // Set autocommit 357 | func (c *Client) SetAutoCommit(state bool) (err os.Error) { 358 | // Log set autocommit 359 | c.log(1, "=== Begin set autocommit ===") 360 | // Use set autocommit query 361 | sql := "set autocommit=" 362 | if state { 363 | sql += "1" 364 | } else { 365 | sql += "0" 366 | } 367 | return c.Query(sql) 368 | } 369 | 370 | // Start a transaction 371 | func (c *Client) Start() (err os.Error) { 372 | // Log start transaction 373 | c.log(1, "=== Begin start transaction ===") 374 | // Use start transaction query 375 | return c.Query("start transaction") 376 | } 377 | 378 | // Commit a transaction 379 | func (c *Client) Commit() (err os.Error) { 380 | // Log commit 381 | c.log(1, "=== Begin commit ===") 382 | // Use commit query 383 | return c.Query("commit") 384 | } 385 | 386 | // Rollback a transaction 387 | func (c *Client) Rollback() (err os.Error) { 388 | // Log rollback 389 | c.log(1, "=== Begin rollback ===") 390 | // Use rollback query 391 | return c.Query("rollback") 392 | } 393 | 394 | // Escape a string 395 | func (c *Client) Escape(s string) (esc string) { 396 | var prev byte 397 | var b bytes.Buffer 398 | for i := 0; i < len(s); i++ { 399 | switch s[i] { 400 | case '\'', '"': 401 | if prev != '\\' { 402 | b.WriteString(s[:i]) 403 | b.WriteByte('\\') 404 | s = s[i:] 405 | i = 0 406 | } 407 | } 408 | prev = s[i] 409 | } 410 | b.WriteString(s) 411 | return b.String() 412 | } 413 | 414 | // Initialise a new statment 415 | func (c *Client) InitStmt() (stmt *Statement, err os.Error) { 416 | // Check connection 417 | if !c.checkConn() { 418 | return nil, &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 419 | } 420 | // Create new statement 421 | stmt = new(Statement) 422 | stmt.c = c 423 | return 424 | } 425 | 426 | // Initialise and prepare a new statement 427 | func (c *Client) Prepare(sql string) (stmt *Statement, err os.Error) { 428 | // Initialise a new statement 429 | stmt, err = c.InitStmt() 430 | if err != nil { 431 | return 432 | } 433 | // Prepare statement 434 | err = stmt.Prepare(sql) 435 | return 436 | } 437 | 438 | // Reset the client 439 | func (c *Client) reset() { 440 | c.sequence = 0 441 | c.serverStatus = 0 442 | c.AffectedRows = 0 443 | c.LastInsertId = 0 444 | c.Warnings = 0 445 | c.result = nil 446 | } 447 | 448 | // Format errors 449 | func (c *Client) fmtError(str Error, args ...interface{}) Error { 450 | return Error(fmt.Sprintf(string(str), args...)) 451 | } 452 | 453 | // Logging 454 | func (c *Client) log(level uint8, format string, args ...interface{}) { 455 | // If logging is disabled, ignore 456 | if level > c.LogLevel { 457 | return 458 | } 459 | // Log based on logging type 460 | switch c.LogType { 461 | // Log to screen 462 | case LOG_SCREEN: 463 | log.Printf(format, args...) 464 | // Log to file 465 | case LOG_FILE: 466 | // If file pointer is nil return 467 | if c.LogFile == nil { 468 | return 469 | } 470 | // This is the same as log package does internally for logging 471 | // to the screen (via stderr) just requires an io.Writer 472 | l := log.New(c.LogFile, "", log.Ldate|log.Ltime) 473 | l.Printf(format, args...) 474 | // Not set 475 | default: 476 | return 477 | } 478 | } 479 | 480 | // Provide detailed log output for server capabilities 481 | func (c *Client) logCaps() { 482 | c.log(3, "=== Server Capabilities ===") 483 | c.log(3, "Long password support: %d", c.serverFlags&CLIENT_LONG_PASSWORD) 484 | c.log(3, "Found rows: %d", c.serverFlags&CLIENT_FOUND_ROWS>>1) 485 | c.log(3, "All column flags: %d", c.serverFlags&CLIENT_LONG_FLAG>>2) 486 | c.log(3, "Connect with database support: %d", c.serverFlags&CLIENT_CONNECT_WITH_DB>>3) 487 | c.log(3, "No schema support: %d", c.serverFlags&CLIENT_NO_SCHEMA>>4) 488 | c.log(3, "Compression support: %d", c.serverFlags&CLIENT_COMPRESS>>5) 489 | c.log(3, "ODBC support: %d", c.serverFlags&CLIENT_ODBC>>6) 490 | c.log(3, "Load data local support: %d", c.serverFlags&CLIENT_LOCAL_FILES>>7) 491 | c.log(3, "Ignore spaces: %d", c.serverFlags&CLIENT_IGNORE_SPACE>>8) 492 | c.log(3, "4.1 protocol support: %d", c.serverFlags&CLIENT_PROTOCOL_41>>9) 493 | c.log(3, "Interactive client: %d", c.serverFlags&CLIENT_INTERACTIVE>>10) 494 | c.log(3, "Switch to SSL: %d", c.serverFlags&CLIENT_SSL>>11) 495 | c.log(3, "Ignore sigpipes: %d", c.serverFlags&CLIENT_IGNORE_SIGPIPE>>12) 496 | c.log(3, "Transaction support: %d", c.serverFlags&CLIENT_TRANSACTIONS>>13) 497 | c.log(3, "4.1 protocol authentication: %d", c.serverFlags&CLIENT_SECURE_CONN>>15) 498 | } 499 | 500 | // Provide detailed log output for the server status flags 501 | func (c *Client) logStatus() { 502 | c.log(3, "=== Server Status ===") 503 | c.log(3, "In transaction: %d", c.serverStatus&SERVER_STATUS_IN_TRANS) 504 | c.log(3, "Auto commit enabled: %d", c.serverStatus&SERVER_STATUS_AUTOCOMMIT>>1) 505 | c.log(3, "More results exist: %d", c.serverStatus&SERVER_MORE_RESULTS_EXISTS>>3) 506 | c.log(3, "No good indexes were used: %d", c.serverStatus&SERVER_QUERY_NO_GOOD_INDEX_USED>>4) 507 | c.log(3, "No indexes were used: %d", c.serverStatus&SERVER_QUERY_NO_INDEX_USED>>5) 508 | c.log(3, "Cursor exists: %d", c.serverStatus&SERVER_STATUS_CURSOR_EXISTS>>6) 509 | c.log(3, "Last row has been sent: %d", c.serverStatus&SERVER_STATUS_LAST_ROW_SENT>>7) 510 | c.log(3, "Database dropped: %d", c.serverStatus&SERVER_STATUS_DB_DROPPED>>8) 511 | c.log(3, "No backslash escapes: %d", c.serverStatus&SERVER_STATUS_NO_BACKSLASH_ESCAPES>>9) 512 | c.log(3, "Metadata has changed: %d", c.serverStatus&SERVER_STATUS_METADATA_CHANGED>>10) 513 | } 514 | 515 | // Check if connected 516 | // @todo expand to perform an actual connection check 517 | func (c *Client) checkConn() bool { 518 | if c.connected { 519 | return true 520 | } 521 | return false 522 | } 523 | 524 | // Check if a result exists 525 | func (c *Client) checkResult() bool { 526 | if c.result != nil { 527 | return true 528 | } 529 | return false 530 | } 531 | 532 | // Check if a network error occurred 533 | func (c *Client) checkNet(err os.Error) bool { 534 | if cErr, ok := err.(*ClientError); ok { 535 | if cErr.Errno == CR_SERVER_GONE_ERROR || cErr.Errno == CR_SERVER_LOST { 536 | return true 537 | } 538 | } 539 | return false 540 | } 541 | 542 | // Performs the actual connect 543 | func (c *Client) connect() (err os.Error) { 544 | // Connect to server 545 | err = c.dial() 546 | if err != nil { 547 | return 548 | } 549 | // Read initial packet from server 550 | err = c.init() 551 | if err != nil { 552 | return 553 | } 554 | // Send auth packet to server 555 | c.sequence++ 556 | err = c.auth() 557 | if err != nil { 558 | return 559 | } 560 | // Read result from server 561 | c.sequence++ 562 | eof, err := c.getResult(PACKET_OK | PACKET_ERROR | PACKET_EOF) 563 | // If eof need to authenticate with a 3.23 password 564 | if eof { 565 | c.sequence++ 566 | // Create packet 567 | p := &packetPassword{ 568 | scrambleBuff: scramble323(c.scrambleBuff, []byte(c.passwd)), 569 | } 570 | p.sequence = c.sequence 571 | // Write packet 572 | err = c.w.writePacket(p) 573 | if err != nil { 574 | return 575 | } 576 | c.log(1, "[%d] Sent old password packet", p.sequence) 577 | // Read result 578 | c.sequence++ 579 | _, err = c.getResult(PACKET_OK | PACKET_ERROR) 580 | } 581 | return 582 | } 583 | 584 | // Connect to server 585 | func (c *Client) dial() (err os.Error) { 586 | // Log connect 587 | c.log(1, "Connecting to server via %s to %s", c.network, c.raddr) 588 | // Connect to server 589 | c.conn, err = net.Dial(c.network, c.raddr) 590 | if err != nil { 591 | // Store error state 592 | if c.network == UNIX { 593 | err = &ClientError{CR_CONNECTION_ERROR, c.fmtError(CR_CONNECTION_ERROR_STR, c.raddr)} 594 | } 595 | if c.network == TCP { 596 | err = &ClientError{CR_CONN_HOST_ERROR, c.fmtError(CR_CONN_HOST_ERROR_STR, c.raddr)} 597 | } 598 | // Log error 599 | if cErr, ok := err.(*ClientError); ok { 600 | c.log(1, string(cErr.Error)) 601 | } 602 | return 603 | } 604 | // Log connect success 605 | c.log(1, "Connected to server") 606 | // Create reader and writer 607 | c.r = newReader(c.conn) 608 | c.w = newWriter(c.conn) 609 | // Set the reader default protocol 610 | c.r.protocol = c.protocol 611 | return 612 | } 613 | 614 | // Read initial packet from server 615 | func (c *Client) init() (err os.Error) { 616 | // Log read packet 617 | c.log(1, "Reading handshake initialization packet from server") 618 | // Read packet 619 | p, err := c.r.readPacket(PACKET_INIT) 620 | if err != nil { 621 | return 622 | } 623 | err = c.checkSequence(p.(*packetInit).sequence) 624 | if err != nil { 625 | return 626 | } 627 | // Log success 628 | c.log(1, "[%d] Received handshake initialization packet", p.(*packetInit).sequence) 629 | // Assign values 630 | c.serverVersion = p.(*packetInit).serverVersion 631 | c.serverProtocol = p.(*packetInit).protocolVersion 632 | c.serverFlags = ClientFlag(p.(*packetInit).serverCaps) 633 | c.serverCharset = p.(*packetInit).serverLanguage 634 | c.serverStatus = ServerStatus(p.(*packetInit).serverStatus) 635 | c.scrambleBuff = p.(*packetInit).scrambleBuff 636 | // Extended logging [level 2+] 637 | if c.LogLevel > 1 { 638 | // Log server info 639 | c.log(2, "Server version: %s", c.serverVersion) 640 | c.log(2, "Protocol version: %d", c.serverProtocol) 641 | } 642 | // Full logging [level 3] 643 | if c.LogLevel > 2 { 644 | c.logCaps() 645 | c.logStatus() 646 | } 647 | // If we're using 4.1 protocol and server doesn't support, drop to 4.0 648 | if c.protocol == PROTOCOL_41 && c.serverFlags&CLIENT_PROTOCOL_41 == 0 { 649 | c.protocol = PROTOCOL_40 650 | c.r.protocol = PROTOCOL_40 651 | } 652 | return 653 | } 654 | 655 | // Send auth packet to the server 656 | func (c *Client) auth() (err os.Error) { 657 | // Log write packet 658 | c.log(1, "Sending authentication packet to server") 659 | // Construct packet 660 | p := &packetAuth{ 661 | clientFlags: uint32(CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS), 662 | maxPacketSize: MAX_PACKET_SIZE, 663 | charsetNumber: c.serverCharset, 664 | user: c.user, 665 | } 666 | // Add protocol and sequence 667 | p.protocol = c.protocol 668 | p.sequence = c.sequence 669 | // Adjust client flags based on server support 670 | if c.serverFlags&CLIENT_LONG_PASSWORD > 0 { 671 | p.clientFlags |= uint32(CLIENT_LONG_PASSWORD) 672 | } 673 | if c.serverFlags&CLIENT_LONG_FLAG > 0 { 674 | p.clientFlags |= uint32(CLIENT_LONG_FLAG) 675 | } 676 | if c.serverFlags&CLIENT_TRANSACTIONS > 0 { 677 | p.clientFlags |= uint32(CLIENT_TRANSACTIONS) 678 | } 679 | // Check protocol 680 | if c.protocol == PROTOCOL_41 { 681 | p.clientFlags |= uint32(CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONN) 682 | p.scrambleBuff = scramble41(c.scrambleBuff, []byte(c.passwd)) 683 | // To specify a db name 684 | if c.serverFlags&CLIENT_CONNECT_WITH_DB > 0 && len(c.dbname) > 0 { 685 | p.clientFlags |= uint32(CLIENT_CONNECT_WITH_DB) 686 | p.database = c.dbname 687 | } 688 | } else { 689 | p.scrambleBuff = scramble323(c.scrambleBuff, []byte(c.passwd)) 690 | } 691 | // Write packet 692 | err = c.w.writePacket(p) 693 | if err != nil { 694 | return 695 | } 696 | // Log write success 697 | c.log(1, "[%d] Sent authentication packet", p.sequence) 698 | return 699 | } 700 | 701 | // Simple non-recovered reconnect 702 | func (c *Client) simpleReconnect(err os.Error) os.Error { 703 | if err != nil && c.checkNet(err) && c.Reconnect { 704 | c.log(1, "!!! Lost connection to server !!!") 705 | c.connected = false 706 | rcErr := c.reconnect() 707 | if rcErr != nil { 708 | return rcErr 709 | } 710 | } 711 | return err 712 | } 713 | 714 | // Perform reconnect if a network error occurs 715 | func (c *Client) reconnect() (err os.Error) { 716 | // Log auto reconnect 717 | c.log(1, "=== Begin auto reconnect attempt ===") 718 | // Reset the client 719 | c.reset() 720 | // Attempt to reconnect 721 | for i := 0; i < 10; i++ { 722 | err = c.connect() 723 | if err == nil { 724 | c.connected = true 725 | break 726 | } 727 | time.Sleep(2000000000) 728 | } 729 | return 730 | } 731 | 732 | // Send a command to the server 733 | func (c *Client) command(command command, args ...interface{}) (err os.Error) { 734 | // Log write packet 735 | c.log(1, "Sending command packet to server") 736 | // Simple validation, arg count 737 | switch command { 738 | // No args 739 | case COM_QUIT, COM_STATISTICS, COM_PROCESS_INFO, COM_DEBUG, COM_PING: 740 | if len(args) != 0 { 741 | return &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 742 | } 743 | // 1 arg 744 | case COM_INIT_DB, COM_QUERY, COM_REFRESH, COM_SHUTDOWN, COM_PROCESS_KILL, COM_STMT_PREPARE, COM_STMT_CLOSE, COM_STMT_RESET: 745 | if len(args) != 1 { 746 | return &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 747 | } 748 | // 1 or 2 args 749 | case COM_FIELD_LIST: 750 | if len(args) != 1 && len(args) != 2 { 751 | return &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 752 | } 753 | // 2 args 754 | case COM_STMT_FETCH: 755 | if len(args) != 2 { 756 | return &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 757 | } 758 | // 4 args 759 | case COM_CHANGE_USER: 760 | if len(args) != 4 { 761 | return &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 762 | } 763 | // Everything else e.g. replication unsupported 764 | default: 765 | return &ClientError{CR_NOT_IMPLEMENTED, CR_NOT_IMPLEMENTED_STR} 766 | } 767 | // Construct packet 768 | p := &packetCommand{ 769 | command: command, 770 | args: args, 771 | } 772 | // Add protocol and sequence 773 | p.protocol = c.protocol 774 | p.sequence = c.sequence 775 | // Write packet 776 | err = c.w.writePacket(p) 777 | if err != nil { 778 | return &ClientError{CR_SERVER_LOST, CR_SERVER_LOST_STR} 779 | } 780 | // Log write success 781 | c.log(1, "[%d] Sent command packet", p.sequence) 782 | return 783 | } 784 | 785 | // Get field packets for a result 786 | func (c *Client) getFields() (err os.Error) { 787 | // Check for a valid result 788 | if c.result == nil { 789 | return &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 790 | } 791 | // Read fields till EOF is returned 792 | for { 793 | c.sequence++ 794 | eof, err := c.getResult(PACKET_FIELD | PACKET_EOF) 795 | if err != nil { 796 | return 797 | } 798 | if eof { 799 | break 800 | } 801 | } 802 | return 803 | } 804 | 805 | // Get next row for a result 806 | func (c *Client) getRow() (eof bool, err os.Error) { 807 | // Check for a valid result 808 | if c.result == nil { 809 | return false, &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 810 | } 811 | // Read next row packet or EOF 812 | c.sequence++ 813 | eof, err = c.getResult(PACKET_ROW | PACKET_EOF) 814 | return 815 | } 816 | 817 | // Get all rows for the result 818 | func (c *Client) getAllRows() (err os.Error) { 819 | for { 820 | eof, err := c.getRow() 821 | if err != nil { 822 | return 823 | } 824 | if eof { 825 | break 826 | } 827 | } 828 | return 829 | } 830 | 831 | // Get result 832 | func (c *Client) getResult(types packetType) (eof bool, err os.Error) { 833 | // Log read result 834 | c.log(1, "Reading result packet from server") 835 | // Get result packet 836 | p, err := c.r.readPacket(types) 837 | if err != nil { 838 | return 839 | } 840 | // Process result packet 841 | switch p.(type) { 842 | default: 843 | err = &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 844 | case *packetOK: 845 | err = handleOK(p.(*packetOK), c, &c.AffectedRows, &c.LastInsertId, &c.Warnings) 846 | case *packetError: 847 | err = handleError(p.(*packetError), c) 848 | case *packetEOF: 849 | eof = true 850 | err = handleEOF(p.(*packetEOF), c) 851 | case *packetResultSet: 852 | c.result = &Result{c: c} 853 | err = handleResultSet(p.(*packetResultSet), c, c.result) 854 | case *packetField: 855 | err = handleField(p.(*packetField), c, c.result) 856 | case *packetRowData: 857 | err = handleRow(p.(*packetRowData), c, c.result) 858 | } 859 | return 860 | } 861 | 862 | // Sequence check 863 | func (c *Client) checkSequence(sequence uint8) (err os.Error) { 864 | if sequence != c.sequence { 865 | c.log(1, "Sequence doesn't match - expected %d but got %d, commands out of sync", c.sequence, sequence) 866 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 867 | } 868 | return 869 | } 870 | -------------------------------------------------------------------------------- /mysql_test.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "rand" 12 | "strconv" 13 | "testing" 14 | ) 15 | 16 | const ( 17 | // Testing credentials, run the following on server client prior to running: 18 | // create database gomysql_test; 19 | // create database gomysql_test2; 20 | // create database gomysql_test3; 21 | // create user gomysql_test@localhost identified by 'abc123'; 22 | // grant all privileges on gomysql_test.* to gomysql_test@localhost; 23 | // grant all privileges on gomysql_test2.* to gomysql_test@localhost; 24 | 25 | // Testing settings 26 | TEST_HOST = "localhost" 27 | TEST_PORT = "3306" 28 | TEST_SOCK = "/var/run/mysqld/mysqld.sock" 29 | TEST_USER = "gomysql_test" 30 | TEST_PASSWD = "abc123" 31 | TEST_BAD_PASSWD = "321cba" 32 | TEST_DBNAME = "gomysql_test" // This is the main database used for testing 33 | TEST_DBNAME2 = "gomysql_test2" // This is a privileged database used to test changedb etc 34 | TEST_DBNAMEUP = "gomysql_test3" // This is an unprivileged database 35 | TEST_DBNAMEBAD = "gomysql_bad" // This is a nonexistant database 36 | 37 | // Simple table queries 38 | 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';" 39 | SELECT_SIMPLE = "SELECT * FROM simple" 40 | INSERT_SIMPLE = "INSERT INTO simple VALUES (null, %d, '%s', '%s', NOW())" 41 | INSERT_SIMPLE_STMT = "INSERT INTO simple VALUES (null, ?, ?, ?, NOW())" 42 | UPDATE_SIMPLE = "UPDATE simple SET `text` = '%s', `datetime` = NOW() WHERE id = %d" 43 | UPDATE_SIMPLE_STMT = "UPDATE simple SET `text` = ?, `datetime` = NOW() WHERE id = ?" 44 | DROP_SIMPLE = "DROP TABLE `simple`" 45 | 46 | // All types table queries 47 | 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'" 48 | DROP_ALLTYPES = "DROP TABLE `all_types`" 49 | ) 50 | 51 | var ( 52 | db *Client 53 | err os.Error 54 | ) 55 | 56 | type SimpleRow struct { 57 | Id uint64 58 | Number string 59 | String string 60 | Text string 61 | Date string 62 | } 63 | 64 | // Test connect to server via TCP 65 | func TestDialTCP(t *testing.T) { 66 | t.Logf("Running DialTCP test to %s:%s", TEST_HOST, TEST_PORT) 67 | db, err = DialTCP(TEST_HOST, TEST_USER, TEST_PASSWD, TEST_DBNAME) 68 | if err != nil { 69 | t.Logf("Error %s", err) 70 | t.Fail() 71 | } 72 | err = db.Close() 73 | if err != nil { 74 | t.Logf("Error %s", err) 75 | t.Fail() 76 | } 77 | } 78 | 79 | // Test connect to server via Unix socket 80 | func TestDialUnix(t *testing.T) { 81 | t.Logf("Running DialUnix test to %s", TEST_SOCK) 82 | db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAME) 83 | if err != nil { 84 | t.Logf("Error %s", err) 85 | t.Fail() 86 | } 87 | err = db.Close() 88 | if err != nil { 89 | t.Logf("Error %s", err) 90 | t.Fail() 91 | } 92 | } 93 | 94 | // Test connect to server with unprivileged database 95 | func TestDialUnixUnpriv(t *testing.T) { 96 | t.Logf("Running DialUnix test to unprivileged database %s", TEST_DBNAMEUP) 97 | db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAMEUP) 98 | if err != nil { 99 | t.Logf("Error %s", err) 100 | } 101 | if cErr, ok := err.(*ClientError); ok { 102 | if cErr.Errno != 1044 { 103 | t.Logf("Error #%d received, expected #1044", cErr.Errno) 104 | t.Fail() 105 | } 106 | } 107 | } 108 | 109 | // Test connect to server with nonexistant database 110 | func TestDialUnixNonex(t *testing.T) { 111 | t.Logf("Running DialUnix test to nonexistant database %s", TEST_DBNAMEBAD) 112 | db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAMEBAD) 113 | if err != nil { 114 | t.Logf("Error %s", err) 115 | } 116 | if cErr, ok := err.(*ClientError); ok { 117 | if cErr.Errno != 1044 { 118 | t.Logf("Error #%d received, expected #1044", cErr.Errno) 119 | t.Fail() 120 | } 121 | } 122 | } 123 | 124 | // Test connect with bad password 125 | func TestDialUnixBadPass(t *testing.T) { 126 | t.Logf("Running DialUnix test with bad password") 127 | db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_BAD_PASSWD, TEST_DBNAME) 128 | if err != nil { 129 | t.Logf("Error %s", err) 130 | } 131 | if cErr, ok := err.(*ClientError); ok { 132 | if cErr.Errno != 1045 { 133 | t.Logf("Error #%d received, expected #1045", cErr.Errno) 134 | t.Fail() 135 | } 136 | } 137 | } 138 | 139 | // Test queries on a simple table (create database, select, insert, update, drop database) 140 | func TestSimple(t *testing.T) { 141 | t.Logf("Running simple table tests") 142 | db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAME) 143 | if err != nil { 144 | t.Logf("Error %s", err) 145 | t.Fail() 146 | } 147 | 148 | t.Logf("Create table") 149 | err = db.Query(CREATE_SIMPLE) 150 | if err != nil { 151 | t.Logf("Error %s", err) 152 | t.Fail() 153 | } 154 | 155 | t.Logf("Insert 1000 records") 156 | rowMap := make(map[uint64][]string) 157 | for i := 0; i < 1000; i++ { 158 | num, str1, str2 := rand.Int(), randString(32), randString(128) 159 | err = db.Query(fmt.Sprintf(INSERT_SIMPLE, num, str1, str2)) 160 | if err != nil { 161 | t.Logf("Error %s", err) 162 | t.Fail() 163 | } 164 | row := []string{fmt.Sprintf("%d", num), str1, str2} 165 | rowMap[db.LastInsertId] = row 166 | } 167 | 168 | t.Logf("Select inserted data") 169 | err = db.Query(SELECT_SIMPLE) 170 | if err != nil { 171 | t.Logf("Error %s", err) 172 | t.Fail() 173 | } 174 | 175 | t.Logf("Use result") 176 | res, err := db.UseResult() 177 | if err != nil { 178 | t.Logf("Error %s", err) 179 | t.Fail() 180 | } 181 | 182 | t.Logf("Validate inserted data") 183 | for { 184 | row := res.FetchRow() 185 | if row == nil { 186 | break 187 | } 188 | id := row[0].(uint64) 189 | num, str1, str2 := strconv.Itoa64(row[1].(int64)), row[2].(string), string(row[3].([]byte)) 190 | if rowMap[id][0] != num || rowMap[id][1] != str1 || rowMap[id][2] != str2 { 191 | t.Logf("String from database doesn't match local string") 192 | t.Fail() 193 | } 194 | } 195 | 196 | t.Logf("Free result") 197 | err = res.Free() 198 | if err != nil { 199 | t.Logf("Error %s", err) 200 | t.Fail() 201 | } 202 | 203 | t.Logf("Update some records") 204 | for i := uint64(0); i < 1000; i += 5 { 205 | rowMap[i+1][2] = randString(256) 206 | err = db.Query(fmt.Sprintf(UPDATE_SIMPLE, rowMap[i+1][2], i+1)) 207 | if err != nil { 208 | t.Logf("Error %s", err) 209 | t.Fail() 210 | } 211 | if db.AffectedRows != 1 { 212 | t.Logf("Expected 1 effected row but got %d", db.AffectedRows) 213 | t.Fail() 214 | } 215 | } 216 | 217 | t.Logf("Select updated data") 218 | err = db.Query(SELECT_SIMPLE) 219 | if err != nil { 220 | t.Logf("Error %s", err) 221 | t.Fail() 222 | } 223 | 224 | t.Logf("Store result") 225 | res, err = db.StoreResult() 226 | if err != nil { 227 | t.Logf("Error %s", err) 228 | t.Fail() 229 | } 230 | 231 | t.Logf("Validate updated data") 232 | for { 233 | row := res.FetchRow() 234 | if row == nil { 235 | break 236 | } 237 | id := row[0].(uint64) 238 | num, str1, str2 := strconv.Itoa64(row[1].(int64)), row[2].(string), string(row[3].([]byte)) 239 | if rowMap[id][0] != num || rowMap[id][1] != str1 || rowMap[id][2] != str2 { 240 | t.Logf("%#v %#v", rowMap[id], row) 241 | t.Logf("String from database doesn't match local string") 242 | t.Fail() 243 | } 244 | } 245 | 246 | t.Logf("Free result") 247 | err = res.Free() 248 | if err != nil { 249 | t.Logf("Error %s", err) 250 | t.Fail() 251 | } 252 | 253 | t.Logf("Drop table") 254 | err = db.Query(DROP_SIMPLE) 255 | if err != nil { 256 | t.Logf("Error %s", err) 257 | t.Fail() 258 | } 259 | 260 | t.Logf("Close connection") 261 | err = db.Close() 262 | if err != nil { 263 | t.Logf("Error %s", err) 264 | t.Fail() 265 | } 266 | } 267 | 268 | // Test queries on a simple table (create database, select, insert, update, drop database) using a statement 269 | func TestSimpleStatement(t *testing.T) { 270 | t.Logf("Running simple table statement tests") 271 | db, err = DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAME) 272 | if err != nil { 273 | t.Logf("Error %s", err) 274 | t.Fail() 275 | } 276 | 277 | t.Logf("Init statement") 278 | stmt, err := db.InitStmt() 279 | if err != nil { 280 | t.Logf("Error %s", err) 281 | t.Fail() 282 | } 283 | 284 | t.Logf("Prepare create table") 285 | err = stmt.Prepare(CREATE_SIMPLE) 286 | if err != nil { 287 | t.Logf("Error %s", err) 288 | t.Fail() 289 | } 290 | 291 | t.Logf("Execute create table") 292 | err = stmt.Execute() 293 | if err != nil { 294 | t.Logf("Error %s", err) 295 | t.Fail() 296 | } 297 | 298 | t.Logf("Prepare insert") 299 | err = stmt.Prepare(INSERT_SIMPLE_STMT) 300 | if err != nil { 301 | t.Logf("Error %s", err) 302 | t.Fail() 303 | } 304 | 305 | t.Logf("Insert 1000 records") 306 | rowMap := make(map[uint64][]string) 307 | for i := 0; i < 1000; i++ { 308 | num, str1, str2 := rand.Int(), randString(32), randString(128) 309 | err = stmt.BindParams(num, str1, str2) 310 | if err != nil { 311 | t.Logf("Error %s", err) 312 | t.Fail() 313 | } 314 | err = stmt.Execute() 315 | if err != nil { 316 | t.Logf("Error %s", err) 317 | t.Fail() 318 | } 319 | row := []string{fmt.Sprintf("%d", num), str1, str2} 320 | rowMap[stmt.LastInsertId] = row 321 | } 322 | 323 | t.Logf("Prepare select") 324 | err = stmt.Prepare(SELECT_SIMPLE) 325 | if err != nil { 326 | t.Logf("Error %s", err) 327 | t.Fail() 328 | } 329 | 330 | t.Logf("Execute select") 331 | err = stmt.Execute() 332 | if err != nil { 333 | t.Logf("Error %s", err) 334 | t.Fail() 335 | } 336 | 337 | t.Logf("Bind result") 338 | row := SimpleRow{} 339 | stmt.BindResult(&row.Id, &row.Number, &row.String, &row.Text, &row.Date) 340 | 341 | t.Logf("Validate inserted data") 342 | for { 343 | eof, err := stmt.Fetch() 344 | if err != nil { 345 | t.Logf("Error %s", err) 346 | t.Fail() 347 | } 348 | if eof { 349 | break 350 | } 351 | if rowMap[row.Id][0] != row.Number || rowMap[row.Id][1] != row.String || rowMap[row.Id][2] != row.Text { 352 | t.Logf("String from database doesn't match local string") 353 | t.Fail() 354 | } 355 | } 356 | 357 | t.Logf("Reset statement") 358 | err = stmt.Reset() 359 | if err != nil { 360 | t.Logf("Error %s", err) 361 | t.Fail() 362 | } 363 | 364 | t.Logf("Prepare update") 365 | err = stmt.Prepare(UPDATE_SIMPLE_STMT) 366 | if err != nil { 367 | t.Logf("Error %s", err) 368 | t.Fail() 369 | } 370 | 371 | t.Logf("Update some records") 372 | for i := uint64(0); i < 1000; i += 5 { 373 | rowMap[i+1][2] = randString(256) 374 | stmt.BindParams(rowMap[i+1][2], i+1) 375 | err = stmt.Execute() 376 | if err != nil { 377 | t.Logf("Error %s", err) 378 | t.Fail() 379 | } 380 | if stmt.AffectedRows != 1 { 381 | t.Logf("Expected 1 effected row but got %d", db.AffectedRows) 382 | t.Fail() 383 | } 384 | } 385 | 386 | t.Logf("Prepare select updated") 387 | err = stmt.Prepare(SELECT_SIMPLE) 388 | if err != nil { 389 | t.Logf("Error %s", err) 390 | t.Fail() 391 | } 392 | 393 | t.Logf("Execute select updated") 394 | err = stmt.Execute() 395 | if err != nil { 396 | t.Logf("Error %s", err) 397 | t.Fail() 398 | } 399 | 400 | t.Logf("Validate updated data") 401 | for { 402 | eof, err := stmt.Fetch() 403 | if err != nil { 404 | t.Logf("Error %s", err) 405 | t.Fail() 406 | } 407 | if eof { 408 | break 409 | } 410 | if rowMap[row.Id][0] != row.Number || rowMap[row.Id][1] != row.String || rowMap[row.Id][2] != row.Text { 411 | t.Logf("String from database doesn't match local string") 412 | t.Fail() 413 | } 414 | } 415 | 416 | t.Logf("Free result") 417 | err = stmt.FreeResult() 418 | if err != nil { 419 | t.Logf("Error %s", err) 420 | t.Fail() 421 | } 422 | 423 | t.Logf("Prepare drop") 424 | err = stmt.Prepare(DROP_SIMPLE) 425 | if err != nil { 426 | t.Logf("Error %s", err) 427 | t.Fail() 428 | } 429 | 430 | t.Logf("Execute drop") 431 | err = stmt.Execute() 432 | if err != nil { 433 | t.Logf("Error %s", err) 434 | t.Fail() 435 | } 436 | 437 | t.Logf("Close statement") 438 | err = stmt.Close() 439 | if err != nil { 440 | t.Logf("Error %s", err) 441 | t.Fail() 442 | } 443 | 444 | t.Logf("Close connection") 445 | err = db.Close() 446 | if err != nil { 447 | t.Logf("Error %s", err) 448 | t.Fail() 449 | } 450 | } 451 | 452 | // Benchmark connect/handshake via TCP 453 | func BenchmarkDialTCP(b *testing.B) { 454 | for i := 0; i < b.N; i++ { 455 | DialTCP(TEST_HOST, TEST_USER, TEST_PASSWD, TEST_DBNAME) 456 | } 457 | } 458 | 459 | // Benchmark connect/handshake via Unix socket 460 | func BenchmarkDialUnix(b *testing.B) { 461 | for i := 0; i < b.N; i++ { 462 | DialUnix(TEST_SOCK, TEST_USER, TEST_PASSWD, TEST_DBNAME) 463 | } 464 | } 465 | 466 | // Create a random string 467 | func randString(strLen int) (randStr string) { 468 | strChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" 469 | for i := 0; i < strLen; i++ { 470 | randUint := rand.Uint32() 471 | pos := randUint % uint32(len(strChars)) 472 | randStr += string(strChars[pos]) 473 | } 474 | return 475 | } 476 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "bytes" 10 | "os" 11 | ) 12 | 13 | // Packet type identifier 14 | type packetType uint32 15 | 16 | // Packet types 17 | const ( 18 | PACKET_INIT packetType = 1 << iota 19 | PACKET_AUTH 20 | PACKET_OK 21 | PACKET_ERROR 22 | PACKET_CMD 23 | PACKET_RESULT 24 | PACKET_FIELD 25 | PACKET_ROW 26 | PACKET_EOF 27 | PACKET_PREPARE_OK 28 | PACKET_PARAM 29 | PACKET_LONG_DATA 30 | PACKET_EXECUTE 31 | PACKET_ROW_BINARY 32 | ) 33 | 34 | // Readable packet interface 35 | type packetReadable interface { 36 | read(data []byte) (err os.Error) 37 | } 38 | 39 | // Writable packet interface 40 | type packetWritable interface { 41 | write() (data []byte, err os.Error) 42 | } 43 | 44 | // Generic packet interface (read/writable) 45 | type packet interface { 46 | packetReadable 47 | packetWritable 48 | } 49 | 50 | // Packet base struct 51 | type packetBase struct { 52 | protocol uint8 53 | sequence uint8 54 | } 55 | 56 | // Read a slice from the data 57 | func (p *packetBase) readSlice(data []byte, delim byte) (slice []byte, err os.Error) { 58 | pos := bytes.IndexByte(data, delim) 59 | if pos > -1 { 60 | slice = data[:pos] 61 | } else { 62 | slice = data 63 | err = os.EOF 64 | } 65 | return 66 | } 67 | 68 | // Read length coded string 69 | func (p *packetBase) readLengthCodedString(data []byte) (s string, n int, err os.Error) { 70 | // Read bytes and convert to string 71 | b, n, err := p.readLengthCodedBytes(data) 72 | if err != nil { 73 | return 74 | } 75 | s = string(b) 76 | return 77 | } 78 | 79 | func (p *packetBase) readLengthCodedBytes(data []byte) (b []byte, n int, err os.Error) { 80 | // Get string length 81 | num, n, err := btolcb(data) 82 | if err != nil { 83 | return 84 | } 85 | // Check data length 86 | if len(data) < n+int(num) { 87 | err = os.EOF 88 | return 89 | } 90 | // Get bytes 91 | b = data[n : n+int(num)] 92 | n += int(num) 93 | return 94 | } 95 | 96 | // Prepend packet data with header info 97 | func (p *packetBase) addHeader(data []byte) (pkt []byte) { 98 | pkt = ui24tob(uint32(len(data))) 99 | pkt = append(pkt, p.sequence) 100 | pkt = append(pkt, data...) 101 | return 102 | } 103 | 104 | 105 | // Init packet 106 | type packetInit struct { 107 | packetBase 108 | protocolVersion uint8 109 | serverVersion string 110 | threadId uint32 111 | scrambleBuff []byte 112 | serverCaps uint16 113 | serverLanguage uint8 114 | serverStatus uint16 115 | } 116 | 117 | // Init packet reader 118 | func (p *packetInit) read(data []byte) (err os.Error) { 119 | // Recover errors 120 | defer func() { 121 | if e := recover(); e != nil { 122 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 123 | } 124 | }() 125 | // Position 126 | pos := 0 127 | // Protocol version [8 bit uint] 128 | p.protocolVersion = data[pos] 129 | pos++ 130 | // Server version [null terminated string] 131 | slice, err := p.readSlice(data[pos:], 0x00) 132 | if err != nil { 133 | return 134 | } 135 | p.serverVersion = string(slice) 136 | pos += len(slice) + 1 137 | // Thread id [32 bit uint] 138 | p.threadId = btoui32(data[pos : pos+4]) 139 | pos += 4 140 | // First part of scramble buffer [8 bytes] 141 | p.scrambleBuff = make([]byte, 8) 142 | p.scrambleBuff = data[pos : pos+8] 143 | pos += 9 144 | // Server capabilities [16 bit uint] 145 | p.serverCaps = btoui16(data[pos : pos+2]) 146 | pos += 2 147 | // Server language [8 bit uint] 148 | p.serverLanguage = data[pos] 149 | pos++ 150 | // Server status [16 bit uint] 151 | p.serverStatus = btoui16(data[pos : pos+2]) 152 | pos += 15 153 | // Second part of scramble buffer, if exists (4.1+) [13 bytes] 154 | if ClientFlag(p.serverCaps)&CLIENT_PROTOCOL_41 > 0 { 155 | p.scrambleBuff = append(p.scrambleBuff, data[pos:pos+12]...) 156 | } 157 | return 158 | } 159 | 160 | // Auth packet 161 | type packetAuth struct { 162 | packetBase 163 | clientFlags uint32 164 | maxPacketSize uint32 165 | charsetNumber uint8 166 | user string 167 | scrambleBuff []byte 168 | database string 169 | } 170 | 171 | // Auth packet writer 172 | func (p *packetAuth) write() (data []byte, err os.Error) { 173 | // Recover errors 174 | defer func() { 175 | if e := recover(); e != nil { 176 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 177 | } 178 | }() 179 | // For MySQL 4.1+ 180 | if p.protocol == PROTOCOL_41 { 181 | // Client flags 182 | data = ui32tob(p.clientFlags) 183 | // Max packet size 184 | data = append(data, ui32tob(p.maxPacketSize)...) 185 | // Charset 186 | data = append(data, p.charsetNumber) 187 | // Filler 188 | data = append(data, make([]byte, 23)...) 189 | // User 190 | if len(p.user) > 0 { 191 | data = append(data, []byte(p.user)...) 192 | } 193 | // Terminator 194 | data = append(data, 0x0) 195 | // Scramble buffer 196 | data = append(data, byte(len(p.scrambleBuff))) 197 | if len(p.scrambleBuff) > 0 { 198 | data = append(data, p.scrambleBuff...) 199 | } 200 | // Database name 201 | if len(p.database) > 0 { 202 | data = append(data, []byte(p.database)...) 203 | // Terminator 204 | data = append(data, 0x0) 205 | } 206 | // For MySQL < 4.1 207 | } else { 208 | // Client flags 209 | data = ui16tob(uint16(p.clientFlags)) 210 | // Max packet size 211 | data = append(data, ui24tob(p.maxPacketSize)...) 212 | // User 213 | if len(p.user) > 0 { 214 | data = append(data, []byte(p.user)...) 215 | } 216 | // Terminator 217 | data = append(data, 0x0) 218 | // Scramble buffer 219 | if len(p.scrambleBuff) > 0 { 220 | data = append(data, p.scrambleBuff...) 221 | } 222 | // Padding 223 | data = append(data, 0x0) 224 | } 225 | // Add the packet header 226 | data = p.addHeader(data) 227 | return 228 | } 229 | 230 | // Ok packet struct 231 | type packetOK struct { 232 | packetBase 233 | affectedRows uint64 234 | insertId uint64 235 | serverStatus uint16 236 | warningCount uint16 237 | message string 238 | } 239 | 240 | // OK packet reader 241 | func (p *packetOK) read(data []byte) (err os.Error) { 242 | // Recover errors 243 | defer func() { 244 | if e := recover(); e != nil { 245 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 246 | } 247 | }() 248 | // Position (skip first byte/field count) 249 | pos := 1 250 | // Affected rows [length coded binary] 251 | num, n, err := btolcb(data[pos:]) 252 | if err != nil { 253 | return 254 | } 255 | p.affectedRows = num 256 | pos += n 257 | // Insert id [length coded binary] 258 | num, n, err = btolcb(data[pos:]) 259 | if err != nil { 260 | return 261 | } 262 | p.insertId = num 263 | pos += n 264 | // Server status [16 bit uint] 265 | p.serverStatus = btoui16(data[pos : pos+2]) 266 | pos += 2 267 | // Warning (4.1 only) [16 bit uint] 268 | if p.protocol == PROTOCOL_41 { 269 | p.warningCount = btoui16(data[pos : pos+2]) 270 | pos += 2 271 | } 272 | // Message (optional) [string] 273 | if pos < len(data) { 274 | p.message = string(data[pos:]) 275 | } 276 | return 277 | } 278 | 279 | // Error packet struct 280 | type packetError struct { 281 | packetBase 282 | errno uint16 283 | state string 284 | error string 285 | } 286 | 287 | // Error packet reader 288 | func (p *packetError) read(data []byte) (err os.Error) { 289 | // Recover errors 290 | defer func() { 291 | if e := recover(); e != nil { 292 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 293 | } 294 | }() 295 | // Position 296 | pos := 1 297 | // Error number [16 bit uint] 298 | p.errno = btoui16(data[pos : pos+2]) 299 | pos += 2 300 | // State (4.1 only) [string] 301 | if p.protocol == PROTOCOL_41 { 302 | pos++ 303 | p.state = string(data[pos : pos+5]) 304 | pos += 5 305 | } 306 | // Message [string] 307 | p.error = string(data[pos:]) 308 | return 309 | } 310 | 311 | // EOF packet struct 312 | type packetEOF struct { 313 | packetBase 314 | warningCount uint16 315 | useWarning bool 316 | serverStatus uint16 317 | useStatus bool 318 | } 319 | 320 | // EOF packet reader 321 | func (p *packetEOF) read(data []byte) (err os.Error) { 322 | // Recover errors 323 | defer func() { 324 | if e := recover(); e != nil { 325 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 326 | } 327 | }() 328 | // Check for 4.1 protocol AND 2 available bytes 329 | if p.protocol == PROTOCOL_41 && len(data) >= 3 { 330 | // Warning count [16 bit uint] 331 | p.warningCount = btoui16(data[1:3]) 332 | p.useWarning = true 333 | } 334 | // Check for 4.1 protocol AND 2 available bytes 335 | if p.protocol == PROTOCOL_41 && len(data) == 5 { 336 | // Server status [16 bit uint] 337 | p.serverStatus = btoui16(data[3:5]) 338 | p.useStatus = true 339 | } 340 | return 341 | } 342 | 343 | // Password packet struct 344 | type packetPassword struct { 345 | packetBase 346 | scrambleBuff []byte 347 | } 348 | 349 | // Password packet writer 350 | func (p *packetPassword) write() (data []byte, err os.Error) { 351 | // Recover errors 352 | defer func() { 353 | if e := recover(); e != nil { 354 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 355 | } 356 | }() 357 | // Set scramble 358 | data = p.scrambleBuff 359 | // Add terminator 360 | data = append(data, 0x0) 361 | // Add the packet header 362 | data = p.addHeader(data) 363 | return 364 | } 365 | 366 | // Command packet struct 367 | type packetCommand struct { 368 | packetBase 369 | command command 370 | args []interface{} 371 | } 372 | 373 | // Command packet writer 374 | func (p *packetCommand) write() (data []byte, err os.Error) { 375 | // Recover errors 376 | defer func() { 377 | if e := recover(); e != nil { 378 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 379 | } 380 | }() 381 | // Make slice from command byte 382 | data = []byte{byte(p.command)} 383 | // Add args to requests 384 | switch p.command { 385 | // Commands with 1 arg unterminated string 386 | case COM_INIT_DB, COM_QUERY, COM_STMT_PREPARE: 387 | data = append(data, []byte(p.args[0].(string))...) 388 | // Commands with 1 arg 32 bit uint 389 | case COM_PROCESS_KILL, COM_STMT_CLOSE, COM_STMT_RESET: 390 | data = append(data, ui32tob(p.args[0].(uint32))...) 391 | // Field list command 392 | case COM_FIELD_LIST: 393 | // Table name 394 | data = append(data, []byte(p.args[0].(string))...) 395 | // Terminator 396 | data = append(data, 0x00) 397 | // Column name 398 | if len(p.args) > 1 { 399 | data = append(data, []byte(p.args[1].(string))...) 400 | } 401 | // Refresh command 402 | case COM_REFRESH: 403 | data = append(data, byte(p.args[0].(Refresh))) 404 | // Shutdown command 405 | case COM_SHUTDOWN: 406 | data = append(data, byte(p.args[0].(Shutdown))) 407 | // Change user command 408 | case COM_CHANGE_USER: 409 | // User 410 | data = append(data, []byte(p.args[0].(string))...) 411 | // Terminator 412 | data = append(data, 0x00) 413 | // Scramble length for 4.1 414 | if p.protocol == PROTOCOL_41 { 415 | data = append(data, byte(len(p.args[1].([]byte)))) 416 | } 417 | // Scramble buffer 418 | if len(p.args[1].([]byte)) > 0 { 419 | data = append(data, p.args[1].([]byte)...) 420 | } 421 | // Temrminator for 3.23 422 | if p.protocol == PROTOCOL_40 { 423 | data = append(data, 0x00) 424 | } 425 | // Database name 426 | if len(p.args[2].(string)) > 0 { 427 | data = append(data, []byte(p.args[2].(string))...) 428 | } 429 | // Terminator 430 | data = append(data, 0x00) 431 | // Character set number (5.1.23+ needs testing with earlier versions) 432 | data = append(data, ui16tob(p.args[3].(uint16))...) 433 | // Fetch statement command 434 | case COM_STMT_FETCH: 435 | // Statement id 436 | data = append(data, ui32tob(p.args[0].(uint32))...) 437 | // Number of rows 438 | data = append(data, ui32tob(p.args[1].(uint32))...) 439 | } 440 | // Add the packet header 441 | data = p.addHeader(data) 442 | return 443 | } 444 | 445 | // Result set packet struct 446 | type packetResultSet struct { 447 | packetBase 448 | fieldCount uint64 449 | extra uint64 450 | } 451 | 452 | // Result set packet reader 453 | func (p *packetResultSet) read(data []byte) (err os.Error) { 454 | // Recover errors 455 | defer func() { 456 | if e := recover(); e != nil { 457 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 458 | } 459 | }() 460 | // Position and bytes read 461 | var pos, n int 462 | // Field count [length coded binary] 463 | p.fieldCount, n, err = btolcb(data[pos:]) 464 | if err != nil { 465 | return 466 | } 467 | pos += n 468 | // Extra [length coded binary] 469 | if pos < len(data) { 470 | p.extra, n, err = btolcb(data[pos:]) 471 | if err != nil { 472 | return 473 | } 474 | } 475 | return 476 | } 477 | 478 | // Field packet struct 479 | type packetField struct { 480 | packetBase 481 | catalog string 482 | database string 483 | table string 484 | orgTable string 485 | name string 486 | orgName string 487 | charsetNumber uint16 488 | length uint32 489 | fieldType uint8 490 | flags uint16 491 | decimals uint8 492 | defaultVal uint64 493 | } 494 | 495 | // Field packet reader 496 | func (p *packetField) read(data []byte) (err os.Error) { 497 | // Recover errors 498 | defer func() { 499 | if e := recover(); e != nil { 500 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 501 | } 502 | }() 503 | // Position and bytes read 504 | var pos, n int 505 | // 4.1 protocol 506 | if p.protocol == PROTOCOL_41 { 507 | // Catalog [len coded string] 508 | p.catalog, n, err = p.readLengthCodedString(data) 509 | if err != nil { 510 | return 511 | } 512 | pos += n 513 | // Database [len coded string] 514 | p.database, n, err = p.readLengthCodedString(data[pos:]) 515 | if err != nil { 516 | return 517 | } 518 | pos += n 519 | // Table [len coded string] 520 | p.table, n, err = p.readLengthCodedString(data[pos:]) 521 | if err != nil { 522 | return 523 | } 524 | pos += n 525 | // Original table [len coded string] 526 | p.orgTable, n, err = p.readLengthCodedString(data[pos:]) 527 | if err != nil { 528 | return 529 | } 530 | pos += n 531 | // Name [len coded string] 532 | p.name, n, err = p.readLengthCodedString(data[pos:]) 533 | if err != nil { 534 | return 535 | } 536 | pos += n 537 | // Original name [len coded string] 538 | p.orgName, n, err = p.readLengthCodedString(data[pos:]) 539 | if err != nil { 540 | return 541 | } 542 | pos += n 543 | // Filler 544 | pos++ 545 | // Charset [16 bit uint] 546 | p.charsetNumber = btoui16(data[pos : pos+2]) 547 | pos += 2 548 | // Length [32 bit uint] 549 | p.length = btoui32(data[pos : pos+4]) 550 | pos += 4 551 | // Field type [byte] 552 | p.fieldType = data[pos] 553 | pos++ 554 | // Flags [16 bit uint] 555 | p.flags = btoui16(data[pos : pos+2]) 556 | pos += 2 557 | // Decimals [8 bit uint] 558 | p.decimals = data[pos] 559 | pos++ 560 | // Default value [len coded binary] 561 | if pos < len(data) { 562 | p.defaultVal, _, err = btolcb(data[pos:]) 563 | } 564 | } else { 565 | // Table [len coded string] 566 | p.table, n, err = p.readLengthCodedString(data[pos:]) 567 | if err != nil { 568 | return 569 | } 570 | pos += n 571 | // Name [len coded string] 572 | p.name, n, err = p.readLengthCodedString(data[pos:]) 573 | if err != nil { 574 | return 575 | } 576 | pos += n 577 | // Length [weird len coded binary] 578 | p.length = btoui32(data[pos+1 : pos+4]) 579 | pos += 4 580 | // Type [weird len coded binary] 581 | p.fieldType = data[pos+1] 582 | pos += 2 583 | // Flags [weird len coded binary] 584 | p.flags = btoui16(data[pos+1 : pos+3]) 585 | pos += 3 586 | // Decimals [8 bit uint] 587 | p.decimals = data[pos] 588 | pos++ 589 | // Default value [unknown len coded binary] 590 | // @todo 591 | } 592 | return 593 | } 594 | 595 | // Row data struct 596 | type packetRowData struct { 597 | packetBase 598 | row []interface{} 599 | } 600 | 601 | // Row data packet reader 602 | func (p *packetRowData) read(data []byte) (err os.Error) { 603 | // Recover errors 604 | defer func() { 605 | if e := recover(); e != nil { 606 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 607 | } 608 | }() 609 | // Position 610 | pos := 0 611 | // Loop until end of packet 612 | for { 613 | // Read string 614 | b, n, err := p.readLengthCodedBytes(data[pos:]) 615 | if err != nil { 616 | return 617 | } 618 | // Add to slice 619 | p.row = append(p.row, b) 620 | // Increment position and check for end of packet 621 | pos += n 622 | if pos == len(data) { 623 | break 624 | } 625 | } 626 | return 627 | } 628 | 629 | // Prepare ok struct 630 | type packetPrepareOK struct { 631 | packetBase 632 | statementId uint32 633 | columnCount uint16 634 | paramCount uint16 635 | warningCount uint16 636 | } 637 | 638 | // Prepare ok packet reader 639 | func (p *packetPrepareOK) read(data []byte) (err os.Error) { 640 | // Recover errors 641 | defer func() { 642 | if e := recover(); e != nil { 643 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 644 | } 645 | }() 646 | // Position (skip first byte/field count) 647 | pos := 1 648 | // Statement id [32 bit uint] 649 | p.statementId = btoui32(data[pos : pos+4]) 650 | pos += 4 651 | // Column count [16 bit uint] 652 | p.columnCount = btoui16(data[pos : pos+2]) 653 | pos += 2 654 | // Param count [16 bit uint] 655 | p.paramCount = btoui16(data[pos : pos+2]) 656 | pos += 2 657 | // Warning count [16 bit uint] 658 | p.warningCount = btoui16(data[pos : pos+2]) 659 | return 660 | } 661 | 662 | // Parameter struct 663 | type packetParameter struct { 664 | packetBase 665 | paramType []byte 666 | flags uint16 667 | decimals uint8 668 | length uint32 669 | } 670 | 671 | // Parameter packet reader 672 | func (p *packetParameter) read(data []byte) (err os.Error) { 673 | // Recover errors 674 | defer func() { 675 | if e := recover(); e != nil { 676 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 677 | } 678 | }() 679 | // Ignore packet for now 680 | return 681 | } 682 | 683 | // Long data struct 684 | type packetLongData struct { 685 | packetBase 686 | command byte 687 | statementId uint32 688 | paramNumber uint16 689 | data []byte 690 | } 691 | 692 | // Long data packet writer 693 | func (p *packetLongData) write() (data []byte, err os.Error) { 694 | // Recover errors 695 | defer func() { 696 | if e := recover(); e != nil { 697 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 698 | } 699 | }() 700 | // Make slice from command byte 701 | data = []byte{byte(p.command)} 702 | // Statement id 703 | data = append(data, ui32tob(p.statementId)...) 704 | // Param number 705 | data = append(data, ui16tob(p.paramNumber)...) 706 | // Data 707 | data = append(data, p.data...) 708 | // Add the packet header 709 | data = p.addHeader(data) 710 | return 711 | } 712 | 713 | // Execute struct 714 | type packetExecute struct { 715 | packetBase 716 | command byte 717 | statementId uint32 718 | flags uint8 719 | iterationCount uint32 720 | nullBitMap []byte 721 | newParamsBound uint8 722 | paramType [][]byte 723 | paramData [][]byte 724 | } 725 | 726 | // Execute packet writer 727 | func (p *packetExecute) write() (data []byte, err os.Error) { 728 | // Recover errors 729 | defer func() { 730 | if e := recover(); e != nil { 731 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 732 | } 733 | }() 734 | // Make slice from command byte 735 | data = []byte{byte(p.command)} 736 | // Statement id 737 | data = append(data, ui32tob(p.statementId)...) 738 | // Flags 739 | data = append(data, p.flags) 740 | // IterationCount 741 | data = append(data, ui32tob(p.iterationCount)...) 742 | // Null bit map 743 | data = append(data, p.nullBitMap...) 744 | // New params bound 745 | data = append(data, p.newParamsBound) 746 | // Param types 747 | if p.newParamsBound == 1 && len(p.paramType) > 0 { 748 | for _, v := range p.paramType { 749 | data = append(data, v...) 750 | } 751 | } 752 | // Param data 753 | if len(p.paramData) > 0 { 754 | for _, v := range p.paramData { 755 | if len(v) > 0 { 756 | data = append(data, v...) 757 | } 758 | } 759 | } 760 | // Add the packet header 761 | data = p.addHeader(data) 762 | return 763 | } 764 | 765 | // Binary row struct 766 | type packetRowBinary struct { 767 | packetBase 768 | data []byte 769 | } 770 | 771 | // Row binary packet reader 772 | func (p *packetRowBinary) read(data []byte) (err os.Error) { 773 | // Recover errors 774 | defer func() { 775 | if e := recover(); e != nil { 776 | err = &ClientError{CR_MALFORMED_PACKET, CR_MALFORMED_PACKET_STR} 777 | } 778 | }() 779 | // Simply store the row 780 | p.data = data 781 | return 782 | } 783 | -------------------------------------------------------------------------------- /password.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "crypto/sha1" 10 | "math" 11 | ) 12 | 13 | const SCRAMBLE_LENGTH_323 = 8 14 | 15 | // Random struct, see libmysql/password.c 16 | type randStruct struct { 17 | maxValue uint32 18 | maxValueDbl float64 19 | seed1 uint32 20 | seed2 uint32 21 | } 22 | 23 | // Initialise rand struct, see libmysql/password.c 24 | func randominit(seed1, seed2 uint32) *randStruct { 25 | return &randStruct{ 26 | maxValue: 0x3FFFFFFF, 27 | maxValueDbl: 0x3FFFFFFF, 28 | seed1: seed1 % 0x3FFFFFFF, 29 | seed2: seed2 % 0x3FFFFFFF, 30 | } 31 | } 32 | 33 | // Generate a random number, see libmysql/password.c 34 | func (r *randStruct) myRnd() float64 { 35 | r.seed1 = (r.seed1*3 + r.seed2) % r.maxValue 36 | r.seed2 = (r.seed1 + r.seed2 + 33) % r.maxValue 37 | return float64(r.seed1) / r.maxValueDbl 38 | } 39 | 40 | // Password hash used in pre-4.1, see libmysql/password.c 41 | func hashPassword(password []byte) []uint32 { 42 | nr := uint32(1345345333) 43 | add := uint32(7) 44 | nr2 := uint32(0x12345671) 45 | for i := 0; i < len(password); i++ { 46 | if password[i] == ' ' || password[i] == '\t' { 47 | continue 48 | } 49 | tmp := uint32(password[i]) 50 | nr ^= (((nr & 63) + add) * tmp) + (nr << 8) 51 | nr2 += (nr2 << 8) ^ nr 52 | add += tmp 53 | } 54 | result := make([]uint32, 2) 55 | result[0] = nr & ((1 << 31) - 1) 56 | result[1] = nr2 & ((1 << 31) - 1) 57 | return result 58 | } 59 | 60 | // Encrypt password the pre-4.1 way, see libmysql/password.c 61 | func scramble323(message, password []byte) (result []byte) { 62 | if len(password) == 0 { 63 | return 64 | } 65 | // Check message is no longer than max length 66 | if len(message) > SCRAMBLE_LENGTH_323 { 67 | message = message[:SCRAMBLE_LENGTH_323] 68 | } 69 | // Generate hashes 70 | hashPass := hashPassword(password) 71 | hashMessage := hashPassword(message) 72 | // Initialise random struct 73 | rand := randominit(hashPass[0]^hashMessage[0], hashPass[1]^hashMessage[1]) 74 | // Generate result 75 | result = make([]byte, SCRAMBLE_LENGTH_323) 76 | for i := 0; i < SCRAMBLE_LENGTH_323; i++ { 77 | result[i] = byte(math.Floor(rand.myRnd()*31) + 64) 78 | } 79 | extra := byte(math.Floor(rand.myRnd() * 31)) 80 | for i := 0; i < SCRAMBLE_LENGTH_323; i++ { 81 | result[i] ^= extra 82 | } 83 | return 84 | } 85 | 86 | // Encrypt password using 4.1+ method 87 | func scramble41(message, password []byte) (result []byte) { 88 | if len(password) == 0 { 89 | return 90 | } 91 | // stage1_hash = SHA1(password) 92 | // SHA1 encode 93 | crypt := sha1.New() 94 | crypt.Write(password) 95 | stg1Hash := crypt.Sum() 96 | // token = SHA1(SHA1(stage1_hash), scramble) XOR stage1_hash 97 | // SHA1 encode again 98 | crypt.Reset() 99 | crypt.Write(stg1Hash) 100 | stg2Hash := crypt.Sum() 101 | // SHA1 2nd hash and scramble 102 | crypt.Reset() 103 | crypt.Write(message) 104 | crypt.Write(stg2Hash) 105 | stg3Hash := crypt.Sum() 106 | // XOR with first hash 107 | result = make([]byte, 20) 108 | for i := range result { 109 | result[i] = stg3Hash[i] ^ stg1Hash[i] 110 | } 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "io" 10 | "net" 11 | "os" 12 | ) 13 | 14 | // Packet reader struct 15 | type reader struct { 16 | conn io.ReadWriteCloser 17 | protocol uint8 18 | } 19 | 20 | // Create a new reader 21 | func newReader(conn io.ReadWriteCloser) *reader { 22 | return &reader{ 23 | conn: conn, 24 | protocol: DEFAULT_PROTOCOL, 25 | } 26 | } 27 | 28 | // Read the next packet 29 | func (r *reader) readPacket(types packetType) (p packetReadable, err os.Error) { 30 | // Deferred error processing 31 | defer func() { 32 | if err != nil { 33 | // EOF errors 34 | if err == os.EOF || err == io.ErrUnexpectedEOF { 35 | err = &ClientError{CR_SERVER_LOST, CR_SERVER_LOST_STR} 36 | } 37 | // OpError 38 | if _, ok := err.(*net.OpError); ok { 39 | err = &ClientError{CR_SERVER_LOST, CR_SERVER_LOST_STR} 40 | } 41 | // Not ClientError, unknown error 42 | if _, ok := err.(*ClientError); !ok { 43 | err = &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 44 | } 45 | } 46 | }() 47 | // Read packet length 48 | pktLen, err := r.readNumber(3) 49 | if err != nil { 50 | return 51 | } 52 | // Read sequence 53 | pktSeq, err := r.readNumber(1) 54 | if err != nil { 55 | return 56 | } 57 | // Read rest of packet 58 | pktData := make([]byte, pktLen) 59 | nr, err := io.ReadFull(r.conn, pktData) 60 | if err != nil { 61 | return 62 | } 63 | if nr != int(pktLen) { 64 | err = &ClientError{CR_DATA_TRUNCATED, CR_DATA_TRUNCATED_STR} 65 | } 66 | // Work out packet type 67 | switch { 68 | // Unknown packet 69 | default: 70 | err = &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 71 | // Initialisation / handshake packet, server > client 72 | case types&PACKET_INIT != 0: 73 | pk := new(packetInit) 74 | pk.sequence = uint8(pktSeq) 75 | return pk, pk.read(pktData) 76 | // Ok packet 77 | case types&PACKET_OK != 0 && pktData[0] == 0x0: 78 | pk := new(packetOK) 79 | pk.protocol = r.protocol 80 | pk.sequence = uint8(pktSeq) 81 | return pk, pk.read(pktData) 82 | // Error packet 83 | case types&PACKET_ERROR != 0 && pktData[0] == 0xff: 84 | pk := new(packetError) 85 | pk.protocol = r.protocol 86 | pk.sequence = uint8(pktSeq) 87 | return pk, pk.read(pktData) 88 | // EOF packet 89 | case types&PACKET_EOF != 0 && pktData[0] == 0xfe: 90 | pk := new(packetEOF) 91 | pk.protocol = r.protocol 92 | pk.sequence = uint8(pktSeq) 93 | return pk, pk.read(pktData) 94 | // Result set packet 95 | case types&PACKET_RESULT != 0 && pktData[0] > 0x0 && pktData[0] < 0xfe: 96 | pk := new(packetResultSet) 97 | pk.sequence = uint8(pktSeq) 98 | return pk, pk.read(pktData) 99 | // Field packet 100 | case types&PACKET_FIELD != 0 && pktData[0] < 0xfe: 101 | pk := new(packetField) 102 | pk.protocol = r.protocol 103 | pk.sequence = uint8(pktSeq) 104 | return pk, pk.read(pktData) 105 | // Row data packet 106 | case types&PACKET_ROW != 0 && pktData[0] < 0xfe: 107 | pk := new(packetRowData) 108 | pk.sequence = uint8(pktSeq) 109 | return pk, pk.read(pktData) 110 | // Prepare ok packet 111 | case types&PACKET_PREPARE_OK != 0 && pktData[0] == 0x0: 112 | pk := new(packetPrepareOK) 113 | pk.sequence = uint8(pktSeq) 114 | return pk, pk.read(pktData) 115 | // Param packet 116 | case types&PACKET_PARAM != 0 && pktData[0] < 0xfe: 117 | pk := new(packetParameter) 118 | pk.sequence = uint8(pktSeq) 119 | return pk, pk.read(pktData) 120 | // Binary row packet 121 | case types&PACKET_ROW_BINARY != 0 && pktData[0] < 0xfe: 122 | pk := new(packetRowBinary) 123 | pk.sequence = uint8(pktSeq) 124 | return pk, pk.read(pktData) 125 | } 126 | return 127 | } 128 | 129 | // Read n bytes long number 130 | func (r *reader) readNumber(n uint8) (num uint64, err os.Error) { 131 | // Read bytes into array 132 | buf := make([]byte, n) 133 | nr, err := io.ReadFull(r.conn, buf) 134 | if err != nil || nr != int(n) { 135 | return 136 | } 137 | // Convert to uint64 138 | num = 0 139 | for i := uint8(0); i < n; i++ { 140 | num |= uint64(buf[i]) << (i * 8) 141 | } 142 | return 143 | } 144 | -------------------------------------------------------------------------------- /result.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import "os" 9 | 10 | // Result struct 11 | type Result struct { 12 | // Pointer to the client 13 | c *Client 14 | 15 | // Fields 16 | fieldCount uint64 17 | fieldPos uint64 18 | fields []*Field 19 | 20 | // Rows 21 | rowPos uint64 22 | rows []Row 23 | 24 | // Storage 25 | mode byte 26 | allRead bool 27 | } 28 | 29 | // Field type 30 | type Field struct { 31 | Database string 32 | Table string 33 | Name string 34 | Length uint32 35 | Type FieldType 36 | Flags FieldFlag 37 | Decimals uint8 38 | } 39 | 40 | // Row types 41 | type Row []interface{} 42 | type Map map[string]interface{} 43 | 44 | // Get field count 45 | func (r *Result) FieldCount() uint64 { 46 | return r.fieldCount 47 | } 48 | 49 | // Fetch the next field 50 | func (r *Result) FetchField() *Field { 51 | // Check if all fields have been fetched 52 | if r.fieldPos < uint64(len(r.fields)) { 53 | // Increment and return current field 54 | r.fieldPos++ 55 | return r.fields[r.fieldPos-1] 56 | } 57 | return nil 58 | } 59 | 60 | // Fetch all fields 61 | func (r *Result) FetchFields() []*Field { 62 | return r.fields 63 | } 64 | 65 | // Get row count 66 | func (r *Result) RowCount() uint64 { 67 | // Stored mode 68 | if r.mode == RESULT_STORED { 69 | return uint64(len(r.rows)) 70 | } 71 | return 0 72 | } 73 | 74 | // Fetch a row 75 | func (r *Result) FetchRow() Row { 76 | // Stored result 77 | if r.mode == RESULT_STORED { 78 | // Check if all rows have been fetched 79 | if r.rowPos < uint64(len(r.rows)) { 80 | // Increment position and return current row 81 | r.rowPos++ 82 | return r.rows[r.rowPos-1] 83 | } 84 | } 85 | // Used result 86 | if r.mode == RESULT_USED { 87 | if r.allRead == false { 88 | eof, err := r.c.getRow() 89 | if err != nil { 90 | return nil 91 | } 92 | if eof { 93 | r.allRead = true 94 | } else { 95 | return r.rows[0] 96 | } 97 | } 98 | } 99 | return nil 100 | } 101 | 102 | // Fetch a map 103 | func (r *Result) FetchMap() Map { 104 | // Fetch row 105 | row := r.FetchRow() 106 | if row != nil { 107 | rowMap := make(Map) 108 | for key, val := range row { 109 | rowMap[r.fields[key].Name] = val 110 | } 111 | return rowMap 112 | } 113 | return nil 114 | } 115 | 116 | // Fetch all rows 117 | func (r *Result) FetchRows() []Row { 118 | if r.mode == RESULT_STORED { 119 | return r.rows 120 | } 121 | return nil 122 | } 123 | 124 | // Free the result 125 | func (r *Result) Free() (err os.Error) { 126 | err = r.c.FreeResult() 127 | return 128 | } 129 | -------------------------------------------------------------------------------- /statement.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "os" 10 | "reflect" 11 | "strconv" 12 | ) 13 | 14 | // Prepared statement struct 15 | type Statement struct { 16 | // Client pointer 17 | c *Client 18 | 19 | // Statement status flags 20 | prepared bool 21 | preparedSql string 22 | paramsBound bool 23 | paramsRebound bool 24 | 25 | // Statement id 26 | statementId uint32 27 | 28 | // Params 29 | paramCount uint16 30 | paramType [][]byte 31 | paramData [][]byte 32 | 33 | // Columns (fields) 34 | columnCount uint64 35 | 36 | // Result 37 | AffectedRows uint64 38 | LastInsertId uint64 39 | Warnings uint16 40 | result *Result 41 | resultParams []interface{} 42 | } 43 | 44 | // Prepare new statement 45 | func (s *Statement) Prepare(sql string) (err os.Error) { 46 | // Auto reconnect 47 | defer func() { 48 | if err != nil && s.c.checkNet(err) && s.c.Reconnect { 49 | s.c.log(1, "!!! Lost connection to server !!!") 50 | s.c.connected = false 51 | err = s.c.reconnect() 52 | if err == nil { 53 | err = s.Prepare(sql) 54 | } 55 | } 56 | }() 57 | // Log prepare 58 | s.c.log(1, "=== Begin prepare '%s' ===", sql) 59 | // Pre-run checks 60 | if !s.c.checkConn() || s.checkResult() { 61 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 62 | } 63 | // Reset client 64 | s.reset() 65 | // Send close command 66 | err = s.c.command(COM_STMT_PREPARE, sql) 67 | if err != nil { 68 | return 69 | } 70 | // Read result from server 71 | s.c.sequence++ 72 | _, err = s.getResult(PACKET_PREPARE_OK | PACKET_ERROR) 73 | if err != nil { 74 | return 75 | } 76 | // Read param packets 77 | if s.paramCount > 0 { 78 | for { 79 | s.c.sequence++ 80 | eof, err := s.getResult(PACKET_PARAM | PACKET_EOF) 81 | if err != nil { 82 | return 83 | } 84 | if eof { 85 | break 86 | } 87 | } 88 | } 89 | // Read field packets 90 | if s.columnCount > 0 { 91 | err = s.getFields() 92 | if err != nil { 93 | return 94 | } 95 | } 96 | // Statement is preapred 97 | s.prepared = true 98 | s.preparedSql = sql 99 | return 100 | } 101 | 102 | // Get number of params 103 | func (s *Statement) ParamCount() uint16 { 104 | return s.paramCount 105 | } 106 | 107 | // Bind params 108 | func (s *Statement) BindParams(params ...interface{}) (err os.Error) { 109 | // Check prepared 110 | if !s.prepared { 111 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 112 | } 113 | // Check number of params is correct 114 | if len(params) != int(s.paramCount) { 115 | return &ClientError{CR_INVALID_PARAMETER_NO, CR_INVALID_PARAMETER_NO_STR} 116 | } 117 | // Reset params 118 | s.paramType = [][]byte{} 119 | s.paramData = [][]byte{} 120 | // Convert params into bytes 121 | for k, param := range params { 122 | // Temp vars 123 | var t FieldType 124 | var d []byte 125 | // Switch on type 126 | switch param.(type) { 127 | // Nil 128 | case nil: 129 | t = FIELD_TYPE_NULL 130 | // Int 131 | case int: 132 | if strconv.IntSize == 32 { 133 | t = FIELD_TYPE_LONG 134 | } else { 135 | t = FIELD_TYPE_LONGLONG 136 | } 137 | d = itob(param.(int)) 138 | // Uint 139 | case uint: 140 | if strconv.IntSize == 32 { 141 | t = FIELD_TYPE_LONG 142 | } else { 143 | t = FIELD_TYPE_LONGLONG 144 | } 145 | d = uitob(param.(uint)) 146 | // Int8 147 | case int8: 148 | t = FIELD_TYPE_TINY 149 | d = []byte{byte(param.(int8))} 150 | // Uint8 151 | case uint8: 152 | t = FIELD_TYPE_TINY 153 | d = []byte{param.(uint8)} 154 | // Int16 155 | case int16: 156 | t = FIELD_TYPE_SHORT 157 | d = i16tob(param.(int16)) 158 | // Uint16 159 | case uint16: 160 | t = FIELD_TYPE_SHORT 161 | d = ui16tob(param.(uint16)) 162 | // Int32 163 | case int32: 164 | t = FIELD_TYPE_LONG 165 | d = i32tob(param.(int32)) 166 | // Uint32 167 | case uint32: 168 | t = FIELD_TYPE_LONG 169 | d = ui32tob(param.(uint32)) 170 | // Int64 171 | case int64: 172 | t = FIELD_TYPE_LONGLONG 173 | d = i64tob(param.(int64)) 174 | // Uint64 175 | case uint64: 176 | t = FIELD_TYPE_LONGLONG 177 | d = ui64tob(param.(uint64)) 178 | // Float32 179 | case float32: 180 | t = FIELD_TYPE_FLOAT 181 | d = f32tob(param.(float32)) 182 | // Float64 183 | case float64: 184 | t = FIELD_TYPE_DOUBLE 185 | d = f64tob(param.(float64)) 186 | // String 187 | case string: 188 | t = FIELD_TYPE_STRING 189 | d = lcbtob(uint64(len(param.(string)))) 190 | d = append(d, []byte(param.(string))...) 191 | // Byte array 192 | case []byte: 193 | t = FIELD_TYPE_BLOB 194 | d = lcbtob(uint64(len(param.([]byte)))) 195 | d = append(d, param.([]byte)...) 196 | // Other types 197 | default: 198 | return &ClientError{CR_UNSUPPORTED_PARAM_TYPE, s.c.fmtError(CR_UNSUPPORTED_PARAM_TYPE_STR, reflect.ValueOf(param).Type(), k)} 199 | } 200 | // Append values 201 | s.paramType = append(s.paramType, []byte{byte(t), 0x0}) 202 | s.paramData = append(s.paramData, d) 203 | } 204 | // Flag params as bound 205 | s.paramsBound = true 206 | s.paramsRebound = true 207 | return 208 | } 209 | 210 | // Send long data 211 | func (s *Statement) SendLongData(num int, data []byte) (err os.Error) { 212 | // Auto reconnect 213 | defer func() { 214 | err = s.c.simpleReconnect(err) 215 | }() 216 | // Log send long data 217 | s.c.log(1, "=== Begin send long data ===") 218 | // Check prepared 219 | if !s.prepared { 220 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 221 | } 222 | // Pre-run checks 223 | if !s.c.checkConn() || s.checkResult() { 224 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 225 | } 226 | // Reset client 227 | s.reset() 228 | // Data position (if data is longer than max packet length 229 | pos := 0 230 | // Send data 231 | for { 232 | // Construct packet 233 | p := &packetLongData{ 234 | command: uint8(COM_STMT_SEND_LONG_DATA), 235 | statementId: s.statementId, 236 | paramNumber: uint16(num), 237 | } 238 | // Add protocol and sequence 239 | p.protocol = s.c.protocol 240 | p.sequence = s.c.sequence 241 | // Add data 242 | if len(data[pos:]) > MAX_PACKET_SIZE-12 { 243 | p.data = data[pos : MAX_PACKET_SIZE-12] 244 | pos += MAX_PACKET_SIZE - 12 245 | } else { 246 | p.data = data[pos:] 247 | pos += len(data[pos:]) 248 | } 249 | // Write packet 250 | err = s.c.w.writePacket(p) 251 | if err != nil { 252 | return 253 | } 254 | // Log write success 255 | s.c.log(1, "[%d] Sent long data packet", p.sequence) 256 | // Check if all data sent 257 | if pos == len(data) { 258 | break 259 | } 260 | // Increment sequence 261 | s.c.sequence++ 262 | } 263 | return 264 | } 265 | 266 | // Execute 267 | func (s *Statement) Execute() (err os.Error) { 268 | // Auto reconnect 269 | defer func() { 270 | if err != nil && s.c.checkNet(err) && s.c.Reconnect { 271 | s.c.log(1, "!!! Lost connection to server !!!") 272 | s.c.connected = false 273 | err = s.c.reconnect() 274 | if err == nil { 275 | err = s.Prepare(s.preparedSql) 276 | if err == nil { 277 | err = s.Execute() 278 | } 279 | } 280 | } 281 | }() 282 | // Log execute 283 | s.c.log(1, "=== Begin execute ===") 284 | // Check prepared 285 | if !s.prepared { 286 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 287 | } 288 | // Check params bound 289 | if s.paramCount > 0 && !s.paramsBound { 290 | return &ClientError{CR_PARAMS_NOT_BOUND, CR_PARAMS_NOT_BOUND_STR} 291 | } 292 | // Pre-run checks 293 | if !s.c.checkConn() || s.checkResult() { 294 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 295 | } 296 | // Reset client 297 | s.reset() 298 | // Construct packet 299 | p := &packetExecute{ 300 | command: byte(COM_STMT_EXECUTE), 301 | statementId: s.statementId, 302 | flags: byte(CURSOR_TYPE_NO_CURSOR), 303 | iterationCount: 1, 304 | nullBitMap: s.getNullBitMap(), 305 | paramType: s.paramType, 306 | paramData: s.paramData, 307 | } 308 | // Add protocol and sequence 309 | p.protocol = s.c.protocol 310 | p.sequence = s.c.sequence 311 | // Add rebound flag 312 | if s.paramsRebound { 313 | p.newParamsBound = byte(1) 314 | } 315 | // Write packet 316 | err = s.c.w.writePacket(p) 317 | if err != nil { 318 | return 319 | } 320 | // Log write success 321 | s.c.log(1, "[%d] Sent execute packet", p.sequence) 322 | // Read result from server 323 | s.c.sequence++ 324 | _, err = s.getResult(PACKET_OK | PACKET_ERROR | PACKET_RESULT) 325 | if err != nil || s.result == nil { 326 | return 327 | } 328 | // Store fields 329 | err = s.getFields() 330 | // Unflag params rebound 331 | s.paramsRebound = false 332 | return 333 | } 334 | 335 | // Get field count 336 | func (s *Statement) FieldCount() uint64 { 337 | if s.checkResult() { 338 | return s.result.fieldCount 339 | } 340 | return 0 341 | } 342 | 343 | // Fetch the next field 344 | func (s *Statement) FetchColumn() *Field { 345 | if s.checkResult() { 346 | // Check if all fields have been fetched 347 | if s.result.fieldPos < uint64(len(s.result.fields)) { 348 | // Increment and return current field 349 | s.result.fieldPos++ 350 | return s.result.fields[s.result.fieldPos-1] 351 | } 352 | } 353 | return nil 354 | } 355 | 356 | // Fetch all fields 357 | func (s *Statement) FetchColumns() []*Field { 358 | if s.checkResult() { 359 | return s.result.fields 360 | } 361 | return nil 362 | } 363 | 364 | // Bind result 365 | func (s *Statement) BindResult(params ...interface{}) (err os.Error) { 366 | s.resultParams = params 367 | return 368 | } 369 | 370 | // Get row count 371 | func (s *Statement) RowCount() uint64 { 372 | // Stored mode 373 | if s.checkResult() && s.result.mode == RESULT_STORED { 374 | return uint64(len(s.result.rows)) 375 | } 376 | return 0 377 | } 378 | 379 | // Fetch next row 380 | func (s *Statement) Fetch() (eof bool, err os.Error) { 381 | // Auto reconnect 382 | defer func() { 383 | err = s.c.simpleReconnect(err) 384 | }() 385 | // Log fetch 386 | s.c.log(1, "=== Begin fetch ===") 387 | // Check prepared 388 | if !s.prepared { 389 | return false, &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 390 | } 391 | // Check result 392 | if !s.checkResult() { 393 | return false, &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 394 | } 395 | var row Row 396 | // Check result mode 397 | switch s.result.mode { 398 | // Used or unused result (needs fetching) 399 | case RESULT_UNUSED, RESULT_USED: 400 | s.result.mode = RESULT_USED 401 | if s.result.allRead == true { 402 | return true, nil 403 | } 404 | eof, err := s.getRow() 405 | if err != nil { 406 | return false, err 407 | } 408 | if eof { 409 | s.result.allRead = true 410 | return true, nil 411 | } 412 | row = s.result.rows[0] 413 | // Stored result 414 | case RESULT_STORED: 415 | if s.result.rowPos >= uint64(len(s.result.rows)) { 416 | return true, nil 417 | } 418 | row = s.result.rows[s.result.rowPos] 419 | s.result.rowPos++ 420 | } 421 | // Recover possible errors from type conversion 422 | defer func() { 423 | if e := recover(); e != nil { 424 | err = &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 425 | return 426 | } 427 | }() 428 | // Iterate bound params and assign from row (partial set quicker this way) 429 | for k, v := range s.resultParams { 430 | switch t := v.(type) { 431 | // Integer types 432 | case *int: 433 | *t = int(atoui64(row[k])) 434 | case *uint: 435 | *t = uint(atoui64(row[k])) 436 | case *int8: 437 | *t = int8(atoui64(row[k])) 438 | case *uint8: 439 | *t = uint8(atoui64(row[k])) 440 | case *int16: 441 | *t = int16(atoui64(row[k])) 442 | case *uint16: 443 | *t = uint16(atoui64(row[k])) 444 | case *int32: 445 | *t = int32(atoui64(row[k])) 446 | case *uint32: 447 | *t = uint32(atoui64(row[k])) 448 | case *int64: 449 | *t = int64(atoui64(row[k])) 450 | case *uint64: 451 | *t = atoui64(row[k]) 452 | // Floating point types 453 | case *float32: 454 | *t = float32(atof64(row[k])) 455 | case *float64: 456 | *t = atof64(row[k]) 457 | // Byte slice, assertion 458 | case *[]byte: 459 | *t = row[k].([]byte) 460 | // Strings 461 | case *string: 462 | *t = atos(row[k]) 463 | // Date/time, assertion 464 | case *Date: 465 | *t = row[k].(Date) 466 | case *Time: 467 | *t = row[k].(Time) 468 | case *DateTime: 469 | *t = row[k].(DateTime) 470 | } 471 | } 472 | return 473 | } 474 | 475 | // Store result 476 | func (s *Statement) StoreResult() (err os.Error) { 477 | // Auto reconnect 478 | defer func() { 479 | err = s.c.simpleReconnect(err) 480 | }() 481 | // Log store result 482 | s.c.log(1, "=== Begin store result ===") 483 | // Check prepared 484 | if !s.prepared { 485 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 486 | } 487 | // Check if result already used/stored 488 | if s.result.mode != RESULT_UNUSED { 489 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 490 | } 491 | // Set storage mode 492 | s.result.mode = RESULT_STORED 493 | // Store all rows 494 | err = s.getAllRows() 495 | if err != nil { 496 | return 497 | } 498 | s.result.allRead = true 499 | return 500 | } 501 | 502 | // Free result 503 | func (s *Statement) FreeResult() (err os.Error) { 504 | // Auto reconnect 505 | defer func() { 506 | err = s.c.simpleReconnect(err) 507 | }() 508 | // Log free result 509 | s.c.log(1, "=== Begin free result ===") 510 | // Check prepared 511 | if !s.prepared { 512 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 513 | } 514 | // Check result 515 | if !s.checkResult() { 516 | return &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 517 | } 518 | // Free the current result set 519 | s.freeAll(false) 520 | return 521 | } 522 | 523 | // More results 524 | func (s *Statement) MoreResults() bool { 525 | return s.c.MoreResults() 526 | } 527 | 528 | // Next result 529 | func (s *Statement) NextResult() (more bool, err os.Error) { 530 | // Auto reconnect 531 | defer func() { 532 | err = s.c.simpleReconnect(err) 533 | }() 534 | // Log next result 535 | s.c.log(1, "=== Begin next result ===") 536 | // Check prepared 537 | if !s.prepared { 538 | return false, &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 539 | } 540 | // Pre-run checks 541 | if !s.c.checkConn() || s.checkResult() { 542 | return false, &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 543 | } 544 | // Check for more results 545 | more = s.MoreResults() 546 | if !more { 547 | return 548 | } 549 | // Read result from server 550 | s.c.sequence++ 551 | _, err = s.getResult(PACKET_OK | PACKET_ERROR | PACKET_RESULT) 552 | if err != nil || s.result == nil { 553 | return 554 | } 555 | // Store fields 556 | err = s.getFields() 557 | return 558 | } 559 | 560 | // Reset statement 561 | func (s *Statement) Reset() (err os.Error) { 562 | // Auto reconnect 563 | defer func() { 564 | err = s.c.simpleReconnect(err) 565 | }() 566 | // Log next result 567 | s.c.log(1, "=== Begin reset statement ===") 568 | // Check prepared 569 | if !s.prepared { 570 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 571 | } 572 | // Pre-run checks 573 | if !s.c.checkConn() { 574 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 575 | } 576 | // Free any results 577 | if s.checkResult() { 578 | err = s.freeAll(true) 579 | } 580 | // Reset client 581 | s.reset() 582 | // Send command 583 | err = s.c.command(COM_STMT_RESET, s.statementId) 584 | if err != nil { 585 | return 586 | } 587 | // Read result from server 588 | s.c.sequence++ 589 | _, err = s.getResult(PACKET_OK | PACKET_ERROR) 590 | return 591 | } 592 | 593 | // Close statement 594 | func (s *Statement) Close() (err os.Error) { 595 | // Auto reconnect 596 | defer func() { 597 | err = s.c.simpleReconnect(err) 598 | }() 599 | // Log next result 600 | s.c.log(1, "=== Begin close statement ===") 601 | // Check prepared 602 | if !s.prepared { 603 | return &ClientError{CR_NO_PREPARE_STMT, CR_NO_PREPARE_STMT_STR} 604 | } 605 | // Pre-run checks 606 | if !s.c.checkConn() || s.checkResult() { 607 | return &ClientError{CR_COMMANDS_OUT_OF_SYNC, CR_COMMANDS_OUT_OF_SYNC_STR} 608 | } 609 | // Reset client 610 | s.reset() 611 | // Send command 612 | err = s.c.command(COM_STMT_CLOSE, s.statementId) 613 | return 614 | } 615 | 616 | // Reset the statement 617 | func (s *Statement) reset() { 618 | s.AffectedRows = 0 619 | s.LastInsertId = 0 620 | s.Warnings = 0 621 | s.result = nil 622 | s.c.reset() 623 | } 624 | 625 | // Check if a result exists 626 | func (s *Statement) checkResult() bool { 627 | if s.result != nil { 628 | return true 629 | } 630 | return false 631 | } 632 | 633 | // Get null bit map 634 | func (s *Statement) getNullBitMap() (nbm []byte) { 635 | nbm = make([]byte, (s.paramCount+7)/8) 636 | bm := uint64(0) 637 | // Check if params are null (nil) 638 | for i := uint16(0); i < s.paramCount; i++ { 639 | if s.paramType[i][0] == byte(FIELD_TYPE_NULL) { 640 | bm += 1 << uint(i) 641 | } 642 | } 643 | // Convert the uint64 value into bytes 644 | for i := 0; i < len(nbm); i++ { 645 | nbm[i] = byte(bm >> uint(i*8)) 646 | } 647 | return 648 | } 649 | 650 | // Get all result fields 651 | func (s *Statement) getFields() (err os.Error) { 652 | // Loop till EOF 653 | for { 654 | s.c.sequence++ 655 | eof, err := s.getResult(PACKET_FIELD | PACKET_EOF) 656 | if err != nil { 657 | return 658 | } 659 | if eof { 660 | break 661 | } 662 | } 663 | return 664 | } 665 | 666 | // Get next row for a result 667 | func (s *Statement) getRow() (eof bool, err os.Error) { 668 | // Check for a valid result 669 | if s.result == nil { 670 | return false, &ClientError{CR_NO_RESULT_SET, CR_NO_RESULT_SET_STR} 671 | } 672 | // Read next row packet or EOF 673 | s.c.sequence++ 674 | eof, err = s.getResult(PACKET_ROW_BINARY | PACKET_EOF) 675 | return 676 | } 677 | 678 | // Get all rows for the result 679 | func (s *Statement) getAllRows() (err os.Error) { 680 | for { 681 | eof, err := s.getRow() 682 | if err != nil { 683 | return 684 | } 685 | if eof { 686 | break 687 | } 688 | } 689 | return 690 | } 691 | 692 | // Get result 693 | func (s *Statement) getResult(types packetType) (eof bool, err os.Error) { 694 | // Log read result 695 | s.c.log(1, "Reading result packet from server") 696 | // Get result packet 697 | p, err := s.c.r.readPacket(types) 698 | if err != nil { 699 | return 700 | } 701 | // Process result packet 702 | switch p.(type) { 703 | default: 704 | err = &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 705 | case *packetOK: 706 | err = handleOK(p.(*packetOK), s.c, &s.AffectedRows, &s.LastInsertId, &s.Warnings) 707 | case *packetError: 708 | err = handleError(p.(*packetError), s.c) 709 | case *packetEOF: 710 | eof = true 711 | err = handleEOF(p.(*packetEOF), s.c) 712 | case *packetPrepareOK: 713 | err = handlePrepareOK(p.(*packetPrepareOK), s.c, s) 714 | case *packetParameter: 715 | err = handleParam(p.(*packetParameter), s.c) 716 | case *packetField: 717 | err = handleField(p.(*packetField), s.c, s.result) 718 | case *packetResultSet: 719 | s.result = &Result{c: s.c} 720 | err = handleResultSet(p.(*packetResultSet), s.c, s.result) 721 | case *packetRowBinary: 722 | err = handleBinaryRow(p.(*packetRowBinary), s.c, s.result) 723 | } 724 | return 725 | } 726 | 727 | // Free any result sets waiting to be read 728 | func (s *Statement) freeAll(next bool) (err os.Error) { 729 | // Check for unread rows 730 | if !s.result.allRead { 731 | // Read all rows 732 | err = s.getAllRows() 733 | if err != nil { 734 | return 735 | } 736 | } 737 | // Unset the result 738 | s.result = nil 739 | // Check for next result 740 | if next { 741 | for { 742 | // Check if more results exist 743 | if !s.c.MoreResults() { 744 | break 745 | } 746 | // Get next result 747 | s.c.sequence++ 748 | _, err = s.getResult(PACKET_OK | PACKET_ERROR | PACKET_RESULT) 749 | if err != nil { 750 | return 751 | } 752 | if s.result == nil { 753 | continue 754 | } 755 | // Set result mode to RESULT_FREE 756 | s.result.mode = RESULT_FREE 757 | // Read fields 758 | err = s.getFields() 759 | if err != nil { 760 | return 761 | } 762 | // Read rows 763 | err = s.getAllRows() 764 | if err != nil { 765 | return 766 | } 767 | } 768 | } 769 | return 770 | } 771 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import "fmt" 9 | 10 | // Date struct 11 | type Date struct { 12 | Year uint16 13 | Month uint8 14 | Day uint8 15 | } 16 | 17 | // Get date as string 18 | func (d *Date) String() string { 19 | return fmt.Sprintf("%d-%02d-%02d", d.Year, d.Month, d.Day) 20 | } 21 | 22 | // Time struct 23 | type Time struct { 24 | Hour uint8 25 | Minute uint8 26 | Second uint8 27 | } 28 | 29 | // Get time as string 30 | func (t *Time) String() string { 31 | return fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) 32 | } 33 | 34 | // DateTime struct 35 | type DateTime struct { 36 | Year uint16 37 | Month uint8 38 | Day uint8 39 | Hour uint8 40 | Minute uint8 41 | Second uint8 42 | } 43 | 44 | // Get date/time as string 45 | func (d *DateTime) String() string { 46 | return fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d", d.Year, d.Month, d.Day, d.Hour, d.Minute, d.Second) 47 | } 48 | -------------------------------------------------------------------------------- /writer.go: -------------------------------------------------------------------------------- 1 | // GoMySQL - A MySQL client library for Go 2 | // 3 | // Copyright 2010-2011 Phil Bayfield. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | package mysql 7 | 8 | import ( 9 | "io" 10 | "net" 11 | "os" 12 | ) 13 | 14 | // Packet writer struct 15 | type writer struct { 16 | conn io.ReadWriteCloser 17 | } 18 | 19 | // Create a new reader 20 | func newWriter(conn io.ReadWriteCloser) *writer { 21 | return &writer{ 22 | conn: conn, 23 | } 24 | } 25 | 26 | // Write packet to the server 27 | func (w *writer) writePacket(p packetWritable) (err os.Error) { 28 | // Deferred error processing 29 | defer func() { 30 | if err != nil { 31 | // EOF errors 32 | if err == os.EOF || err == io.ErrUnexpectedEOF { 33 | err = &ClientError{CR_SERVER_LOST, CR_SERVER_LOST_STR} 34 | } 35 | // OpError 36 | if _, ok := err.(*net.OpError); ok { 37 | err = &ClientError{CR_SERVER_LOST, CR_SERVER_LOST_STR} 38 | } 39 | // Not ClientError, unknown error 40 | if _, ok := err.(*ClientError); !ok { 41 | err = &ClientError{CR_UNKNOWN_ERROR, CR_UNKNOWN_ERROR_STR} 42 | } 43 | } 44 | }() 45 | // Get data in binary format 46 | pktData, err := p.write() 47 | if err != nil { 48 | return 49 | } 50 | // Write packet 51 | nw, err := w.conn.Write(pktData) 52 | if err != nil { 53 | return 54 | } 55 | if nw != len(pktData) { 56 | err = &ClientError{CR_DATA_TRUNCATED, CR_DATA_TRUNCATED_STR} 57 | } 58 | return 59 | } 60 | --------------------------------------------------------------------------------