[
  {
    "path": ".gitattributes",
    "content": "/.gitattributes export-ignore\n/.github/ export-ignore\n/.gitignore export-ignore\n/examples/ export-ignore\n/phpunit.xml.dist export-ignore\n/phpunit.xml.legacy export-ignore\n/tests/ export-ignore\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n\njobs:\n  PHPUnit:\n    name: PHPUnit (PHP ${{ matrix.php }} + ${{ matrix.rdbms }})\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        rdbms:\n          - mysql:5\n        php:\n          - 8.4\n          - 8.3\n          - 8.2\n          - 8.1\n          - 8.0\n          - 7.4\n          - 7.3\n          - 7.2\n          - 7.1\n          - 7.0\n          - 5.6\n          - 5.5\n          - 5.4\n        include:\n          - php: 8.4\n            rdbms: mysql:9\n          - php: 8.4\n            rdbms: mysql:8\n          - php: 8.4\n            rdbms: mariadb:10\n    steps:\n      - uses: actions/checkout@v4\n      - uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          coverage: ${{ matrix.php < 8.0 && 'xdebug' || 'pcov' }}\n          ini-file: development\n      - run: composer install\n      - run: docker run -d --name mysql --net=host -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test -e MYSQL_USER=test -e MYSQL_PASSWORD=test ${{ matrix.rdbms }}\n      - run: bash tests/wait-for-mysql.sh\n      - run: vendor/bin/phpunit --coverage-text ${{ matrix.php < 7.3 && '-c phpunit.xml.legacy' || '' }}\n\n  PHPUnit-hhvm:\n    name: PHPUnit (HHVM)\n    runs-on: ubuntu-24.04\n    continue-on-error: true\n    steps:\n      - uses: actions/checkout@v4\n      - run: cp \"$(which composer)\" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM\n      - name: Run hhvm composer.phar install\n        uses: docker://hhvm/hhvm:3.30-lts-latest\n        with:\n          args: hhvm composer.phar install\n      - run: docker run -d --name mysql --net=host -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5\n      - run: bash tests/wait-for-mysql.sh\n      - run: docker run -i --rm --workdir=/data -v \"$(pwd):/data\" --net=host hhvm/hhvm:3.30-lts-latest hhvm vendor/bin/phpunit\n"
  },
  {
    "path": ".gitignore",
    "content": "/composer.lock\n/vendor/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 0.6.0 (2023-11-10)\n\n*   Feature: Improve Promise v3 support and use template types.\n    (#183 and #178 by @clue)\n\n*   Feature: Full PHP 8.3 compatibility.\n    (#180 by @clue)\n\n*   Feature / BC break: Update default charset encoding to `utf8mb4` for full UTF-8 support.\n    (#165 by @clue)\n\n    This feature updates the MySQL client to use `utf8mb4` as the default charset\n    encoding for full UTF-8 support instead of the legacy `utf8mb3` charset encoding.\n    For legacy reasons you can still change this to use a different ASCII-compatible\n    charset encoding like this:\n\n    ```php\n    $factory->createConnection('localhost?charset=utf8mb4');\n    ```\n\n*   Feature: Reduce default idle time to 1ms.\n    (#182 by @clue)\n\n    The idle time defines the time the client is willing to keep the underlying\n    connection alive before automatically closing it. The default idle time was\n    previously 60s and can be configured for more specific requirements like this:\n\n    ```php\n    $factory->createConnection('localhost?idle=10.0');\n    ```\n\n*   Minor documentation improvements.\n    (#184 by @yadaiio)\n\n*   Improve test suite, update to use reactphp/async and report failed assertions.\n    (#164 and #170 by @clue, #163 by @dinooo13 and #181 by @SimonFrings)\n\n## 0.5.7 (2022-09-15)\n\n*   Feature: Full support for PHP 8.2.\n    (#161 by @clue)\n\n*   Feature: Mark passwords and URIs as `#[\\SensitiveParameter]` (PHP 8.2+).\n    (#162 by @clue)\n\n*   Feature: Forward compatibility with upcoming Promise v3.\n    (#157 by @clue)\n\n*   Feature / Fix: Improve protocol parser, emit parser errors and close invalid connections.\n    (#158 and #159 by @clue)\n\n*   Improve test suite, fix legacy HHVM build by downgrading Composer.\n    (#160 by @clue)\n\n## 0.5.6 (2021-12-14)\n\n*   Feature: Support optional `charset` parameter for full UTF-8 support (`utf8mb4`).\n    (#135 by @clue)\n\n    ```php\n    $db = $factory->createLazyConnection('localhost?charset=utf8mb4');\n    ```\n\n*   Feature: Improve error reporting, include MySQL URI and socket error codes in all connection errors.\n    (#141 by @clue and #138 by @SimonFrings)\n\n    For most common use cases this means that simply reporting the `Exception`\n    message should give the most relevant details for any connection issues:\n\n    ```php\n    $db->query($sql)->then(function (React\\MySQL\\QueryResult $result) {\n        // …\n    }, function (Exception $e) {\n        echo 'Error:' . $e->getMessage() . PHP_EOL;\n    });\n    ```\n\n*   Feature: Full support for PHP 8.1 release.\n    (#150 by @clue)\n\n*   Feature: Provide limited support for `NO_BACKSLASH_ESCAPES` SQL mode.\n    (#139 by @clue)\n\n*   Update project dependencies, simplify socket usage, and improve documentation.\n    (#136 and #137 by @SimonFrings)\n\n*   Improve test suite and add `.gitattributes` to exclude dev files from exports.\n    Run tests on PHPUnit 9 and PHP 8 and clean up test suite.\n    (#142 and #143 by @SimonFrings)\n\n## 0.5.5 (2021-07-19)\n\n*   Feature: Simplify usage by supporting new default loop.\n    (#134 by @clue)\n\n    ```php\n    // old (still supported)\n    $factory = new React\\MySQL\\Factory($loop);\n\n    // new (using default loop)\n    $factory = new React\\MySQL\\Factory();\n    ```\n\n*   Improve test setup, use GitHub actions for continuous integration (CI) and fix minor typo.\n    (#132 by @SimonFrings and #129 by @mmoreram)\n\n## 0.5.4 (2019-05-21)\n\n*   Fix: Do not start idle timer when lazy connection is already closed.\n    (#110 by @clue)\n\n*   Fix: Fix explicit `close()` on lazy connection when connection is active.\n    (#109 by @clue)\n\n## 0.5.3 (2019-04-03)\n\n*   Fix: Ignore unsolicited server error when not executing any commands.\n    (#102 by @clue)\n\n*   Fix: Fix decoding URL-encoded special characters in credentials from database connection URI.\n    (#98 and #101 by @clue)\n\n## 0.5.2 (2019-02-05)\n\n*   Fix: Fix `ConnectionInterface` return type hint in `Factory`.\n    (#93 by @clue)\n\n*   Minor documentation typo fix and improve test suite to test against PHP 7.3,\n    add forward compatibility with PHPUnit 7 and use legacy PHPUnit 5 on HHVM.\n    (#92 and #94 by @clue)\n\n## 0.5.1 (2019-01-12)\n\n*   Fix: Fix \"bad handshake\" error when connecting without database name.\n    (#91 by @clue)\n\n## 0.5.0 (2018-11-28)\n\nA major feature release with a significant API improvement!\n\nThis update does not involve any BC breaks, but we figured the new API provides\nsignificant features that warrant a major version bump. Existing code will\ncontinue to work without changes, but you're highly recommended to consider\nusing the new lazy connections as detailed below.\n\n*   Feature: Add new `createLazyConnection()` method to only connect on demand and\n    implement \"idle\" timeout to close underlying connection when unused.\n    (#87 and #88 by @clue)\n\n    ```php\n    // new\n    $connection = $factory->createLazyConnection($url);\n    $connection->query(…);\n    ```\n\n    This method immediately returns a \"virtual\" connection implementing the\n    [`ConnectionInterface`](README.md#connectioninterface) that can be used to\n    interface with your MySQL database. Internally, it lazily creates the\n    underlying database connection only on demand once the first request is\n    invoked on this instance and will queue all outstanding requests until\n    the underlying connection is ready. Additionally, it will only keep this\n    underlying connection in an \"idle\" state for 60s by default and will\n    automatically end the underlying connection when it is no longer needed.\n\n    From a consumer side this means that you can start sending queries to the\n    database right away while the underlying connection may still be\n    outstanding. Because creating this underlying connection may take some\n    time, it will enqueue all outstanding commands and will ensure that all\n    commands will be executed in correct order once the connection is ready.\n    In other words, this \"virtual\" connection behaves just like a \"real\"\n    connection as described in the `ConnectionInterface` and frees you from\n    having to deal with its async resolution.\n\n*   Feature: Support connection timeouts.\n    (#86 by @clue)\n\n## 0.4.1 (2018-10-18)\n\n*   Feature: Support cancellation of pending connection attempts.\n    (#84 by @clue)\n\n*   Feature: Add `warningCount` to `QueryResult`.\n    (#82 by @legionth)\n\n*   Feature: Add exception message for invalid MySQL URI.\n    (#80 by @CharlotteDunois)\n\n*   Fix: Fix parsing error message during handshake (Too many connections).\n    (#83 by @clue)\n\n## 0.4.0 (2018-09-21)\n\nA major feature release with a significant documentation overhaul and long overdue API cleanup!\n\nThis update involves a number of BC breaks due to various changes to make the\nAPI more consistent with the ReactPHP ecosystem. In particular, this now uses\npromises consistently as return values instead of accepting callback functions\nand this now offers an additional streaming API for processing very large result\nsets efficiently.\n\nWe realize that the changes listed below may seem a bit overwhelming, but we've\ntried to be very clear about any possible BC breaks. See below for changes you\nhave to take care of when updating from an older version.\n\n*   Feature / BC break: Add Factory to simplify connecting and keeping connection state,\n    mark `Connection` class as internal and remove `connect()` method.\n    (#64 by @clue)\n\n    ```php\n    // old\n    $connection = new Connection($loop, $options);\n    $connection->connect(function (?Exception $error, $connection) {\n        if ($error) {\n            // an error occurred while trying to connect or authorize client\n        } else {\n            // client connection established (and authenticated)\n        }\n    });\n\n    // new\n    $factory = new Factory($loop);\n    $factory->createConnection($url)->then(\n        function (ConnectionInterface $connection) {\n            // client connection established (and authenticated)\n        },\n        function (Exception $e) {\n            // an error occurred while trying to connect or authorize client\n        }\n    );\n    ```\n\n*   Feature / BC break: Use promises for `query()` method and resolve with `QueryResult` on success and\n    and mark all commands as internal and move its base to Commands namespace.\n    (#61 and #62 by @clue)\n\n    ```php\n    // old\n    $connection->query('CREATE TABLE test');\n    $connection->query('DELETE FROM user WHERE id < ?', $id);\n    $connection->query('SELECT * FROM user', function (QueryCommand $command) {\n        if ($command->hasError()) {\n            echo 'Error: ' . $command->getError()->getMessage() . PHP_EOL;\n        } elseif (isset($command->resultRows)) {\n            var_dump($command->resultRows);\n        }\n    });\n\n    // new\n    $connection->query('CREATE TABLE test');\n    $connection->query('DELETE FROM user WHERE id < ?', [$id]);\n    $connection->query('SELECT * FROM user')->then(function (QueryResult $result) {\n        var_dump($result->resultRows);\n    }, function (Exception $error) {\n        echo 'Error: ' . $error->getMessage() . PHP_EOL;\n    });\n    ```\n\n*   Feature / BC break: Add new `queryStream()` method to stream result set rows and\n    remove undocumented \"results\" event.\n    (#57 and #77 by @clue)\n\n    ```php\n    $stream = $connection->queryStream('SELECT * FROM users');\n\n    $stream->on('data', function ($row) {\n        var_dump($row);\n    });\n    $stream->on('end', function () {\n        echo 'DONE' . PHP_EOL;\n    });\n    ```\n\n*   Feature / BC break: Rename `close()` to `quit()`, use promises for `quit()` method and\n    add new `close()` method to force-close the connection.\n    (#65 and #76 by @clue)\n\n    ```php\n    // old: soft-close/quit\n    $connection->close(function () {\n        echo 'closed';\n    });\n\n    // new: soft-close/quit\n    $connection->quit()->then(function () {\n        echo 'closed';\n    });\n\n    // new: force-close\n    $connection->close();\n    ```\n\n*   Feature / BC break: Use promises for `ping()` method and resolve with void value on success.\n    (#63 and #66 by @clue)\n\n    ```php\n    // old\n    $connection->ping(function ($error, $connection) {\n        if ($error) {\n            echo 'Error: ' . $error->getMessage() . PHP_EOL;\n        } else {\n            echo 'OK' . PHP_EOL;\n        }\n    });\n\n    // new \n    $connection->ping(function () {\n        echo 'OK' . PHP_EOL;\n    }, function (Exception $error) {\n        echo 'Error: ' . $error->getMessage() . PHP_EOL;\n    });\n    ```\n\n*   Feature / BC break: Define events on ConnectionInterface\n    (#78 by @clue)\n\n*   BC break: Remove unneeded `ConnectionInterface` methods `getState()`,\n    `getOptions()`, `setOptions()` and `getServerOptions()`, `selectDb()` and `listFields()` dummy.\n    (#60 and #68 by @clue)\n\n*   BC break: Mark all protocol logic classes as internal and move to new Io namespace.\n    (#53 and #62 by @clue)\n\n*   Fix: Fix executing queued commands in the order they are enqueued\n    (#75 by @clue)\n\n*   Fix: Fix reading all incoming response packets until end\n    (#59 by @clue)\n\n*   [maintenance] Internal refactoring to simplify connection and authentication logic\n    (#69 by @clue)\n*   [maintenance] Internal refactoring to remove unneeded references from Commands\n    (#67 by @clue)\n*   [maintenance] Internal refactoring to remove unneeded EventEmitter implementation and circular references\n    (#56 by @clue)\n*   [maintenance] Refactor internal parsing logic to separate Buffer class, remove dead code and improve performance\n    (#54 by @clue)\n\n## 0.3.3 (2018-06-18)\n\n*   Fix: Reject pending commands if connection is closed\n    (#52 by @clue)\n\n*   Fix: Do not support multiple statements for security and API reasons\n    (#51 by @clue)\n\n*   Fix: Fix reading empty rows containing only empty string columns \n    (#46 by @clue)\n\n*   Fix: Report correct field length for fields longer than 16k chars\n    (#42 by @clue)\n\n*   Add quickstart example and interactive CLI example\n    (#45 by @clue)\n\n## 0.3.2 (2018-04-04)\n\n*   Fix: Fix parameter binding if query contains question marks\n    (#40 by @clue)\n\n*   Improve test suite by simplifying test structure, improve test isolation and remove dbunit\n    (#39 by @clue)\n\n## 0.3.1 (2018-03-26)\n\n*   Feature: Forward compatibility with upcoming ReactPHP components\n    (#37 by @clue)\n\n*   Fix: Consistent `connect()` behavior for all connection states\n    (#36 by @clue)\n\n*   Fix: Report connection error to `connect()` callback\n    (#35 by @clue)\n\n## 0.3.0 (2018-03-13)\n\n*   This is now a community project managed by @friends-of-reactphp. Thanks to\n    @bixuehujin for releasing this project under MIT license and handing over!\n    (#12 and #33 by @bixuehujin and @clue)\n\n*   Feature / BC break: Update react/socket to v0.8.0\n    (#21 by @Fneufneu)\n\n*   Feature: Support passing custom connector and\n    load system default DNS config by default\n    (#24 by @flow-control and #30 by @clue)\n\n*   Feature: Add `ConnectionInterface` with documentation\n    (#26 by @freedemster)\n\n*   Fix: Last query param is lost if no callback is given\n    (#22 by @Fneufneu)\n\n*   Fix: Fix memory increase (memory leak due to keeping incoming receive buffer)\n    (#17 by @sukui)\n\n*   Improve test suite by adding test instructions and adding Travis CI\n    (#34 by @clue and #25 by @freedemster)\n\n*   Improve documentation\n    (#8 by @ovr and #10 by @RafaelKa)\n\n## 0.2.0 (2014-10-15)\n\n*   Now compatible with ReactPHP v0.4\n\n## 0.1.0 (2014-02-18)\n\n*   First tagged release (ReactPHP v0.3)\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Jin Hu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# MySQL\n\n[![CI status](https://github.com/friends-of-reactphp/mysql/actions/workflows/ci.yml/badge.svg)](https://github.com/friends-of-reactphp/mysql/actions)\n\nAsync MySQL database client for [ReactPHP](https://reactphp.org/).\n\n> **Development version:** This branch contains the code for the upcoming\n> version 0.7 release. For the code of the current stable version 0.6 release, check\n> out the [`0.6.x` branch](https://github.com/friends-of-reactphp/mysql/tree/0.6.x).\n>\n> The upcoming version 0.7 release will be the way forward for this package.\n> However, we will still actively support version 0.6 for those not yet on the\n> latest version.\n> See also [installation instructions](#install) for more details.\n\nThis is a MySQL database driver for [ReactPHP](https://reactphp.org/).\nIt implements the MySQL protocol and allows you to access your existing MySQL\ndatabase.\nIt is written in pure PHP and does not require any extensions.\n\n**Table of contents**\n\n* [Quickstart example](#quickstart-example)\n* [Usage](#usage)\n  * [MysqlClient](#mysqlclient)\n    * [__construct()](#__construct)\n    * [query()](#query)\n    * [queryStream()](#querystream)\n    * [ping()](#ping)\n    * [quit()](#quit)\n    * [close()](#close)\n    * [error event](#error-event)\n    * [close event](#close-event)\n* [Install](#install)\n* [Tests](#tests)\n* [License](#license)\n\n## Quickstart example\n\nThis example runs a simple `SELECT` query and dumps all the records from a `book` table:\n\n```php\n<?php\n\nrequire __DIR__ . '/vendor/autoload.php';\n\n$mysql = new React\\Mysql\\MysqlClient('user:pass@localhost/bookstore');\n\n$mysql->query('SELECT * FROM book')->then(\n    function (React\\Mysql\\MysqlResult $command) {\n        print_r($command->resultFields);\n        print_r($command->resultRows);\n        echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;\n    },\n    function (Exception $error) {\n        echo 'Error: ' . $error->getMessage() . PHP_EOL;\n    }\n);\n```\n\nSee also the [examples](examples).\n\n## Usage\n\n### MysqlClient\n\nThe `MysqlClient` is responsible for exchanging messages with your MySQL server\nand keeps track of pending queries.\n\n```php\n$mysql = new React\\Mysql\\MysqlClient($uri);\n\n$mysql->query(…);\n```\n\nThis class represents a connection that is responsible for communicating\nwith your MySQL server instance, managing the connection state and sending\nyour database queries. Internally, it creates the underlying database\nconnection only on demand once the first request is invoked on this\ninstance and will queue all outstanding requests until the underlying\nconnection is ready. This underlying connection will be reused for all\nrequests until it is closed. By default, idle connections will be held\nopen for 1ms (0.001s) when not used. The next request will either reuse\nthe existing connection or will automatically create a new underlying\nconnection if this idle time is expired.\n\nFrom a consumer side this means that you can start sending queries to the\ndatabase right away while the underlying connection may still be\noutstanding. Because creating this underlying connection may take some\ntime, it will enqueue all outstanding commands and will ensure that all\ncommands will be executed in correct order once the connection is ready.\n\nIf the underlying database connection fails, it will reject all\noutstanding commands and will return to the initial \"idle\" state. This\nmeans that you can keep sending additional commands at a later time which\nwill again try to open a new underlying connection. Note that this may\nrequire special care if you're using transactions that are kept open for\nlonger than the idle period.\n\nNote that creating the underlying connection will be deferred until the\nfirst request is invoked. Accordingly, any eventual connection issues\nwill be detected once this instance is first used. You can use the\n`quit()` method to ensure that the connection will be soft-closed\nand no further commands can be enqueued. Similarly, calling `quit()` on\nthis instance when not currently connected will succeed immediately and\nwill not have to wait for an actual underlying connection.\n\n#### __construct()\n\nThe `new MysqlClient(string $uri, ?ConnectorInterface $connector = null, ?LoopInterface $loop = null)` constructor can be used to\ncreate a new `MysqlClient` instance.\n\nThe `$uri` parameter must contain the database host, optional\nauthentication, port and database to connect to:\n\n```php\n$mysql = new React\\Mysql\\MysqlClient('user:secret@localhost:3306/database');\n```\n\nNote that both the username and password must be URL-encoded (percent-encoded)\nif they contain special characters:\n\n```php\n$user = 'he:llo';\n$pass = 'p@ss';\n\n$mysql = new React\\Mysql\\MysqlClient(\n    rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost:3306/db'\n);\n```\n\nYou can omit the port if you're connecting to default port `3306`:\n\n```php\n$mysql = new React\\Mysql\\MysqlClient('user:secret@localhost/database');\n```\n\nIf you do not include authentication and/or database, then this method\nwill default to trying to connect as user `root` with an empty password\nand no database selected. This may be useful when initially setting up a\ndatabase, but likely to yield an authentication error in a production system:\n\n```php\n$mysql = new React\\Mysql\\MysqlClient('localhost');\n```\n\nThis method respects PHP's `default_socket_timeout` setting (default 60s)\nas a timeout for establishing the underlying connection and waiting for\nsuccessful authentication. You can explicitly pass a custom timeout value\nin seconds (or use a negative number to not apply a timeout) like this:\n\n```php\n$mysql = new React\\Mysql\\MysqlClient('localhost?timeout=0.5');\n```\n\nBy default, idle connections will be held open for 1ms (0.001s) when not\nused. The next request will either reuse the existing connection or will\nautomatically create a new underlying connection if this idle time is\nexpired. This ensures you always get a \"fresh\" connection and as such\nshould not be confused with a \"keepalive\" or \"heartbeat\" mechanism, as\nthis will not actively try to probe the connection. You can explicitly\npass a custom idle timeout value in seconds (or use a negative number to\nnot apply a timeout) like this:\n\n```php\n$mysql = new React\\Mysql\\MysqlClient('localhost?idle=10.0');\n```\n\nBy default, the connection provides full UTF-8 support (using the\n`utf8mb4` charset encoding). This should usually not be changed for most\napplications nowadays, but for legacy reasons you can change this to use\na different ASCII-compatible charset encoding like this:\n\n```php\n$mysql = new React\\Mysql\\MysqlClient('localhost?charset=utf8mb4');\n```\n\nIf you need custom connector settings (DNS resolution, TLS parameters, timeouts,\nproxy servers etc.), you can explicitly pass a custom instance of the\n[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):\n\n```php\n$connector = new React\\Socket\\Connector([\n    'dns' => '127.0.0.1',\n    'tcp' => [\n        'bindto' => '192.168.10.1:0'\n    ],\n    'tls' => [\n        'verify_peer' => false,\n        'verify_peer_name' => false\n    )\n]);\n\n$mysql = new React\\Mysql\\MysqlClient('user:secret@localhost:3306/database', $connector);\n```\n\nThis class takes an optional `LoopInterface|null $loop` parameter that can be used to\npass the event loop instance to use for this object. You can use a `null` value\nhere in order to use the [default loop](https://github.com/reactphp/event-loop#loop).\nThis value SHOULD NOT be given unless you're sure you want to explicitly use a\ngiven event loop instance.\n\n#### query()\n\nThe `query(string $query, list<string|int|float|bool|null> $params = []): PromiseInterface<MysqlResult>` method can be used to\nperform an async query.\n\nThis method returns a promise that will resolve with a `MysqlResult` on\nsuccess or will reject with an `Exception` on error. The MySQL protocol\nis inherently sequential, so that all queries will be performed in order\nand outstanding queries will be put into a queue to be executed once the\nprevious queries are completed.\n\n```php\n$mysql->query('CREATE TABLE test ...');\n$mysql->query('INSERT INTO test (id) VALUES (1)');\n```\n\nIf this SQL statement returns a result set (such as from a `SELECT`\nstatement), this method will buffer everything in memory until the result\nset is completed and will then resolve the resulting promise. This is\nthe preferred method if you know your result set to not exceed a few\ndozens or hundreds of rows. If the size of your result set is either\nunknown or known to be too large to fit into memory, you should use the\n[`queryStream()`](#querystream) method instead.\n\n```php\n$mysql->query($query)->then(function (React\\Mysql\\MysqlResult $command) {\n    if (isset($command->resultRows)) {\n        // this is a response to a SELECT etc. with some rows (0+)\n        print_r($command->resultFields);\n        print_r($command->resultRows);\n        echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;\n    } else {\n        // this is an OK message in response to an UPDATE etc.\n        if ($command->insertId !== 0) {\n            var_dump('last insert ID', $command->insertId);\n        }\n        echo 'Query OK, ' . $command->affectedRows . ' row(s) affected' . PHP_EOL;\n    }\n}, function (Exception $error) {\n    // the query was not executed successfully\n    echo 'Error: ' . $error->getMessage() . PHP_EOL;\n});\n```\n\nYou can optionally pass an array of `$params` that will be bound to the\nquery like this:\n\n```php\n$mysql->query('SELECT * FROM user WHERE id > ?', [$id]);\n```\n\nThe given `$sql` parameter MUST contain a single statement. Support\nfor multiple statements is disabled for security reasons because it\ncould allow for possible SQL injection attacks and this API is not\nsuited for exposing multiple possible results.\n\n#### queryStream()\n\nThe `queryStream(string $sql, list<string|int|float|bool|null> $params = []): ReadableStreamInterface` method can be used to\nperform an async query and stream the rows of the result set.\n\nThis method returns a readable stream that will emit each row of the\nresult set as a `data` event. It will only buffer data to complete a\nsingle row in memory and will not store the whole result set. This allows\nyou to process result sets of unlimited size that would not otherwise fit\ninto memory. If you know your result set to not exceed a few dozens or\nhundreds of rows, you may want to use the [`query()`](#query) method instead.\n\n```php\n$stream = $mysql->queryStream('SELECT * FROM user');\n$stream->on('data', function ($row) {\n    echo $row['name'] . PHP_EOL;\n});\n$stream->on('end', function () {\n    echo 'Completed.';\n});\n```\n\nYou can optionally pass an array of `$params` that will be bound to the\nquery like this:\n\n```php\n$stream = $mysql->queryStream('SELECT * FROM user WHERE id > ?', [$id]);\n```\n\nThis method is specifically designed for queries that return a result set\n(such as from a `SELECT` or `EXPLAIN` statement). Queries that do not\nreturn a result set (such as a `UPDATE` or `INSERT` statement) will not\nemit any `data` events.\n\nSee also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)\nfor more details about how readable streams can be used in ReactPHP. For\nexample, you can also use its `pipe()` method to forward the result set\nrows to a [`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface)\nlike this:\n\n```php\n$mysql->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger);\n```\n\nNote that as per the underlying stream definition, calling `pause()` and\n`resume()` on this stream is advisory-only, i.e. the stream MAY continue\nemitting some data until the underlying network buffer is drained. Also\nnotice that the server side limits how long a connection is allowed to be\nin a state that has outgoing data. Special care should be taken to ensure\nthe stream is resumed in time. This implies that using `pipe()` with a\nslow destination stream may cause the connection to abort after a while.\n\nThe given `$sql` parameter MUST contain a single statement. Support\nfor multiple statements is disabled for security reasons because it\ncould allow for possible SQL injection attacks and this API is not\nsuited for exposing multiple possible results.\n\n#### ping()\n\nThe `ping(): PromiseInterface<void>` method can be used to\ncheck that the connection is alive.\n\nThis method returns a promise that will resolve (with a void value) on\nsuccess or will reject with an `Exception` on error. The MySQL protocol\nis inherently sequential, so that all commands will be performed in order\nand outstanding command will be put into a queue to be executed once the\nprevious queries are completed.\n\n```php\n$mysql->ping()->then(function () {\n    echo 'OK' . PHP_EOL;\n}, function (Exception $e) {\n    echo 'Error: ' . $e->getMessage() . PHP_EOL;\n});\n```\n\n#### quit()\n\nThe `quit(): PromiseInterface<void>` method can be used to\nquit (soft-close) the connection.\n\nThis method returns a promise that will resolve (with a void value) on\nsuccess or will reject with an `Exception` on error. The MySQL protocol\nis inherently sequential, so that all commands will be performed in order\nand outstanding commands will be put into a queue to be executed once the\nprevious commands are completed.\n\n```php\n$mysql->query('CREATE TABLE test ...');\n$mysql->quit();\n```\n\nThis method will gracefully close the connection to the MySQL database\nserver once all outstanding commands are completed. See also\n[`close()`](#close) if you want to force-close the connection without\nwaiting for any commands to complete instead.\n\n#### close()\n\nThe `close(): void` method can be used to\nforce-close the connection.\n\nUnlike the `quit()` method, this method will immediately force-close the\nconnection and reject all outstanding commands.\n\n```php\n$mysql->close();\n```\n\nForcefully closing the connection will yield a warning in the server logs\nand should generally only be used as a last resort. See also\n[`quit()`](#quit) as a safe alternative.\n\n#### error event\n\nThe `error` event will be emitted once a fatal error occurs, such as\nwhen the connection is lost or is invalid.\nThe event receives a single `Exception` argument for the error instance.\n\n```php\n$mysql->on('error', function (Exception $e) {\n    echo 'Error: ' . $e->getMessage() . PHP_EOL;\n});\n```\n\nThis event will only be triggered for fatal errors and will be followed\nby closing the connection. It is not to be confused with \"soft\" errors\ncaused by invalid SQL queries.\n\n#### close event\n\nThe `close` event will be emitted once the connection closes (terminates).\n\n```php\n$mysql->on('close', function () {\n    echo 'Connection closed' . PHP_EOL;\n});\n```\n\nSee also the [`close()`](#close) method.\n\n## Install\n\nThe recommended way to install this library is [through Composer](https://getcomposer.org/).\n[New to Composer?](https://getcomposer.org/doc/00-intro.md)\n\nOnce released, this project will follow [SemVer](https://semver.org/).\nAt the moment, this will install the latest development version:\n\n```bash\ncomposer require react/mysql:^0.7@dev\n```\n\nSee also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.\n\nThis project aims to run on any platform and thus does not require any PHP\nextensions and supports running on legacy PHP 5.4 through current PHP 8+ and\nHHVM.\nIt's *highly recommended to use the latest supported PHP version* for this project.\n\nThis project supports connecting to a variety of MySQL database versions and\ncompatible projects using the MySQL protocol. The `caching_sha2_password`\nauthentication plugin (default in MySQL 8+) requires PHP 7.1+ and `ext-openssl`\nto be installed, while the older `mysql_native_password` authentication plugin\n(default in MySQL 5.7) is supported across all supported PHP versions.\n\n## Tests\n\nTo run the test suite, you first need to clone this repo and then install all\ndependencies [through Composer](https://getcomposer.org/):\n\n```bash\ncomposer install\n```\n\nThe test suite contains a number of functional integration tests that send\nactual test SQL queries against your local database and thus rely on a local\nMySQL test database with appropriate write access.\nThe test suite creates and modifies a test table in this database, so make sure\nto not use a production database!\nYou can change your test database credentials by passing these ENV variables:\n\n```bash\nexport DB_HOST=localhost\nexport DB_PORT=3306\nexport DB_USER=test\nexport DB_PASSWD=test\nexport DB_DBNAME=test\n```\n\nFor example, to create an empty test database, you can also use a temporary\n[`mysql` Docker image](https://hub.docker.com/_/mysql/) like this:\n\n```bash\ndocker run -it --rm --net=host \\\n    -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test \\\n    -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5\n```\n\nTo run the test suite, go to the project root and run:\n\n```bash\nvendor/bin/phpunit\n```\n\n## License\n\nMIT, see [LICENSE file](LICENSE).\n\nThis is a community project now managed by\n[@friends-of-reactphp](https://github.com/friends-of-reactphp).\nThe original implementation was created by\n[@bixuehujin](https://github.com/bixuehujin) starting in 2013 and has been\nmigrated to [@friends-of-reactphp](https://github.com/friends-of-reactphp) in\n2018 to help with maintenance and upcoming feature development.\n\nThe original implementation was made possible thanks to the following projects:\n\n* [phpdaemon](https://github.com/kakserpom/phpdaemon): the MySQL protocol\n  implementation is based on code of this project (with permission).\n* [node-mysql](https://github.com/felixge/node-mysql): the API design is\n  inspired by this project.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"react/mysql\",\n    \"description\": \"Async MySQL database client for ReactPHP.\",\n    \"keywords\": [\"mysql\", \"database\", \"async\", \"reactphp\"],\n    \"license\": \"MIT\",\n    \"require\": {\n        \"php\": \">=5.4.0\",\n        \"evenement/evenement\": \"^3.0 || ^2.1 || ^1.1\",\n        \"react/event-loop\": \"^1.2\",\n        \"react/promise\": \"^3.2 || ^2.7\",\n        \"react/promise-stream\": \"^1.6\",\n        \"react/promise-timer\": \"^1.11\",\n        \"react/socket\": \"^1.16\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^9.6 || ^8.5 || ^5.7 || ^4.8.36\",\n        \"react/async\": \"^4.3 || ^3 || ^2\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"React\\\\Mysql\\\\\": \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"React\\\\Tests\\\\Mysql\\\\\": \"tests/\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-query.php",
    "content": "<?php\n\n// $ php examples/01-query.php\n// $ MYSQL_URI=test:test@localhost/test php examples/01-query.php \"SELECT * FROM book\"\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$mysql = new React\\Mysql\\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test');\n\n$query = isset($argv[1]) ? $argv[1] : 'select * from book';\n$mysql->query($query)->then(function (React\\Mysql\\MysqlResult $command) {\n    if (isset($command->resultRows)) {\n        // this is a response to a SELECT etc. with some rows (0+)\n        print_r($command->resultFields);\n        print_r($command->resultRows);\n        echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;\n    } else {\n        // this is an OK message in response to an UPDATE etc.\n        if ($command->insertId !== 0) {\n            var_dump('last insert ID', $command->insertId);\n        }\n        echo 'Query OK, ' . $command->affectedRows . ' row(s) affected' . PHP_EOL;\n    }\n}, function (Exception $error) {\n    // the query was not executed successfully\n    echo 'Error: ' . $error->getMessage() . PHP_EOL;\n});\n"
  },
  {
    "path": "examples/02-query-stream.php",
    "content": "<?php\n\n// $ php examples/02-query-stream.php \"SHOW VARIABLES\"\n// $ MYSQL_URI=test:test@localhost/test php examples/02-query-stream.php \"SELECT * FROM book\"\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$mysql = new React\\Mysql\\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test');\n\n$query = isset($argv[1]) ? $argv[1] : 'select * from book';\n$stream = $mysql->queryStream($query);\n\n$stream->on('data', function ($row) {\n    echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;\n});\n\n$stream->on('error', function (Exception $e) {\n    echo 'Error: ' . $e->getMessage() . PHP_EOL;\n});\n\n$stream->on('close', function () {\n    echo 'CLOSED' . PHP_EOL;\n});\n"
  },
  {
    "path": "examples/11-interactive.php",
    "content": "<?php\n\n// $ php examples/11-interactive.php\n// $ MYSQL_URI=test:test@localhost/test php examples/11-interactive.php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$mysql = new React\\Mysql\\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test');\n\n// open a STDIN stream to read keyboard input (not supported on Windows)\n$stdin = new React\\Stream\\ReadableResourceStream(STDIN);\n\n$stdin->on('data', function ($line) use ($mysql) {\n    $query = trim($line);\n\n    if ($query === '') {\n        // skip empty commands\n        return;\n    }\n    if ($query === 'exit') {\n        // exit command should close the connection\n        echo 'bye.' . PHP_EOL;\n        $mysql->quit();\n        return;\n    }\n\n    $time = microtime(true);\n    $mysql->query($query)->then(function (React\\Mysql\\MysqlResult $command) use ($time) {\n        if (isset($command->resultRows)) {\n            // this is a response to a SELECT etc. with some rows (0+)\n            echo implode(\"\\t\", array_column($command->resultFields, 'name')) . PHP_EOL;\n            foreach ($command->resultRows as $row) {\n                echo implode(\"\\t\", $row) . PHP_EOL;\n            }\n\n            printf(\n                '%d row%s in set (%.03f sec)%s',\n                count($command->resultRows),\n                count($command->resultRows) === 1 ? '' : 's',\n                microtime(true) - $time,\n                PHP_EOL\n            );\n        } else {\n            // this is an OK message in response to an UPDATE etc.\n            // the insertId will only be set if this is\n            if ($command->insertId !== 0) {\n                var_dump('last insert ID', $command->insertId);\n            }\n\n            printf(\n                'Query OK, %d row%s affected (%.03f sec)%s',\n                $command->affectedRows,\n                $command->affectedRows === 1 ? '' : 's',\n                microtime(true) - $time,\n                PHP_EOL\n            );\n        }\n    }, function (Exception $error) {\n        // the query was not executed successfully\n        echo 'Error: ' . $error->getMessage() . PHP_EOL;\n    });\n});\n\n// close connection when STDIN closes (EOF or CTRL+D)\n$stdin->on('close', function () use ($mysql) {\n    $mysql->quit();\n});\n\n// close STDIN (stop reading) when connection closes\n$mysql->on('close', function () use ($stdin) {\n    $stdin->close();\n    echo 'Disconnected.' . PHP_EOL;\n});\n\necho '# Entering interactive mode ready, hit CTRL-D to quit' . PHP_EOL;\n"
  },
  {
    "path": "examples/12-slow-stream.php",
    "content": "<?php\n\n// $ php examples/12-slow-stream.php \"SHOW VARIABLES\"\n// $ MYSQL_URI=test:test@localhost/test php examples/12-slow-stream.php \"SELECT * FROM book\"\n\nuse React\\EventLoop\\Loop;\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$mysql = new React\\Mysql\\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test');\n\n$query = isset($argv[1]) ? $argv[1] : 'select * from book';\n$stream = $mysql->queryStream($query);\n\n$ref = new ReflectionProperty($mysql, 'connecting');\n$ref->setAccessible(true);\n$promise = $ref->getValue($mysql);\nassert($promise instanceof React\\Promise\\PromiseInterface);\n\n$promise->then(function (React\\Mysql\\Io\\Connection $connection) {\n    // The protocol parser reads rather large chunks from the underlying connection\n    // and as such can yield multiple (dozens to hundreds) rows from a single data\n    // chunk. We try to artificially limit the stream chunk size here to try to\n    // only ever read a single row so we can demonstrate throttling this stream.\n    // It goes without saying this is only a hack! Real world applications rarely\n    // have the need to limit the chunk size. As an alternative, consider using\n    // a stream decorator that rate-limits and buffers the resulting flow.\n    try {\n        // accept private \"stream\" (instanceof React\\Socket\\ConnectionInterface)\n        $ref = new ReflectionProperty($connection, 'stream');\n        $ref->setAccessible(true);\n        $conn = $ref->getValue($connection);\n        assert($conn instanceof React\\Socket\\ConnectionInterface);\n\n        // access private \"input\" (instanceof React\\Stream\\DuplexStreamInterface)\n        $ref = new ReflectionProperty($conn, 'input');\n        $ref->setAccessible(true);\n        $stream = $ref->getValue($conn);\n        assert($stream instanceof React\\Stream\\DuplexStreamInterface);\n\n        // reduce private bufferSize to just a few bytes to slow things down\n        $ref = new ReflectionProperty($stream, 'bufferSize');\n        $ref->setAccessible(true);\n        $ref->setValue($stream, 8);\n    } catch (Exception $e) {\n        echo 'Warning: Unable to reduce buffer size: ' . $e->getMessage() . PHP_EOL;\n    }\n});\n\n$throttle = null;\n$stream->on('data', function ($row) use (&$throttle, $stream) {\n    echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;\n\n    // simple throttle mechanism: explicitly pause the result stream and\n    // resume it again after some time.\n    if ($throttle === null) {\n        $throttle = Loop::addTimer(1.0, function () use ($stream, &$throttle) {\n            $throttle = null;\n            $stream->resume();\n        });\n        $stream->pause();\n    }\n});\n\n$stream->on('error', function (Exception $e) {\n    echo 'Error: ' . $e->getMessage() . PHP_EOL;\n});\n\n$stream->on('close', function () use (&$throttle) {\n    echo 'CLOSED' . PHP_EOL;\n\n    if ($throttle) {\n        Loop::cancelTimer($throttle);\n        $throttle = null;\n    }\n});\n\n$mysql->quit();\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!-- PHPUnit configuration file with new format for PHPUnit 9.6+ -->\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.6/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         cacheResult=\"false\"\n         colors=\"true\"\n         convertDeprecationsToExceptions=\"true\">\n    <testsuites>\n        <testsuite name=\"React.MySQL Test Suite\">\n            <directory>./tests/</directory>\n        </testsuite>\n    </testsuites>\n    <coverage>\n        <include>\n            <directory>./src/</directory>\n        </include>\n    </coverage>\n    <php>\n        <env name=\"DB_HOST\" value=\"127.0.0.1\"/>\n        <env name=\"DB_PORT\" value=\"3306\"/>\n        <env name=\"DB_USER\" value=\"test\"/>\n        <env name=\"DB_PASSWD\" value=\"test\"/>\n        <env name=\"DB_DBNAME\" value=\"test\"/>\n        <ini name=\"error_reporting\" value=\"-1\" />\n        <!-- Evaluate assertions, requires running with \"php -d zend.assertions=1 vendor/bin/phpunit\" -->\n        <!-- <ini name=\"zend.assertions\" value=\"1\" /> -->\n        <ini name=\"assert.active\" value=\"1\" />\n        <ini name=\"assert.exception\" value=\"1\" />\n        <ini name=\"assert.bail\" value=\"0\" />\n    </php>\n</phpunit>\n"
  },
  {
    "path": "phpunit.xml.legacy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!-- PHPUnit configuration file with old format for legacy PHPUnit -->\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/4.8/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\">\n    <testsuites>\n        <testsuite name=\"React.MySQL Test Suite\">\n            <directory>./tests/</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory>./src/</directory>\n        </whitelist>\n    </filter>\n    <php>\n        <env name=\"DB_HOST\" value=\"127.0.0.1\"/>\n        <env name=\"DB_PORT\" value=\"3306\"/>\n        <env name=\"DB_USER\" value=\"test\"/>\n        <env name=\"DB_PASSWD\" value=\"test\"/>\n        <env name=\"DB_DBNAME\" value=\"test\"/>\n        <ini name=\"error_reporting\" value=\"-1\" />\n        <!-- Evaluate assertions, requires running with \"php -d zend.assertions=1 vendor/bin/phpunit\" -->\n        <!-- <ini name=\"zend.assertions\" value=\"1\" /> -->\n        <ini name=\"assert.active\" value=\"1\" />\n        <ini name=\"assert.exception\" value=\"1\" />\n        <ini name=\"assert.bail\" value=\"0\" />\n    </php>\n</phpunit>\n"
  },
  {
    "path": "src/Commands/AbstractCommand.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Commands;\n\nuse Evenement\\EventEmitter;\n\n/**\n * @internal\n */\nabstract class AbstractCommand extends EventEmitter implements CommandInterface\n{\n    /**\n     * (none, this is an internal thread state)\n     */\n    const SLEEP = 0x00;\n    /**\n     * mysql_close\n     */\n    const QUIT = 0x01;\n    /**\n     * mysql_select_db\n     */\n    const INIT_DB = 0x02;\n    /**\n     * mysql_real_query\n     */\n    const QUERY = 0x03;\n    /**\n     * mysql_list_fields\n     */\n    const FIELD_LIST = 0x04;\n    /**\n     * mysql_create_db (deprecated)\n     */\n    const CREATE_DB = 0x05;\n    /**\n     * mysql_drop_db (deprecated)\n     */\n    const DROP_DB = 0x06;\n    /**\n     * mysql_refresh\n     */\n    const REFRESH = 0x07;\n    /**\n     * mysql_shutdown\n     */\n    const SHUTDOWN = 0x08;\n    /**\n     * mysql_stat\n     */\n    const STATISTICS = 0x09;\n    /**\n     * mysql_list_processes\n     */\n    const PROCESS_INFO = 0x0a;\n    /**\n     * (none, this is an internal thread state)\n     */\n    const CONNECT = 0x0b;\n    /**\n     * mysql_kill\n     */\n    const PROCESS_KILL = 0x0c;\n    /**\n     * mysql_dump_debug_info\n     */\n    const DEBUG = 0x0d;\n    /**\n     * mysql_ping\n     */\n    const PING = 0x0e;\n    /**\n     * (none, this is an internal thread state)\n     */\n    const TIME = 0x0f;\n    /**\n     * (none, this is an internal thread state)\n     */\n    const DELAYED_INSERT = 0x10;\n    /**\n     * mysql_change_user\n     */\n    const CHANGE_USER = 0x11;\n    /**\n     * sent by the slave IO thread to request a binlog\n     */\n    const BINLOG_DUMP = 0x12;\n    /**\n     * LOAD TABLE ... FROM MASTER (deprecated)\n     */\n    const TABLE_DUMP = 0x13;\n    /**\n     * (none, this is an internal thread state)\n     */\n    const CONNECT_OUT = 0x14;\n    /**\n     * sent by the slave to register with the master (optional)\n     */\n    const REGISTER_SLAVE = 0x15;\n    /**\n     * mysql_stmt_prepare\n     */\n    const STMT_PREPARE = 0x16;\n    /**\n     * mysql_stmt_execute\n     */\n    const STMT_EXECUTE = 0x17;\n    /**\n     * mysql_stmt_send_long_data\n     */\n    const STMT_SEND_LONG_DATA = 0x18;\n    /**\n     * mysql_stmt_close\n     */\n    const STMT_CLOSE = 0x19;\n    /**\n     * mysql_stmt_reset\n     */\n    const STMT_RESET = 0x1a;\n    /**\n     * mysql_set_server_option\n     */\n    const SET_OPTION = 0x1b;\n    /**\n     * mysql_stmt_fetch\n     */\n    const STMT_FETCH = 0x1c;\n}\n"
  },
  {
    "path": "src/Commands/AuthenticateCommand.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Commands;\n\nuse React\\Mysql\\Io\\Buffer;\nuse React\\Mysql\\Io\\Constants;\n\n/**\n * @internal\n * @link https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html#sect_protocol_connection_phase_packets_protocol_handshake_response41\n */\nclass AuthenticateCommand extends AbstractCommand\n{\n    private $user;\n    private $passwd;\n    private $dbname;\n\n    private $maxPacketSize = 0x1000000;\n\n    /**\n     * @var int\n     * @link https://dev.mysql.com/doc/internals/en/character-set.html#packet-Protocol::CharacterSet\n     */\n    private $charsetNumber;\n\n    /**\n     * Mapping from charset name to internal charset ID\n     *\n     * Note that this map currently only contains ASCII-compatible charset encodings\n     * because of quoting rules as defined in the `Query` class.\n     *\n     * @var array<string,int>\n     * @see self::$charsetNumber\n     * @see \\React\\Mysql\\Io\\Query::$escapeChars\n     */\n    private static $charsetMap = [\n        'latin1' => 8,\n        'latin2' => 9,\n        'ascii' => 11,\n        'latin5' => 30,\n        'utf8' => 33,\n        'latin7' => 41,\n        'utf8mb4' => 45,\n        'binary' => 63\n    ];\n\n    /**\n     * @param string $user\n     * @param string $passwd\n     * @param string $dbname\n     * @param string $charset\n     * @throws \\InvalidArgumentException for invalid/unknown charset name\n     */\n    public function __construct(\n        $user,\n        #[\\SensitiveParameter]\n        $passwd,\n        $dbname,\n        $charset\n    ) {\n        if (!isset(self::$charsetMap[$charset])) {\n            throw new \\InvalidArgumentException('Unsupported charset selected');\n        }\n\n        $this->user = $user;\n        $this->passwd = $passwd;\n        $this->dbname = $dbname;\n        $this->charsetNumber = self::$charsetMap[$charset];\n    }\n\n    public function getId()\n    {\n        return 0;\n    }\n\n    /**\n     * @param string $scramble\n     * @param ?string $authPlugin\n     * @param Buffer $buffer\n     * @return string\n     * @throws \\UnexpectedValueException for unsupported authentication plugin\n     */\n    public function authenticatePacket($scramble, $authPlugin, Buffer $buffer)\n    {\n        $clientFlags = Constants::CLIENT_LONG_PASSWORD |\n            Constants::CLIENT_LONG_FLAG |\n            Constants::CLIENT_LOCAL_FILES |\n            Constants::CLIENT_PROTOCOL_41 |\n            Constants::CLIENT_INTERACTIVE |\n            Constants::CLIENT_TRANSACTIONS |\n            Constants::CLIENT_SECURE_CONNECTION |\n            Constants::CLIENT_CONNECT_WITH_DB;\n\n        if ($authPlugin !== null) {\n            $clientFlags |= Constants::CLIENT_PLUGIN_AUTH;\n        }\n\n        return pack('VVc', $clientFlags, $this->maxPacketSize, $this->charsetNumber)\n            . \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n            . $this->user . \"\\x00\"\n            . $buffer->buildStringLen($this->authResponse($scramble, $authPlugin))\n            . $this->dbname . \"\\x00\"\n            . ($authPlugin !== null ? $authPlugin . \"\\0\" : '');\n    }\n\n    /**\n     * @param string $scramble\n     * @param ?string $authPlugin\n     * @return string\n     * @throws \\UnexpectedValueException for unsupported authentication plugin\n     */\n    public function authResponse($scramble, $authPlugin)\n    {\n        if ($authPlugin === null || $authPlugin === 'mysql_native_password') {\n            return $this->authMysqlNativePassword($scramble);\n        } elseif ($authPlugin === 'caching_sha2_password') {\n            return $this->authCachingSha2Password($scramble);\n        } else {\n            throw new \\UnexpectedValueException('Unknown authentication plugin \"' . addslashes($authPlugin) . '\" requested by server');\n        }\n    }\n\n    /**\n     * @param string $scramble\n     * @return string\n     */\n    private function authMysqlNativePassword($scramble)\n    {\n        if ($this->passwd === '') {\n            return '';\n        }\n\n        return \\sha1($scramble . \\sha1($hash1 = \\sha1($this->passwd, true), true), true) ^ $hash1;\n    }\n\n    /**\n     * @param string $scramble\n     * @return string\n     * @throws \\BadFunctionCallException if SHA256 hash algorithm is not available if ext-hash is missing, only possible in PHP < 7.4\n     */\n    private function authCachingSha2Password($scramble)\n    {\n        if ($this->passwd === '') {\n            return '';\n        }\n\n        if (\\PHP_VERSION_ID < 70100 || !\\function_exists('hash')) {\n            throw new \\UnexpectedValueException('Requires PHP 7.1+ with ext-hash for authentication plugin \"caching_sha2_password\" requested by server');\n        }\n\n        \\assert(\\in_array('sha256', \\hash_algos(), true));\n        return ($hash1 = \\hash('sha256', $this->passwd, true)) ^ \\hash('sha256', \\hash('sha256', $hash1, true) . $scramble, true);\n    }\n\n    /**\n     * @param string $scramble\n     * @param string $pubkey\n     * @return string\n     * @throws \\UnexpectedValueException if encryption fails (e.g. missing ext-openssl or invalid public key)\n     */\n    public function authSha256($scramble, $pubkey)\n    {\n        if (!\\function_exists('openssl_public_encrypt')) {\n            throw new \\UnexpectedValueException('Requires ext-openssl for authentication plugin \"caching_sha2_password\" requested by server');\n        }\n\n        $ret = @\\openssl_public_encrypt(\n            $this->passwd . \"\\x00\" ^ \\str_pad($scramble, \\strlen($this->passwd) + 1, $scramble),\n            $auth,\n            $pubkey,\n            \\OPENSSL_PKCS1_OAEP_PADDING\n        );\n\n        // unlikely: openssl_public_encrypt() may return false if the public key sent by the server is invalid\n        if ($ret === false) {\n            throw new \\UnexpectedValueException('Failed to encrypt password with public key');\n        }\n\n        return $auth;\n    }\n}\n"
  },
  {
    "path": "src/Commands/CommandInterface.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Commands;\n\nuse Evenement\\EventEmitterInterface;\n\n/**\n * @internal\n */\ninterface CommandInterface extends EventEmitterInterface\n{\n    public function getId();\n}\n"
  },
  {
    "path": "src/Commands/PingCommand.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Commands;\n\n/**\n * @internal\n */\nclass PingCommand extends AbstractCommand\n{\n    public function getId()\n    {\n        return self::PING;\n    }\n\n    public function getSql()\n    {\n        return '';\n    }\n}\n"
  },
  {
    "path": "src/Commands/QueryCommand.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Commands;\n\nuse React\\Mysql\\Io\\Query;\n\n/**\n * @internal\n */\nclass QueryCommand extends AbstractCommand\n{\n    public $query;\n    public $fields;\n    public $insertId;\n    public $affectedRows;\n    public $warningCount;\n\n    public function getId()\n    {\n        return self::QUERY;\n    }\n\n    public function getQuery()\n    {\n        return $this->query;\n    }\n\n    public function setQuery($query)\n    {\n        if ($query instanceof Query) {\n            $this->query = $query;\n        } elseif (is_string($query)) {\n            $this->query = new Query($query);\n        } else {\n            throw new \\InvalidArgumentException('Invalid argument type of query specified.');\n        }\n    }\n\n    public function getSql()\n    {\n        $query = $this->query;\n\n        if ($query instanceof Query) {\n            return $query->getSql();\n        }\n\n        return $query;\n    }\n}\n"
  },
  {
    "path": "src/Commands/QuitCommand.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Commands;\n\n/**\n * @internal\n */\nclass QuitCommand extends AbstractCommand\n{\n    public function getId()\n    {\n        return self::QUIT;\n    }\n\n    public function getSql()\n    {\n        return '';\n    }\n}\n"
  },
  {
    "path": "src/Exception.php",
    "content": "<?php\n\nnamespace React\\Mysql;\n\nclass Exception extends \\Exception\n{\n}\n"
  },
  {
    "path": "src/Io/Buffer.php",
    "content": "<?php\nnamespace React\\Mysql\\Io;\n\n/**\n * @internal\n */\nclass Buffer\n{\n    private $buffer = '';\n    private $bufferPos = 0;\n\n    /**\n     * appends some data to the end of the buffer without moving buffer position\n     *\n     * @param string $str\n     * @return void\n     */\n    public function append($str)\n    {\n        $this->buffer .= $str;\n    }\n\n    /**\n     * prepends some data to start of buffer and resets buffer position to start\n     *\n     * @param string $str\n     * @return void\n     */\n    public function prepend($str)\n    {\n        $this->buffer = $str . \\substr($this->buffer, $this->bufferPos);\n        $this->bufferPos = 0;\n    }\n\n    /**\n     * Reads binary string data with given byte length from buffer\n     *\n     * @param int $len length in bytes, must be positive or zero\n     * @return string\n     * @throws \\UnderflowException\n     */\n    public function read($len)\n    {\n        // happy path to return empty string for zero length string\n        if ($len === 0) {\n            return '';\n        }\n\n        // happy path for single byte strings without using substrings\n        if ($len === 1 && isset($this->buffer[$this->bufferPos])) {\n            return $this->buffer[$this->bufferPos++];\n        }\n\n        // ensure buffer size contains $len bytes by checking target buffer position\n        if ($len < 0 || !isset($this->buffer[$this->bufferPos + $len - 1])) {\n            throw new \\UnderflowException('Not enough data in buffer to read ' . $len . ' bytes');\n        }\n        $buffer = \\substr($this->buffer, $this->bufferPos, $len);\n        $this->bufferPos += $len;\n\n        return $buffer;\n    }\n\n    /**\n     * Reads data with given byte length from buffer into a new buffer\n     *\n     * This class keeps consumed data in memory for performance reasons and only\n     * advances the internal buffer position by default. Reading data into a new\n     * buffer will clear the data from the original buffer to free memory.\n     *\n     * @param int $len length in bytes, must be positive or zero\n     * @return self\n     * @throws \\UnderflowException\n     */\n    public function readBuffer($len)\n    {\n        // happy path to return empty buffer without any memory access for zero length string\n        if ($len === 0) {\n            return new self();\n        }\n\n        // ensure buffer size contains $len bytes by checking target buffer position\n        if ($len < 0 || !isset($this->buffer[$this->bufferPos + $len - 1])) {\n            throw new \\UnderflowException('Not enough data in buffer to read ' . $len . ' bytes');\n        }\n\n        $buffer = new self();\n        $buffer->buffer = $this->read($len);\n\n        if (!isset($this->buffer[$this->bufferPos])) {\n            $this->buffer = '';\n        } else {\n            $this->buffer = \\substr($this->buffer, $this->bufferPos);\n        }\n        $this->bufferPos = 0;\n\n        return $buffer;\n\n    }\n\n    /**\n     * Skips binary string data with given byte length from buffer\n     *\n     * This method can be used instead of `read()` if you do not care about the\n     * bytes that will be skipped.\n     *\n     * @param int $len length in bytes, must be positive and non-zero\n     * @return void\n     * @throws \\UnderflowException\n     */\n    public function skip($len)\n    {\n        if ($len < 1 || !isset($this->buffer[$this->bufferPos + $len - 1])) {\n            throw new \\UnderflowException('Not enough data in buffer');\n        }\n        $this->bufferPos += $len;\n    }\n\n    /**\n     * returns the buffer length measures in number of bytes\n     *\n     * @return int\n     */\n    public function length()\n    {\n        return \\strlen($this->buffer) - $this->bufferPos;\n    }\n\n    /**\n     * @return int 1 byte / 8 bit integer (0 to 255)\n     */\n    public function readInt1()\n    {\n        return \\ord($this->read(1));\n    }\n\n    /**\n     * @return int 2 byte / 16 bit integer (0 to 64 K / 0xFFFF)\n     */\n    public function readInt2()\n    {\n        $v = \\unpack('v', $this->read(2));\n        return $v[1];\n    }\n\n    /**\n     * @return int 3 byte / 24 bit integer (0 to 16 M / 0xFFFFFF)\n     */\n    public function readInt3()\n    {\n        $v = \\unpack('V', $this->read(3) . \"\\0\");\n        return $v[1];\n    }\n\n    /**\n     * @return int 4 byte / 32 bit integer (0 to 4 G / 0xFFFFFFFF)\n     */\n    public function readInt4()\n    {\n        $v = \\unpack('V', $this->read(4));\n        return $v[1];\n    }\n\n    /**\n     * @return int 8 byte / 64 bit integer (0 to 2^64-1)\n     * @codeCoverageIgnore\n     */\n    public function readInt8()\n    {\n        // PHP < 5.6.3 does not support packing 64 bit ints, so use manual bit shifting\n        if (\\PHP_VERSION_ID < 50603) {\n            $v = \\unpack('V*', $this->read(8));\n            return $v[1] + ($v[2] << 32);\n        }\n\n        $v = \\unpack('P', $this->read(8));\n        return $v[1];\n    }\n\n    /**\n     * Parses length-encoded binary integer\n     *\n     * @return int|null decoded integer 0 to 2^64 or null for special null int\n     */\n    public function readIntLen()\n    {\n        $f = $this->readInt1();\n        if ($f <= 250) {\n            return $f;\n        }\n        if ($f === 251) {\n            return null;\n        }\n        if ($f === 252) {\n            return $this->readInt2();\n        }\n        if ($f === 253) {\n            return $this->readInt3();\n        }\n\n        return $this->readInt8();\n    }\n\n    /**\n     * Parses length-encoded binary string\n     *\n     * @return string|null decoded string or null if length indicates null\n     */\n    public function readStringLen()\n    {\n        $l = $this->readIntLen();\n        if ($l === null) {\n            return $l;\n        }\n\n        return $this->read($l);\n    }\n\n    /**\n     * Reads string until NULL character\n     *\n     * @return string\n     * @throws \\UnderflowException\n     */\n    public function readStringNull()\n    {\n        $pos = \\strpos($this->buffer, \"\\0\", $this->bufferPos);\n        if ($pos === false) {\n            throw new \\UnderflowException('Missing NULL character');\n        }\n\n        $ret = $this->read($pos - $this->bufferPos);\n        ++$this->bufferPos;\n\n        return $ret;\n    }\n\n    /**\n     * @param int $int\n     * @return string\n     */\n    public function buildInt1($int)\n    {\n        return \\chr($int);\n    }\n\n    /**\n     * @param int $int\n     * @return string\n     */\n    public function buildInt2($int)\n    {\n        return \\pack('v', $int);\n    }\n\n    /**\n     * @param int $int\n     * @return string\n     */\n    public function buildInt3($int)\n    {\n        return \\substr(\\pack('V', $int), 0, 3);\n    }\n\n    /**\n     * @param int $int\n     * @return string\n     * @codeCoverageIgnore\n     */\n    public function buildInt8($int)\n    {\n        // PHP < 5.6.3 does not support packing 64 bit ints, so use manual bit shifting\n        if (\\PHP_VERSION_ID < 50603) {\n            return \\pack('VV', $int, $int >> 32);\n        }\n        return \\pack('P', $int);\n    }\n\n    /**\n     * Builds length-encoded binary string\n     *\n     * @param string|null $s\n     * @return string Resulting binary string\n     */\n    public function buildStringLen($s)\n    {\n        if ($s === NULL) {\n            // \\xFB (251)\n            return \"\\xFB\";\n        }\n\n        $l = \\strlen($s);\n\n        if ($l <= 250) {\n            // this is the only path that is currently used in fact.\n            return $this->buildInt1($l) . $s;\n        }\n\n        if ($l <= 0xFFFF) {\n            // max 2^16: \\xFC (252)\n            return \"\\xFC\" . $this->buildInt2($l) . $s;\n        }\n\n        if ($l <= 0xFFFFFF) {\n            // max 2^24: \\xFD (253)\n            return \"\\xFD\" . $this->buildInt3($l) . $s;\n        }\n\n        // max 2^64: \\xFE (254)\n        return \"\\xFE\" . $this->buildInt8($l) . $s;\n    }\n}\n"
  },
  {
    "path": "src/Io/Connection.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\nuse Evenement\\EventEmitter;\nuse React\\EventLoop\\LoopInterface;\nuse React\\Mysql\\Commands\\CommandInterface;\nuse React\\Mysql\\Commands\\PingCommand;\nuse React\\Mysql\\Commands\\QueryCommand;\nuse React\\Mysql\\Commands\\QuitCommand;\nuse React\\Mysql\\Exception;\nuse React\\Mysql\\MysqlResult;\nuse React\\Promise\\Deferred;\nuse React\\Promise\\Promise;\nuse React\\Socket\\ConnectionInterface as SocketConnectionInterface;\n\n/**\n * @internal\n * @see \\React\\Mysql\\MysqlClient\n */\nclass Connection extends EventEmitter\n{\n    const STATE_AUTHENTICATED       = 5;\n    const STATE_CLOSING             = 6;\n    const STATE_CLOSED              = 7;\n\n    /**\n     * @var Executor\n     */\n    private $executor;\n\n    /**\n     * @var int one of the state constants (may change, but should be used readonly from outside)\n     * @see self::STATE_*\n     */\n    public $state = self::STATE_AUTHENTICATED;\n\n    /**\n     * @var SocketConnectionInterface\n     */\n    private $stream;\n\n    /** @var Parser */\n    private $parser;\n\n    /** @var LoopInterface */\n    private $loop;\n\n    /** @var float */\n    private $idlePeriod = 0.001;\n\n    /** @var ?\\React\\EventLoop\\TimerInterface */\n    private $idleTimer;\n\n    /** @var int */\n    private $pending = 0;\n\n    /**\n     * Connection constructor.\n     *\n     * @param SocketConnectionInterface $stream\n     * @param Executor                  $executor\n     * @param Parser                    $parser\n     * @param LoopInterface             $loop\n     * @param ?float                    $idlePeriod\n     */\n    public function __construct(SocketConnectionInterface $stream, Executor $executor, Parser $parser, LoopInterface $loop, $idlePeriod)\n    {\n        $this->stream   = $stream;\n        $this->executor = $executor;\n        $this->parser   = $parser;\n\n        $this->loop = $loop;\n        if ($idlePeriod !== null) {\n            $this->idlePeriod = $idlePeriod;\n        }\n\n        $stream->on('error', [$this, 'handleConnectionError']);\n        $stream->on('close', [$this, 'handleConnectionClosed']);\n    }\n\n    /**\n     * busy executing some command such as query or ping\n     *\n     * @return bool\n     * @throws void\n     */\n    public function isBusy()\n    {\n        return $this->parser->isBusy() || !$this->executor->isIdle();\n    }\n\n    public function query(Query $query)\n    {\n        $command = new QueryCommand();\n        $command->setQuery($query);\n        try {\n            $this->_doCommand($command);\n        } catch (\\Exception $e) {\n            return \\React\\Promise\\reject($e);\n        }\n\n        $this->awake();\n        $deferred = new Deferred();\n\n        // store all result set rows until result set end\n        $rows = [];\n        $command->on('result', function ($row) use (&$rows) {\n            $rows[] = $row;\n        });\n        $command->on('end', function () use ($command, $deferred, &$rows) {\n            $result = new MysqlResult();\n            $result->resultFields = $command->fields;\n            $result->resultRows = $rows;\n            $result->warningCount = $command->warningCount;\n\n            $rows = [];\n\n            $this->idle();\n            $deferred->resolve($result);\n        });\n\n        // resolve / reject status reply (response without result set)\n        $command->on('error', function ($error) use ($deferred) {\n            $this->idle();\n            $deferred->reject($error);\n        });\n        $command->on('success', function () use ($command, $deferred) {\n            $result = new MysqlResult();\n            $result->affectedRows = $command->affectedRows;\n            $result->insertId = $command->insertId;\n            $result->warningCount = $command->warningCount;\n\n            $this->idle();\n            $deferred->resolve($result);\n        });\n\n        return $deferred->promise();\n    }\n\n    public function queryStream(Query $query)\n    {\n        $command = new QueryCommand();\n        $command->setQuery($query);\n        $this->_doCommand($command);\n        $this->awake();\n\n        $stream = new QueryStream($command, $this->stream);\n        $stream->on('close', function () {\n            $this->idle();\n        });\n\n        return $stream;\n    }\n\n    public function ping()\n    {\n        return new Promise(function ($resolve, $reject) {\n            $command = $this->_doCommand(new PingCommand());\n            $this->awake();\n\n            $command->on('success', function () use ($resolve) {\n                $this->idle();\n                $resolve(null);\n            });\n            $command->on('error', function ($reason) use ($reject) {\n                $this->idle();\n                $reject($reason);\n            });\n        });\n    }\n\n    public function quit()\n    {\n        return new Promise(function ($resolve, $reject) {\n            $command = $this->_doCommand(new QuitCommand());\n            $this->state = self::STATE_CLOSING;\n\n            // mark connection as \"awake\" until it is closed, so never \"idle\"\n            $this->awake();\n\n            $command->on('success', function () use ($resolve) {\n                $resolve(null);\n                $this->close();\n            });\n            $command->on('error', function ($reason) use ($reject) {\n                $reject($reason);\n                $this->close();\n            });\n        });\n    }\n\n    public function close()\n    {\n        if ($this->state === self::STATE_CLOSED) {\n            return;\n        }\n\n        $this->state = self::STATE_CLOSED;\n        $remoteClosed = $this->stream->isReadable() === false && $this->stream->isWritable() === false;\n        $this->stream->close();\n\n        if ($this->idleTimer !== null) {\n            $this->loop->cancelTimer($this->idleTimer);\n            $this->idleTimer = null;\n        }\n\n        // reject all pending commands if connection is closed\n        while (!$this->executor->isIdle()) {\n            $command = $this->executor->dequeue();\n            assert($command instanceof CommandInterface);\n\n            if ($remoteClosed) {\n                $command->emit('error', [new \\RuntimeException(\n                    'Connection closed by peer (ECONNRESET)',\n                    \\defined('SOCKET_ECONNRESET') ? \\SOCKET_ECONNRESET : 104\n                )]);\n            } else {\n                $command->emit('error', [new \\RuntimeException(\n                    'Connection closing (ECONNABORTED)',\n                    \\defined('SOCKET_ECONNABORTED') ? \\SOCKET_ECONNABORTED : 103\n                )]);\n            }\n        }\n\n        $this->emit('close');\n        $this->removeAllListeners();\n    }\n\n    /**\n     * @param Exception $err Error from socket.\n     *\n     * @return void\n     * @internal\n     */\n    public function handleConnectionError($err)\n    {\n        $this->emit('error', [$err, $this]);\n    }\n\n    /**\n     * @return void\n     * @internal\n     */\n    public function handleConnectionClosed()\n    {\n        if ($this->state < self::STATE_CLOSING) {\n            $this->emit('error', [new \\RuntimeException(\n                'Connection closed by peer (ECONNRESET)',\n                \\defined('SOCKET_ECONNRESET') ? \\SOCKET_ECONNRESET : 104\n            )]);\n        }\n\n        $this->close();\n    }\n\n    /**\n     * @param CommandInterface $command The command which should be executed.\n     * @return CommandInterface\n     * @throws Exception Can't send command\n     */\n    protected function _doCommand(CommandInterface $command)\n    {\n        if ($this->state !== self::STATE_AUTHENTICATED) {\n            throw new \\RuntimeException(\n                'Connection ' . ($this->state === self::STATE_CLOSED ? 'closed' : 'closing'). ' (ENOTCONN)',\n                \\defined('SOCKET_ENOTCONN') ? \\SOCKET_ENOTCONN : 107\n            );\n        }\n\n        return $this->executor->enqueue($command);\n    }\n\n    private function awake()\n    {\n        ++$this->pending;\n\n        if ($this->idleTimer !== null) {\n            $this->loop->cancelTimer($this->idleTimer);\n            $this->idleTimer = null;\n        }\n    }\n\n    private function idle()\n    {\n        --$this->pending;\n\n        if ($this->pending < 1 && $this->idlePeriod >= 0 && $this->state === self::STATE_AUTHENTICATED) {\n            $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () {\n                // soft-close connection and emit close event afterwards both on success or on error\n                $this->idleTimer = null;\n                $this->quit()->then(null, function () {\n                    // ignore to avoid reporting unhandled rejection\n                });\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/Io/Constants.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\n/**\n * @internal\n */\nclass Constants\n{\n    /**\n     * new more secure passwords\n     */\n    const CLIENT_LONG_PASSWORD = 1;\n    /**\n     * Found instead of affected rows\n     */\n    const CLIENT_FOUND_ROWS = 2;\n    /**\n     * Get all column flags\n     */\n    const CLIENT_LONG_FLAG = 4;\n    /**\n     * One can specify db on connect\n     */\n    const CLIENT_CONNECT_WITH_DB = 8;\n    /**\n     * Don't allow database.table.column\n     */\n    const CLIENT_NO_SCHEMA = 16;\n    /**\n     * Can use compression protocol\n     */\n    const CLIENT_COMPRESS = 32;\n    /**\n     * Odbc client\n     */\n    const CLIENT_ODBC = 64;\n    /**\n     * Can use LOAD DATA LOCAL\n     */\n    const CLIENT_LOCAL_FILES = 128;\n    /**\n     * Ignore spaces before '('\n     */\n    const CLIENT_IGNORE_SPACE = 256;\n    /**\n     * New 4.1 protocol\n     */\n    const CLIENT_PROTOCOL_41 = 512;\n    /**\n     * This is an interactive client\n     */\n    const CLIENT_INTERACTIVE = 1024;\n    /**\n     * Switch to SSL after handshake\n     */\n    const CLIENT_SSL = 2048;\n    /**\n     * IGNORE sigpipes\n     */\n    const CLIENT_IGNORE_SIGPIPE = 4096;\n    /**\n     * Client knows about transactions\n     */\n    const CLIENT_TRANSACTIONS = 8192;\n    /**\n     * Old flag for 4.1 protocol\n     */\n    const CLIENT_RESERVED = 16384;\n    /**\n     * New 4.1 authentication\n     */\n    const CLIENT_SECURE_CONNECTION = 32768;\n    /**\n     * Enable/disable multi-stmt support\n     */\n    const CLIENT_MULTI_STATEMENTS = 65536;\n    /**\n     * Enable/disable multi-results\n     */\n    const CLIENT_MULTI_RESULTS = 131072;\n\n    /**\n     * Client supports plugin authentication (1 << 19)\n     */\n    const CLIENT_PLUGIN_AUTH = 524288;\n\n    const FIELD_TYPE_DECIMAL     = 0x00;\n    const FIELD_TYPE_TINY        = 0x01;\n    const FIELD_TYPE_SHORT       = 0x02;\n    const FIELD_TYPE_LONG        = 0x03;\n    const FIELD_TYPE_FLOAT       = 0x04;\n    const FIELD_TYPE_DOUBLE      = 0x05;\n    const FIELD_TYPE_NULL        = 0x06;\n    const FIELD_TYPE_TIMESTAMP   = 0x07;\n    const FIELD_TYPE_LONGLONG    = 0x08;\n    const FIELD_TYPE_INT24       = 0x09;\n    const FIELD_TYPE_DATE        = 0x0a;\n    const FIELD_TYPE_TIME        = 0x0b;\n    const FIELD_TYPE_DATETIME    = 0x0c;\n    const FIELD_TYPE_YEAR        = 0x0d;\n    const FIELD_TYPE_NEWDATE     = 0x0e;\n    const FIELD_TYPE_VARCHAR     = 0x0f;\n    const FIELD_TYPE_BIT         = 0x10;\n    const FIELD_TYPE_NEWDECIMAL  = 0xf6;\n    const FIELD_TYPE_ENUM        = 0xf7;\n    const FIELD_TYPE_SET         = 0xf8;\n    const FIELD_TYPE_TINY_BLOB   = 0xf9;\n    const FIELD_TYPE_MEDIUM_BLOB = 0xfa;\n    const FIELD_TYPE_LONG_BLOB   = 0xfb;\n    const FIELD_TYPE_BLOB        = 0xfc;\n    const FIELD_TYPE_VAR_STRING  = 0xfd;\n    const FIELD_TYPE_STRING      = 0xfe;\n    const FIELD_TYPE_GEOMETRY    = 0xff;\n    const NOT_NULL_FLAG       = 0x1;\n    const PRI_KEY_FLAG        = 0x2;\n    const UNIQUE_KEY_FLAG     = 0x4;\n    const MULTIPLE_KEY_FLAG   = 0x8;\n    const BLOB_FLAG           = 0x10;\n    const UNSIGNED_FLAG       = 0x20;\n    const ZEROFILL_FLAG       = 0x40;\n    const BINARY_FLAG         = 0x80;\n    const ENUM_FLAG           = 0x100;\n    const AUTO_INCREMENT_FLAG = 0x200;\n    const TIMESTAMP_FLAG      = 0x400;\n    const SET_FLAG            = 0x800;\n}\n"
  },
  {
    "path": "src/Io/Executor.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\nuse Evenement\\EventEmitter;\n\n/**\n * @internal\n */\nclass Executor extends EventEmitter\n{\n    public $queue;\n\n    public function __construct()\n    {\n        $this->queue = new \\SplQueue();\n    }\n\n    public function isIdle()\n    {\n        return $this->queue->isEmpty();\n    }\n\n    public function enqueue($command)\n    {\n        $this->queue->enqueue($command);\n        $this->emit('new');\n\n        return $command;\n    }\n\n    public function dequeue()\n    {\n        return $this->queue->dequeue();\n    }\n}\n"
  },
  {
    "path": "src/Io/Factory.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\nuse React\\EventLoop\\Loop;\nuse React\\EventLoop\\LoopInterface;\nuse React\\Mysql\\Commands\\AuthenticateCommand;\nuse React\\Mysql\\Exception;\nuse React\\Promise\\Deferred;\nuse React\\Promise\\PromiseInterface;\nuse React\\Promise\\Timer\\TimeoutException;\nuse React\\Socket\\Connector;\nuse React\\Socket\\ConnectorInterface;\nuse React\\Socket\\ConnectionInterface as SocketConnectionInterface;\n\n/**\n * @internal\n * @see \\React\\Mysql\\MysqlClient\n */\nclass Factory\n{\n    /** @var LoopInterface */\n    private $loop;\n\n    /** @var ConnectorInterface */\n    private $connector;\n\n    /**\n     * The `Factory` is responsible for creating an internal `Connection` instance.\n     *\n     * ```php\n     * $factory = new React\\Mysql\\Io\\Factory();\n     * ```\n     *\n     * This class takes an optional `LoopInterface|null $loop` parameter that can be used to\n     * pass the event loop instance to use for this object. You can use a `null` value\n     * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).\n     * This value SHOULD NOT be given unless you're sure you want to explicitly use a\n     * given event loop instance.\n     *\n     * If you need custom connector settings (DNS resolution, TLS parameters, timeouts,\n     * proxy servers etc.), you can explicitly pass a custom instance of the\n     * [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):\n     *\n     * ```php\n     * $connector = new React\\Socket\\Connector([\n     *     'dns' => '127.0.0.1',\n     *     'tcp' => [\n     *         'bindto' => '192.168.10.1:0'\n     *     ],\n     *     'tls' => [\n     *         'verify_peer' => false,\n     *         'verify_peer_name' => false\n     *     ]\n     * ]);\n     *\n     * $factory = new React\\Mysql\\Factory(null, $connector);\n     * ```\n     *\n     * @param ?LoopInterface $loop\n     * @param ?ConnectorInterface $connector\n     */\n    public function __construct($loop = null, $connector = null)\n    {\n        // manual type check to support legacy PHP < 7.1\n        assert($loop === null || $loop instanceof LoopInterface);\n        assert($connector === null || $connector instanceof ConnectorInterface);\n\n        $this->loop = $loop ?: Loop::get();\n        $this->connector = $connector ?: new Connector([], $this->loop);\n    }\n\n    /**\n     * Creates a new connection.\n     *\n     * It helps with establishing a TCP/IP connection to your MySQL database\n     * and issuing the initial authentication handshake.\n     *\n     * ```php\n     * $factory->createConnection($url)->then(\n     *     function (Connection $connection) {\n     *         // client connection established (and authenticated)\n     *     },\n     *     function (Exception $e) {\n     *         // an error occurred while trying to connect or authorize client\n     *     }\n     * );\n     * ```\n     *\n     * The method returns a [Promise](https://github.com/reactphp/promise) that\n     * will resolve with an internal `Connection`\n     * instance on success or will reject with an `Exception` if the URL is\n     * invalid or the connection or authentication fails.\n     *\n     * The returned Promise is implemented in such a way that it can be\n     * cancelled when it is still pending. Cancelling a pending promise will\n     * reject its value with an Exception and will cancel the underlying TCP/IP\n     * connection attempt and/or MySQL authentication.\n     *\n     * ```php\n     * $promise = $factory->createConnection($url);\n     *\n     * Loop::addTimer(3.0, function () use ($promise) {\n     *     $promise->cancel();\n     * });\n     * ```\n     *\n     * The `$url` parameter must contain the database host, optional\n     * authentication, port and database to connect to:\n     *\n     * ```php\n     * $factory->createConnection('user:secret@localhost:3306/database');\n     * ```\n     *\n     * Note that both the username and password must be URL-encoded (percent-encoded)\n     * if they contain special characters:\n     *\n     * ```php\n     * $user = 'he:llo';\n     * $pass = 'p@ss';\n     *\n     * $promise = $factory->createConnection(\n     *     rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost:3306/db'\n     * );\n     * ```\n     *\n     * You can omit the port if you're connecting to default port `3306`:\n     *\n     * ```php\n     * $factory->createConnection('user:secret@localhost/database');\n     * ```\n     *\n     * If you do not include authentication and/or database, then this method\n     * will default to trying to connect as user `root` with an empty password\n     * and no database selected. This may be useful when initially setting up a\n     * database, but likely to yield an authentication error in a production system:\n     *\n     * ```php\n     * $factory->createConnection('localhost');\n     * ```\n     *\n     * This method respects PHP's `default_socket_timeout` setting (default 60s)\n     * as a timeout for establishing the connection and waiting for successful\n     * authentication. You can explicitly pass a custom timeout value in seconds\n     * (or use a negative number to not apply a timeout) like this:\n     *\n     * ```php\n     * $factory->createConnection('localhost?timeout=0.5');\n     * ```\n     *\n     * By default, the connection provides full UTF-8 support (using the\n     * `utf8mb4` charset encoding). This should usually not be changed for most\n     * applications nowadays, but for legacy reasons you can change this to use\n     * a different ASCII-compatible charset encoding like this:\n     *\n     * ```php\n     * $factory->createConnection('localhost?charset=utf8mb4');\n     * ```\n     *\n     * @param string $uri\n     * @return PromiseInterface<Connection>\n     *     Resolves with a `Connection` on success or rejects with an `Exception` on error.\n     */\n    public function createConnection(\n        #[\\SensitiveParameter]\n        $uri\n    ) {\n        $parts = parse_url($uri);\n        $uri = preg_replace('#:[^:/]*@#', ':***@', $uri);\n        assert(is_array($parts) && isset($parts['scheme'], $parts['host']));\n        assert($parts['scheme'] === 'mysql');\n\n        $args = [];\n        if (isset($parts['query'])) {\n            parse_str($parts['query'], $args);\n        }\n\n        /** @throws void already validated in MysqlClient ctor */\n        $authCommand = new AuthenticateCommand(\n            isset($parts['user']) ? rawurldecode($parts['user']) : 'root',\n            isset($parts['pass']) ? rawurldecode($parts['pass']) : '',\n            isset($parts['path']) ? rawurldecode(ltrim($parts['path'], '/')) : '',\n            isset($args['charset']) ? $args['charset'] : 'utf8mb4'\n        );\n\n        $connecting = $this->connector->connect(\n            $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 3306)\n        );\n\n        $deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {\n            // connection cancelled, start with rejecting attempt, then clean up\n            $reject(new \\RuntimeException(\n                'Connection to ' . $uri . ' cancelled (ECONNABORTED)',\n                \\defined('SOCKET_ECONNABORTED') ? \\SOCKET_ECONNABORTED : 103\n            ));\n\n            // either close successful connection or cancel pending connection attempt\n            $connecting->then(function (SocketConnectionInterface $connection) {\n                $connection->close();\n            }, function () {\n                // ignore to avoid reporting unhandled rejection\n            });\n            $connecting->cancel();\n        });\n\n        $idlePeriod = isset($args['idle']) ? (float) $args['idle'] : null;\n        $connecting->then(function (SocketConnectionInterface $stream) use ($authCommand, $deferred, $uri, $idlePeriod) {\n            $executor = new Executor();\n            $parser = new Parser($stream, $executor);\n\n            $connection = new Connection($stream, $executor, $parser, $this->loop, $idlePeriod);\n            $command = $executor->enqueue($authCommand);\n            $parser->start();\n\n            $command->on('success', function () use ($deferred, $connection) {\n                $deferred->resolve($connection);\n            });\n            $command->on('error', function (\\Exception $error) use ($deferred, $stream, $uri) {\n                $const = '';\n                $errno = $error->getCode();\n                if ($error instanceof Exception) {\n                    $const = ' (EACCES)';\n                    $errno = \\defined('SOCKET_EACCES') ? \\SOCKET_EACCES : 13;\n                }\n\n                $deferred->reject(new \\RuntimeException(\n                    'Connection to ' . $uri . ' failed during authentication: ' . $error->getMessage() . $const,\n                    $errno,\n                    $error\n                ));\n                $stream->close();\n            });\n        }, function (\\Exception $error) use ($deferred, $uri) {\n            $deferred->reject(new \\RuntimeException(\n                'Connection to ' . $uri . ' failed: ' . $error->getMessage(),\n                $error->getCode(),\n                $error\n            ));\n        });\n\n        // use timeout from explicit ?timeout=x parameter or default to PHP's default_socket_timeout (60)\n        $timeout = (float) isset($args['timeout']) ? $args['timeout'] : ini_get(\"default_socket_timeout\");\n        if ($timeout < 0) {\n            return $deferred->promise();\n        }\n\n        return \\React\\Promise\\Timer\\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) use ($uri) {\n            if ($e instanceof TimeoutException) {\n                throw new \\RuntimeException(\n                    'Connection to ' . $uri . ' timed out after ' . $e->getTimeout() . ' seconds (ETIMEDOUT)',\n                    \\defined('SOCKET_ETIMEDOUT') ? \\SOCKET_ETIMEDOUT : 110\n                );\n            }\n            throw $e;\n        });\n    }\n}\n"
  },
  {
    "path": "src/Io/Parser.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\nuse React\\Mysql\\Commands\\AuthenticateCommand;\nuse React\\Mysql\\Commands\\QueryCommand;\nuse React\\Mysql\\Commands\\QuitCommand;\nuse React\\Mysql\\Exception as MysqlException;\nuse React\\Stream\\DuplexStreamInterface;\n\n/**\n * @internal\n */\nclass Parser\n{\n    const PHASE_GOT_INIT   = 1;\n    const PHASE_AUTH_SENT  = 2;\n    const PHASE_AUTH_ERR   = 3;\n    const PHASE_HANDSHAKED = 4;\n\n    const RS_STATE_HEADER = 0;\n    const RS_STATE_FIELD  = 1;\n    const RS_STATE_ROW    = 2;\n\n    const STATE_STANDBY = 0;\n    const STATE_BODY    = 1;\n\n    /**\n     * The packet header always consists of 4 bytes, 3 bytes packet length + 1 byte sequence number\n     *\n     * @var integer\n     */\n    const PACKET_SIZE_HEADER = 4;\n\n    /**\n     * Keeps a reference to the command that is currently being processed.\n     *\n     * The MySQL protocol is inherently sequential, the pending commands will be\n     * stored in an `Executor` queue.\n     *\n     * The MySQL protocol communication starts with the server sending a\n     * handshake message, so the current command will be `null` until it's our\n     * turn.\n     *\n     * Similarly, when one command is finished, it will continue processing the\n     * next command from the `Executor` queue. If no command is outstanding,\n     * this will be reset to the `null` state.\n     *\n     * @var \\React\\Mysql\\Commands\\AbstractCommand|null\n     */\n    protected $currCommand;\n\n    protected $debug = false;\n\n    protected $state = 0;\n\n    protected $phase = 0;\n\n    public $seq = 0;\n\n    public $warningCount;\n    public $message;\n\n    protected $serverVersion;\n    protected $threadId;\n    protected $scramble;\n\n    protected $serverCaps;\n    protected $serverLang;\n    protected $serverStatus;\n\n    protected $rsState = 0;\n\n    /**\n     * Packet size expected in number of bytes\n     *\n     * Depending on `self::$state`, the Parser excepts either a packet header\n     * (always 4 bytes) or the packet contents (n bytes determined by prior\n     * packet header).\n     *\n     * @var int\n     * @see self::$state\n     * @see self::PACKET_SIZE_HEADER\n     */\n    private $pctSize = self::PACKET_SIZE_HEADER;\n\n    protected $resultFields = [];\n\n    protected $insertId;\n    protected $affectedRows;\n\n    public $protocolVersion = 0;\n\n    private $buffer;\n\n    protected $connectOptions;\n\n    /**\n     * @var \\React\\Stream\\DuplexStreamInterface\n     */\n    protected $stream;\n    /**\n     * @var Executor\n     */\n    protected $executor;\n\n    /**\n     * @var ?string authentication plugin name, set if server capabilities include CLIENT_PLUGIN_AUTH\n     * @link https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods.html\n     */\n    private $authPlugin;\n\n    public function __construct(DuplexStreamInterface $stream, Executor $executor)\n    {\n        $this->stream   = $stream;\n        $this->executor = $executor;\n\n        $this->buffer   = new Buffer();\n        $executor->on('new', function () {\n            $this->nextRequest();\n        });\n    }\n\n    /**\n     * busy executing some command such as query or ping\n     *\n     * @return bool\n     * @throws void\n     */\n    public function isBusy()\n    {\n        return $this->currCommand !== null;\n    }\n\n    public function start()\n    {\n        $this->stream->on('data', [$this, 'handleData']);\n        $this->stream->on('close', [$this, 'onClose']);\n    }\n\n    public function debug($message)\n    {\n        if ($this->debug) {\n            $bt = \\debug_backtrace();\n            $caller = \\array_shift($bt);\n            printf(\"[DEBUG] <%s:%d> %s\\n\", $caller['class'], $caller['line'], $message);\n        }\n    }\n\n    /** @var string $data */\n    public function handleData($data)\n    {\n        $this->buffer->append($data);\n\n        if ($this->debug) {\n            $this->debug('Received ' . strlen($data) . ' byte(s), buffer now has ' . ($len = $this->buffer->length()) . ' byte(s): ' . wordwrap(bin2hex($b = $this->buffer->read($len)), 2, ' ', true)); $this->buffer->append($b); // @codeCoverageIgnore\n        }\n\n        while ($this->buffer->length() >= $this->pctSize) {\n            if ($this->state === self::STATE_STANDBY) {\n                $this->pctSize = $this->buffer->readInt3();\n                //printf(\"packet size:%d\\n\", $this->pctSize);\n                $this->state = self::STATE_BODY;\n                $this->seq = $this->buffer->readInt1() + 1;\n            }\n\n            $len = $this->buffer->length();\n            if ($len < $this->pctSize) {\n                $this->debug('Waiting for complete packet with ' . $len . '/' . $this->pctSize . ' bytes');\n\n                return;\n            }\n\n            $packet = $this->buffer->readBuffer($this->pctSize);\n            $this->state = self::STATE_STANDBY;\n            $this->pctSize = self::PACKET_SIZE_HEADER;\n\n            try {\n                $this->parsePacket($packet);\n            } catch (\\UnderflowException $e) {\n                $this->onError(new \\UnexpectedValueException('Unexpected protocol error, received malformed packet: ' . $e->getMessage(), 0, $e));\n                $this->stream->close();\n                return;\n            }\n\n            if ($packet->length() !== 0) {\n                $this->onError(new \\UnexpectedValueException('Unexpected protocol error, received malformed packet with ' . $packet->length() . ' unknown byte(s)'));\n                $this->stream->close();\n                return;\n            }\n        }\n    }\n\n    /** @return void */\n    private function parsePacket(Buffer $packet)\n    {\n        if ($this->debug) {\n            $this->debug('Parse packet#' . $this->seq . ' with ' . ($len = $packet->length()) . ' bytes: ' . wordwrap(bin2hex($b = $packet->read($len)), 2, ' ', true)); $packet->append($b); // @codeCoverageIgnore\n        }\n\n        if ($this->phase === 0) {\n            $response = $packet->readInt1();\n            if ($response === 0xFF) {\n                // error packet before handshake means we did not exchange capabilities and error does not include SQL state\n                $this->phase   = self::PHASE_AUTH_ERR;\n\n                $code = $packet->readInt2();\n                $exception = new MysqlException($packet->read($packet->length()), $code);\n                $this->debug(sprintf(\"Error Packet:%d %s\\n\", $code, $exception->getMessage()));\n\n                // error during init phase also means we're not currently executing any command\n                // simply reject the first outstanding command in the queue (AuthenticateCommand)\n                $this->currCommand = $this->executor->dequeue();\n                $this->onError($exception);\n                return;\n            }\n\n            $this->phase = self::PHASE_GOT_INIT;\n            $this->protocolVersion = $response;\n            $this->debug(sprintf(\"Protocol Version: %d\", $this->protocolVersion));\n\n            $options = &$this->connectOptions;\n            $options['serverVersion'] = $packet->readStringNull();\n            $options['threadId']      = $packet->readInt4();\n            $this->scramble           = $packet->read(8); // 1st part\n            $packet->skip(1); // filler\n            $options['ServerCaps']    = $packet->readInt2(); // 1st part\n            $options['serverLang']    = $packet->readInt1();\n            $options['serverStatus']  = $packet->readInt2();\n            $options['ServerCaps']   += $packet->readInt2() << 16; // 2nd part\n            $packet->skip(11); // plugin length, 6 + 4 filler\n            $this->scramble          .= $packet->read(12); // 2nd part\n            $packet->skip(1);\n\n            if ($this->connectOptions['ServerCaps'] & Constants::CLIENT_PLUGIN_AUTH) {\n                $this->authPlugin = $packet->readStringNull();\n                $this->debug('Authentication plugin: ' . $this->authPlugin);\n            }\n\n            // init completed, continue with sending AuthenticateCommand\n            $this->nextRequest(true);\n        } else {\n            $fieldCount = $packet->readInt1();\n\n            if ($fieldCount === 0xFF) {\n                // error packet\n                $code = $packet->readInt2();\n                $packet->skip(6); // skip SQL state\n                $exception = new MysqlException($packet->read($packet->length()), $code);\n                $this->debug(sprintf(\"Error Packet:%d %s\\n\", $code, $exception->getMessage()));\n\n                $this->onError($exception);\n                $this->nextRequest();\n            } elseif ($fieldCount === 0x00 && $this->rsState !== self::RS_STATE_ROW) {\n                // Empty OK Packet terminates a query without a result set (UPDATE, INSERT etc.)\n                $this->debug('Ok Packet');\n\n                if ($this->phase === self::PHASE_AUTH_SENT) {\n                    $this->phase = self::PHASE_HANDSHAKED;\n                }\n\n                $this->affectedRows = $packet->readIntLen();\n                $this->insertId     = $packet->readIntLen();\n                $this->serverStatus = $packet->readInt2();\n                $this->warningCount = $packet->readInt2();\n\n                $this->message      = $packet->read($packet->length());\n\n                $this->debug(sprintf(\"AffectedRows: %d, InsertId: %d, WarningCount:%d\", $this->affectedRows, $this->insertId, $this->warningCount));\n                $this->onSuccess();\n                $this->nextRequest();\n            } elseif ($fieldCount === 0xFE && $this->phase !== self::PHASE_AUTH_SENT) {\n                // EOF Packet\n                $packet->skip(4); // warn, status\n                if ($this->rsState === self::RS_STATE_ROW) {\n                    // finalize this result set (all rows completed)\n                    $this->debug('Result set done');\n\n                    $this->onResultDone();\n                    $this->nextRequest();\n                } else {\n                    // move to next part of result set (header->field->row)\n                    $this->debug('Result set next part');\n                    ++$this->rsState;\n                }\n            } elseif ($fieldCount === 0xFE && $this->phase === self::PHASE_AUTH_SENT) {\n                // Protocol::AuthSwitchRequest packet\n                // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html\n                $this->authPlugin = $packet->readStringNull();\n                $this->scramble = $packet->read($packet->length() - 1);\n                $packet->skip(1); // 0x00\n                $this->debug('Switched to authentication plugin: ' . $this->authPlugin);\n\n                try {\n                    assert($this->currCommand instanceof AuthenticateCommand);\n                    $this->sendPacket($this->currCommand->authResponse($this->scramble, $this->authPlugin));\n                    //$this->sendPacket($this->currCommand->authenticatePacket($this->scramble, $this->authPlugin, $this->buffer));\n                } catch (\\UnexpectedValueException $e) {\n                    $this->onError($e);\n                    $this->stream->close();\n                }\n            } elseif ($fieldCount === 0x01 && $this->phase === self::PHASE_AUTH_SENT && $this->authPlugin === 'caching_sha2_password') {\n                // Protocol::AuthMoreData packet\n                // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html\n                $status = $packet->readInt1();\n                if ($status === 0x03 && $packet->length() === 0) {\n                    // ignore fast auth success here, will be followed by OK packet\n                    $this->debug('Fast auth success');\n                } elseif ($status === 0x04 && $packet->length() === 0) {\n                    // fast auth failure means we need to request the certificate to send the encrypted password\n                    $this->debug('Fast auth failure, request certificate');\n                    $this->sendPacket(\"\\x02\");\n                } else {\n                    // extra auth containing certificate data\n                    $this->debug('Extra auth certificate received, send encrypted password');\n                    $packet->prepend($packet->buildInt1($status));\n\n                    try {\n                        assert($this->currCommand instanceof AuthenticateCommand);\n                        $this->sendPacket($this->currCommand->authSha256($this->scramble, $packet->read($packet->length())));\n                    } catch (\\UnexpectedValueException $e) {\n                        $this->onError($e);\n                        $this->stream->close();\n                    }\n                }\n            } else {\n                // Data packet\n                $packet->prepend($packet->buildInt1($fieldCount));\n\n                if ($this->rsState === self::RS_STATE_HEADER) {\n                    $columns = $packet->readIntLen(); // extra\n                    $this->debug('Result set with ' . $columns . ' column(s)');\n                    $this->rsState = self::RS_STATE_FIELD;\n                } elseif ($this->rsState === self::RS_STATE_FIELD) {\n                    $field = [\n                        'catalog'   => $packet->readStringLen(),\n                        'db'        => $packet->readStringLen(),\n                        'table'     => $packet->readStringLen(),\n                        'org_table' => $packet->readStringLen(),\n                        'name'      => $packet->readStringLen(),\n                        'org_name'  => $packet->readStringLen()\n                    ];\n\n                    $packet->skip(1); // 0xC0\n                    $field['charset']     = $packet->readInt2();\n                    $field['length']      = $packet->readInt4();\n                    $field['type']        = $packet->readInt1();\n                    $field['flags']       = $packet->readInt2();\n                    $field['decimals']    = $packet->readInt1();\n                    $packet->skip(2); // unused\n\n                    if ($this->debug) {\n                        $this->debug('Result set column: ' . json_encode($field, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE)); // @codeCoverageIgnore\n                    }\n                    $this->resultFields[] = $field;\n                } elseif ($this->rsState === self::RS_STATE_ROW) {\n                    $row = [];\n                    foreach ($this->resultFields as $field) {\n                        $row[$field['name']] = $packet->readStringLen();\n                    }\n\n                    if ($this->debug) {\n                        $this->debug('Result set row: ' . json_encode($row, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE)); // @codeCoverageIgnore\n                    }\n                    $this->onResultRow($row);\n                }\n            }\n        }\n    }\n\n    private function onResultRow($row)\n    {\n        // $this->debug('row data: ' . json_encode($row));\n        $command = $this->currCommand;\n        $command->emit('result', [$row]);\n    }\n\n    private function onError(\\Exception $error)\n    {\n        $this->rsState      = self::RS_STATE_HEADER;\n        $this->resultFields = [];\n\n        // reject current command with error if we're currently executing any commands\n        // ignore unsolicited server error in case we're not executing any commands (connection will be dropped)\n        if ($this->currCommand !== null) {\n            $command = $this->currCommand;\n            $this->currCommand = null;\n\n            $command->emit('error', [$error]);\n        }\n    }\n\n    protected function onResultDone()\n    {\n        $command = $this->currCommand;\n        $this->currCommand = null;\n\n        assert($command instanceof QueryCommand);\n        $command->fields = $this->resultFields;\n        $command->emit('end');\n\n        $this->rsState      = self::RS_STATE_HEADER;\n        $this->resultFields = [];\n    }\n\n    protected function onSuccess()\n    {\n        $command = $this->currCommand;\n        $this->currCommand = null;\n\n        if ($command instanceof QueryCommand) {\n            $command->affectedRows = $this->affectedRows;\n            $command->insertId     = $this->insertId;\n            $command->warningCount = $this->warningCount;\n        }\n        $command->emit('success');\n    }\n\n    public function onClose()\n    {\n        if ($this->currCommand !== null) {\n            $command = $this->currCommand;\n            $this->currCommand = null;\n\n            if ($command instanceof QuitCommand) {\n                $command->emit('success');\n            } else {\n                $command->emit('error', [new \\RuntimeException(\n                    'Connection closing (ECONNABORTED)',\n                    \\defined('SOCKET_ECONNABORTED') ? \\SOCKET_ECONNABORTED : 103\n                )]);\n            }\n        }\n    }\n\n    public function sendPacket($packet)\n    {\n        return $this->stream->write($this->buffer->buildInt3(\\strlen($packet)) . $this->buffer->buildInt1($this->seq++) . $packet);\n    }\n\n    protected function nextRequest($isHandshake = false)\n    {\n        if (!$isHandshake && $this->phase != self::PHASE_HANDSHAKED) {\n            return false;\n        }\n\n        if ($this->currCommand === null && !$this->executor->isIdle()) {\n            $command = $this->executor->dequeue();\n            $this->currCommand = $command;\n\n            if ($command instanceof AuthenticateCommand) {\n                $this->phase = self::PHASE_AUTH_SENT;\n                try {\n                    $this->sendPacket($command->authenticatePacket($this->scramble, $this->authPlugin, $this->buffer));\n                } catch (\\UnexpectedValueException $e) {\n                    $this->onError($e);\n                    $this->stream->close();\n                }\n            } else {\n                $this->seq = 0;\n                $this->sendPacket($this->buffer->buildInt1($command->getId()) . $command->getSql());\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Io/Query.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\n/**\n * @internal\n */\nclass Query\n{\n    private $sql;\n\n    private $builtSql;\n\n    private $params = [];\n\n    /**\n     * Mapping from byte/character to escaped character string\n     *\n     * Note that this mapping assumes an ASCII-compatible charset encoding such\n     * as UTF-8, ISO 8859 and others.\n     *\n     * Note that `'` will be escaped as `''` instead of `\\'` to provide some\n     * limited support for the `NO_BACKSLASH_ESCAPES` SQL mode. This assumes all\n     * strings will always be enclosed in `'` instead of `\"` which is guaranteed\n     * as long as this class is only used internally for the `query()` method.\n     *\n     * @var array<string,string>\n     * @see \\React\\Mysql\\Commands\\AuthenticateCommand::$charsetMap\n     */\n    private $escapeChars = [\n            //\"\\x00\"   => \"\\\\0\",\n            //\"\\r\"   => \"\\\\r\",\n            //\"\\n\"   => \"\\\\n\",\n            //\"\\t\"   => \"\\\\t\",\n            //\"\\b\"   => \"\\\\b\",\n            //\"\\x1a\" => \"\\\\Z\",\n            \"'\"    => \"''\",\n            //'\"'    => '\\\"',\n            \"\\\\\"   => \"\\\\\\\\\",\n            //\"%\"    => \"\\\\%\",\n            //\"_\"    => \"\\\\_\",\n        ];\n\n    /**\n     * @param string $sql\n     * @param list<string|int|float|bool|null> $params\n     * @throws \\InvalidArgumentException if given $params are invalid\n     */\n    public function __construct($sql, array $params = [])\n    {\n        foreach ($params as $param) {\n            if (!\\is_scalar($param) && $param !== null) {\n                throw new \\InvalidArgumentException('Query param must be of type string|int|float|bool|null, ' . (\\is_object($param) ? \\get_class($param) : \\gettype($param)) . ' given');\n            }\n        }\n\n        $this->sql = $sql;\n        $this->builtSql = $params ? null : $sql;\n        $this->params = $params;\n    }\n\n    public function escape($str)\n    {\n        return strtr($str, $this->escapeChars);\n    }\n\n    /**\n     * @param  mixed  $value\n     * @return string\n     */\n    protected function resolveValueForSql($value)\n    {\n        $type = gettype($value);\n        switch ($type) {\n            case 'boolean':\n                $value = (int) $value;\n                break;\n            case 'double':\n            case 'integer':\n                break;\n            case 'string':\n                $value = \"'\" . $this->escape($value) . \"'\";\n                break;\n            case 'NULL':\n                $value = 'NULL';\n                break;\n        }\n\n        return $value;\n    }\n\n    protected function buildSql()\n    {\n        $sql = $this->sql;\n\n        $offset = strpos($sql, '?');\n        foreach ($this->params as $param) {\n            $replacement = $this->resolveValueForSql($param);\n            $sql = substr_replace($sql, $replacement, $offset, 1);\n            $offset = strpos($sql, '?', $offset + strlen($replacement));\n        }\n        if ($offset !== false) {\n            throw new \\LogicException('Params not enough to build sql');\n        }\n\n        return $sql;\n        /*\n        $names    = [];\n        $inName   = false;\n        $currName = '';\n        $currIdx  = 0;\n        $sql      = $this->sql;\n        $len      = strlen($sql);\n        $i        = 0;\n        do {\n            $c    = $sql[$i];\n            if ($c === '?') {\n                $names[$i] = $c;\n            } elseif ($c === ':') {\n                $currName .= $c;\n                $currIdx  = $i;\n                $inName   = true;\n            } elseif ($c === ' ') {\n                $inName   = false;\n                if ($currName) {\n                    $names[$currIdx] = $currName;\n                    $currName = '';\n                }\n            } else {\n                if ($inName) {\n                    $currName .= $c;\n                }\n            }\n        } while (++ $i < $len);\n\n        if ($inName) {\n            $names[$currIdx] = $currName;\n        }\n\n        $namedMarks = $unnamedMarks = [];\n        foreach ($this->params as $arg) {\n            if (is_array($arg)) {\n                $namedMarks += $arg;\n            } else {\n                $unnamedMarks[] = $arg;\n            }\n        }\n\n        $offset = 0;\n        foreach ($names as $idx => $value) {\n            if ($value === '?') {\n                $replacement = array_shift($unnamedMarks);\n            } else {\n                $replacement = $namedMarks[$value];\n            }\n            list($arg, $len) = $this->getEscapedStringAndLen($replacement);\n            $sql = substr_replace($sql, $arg, $idx + $offset, strlen($value));\n            $offset += $len - strlen($value);\n        }\n\n        return $sql;\n        */\n    }\n\n    /**\n     * Get the constructed and escaped sql string.\n     *\n     * @return string\n     */\n    public function getSql()\n    {\n        if ($this->builtSql === null) {\n            $this->builtSql = $this->buildSql();\n        }\n\n        return $this->builtSql;\n    }\n}\n"
  },
  {
    "path": "src/Io/QueryStream.php",
    "content": "<?php\n\nnamespace React\\Mysql\\Io;\n\nuse Evenement\\EventEmitter;\nuse React\\Mysql\\Commands\\QueryCommand;\nuse React\\Socket\\ConnectionInterface;\nuse React\\Stream\\ReadableStreamInterface;\nuse React\\Stream\\Util;\nuse React\\Stream\\WritableStreamInterface;\n\n/**\n * @internal\n * @see Connection::queryStream()\n */\nclass QueryStream extends EventEmitter implements ReadableStreamInterface\n{\n    private $connection;\n    private $started = false;\n    private $closed = false;\n    private $paused = false;\n\n    public function __construct(QueryCommand $command, ConnectionInterface $connection)\n    {\n        $this->connection = $connection;\n\n        // forward result set rows until result set end\n        $command->on('result', function ($row) {\n            if (!$this->started && $this->paused) {\n                $this->connection->pause();\n            }\n            $this->started = true;\n\n            $this->emit('data', [$row]);\n        });\n        $command->on('end', function () {\n            $this->emit('end');\n            $this->close();\n        });\n\n        // status reply (response without result set) ends stream without data\n        $command->on('success', function () {\n            $this->emit('end');\n            $this->close();\n        });\n        $command->on('error', function ($err) {\n            $this->emit('error', [$err]);\n            $this->close();\n        });\n    }\n\n    public function isReadable()\n    {\n        return !$this->closed;\n    }\n\n    public function pause()\n    {\n        $this->paused = true;\n        if ($this->started && !$this->closed) {\n            $this->connection->pause();\n        }\n    }\n\n    public function resume()\n    {\n        $this->paused = false;\n        if ($this->started && !$this->closed) {\n            $this->connection->resume();\n        }\n    }\n\n    public function close()\n    {\n        if ($this->closed) {\n            return;\n        }\n\n        $this->closed = true;\n        if ($this->started && $this->paused) {\n            $this->connection->resume();\n        }\n\n        $this->emit('close');\n        $this->removeAllListeners();\n    }\n\n    public function pipe(WritableStreamInterface $dest, array $options = [])\n    {\n        return Util::pipe($this, $dest, $options);\n    }\n}\n"
  },
  {
    "path": "src/MysqlClient.php",
    "content": "<?php\n\nnamespace React\\Mysql;\n\nuse Evenement\\EventEmitter;\nuse React\\EventLoop\\LoopInterface;\nuse React\\Mysql\\Commands\\AuthenticateCommand;\nuse React\\Mysql\\Io\\Connection;\nuse React\\Mysql\\Io\\Factory;\nuse React\\Mysql\\Io\\Query;\nuse React\\Promise\\Deferred;\nuse React\\Promise\\Promise;\nuse React\\Promise\\PromiseInterface;\nuse React\\Socket\\ConnectorInterface;\nuse React\\Stream\\ReadableStreamInterface;\n\n/**\n * This class represents a connection that is responsible for communicating\n * with your MySQL server instance, managing the connection state and sending\n * your database queries.\n *\n * Besides defining a few methods, this class also implements the\n * `EventEmitterInterface` which allows you to react to certain events:\n *\n * error event:\n *     The `error` event will be emitted once a fatal error occurs, such as\n *     when the connection is lost or is invalid.\n *     The event receives a single `Exception` argument for the error instance.\n *\n *     ```php\n *     $mysql->on('error', function (Exception $e) {\n *         echo 'Error: ' . $e->getMessage() . PHP_EOL;\n *     });\n *     ```\n *\n *     This event will only be triggered for fatal errors and will be followed\n *     by closing the connection. It is not to be confused with \"soft\" errors\n *     caused by invalid SQL queries.\n *\n * close event:\n *     The `close` event will be emitted once the connection closes (terminates).\n *\n *     ```php\n *     $mysql->on('close', function () {\n *         echo 'Connection closed' . PHP_EOL;\n *     });\n *     ```\n *\n *     See also the [`close()`](#close) method.\n *\n * @final\n */\nclass MysqlClient extends EventEmitter\n{\n    private $factory;\n    private $uri;\n    private $closed = false;\n\n    /** @var PromiseInterface<Connection>|null */\n    private $connecting;\n\n    /** @var ?Connection */\n    private $connection;\n\n    /**\n     * array of outstanding connection requests to send next commands once a connection becomes ready\n     *\n     * @var array<int,Deferred<Connection>>\n     */\n    private $pending = [];\n\n    /**\n     * set to true only between calling `quit()` and the connection closing in response\n     *\n     * @var bool\n     * @see self::quit()\n     * @see self::$closed\n     */\n    private $quitting = false;\n\n    /**\n     * @param string $uri\n     * @param ?ConnectorInterface $connector\n     * @param ?LoopInterface $loop\n     * @throws \\InvalidArgumentException if $uri is not a valid MySQL URI\n     */\n    public function __construct(\n        #[\\SensitiveParameter]\n        $uri,\n        $connector = null,\n        $loop = null\n    ) {\n        if (strpos($uri, '://') === false) {\n            $uri = 'mysql://' . $uri;\n        }\n\n        $parts = parse_url($uri);\n        if ($parts === false || !isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'mysql') {\n            $uri = preg_replace('#:[^:/]*@#', ':***@', $uri);\n            throw new \\InvalidArgumentException(\n                'Invalid MySQL URI \"' . $uri . '\" (EINVAL)',\n                defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22\n            );\n        }\n\n        if (isset($parts['query'])) {\n            $query = [];\n            parse_str($parts['query'], $query);\n\n            // validate charset if given\n            if (isset($query['charset'])) {\n                new AuthenticateCommand('', '', '', $query['charset']);\n            }\n        }\n\n        if ($connector !== null && !$connector instanceof ConnectorInterface) { // manual type check to support legacy PHP < 7.1\n            throw new \\InvalidArgumentException('Argument #2 ($connector) expected null|React\\Socket\\ConnectorInterface');\n        }\n        if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1\n            throw new \\InvalidArgumentException('Argument #3 ($loop) expected null|React\\EventLoop\\LoopInterface');\n        }\n\n        $this->factory = new Factory($loop, $connector);\n        $this->uri = $uri;\n    }\n\n    /**\n     * Performs an async query.\n     *\n     * This method returns a promise that will resolve with a `MysqlResult` on\n     * success or will reject with an `Exception` on error. The MySQL protocol\n     * is inherently sequential, so that all queries will be performed in order\n     * and outstanding queries will be put into a queue to be executed once the\n     * previous queries are completed.\n     *\n     * ```php\n     * $mysql->query('CREATE TABLE test ...');\n     * $mysql->query('INSERT INTO test (id) VALUES (1)');\n     * ```\n     *\n     * If this SQL statement returns a result set (such as from a `SELECT`\n     * statement), this method will buffer everything in memory until the result\n     * set is completed and will then resolve the resulting promise. This is\n     * the preferred method if you know your result set to not exceed a few\n     * dozens or hundreds of rows. If the size of your result set is either\n     * unknown or known to be too large to fit into memory, you should use the\n     * [`queryStream()`](#querystream) method instead.\n     *\n     * ```php\n     * $mysql->query($query)->then(function (React\\Mysql\\MysqlResult $command) {\n     *     if (isset($command->resultRows)) {\n     *         // this is a response to a SELECT etc. with some rows (0+)\n     *         print_r($command->resultFields);\n     *         print_r($command->resultRows);\n     *         echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;\n     *     } else {\n     *         // this is an OK message in response to an UPDATE etc.\n     *         if ($command->insertId !== 0) {\n     *             var_dump('last insert ID', $command->insertId);\n     *         }\n     *         echo 'Query OK, ' . $command->affectedRows . ' row(s) affected' . PHP_EOL;\n     *     }\n     * }, function (Exception $error) {\n     *     // the query was not executed successfully\n     *     echo 'Error: ' . $error->getMessage() . PHP_EOL;\n     * });\n     * ```\n     *\n     * You can optionally pass an array of `$params` that will be bound to the\n     * query like this:\n     *\n     * ```php\n     * $mysql->query('SELECT * FROM user WHERE id > ?', [$id]);\n     * ```\n     *\n     * The given `$sql` parameter MUST contain a single statement. Support\n     * for multiple statements is disabled for security reasons because it\n     * could allow for possible SQL injection attacks and this API is not\n     * suited for exposing multiple possible results.\n     *\n     * @param string $sql SQL statement\n     * @param list<string|int|float|bool|null> $params Parameters which should be bound to query\n     * @return PromiseInterface<MysqlResult>\n     *     Resolves with a `MysqlResult` on success or rejects with an `Exception` on error.\n     * @throws \\InvalidArgumentException if given $params are invalid\n     */\n    public function query($sql, array $params = [])\n    {\n        $query = new Query($sql, $params);\n\n        if ($this->closed || $this->quitting) {\n            return \\React\\Promise\\reject(new Exception('Connection closed'));\n        }\n\n        return $this->getConnection()->then(function (Connection $connection) use ($query) {\n            return $connection->query($query)->then(function (MysqlResult $result) use ($connection) {\n                $this->handleConnectionReady($connection);\n                return $result;\n            }, function (\\Exception $e) use ($connection) {\n                $this->handleConnectionReady($connection);\n                throw $e;\n            });\n        });\n    }\n\n    /**\n     * Performs an async query and streams the rows of the result set.\n     *\n     * This method returns a readable stream that will emit each row of the\n     * result set as a `data` event. It will only buffer data to complete a\n     * single row in memory and will not store the whole result set. This allows\n     * you to process result sets of unlimited size that would not otherwise fit\n     * into memory. If you know your result set to not exceed a few dozens or\n     * hundreds of rows, you may want to use the [`query()`](#query) method instead.\n     *\n     * ```php\n     * $stream = $mysql->queryStream('SELECT * FROM user');\n     * $stream->on('data', function ($row) {\n     *     echo $row['name'] . PHP_EOL;\n     * });\n     * $stream->on('end', function () {\n     *     echo 'Completed.';\n     * });\n     * ```\n     *\n     * You can optionally pass an array of `$params` that will be bound to the\n     * query like this:\n     *\n     * ```php\n     * $stream = $mysql->queryStream('SELECT * FROM user WHERE id > ?', [$id]);\n     * ```\n     *\n     * This method is specifically designed for queries that return a result set\n     * (such as from a `SELECT` or `EXPLAIN` statement). Queries that do not\n     * return a result set (such as a `UPDATE` or `INSERT` statement) will not\n     * emit any `data` events.\n     *\n     * See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)\n     * for more details about how readable streams can be used in ReactPHP. For\n     * example, you can also use its `pipe()` method to forward the result set\n     * rows to a [`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface)\n     * like this:\n     *\n     * ```php\n     * $mysql->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger);\n     * ```\n     *\n     * Note that as per the underlying stream definition, calling `pause()` and\n     * `resume()` on this stream is advisory-only, i.e. the stream MAY continue\n     * emitting some data until the underlying network buffer is drained. Also\n     * notice that the server side limits how long a connection is allowed to be\n     * in a state that has outgoing data. Special care should be taken to ensure\n     * the stream is resumed in time. This implies that using `pipe()` with a\n     * slow destination stream may cause the connection to abort after a while.\n     *\n     * The given `$sql` parameter MUST contain a single statement. Support\n     * for multiple statements is disabled for security reasons because it\n     * could allow for possible SQL injection attacks and this API is not\n     * suited for exposing multiple possible results.\n     *\n     * @param string $sql SQL statement\n     * @param list<string|int|float|bool|null> $params Parameters which should be bound to query\n     * @return ReadableStreamInterface\n     * @throws \\InvalidArgumentException if given $params are invalid\n     * @throws Exception if connection is already closed/closing\n     */\n    public function queryStream($sql, array $params = [])\n    {\n        $query = new Query($sql, $params);\n\n        if ($this->closed || $this->quitting) {\n            throw new Exception('Connection closed');\n        }\n\n        return \\React\\Promise\\Stream\\unwrapReadable(\n            $this->getConnection()->then(function (Connection $connection) use ($query) {\n                $stream = $connection->queryStream($query);\n\n                $stream->on('end', function () use ($connection) {\n                    $this->handleConnectionReady($connection);\n                });\n                $stream->on('error', function () use ($connection) {\n                    $this->handleConnectionReady($connection);\n                });\n\n                return $stream;\n            })\n        );\n    }\n\n    /**\n     * Checks that the connection is alive.\n     *\n     * This method returns a promise that will resolve (with a void value) on\n     * success or will reject with an `Exception` on error. The MySQL protocol\n     * is inherently sequential, so that all commands will be performed in order\n     * and outstanding command will be put into a queue to be executed once the\n     * previous queries are completed.\n     *\n     * ```php\n     * $mysql->ping()->then(function () {\n     *     echo 'OK' . PHP_EOL;\n     * }, function (Exception $e) {\n     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;\n     * });\n     * ```\n     *\n     * @return PromiseInterface<void>\n     *     Resolves with a `void` value on success or rejects with an `Exception` on error.\n     */\n    public function ping()\n    {\n        if ($this->closed || $this->quitting) {\n            return \\React\\Promise\\reject(new Exception('Connection closed'));\n        }\n\n        return $this->getConnection()->then(function (Connection $connection) {\n            return $connection->ping()->then(function () use ($connection) {\n                $this->handleConnectionReady($connection);\n            }, function (\\Exception $e) use ($connection) {\n                $this->handleConnectionReady($connection);\n                throw $e;\n            });\n        });\n    }\n\n    /**\n     * Quits (soft-close) the connection.\n     *\n     * This method returns a promise that will resolve (with a void value) on\n     * success or will reject with an `Exception` on error. The MySQL protocol\n     * is inherently sequential, so that all commands will be performed in order\n     * and outstanding commands will be put into a queue to be executed once the\n     * previous commands are completed.\n     *\n     * ```php\n     * $mysql->query('CREATE TABLE test ...');\n     * $mysql->quit();\n     * ```\n     *\n     * This method will gracefully close the connection to the MySQL database\n     * server once all outstanding commands are completed. See also\n     * [`close()`](#close) if you want to force-close the connection without\n     * waiting for any commands to complete instead.\n     *\n     * @return PromiseInterface<void>\n     *     Resolves with a `void` value on success or rejects with an `Exception` on error.\n     */\n    public function quit()\n    {\n        if ($this->closed || $this->quitting) {\n            return \\React\\Promise\\reject(new Exception('Connection closed'));\n        }\n\n        // not already connecting => no need to connect, simply close virtual connection\n        if ($this->connection === null && $this->connecting === null) {\n            $this->close();\n            return \\React\\Promise\\resolve(null);\n        }\n\n        $this->quitting = true;\n        return new Promise(function (callable $resolve, callable $reject) {\n            $this->getConnection()->then(function (Connection $connection) use ($resolve, $reject) {\n                // soft-close connection and emit close event afterwards both on success or on error\n                $connection->quit()->then(\n                    function () use ($resolve){\n                        $resolve(null);\n                        $this->close();\n                    },\n                    function (\\Exception $e) use ($reject) {\n                        $reject($e);\n                        $this->close();\n                    }\n                );\n            }, function (\\Exception $e) use ($reject) {\n                // emit close event afterwards when no connection can be established\n                $reject($e);\n                $this->close();\n            });\n        });\n    }\n\n    /**\n     * Force-close the connection.\n     *\n     * Unlike the `quit()` method, this method will immediately force-close the\n     * connection and reject all outstanding commands.\n     *\n     * ```php\n     * $mysql->close();\n     * ```\n     *\n     * Forcefully closing the connection will yield a warning in the server logs\n     * and should generally only be used as a last resort. See also\n     * [`quit()`](#quit) as a safe alternative.\n     *\n     * @return void\n     */\n    public function close()\n    {\n        if ($this->closed) {\n            return;\n        }\n\n        $this->closed = true;\n        $this->quitting = false;\n\n        // either close active connection or cancel pending connection attempt\n        // below branches are exclusive, there can only be a single connection\n        if ($this->connection !== null) {\n            $this->connection->close();\n            $this->connection = null;\n        } elseif ($this->connecting !== null) {\n            $this->connecting->cancel();\n            $this->connecting = null;\n        }\n\n        // clear all outstanding commands\n        foreach ($this->pending as $deferred) {\n            $deferred->reject(new \\RuntimeException('Connection closed'));\n        }\n        $this->pending = [];\n\n        $this->emit('close');\n        $this->removeAllListeners();\n    }\n\n\n    /**\n     * @return PromiseInterface<Connection>\n     */\n    private function getConnection()\n    {\n        $deferred = new Deferred();\n\n        // force-close connection if still waiting for previous disconnection due to idle timer\n        if ($this->connection !== null && $this->connection->state === Connection::STATE_CLOSING) {\n            $this->connection->close();\n            $this->connection = null;\n        }\n\n        // happy path: reuse existing connection unless it is currently busy executing another command\n        if ($this->connection !== null && !$this->connection->isBusy()) {\n            $deferred->resolve($this->connection);\n            return $deferred->promise();\n        }\n\n        // queue pending connection request until connection becomes ready\n        $this->pending[] = $deferred;\n\n        // create new connection if not already connected or connecting\n        if ($this->connection === null && $this->connecting === null) {\n            $this->connecting = $this->factory->createConnection($this->uri);\n            $this->connecting->then(function (Connection $connection) {\n                // connection completed => remember only until closed\n                $this->connecting = null;\n                $this->connection = $connection;\n                $connection->on('close', function () {\n                    $this->connection = null;\n                });\n\n                // handle first command from queue when connection is ready\n                $this->handleConnectionReady($connection);\n            }, function (\\Exception $e) {\n                // connection failed => discard connection attempt\n                $this->connecting = null;\n\n                foreach ($this->pending as $key => $deferred) {\n                    $deferred->reject($e);\n                    unset($this->pending[$key]);\n                }\n            });\n        }\n\n        return $deferred->promise();\n    }\n\n    private function handleConnectionReady(Connection $connection)\n    {\n        $deferred = \\reset($this->pending);\n        if ($deferred === false) {\n            // nothing to do if there are no outstanding connection requests\n            return;\n        }\n\n        assert($deferred instanceof Deferred);\n        unset($this->pending[\\key($this->pending)]);\n\n        $deferred->resolve($connection);\n    }\n}\n"
  },
  {
    "path": "src/MysqlResult.php",
    "content": "<?php\n\nnamespace React\\Mysql;\n\nclass MysqlResult\n{\n    /**\n     * last inserted ID (if any)\n     * @var int|null\n     */\n    public $insertId;\n\n    /**\n     * number of affected rows (for UPDATE, DELETE etc.)\n     *\n     * @var int|null\n     */\n    public $affectedRows;\n\n    /**\n     * result set fields (if any)\n     *\n     * @var array|null\n     */\n    public $resultFields;\n\n    /**\n     * result set rows (if any)\n     *\n     * @var array|null\n     */\n    public $resultRows;\n\n    /**\n     * number of warnings (if any)\n     * @var int|null\n     */\n    public $warningCount;\n}\n"
  },
  {
    "path": "tests/BaseTestCase.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql;\n\nuse PHPUnit\\Framework\\TestCase;\nuse React\\EventLoop\\LoopInterface;\nuse React\\Mysql\\Io\\Connection;\nuse React\\Mysql\\Io\\Factory;\n\nclass BaseTestCase extends TestCase\n{\n    protected function getConnectionOptions($debug = false)\n    {\n        // can be controlled through ENV or by changing defaults in phpunit.xml\n        return [\n            'host'   => getenv('DB_HOST'),\n            'port'   => (int)getenv('DB_PORT'),\n            'dbname' => getenv('DB_DBNAME'),\n            'user'   => getenv('DB_USER'),\n            'passwd' => getenv('DB_PASSWD'),\n        ] + ($debug ? ['debug' => true] : []);\n    }\n\n    protected function getConnectionString($params = [])\n    {\n        $parts = $params + $this->getConnectionOptions();\n\n        return 'mysql://' . rawurlencode($parts['user']) . ':' . rawurlencode($parts['passwd']) . '@' . $parts['host'] . ':' . $parts['port'] . '/' . rawurlencode($parts['dbname']);\n    }\n\n    /**\n     * @param LoopInterface $loop\n     * @return Connection\n     */\n    protected function createConnection(LoopInterface $loop)\n    {\n        $factory = new Factory($loop);\n        $promise = $factory->createConnection($this->getConnectionString());\n\n        return \\React\\Async\\await(\\React\\Promise\\Timer\\timeout($promise, 10.0));\n    }\n\n    protected function getDataTable()\n    {\n        return <<<SQL\nCREATE TABLE `book` (\n    `id`      INT(11)      NOT NULL AUTO_INCREMENT,\n    `name`    VARCHAR(255) NOT NULL,\n    `isbn`    VARCHAR(255) NULL,\n    `author`  VARCHAR(255) NULL,\n    `created` INT(11)      NULL,\n    PRIMARY KEY (`id`)\n)\nSQL;\n    }\n\n    protected function expectCallableOnce()\n    {\n        $mock = $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock();\n        $mock->expects($this->once())->method('__invoke');\n\n        return $mock;\n    }\n\n    protected function expectCallableOnceWith($value)\n    {\n        $mock = $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock();\n        $mock->expects($this->once())->method('__invoke')->with($value);\n\n        return $mock;\n    }\n\n    protected function expectCallableNever()\n    {\n        $mock = $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock();\n        $mock->expects($this->never())->method('__invoke');\n\n        return $mock;\n    }\n\n    public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)\n    {\n        if (method_exists($this, 'expectException')) {\n            // PHPUnit 5.2+\n            $this->expectException($exception);\n            if ($exceptionMessage !== '') {\n                $this->expectExceptionMessage($exceptionMessage);\n            }\n            if ($exceptionCode !== null) {\n                $this->expectExceptionCode($exceptionCode);\n            }\n        } else {\n            // legacy PHPUnit 4 - PHPUnit 5.1\n            parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Commands/AuthenticateCommandTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Commands;\n\nuse PHPUnit\\Framework\\TestCase;\nuse React\\Mysql\\Commands\\AuthenticateCommand;\nuse React\\Mysql\\Io\\Buffer;\n\nclass AuthenticateCommandTest extends TestCase\n{\n    /**\n     * @doesNotPerformAssertions\n     */\n    public function testCtorWithKnownCharset()\n    {\n        new AuthenticateCommand('Alice', 'secret', '', 'utf8');\n    }\n\n    public function testCtorWithUnknownCharsetThrows()\n    {\n        if (method_exists($this, 'expectException')) {\n            $this->expectException('InvalidArgumentException');\n        } else {\n            // legacy PHPUnit < 5.2\n            $this->setExpectedException('InvalidArgumentException');\n        }\n        new AuthenticateCommand('Alice', 'secret', '', 'utf16');\n    }\n\n    public function testAuthenticatePacketWithEmptyPassword()\n    {\n        $command = new AuthenticateCommand('root', '', 'test', 'utf8mb4');\n\n        $data = $command->authenticatePacket('scramble', null, new Buffer());\n\n        $this->assertEquals(\"\\x8d\\xa6\\0\\0\\0\\0\\0\\x01\\x2d\" . str_repeat(\"\\0\", 23) . \"root\\0\" . \"\\0\" . \"test\\0\", $data);\n    }\n\n    public function testAuthenticatePacketWithMysqlNativePasswordAuthPluginAndEmptyPassword()\n    {\n        $command = new AuthenticateCommand('root', '', 'test', 'utf8mb4');\n\n        $data = $command->authenticatePacket('scramble', 'mysql_native_password', new Buffer());\n\n        $this->assertEquals(\"\\x8d\\xa6\\x08\\0\\0\\0\\0\\x01\\x2d\" . str_repeat(\"\\0\", 23) . \"root\\0\" . \"\\0\" . \"test\\0\" . \"mysql_native_password\\0\", $data);\n    }\n\n    public function testAuthenticatePacketWithCachingSha2PasswordAuthPluginAndEmptyPassword()\n    {\n        $command = new AuthenticateCommand('root', '', 'test', 'utf8mb4');\n\n        $data = $command->authenticatePacket('scramble', 'caching_sha2_password', new Buffer());\n\n        $this->assertEquals(\"\\x8d\\xa6\\x08\\0\\0\\0\\0\\x01\\x2d\" . str_repeat(\"\\0\", 23) . \"root\\0\" . \"\\0\" . \"test\\0\" . \"caching_sha2_password\\0\", $data);\n    }\n\n    public function testAuthenticatePacketWithSecretPassword()\n    {\n        $command = new AuthenticateCommand('root', 'secret', 'test', 'utf8mb4');\n\n        $data = $command->authenticatePacket('scramble', null, new Buffer());\n\n        $this->assertEquals(\"\\x8d\\xa6\\0\\0\\0\\0\\0\\x01\\x2d\" . str_repeat(\"\\0\", 23) . \"root\\0\" . \"\\x14\\x18\\xd8\\x8d\\x74\\x77\\x2c\\x27\\x22\\x89\\xe1\\xcd\\xbc\\x4b\\x5f\\x77\\x08\\x18\\x3c\\x6e\\xba\" . \"test\\0\", $data);\n    }\n\n    /**\n     * @requires PHP 7.1\n     * @requires function hash\n     */\n    public function testAuthenticatePacketWithCachingSha2PasswordWithSecretPasswordHashed()\n    {\n        $command = new AuthenticateCommand('root', 'secret', 'test', 'utf8mb4');\n\n        $data = $command->authenticatePacket('scramble', 'caching_sha2_password', new Buffer());\n\n        $this->assertEquals(\"\\x8d\\xa6\\x08\\0\\0\\0\\0\\x01\\x2d\" . str_repeat(\"\\0\", 23) . \"root\\0\" . \"\\x20\\x7a\\x62\\x89\\x95\\x53\\xed\\xdd\\xa4\\x11\\x2d\\x28\\x9a\\x02\\x72\\x12\\xbb\\x4c\\xdd\\xfd\\xd3\\x08\\xfe\\xc3\\x6a\\x85\\xf1\\xe9\\x4a\\xdb\\xcf\\x8b\\xf3\" . \"test\\0\" . \"caching_sha2_password\\0\", $data);\n    }\n\n    public function testAuthenticatePacketWithUnknownAuthPluginThrows()\n    {\n        $command = new AuthenticateCommand('root', 'secret', 'test', 'utf8mb4');\n\n        if (method_exists($this, 'expectException')) {\n            $this->expectException('UnexpectedValueException');\n            $this->expectExceptionMessage('Unknown authentication plugin \"mysql_old_password\" requested by server');\n        } else {\n            // legacy PHPUnit < 5.2\n            $this->setExpectedException('UnexpectedValueException', 'Unknown authentication plugin \"mysql_old_password\" requested by server');\n        }\n        $command->authenticatePacket('scramble', 'mysql_old_password', new Buffer());\n    }\n\n    /**\n     * @requires function openssl_public_encrypt\n     */\n    public function testAuthSha256WithValidPublicKeyReturnsScrambledDataThatCanBeDecryptedByReceiverWithPrivateKey()\n    {\n        $command = new AuthenticateCommand('root', 'secret', 'test', 'utf8mb4');\n\n        $key = openssl_pkey_new();\n\n        $encrypted = $command->authSha256('scramble', openssl_pkey_get_details($key)['key']);\n\n        $decrypted = '';\n        $ok = openssl_private_decrypt($encrypted, $decrypted, $key, OPENSSL_PKCS1_OAEP_PADDING);\n\n        $this->assertTrue($ok);\n        $this->assertEquals(\"secret\\0\", $decrypted ^ \"scramble\");\n    }\n\n    /**\n     * @requires function openssl_public_encrypt\n     */\n    public function testAuthSha256WithPasswordLongerThanScrambleLengthReturnsScrambledDataThatCanBeDecryptedByReceiverWithPrivateKey()\n    {\n        $command = new AuthenticateCommand('root', '012345678901234567890123456789', 'test', 'utf8mb4');\n\n        $key = openssl_pkey_new();\n\n        $encrypted = $command->authSha256('scramble', openssl_pkey_get_details($key)['key']);\n\n        $decrypted = '';\n        $ok = openssl_private_decrypt($encrypted, $decrypted, $key, OPENSSL_PKCS1_OAEP_PADDING);\n\n        $this->assertTrue($ok);\n        $this->assertEquals(\"012345678901234567890123456789\\0\", $decrypted ^ \"scramblescramblescramblescramblescramble\");\n    }\n\n    /**\n     * @requires function openssl_public_encrypt\n     */\n    public function testAuthSha256WithInvalidPublicKeyThrows()\n    {\n        $command = new AuthenticateCommand('root', 'secret', 'test', 'utf8mb4');\n\n        if (method_exists($this, 'expectException')) {\n            $this->expectException('UnexpectedValueException');\n            $this->expectExceptionMessage('Failed to encrypt password with public key');\n        } else {\n            // legacy PHPUnit < 5.2\n            $this->setExpectedException('UnexpectedValueException', 'Failed to encrypt password with public key');\n        }\n        $command->authSha256('scramble', 'invalid pubkey');\n    }\n}\n"
  },
  {
    "path": "tests/Io/BufferTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Io;\n\nuse React\\Mysql\\Io\\Buffer;\nuse React\\Tests\\Mysql\\BaseTestCase;\n\nclass BufferTest extends BaseTestCase\n{\n    public function testAppendAndReadBinary()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append('hello');\n\n        $this->assertSame('hello', $buffer->read(5));\n    }\n\n    public function testReadBeyondLimitThrows()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append('hi');\n\n        $this->setExpectedException('UnderflowException');\n        $buffer->read(3);\n    }\n\n    public function testReadAfterSkipOne()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append('hi');\n        $buffer->skip(1);\n\n        $this->assertSame('i', $buffer->read(1));\n    }\n\n    public function testReadBufferEmptyIsNoop()\n    {\n        $buffer = new Buffer();\n\n        $new = $buffer->readBuffer(0);\n\n        $this->assertSame(0, $buffer->length());\n        $this->assertSame(0, $new->length());\n    }\n\n    public function testReadBufferReturnsBufferWithOriginalLengthAndClearsOriginalBuffer()\n    {\n        $buffer = new Buffer();\n        $buffer->append('foo');\n\n        $new = $buffer->readBuffer($buffer->length());\n\n        $this->assertSame(0, $buffer->length());\n        $this->assertSame(3, $new->length());\n    }\n\n    public function testReadBufferBeyondLimitThrows()\n    {\n        $buffer = new Buffer();\n\n        $this->setExpectedException('UnderflowException');\n        $buffer->readBuffer(3);\n    }\n\n    public function testSkipZeroThrows()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append('hi');\n\n        $this->setExpectedException('UnderflowException');\n        $buffer->skip(0);\n    }\n\n    public function testSkipBeyondLimitThrows()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append('hi');\n\n        $this->setExpectedException('UnderflowException');\n        $buffer->skip(3);\n    }\n\n    public function testParseInt1()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildInt1(0) . $buffer->buildInt1(255));\n\n        $this->assertSame(0, $buffer->readInt1());\n        $this->assertSame(255, $buffer->readInt1());\n    }\n\n    public function testParseInt2()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildInt2(0) . $buffer->buildInt2(65535));\n\n        $this->assertSame(0, $buffer->readInt2());\n        $this->assertSame(65535, $buffer->readInt2());\n    }\n\n    public function testParseInt3()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildInt3(0) . $buffer->buildInt3(0xFFFFFF));\n\n        $this->assertSame(0, $buffer->readInt3());\n        $this->assertSame(0xFFFFFF, $buffer->readInt3());\n    }\n\n    public function testParseInt8()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildInt8(0) . $buffer->buildInt8(PHP_INT_MAX));\n\n        $this->assertSame(0, $buffer->readInt8());\n        $this->assertSame(PHP_INT_MAX, $buffer->readInt8());\n    }\n\n    public function testParseIntLen()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append(\"\\x0A\" . \"\\xFC\" . \"\\x00\\x04\");\n\n        $this->assertSame(10, $buffer->readIntLen());\n        $this->assertSame(1024, $buffer->readIntLen());\n    }\n\n    public function testParseStringEmpty()\n    {\n        $buffer = new Buffer();\n\n        $data = $buffer->buildStringLen('');\n        $this->assertEquals(\"\\x00\", $data);\n\n        $buffer->append($data);\n        $this->assertSame('', $buffer->readStringLen());\n    }\n\n    public function testParseStringShort()\n    {\n        $buffer = new Buffer();\n\n        $data = $buffer->buildStringLen('hello');\n        $this->assertEquals(\"\\x05\" . \"hello\", $data);\n\n        $buffer->append($data);\n        $this->assertSame('hello', $buffer->readStringLen());\n    }\n\n    public function testParseStringKilo()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildStringLen(str_repeat('.', 1024)));\n\n        $this->assertSame(1024, strlen($buffer->readStringLen()));\n    }\n\n    public function testParseStringMega()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildStringLen(str_repeat('.', 1000000)));\n\n        $this->assertSame(1000000, strlen($buffer->readStringLen()));\n    }\n\n    /**\n     * Test encoding/parsing string larger than 16 MiB. This should not happen\n     * in practice as the protocol parser is currently limited to a packet\n     * size of 16 MiB.\n     */\n    public function testParseStringExcessive()\n    {\n        $buffer = new Buffer();\n\n        $buffer->append($buffer->buildStringLen(str_repeat('.', 17000000)));\n\n        $this->assertSame(17000000, strlen($buffer->readStringLen()));\n    }\n\n    public function testParseStringNullLength()\n    {\n        $buffer = new Buffer();\n\n        $data = $buffer->buildStringLen(null);\n        $this->assertEquals(\"\\xFB\", $data);\n\n        $buffer->append($data);\n        $this->assertNull($buffer->readStringLen());\n    }\n\n    public function testParseStringNullCharacterTwice()\n    {\n        $buffer = new Buffer();\n        $buffer->append(\"hello\" . \"\\x00\" . \"world\" . \"\\x00\");\n\n        $this->assertEquals('hello', $buffer->readStringNull());\n        $this->assertEquals('world', $buffer->readStringNull());\n    }\n\n    public function testParseStringNullCharacterThrowsIfNullNotFound()\n    {\n        $buffer = new Buffer();\n        $buffer->append(\"hello\");\n\n        $this->setExpectedException('UnderflowException');\n        $buffer->readStringNull();\n    }\n}\n"
  },
  {
    "path": "tests/Io/ConnectionTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Io;\n\nuse React\\Mysql\\Io\\Connection;\nuse React\\Mysql\\Io\\Query;\nuse React\\Tests\\Mysql\\BaseTestCase;\n\nclass ConnectionTest extends BaseTestCase\n{\n    public function testIsBusyReturnsTrueWhenParserIsBusy()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue', 'isIdle'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n        $executor->expects($this->never())->method('isIdle');\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n        $parser->expects($this->once())->method('isBusy')->willReturn(true);\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $connection->query(new Query('SELECT 1'));\n\n        $this->assertTrue($connection->isBusy());\n    }\n\n    public function testIsBusyReturnsFalseWhenParserIsNotBusyAndExecutorIsIdle()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->getMock();\n        $executor->expects($this->once())->method('isIdle')->willReturn(true);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertFalse($connection->isBusy());\n    }\n\n    public function testQueryWillEnqueueOneCommand()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $stream->expects($this->never())->method('close');\n\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->query(new Query('SELECT 1'));\n    }\n\n    public function testQueryWillReturnResolvedPromiseAndStartIdleTimerWhenQueryCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->query(new Query('SELECT 1'));\n\n        $promise->then($this->expectCallableOnceWith($this->isInstanceOf('React\\Mysql\\MysqlResult')));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testQueryWillReturnResolvedPromiseAndStartIdleTimerWhenQueryCommandEmitsEnd()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->query(new Query('SELECT 1'));\n\n        $promise->then($this->expectCallableOnceWith($this->isInstanceOf('React\\Mysql\\MysqlResult')));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('end');\n    }\n\n    public function testQueryWillReturnResolvedPromiseAndStartIdleTimerWhenIdlePeriodIsGivenAndQueryCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(1.0, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, 1.0);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->query(new Query('SELECT 1'));\n\n        $promise->then($this->expectCallableOnceWith($this->isInstanceOf('React\\Mysql\\MysqlResult')));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testQueryWillReturnResolvedPromiseAndNotStartIdleTimerWhenIdlePeriodIsNegativeAndQueryCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, -1);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->query(new Query('SELECT 1'));\n\n        $promise->then($this->expectCallableOnceWith($this->isInstanceOf('React\\Mysql\\MysqlResult')));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testQueryWillReturnRejectedPromiseAndStartIdleTimerWhenQueryCommandEmitsError()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->query(new Query('SELECT 1'));\n\n        $promise->then(null, $this->expectCallableOnce());\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('error', [new \\RuntimeException()]);\n    }\n\n    public function testQueryFollowedByIdleTimerWillQuitUnderlyingConnectionAndEmitCloseEventWhenQuitCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $stream->expects($this->once())->method('close');\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->any())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timeout = null;\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->callback(function ($cb) use (&$timeout) {\n            $timeout = $cb;\n            return true;\n        }))->willReturn($timer);\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $connection->on('close', $this->expectCallableOnce());\n\n        $this->assertNull($currentCommand);\n\n        $connection->query(new Query('SELECT 1'));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n\n        $this->assertNotNull($timeout);\n        $timeout();\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testQueryFollowedByIdleTimerWillQuitUnderlyingConnectionAndEmitCloseEventWhenQuitCommandEmitsError()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $stream->expects($this->once())->method('close');\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->any())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timeout = null;\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->callback(function ($cb) use (&$timeout) {\n            $timeout = $cb;\n            return true;\n        }))->willReturn($timer);\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $connection->on('close', $this->expectCallableOnce());\n\n        $this->assertNull($currentCommand);\n\n        $connection->query(new Query('SELECT 1'));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n\n        $this->assertNotNull($timeout);\n        $timeout();\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('error', [new \\RuntimeException()]);\n    }\n\n    public function testQueryTwiceWillEnqueueSecondQueryWithoutStartingIdleTimerWhenFirstQueryCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->any())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $connection->query(new Query('SELECT 1'));\n        $connection->query(new Query('SELECT 2'));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testQueryTwiceAfterIdleTimerWasStartedWillCancelIdleTimerAndEnqueueSecondCommand()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->any())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->once())->method('cancelTimer')->with($timer);\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $connection->query(new Query('SELECT 1'));\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n\n        $connection->query(new Query('SELECT 2'));\n    }\n\n    public function testQueryStreamWillEnqueueOneCommand()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $stream->expects($this->never())->method('close');\n\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->queryStream(new Query('SELECT 1'));\n    }\n\n    public function testQueryStreamWillReturnStreamThatWillEmitEndEventAndStartIdleTimerWhenQueryCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $stream = $connection->queryStream(new Query('SELECT 1'));\n\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testQueryStreamWillReturnStreamThatWillEmitErrorEventAndStartIdleTimerWhenQueryCommandEmitsError()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $stream = $connection->queryStream(new Query('SELECT 1'));\n\n        $stream->on('error', $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));\n        $stream->on('close', $this->expectCallableOnce());\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('error', [new \\RuntimeException()]);\n    }\n\n    public function testPingWillEnqueueOneCommand()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $stream->expects($this->never())->method('close');\n\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->ping();\n    }\n\n    public function testPingWillReturnResolvedPromiseAndStartIdleTimerWhenPingCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->ping();\n\n        $promise->then($this->expectCallableOnce());\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n    }\n\n    public function testPingWillReturnRejectedPromiseAndStartIdleTimerWhenPingCommandEmitsError()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->never())->method('cancelTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $promise = $connection->ping();\n\n        $promise->then(null, $this->expectCallableOnce());\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('error', [new \\RuntimeException()]);\n    }\n\n    public function testQuitWillEnqueueOneCommand()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->quit();\n    }\n\n    public function testQuitWillResolveBeforeEmittingCloseEventWhenQuitCommandEmitsSuccess()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $pingCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$pingCommand) {\n            return $pingCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $events = '';\n        $connection->on('close', function () use (&$events) {\n            $events .= 'closed.';\n        });\n\n        $this->assertEquals('', $events);\n\n        $promise = $connection->quit();\n\n        $promise->then(function () use (&$events) {\n            $events .= 'fulfilled.';\n        });\n\n        $this->assertEquals('', $events);\n\n        $this->assertNotNull($pingCommand);\n        $pingCommand->emit('success');\n\n        $this->assertEquals('fulfilled.closed.', $events);\n    }\n\n    public function testQuitWillRejectBeforeEmittingCloseEventWhenQuitCommandEmitsError()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $pingCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$pingCommand) {\n            return $pingCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $events = '';\n        $connection->on('close', function () use (&$events) {\n            $events .= 'closed.';\n        });\n\n        $this->assertEquals('', $events);\n\n        $promise = $connection->quit();\n\n        $promise->then(null, function () use (&$events) {\n            $events .= 'rejected.';\n        });\n\n        $this->assertEquals('', $events);\n\n        $this->assertNotNull($pingCommand);\n        $pingCommand->emit('error', [new \\RuntimeException()]);\n\n        $this->assertEquals('rejected.closed.', $events);\n    }\n\n    public function testCloseWillEmitCloseEvent()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->getMock();\n        $executor->expects($this->once())->method('isIdle')->willReturn(true);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->never())->method('addTimer');\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $connection->on('close', $this->expectCallableOnce());\n\n        $connection->close();\n    }\n\n    public function testCloseAfterIdleTimerWasStartedWillCancelIdleTimerAndEmitCloseEvent()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $currentCommand = null;\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnCallback(function ($command) use (&$currentCommand) {\n            return $currentCommand = $command;\n        });\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $timer = $this->getMockBuilder('React\\EventLoop\\TimerInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer);\n        $loop->expects($this->once())->method('cancelTimer')->with($timer);\n\n        $connection = new Connection($stream, $executor, $parser, $loop, null);\n\n        $this->assertNull($currentCommand);\n\n        $connection->ping();\n\n        $this->assertNotNull($currentCommand);\n        $currentCommand->emit('success');\n\n        $connection->on('close', $this->expectCallableOnce());\n\n        $connection->close();\n    }\n\n    public function testQueryAfterQuitRejectsImmediately()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->quit();\n        $promise = $conn->query(new Query('SELECT 1'));\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection closing (ENOTCONN)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107);\n                })\n            )\n        ));\n    }\n\n    public function testQueryAfterCloseRejectsImmediately()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->never())->method('enqueue');\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->close();\n        $promise = $conn->query(new Query('SELECT 1'));\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection closed (ENOTCONN)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107);\n                })\n            )\n        ));\n    }\n\n    public function testQueryStreamAfterQuitThrows()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->quit();\n\n        try {\n            $conn->queryStream(new Query('SELECT 1'));\n        } catch (\\RuntimeException $e) {\n            $this->assertEquals('Connection closing (ENOTCONN)', $e->getMessage());\n            $this->assertEquals(defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107, $e->getCode());\n        }\n    }\n\n    public function testPingAfterQuitRejectsImmediately()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->quit();\n        $promise = $conn->ping();\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection closing (ENOTCONN)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107);\n                })\n            )\n        ));\n    }\n\n    public function testQuitAfterQuitRejectsImmediately()\n    {\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->once())->method('enqueue')->willReturnArgument(0);\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->quit();\n        $promise = $conn->quit();\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection closing (ENOTCONN)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107);\n                })\n            )\n        ));\n    }\n\n    public function testCloseStreamEmitsErrorEvent()\n    {\n        $closeHandler = null;\n        $stream = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $stream->expects($this->exactly(2))->method('on')->withConsecutive(\n            array('error', $this->anything()),\n            array('close', $this->callback(function ($arg) use (&$closeHandler) {\n                $closeHandler = $arg;\n                return true;\n            }))\n        );\n        $executor = $this->getMockBuilder('React\\Mysql\\Io\\Executor')->setMethods(['enqueue'])->getMock();\n        $executor->expects($this->never())->method('enqueue');\n\n        $parser = $this->getMockBuilder('React\\Mysql\\Io\\Parser')->disableOriginalConstructor()->getMock();\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $conn = new Connection($stream, $executor, $parser, $loop, null);\n        $conn->on('error', $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection closed by peer (ECONNRESET)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104);\n                })\n            )\n        ));\n\n        $this->assertNotNull($closeHandler);\n        $closeHandler();\n    }\n}\n"
  },
  {
    "path": "tests/Io/FactoryTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Io;\n\nuse React\\EventLoop\\Loop;\nuse React\\Mysql\\Io\\Connection;\nuse React\\Mysql\\Io\\Factory;\nuse React\\Promise\\Promise;\nuse React\\Socket\\SocketServer;\nuse React\\Tests\\Mysql\\BaseTestCase;\n\nclass FactoryTest extends BaseTestCase\n{\n    public function testConstructWithoutLoopAssignsLoopAutomatically()\n    {\n        $factory = new Factory();\n\n        $ref = new \\ReflectionProperty($factory, 'loop');\n        $ref->setAccessible(true);\n        $loop = $ref->getValue($factory);\n\n        $this->assertInstanceOf('React\\EventLoop\\LoopInterface', $loop);\n    }\n\n    public function testConnectWillUseHostAndDefaultPort()\n    {\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $pending = $this->getMockBuilder('React\\Promise\\PromiseInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->with('127.0.0.1:3306')->willReturn($pending);\n\n        $factory = new Factory($loop, $connector);\n        $factory->createConnection('mysql://127.0.0.1');\n    }\n\n    public function testConnectWillUseGivenScheme()\n    {\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $pending = $this->getMockBuilder('React\\Promise\\PromiseInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->with('127.0.0.1:3306')->willReturn($pending);\n\n        $factory = new Factory($loop, $connector);\n        $factory->createConnection('mysql://127.0.0.1');\n    }\n\n    public function testConnectWillUseGivenHostAndGivenPort()\n    {\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $pending = $this->getMockBuilder('React\\Promise\\PromiseInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->with('127.0.0.1:1234')->willReturn($pending);\n\n        $factory = new Factory($loop, $connector);\n        $factory->createConnection('mysql://127.0.0.1:1234');\n    }\n\n    public function testConnectWillUseGivenUserInfoAsDatabaseCredentialsAfterUrldecoding()\n    {\n        $connection = $this->getMockBuilder('React\\Socket\\Connection')->disableOriginalConstructor()->setMethods(['write'])->getMock();\n        $connection->expects($this->once())->method('write')->with($this->stringContains(\"user!\\0\"));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->with('127.0.0.1:3306')->willReturn(\\React\\Promise\\resolve($connection));\n\n        $factory = new Factory($loop, $connector);\n        $promise = $factory->createConnection('mysql://user%21@127.0.0.1');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n\n        $connection->emit('data', [\"\\x33\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 44)]);\n    }\n\n    public function testConnectWillUseGivenPathAsDatabaseNameAfterUrldecoding()\n    {\n        $connection = $this->getMockBuilder('React\\Socket\\Connection')->disableOriginalConstructor()->setMethods(['write'])->getMock();\n        $connection->expects($this->once())->method('write')->with($this->stringContains(\"test database\\0\"));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->with('127.0.0.1:3306')->willReturn(\\React\\Promise\\resolve($connection));\n\n        $factory = new Factory($loop, $connector);\n        $promise = $factory->createConnection('mysql://127.0.0.1/test%20database');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n\n        $connection->emit('data', [\"\\x33\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 44)]);\n    }\n\n    public function testConnectWithInvalidHostRejectsWithConnectionError()\n    {\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString(['host' => 'example.invalid']);\n        $promise = $factory->createConnection($uri);\n\n        $promise->then(null, $this->expectCallableOnce());\n\n        Loop::run();\n    }\n\n    public function testConnectWithInvalidPassRejectsWithAuthenticationError()\n    {\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString(['passwd' => 'invalidpass']);\n        $promise = $factory->createConnection($uri);\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return !!preg_match(\"/^Connection to mysql:\\/\\/[^ ]* failed during authentication: Access denied for user '.*?'@'.*?' \\(using password: YES\\) \\(EACCES\\)$/\", $e->getMessage());\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return !!preg_match(\"/^Access denied for user '.*?'@'.*?' \\(using password: YES\\)$/\", $e->getPrevious()->getMessage());\n                })\n            )\n        ));\n\n        Loop::run();\n    }\n\n    public function testConnectWillRejectWhenServerClosesConnection()\n    {\n        $factory = new Factory();\n\n        $socket = new SocketServer('127.0.0.1:0', []);\n        $socket->on('connection', function ($connection) use ($socket) {\n            $socket->close();\n            $connection->close();\n        });\n\n        $parts = parse_url($socket->getAddress());\n        $uri = $this->getConnectionString(['host' => $parts['host'], 'port' => $parts['port']]);\n\n        $promise = $factory->createConnection($uri);\n\n        $uri = preg_replace('/:[^:]*@/', ':***@', $uri);\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) use ($uri) {\n                    return $e->getMessage() === 'Connection to ' . $uri . ' failed during authentication: Connection closed by peer (ECONNRESET)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104);\n                })\n            )\n        ));\n\n        Loop::run();\n    }\n\n    public function testConnectWillRejectOnExplicitTimeoutDespiteValidAuth()\n    {\n        $factory = new Factory();\n\n        $uri = 'mysql://' . $this->getConnectionString() . '?timeout=0';\n\n        $promise = $factory->createConnection($uri);\n\n        $uri = preg_replace('/:[^:]*@/', ':***@', $uri);\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) use ($uri) {\n                    return $e->getMessage() === 'Connection to ' . $uri . ' timed out after 0 seconds (ETIMEDOUT)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);\n                })\n            )\n        ));\n\n        Loop::run();\n    }\n\n    public function testConnectWillRejectOnDefaultTimeoutFromIniDespiteValidAuth()\n    {\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n\n        $old = ini_get('default_socket_timeout');\n        ini_set('default_socket_timeout', '0');\n        $promise = $factory->createConnection($uri);\n        ini_set('default_socket_timeout', $old);\n\n        $uri = preg_replace('/:[^:]*@/', ':***@', $uri);\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) use ($uri) {\n                    return $e->getMessage() === 'Connection to ' . $uri . ' timed out after 0 seconds (ETIMEDOUT)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);\n                })\n            )\n        ));\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthWillRunUntilQuit()\n    {\n        $this->expectOutputString('connected.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->quit()->then(function () {\n                echo 'closed.';\n            });\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthAndWithoutDbNameWillRunUntilQuit()\n    {\n        $this->expectOutputString('connected.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString(['dbname' => '']);\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->quit()->then(function () {\n                echo 'closed.';\n            });\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthWillIgnoreNegativeTimeoutAndRunUntilQuit()\n    {\n        $this->expectOutputString('connected.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString() . '?timeout=-1';\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->quit()->then(function () {\n                echo 'closed.';\n            });\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthCanPingAndThenQuit()\n    {\n        $this->expectOutputString('connected.ping.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->ping()->then(function () use ($connection) {\n                echo 'ping.';\n                $connection->quit()->then(function () {\n                    echo 'closed.';\n                });\n            });\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthCanQueuePingAndQuit()\n    {\n        $this->expectOutputString('connected.ping.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->ping()->then(function () {\n                echo 'ping.';\n            });\n            $connection->quit()->then(function () {\n                echo 'closed.';\n            });\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthQuitOnlyOnce()\n    {\n        $this->expectOutputString('connected.rejected.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->quit()->then(function () {\n                echo 'closed.';\n            });\n            $connection->quit()->then(function () {\n                echo 'never reached.';\n            }, function () {\n                echo 'rejected.';\n            });\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthCanCloseOnlyOnce()\n    {\n        $this->expectOutputString('connected.closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->on('close', function () {\n                echo 'closed.';\n            });\n            $connection->on('error', function () {\n                echo 'error?';\n            });\n\n            $connection->close();\n            $connection->close();\n        });\n\n        Loop::run();\n    }\n\n    public function testConnectWithValidAuthCanCloseAndAbortPing()\n    {\n        $this->expectOutputString('connected.aborted pending (Connection closing (ECONNABORTED)).aborted queued (Connection closing (ECONNABORTED)).closed.');\n\n        $factory = new Factory();\n\n        $uri = $this->getConnectionString();\n        $factory->createConnection($uri)->then(function (Connection $connection) {\n            echo 'connected.';\n            $connection->on('close', function () {\n                echo 'closed.';\n            });\n            $connection->on('error', function () {\n                echo 'error?';\n            });\n\n            $connection->ping()->then(null, function ($e) {\n                echo 'aborted pending (' . $e->getMessage() .').';\n            });\n            $connection->ping()->then(null, function ($e) {\n                echo 'aborted queued (' . $e->getMessage() . ').';\n            });\n            $connection->close();\n        });\n\n        Loop::run();\n    }\n\n    public function testlConnectWillRejectWhenUnderlyingConnectorRejects()\n    {\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->willReturn(\\React\\Promise\\reject(new \\RuntimeException('Failed', 123)));\n\n        $factory = new Factory($loop, $connector);\n        $promise = $factory->createConnection('mysql://user:secret@127.0.0.1');\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection to mysql://user:***@127.0.0.1 failed: Failed';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === 123;\n                })\n            )\n        ));\n    }\n\n    public function provideUris()\n    {\n        return [\n            [\n                'mysql://localhost',\n                'mysql://localhost'\n            ],\n            [\n                'mysql://user:pass@localhost',\n                'mysql://user:***@localhost'\n            ],\n            [\n                'mysql://user:@localhost',\n                'mysql://user:***@localhost'\n            ],\n            [\n                'mysql://user@localhost',\n                'mysql://user@localhost'\n            ]\n        ];\n    }\n\n    /**\n     * @dataProvider provideUris\n     * @param string $uri\n     * @param string $safe\n     */\n    public function testCancelConnectWillCancelPendingConnection($uri, $safe)\n    {\n        $pending = new Promise(function () { }, $this->expectCallableOnce());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->willReturn($pending);\n\n        $factory = new Factory($loop, $connector);\n        $promise = $factory->createConnection($uri);\n\n        $promise->cancel();\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) use ($safe) {\n                    return $e->getMessage() === 'Connection to ' . $safe . ' cancelled (ECONNABORTED)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103);\n                })\n            )\n        ));\n    }\n\n    public function testCancelConnectWillCancelPendingConnectionWithRuntimeException()\n    {\n        $pending = new Promise(function () { }, function () {\n            throw new \\UnexpectedValueException('ignored');\n        });\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->willReturn($pending);\n\n        $factory = new Factory($loop, $connector);\n        $promise = $factory->createConnection('mysql://127.0.0.1');\n\n        $promise->cancel();\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection to mysql://127.0.0.1 cancelled (ECONNABORTED)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103);\n                })\n            )\n        ));\n    }\n\n    public function testCancelConnectDuringAuthenticationWillCloseConnection()\n    {\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->once())->method('close');\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $connector->expects($this->once())->method('connect')->willReturn(\\React\\Promise\\resolve($connection));\n\n        $factory = new Factory($loop, $connector);\n        $promise = $factory->createConnection('mysql://127.0.0.1');\n\n        $promise->cancel();\n\n        $promise->then(null, $this->expectCallableOnceWith(\n            $this->logicalAnd(\n                $this->isInstanceOf('RuntimeException'),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getMessage() === 'Connection to mysql://127.0.0.1 cancelled (ECONNABORTED)';\n                }),\n                $this->callback(function (\\RuntimeException $e) {\n                    return $e->getCode() === (defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103);\n                })\n            )\n        ));\n    }\n}\n"
  },
  {
    "path": "tests/Io/ParserTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Io;\n\nuse React\\Mysql\\Commands\\AuthenticateCommand;\nuse React\\Mysql\\Commands\\QueryCommand;\nuse React\\Mysql\\Exception;\nuse React\\Mysql\\Io\\Executor;\nuse React\\Mysql\\Io\\Parser;\nuse React\\Stream\\CompositeStream;\nuse React\\Stream\\ThroughStream;\nuse React\\Tests\\Mysql\\BaseTestCase;\n\nclass ParserTest extends BaseTestCase\n{\n    public function testClosingStreamEmitsErrorForCurrentCommand()\n    {\n        $stream = new ThroughStream();\n        $executor = new Executor();\n\n        $parser = new Parser($stream, $executor);\n        $parser->start();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableOnce());\n\n        $error = null;\n        $command->on('error', function ($e) use (&$error) {\n            $error = $e;\n        });\n\n        // hack to inject command as current command\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->close();\n\n        $this->assertInstanceOf('RuntimeException', $error);\n        assert($error instanceof \\RuntimeException);\n\n        $this->assertEquals('Connection closing (ECONNABORTED)', $error->getMessage());\n        $this->assertEquals(defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103, $error->getCode());\n    }\n\n    public function testParseValidAuthPluginWillSendAuthResponse()\n    {\n        $stream = new ThroughStream();\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableOnceWith(\"\\x08\\0\\0\\x01\" . \"response\"));\n\n        $command = $this->getMockBuilder('React\\Mysql\\Commands\\AuthenticateCommand')->disableOriginalConstructor()->getMock();\n        $command->expects($this->once())->method('authenticatePacket')->with($this->anything(), 'caching_sha2_password')->willReturn('response');\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $stream->write(\"\\x49\\0\\0\\0\\x0a\\x38\\x2e\\x34\\x2e\\x35\\0\\x5e\\0\\0\\0\\x08\\x0c\\x41\\x44\\x12\\x5e\\x69\\x59\\0\\xff\\xff\\xff\\x02\\0\\xff\\xdf\\x15\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x3c\\x2c\\x5e\\x54\\x06\\x04\\x01\\x61\\x01\\x20\\x79\\x1b\\0\\x63\\x61\\x63\\x68\\x69\\x6e\\x67\\x5f\\x73\\x68\\x61\\x32\\x5f\\x70\\x61\\x73\\x73\\x77\\x6f\\x72\\x64\\0\");\n\n        $ref = new \\ReflectionProperty($parser, 'authPlugin');\n        $ref->setAccessible(true);\n        $this->assertEquals('caching_sha2_password', $ref->getValue($parser));\n    }\n\n    public function testUnexpectedAuthPluginShouldEmitErrorOnAuthenticateCommandAndCloseStream()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableOnce());\n\n        $command = new AuthenticateCommand('root', '', 'test', 'utf8mb4');\n        $command->on('error', $this->expectCallableOnceWith(new \\UnexpectedValueException('Unknown authentication plugin \"sha256_password\" requested by server')));\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser($stream, $executor);\n        $parser->start();\n\n        $stream->write(\"\\x43\\0\\0\\0\\x0a\\x38\\x2e\\x34\\x2e\\x35\\0\\x5e\\0\\0\\0\\x08\\x0c\\x41\\x44\\x12\\x5e\\x69\\x59\\0\\xff\\xff\\xff\\x02\\0\\xff\\xdf\\x15\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x3c\\x2c\\x5e\\x54\\x06\\x04\\x01\\x61\\x01\\x20\\x79\\x1b\\0\\x73\\x68\\x61\\x32\\x35\\x36\\x5f\\x70\\x61\\x73\\x73\\x77\\x6f\\x72\\x64\\0\");\n    }\n\n    public function testParseAuthSwitchRequestWillSendAuthSwitchResponsePacket()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableNever());\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableOnceWith(\"\\x09\\0\\0\\x01\" . \"encrypted\"));\n\n        $executor = new Executor();\n\n        $command = $this->getMockBuilder('React\\Mysql\\Commands\\AuthenticateCommand')->disableOriginalConstructor()->getMock();\n        $command->expects($this->once())->method('authResponse')->with('scramble', 'caching_sha2_password')->willReturn('encrypted');\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $ref = new \\ReflectionProperty($parser, 'phase');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, Parser::PHASE_AUTH_SENT);\n\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->write(\"\\x20\\0\\0\\0\" . \"\\xfe\" . \"caching_sha2_password\" . \"\\0\" . \"scramble\" . \"\\0\");\n    }\n\n    public function testParseAuthSwitchRequestWithUnexpectedAuthPluginWillEmitErrorAndCloseConnection()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableOnce());\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableNever());\n\n        $command = new AuthenticateCommand('root', '', 'test', 'utf8mb4');\n        $command->on('error', $this->expectCallableOnceWith(new \\UnexpectedValueException('Unknown authentication plugin \"sha256_password\" requested by server')));\n\n        $executor = new Executor();\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $ref = new \\ReflectionProperty($parser, 'phase');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, Parser::PHASE_AUTH_SENT);\n\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->write(\"\\x19\\0\\0\\0\" . \"\\xfe\" . \"sha256_password\" . \"\\0\" . \"scramble\" . \"\\0\");\n    }\n\n    public function testParseAuthMoreDataWithFastAuthSuccessWillPrintDebugLogAndWaitForOkPacketWithoutSendingPacket()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableNever());\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableNever());\n\n        $executor = new Executor();\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $ref = new \\ReflectionProperty($parser, 'debug');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, true);\n\n        $ref = new \\ReflectionProperty($parser, 'phase');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, Parser::PHASE_AUTH_SENT);\n\n        $ref = new \\ReflectionProperty($parser, 'authPlugin');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, 'caching_sha2_password');\n\n        $this->expectOutputRegex('/Fast auth success\\n$/');\n        $stream->write(\"\\x02\\0\\0\\0\" . \"\\x01\\x03\");\n    }\n\n    public function testParseAuthMoreDataWithFastAuthFailureWillSendCertificateRequest()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableNever());\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableOnceWith(\"\\x01\\0\\0\\x01\" . \"\\x02\"));\n\n        $executor = new Executor();\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $ref = new \\ReflectionProperty($parser, 'phase');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, Parser::PHASE_AUTH_SENT);\n\n        $ref = new \\ReflectionProperty($parser, 'authPlugin');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, 'caching_sha2_password');\n\n        $stream->write(\"\\x02\\0\\0\\0\" . \"\\x01\\x04\");\n    }\n\n    public function testParseAuthMoreDataWithCertificateWillSendEncryptedPassword()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableNever());\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableOnceWith(\"\\x09\\0\\0\\x01\" . \"encrypted\"));\n\n        $command = $this->getMockBuilder('React\\Mysql\\Commands\\AuthenticateCommand')->disableOriginalConstructor()->getMock();\n        $command->expects($this->once())->method('authSha256')->with('', '---')->willReturn('encrypted');\n\n        $executor = new Executor();\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $ref = new \\ReflectionProperty($parser, 'phase');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, Parser::PHASE_AUTH_SENT);\n\n        $ref = new \\ReflectionProperty($parser, 'authPlugin');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, 'caching_sha2_password');\n\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->write(\"\\x04\\0\\0\\0\" . \"\\x01---\");\n    }\n\n    public function testParseAuthMoreDataWithCertificateWillEmitErrorAndCloseConnectionWhenEncryptingPasswordThrows()\n    {\n        $stream = new ThroughStream();\n        $stream->on('close', $this->expectCallableOnce());\n\n        $outgoing = new ThroughStream();\n        $outgoing->on('data', $this->expectCallableNever());\n\n        $command = $this->getMockBuilder('React\\Mysql\\Commands\\AuthenticateCommand')->disableOriginalConstructor()->getMock();\n        $command->expects($this->once())->method('authSha256')->with('', '---')->willThrowException(new \\UnexpectedValueException('Error'));\n        $command->expects($this->once())->method('emit')->with('error', [new \\UnexpectedValueException('Error')]);\n\n        $executor = new Executor();\n\n        $parser = new Parser(new CompositeStream($stream, $outgoing), $executor);\n        $parser->start();\n\n        $ref = new \\ReflectionProperty($parser, 'phase');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, Parser::PHASE_AUTH_SENT);\n\n        $ref = new \\ReflectionProperty($parser, 'authPlugin');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, 'caching_sha2_password');\n\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->write(\"\\x04\\0\\0\\0\" . \"\\x01---\");\n    }\n\n    public function testUnexpectedErrorWithoutCurrentCommandWillBeIgnored()\n    {\n        $stream = new ThroughStream();\n\n        $executor = new Executor();\n\n        $parser = new Parser($stream, $executor);\n        $parser->start();\n\n        $stream->on('close', $this->expectCallableNever());\n\n        $stream->write(\"\\x33\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 44));\n        $stream->write(\"\\x17\\0\\0\\0\" . \"\\xFF\" . \"\\x10\\x04\" . \"Too many connections\");\n    }\n\n    public function testReceivingErrorFrameDuringHandshakeShouldEmitErrorOnFollowingCommand()\n    {\n        $stream = new ThroughStream();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableOnce());\n\n        $error = null;\n        $command->on('error', function ($e) use (&$error) {\n            $error = $e;\n        });\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser($stream, $executor);\n        $parser->start();\n\n        $stream->write(\"\\x17\\0\\0\\0\" . \"\\xFF\" . \"\\x10\\x04\" . \"Too many connections\");\n\n        $this->assertTrue($error instanceof Exception);\n        $this->assertEquals(1040, $error->getCode());\n        $this->assertEquals('Too many connections', $error->getMessage());\n    }\n\n    public function testReceivingErrorFrameForQueryShouldEmitError()\n    {\n        $stream = new ThroughStream();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableOnce());\n\n        $error = null;\n        $command->on('error', function ($e) use (&$error) {\n            $error = $e;\n        });\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser($stream, $executor);\n        $parser->start();\n\n        $stream->on('close', $this->expectCallableNever());\n\n        $stream->write(\"\\x33\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 44));\n        $stream->write(\"\\x1E\\0\\0\\1\" . \"\\xFF\" . \"\\x46\\x04\" . \"#abcde\" . \"Unknown thread id: 42\");\n\n        $this->assertTrue($error instanceof Exception);\n        $this->assertEquals(1094, $error->getCode());\n        $this->assertEquals('Unknown thread id: 42', $error->getMessage());\n    }\n\n    public function testReceivingErrorFrameForQueryAfterResultSetHeadersShouldEmitError()\n    {\n        $stream = new ThroughStream();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableOnce());\n\n        $error = null;\n        $command->on('error', function ($e) use (&$error) {\n            $error = $e;\n        });\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser(new CompositeStream($stream, new ThroughStream()), $executor);\n        $parser->start();\n\n        $stream->on('close', $this->expectCallableNever());\n\n        $stream->write(\"\\x33\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 44));\n        $stream->write(\"\\x01\\0\\0\\1\" . \"\\x01\");\n        $stream->write(\"\\x1F\\0\\0\\2\" . \"\\x03\" . \"def\" . \"\\0\" . \"\\0\" . \"\\0\" . \"\\x09\" . \"sleep(10)\" . \"\\0\" . \"\\xC0\" . \"\\x3F\\0\" . \"\\1\\0\\0\\0\" . \"\\3\" . \"\\x81\\0\". \"\\0\" . \"\\0\\0\");\n        $stream->write(\"\\x05\\0\\0\\3\" . \"\\xFE\" . \"\\0\\0\\2\\0\");\n        $stream->write(\"\\x28\\0\\0\\4\" . \"\\xFF\" . \"\\x25\\x05\" . \"#abcde\" . \"Query execution was interrupted\");\n\n        $this->assertTrue($error instanceof Exception);\n        $this->assertEquals(1317, $error->getCode());\n        $this->assertEquals('Query execution was interrupted', $error->getMessage());\n\n        $ref = new \\ReflectionProperty($parser, 'rsState');\n        $ref->setAccessible(true);\n        $this->assertEquals(0, $ref->getValue($parser));\n\n        $ref = new \\ReflectionProperty($parser, 'resultFields');\n        $ref->setAccessible(true);\n        $this->assertEquals([], $ref->getValue($parser));\n    }\n\n    public function testReceivingInvalidPacketWithMissingDataShouldEmitErrorAndCloseConnection()\n    {\n        $stream = new ThroughStream();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableOnce());\n\n        $error = null;\n        $command->on('error', function ($e) use (&$error) {\n            $error = $e;\n        });\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser(new CompositeStream($stream, new ThroughStream()), $executor);\n        $parser->start();\n\n        // hack to inject command as current command\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->on('close', $this->expectCallableOnce());\n\n        $stream->write(\"\\x32\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 43));\n\n        $this->assertTrue($error instanceof \\UnexpectedValueException);\n        $this->assertEquals('Unexpected protocol error, received malformed packet: Not enough data in buffer', $error->getMessage());\n        $this->assertEquals(0, $error->getCode());\n        $this->assertInstanceOf('UnderflowException', $error->getPrevious());\n    }\n\n    public function testReceivingInvalidPacketWithExcessiveDataShouldEmitErrorAndCloseConnection()\n    {\n        $stream = new ThroughStream();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableOnce());\n\n        $error = null;\n        $command->on('error', function ($e) use (&$error) {\n            $error = $e;\n        });\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser(new CompositeStream($stream, new ThroughStream()), $executor);\n        $parser->start();\n\n        // hack to inject command as current command\n        $ref = new \\ReflectionProperty($parser, 'currCommand');\n        $ref->setAccessible(true);\n        $ref->setValue($parser, $command);\n\n        $stream->on('close', $this->expectCallableOnce());\n\n        $stream->write(\"\\x34\\0\\0\\0\" . \"\\x0a\" . \"mysql\\0\" . str_repeat(\"\\0\", 45));\n\n        $this->assertTrue($error instanceof \\UnexpectedValueException);\n        $this->assertEquals('Unexpected protocol error, received malformed packet with 1 unknown byte(s)', $error->getMessage());\n        $this->assertEquals(0, $error->getCode());\n        $this->assertNull($error->getPrevious());\n    }\n\n    public function testReceivingIncompleteErrorFrameDuringHandshakeShouldNotEmitError()\n    {\n        $stream = new ThroughStream();\n\n        $command = new QueryCommand();\n        $command->on('error', $this->expectCallableNever());\n\n        $executor = new Executor();\n        $executor->enqueue($command);\n\n        $parser = new Parser($stream, $executor);\n        $parser->start();\n\n        $stream->write(\"\\xFF\\0\\0\\0\" . \"\\xFF\" . \"\\x12\\x34\" . \"Some incomplete error message...\");\n    }\n}\n"
  },
  {
    "path": "tests/Io/QueryStreamTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Io;\n\nuse React\\Mysql\\Commands\\QueryCommand;\nuse React\\Mysql\\Io\\QueryStream;\nuse React\\Tests\\Mysql\\BaseTestCase;\n\nclass QueryStreamTest extends BaseTestCase\n{\n    public function testDataEventWillBeForwardedFromCommandResult()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n        $stream->on('data', $this->expectCallableOnceWith(['key' => 'value']));\n\n        $command->emit('result', [['key' => 'value']]);\n    }\n\n    public function testDataEventWillNotBeForwardedFromCommandResultAfterClosingStream()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n        $stream->on('data', $this->expectCallableNever());\n        $stream->close();\n\n        $command->emit('result', [['key' => 'value']]);\n    }\n\n    public function testEndEventWillBeForwardedFromCommandResult()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $command->emit('end');\n    }\n\n    public function testSuccessEventWillBeForwardedFromCommandResultAsEndWithoutData()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n        $stream->on('data', $this->expectCallableNever());\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $command->emit('success');\n    }\n\n    public function testErrorEventWillBeForwardedFromCommandResult()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n        $stream->on('error', $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));\n        $stream->on('close', $this->expectCallableOnce());\n\n        $command->emit('error', [new \\RuntimeException()]);\n    }\n\n    public function testPauseForwardsToConnectionAfterResultStarted()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->once())->method('pause');\n\n        $stream = new QueryStream($command, $connection);\n        $command->emit('result', [[]]);\n\n        $stream->pause();\n    }\n\n    public function testPauseForwardsToConnectionWhenResultStarted()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->once())->method('pause');\n\n        $stream = new QueryStream($command, $connection);\n        $stream->pause();\n\n        $command->emit('result', [[]]);\n    }\n\n    public function testPauseDoesNotForwardToConnectionWhenResultIsNotStarted()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->never())->method('pause');\n\n        $stream = new QueryStream($command, $connection);\n        $stream->pause();\n    }\n\n    public function testPauseDoesNotForwardToConnectionAfterClosing()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->never())->method('pause');\n\n        $stream = new QueryStream($command, $connection);\n        $stream->close();\n        $stream->pause();\n    }\n\n    public function testResumeForwardsToConnectionAfterResultStarted()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->once())->method('resume');\n\n        $stream = new QueryStream($command, $connection);\n        $command->emit('result', [[]]);\n\n        $stream->resume();\n    }\n\n    public function testResumeDoesNotForwardToConnectionAfterClosing()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->never())->method('resume');\n\n        $stream = new QueryStream($command, $connection);\n        $stream->close();\n        $stream->resume();\n    }\n\n    public function testPipeReturnsDestStream()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n\n        $dest = $this->getMockBuilder('React\\Stream\\WritableStreamInterface')->getMock();\n        $ret = $stream->pipe($dest);\n\n        $this->assertSame($dest, $ret);\n    }\n\n    public function testCloseTwiceEmitsCloseEventOnce()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n\n        $stream = new QueryStream($command, $connection);\n        $stream->on('close', $this->expectCallableOnce());\n\n        $stream->close();\n        $stream->close();\n    }\n\n    public function testCloseForwardsResumeToConnectionIfPreviouslyPaused()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->once())->method('resume');\n\n        $stream = new QueryStream($command, $connection);\n        $command->emit('result', [[]]);\n        $stream->pause();\n        $stream->close();\n    }\n\n    public function testCloseDoesNotResumeConnectionIfNotPreviouslyPaused()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->never())->method('resume');\n\n        $stream = new QueryStream($command, $connection);\n        $stream->close();\n    }\n\n    public function testCloseDoesNotResumeConnectionIfPreviouslyPausedWhenResultIsNotActive()\n    {\n        $command = new QueryCommand();\n        $connection = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();\n        $connection->expects($this->never())->method('resume');\n\n        $stream = new QueryStream($command, $connection);\n        $stream->pause();\n        $stream->close();\n    }\n}\n"
  },
  {
    "path": "tests/Io/QueryTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql\\Io;\n\nuse React\\Mysql\\Io\\Query;\nuse React\\Tests\\Mysql\\BaseTestCase;\n\nclass QueryTest extends BaseTestCase\n{\n    public function testCtorThrowsForInvalidParams()\n    {\n        $this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, resource given');\n        new Query('SELECT ?', [tmpfile()]);\n    }\n\n    public function testBindParams()\n    {\n        $query = new Query('select * from test where id = ? and name = ?', [100, 'test']);\n        $this->assertEquals(\"select * from test where id = 100 and name = 'test'\", $query->getSql());\n\n        $query = new Query('select * from test where id in (?,?) and name = ?', [1, 2, 'test']);\n        $this->assertEquals(\"select * from test where id in (1,2) and name = 'test'\", $query->getSql());\n        /*\n        $query = new Query('select * from test where id = :id and name = :name', [':id' => 100, ':name' => 'test']);\n        $this->assertEquals(\"select * from test where id = 100 and name = 'test'\", $query->getSql());\n\n        $query = new Query('select * from test where id = :id and name = ?', ['test', ':id' => 100]);\n        $this->assertEquals(\"select * from test where id = 100 and name = 'test'\", $query->getSql());\n        */\n    }\n\n    public function testGetSqlReturnsQuestionMarkReplacedWhenBound()\n    {\n        $query = new Query('select ?', ['hello']);\n        $this->assertEquals(\"select 'hello'\", $query->getSql());\n    }\n\n    public function testGetSqlReturnsQuestionMarkReplacedWithNullValueWhenBound()\n    {\n        $query = new Query('select ?', [null]);\n        $this->assertEquals(\"select NULL\", $query->getSql());\n    }\n\n    public function testGetSqlReturnsQuestionMarkReplacedFromBoundWhenBound()\n    {\n        $query = new Query('select CONCAT(?, ?)', ['hello??', 'world??']);\n        $this->assertEquals(\"select CONCAT('hello??', 'world??')\", $query->getSql());\n    }\n\n    public function testGetSqlReturnsQuestionMarksAsIsWhenNotBound()\n    {\n        $query = new Query('select \"hello?\"');\n        $this->assertEquals(\"select \\\"hello?\\\"\", $query->getSql());\n    }\n\n    public function testEscapeChars()\n    {\n        $query = new Query('');\n        $this->assertEquals('\\\\\\\\', $query->escape('\\\\'));\n        $this->assertEquals(\"''\", $query->escape(\"'\"));\n        $this->assertEquals(\"foo\\0bar\", $query->escape(\"foo\" . chr(0) . \"bar\"));\n        $this->assertEquals(\"n%3A\", $query->escape(\"n%3A\"));\n        $this->assertEquals('§ä¨ì¥H¤U¤º®e\\\\\\\\§ä¨ì¥H¤U¤º®e', $query->escape('§ä¨ì¥H¤U¤º®e\\\\§ä¨ì¥H¤U¤º®e'));\n    }\n}\n"
  },
  {
    "path": "tests/MysqlClientTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql;\n\nuse React\\Mysql\\Io\\Connection;\nuse React\\Mysql\\Io\\Query;\nuse React\\Mysql\\MysqlClient;\nuse React\\Mysql\\MysqlResult;\nuse React\\Promise\\Deferred;\nuse React\\Promise\\Promise;\nuse React\\Promise\\PromiseInterface;\nuse React\\Stream\\ReadableStreamInterface;\nuse React\\Stream\\ThroughStream;\n\nclass MysqlClientTest extends BaseTestCase\n{\n    public function testConstructWithoutConnectorAndLoopAssignsConnectorAndLoopAutomatically()\n    {\n        $mysql = new MysqlClient('localhost');\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $factory = $ref->getValue($mysql);\n\n        $ref = new \\ReflectionProperty($factory, 'connector');\n        $ref->setAccessible(true);\n        $connector = $ref->getValue($factory);\n\n        $this->assertInstanceOf('React\\Socket\\ConnectorInterface', $connector);\n\n        $ref = new \\ReflectionProperty($factory, 'loop');\n        $ref->setAccessible(true);\n        $loop = $ref->getValue($factory);\n\n        $this->assertInstanceOf('React\\EventLoop\\LoopInterface', $loop);\n    }\n\n    public function testConstructWithConnectorAndLoopAssignsGivenConnectorAndLoop()\n    {\n        $connector = $this->getMockBuilder('React\\Socket\\ConnectorInterface')->getMock();\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', $connector, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $factory = $ref->getValue($mysql);\n\n        $ref = new \\ReflectionProperty($factory, 'connector');\n        $ref->setAccessible(true);\n\n        $this->assertSame($connector, $ref->getValue($factory));\n\n        $ref = new \\ReflectionProperty($factory, 'loop');\n        $ref->setAccessible(true);\n\n        $this->assertSame($loop, $ref->getValue($factory));\n    }\n\n    public static function provideInvalidUris()\n    {\n        return [\n            [\n                '',\n                'mysql://'\n            ],\n            [\n                'localhost:100000',\n                'mysql://localhost:100000'\n            ],\n            [\n                'tcp://localhost',\n                'tcp://localhost'\n            ],\n            [\n                'mysql://',\n                'mysql://'\n            ],\n            [\n                'mysql+unix://',\n                'mysql+unix://'\n            ],\n            [\n                'user@localhost:100000',\n                'mysql://user@localhost:100000'\n            ],\n            [\n                ':pass@localhost:100000',\n                'mysql://:***@localhost:100000'\n            ],\n            [\n                'user:@localhost:100000',\n                'mysql://user:***@localhost:100000'\n            ],\n            [\n                'user:pass@localhost:100000',\n                'mysql://user:***@localhost:100000'\n            ],\n            [\n                'user@',\n                'mysql://user@'\n            ],\n            [\n                'user:pass@',\n                'mysql://user:***@'\n            ]\n        ];\n    }\n\n    /** @dataProvider provideInvalidUris */\n    public function testContructorThrowsExceptionForInvalidUri($uri, $message)\n    {\n        $this->setExpectedException(\n            'InvalidArgumentException',\n            'Invalid MySQL URI \"' . $message . '\" (EINVAL)',\n            defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22\n        );\n        new MysqlClient($uri);\n    }\n\n    public function testContructorThrowsExceptionForInvalidCharset()\n    {\n        $this->setExpectedException('InvalidArgumentException', 'Unsupported charset selected');\n        new MysqlClient('localhost?charset=unknown');\n    }\n\n    public function testContructorThrowsExceptionForInvalidConnector()\n    {\n        $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($connector) expected null|React\\Socket\\ConnectorInterface');\n        new MysqlClient('localhost', 'connector');\n    }\n\n    public function testContructorThrowsExceptionForInvalidLoop()\n    {\n        $this->setExpectedException('InvalidArgumentException', 'Argument #3 ($loop) expected null|React\\EventLoop\\LoopInterface');\n        new MysqlClient('localhost', null, 'loop');\n    }\n\n    public function testPingWillNotCloseConnectionWhenPendingConnectionFails()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableNever());\n\n        $promise = $connection->ping();\n\n        $promise->then(null, $this->expectCallableOnce()); // avoid reporting unhandled rejection\n\n        $deferred->reject(new \\RuntimeException());\n    }\n\n    public function testConnectionCloseEventAfterPingWillNotEmitCloseEvent()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableNever());\n\n        $connection->ping();\n\n        assert($base instanceof Connection);\n        $base->emit('close');\n    }\n\n    public function testConnectionErrorEventAfterPingWillNotEmitErrorEvent()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->setMethods(['ping'])->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableNever());\n\n        $connection->ping();\n\n        assert($base instanceof Connection);\n        $base->emit('error', [new \\RuntimeException()]);\n    }\n\n    public function testPingAfterConnectionIsInClosingStateDueToIdleTimerWillCloseConnectionBeforeCreatingSecondConnection()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->never())->method('quit');\n        $base->expects($this->once())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($base),\n            new Promise(function () { })\n        );\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('close', $this->expectCallableNever());\n\n        $connection->ping();\n\n        // emulate triggering idle timer by setting connection state to closing\n        $base->state = Connection::STATE_CLOSING;\n\n        $connection->ping();\n    }\n\n    public function testQueryWillCreateNewConnectionAndReturnPendingPromiseWhenConnectionIsPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(new Promise(function () { }));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->query('SELECT 1');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryWillCreateNewConnectionAndReturnPendingPromiseWhenConnectionResolvesAndQueryOnConnectionIsPending()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(new Promise(function () { }));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->query('SELECT 1');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryWillReturnResolvedPromiseWhenQueryOnConnectionResolves()\n    {\n        $result = new MysqlResult();\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(\\React\\Promise\\resolve($result));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->query('SELECT 1');\n\n        $promise->then($this->expectCallableOnceWith($result));\n    }\n\n    public function testQueryWillReturnRejectedPromiseWhenCreateConnectionRejects()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\reject(new \\RuntimeException()));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->query('SELECT 1');\n\n        $promise->then(null, $this->expectCallableOnce());\n    }\n\n    public function testQueryWillReturnRejectedPromiseWhenQueryOnConnectionRejectsAfterCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(\\React\\Promise\\reject(new \\RuntimeException()));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->query('SELECT 1');\n\n        $promise->then(null, $this->expectCallableOnce());\n    }\n\n    public function testQueryTwiceWillCreateSingleConnectionAndReturnPendingPromiseWhenCreateConnectionIsPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(new Promise(function () { }));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->query('SELECT 1');\n\n        $promise = $mysql->query('SELECT 2');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillCallQueryOnConnectionOnlyOnceWhenQueryIsStillPending()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(new Promise(function () { }));\n        $connection->expects($this->once())->method('isBusy')->willReturn(true);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->query('SELECT 1');\n\n        $promise = $mysql->query('SELECT 2');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillReuseConnectionForSecondQueryWhenFirstQueryIsAlreadyResolved()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('query')->withConsecutive(\n            [new Query('SELECT 1')],\n            [new Query('SELECT 2')]\n        )->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve(new MysqlResult()),\n            new Promise(function () { })\n        );\n        $connection->expects($this->once())->method('isBusy')->willReturn(false);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->query('SELECT 1');\n\n        $promise = $mysql->query('SELECT 2');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillCallSecondQueryOnConnectionAfterFirstQueryResolvesWhenBothQueriesAreGivenBeforeCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('query')->withConsecutive(\n            [new Query('SELECT 1')],\n            [new Query('SELECT 2')]\n        )->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve(new MysqlResult()),\n            new Promise(function () { })\n        );\n        $connection->expects($this->never())->method('isBusy');\n\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->query('SELECT 1');\n\n        $promise = $mysql->query('SELECT 2');\n\n        $deferred->resolve($connection);\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillCreateNewConnectionForSecondQueryWhenFirstConnectionIsClosedAfterFirstQueryIsResolved()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->setMethods(['query', 'isBusy'])->getMock();\n        $connection->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(\\React\\Promise\\resolve(new MysqlResult()));\n        $connection->expects($this->never())->method('isBusy');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($connection),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->query('SELECT 1');\n\n        assert($connection instanceof Connection);\n        $connection->emit('close');\n\n        $promise = $mysql->query('SELECT 2');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillCloseFirstConnectionAndCreateNewConnectionForSecondQueryWhenFirstConnectionIsInClosingStateDueToIdleTimerAfterFirstQueryIsResolved()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->setMethods(['query', 'isBusy', 'close'])->getMock();\n        $connection->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(\\React\\Promise\\resolve(new MysqlResult()));\n        $connection->expects($this->once())->method('close');\n        $connection->expects($this->never())->method('isBusy');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($connection),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $mysql->on('close', $this->expectCallableNever());\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->query('SELECT 1');\n\n        // emulate triggering idle timer by setting connection state to closing\n        $connection->state = Connection::STATE_CLOSING;\n\n        $promise = $mysql->query('SELECT 2');\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillRejectFirstQueryWhenCreateConnectionRejectsAndWillCreateNewConnectionForSecondQuery()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\reject(new \\RuntimeException()),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->query('SELECT 1');\n\n        $promise2 = $mysql->query('SELECT 2');\n\n        $promise1->then(null, $this->expectCallableOnce());\n\n        $promise2->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillRejectBothQueriesWhenBothQueriesAreGivenBeforeCreateConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->query('SELECT 1');\n        $promise2 = $mysql->query('SELECT 2');\n\n        $deferred->reject(new \\RuntimeException());\n\n        $promise1->then(null, $this->expectCallableOnce());\n        $promise2->then(null, $this->expectCallableOnce());\n    }\n\n    public function testQueryTriceWillRejectFirstTwoQueriesAndKeepThirdPendingWhenTwoQueriesAreGivenBeforeCreateConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            $deferred->promise(),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->query('SELECT 1');\n        $promise2 = $mysql->query('SELECT 2');\n\n        $promise3 = $promise1->then(null, function () use ($mysql) {\n            return $mysql->query('SELECT 3');\n        });\n\n        $deferred->reject(new \\RuntimeException());\n\n        $promise1->then(null, $this->expectCallableOnce());\n        $promise2->then(null, $this->expectCallableOnce());\n        $promise3->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryTwiceWillCallSecondQueryOnConnectionAfterFirstQueryRejectsWhenBothQueriesAreGivenBeforeCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('query')->withConsecutive(\n            [new Query('SELECT 1')],\n            [new Query('SELECT 2')]\n        )->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\reject(new \\RuntimeException()),\n            new Promise(function () { })\n        );\n        $connection->expects($this->never())->method('isBusy');\n\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->query('SELECT 1');\n\n        $promise2 = $mysql->query('SELECT 2');\n\n        $deferred->resolve($connection);\n\n        $promise1->then(null, $this->expectCallableOnce());\n        $promise2->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryStreamWillCreateNewConnectionAndReturnReadableStreamWhenConnectionIsPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(new Promise(function () { }));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $stream = $mysql->queryStream('SELECT 1');\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamWillCreateNewConnectionAndReturnReadableStreamWhenConnectionResolvesAndQueryStreamOnConnectionReturnsReadableStream()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn(new ThroughStream());\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $stream = $mysql->queryStream('SELECT 1');\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillCallQueryStreamOnConnectionOnlyOnceWhenQueryStreamIsStillReadable()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn(new ThroughStream());\n        $connection->expects($this->once())->method('isBusy')->willReturn(true);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->queryStream('SELECT 1');\n\n        $stream = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillReuseConnectionForSecondQueryStreamWhenFirstQueryStreamEnds()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('queryStream')->withConsecutive(\n            [new Query('SELECT 1')],\n            [new Query('SELECT 2')]\n        )->willReturnOnConsecutiveCalls(\n            $base = new ThroughStream(),\n            new ThroughStream()\n        );\n        $connection->expects($this->once())->method('isBusy')->willReturn(false);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->queryStream('SELECT 1');\n\n        $base->end();\n\n        $stream = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillReuseConnectionForSecondQueryStreamWhenFirstQueryStreamEmitsError()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('queryStream')->withConsecutive(\n            [new Query('SELECT 1')],\n            [new Query('SELECT 2')]\n        )->willReturnOnConsecutiveCalls(\n            $base = new ThroughStream(),\n            new ThroughStream()\n        );\n        $connection->expects($this->once())->method('isBusy')->willReturn(true);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $stream1 = $mysql->queryStream('SELECT 1');\n        $stream2 = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream1->isReadable());\n        $this->assertTrue($stream2->isReadable());\n\n        $base->emit('error', [new \\RuntimeException()]);\n\n        $this->assertFalse($stream1->isReadable());\n        $this->assertTrue($stream2->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillWaitForFirstQueryStreamToEndBeforeStartingSecondQueryStreamWhenFirstQueryStreamIsExplicitlyClosed()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn(new ThroughStream());\n        $connection->expects($this->once())->method('isBusy')->willReturn(true);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $stream1 = $mysql->queryStream('SELECT 1');\n        $stream2 = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream1->isReadable());\n        $this->assertTrue($stream2->isReadable());\n\n        $stream1->close();\n\n        $this->assertFalse($stream1->isReadable());\n        $this->assertTrue($stream2->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillCallSecondQueryStreamOnConnectionAfterFirstQueryStreamIsClosedWhenBothQueriesAreGivenBeforeCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('queryStream')->withConsecutive(\n            [new Query('SELECT 1')],\n            [new Query('SELECT 2')]\n        )->willReturnOnConsecutiveCalls(\n            $base = new ThroughStream(),\n            new ThroughStream()\n        );\n        $connection->expects($this->never())->method('isBusy');\n\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->queryStream('SELECT 1');\n\n        $stream = $mysql->queryStream('SELECT 2');\n\n        $deferred->resolve($connection);\n        $base->end();\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillCreateNewConnectionForSecondQueryStreamWhenFirstConnectionIsClosedAfterFirstQueryStreamIsClosed()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->setMethods(['queryStream', 'isBusy'])->getMock();\n        $connection->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn($base = new ThroughStream());\n        $connection->expects($this->never())->method('isBusy');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($connection),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->queryStream('SELECT 1');\n\n        $base->end();\n        assert($connection instanceof Connection);\n        $connection->emit('close');\n\n        $stream = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillCloseFirstConnectionAndCreateNewConnectionForSecondQueryStreamWhenFirstConnectionIsInClosingStateDueToIdleTimerAfterFirstQueryStreamIsClosed()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->setMethods(['queryStream', 'isBusy', 'close'])->getMock();\n        $connection->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn($base = new ThroughStream());\n        $connection->expects($this->once())->method('close');\n        $connection->expects($this->never())->method('isBusy');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($connection),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $mysql->on('close', $this->expectCallableNever());\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->queryStream('SELECT 1');\n\n        $base->end();\n        // emulate triggering idle timer by setting connection state to closing\n        $connection->state = Connection::STATE_CLOSING;\n\n        $stream = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillEmitErrorOnFirstQueryStreamWhenCreateConnectionRejectsAndWillCreateNewConnectionForSecondQueryStream()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\reject(new \\RuntimeException()),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $stream1 = $mysql->queryStream('SELECT 1');\n\n        $this->assertFalse($stream1->isReadable());\n\n        $stream2 = $mysql->queryStream('SELECT 2');\n\n        $this->assertTrue($stream2->isReadable());\n    }\n\n    public function testQueryStreamTwiceWillEmitErrorOnBothQueriesWhenBothQueriesAreGivenBeforeCreateConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $stream1 = $mysql->queryStream('SELECT 1');\n        $stream2 = $mysql->queryStream('SELECT 2');\n\n        $stream1->on('error', $this->expectCallableOnceWith($this->isInstanceOf('Exception')));\n        $stream1->on('close', $this->expectCallableOnce());\n\n        $stream2->on('error', $this->expectCallableOnceWith($this->isInstanceOf('Exception')));\n        $stream2->on('close', $this->expectCallableOnce());\n\n        $deferred->reject(new \\RuntimeException());\n\n        $this->assertFalse($stream1->isReadable());\n        $this->assertFalse($stream2->isReadable());\n    }\n\n    public function testPingWillCreateNewConnectionAndReturnPendingPromiseWhenConnectionIsPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(new Promise(function () { }));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingWillCreateNewConnectionAndReturnPendingPromiseWhenConnectionResolvesAndPingOnConnectionIsPending()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(new Promise(function () { }));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingWillReturnResolvedPromiseWhenPingOnConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableOnce());\n    }\n\n    public function testPingWillReturnRejectedPromiseWhenCreateConnectionRejects()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\reject(new \\RuntimeException()));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->ping();\n\n        $promise->then(null, $this->expectCallableOnce());\n    }\n\n    public function testPingWillReturnRejectedPromiseWhenPingOnConnectionRejectsAfterCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\reject(new \\RuntimeException()));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise = $mysql->ping();\n\n        $promise->then(null, $this->expectCallableOnce());\n    }\n\n    public function testPingTwiceWillCreateSingleConnectionAndReturnPendingPromiseWhenCreateConnectionIsPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(new Promise(function () { }));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->ping();\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillCallPingOnConnectionOnlyOnceWhenPingIsStillPending()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(new Promise(function () { }));\n        $connection->expects($this->once())->method('isBusy')->willReturn(true);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->ping();\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillReuseConnectionForSecondPingWhenFirstPingIsAlreadyResolved()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('ping')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve(null),\n            new Promise(function () { })\n        );\n        $connection->expects($this->once())->method('isBusy')->willReturn(false);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->ping();\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillCallSecondPingOnConnectionAfterFirstPingResolvesWhenBothQueriesAreGivenBeforeCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('ping')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve(new MysqlResult()),\n            new Promise(function () { })\n        );\n        $connection->expects($this->never())->method('isBusy');\n\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->ping();\n\n        $promise = $mysql->ping();\n\n        $deferred->resolve($connection);\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillCreateNewConnectionForSecondPingWhenFirstConnectionIsClosedAfterFirstPingIsResolved()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->setMethods(['ping', 'isBusy'])->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $connection->expects($this->never())->method('isBusy');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($connection),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->ping();\n\n        assert($connection instanceof Connection);\n        $connection->emit('close');\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillCloseFirstConnectionAndCreateNewConnectionForSecondPingWhenFirstConnectionIsInClosingStateDueToIdleTimerAfterFirstPingIsResolved()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->setMethods(['ping', 'isBusy', 'close'])->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $connection->expects($this->once())->method('close');\n        $connection->expects($this->never())->method('isBusy');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\resolve($connection),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $mysql->on('close', $this->expectCallableNever());\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->ping();\n\n        // emulate triggering idle timer by setting connection state to closing\n        $connection->state = Connection::STATE_CLOSING;\n\n        $promise = $mysql->ping();\n\n        $promise->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillRejectFirstPingWhenCreateConnectionRejectsAndWillCreateNewConnectionForSecondPing()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\reject(new \\RuntimeException()),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->ping();\n\n        $promise2 = $mysql->ping();\n\n        $promise1->then(null, $this->expectCallableOnce());\n\n        $promise2->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillRejectBothQueriesWhenBothQueriesAreGivenBeforeCreateConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->ping();\n        $promise2 = $mysql->ping();\n\n        $deferred->reject(new \\RuntimeException());\n\n        $promise1->then(null, $this->expectCallableOnce());\n        $promise2->then(null, $this->expectCallableOnce());\n    }\n\n    public function testPingTriceWillRejectFirstTwoQueriesAndKeepThirdPendingWhenTwoQueriesAreGivenBeforeCreateConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls(\n            $deferred->promise(),\n            new Promise(function () { })\n        );\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->ping();\n        $promise2 = $mysql->ping();\n\n        $promise3 = $promise1->then(null, function () use ($mysql) {\n            return $mysql->ping();\n        });\n\n        $deferred->reject(new \\RuntimeException());\n\n        $promise1->then(null, $this->expectCallableOnce());\n        $promise2->then(null, $this->expectCallableOnce());\n        $promise3->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingTwiceWillCallSecondPingOnConnectionAfterFirstPingRejectsWhenBothQueriesAreGivenBeforeCreateConnectionResolves()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $connection->expects($this->exactly(2))->method('ping')->willReturnOnConsecutiveCalls(\n            \\React\\Promise\\reject(new \\RuntimeException()),\n            new Promise(function () { })\n        );\n        $connection->expects($this->never())->method('isBusy');\n\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $promise1 = $mysql->ping();\n\n        $promise2 = $mysql->ping();\n\n        $deferred->resolve($connection);\n\n        $promise1->then(null, $this->expectCallableOnce());\n        $promise2->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQueryWillResolveWhenQueryFromUnderlyingConnectionResolves()\n    {\n        $result = new MysqlResult();\n\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(\\React\\Promise\\resolve($result));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->query('SELECT 1');\n        $ret->then($this->expectCallableOnceWith($result), $this->expectCallableNever());\n    }\n\n    public function testPingAfterQueryWillPassPingToConnectionWhenQueryResolves()\n    {\n        $result = new MysqlResult();\n        $deferred = new Deferred();\n\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn($deferred->promise());\n        $base->expects($this->once())->method('ping')->willReturn(new Promise(function () { }));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->query('SELECT 1');\n        $connection->ping();\n\n        $deferred->resolve($result);\n\n        $ret->then($this->expectCallableOnceWith($result), $this->expectCallableNever());\n    }\n\n    public function testQueryWillRejectWhenQueryFromUnderlyingConnectionRejects()\n    {\n        $error = new \\RuntimeException();\n\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('query')->with(new Query('SELECT 1'))->willReturn(\\React\\Promise\\reject($error));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->query('SELECT 1');\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n    }\n\n    public function testQueryWillRejectWhenUnderlyingConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->query('SELECT 1');\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnce());\n\n        $deferred->reject(new \\RuntimeException());\n    }\n\n    public function testQueryStreamReturnsReadableStreamWhenConnectionIsPending()\n    {\n        $promise = new Promise(function () { });\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($promise);\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->queryStream('SELECT 1');\n\n        $this->assertTrue($ret instanceof ReadableStreamInterface);\n        $this->assertTrue($ret->isReadable());\n    }\n\n    public function testQueryStreamWillReturnStreamFromUnderlyingConnectionWhenResolved()\n    {\n        $stream = new ThroughStream();\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn($stream);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->queryStream('SELECT 1');\n\n        $ret->on('data', $this->expectCallableOnceWith('hello'));\n        $stream->write('hello');\n\n        $this->assertTrue($ret->isReadable());\n    }\n\n    public function testQueryStreamWillReturnStreamFromUnderlyingConnectionWhenResolvedAndClosed()\n    {\n        $stream = new ThroughStream();\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('queryStream')->with(new Query('SELECT 1'))->willReturn($stream);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->queryStream('SELECT 1');\n\n        $ret->on('data', $this->expectCallableOnceWith('hello'));\n        $stream->write('hello');\n\n        $ret->on('close', $this->expectCallableOnce());\n        $stream->close();\n\n        $this->assertFalse($ret->isReadable());\n    }\n\n    public function testQueryStreamWillCloseStreamWithErrorWhenUnderlyingConnectionRejects()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->queryStream('SELECT 1');\n\n        $ret->on('error', $this->expectCallableOnce());\n        $ret->on('close', $this->expectCallableOnce());\n\n        $deferred->reject(new \\RuntimeException());\n\n        $this->assertFalse($ret->isReadable());\n    }\n\n    public function testPingReturnsPendingPromiseWhenConnectionIsPending()\n    {\n        $promise = new Promise(function () { });\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($promise);\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->ping();\n\n        $this->assertTrue($ret instanceof PromiseInterface);\n        $ret->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testPingWillPingUnderlyingConnectionWhenResolved()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(new Promise(function () { }));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n    }\n\n    public function testPingTwiceWillBothRejectWithSameErrorWhenUnderlyingConnectionRejects()\n    {\n        $error = new \\RuntimeException();\n        $deferred = new Deferred();\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n        $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n\n        $deferred->reject($error);\n    }\n\n    public function testPingWillTryToCreateNewUnderlyingConnectionAfterPreviousPingFailedToCreateUnderlyingConnection()\n    {\n        $error = new \\RuntimeException();\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->exactly(2))->method('createConnection')->willReturn(\\React\\Promise\\reject($error));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n        $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n    }\n\n    public function testPingWillResolveWhenPingFromUnderlyingConnectionResolves()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->ping();\n        $ret->then($this->expectCallableOnce(), $this->expectCallableNever());\n    }\n\n    public function testPingWillRejectWhenPingFromUnderlyingConnectionRejects()\n    {\n        $error = new \\RuntimeException();\n\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\reject($error));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->ping();\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n    }\n\n    public function testPingWillRejectWhenPingFromUnderlyingConnectionEmitsCloseEventAndRejects()\n    {\n        $error = new \\RuntimeException();\n\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturnCallback(function () use ($base, $error) {\n            $base->emit('close');\n            return \\React\\Promise\\reject($error);\n        });\n        $base->expects($this->never())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $ret = $connection->ping();\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error));\n    }\n\n    public function testQuitResolvesAndEmitsCloseImmediatelyWhenConnectionIsNotAlreadyPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableOnce());\n\n        $ret = $connection->quit();\n\n        $this->assertTrue($ret instanceof PromiseInterface);\n        $ret->then($this->expectCallableOnce(), $this->expectCallableNever());\n    }\n\n    public function testQuitAfterPingReturnsPendingPromiseWhenConnectionIsPending()\n    {\n        $promise = new Promise(function () { });\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($promise);\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n        $ret = $connection->quit();\n\n        $this->assertTrue($ret instanceof PromiseInterface);\n        $ret->then($this->expectCallableNever(), $this->expectCallableNever());\n    }\n\n    public function testQuitAfterPingRejectsAndThenEmitsCloseWhenFactoryFailsToCreateUnderlyingConnection()\n    {\n        $deferred = new Deferred();\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping()->then(null, $this->expectCallableOnce());\n\n        $this->expectOutputString('reject.close.');\n        $connection->on('close', function () {\n            echo 'close.';\n        });\n        $connection->quit()->then(null, function () {\n            echo 'reject.';\n        });\n\n        $deferred->reject(new \\RuntimeException());\n    }\n\n    public function testQuitAfterPingWillQuitUnderlyingConnectionWhenResolved()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { }));\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n        $connection->quit();\n    }\n\n    public function testQuitAfterPingResolvesAndThenEmitsCloseWhenUnderlyingConnectionQuits()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $deferred = new Deferred();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('quit')->willReturn($deferred->promise());\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n\n        $this->expectOutputString('quit.close.');\n        $connection->on('close', function () {\n            echo 'close.';\n        });\n        $connection->quit()->then(function () {\n            echo 'quit.';\n        });\n\n        $deferred->resolve(null);\n    }\n\n    public function testQuitAfterPingRejectsAndThenEmitsCloseWhenUnderlyingConnectionFailsToQuit()\n    {\n        $deferred = new Deferred();\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('quit')->willReturn($deferred->promise());\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n\n        $this->expectOutputString('reject.close.');\n        $connection->on('close', function () {\n            echo 'close.';\n        });\n        $connection->quit()->then(null, function () {\n            echo 'reject.';\n        });\n\n        $deferred->reject(new \\RuntimeException());\n    }\n\n    public function testPingAfterQuitWillNotPassPingCommandToConnection()\n    {\n        $connection = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->setMethods(['ping', 'quit', 'close', 'isBusy'])->disableOriginalConstructor()->getMock();\n        $connection->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $connection->expects($this->once())->method('quit')->willReturn(new Promise(function () { }));\n        $connection->expects($this->never())->method('close');\n        $connection->expects($this->once())->method('isBusy')->willReturn(false);\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($connection));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $mysql = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($mysql, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($mysql, $factory);\n\n        $mysql->on('close', $this->expectCallableNever());\n\n        $mysql->ping();\n\n        $mysql->quit();\n\n        $mysql->ping()->then(null, $this->expectCallableOnce());\n    }\n\n    public function testCloseEmitsCloseImmediatelyWhenConnectionIsNotAlreadyPending()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableOnce());\n\n        $connection->close();\n    }\n\n    public function testCloseAfterPingCancelsPendingConnection()\n    {\n        $deferred = new Deferred($this->expectCallableOnce());\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise());\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping()->then(null, $this->expectCallableOnce());\n        $connection->close();\n    }\n\n    public function testCloseTwiceAfterPingWillCloseUnderlyingConnectionWhenResolved()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n        $connection->close();\n        $connection->close();\n    }\n\n    public function testCloseAfterPingDoesNotEmitConnectionErrorFromAbortedConnection()\n    {\n        $promise = new Promise(function () { }, function () {\n            throw new \\RuntimeException();\n        });\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($promise);\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableOnce());\n\n        $promise = $connection->ping();\n\n        $promise->then(null, $this->expectCallableOnce()); // avoid reporting unhandled rejection\n\n        $connection->close();\n    }\n\n    public function testCloseAfterPingWillCloseUnderlyingConnection()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping()->then($this->expectCallableOnce(), $this->expectCallableNever());\n        $connection->close();\n    }\n\n    public function testCloseAfterPingHasResolvedWillCloseUnderlyingConnectionWithoutTryingToCancelConnection()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n        $connection->close();\n    }\n\n    public function testCloseAfterQuitAfterPingWillCloseUnderlyingConnectionWhenQuitIsStillPending()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { }));\n        $base->expects($this->once())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n        $connection->quit();\n        $connection->close();\n    }\n\n    public function testCloseAfterConnectionIsInClosingStateDueToIdleTimerWillCloseUnderlyingConnection()\n    {\n        $base = $this->getMockBuilder('React\\Mysql\\Io\\Connection')->disableOriginalConstructor()->getMock();\n        $base->expects($this->once())->method('ping')->willReturn(\\React\\Promise\\resolve(null));\n        $base->expects($this->never())->method('quit');\n        $base->expects($this->once())->method('close');\n\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn(\\React\\Promise\\resolve($base));\n\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->ping();\n\n        // emulate triggering idle timer by setting connection state to closing\n        $base->state = Connection::STATE_CLOSING;\n\n        $connection->close();\n    }\n\n    public function testCloseTwiceAfterPingEmitsCloseEventOnceWhenConnectionIsPending()\n    {\n        $promise = new Promise(function () { });\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->once())->method('createConnection')->willReturn($promise);\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableOnce());\n\n        $connection->ping()->then(null, $this->expectCallableOnce());\n        $connection->close();\n        $connection->close();\n    }\n\n    public function testQueryReturnsRejectedPromiseAfterConnectionIsClosed()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->close();\n        $ret = $connection->query('SELECT 1');\n\n        $this->assertTrue($ret instanceof PromiseInterface);\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnce());\n    }\n\n    public function testQueryThrowsForInvalidQueryParamsWithoutCreatingNewConnection()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, array given');\n        $connection->query('SELECT ?', [[]]);\n    }\n\n    public function testQueryThrowsForInvalidQueryParamsWhenConnectionIsAlreadyClosed()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->close();\n\n        $this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, array given');\n        $connection->query('SELECT ?', [[]]);\n    }\n\n    public function testQueryStreamThrowsAfterConnectionIsClosed()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->close();\n\n        $this->setExpectedException('React\\Mysql\\Exception');\n        $connection->queryStream('SELECT 1');\n    }\n\n    public function testQueryStreamThrowsForInvalidQueryParamsWithoutCreatingNewConnection()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, stdClass given');\n        $connection->queryStream('SELECT ?', [new \\stdClass()]);\n    }\n\n    public function testQueryStreamThrowsForInvalidQueryParamsWhenConnectionIsAlreadyClosed()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->close();\n\n        $this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, stdClass given');\n        $connection->queryStream('SELECT ?', [new \\stdClass()]);\n    }\n\n    public function testPingReturnsRejectedPromiseAfterConnectionIsClosed()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->close();\n        $ret = $connection->ping();\n\n        $this->assertTrue($ret instanceof PromiseInterface);\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnce());\n    }\n\n    public function testQuitReturnsRejectedPromiseAfterConnectionIsClosed()\n    {\n        $factory = $this->getMockBuilder('React\\Mysql\\Io\\Factory')->disableOriginalConstructor()->getMock();\n        $factory->expects($this->never())->method('createConnection');\n        $loop = $this->getMockBuilder('React\\EventLoop\\LoopInterface')->getMock();\n\n        $connection = new MysqlClient('localhost', null, $loop);\n\n        $ref = new \\ReflectionProperty($connection, 'factory');\n        $ref->setAccessible(true);\n        $ref->setValue($connection, $factory);\n\n        $connection->close();\n        $ret = $connection->quit();\n\n        $this->assertTrue($ret instanceof PromiseInterface);\n        $ret->then($this->expectCallableNever(), $this->expectCallableOnce());\n    }\n}\n"
  },
  {
    "path": "tests/NoResultQueryTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql;\n\nuse React\\EventLoop\\Loop;\nuse React\\Mysql\\Io\\Query;\nuse React\\Mysql\\MysqlClient;\nuse React\\Mysql\\MysqlResult;\n\nclass NoResultQueryTest extends BaseTestCase\n{\n    /**\n     * @before\n     */\n    public function setUpDataTable()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        // re-create test \"book\" table\n        $connection->query(new Query('DROP TABLE IF EXISTS book'));\n        $connection->query(new Query($this->getDataTable()));\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testUpdateSimpleNonExistentReportsNoAffectedRows()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('update book set created=999 where id=999'))->then(function (MysqlResult $command) {\n            $this->assertEquals(0, $command->affectedRows);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testInsertSimpleReportsFirstInsertId()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query(\"insert into book (`name`) values ('foo')\"))->then(function (MysqlResult $command) {\n            $this->assertEquals(1, $command->affectedRows);\n            $this->assertEquals(1, $command->insertId);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testUpdateSimpleReportsAffectedRow()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query(\"insert into book (`name`) values ('foo')\"));\n        $connection->query(new Query('update book set created=999 where id=1'))->then(function (MysqlResult $command) {\n            $this->assertEquals(1, $command->affectedRows);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testCreateTableAgainWillAddWarning()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $sql = '\nCREATE TABLE IF NOT EXISTS `book` (\n    `id`      INT(11)      NOT NULL AUTO_INCREMENT,\n    `name`    VARCHAR(255) NOT NULL,\n    `isbn`    VARCHAR(255) NULL,\n    `author`  VARCHAR(255) NULL,\n    `created` INT(11)      NULL,\n    PRIMARY KEY (`id`)\n)';\n\n        $connection->query(new Query($sql))->then(function (MysqlResult $command) {\n            // 3 warnings on MySQL 8+, 1 warning on legacy MySQL 5\n            $this->assertGreaterThanOrEqual(1, $command->warningCount);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testPingMultipleWillBeExecutedInSameOrderTheyAreEnqueuedFromHandlers()\n    {\n        $this->expectOutputString('123');\n\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->ping()->then(function () use ($connection) {\n            echo '1';\n\n            $connection->ping()->then(function () use ($connection) {\n                echo '3';\n                $connection->quit();\n            });\n        });\n        $connection->ping()->then(function () {\n            echo '2';\n        });\n\n        Loop::run();\n    }\n\n\n    public function testQuitWithAnyAuthWillQuitWithoutRunning()\n    {\n        $this->expectOutputString('closed.');\n\n        $uri = 'mysql://random:pass@host';\n        $connection = new MysqlClient($uri);\n\n        $connection->quit()->then(function () {\n            echo 'closed.';\n        });\n    }\n\n    public function testPingWithValidAuthWillRunUntilQuitAfterPing()\n    {\n        $this->expectOutputString('closed.');\n\n        $uri = $this->getConnectionString();\n        $connection = new MysqlClient($uri);\n\n        $connection->ping();\n\n        $connection->quit()->then(function () {\n            echo 'closed.';\n        });\n\n        Loop::run();\n    }\n\n    public function testPingAndQuitWillFulfillPingBeforeQuitBeforeCloseEvent()\n    {\n        $this->expectOutputString('ping.quit.close.');\n\n        $uri = $this->getConnectionString();\n        $connection = new MysqlClient($uri);\n\n        $connection->on('close', function () {\n            echo 'close.';\n        });\n\n        $connection->ping()->then(function () {\n            echo 'ping.';\n        });\n\n        $connection->quit()->then(function () {\n            echo 'quit.';\n        });\n\n        Loop::run();\n    }\n\n    public function testPingWithValidAuthWillRunUntilIdleTimerAfterPingEvenWithoutQuit()\n    {\n        $uri = $this->getConnectionString();\n        $connection = new MysqlClient($uri);\n\n        $connection->on('close', $this->expectCallableNever());\n\n        $connection->ping();\n\n        Loop::run();\n    }\n\n    public function testPingWithInvalidAuthWillRejectPingButWillNotEmitErrorOrClose()\n    {\n        $uri = $this->getConnectionString(['passwd' => 'invalidpass']);\n        $connection = new MysqlClient($uri);\n\n        $connection->on('error', $this->expectCallableNever());\n        $connection->on('close', $this->expectCallableNever());\n\n        $connection->ping()->then(null, $this->expectCallableOnce());\n\n        Loop::run();\n    }\n\n    public function testPingWithValidAuthWillPingBeforeQuitButNotAfter()\n    {\n        $this->expectOutputString('rejected.ping.closed.');\n\n        $uri = $this->getConnectionString();\n        $connection = new MysqlClient($uri);\n\n        $connection->ping()->then(function () {\n            echo 'ping.';\n        });\n\n        $connection->quit()->then(function () {\n            echo 'closed.';\n        });\n\n        $connection->ping()->then(function () {\n            echo 'never reached';\n        }, function () {\n            echo 'rejected.';\n        });\n\n        Loop::run();\n    }\n}\n"
  },
  {
    "path": "tests/ResultQueryTest.php",
    "content": "<?php\n\nnamespace React\\Tests\\Mysql;\n\nuse React\\EventLoop\\Loop;\nuse React\\Mysql\\Io\\Constants;\nuse React\\Mysql\\Io\\Query;\nuse React\\Mysql\\MysqlClient;\nuse React\\Mysql\\MysqlResult;\n\nclass ResultQueryTest extends BaseTestCase\n{\n    public function testSelectStaticText()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \\'foo\\''))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame('foo', reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function provideValuesThatWillBeReturnedAsIs()\n    {\n        return array_map(function ($e) { return [$e]; }, [\n            'foo',\n            'hello?',\n            'FööBär',\n            'pile of 💩',\n            'Dave\\'s Diner',\n            'Robert \"Bobby\"',\n            \"first\\r\\nsecond\",\n            'C:\\\\\\\\Users\\\\',\n            '<>&--\\'\";',\n            \"\\0\\1\\2\\3\\4\\5\\6\\7\\10\\xff\",\n            implode('', range(\"\\x00\", \"\\x2F\")) . implode('', range(\"\\x7f\", \"\\xFF\")),\n            '',\n            null\n        ]);\n    }\n\n    /**\n     * @dataProvider provideValuesThatWillBeReturnedAsIs\n     */\n    public function testSelectStaticValueWillBeReturnedAsIs($value)\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $expected = $value;\n\n        $connection->query(new Query('select ?', [$value]))->then(function (MysqlResult $command) use ($expected) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame($expected, reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    /**\n     * @dataProvider provideValuesThatWillBeReturnedAsIs\n     */\n    public function testSelectStaticValueWillBeReturnedAsIsWithNoBackslashEscapesSqlMode($value)\n    {\n        if ($value !== null && strpos($value, '\\\\') !== false) {\n            // TODO: strings such as '%\\\\' work as-is when string contains percent?!\n            $this->markTestIncomplete('Escaping backslash not supported when using NO_BACKSLASH_ESCAPES SQL mode');\n        }\n\n        $connection = $this->createConnection(Loop::get());\n\n        $expected = $value;\n\n        $connection->query(new Query('SET SQL_MODE=\"NO_BACKSLASH_ESCAPES\"'));\n        $connection->query(new Query('select ?', [$value]))->then(function (MysqlResult $command) use ($expected) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame($expected, reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function provideValuesThatWillBeConvertedToString()\n    {\n        return [\n            [1, '1'],\n            [1.5, '1.5'],\n            [true, '1'],\n            [false, '0']\n        ];\n    }\n\n    /**\n     * @dataProvider provideValuesThatWillBeConvertedToString\n     */\n    public function testSelectStaticValueWillBeConvertedToString($value, $expected)\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select ?', [$value]))->then(function (MysqlResult $command) use ($expected) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame($expected, reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextWithQuestionMark()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \\'hello?\\''))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertEquals('hello?', reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectLongStaticTextHasTypeStringWithValidLength()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $length = 40000;\n        $value = str_repeat('.', $length);\n\n        $connection->query(new Query('SELECT ?', [$value]))->then(function (MysqlResult $command) use ($length) {\n            $this->assertCount(1, $command->resultFields);\n            $this->assertEquals($length * 4, $command->resultFields[0]['length']);\n            $this->assertSame(Constants::FIELD_TYPE_VAR_STRING, $command->resultFields[0]['type']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextWithEmptyLabel()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \\'foo\\' as ``'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertSame('', key($command->resultRows[0]));\n\n            $this->assertCount(1, $command->resultFields);\n            $this->assertSame('', $command->resultFields[0]['name']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticNullHasTypeNull()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select null'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertNull(reset($command->resultRows[0]));\n\n            $this->assertCount(1, $command->resultFields);\n            $this->assertSame(Constants::FIELD_TYPE_NULL, $command->resultFields[0]['type']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoRows()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\" UNION select \"bar\"'))->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertSame('bar', reset($command->resultRows[1]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoRowsWithNullHasTypeString()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\" UNION select null'))->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertNull(reset($command->resultRows[1]));\n\n            $this->assertCount(1, $command->resultFields);\n            $this->assertSame(Constants::FIELD_TYPE_VAR_STRING, $command->resultFields[0]['type']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticIntegerTwoRowsWithNullHasTypeLongButReturnsIntAsString()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select 0 UNION select null'))->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n\n            $this->assertSame('0', reset($command->resultRows[0]));\n            $this->assertNull(reset($command->resultRows[1]));\n\n            $this->assertCount(1, $command->resultFields);\n            $this->assertSame(Constants::FIELD_TYPE_LONGLONG, $command->resultFields[0]['type']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoRowsWithIntegerHasTypeString()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\" UNION select 1'))->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertSame('1', reset($command->resultRows[1]));\n\n            $this->assertCount(1, $command->resultFields);\n            $this->assertSame(Constants::FIELD_TYPE_VAR_STRING, $command->resultFields[0]['type']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoRowsWithEmptyRow()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\" UNION select \"\"'))->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertSame('', reset($command->resultRows[1]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextNoRows()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\" LIMIT 0'))->then(function (MysqlResult $command) {\n            $this->assertCount(0, $command->resultRows);\n\n            $this->assertCount(1, $command->resultFields);\n            $this->assertSame('foo', $command->resultFields[0]['name']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoColumns()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\",\"bar\"'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(2, $command->resultRows[0]);\n\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertSame('bar', next($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoColumnsWithOneEmptyColumn()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\",\"\"'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(2, $command->resultRows[0]);\n\n            $this->assertSame('foo', reset($command->resultRows[0]));\n            $this->assertSame('', next($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoColumnsWithBothEmpty()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \\'\\' as `first`, \\'\\' as `second`'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(2, $command->resultRows[0]);\n            $this->assertSame(['', ''], array_values($command->resultRows[0]));\n\n            $this->assertCount(2, $command->resultFields);\n            $this->assertSame(Constants::FIELD_TYPE_VAR_STRING, $command->resultFields[0]['type']);\n            $this->assertSame(Constants::FIELD_TYPE_VAR_STRING, $command->resultFields[1]['type']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectStaticTextTwoColumnsWithSameNameOverwritesValue()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select \"foo\" as `col`,\"bar\" as `col`'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n\n            $this->assertSame('bar', reset($command->resultRows[0]));\n\n            $this->assertCount(2, $command->resultFields);\n            $this->assertSame('col', $command->resultFields[0]['name']);\n            $this->assertSame('col', $command->resultFields[1]['name']);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectCharsetDefaultsToUtf8()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('SELECT @@character_set_client'))->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame('utf8mb4', reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectWithExplicitCharsetReturnsCharset()\n    {\n        $uri = $this->getConnectionString() . '?charset=latin1';\n        $connection = new MysqlClient($uri);\n\n        $connection->query('SELECT @@character_set_client')->then(function (MysqlResult $command) {\n            $this->assertCount(1, $command->resultRows);\n            $this->assertCount(1, $command->resultRows[0]);\n            $this->assertSame('latin1', reset($command->resultRows[0]));\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSimpleSelect()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        // re-create test \"book\" table\n        $connection->query(new Query('DROP TABLE IF EXISTS book'));\n        $connection->query(new Query($this->getDataTable()));\n        $connection->query(new Query(\"insert into book (`name`) values ('foo')\"));\n        $connection->query(new Query(\"insert into book (`name`) values ('bar')\"));\n\n        $connection->query(new Query('select * from book'))->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    /**\n     * @depends testSimpleSelect\n     */\n    public function testSimpleSelectFromMysqlClientWithoutDatabaseNameReturnsSameData()\n    {\n        $uri = $this->getConnectionString(['dbname' => '']);\n        $connection = new MysqlClient($uri);\n\n        $connection->query('select * from test.book')->then(function (MysqlResult $command) {\n            $this->assertCount(2, $command->resultRows);\n        });\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testInvalidSelectShouldFail()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $options = $this->getConnectionOptions();\n        $db = $options['dbname'];\n\n        $connection->query(new Query('select * from invalid_table'))->then(\n            $this->expectCallableNever(),\n            function (\\Exception $error) use ($db) {\n                $this->assertEquals(\"Table '$db.invalid_table' doesn't exist\", $error->getMessage());\n            }\n        );\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testInvalidMultiStatementsShouldFailToPreventSqlInjections()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $connection->query(new Query('select 1;select 2;'))->then(\n            $this->expectCallableNever(),\n            function (\\Exception $error) {\n                if (method_exists($this, 'assertStringContainsString')) {\n                    // PHPUnit 9+\n                    $this->assertStringContainsString(\"You have an error in your SQL syntax\", $error->getMessage());\n                } else {\n                    // legacy PHPUnit < 9\n                    $this->assertContains(\"You have an error in your SQL syntax\", $error->getMessage());\n                }\n            }\n        );\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testSelectAfterDelay()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        Loop::addTimer(0.1, function () use ($connection) {\n            $connection->query(new Query('select 1+1'))->then(function (MysqlResult $command) {\n                $this->assertEquals([['1+1' => 2]], $command->resultRows);\n            });\n            $connection->quit();\n        });\n\n        $timeout = Loop::addTimer(1, function () {\n            Loop::stop();\n            $this->fail('Test timeout');\n        });\n        $connection->on('close', function () use ($timeout) {\n            Loop::cancelTimer($timeout);\n        });\n\n        Loop::run();\n    }\n\n    public function testQueryStreamStaticEmptyEmitsSingleRow()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $stream = $connection->queryStream(new Query('SELECT 1'));\n        $stream->on('data', $this->expectCallableOnceWith(['1' => '1']));\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamBoundVariableEmitsSingleRow()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $stream = $connection->queryStream(new Query('SELECT ? as value', ['test']));\n        $stream->on('data', $this->expectCallableOnceWith(['value' => 'test']));\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamZeroRowsEmitsEndWithoutData()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $stream = $connection->queryStream(new Query('SELECT 1 LIMIT 0'));\n        $stream->on('data', $this->expectCallableNever());\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamInvalidStatementEmitsError()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $stream = $connection->queryStream(new Query('SELECT'));\n        $stream->on('data', $this->expectCallableNever());\n        $stream->on('end', $this->expectCallableNever());\n        $stream->on('error', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamDropStatementEmitsEndWithoutData()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $stream = $connection->queryStream(new Query('DROP TABLE IF exists helloworldtest1'));\n        $stream->on('data', $this->expectCallableNever());\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamExplicitCloseEmitsCloseEventWithoutData()\n    {\n        $connection = $this->createConnection(Loop::get());\n\n        $stream = $connection->queryStream(new Query('SELECT 1'));\n        $stream->on('data', $this->expectCallableNever());\n        $stream->on('end', $this->expectCallableNever());\n        $stream->on('close', $this->expectCallableOnce());\n        $stream->close();\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamFromMysqlClientEmitsSingleRow()\n    {\n        $uri = $this->getConnectionString();\n        $connection = new MysqlClient($uri);\n\n        $stream = $connection->queryStream('SELECT 1');\n\n        $stream->on('data', $this->expectCallableOnceWith([1 => '1']));\n        $stream->on('end', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->quit();\n        Loop::run();\n    }\n\n    public function testQueryStreamFromMysqlClientWillErrorWhenConnectionIsClosed()\n    {\n        $uri = $this->getConnectionString();\n        $connection = new MysqlClient($uri);\n\n        $stream = $connection->queryStream('SELECT 1');\n\n        $stream->on('data', $this->expectCallableNever());\n        $stream->on('error', $this->expectCallableOnce());\n        $stream->on('close', $this->expectCallableOnce());\n\n        $connection->close();\n    }\n}\n"
  },
  {
    "path": "tests/wait-for-mysql.sh",
    "content": "#!/bin/sh\n\nCONTAINER=\"mysql\"\nUSERNAME=\"test\"\nPASSWORD=\"test\"\nwhile ! docker exec $CONTAINER mysql --host=127.0.0.1 --port=3306 --user=$USERNAME --password=$PASSWORD -e \"SELECT 1\" >/dev/null 2>&1; do\n    sleep 1\ndone\n"
  }
]