Skip to content

Commit

Permalink
Merge pull request #3742 from magento-thunder/MAGETWO-94241
Browse files Browse the repository at this point in the history
Fixed issues:
- MAGETWO-94241: Mysql reconnect does not work
  • Loading branch information
Yevhen Miroshnychenko authored Feb 13, 2019
2 parents 5276b27 + 192c6d1 commit f211371
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Magento\Framework\App\ResourceConnection;
use Magento\TestFramework\Helper\CacheCleaner;
use Magento\Framework\DB\Ddl\Table;
use Magento\TestFramework\Helper\Bootstrap;

class MysqlTest extends \PHPUnit\Framework\TestCase
{
Expand All @@ -19,7 +20,7 @@ class MysqlTest extends \PHPUnit\Framework\TestCase
protected function setUp()
{
set_error_handler(null);
$this->resourceConnection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
$this->resourceConnection = Bootstrap::getObjectManager()
->get(ResourceConnection::class);
CacheCleaner::cleanAll();
}
Expand All @@ -40,7 +41,6 @@ public function testWaitTimeout()
$this->markTestSkipped('This test is for \Magento\Framework\DB\Adapter\Pdo\Mysql');
}
try {
$defaultWaitTimeout = $this->getWaitTimeout();
$minWaitTimeout = 1;
$this->setWaitTimeout($minWaitTimeout);
$this->assertEquals($minWaitTimeout, $this->getWaitTimeout(), 'Wait timeout was not changed');
Expand All @@ -49,17 +49,8 @@ public function testWaitTimeout()
sleep($minWaitTimeout + 1);
$result = $this->executeQuery('SELECT 1');
$this->assertInstanceOf(\Magento\Framework\DB\Statement\Pdo\Mysql::class, $result);
// Restore wait_timeout
$this->setWaitTimeout($defaultWaitTimeout);
$this->assertEquals(
$defaultWaitTimeout,
$this->getWaitTimeout(),
'Default wait timeout was not restored'
);
} catch (\Exception $e) {
// Reset connection on failure to restore global variables
} finally {
$this->getDbAdapter()->closeConnection();
throw $e;
}
}

Expand Down Expand Up @@ -87,30 +78,14 @@ private function setWaitTimeout($waitTimeout)
/**
* Execute SQL query and return result statement instance
*
* @param string $sql
* @return \Zend_Db_Statement_Interface
* @throws \Exception
* @param $sql
* @return void|\Zend_Db_Statement_Pdo
* @throws \Magento\Framework\Exception\LocalizedException
* @throws \Zend_Db_Adapter_Exception
*/
private function executeQuery($sql)
{
/**
* Suppress PDO warnings to work around the bug https://bugs.php.net/bug.php?id=63812
*/
$phpErrorReporting = error_reporting();
/** @var $pdoConnection \PDO */
$pdoConnection = $this->getDbAdapter()->getConnection();
$pdoWarningsEnabled = $pdoConnection->getAttribute(\PDO::ATTR_ERRMODE) & \PDO::ERRMODE_WARNING;
if (!$pdoWarningsEnabled) {
error_reporting($phpErrorReporting & ~E_WARNING);
}
try {
$result = $this->getDbAdapter()->query($sql);
error_reporting($phpErrorReporting);
} catch (\Exception $e) {
error_reporting($phpErrorReporting);
throw $e;
}
return $result;
return $this->getDbAdapter()->query($sql);
}

/**
Expand Down
41 changes: 30 additions & 11 deletions lib/internal/Magento/Framework/DB/Statement/Pdo/Mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\DB\Statement\Pdo;

use Magento\Framework\DB\Statement\Parameter;

/**
* Mysql DB Statement
*
* @author Magento Core Team <core@magentocommerce.com>
*/
namespace Magento\Framework\DB\Statement\Pdo;

use Magento\Framework\DB\Statement\Parameter;

class Mysql extends \Zend_Db_Statement_Pdo
{

/**
* Executes statement with binding values to it.
* Allows transferring specific options to DB driver.
* Executes statement with binding values to it. Allows transferring specific options to DB driver.
*
* @param array $params Array of values to bind to parameter placeholders.
* @return bool
Expand Down Expand Up @@ -61,11 +60,9 @@ public function _executeWithBinding(array $params)
$statement->bindParam($paramName, $bindValues[$name], $dataType, $length, $driverOptions);
}

try {
return $this->tryExecute(function () use ($statement) {
return $statement->execute();
} catch (\PDOException $e) {
throw new \Zend_Db_Statement_Exception($e->getMessage(), (int)$e->getCode(), $e);
}
});
}

/**
Expand All @@ -90,7 +87,29 @@ public function _execute(array $params = null)
if ($specialExecute) {
return $this->_executeWithBinding($params);
} else {
return parent::_execute($params);
return $this->tryExecute(function () use ($params) {
return $params !== null ? $this->_stmt->execute($params) : $this->_stmt->execute();
});
}
}

/**
* Executes query and avoid warnings.
*
* @param callable $callback
* @return bool
* @throws \Zend_Db_Statement_Exception
*/
private function tryExecute($callback)
{
$previousLevel = error_reporting(\E_ERROR); // disable warnings for PDO bugs #63812, #74401
try {
return $callback();
} catch (\PDOException $e) {
$message = sprintf('%s, query was: %s', $e->getMessage(), $this->_stmt->queryString);
throw new \Zend_Db_Statement_Exception($message, (int)$e->getCode(), $e);
} finally {
error_reporting($previousLevel);
}
}
}
154 changes: 154 additions & 0 deletions lib/internal/Magento/Framework/DB/Test/Unit/DB/Statement/MysqlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Framework\DB\Test\Unit\DB\Statement;

use Magento\Framework\DB\Statement\Parameter;
use Magento\Framework\DB\Statement\Pdo\Mysql;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

/**
* @inheritdoc
*/
class MysqlTest extends TestCase
{
/**
* @var \Zend_Db_Adapter_Abstract|MockObject
*/
private $adapterMock;

/**
* @var \PDO|MockObject
*/
private $pdoMock;

/**
* @var \Zend_Db_Profiler|MockObject
*/
private $zendDbProfilerMock;

/**
* @var \PDOStatement|MockObject
*/
private $pdoStatementMock;

/**
* @inheritdoc
*/
public function setUp()
{
$this->adapterMock = $this->getMockForAbstractClass(
\Zend_Db_Adapter_Abstract::class,
[],
'',
false,
true,
true,
['getConnection', 'getProfiler']
);
$this->pdoMock = $this->createMock(\PDO::class);
$this->adapterMock->expects($this->once())
->method('getConnection')
->willReturn($this->pdoMock);
$this->zendDbProfilerMock = $this->createMock(\Zend_Db_Profiler::class);
$this->adapterMock->expects($this->once())
->method('getProfiler')
->willReturn($this->zendDbProfilerMock);
$this->pdoStatementMock = $this->createMock(\PDOStatement::class);
}

public function testExecuteWithoutParams()
{
$query = 'SET @a=1;';
$this->pdoMock->expects($this->once())
->method('prepare')
->with($query)
->willReturn($this->pdoStatementMock);
$this->pdoStatementMock->expects($this->once())
->method('execute');
(new Mysql($this->adapterMock, $query))->_execute();
}

public function testExecuteWhenThrowPDOException()
{
$this->expectException(\Zend_Db_Statement_Exception::class);
$this->expectExceptionMessage('test message, query was:');
$errorReporting = error_reporting();
$query = 'SET @a=1;';
$this->pdoMock->expects($this->once())
->method('prepare')
->with($query)
->willReturn($this->pdoStatementMock);
$this->pdoStatementMock->expects($this->once())
->method('execute')
->willThrowException(new \PDOException('test message'));

$this->assertEquals($errorReporting, error_reporting(), 'Error report level was\'t restored');

(new Mysql($this->adapterMock, $query))->_execute();
}

public function testExecuteWhenParamsAsPrimitives()
{
$params = [':param1' => 'value1', ':param2' => 'value2'];
$query = 'UPDATE `some_table1` SET `col1`=\'val1\' WHERE `param1`=\':param1\' AND `param2`=\':param2\';';
$this->pdoMock->expects($this->once())
->method('prepare')
->with($query)
->willReturn($this->pdoStatementMock);
$this->pdoStatementMock->expects($this->never())
->method('bindParam');
$this->pdoStatementMock->expects($this->once())
->method('execute')
->with($params);

(new Mysql($this->adapterMock, $query))->_execute($params);
}

public function testExecuteWhenParamsAsParameterObject()
{
$param1 = $this->createMock(Parameter::class);
$param1Value = 'SomeValue';
$param1DataType = 'dataType';
$param1Length = '9';
$param1DriverOptions = 'some driver options';
$param1->expects($this->once())
->method('getIsBlob')
->willReturn(false);
$param1->expects($this->once())
->method('getDataType')
->willReturn($param1DataType);
$param1->expects($this->once())
->method('getLength')
->willReturn($param1Length);
$param1->expects($this->once())
->method('getDriverOptions')
->willReturn($param1DriverOptions);
$param1->expects($this->once())
->method('getValue')
->willReturn($param1Value);
$params = [
':param1' => $param1,
':param2' => 'value2',
];
$query = 'UPDATE `some_table1` SET `col1`=\'val1\' WHERE `param1`=\':param1\' AND `param2`=\':param2\';';
$this->pdoMock->expects($this->once())
->method('prepare')
->with($query)
->willReturn($this->pdoStatementMock);
$this->pdoStatementMock->expects($this->exactly(2))
->method('bindParam')
->withConsecutive(
[':param1', $param1Value, $param1DataType, $param1Length, $param1DriverOptions],
[':param2', 'value2', \PDO::PARAM_STR, null, null]
);
$this->pdoStatementMock->expects($this->once())
->method('execute');

(new Mysql($this->adapterMock, $query))->_execute($params);
}
}

0 comments on commit f211371

Please sign in to comment.