├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmark └── speed_test.dart ├── connection.options.example ├── example └── example.dart ├── javaspeed ├── build.gradle ├── gradlew ├── gradlew.bat └── src │ └── main │ └── com │ └── jamesots │ └── dart │ └── sqljocky │ └── speedtest │ └── SpeedTest.java ├── lib ├── constants.dart ├── sqljocky.dart ├── src │ ├── auth │ │ ├── auth_handler.dart │ │ ├── character_set.dart │ │ ├── handshake_handler.dart │ │ └── ssl_handler.dart │ ├── blob.dart │ ├── buffer.dart │ ├── buffered_socket.dart │ ├── connection.dart │ ├── connection_pool.dart │ ├── handlers │ │ ├── debug_handler.dart │ │ ├── handler.dart │ │ ├── ok_packet.dart │ │ ├── parameter_packet.dart │ │ ├── ping_handler.dart │ │ ├── quit_handler.dart │ │ └── use_db_handler.dart │ ├── mysql_client_error.dart │ ├── mysql_exception.dart │ ├── mysql_protocol_error.dart │ ├── prepared_statements │ │ ├── binary_data_packet.dart │ │ ├── close_statement_handler.dart │ │ ├── execute_query_handler.dart │ │ ├── prepare_handler.dart │ │ ├── prepare_ok_packet.dart │ │ └── prepared_query.dart │ ├── query.dart │ ├── query │ │ ├── query_stream_handler.dart │ │ ├── result_set_header_packet.dart │ │ └── standard_data_packet.dart │ ├── results.dart │ ├── results │ │ ├── field.dart │ │ ├── field_impl.dart │ │ ├── results.dart │ │ ├── results_impl.dart │ │ └── row.dart │ ├── retained_connection.dart │ ├── transaction.dart │ └── utils.dart └── utils.dart ├── pubspec.yaml ├── test ├── integration │ ├── base.dart │ ├── blob.dart │ ├── charset.dart │ ├── errors.dart │ ├── execute_multi.dart │ ├── largeblob.dart │ ├── nullmap.dart │ ├── numbers.dart │ ├── one.dart │ ├── prepared_query.dart │ ├── row.dart │ ├── stored_procedures.dart │ ├── stream.dart │ └── two.dart ├── integration_test.dart ├── interleave_test.dart ├── unit │ ├── auth_handler_test.dart │ ├── binary_data_packet_test.dart │ ├── buffer_test.dart │ ├── buffered_socket_test.dart │ ├── connection_test.dart │ ├── execute_query_handler_test.dart │ ├── field_by_name_test.dart │ ├── handshake_handler_test.dart │ ├── prepared_statements_test.dart │ ├── serialize_test.dart │ └── types_test.dart └── unit_test.dart └── tool └── build_docs /.gitignore: -------------------------------------------------------------------------------- 1 | connection.options 2 | .children 3 | .project 4 | .idea 5 | *.iml 6 | examples 7 | docs 8 | .packages 9 | .pub 10 | packages 11 | pubspec.lock 12 | *~ 13 | out 14 | javaspeed/build 15 | javaspeed/gradle 16 | javaspeed/.gradle 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | v0.14.2 5 | ------- 6 | * Updated crypto dependency 7 | 8 | v0.14.1 9 | ------- 10 | * Fix the changelog formatting, so you can actually see what changed in v0.14.0 11 | 12 | v0.14.0 13 | ------- 14 | * Requires Dart 1.11 15 | * Use newer logging library 16 | * Use async/await in library code and examples. 17 | * Fix bug with closing prepared queries, where it sometimes tried to close a query which was in use. 18 | * Don't throw an error if username is null. 19 | * Fix bug in blobs, where it was trying to decode binary blobs as UTF-8 strings. 20 | * Close connections and return them to the pool when a connection times out on the server. 21 | 22 | v0.13.0 23 | ------- 24 | * Fixes an issue with executeMulti being broken. 25 | * Fixes an issue with query failing if the first field in a SELECT is an empty string 26 | 27 | v0.12.0 28 | ------- 29 | * Breaking change: ConnectionPool.close() has been renamed to ConnectionPool.closeConnectionsNow. 30 | It is a dangerous method to call as it closes all connections even if they are in the middle 31 | of an operation. ConnectionPool.closeConnectionsWhenNotInUse has been added, which is much 32 | safer. 33 | * Fixed an issue with closing prepared queries which caused connections to remain open. 34 | 35 | v0.11.0 36 | ------- 37 | * Added support for packets larger than 16 MB. ConnectionPool's constructor has a new parameter, 38 | 'maxPacketSize', which specifies the maximum packet size in bytes. Using packets larger than 39 | 16 MB is not currently particularly optimised. 40 | * Fixed some issues with authentication. In particular, errors should now be thrown when you 41 | try to connect to a server which is using an old or unsupported authentication protocol. 42 | 43 | v0.10.0 44 | ------- 45 | * Added SSL connections. Pass 'useSSL: true' to ConnectionPool constructor. If server doesn't support 46 | SSL, connection will continue unsecured. You can check if the connections are secure by calling 47 | pool.getConnection().then((cnx) {print(cnx.usingSSL); cnx.release();}); 48 | 49 | v0.9.0 50 | ------ 51 | * Added ConnectionPool.getConnection() which returns a RetainedConnection. Useful 52 | if you need to keep a specific connection around (for example, if you need to 53 | lock tables). 54 | 55 | v0.8.3 56 | ------ 57 | * Fixed connection retention error in Query.executeMulti 58 | 59 | v0.8.1 60 | ------ 61 | * Can now access fields by name. 62 | 63 | v0.8.0 64 | ------ 65 | * Breaking change: Results no longer has a 'stream' property - it now implements Stream itself. 66 | As a result, it also no longer has a 'rows' property, or a 'toResultsList()' method - you 67 | can use 'toList()' to convert it into a list instead. 68 | 69 | v0.7.0 70 | ------ 71 | * Rewritten some connection handling code to make it more robust, and 72 | so that it handles stream operations such as 'first' correctly (i.e. 73 | without hanging forever). 74 | * Updated spec for Dart 1.0 75 | 76 | v0.6.2 77 | ------ 78 | * Support for latest SDK (removal of dart:utf8 library) 79 | 80 | v0.6.1 81 | ------ 82 | * Support for latest SDK 83 | 84 | v0.6.0 85 | ------ 86 | * Change prepared statement syntax. Values must now be passed into the execute() method 87 | in an array. This change was made because otherwise prepared statements couldn't be used 88 | asynchronously correctly - if you used the same prepared query object for multiple queries 89 | 'at the same time', the wrong values could get used. 90 | 91 | v0.5.8 92 | ------ 93 | * Handle errors in the utils package properly 94 | * Pre-emptively fixed some errors, wrote more tests. 95 | 96 | v0.5.7 97 | ------ 98 | * Fixed error with large fields. 99 | 100 | v0.5.6 101 | ------ 102 | * Hopefully full unicode support 103 | * Fixed problem with null values in prepared queries. 104 | 105 | v0.5.5 106 | ------ 107 | * Some initial changes for better unicode handling. 108 | 109 | v0.5.4 110 | ------ 111 | * Blobs and Texts which are bigger than 250 characters now work. 112 | 113 | v0.5.3 114 | ------ 115 | * Make ConnectionPool and Transaction implement QueriableConnection 116 | * Improved tests. 117 | 118 | v0.5.2 119 | ------ 120 | * Fix for new SDK 121 | 122 | v0.5.1 123 | ------ 124 | * Made an internal class private 125 | 126 | v0.5.0 127 | ------ 128 | * Breaking change: Now uses streams to return results. 129 | 130 | v0.4.1 131 | ------ 132 | * Major refactoring so that only the parts of sqljocky which are supposed to be exposed are. 133 | 134 | v0.4.0 135 | ------ 136 | * Support for M4. 137 | 138 | v0.3.0 139 | ------ 140 | * Support for M3. 141 | * Bit fields are now numbers, not lists. 142 | * Dates now use the DateTime class instead of the Date class. 143 | * Use new IO classes. 144 | 145 | v0.2.0 146 | ------ 147 | * Support for the new SDK. 148 | 149 | v0.1.3 150 | ------ 151 | * SQLJocky now uses a connection pooling model, so the API has changed somewhat. 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 James Ots 2 | 3 | This program is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation; either version 2 6 | of the License, or (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program; if not, write to the Free Software 15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SQLJocky 2 | ======== 3 | 4 | This is a MySQL connector for the Dart programming language. It isn't finished, but should 5 | work for most normal use. The API is getting reasonably close to where I want it to 6 | be now, so hopefully there shouldn't be too many breaking changes in the future. 7 | 8 | It will only work in the command-line VM, not in a browser. 9 | 10 | News 11 | ---- 12 | 13 | The changelog has now been moved to CHANGELOG.md 14 | 15 | Usage 16 | ----- 17 | 18 | Create a connection pool: 19 | 20 | ```dart 21 | var pool = new ConnectionPool( 22 | host: 'localhost', port: 3306, 23 | user: 'bob', password: 'wibble', 24 | db: 'stuff', max: 5); 25 | ``` 26 | 27 | Execute a query: 28 | 29 | ```dart 30 | var results = await pool.query('select name, email from users'); 31 | ``` 32 | 33 | Use the results: (*Note: forEach is asynchronous.*) 34 | 35 | ```dart 36 | results.forEach((row) { 37 | print('Name: ${row[0]}, email: ${row[1]}'); 38 | }); 39 | ``` 40 | 41 | Or access the fields by name: 42 | 43 | ```dart 44 | results.forEach((row) { 45 | print('Name: ${row.name}, email: ${row.email}'); 46 | }); 47 | ``` 48 | 49 | Prepare a query: 50 | 51 | ```dart 52 | var query = await pool.prepare( 53 | 'insert into users (name, email, age) values (?, ?, ?)'); 54 | ``` 55 | 56 | Execute the query: 57 | 58 | ```dart 59 | var result = await query.execute(['Bob', 'bob@bob.com', 25]); 60 | ``` 61 | 62 | An insert query's results will be empty, but will have an id if there was an auto-increment column in the table: 63 | 64 | ```dart 65 | print("New user's id: ${result.insertId}"); 66 | ``` 67 | 68 | Execute a query with multiple sets of parameters: 69 | 70 | ```dart 71 | var results = await query.executeMulti([['Bob', 'bob@bob.com', 25], 72 | ['Bill', 'bill@bill.com', 26], 73 | ['Joe', 'joe@joe.com', 37]]); 74 | ``` 75 | 76 | Use the list of results: 77 | 78 | ```dart 79 | for (result in results) { 80 | print("New user's id: ${result.insertId}"); 81 | } 82 | ``` 83 | 84 | Use a transaction: 85 | 86 | ```dart 87 | var trans = await pool.startTransaction(); 88 | var result = await trans.query('...'); 89 | await trans.commit(); 90 | ``` 91 | 92 | Development 93 | ----------- 94 | 95 | To run the examples and tests, you'll need to create a 'connection.options' file by 96 | copying 'connection.options.example' and modifying the settings. 97 | 98 | Licence 99 | ------- 100 | 101 | It is released under the GPL, because it uses a modified part of mysql's include/mysql_com.h in constants.dart, 102 | which is licensed under the GPL. I would prefer to release it under the BSD Licence, but there you go. 103 | 104 | The Name 105 | -------- 106 | 107 | It is named after [Jocky Wilson](http://en.wikipedia.org/wiki/Jocky_Wilson), the late, great 108 | darts player. (Hence the lack of an 'e' in Jocky.) 109 | 110 | Things to do 111 | ------------ 112 | 113 | * Compression 114 | * COM_SEND_LONG_DATA 115 | * CLIENT_MULTI_STATEMENTS and CLIENT_MULTI_RESULTS for stored procedures 116 | * More connection pool management (close after timeout, change pool size...) 117 | * Better handling of various data types, especially BLOBs, which behave differently when using straight queries and prepared queries. 118 | * Implement the rest of mysql's commands 119 | * Handle character sets properly? Currently defaults to UTF8 for the connection character set. Is it 120 | necessary to support anything else? 121 | * Improve performance where possible 122 | * Geometry type 123 | * Decimal type should probably use a bigdecimal type of some sort 124 | * MySQL 4 types (old decimal, anything else?) 125 | * Test against multiple mysql versions 126 | -------------------------------------------------------------------------------- /benchmark/speed_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:logging/logging.dart'; 4 | import 'package:options_file/options_file.dart'; 5 | import 'package:sqljocky/sqljocky.dart'; 6 | import 'package:sqljocky/utils.dart'; 7 | 8 | class SpeedTest { 9 | static const SIMPLE_INSERTS = 200; 10 | static const PREPARED_INSERTS = 200; 11 | static const POOL_SIZE = 1; 12 | 13 | ConnectionPool pool; 14 | Logger log; 15 | 16 | SpeedTest(this.pool) : log = new Logger("Speed"); 17 | 18 | Future run() async { 19 | await dropTables(); 20 | await createTables(); 21 | await insertSimpleData(); 22 | await insertPreparedData(); 23 | await pool.closeConnectionsWhenNotInUse(); 24 | } 25 | 26 | Future dropTables() { 27 | log.fine("dropping tables"); 28 | var dropper = new TableDropper(pool, ['pets', 'people']); 29 | return dropper.dropTables(); 30 | } 31 | 32 | Future createTables() { 33 | log.fine("creating tables"); 34 | var querier = new QueryRunner(pool, [ 35 | 'create table people (id integer not null auto_increment, ' 36 | 'name varchar(255), ' 37 | 'age integer, ' 38 | 'primary key (id))', 39 | 'create table pets (id integer not null auto_increment, ' 40 | 'name varchar(255), ' 41 | 'species varchar(255), ' 42 | 'owner_id integer, ' 43 | 'primary key (id),' 44 | 'foreign key (owner_id) references people (id))' 45 | ]); 46 | log.fine("executing queries"); 47 | return querier.executeQueries(); 48 | } 49 | 50 | Future insertSimpleData() async { 51 | log.fine("inserting simple data"); 52 | var sw = new Stopwatch()..start(); 53 | var futures = []; 54 | for (var i = 0; i < SIMPLE_INSERTS; i++) { 55 | futures.add(pool.query("insert into people (name, age) values ('person$i', $i)")); 56 | } 57 | await Future.wait(futures); 58 | logTime("simple insertions", sw); 59 | log.fine("inserted"); 60 | } 61 | 62 | Future insertPreparedData() async { 63 | log.fine("inserting prepared data"); 64 | var sw = new Stopwatch()..start(); 65 | var futures = []; 66 | var query = await pool.prepare("insert into people (name, age) values (?, ?)"); 67 | for (var i = 0; i < PREPARED_INSERTS; i++) { 68 | futures.add(query.execute(["person$i", i])); 69 | } 70 | await Future.wait(futures); 71 | logTime("prepared insertions", sw); 72 | log.fine("inserted"); 73 | } 74 | 75 | void logTime(String operation, Stopwatch sw) { 76 | var time = sw.elapsedMicroseconds; 77 | var seconds = time / 1000000; 78 | log.fine("$operation took: ${seconds}s"); 79 | } 80 | } 81 | 82 | main() async { 83 | hierarchicalLoggingEnabled = true; 84 | Logger.root.level = Level.OFF; 85 | // new Logger("ConnectionPool").level = Level.ALL; 86 | // new Logger("Connection.Lifecycle").level = Level.ALL; 87 | // new Logger("Query").level = Level.ALL; 88 | Logger.root.onRecord.listen((LogRecord r) { 89 | print("${r.time}: ${r.loggerName}: ${r.message}"); 90 | }); 91 | 92 | var log = new Logger("Speed"); 93 | log.level = Level.ALL; 94 | 95 | var options = new OptionsFile('connection.options'); 96 | var user = options.getString('user'); 97 | var password = options.getString('password'); 98 | var port = options.getInt('port', 3306); 99 | var db = options.getString('db'); 100 | var host = options.getString('host', 'localhost'); 101 | 102 | // create a connection 103 | log.fine("opening connection"); 104 | var pool = 105 | new ConnectionPool(host: host, port: port, user: user, password: password, db: db, max: SpeedTest.POOL_SIZE); 106 | log.fine("connection open"); 107 | 108 | var stopwatch = new Stopwatch()..start(); 109 | await new SpeedTest(pool).run(); 110 | var time = stopwatch.elapsedMicroseconds; 111 | var seconds = time / 1000000; 112 | log.fine("Time taken: ${seconds}s"); 113 | } 114 | -------------------------------------------------------------------------------- /connection.options.example: -------------------------------------------------------------------------------- 1 | # create connection.options to define how the integration tests connect 2 | user=test 3 | password=test 4 | #defaults to 3306 5 | port=3306 6 | db=test 7 | #defaults to localhost 8 | host=localhost -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqljocky/sqljocky.dart'; 2 | import 'package:sqljocky/utils.dart'; 3 | import 'package:options_file/options_file.dart'; 4 | import 'dart:async'; 5 | 6 | /* 7 | * This example drops a couple of tables if they exist, before recreating them. 8 | * It then stores some data in the database and reads it back out. 9 | * You must have a connection.options file in order for this to connect. 10 | */ 11 | 12 | class Example { 13 | ConnectionPool pool; 14 | 15 | Example(this.pool); 16 | 17 | Future run() async { 18 | // drop the tables if they already exist 19 | await dropTables(); 20 | print("dropped tables"); 21 | // then recreate the tables 22 | await createTables(); 23 | print("created tables"); 24 | // add some data 25 | await addData(); 26 | // and read it back out 27 | await readData(); 28 | } 29 | 30 | Future dropTables() { 31 | print("dropping tables"); 32 | var dropper = new TableDropper(pool, ['pets', 'people']); 33 | return dropper.dropTables(); 34 | } 35 | 36 | Future createTables() { 37 | print("creating tables"); 38 | var querier = new QueryRunner(pool, [ 39 | 'create table people (id integer not null auto_increment, ' 40 | 'name varchar(255), ' 41 | 'age integer, ' 42 | 'primary key (id))', 43 | 'create table pets (id integer not null auto_increment, ' 44 | 'name varchar(255), ' 45 | 'species text, ' 46 | 'owner_id integer, ' 47 | 'primary key (id),' 48 | 'foreign key (owner_id) references people (id))' 49 | ]); 50 | print("executing queries"); 51 | return querier.executeQueries(); 52 | } 53 | 54 | Future addData() async { 55 | var query = await pool.prepare("insert into people (name, age) values (?, ?)"); 56 | print("prepared query 1"); 57 | var parameters = [ 58 | ["Dave", 15], 59 | ["John", 16], 60 | ["Mavis", 93] 61 | ]; 62 | await query.executeMulti(parameters); 63 | 64 | print("executed query 1"); 65 | query = await pool.prepare("insert into pets (name, species, owner_id) values (?, ?, ?)"); 66 | 67 | print("prepared query 2"); 68 | parameters = [ 69 | ["Rover", "Dog", 1], 70 | ["Daisy", "Cow", 2], 71 | ["Spot", "Dog", 2] 72 | ]; 73 | // ["Spot", "D\u0000og", 2]]; 74 | await query.executeMulti(parameters); 75 | 76 | print("executed query 2"); 77 | } 78 | 79 | Future readData() async { 80 | print("querying"); 81 | var result = await pool.query('select p.id, p.name, p.age, t.name, t.species ' 82 | 'from people p ' 83 | 'left join pets t on t.owner_id = p.id'); 84 | print("got results"); 85 | return result.forEach((row) { 86 | if (row[3] == null) { 87 | print("ID: ${row[0]}, Name: ${row[1]}, Age: ${row[2]}, No Pets"); 88 | } else { 89 | print("ID: ${row[0]}, Name: ${row[1]}, Age: ${row[2]}, Pet Name: ${row[3]}, Pet Species ${row[4]}"); 90 | } 91 | }); 92 | } 93 | } 94 | 95 | main() async { 96 | OptionsFile options = new OptionsFile('connection.options'); 97 | String user = options.getString('user'); 98 | String password = options.getString('password'); 99 | int port = options.getInt('port', 3306); 100 | String db = options.getString('db'); 101 | String host = options.getString('host', 'localhost'); 102 | 103 | // create a connection 104 | print("opening connection"); 105 | var pool = new ConnectionPool(host: host, port: port, user: user, password: password, db: db, max: 1); 106 | print("connection open"); 107 | // create an example class 108 | var example = new Example(pool); 109 | // run the example 110 | print("running example"); 111 | await example.run(); 112 | // finally, close the connection 113 | print("K THNX BYE!"); 114 | pool.closeConnectionsNow(); 115 | } 116 | -------------------------------------------------------------------------------- /javaspeed/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.26' 9 | } 10 | 11 | task wrapper(type: Wrapper) { 12 | gradleVersion = '1.4' 13 | } 14 | -------------------------------------------------------------------------------- /javaspeed/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /javaspeed/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /javaspeed/src/main/com/jamesots/dart/sqljocky/speedtest/SpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.jamesots.dart.sqljocky.speedtest; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.PreparedStatement; 6 | import java.sql.Statement; 7 | 8 | public class SpeedTest { 9 | Connection cnx; 10 | 11 | void run() throws Exception { 12 | Class.forName("com.mysql.jdbc.Driver"); 13 | 14 | cnx = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", 15 | "test", "test"); 16 | 17 | dropTables(); 18 | createTables(); 19 | insertSimpleData(); 20 | insertPreparedData(); 21 | } 22 | 23 | private void insertPreparedData() throws Exception { 24 | long now = System.currentTimeMillis(); 25 | final PreparedStatement stmt = cnx.prepareStatement("insert into people (name, age) values (?, ?)"); 26 | for (int i = 0; i < 200; i++) { 27 | stmt.setString(1, "person" + i); 28 | stmt.setInt(2, i); 29 | stmt.execute(); 30 | } 31 | long elapsed = System.currentTimeMillis() - now; 32 | System.out.println("Prepared insertions: " + (elapsed / 1000.0) + "s"); 33 | } 34 | 35 | private void insertSimpleData() throws Exception { 36 | long now = System.currentTimeMillis(); 37 | final Statement stmt = cnx.createStatement(); 38 | for (int i = 0; i < 200; i++) { 39 | stmt.execute("insert into people (name, age) values ('person" + i + "', " + i + ")"); 40 | } 41 | long elapsed = System.currentTimeMillis() - now; 42 | System.out.println("Simple insertions: " + (elapsed / 1000.0) + "s"); 43 | } 44 | 45 | private void createTables() throws Exception { 46 | cnx.createStatement().execute("create table people (id integer not null auto_increment, " + 47 | "name varchar(255), " + 48 | "age integer, " + 49 | "primary key (id))"); 50 | 51 | cnx.createStatement().execute("create table pets (id integer not null auto_increment, " + 52 | "name varchar(255), " + 53 | "species varchar(255), " + 54 | "owner_id integer, " + 55 | "primary key (id), " + 56 | "foreign key (owner_id) references people (id))"); 57 | } 58 | 59 | private void dropTables() throws Exception { 60 | try { 61 | cnx.createStatement().execute("drop table pets"); 62 | } catch (Exception e) { 63 | 64 | } 65 | try { 66 | cnx.createStatement().execute("drop table people"); 67 | } catch (Exception e) { 68 | 69 | } 70 | } 71 | 72 | public static void main(String[] args) { 73 | try { 74 | SpeedTest test = new SpeedTest(); 75 | test.run(); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | library constants; 2 | 3 | /* Copyright (C) 2000 MySQL AB 4 | Copyright (C) 2012 James Ots 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; version 2 of the License. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 18 | 19 | /* 20 | * This file is based on excerpts from include/mysql_com.h 21 | */ 22 | 23 | const int PACKET_OK = 0; 24 | const int PACKET_ERROR = 0xFF; 25 | const int PACKET_EOF = 0xFE; 26 | 27 | const int CLIENT_LONG_PASSWORD = 1 << 0; 28 | const int CLIENT_FOUND_ROWS = 1 << 1; 29 | const int CLIENT_LONG_FLAG = 1 << 2; 30 | const int CLIENT_CONNECT_WITH_DB = 1 << 3; 31 | const int CLIENT_NO_SCHEMA = 1 << 4; 32 | const int CLIENT_COMPRESS = 1 << 5; 33 | const int CLIENT_ODBC = 1 << 6; 34 | const int CLIENT_LOCAL_FILES = 1 << 7; 35 | const int CLIENT_IGNORE_SPACE = 1 << 8; 36 | const int CLIENT_PROTOCOL_41 = 1 << 9; 37 | const int CLIENT_INTERACTIVE = 1 << 10; 38 | const int CLIENT_SSL = 1 << 11; 39 | const int CLIENT_IGNORE_SIGPIPE = 1 << 12; 40 | const int CLIENT_TRANSACTIONS = 1 << 13; 41 | const int CLIENT_RESERVED = 1 << 14; 42 | const int CLIENT_SECURE_CONNECTION = 1 << 15; 43 | const int CLIENT_MULTI_STATEMENTS = 1 << 16; 44 | const int CLIENT_MULTI_RESULTS = 1 << 17; 45 | const int CLIENT_PLUGIN_AUTH = 1 << 19; 46 | 47 | const int SERVER_STATUS_IN_TRANS = 1; 48 | const int SERVER_STATUS_AUTOCOMMIT = 2; 49 | const int SERVER_MORE_RESULTS_EXISTS = 8; 50 | 51 | const int ERROR_UNKNOWN_TABLE = 1051; 52 | const int ERROR_CANNOT_DELETE_OR_UPDATE_PARENT_ROW_FOREIGN_KEY_CONSTRAINT_FAILS = 1217; 53 | 54 | const int COM_SLEEP = 0x00; 55 | const int COM_QUIT = 0x01; 56 | const int COM_INIT_DB = 0x02; 57 | const int COM_QUERY = 0x03; 58 | const int COM_FIELD_LIST = 0x04; 59 | const int COM_CREATE_DB = 0x05; 60 | const int COM_DROP_DB = 0x06; 61 | const int COM_REFRESH = 0x07; 62 | const int COM_SHUTDOWN = 0x08; 63 | const int COM_STATISTICS = 0x09; 64 | const int COM_PROCESS_INFO = 0x0a; 65 | const int COM_CONNECT = 0x0b; 66 | const int COM_PROCESS_KILL = 0x0c; 67 | const int COM_DEBUG = 0x0d; 68 | const int COM_PING = 0x0e; 69 | const int COM_TIME = 0x0f; 70 | const int COM_DELAYED_INSERT = 0x10; 71 | const int COM_CHANGE_USER = 0x11; 72 | const int COM_BINLOG_DUMP = 0x12; 73 | const int COM_TABLE_DUMP = 0x13; 74 | const int COM_CONNECT_OUT = 0x14; 75 | const int COM_REGISTER_SLAVE = 0x15; 76 | const int COM_STMT_PREPARE = 0x16; 77 | const int COM_STMT_EXECUTE = 0x17; 78 | const int COM_STMT_SEND_LONG_DATA = 0x18; 79 | const int COM_STMT_CLOSE = 0x19; 80 | const int COM_STMT_RESET = 0x1a; 81 | const int COM_SET_OPTION = 0x1b; 82 | const int COM_STMT_FETCH = 0x1c; 83 | 84 | const int FIELD_TYPE_DECIMAL = 0x00; 85 | const int FIELD_TYPE_TINY = 0x01; 86 | const int FIELD_TYPE_SHORT = 0x02; 87 | const int FIELD_TYPE_LONG = 0x03; 88 | const int FIELD_TYPE_FLOAT = 0x04; 89 | const int FIELD_TYPE_DOUBLE = 0x05; 90 | const int FIELD_TYPE_NULL = 0x06; 91 | const int FIELD_TYPE_TIMESTAMP = 0x07; 92 | const int FIELD_TYPE_LONGLONG = 0x08; 93 | const int FIELD_TYPE_INT24 = 0x09; 94 | const int FIELD_TYPE_DATE = 0x0a; 95 | const int FIELD_TYPE_TIME = 0x0b; 96 | const int FIELD_TYPE_DATETIME = 0x0c; 97 | const int FIELD_TYPE_YEAR = 0x0d; 98 | const int FIELD_TYPE_NEWDATE = 0x0e; 99 | const int FIELD_TYPE_VARCHAR = 0x0f; 100 | const int FIELD_TYPE_BIT = 0x10; 101 | const int FIELD_TYPE_NEWDECIMAL = 0xf6; 102 | const int FIELD_TYPE_ENUM = 0xf7; 103 | const int FIELD_TYPE_SET = 0xf8; 104 | const int FIELD_TYPE_TINY_BLOB = 0xf9; 105 | const int FIELD_TYPE_MEDIUM_BLOB = 0xfa; 106 | const int FIELD_TYPE_LONG_BLOB = 0xfb; 107 | const int FIELD_TYPE_BLOB = 0xfc; 108 | const int FIELD_TYPE_VAR_STRING = 0xfd; 109 | const int FIELD_TYPE_STRING = 0xfe; 110 | const int FIELD_TYPE_GEOMETRY = 0xff; 111 | 112 | const int NOT_NULL_FLAG = 0x0001; 113 | const int PRI_KEY_FLAG = 0x0002; 114 | const int UNIQUE_KEY_FLAG = 0x0004; 115 | const int MULTIPLE_KEY_FLAG = 0x0008; 116 | const int BLOB_FLAG = 0x0010; 117 | const int UNSIGNED_FLAG = 0x0020; 118 | const int ZEROFILL_FLAG = 0x0040; 119 | const int BINARY_FLAG = 0x0080; 120 | const int ENUM_FLAG = 0x0100; 121 | const int AUTO_INCREMENT_FLAG = 0x0200; 122 | const int TIMESTAMP_FLAG = 0x0400; 123 | const int SET_FLAG = 0x0800; 124 | 125 | String fieldTypeToString(int type) { 126 | switch (type) { 127 | case 0x00: 128 | return "DECIMAL"; 129 | case 0x01: 130 | return "TINY"; 131 | case 0x02: 132 | return "SHORT"; 133 | case 0x03: 134 | return "LONG"; 135 | case 0x04: 136 | return "FLOAT"; 137 | case 0x05: 138 | return "DOUBLE"; 139 | case 0x06: 140 | return "NULL"; 141 | case 0x07: 142 | return "TIMESTAMP"; 143 | case 0x08: 144 | return "LONGLONG"; 145 | case 0x09: 146 | return "INT24"; 147 | case 0x0a: 148 | return "DATE"; 149 | case 0x0b: 150 | return "TIME"; 151 | case 0x0c: 152 | return "DATETIME"; 153 | case 0x0d: 154 | return "YEAR"; 155 | case 0x0e: 156 | return "NEWDATE"; 157 | case 0x0f: 158 | return "VARCHAR"; 159 | case 0x10: 160 | return "BIT"; 161 | case 0xf6: 162 | return "NEWDECIMAL"; 163 | case 0xf7: 164 | return "ENUM"; 165 | case 0xf8: 166 | return "SET"; 167 | case 0xf9: 168 | return "TINY_BLOB"; 169 | case 0xfa: 170 | return "MEDIUM_BLOB"; 171 | case 0xfb: 172 | return "LONG_BLOB"; 173 | case 0xfc: 174 | return "BLOB"; 175 | case 0xfd: 176 | return "VAR_STRING"; 177 | case 0xfe: 178 | return "STRING"; 179 | case 0xff: 180 | return "GEOMETRY"; 181 | default: 182 | return "UNKNOWN"; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lib/sqljocky.dart: -------------------------------------------------------------------------------- 1 | library sqljocky; 2 | // named after Jocky Wilson, the late, great darts player 3 | 4 | import 'dart:async'; 5 | import 'dart:collection'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | import 'dart:math' as math; 9 | 10 | import 'package:crypto/crypto.dart'; 11 | import 'package:logging/logging.dart'; 12 | 13 | import 'constants.dart'; 14 | 15 | import 'src/buffer.dart'; 16 | import 'src/buffered_socket.dart'; 17 | import 'src/results.dart'; 18 | export 'src/results.dart'; 19 | 20 | part 'src/blob.dart'; 21 | 22 | part 'src/connection_pool.dart'; 23 | part 'src/connection.dart'; 24 | part 'src/transaction.dart'; 25 | part 'src/retained_connection.dart'; 26 | part 'src/query.dart'; 27 | part 'src/mysql_exception.dart'; 28 | part 'src/mysql_protocol_error.dart'; 29 | part 'src/mysql_client_error.dart'; 30 | 31 | //general handlers 32 | part 'src/handlers/parameter_packet.dart'; 33 | part 'src/handlers/ok_packet.dart'; 34 | part 'src/handlers/handler.dart'; 35 | part 'src/handlers/use_db_handler.dart'; 36 | part 'src/handlers/ping_handler.dart'; 37 | part 'src/handlers/debug_handler.dart'; 38 | part 'src/handlers/quit_handler.dart'; 39 | 40 | //auth handlers 41 | part 'src/auth/handshake_handler.dart'; 42 | part 'src/auth/auth_handler.dart'; 43 | part 'src/auth/ssl_handler.dart'; 44 | part 'src/auth/character_set.dart'; 45 | 46 | //prepared statements handlers 47 | part 'src/prepared_statements/prepare_ok_packet.dart'; 48 | part 'src/prepared_statements/prepared_query.dart'; 49 | part 'src/prepared_statements/prepare_handler.dart'; 50 | part 'src/prepared_statements/close_statement_handler.dart'; 51 | part 'src/prepared_statements/execute_query_handler.dart'; 52 | part 'src/prepared_statements/binary_data_packet.dart'; 53 | 54 | //query handlers 55 | part 'src/query/result_set_header_packet.dart'; 56 | part 'src/query/standard_data_packet.dart'; 57 | part 'src/query/query_stream_handler.dart'; 58 | 59 | part 'src/results/results_impl.dart'; 60 | part 'src/results/field_impl.dart'; 61 | -------------------------------------------------------------------------------- /lib/src/auth/auth_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _AuthHandler extends _Handler { 4 | final String _username; 5 | final String _password; 6 | final String _db; 7 | final List _scrambleBuffer; 8 | final int _clientFlags; 9 | final int _maxPacketSize; 10 | final int _characterSet; 11 | final bool _ssl; 12 | 13 | _AuthHandler(String this._username, String this._password, String this._db, List this._scrambleBuffer, 14 | int this._clientFlags, int this._maxPacketSize, int this._characterSet, 15 | {bool ssl: false}) 16 | : this._ssl = false { 17 | log = new Logger("AuthHandler"); 18 | } 19 | 20 | List _getHash() { 21 | List hash; 22 | if (_password == null) { 23 | hash = []; 24 | } else { 25 | var hashedPassword = sha1.convert(UTF8.encode(_password)).bytes; 26 | var doubleHashedPassword = sha1.convert(hashedPassword).bytes; 27 | 28 | var bytes = []..addAll(_scrambleBuffer)..addAll(doubleHashedPassword); 29 | var hashedSaltedPassword = sha1.convert(bytes).bytes; 30 | 31 | hash = new List(hashedSaltedPassword.length); 32 | for (var i = 0; i < hash.length; i++) { 33 | hash[i] = hashedSaltedPassword[i] ^ hashedPassword[i]; 34 | } 35 | } 36 | return hash; 37 | } 38 | 39 | Buffer createRequest() { 40 | // calculate the mysql password hash 41 | var hash = _getHash(); 42 | 43 | var encodedUsername = _username == null ? [] : UTF8.encode(_username); 44 | var encodedDb; 45 | 46 | var size = hash.length + encodedUsername.length + 2 + 32; 47 | var clientFlags = _clientFlags; 48 | if (_db != null) { 49 | encodedDb = UTF8.encode(_db); 50 | size += encodedDb.length + 1; 51 | clientFlags |= CLIENT_CONNECT_WITH_DB; 52 | } 53 | 54 | var buffer = new Buffer(size); 55 | buffer.seekWrite(0); 56 | buffer.writeUint32(clientFlags); 57 | buffer.writeUint32(_maxPacketSize); 58 | buffer.writeByte(_characterSet); 59 | buffer.fill(23, 0); 60 | buffer.writeNullTerminatedList(encodedUsername); 61 | buffer.writeByte(hash.length); 62 | buffer.writeList(hash); 63 | 64 | if (_db != null) { 65 | buffer.writeNullTerminatedList(encodedDb); 66 | } 67 | 68 | return buffer; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/auth/character_set.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class CharacterSet { 4 | static const int UTF8 = 33; 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/auth/handshake_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _HandshakeHandler extends _Handler { 4 | static const String MYSQL_NATIVE_PASSWORD = "mysql_native_password"; 5 | 6 | final String _user; 7 | final String _password; 8 | final String _db; 9 | final int _maxPacketSize; 10 | 11 | int protocolVersion; 12 | String serverVersion; 13 | int threadId; 14 | List scrambleBuffer; 15 | int serverCapabilities; 16 | int serverLanguage; 17 | int serverStatus; 18 | int scrambleLength; 19 | String pluginName; 20 | bool useCompression = false; 21 | bool useSSL = false; 22 | 23 | _HandshakeHandler(String this._user, String this._password, int this._maxPacketSize, 24 | [String db, bool useCompression, bool useSSL]) 25 | : _db = db, 26 | this.useCompression = useCompression, 27 | this.useSSL = useSSL { 28 | log = new Logger("HandshakeHandler"); 29 | } 30 | 31 | /** 32 | * The server initiates the handshake after the client connects, 33 | * so a request will never be created. 34 | */ 35 | Buffer createRequest() { 36 | throw new MySqlClientError._("Cannot create a handshake request"); 37 | } 38 | 39 | _readResponseBuffer(Buffer response) { 40 | response.seek(0); 41 | protocolVersion = response.readByte(); 42 | if (protocolVersion != 10) { 43 | throw new MySqlClientError._("Protocol not supported"); 44 | } 45 | serverVersion = response.readNullTerminatedString(); 46 | threadId = response.readUint32(); 47 | var scrambleBuffer1 = response.readList(8); 48 | response.skip(1); 49 | serverCapabilities = response.readUint16(); 50 | if (response.hasMore) { 51 | serverLanguage = response.readByte(); 52 | serverStatus = response.readUint16(); 53 | serverCapabilities += (response.readUint16() << 0x10); 54 | 55 | var secure = serverCapabilities & CLIENT_SECURE_CONNECTION; 56 | var plugin = serverCapabilities & CLIENT_PLUGIN_AUTH; 57 | 58 | scrambleLength = response.readByte(); 59 | response.skip(10); 60 | if (serverCapabilities & CLIENT_SECURE_CONNECTION > 0) { 61 | var scrambleBuffer2 = response.readList(math.max(13, scrambleLength - 8) - 1); 62 | var nullTerminator = response.readByte(); 63 | scrambleBuffer = new List(scrambleBuffer1.length + scrambleBuffer2.length); 64 | scrambleBuffer.setRange(0, 8, scrambleBuffer1); 65 | scrambleBuffer.setRange(8, 8 + scrambleBuffer2.length, scrambleBuffer2); 66 | } else { 67 | scrambleBuffer = scrambleBuffer1; 68 | } 69 | 70 | if (serverCapabilities & CLIENT_PLUGIN_AUTH > 0) { 71 | pluginName = response.readStringToEnd(); 72 | if (pluginName.codeUnitAt(pluginName.length - 1) == 0) { 73 | pluginName = pluginName.substring(0, pluginName.length - 1); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * After receiving the handshake packet, if all is well, an [_AuthHandler] 81 | * is created and returned to handle authentication. 82 | * 83 | * Currently, if the client protocol version is not 4.1, an 84 | * exception is thrown. 85 | */ 86 | _HandlerResponse processResponse(Buffer response) { 87 | _readResponseBuffer(response); 88 | 89 | if ((serverCapabilities & CLIENT_PROTOCOL_41) == 0) { 90 | throw new MySqlClientError._("Unsupported protocol (must be 4.1 or newer"); 91 | } 92 | 93 | if ((serverCapabilities & CLIENT_SECURE_CONNECTION) == 0) { 94 | throw new MySqlClientError._("Old Password AUthentication is not supported"); 95 | } 96 | 97 | if ((serverCapabilities & CLIENT_PLUGIN_AUTH) != 0 && pluginName != MYSQL_NATIVE_PASSWORD) { 98 | throw new MySqlClientError._("Authentication plugin not supported: $pluginName"); 99 | } 100 | 101 | int clientFlags = 102 | CLIENT_PROTOCOL_41 | CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_SECURE_CONNECTION; 103 | 104 | if (useCompression && (serverCapabilities & CLIENT_COMPRESS) != 0) { 105 | log.shout("Compression enabled"); 106 | clientFlags |= CLIENT_COMPRESS; 107 | } else { 108 | useCompression = false; 109 | } 110 | 111 | if (useSSL && (serverCapabilities & CLIENT_SSL) != 0) { 112 | log.shout("SSL enabled"); 113 | clientFlags |= CLIENT_SSL | CLIENT_SECURE_CONNECTION; 114 | } else { 115 | useSSL = false; 116 | } 117 | 118 | if (useSSL) { 119 | return new _HandlerResponse( 120 | nextHandler: new _SSLHandler( 121 | clientFlags, 122 | _maxPacketSize, 123 | CharacterSet.UTF8, 124 | new _AuthHandler(_user, _password, _db, scrambleBuffer, clientFlags, _maxPacketSize, CharacterSet.UTF8, 125 | ssl: true))); 126 | } 127 | 128 | return new _HandlerResponse( 129 | nextHandler: 130 | new _AuthHandler(_user, _password, _db, scrambleBuffer, clientFlags, _maxPacketSize, CharacterSet.UTF8)); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/src/auth/ssl_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _SSLHandler extends _Handler { 4 | final int _clientFlags; 5 | final int _maxPacketSize; 6 | final int _characterSet; 7 | final _Handler _handler; 8 | 9 | _Handler get nextHandler => _handler; 10 | 11 | _SSLHandler(this._clientFlags, this._maxPacketSize, this._characterSet, this._handler) { 12 | log = new Logger("SSLHandler"); 13 | } 14 | 15 | Buffer createRequest() { 16 | var buffer = new Buffer(32); 17 | buffer.seekWrite(0); 18 | buffer.writeUint32(_clientFlags); 19 | buffer.writeUint32(_maxPacketSize); 20 | buffer.writeByte(_characterSet); 21 | buffer.fill(23, 0); 22 | 23 | return buffer; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/blob.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | /** 4 | * Holds blob data, and can be created or accessed as either a [String] or a [List] of 5 | * 8-bit integers. 6 | * 7 | * When a blob which was created as a list of integers is accessed as a string, those 8 | * integers are treated as UTF-8 code units (unsigned 8-bit integers). 9 | */ 10 | class Blob { 11 | String _string; 12 | List _codeUnits; 13 | int _hashcode; 14 | 15 | /// Create a [Blob] from a [string]. 16 | Blob.fromString(String string) { 17 | this._string = string; 18 | } 19 | 20 | /// Create a [Blob] from a list of [codeUnits]. 21 | Blob.fromBytes(List codeUnits) { 22 | this._codeUnits = codeUnits; 23 | } 24 | 25 | /// Returns the value of the blob as a [String]. 26 | String toString() { 27 | if (_string != null) { 28 | return _string; 29 | } 30 | return UTF8.decode(_codeUnits, allowMalformed: true); 31 | } 32 | 33 | /// Returns the value of the blob as a list of code units. 34 | List toBytes() { 35 | if (_codeUnits != null) { 36 | return _codeUnits; 37 | } 38 | return UTF8.encode(_string); 39 | } 40 | 41 | int get hashCode { 42 | if (_hashcode == null) { 43 | if (_string != null) { 44 | _hashcode = _string.hashCode; 45 | } else { 46 | _hashcode = UTF8.decode(_codeUnits, allowMalformed: true).hashCode; 47 | } 48 | } 49 | return _hashcode; 50 | } 51 | 52 | bool operator ==(other) => toString() == other.toString(); 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/buffered_socket.dart: -------------------------------------------------------------------------------- 1 | library buffered_socket; 2 | 3 | import 'dart:io'; 4 | import 'dart:async'; 5 | import 'package:logging/logging.dart'; 6 | import 'buffer.dart'; 7 | 8 | typedef ErrorHandler(AsyncError); 9 | typedef DoneHandler(); 10 | typedef DataReadyHandler(); 11 | typedef ClosedHandler(); 12 | 13 | typedef Future SocketFactory(host, int port); 14 | typedef OnConnection(BufferedSocket); 15 | 16 | class BufferedSocket { 17 | final Logger log; 18 | 19 | ErrorHandler onError; 20 | DoneHandler onDone; 21 | ClosedHandler onClosed; 22 | 23 | /** 24 | * When data arrives and there is no read currently in progress, the onDataReady handler is called. 25 | */ 26 | DataReadyHandler onDataReady; 27 | 28 | RawSocket _socket; 29 | 30 | Buffer _writingBuffer; 31 | int _writeOffset; 32 | int _writeLength; 33 | Completer _writeCompleter; 34 | 35 | Buffer _readingBuffer; 36 | int _readOffset; 37 | Completer _readCompleter; 38 | StreamSubscription _subscription; 39 | bool _closed = false; 40 | 41 | BufferedSocket._(this._socket, this.onDataReady, this.onDone, this.onError, this.onClosed) 42 | : log = new Logger("BufferedSocket") { 43 | _subscription = _socket.listen(_onData, onError: _onSocketError, onDone: _onSocketDone, cancelOnError: true); 44 | } 45 | 46 | _onSocketError(error) { 47 | if (onError != null) { 48 | onError(error); 49 | } 50 | } 51 | 52 | _onSocketDone() { 53 | if (onDone != null) { 54 | onDone(); 55 | _closed = true; 56 | } 57 | } 58 | 59 | static defaultSocketFactory(host, port) => RawSocket.connect(host, port); 60 | 61 | static Future connect(String host, int port, 62 | {DataReadyHandler onDataReady, 63 | DoneHandler onDone, 64 | ErrorHandler onError, 65 | ClosedHandler onClosed, 66 | SocketFactory socketFactory: defaultSocketFactory, 67 | OnConnection onConnection}) async { 68 | try { 69 | var socket; 70 | socket = await socketFactory(host, port); 71 | socket.setOption(SocketOption.TCP_NODELAY, true); 72 | var bufferedSocket = new BufferedSocket._(socket, onDataReady, onDone, onError, onClosed); 73 | if (onConnection != null) { 74 | onConnection(bufferedSocket); 75 | } 76 | return bufferedSocket; 77 | } catch (e) { 78 | onError(e); 79 | } 80 | } 81 | 82 | void _onData(RawSocketEvent event) { 83 | if (event == RawSocketEvent.READ) { 84 | log.fine("READ data"); 85 | if (_readingBuffer == null) { 86 | log.fine("READ data: no buffer"); 87 | if (onDataReady != null) { 88 | onDataReady(); 89 | } 90 | } else { 91 | _readBuffer(); 92 | } 93 | } else if (event == RawSocketEvent.READ_CLOSED) { 94 | log.fine("READ_CLOSED"); 95 | if (this.onClosed != null) { 96 | this.onClosed(); 97 | } 98 | } else if (event == RawSocketEvent.CLOSED) { 99 | log.fine("CLOSED"); 100 | } else if (event == RawSocketEvent.WRITE) { 101 | log.fine("WRITE data"); 102 | if (_writingBuffer != null) { 103 | _writeBuffer(); 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Writes [buffer] to the socket, and returns the same buffer in a [Future] which 110 | * completes when it has all been written. 111 | */ 112 | Future writeBuffer(Buffer buffer) { 113 | return writeBufferPart(buffer, 0, buffer.length); 114 | } 115 | 116 | Future writeBufferPart(Buffer buffer, int start, int length) { 117 | log.fine("writeBuffer length=${buffer.length}"); 118 | if (_closed) { 119 | throw new StateError("Cannot write to socket, it is closed"); 120 | } 121 | if (_writingBuffer != null) { 122 | throw new StateError("Cannot write to socket, already writing"); 123 | } 124 | _writingBuffer = buffer; 125 | _writeCompleter = new Completer(); 126 | _writeOffset = start; 127 | _writeLength = length + start; 128 | 129 | _writeBuffer(); 130 | 131 | return _writeCompleter.future; 132 | } 133 | 134 | void _writeBuffer() { 135 | log.fine("_writeBuffer offset=${_writeOffset}"); 136 | int bytesWritten = _writingBuffer.writeToSocket(_socket, _writeOffset, _writeLength - _writeOffset); 137 | log.fine("Wrote $bytesWritten bytes"); 138 | if (log.isLoggable(Level.FINE)) { 139 | log.fine("\n${Buffer.debugChars(_writingBuffer.list)}"); 140 | } 141 | _writeOffset += bytesWritten; 142 | if (_writeOffset == _writeLength) { 143 | _writeCompleter.complete(_writingBuffer); 144 | _writingBuffer = null; 145 | } else { 146 | _socket.writeEventsEnabled = true; 147 | } 148 | } 149 | 150 | /** 151 | * Reads into [buffer] from the socket, and returns the same buffer in a [Future] which 152 | * completes when enough bytes have been read to fill the buffer. 153 | * 154 | * This must not be called while there is still a read ongoing, but may be called before 155 | * onDataReady is called, in which case onDataReady will not be called when data arrives, 156 | * and the read will start instead. 157 | */ 158 | Future readBuffer(Buffer buffer) { 159 | log.fine("readBuffer, length=${buffer.length}"); 160 | if (_closed) { 161 | throw new StateError("Cannot read from socket, it is closed"); 162 | } 163 | if (_readingBuffer != null) { 164 | throw new StateError("Cannot read from socket, already reading"); 165 | } 166 | _readingBuffer = buffer; 167 | _readOffset = 0; 168 | _readCompleter = new Completer(); 169 | 170 | if (_socket.available() > 0) { 171 | log.fine("readBuffer, data already ready"); 172 | _readBuffer(); 173 | } 174 | 175 | return _readCompleter.future; 176 | } 177 | 178 | void _readBuffer() { 179 | int bytesRead = _readingBuffer.readFromSocket(_socket, _readingBuffer.length - _readOffset); 180 | log.fine("read $bytesRead bytes"); 181 | if (log.isLoggable(Level.FINE)) { 182 | log.fine("\n${Buffer.debugChars(_readingBuffer.list)}"); 183 | } 184 | _readOffset += bytesRead; 185 | if (_readOffset == _readingBuffer.length) { 186 | _readCompleter.complete(_readingBuffer); 187 | _readingBuffer = null; 188 | } 189 | } 190 | 191 | void close() { 192 | _socket.close(); 193 | _closed = true; 194 | } 195 | 196 | Future startSSL() async { 197 | log.fine("Securing socket"); 198 | var socket = await RawSecureSocket.secure(_socket, subscription: _subscription, onBadCertificate: (cert) => true); 199 | log.fine("Socket is secure"); 200 | _socket = socket; 201 | _subscription = _socket.listen(_onData, onError: _onSocketError, onDone: _onSocketDone, cancelOnError: true); 202 | _socket.writeEventsEnabled = true; 203 | _socket.readEventsEnabled = true; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /lib/src/connection.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _Connection { 4 | static const int HEADER_SIZE = 4; 5 | static const int COMPRESSED_HEADER_SIZE = 7; 6 | static const int STATE_PACKET_HEADER = 0; 7 | static const int STATE_PACKET_DATA = 1; 8 | final Logger log; 9 | final Logger lifecycleLog; 10 | 11 | ConnectionPool _pool; 12 | _Handler _handler; 13 | Completer _completer; 14 | 15 | // this is for unit testing, so we can replace this method with a spy 16 | var _dataHandler; 17 | 18 | BufferedSocket _socket; 19 | var _largePacketBuffers = new List(); 20 | 21 | final Buffer _headerBuffer; 22 | final Buffer _compressedHeaderBuffer; 23 | Buffer _dataBuffer; 24 | bool _readyForHeader = true; 25 | bool _closeRequested = false; 26 | 27 | int _packetNumber = 0; 28 | 29 | int _compressedPacketNumber = 0; 30 | bool _useCompression = false; 31 | bool _useSSL = false; 32 | bool _secure = false; 33 | int _maxPacketSize; 34 | 35 | int _dataSize; 36 | 37 | String _user; 38 | String _password; 39 | final int number; 40 | 41 | bool _inUse; 42 | bool autoRelease; 43 | bool inTransaction = false; 44 | final Map _preparedQueryCache; 45 | 46 | _Connection(this._pool, this.number, this._maxPacketSize) 47 | : log = new Logger("Connection"), 48 | lifecycleLog = new Logger("Connection.Lifecycle"), 49 | _headerBuffer = new Buffer(HEADER_SIZE), 50 | _compressedHeaderBuffer = new Buffer(COMPRESSED_HEADER_SIZE), 51 | _preparedQueryCache = new Map(), 52 | _inUse = false { 53 | _dataHandler = this._handleData; 54 | } 55 | 56 | void close() { 57 | if (_socket != null) { 58 | _socket.close(); 59 | } 60 | _pool._removeConnection(this); 61 | } 62 | 63 | void closeWhenFinished() { 64 | if (_inUse) { 65 | _closeRequested = true; 66 | } else { 67 | close(); 68 | } 69 | } 70 | 71 | bool get inUse => _inUse; 72 | 73 | void use() { 74 | lifecycleLog.finest("Use connection #$number"); 75 | _inUse = true; 76 | autoRelease = true; 77 | } 78 | 79 | void release() { 80 | _inUse = false; 81 | lifecycleLog.finest("Release connection #$number"); 82 | } 83 | 84 | /** 85 | * Connects to the given [host] on [port], authenticates using [user] 86 | * and [password] and connects to [db]. Returns a future which completes 87 | * when this has happened. The future's value is an OkPacket if the connection 88 | * is succesful. 89 | */ 90 | Future connect( 91 | {String host, int port, String user, String password, String db, bool useCompression, bool useSSL}) async { 92 | if (_socket != null) { 93 | throw new MySqlClientError._("Cannot connect to server while a connection is already open"); 94 | } 95 | 96 | _user = user; 97 | _password = password; 98 | _handler = new _HandshakeHandler(user, password, _maxPacketSize, db, useCompression, useSSL); 99 | 100 | _completer = new Completer(); 101 | log.fine("opening connection to $host:$port/$db"); 102 | BufferedSocket.connect(host, port, 103 | onConnection: (socket) { 104 | _socket = socket; 105 | }, 106 | onDataReady: _readPacket, 107 | onDone: () { 108 | release(); 109 | log.fine("done"); 110 | }, 111 | onError: (error) { 112 | log.fine("error $error"); 113 | release(); 114 | if (_completer.isCompleted) { 115 | throw error; 116 | } else { 117 | _completer.completeError(error); 118 | } 119 | }, 120 | onClosed: () { 121 | close(); 122 | }); 123 | //TODO Only useDatabase if connection actually ended up as an SSL connection? 124 | //TODO On the other hand, it doesn't hurt to call useDatabase anyway. 125 | if (useSSL) { 126 | await _completer.future; 127 | return _useDatabase(db); 128 | } else { 129 | return _completer.future; 130 | } 131 | } 132 | 133 | Future _useDatabase(String dbName) { 134 | var handler = new _UseDbHandler(dbName); 135 | return processHandler(handler); 136 | } 137 | 138 | _readPacket() async { 139 | log.fine("readPacket readyForHeader=${_readyForHeader}"); 140 | if (_readyForHeader) { 141 | _readyForHeader = false; 142 | var buffer = await _socket.readBuffer(_headerBuffer); 143 | _handleHeader(buffer); 144 | } 145 | } 146 | 147 | _handleHeader(buffer) async { 148 | _dataSize = buffer[0] + (buffer[1] << 8) + (buffer[2] << 16); 149 | _packetNumber = buffer[3]; 150 | log.fine("about to read $_dataSize bytes for packet ${_packetNumber}"); 151 | _dataBuffer = new Buffer(_dataSize); 152 | log.fine("buffer size=${_dataBuffer.length}"); 153 | if (_dataSize == 0xffffff || _largePacketBuffers.length > 0) { 154 | var buffer = await _socket.readBuffer(_dataBuffer); 155 | _handleMoreData(buffer); 156 | } else { 157 | var buffer = await _socket.readBuffer(_dataBuffer); 158 | _dataHandler(buffer); 159 | } 160 | } 161 | 162 | void _handleMoreData(buffer) { 163 | _largePacketBuffers.add(buffer); 164 | if (buffer.length < 0xffffff) { 165 | var length = _largePacketBuffers.fold(0, (length, buf) { 166 | return length + buf.length; 167 | }); 168 | var combinedBuffer = new Buffer(length); 169 | var start = 0; 170 | _largePacketBuffers.forEach((aBuffer) { 171 | combinedBuffer.list.setRange(start, start + aBuffer.length, aBuffer.list); 172 | start += aBuffer.length; 173 | }); 174 | _largePacketBuffers.clear(); 175 | _dataHandler(combinedBuffer); 176 | } else { 177 | _readyForHeader = true; 178 | _headerBuffer.reset(); 179 | _readPacket(); 180 | } 181 | } 182 | 183 | _handleData(buffer) async { 184 | _readyForHeader = true; 185 | //log.fine("read all data: ${_dataBuffer._list}"); 186 | //log.fine("read all data: ${Buffer.listChars(_dataBuffer._list)}"); 187 | _headerBuffer.reset(); 188 | 189 | try { 190 | var response = _handler.processResponse(buffer); 191 | if (_handler is _HandshakeHandler) { 192 | _useCompression = (_handler as _HandshakeHandler).useCompression; 193 | _useSSL = (_handler as _HandshakeHandler).useSSL; 194 | } 195 | if (response.nextHandler != null) { 196 | // if handler.processResponse() returned a Handler, pass control to that handler now 197 | _handler = response.nextHandler; 198 | await _sendBuffer(_handler.createRequest()); 199 | if (_useSSL && _handler is _SSLHandler) { 200 | log.fine("Use SSL"); 201 | await _socket.startSSL(); 202 | _secure = true; 203 | _handler = (_handler as _SSLHandler).nextHandler; 204 | await _sendBuffer(_handler.createRequest()); 205 | log.fine("Sent buffer"); 206 | return; 207 | } 208 | } 209 | if (response.finished) { 210 | _finishAndReuse(); 211 | } 212 | if (response.hasResult) { 213 | if (_completer.isCompleted) { 214 | _completer.completeError(new StateError("Request has already completed")); 215 | } 216 | _completer.complete(response.result); 217 | } 218 | } catch (e, st) { 219 | autoRelease = true; 220 | _finishAndReuse(); 221 | log.fine("completing with exception: $e"); 222 | if (_completer.isCompleted) { 223 | throw e; 224 | } 225 | _completer.completeError(e, st); 226 | } 227 | } 228 | 229 | void _finishAndReuse() { 230 | if (autoRelease && !inTransaction) { 231 | log.finest("Response finished for #$number, setting handler to null and waiting to release and reuse"); 232 | new Future.delayed(new Duration(seconds: 0), () { 233 | if (_closeRequested) { 234 | close(); 235 | return; 236 | } 237 | if (_inUse) { 238 | log.finest("Releasing and reusing connection #$number"); 239 | _inUse = false; 240 | _handler = null; 241 | _pool._reuseConnectionForQueuedOperations(this); 242 | } 243 | }); 244 | } else { 245 | log.finest("Response finished for #$number. Not auto-releasing"); 246 | _handler = null; 247 | } 248 | } 249 | 250 | Future _sendBuffer(Buffer buffer) { 251 | if (buffer.length > _maxPacketSize) { 252 | throw new MySqlClientError._("Buffer length (${buffer.length}) bigger than maxPacketSize ($_maxPacketSize)"); 253 | } 254 | if (_useCompression) { 255 | _headerBuffer[0] = buffer.length & 0xFF; 256 | _headerBuffer[1] = (buffer.length & 0xFF00) >> 8; 257 | _headerBuffer[2] = (buffer.length & 0xFF0000) >> 16; 258 | _headerBuffer[3] = ++_packetNumber; 259 | var encodedHeader = ZLIB.encode(_headerBuffer.list); 260 | var encodedBuffer = ZLIB.encode(buffer.list); 261 | _compressedHeaderBuffer.writeUint24(encodedHeader.length + encodedBuffer.length); 262 | _compressedHeaderBuffer.writeByte(++_compressedPacketNumber); 263 | _compressedHeaderBuffer.writeUint24(4 + buffer.length); 264 | _socket.writeBuffer(_compressedHeaderBuffer); 265 | } else { 266 | log.fine("sendBuffer header"); 267 | return _sendBufferPart(buffer, 0); 268 | } 269 | } 270 | 271 | Future _sendBufferPart(Buffer buffer, int start) async { 272 | var len = math.min(buffer.length - start, 0xFFFFFF); 273 | 274 | _headerBuffer[0] = len & 0xFF; 275 | _headerBuffer[1] = (len & 0xFF00) >> 8; 276 | _headerBuffer[2] = (len & 0xFF0000) >> 16; 277 | _headerBuffer[3] = ++_packetNumber; 278 | log.fine("sending header, packet $_packetNumber"); 279 | await _socket.writeBuffer(_headerBuffer); 280 | log.fine("sendBuffer body, buffer length=${buffer.length}, start=$start, len=$len"); 281 | await _socket.writeBufferPart(buffer, start, len); 282 | if (len == 0xFFFFFF) { 283 | return _sendBufferPart(buffer, start + len); 284 | } else { 285 | return buffer; 286 | } 287 | } 288 | 289 | /** 290 | * Processes a handler, from sending the initial request to handling any packets returned from 291 | * mysql (unless [noResponse] is true). 292 | * 293 | * Returns a future 294 | */ 295 | Future processHandler(_Handler handler, {bool noResponse: false}) async { 296 | if (_handler != null) { 297 | throw new MySqlClientError._( 298 | "Connection #$number cannot process a request for $handler while a request is already in progress for $_handler"); 299 | } 300 | _packetNumber = -1; 301 | _compressedPacketNumber = -1; 302 | _completer = new Completer(); 303 | if (!noResponse) { 304 | _handler = handler; 305 | } 306 | await _sendBuffer(handler.createRequest()); 307 | if (noResponse) { 308 | _finishAndReuse(); 309 | } 310 | return _completer.future; 311 | } 312 | 313 | _PreparedQuery removePreparedQueryFromCache(String sql) { 314 | var preparedQuery = null; 315 | if (_preparedQueryCache.containsKey(sql)) { 316 | preparedQuery = _preparedQueryCache[sql]; 317 | _preparedQueryCache.remove(sql); 318 | } 319 | return preparedQuery; 320 | } 321 | 322 | _PreparedQuery getPreparedQueryFromCache(String sql) { 323 | return _preparedQueryCache[sql]; 324 | } 325 | 326 | putPreparedQueryInCache(String sql, _PreparedQuery preparedQuery) { 327 | _preparedQueryCache[sql] = preparedQuery; 328 | } 329 | 330 | bool get usingSSL => _secure; 331 | } 332 | -------------------------------------------------------------------------------- /lib/src/handlers/debug_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _DebugHandler extends _Handler { 4 | _DebugHandler() { 5 | log = new Logger("DebugHandler"); 6 | } 7 | 8 | Buffer createRequest() { 9 | var buffer = new Buffer(1); 10 | buffer.writeByte(COM_DEBUG); 11 | return buffer; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/handlers/handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _NoResult { 4 | const _NoResult(); 5 | } 6 | 7 | const _NO_RESULT = const _NoResult(); 8 | 9 | /** 10 | * Represents the response from a [_Handler] when [_Handler.processResponse] is 11 | * called. If the handler has finished processing the response, [finished] is true, 12 | * [nextHandler] is irrelevant and [result] contains the result to return to the 13 | * user. If the handler needs another handler to process the response, [finished] 14 | * is false, [nextHandler] contains the next handler which should process the 15 | * next packet from the server, and [result] is [_NO_RESULT]. 16 | */ 17 | class _HandlerResponse { 18 | final bool finished; 19 | final _Handler nextHandler; 20 | final dynamic result; 21 | 22 | bool get hasResult => result != _NO_RESULT; 23 | 24 | _HandlerResponse({this.finished: false, this.nextHandler: null, this.result: _NO_RESULT}); 25 | 26 | static final _HandlerResponse notFinished = new _HandlerResponse(); 27 | } 28 | 29 | /** 30 | * Each command which the mysql protocol implements is handled with a [_Handler] object. 31 | * A handler is created with the appropriate parameters when the command is invoked 32 | * from the connection. The transport is then responsible for sending the 33 | * request which the handler creates, and then parsing the result returned by 34 | * the mysql server, either synchronously or asynchronously. 35 | */ 36 | abstract class _Handler { 37 | Logger log; 38 | 39 | /** 40 | * Returns a [Buffer] containing the command packet. 41 | */ 42 | Buffer createRequest(); 43 | 44 | /** 45 | * Parses a [Buffer] containing the response to the command. 46 | * Returns a [_HandlerResponse]. The default 47 | * implementation returns a finished [_HandlerResponse] with 48 | * a result which is obtained by calling [checkResponse] 49 | */ 50 | _HandlerResponse processResponse(Buffer response) => 51 | new _HandlerResponse(finished: true, result: checkResponse(response)); 52 | 53 | /** 54 | * Parses the response packet to recognise Ok and Error packets. 55 | * Returns an [_OkPacket] if the packet was an Ok packet, throws 56 | * a [MySqlException] if it was an Error packet, or returns [:null:] 57 | * if the packet has not been handled by this method. 58 | */ 59 | dynamic checkResponse(Buffer response, [bool prepareStmt = false, bool isHandlingRows = false]) { 60 | if (response[0] == PACKET_OK && !isHandlingRows) { 61 | if (prepareStmt) { 62 | var okPacket = new _PrepareOkPacket(response); 63 | log.fine(okPacket.toString()); 64 | return okPacket; 65 | } else { 66 | var okPacket = new _OkPacket(response); 67 | log.fine(okPacket.toString()); 68 | return okPacket; 69 | } 70 | } else if (response[0] == PACKET_ERROR) { 71 | throw new MySqlException._(response); 72 | } 73 | return null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/handlers/ok_packet.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _OkPacket { 4 | int _affectedRows; 5 | int _insertId; 6 | int _serverStatus; 7 | String _message; 8 | 9 | int get affectedRows => _affectedRows; 10 | int get insertId => _insertId; 11 | int get serverStatus => _serverStatus; 12 | String get message => _message; 13 | 14 | _OkPacket(Buffer buffer) { 15 | buffer.seek(1); 16 | _affectedRows = buffer.readLengthCodedBinary(); 17 | _insertId = buffer.readLengthCodedBinary(); 18 | _serverStatus = buffer.readUint16(); 19 | _message = buffer.readStringToEnd(); 20 | } 21 | 22 | String toString() => 23 | "OK: affected rows: $affectedRows, insert id: $insertId, server status: $serverStatus, message: $message"; 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/handlers/parameter_packet.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | // not using this one yet 4 | class _ParameterPacket { 5 | int _type; 6 | int _flags; 7 | int _decimals; 8 | int _length; 9 | 10 | int get type => _type; 11 | int get flags => _flags; 12 | int get decimals => _decimals; 13 | int get length => _length; 14 | 15 | _ParameterPacket(Buffer buffer) { 16 | _type = buffer.readUint16(); 17 | _flags = buffer.readUint16(); 18 | _decimals = buffer.readByte(); 19 | _length = buffer.readUint32(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/handlers/ping_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _PingHandler extends _Handler { 4 | _PingHandler() { 5 | log = new Logger("PingHandler"); 6 | } 7 | 8 | Buffer createRequest() { 9 | log.finest("Creating buffer for PingHandler"); 10 | var buffer = new Buffer(1); 11 | buffer.writeByte(COM_PING); 12 | return buffer; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/handlers/quit_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _QuitHandler extends _Handler { 4 | _QuitHandler() { 5 | log = new Logger("QuitHandler"); 6 | } 7 | 8 | Buffer createRequest() { 9 | var buffer = new Buffer(1); 10 | buffer.writeByte(COM_QUIT); 11 | return buffer; 12 | } 13 | 14 | dynamic processResponse(Buffer response) { 15 | throw new MySqlProtocolError._("Shouldn't have received a response after sending a QUIT message"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/handlers/use_db_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _UseDbHandler extends _Handler { 4 | final String _dbName; 5 | 6 | _UseDbHandler(String this._dbName) { 7 | log = new Logger("UseDbHandler"); 8 | } 9 | 10 | Buffer createRequest() { 11 | var encoded = UTF8.encode(_dbName); 12 | var buffer = new Buffer(encoded.length + 1); 13 | buffer.writeByte(COM_INIT_DB); 14 | buffer.writeList(encoded); 15 | return buffer; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/mysql_client_error.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | /** 4 | * An error which is thrown when the client is used improperly. 5 | */ 6 | class MySqlClientError extends Error { 7 | final String message; 8 | 9 | /** 10 | * Create a [MySqlClientError] 11 | */ 12 | MySqlClientError._(this.message); 13 | 14 | String toString() => "MySQL Client Error: $message"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/mysql_exception.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | /** 4 | * An exception which is returned by the MySQL server. 5 | */ 6 | class MySqlException implements Exception { 7 | int _errorNumber; 8 | String _sqlState; 9 | String _message; 10 | 11 | /// The MySQL error number 12 | int get errorNumber => _errorNumber; 13 | 14 | /// A five character ANSI SQLSTATE value 15 | String get sqlState => _sqlState; 16 | 17 | /// A textual description of the error 18 | String get message => _message; 19 | 20 | /** 21 | * Create a [MySqlException] based on an error response from the mysql server 22 | */ 23 | MySqlException._(Buffer buffer) { 24 | buffer.seek(1); 25 | _errorNumber = buffer.readUint16(); 26 | buffer.skip(1); 27 | _sqlState = buffer.readString(5); 28 | _message = buffer.readStringToEnd(); 29 | } 30 | 31 | String toString() => "Error $_errorNumber ($_sqlState): $_message"; 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/mysql_protocol_error.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | /** 4 | * An error which is thrown when something unexpected is read from the the MySQL protocol. 5 | */ 6 | class MySqlProtocolError extends Error { 7 | final String message; 8 | 9 | /** 10 | * Create a [MySqlProtocolError] 11 | */ 12 | MySqlProtocolError._(this.message); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/prepared_statements/binary_data_packet.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _BinaryDataPacket extends Row { 4 | List values; 5 | final Map _fieldIndex; 6 | final Logger log; 7 | 8 | _BinaryDataPacket._forTests(this.values, this._fieldIndex) : log = new Logger("BinaryDataPacket"); 9 | 10 | _BinaryDataPacket(Buffer buffer, List<_FieldImpl> fields, this._fieldIndex) : log = new Logger("BinaryDataPacket") { 11 | buffer.skip(1); 12 | var nulls = buffer.readList(((fields.length + 7 + 2) / 8).floor().toInt()); 13 | log.fine("Nulls: $nulls"); 14 | var nullMap = new List(fields.length); 15 | var shift = 2; 16 | var byte = 0; 17 | for (var i = 0; i < fields.length; i++) { 18 | var mask = 1 << shift; 19 | nullMap[i] = (nulls[byte] & mask) != 0; 20 | shift++; 21 | if (shift > 7) { 22 | shift = 0; 23 | byte++; 24 | } 25 | } 26 | 27 | values = new List(fields.length); 28 | for (var i = 0; i < fields.length; i++) { 29 | log.fine("$i: ${fields[i].name}"); 30 | if (nullMap[i]) { 31 | log.fine("Value: null"); 32 | values[i] = null; 33 | continue; 34 | } 35 | var field = fields[i]; 36 | values[i] = _readField(field, buffer); 37 | } 38 | } 39 | 40 | _readField(_FieldImpl field, Buffer buffer) { 41 | switch (field.type) { 42 | case FIELD_TYPE_BLOB: 43 | log.fine("BLOB"); 44 | var len = buffer.readLengthCodedBinary(); 45 | var value = new Blob.fromBytes(buffer.readList(len)); 46 | log.fine("Value: ${value}"); 47 | return value; 48 | case FIELD_TYPE_TINY: 49 | log.fine("TINY"); 50 | var value = buffer.readByte(); 51 | log.fine("Value: ${value}"); 52 | return value; 53 | case FIELD_TYPE_SHORT: 54 | log.fine("SHORT"); 55 | var value = buffer.readInt16(); 56 | log.fine("Value: ${value}"); 57 | return value; 58 | case FIELD_TYPE_INT24: 59 | log.fine("INT24"); 60 | var value = buffer.readInt32(); 61 | log.fine("Value: ${value}"); 62 | return value; 63 | case FIELD_TYPE_LONG: 64 | log.fine("LONG"); 65 | var value = buffer.readInt32(); 66 | log.fine("Value: ${value}"); 67 | return value; 68 | case FIELD_TYPE_LONGLONG: 69 | log.fine("LONGLONG"); 70 | var value = buffer.readInt64(); 71 | log.fine("Value: ${value}"); 72 | return value; 73 | case FIELD_TYPE_NEWDECIMAL: 74 | log.fine("NEWDECIMAL"); 75 | var len = buffer.readByte(); 76 | var num = buffer.readString(len); 77 | var value = double.parse(num); 78 | log.fine("Value: ${value}"); 79 | return value; 80 | case FIELD_TYPE_FLOAT: 81 | log.fine("FLOAT"); 82 | var value = buffer.readFloat(); 83 | log.fine("Value: ${value}"); 84 | return value; 85 | case FIELD_TYPE_DOUBLE: 86 | log.fine("DOUBLE"); 87 | var value = buffer.readDouble(); 88 | log.fine("Value: ${value}"); 89 | return value; 90 | case FIELD_TYPE_BIT: 91 | log.fine("BIT"); 92 | var len = buffer.readByte(); 93 | var list = buffer.readList(len); 94 | var value = 0; 95 | for (var num in list) { 96 | value = (value << 8) + num; 97 | } 98 | log.fine("Value: ${value}"); 99 | return value; 100 | case FIELD_TYPE_DATETIME: 101 | case FIELD_TYPE_DATE: 102 | case FIELD_TYPE_TIMESTAMP: 103 | log.fine("DATE/DATETIME"); 104 | var len = buffer.readByte(); 105 | var date = buffer.readList(len); 106 | var year = 0; 107 | var month = 0; 108 | var day = 0; 109 | var hours = 0; 110 | var minutes = 0; 111 | var seconds = 0; 112 | var billionths = 0; 113 | 114 | if (date.length > 0) { 115 | year = date[0] + (date[1] << 0x08); 116 | month = date[2]; 117 | day = date[3]; 118 | if (date.length > 4) { 119 | hours = date[4]; 120 | minutes = date[5]; 121 | seconds = date[6]; 122 | if (date.length > 7) { 123 | billionths = date[7] + (date[8] << 0x08) + (date[9] << 0x10) + (date[10] << 0x18); 124 | } 125 | } 126 | } 127 | 128 | var value = new DateTime(year, month, day, hours, minutes, seconds, billionths ~/ 1000000); 129 | log.fine("Value: ${value}"); 130 | return value; 131 | case FIELD_TYPE_TIME: 132 | log.fine("TIME"); 133 | var len = buffer.readByte(); 134 | var time = buffer.readList(len); 135 | 136 | var sign = 1; 137 | var days = 0; 138 | var hours = 0; 139 | var minutes = 0; 140 | var seconds = 0; 141 | var billionths = 0; 142 | 143 | log.fine("time: $time"); 144 | if (time.length > 0) { 145 | sign = time[0] == 1 ? -1 : 1; 146 | days = time[1] + (time[2] << 0x08) + (time[3] << 0x10) + (time[4] << 0x18); 147 | hours = time[5]; 148 | minutes = time[6]; 149 | seconds = time[7]; 150 | if (time.length > 8) { 151 | billionths = time[8] + (time[9] << 0x08) + (time[10] << 0x10) + (time[11] << 0x18); 152 | } 153 | } 154 | var value = new Duration( 155 | days: days * sign, 156 | hours: hours * sign, 157 | minutes: minutes * sign, 158 | seconds: seconds * sign, 159 | milliseconds: (billionths ~/ 1000000) * sign); 160 | return value; 161 | case FIELD_TYPE_YEAR: 162 | log.fine("YEAR"); 163 | var value = buffer.readInt16(); 164 | log.fine("Value: ${value}"); 165 | return value; 166 | case FIELD_TYPE_STRING: 167 | log.fine("STRING"); 168 | var value = buffer.readLengthCodedString(); 169 | log.fine("Value: ${value}"); 170 | return value; 171 | case FIELD_TYPE_VAR_STRING: 172 | log.fine("STRING"); 173 | var value = buffer.readLengthCodedString(); 174 | log.fine("Value: ${value}"); 175 | return value; 176 | case FIELD_TYPE_GEOMETRY: 177 | log.fine("GEOMETRY - not implemented"); 178 | var len = buffer.readByte(); 179 | //TODO 180 | var value = buffer.readList(len); 181 | return value; 182 | case FIELD_TYPE_NEWDATE: 183 | case FIELD_TYPE_DECIMAL: 184 | //TODO pre 5.0.3 will return old decimal values 185 | case FIELD_TYPE_SET: 186 | case FIELD_TYPE_ENUM: 187 | case FIELD_TYPE_TINY_BLOB: 188 | case FIELD_TYPE_MEDIUM_BLOB: 189 | case FIELD_TYPE_LONG_BLOB: 190 | case FIELD_TYPE_VARCHAR: 191 | //Are there any other types a mysql server can return? 192 | log.fine("Field type not implemented yet ${field.type}"); 193 | log.fine(buffer.readList(8).toString()); 194 | break; 195 | default: 196 | log.fine("Unsupported field type ${field.type}"); 197 | break; 198 | } 199 | return null; 200 | } 201 | 202 | int get length => values.length; 203 | 204 | dynamic operator [](int index) => values[index]; 205 | 206 | void operator []=(int index, dynamic value) { 207 | throw new UnsupportedError("Cannot modify row"); 208 | } 209 | 210 | void set length(int newLength) { 211 | throw new UnsupportedError("Cannot set length of results"); 212 | } 213 | 214 | noSuchMethod(Invocation invocation) { 215 | var name = invocation.memberName; 216 | if (invocation.isGetter) { 217 | var i = _fieldIndex[name]; 218 | if (i != null) { 219 | return values[i]; 220 | } 221 | } 222 | return super.noSuchMethod(invocation); 223 | } 224 | 225 | String toString() => "Value: $values"; 226 | } 227 | -------------------------------------------------------------------------------- /lib/src/prepared_statements/close_statement_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _CloseStatementHandler extends _Handler { 4 | final int _handle; 5 | 6 | _CloseStatementHandler(int this._handle) { 7 | log = new Logger("CloseStatementHandler"); 8 | } 9 | 10 | Buffer createRequest() { 11 | var buffer = new Buffer(5); 12 | buffer.writeByte(COM_STMT_CLOSE); 13 | buffer.writeUint32(_handle); 14 | return buffer; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/prepared_statements/execute_query_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _ExecuteQueryHandler extends _Handler { 4 | static const int STATE_HEADER_PACKET = 0; 5 | static const int STATE_FIELD_PACKETS = 1; 6 | static const int STATE_ROW_PACKETS = 2; 7 | 8 | int _state = STATE_HEADER_PACKET; 9 | 10 | _ResultSetHeaderPacket _resultSetHeaderPacket; 11 | List<_FieldImpl> _fieldPackets; 12 | Map _fieldIndex; 13 | StreamController _streamController; 14 | 15 | final _PreparedQuery _preparedQuery; 16 | final List _values; 17 | List _preparedValues; 18 | _OkPacket _okPacket; 19 | bool _executed; 20 | bool _cancelled = false; 21 | 22 | _ExecuteQueryHandler(_PreparedQuery this._preparedQuery, bool this._executed, List this._values) { 23 | _fieldPackets = <_FieldImpl>[]; 24 | log = new Logger("ExecuteQueryHandler"); 25 | } 26 | 27 | Buffer createRequest() { 28 | var length = 0; 29 | var types = new List(_values.length * 2); 30 | var nullMap = _createNullMap(); 31 | _preparedValues = new List(_values.length); 32 | for (var i = 0; i < _values.length; i++) { 33 | types[i * 2] = _getType(_values[i]); 34 | types[i * 2 + 1] = 0; 35 | _preparedValues[i] = _prepareValue(_values[i]); 36 | length += _measureValue(_values[i], _preparedValues[i]); 37 | } 38 | 39 | var buffer = _writeValuesToBuffer(nullMap, length, types); 40 | // log.fine(Buffer.listChars(buffer._list)); 41 | return buffer; 42 | } 43 | 44 | _prepareValue(value) { 45 | if (value != null) { 46 | if (value is int) { 47 | return _prepareInt(value); 48 | } else if (value is double) { 49 | return _prepareDouble(value); 50 | } else if (value is DateTime) { 51 | return _prepareDateTime(value); 52 | } else if (value is bool) { 53 | return _prepareBool(value); 54 | } else if (value is List) { 55 | return _prepareList(value); 56 | } else if (value is Blob) { 57 | return _prepareBlob(value); 58 | } else { 59 | return _prepareString(value); 60 | } 61 | } 62 | return value; 63 | } 64 | 65 | _measureValue(value, preparedValue) { 66 | if (value != null) { 67 | if (value is int) { 68 | return _measureInt(value, preparedValue); 69 | } else if (value is double) { 70 | return _measureDouble(value, preparedValue); 71 | } else if (value is DateTime) { 72 | return _measureDateTime(value, preparedValue); 73 | } else if (value is bool) { 74 | return _measureBool(value, preparedValue); 75 | } else if (value is List) { 76 | return _measureList(value, preparedValue); 77 | } else if (value is Blob) { 78 | return _measureBlob(value, preparedValue); 79 | } else { 80 | return _measureString(value, preparedValue); 81 | } 82 | } 83 | return 0; 84 | } 85 | 86 | _getType(value) { 87 | if (value != null) { 88 | if (value is int) { 89 | return FIELD_TYPE_LONGLONG; 90 | } else if (value is double) { 91 | return FIELD_TYPE_VARCHAR; 92 | } else if (value is DateTime) { 93 | return FIELD_TYPE_DATETIME; 94 | } else if (value is bool) { 95 | return FIELD_TYPE_TINY; 96 | } else if (value is List) { 97 | return FIELD_TYPE_BLOB; 98 | } else if (value is Blob) { 99 | return FIELD_TYPE_BLOB; 100 | } else { 101 | return FIELD_TYPE_VARCHAR; 102 | } 103 | } else { 104 | return FIELD_TYPE_NULL; 105 | } 106 | } 107 | 108 | _writeValue(value, preparedValue, Buffer buffer) { 109 | if (value != null) { 110 | if (value is int) { 111 | _writeInt(value, preparedValue, buffer); 112 | } else if (value is double) { 113 | _writeDouble(value, preparedValue, buffer); 114 | } else if (value is DateTime) { 115 | _writeDateTime(value, preparedValue, buffer); 116 | } else if (value is bool) { 117 | _writeBool(value, preparedValue, buffer); 118 | } else if (value is List) { 119 | _writeList(value, preparedValue, buffer); 120 | } else if (value is Blob) { 121 | _writeBlob(value, preparedValue, buffer); 122 | } else { 123 | _writeString(value, preparedValue, buffer); 124 | } 125 | } 126 | } 127 | 128 | _prepareInt(value) { 129 | return value; 130 | } 131 | 132 | int _measureInt(value, preparedValue) { 133 | return 8; 134 | } 135 | 136 | _writeInt(value, preparedValue, Buffer buffer) { 137 | // if (value < 128 && value > -127) { 138 | // log.fine("TINYINT: value"); 139 | // types.add(FIELD_TYPE_TINY); 140 | // types.add(0); 141 | // values.add(value & 0xFF); 142 | // } else { 143 | log.fine("LONG: $value"); 144 | buffer.writeByte(value >> 0x00 & 0xFF); 145 | buffer.writeByte(value >> 0x08 & 0xFF); 146 | buffer.writeByte(value >> 0x10 & 0xFF); 147 | buffer.writeByte(value >> 0x18 & 0xFF); 148 | buffer.writeByte(value >> 0x20 & 0xFF); 149 | buffer.writeByte(value >> 0x28 & 0xFF); 150 | buffer.writeByte(value >> 0x30 & 0xFF); 151 | buffer.writeByte(value >> 0x38 & 0xFF); 152 | // } 153 | } 154 | 155 | _prepareDouble(value) { 156 | return UTF8.encode(value.toString()); 157 | } 158 | 159 | int _measureDouble(value, preparedValue) { 160 | return Buffer.measureLengthCodedBinary(preparedValue.length) + preparedValue.length; 161 | } 162 | 163 | _writeDouble(value, preparedValue, Buffer buffer) { 164 | log.fine("DOUBLE: $value"); 165 | 166 | buffer.writeLengthCodedBinary(preparedValue.length); 167 | buffer.writeList(preparedValue); 168 | 169 | // TODO: if you send a double value for a decimal field, it doesn't like it 170 | // types.add(FIELD_TYPE_FLOAT); 171 | // types.add(0); 172 | // values.addAll(doubleToList(value)); 173 | } 174 | 175 | _prepareDateTime(value) { 176 | return value; 177 | } 178 | 179 | int _measureDateTime(value, preparedValue) { 180 | return 8; 181 | } 182 | 183 | _writeDateTime(value, preparedValue, Buffer buffer) { 184 | // TODO remove Date eventually 185 | log.fine("DATE: $value"); 186 | buffer.writeByte(7); 187 | buffer.writeByte(value.year >> 0x00 & 0xFF); 188 | buffer.writeByte(value.year >> 0x08 & 0xFF); 189 | buffer.writeByte(value.month); 190 | buffer.writeByte(value.day); 191 | buffer.writeByte(value.hour); 192 | buffer.writeByte(value.minute); 193 | buffer.writeByte(value.second); 194 | } 195 | 196 | _prepareBool(value) { 197 | return value; 198 | } 199 | 200 | int _measureBool(value, preparedValue) { 201 | return 1; 202 | } 203 | 204 | _writeBool(value, preparedValue, Buffer buffer) { 205 | log.fine("BOOL: $value"); 206 | buffer.writeByte(value ? 1 : 0); 207 | } 208 | 209 | _prepareList(value) { 210 | return value; 211 | } 212 | 213 | int _measureList(value, preparedValue) { 214 | return Buffer.measureLengthCodedBinary(value.length) + value.length; 215 | } 216 | 217 | _writeList(value, preparedValue, Buffer buffer) { 218 | log.fine("LIST: $value"); 219 | buffer.writeLengthCodedBinary(value.length); 220 | buffer.writeList(value); 221 | } 222 | 223 | _prepareBlob(value) { 224 | return (value as Blob).toBytes(); 225 | } 226 | 227 | int _measureBlob(value, preparedValue) { 228 | return Buffer.measureLengthCodedBinary(preparedValue.length) + preparedValue.length; 229 | } 230 | 231 | _writeBlob(value, preparedValue, Buffer buffer) { 232 | log.fine("BLOB: $value"); 233 | buffer.writeLengthCodedBinary(preparedValue.length); 234 | buffer.writeList(preparedValue); 235 | } 236 | 237 | _prepareString(value) { 238 | return UTF8.encode(value.toString()); 239 | } 240 | 241 | int _measureString(value, preparedValue) { 242 | return Buffer.measureLengthCodedBinary(preparedValue.length) + preparedValue.length; 243 | } 244 | 245 | _writeString(value, preparedValue, Buffer buffer) { 246 | log.fine("STRING: $value"); 247 | buffer.writeLengthCodedBinary(preparedValue.length); 248 | buffer.writeList(preparedValue); 249 | } 250 | 251 | List _createNullMap() { 252 | var bytes = ((_values.length + 7) / 8).floor().toInt(); 253 | var nullMap = new List(bytes); 254 | var byte = 0; 255 | var bit = 0; 256 | for (var i = 0; i < _values.length; i++) { 257 | if (nullMap[byte] == null) { 258 | nullMap[byte] = 0; 259 | } 260 | if (_values[i] == null) { 261 | nullMap[byte] = nullMap[byte] + (1 << bit); 262 | } 263 | bit++; 264 | if (bit > 7) { 265 | bit = 0; 266 | byte++; 267 | } 268 | } 269 | 270 | return nullMap; 271 | } 272 | 273 | Buffer _writeValuesToBuffer(List nullMap, int length, List types) { 274 | var buffer = new Buffer(10 + nullMap.length + 1 + types.length + length); 275 | buffer.writeByte(COM_STMT_EXECUTE); 276 | buffer.writeUint32(_preparedQuery.statementHandlerId); 277 | buffer.writeByte(0); 278 | buffer.writeUint32(1); 279 | buffer.writeList(nullMap); 280 | if (!_executed) { 281 | buffer.writeByte(1); 282 | buffer.writeList(types); 283 | for (int i = 0; i < _values.length; i++) { 284 | _writeValue(_values[i], _preparedValues[i], buffer); 285 | } 286 | } else { 287 | buffer.writeByte(0); 288 | } 289 | return buffer; 290 | } 291 | 292 | _HandlerResponse processResponse(Buffer response) { 293 | var packet; 294 | if (_cancelled) { 295 | _streamController.close(); 296 | return new _HandlerResponse(finished: true); 297 | } 298 | if (_state == STATE_HEADER_PACKET) { 299 | packet = checkResponse(response); 300 | } 301 | if (packet == null) { 302 | if (response[0] == PACKET_EOF) { 303 | log.fine('Got an EOF'); 304 | if (_state == STATE_FIELD_PACKETS) { 305 | return _handleEndOfFields(); 306 | } else if (_state == STATE_ROW_PACKETS) { 307 | return _handleEndOfRows(); 308 | } 309 | } else { 310 | switch (_state) { 311 | case STATE_HEADER_PACKET: 312 | _handleHeaderPacket(response); 313 | break; 314 | case STATE_FIELD_PACKETS: 315 | _handleFieldPacket(response); 316 | break; 317 | case STATE_ROW_PACKETS: 318 | _handleRowPacket(response); 319 | break; 320 | } 321 | } 322 | } else if (packet is _OkPacket) { 323 | _okPacket = packet; 324 | if ((packet.serverStatus & SERVER_MORE_RESULTS_EXISTS) == 0) { 325 | return new _HandlerResponse( 326 | finished: true, result: new _ResultsImpl(_okPacket.insertId, _okPacket.affectedRows, null)); 327 | } 328 | } 329 | return _HandlerResponse.notFinished; 330 | } 331 | 332 | _handleEndOfFields() { 333 | _state = STATE_ROW_PACKETS; 334 | _streamController = new StreamController(); 335 | _streamController.onCancel = () { 336 | _cancelled = true; 337 | }; 338 | this._fieldIndex = _createFieldIndex(); 339 | return new _HandlerResponse(result: new _ResultsImpl(null, null, _fieldPackets, stream: _streamController.stream)); 340 | } 341 | 342 | _handleEndOfRows() { 343 | _streamController.close(); 344 | return new _HandlerResponse(finished: true); 345 | } 346 | 347 | _handleHeaderPacket(Buffer response) { 348 | log.fine('Got a header packet'); 349 | _resultSetHeaderPacket = new _ResultSetHeaderPacket(response); 350 | log.fine(_resultSetHeaderPacket.toString()); 351 | _state = STATE_FIELD_PACKETS; 352 | } 353 | 354 | _handleFieldPacket(Buffer response) { 355 | log.fine('Got a field packet'); 356 | var fieldPacket = new _FieldImpl._(response); 357 | log.fine(fieldPacket.toString()); 358 | _fieldPackets.add(fieldPacket); 359 | } 360 | 361 | _handleRowPacket(Buffer response) { 362 | log.fine('Got a row packet'); 363 | var dataPacket = new _BinaryDataPacket(response, _fieldPackets, _fieldIndex); 364 | log.fine(dataPacket.toString()); 365 | _streamController.add(dataPacket); 366 | } 367 | 368 | Map _createFieldIndex() { 369 | var identifierPattern = new RegExp(r'^[a-zA-Z][a-zA-Z0-9_]*$'); 370 | var fieldIndex = new Map(); 371 | for (var i = 0; i < _fieldPackets.length; i++) { 372 | var name = _fieldPackets[i].name; 373 | if (identifierPattern.hasMatch(name)) { 374 | fieldIndex[new Symbol(name)] = i; 375 | } 376 | } 377 | return fieldIndex; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /lib/src/prepared_statements/prepare_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _PrepareHandler extends _Handler { 4 | final String _sql; 5 | _PrepareOkPacket _okPacket; 6 | int _parametersToRead; 7 | int _columnsToRead; 8 | List<_FieldImpl> _parameters; 9 | List<_FieldImpl> _columns; 10 | 11 | String get sql => _sql; 12 | _PrepareOkPacket get okPacket => _okPacket; 13 | List<_FieldImpl> get parameters => _parameters; 14 | List<_FieldImpl> get columns => _columns; 15 | 16 | _PrepareHandler(String this._sql) { 17 | log = new Logger("PrepareHandler"); 18 | } 19 | 20 | Buffer createRequest() { 21 | var encoded = UTF8.encode(_sql); 22 | var buffer = new Buffer(encoded.length + 1); 23 | buffer.writeByte(COM_STMT_PREPARE); 24 | buffer.writeList(encoded); 25 | return buffer; 26 | } 27 | 28 | _HandlerResponse processResponse(Buffer response) { 29 | log.fine("Prepare processing response"); 30 | var packet = checkResponse(response, true); 31 | if (packet == null) { 32 | log.fine('Not an OK packet, params to read: $_parametersToRead'); 33 | if (_parametersToRead > -1) { 34 | if (response[0] == PACKET_EOF) { 35 | log.fine("EOF"); 36 | if (_parametersToRead != 0) { 37 | throw new MySqlProtocolError._( 38 | "Unexpected EOF packet; was expecting another $_parametersToRead parameter(s)"); 39 | } 40 | } else { 41 | var fieldPacket = new _FieldImpl._(response); 42 | log.fine("field packet: $fieldPacket"); 43 | _parameters[_okPacket.parameterCount - _parametersToRead] = fieldPacket; 44 | } 45 | _parametersToRead--; 46 | } else if (_columnsToRead > -1) { 47 | if (response[0] == PACKET_EOF) { 48 | log.fine("EOF"); 49 | if (_columnsToRead != 0) { 50 | throw new MySqlProtocolError._("Unexpected EOF packet; was expecting another $_columnsToRead column(s)"); 51 | } 52 | } else { 53 | var fieldPacket = new _FieldImpl._(response); 54 | log.fine("field packet (column): $fieldPacket"); 55 | _columns[_okPacket.columnCount - _columnsToRead] = fieldPacket; 56 | } 57 | _columnsToRead--; 58 | } 59 | } else if (packet is _PrepareOkPacket) { 60 | log.fine(packet.toString()); 61 | _okPacket = packet; 62 | _parametersToRead = packet.parameterCount; 63 | _columnsToRead = packet.columnCount; 64 | _parameters = new List<_FieldImpl>(_parametersToRead); 65 | _columns = new List<_FieldImpl>(_columnsToRead); 66 | if (_parametersToRead == 0) { 67 | _parametersToRead = -1; 68 | } 69 | if (_columnsToRead == 0) { 70 | _columnsToRead = -1; 71 | } 72 | } 73 | 74 | if (_parametersToRead == -1 && _columnsToRead == -1) { 75 | log.fine("finished"); 76 | return new _HandlerResponse(finished: true, result: new _PreparedQuery(this)); 77 | } 78 | return _HandlerResponse.notFinished; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/prepared_statements/prepare_ok_packet.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _PrepareOkPacket { 4 | int _statementHandlerId; 5 | int _columnCount; 6 | int _parameterCount; 7 | int _warningCount; 8 | 9 | int get statementHandlerId => _statementHandlerId; 10 | int get columnCount => _columnCount; 11 | int get parameterCount => _parameterCount; 12 | int get warningCount => _warningCount; 13 | 14 | _PrepareOkPacket(Buffer buffer) { 15 | buffer.seek(1); 16 | _statementHandlerId = buffer.readUint32(); 17 | _columnCount = buffer.readUint16(); 18 | _parameterCount = buffer.readUint16(); 19 | buffer.skip(1); 20 | _warningCount = buffer.readUint16(); 21 | } 22 | 23 | String toString() => "OK: statement handler id: $_statementHandlerId, columns: $_columnCount, " 24 | "parameters: $_parameterCount, warnings: $_warningCount"; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/prepared_statements/prepared_query.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _PreparedQuery { 4 | final String sql; 5 | final List<_FieldImpl> parameters; 6 | final List<_FieldImpl> columns; 7 | final int statementHandlerId; 8 | _Connection cnx; 9 | 10 | _PreparedQuery(_PrepareHandler handler) 11 | : sql = handler.sql, 12 | parameters = handler.parameters, 13 | columns = handler.columns, 14 | statementHandlerId = handler.okPacket.statementHandlerId; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/query.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | /** 4 | * Query is created by `ConnectionPool.prepare(sql)` and `Transaction.prepare(sql)`. It holds 5 | * a prepared query. 6 | * 7 | * In MySQL, a query must be prepared on a specific connection. If you execute this 8 | * query and a connection is used from the pool which doesn't yet have the prepared query 9 | * in its cache, it will first prepare the query on that connection before executing it. 10 | */ 11 | class Query extends Object with _ConnectionHelpers { 12 | final ConnectionPool _pool; 13 | final _Connection _cnx; 14 | final String sql; 15 | final Logger _log; 16 | final _inTransaction; 17 | bool _executed = false; 18 | 19 | Query._internal(this._pool, this.sql) 20 | : _cnx = null, 21 | _inTransaction = false, 22 | _log = new Logger("Query"); 23 | 24 | Query._forTransaction(this._pool, _Connection cnx, this.sql) 25 | : _cnx = cnx, 26 | _inTransaction = true, 27 | _log = new Logger("Query"); 28 | 29 | Future<_Connection> _getConnection() async { 30 | if (_cnx != null) { 31 | return _cnx; 32 | } 33 | return _pool._getConnection(); 34 | } 35 | 36 | Future<_PreparedQuery> _prepare(bool retainConnection) async { 37 | _log.fine("Getting prepared query for: $sql"); 38 | 39 | var cnx = await _getConnection(); 40 | cnx.autoRelease = !retainConnection; 41 | _log.fine("Got cnx#${cnx.number}"); 42 | var preparedQuery = _useCachedQuery(cnx); 43 | if (preparedQuery != null) { 44 | if (!retainConnection) { 45 | // didn't actually use the connection, so the auto-release 46 | // mechanism will never get fired, so we'd better give up 47 | // on the connection now 48 | cnx.release(); 49 | } 50 | return preparedQuery; 51 | } else { 52 | return await _prepareAndCacheQuery(cnx, retainConnection); 53 | } 54 | } 55 | 56 | /** 57 | * Returns true if there was already a cached query which has been used. 58 | */ 59 | _PreparedQuery _useCachedQuery(_Connection cnx) { 60 | var preparedQuery = cnx.getPreparedQueryFromCache(sql); 61 | if (preparedQuery == null) { 62 | return null; 63 | } 64 | 65 | _log.fine("Got prepared query from cache in cnx#${cnx.number} for: $sql"); 66 | return preparedQuery; 67 | } 68 | 69 | _prepareAndCacheQuery(_Connection cnx, retainConnection) async { 70 | _log.fine("Preparing new query in cnx#${cnx.number} for: $sql"); 71 | var handler = new _PrepareHandler(sql); 72 | cnx.use(); 73 | cnx.autoRelease = !retainConnection; 74 | var preparedQuery = await cnx.processHandler(handler); 75 | try { 76 | _log.fine("Prepared new query in cnx#${cnx.number} for: $sql"); 77 | preparedQuery.cnx = cnx; 78 | cnx.putPreparedQueryInCache(sql, preparedQuery); 79 | return preparedQuery; 80 | } catch (e) { 81 | _releaseReuseThrow(cnx, e); 82 | } 83 | } 84 | 85 | /// Closes this query and removes it from all connections in the pool. 86 | close() async { 87 | _pool._closeQuery(this, _inTransaction); 88 | } 89 | 90 | /** 91 | * Executes the query, returning a future [Results] object. 92 | */ 93 | Future execute([List values]) async { 94 | _log.fine("Prepare..."); 95 | var preparedQuery = await _prepare(true); 96 | _log.fine("Prepared, now to execute"); 97 | Results results = await _execute(preparedQuery, values == null ? [] : values); 98 | _log.fine("Got prepared query results on #${preparedQuery.cnx.number} for: ${sql}"); 99 | return results; 100 | } 101 | 102 | Future _execute(_PreparedQuery preparedQuery, List values, {bool retainConnection: false}) async { 103 | _log.finest("About to execute"); 104 | var handler = new _ExecuteQueryHandler(preparedQuery, _executed, values); 105 | preparedQuery.cnx.autoRelease = !retainConnection; 106 | try { 107 | Results results = await preparedQuery.cnx.processHandler(handler); 108 | _log.finest("Prepared query got results"); 109 | return results; 110 | } catch (e) { 111 | _releaseReuseThrow(preparedQuery.cnx, e); 112 | } 113 | } 114 | 115 | /** 116 | * Executes the query once for each set of [parameters], and returns a future list 117 | * of results, one for each set of parameters, that completes when the query has been executed. 118 | * 119 | * Because this method has to wait for all the results to return from the server before it 120 | * can move onto the next query, it ends up keeping all the results in memory, rather than 121 | * streaming them, which can be less efficient. 122 | */ 123 | Future> executeMulti(List parameters) async { 124 | var preparedQuery = await _prepare(true); 125 | _log.fine("Prepared query for multi execution. Number of values: ${parameters.length}"); 126 | var resultList = new List(); 127 | 128 | for (int i = 0; i < parameters.length; i++) { 129 | try { 130 | _log.fine("Executing query, loop $i"); 131 | Results results = await _execute(preparedQuery, parameters[i], retainConnection: true); 132 | _log.fine("Got results, loop $i"); 133 | Results deStreamedResults = await _ResultsImpl.destream(results); 134 | resultList.add(deStreamedResults); 135 | } catch (e) { 136 | _releaseReuseThrow(preparedQuery.cnx, e); 137 | } 138 | } 139 | preparedQuery.cnx.release(); 140 | return resultList; 141 | } 142 | 143 | _removeConnection(_Connection cnx) { 144 | if (!_inTransaction) { 145 | _pool._removeConnection(cnx); 146 | } 147 | } 148 | // dynamic longData(int index, data); 149 | // dynamic reset(); 150 | // dynamic fetch(int rows); 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/query/query_stream_handler.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _QueryStreamHandler extends _Handler { 4 | static const int STATE_HEADER_PACKET = 0; 5 | static const int STATE_FIELD_PACKETS = 1; 6 | static const int STATE_ROW_PACKETS = 2; 7 | final String _sql; 8 | int _state = STATE_HEADER_PACKET; 9 | 10 | _OkPacket _okPacket; 11 | _ResultSetHeaderPacket _resultSetHeaderPacket; 12 | List<_FieldImpl> _fieldPackets; 13 | Map _fieldIndex; 14 | 15 | StreamController _streamController; 16 | 17 | _QueryStreamHandler(String this._sql) { 18 | log = new Logger("QueryStreamHandler"); 19 | _fieldPackets = <_FieldImpl>[]; 20 | } 21 | 22 | Buffer createRequest() { 23 | var encoded = UTF8.encode(_sql); 24 | var buffer = new Buffer(encoded.length + 1); 25 | buffer.writeByte(COM_QUERY); 26 | buffer.writeList(encoded); 27 | return buffer; 28 | } 29 | 30 | _HandlerResponse processResponse(Buffer response) { 31 | log.fine("Processing query response"); 32 | var packet = checkResponse(response, false, _state == STATE_ROW_PACKETS); 33 | if (packet == null) { 34 | if (response[0] == PACKET_EOF) { 35 | if (_state == STATE_FIELD_PACKETS) { 36 | return _handleEndOfFields(); 37 | } else if (_state == STATE_ROW_PACKETS) { 38 | return _handleEndOfRows(); 39 | } 40 | } else { 41 | switch (_state) { 42 | case STATE_HEADER_PACKET: 43 | _handleHeaderPacket(response); 44 | break; 45 | case STATE_FIELD_PACKETS: 46 | _handleFieldPacket(response); 47 | break; 48 | case STATE_ROW_PACKETS: 49 | _handleRowPacket(response); 50 | break; 51 | } 52 | } 53 | } else if (packet is _OkPacket) { 54 | return _handleOkPacket(packet); 55 | } 56 | return _HandlerResponse.notFinished; 57 | } 58 | 59 | _handleEndOfFields() { 60 | _state = STATE_ROW_PACKETS; 61 | _streamController = new StreamController(onCancel: () { 62 | _streamController.close(); 63 | }); 64 | this._fieldIndex = _createFieldIndex(); 65 | return new _HandlerResponse(result: new _ResultsImpl(null, null, _fieldPackets, stream: _streamController.stream)); 66 | } 67 | 68 | _handleEndOfRows() { 69 | // the connection's _handler field needs to have been nulled out before the stream is closed, 70 | // otherwise the stream will be reused in an unfinished state. 71 | // TODO: can we use Future.delayed elsewhere, to make reusing connections nicer? 72 | new Future.delayed(new Duration(seconds: 0), _streamController.close); 73 | return new _HandlerResponse(finished: true); 74 | } 75 | 76 | _handleHeaderPacket(Buffer response) { 77 | _resultSetHeaderPacket = new _ResultSetHeaderPacket(response); 78 | log.fine(_resultSetHeaderPacket.toString()); 79 | _state = STATE_FIELD_PACKETS; 80 | } 81 | 82 | _handleFieldPacket(Buffer response) { 83 | var fieldPacket = new _FieldImpl._(response); 84 | log.fine(fieldPacket.toString()); 85 | _fieldPackets.add(fieldPacket); 86 | } 87 | 88 | _handleRowPacket(Buffer response) { 89 | var dataPacket = new _StandardDataPacket(response, _fieldPackets, _fieldIndex); 90 | log.fine(dataPacket.toString()); 91 | _streamController.add(dataPacket); 92 | } 93 | 94 | _handleOkPacket(packet) { 95 | _okPacket = packet; 96 | var finished = false; 97 | // TODO: I think this is to do with multiple queries. Will probably break. 98 | if ((packet.serverStatus & SERVER_MORE_RESULTS_EXISTS) == 0) { 99 | finished = true; 100 | } 101 | 102 | //TODO is this finished value right? 103 | return new _HandlerResponse( 104 | finished: finished, result: new _ResultsImpl(_okPacket.insertId, _okPacket.affectedRows, _fieldPackets)); 105 | } 106 | 107 | Map _createFieldIndex() { 108 | var identifierPattern = new RegExp(r'^[a-zA-Z][a-zA-Z0-9_]*$'); 109 | var fieldIndex = new Map(); 110 | for (var i = 0; i < _fieldPackets.length; i++) { 111 | var name = _fieldPackets[i].name; 112 | if (identifierPattern.hasMatch(name)) { 113 | fieldIndex[new Symbol(name)] = i; 114 | } 115 | } 116 | return fieldIndex; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/src/query/result_set_header_packet.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _ResultSetHeaderPacket { 4 | int _fieldCount; 5 | int _extra; 6 | Logger log; 7 | 8 | int get fieldCount => _fieldCount; 9 | 10 | _ResultSetHeaderPacket(Buffer buffer) { 11 | log = new Logger("ResultSetHeaderPacket"); 12 | _fieldCount = buffer.readLengthCodedBinary(); 13 | if (buffer.canReadMore()) { 14 | _extra = buffer.readLengthCodedBinary(); 15 | } 16 | } 17 | 18 | String toString() => "Field count: $_fieldCount, Extra: $_extra"; 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/query/standard_data_packet.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _StandardDataPacket extends Row { 4 | final List values; 5 | final Map _fieldIndex; 6 | 7 | _StandardDataPacket(Buffer buffer, List<_FieldImpl> fieldPackets, this._fieldIndex) 8 | : values = new List(fieldPackets.length) { 9 | for (var i = 0; i < fieldPackets.length; i++) { 10 | var list; 11 | int length = buffer.readLengthCodedBinary(); 12 | if (length != null) { 13 | list = buffer.readList(length); 14 | } else { 15 | values[i] = null; 16 | continue; 17 | } 18 | switch (fieldPackets[i].type) { 19 | case FIELD_TYPE_TINY: // tinyint/bool 20 | case FIELD_TYPE_SHORT: // smallint 21 | case FIELD_TYPE_INT24: // mediumint 22 | case FIELD_TYPE_LONGLONG: // bigint/serial 23 | case FIELD_TYPE_LONG: // int 24 | var s = UTF8.decode(list); 25 | values[i] = int.parse(s); 26 | break; 27 | case FIELD_TYPE_NEWDECIMAL: // decimal 28 | case FIELD_TYPE_FLOAT: // float 29 | case FIELD_TYPE_DOUBLE: // double 30 | var s = UTF8.decode(list); 31 | values[i] = double.parse(s); 32 | break; 33 | case FIELD_TYPE_BIT: // bit 34 | var value = 0; 35 | for (var num in list) { 36 | value = (value << 8) + num; 37 | } 38 | values[i] = value; 39 | break; 40 | case FIELD_TYPE_DATE: // date 41 | case FIELD_TYPE_DATETIME: // datetime 42 | case FIELD_TYPE_TIMESTAMP: // timestamp 43 | var s = UTF8.decode(list); 44 | values[i] = DateTime.parse(s); 45 | break; 46 | case FIELD_TYPE_TIME: // time 47 | var s = UTF8.decode(list); 48 | var parts = s.split(":"); 49 | values[i] = new Duration( 50 | days: 0, 51 | hours: int.parse(parts[0]), 52 | minutes: int.parse(parts[1]), 53 | seconds: int.parse(parts[2]), 54 | milliseconds: 0); 55 | break; 56 | case FIELD_TYPE_YEAR: // year 57 | var s = UTF8.decode(list); 58 | values[i] = int.parse(s); 59 | break; 60 | case FIELD_TYPE_STRING: // char/binary/enum/set 61 | case FIELD_TYPE_VAR_STRING: // varchar/varbinary 62 | var s = UTF8.decode(list); 63 | values[i] = s; 64 | break; 65 | case FIELD_TYPE_BLOB: // tinytext/text/mediumtext/longtext/tinyblob/mediumblob/blob/longblob 66 | values[i] = new Blob.fromBytes(list); 67 | break; 68 | case FIELD_TYPE_GEOMETRY: // geometry 69 | var s = UTF8.decode(list); 70 | values[i] = s; 71 | break; 72 | } 73 | } 74 | } 75 | 76 | _StandardDataPacket._forTests(this.values, this._fieldIndex); 77 | 78 | int get length => values.length; 79 | 80 | dynamic operator [](int index) => values[index]; 81 | 82 | void operator []=(int index, dynamic value) { 83 | throw new UnsupportedError("Cannot modify row"); 84 | } 85 | 86 | void set length(int newLength) { 87 | throw new UnsupportedError("Cannot set length of results"); 88 | } 89 | 90 | noSuchMethod(Invocation invocation) { 91 | var name = invocation.memberName; 92 | if (invocation.isGetter) { 93 | var i = _fieldIndex[name]; 94 | if (i != null) { 95 | return values[i]; 96 | } 97 | } 98 | return super.noSuchMethod(invocation); 99 | } 100 | 101 | String toString() => "Value: $values"; 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/results.dart: -------------------------------------------------------------------------------- 1 | /** 2 | * results is exported by sqljocky, so there is no need to 3 | * separately import this library. 4 | */ 5 | library results; 6 | 7 | import 'dart:collection'; 8 | import 'dart:async'; 9 | 10 | part 'results/field.dart'; 11 | part 'results/row.dart'; 12 | part 'results/results.dart'; 13 | -------------------------------------------------------------------------------- /lib/src/results/field.dart: -------------------------------------------------------------------------------- 1 | part of results; 2 | 3 | //TODO document these fields 4 | /// A MySQL field 5 | abstract class Field { 6 | String get catalog; 7 | String get db; 8 | String get table; 9 | String get orgTable; 10 | 11 | /// The name of the field 12 | String get name; 13 | String get orgName; 14 | int get characterSet; 15 | int get length; 16 | int get type; 17 | int get flags; 18 | int get decimals; 19 | int get defaultValue; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/results/field_impl.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _FieldImpl implements Field { 4 | String _catalog; 5 | String _db; 6 | String _table; 7 | String _orgTable; 8 | String _name; 9 | String _orgName; 10 | int _characterSet; 11 | int _length; 12 | int _type; 13 | int _flags; 14 | int _decimals; 15 | int _defaultValue; 16 | 17 | String get catalog => _catalog; 18 | String get db => _db; 19 | String get table => _table; 20 | String get orgTable => _orgTable; 21 | String get name => _name; 22 | String get orgName => _orgName; 23 | int get characterSet => _characterSet; 24 | int get length => _length; 25 | int get type => _type; 26 | int get flags => _flags; 27 | int get decimals => _decimals; 28 | int get defaultValue => _defaultValue; 29 | 30 | _FieldImpl._forTests(this._type); 31 | 32 | _FieldImpl._(Buffer buffer) { 33 | _catalog = buffer.readLengthCodedString(); 34 | _db = buffer.readLengthCodedString(); 35 | _table = buffer.readLengthCodedString(); 36 | _orgTable = buffer.readLengthCodedString(); 37 | _name = buffer.readLengthCodedString(); 38 | _orgName = buffer.readLengthCodedString(); 39 | buffer.skip(1); 40 | _characterSet = buffer.readUint16(); 41 | _length = buffer.readUint32(); 42 | _type = buffer.readByte(); 43 | _flags = buffer.readUint16(); 44 | _decimals = buffer.readByte(); 45 | buffer.skip(2); 46 | if (buffer.canReadMore()) { 47 | _defaultValue = buffer.readLengthCodedBinary(); 48 | } 49 | } 50 | 51 | String toString() => "Catalog: $_catalog, DB: $_db, Table: $_table, Org Table: $_orgTable, " 52 | "Name: $_name, Org Name: $_orgName, Character Set: $_characterSet, " 53 | "Length: $_length, Type: $_type, Flags: $_flags, Decimals: $_decimals, " 54 | "Default Value: $_defaultValue"; 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/results/results.dart: -------------------------------------------------------------------------------- 1 | part of results; 2 | 3 | /** 4 | * Holds query results. 5 | * 6 | * If the query was an insert statement, the id of the inserted row is in [insertId]. 7 | * 8 | * If the query was an update statement, the number of affected rows is in [affectedRows]. 9 | * 10 | * If the query was a select statement, the stream contains the row results and 11 | * the [fields] are also available. 12 | */ 13 | abstract class Results implements Stream { 14 | /** 15 | * The id of the inserted row, or [null] if no row was inserted. 16 | */ 17 | int get insertId; 18 | 19 | /** 20 | * The number of affected rows in an update statement, or 21 | * [null] in other cases. 22 | */ 23 | int get affectedRows; 24 | 25 | /** 26 | * A list of the fields returned by the query. 27 | */ 28 | List get fields; 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/results/results_impl.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | class _ResultsImpl extends StreamView implements Results { 4 | final int insertId; 5 | final int affectedRows; 6 | List _fields; 7 | 8 | List get fields => _fields; 9 | 10 | factory _ResultsImpl(int insertId, int affectedRows, List fields, {Stream stream: null}) { 11 | if (stream != null) { 12 | var newStream = stream.transform(new StreamTransformer.fromHandlers(handleDone: (EventSink sink) { 13 | sink.close(); 14 | })); 15 | return new _ResultsImpl._fromStream(insertId, affectedRows, fields, newStream); 16 | } else { 17 | var newStream = new Stream.fromIterable(new List()); 18 | return new _ResultsImpl._fromStream(insertId, affectedRows, fields, newStream); 19 | } 20 | } 21 | 22 | _ResultsImpl._fromStream(this.insertId, this.affectedRows, List fields, Stream stream) : super(stream) { 23 | _fields = new UnmodifiableListView(fields); 24 | } 25 | 26 | /** 27 | * Takes a _ResultsImpl and destreams it. That is, it listens to the stream, collecting 28 | * all the rows into a list until the stream has finished. It then returns a new 29 | * _ResultsImpl which wraps that list of rows. 30 | */ 31 | static Future<_ResultsImpl> destream(_ResultsImpl results) async { 32 | var rows = new List(); 33 | await results.forEach((row) { 34 | rows.add(row); 35 | }); 36 | var newStream = new Stream.fromIterable(rows); 37 | return new _ResultsImpl._fromStream(results.insertId, results.affectedRows, results.fields, newStream); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/results/row.dart: -------------------------------------------------------------------------------- 1 | part of results; 2 | 3 | /** 4 | * A row of data. Fields can be retrieved by index, or by name. 5 | * 6 | * When retrieving a field by name, only fields which are valid Dart 7 | * identifiers, and which aren't part of the List object, can be used. 8 | */ 9 | @proxy 10 | abstract class Row extends ListBase {} 11 | -------------------------------------------------------------------------------- /lib/src/retained_connection.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | abstract class _RetainedConnectionBase extends Object with _ConnectionHelpers implements QueriableConnection { 4 | _Connection _cnx; 5 | ConnectionPool _pool; 6 | bool _released; 7 | 8 | _RetainedConnectionBase._(this._cnx, this._pool) : _released = false; 9 | 10 | Future query(String sql) { 11 | _checkReleased(); 12 | var handler = new _QueryStreamHandler(sql); 13 | return _cnx.processHandler(handler); 14 | } 15 | 16 | Future prepare(String sql) async { 17 | _checkReleased(); 18 | var query = new Query._forTransaction(new _TransactionPool(_cnx), _cnx, sql); 19 | await query._prepare(true); 20 | return new Future.value(query); 21 | } 22 | 23 | Future prepareExecute(String sql, List parameters) async { 24 | _checkReleased(); 25 | var query = await prepare(sql); 26 | var results = await query.execute(parameters); 27 | //TODO is it right to close here? Query might still be running 28 | query.close(); 29 | return new Future.value(results); 30 | } 31 | 32 | void _checkReleased(); 33 | 34 | _removeConnection(_Connection cnx) { 35 | _pool._removeConnection(cnx); 36 | } 37 | 38 | bool get usingSSL => _cnx.usingSSL; 39 | } 40 | 41 | /** 42 | * Use [ConnectionPool.getConnection] to get a connection to the database which 43 | * isn't released after each query. When you have finished with the connection 44 | * you must [release] it, otherwise it will never be available in the pool 45 | * again. 46 | */ 47 | abstract class RetainedConnection extends QueriableConnection { 48 | /** 49 | * Releases the connection back to the connection pool. 50 | */ 51 | Future release(); 52 | } 53 | 54 | class _RetainedConnectionImpl extends _RetainedConnectionBase implements RetainedConnection { 55 | _RetainedConnectionImpl._(cnx, pool) : super._(cnx, pool); 56 | 57 | Future release() { 58 | _checkReleased(); 59 | _released = true; 60 | 61 | _cnx.inTransaction = false; 62 | _cnx.release(); 63 | _pool._reuseConnectionForQueuedOperations(_cnx); 64 | } 65 | 66 | void _checkReleased() { 67 | if (_released) { 68 | throw new StateError("Connection has already been released"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/transaction.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | /** 4 | * Start a transaction by using [ConnectionPool.startTransaction]. Once a transaction 5 | * is started it retains its connection until the transaction is committed or rolled 6 | * back. You must use the [commit] and [rollback] methods to do this, otherwise 7 | * the connection will not be released back to the pool. 8 | */ 9 | abstract class Transaction extends QueriableConnection { 10 | /** 11 | * Commits the transaction and released the connection. An error will be thrown 12 | * if any queries are executed after calling commit. 13 | */ 14 | Future commit(); 15 | 16 | /** 17 | * Rolls back the transaction and released the connection. An error will be thrown 18 | * if any queries are executed after calling rollback. 19 | */ 20 | Future rollback(); 21 | } 22 | 23 | class _TransactionImpl extends _RetainedConnectionBase implements Transaction { 24 | _TransactionImpl._(cnx, pool) : super._(cnx, pool); 25 | 26 | Future commit() async { 27 | _checkReleased(); 28 | _released = true; 29 | 30 | var handler = new _QueryStreamHandler("commit"); 31 | var results = await _cnx.processHandler(handler); 32 | _cnx.inTransaction = false; 33 | _cnx.release(); 34 | _pool._reuseConnectionForQueuedOperations(_cnx); 35 | return results; 36 | } 37 | 38 | Future rollback() async { 39 | _checkReleased(); 40 | _released = true; 41 | 42 | var handler = new _QueryStreamHandler("rollback"); 43 | var results = await _cnx.processHandler(handler); 44 | _cnx.inTransaction = false; 45 | _cnx.release(); 46 | _pool._reuseConnectionForQueuedOperations(_cnx); 47 | return results; 48 | } 49 | 50 | void _checkReleased() { 51 | if (_released) { 52 | throw new StateError("Transaction has already finished"); 53 | } 54 | } 55 | } 56 | 57 | class _TransactionPool extends ConnectionPool { 58 | final _Connection cnx; 59 | 60 | _TransactionPool(this.cnx); 61 | 62 | Future<_Connection> _getConnection() => new Future.value(cnx); 63 | 64 | _removeConnection(_Connection cnx) {} 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | part of utils; 2 | 3 | /** 4 | * Drops a set of tables. 5 | */ 6 | class TableDropper { 7 | ConnectionPool pool; 8 | List tables; 9 | 10 | /** 11 | * Create a [TableDropper]. Needs a [pool] and 12 | * a list of [tables]. 13 | */ 14 | TableDropper(this.pool, this.tables); 15 | 16 | /** 17 | * Drops the tables this [TableDropper] was created with. The 18 | * returned [Future] completes when all the tables have been dropped. 19 | * If a table doesn't exist, it is ignored. 20 | */ 21 | Future dropTables() async { 22 | await Future.forEach(tables, (table) async { 23 | try { 24 | await pool.query('drop table $table'); 25 | } catch (e) { 26 | if (e is MySqlException && (e as MySqlException).errorNumber == ERROR_UNKNOWN_TABLE) { 27 | // if it's an unknown table, ignore the error and continue 28 | } else { 29 | throw e; 30 | } 31 | } 32 | }); 33 | } 34 | } 35 | 36 | /** 37 | * Runs a list of arbitrary queries. Currently only handles update 38 | * queries as the results are ignored. 39 | */ 40 | class QueryRunner { 41 | final ConnectionPool pool; 42 | final List queries; 43 | 44 | /** 45 | * Create a [QueryRunner]. Needs a [pool] and 46 | * a list of [queries]. 47 | */ 48 | QueryRunner(this.pool, this.queries); 49 | 50 | /** 51 | * Executes the queries this [QueryRunner] was created with. The 52 | * returned [Future] completes when all the queries have been executed. 53 | * Results are ignored. 54 | */ 55 | Future executeQueries() async { 56 | await Future.forEach(queries, (query) async { 57 | await pool.query(query); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | library utils; 2 | 3 | import 'dart:async'; 4 | import 'sqljocky.dart'; 5 | import 'constants.dart'; 6 | 7 | part 'src/utils.dart'; 8 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sqljocky 2 | version: 0.14.1 3 | author: James Ots 4 | description: A MySQL connector 5 | homepage: https://github.com/jamesots/sqljocky 6 | environment: 7 | sdk: '>=1.11.0 <2.0.0' 8 | documentation: http://jamesots.github.io/sqljocky/docs 9 | dependencies: 10 | crypto: ">=0.9.2 <3.0.0" 11 | logging: '>=0.11.1 <0.12.0' 12 | options_file: '>=0.11.0 <0.12.0' 13 | dev_dependencies: 14 | test: '>=0.12.3+9 <0.13.0' 15 | mockito: '>=0.10.1 <0.11.0' 16 | args: any 17 | -------------------------------------------------------------------------------- /test/integration/base.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | Future setup(ConnectionPool pool, String tableName, String createSql, [String insertSql]) async { 4 | await new TableDropper(pool, [tableName]).dropTables(); 5 | var result = await pool.query(createSql); 6 | expect(result, isNotNull); 7 | if (insertSql != null) { 8 | return pool.query(insertSql); 9 | } else { 10 | return new Future.value(null); 11 | } 12 | } 13 | 14 | // thinking of putting other stuff in here too. 15 | void close(ConnectionPool pool) { 16 | pool.closeConnectionsWhenNotInUse(); 17 | } 18 | -------------------------------------------------------------------------------- /test/integration/blob.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runBlobTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('charset tests:', () { 6 | test('setup', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 8 | return setup(pool, "blobtest", "create table blobtest (stuff blob)"); 9 | }); 10 | 11 | test('write blob', () async { 12 | var query = await pool.prepare("insert into blobtest (stuff) values (?)"); 13 | await query.execute([ 14 | [0xc3, 0x28] 15 | ]); // this is an invalid UTF8 string 16 | }); 17 | 18 | test('read data', () async { 19 | var c = new Completer(); 20 | var results = await pool.query('select * from blobtest'); 21 | results.listen((row) { 22 | expect((row[0] as Blob).toBytes(), equals([0xc3, 0x28])); 23 | }, onDone: () { 24 | c.complete(); 25 | }); 26 | return c.future; 27 | }); 28 | 29 | test('close connection', () { 30 | pool.closeConnectionsWhenNotInUse(); 31 | }); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /test/integration/charset.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runCharsetTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('charset tests:', () { 6 | test('setup', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 8 | return setup(pool, "cset", "create table cset (stuff text character set utf8)", 9 | "insert into cset (stuff) values ('здрасти')"); 10 | }); 11 | 12 | test('read data', () async { 13 | var c = new Completer(); 14 | var results = await pool.query('select * from cset'); 15 | results.listen((row) { 16 | expect(row[0].toString(), equals("здрасти")); 17 | }, onDone: () { 18 | c.complete(); 19 | }); 20 | return c.future; 21 | }); 22 | 23 | test('close connection', () { 24 | pool.closeConnectionsWhenNotInUse(); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/integration/errors.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runErrorTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('error tests:', () { 6 | test('setup', () async { 7 | pool = new ConnectionPool( 8 | user: user, 9 | password: password, 10 | db: db, 11 | port: port, 12 | host: host, 13 | max: 1, 14 | // useCompression: false, 15 | useSSL: true); 16 | var cnx = await pool.getConnection(); 17 | print("Connection secure: ${cnx.usingSSL}"); 18 | cnx.release(); 19 | return setup(pool, "stream", "create table stream (id integer, name text)"); 20 | }); 21 | 22 | test('store data', () async { 23 | var query = await pool.prepare('insert into stream (id, name) values (?, ?)'); 24 | await query.execute([0, 'Bob']); 25 | }); 26 | 27 | test('select from stream using query and listen', () { 28 | var futures = []; 29 | for (var i = 0; i < 1; i++) { 30 | var c = new Completer(); 31 | pool.query('squiggle').then((Results results) { 32 | results.listen((row) {}, onDone: () { 33 | c.complete(); 34 | }); 35 | }).catchError((error) { 36 | c.complete(); 37 | }); 38 | futures.add(c.future); 39 | } 40 | return Future.wait(futures); 41 | }); 42 | test('close connection', () { 43 | pool.closeConnectionsWhenNotInUse(); 44 | }); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /test/integration/execute_multi.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runExecuteMultiTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('executeMulti tests:', () { 6 | test('setup', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 2); 8 | return setup(pool, "stream", "create table stream (id integer, name text)", 9 | "insert into stream (id, name) values (1, 'A'), (2, 'B'), (3, 'C')"); 10 | }); 11 | 12 | test('store data', () async { 13 | var query = await pool.prepare('select * from stream where id = ?'); 14 | var values = await query.executeMulti([ 15 | [1], 16 | [2], 17 | [3] 18 | ]); 19 | expect(values, hasLength(3)); 20 | 21 | var resultList = await values[0].toList(); 22 | expect(resultList[0][0], equals(1)); 23 | expect(resultList[0][1].toString(), equals('A')); 24 | 25 | resultList = await values[1].toList(); 26 | expect(resultList[0][0], equals(2)); 27 | expect(resultList[0][1].toString(), equals('B')); 28 | 29 | resultList = await values[2].toList(); 30 | expect(resultList[0][0], equals(3)); 31 | expect(resultList[0][1].toString(), equals('C')); 32 | }); 33 | 34 | test('issue 43', () async { 35 | var tran = await pool.startTransaction(); 36 | var query = await tran.prepare("SELECT * FROM stream"); 37 | var result = await query.execute(); 38 | 39 | await result.first; 40 | 41 | await query.close(); 42 | await tran.rollback(); 43 | }); 44 | 45 | test('close connection', () { 46 | pool.closeConnectionsWhenNotInUse(); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /test/integration/largeblob.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runLargeBlobTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | var text; 6 | group('large blob tests:', () { 7 | test('setup', () { 8 | pool = new ConnectionPool( 9 | user: user, password: password, db: db, port: port, host: host, max: 1, maxPacketSize: 32 * 1024 * 1024); 10 | text = new String.fromCharCodes(new List.filled(16 * 1024 * 1024, 65)); 11 | var sql = "insert into large (stuff) values ('$text')"; 12 | return setup(pool, "large", "create table large (stuff longtext)", sql); 13 | }); 14 | 15 | test('read data', () { 16 | var c = new Completer(); 17 | pool.query('select * from large').then(expectAsync1((Results results) { 18 | results.listen((row) { 19 | var t = row[0].toString(); 20 | expect(t.length, equals(text.length)); 21 | expect(t, equals(text)); 22 | // shouldn't get exception here 23 | }, onDone: () { 24 | c.complete(); 25 | }); 26 | })); 27 | return c.future; 28 | }); 29 | 30 | test('close connection', () { 31 | pool.closeConnectionsWhenNotInUse(); 32 | }); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /test/integration/nullmap.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runNullMapTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('nullmap tests:', () { 6 | test('setup', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 8 | return setup(pool, "nullmap", "create table nullmap (a text, b text, c text, d text)"); 9 | }); 10 | 11 | test('store data', () async { 12 | var query = await pool.prepare('insert into nullmap (a, b, c, d) values (?, ?, ?, ?)'); 13 | await query.execute([null, 'b', 'c', 'd']); 14 | }); 15 | 16 | test('read data', () { 17 | var c = new Completer(); 18 | pool.query('select * from nullmap').then((Results results) { 19 | results.listen((row) { 20 | expect(row[0], equals(null)); 21 | expect(row[1].toString(), equals('b')); 22 | expect(row[2].toString(), equals('c')); 23 | expect(row[3].toString(), equals('d')); 24 | }, onDone: () { 25 | c.complete(); 26 | }); 27 | }); 28 | return c.future; 29 | }); 30 | 31 | test('close connection', () { 32 | pool.closeConnectionsWhenNotInUse(); 33 | }); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/integration/numbers.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | Future deleteInsertSelect(ConnectionPool pool, table, insert, select) async { 4 | await pool.query('delete from $table'); 5 | await pool.query(insert); 6 | return pool.query(select); 7 | } 8 | 9 | void runNumberTests(String user, String password, String db, int port, String host) { 10 | ConnectionPool pool; 11 | group('number tests:', () { 12 | test('setup', () { 13 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 14 | return setup( 15 | pool, 16 | "nums", 17 | "create table nums (" 18 | "atinyint tinyint, asmallint smallint, amediumint mediumint, aint int, abigint bigint, " 19 | "utinyint tinyint unsigned, usmallint smallint unsigned, umediumint mediumint unsigned, uint int unsigned, ubigint bigint unsigned, " 20 | "adecimal decimal(20,10), afloat float, adouble double, areal real)", 21 | "insert into nums (atinyint, asmallint, amediumint, aint, abigint) values (" 22 | "-128, -32768, -8388608, -2147483648, -9223372036854775808)"); 23 | }); 24 | 25 | test('minimum values', () async { 26 | var c = new Completer(); 27 | var results = await pool.query('select atinyint, asmallint, amediumint, aint, abigint from nums'); 28 | results.listen((row) { 29 | expect(row[0], equals(-128)); 30 | expect(row[1], equals(-32768)); 31 | expect(row[2], equals(-8388608)); 32 | expect(row[3], equals(-2147483648)); 33 | expect(row[4], equals(-9223372036854775808)); 34 | }, onDone: () { 35 | c.complete(); 36 | }); 37 | return c.future; 38 | }); 39 | 40 | test('maximum values', () { 41 | var c = new Completer(); 42 | deleteInsertSelect( 43 | pool, 44 | 'nums', 45 | "insert into nums (atinyint, asmallint, amediumint, aint, abigint, " 46 | "adecimal, afloat, adouble, areal) values (" 47 | "127, 32767, 8388607, 2147483647, 9223372036854775807, " 48 | "0, 0, 0, 0)", 49 | 'select atinyint, asmallint, amediumint, aint, abigint from nums').then((results) { 50 | results.listen((row) { 51 | expect(row[0], equals(127)); 52 | expect(row[1], equals(32767)); 53 | expect(row[2], equals(8388607)); 54 | expect(row[3], equals(2147483647)); 55 | expect(row[4], equals(9223372036854775807)); 56 | }, onDone: () { 57 | c.complete(); 58 | }); 59 | }); 60 | return c.future; 61 | }); 62 | 63 | test('maximum unsigned values', () { 64 | var c = new Completer(); 65 | deleteInsertSelect( 66 | pool, 67 | 'nums', 68 | "insert into nums (utinyint, usmallint, umediumint, uint, ubigint) values (" 69 | "255, 65535, 12777215, 4294967295, 18446744073709551615)", 70 | 'select utinyint, usmallint, umediumint, uint, ubigint from nums').then((results) { 71 | results.listen((row) { 72 | expect(row[0], equals(255)); 73 | expect(row[1], equals(65535)); 74 | expect(row[2], equals(12777215)); 75 | expect(row[3], equals(4294967295)); 76 | expect(row[4], equals(18446744073709551615)); 77 | }, onDone: () { 78 | c.complete(); 79 | }); 80 | }); 81 | return c.future; 82 | }); 83 | 84 | test('max decimal', () { 85 | var c = new Completer(); 86 | deleteInsertSelect( 87 | pool, 88 | 'nums', 89 | "insert into nums (adecimal) values (" 90 | "99999999999999999999.9999999999)", 91 | 'select adecimal from nums').then((results) { 92 | results.listen((row) { 93 | expect(row[0], equals(9999999999.9999999999)); 94 | }, onDone: () { 95 | c.complete(); 96 | }); 97 | }); 98 | return c.future; 99 | }); 100 | 101 | test('min decimal', () { 102 | var c = new Completer(); 103 | deleteInsertSelect( 104 | pool, 105 | 'nums', 106 | "insert into nums (adecimal) values (" 107 | "-99999999999999999999.9999999999)", 108 | 'select adecimal from nums').then((results) { 109 | results.listen((row) { 110 | expect(row[0], equals(-9999999999.9999999999)); 111 | }, onDone: () { 112 | c.complete(); 113 | }); 114 | }); 115 | return c.future; 116 | }); 117 | 118 | test('close connection', () { 119 | pool.closeConnectionsWhenNotInUse(); 120 | }); 121 | }); 122 | } 123 | -------------------------------------------------------------------------------- /test/integration/one.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runIntTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('some tests:', () { 6 | test('create pool', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host); 8 | expect(pool, isNotNull); 9 | }); 10 | 11 | test('dropTables', () async { 12 | await new TableDropper(pool, ["test1"]).dropTables(); 13 | }); 14 | 15 | test('create tables', () async { 16 | var results = await pool.query("create table test1 (" 17 | "atinyint tinyint, asmallint smallint, amediumint mediumint, abigint bigint, aint int, " 18 | "adecimal decimal(20,10), afloat float, adouble double, areal real, " 19 | "aboolean boolean, abit bit(20), aserial serial, " 20 | "adate date, adatetime datetime, atimestamp timestamp, atime time, ayear year, " 21 | "achar char(10), avarchar varchar(10), " 22 | "atinytext tinytext, atext text, amediumtext mediumtext, alongtext longtext, " 23 | "abinary binary(10), avarbinary varbinary(10), " 24 | "atinyblob tinyblob, amediumblob mediumblob, ablob blob, alongblob longblob, " 25 | "aenum enum('a', 'b', 'c'), aset set('a', 'b', 'c'), ageometry geometry)"); 26 | expect(results.affectedRows, equals(0)); 27 | expect(results.insertId, equals(0)); 28 | var list = await results.toList(); 29 | expect(list, hasLength(0)); 30 | }); 31 | 32 | test('show tables', () async { 33 | var c = new Completer(); 34 | var results = await pool.query("show tables"); 35 | print("tables"); 36 | results.listen((row) { 37 | print("table: $row"); 38 | }, onDone: () { 39 | c.complete(); 40 | }); 41 | return c.future; 42 | }); 43 | 44 | test('describe stuff', () async { 45 | var results = await pool.query("describe test1"); 46 | print("table test1"); 47 | await showResults(results); 48 | }); 49 | 50 | test('small blobs', () async { 51 | var query = await pool.prepare("insert into test1 (atext) values (?)"); 52 | var longstring = ""; 53 | for (var i = 0; i < 200; i++) { 54 | longstring += "x"; 55 | } 56 | var results = await query.execute([new Blob.fromString(longstring)]); 57 | expect(results.affectedRows, equals(1)); 58 | 59 | results = await pool.query("select atext from test1"); 60 | var list = await results.toList(); 61 | expect(list.length, equals(1)); 62 | expect((list[0][0] as Blob).toString().length, equals(200)); 63 | }); 64 | 65 | test('medium blobs', () async { 66 | var query = await pool.prepare("insert into test1 (atext) values (?)"); 67 | var longstring = ""; 68 | for (var i = 0; i < 2000; i++) { 69 | longstring += "x"; 70 | } 71 | var results = await query.execute([new Blob.fromString(longstring)]); 72 | expect(results.affectedRows, equals(1)); 73 | 74 | results = await pool.query("select atext from test1"); 75 | var list = await results.toList(); 76 | expect(list.length, equals(2)); 77 | expect((list[1][0] as Blob).toString().length, equals(2000)); 78 | }); 79 | 80 | test('clear stuff', () { 81 | return pool.query('delete from test1'); 82 | }); 83 | 84 | test('insert stuff', () async { 85 | print("insert stuff test"); 86 | var query = await pool.prepare("insert into test1 (atinyint, asmallint, amediumint, abigint, aint, " 87 | "adecimal, afloat, adouble, areal, " 88 | "aboolean, abit, aserial, " 89 | "adate, adatetime, atimestamp, atime, ayear, " 90 | "achar, avarchar, atinytext, atext, amediumtext, alongtext, " 91 | "abinary, avarbinary, atinyblob, amediumblob, ablob, alongblob, " 92 | "aenum, aset) values" 93 | "(?, ?, ?, ?, ?, " 94 | "?, ?, ?, ?, " 95 | "?, ?, ?, " 96 | "?, ?, ?, ?, ?, " 97 | "?, ?, ?, ?, ?, ?, " 98 | "?, ?, ?, ?, ?, ?, " 99 | "?, ?)"); 100 | var values = []; 101 | values.add(126); 102 | values.add(164); 103 | values.add(165); 104 | values.add(166); 105 | values.add(167); 106 | 107 | values.add(592); 108 | values.add(123.456); 109 | values.add(123.456); 110 | values.add(123.456); 111 | 112 | values.add(true); 113 | values.add(0x010203); //[1, 2, 3]); 114 | values.add(123); 115 | 116 | values.add(new DateTime.now()); 117 | values.add(new DateTime.now()); 118 | values.add(new DateTime.now()); 119 | values.add(new DateTime.now()); 120 | values.add(2012); 121 | 122 | values.add("Hello"); 123 | values.add("Hey"); 124 | values.add("Hello there"); 125 | values.add("Good morning"); 126 | values.add("Habari boss"); 127 | values.add("Bonjour"); 128 | 129 | values.add([65, 66, 67, 68]); 130 | values.add([65, 66, 67, 68]); 131 | values.add([65, 66, 67, 68]); 132 | values.add([65, 66, 67, 68]); 133 | values.add([65, 66, 67, 68]); 134 | values.add([65, 66, 67, 68]); 135 | 136 | values.add("a"); 137 | values.add("a,b"); 138 | 139 | print("executing"); 140 | expect(1, equals(1)); // put some real expectations here 141 | var results = await query.execute(values); 142 | print("updated ${results.affectedRows} ${results.insertId}"); 143 | expect(results.affectedRows, equals(1)); 144 | }); 145 | 146 | test('select everything', () async { 147 | var results = await pool.query('select * from test1'); 148 | var list = await results.toList(); 149 | expect(list.length, equals(1)); 150 | var row = list.first; 151 | expect(row[10], equals(0x010203)); 152 | }); 153 | 154 | test('update', () async { 155 | Query preparedQuery; 156 | var query = await pool.prepare("update test1 set atinyint = ?, adecimal = ?"); 157 | preparedQuery = query; 158 | expect(1, equals(1)); // put some real expectations here 159 | await query.execute([127, "123456789.987654321"]); 160 | preparedQuery.close(); 161 | }); 162 | 163 | test('select stuff', () async { 164 | var results = await pool.query("select atinyint, adecimal from test1"); 165 | var list = await results.toList(); 166 | var row = list[0]; 167 | expect(row[0], equals(127)); 168 | expect(row[1], equals(123456789.987654321)); 169 | }); 170 | 171 | test('prepare execute', () async { 172 | var results = await pool.prepareExecute('insert into test1 (atinyint, adecimal) values (?, ?)', [123, 123.321]); 173 | expect(results.affectedRows, equals(1)); 174 | }); 175 | 176 | List preparedFields; 177 | List values; 178 | 179 | test('data types (prepared)', () async { 180 | var results = await pool.prepareExecute('select * from test1', []); 181 | print("----------- prepared results ---------------"); 182 | preparedFields = results.fields; 183 | var list = await results.toList(); 184 | values = list[0]; 185 | for (var i = 0; i < results.fields.length; i++) { 186 | var field = results.fields[i]; 187 | print("${field.name} ${fieldTypeToString(field.type)} ${typeof(values[i])}"); 188 | } 189 | }); 190 | 191 | test('data types (query)', () async { 192 | var results = await pool.query('select * from test1'); 193 | print("----------- query results ---------------"); 194 | var list = await results.toList(); 195 | var row = list[0]; 196 | for (var i = 0; i < results.fields.length; i++) { 197 | var field = results.fields[i]; 198 | 199 | // make sure field types returned by both queries are the same 200 | expect(field.type, equals(preparedFields[i].type)); 201 | // make sure results types are the same 202 | expect(typeof(row[i]), equals(typeof(values[i]))); 203 | // make sure the values are the same 204 | if (row[i] is double) { 205 | // or at least close 206 | expect(row[i], closeTo(values[i], 0.1)); 207 | } else { 208 | expect(row[i], equals(values[i])); 209 | } 210 | print("${field.name} ${fieldTypeToString(field.type)} ${typeof(row[i])}"); 211 | } 212 | }); 213 | 214 | test('multi queries', () async { 215 | var trans = await pool.startTransaction(); 216 | var start = new DateTime.now(); 217 | var query = await trans.prepare('insert into test1 (aint) values (?)'); 218 | var params = []; 219 | for (var i = 0; i < 50; i++) { 220 | params.add([i]); 221 | } 222 | var resultList = await query.executeMulti(params); 223 | var end = new DateTime.now(); 224 | print(end.difference(start)); 225 | expect(resultList.length, equals(50)); 226 | await trans.commit(); 227 | }); 228 | 229 | test('blobs in prepared queries', () async { 230 | var abc = new Blob.fromBytes([65, 66, 67, 0, 68, 69, 70]); 231 | var results = await pool.prepareExecute("insert into test1 (aint, atext) values (?, ?)", [12344, abc]); 232 | expect(1, equals(1)); // put some real expectations here 233 | results = await pool.prepareExecute("select atext from test1 where aint = 12344", []); 234 | var list = await results.toList(); 235 | expect(list.length, equals(1)); 236 | values = list[0]; 237 | expect(values[0].toString(), equals("ABC\u0000DEF")); 238 | }); 239 | 240 | test('blobs with nulls', () async { 241 | var results = await pool.query("insert into test1 (aint, atext) values (12345, \"ABC\u0000DEF\")"); 242 | expect(1, equals(1)); // put some real expectations here 243 | results = await pool.query("select atext from test1 where aint = 12345"); 244 | results = await results.toList(); 245 | expect(results.length, equals(1)); 246 | values = results[0]; 247 | expect(values[0].toString(), equals("ABC\u0000DEF")); 248 | 249 | results = await pool.query("delete from test1 where aint = 12345"); 250 | var abc = new String.fromCharCodes([65, 66, 67, 0, 68, 69, 70]); 251 | expect(1, equals(1)); // put some real expectations here 252 | results = await pool.prepareExecute("insert into test1 (aint, atext) values (?, ?)", [12345, abc]); 253 | expect(1, equals(1)); // put some real expectations here 254 | results = await pool.prepareExecute("select atext from test1 where aint = 12345", []); 255 | results = await results.toList(); 256 | expect(results.length, equals(1)); 257 | values = results[0]; 258 | expect(values[0].toString(), equals("ABC\u0000DEF")); 259 | }); 260 | 261 | test('close connection', () { 262 | pool.closeConnectionsWhenNotInUse(); 263 | }); 264 | }); 265 | } 266 | 267 | Future showResults(Results results) { 268 | var c = new Completer(); 269 | var fieldNames = []; 270 | for (var field in results.fields) { 271 | fieldNames.add("${field.name}:${field.type}"); 272 | } 273 | print(fieldNames); 274 | results.listen((row) { 275 | print(row); 276 | }, onDone: () { 277 | c.complete(null); 278 | }); 279 | 280 | return c.future; 281 | } 282 | 283 | String typeof(dynamic item) { 284 | if (item is String) { 285 | return "String"; 286 | } else if (item is int) { 287 | return "int"; 288 | } else if (item is double) { 289 | return "double"; 290 | } else if (item is DateTime) { 291 | return "Date"; 292 | } else if (item is Uint8List) { 293 | return "Uint8List"; 294 | } else if (item is List) { 295 | return "List"; 296 | } else if (item is List) { 297 | return "List"; 298 | } else if (item is Duration) { 299 | return "Duration"; 300 | } else { 301 | return "Unknown"; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /test/integration/prepared_query.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runPreparedQueryTests(String user, String password, String db, int port, String host) { 4 | group('prepared query', () { 5 | ConnectionPool pool; 6 | 7 | setUp(() { 8 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 2); 9 | return setup( 10 | pool, "row", "create table row (id integer, name text)", "insert into row values (0, 'One'), (1, 'One')"); 11 | }); 12 | 13 | tearDown(() { 14 | pool.closeConnectionsNow(); 15 | }); 16 | 17 | test('can close prepared query on in-use connections', () async { 18 | var cnx = await pool.getConnection(); 19 | var query = await pool.prepare("select * from row"); 20 | await query.close(); 21 | cnx.release(); 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /test/integration/row.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runRowTests(String user, String password, String db, int port, String host) { 4 | group('row tests:', () { 5 | ConnectionPool pool; 6 | 7 | setUp(() { 8 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 9 | return setup(pool, "row", "create table row (id integer, name text, `the field` text, length integer)"); 10 | }); 11 | 12 | tearDown(() { 13 | pool.closeConnectionsNow(); 14 | }); 15 | 16 | test('store data', () async { 17 | var query = await pool.prepare('insert into row (id, name, `the field`, length) values (?, ?, ?, ?)'); 18 | await query.execute([0, 'Bob', 'Thing', 5000]); 19 | }); 20 | 21 | test('first field is empty', () async { 22 | final query = await pool.prepare('insert into row (id, name, `the field`, length) values (?, ?, ?, ?)'); 23 | await (await query.execute([1, '', 'Thing', 5000])).toList(); 24 | Iterable results = await (await pool.query("select name, length from row")).toList(); 25 | expect(results.map((r) => [r[0].toString(), r[1]]).toList().first, equals(['', 5000])); 26 | }); 27 | 28 | test('select from stream using query and listen', () async { 29 | var futures = []; 30 | for (var i = 0; i < 5; i++) { 31 | var c = new Completer(); 32 | var results = await pool.query('select * from row'); 33 | results.listen((row) { 34 | expect(row.id, equals(0)); 35 | expect(row.name.toString(), equals("Bob")); 36 | // length is a getter on List, so it isn't mapped to the result field 37 | expect(row.length, equals(4)); 38 | }, onDone: () { 39 | c.complete(); 40 | }); 41 | futures.add(c.future); 42 | } 43 | return Future.wait(futures); 44 | }); 45 | 46 | test('select from stream using prepareExecute and listen', () async { 47 | var futures = []; 48 | for (var i = 0; i < 5; i++) { 49 | var c = new Completer(); 50 | var results = await pool.prepareExecute('select * from row where id = ?', [0]); 51 | results.listen((row) { 52 | expect(row.id, equals(0)); 53 | expect(row.name.toString(), equals("Bob")); 54 | // length is a getter on List, so it isn't mapped to the result field 55 | expect(row.length, equals(4)); 56 | }, onDone: () { 57 | c.complete(); 58 | }); 59 | futures.add(c.future); 60 | } 61 | return Future.wait(futures); 62 | }); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /test/integration/stored_procedures.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runStoredProcedureTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('error tests:', () { 6 | test('setup', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 8 | // return setup(pool, "stream", "create table stream (id integer, name text)"); 9 | }); 10 | 11 | test('store data', () async { 12 | // pool.query(''' 13 | //CREATE PROCEDURE getall () 14 | //BEGIN 15 | //select * from stream; 16 | //END 17 | //''').then((results) { 18 | // return query.query('call getall()'); 19 | var results = await pool.query('call getall()'); 20 | results.listen((row) {}, onDone: () { 21 | c.complete(); 22 | }); 23 | return c.future; 24 | }); 25 | 26 | test('close connection', () { 27 | pool.closeConnectionsWhenNotInUse(); 28 | }); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/integration/stream.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runStreamTests(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('stream tests:', () { 6 | test('setup', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 8 | return setup(pool, "stream", "create table stream (id integer, name text)"); 9 | }); 10 | 11 | test('store data', () async { 12 | var query = await pool.prepare('insert into stream (id, name) values (?, ?)'); 13 | await query.execute([0, 'Bob']); 14 | }); 15 | 16 | test('select from stream using query and listen', () async { 17 | var futures = []; 18 | for (var i = 0; i < 5; i++) { 19 | var c = new Completer(); 20 | var results = await pool.query('select * from stream'); 21 | results.listen((row) {}, onDone: () { 22 | c.complete(); 23 | }); 24 | futures.add(c.future); 25 | } 26 | return Future.wait(futures); 27 | }); 28 | 29 | test('select nothing from stream using query and listen', () async { 30 | var futures = []; 31 | for (var i = 0; i < 5; i++) { 32 | var c = new Completer(); 33 | var results = await pool.query('select * from stream where id=5'); 34 | results.listen((row) {}, onDone: () { 35 | c.complete(); 36 | }); 37 | futures.add(c.future); 38 | } 39 | return Future.wait(futures); 40 | }); 41 | 42 | test('select from stream using query and first', () async { 43 | for (var i = 0; i < 5; i++) { 44 | var results = await pool.query('select * from stream'); 45 | await results.first; 46 | } 47 | }); 48 | 49 | test('select from stream using query and drain', () async { 50 | for (var i = 0; i < 5; i++) { 51 | var results = await pool.query('select * from stream'); 52 | await results.drain(); 53 | } 54 | }); 55 | 56 | test('select from stream using prepare and listen', () async { 57 | var futures = []; 58 | for (var i = 0; i < 5; i++) { 59 | var c = new Completer(); 60 | var query = await pool.prepare('select * from stream'); 61 | var results = await query.execute(); 62 | results.listen((row) {}, onDone: () { 63 | c.complete(); 64 | }); 65 | futures.add(c.future); 66 | } 67 | return Future.wait(futures); 68 | }); 69 | 70 | test('select nothing from stream using prepare and listen', () async { 71 | var futures = []; 72 | for (var i = 0; i < 5; i++) { 73 | var c = new Completer(); 74 | var query = await pool.prepare('select * from stream where id=5'); 75 | var results = await query.execute(); 76 | results.listen((row) {}, onDone: () { 77 | c.complete(); 78 | }); 79 | futures.add(c.future); 80 | } 81 | return Future.wait(futures); 82 | }); 83 | 84 | test('select from stream using prepare and first', () async { 85 | for (var i = 0; i < 5; i++) { 86 | var query = await pool.prepare('select * from stream'); 87 | var results = await query.execute(); 88 | await results.first; 89 | } 90 | }); 91 | 92 | test('select from stream using prepare and drain', () async { 93 | for (var i = 0; i < 5; i++) { 94 | var query = await pool.prepare('select * from stream'); 95 | var results = await query.execute(); 96 | await results.drain(); 97 | } 98 | }); 99 | 100 | test('close connection', () { 101 | pool.closeConnectionsWhenNotInUse(); 102 | }); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /test/integration/two.dart: -------------------------------------------------------------------------------- 1 | part of integrationtests; 2 | 3 | void runIntTests2(String user, String password, String db, int port, String host) { 4 | ConnectionPool pool; 5 | group('some tests:', () { 6 | test('create pool', () { 7 | pool = new ConnectionPool(user: user, password: password, db: db, port: port, host: host, max: 1); 8 | expect(pool, isNotNull); 9 | }); 10 | 11 | test('four pings', () { 12 | var c1 = new Completer(); 13 | var c2 = new Completer(); 14 | var c3 = new Completer(); 15 | var c4 = new Completer(); 16 | var futures = [c1.future, c2.future, c3.future, c4.future]; 17 | var finished = []; 18 | pool.ping().then((_) { 19 | finished.add(1); 20 | print("ping 1 received"); 21 | c1.complete(); 22 | 23 | pool.ping().then((_) { 24 | finished.add(4); 25 | print("ping 4 received"); 26 | c4.complete(); 27 | }); 28 | print("ping 4 sent"); 29 | }); 30 | print("ping 1 sent"); 31 | 32 | pool.ping().then((_) { 33 | finished.add(2); 34 | print("ping 2 received"); 35 | c2.complete(); 36 | }); 37 | print("ping 2 sent"); 38 | 39 | pool.ping().then((_) { 40 | finished.add(3); 41 | print("ping 3 received"); 42 | c3.complete(); 43 | }); 44 | print("ping 3 sent"); 45 | 46 | expect(finished, equals([])); 47 | 48 | return Future.wait(futures).then((_) { 49 | expect(finished, contains(1)); 50 | expect(finished, contains(2)); 51 | expect(finished, contains(3)); 52 | expect(finished, contains(4)); 53 | expect(finished, hasLength(4)); 54 | }); 55 | }); 56 | 57 | test('close connection', () { 58 | pool.closeConnectionsWhenNotInUse(); 59 | expect(1, equals(1)); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /test/integration_test.dart: -------------------------------------------------------------------------------- 1 | library integrationtests; 2 | 3 | import 'dart:async'; 4 | import 'dart:typed_data'; 5 | import 'package:args/args.dart'; 6 | import 'package:logging/logging.dart'; 7 | import 'package:options_file/options_file.dart'; 8 | import 'package:sqljocky/constants.dart'; 9 | import 'package:sqljocky/sqljocky.dart'; 10 | import 'package:sqljocky/utils.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | part 'integration/base.dart'; 14 | part 'integration/blob.dart'; 15 | part 'integration/charset.dart'; 16 | part 'integration/errors.dart'; 17 | part 'integration/execute_multi.dart'; 18 | part 'integration/largeblob.dart'; 19 | part 'integration/nullmap.dart'; 20 | part 'integration/numbers.dart'; 21 | part 'integration/one.dart'; 22 | part 'integration/prepared_query.dart'; 23 | part 'integration/row.dart'; 24 | part 'integration/stored_procedures.dart'; 25 | part 'integration/stream.dart'; 26 | part 'integration/two.dart'; 27 | 28 | void main(List args) { 29 | hierarchicalLoggingEnabled = true; 30 | Logger.root.level = Level.OFF; 31 | // new Logger("ConnectionPool").level = Level.ALL; 32 | // new Logger("Query").level = Level.ALL; 33 | var listener = (LogRecord r) { 34 | var name = r.loggerName; 35 | if (name.length > 15) { 36 | name = name.substring(0, 15); 37 | } 38 | while (name.length < 15) { 39 | name = "$name "; 40 | } 41 | print("${r.time}: $name: ${r.message}"); 42 | }; 43 | Logger.root.onRecord.listen(listener); 44 | 45 | var parser = new ArgParser(); 46 | parser.addOption('large_packets', allowed: ['true', 'false'], defaultsTo: 'true'); 47 | var results = parser.parse(args); 48 | 49 | var options = new OptionsFile('connection.options'); 50 | var user = options.getString('user'); 51 | var password = options.getString('password', null); 52 | var port = options.getInt('port', 3306); 53 | var db = options.getString('db'); 54 | var host = options.getString('host', 'localhost'); 55 | 56 | runPreparedQueryTests(user, password, db, port, host); 57 | runIntTests(user, password, db, port, host); 58 | runIntTests2(user, password, db, port, host); 59 | runCharsetTests(user, password, db, port, host); 60 | runNullMapTests(user, password, db, port, host); 61 | runNumberTests(user, password, db, port, host); 62 | runStreamTests(user, password, db, port, host); 63 | runRowTests(user, password, db, port, host); 64 | runErrorTests(user, password, db, port, host); 65 | runBlobTests(user, password, db, port, host); 66 | // runStoredProcedureTests(user, password, db, port, host); 67 | runExecuteMultiTests(user, password, db, port, host); 68 | // if (results['large_packets'] == 'true') { 69 | // runLargeBlobTests(user, password, db, port, host); 70 | // } 71 | } 72 | -------------------------------------------------------------------------------- /test/interleave_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | import 'package:logging/logging.dart'; 5 | import 'package:options_file/options_file.dart'; 6 | import 'package:sqljocky/sqljocky.dart'; 7 | import 'package:sqljocky/utils.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | /* 11 | * This example drops a couple of tables if they exist, before recreating them. 12 | * It then stores some data in the database and reads it back out. 13 | * You must have a connection.options file in order for this to connect. 14 | */ 15 | 16 | class Example { 17 | var insertedIds = []; 18 | var rnd = new Random(); 19 | ConnectionPool pool; 20 | 21 | Example(this.pool); 22 | 23 | Future run() async { 24 | // drop the tables if they already exist 25 | await dropTables(); 26 | print("dropped tables"); 27 | // then recreate the tables 28 | await createTables(); 29 | print("created tables"); 30 | // add some data 31 | var futures = new List(); 32 | for (var i = 0; i < 10; i++) { 33 | futures.add(addDataInTransaction()); 34 | futures.add(readData()); 35 | } 36 | print("queued all operations"); 37 | await Future.wait(futures); 38 | print("data added and read"); 39 | } 40 | 41 | Future dropTables() { 42 | print("dropping tables"); 43 | var dropper = new TableDropper(pool, ['pets', 'people']); 44 | return dropper.dropTables(); 45 | } 46 | 47 | Future createTables() { 48 | print("creating tables"); 49 | var querier = new QueryRunner(pool, [ 50 | 'create table people (id integer not null auto_increment, ' 51 | 'name varchar(255), ' 52 | 'age integer, ' 53 | 'primary key (id))', 54 | 'create table pets (id integer not null auto_increment, ' 55 | 'name varchar(255), ' 56 | 'species varchar(255), ' 57 | 'owner_id integer, ' 58 | 'primary key (id),' 59 | 'foreign key (owner_id) references people (id))' 60 | ]); 61 | print("executing queries"); 62 | return querier.executeQueries(); 63 | } 64 | 65 | Future addData() async { 66 | print("adding"); 67 | var query = await pool.prepare("insert into people (name, age) values (?, ?)"); 68 | var parameters = [ 69 | ["Dave", 15], 70 | ["John", 16], 71 | ["Mavis", 93] 72 | ]; 73 | await query.executeMulti(parameters); 74 | query = await pool.prepare("insert into pets (name, species, owner_id) values (?, ?, ?)"); 75 | parameters = [ 76 | ["Rover", "Dog", 1], 77 | ["Daisy", "Cow", 2], 78 | ["Spot", "Dog", 2] 79 | ]; 80 | await query.executeMulti(parameters); 81 | } 82 | 83 | Future addDataInTransaction() async { 84 | print("adding"); 85 | var ids = []; 86 | var trans = await pool.startTransaction(); 87 | var query = await trans.prepare("insert into people (name, age) values (?, ?)"); 88 | var parameters = [ 89 | ["Dave", 15], 90 | ["John", 16], 91 | ["Mavis", 93] 92 | ]; 93 | var results = await query.executeMulti(parameters); 94 | for (var result in results) { 95 | ids.add(result.insertId); 96 | } 97 | print("added people"); 98 | query = await trans.prepare("insert into pets (name, species, owner_id) values (?, ?, ?)"); 99 | var id1, id2, id3; 100 | if (insertedIds.length < 3) { 101 | id1 = ids[0]; 102 | id2 = ids[1]; 103 | id3 = ids[2]; 104 | } else { 105 | id1 = insertedIds[rnd.nextInt(insertedIds.length)]; 106 | id2 = insertedIds[rnd.nextInt(insertedIds.length)]; 107 | id3 = insertedIds[rnd.nextInt(insertedIds.length)]; 108 | } 109 | parameters = [ 110 | ["Rover", "Dog", id1], 111 | ["Daisy", "Cow", id2], 112 | ["Spot", "Dog", id3] 113 | ]; 114 | print("adding pets"); 115 | try { 116 | results = await query.executeMulti(parameters); 117 | print("added pets"); 118 | } catch (e) { 119 | print("Exception: $e"); 120 | } 121 | print("committing"); 122 | await trans.commit(); 123 | print("committed"); 124 | insertedIds.addAll(ids); 125 | } 126 | 127 | Future readData() async { 128 | print("querying"); 129 | var result = await pool.query('select p.id, p.name, p.age, t.name, t.species ' 130 | 'from people p ' 131 | 'left join pets t on t.owner_id = p.id'); 132 | print("got results"); 133 | var list = await result.toList(); 134 | if (list != null) { 135 | for (var row in list) { 136 | if (row[3] == null) { 137 | print("ID: ${row[0]}, Name: ${row[1]}, Age: ${row[2]}, No Pets"); 138 | } else { 139 | print("ID: ${row[0]}, Name: ${row[1]}, Age: ${row[2]}, Pet Name: ${row[3]}, Pet Species ${row[4]}"); 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | void main() { 147 | hierarchicalLoggingEnabled = true; 148 | Logger.root.level = Level.OFF; 149 | // new Logger("ConnectionPool").level = Level.ALL; 150 | // new Logger("Connection.Lifecycle").level = Level.ALL; 151 | // new Logger("Query").level = Level.ALL; 152 | Logger.root.onRecord.listen((LogRecord r) { 153 | print("${r.time}: ${r.loggerName}: ${r.message}"); 154 | }); 155 | 156 | var log = new Logger("Interleave"); 157 | log.level = Level.ALL; 158 | 159 | group('interleave', () { 160 | test('should complete interleaved operations', () async { 161 | var options = new OptionsFile('connection.options'); 162 | var user = options.getString('user'); 163 | var password = options.getString('password'); 164 | var port = options.getInt('port', 3306); 165 | var db = options.getString('db'); 166 | var host = options.getString('host', 'localhost'); 167 | 168 | // create a connection 169 | log.fine("opening connection"); 170 | var pool = new ConnectionPool(host: host, port: port, user: user, password: password, db: db, max: 5); 171 | log.fine("connection open"); 172 | // create an example class 173 | var example = new Example(pool); 174 | // run the example 175 | log.fine("running example"); 176 | await example.run(); 177 | // finally, close the connection 178 | log.fine("closing"); 179 | pool.closeConnectionsWhenNotInUse(); 180 | // not much of a test, is it? 181 | expect(true, isTrue); 182 | }); 183 | }); 184 | } 185 | -------------------------------------------------------------------------------- /test/unit/auth_handler_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runAuthHandlerTests() { 4 | group('auth_handler:', () { 5 | test('hash password correctly', () { 6 | var handler = new _AuthHandler('username', 'password', 'db', [1, 2, 3, 4], 0, 100, 0); 7 | 8 | var hash = handler._getHash(); 9 | 10 | expect( 11 | hash, equals([211, 136, 65, 109, 153, 241, 227, 117, 168, 83, 80, 136, 188, 116, 50, 54, 235, 225, 54, 225])); 12 | }); 13 | 14 | test('hash password correctly', () { 15 | var clientFlags = 12345; 16 | var maxPacketSize = 9898; 17 | var characterSet = 56; 18 | var username = 'Boris'; 19 | var password = 'Password'; 20 | var handler = new _AuthHandler(username, password, null, [1, 2, 3, 4], clientFlags, maxPacketSize, characterSet); 21 | 22 | var hash = handler._getHash(); 23 | var buffer = handler.createRequest(); 24 | 25 | buffer.seek(0); 26 | expect(buffer.readUint32(), equals(clientFlags)); 27 | expect(buffer.readUint32(), equals(maxPacketSize)); 28 | expect(buffer.readByte(), equals(characterSet)); 29 | buffer.skip(23); 30 | expect(buffer.readNullTerminatedString(), equals(username)); 31 | expect(buffer.readByte(), equals(hash.length)); 32 | expect(buffer.readList(hash.length), equals(hash)); 33 | expect(buffer.hasMore, isFalse); 34 | }); 35 | 36 | test('check another set of values', () { 37 | var clientFlags = 2435623 & ~CLIENT_CONNECT_WITH_DB; 38 | var maxPacketSize = 34536; 39 | var characterSet = 255; 40 | var username = 'iamtheuserwantingtologin'; 41 | var password = 'wibblededee'; 42 | var database = 'thisisthenameofthedatabase'; 43 | var handler = new _AuthHandler(username, password, database, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], clientFlags, 44 | maxPacketSize, characterSet); 45 | 46 | var hash = handler._getHash(); 47 | var buffer = handler.createRequest(); 48 | 49 | buffer.seek(0); 50 | expect(buffer.readUint32(), equals(clientFlags | CLIENT_CONNECT_WITH_DB)); 51 | expect(buffer.readUint32(), equals(maxPacketSize)); 52 | expect(buffer.readByte(), equals(characterSet)); 53 | buffer.skip(23); 54 | expect(buffer.readNullTerminatedString(), equals(username)); 55 | expect(buffer.readByte(), equals(hash.length)); 56 | expect(buffer.readList(hash.length), equals(hash)); 57 | expect(buffer.readNullTerminatedString(), equals(database)); 58 | expect(buffer.hasMore, isFalse); 59 | }); 60 | }); 61 | 62 | test('check utf8', () { 63 | var username = 'Борис'; 64 | var password = 'здрасти'; 65 | var database = 'дтабасе'; 66 | var handler = new _AuthHandler(username, password, database, [1, 2, 3, 4], 0, 100, 0); 67 | 68 | var hash = handler._getHash(); 69 | var buffer = handler.createRequest(); 70 | 71 | buffer.seek(0); 72 | buffer.readUint32(); 73 | buffer.readUint32(); 74 | buffer.readByte(); 75 | buffer.skip(23); 76 | expect(buffer.readNullTerminatedString(), equals(username)); 77 | expect(buffer.readByte(), equals(hash.length)); 78 | expect(buffer.readList(hash.length), equals(hash)); 79 | expect(buffer.readNullTerminatedString(), equals(database)); 80 | expect(buffer.hasMore, isFalse); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/unit/binary_data_packet_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runBinaryDataPacketTests() { 4 | group('buffer:', () { 5 | test('can read short blob', () { 6 | var packet = new _BinaryDataPacket._forTests(null, null); 7 | var field = new _FieldImpl._forTests(FIELD_TYPE_BLOB); 8 | var buffer = new Buffer.fromList([1, 32]); 9 | var value = packet._readField(field, buffer); 10 | 11 | expect(value, new isInstanceOf()); 12 | expect((value as Blob).toString(), equals(" ")); 13 | }); 14 | 15 | test('can read long blob', () { 16 | var packet = new _BinaryDataPacket._forTests(null, null); 17 | var field = new _FieldImpl._forTests(FIELD_TYPE_BLOB); 18 | 19 | var buffer = new Buffer(500 + 3); 20 | buffer.writeLengthCodedBinary(500); 21 | for (int i = 0; i < 500; i++) { 22 | buffer.writeByte(32); 23 | } 24 | var value = packet._readField(field, buffer); 25 | 26 | expect(value, new isInstanceOf()); 27 | expect((value as Blob).toString(), hasLength(500)); 28 | }); 29 | 30 | test('can read very long blob', () { 31 | var packet = new _BinaryDataPacket._forTests(null, null); 32 | var field = new _FieldImpl._forTests(FIELD_TYPE_BLOB); 33 | 34 | var buffer = new Buffer(50000 + 3); 35 | buffer.writeLengthCodedBinary(50000); 36 | for (int i = 0; i < 50000; i++) { 37 | buffer.writeByte(32); 38 | } 39 | var value = packet._readField(field, buffer); 40 | 41 | expect(value, new isInstanceOf()); 42 | expect((value as Blob).toString(), hasLength(50000)); 43 | }); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /test/unit/buffer_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runBufferTests() { 4 | group('buffer:', () { 5 | test('can write byte to buffer', () { 6 | var buffer = new Buffer(1); 7 | buffer.writeByte(15); 8 | expect(buffer.list[0], equals(15)); 9 | }); 10 | 11 | test('can write int16 to buffer', () { 12 | var buffer = new Buffer(2); 13 | buffer.writeInt16(12345); 14 | expect(buffer.list[0], equals(0x39)); 15 | expect(buffer.list[1], equals(0x30)); 16 | }); 17 | 18 | test('can read int16 from buffer', () { 19 | var buffer = new Buffer(2); 20 | buffer.list[0] = 0x39; 21 | buffer.list[1] = 0x30; 22 | expect(buffer.readInt16(), equals(12345)); 23 | }); 24 | 25 | test('knows if there is no more data available', () { 26 | var buffer = new Buffer(2); 27 | buffer.readInt16(); 28 | expect(buffer.hasMore, isFalse); 29 | }); 30 | 31 | test('knows if there is more data available', () { 32 | var buffer = new Buffer(3); 33 | buffer.readInt16(); 34 | expect(buffer.hasMore, isTrue); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /test/unit/buffered_socket_test.dart: -------------------------------------------------------------------------------- 1 | library buffered_socket_test; 2 | 3 | import 'package:test/test.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | import 'dart:io'; 6 | import 'dart:async'; 7 | 8 | import 'package:sqljocky/src/buffered_socket.dart'; 9 | import 'package:sqljocky/src/buffer.dart'; 10 | 11 | class MockSocket extends StreamView implements RawSocket { 12 | MockSocket(StreamController streamController) : super(streamController.stream) { 13 | _streamController = streamController; 14 | _data = new List(); 15 | } 16 | 17 | StreamController _streamController; 18 | List _data; 19 | int available() => _data.length; 20 | 21 | List read([int len]) { 22 | var count = len; 23 | if (count > _data.length) { 24 | count = _data.length; 25 | } 26 | var data = _data.getRange(0, count); 27 | var list = new List(); 28 | list.addAll(data); 29 | _data.removeRange(0, count); 30 | return list; 31 | } 32 | 33 | addData(List data) { 34 | _data.addAll(data); 35 | _streamController.add(RawSocketEvent.READ); 36 | } 37 | 38 | closeRead() { 39 | _streamController.add(RawSocketEvent.READ_CLOSED); 40 | } 41 | 42 | InternetAddress get address => null; 43 | 44 | Future close() {} 45 | 46 | int get port => null; 47 | 48 | bool get readEventsEnabled => null; 49 | 50 | void set readEventsEnabled(bool value) {} 51 | 52 | InternetAddress get remoteAddress => null; 53 | 54 | int get remotePort => null; 55 | 56 | bool setOption(SocketOption option, bool enabled) {} 57 | 58 | void shutdown(SocketDirection direction) {} 59 | 60 | int write(List buffer, [int offset, int count]) {} 61 | 62 | void set writeEventsEnabled(bool value) { 63 | if (value) { 64 | _streamController.add(RawSocketEvent.WRITE); 65 | } 66 | } 67 | 68 | bool get writeEventsEnabled => null; 69 | } 70 | 71 | class MockBuffer extends Mock implements Buffer {} 72 | 73 | void runBufferedSocketTests() { 74 | group('buffered socket', () { 75 | var rawSocket; 76 | var factory; 77 | 78 | setUp(() { 79 | var streamController = new StreamController(); 80 | factory = (host, port) { 81 | var c = new Completer(); 82 | rawSocket = new MockSocket(streamController); 83 | c.complete(rawSocket); 84 | return c.future; 85 | }; 86 | }); 87 | 88 | test('can read data which is already available', () async { 89 | var c = new Completer(); 90 | 91 | var socket; 92 | var thesocket = await BufferedSocket.connect('localhost', 100, onDataReady: () async { 93 | var buffer = new Buffer(4); 94 | await socket.readBuffer(buffer); 95 | expect(buffer.list, equals([1, 2, 3, 4])); 96 | c.complete(); 97 | }, onDone: () {}, onError: (e) {}, socketFactory: factory); 98 | socket = thesocket; 99 | rawSocket.addData([1, 2, 3, 4]); 100 | return c.future; 101 | }); 102 | 103 | test('can read data which is partially available', () async { 104 | var c = new Completer(); 105 | 106 | var socket; 107 | var thesocket = await BufferedSocket.connect('localhost', 100, onDataReady: () async { 108 | var buffer = new Buffer(4); 109 | socket.readBuffer(buffer).then((_) { 110 | expect(buffer.list, equals([1, 2, 3, 4])); 111 | c.complete(); 112 | }); 113 | rawSocket.addData([3, 4]); 114 | }, onDone: () {}, onError: (e) {}, socketFactory: factory); 115 | socket = thesocket; 116 | rawSocket.addData([1, 2]); 117 | return c.future; 118 | }); 119 | 120 | test('can read data which is not yet available', () async { 121 | var c = new Completer(); 122 | var socket = await BufferedSocket.connect('localhost', 100, 123 | onDataReady: () {}, onDone: () {}, onError: (e) {}, socketFactory: factory); 124 | var buffer = new Buffer(4); 125 | socket.readBuffer(buffer).then((_) { 126 | expect(buffer.list, equals([1, 2, 3, 4])); 127 | c.complete(); 128 | }); 129 | rawSocket.addData([1, 2, 3, 4]); 130 | return c.future; 131 | }); 132 | 133 | test('can read data which is not yet available, arriving in two chunks', () async { 134 | var c = new Completer(); 135 | var socket = await BufferedSocket.connect('localhost', 100, 136 | onDataReady: () {}, onDone: () {}, onError: (e) {}, socketFactory: factory); 137 | var buffer = new Buffer(4); 138 | socket.readBuffer(buffer).then((_) { 139 | expect(buffer.list, equals([1, 2, 3, 4])); 140 | c.complete(); 141 | }); 142 | rawSocket.addData([1, 2]); 143 | rawSocket.addData([3, 4]); 144 | return c.future; 145 | }); 146 | 147 | test('cannot read data when already reading', () async { 148 | var socket = await BufferedSocket.connect('localhost', 100, 149 | onDataReady: () {}, onDone: () {}, onError: (e) {}, socketFactory: factory); 150 | var buffer = new Buffer(4); 151 | socket.readBuffer(buffer).then((_) { 152 | expect(buffer.list, equals([1, 2, 3, 4])); 153 | }); 154 | expect(() { 155 | socket.readBuffer(buffer); 156 | }, throwsA(new isInstanceOf())); 157 | }); 158 | 159 | test('should write buffer', () async { 160 | var socket = await BufferedSocket.connect('localhost', 100, 161 | onDataReady: () {}, onDone: () {}, onError: (e) {}, socketFactory: factory); 162 | var buffer = new MockBuffer(); 163 | when(buffer.length).thenReturn(100); 164 | when(buffer.writeToSocket(any, any, any)).thenReturn(25); 165 | await socket.writeBuffer(buffer); 166 | verify(buffer.writeToSocket(any, any, any)).called(4); 167 | }); 168 | 169 | test('should write part of buffer', () async { 170 | var socket = await BufferedSocket.connect('localhost', 100, 171 | onDataReady: () {}, onDone: () {}, onError: (e) {}, socketFactory: factory); 172 | var buffer = new MockBuffer(); 173 | when(buffer.length).thenReturn(100); 174 | when(buffer.writeToSocket(any, any, any)).thenReturn(25); 175 | await socket.writeBufferPart(buffer, 25, 50); 176 | verify(buffer.writeToSocket(any, any, any)).called(2); 177 | }); 178 | 179 | test('should send close event', () async { 180 | var closed = false; 181 | var onClosed = () { 182 | closed = true; 183 | }; 184 | var socket = await BufferedSocket.connect('localhost', 100, 185 | onDataReady: () {}, onDone: () {}, onError: (e) {}, onClosed: onClosed, socketFactory: factory); 186 | await rawSocket.closeRead(); 187 | expect(closed, equals(true)); 188 | }); 189 | }); 190 | } 191 | 192 | void main() { 193 | // hierarchicalLoggingEnabled = true; 194 | // Logger.root.level = Level.ALL; 195 | // var listener = (LogRecord r) { 196 | // var name = r.loggerName; 197 | // if (name.length > 15) { 198 | // name = name.substring(0, 15); 199 | // } 200 | // while (name.length < 15) { 201 | // name = "$name "; 202 | // } 203 | // print("${r.time}: $name: ${r.message}"); 204 | // }; 205 | // Logger.root.onRecord.listen(listener); 206 | 207 | runBufferedSocketTests(); 208 | } 209 | -------------------------------------------------------------------------------- /test/unit/connection_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runConnectionTests() { 4 | group('Connection', () { 5 | test('should throw error if buffer is too big', () { 6 | final MAX_PACKET_SIZE = 10; 7 | var cnx = new _Connection(null, 15, MAX_PACKET_SIZE); 8 | final PACKET_SIZE = 11; 9 | var buffer = new Buffer(PACKET_SIZE); 10 | expect(() { 11 | cnx._sendBuffer(buffer); 12 | }, throwsA(new isInstanceOf())); 13 | }); 14 | 15 | test('should send buffer', () async { 16 | final MAX_PACKET_SIZE = 16 * 1024 * 1024; 17 | var cnx = new _Connection(null, 15, MAX_PACKET_SIZE); 18 | var socket = new MockSocket(); 19 | cnx._socket = socket; 20 | 21 | when(socket.writeBuffer(any)).thenReturn(new Future.value()); 22 | when(socket.writeBufferPart(any, any, any)).thenReturn(new Future.value()); 23 | 24 | var buffer = new Buffer.fromList([1, 2, 3]); 25 | await cnx._sendBuffer(buffer); 26 | var captured = verify(socket.writeBuffer(captureAny)).captured; 27 | expect(captured[0], hasLength(4)); 28 | expect(captured[0].list, equals([3, 0, 0, 1])); 29 | captured = verify(socket.writeBufferPart(captureAny, captureAny, captureAny)).captured; 30 | expect(captured[0].list, equals([1, 2, 3])); 31 | expect(captured[1], equals(0)); 32 | expect(captured[2], equals(3)); 33 | 34 | buffer = new Buffer.fromList([1, 2, 3]); 35 | await cnx._sendBuffer(buffer); 36 | captured = verify(socket.writeBuffer(captureAny)).captured; 37 | expect(captured[0], hasLength(4)); 38 | expect(captured[0].list, equals([3, 0, 0, 2])); 39 | captured = verify(socket.writeBufferPart(captureAny, captureAny, captureAny)).captured; 40 | expect(captured[0].list, equals([1, 2, 3])); 41 | expect(captured[1], equals(0)); 42 | expect(captured[2], equals(3)); 43 | }); 44 | 45 | test('should send large buffer', () async { 46 | final MAX_PACKET_SIZE = 32 * 1024 * 1024; 47 | var cnx = new _Connection(null, 15, MAX_PACKET_SIZE); 48 | var socket = new MockSocket(); 49 | cnx._socket = socket; 50 | 51 | var buffers = []; 52 | when(socket.writeBuffer(any)).thenAnswer((mirror) { 53 | var buffer = mirror.positionalArguments[0]; 54 | buffers.add(new List.from(buffer.list)); 55 | return new Future.value(); 56 | }); 57 | when(socket.writeBufferPart(any, any, any)).thenReturn(new Future.value()); 58 | 59 | final PACKET_SIZE = 17 * 1024 * 1024; 60 | var buffer = new Buffer(PACKET_SIZE); 61 | await cnx._sendBuffer(buffer); 62 | verify(socket.writeBuffer(any)).called(2); 63 | expect(buffers[0], equals([0xff, 0xff, 0xff, 1])); 64 | expect(buffers[1], equals([1, 0, 16, 2])); 65 | var captured = verify(socket.writeBufferPart(captureAny, captureAny, captureAny)).captured; 66 | expect(captured, hasLength(6)); 67 | expect(captured[1], equals(0)); 68 | expect(captured[2], equals(0xffffff)); 69 | expect(captured[4], equals(0xffffff)); 70 | expect(captured[5], equals(PACKET_SIZE - 0xffffff)); 71 | }); 72 | 73 | test('should receive buffer', () async { 74 | final MAX_PACKET_SIZE = 16 * 1024 * 1024; 75 | var cnx = new _Connection(null, 15, MAX_PACKET_SIZE); 76 | var socket = new MockSocket(); 77 | cnx._socket = socket; 78 | 79 | var c = new Completer(); 80 | 81 | var buffer; 82 | cnx._dataHandler = (newBuffer) { 83 | buffer = newBuffer; 84 | c.complete(); 85 | }; 86 | 87 | var bufferReturnCount = 0; 88 | when(socket.readBuffer(any)).thenAnswer((_) async { 89 | if (bufferReturnCount == 0) { 90 | bufferReturnCount++; 91 | return new Buffer.fromList([3, 0, 0, 1]); 92 | } else { 93 | bufferReturnCount++; 94 | return new Buffer.fromList([1, 2, 3]); 95 | } 96 | }); // 2 97 | 98 | cnx._readPacket(); 99 | 100 | await c.future; 101 | 102 | verify(socket.readBuffer(any)).called(2); 103 | expect(buffer.list, equals([1, 2, 3])); 104 | }); 105 | 106 | test('should receive large buffer', () async { 107 | final MAX_PACKET_SIZE = 32 * 1024 * 1024; 108 | var cnx = new _Connection(null, 15, MAX_PACKET_SIZE); 109 | var socket = new MockSocket(); 110 | cnx._socket = socket; 111 | 112 | var c = new Completer(); 113 | 114 | var buffer; 115 | cnx._dataHandler = (newBuffer) { 116 | buffer = newBuffer; 117 | c.complete(); 118 | }; 119 | 120 | var bufferReturnCount = 0; 121 | var bufferReturn = (_) { 122 | if (bufferReturnCount == 0) { 123 | bufferReturnCount++; 124 | return new Future.value(new Buffer.fromList([0xff, 0xff, 0xff, 1])); 125 | } else if (bufferReturnCount == 1) { 126 | bufferReturnCount++; 127 | var bigBuffer = new Buffer(0xffffff); 128 | bigBuffer.list[0] = 1; 129 | bigBuffer.list[0xffffff - 1] = 2; 130 | return new Future.value(bigBuffer); 131 | } else if (bufferReturnCount == 2) { 132 | bufferReturnCount++; 133 | return new Future.value(new Buffer.fromList([3, 0, 0, 2])); 134 | } else { 135 | bufferReturnCount++; 136 | var bufferSize = 17 * 1024 * 1024 - 0xffffff; 137 | var littleBuffer = new Buffer(bufferSize); 138 | littleBuffer[0] = 3; 139 | littleBuffer[bufferSize - 1] = 4; 140 | return new Future.value(littleBuffer); 141 | } 142 | }; 143 | when(socket.readBuffer(any)).thenAnswer(bufferReturn); // 4 144 | 145 | cnx._readPacket(); 146 | 147 | await c.future; 148 | verify(socket.readBuffer(any)).called(4); 149 | expect(buffer.list.length, equals(17 * 1024 * 1024)); 150 | expect(buffer.list[0], equals(1)); 151 | expect(buffer.list[0xffffff - 1], equals(2)); 152 | expect(buffer.list[0xffffff], equals(3)); 153 | expect(buffer.list[buffer.list.length - 1], equals(4)); 154 | }); 155 | }); 156 | } 157 | 158 | class MockSocket extends Mock implements BufferedSocket {} 159 | 160 | class MockConnection extends Mock implements _Connection {} 161 | -------------------------------------------------------------------------------- /test/unit/execute_query_handler_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runExecuteQueryHandlerTests() { 4 | group('ExecuteQueryHandler._createNullMap', () { 5 | test('can build empty map', () { 6 | var handler = new _ExecuteQueryHandler(null, false, []); 7 | var nullmap = handler._createNullMap(); 8 | expect(nullmap, equals([])); 9 | }); 10 | 11 | test('can build map with no nulls', () { 12 | var handler = new _ExecuteQueryHandler(null, false, [1]); 13 | var nullmap = handler._createNullMap(); 14 | expect(nullmap, equals([0])); 15 | }); 16 | 17 | test('can build map with one null', () { 18 | var handler = new _ExecuteQueryHandler(null, false, [null]); 19 | var nullmap = handler._createNullMap(); 20 | expect(nullmap, equals([1])); 21 | }); 22 | 23 | test('can build map with eight nulls', () { 24 | var handler = new _ExecuteQueryHandler(null, false, [null, null, null, null, null, null, null, null]); 25 | var nullmap = handler._createNullMap(); 26 | expect(nullmap, equals([255])); 27 | }); 28 | 29 | test('can build map with eight not nulls', () { 30 | var handler = new _ExecuteQueryHandler(null, false, [0, 0, 0, 0, 0, 0, 0, 0]); 31 | var nullmap = handler._createNullMap(); 32 | expect(nullmap, equals([0])); 33 | }); 34 | 35 | test('can build map with some nulls and some not', () { 36 | var handler = new _ExecuteQueryHandler(null, false, [null, 0, 0, 0, 0, 0, 0, null]); 37 | var nullmap = handler._createNullMap(); 38 | expect(nullmap, equals([129])); 39 | }); 40 | 41 | test('can build map with some nulls and some not', () { 42 | var handler = new _ExecuteQueryHandler(null, false, [null, 0, 0, 0, 0, 0, 0, null]); 43 | var nullmap = handler._createNullMap(); 44 | expect(nullmap, equals([129])); 45 | }); 46 | 47 | test('can build map which is more than one byte', () { 48 | var handler = new _ExecuteQueryHandler(null, false, [null, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0]); 49 | var nullmap = handler._createNullMap(); 50 | expect(nullmap, equals([129, 0])); 51 | }); 52 | 53 | test('can build map which just is more than one byte', () { 54 | var handler = new _ExecuteQueryHandler(null, false, [null, 0, 0, 0, 0, 0, 0, null, 0]); 55 | var nullmap = handler._createNullMap(); 56 | expect(nullmap, equals([129, 0])); 57 | }); 58 | 59 | test('can build map which just is more than one byte with a null', () { 60 | var handler = new _ExecuteQueryHandler(null, false, [null, 0, 0, 0, 0, 0, 0, null, null]); 61 | var nullmap = handler._createNullMap(); 62 | expect(nullmap, equals([129, 1])); 63 | }); 64 | 65 | test('can build map which just is more than one byte with a null, another pattern', () { 66 | var handler = new _ExecuteQueryHandler(null, false, [null, 0, null, 0, 0, 0, 0, null, null]); 67 | var nullmap = handler._createNullMap(); 68 | expect(nullmap, equals([129 + 4, 1])); 69 | }); 70 | }); 71 | 72 | group('ExecuteQueryHandler._writeValuesToBuffer', () { 73 | var types; 74 | 75 | setUp(() { 76 | types = []; 77 | }); 78 | 79 | test('can write values for unexecuted query', () { 80 | var preparedQuery = new MockPreparedQuery(); 81 | when(preparedQuery.statementHandlerId).thenReturn(123); 82 | 83 | var handler = new _ExecuteQueryHandler(preparedQuery, false, []); 84 | handler._preparedValues = []; 85 | var buffer = handler._writeValuesToBuffer([], 0, types); 86 | expect(buffer.length, equals(11)); 87 | expect(buffer.list, equals([23, 123, 0, 0, 0, 0, 1, 0, 0, 0, 1])); 88 | }); 89 | 90 | test('can write values for executed query', () { 91 | var preparedQuery = new MockPreparedQuery(); 92 | when(preparedQuery.statementHandlerId).thenReturn(123); 93 | 94 | var handler = new _ExecuteQueryHandler(preparedQuery, true, []); 95 | handler._preparedValues = []; 96 | var buffer = handler._writeValuesToBuffer([], 0, types); 97 | expect(buffer.length, equals(11)); 98 | expect(buffer.list, equals([23, 123, 0, 0, 0, 0, 1, 0, 0, 0, 0])); 99 | }); 100 | 101 | test('can write values for executed query with nullmap', () { 102 | var preparedQuery = new MockPreparedQuery(); 103 | when(preparedQuery.statementHandlerId).thenReturn(123); 104 | 105 | var handler = new _ExecuteQueryHandler(preparedQuery, true, []); 106 | handler._preparedValues = []; 107 | var buffer = handler._writeValuesToBuffer([5, 6, 7], 0, types); 108 | expect(buffer.length, equals(14)); 109 | expect(buffer.list, equals([23, 123, 0, 0, 0, 0, 1, 0, 0, 0, 5, 6, 7, 0])); 110 | }); 111 | 112 | test('can write values for unexecuted query with values', () { 113 | var preparedQuery = new MockPreparedQuery(); 114 | when(preparedQuery.statementHandlerId).thenReturn(123); 115 | 116 | types = [100]; 117 | var handler = new _ExecuteQueryHandler(preparedQuery, false, [123]); 118 | handler._preparedValues = [123]; 119 | var buffer = handler._writeValuesToBuffer([5, 6, 7], 8, types); 120 | expect(buffer.length, equals(23)); 121 | expect(buffer.list, equals([23, 123, 0, 0, 0, 0, 1, 0, 0, 0, 5, 6, 7, 1, 100, 123, 0, 0, 0, 0, 0, 0, 0])); 122 | }); 123 | }); 124 | 125 | group('ExecuteQueryHandler._prepareValue', () { 126 | var preparedQuery; 127 | var handler; 128 | 129 | setUp(() { 130 | preparedQuery = new MockPreparedQuery(); 131 | handler = new _ExecuteQueryHandler(preparedQuery, false, []); 132 | }); 133 | 134 | test('can prepare int values correctly', () { 135 | expect(handler._prepareValue(123), equals(123)); 136 | }); 137 | 138 | test('can prepare string values correctly', () { 139 | expect(handler._prepareValue("hello"), equals(UTF8.encode("hello"))); 140 | }); 141 | 142 | test('can prepare double values correctly', () { 143 | expect(handler._prepareValue(123.45), equals(UTF8.encode("123.45"))); 144 | }); 145 | 146 | test('can prepare datetime values correctly', () { 147 | var dateTime = new DateTime.utc(2014, 3, 4, 5, 6, 7, 8); 148 | expect(handler._prepareValue(dateTime), equals(dateTime)); 149 | }); 150 | 151 | test('can prepare bool values correctly', () { 152 | expect(handler._prepareValue(true), equals(true)); 153 | }); 154 | 155 | test('can prepare list values correctly', () { 156 | expect(handler._prepareValue([1, 2, 3]), equals([1, 2, 3])); 157 | }); 158 | 159 | test('can prepare blob values correctly', () { 160 | expect(handler._prepareValue(new Blob.fromString("hello")), equals(UTF8.encode("hello"))); 161 | }); 162 | }); 163 | 164 | group('ExecuteQueryHandler._measureValue', () { 165 | var preparedQuery; 166 | var handler; 167 | 168 | setUp(() { 169 | preparedQuery = new MockPreparedQuery(); 170 | handler = new _ExecuteQueryHandler(preparedQuery, false, []); 171 | }); 172 | 173 | test('can measure int values correctly', () { 174 | expect(handler._measureValue(123, 123), equals(8)); 175 | }); 176 | 177 | test('can measure short string correctly', () { 178 | var string = "a"; 179 | var preparedString = UTF8.encode(string); 180 | expect(handler._measureValue(string, preparedString), equals(2)); 181 | }); 182 | 183 | test('can measure longer string correctly', () { 184 | var string = new String.fromCharCodes(new List.filled(300, 65)); 185 | var preparedString = UTF8.encode(string); 186 | expect(handler._measureValue(string, preparedString), equals(3 + string.length)); 187 | }); 188 | 189 | test('can measure even longer string correctly', () { 190 | var string = new String.fromCharCodes(new List.filled(70000, 65)); 191 | var preparedString = UTF8.encode(string); 192 | expect(handler._measureValue(string, preparedString), equals(4 + string.length)); 193 | }); 194 | 195 | test('can measure even very long string correctly', () { 196 | var string = new String.fromCharCodes(new List.filled(2 << 23 + 1, 65)); 197 | var preparedString = UTF8.encode(string); 198 | expect(handler._measureValue(string, preparedString), equals(5 + string.length)); 199 | }); 200 | 201 | //etc 202 | }); 203 | } 204 | 205 | class MockPreparedQuery extends Mock implements _PreparedQuery {} 206 | -------------------------------------------------------------------------------- /test/unit/field_by_name_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runFieldByNameTests() { 4 | group('field by name, standard data packets:', () { 5 | test('should create field index', () { 6 | var handler = new _QueryStreamHandler(""); 7 | var field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 8 | field._name = "123"; 9 | handler._fieldPackets.add(field); 10 | var fieldIndex = handler._createFieldIndex(); 11 | expect(fieldIndex, hasLength(0)); 12 | 13 | field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 14 | field._name = "_abc"; 15 | handler._fieldPackets.add(field); 16 | fieldIndex = handler._createFieldIndex(); 17 | expect(fieldIndex, hasLength(0)); 18 | 19 | field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 20 | field._name = "abc"; 21 | handler._fieldPackets.add(field); 22 | fieldIndex = handler._createFieldIndex(); 23 | expect(fieldIndex, hasLength(1)); 24 | expect(fieldIndex.keys, contains(new Symbol("abc"))); 25 | 26 | field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 27 | field._name = "a123"; 28 | handler._fieldPackets.clear(); 29 | handler._fieldPackets.add(field); 30 | fieldIndex = handler._createFieldIndex(); 31 | expect(fieldIndex, hasLength(1)); 32 | expect(fieldIndex.keys, contains(new Symbol("a123"))); 33 | }); 34 | 35 | test('should call noSuchMethod', () { 36 | var fieldIndex = new Map(); 37 | fieldIndex[new Symbol("one")] = 0; 38 | fieldIndex[new Symbol("two")] = 1; 39 | fieldIndex[new Symbol("three")] = 2; 40 | var values = [5, "hello", null]; 41 | 42 | Row row = new _StandardDataPacket._forTests(values, fieldIndex); 43 | expect(row.one, equals(5)); 44 | expect(row.two, equals("hello")); 45 | expect(row.three, equals(null)); 46 | }); 47 | 48 | test('should fail for non-existent properties', () { 49 | var fieldIndex = new Map(); 50 | var values = []; 51 | 52 | Row row = new _StandardDataPacket._forTests(values, fieldIndex); 53 | try { 54 | var x = row.one; 55 | expect(true, isFalse); 56 | } on NoSuchMethodError { 57 | expect(true, isTrue); 58 | } 59 | }); 60 | }); 61 | 62 | group('field by name, binary data packets:', () { 63 | test('should create field index', () { 64 | var handler = new _ExecuteQueryHandler(null, null, null); 65 | var field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 66 | field._name = "123"; 67 | handler._fieldPackets.add(field); 68 | var fieldIndex = handler._createFieldIndex(); 69 | expect(fieldIndex, hasLength(0)); 70 | 71 | field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 72 | field._name = "_abc"; 73 | handler._fieldPackets.add(field); 74 | fieldIndex = handler._createFieldIndex(); 75 | expect(fieldIndex, hasLength(0)); 76 | 77 | field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 78 | field._name = "abc"; 79 | handler._fieldPackets.add(field); 80 | fieldIndex = handler._createFieldIndex(); 81 | expect(fieldIndex, hasLength(1)); 82 | expect(fieldIndex.keys, contains(new Symbol("abc"))); 83 | 84 | field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 85 | field._name = "a123"; 86 | handler._fieldPackets.clear(); 87 | handler._fieldPackets.add(field); 88 | fieldIndex = handler._createFieldIndex(); 89 | expect(fieldIndex, hasLength(1)); 90 | expect(fieldIndex.keys, contains(new Symbol("a123"))); 91 | }); 92 | 93 | test('should call noSuchMethod', () { 94 | var fieldIndex = new Map(); 95 | fieldIndex[new Symbol("one")] = 0; 96 | fieldIndex[new Symbol("two")] = 1; 97 | fieldIndex[new Symbol("three")] = 2; 98 | var values = [5, "hello", null]; 99 | 100 | Row row = new _BinaryDataPacket._forTests(values, fieldIndex); 101 | expect(row.one, equals(5)); 102 | expect(row.two, equals("hello")); 103 | expect(row.three, equals(null)); 104 | }); 105 | 106 | test('should fail for non-existent properties', () { 107 | var fieldIndex = new Map(); 108 | var values = []; 109 | 110 | Row row = new _BinaryDataPacket._forTests(values, fieldIndex); 111 | try { 112 | var x = row.one; 113 | expect(true, isFalse); 114 | } on NoSuchMethodError { 115 | expect(true, isTrue); 116 | } 117 | }); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /test/unit/prepared_statements_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runPreparedStatementTests() { 4 | group('read fields:', () { 5 | test('can read a tiny BLOB', () { 6 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 7 | var buffer = new Buffer.fromList([3, 65, 66, 67]); 8 | var field = new _FieldImpl._forTests(FIELD_TYPE_BLOB); 9 | var value = dataPacket._readField(field, buffer); 10 | expect(true, equals(value is Blob)); 11 | expect(value.toString(), equals("ABC")); 12 | }); 13 | 14 | test('can read a very tiny BLOB', () { 15 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 16 | var buffer = new Buffer.fromList([0]); 17 | var field = new _FieldImpl._forTests(FIELD_TYPE_BLOB); 18 | var value = dataPacket._readField(field, buffer); 19 | expect(true, equals(value is Blob)); 20 | expect(value.toString(), equals("")); 21 | }); 22 | 23 | test('can read a several BLOBs', () { 24 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 25 | var buffer = new Buffer.fromList([0, 3, 65, 66, 67, 1, 65, 0, 0, 1, 65]); 26 | var field = new _FieldImpl._forTests(FIELD_TYPE_BLOB); 27 | 28 | var value = dataPacket._readField(field, buffer); 29 | expect(true, equals(value is Blob)); 30 | expect(value.toString(), equals("")); 31 | 32 | value = dataPacket._readField(field, buffer); 33 | expect(true, equals(value is Blob)); 34 | expect(value.toString(), equals("ABC")); 35 | 36 | value = dataPacket._readField(field, buffer); 37 | expect(true, equals(value is Blob)); 38 | expect(value.toString(), equals("A")); 39 | 40 | value = dataPacket._readField(field, buffer); 41 | expect(true, equals(value is Blob)); 42 | expect(value.toString(), equals("")); 43 | 44 | value = dataPacket._readField(field, buffer); 45 | expect(true, equals(value is Blob)); 46 | expect(value.toString(), equals("")); 47 | 48 | value = dataPacket._readField(field, buffer); 49 | expect(true, equals(value is Blob)); 50 | expect(value.toString(), equals("A")); 51 | }); 52 | 53 | test('can read TINYs', () { 54 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 55 | var buffer = new Buffer.fromList([0, 3, 65]); 56 | var field = new _FieldImpl._forTests(FIELD_TYPE_TINY); 57 | 58 | var value = dataPacket._readField(field, buffer); 59 | expect(true, equals(value is num)); 60 | expect(value, equals(0)); 61 | 62 | value = dataPacket._readField(field, buffer); 63 | expect(true, equals(value is num)); 64 | expect(value, equals(3)); 65 | 66 | value = dataPacket._readField(field, buffer); 67 | expect(true, equals(value is num)); 68 | expect(value, equals(65)); 69 | }); 70 | 71 | test('can read SHORTs', () { 72 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 73 | var buffer = new Buffer.fromList([0, 0, 255, 255, 255, 0]); 74 | var field = new _FieldImpl._forTests(FIELD_TYPE_SHORT); 75 | 76 | var value = dataPacket._readField(field, buffer); 77 | expect(true, equals(value is num)); 78 | expect(value, equals(0)); 79 | 80 | value = dataPacket._readField(field, buffer); 81 | expect(true, equals(value is num)); 82 | expect(value, equals(-1)); 83 | 84 | value = dataPacket._readField(field, buffer); 85 | expect(true, equals(value is num)); 86 | expect(value, equals(255)); 87 | }); 88 | 89 | test('can read INT24s', () { 90 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 91 | var buffer = new Buffer.fromList([0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0]); 92 | var field = new _FieldImpl._forTests(FIELD_TYPE_INT24); 93 | 94 | var value = dataPacket._readField(field, buffer); 95 | expect(true, equals(value is num)); 96 | expect(value, equals(0)); 97 | 98 | value = dataPacket._readField(field, buffer); 99 | expect(true, equals(value is num)); 100 | expect(value, equals(-1)); 101 | 102 | value = dataPacket._readField(field, buffer); 103 | expect(true, equals(value is num)); 104 | expect(value, equals(255)); 105 | }); 106 | 107 | test('can read LONGs', () { 108 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 109 | var buffer = new Buffer.fromList([0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0]); 110 | var field = new _FieldImpl._forTests(FIELD_TYPE_LONG); 111 | 112 | var value = dataPacket._readField(field, buffer); 113 | expect(true, equals(value is num)); 114 | expect(value, equals(0)); 115 | 116 | value = dataPacket._readField(field, buffer); 117 | expect(true, equals(value is num)); 118 | expect(value, equals(-1)); 119 | 120 | value = dataPacket._readField(field, buffer); 121 | expect(true, equals(value is num)); 122 | expect(value, equals(255)); 123 | }); 124 | 125 | test('can read LONGLONGs', () { 126 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 127 | var buffer = new Buffer.fromList( 128 | [0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0]); 129 | var field = new _FieldImpl._forTests(FIELD_TYPE_LONGLONG); 130 | 131 | var value = dataPacket._readField(field, buffer); 132 | expect(true, equals(value is num)); 133 | expect(value, equals(0)); 134 | 135 | value = dataPacket._readField(field, buffer); 136 | expect(true, equals(value is num)); 137 | expect(value, equals(-1)); 138 | 139 | value = dataPacket._readField(field, buffer); 140 | expect(true, equals(value is num)); 141 | expect(value, equals(255)); 142 | }); 143 | 144 | test('can read NEWDECIMALs', () { 145 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 146 | var buffer = new Buffer.fromList([5, 0x31, 0x33, 0x2E, 0x39, 0x33]); 147 | var field = new _FieldImpl._forTests(FIELD_TYPE_NEWDECIMAL); 148 | 149 | var value = dataPacket._readField(field, buffer); 150 | expect(value is num, equals(true)); 151 | expect(value, equals(13.93)); 152 | }); 153 | 154 | //test FLOAT 155 | //test DOUBLE 156 | 157 | test('can read BITs', () { 158 | var dataPacket = new _BinaryDataPacket._forTests(null, null); 159 | var buffer = new Buffer.fromList([ 160 | 1, 161 | 123, 162 | 20, 163 | 0x01, 164 | 0x02, 165 | 0x03, 166 | 0x04, 167 | 0x05, 168 | 0x06, 169 | 0x07, 170 | 0x08, 171 | 0x09, 172 | 0x00, 173 | 0x11, 174 | 0x12, 175 | 0x13, 176 | 0x14, 177 | 0x15, 178 | 0x16, 179 | 0x17, 180 | 0x18, 181 | 0x19, 182 | 0x10 183 | ]); 184 | var field = new _FieldImpl._forTests(FIELD_TYPE_BIT); 185 | 186 | var value = dataPacket._readField(field, buffer); 187 | expect(value is num, equals(true)); 188 | expect(value, equals(123)); 189 | 190 | value = dataPacket._readField(field, buffer); 191 | expect(value is num, equals(true)); 192 | expect(value, equals(0x0102030405060708090011121314151617181910)); 193 | }); 194 | }); 195 | } 196 | -------------------------------------------------------------------------------- /test/unit/serialize_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | final double SMALLEST_POSITIVE_SUBNORMAL_FLOAT = 1.4012984643248170E-45; 4 | final double LARGEST_POSITIVE_SUBNORMAL_FLOAT = 1.1754942106924411E-38; 5 | final double SMALLEST_POSITIVE_NORMAL_FLOAT = 1.1754943508222875E-38; 6 | final double LARGEST_POSITIVE_NORMAL_FLOAT = 3.4028234663852886E+38; 7 | 8 | final double LARGEST_NEGATIVE_NORMAL_FLOAT = -1.1754943508222875E-38; // closest to zero 9 | final double SMALLEST_NEGATIVE_NORMAL_FLOAT = -3.4028234663852886E+38; // most negative 10 | final double LARGEST_NEGATIVE_SUBNORMAL_FLOAT = -1.1754942106924411E-38; 11 | final double SMALLEST_NEGATIVE_SUBNORMAL_FLOAT = -1.4012984643248170E-45; 12 | 13 | final double SMALLEST_POSITIVE_SUBNORMAL_DOUBLE = 4.9406564584124654E-324; 14 | final double LARGEST_POSITIVE_SUBNORMAL_DOUBLE = 2.2250738585072010E-308; 15 | final double SMALLEST_POSITIVE_NORMAL_DOUBLE = 2.2250738585072014E-308; 16 | final double LARGEST_POSITIVE_NORMAL_DOUBLE = 1.7976931348623157E+308; 17 | 18 | final double LARGEST_NEGATIVE_NORMAL_DOUBLE = -2.2250738585072014E-308; // closest to zero 19 | final double SMALLEST_NEGATIVE_NORMAL_DOUBLE = -1.7976931348623157E+308; // most negative 20 | final double LARGEST_NEGATIVE_SUBNORMAL_DOUBLE = -4.9406564584124654E-324; 21 | final double SMALLEST_NEGATIVE_SUBNORMAL_DOUBLE = -2.2250738585072010E-308; 22 | 23 | String _BufferToHexString(Buffer list, [bool reverse = false]) { 24 | var s = new StringBuffer(); 25 | for (int i = 0; i < list.length; i++) { 26 | var x = list[reverse ? list.length - i - 1 : i].toRadixString(16).toUpperCase(); 27 | if (x.length == 1) { 28 | s.write("0"); 29 | } 30 | s.write(x); 31 | } 32 | return s.toString(); 33 | } 34 | 35 | void runSerializationTests() { 36 | group('serialization:', () { 37 | test('can write zero float', () { 38 | var buffer = new Buffer(4); 39 | var n = 0.0; 40 | buffer.writeFloat(n); 41 | expect(_BufferToHexString(buffer, true), equals("00000000")); 42 | }); 43 | 44 | test('can write zero double', () { 45 | var buffer = new Buffer(8); 46 | var n = 0.0; 47 | buffer.writeDouble(n); 48 | expect(_BufferToHexString(buffer, true), equals("0000000000000000")); 49 | }); 50 | 51 | test('can write one or greater float', () { 52 | var buffer = new Buffer(4); 53 | var n = 1.0; 54 | buffer.writeFloat(n); 55 | expect(_BufferToHexString(buffer, true), equals("3F800000")); 56 | 57 | n = 100.0; 58 | buffer.reset(); 59 | buffer.writeFloat(n); 60 | expect(_BufferToHexString(buffer, true), equals("42C80000")); 61 | 62 | n = 123487.982374; 63 | buffer.reset(); 64 | buffer.writeFloat(n); 65 | expect(_BufferToHexString(buffer, true), equals("47F12FFE")); 66 | 67 | n = 10000000000000000000000000000.0; 68 | buffer.reset(); 69 | buffer.writeFloat(n); 70 | expect(_BufferToHexString(buffer, true), equals("6E013F39")); 71 | 72 | // TODO: test very large numbers 73 | }); 74 | 75 | test('can write one or greater double', () { 76 | var buffer = new Buffer(8); 77 | var n = 1.0; 78 | buffer.writeDouble(n); 79 | expect(_BufferToHexString(buffer, true), equals("3FF0000000000000")); 80 | 81 | n = 100.0; 82 | buffer.reset(); 83 | buffer.writeDouble(n); 84 | expect(_BufferToHexString(buffer, true), equals("4059000000000000")); 85 | 86 | n = 123487.982374; 87 | buffer.reset(); 88 | buffer.writeDouble(n); 89 | expect(_BufferToHexString(buffer, true), equals("40FE25FFB7CDCCA7")); 90 | 91 | n = 10000000000000000000000000000.0; 92 | buffer.reset(); 93 | buffer.writeDouble(n); 94 | expect(_BufferToHexString(buffer, true), equals("45C027E72F1F1281")); 95 | 96 | // TODO: test very large numbers 97 | }); 98 | 99 | test('can write less than one float', () { 100 | var buffer = new Buffer(4); 101 | 102 | var n = 0.1; 103 | buffer.writeFloat(n); 104 | expect(_BufferToHexString(buffer, true), equals("3DCCCCCD")); 105 | 106 | // TODO: test very small numbers 107 | n = 3.4028234663852886E+38; 108 | buffer.reset(); 109 | buffer.writeFloat(n); 110 | expect(_BufferToHexString(buffer, true), equals("7F7FFFFF")); 111 | 112 | n = 1.1754943508222875E-38; 113 | buffer.reset(); 114 | buffer.writeFloat(n); 115 | expect(_BufferToHexString(buffer, true), equals("00800000")); 116 | 117 | n = SMALLEST_POSITIVE_SUBNORMAL_FLOAT / 2; 118 | buffer.reset(); 119 | buffer.writeFloat(n); 120 | expect(_BufferToHexString(buffer, true), equals("00000000")); 121 | }); 122 | 123 | test('can write less than one double', () { 124 | var buffer = new Buffer(8); 125 | 126 | var n = 0.1; 127 | buffer.writeDouble(n); 128 | expect(_BufferToHexString(buffer, true), equals("3FB999999999999A")); 129 | 130 | // TODO: test very small numbers 131 | n = 1.7976931348623157E+308; 132 | buffer.reset(); 133 | buffer.writeDouble(n); 134 | expect(_BufferToHexString(buffer, true), equals("7FEFFFFFFFFFFFFF")); 135 | 136 | n = -1.7976931348623157E+308; 137 | buffer.reset(); 138 | buffer.writeDouble(n); 139 | expect(_BufferToHexString(buffer, true), equals("FFEFFFFFFFFFFFFF")); 140 | }); 141 | 142 | test('can write non numbers float', () { 143 | var buffer = new Buffer(4); 144 | 145 | var n = 1.0 / 0.0; 146 | buffer.writeFloat(n); 147 | expect(_BufferToHexString(buffer, true), equals("7F800000")); 148 | 149 | n = -1.0 / 0.0; 150 | buffer.reset(); 151 | buffer.writeFloat(n); 152 | expect(_BufferToHexString(buffer, true), equals("FF800000")); 153 | 154 | n = 0.0 / 0.0; 155 | buffer.reset(); 156 | buffer.writeFloat(n); 157 | expect(_BufferToHexString(buffer, true), equals("FFC00000")); 158 | }); 159 | 160 | test('can write non numbers double', () { 161 | var buffer = new Buffer(8); 162 | 163 | var n = 1.0 / 0.0; 164 | buffer.writeDouble(n); 165 | expect(_BufferToHexString(buffer, true), equals("7FF0000000000000")); 166 | 167 | n = -1.0 / 0.0; 168 | buffer.reset(); 169 | buffer.writeDouble(n); 170 | expect(_BufferToHexString(buffer, true), equals("FFF0000000000000")); 171 | 172 | n = 0.0 / 0.0; 173 | buffer.reset(); 174 | buffer.writeDouble(n); 175 | expect(_BufferToHexString(buffer, true), equals("FFF8000000000000")); 176 | }); 177 | }); 178 | } 179 | -------------------------------------------------------------------------------- /test/unit/types_test.dart: -------------------------------------------------------------------------------- 1 | part of sqljocky; 2 | 3 | void runTypesTests() { 4 | group('types:', () { 5 | test('can create blob from string', () { 6 | var blob = new Blob.fromString("Hello"); 7 | expect(blob, isNotNull); 8 | }); 9 | 10 | test('can string blob can turn into a string', () { 11 | var blob = new Blob.fromString("Hello"); 12 | expect(blob.toString(), equals("Hello")); 13 | }); 14 | 15 | test('can string blob can turn into bytes', () { 16 | var blob = new Blob.fromString("ABC"); 17 | var bytes = blob.toBytes(); 18 | expect(bytes[0], equals(65)); 19 | }); 20 | 21 | test('can create blob from bytes', () { 22 | var bytes = new Uint8List(3); 23 | bytes[0] = 1; 24 | bytes[1] = 1; 25 | bytes[2] = 1; 26 | var blob = new Blob.fromBytes(bytes); 27 | expect(blob, isNotNull); 28 | }); 29 | 30 | test('can bytes blob turn into a string', () { 31 | var bytes = new Uint8List(3); 32 | bytes[0] = 65; 33 | bytes[1] = 66; 34 | bytes[2] = 67; 35 | var blob = new Blob.fromBytes(bytes); 36 | expect(blob.toString(), "ABC"); 37 | }); 38 | 39 | test('can bytes blob turn into bytes', () { 40 | var bytes = new Uint8List(3); 41 | bytes[0] = 65; 42 | bytes[1] = 66; 43 | bytes[2] = 67; 44 | var blob = new Blob.fromBytes(bytes); 45 | var outBytes = blob.toBytes(); 46 | expect(outBytes, bytes); 47 | }); 48 | 49 | test('string blobs are equal', () { 50 | var blob1 = new Blob.fromString("ABC"); 51 | var blob2 = new Blob.fromString("ABC"); 52 | expect(blob1 == blob2, isTrue); 53 | expect(blob1.hashCode == blob2.hashCode, isTrue); 54 | }); 55 | 56 | test('string blobs are not equal', () { 57 | var blob1 = new Blob.fromString("ABC"); 58 | var blob2 = new Blob.fromString("ABD"); 59 | expect(blob1 == blob2, isFalse); 60 | // hashCode may be equal, but probably isn't 61 | }); 62 | 63 | test('byte blobs are equal', () { 64 | var bytes1 = new Uint8List(3); 65 | bytes1[0] = 65; 66 | bytes1[1] = 66; 67 | bytes1[2] = 67; 68 | var blob1 = new Blob.fromBytes(bytes1); 69 | var bytes2 = new Uint8List(3); 70 | bytes2[0] = 65; 71 | bytes2[1] = 66; 72 | bytes2[2] = 67; 73 | var blob2 = new Blob.fromBytes(bytes2); 74 | expect(blob1 == blob2, isTrue); 75 | expect(blob1.hashCode == blob2.hashCode, isTrue); 76 | }); 77 | 78 | test('byte blobs are not equal', () { 79 | var bytes1 = new Uint8List(3); 80 | bytes1[0] = 65; 81 | bytes1[1] = 66; 82 | bytes1[2] = 67; 83 | var blob1 = new Blob.fromBytes(bytes1); 84 | var bytes2 = new Uint8List(3); 85 | bytes2[0] = 65; 86 | bytes2[1] = 66; 87 | bytes2[2] = 68; 88 | var blob2 = new Blob.fromBytes(bytes2); 89 | expect(blob1 == blob2, isFalse); 90 | }); 91 | 92 | test('byte blobs equal to string blobs', () { 93 | var bytes1 = new Uint8List(3); 94 | bytes1[0] = 65; 95 | bytes1[1] = 66; 96 | bytes1[2] = 67; 97 | var blob1 = new Blob.fromBytes(bytes1); 98 | var blob2 = new Blob.fromString("ABC"); 99 | expect(blob1 == blob2, isTrue); 100 | expect(blob1.hashCode == blob2.hashCode, isTrue); 101 | }); 102 | 103 | test('utf blobs', () { 104 | var blob1 = new Blob.fromString("здрасти"); 105 | var bytes = blob1.toBytes(); 106 | var trimmedBytes = new List(); 107 | for (var b in bytes) { 108 | trimmedBytes.add(b & 0xFF); 109 | } 110 | var blob2 = new Blob.fromBytes(trimmedBytes); 111 | expect(blob2.toString(), equals("здрасти")); 112 | }); 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /test/unit_test.dart: -------------------------------------------------------------------------------- 1 | library sqljocky; 2 | 3 | import 'dart:async'; 4 | import 'dart:collection'; 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | import 'dart:math' as math; 8 | import 'dart:typed_data'; 9 | 10 | import 'package:crypto/crypto.dart'; 11 | import 'package:logging/logging.dart'; 12 | import 'package:mockito/mockito.dart'; 13 | import 'package:sqljocky/constants.dart'; 14 | import 'package:sqljocky/src/buffer.dart'; 15 | import 'package:sqljocky/src/buffered_socket.dart'; 16 | import 'package:sqljocky/src/results.dart'; 17 | import 'package:test/test.dart'; 18 | 19 | import 'unit/buffered_socket_test.dart'; 20 | 21 | part '../lib/src/auth/auth_handler.dart'; 22 | part '../lib/src/auth/handshake_handler.dart'; 23 | part '../lib/src/auth/ssl_handler.dart'; 24 | part '../lib/src/auth/character_set.dart'; 25 | part '../lib/src/blob.dart'; 26 | part '../lib/src/connection.dart'; 27 | part '../lib/src/connection_pool.dart'; 28 | part '../lib/src/handlers/handler.dart'; 29 | part '../lib/src/handlers/ok_packet.dart'; 30 | part '../lib/src/handlers/use_db_handler.dart'; 31 | part '../lib/src/mysql_client_error.dart'; 32 | part '../lib/src/mysql_exception.dart'; 33 | part '../lib/src/mysql_protocol_error.dart'; 34 | part '../lib/src/prepared_statements/binary_data_packet.dart'; 35 | part '../lib/src/prepared_statements/prepare_ok_packet.dart'; 36 | part '../lib/src/prepared_statements/execute_query_handler.dart'; 37 | part '../lib/src/prepared_statements/prepared_query.dart'; 38 | part '../lib/src/prepared_statements/prepare_handler.dart'; 39 | part '../lib/src/results/field_impl.dart'; 40 | part '../lib/src/results/results_impl.dart'; 41 | part '../lib/src/query/query_stream_handler.dart'; 42 | part '../lib/src/query/result_set_header_packet.dart'; 43 | part '../lib/src/query/standard_data_packet.dart'; 44 | 45 | part 'unit/buffer_test.dart'; 46 | part 'unit/auth_handler_test.dart'; 47 | part 'unit/prepared_statements_test.dart'; 48 | part 'unit/serialize_test.dart'; 49 | part 'unit/types_test.dart'; 50 | part 'unit/field_by_name_test.dart'; 51 | part 'unit/binary_data_packet_test.dart'; 52 | part 'unit/execute_query_handler_test.dart'; 53 | part 'unit/handshake_handler_test.dart'; 54 | part 'unit/connection_test.dart'; 55 | 56 | void main() { 57 | runBufferTests(); 58 | runBufferedSocketTests(); 59 | runSerializationTests(); 60 | runTypesTests(); 61 | runPreparedStatementTests(); 62 | runAuthHandlerTests(); 63 | runFieldByNameTests(); 64 | runBinaryDataPacketTests(); 65 | runExecuteQueryHandlerTests(); 66 | runHandshakeHandlerTests(); 67 | runConnectionTests(); 68 | } 69 | -------------------------------------------------------------------------------- /tool/build_docs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPT=$(readlink -f $0) 3 | SCRIPTPATH=$(dirname $SCRIPT) 4 | cd $SCRIPTPATH/.. 5 | 6 | dartdoc --link-api --out ../sqljocky_pages/docs --package-root packages \ 7 | --exclude-lib logging,buffer,buffered_socket,crypto,unmodifiable_collection,constants \ 8 | lib/sqljocky.dart lib/constants.dart lib/utils.dart 9 | 10 | dartdoc --link-api --out docs --mode static --package-root packages \ 11 | --exclude-lib logging,buffer,buffered_socket,crypto,unmodifiable_collection,constants \ 12 | lib/sqljocky.dart lib/constants.dart lib/utils.dart 13 | --------------------------------------------------------------------------------