diff --git a/.travis.yml b/.travis.yml index b067f6f..7437b54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,16 @@ php: - "5.6" - "7.0" +addons: + postgresql: "9.4" + before_script: - composer install + - psql -c "create role admin login createdb password 'adminpassword' superuser ;" -U postgres + - psql -c "create role behat login createdb password 'behatpassword';" -U postgres + - psql -c 'create database behat OWNER=behat;' -U postgres + - psql -c 'create database dblinker_test OWNER=behat;' -U postgres + - psql -c 'create database forbidden_db;' - mysql -e 'create database dblinker_test;' - mysql -e 'create database forbidden_db;' - mysql -e 'CREATE USER "behat"@"localhost" IDENTIFIED BY "behatpassword";' @@ -18,8 +26,9 @@ script: - vendor/bin/behat -s $BEHAT_DB_DRIVER --tags ~@skip-travis env: - - BEHAT_DB_DRIVER=mysqli DBLINKER_DB_1_ENV_MYSQL_DATABASE=dblinker_test DBLINKER_DB_1_PORT_3306_TCP_ADDR=127.0.0.1 DBLINKER_DB_1_ENV_MYSQL_USER=behat DBLINKER_DB_1_ENV_MYSQL_PASSWORD=behatpassword DBLINKER_DB_1_ENV_MYSQL_ROOT_PASSWORD= - - BEHAT_DB_DRIVER=pdo_mysql DBLINKER_DB_1_ENV_MYSQL_DATABASE=dblinker_test DBLINKER_DB_1_PORT_3306_TCP_ADDR=127.0.0.1 DBLINKER_DB_1_ENV_MYSQL_USER=behat DBLINKER_DB_1_ENV_MYSQL_PASSWORD=behatpassword DBLINKER_DB_1_ENV_MYSQL_ROOT_PASSWORD= + - BEHAT_DB_DRIVER=mysqli DBLINKER_MYSQL_1_ENV_MYSQL_DATABASE=dblinker_test DBLINKER_MYSQL_1_PORT_3306_TCP_ADDR=127.0.0.1 DBLINKER_MYSQL_1_ENV_MYSQL_USER=behat DBLINKER_MYSQL_1_ENV_MYSQL_PASSWORD=behatpassword DBLINKER_MYSQL_1_ENV_MYSQL_ROOT_PASSWORD= + - BEHAT_DB_DRIVER=pdo_mysql DBLINKER_MYSQL_1_ENV_MYSQL_DATABASE=dblinker_test DBLINKER_MYSQL_1_PORT_3306_TCP_ADDR=127.0.0.1 DBLINKER_MYSQL_1_ENV_MYSQL_USER=behat DBLINKER_MYSQL_1_ENV_MYSQL_PASSWORD=behatpassword DBLINKER_MYSQL_1_ENV_MYSQL_ROOT_PASSWORD= + - BEHAT_DB_DRIVER=pdo_pgsql DBLINKER_POSTGRESQL_1_ENV_POSTGRES_DATABASE=dblinker_test DBLINKER_POSTGRESQL_1_PORT_5432_TCP_ADDR=127.0.0.1 DBLINKER_POSTGRESQL_1_ENV_POSTGRES_USER=behat DBLINKER_POSTGRESQL_1_ENV_POSTGRES_PASSWORD=behatpassword DBLINKER_POSTGRES_1_ENV_POSTGRES_ROOT_USER=admin DBLINKER_POSTGRES_1_ENV_POSTGRES_ROOT_PASSWORD=adminpassword matrix: allow_failures: diff --git a/behat.yml b/behat.yml index cda37ad..234904f 100644 --- a/behat.yml +++ b/behat.yml @@ -10,3 +10,7 @@ default: contexts: [ PdoMysqlContext ] filters: tags: ~@skip-pdo-mysql + pdo_pgsql: + contexts: [ PdoPgsqlContext ] + filters: + tags: ~@skip-pdo-pgsql diff --git a/config/docker/php/behat/Dockerfile b/config/docker/php/behat/Dockerfile index c862ce7..82afa8a 100644 --- a/config/docker/php/behat/Dockerfile +++ b/config/docker/php/behat/Dockerfile @@ -1,12 +1,12 @@ FROM php:5.6 RUN apt-get update -RUN apt-get install -y git file +RUN apt-get install -y git file libpq-dev RUN cd /tmp/ && git clone git://github.com/xdebug/xdebug.git RUN cd /tmp/xdebug && git checkout XDEBUG_2_3_2 && phpize && ./configure --enable-xdebug && make && cp modules/xdebug.so /usr/local/lib/php/extensions/ RUN docker-php-ext-install mbstring -RUN docker-php-ext-install mysql pdo_mysql mysqli +RUN docker-php-ext-install pdo_mysql pdo_pgsql mysqli RUN docker-php-ext-install pcntl ENTRYPOINT ["php", "/scripts/vendor/bin/behat"] diff --git a/docker-compose.yml b/docker-compose.yml index 7e70ac3..a178c57 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,11 +19,19 @@ behat: volumes_from: - vendor links: - - db -db: + - mysql + - postgresql +mysql: image: mysql:5.7 environment: MYSQL_DATABASE: db MYSQL_USER: user MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: rootpassword +postgresql: + image: postgres:9.4 + environment: + POSTGRES_DATABASE: db + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_ROOT_PASSWORD: password diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index e525f0b..183e63a 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -1,5 +1,6 @@ connections = [ - '@master' => [ - 'params' => $this->masterParams(), - 'instance' => null, - 'last-result' => null, - 'last-error' => null, - ] - ]; - $connection = $this->rootConnection(); - $connection->exec("SET GLOBAL MAX_CONNECTIONS = 50"); - $connection->close(); - $connection = null; - gc_collect_cycles(); - } + abstract public function clearConnections(); /** * @BeforeScenario @@ -50,16 +36,7 @@ public function assertNoActiveConnection() assert($n === 0, "There is $n active connection(s) on the test server"); } - private function activeConnectionsCount() - { - $connection = $this->rootConnection(); - gc_collect_cycles(); - $n = (int)$connection->fetchAll("show status like 'Threads_connected'")[0]['Value']; - $connection->close(); - $connection = null; - gc_collect_cycles(); - return $n - 1; - } + abstract protected function activeConnectionsCount(); private function rootConnection() { @@ -87,15 +64,7 @@ public function closeConnections() * @Given the server accept :n more connection * @Given the server accept :n more connections */ - public function theServerAcceptMoreConnections($n) - { - $n += $this->activeConnectionsCount(); - $connection = $this->rootConnection(); - $connection->exec("SET GLOBAL MAX_CONNECTIONS = $n"); - $connection->close(); - $connection = null; - gc_collect_cycles(); - } + abstract public function theServerAcceptMoreConnections($n); /** * @Given a master\/slaves connection :connectionName with :slaveCount slaves @@ -148,7 +117,7 @@ public function aRetryMasterSlavesConnectionWithSlavesLimitedToRetriesWithuserna 'slaves' => $slaves, 'driverClass' => 'Ez\DbLinker\Driver\MysqlMasterSlavesDriver', ], - 'retryStrategy' => new MysqlRetryStrategy($n), + 'retryStrategy' => $this->retryStrategy($n), ]; $this->connections[$connectionName] = [ 'params' => $params, @@ -266,7 +235,7 @@ public function aRetryConnectionLimitedToRetryWithusername($connectionName, $n, $params = [ 'driverClass' => 'Ez\DbLinker\Driver\MysqlRetryDriver', 'connectionParams' => $this->masterParams($username), - 'retryStrategy' => new MysqlRetryStrategy($n), + 'retryStrategy' => $this->retryStrategy($n), ]; $this->connections[$connectionName] = [ 'params' => $params, @@ -299,7 +268,7 @@ public function aRetryMasterSlavesConnectionWithSlavesLimitedToRetryWithDbAndUse 'slaves' => $slaves, 'driverClass' => 'Ez\DbLinker\Driver\MysqlMasterSlavesDriver', ], - 'retryStrategy' => new MysqlRetryStrategy($n), + 'retryStrategy' => $this->retryStrategy($n), ]; $this->connections[$connectionName] = [ 'params' => $params, @@ -319,7 +288,7 @@ public function aRetryConnectionLimitedToRetryWithDbAndUsername($connectionName, $params = [ 'driverClass' => 'Ez\DbLinker\Driver\MysqlRetryDriver', 'connectionParams' => $master, - 'retryStrategy' => new MysqlRetryStrategy($n), + 'retryStrategy' => $this->retryStrategy($n), ]; $params['connectionParams']['dbname'] = $db; $this->connections[$connectionName] = [ @@ -332,9 +301,9 @@ public function aRetryConnectionLimitedToRetryWithDbAndUsername($connectionName, /** - * @Given MySQL has Gone Away on :connectionName + * @Given database has Gone Away on :connectionName */ - public function mysqlHasGoneAwayOn($connectionName) + public function databaseHasGoneAwayOn($connectionName) { $this->getConnection($connectionName)->exec('SET SESSION WAIT_TIMEOUT=1'); usleep(1100000); @@ -398,27 +367,6 @@ public function theLastQuerySucceededOn($connectionName) } } - /** - * @Then the last error code should be :expectedErrorCode on :connectionName - */ - public function theLastExpectedErrorCodeShouldBeOn($expectedErrorCode, $connectionName) - { - $errorCodeAssertFailureMessage = "No error found, error code $expectedErrorCode expected"; - $exception = $this->connections[$connectionName]['last-error']; - if ($exception === null) { - $exception = $this->getWrappedConnection($connectionName)->retryStrategy()->lastError(); - } - $errorCode = null; - while ($exception !== null && !($exception instanceof \Doctrine\DBAL\Exception\DriverException)) { - $exception = $exception->getPrevious(); - } - if ($exception !== null) { - $errorCode = $exception->getErrorCode(); - $errorCodeAssertFailureMessage = "Error code is $errorCode, error code $expectedErrorCode expected"; - } - assert($errorCode === (int) $expectedErrorCode, $errorCodeAssertFailureMessage); - } - /** * @Then the last query failed on :connectionName */ @@ -503,21 +451,34 @@ public function theLastStatementSucceeded() public function theLastStatementExpectedErrorCodeShouldBe($expectedErrorCode) { $errorCodeAssertFailureMessage = "No error found, error code $expectedErrorCode expected"; - $exception = $this->lastStatementError; - if ($exception === null) { - $exception = $this->statement->connection->getWrappedConnection()->retryStrategy()->lastError(); - } - $errorCode = null; - while ($exception !== null && !($exception instanceof \Doctrine\DBAL\Exception\DriverException)) { - $exception = $exception->getPrevious(); + $errorCode = $this->errorCode( + $this->lastStatementError ?: + $this->statement->connection->getWrappedConnection()->retryStrategy()->lastError() + ); + if ($errorCode !== null) { + $errorCodeAssertFailureMessage = "Error code is $errorCode, error code $expectedErrorCode expected"; } - if ($exception !== null) { - $errorCode = $exception->getErrorCode(); + assert($errorCode === (int) $expectedErrorCode, $errorCodeAssertFailureMessage); + } + + /** + * @Then the last error code should be :expectedErrorCode on :connectionName + */ + public function theLastExpectedErrorCodeShouldBeOn($expectedErrorCode, $connectionName) + { + $errorCodeAssertFailureMessage = "No error found, error code $expectedErrorCode expected"; + $errorCode = $this->errorCode( + $this->connections[$connectionName]['last-error'] ?: + $this->getWrappedConnection($connectionName)->retryStrategy()->lastError() + ); + if ($errorCode !== null) { $errorCodeAssertFailureMessage = "Error code is $errorCode, error code $expectedErrorCode expected"; } assert($errorCode === (int) $expectedErrorCode, $errorCodeAssertFailureMessage); } + abstract protected function errorCode($exception); + private function getWrappedConnection($connectionName) { return $this->getConnection($connectionName)->getWrappedConnection(); @@ -535,22 +496,7 @@ private function getConnection($connectionName) protected abstract function params(Array $params); - private function masterParams($username = null, $password = '') { - $params = [ - 'host' => getenv('DBLINKER_DB_1_PORT_3306_TCP_ADDR'), - 'user' => getenv('DBLINKER_DB_1_ENV_MYSQL_USER'), - 'password' => getenv('DBLINKER_DB_1_ENV_MYSQL_PASSWORD'), - 'dbname' => getenv('DBLINKER_DB_1_ENV_MYSQL_DATABASE'), - ]; - if ($username !== null) { - $params['user'] = $username; - if ($username === 'root') { - $password = getenv('DBLINKER_DB_1_ENV_MYSQL_ROOT_PASSWORD'); - } - $params['password'] = $password; - } - return $this->params($params); - } + abstract protected function masterParams($username = null, $password = ''); /** * @Given table :tableName can be created automatically on :connectionName @@ -562,38 +508,15 @@ public function tableCanBeCreatedAutomaticallyOn($tableName, $connectionName) Doctrine\DBAL\DBALException $exception, Ez\DbLinker\Driver\Connection\RetryConnection $connection ) use ($tableName) { - if (strpos($exception->getMessage(), "An exception occurred while executing 'SELECT * FROM {$tableName}':") === 0) { + if ( + $exception instanceof TableNotFoundException && + strpos($exception->getMessage(), $tableName) !== false + ) { $connection->exec("CREATE TABLE {$tableName} (id INT)"); return true; } }); } -} - -class MysqlRetryStrategy extends Ez\DbLinker\RetryStrategy\MysqlRetryStrategy -{ - private $lastError = null; - private $handlers = []; - public function shouldRetry( - Doctrine\DBAL\DBALException $exception, - Ez\DbLinker\Driver\Connection\RetryConnection $connection, - $method, - Array $arguments - ) { - $this->lastError = $exception; - return array_reduce($this->handlers, function($retry, Closure $handler) use ($exception, $connection) { - return $retry || $handler($exception, $connection); - }, false) || parent::shouldRetry($exception, $connection, $method, $arguments); - } - - public function lastError() - { - return $this->lastError; - } - - public function addHandler(Closure $handler) - { - $this->handlers[] = $handler; - } + abstract protected function retryStrategy($n); } diff --git a/features/bootstrap/MySQLContext.php b/features/bootstrap/MySQLContext.php new file mode 100644 index 0000000..11f7485 --- /dev/null +++ b/features/bootstrap/MySQLContext.php @@ -0,0 +1,111 @@ + getenv('DBLINKER_MYSQL_1_PORT_3306_TCP_ADDR'), + 'user' => getenv('DBLINKER_MYSQL_1_ENV_MYSQL_USER'), + 'password' => getenv('DBLINKER_MYSQL_1_ENV_MYSQL_PASSWORD'), + 'dbname' => getenv('DBLINKER_MYSQL_1_ENV_MYSQL_DATABASE'), + ]; + if ($username !== null) { + $params['user'] = $username; + if ($username === 'root') { + $password = getenv('DBLINKER_MYSQL_1_ENV_MYSQL_ROOT_PASSWORD'); + } + $params['password'] = $password; + } + return $this->params($params); + } + + private function activeConnectionsCount() + { + $connection = $this->rootConnection(); + gc_collect_cycles(); + $n = (int)$connection->fetchAll("show status like 'Threads_connected'")[0]['Value']; + $connection->close(); + $connection = null; + gc_collect_cycles(); + return $n - 1; + } + + /** + * @Given the server accept :n more connection + * @Given the server accept :n more connections + */ + public function theServerAcceptMoreConnections($n) + { + $n += $this->activeConnectionsCount(); + $connection = $this->rootConnection(); + $connection->exec("SET GLOBAL MAX_CONNECTIONS = $n"); + $connection->close(); + $connection = null; + gc_collect_cycles(); + } + + /** + * @BeforeScenario + */ + public function clearConnections() + { + $this->connections = [ + '@master' => [ + 'params' => $this->masterParams(), + 'instance' => null, + 'last-result' => null, + 'last-error' => null, + ] + ]; + $connection = $this->rootConnection(); + $connection->exec("SET GLOBAL MAX_CONNECTIONS = 50"); + $connection->close(); + $connection = null; + gc_collect_cycles(); + } + + private function retryStrategy($n) + { + return new MysqlRetryStrategy($n); + } + + private function errorCode($exception) + { + while ($exception !== null) { + if ($exception instanceof \Doctrine\DBAL\Exception\DriverException) { + return $exception->getErrorCode(); + } + $exception = $exception->getPrevious(); + } + } +} + +class MysqlRetryStrategy extends Ez\DbLinker\RetryStrategy\MysqlRetryStrategy +{ + private $lastError = null; + private $handlers = []; + + public function shouldRetry( + Doctrine\DBAL\DBALException $exception, + Ez\DbLinker\Driver\Connection\RetryConnection $connection, + $method, + Array $arguments + ) { + $this->lastError = $exception; + return array_reduce($this->handlers, function($retry, Closure $handler) use ($exception, $connection) { + return $retry || $handler($exception, $connection); + }, false) || parent::shouldRetry($exception, $connection, $method, $arguments); + } + + public function lastError() + { + return $this->lastError; + } + + public function addHandler(Closure $handler) + { + $this->handlers[] = $handler; + } +} diff --git a/features/bootstrap/MysqliContext.php b/features/bootstrap/MysqliContext.php index 774c7a0..e83f419 100644 --- a/features/bootstrap/MysqliContext.php +++ b/features/bootstrap/MysqliContext.php @@ -6,6 +6,7 @@ class MysqliContext implements Context, SnippetAcceptingContext { use FeatureContext; + use MySQLContext; private function params(Array $params) { diff --git a/features/bootstrap/PdoMysqlContext.php b/features/bootstrap/PdoMysqlContext.php index 54e15dc..d4af160 100644 --- a/features/bootstrap/PdoMysqlContext.php +++ b/features/bootstrap/PdoMysqlContext.php @@ -6,6 +6,7 @@ class PdoMysqlContext implements Context, SnippetAcceptingContext { use FeatureContext; + use MySQLContext; private function params(Array $params) { diff --git a/features/bootstrap/PdoPgsqlContext.php b/features/bootstrap/PdoPgsqlContext.php new file mode 100644 index 0000000..5c9731a --- /dev/null +++ b/features/bootstrap/PdoPgsqlContext.php @@ -0,0 +1,16 @@ + getenv('DBLINKER_POSTGRESQL_1_PORT_5432_TCP_ADDR'), + 'user' => getenv('DBLINKER_POSTGRESQL_1_ENV_POSTGRES_USER'), + 'password' => getenv('DBLINKER_POSTGRESQL_1_ENV_POSTGRES_PASSWORD'), + 'dbname' => getenv('DBLINKER_POSTGRESQL_1_ENV_POSTGRES_DATABASE'), + ]; + if ($username !== null && $username !== 'root') { + if ($username === 'root') { + $password = getenv('DBLINKER_POSTGRESQL_1_ENV_POSTGRES_ROOT_PASSWORD'); + $username = getenv('DBLINKER_POSTGRESQL_1_ENV_POSTGRES_ROOT_USER'); + } + $params['user'] = $username; + $params['password'] = $password; + } + return $this->params($params); + } + + private function activeConnectionsCount() + { + return 0; + } + + /** + * @Given the server accept :n more connection + * @Given the server accept :n more connections + */ + public function theServerAcceptMoreConnections($n) + { + throw new PendingException; + } + + /** + * @BeforeScenario + */ + public function clearConnections() + { + $this->connections = [ + '@master' => [ + 'params' => $this->masterParams(), + 'instance' => null, + 'last-result' => null, + 'last-error' => null, + ] + ]; + $connection = $this->rootConnection(); + $connection->close(); + $connection = null; + gc_collect_cycles(); + } + + private function retryStrategy($n) + { + return new PostgreSQLRetryStrategy($n); + } + + + private function errorCode($exception) + { + $errorsCodes = [ + 7 => 1146, + ]; + while ($exception !== null) { + if ($exception instanceof \Doctrine\DBAL\Exception\DriverException) { + $errorCode = $exception->getErrorCode(); + if (array_key_exists($errorCode, $errorsCodes)) { + return $errorsCodes[$errorCode]; + } + return $errorCode; + } + $exception = $exception->getPrevious(); + } + } +} + +class PostgreSQLRetryStrategy extends Ez\DbLinker\RetryStrategy\PostgreSQLRetryStrategy +{ + private $lastError = null; + private $handlers = []; + + public function shouldRetry( + Doctrine\DBAL\DBALException $exception, + Ez\DbLinker\Driver\Connection\RetryConnection $connection, + $method, + Array $arguments + ) { + $this->lastError = $exception; + return array_reduce($this->handlers, function($retry, Closure $handler) use ($exception, $connection) { + return $retry || $handler($exception, $connection); + }, false) || parent::shouldRetry($exception, $connection, $method, $arguments); + } + + public function lastError() + { + return $this->lastError; + } + + public function addHandler(Closure $handler) + { + $this->handlers[] = $handler; + } +} diff --git a/features/master-slave.feature b/features/master-slave.feature index 6a78792..5668e0d 100644 --- a/features/master-slave.feature +++ b/features/master-slave.feature @@ -1,4 +1,4 @@ -@master-slaves +@master-slaves @skip-pdo-pgsql Feature: Master / Slaves Background: diff --git a/features/retry-master-slave.feature b/features/retry-master-slave.feature index fea261f..7e1dd92 100644 --- a/features/retry-master-slave.feature +++ b/features/retry-master-slave.feature @@ -1,4 +1,4 @@ -@retry @master-slaves +@retry @master-slaves @skip-pdo-pgsql Feature: Retry Master/Slaves Scenario: ACCESS_DENIED_ERROR restart on another slave @@ -35,9 +35,9 @@ Feature: Retry Master/Slaves And "conn" retry limit should be 1 And "conn" should have 2 slaves - Scenario: MySQL has Gone Away + Scenario: database has Gone Away Given a retry master/slaves connection "conn" with 2 slaves limited to 1 retry - And MySQL has Gone Away on "conn" + And database has Gone Away on "conn" When I query "SELECT 1" on "conn" Then the last query succeeded on "conn" And the last error code should be 2006 on "conn" diff --git a/features/retry.feature b/features/retry.feature index 2668be1..e6fca9d 100644 --- a/features/retry.feature +++ b/features/retry.feature @@ -3,14 +3,14 @@ Feature: Retry Background: Given a retry connection "conn" limited to 1 retry - - Scenario: MySQL has Gone Away - Given MySQL has Gone Away on "conn" + @skip-pdo-pgsql + Scenario: database has Gone Away + Given database has Gone Away on "conn" When I query "SELECT 1" on "conn" Then the last query succeeded on "conn" And the last error code should be 2006 on "conn" And "conn" retry limit should be 0 - + @skip-pdo-pgsql Scenario: Lock wait timeout exceeded Given a retry connection "once" limited to 1 retry And I exec "SET SESSION innodb_lock_wait_timeout = 1" on "@master" @@ -24,50 +24,50 @@ Feature: Retry Then the last query failed on "conn" And the last error code should be 1205 on "once" And "once" retry limit should be 0 - @skip-mysqli @skip-travis + @skip-mysqli @skip-travis @skip-pdo-pgsql Scenario: Deadlock found when trying to get lock When I create a deadlock on "conn" with "@master" Then the last query succeeded on "conn" And the last error code should be 1213 on "conn" And "conn" retry limit should be 0 - + @skip-pdo-pgsql Scenario: ER_DBACCESS_DENIED_ERROR don't restart Given a retry connection "conn" limited to 1 retry with db "forbidden_db" When I query "SELECT 1" on "conn" Then the last query failed on "conn" And the last error code should be 1044 on "conn" And "conn" retry limit should be 1 - + @skip-pdo-pgsql Scenario: ACCESS_DENIED_ERROR don't restart Given a retry connection "conn" limited to 1 retry with username "nobody" When I query "SELECT 1" on "conn" Then the last query failed on "conn" And the last error code should be 1045 on "conn" And "conn" retry limit should be 1 - + @skip-pdo-pgsql Scenario: ER_BAD_DB_ERROR don't restart Given a retry connection "conn" limited to 1 retry with db "unknown_db" and username "root" When I query "SELECT 1" on "conn" Then the last query failed on "conn" And the last error code should be 1049 on "conn" And "conn" retry limit should be 1 - + @skip-pdo-pgsql Scenario: Don't restart transaction Given a transaction is started on "conn" - And MySQL has Gone Away on "conn" + And database has Gone Away on "conn" When I query "SELECT 1" on "conn" Then the last query failed on "conn" And "conn" retry limit should be 1 - + @skip-pdo-pgsql Scenario: Restart after transaction is done Given a transaction is started on "conn" When I query "SELECT 1" on "conn" And I commit the transaction on "conn" - And MySQL has Gone Away on "conn" + And database has Gone Away on "conn" And I query "SELECT 1" on "conn" Then the last query succeeded on "conn" And "conn" retry limit should be 0 - + @skip-pdo-pgsql Scenario: Too many connections Given the server accept 1 more connections And a retry connection "conn1" limited to 1 retry diff --git a/src/Driver/Connection/CallAndRetry.php b/src/Driver/Connection/CallAndRetry.php index 463ed80..61f51f5 100644 --- a/src/Driver/Connection/CallAndRetry.php +++ b/src/Driver/Connection/CallAndRetry.php @@ -17,7 +17,7 @@ private function callAndRetry($method, array $arguments) try { return @call_user_func_array([$this->wrappedObject(), $method], $arguments); } catch (DBALException $exception) { - if (!$this->retryStrategy->shouldRetry( + if (!$this->retryStrategy()->shouldRetry( $exception, $this->retryConnection(), $method, diff --git a/src/Driver/Connection/RetryConnection.php b/src/Driver/Connection/RetryConnection.php index 0b81dc8..253421a 100644 --- a/src/Driver/Connection/RetryConnection.php +++ b/src/Driver/Connection/RetryConnection.php @@ -2,7 +2,6 @@ namespace Ez\DbLinker\Driver\Connection; -use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\DriverManager; use Ez\DbLinker\RetryStrategy; @@ -130,7 +129,10 @@ public function errorInfo() { public function close() { - $this->wrappedConnection = null; + if ($this->wrappedConnection !== null) { + $this->wrappedConnection->close(); + $this->wrappedConnection = null; + } } public function transactionLevel() diff --git a/src/Driver/Connection/RetryStatement.php b/src/Driver/Connection/RetryStatement.php index b5d3e76..891ab18 100644 --- a/src/Driver/Connection/RetryStatement.php +++ b/src/Driver/Connection/RetryStatement.php @@ -73,18 +73,6 @@ public function bindValue($param, $value, $type = null) return $this->statement->bindValue($param, $value, $type); } - /** - * Binds a array of values to bound parameters. - * - * @param array $values - * - * @return boolean - */ - private function _bindValues($values) - { - return $this->statement->_bindValues($values); - } - /** * {@inheritdoc} */ @@ -162,6 +150,6 @@ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) */ public function getIterator() { - return $this->statement->getIterator(); + return $this->statement; } } diff --git a/src/Driver/PostgreSQLRetryDriver.php b/src/Driver/PostgreSQLRetryDriver.php new file mode 100644 index 0000000..2297d6c --- /dev/null +++ b/src/Driver/PostgreSQLRetryDriver.php @@ -0,0 +1,19 @@ +