diff --git a/README.md b/README.md index 0e01b9b..ceb0df9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ php-mysql-replication [![Build Status](https://travis-ci.org/krowinski/php-mysql-replication.svg?branch=master)](https://travis-ci.org/krowinski/php-mysql-replication) [![Latest Stable Version](https://poser.pugx.org/krowinski/php-mysql-replication/v/stable)](https://packagist.org/packages/krowinski/php-mysql-replication) [![Total Downloads](https://poser.pugx.org/krowinski/php-mysql-replication/downloads)](https://packagist.org/packages/krowinski/php-mysql-replication) [![Latest Unstable Version](https://poser.pugx.org/krowinski/php-mysql-replication/v/unstable)](https://packagist.org/packages/krowinski/php-mysql-replication) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/4a0e49d4-3802-41d3-bb32-0a8194d0fd4d/mini.png)](https://insight.sensiolabs.com/projects/4a0e49d4-3802-41d3-bb32-0a8194d0fd4d) [![License](https://poser.pugx.org/krowinski/php-mysql-replication/license)](https://packagist.org/packages/krowinski/php-mysql-replication) - +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/krowinski/php-mysql-replication/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/krowinski/php-mysql-replication/?branch=master) Pure PHP Implementation of MySQL replication protocol. This allow you to receive event like insert, update, delete with their data and raw SQL queries. diff --git a/composer.json b/composer.json index bb738b8..4070c36 100644 --- a/composer.json +++ b/composer.json @@ -13,11 +13,11 @@ "doctrine/dbal": "^2.5", "doctrine/collections": "^1.3", "ext-sockets": "*", - "symfony/event-dispatcher": "^3.1", - "symfony/dependency-injection": "^3.1" + "symfony/event-dispatcher": ">=2.8 <=3.1", + "symfony/dependency-injection": ">=2.8 <=3.1" }, "require-dev": { - "phpunit/phpunit": "*" + "phpunit/phpunit": ">=4.8 <=5.6" }, "license": "MIT", "authors": [ diff --git a/example/dump_events.php b/example/dump_events.php index f60d6e4..155896f 100644 --- a/example/dump_events.php +++ b/example/dump_events.php @@ -48,4 +48,5 @@ public function allEvents(EventDTO $event) // start consuming events while (1) { $binLogStream->binLogEvent(); -} \ No newline at end of file +} + diff --git a/src/MySQLReplication/BinLog/BinLogConnect.php b/src/MySQLReplication/BinLog/BinLogConnect.php index 7b9d482..6337ab5 100644 --- a/src/MySQLReplication/BinLog/BinLogConnect.php +++ b/src/MySQLReplication/BinLog/BinLogConnect.php @@ -35,6 +35,10 @@ class BinLogConnect * @var BinLogAuth */ private $packAuth; + /** + * @var GtidService + */ + private $gtidService; /** * http://dev.mysql.com/doc/internals/en/auth-phase-fast-path.html 00 FE * @var array @@ -96,7 +100,7 @@ public function connectToStream() socket_set_block($this->socket); socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1); - if (false === socket_connect($this->socket, $this->config->getIp(), $this->config->getPort())) + if (false === socket_connect($this->socket, $this->config->getHost(), $this->config->getPort())) { throw new BinLogException(socket_strerror(socket_last_error()), socket_last_error()); } @@ -125,7 +129,7 @@ public function getPacket($checkForOkByte = true) $header = $this->readFromSocket(4); if (false === $header) { - return false; + return ''; } $dataLength = unpack('L', $header[0] . $header[1] . $header[2] . chr(0))[1]; @@ -310,7 +314,7 @@ private function setBinLogDump() $this->execute('SET @slave_gtid_ignore_duplicates = 0'); } - if ('' === $binFilePos || '' === $binFileName) + if (0 === $binFilePos || '' === $binFileName) { $master = $this->mySQLRepository->getMasterStatus(); $binFilePos = $master['Position']; diff --git a/src/MySQLReplication/BinLog/BinLogServerInfo.php b/src/MySQLReplication/BinLog/BinLogServerInfo.php index 1169e5f..6a1afe5 100644 --- a/src/MySQLReplication/BinLog/BinLogServerInfo.php +++ b/src/MySQLReplication/BinLog/BinLogServerInfo.php @@ -43,7 +43,7 @@ public static function parsePackage($pack) } //connection_id 4 bytes - self::$serverInfo['connection_id'] = $pack[$i] . $pack[++$i] . $pack[++$i] . $pack[++$i]; + self::$serverInfo['connection_id'] = unpack('I', $pack[$i] . $pack[++$i] . $pack[++$i] . $pack[++$i])[1]; $i++; //auth_plugin_data_part_1 diff --git a/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php b/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php index c3fb31d..9693ecd 100644 --- a/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php +++ b/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php @@ -183,7 +183,7 @@ public function readInt24() public function readInt64() { $data = unpack('V*', $this->read(self::UNSIGNED_INT64_LENGTH)); - return bcadd($data[1], ($data[2] << 32)); + return bcadd($data[1], $data[2] << 32); } /** @@ -424,15 +424,22 @@ public function isComplete($size) */ public static function pack64bit($value) { - return pack('C8', - ($value >> 0) & 0xFF, - ($value >> 8) & 0xFF, - ($value >> 16) & 0xFF, - ($value >> 24) & 0xFF, - ($value >> 32) & 0xFF, - ($value >> 40) & 0xFF, - ($value >> 48) & 0xFF, - ($value >> 56) & 0xFF - ); + return pack('C8', ($value >> 0) & 0xFF, ($value >> 8) & 0xFF, ($value >> 16) & 0xFF, ($value >> 24) & 0xFF, ($value >> 32) & 0xFF, ($value >> 40) & 0xFF, ($value >> 48) & 0xFF, ($value >> 56) & 0xFF); + } + + /** + * @return int + */ + public function getBinaryDataLength() + { + return strlen($this->binaryData); + } + + /** + * @return string + */ + public function getBinaryData() + { + return $this->binaryData; } } \ No newline at end of file diff --git a/src/MySQLReplication/BinaryDataReader/BinaryDataReaderBuilder.php b/src/MySQLReplication/BinaryDataReader/BinaryDataReaderBuilder.php index 113ce11..9af4870 100644 --- a/src/MySQLReplication/BinaryDataReader/BinaryDataReaderBuilder.php +++ b/src/MySQLReplication/BinaryDataReader/BinaryDataReaderBuilder.php @@ -8,6 +8,9 @@ */ class BinaryDataReaderBuilder { + /** + * @var string + */ private $binaryData = ''; /** @@ -18,6 +21,9 @@ public function withBinaryData($binaryData) $this->binaryData = $binaryData; } + /** + * @return BinaryDataReader + */ public function build() { return new BinaryDataReader( diff --git a/src/MySQLReplication/BinaryDataReader/BinaryDataReaderService.php b/src/MySQLReplication/BinaryDataReader/BinaryDataReaderService.php index 73286ce..acd5e25 100644 --- a/src/MySQLReplication/BinaryDataReader/BinaryDataReaderService.php +++ b/src/MySQLReplication/BinaryDataReader/BinaryDataReaderService.php @@ -9,7 +9,7 @@ class BinaryDataReaderService { /** - * @param $binaryData + * @param string $binaryData * @return BinaryDataReader */ public function makePackageFromBinaryData($binaryData) diff --git a/src/MySQLReplication/Config/Config.php b/src/MySQLReplication/Config/Config.php index 0beea64..6f8907c 100644 --- a/src/MySQLReplication/Config/Config.php +++ b/src/MySQLReplication/Config/Config.php @@ -17,7 +17,7 @@ class Config /** * @var string */ - private $ip; + private $host; /** * @var int */ @@ -74,7 +74,7 @@ class Config /** * Config constructor. * @param string $user - * @param string $ip + * @param string $host * @param int $port * @param string $password * @param string $dbName @@ -91,7 +91,7 @@ class Config */ public function __construct( $user, - $ip, + $host, $port, $password, $dbName, @@ -107,7 +107,7 @@ public function __construct( array $databasesOnly ) { $this->user = $user; - $this->ip = $ip; + $this->host = $host; $this->port = $port; $this->password = $password; $this->dbName = $dbName; @@ -132,9 +132,13 @@ public function validate() { throw new ConfigException(ConfigException::USER_ERROR_MESSAGE, ConfigException::USER_ERROR_CODE); } - if (!empty($this->ip) && false === filter_var($this->ip, FILTER_VALIDATE_IP)) + if (!empty($this->host)) { - throw new ConfigException(ConfigException::IP_ERROR_MESSAGE, ConfigException::IP_ERROR_CODE); + $ip = gethostbyname($this->host); + if (false === filter_var($ip, FILTER_VALIDATE_IP)) + { + throw new ConfigException(ConfigException::IP_ERROR_MESSAGE, ConfigException::IP_ERROR_CODE); + } } if (!empty($this->port) && false === filter_var($this->port, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { @@ -191,9 +195,9 @@ public function getUser() /** * @return string */ - public function getIp() + public function getHost() { - return $this->ip; + return $this->host; } /** @@ -299,5 +303,4 @@ public function getDatabasesOnly() { return $this->databasesOnly; } - } \ No newline at end of file diff --git a/src/MySQLReplication/Config/ConfigBuilder.php b/src/MySQLReplication/Config/ConfigBuilder.php index e787d78..b4f8f1d 100644 --- a/src/MySQLReplication/Config/ConfigBuilder.php +++ b/src/MySQLReplication/Config/ConfigBuilder.php @@ -47,7 +47,7 @@ class ConfigBuilder /** * @var int */ - private $binLogPosition = ''; + private $binLogPosition = 0; /** * @var array */ @@ -69,146 +69,193 @@ class ConfigBuilder */ private $mariaDbGtid; - /** - * @return Config - */ - public function build() - { - return new Config( - $this->user, - $this->host, - $this->port, - $this->password, - $this->dbName, - $this->charset, - $this->gtid, - $this->mariaDbGtid, - $this->slaveId, - $this->binLogFileName, - $this->binLogPosition, - $this->eventsOnly, - $this->eventsIgnore, - $this->tablesOnly, - $this->databasesOnly - ); - } /** * @param string $user + * @return ConfigBuilder */ public function withUser($user) { $this->user = $user; + + return $this; } /** * @param string $host + * @return ConfigBuilder */ public function withHost($host) { $this->host = $host; + + return $this; } /** * @param int $port + * @return ConfigBuilder */ public function withPort($port) { $this->port = $port; + + return $this; } /** * @param string $password + * @return ConfigBuilder */ public function withPassword($password) { $this->password = $password; + + return $this; } /** * @param string $dbName + * @return ConfigBuilder */ public function withDbName($dbName) { $this->dbName = $dbName; + + return $this; } /** * @param string $charset + * @return ConfigBuilder */ public function withCharset($charset) { $this->charset = $charset; + + return $this; } /** * @param string $gtid + * @return ConfigBuilder */ public function withGtid($gtid) { $this->gtid = $gtid; + + return $this; } /** * @param int $slaveId + * @return ConfigBuilder */ public function withSlaveId($slaveId) { $this->slaveId = $slaveId; + + return $this; } /** * @param string $binLogFileName + * @return ConfigBuilder */ public function withBinLogFileName($binLogFileName) { $this->binLogFileName = $binLogFileName; + + return $this; } /** * @param int $binLogPosition + * @return ConfigBuilder */ public function withBinLogPosition($binLogPosition) { $this->binLogPosition = $binLogPosition; + + return $this; } /** * @param array $eventsOnly + * @return ConfigBuilder */ - public function withEventsOnly(array $eventsOnly) + public function withEventsOnly($eventsOnly) { $this->eventsOnly = $eventsOnly; + + return $this; } /** * @param array $eventsIgnore + * @return ConfigBuilder */ public function withEventsIgnore(array $eventsIgnore) { $this->eventsIgnore = $eventsIgnore; + + return $this; } /** * @param array $tablesOnly + * @return ConfigBuilder */ public function withTablesOnly(array $tablesOnly) { $this->tablesOnly = $tablesOnly; + + return $this; } /** * @param array $databasesOnly + * @return ConfigBuilder */ public function withDatabasesOnly(array $databasesOnly) { $this->databasesOnly = $databasesOnly; + + return $this; } /** * @param string $mariaDbGtid + * @return ConfigBuilder */ public function withMariaDbGtid($mariaDbGtid) { $this->mariaDbGtid = $mariaDbGtid; + + return $this; } + + /** + * @return Config + */ + public function build() + { + return new Config( + $this->user, + $this->host, + $this->port, + $this->password, + $this->dbName, + $this->charset, + $this->gtid, + $this->mariaDbGtid, + $this->slaveId, + $this->binLogFileName, + $this->binLogPosition, + $this->eventsOnly, + $this->eventsIgnore, + $this->tablesOnly, + $this->databasesOnly + ); + } + } \ No newline at end of file diff --git a/src/MySQLReplication/Config/ConfigService.php b/src/MySQLReplication/Config/ConfigService.php index fd5397f..64c5bb6 100644 --- a/src/MySQLReplication/Config/ConfigService.php +++ b/src/MySQLReplication/Config/ConfigService.php @@ -21,7 +21,7 @@ public function makeConfigFromArray(array $config) { $configBuilder->withUser($v); } - if ('ip' === $k) + if ('ip' === $k || 'host' === $k) { $configBuilder->withHost($v); } diff --git a/src/MySQLReplication/Definitions/ConstFieldType.php b/src/MySQLReplication/Definitions/ConstFieldType.php index 67dca04..18583f8 100644 --- a/src/MySQLReplication/Definitions/ConstFieldType.php +++ b/src/MySQLReplication/Definitions/ConstFieldType.php @@ -28,6 +28,7 @@ class ConstFieldType const TIMESTAMP2 = 17; const DATETIME2 = 18; const TIME2 = 19; + const JSON = 245; const NEWDECIMAL = 246; const ENUM = 247; const SET = 248; diff --git a/src/MySQLReplication/Event/RowEvent/RowEvent.php b/src/MySQLReplication/Event/RowEvent/RowEvent.php index 99f6b25..3769f16 100644 --- a/src/MySQLReplication/Event/RowEvent/RowEvent.php +++ b/src/MySQLReplication/Event/RowEvent/RowEvent.php @@ -2,9 +2,8 @@ namespace MySQLReplication\Event\RowEvent; +use MySQLReplication\BinaryDataReader\BinaryDataReader; use MySQLReplication\BinaryDataReader\Exception\BinaryDataReaderException; -use MySQLReplication\Event\EventCommon; -use MySQLReplication\Event\EventInfo; use MySQLReplication\Config\Config; use MySQLReplication\Definitions\ConstEventType; use MySQLReplication\Definitions\ConstFieldType; @@ -12,10 +11,13 @@ use MySQLReplication\Event\DTO\TableMapDTO; use MySQLReplication\Event\DTO\UpdateRowsDTO; use MySQLReplication\Event\DTO\WriteRowsDTO; +use MySQLReplication\Event\EventCommon; +use MySQLReplication\Event\EventInfo; use MySQLReplication\Event\Exception\EventException; use MySQLReplication\Exception\MySQLReplicationException; +use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderException; +use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderFactory; use MySQLReplication\Repository\MySQLRepository; -use MySQLReplication\BinaryDataReader\BinaryDataReader; /** * Class RowEvent @@ -23,6 +25,10 @@ */ class RowEvent extends EventCommon { + /** + * @var TableMap[] + */ + private static $tableMapCache; /** * @var array */ @@ -44,10 +50,6 @@ class RowEvent extends EventCommon 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, ]; - /** - * @var TableMap[] - */ - private static $tableMapCache; /** * @var MySQLRepository */ @@ -60,6 +62,10 @@ class RowEvent extends EventCommon * @var TableMap */ private $currentTableMap; + /** + * @var JsonBinaryDecoderFactory + */ + private $jsonBinaryDecoderFactory; /** * RowEvent constructor. @@ -67,18 +73,21 @@ class RowEvent extends EventCommon * @param MySQLRepository $MySQLRepository * @param BinaryDataReader $binaryDataReader * @param EventInfo $eventInfo + * @param JsonBinaryDecoderFactory $jsonBinaryDecoderFactory */ public function __construct( Config $config, MySQLRepository $MySQLRepository, BinaryDataReader $binaryDataReader, - EventInfo $eventInfo + EventInfo $eventInfo, + JsonBinaryDecoderFactory $jsonBinaryDecoderFactory ) { parent::__construct($eventInfo, $binaryDataReader); $this->MySQLRepository = $MySQLRepository; $this->config = $config; + $this->jsonBinaryDecoderFactory = $jsonBinaryDecoderFactory; } /** @@ -180,6 +189,10 @@ public function makeTableMapDTO() /** * @return WriteRowsDTO + * @throws BinaryDataReaderException + * @throws EventException + * @throws JsonBinaryDecoderException + * @throws MySQLReplicationException */ public function makeWriteRowsDTO() { @@ -200,6 +213,7 @@ public function makeWriteRowsDTO() /** * @return bool + * @throws BinaryDataReaderException */ private function rowInit() { @@ -228,10 +242,43 @@ private function rowInit() return false; } + /** + * @return array + * @throws BinaryDataReaderException + * @throws EventException + * @throws JsonBinaryDecoderException + * @throws MySQLReplicationException + */ + private function getValues() + { + $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount()); + $binaryData = $this->binaryDataReader->read($columnsBinarySize); + + $values = []; + while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) + { + $values[] = $this->getColumnData($binaryData); + } + + return $values; + } + + /** + * @param int $columnsAmount + * @return int + */ + private function getColumnsBinarySize($columnsAmount) + { + return (int)(($columnsAmount + 7) / 8); + } + /** * @param int $colsBitmap * @return array - * @throws \Exception + * @throws BinaryDataReaderException + * @throws EventException + * @throws JsonBinaryDecoderException + * @throws MySQLReplicationException */ private function getColumnData($colsBitmap) { @@ -394,6 +441,10 @@ private function getColumnData($colsBitmap) { $values[$name] = $this->binaryDataReader->readLengthCodedPascalString($column['length_size']); } + elseif ($column['type'] === ConstFieldType::JSON) + { + $values[$name] = $this->jsonBinaryDecoderFactory->makeJsonBinaryDecoder($this->getString(BinaryDataReader::UNSIGNED_INT32_LENGTH, $column))->parseToString(); + } else { throw new MySQLReplicationException('Unknown row type: ' . $column['type']); @@ -405,15 +456,6 @@ private function getColumnData($colsBitmap) return $values; } - /** - * @param int $columnsAmount - * @return int - */ - private function getColumnsBinarySize($columnsAmount) - { - return (int)(($columnsAmount + 7) / 8); - } - /** * @param string $bitmap * @return int @@ -511,7 +553,7 @@ private function getDecimal(array $column) $mask = -1; $res = '-'; } - $this->binaryDataReader->unread(pack('C', ($value ^ 0x80))); + $this->binaryDataReader->unread(pack('C', $value ^ 0x80)); $size = $compressed_bytes[$comp_integral]; if ($size > 0) @@ -551,7 +593,7 @@ private function getDatetime() { $value = $this->binaryDataReader->readUInt64(); // nasty mysql 0000-00-00 dates - if ($value == 0) + if ($value === 0) { return null; } @@ -580,7 +622,7 @@ private function getDatetime() * 40 bits = 5 bytes * @param array $column * @return string - * @throws \Exception + * @throws BinaryDataReaderException */ private function getDatetime2(array $column) { @@ -632,42 +674,40 @@ private function getBinarySlice($binary, $start, $size, $data_length) * * @param array $column * @return int|string - * @throws \Exception + * @throws BinaryDataReaderException */ private function getFSP(array $column) { $read = 0; $time = ''; - if ($column['fsp'] == 1 || $column['fsp'] == 2) + if ($column['fsp'] === 1 || $column['fsp'] === 2) { $read = 1; } - elseif ($column['fsp'] == 3 || $column['fsp'] == 4) + elseif ($column['fsp'] === 3 || $column['fsp'] === 4) { $read = 2; } - elseif ($column ['fsp'] == 5 || $column['fsp'] == 6) + elseif ($column ['fsp'] === 5 || $column['fsp'] === 6) { $read = 3; } if ($read > 0) { $microsecond = $this->binaryDataReader->readIntBeBySize($read); + + $time = $microsecond; if ($column['fsp'] % 2) { $time = (int)($microsecond / 10); } - else - { - $time = $microsecond; - } } return $time; } /** - * TIME encoding for nonfractional part: + * TIME encoding for non fractional part: * 1 bit sign (1= non-negative, 0= negative) * 1 bit unused (reserved for future extensions) * 10 bits hour (0-838) @@ -678,6 +718,7 @@ private function getFSP(array $column) * * @param array $column * @return string + * @throws BinaryDataReaderException */ private function getTime2(array $column) { @@ -694,6 +735,7 @@ private function getTime2(array $column) * @param array $column * @return bool|string * @throws EventException + * @throws BinaryDataReaderException */ private function getTimestamp2(array $column) { @@ -712,7 +754,7 @@ private function getTimestamp2(array $column) private function getDate() { $time = $this->binaryDataReader->readUInt24(); - if (0 == $time) + if (0 === $time) { return null; } @@ -720,7 +762,7 @@ private function getDate() $year = ($time & ((1 << 15) - 1) << 9) >> 9; $month = ($time & ((1 << 4) - 1) << 5) >> 5; $day = ($time & ((1 << 5) - 1)); - if ($year == 0 || $month == 0 || $day == 0) + if ($year === 0 || $month === 0 || $day === 0) { return null; } @@ -732,6 +774,7 @@ private function getDate() * @param array $column * @return array * @throws EventException + * @throws BinaryDataReaderException */ private function getSet(array $column) { @@ -801,6 +844,10 @@ private function getBit(array $column) /** * @return DeleteRowsDTO + * @throws BinaryDataReaderException + * @throws EventException + * @throws JsonBinaryDecoderException + * @throws MySQLReplicationException */ public function makeDeleteRowsDTO() { @@ -821,6 +868,10 @@ public function makeDeleteRowsDTO() /** * @return UpdateRowsDTO + * @throws BinaryDataReaderException + * @throws EventException + * @throws JsonBinaryDecoderException + * @throws MySQLReplicationException */ public function makeUpdateRowsDTO() { @@ -849,22 +900,4 @@ public function makeUpdateRowsDTO() $values ); } - - /** - * @return array - * @throws EventException - */ - private function getValues() - { - $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount()); - $binaryData = $this->binaryDataReader->read($columnsBinarySize); - - $values = []; - while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) - { - $values[] = $this->getColumnData($binaryData); - } - - return $values; - } } diff --git a/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php b/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php index d624162..19ff661 100644 --- a/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php +++ b/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php @@ -5,6 +5,7 @@ use MySQLReplication\BinaryDataReader\BinaryDataReader; use MySQLReplication\Event\EventInfo; use MySQLReplication\Config\Config; +use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderFactory; use MySQLReplication\Repository\MySQLRepository; /** @@ -29,14 +30,26 @@ class RowEventBuilder * @var EventInfo */ private $eventInfo; + /** + * @var JsonBinaryDecoderFactory + */ + private $jsonBinaryDecoderFactory; + /** + * RowEventBuilder constructor. + * @param Config $config + * @param MySQLRepository $MySQLRepository + * @param JsonBinaryDecoderFactory $jsonBinaryDecoderFactory + */ public function __construct( Config $config, - MySQLRepository $MySQLRepository + MySQLRepository $MySQLRepository, + JsonBinaryDecoderFactory $jsonBinaryDecoderFactory ) { $this->MySQLRepository = $MySQLRepository; $this->config = $config; + $this->jsonBinaryDecoderFactory = $jsonBinaryDecoderFactory; } /** @@ -56,7 +69,8 @@ public function build() $this->config, $this->MySQLRepository, $this->package, - $this->eventInfo + $this->eventInfo, + $this->jsonBinaryDecoderFactory ); } diff --git a/src/MySQLReplication/Event/RowEvent/RowEventService.php b/src/MySQLReplication/Event/RowEvent/RowEventService.php index 1eaef55..f526754 100644 --- a/src/MySQLReplication/Event/RowEvent/RowEventService.php +++ b/src/MySQLReplication/Event/RowEvent/RowEventService.php @@ -5,6 +5,7 @@ use MySQLReplication\BinaryDataReader\BinaryDataReader; use MySQLReplication\Event\EventInfo; use MySQLReplication\Config\Config; +use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderFactory; use MySQLReplication\Repository\MySQLRepository; /** @@ -13,17 +14,24 @@ */ class RowEventService { + /** + * @var RowEventBuilder + */ + private $rowEventBuilder; + /** * RowEventService constructor. * @param Config $config * @param MySQLRepository $mySQLRepository + * @param JsonBinaryDecoderFactory $jsonBinaryDecoderFactory */ public function __construct( Config $config, - MySQLRepository $mySQLRepository + MySQLRepository $mySQLRepository, + JsonBinaryDecoderFactory $jsonBinaryDecoderFactory ) { - $this->rowEventBuilder = new RowEventBuilder($config, $mySQLRepository); + $this->rowEventBuilder = new RowEventBuilder($config, $mySQLRepository, $jsonBinaryDecoderFactory); } /** diff --git a/src/MySQLReplication/Gtid/GtidService.php b/src/MySQLReplication/Gtid/GtidService.php index e9e7db3..04c9edf 100644 --- a/src/MySQLReplication/Gtid/GtidService.php +++ b/src/MySQLReplication/Gtid/GtidService.php @@ -8,6 +8,11 @@ */ class GtidService { + /** + * @var GtidCollection + */ + private $GtidCollection; + /** * GtidSet constructor. */ diff --git a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderException.php b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderException.php new file mode 100644 index 0000000..e1fcd87 --- /dev/null +++ b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderException.php @@ -0,0 +1,16 @@ +jsonString .= var_export($bool, true); + } + + /** + * @param int $val + */ + public function formatValueNumeric($val) + { + $this->jsonString .= $val; + } + + /** + * @param string $val + */ + public function formatValue($val) + { + $this->jsonString .= '"' . $val . '"'; + } + + public function formatEndObject() + { + $this->jsonString .= '}'; + } + + public function formatBeginArray() + { + $this->jsonString .= '['; + } + + public function formatEndArray() + { + $this->jsonString .= ']'; + } + + public function formatBeginObject() + { + $this->jsonString .= '{'; + } + + public function formatNextEntry() + { + $this->jsonString .= ','; + } + + /** + * @param string $name + */ + public function formatName($name) + { + $this->jsonString .= '"' . $name . '":'; + } + + public function formatValueNull() + { + $this->formatValue('null'); + } + + /** + * @return string + */ + public function getJsonString() + { + return $this->jsonString; + } +} \ No newline at end of file diff --git a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php new file mode 100644 index 0000000..f94dae7 --- /dev/null +++ b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php @@ -0,0 +1,370 @@ +binaryDataReader = $binaryDataReader; + $this->jsonBinaryDecoderFormatter = $jsonBinaryDecoderFormatter; + } + + /** + * @return string + * @throws JsonBinaryDecoderException + * @throws BinaryDataReaderException + */ + public function parseToString() + { + $this->parseJson($this->binaryDataReader->readUInt8()); + + return $this->jsonBinaryDecoderFormatter->getJsonString(); + } + + /** + * @return int + * @throws BinaryDataReaderException + */ + private function readVariableInt() + { + $length = $this->binaryDataReader->getBinaryDataLength(); + $len = 0; + for ($i = 0; $i < $length; $i++) + { + $size = $this->binaryDataReader->readUInt8(); + // Get the next 7 bits of the length. + $len |= ($size & 127) << (7 * $i); + if (($size & 128) === 0) + { + // This was the last byte. Return successfully. + return $len; + } + } + + return $len; + } + + /** + * @param int $type + * @throws JsonBinaryDecoderException + * @throws BinaryDataReaderException + */ + private function parseJson($type) + { + if (self::SMALL_OBJECT === $type) + { + $this->parseObject(); + } + else if (self::LARGE_OBJECT === $type) + { + //TODO + } + else if (self::SMALL_ARRAY === $type) + { + $this->parseArray(); + } + else if (self::LARGE_ARRAY === $type) + { + //TODO + } + else + { + $this->parseScalar($type); + } + } + + private function parseObject() + { + $elementCount = $this->binaryDataReader->readUInt16(); + $size = $this->binaryDataReader->readUInt16(); + + // Read each key-entry, consisting of the offset and length of each key ... + $keyLengths = []; + for ($i = 0; $i !== $elementCount; ++$i) + { + $this->binaryDataReader->readUInt16(); // $keyOffset unused + $keyLengths[$i] = $this->binaryDataReader->readUInt16(); + } + + $entries = []; + for ($i = 0; $i !== $elementCount; ++$i) + { + $entries[$i] = $this->parseValueType($size); + } + + // Read each key ... + $keys = []; + for ($i = 0; $i !== $elementCount; ++$i) + { + $keys[$i] = $this->binaryDataReader->read($keyLengths[$i]); + } + + $this->jsonBinaryDecoderFormatter->formatBeginObject(); + + for ($i = 0; $i !== $elementCount; ++$i) + { + if ($i !== 0) + { + $this->jsonBinaryDecoderFormatter->formatNextEntry(); + } + + $this->jsonBinaryDecoderFormatter->formatName($keys[$i]); + + /* @var JsonBinaryDecoderValue[] $entries */ + $this->assignValues($entries[$i]); + } + + $this->jsonBinaryDecoderFormatter->formatEndObject(); + } + + /** + * @param int $numBytes + * @return JsonBinaryDecoderValue + * @throws BinaryDataReaderException + * @throws \LengthException + */ + private function parseValueType($numBytes) + { + $type = $this->binaryDataReader->readInt8(); + + if (self::LITERAL === $type) + { + return new JsonBinaryDecoderValue( + true, + $this->readLiteral(), + $type + ); + } + else if (self::INT16 === $type) + { + return new JsonBinaryDecoderValue( + true, + $this->binaryDataReader->readInt16(), + $type + ); + } + else if (self::UINT16 === $type) + { + return new JsonBinaryDecoderValue( + true, + $this->binaryDataReader->readUInt16(), + $type + ); + } + else if (self::INT32 === $type) + { + return new JsonBinaryDecoderValue( + true, + $this->binaryDataReader->readInt32(), + $type + ); + } + else if (self::UINT32 === $type) + { + return new JsonBinaryDecoderValue( + true, + $this->binaryDataReader->readUInt32(), + $type + ); + } + else + { + $offset = $this->binaryDataReader->readUInt16(); + if ($offset > $numBytes) + { + throw new \LengthException( + 'The offset for the value in the JSON binary document is ' . + $offset . + ', which is larger than the binary form of the JSON document (' . + $numBytes . ' bytes)' + ); + } + + return new JsonBinaryDecoderValue( + false, + null, + $type + ); + } + } + + /** + * @return bool|null + * @throws BinaryDataReaderException + */ + private function readLiteral() + { + $literal = ord($this->binaryDataReader->read(2)); + if (0 === $literal) + { + return null; + } + else if (1 === $literal) + { + return true; + } + else if (2 === $literal) + { + return false; + } + } + + /** + * @param JsonBinaryDecoderValue $jsonBinaryDecoderValue + * @throws JsonBinaryDecoderException + * @throws BinaryDataReaderException + */ + private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue) + { + if (false === $jsonBinaryDecoderValue->isIsResolved()) + { + $this->parseJson($jsonBinaryDecoderValue->getType()); + } + else + { + if (null === $jsonBinaryDecoderValue->getValue()) + { + $this->jsonBinaryDecoderFormatter->formatValueNull(); + } + elseif (is_bool($jsonBinaryDecoderValue->getValue())) + { + $this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue()); + } + elseif (is_numeric($jsonBinaryDecoderValue->getValue())) + { + $this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue()); + } + } + } + + private function parseArray() + { + $numElements = $this->binaryDataReader->readUInt16(); + $numBytes = $this->binaryDataReader->readUInt16(); + + $entries = []; + for ($i = 0; $i !== $numElements; ++$i) + { + $entries[$i] = $this->parseValueType($numBytes); + } + + $this->jsonBinaryDecoderFormatter->formatBeginArray(); + + for ($i = 0; $i !== $numElements; ++$i) + { + if ($i !== 0) + { + $this->jsonBinaryDecoderFormatter->formatNextEntry(); + } + + /* @var JsonBinaryDecoderValue[] $entries */ + $this->assignValues($entries[$i]); + } + + $this->jsonBinaryDecoderFormatter->formatEndArray(); + } + + private function parseBoolean() + { + $r = $this->readLiteral(); + if (null === $r) + { + $this->jsonBinaryDecoderFormatter->formatValueNull(); + } + else + { + $this->jsonBinaryDecoderFormatter->formatValueBool($r); + } + } + + private function parseScalar($type) + { + if (self::LITERAL === $type) + { + $this->parseBoolean(); + } + else if (self::INT16 === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt16()); + } + else if (self::INT32 === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt32()); + } + else if (self::INT64 === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt64()); + } + else if (self::UINT16 === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt16()); + } + else if (self::UINT64 === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt64()); + } + else if (self::DOUBLE === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readDouble()); + } + else if (self::STRING === $type) + { + $this->jsonBinaryDecoderFormatter->formatValue( + $this->binaryDataReader->read($this->readVariableInt()) + ); + } + /** + * else if (self::OPAQUE === $type) + * { + * + * } + */ + else + { + throw new JsonBinaryDecoderException( + JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_MESSAGE . $type, + JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_CODE + ); + } + } +} \ No newline at end of file diff --git a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php new file mode 100644 index 0000000..4c0b06a --- /dev/null +++ b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php @@ -0,0 +1,61 @@ +isResolved = $isResolved; + $this->value = $value; + $this->type = $type; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @return boolean + */ + public function isIsResolved() + { + return $this->isResolved; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } +} \ No newline at end of file diff --git a/src/MySQLReplication/MySQLReplicationFactory.php b/src/MySQLReplication/MySQLReplicationFactory.php index baf738f..547efea 100644 --- a/src/MySQLReplication/MySQLReplicationFactory.php +++ b/src/MySQLReplication/MySQLReplicationFactory.php @@ -16,6 +16,7 @@ use MySQLReplication\Event\RowEvent\RowEventService; use MySQLReplication\Exception\MySQLReplicationException; use MySQLReplication\Gtid\GtidService; +use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderFactory; use MySQLReplication\Repository\MySQLRepository; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -57,6 +58,14 @@ class MySQLReplicationFactory * @var GtidService */ private $GtiService; + /** + * @var RowEventService + */ + private $rowEventService; + /** + * @var JsonBinaryDecoderFactory + */ + private $jsonBinaryDecoderFactory; /** * @param Config $config @@ -72,20 +81,30 @@ public function __construct(Config $config) $this->connection = DriverManager::getConnection([ 'user' => $config->getUser(), 'password' => $config->getPassword(), - 'host' => $config->getIp(), + 'host' => $config->getHost(), 'port' => $config->getPort(), 'driver' => 'pdo_mysql', - 'charset' => $config->getCharset() + 'charset' => $config->getCharset() ]); $this->binLogAuth = new BinLogAuth(); $this->MySQLRepository = new MySQLRepository($this->connection); $this->GtiService = new GtidService(); + $this->binLogConnect = new BinLogConnect($config, $this->MySQLRepository, $this->binLogAuth, $this->GtiService); $this->binLogConnect->connectToStream(); + + $this->jsonBinaryDecoderFactory = new JsonBinaryDecoderFactory(); $this->binaryDataReaderService = new BinaryDataReaderService(); - $this->rowEventService = new RowEventService($config, $this->MySQLRepository); + $this->rowEventService = new RowEventService($config, $this->MySQLRepository, $this->jsonBinaryDecoderFactory); $this->eventDispatcher = new EventDispatcher(); - $this->event = new Event($config, $this->binLogConnect, $this->binaryDataReaderService, $this->rowEventService, $this->eventDispatcher); + + $this->event = new Event( + $config, + $this->binLogConnect, + $this->binaryDataReaderService, + $this->rowEventService, + $this->eventDispatcher + ); } /** diff --git a/tests/Integration/TypesTest.php b/tests/Integration/TypesTest.php old mode 100644 new mode 100755 index 8195e4c..2eb0a0a --- a/tests/Integration/TypesTest.php +++ b/tests/Integration/TypesTest.php @@ -2,10 +2,10 @@ namespace Integration; +use MySQLReplication\Config\ConfigService; use MySQLReplication\Event\DTO\EventDTO; use MySQLReplication\Event\EventSubscribers; use MySQLReplication\MySQLReplicationFactory; -use MySQLReplication\Config\ConfigService; /** * Class BenchmarkEventSubscribers @@ -73,6 +73,7 @@ protected function setUp() $this->conn->exec('DROP DATABASE IF EXISTS ' . $this->database); $this->conn->exec('CREATE DATABASE ' . $this->database); $this->conn->exec('USE ' . $this->database); + $this->conn->exec('SET SESSION sql_mode = \'\';'); } /** @@ -572,7 +573,7 @@ public function shouldBeBrokenDateTime() */ public function shouldBeYear() { - $create_query = "CREATE TABLE test (test YEAR(4), test2 YEAR(2))"; + $create_query = "CREATE TABLE test (test YEAR(4), test2 YEAR)"; $insert_query = "INSERT INTO test VALUES(1984, 1984)"; $event = $this->createAndInsertValue($create_query, $insert_query); @@ -812,4 +813,87 @@ public function shouldBeEncodedUTF8() $this->assertEquals($string, $event->getValues()[0]['test']); } + + /** + * @test + */ + public function shouldBeJson() + { + if (false === strpos($this->conn->fetchColumn('SELECT VERSION()'), '5.7')) + { + $this->markTestIncomplete('Only for mysql 5.7'); + } + + $create_query = "create table t1 (i INT, j JSON)"; + $insert_query = "INSERT INTO t1 VALUES + (0, NULL) , + (1, '{\"a\": 2}'), + (2, '[1,2]'), + (3, '{\"a\":\"b\", \"c\":\"d\",\"ab\":\"abc\", \"bc\": [\"x\", \"y\"]}'), + (4, '[\"here\", [\"I\", \"am\"], \"!!!\"]'), + (5, '\"scalar string\"'), + (6, 'true'), + (7, 'false'), + (8, 'null'), + (9, '-1'), + (10, CAST(CAST(1 AS UNSIGNED) AS JSON)), + (11, '32767'), + (12, '32768'), + (13, '-32768'), + (14, '-32769'), + (15, '2147483647'), + (16, '2147483648'), + (17, '-2147483648'), + (18, '-2147483649'), + (19, '18446744073709551615'), + (20, '18446744073709551616'), + (21, '3.14'), + (22, '{}'), + (23, '[]'), + -- (24, CAST(CAST('2015-01-15 23:24:25' AS DATETIME) AS JSON)), + -- (25, CAST(CAST('23:24:25' AS TIME) AS JSON)), + -- (125, CAST(CAST('23:24:25.12' AS TIME(3)) AS JSON)), + -- (225, CAST(CAST('23:24:25.0237' AS TIME(3)) AS JSON)), + -- (26, CAST(CAST('2015-01-15' AS DATE) AS JSON)), + -- (27, CAST(TIMESTAMP'2015-01-15 23:24:25' AS JSON)), + -- (127, CAST(TIMESTAMP'2015-01-15 23:24:25.12' AS JSON)), + -- (227, CAST(TIMESTAMP'2015-01-15 23:24:25.0237' AS JSON)), + -- (327, CAST(UNIX_TIMESTAMP('2015-01-15 23:24:25') AS JSON)), + -- (28, CAST(ST_GeomFromText('POINT(1 1)') AS JSON)), + (29, CAST('[]' AS CHAR CHARACTER SET 'ascii')), + -- (30, CAST(x'cafe' AS JSON)), + -- (31, CAST(x'cafebabe' AS JSON)), + (100, CONCAT('{\"', REPEAT('a', 64 * 1024 - 1), '\":123}')) + "; + + $event = $this->createAndInsertValue($create_query, $insert_query); + + $results = $event->getValues(); + + $this->assertEquals($results[0]['j'], null); + $this->assertEquals($results[1]['j'], '{"a":2}'); + $this->assertEquals($results[2]['j'], '[1,2]'); + $this->assertEquals($results[3]['j'], '{"a":"b","c":"d","ab":"abc","bc":["x","y"]}'); + $this->assertEquals($results[4]['j'], '["here",["I","am"],"!!!"]'); + $this->assertEquals($results[5]['j'], '"scalar string"'); + $this->assertEquals($results[6]['j'], 'true'); + $this->assertEquals($results[7]['j'], 'false'); + $this->assertEquals($results[8]['j'], '"null"'); + $this->assertEquals($results[9]['j'], '"-1"'); + $this->assertEquals($results[10]['j'], '"1"'); + $this->assertEquals($results[11]['j'], '"32767"'); + $this->assertEquals($results[12]['j'], '"32768"'); + $this->assertEquals($results[13]['j'], '"-32768"'); + $this->assertEquals($results[14]['j'], '"-32769"'); + $this->assertEquals($results[15]['j'], '"2147483647"'); + $this->assertEquals($results[16]['j'], '"2147483648"'); + $this->assertEquals($results[17]['j'], '"-2147483648"'); + $this->assertEquals($results[18]['j'], '"-2147483649"'); + $this->assertEquals($results[19]['j'], '"18446744073709551615"'); + $this->assertEquals($results[20]['j'], '"1.844674407371E+19"'); + $this->assertEquals($results[21]['j'], '"3.14"'); + $this->assertEquals($results[22]['j'], '{}'); + $this->assertEquals($results[23]['j'], '[]'); + $this->assertEquals($results[24]['j'], '[]'); + } } \ No newline at end of file diff --git a/tests/Unit/BinaryDataReader/BinaryDataReaderBuilderTest.php b/tests/Unit/BinaryDataReader/BinaryDataReaderBuilderTest.php new file mode 100755 index 0000000..10e91d8 --- /dev/null +++ b/tests/Unit/BinaryDataReader/BinaryDataReaderBuilderTest.php @@ -0,0 +1,32 @@ +withBinaryData($expected); + $class = $builder->build(); + + self::assertAttributeEquals($expected, 'binaryData', $builder); + self::assertInstanceOf(BinaryDataReader::class, $class); + } +} \ No newline at end of file diff --git a/tests/Unit/BinaryDataReader/BinaryDataReaderServiceTest.php b/tests/Unit/BinaryDataReader/BinaryDataReaderServiceTest.php new file mode 100755 index 0000000..9c71308 --- /dev/null +++ b/tests/Unit/BinaryDataReader/BinaryDataReaderServiceTest.php @@ -0,0 +1,25 @@ +makePackageFromBinaryData('foo'); + self::assertInstanceOf(BinaryDataReader::class, $service); + } +} \ No newline at end of file diff --git a/tests/Unit/BinaryDataReader/BinaryDataReaderTest.php b/tests/Unit/BinaryDataReader/BinaryDataReaderTest.php old mode 100644 new mode 100755 index fb9ac2c..925212a --- a/tests/Unit/BinaryDataReader/BinaryDataReaderTest.php +++ b/tests/Unit/BinaryDataReader/BinaryDataReaderTest.php @@ -27,7 +27,7 @@ private function getBinaryRead($data) public function shouldRead() { $expected = 'zażółć gęślą jaźń'; - $this->assertSame($expected, pack('H*', $this->getBinaryRead(unpack('H*', $expected)[1])->read(52))); + self::assertSame($expected, pack('H*', self::getBinaryRead(unpack('H*', $expected)[1])->read(52))); } /** @@ -35,11 +35,11 @@ public function shouldRead() */ public function shouldReadCodedBinary() { - $this->getBinaryRead(pack('C', ''))->readCodedBinary(); - $this->getBinaryRead(pack('C', BinaryDataReader::NULL_COLUMN))->readCodedBinary(); - $this->getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_SHORT_COLUMN))->readCodedBinary(); - $this->getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_INT24_COLUMN))->readCodedBinary(); - $this->getBinaryRead(pack('V', BinaryDataReader::UNSIGNED_INT64_COLUMN) . pack('V', 2147483647) . pack('V', 2147483647))->readCodedBinary(); + self::getBinaryRead(pack('C', ''))->readCodedBinary(); + self::getBinaryRead(pack('C', BinaryDataReader::NULL_COLUMN))->readCodedBinary(); + self::getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_SHORT_COLUMN))->readCodedBinary(); + self::getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_INT24_COLUMN))->readCodedBinary(); + self::getBinaryRead(pack('V', BinaryDataReader::UNSIGNED_INT64_COLUMN) . pack('V', 2147483647) . pack('V', 2147483647))->readCodedBinary(); } /** @@ -48,7 +48,7 @@ public function shouldReadCodedBinary() */ public function shouldThrowErrorOnUnknownCodedBinary() { - $this->getBinaryRead(pack('i', 255))->readCodedBinary(); + self::getBinaryRead(pack('i', 255))->readCodedBinary(); } public function dataProviderForUInt() @@ -75,7 +75,7 @@ public function dataProviderForUInt() */ public function shouldReadUIntBySize($size, $data, $expected) { - $this->assertSame($expected, $this->getBinaryRead($data)->readUIntBySize($size)); + self::assertSame($expected, self::getBinaryRead($data)->readUIntBySize($size)); } /** @@ -84,7 +84,7 @@ public function shouldReadUIntBySize($size, $data, $expected) */ public function shouldThrowErrorOnReadUIntBySizeNotSupported() { - $this->getBinaryRead('')->readUIntBySize(32); + self::getBinaryRead('')->readUIntBySize(32); } public function dataProviderForBeInt() @@ -108,7 +108,7 @@ public function dataProviderForBeInt() */ public function shouldReadIntBeBySize($size, $data, $expected) { - $this->assertSame($expected, $this->getBinaryRead($data)->readIntBeBySize($size)); + self::assertSame($expected, self::getBinaryRead($data)->readIntBeBySize($size)); } /** @@ -117,7 +117,7 @@ public function shouldReadIntBeBySize($size, $data, $expected) */ public function shouldThrowErrorOnReadIntBeBySizeNotSupported() { - $this->getBinaryRead('')->readIntBeBySize(666); + self::getBinaryRead('')->readIntBeBySize(666); } /** @@ -126,7 +126,7 @@ public function shouldThrowErrorOnReadIntBeBySizeNotSupported() public function shouldReadInt16() { $expected = 1000; - $this->assertSame($expected, $this->getBinaryRead(pack('s', $expected))->readInt16()); + self::assertSame($expected, self::getBinaryRead(pack('s', $expected))->readInt16()); } /** @@ -134,20 +134,20 @@ public function shouldReadInt16() */ public function shouldUnreadAdvance() { - $binaryDataReader = $this->getBinaryRead('123'); + $binaryDataReader = self::getBinaryRead('123'); - $this->assertAttributeEquals('123', 'binaryData', $binaryDataReader); - $this->assertAttributeEquals(0, 'readBytes', $binaryDataReader); + self::assertAttributeEquals('123', 'binaryData', $binaryDataReader); + self::assertAttributeEquals(0, 'readBytes', $binaryDataReader); $binaryDataReader->advance(2); - $this->assertAttributeEquals('3', 'binaryData', $binaryDataReader); - $this->assertAttributeEquals(2, 'readBytes', $binaryDataReader); + self::assertAttributeEquals('3', 'binaryData', $binaryDataReader); + self::assertAttributeEquals(2, 'readBytes', $binaryDataReader); $binaryDataReader->unread('12'); - $this->assertAttributeEquals('123', 'binaryData', $binaryDataReader); - $this->assertAttributeEquals(0, 'readBytes', $binaryDataReader); + self::assertAttributeEquals('123', 'binaryData', $binaryDataReader); + self::assertAttributeEquals(0, 'readBytes', $binaryDataReader); } /** @@ -155,7 +155,7 @@ public function shouldUnreadAdvance() */ public function shouldReadInt24() { - $this->assertSame(-6513508, $this->getBinaryRead(pack('C3', -100, -100, -100))->readInt24()); + self::assertSame(-6513508, self::getBinaryRead(pack('C3', -100, -100, -100))->readInt24()); } /** @@ -163,7 +163,7 @@ public function shouldReadInt24() */ public function shouldReadInt64() { - $this->assertSame('-72057589759737856', $this->getBinaryRead(pack('VV', 4278190080, 4278190080))->readInt64()); + self::assertSame('-72057589759737856', self::getBinaryRead(pack('VV', 4278190080, 4278190080))->readInt64()); } /** @@ -172,7 +172,7 @@ public function shouldReadInt64() public function shouldReadLengthCodedPascalString() { $expected = 255; - $this->assertSame($expected, hexdec(bin2hex($this->getBinaryRead(pack('cc', 1, $expected))->readLengthCodedPascalString(1)))); + self::assertSame($expected, hexdec(bin2hex(self::getBinaryRead(pack('cc', 1, $expected))->readLengthCodedPascalString(1)))); } /** @@ -181,7 +181,7 @@ public function shouldReadLengthCodedPascalString() public function shouldReadInt32() { $expected = 777333; - $this->assertSame($expected, $this->getBinaryRead(pack('i', $expected))->readInt32()); + self::assertSame($expected, self::getBinaryRead(pack('i', $expected))->readInt32()); } @@ -191,7 +191,7 @@ public function shouldReadInt32() public function shouldReadFloat() { $expected = 0.001; - $this->assertSame($expected, $this->getBinaryRead(pack('f', $expected))->readFloat()); + self::assertSame($expected, self::getBinaryRead(pack('f', $expected))->readFloat()); } /** @@ -200,7 +200,7 @@ public function shouldReadFloat() public function shouldReadDouble() { $expected = 1321312312.143567586; - $this->assertSame($expected, $this->getBinaryRead(pack('d', $expected))->readDouble()); + self::assertSame($expected, self::getBinaryRead(pack('d', $expected))->readDouble()); } /** @@ -208,7 +208,7 @@ public function shouldReadDouble() */ public function shouldReadTableId() { - $this->assertSame('7456176998088', $this->getBinaryRead(pack('v3', 2570258120, 2570258120, 2570258120))->readTableId()); + self::assertSame('7456176998088', self::getBinaryRead(pack('v3', 2570258120, 2570258120, 2570258120))->readTableId()); } /** @@ -216,11 +216,19 @@ public function shouldReadTableId() */ public function shouldCheckIsCompleted() { - $this->assertFalse($this->getBinaryRead('')->isComplete(1)); + self::assertFalse(self::getBinaryRead('')->isComplete(1)); - $r = $this->getBinaryRead(str_repeat('-', 30)); + $r = self::getBinaryRead(str_repeat('-', 30)); $r->advance(20); - $this->assertTrue($r->isComplete(1)); + self::assertTrue($r->isComplete(1)); } + /** + * @test + */ + public function shouldPack64bit() + { + $expected = '9223372036854775807'; + self::assertSame($expected, self::getBinaryRead(BinaryDataReader::pack64bit($expected))->readInt64()); + } } \ No newline at end of file diff --git a/tests/Unit/Event/RowEvent/RowEventTest.php b/tests/Unit/Event/RowEvent/RowEventTest.php new file mode 100755 index 0000000..2f7002d --- /dev/null +++ b/tests/Unit/Event/RowEvent/RowEventTest.php @@ -0,0 +1,71 @@ +config = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); + $this->mySQLRepository = $this->getMockBuilder(MySQLRepository::class)->disableOriginalConstructor()->getMock(); + $this->binaryDataReader = $this->getMockBuilder(BinaryDataReader::class)->disableOriginalConstructor()->getMock(); + $this->eventInfo = $this->getMockBuilder(EventInfo::class)->disableOriginalConstructor()->getMock(); + $this->jsonBinaryDecoderFactory = $this->getMockBuilder(JsonBinaryDecoderFactory::class)->disableOriginalConstructor()->getMock(); + + $this->rowEvent = new RowEvent( + $this->config, + $this->mySQLRepository, + $this->binaryDataReader, + $this->eventInfo, + $this->jsonBinaryDecoderFactory + ); + } + + /** + * @test + */ + public function shouldMakeUpdateRowsDTO() + { + + } +} \ No newline at end of file diff --git a/tests/Unit/Repository/MySQLRepositoryTest.php b/tests/Unit/Repository/MySQLRepositoryTest.php new file mode 100755 index 0000000..5c1d368 --- /dev/null +++ b/tests/Unit/Repository/MySQLRepositoryTest.php @@ -0,0 +1,110 @@ +connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock(); + $this->mySQLRepositoryTest = new MySQLRepository($this->connection); + } + + /** + * @test + */ + public function shouldGetFields() + { + $expected = [ + 'COLUMN_NAME' => 'cname', + 'COLLATION_NAME' => 'colname', + 'CHARACTER_SET_NAME' => 'charname', + 'COLUMN_COMMENT' => 'colcommnet', + 'COLUMN_TYPE' => 'coltype', + 'COLUMN_KEY' => 'colkey' + ]; + + $this->connection->method('fetchAll')->willReturn($expected); + + self::assertEquals($expected,$this->mySQLRepositoryTest->getFields('foo', 'bar')); + } + + /** + * @test + */ + public function shouldIsCheckSum() + { + self::assertFalse($this->mySQLRepositoryTest->isCheckSum()); + + $this->connection->method('fetchAssoc')->willReturn(['Value' => 'CRC32']); + self::assertTrue($this->mySQLRepositoryTest->isCheckSum()); + } + + /** + * @test + */ + public function shouldGetVersion() + { + $expected = [ + ['Value' => 'foo'], + ['Value' => 'bar'], + ['Value' => '123'], + ]; + + $this->connection->method('fetchAll')->willReturn($expected); + + self::assertEquals('foobar123',$this->mySQLRepositoryTest->getVersion()); + } + + /** + * @test + */ + public function shouldGetMasterStatus() + { + $expected = [ + 'File' => 'mysql-bin.000002', + 'Position' => 4587305, + 'Binlog_Do_DB' => '', + 'Binlog_Ignore_DB' => '', + 'Executed_Gtid_Set' => '041de05f-a36a-11e6-bc73-000c2976f3f3:1-8023', + ]; + + $this->connection->method('fetchAssoc')->willReturn($expected); + + self::assertEquals($expected,$this->mySQLRepositoryTest->getMasterStatus()); + } + + /** + * @test + */ + public function shouldGetConnection() + { + $this->connection->method('ping')->willReturn(false); + self::assertInstanceOf(Connection::class, $this->mySQLRepositoryTest->getConnection()); + } + + /** + * @test + */ + public function shouldDestroy() + { + $this->mySQLRepositoryTest = null; + } +} \ No newline at end of file