diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e800ac65..fc040549 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,12 +16,12 @@ jobs: strategy: matrix: - php: [ '7.3', '7.4', '8.0' ] + php: [ '8.2' ] steps: - name: Checkout uses: actions/checkout@v2 - + - name: Start mysql service run: | echo -e "\n[mysqld]\nserver-id=1\nbinlog_format=row\nlog_bin=/var/log/mysql/mysql-bin.log" | sudo tee -a /etc/mysql/my.cnf diff --git a/.gitignore b/.gitignore index bcc3a2ed..8a895654 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ composer.phar composer.lock .php_cs.cache /example/profiler.php -.idea/ \ No newline at end of file +.idea/ +.cache/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 720f22a0..00000000 --- a/.travis.yml +++ /dev/null @@ -1,88 +0,0 @@ -dist: xenial - -language: php - -cache: - bundler: true - directories: - - $HOME/.composer/cache - -sudo: required - -services: - - docker - -matrix: - include: - - env: - - DB=mariadb:5.5 - php: "7.3" - - env: - - DB=mariadb:10.0 - php: "7.3" - - env: - - DB=mariadb:10.1 - php: "7.3" - - env: - - DB=mysql:5.5 - php: "7.3" - - env: - - DB=mysql:5.6 - php: "7.3" - - env: - - DB=mysql:5.7 - php: "7.3" - - env: - - DB=mysql:8.0 - - TEST_AUTH=yes - php: "7.3" - - env: - - DB=mariadb:5.5 - php: "7.4" - - env: - - DB=mariadb:10.0 - php: "7.4" - - env: - - DB=mariadb:10.1 - php: "7.4" - - env: - - DB=mysql:5.5 - php: "7.4" - - env: - - DB=mysql:5.6 - php: "7.4" - - env: - - DB=mysql:5.7 - php: "7.4" - - env: - - DB=mysql:8.0 - - TEST_AUTH=yes - php: "7.4" - - env: - - DB=mariadb:5.5 - php: "8.0" - - env: - - DB=mariadb:10.0 - php: "8.0" - - env: - - DB=mariadb:10.1 - php: "8.0" - - env: - - DB=mysql:5.5 - php: "8.0" - - env: - - DB=mysql:5.6 - php: "8.0" - - env: - - DB=mysql:5.7 - php: "8.0" - - env: - - DB=mysql:8.0 - - TEST_AUTH=yes - php: "8.0" - -before_script: - - ./.travis/initializedb.sh - -install: - travis_retry composer install --no-interaction --prefer-source; diff --git a/.travis/initializedb.sh b/.travis/initializedb.sh deleted file mode 100755 index c7fab8de..00000000 --- a/.travis/initializedb.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -set -ex - -if [ $DB == 'mysql:8.0' ]; then - docker run -p 0.0.0.0:3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_ROOT_HOST=% --name=mysql -d ${DB} \ - mysqld \ - --datadir=/var/lib/mysql \ - --user=mysql \ - --server-id=1 \ - --log-bin=/var/lib/mysql/mysql-bin.log \ - --binlog-format=row \ - --max_allowed_packet=64M \ - --default_authentication_plugin=mysql_native_password -else - docker run -p 0.0.0.0:3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_ROOT_HOST=% --name=mysql -d ${DB} \ - mysqld \ - --datadir=/var/lib/mysql \ - --user=mysql \ - --server-id=1 \ - --log-bin=/var/lib/mysql/mysql-bin.log \ - --binlog-format=row \ - --max_allowed_packet=64M -fi - -mysql() { - docker exec mysql mysql -proot "${@}" -} -while : -do - sleep 3 - mysql --protocol=tcp -e 'select version()' && break -done - -docker logs mysql - diff --git a/CHANGELOG.md b/CHANGELOG.md index b326daa9..e5cc6b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Release Notes +## v8.0.0 (2024-01-29) + +- Change: drop support for < 8.2 +- Change: moved to enums, promoted properties +- Added: logger for more socket info +- Added: slave_uuid support (#99) +- Change: EventInfo->id is now EventInfo->serverId (#83) +- Change: config no longer static (#94) +- Chore: typos in README/code +- Chore: replace/remove old dead doc urls from code +- Chore: changed variables to underscore +- Added: support caching_sha2_password (#102) +- Change: BinLogServerInfo static calls removed also added method getServerInfo to MySQLReplicationFactory +- Change: type of bin log position is now string as it can be bigger then php can hande 2^64-1 (#84) + ## v7.0.1 (2021-03-09) + - Fixed negative number handling (#80) ## v7.0.0 (2021-01-24) diff --git a/README.md b/README.md index 110a4d0e..20d212e3 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ 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) +[![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) [![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) [![Code Coverage](https://scrutinizer-ci.com/g/krowinski/php-mysql-replication/badges/coverage.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. +Pure PHP Implementation of MySQL replication protocol. This allows you to receive event like insert, update, delete with their data and raw SQL queries. Based on a great work of creators:https://github.com/noplay/python-mysql-replication and https://github.com/fengxiangyun/mysql-replication @@ -29,11 +27,16 @@ composer install -o Compatibility (based on integration tests) ========= +PHP +- php 8.2 +- php 8.3 + +MYSQL - mysql 5.5 - mysql 5.6 - mysql 5.7 - - mysql 8.0 (ONLY with mysql_native_password) + - mysql 8.0 (mysql_native_password and caching_sha2_password supported) - mariadb 5.5 - mariadb 10.0 - mariadb 10.1 @@ -83,7 +86,7 @@ Available options: 'mariaDbGtid' - MariaDB GTID marker(s) to start from (format 1-1-3,0-1-88) -'slaveId' - script slave id for identification (SHOW SLAVE HOSTS) +'slaveId' - script slave id for identification (default: 666) (SHOW SLAVE HOSTS) 'binLogFileName' - bin log file name to start from @@ -103,6 +106,8 @@ Available options: 'heartbeatPeriod' - sets the interval in seconds between replication heartbeats. Whenever the master's binary log is updated with an event, the waiting period for the next heartbeat is reset. interval is a decimal value having the range 0 to 4294967 seconds and a resolution in milliseconds; the smallest nonzero value is 0.001. Heartbeats are sent by the master only if there are no unsent events in the binary log file for a period longer than interval. +'saveUuid' - sets slave uuid for identification (default: 0015d2b6-8a06-4e5e-8c07-206ef3fbd274) + Similar projects ========= Ruby: https://github.com/y310/kodama @@ -394,11 +399,19 @@ FAQ ========= 1. ### Why and when need php-mysql-replication ? - Well first of all mysql don't give you async calls. You usually need to program this in your application (by event dispaching and adding to some queue system and if your db have many point of entry like web, backend other microservices its not always cheap to add processing to all of them. But using mysql replication protocol you can lisen on write events and process then asynchronously (best combo it's to add item to some queue system like rabbitmq, redis or kafka). Also in invalidate cache, search engine replication, real time analytics and audits. -2. ### It's awsome ! but what is the catch ? - Well first of all you need to know that a lot of events may come through, like if you update 1 000 000 records in table "bar" and you need this one insert from your table "foo" Then all must be processed by script and you need to wait for your data. This is normal and this how it's work. You can speed up using [config options](https://github.com/krowinski/php-mysql-replication#configuration). -Also if script crashes you need to save from time to time position form binlog (or gtid) to start from this position when you run this script again to avoid duplicates. +Well first of all MYSQL don't give you async calls. You usually need to program this in your application (by event dispatching and adding to some queue system +and if your db have many point of entry like web, backend other microservices its not always cheap to add processing to all of them. But using mysql replication +protocol you can listen on write events and process then asynchronously (the best combo it's to add item to some queue system like rabbitmq, redis or kafka). +Also in invalidate cache, search engine replication, real time analytics and audits. + +2. ### It's awesome ! but what is the catch ? + +Well first of all you need to know that a lot of events may come through, like if you update 1 000 000 records in table "bar" and you need this one insert from +your table "foo" Then all must be processed by script, and you need to wait for your data. This is normal and this how it's work. You can speed up +using [config options](https://github.com/krowinski/php-mysql-replication#configuration). +Also, if script crashes you need to save from time to time position form binlog (or gtid) to start from this position when you run this script again to avoid +duplicates. 3. ### I need to process 1 000 000 records and its taking forever!! Like I mention in 1 point use queue system like rabbitmq, redis or kafka, they will give you ability to process data in multiple scripts. @@ -406,15 +419,22 @@ Also if script crashes you need to save from time to time position form binlog ( 4. ### I have a problem ? you script is missing something ! I have found a bug ! Create an [issue](https://github.com/krowinski/php-mysql-replication/issues) I will try to work on it in my free time :) -5. ### How much its give overhead to mysql server ? - It work like any other mysql in slave mode and its giving same overhead. +5. ### How much its give overhead to MYSQL server ? + +It work like any other MYSQL in slave mode and its giving same overhead. 6. ### Socket timeouts error - To fix this best is to increase db configurations "net_read_timeout" and "net_write_timeout" to 3600. (tx Bijimon) + +To fix this best is to increase db configurations ```net_read_timeout``` and ```net_write_timeout``` to 3600. (tx Bijimon) 7. ### Partial updates fix - Set in my.conf ```binlog_row_image=full``` to fix reciving only partial updates. - + +Set in my.conf ```binlog_row_image=full``` to fix receiving only partial updates. + 8. ### No replication events when connected to replica server - Set in my.conf ```log_slave_updates=on``` to fix this (#71)(#66) +Set in my.conf ```log_slave_updates=on``` to fix this (#71)(#66) +9. ### "Big" updates / inserts +Default MYSQL setting generates one big blob of stream this require more RAM/CPU you can change this for smaller stream using +variable ```binlog_row_event_max_size``` [https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_row_event_max_size] to +split into smaller chunks diff --git a/composer.json b/composer.json index 2e7cc69f..81968e59 100644 --- a/composer.json +++ b/composer.json @@ -12,18 +12,23 @@ ], "type": "library", "require": { - "php": ">=7.3", + "php": ">=8.2", "ext-bcmath": "*", "ext-json": "*", "ext-sockets": "*", - "doctrine/collections": "^1.3", - "doctrine/dbal": "^3.0", - "psr/simple-cache": "^1.0", - "symfony/dependency-injection": "^3.1|^4.0|^5.0", - "symfony/event-dispatcher": "^3.1|^4.0|^5.0" + "doctrine/collections": "^2.1", + "doctrine/dbal": "^3.8", + "psr/log": "^3.0", + "psr/simple-cache": "^3.0", + "symfony/dependency-injection": "^7.0", + "symfony/event-dispatcher": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "kubawerlos/php-cs-fixer-custom-fixers": "^3.19", + "monolog/monolog": "^3.5", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "symplify/easy-coding-standard": "^12.1" }, "license": "MIT", "authors": [ @@ -45,5 +50,14 @@ "minimum-stability": "stable", "config": { "sort-packages": true + }, + "scripts": { + "cs:check": "ecs check", + "cs:fix": "ecs check --fix", + "phpstan:analyse": "phpstan analyse -cphpstan.neon", + "sa": [ + "@cs:check", + "@phpstan:analyse" + ] } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..2bc77649 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + replication-test-mysql-percona: + container_name: replication-test-mysql-percona + hostname: replication-test-mysql-percona + image: mysql:8.0 + platform: linux/amd64 + command: [ + '--character-set-server=utf8mb4', + '--collation-server=utf8mb4_unicode_ci', + # '--default-authentication-plugin=caching_sha2_password', + #'--default-authentication-plugin=mysql_native_password', + '--log_bin=binlog', + '--max_binlog_size=8M', + '--binlog_format=row', + '--server-id=1' + ] + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=mysqlreplication_test + ports: + - "3306:3306/tcp" + restart: unless-stopped diff --git a/ecs.php b/ecs.php new file mode 100644 index 00000000..7674cd0f --- /dev/null +++ b/ecs.php @@ -0,0 +1,50 @@ +parallel(); + $ecsConfig->sets([ + SetList::PSR_12, + SetList::CLEAN_CODE, + SetList::STRICT, + SetList::ARRAY, + SetList::PHPUNIT, + SetList::DOCTRINE_ANNOTATIONS, + SetList::COMMENTS, + SetList::SYMPLIFY, + SetList::CONTROL_STRUCTURES, + ]); + + $ecsConfig->rules([ + NativeTypeDeclarationCasingFixer::class, + ReturnToYieldFromFixer::class, + TypeDeclarationSpacesFixer::class, + YieldFromArrayToYieldsFixer::class, + PhpdocToPropertyTypeFixer::class, + PhpdocToParamTypeFixer::class, + PhpdocToReturnTypeFixer::class, + PromotedConstructorPropertyFixer::class, + NoLeadingSlashInGlobalNamespaceFixer::class, + PhpdocNoSuperfluousParamFixer::class, + PhpdocAddMissingParamAnnotationFixer::class, + ]); + + $ecsConfig->fileExtensions(['php']); + $ecsConfig->cacheDirectory('.cache/ecs'); + $ecsConfig->paths([__DIR__ . '/src', __DIR__ . '/tests', __DIR__ . '/example',]); +}; diff --git a/example/benchmark.php b/example/benchmark.php index 34a90292..df528690 100644 --- a/example/benchmark.php +++ b/example/benchmark.php @@ -1,5 +1,9 @@ getConnection(); - $conn->exec('DROP DATABASE IF EXISTS ' . self::DB_NAME); - $conn->exec('CREATE DATABASE ' . self::DB_NAME); - $conn->exec('USE ' . self::DB_NAME); - $conn->exec('CREATE TABLE test (i INT) ENGINE = MEMORY'); - $conn->exec('INSERT INTO test VALUES(1)'); - $conn->exec('CREATE TABLE test2 (i INT) ENGINE = MEMORY'); - $conn->exec('INSERT INTO test2 VALUES(1)'); - $conn->exec('RESET MASTER'); + $conn->executeStatement('DROP DATABASE IF EXISTS ' . self::DB_NAME); + $conn->executeStatement('CREATE DATABASE ' . self::DB_NAME); + $conn->executeStatement('USE ' . self::DB_NAME); + $conn->executeStatement('CREATE TABLE test (i INT) ENGINE = MEMORY'); + $conn->executeStatement('INSERT INTO test VALUES(1)'); + $conn->executeStatement('CREATE TABLE test2 (i INT) ENGINE = MEMORY'); + $conn->executeStatement('INSERT INTO test2 VALUES(1)'); + $conn->executeStatement('RESET MASTER'); $this->binLogStream = new MySQLReplicationFactory( (new ConfigBuilder()) @@ -43,17 +47,23 @@ public function __construct() ->withPassword(self::DB_PASS) ->withHost(self::DB_HOST) ->withPort(self::DB_PORT) - ->withEventsOnly([ConstEventType::UPDATE_ROWS_EVENT_V2]) + ->withEventsOnly( + [ + ConstEventType::UPDATE_ROWS_EVENT_V2->value, + // for mariadb v1 + ConstEventType::UPDATE_ROWS_EVENT_V1->value, + ] + ) ->withSlaveId(9999) ->withDatabasesOnly([self::DB_NAME]) ->build() ); $this->binLogStream->registerSubscriber( - new class extends EventSubscribers - { - private $start; - private $counter = 0; + new class() extends EventSubscribers { + private float $start; + + private int $counter = 0; public function __construct() { @@ -64,27 +74,15 @@ public function onUpdate(UpdateRowsDTO $event): void { ++$this->counter; if (0 === ($this->counter % 1000)) { - echo ((int)($this->counter / (microtime(true) - $this->start)) . ' event by seconds (' . $this->counter . ' total)') . PHP_EOL; + echo ((int)($this->counter / (microtime( + true + ) - $this->start)) . ' event by seconds (' . $this->counter . ' total)') . PHP_EOL; } } } ); } - private function getConnection(): Connection - { - return DriverManager::getConnection( - [ - 'user' => self::DB_USER, - 'password' => self::DB_PASS, - 'host' => self::DB_HOST, - 'port' => self::DB_PORT, - 'driver' => 'pdo_mysql', - 'dbname' => self::DB_NAME - ] - ); - } - public function run(): void { $pid = pcntl_fork(); @@ -100,6 +98,20 @@ public function run(): void } } + private function getConnection(): Connection + { + return DriverManager::getConnection( + [ + 'user' => self::DB_USER, + 'password' => self::DB_PASS, + 'host' => self::DB_HOST, + 'port' => self::DB_PORT, + 'driver' => 'pdo_mysql', + 'dbname' => self::DB_NAME, + ] + ); + } + private function consume(): void { $this->binLogStream->run(); @@ -110,9 +122,11 @@ private function produce(): void $conn = $this->getConnection(); echo 'Start insert data' . PHP_EOL; + + /** @phpstan-ignore-next-line */ while (1) { - $conn->exec('UPDATE test SET i = i + 1;'); - $conn->exec('UPDATE test2 SET i = i + 1;'); + $conn->executeStatement('UPDATE test SET i = i + 1;'); + $conn->executeStatement('UPDATE test2 SET i = i + 1;'); } } } diff --git a/example/dump_events.php b/example/dump_events.php index 08d2172b..e5c6506d 100644 --- a/example/dump_events.php +++ b/example/dump_events.php @@ -1,11 +1,15 @@ withUser('root') - ->withHost('127.0.0.1') + ->withHost('0.0.0.0') ->withPassword('root') ->withPort(3306) - ->withSlaveId(100) - ->withHeartbeatPeriod(2) - ->build() + ->withHeartbeatPeriod(60) + ->withEventsIgnore([ConstEventType::HEARTBEAT_LOG_EVENT->value]) + ->build(), + logger: new Logger('replicator', [new StreamHandler(STDOUT)]) ); /** @@ -31,15 +36,14 @@ * @see EventSubscribers */ $binLogStream->registerSubscriber( - new class() extends EventSubscribers - { + new class() extends EventSubscribers { public function allEvents(EventDTO $event): void { // all events got __toString() implementation - echo $event; + #echo $event; // all events got JsonSerializable implementation - //echo json_encode($event, JSON_PRETTY_PRINT); + echo json_encode($event, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); echo 'Memory usage ' . round(memory_get_usage() / 1048576, 2) . ' MB' . PHP_EOL; } diff --git a/example/resuming.php b/example/resuming.php index eb11c82a..ea252564 100644 --- a/example/resuming.php +++ b/example/resuming.php @@ -1,4 +1,5 @@ build() ); -/** - * Class BenchmarkEventSubscribers - * @package example - */ class MyEventSubscribers extends EventSubscribers { /** @@ -45,38 +42,16 @@ public function allEvents(EventDTO $event): void echo 'Memory usage ' . round(memory_get_usage() / 1048576, 2) . ' MB' . PHP_EOL; // save event for resuming it later - BinLogBootstrap::save($event->getEventInfo()->getBinLogCurrent()); + BinLogBootstrap::save($event->getEventInfo()->binLogCurrent); } } -/** - * Class SaveBinLogPos - * @package example - */ class BinLogBootstrap { - /** - * @var string - */ - private static $fileAndPath; + private static ?string $fileAndPath = null; - /** - * @return string - */ - private static function getFileAndPath(): string - { - if (null === self::$fileAndPath) { - self::$fileAndPath = sys_get_temp_dir() . '/bin-log-replicator-last-position'; - } - return self::$fileAndPath; - } - - /** - * @param BinLogCurrent $binLogCurrent - */ public static function save(BinLogCurrent $binLogCurrent): void { - echo 'saving file:' . $binLogCurrent->getBinFileName() . ', position:' . $binLogCurrent->getBinLogPosition() . ' bin log position' . PHP_EOL; // can be redis/nosql/file - something fast! @@ -85,10 +60,6 @@ public static function save(BinLogCurrent $binLogCurrent): void file_put_contents(self::getFileAndPath(), serialize($binLogCurrent)); } - /** - * @param ConfigBuilder $builder - * @return ConfigBuilder - */ public static function startFromPosition(ConfigBuilder $builder): ConfigBuilder { if (!is_file(self::getFileAndPath())) { @@ -104,6 +75,14 @@ public static function startFromPosition(ConfigBuilder $builder): ConfigBuilder ->withBinLogFileName($binLogCurrent->getBinFileName()) ->withBinLogPosition($binLogCurrent->getBinLogPosition()); } + + private static function getFileAndPath(): string + { + if (self::$fileAndPath === null) { + self::$fileAndPath = sys_get_temp_dir() . '/bin-log-replicator-last-position'; + } + return self::$fileAndPath; + } } // register your events handler here @@ -111,4 +90,3 @@ public static function startFromPosition(ConfigBuilder $builder): ConfigBuilder // start consuming events $binLogStream->run(); - diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..5938aa8e --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 9 + checkMissingIterableValueType: false + tmpDir: .cache/phpstan/ + paths: + - src diff --git a/phpunit.xml b/phpunit.xml index 964dde4e..df383901 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,7 @@ - + ./tests/Integration @@ -16,9 +10,9 @@ ./tests/Unit - - + + src/ - - - \ No newline at end of file + + + diff --git a/src/MySQLReplication/BinLog/BinLogAuthPluginMode.php b/src/MySQLReplication/BinLog/BinLogAuthPluginMode.php new file mode 100644 index 00000000..5ef6d2c4 --- /dev/null +++ b/src/MySQLReplication/BinLog/BinLogAuthPluginMode.php @@ -0,0 +1,11 @@ +binLogPosition; } - public function setBinLogPosition(int $binLogPosition): void + public function setBinLogPosition(string $binLogPosition): void { $this->binLogPosition = $binLogPosition; } @@ -64,8 +56,8 @@ public function setMariaDbGtid(string $mariaDbGtid): void $this->mariaDbGtid = $mariaDbGtid; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/BinLog/BinLogException.php b/src/MySQLReplication/BinLog/BinLogException.php index d99819ff..41718b2b 100644 --- a/src/MySQLReplication/BinLog/BinLogException.php +++ b/src/MySQLReplication/BinLog/BinLogException.php @@ -1,4 +1,5 @@ 0 - $salt_len = ord($data[$i]); + $saltLen = ord($data[$i]); ++$i; - $salt_len = max(12, $salt_len - 9); + $saltLen = max(12, $saltLen - 9); $i += 10; //next salt - if ($length >= $i + $salt_len) { - for ($j = $i; $j < $i + $salt_len; ++$j) { - self::$serverInfo['salt'] .= $data[$j]; + if ($length >= $i + $saltLen) { + for ($j = $i; $j < $i + $saltLen; ++$j) { + $salt .= $data[$j]; } - } - self::$serverInfo['auth_plugin_name'] = ''; - $i += $salt_len + 1; + $authPlugin = ''; + $i += $saltLen + 1; for ($j = $i; $j < $length - 1; ++$j) { - self::$serverInfo['auth_plugin_name'] .= $data[$j]; + $authPlugin .= $data[$j]; } - self::$serverInfo['version_name'] = self::parseVersion($version); - self::$serverInfo['version_revision'] = self::parseRevision($version); + return new self( + $protocolVersion, + $serverVersion, + $connectionId, + $salt, + BinLogAuthPluginMode::tryFrom($authPlugin), + self::parseVersion($serverVersion), + self::parseRevision($version) + ); } - public static function getSalt(): string + public function isMariaDb(): bool { - return self::$serverInfo['salt']; + return $this->versionName === self::MYSQL_VERSION_MARIADB; + } + + public function isPercona(): bool + { + return $this->versionName === self::MYSQL_VERSION_PERCONA; + } + + public function isGeneric(): bool + { + return $this->versionName === self::MYSQL_VERSION_GENERIC; } /** @@ -91,11 +120,11 @@ public static function getSalt(): string */ private static function parseVersion(string $version): string { - if ('' !== $version) { - if (false !== strpos($version, self::MYSQL_VERSION_MARIADB)) { + if ($version !== '') { + if (str_contains($version, self::MYSQL_VERSION_MARIADB)) { return self::MYSQL_VERSION_MARIADB; } - if (false !== strpos($version, self::MYSQL_VERSION_PERCONA)) { + if (str_contains($version, self::MYSQL_VERSION_PERCONA)) { return self::MYSQL_VERSION_PERCONA; } } @@ -103,33 +132,8 @@ private static function parseVersion(string $version): string return self::MYSQL_VERSION_GENERIC; } - public static function getRevision(): float - { - return self::$serverInfo['version_revision']; - } - - public static function getVersion(): string - { - return self::$serverInfo['version_name']; - } - - public static function isMariaDb(): bool - { - return self::MYSQL_VERSION_MARIADB === self::getVersion(); - } - - public static function isPercona(): bool - { - return self::MYSQL_VERSION_PERCONA === self::getVersion(); - } - - public static function isGeneric(): bool - { - return self::MYSQL_VERSION_GENERIC === self::getVersion(); - } - private static function parseRevision(string $version): float { return (float)$version; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/BinLog/BinLogSocketConnect.php b/src/MySQLReplication/BinLog/BinLogSocketConnect.php index 5d47df1e..ccdbabab 100644 --- a/src/MySQLReplication/BinLog/BinLogSocketConnect.php +++ b/src/MySQLReplication/BinLog/BinLogSocketConnect.php @@ -1,77 +1,82 @@ repository = $repository; - $this->socket = $socket; $this->binLogCurrent = new BinLogCurrent(); - $this->socket->connectToStream(Config::getHost(), Config::getPort()); - BinLogServerInfo::parsePackage($this->getResponse(false), $this->repository->getVersion()); + $this->socket->connectToStream($config->host, $config->port); + + $this->logger->info('Connected to ' . $config->host . ':' . $config->port); + + $this->binLogServerInfo = BinLogServerInfo::make( + $this->getResponse(false), + $this->repository->getVersion() + ); + + $this->logger->info( + 'Server version name: ' . $this->binLogServerInfo->versionName . ', revision: ' . $this->binLogServerInfo->versionRevision + ); + $this->authenticate(); $this->getBinlogStream(); } - /** - * @throws BinLogException - * @throws SocketException - */ + public function getBinLogServerInfo(): BinLogServerInfo + { + return $this->binLogServerInfo; + } + public function getResponse(bool $checkResponse = true): string { $header = $this->socket->readFromSocket(4); - if ('' === $header) { + if ($header === '') { return ''; } - $dataLength = unpack('L', $header[0] . $header[1] . $header[2] . chr(0))[1]; + $dataLength = BinaryDataReader::unpack('L', $header[0] . $header[1] . $header[2] . chr(0))[1]; $isMaxDataLength = $dataLength === $this->binaryDataMaxLength; $result = $this->socket->readFromSocket($dataLength); - if (true === $checkResponse) { + if ($checkResponse === true) { $this->isWriteSuccessful($result); } // https://dev.mysql.com/doc/internals/en/sending-more-than-16mbyte.html while ($isMaxDataLength) { $header = $this->socket->readFromSocket(4); - if ('' === $header) { + if ($header === '') { return $result; } - $dataLength = unpack('L', $header[0] . $header[1] . $header[2] . chr(0))[1]; + $dataLength = BinaryDataReader::unpack('L', $header[0] . $header[1] . $header[2] . chr(0))[1]; $isMaxDataLength = $dataLength === $this->binaryDataMaxLength; $next_result = $this->socket->readFromSocket($dataLength); $result .= $next_result; @@ -80,14 +85,21 @@ public function getResponse(bool $checkResponse = true): string return $result; } - /** - * @throws BinLogException - */ + public function getBinLogCurrent(): BinLogCurrent + { + return $this->binLogCurrent; + } + + public function getCheckSum(): bool + { + return $this->checkSum; + } + private function isWriteSuccessful(string $data): void { $head = ord($data[0]); if (!in_array($head, $this->packageOkHeader, true)) { - $errorCode = unpack('v', $data[1] . $data[2])[1]; + $errorCode = BinaryDataReader::unpack('v', $data[1] . $data[2])[1]; $errorMessage = ''; $packetLength = strlen($data); for ($i = 9; $i < $packetLength; ++$i) { @@ -98,34 +110,61 @@ private function isWriteSuccessful(string $data): void } } - /** - * @throws BinLogException - * @throws SocketException - * @link http://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41 - */ private function authenticate(): void { + if ($this->binLogServerInfo->authPlugin === null) { + throw new MySQLReplicationException( + MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED, + MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED_CODE + ); + } + + $this->logger->info( + 'Trying to authenticate user: ' . $this->config->user . ' using ' . $this->binLogServerInfo->authPlugin->value . ' plugin' + ); + $data = pack('L', self::getCapabilities()); $data .= pack('L', $this->binaryDataMaxLength); $data .= chr(33); - for ($i = 0; $i < 23; ++$i) { - $data .= chr(0); + $data .= str_repeat(chr(0), 23); + $data .= $this->config->user . chr(0); + + $auth = ''; + if ($this->binLogServerInfo->authPlugin === BinLogAuthPluginMode::MysqlNativePassword) { + $auth = $this->authenticateMysqlNativePasswordPlugin(); + } elseif ($this->binLogServerInfo->authPlugin === BinLogAuthPluginMode::CachingSha2Password) { + $auth = $this->authenticateCachingSha2PasswordPlugin(); } - $result = sha1(Config::getPassword(), true) ^ sha1( - BinLogServerInfo::getSalt() . sha1(sha1(Config::getPassword(), true), true), true - ); - $data = $data . Config::getUser() . chr(0) . chr(strlen($result)) . $result; + $data .= chr(strlen($auth)) . $auth; + $data .= $this->binLogServerInfo->authPlugin->value . chr(0); $str = pack('L', strlen($data)); $s = $str[0] . $str[1] . $str[2]; $data = $s . chr(1) . $data; $this->socket->writeToSocket($data); $this->getResponse(); + + $this->logger->info('User authenticated'); + } + + private function authenticateCachingSha2PasswordPlugin(): string + { + $hash1 = hash('sha256', $this->config->password, true); + $hash2 = hash('sha256', $hash1, true); + $hash3 = hash('sha256', $hash2 . $this->binLogServerInfo->salt, true); + return $hash1 ^ $hash3; + } + + private function authenticateMysqlNativePasswordPlugin(): string + { + $hash1 = sha1($this->config->password, true); + $hash2 = sha1($this->binLogServerInfo->salt . sha1(sha1($this->config->password, true), true), true); + return $hash1 ^ $hash2; } /** - * http://dev.mysql.com/doc/internals/en/capability-flags.html#packet-protocol::capabilityflags + * https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html * https://github.com/siddontang/mixer/blob/master/doc/protocol.txt */ private static function getCapabilities(): int @@ -136,44 +175,46 @@ private static function getCapabilities(): int $transactions = 1 << 13; $secureConnection = 1 << 15; $protocol41 = 1 << 9; + $authPlugin = 1 << 19; - return ($longPassword | $longFlag | $transactions | $protocol41 | $secureConnection | $noSchema); + return $longPassword | $longFlag | $transactions | $protocol41 | $secureConnection | $noSchema | $authPlugin; } - /** - * @throws BinLogException - * @throws GtidException - * @throws SocketException - */ private function getBinlogStream(): void { $this->checkSum = $this->repository->isCheckSum(); if ($this->checkSum) { - $this->execute('SET @master_binlog_checksum = @@global.binlog_checksum'); + $this->executeSQL('SET @master_binlog_checksum = @@global.binlog_checksum'); } - if (0 !== Config::getHeartbeatPeriod()) { + if ($this->config->heartbeatPeriod > 0.00) { // master_heartbeat_period is in nanoseconds - $this->execute('SET @master_heartbeat_period = ' . Config::getHeartbeatPeriod() * 1000000000); + $this->executeSQL('SET @master_heartbeat_period = ' . $this->config->heartbeatPeriod * 1000000000); + + $this->logger->info('Heartbeat period set to ' . $this->config->heartbeatPeriod . ' seconds'); + } + + if ($this->config->slaveUuid !== '') { + $this->executeSQL( + 'SET @slave_uuid = \'' . $this->config->slaveUuid . '\', @replica_uuid = \'' . $this->config->slaveUuid . '\'' + ); + + $this->logger->info('Salve uuid set to ' . $this->config->slaveUuid); } $this->registerSlave(); - if ('' !== Config::getMariaDbGtid()) { + if ($this->config->mariaDbGtid !== '') { $this->setBinLogDumpMariaGtid(); } - if ('' !== Config::getGtid()) { + if ($this->config->gtid !== '') { $this->setBinLogDumpGtid(); } else { $this->setBinLogDump(); } } - /** - * @throws BinLogException - * @throws SocketException - */ - private function execute(string $sql): void + private function executeSQL(string $sql): void { $this->socket->writeToSocket(pack('LC', strlen($sql) + 1, 0x03) . $sql); $this->getResponse(); @@ -181,60 +222,52 @@ private function execute(string $sql): void /** * @see https://dev.mysql.com/doc/internals/en/com-register-slave.html - * @throws BinLogException - * @throws SocketException */ private function registerSlave(): void { - $host = gethostname(); + $host = (string)gethostname(); $hostLength = strlen($host); - $userLength = strlen(Config::getUser()); - $passLength = strlen(Config::getPassword()); + $userLength = strlen($this->config->user); + $passLength = strlen($this->config->password); $data = pack('l', 18 + $hostLength + $userLength + $passLength); $data .= chr(self::COM_REGISTER_SLAVE); - $data .= pack('V', Config::getSlaveId()); + $data .= pack('V', $this->config->slaveId); $data .= pack('C', $hostLength); $data .= $host; $data .= pack('C', $userLength); - $data .= Config::getUser(); + $data .= $this->config->user; $data .= pack('C', $passLength); - $data .= Config::getPassword(); - $data .= pack('v', Config::getPort()); + $data .= $this->config->password; + $data .= pack('v', $this->config->port); $data .= pack('V', 0); $data .= pack('V', 0); $this->socket->writeToSocket($data); $this->getResponse(); + + $this->logger->info('Slave registered with id ' . $this->config->slaveId); } - /** - * @throws SocketException - * @throws BinLogException - */ private function setBinLogDumpMariaGtid(): void { - $this->execute('SET @mariadb_slave_capability = 4'); - $this->execute('SET @slave_connect_state = \'' . Config::getMariaDbGtid() . '\''); - $this->execute('SET @slave_gtid_strict_mode = 0'); - $this->execute('SET @slave_gtid_ignore_duplicates = 0'); + $this->executeSQL('SET @mariadb_slave_capability = 4'); + $this->executeSQL('SET @slave_connect_state = \'' . $this->config->mariaDbGtid . '\''); + $this->executeSQL('SET @slave_gtid_strict_mode = 0'); + $this->executeSQL('SET @slave_gtid_ignore_duplicates = 0'); + + $this->binLogCurrent->setMariaDbGtid($this->config->mariaDbGtid); - $this->binLogCurrent->setMariaDbGtid(Config::getMariaDbGtid()); + $this->logger->info('Set Maria GTID to start from: ' . $this->config->mariaDbGtid); } - /** - * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html - * @throws BinLogException - * @throws GtidException - * @throws SocketException - */ private function setBinLogDumpGtid(): void { - $collection = GtidCollection::makeCollectionFromString(Config::getGtid()); + $collection = GtidCollection::makeCollectionFromString($this->config->gtid); $data = pack('l', 26 + $collection->getEncodedLength()) . chr(self::COM_BINLOG_DUMP_GTID); $data .= pack('S', 0); - $data .= pack('I', Config::getSlaveId()); + $data .= pack('I', $this->config->slaveId); $data .= pack('I', 3); $data .= chr(0); $data .= chr(0); @@ -246,28 +279,33 @@ private function setBinLogDumpGtid(): void $this->socket->writeToSocket($data); $this->getResponse(); - $this->binLogCurrent->setGtid(Config::getGtid()); + $this->binLogCurrent->setGtid($this->config->gtid); + + $this->logger->info('Set GTID to start from: ' . $this->config->gtid); } /** - * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump.html - * @throws BinLogException - * @throws SocketException + * 1 [12] COM_BINLOG_DUMP + * 4 binlog-pos + * 2 flags + * 4 server-id + * string[EOF] binlog-filename */ private function setBinLogDump(): void { - $binFilePos = Config::getBinLogPosition(); - $binFileName = Config::getBinLogFileName(); - if (0 === $binFilePos && '' === $binFileName) { + $binFilePos = $this->config->binLogPosition; + $binFileName = $this->config->binLogFileName; + // if not set start from newest binlog + if ($binFilePos === '' && $binFileName === '') { $masterStatusDTO = $this->repository->getMasterStatus(); - $binFilePos = $masterStatusDTO->getPosition(); - $binFileName = $masterStatusDTO->getFile(); + $binFilePos = $masterStatusDTO->position; + $binFileName = $masterStatusDTO->file; } $data = pack('i', strlen($binFileName) + 11) . chr(self::COM_BINLOG_DUMP); $data .= pack('I', $binFilePos); $data .= pack('v', 0); - $data .= pack('I', Config::getSlaveId()); + $data .= pack('I', $this->config->slaveId); $data .= $binFileName; $this->socket->writeToSocket($data); @@ -275,15 +313,7 @@ private function setBinLogDump(): void $this->binLogCurrent->setBinLogPosition($binFilePos); $this->binLogCurrent->setBinFileName($binFileName); - } - public function getBinLogCurrent(): BinLogCurrent - { - return $this->binLogCurrent; - } - - public function getCheckSum(): bool - { - return $this->checkSum; + $this->logger->info('Set binlog to start from: ' . $binFileName . ':' . $binFilePos); } } diff --git a/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php b/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php index c0066eaa..d854dd1f 100644 --- a/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php +++ b/src/MySQLReplication/BinaryDataReader/BinaryDataReader.php @@ -1,4 +1,5 @@ data = $data; + public function __construct( + private string $binaryData + ) { } public static function pack64bit(int $value): string { 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 + '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 ); } @@ -48,14 +65,14 @@ public function advance(int $length): void public function readInt16(): int { - return unpack('s', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; + return self::unpack('s', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; } public function read(int $length): string { - $return = substr($this->data, 0, $length); + $return = substr($this->binaryData, 0, $length); $this->readBytes += $length; - $this->data = substr($this->data, $length); + $this->binaryData = substr($this->binaryData, $length); return $return; } @@ -63,12 +80,9 @@ public function read(int $length): string public function unread(string $data): void { $this->readBytes -= strlen($data); - $this->data = $data . $this->data; + $this->binaryData = $data . $this->binaryData; } - /** - * @throws BinaryDataReaderException - */ public function readCodedBinary(): ?int { $c = ord($this->read(self::UNSIGNED_CHAR_LENGTH)); @@ -90,12 +104,12 @@ public function readCodedBinary(): ?int public function readUInt16(): int { - return unpack('v', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; + return self::unpack('v', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; } public function readUInt24(): int { - $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH)); + $data = self::unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH)); return $data[1] + ($data[2] << 8) + ($data[3] << 16); } @@ -107,14 +121,14 @@ public function readUInt64(): string public function unpackUInt64(string $binary): string { - $data = unpack('V*', $binary); + $data = self::unpack('V*', $binary); return bcadd((string)$data[1], bcmul((string)$data[2], bcpow('2', '32'))); } public function readInt24(): int { - $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH)); + $data = self::unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH)); $res = $data[1] | ($data[2] << 8) | ($data[3] << 16); if ($res >= 0x800000) { @@ -126,22 +140,16 @@ public function readInt24(): int public function readInt64(): string { - $data = unpack('V*', $this->read(self::UNSIGNED_INT64_LENGTH)); + $data = self::unpack('V*', $this->read(self::UNSIGNED_INT64_LENGTH)); return bcadd((string)$data[1], (string)($data[2] << 32)); } - /** - * @throws BinaryDataReaderException - */ public function readLengthString(int $size): string { return $this->read($this->readUIntBySize($size)); } - /** - * @throws BinaryDataReaderException - */ public function readUIntBySize(int $size): int { if ($size === self::UNSIGNED_CHAR_LENGTH) { @@ -171,41 +179,38 @@ public function readUIntBySize(int $size): int public function readUInt8(): int { - return unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; + return self::unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; } public function readUInt32(): int { - return unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + return self::unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; } public function readUInt40(): int { - $data1 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; - $data2 = unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + $data1 = self::unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; + $data2 = self::unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; return $data1 + ($data2 << 8); } public function readUInt48(): int { - $data = unpack('v3', $this->read(self::UNSIGNED_INT48_LENGTH)); + $data = self::unpack('v3', $this->read(self::UNSIGNED_INT48_LENGTH)); return $data[1] + ($data[2] << 16) + ($data[3] << 32); } public function readUInt56(): int { - $data1 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; - $data2 = unpack('S', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; - $data3 = unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + $data1 = self::unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; + $data2 = self::unpack('S', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; + $data3 = self::unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; return $data1 + ($data2 << 8) + ($data3 << 24); } - /** - * @throws BinaryDataReaderException - */ public function readIntBeBySize(int $size): int { if ($size === self::UNSIGNED_CHAR_LENGTH) { @@ -229,50 +234,54 @@ public function readIntBeBySize(int $size): int public function readInt8(): int { - $re = unpack('c', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; + $re = self::unpack('c', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; + return $re >= 0x80 ? $re - 0x100 : $re; } public function readInt16Be(): int { - $re = unpack('n', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; + $re = self::unpack('n', $this->read(self::UNSIGNED_SHORT_LENGTH))[1]; + return $re >= 0x8000 ? $re - 0x10000 : $re; } public function readInt24Be(): int { - $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH)); + $data = self::unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH)); $re = ($data[1] << 16) | ($data[2] << 8) | $data[3]; + return $re >= 0x800000 ? $re - 0x1000000 : $re; } public function readInt32Be(): int { - $re = unpack('N', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + $re = self::unpack('N', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + return $re >= 0x80000000 ? $re - 0x100000000 : $re; } public function readInt40Be(): int { - $data1 = unpack('N', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; - $data2 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; + $data1 = self::unpack('N', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + $data2 = self::unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1]; return $data2 + ($data1 << 8); } public function readInt32(): int { - return unpack('i', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; + return self::unpack('i', $this->read(self::UNSIGNED_INT32_LENGTH))[1]; } public function readFloat(): float { - return unpack('f', $this->read(self::UNSIGNED_FLOAT_LENGTH))[1]; + return self::unpack('f', $this->read(self::UNSIGNED_FLOAT_LENGTH))[1]; } public function readDouble(): float { - return unpack('d', $this->read(self::UNSIGNED_DOUBLE_LENGTH))[1]; + return self::unpack('d', $this->read(self::UNSIGNED_DOUBLE_LENGTH))[1]; } public function readTableId(): string @@ -287,12 +296,12 @@ public function isComplete(int $size): bool public function getBinaryDataLength(): int { - return strlen($this->data); + return strlen($this->binaryData); } - public function getData(): string + public function getBinaryData(): string { - return $this->data; + return $this->binaryData; } public function getBinarySlice(int $binary, int $start, int $size, int $binaryLength): int @@ -307,4 +316,13 @@ public function getReadBytes(): int { return $this->readBytes; } -} \ No newline at end of file + + public static function unpack(string $format, string $string): array + { + $unpacked = unpack($format, $string); + if ($unpacked) { + return $unpacked; + } + return []; + } +} diff --git a/src/MySQLReplication/BinaryDataReader/BinaryDataReaderException.php b/src/MySQLReplication/BinaryDataReader/BinaryDataReaderException.php index 5f84394b..6f7fd464 100644 --- a/src/MySQLReplication/BinaryDataReader/BinaryDataReaderException.php +++ b/src/MySQLReplication/BinaryDataReader/BinaryDataReaderException.php @@ -1,4 +1,5 @@ has($key) ? self::$tableMapCache[$key] : $default; } - /** - * @inheritDoc - */ public function has($key): bool { return isset(self::$tableMapCache[$key]); } - /** - * @inheritDoc - */ public function clear(): bool { self::$tableMapCache = []; @@ -36,10 +33,7 @@ public function clear(): bool return true; } - /** - * @inheritDoc - */ - public function getMultiple($keys, $default = null) + public function getMultiple(iterable $keys, mixed $default = null): iterable { $data = []; foreach ($keys as $key) { @@ -48,13 +42,10 @@ public function getMultiple($keys, $default = null) } } - return [] !== $data ? $data : $default; + return $data !== [] ? $data : (array)$default; } - /** - * @inheritDoc - */ - public function setMultiple($values, $ttl = null): bool + public function setMultiple(iterable $values, null|int|DateInterval $ttl = null): bool { foreach ($values as $key => $value) { $this->set($key, $value); @@ -63,19 +54,11 @@ public function setMultiple($values, $ttl = null): bool return true; } - /** - * @inheritDoc - */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, null|int|DateInterval $ttl = null): bool { // automatically clear table cache to save memory - if (count(self::$tableMapCache) > Config::getTableCacheSize()) { - self::$tableMapCache = array_slice( - self::$tableMapCache, - (int)(Config::getTableCacheSize() / 2), - null, - true - ); + if (count(self::$tableMapCache) > $this->tableCacheSize) { + self::$tableMapCache = array_slice(self::$tableMapCache, (int)($this->tableCacheSize / 2), null, true); } self::$tableMapCache[$key] = $value; @@ -83,10 +66,7 @@ public function set($key, $value, $ttl = null): bool return true; } - /** - * @inheritDoc - */ - public function deleteMultiple($keys): bool + public function deleteMultiple(iterable $keys): bool { foreach ($keys as $key) { $this->delete($key); @@ -95,13 +75,10 @@ public function deleteMultiple($keys): bool return true; } - /** - * @inheritDoc - */ - public function delete($key): bool + public function delete(string $key): bool { unset(self::$tableMapCache[$key]); return true; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Config/Config.php b/src/MySQLReplication/Config/Config.php index 251baaef..e37e65f3 100644 --- a/src/MySQLReplication/Config/Config.php +++ b/src/MySQLReplication/Config/Config.php @@ -1,227 +1,132 @@ host)) { + $ip = gethostbyname($this->host); + if (filter_var($ip, FILTER_VALIDATE_IP) === false) { throw new ConfigException(ConfigException::IP_ERROR_MESSAGE, ConfigException::IP_ERROR_CODE); } } - if (!empty(self::$port) && false === filter_var( - self::$port, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]] - )) { + if (!empty($this->port) && filter_var( + $this->port, + FILTER_VALIDATE_INT, + [ + 'options' => [ + 'min_range' => 0, + ], + ] + ) === false) { throw new ConfigException(ConfigException::PORT_ERROR_MESSAGE, ConfigException::PORT_ERROR_CODE); } - if (!empty(self::$gtid)) { - foreach (explode(',', self::$gtid) as $gtid) { + if (!empty($this->gtid)) { + foreach (explode(',', $this->gtid) as $gtid) { if (!(bool)preg_match( - '/^([0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12})((?::[0-9-]+)+)$/', $gtid, $matches + '/^([0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12})((?::[0-9-]+)+)$/', + $gtid, + $matches )) { throw new ConfigException(ConfigException::GTID_ERROR_MESSAGE, ConfigException::GTID_ERROR_CODE); } } } - if (!empty(self::$slaveId) && false === filter_var( - self::$slaveId, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]] - )) { - throw new ConfigException(ConfigException::SLAVE_ID_ERROR_MESSAGE, ConfigException::SLAVE_ID_ERROR_CODE); + if (!empty($this->slaveId) && filter_var( + $this->slaveId, + FILTER_VALIDATE_INT, + [ + 'options' => [ + 'min_range' => 0, + ], + ] + ) === false) { + throw new ConfigException( + ConfigException::SLAVE_ID_ERROR_MESSAGE, + ConfigException::SLAVE_ID_ERROR_CODE + ); } - if (false === filter_var(self::$binLogPosition, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { + if (bccomp($this->binLogPosition, '0') === -1) { throw new ConfigException( - ConfigException::BIN_LOG_FILE_POSITION_ERROR_MESSAGE, ConfigException::BIN_LOG_FILE_POSITION_ERROR_CODE + ConfigException::BIN_LOG_FILE_POSITION_ERROR_MESSAGE, + ConfigException::BIN_LOG_FILE_POSITION_ERROR_CODE ); } - if (false === filter_var(self::$tableCacheSize, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { + if (filter_var($this->tableCacheSize, FILTER_VALIDATE_INT, [ + 'options' => [ + 'min_range' => 0, + ], + ]) === false) { throw new ConfigException( - ConfigException::TABLE_CACHE_SIZE_ERROR_MESSAGE, ConfigException::TABLE_CACHE_SIZE_ERROR_CODE + ConfigException::TABLE_CACHE_SIZE_ERROR_MESSAGE, + ConfigException::TABLE_CACHE_SIZE_ERROR_CODE ); } - if (0.0 !== self::$heartbeatPeriod && false === ( - self::$heartbeatPeriod >= 0.001 && self::$heartbeatPeriod <= 4294967.0 - )) { + if ($this->heartbeatPeriod !== 0.0 && false === ( + $this->heartbeatPeriod >= 0.001 && $this->heartbeatPeriod <= 4294967.0 + )) { throw new ConfigException( - ConfigException::HEARTBEAT_PERIOD_ERROR_MESSAGE, ConfigException::HEARTBEAT_PERIOD_ERROR_CODE + ConfigException::HEARTBEAT_PERIOD_ERROR_MESSAGE, + ConfigException::HEARTBEAT_PERIOD_ERROR_CODE ); } } - public static function getCustom(): array - { - return self::$custom; - } - - public static function getUser(): string - { - return self::$user; - } - public static function getHost(): string + public function checkDataBasesOnly(string $database): bool { - return self::$host; + return $this->databasesOnly !== [] && !in_array($database, $this->databasesOnly, true); } - public static function getPort(): int - { - return self::$port; - } - - public static function getPassword(): string - { - return self::$password; - } - public static function getCharset(): string + public function checkTablesOnly(string $table): bool { - return self::$charset; + return $this->tablesOnly !== [] && !in_array($table, $this->tablesOnly, true); } - public static function getGtid(): string + public function checkEvent(int $type): bool { - return self::$gtid; - } - - public static function getMariaDbGtid(): string - { - return self::$mariaDbGtid; - } - - public static function getSlaveId(): int - { - return self::$slaveId; - } - - public static function getBinLogFileName(): string - { - return self::$binLogFileName; - } - - public static function getBinLogPosition(): int - { - return self::$binLogPosition; - } - - public static function getTableCacheSize(): int - { - return self::$tableCacheSize; - } - - public static function checkDataBasesOnly(string $database): bool - { - return [] !== self::getDatabasesOnly() && !in_array($database, self::getDatabasesOnly(), true); - } - - public static function getDatabasesOnly(): array - { - return self::$databasesOnly; - } - - public static function checkTablesOnly(string $table): bool - { - return [] !== self::getTablesOnly() && !in_array($table, self::getTablesOnly(), true); - } - - public static function getTablesOnly(): array - { - return self::$tablesOnly; - } - - public static function checkEvent(int $type): bool - { - if ([] !== self::getEventsOnly() && !in_array($type, self::getEventsOnly(), true)) { + if ($this->eventsOnly !== [] && !in_array($type, $this->eventsOnly, true)) { return false; } - if (in_array($type, self::getEventsIgnore(), true)) { + if (in_array($type, $this->eventsIgnore, true)) { return false; } return true; } - public static function getEventsOnly(): array - { - return self::$eventsOnly; - } - - public static function getEventsIgnore(): array - { - return self::$eventsIgnore; - } - - public static function getHeartbeatPeriod(): float - { - return self::$heartbeatPeriod; - } - - public function jsonSerialize() + public function jsonSerialize(): array { return get_class_vars(self::class); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Config/ConfigBuilder.php b/src/MySQLReplication/Config/ConfigBuilder.php index ce4b6f23..e86e9d98 100644 --- a/src/MySQLReplication/Config/ConfigBuilder.php +++ b/src/MySQLReplication/Config/ConfigBuilder.php @@ -1,27 +1,53 @@ slaveUuid = $slaveUuid; + + return $this; + } public function withUser(string $user): self { @@ -79,7 +105,7 @@ public function withBinLogFileName(string $binLogFileName): self return $this; } - public function withBinLogPosition(int $binLogPosition): self + public function withBinLogPosition(string $binLogPosition): self { $this->binLogPosition = $binLogPosition; @@ -93,6 +119,9 @@ public function withEventsOnly(array $eventsOnly): self return $this; } + /** + * @param array $eventsIgnore + */ public function withEventsIgnore(array $eventsIgnore): self { $this->eventsIgnore = $eventsIgnore; @@ -121,7 +150,6 @@ public function withMariaDbGtid(string $mariaDbGtid): self return $this; } - public function withTableCacheSize(int $tableCacheSize): self { $this->tableCacheSize = $tableCacheSize; @@ -129,7 +157,6 @@ public function withTableCacheSize(int $tableCacheSize): self return $this; } - public function withCustom(array $custom): self { $this->custom = $custom; @@ -166,7 +193,8 @@ public function build(): Config $this->databasesOnly, $this->tableCacheSize, $this->custom, - $this->heartbeatPeriod + $this->heartbeatPeriod, + $this->slaveUuid ); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Config/ConfigException.php b/src/MySQLReplication/Config/ConfigException.php index 90639b5b..0dc05eb3 100644 --- a/src/MySQLReplication/Config/ConfigException.php +++ b/src/MySQLReplication/Config/ConfigException.php @@ -1,4 +1,5 @@ type; + return $this->type->value; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/EventDTO.php b/src/MySQLReplication/Event/DTO/EventDTO.php index 5f9cce43..ea23d5d4 100644 --- a/src/MySQLReplication/Event/DTO/EventDTO.php +++ b/src/MySQLReplication/Event/DTO/EventDTO.php @@ -1,4 +1,5 @@ eventInfo = $eventInfo; + parent::__construct(); } + abstract public function __toString(): string; + public function getEventInfo(): EventInfo { return $this->eventInfo; } abstract public function getType(): string; - - abstract public function __toString(): string; } diff --git a/src/MySQLReplication/Event/DTO/FormatDescriptionEventDTO.php b/src/MySQLReplication/Event/DTO/FormatDescriptionEventDTO.php index 691c2e2b..1377d87f 100644 --- a/src/MySQLReplication/Event/DTO/FormatDescriptionEventDTO.php +++ b/src/MySQLReplication/Event/DTO/FormatDescriptionEventDTO.php @@ -1,4 +1,5 @@ type; - } + private ConstEventsNames $type = ConstEventsNames::FORMAT_DESCRIPTION; public function __toString(): string { return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL; + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL; + } + + public function getType(): string + { + return $this->type->value; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/GTIDLogDTO.php b/src/MySQLReplication/Event/DTO/GTIDLogDTO.php index a816cc89..1a28eae5 100644 --- a/src/MySQLReplication/Event/DTO/GTIDLogDTO.php +++ b/src/MySQLReplication/Event/DTO/GTIDLogDTO.php @@ -1,4 +1,5 @@ commit = $commit; - $this->gtid = $gtid; - } - - public function isCommit(): bool - { - return $this->commit; - } - - public function getGtid(): string - { - return $this->gtid; - } - - public function getType(): string - { - return $this->type; } public function __toString(): string @@ -49,14 +24,19 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . 'Commit: ' . var_export($this->commit, true) . PHP_EOL . 'GTID NEXT: ' . $this->gtid . PHP_EOL; } - public function jsonSerialize() + public function getType(): string + { + return $this->type->value; + } + + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/HeartbeatDTO.php b/src/MySQLReplication/Event/DTO/HeartbeatDTO.php index 520c10b9..bd9c168c 100644 --- a/src/MySQLReplication/Event/DTO/HeartbeatDTO.php +++ b/src/MySQLReplication/Event/DTO/HeartbeatDTO.php @@ -1,4 +1,5 @@ getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL; + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL; } public function getType(): string { - return $this->type; + return $this->type->value; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/MariaDbGtidLogDTO.php b/src/MySQLReplication/Event/DTO/MariaDbGtidLogDTO.php index 375caf89..0d113245 100644 --- a/src/MySQLReplication/Event/DTO/MariaDbGtidLogDTO.php +++ b/src/MySQLReplication/Event/DTO/MariaDbGtidLogDTO.php @@ -1,4 +1,5 @@ flag = $flag; - $this->domainId = $domainId; - $this->mariaDbGtid = $mariaDbGtid; } public function __toString(): string @@ -31,37 +25,20 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . 'Flag: ' . var_export($this->flag, true) . PHP_EOL . 'Domain Id: ' . $this->domainId . PHP_EOL . 'Sequence Number: ' . $this->mariaDbGtid . PHP_EOL; } - public function getType(): string { - return $this->type; + return $this->type->value; } - - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } - - public function getFlag(): int - { - return $this->flag; - } - - public function getMariaDbGtid(): string - { - return $this->mariaDbGtid; - } - - public function getDomainId(): int - { - return $this->domainId; - } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/QueryDTO.php b/src/MySQLReplication/Event/DTO/QueryDTO.php index 1ec3604d..1bc21ce8 100644 --- a/src/MySQLReplication/Event/DTO/QueryDTO.php +++ b/src/MySQLReplication/Event/DTO/QueryDTO.php @@ -1,4 +1,5 @@ executionTime = $executionTime; - $this->query = $query; - $this->database = $database; - $this->threadId = $threadId; - } - - public function getDatabase(): string - { - return $this->database; - } - - public function getExecutionTime(): int - { - return $this->executionTime; - } - - public function getQuery(): string - { - return $this->query; - } - - public function getType(): string - { - return $this->type; - } - - public function getThreadId(): int - { - return $this->threadId; } public function __toString(): string @@ -59,15 +26,21 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . 'Database: ' . $this->database . PHP_EOL . 'Execution time: ' . $this->executionTime . PHP_EOL . - 'Query: ' . $this->query . PHP_EOL; + 'Query: ' . $this->query . PHP_EOL . + 'Thread id: ' . $this->threadId . PHP_EOL; + } + + public function getType(): string + { + return $this->type->value; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/RotateDTO.php b/src/MySQLReplication/Event/DTO/RotateDTO.php index d7bb6101..66ae072d 100644 --- a/src/MySQLReplication/Event/DTO/RotateDTO.php +++ b/src/MySQLReplication/Event/DTO/RotateDTO.php @@ -1,4 +1,5 @@ position = $position; - $this->nextBinlog = $nextBinlog; - } - - public function getPosition(): int - { - return $this->position; - } - - public function getNextBinlog(): string - { - return $this->nextBinlog; - } - - public function getType(): string - { - return $this->type; } public function __toString(): string @@ -43,14 +24,19 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . 'Binlog position: ' . $this->position . PHP_EOL . 'Binlog filename: ' . $this->nextBinlog . PHP_EOL; } - public function jsonSerialize() + public function getType(): string + { + return $this->type->value; + } + + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/RowsDTO.php b/src/MySQLReplication/Event/DTO/RowsDTO.php index da6a2737..2b44c322 100644 --- a/src/MySQLReplication/Event/DTO/RowsDTO.php +++ b/src/MySQLReplication/Event/DTO/RowsDTO.php @@ -1,4 +1,5 @@ changedRows = $changedRows; - $this->values = $values; - $this->tableMap = $tableMap; - } - - public function getTableMap(): TableMap - { - return $this->tableMap; - } - - public function getChangedRows(): int - { - return $this->changedRows; - } - - public function getValues(): array - { - return $this->values; } public function __toString(): string @@ -45,16 +23,16 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . - 'Table: ' . $this->tableMap->getTable() . PHP_EOL . - 'Affected columns: ' . $this->tableMap->getColumnsAmount() . PHP_EOL . + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . + 'Table: ' . $this->tableMap->table . PHP_EOL . + 'Affected columns: ' . $this->tableMap->columnsAmount . PHP_EOL . 'Changed rows: ' . $this->changedRows . PHP_EOL . 'Values: ' . print_r($this->values, true) . PHP_EOL; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/TableMapDTO.php b/src/MySQLReplication/Event/DTO/TableMapDTO.php index 64d31d48..96265e53 100644 --- a/src/MySQLReplication/Event/DTO/TableMapDTO.php +++ b/src/MySQLReplication/Event/DTO/TableMapDTO.php @@ -1,4 +1,5 @@ tableMap = $tableMap; } public function __toString(): string @@ -26,26 +24,21 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . - 'Table: ' . $this->tableMap->getTable() . PHP_EOL . - 'Database: ' . $this->tableMap->getDatabase() . PHP_EOL . - 'Table Id: ' . $this->tableMap->getTableId() . PHP_EOL . - 'Columns amount: ' . $this->tableMap->getColumnsAmount() . PHP_EOL; + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . + 'Table: ' . $this->tableMap->table . PHP_EOL . + 'Database: ' . $this->tableMap->database . PHP_EOL . + 'Table Id: ' . $this->tableMap->tableId . PHP_EOL . + 'Columns amount: ' . $this->tableMap->columnsAmount . PHP_EOL; } public function getType(): string { - return $this->type; + return $this->type->value; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } - - public function getTableMap(): TableMap - { - return $this->tableMap; - } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/UpdateRowsDTO.php b/src/MySQLReplication/Event/DTO/UpdateRowsDTO.php index aa59a599..ffe5a726 100644 --- a/src/MySQLReplication/Event/DTO/UpdateRowsDTO.php +++ b/src/MySQLReplication/Event/DTO/UpdateRowsDTO.php @@ -1,4 +1,5 @@ type; + return $this->type->value; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/WriteRowsDTO.php b/src/MySQLReplication/Event/DTO/WriteRowsDTO.php index caa705e7..e46b5392 100644 --- a/src/MySQLReplication/Event/DTO/WriteRowsDTO.php +++ b/src/MySQLReplication/Event/DTO/WriteRowsDTO.php @@ -1,4 +1,5 @@ type; + return $this->type->value; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/DTO/XidDTO.php b/src/MySQLReplication/Event/DTO/XidDTO.php index ca729151..4150fbc8 100644 --- a/src/MySQLReplication/Event/DTO/XidDTO.php +++ b/src/MySQLReplication/Event/DTO/XidDTO.php @@ -1,4 +1,5 @@ xid = $xid; - } - - public function getXid(): string - { - return $this->xid; } public function __toString(): string @@ -30,18 +23,18 @@ public function __toString(): string return PHP_EOL . '=== Event ' . $this->getType() . ' === ' . PHP_EOL . 'Date: ' . $this->eventInfo->getDateTime() . PHP_EOL . - 'Log position: ' . $this->eventInfo->getPos() . PHP_EOL . - 'Event size: ' . $this->eventInfo->getSize() . PHP_EOL . + 'Log position: ' . $this->eventInfo->pos . PHP_EOL . + 'Event size: ' . $this->eventInfo->size . PHP_EOL . 'Transaction ID: ' . $this->xid . PHP_EOL; } public function getType(): string { - return $this->type; + return $this->type->value; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/Event.php b/src/MySQLReplication/Event/Event.php index 8a8066e0..4af0caf2 100644 --- a/src/MySQLReplication/Event/Event.php +++ b/src/MySQLReplication/Event/Event.php @@ -1,11 +1,10 @@ binLogSocketConnect = $binLogSocketConnect; - $this->rowEventFactory = $rowEventFactory; - $this->eventDispatcher = $eventDispatcher; - $this->cache = $cache; } - /** - * @throws BinaryDataReaderException - * @throws BinLogException - * @throws MySQLReplicationException - * @throws JsonBinaryDecoderException - * @throws InvalidArgumentException - * @throws SocketException - */ public function consume(): void { $binaryDataReader = new BinaryDataReader($this->binLogSocketConnect->getResponse()); // check EOF_Packet -> https://dev.mysql.com/doc/internals/en/packet-EOF_Packet.html - if (self::EOF_HEADER_VALUE === $binaryDataReader->readUInt8()) { + if ($binaryDataReader->readUInt8() === self::EOF_HEADER_VALUE) { return; } + $this->dispatch($this->makeEvent($binaryDataReader)); + } + + private function makeEvent(BinaryDataReader $binaryDataReader): ?EventDTO + { // decode all events data $eventInfo = $this->createEventInfo($binaryDataReader); - $eventDTO = null; - - // we always need this events to clean table maps and for BinLogCurrent class to keep track of binlog position + // we always need these events to clean table maps and for BinLogCurrent class to keep track of binlog position // always parse table map event but propagate when needed (we need this for creating table cache) - if (ConstEventType::TABLE_MAP_EVENT === $eventInfo->getType()) { - $eventDTO = $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo)->makeTableMapDTO(); - } else if (ConstEventType::ROTATE_EVENT === $eventInfo->getType()) { + if ($eventInfo->type === ConstEventType::TABLE_MAP_EVENT->value) { + return $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo) + ->makeTableMapDTO(); + } + + if ($eventInfo->type === ConstEventType::ROTATE_EVENT->value) { $this->cache->clear(); - $eventDTO = (new RotateEvent($eventInfo, $binaryDataReader))->makeRotateEventDTO(); - } else if (ConstEventType::GTID_LOG_EVENT === $eventInfo->getType()) { - $eventDTO = (new GtidEvent($eventInfo, $binaryDataReader))->makeGTIDLogDTO(); - } else if (ConstEventType::HEARTBEAT_LOG_EVENT === $eventInfo->getType()) { - $eventDTO = new HeartbeatDTO($eventInfo); - } else if (ConstEventType::MARIA_GTID_EVENT === $eventInfo->getType()) { - $eventDTO = (new MariaDbGtidEvent($eventInfo, $binaryDataReader))->makeMariaDbGTIDLogDTO(); + return (new RotateEvent($eventInfo, $binaryDataReader, $this->binLogServerInfo))->makeRotateEventDTO(); + } + + if ($eventInfo->type === ConstEventType::GTID_LOG_EVENT->value) { + return (new GtidEvent($eventInfo, $binaryDataReader, $this->binLogServerInfo))->makeGTIDLogDTO(); + } + + if ($eventInfo->type === ConstEventType::HEARTBEAT_LOG_EVENT->value) { + return new HeartbeatDTO($eventInfo); + } + + if ($eventInfo->type === ConstEventType::MARIA_GTID_EVENT->value) { + return (new MariaDbGtidEvent( + $eventInfo, + $binaryDataReader, + $this->binLogServerInfo + ))->makeMariaDbGTIDLogDTO(); } // check for ignore and permitted events - if (!Config::checkEvent($eventInfo->getType())) { - return; + if ($this->ignoreEvent($eventInfo->type)) { + return null; + } + + if (in_array( + $eventInfo->type, + [ConstEventType::UPDATE_ROWS_EVENT_V1->value, ConstEventType::UPDATE_ROWS_EVENT_V2->value], + true + )) { + return $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo) + ->makeUpdateRowsDTO(); + } + + if (in_array( + $eventInfo->type, + [ConstEventType::WRITE_ROWS_EVENT_V1->value, ConstEventType::WRITE_ROWS_EVENT_V2->value], + true + )) { + return $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo) + ->makeWriteRowsDTO(); + } + + if (in_array( + $eventInfo->type, + [ConstEventType::DELETE_ROWS_EVENT_V1->value, ConstEventType::DELETE_ROWS_EVENT_V2->value], + true + )) { + return $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo) + ->makeDeleteRowsDTO(); } - if (in_array($eventInfo->getType(), [ConstEventType::UPDATE_ROWS_EVENT_V1, ConstEventType::UPDATE_ROWS_EVENT_V2], true)) { - $eventDTO = $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo)->makeUpdateRowsDTO(); - } else if (in_array($eventInfo->getType(), [ConstEventType::WRITE_ROWS_EVENT_V1, ConstEventType::WRITE_ROWS_EVENT_V2], true)) { - $eventDTO = $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo)->makeWriteRowsDTO(); - } else if (in_array($eventInfo->getType(), [ConstEventType::DELETE_ROWS_EVENT_V1, ConstEventType::DELETE_ROWS_EVENT_V2], true)) { - $eventDTO = $this->rowEventFactory->makeRowEvent($binaryDataReader, $eventInfo)->makeDeleteRowsDTO(); - } else if (ConstEventType::XID_EVENT === $eventInfo->getType()) { - $eventDTO = (new XidEvent($eventInfo, $binaryDataReader))->makeXidDTO(); - } else if (ConstEventType::QUERY_EVENT === $eventInfo->getType()) { - $eventDTO = $this->filterDummyMariaDbEvents((new QueryEvent($eventInfo, $binaryDataReader))->makeQueryDTO()); - } else if (ConstEventType::FORMAT_DESCRIPTION_EVENT === $eventInfo->getType()) { - $eventDTO = new FormatDescriptionEventDTO($eventInfo); + if ($eventInfo->type === ConstEventType::XID_EVENT->value) { + return (new XidEvent($eventInfo, $binaryDataReader, $this->binLogServerInfo))->makeXidDTO(); } - $this->dispatch($eventDTO); + if ($eventInfo->type === ConstEventType::QUERY_EVENT->value) { + return $this->filterDummyMariaDbEvents( + (new QueryEvent($eventInfo, $binaryDataReader, $this->binLogServerInfo))->makeQueryDTO() + ); + } + + if ($eventInfo->type === ConstEventType::FORMAT_DESCRIPTION_EVENT->value) { + return new FormatDescriptionEventDTO($eventInfo); + } + + return null; } private function createEventInfo(BinaryDataReader $binaryDataReader): EventInfo @@ -110,7 +134,7 @@ private function createEventInfo(BinaryDataReader $binaryDataReader): EventInfo $binaryDataReader->readUInt8(), $binaryDataReader->readInt32(), $binaryDataReader->readInt32(), - $binaryDataReader->readInt32(), + (string)$binaryDataReader->readInt32(), $binaryDataReader->readUInt16(), $this->binLogSocketConnect->getCheckSum(), $this->binLogSocketConnect->getBinLogCurrent() @@ -119,17 +143,25 @@ private function createEventInfo(BinaryDataReader $binaryDataReader): EventInfo private function filterDummyMariaDbEvents(QueryDTO $queryDTO): ?QueryDTO { - if (BinLogServerInfo::isMariaDb() && false !== strpos($queryDTO->getQuery(), self::MARIADB_DUMMY_QUERY)) { + if ($this->binLogServerInfo->isMariaDb() && str_contains($queryDTO->query, self::MARIADB_DUMMY_QUERY)) { return null; } return $queryDTO; } - private function dispatch(EventDTO $eventDTO = null): void + private function dispatch(?EventDTO $eventDTO): void { - if (null !== $eventDTO) { + if ($eventDTO) { + if ($this->ignoreEvent($eventDTO->getEventInfo()->type)) { + return; + } $this->eventDispatcher->dispatch($eventDTO, $eventDTO->getType()); } } + + private function ignoreEvent(int $type): bool + { + return !$this->config->checkEvent($type); + } } diff --git a/src/MySQLReplication/Event/EventCommon.php b/src/MySQLReplication/Event/EventCommon.php index 8c40fa1b..e8426162 100644 --- a/src/MySQLReplication/Event/EventCommon.php +++ b/src/MySQLReplication/Event/EventCommon.php @@ -1,20 +1,18 @@ eventInfo = $eventInfo; - $this->binaryDataReader = $binaryDataReader; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/EventInfo.php b/src/MySQLReplication/Event/EventInfo.php index 054fc1d9..b11a2a27 100644 --- a/src/MySQLReplication/Event/EventInfo.php +++ b/src/MySQLReplication/Event/EventInfo.php @@ -1,55 +1,47 @@ timestamp = $timestamp; - $this->type = $type; - $this->id = $id; - $this->size = $size; - $this->pos = $pos; - $this->flag = $flag; - $this->checkSum = $checkSum; - $this->binLogCurrent = $binLogCurrent; - if ($pos > 0) { $this->binLogCurrent->setBinLogPosition($pos); } + $this->sizeNoHeader = $this->dateTime = null; + $this->typeName = ConstEventType::from($this->type)->name; } - public function getBinLogCurrent(): BinLogCurrent + public function getTypeName(): ?string { - return $this->binLogCurrent; + return $this->typeName; } - public function getDateTime(): string + public function getDateTime(): ?string { + if ($this->timestamp === 0) { + return null; + } + if (empty($this->dateTime)) { $this->dateTime = date('c', $this->timestamp); } @@ -60,45 +52,14 @@ public function getDateTime(): string public function getSizeNoHeader(): int { if (empty($this->sizeNoHeader)) { - $this->sizeNoHeader = (true === $this->checkSum ? $this->size - 23 : $this->size - 19); + $this->sizeNoHeader = ($this->checkSum === true ? $this->size - 23 : $this->size - 19); } return $this->sizeNoHeader; } - public function getTimestamp(): int - { - return $this->timestamp; - } - - public function getType(): int - { - return $this->type; - } - - public function getId(): int - { - return $this->id; - } - - - public function getSize(): int - { - return $this->size; - } - - public function getPos(): int - { - return $this->pos; - } - - public function getFlag(): int - { - return $this->flag; - } - - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/EventSubscribers.php b/src/MySQLReplication/Event/EventSubscribers.php index 768d35d2..1bb30c27 100644 --- a/src/MySQLReplication/Event/EventSubscribers.php +++ b/src/MySQLReplication/Event/EventSubscribers.php @@ -1,4 +1,5 @@ 'onTableMap', - ConstEventsNames::UPDATE => 'onUpdate', - ConstEventsNames::DELETE => 'onDelete', - ConstEventsNames::GTID => 'onGTID', - ConstEventsNames::QUERY => 'onQuery', - ConstEventsNames::ROTATE => 'onRotate', - ConstEventsNames::XID => 'onXID', - ConstEventsNames::WRITE => 'onWrite', - ConstEventsNames::MARIADB_GTID => 'onMariaDbGtid', - ConstEventsNames::FORMAT_DESCRIPTION => 'onFormatDescription', - ConstEventsNames::HEARTBEAT => 'onHeartbeat', + ConstEventsNames::TABLE_MAP->value => 'onTableMap', + ConstEventsNames::UPDATE->value => 'onUpdate', + ConstEventsNames::DELETE->value => 'onDelete', + ConstEventsNames::GTID->value => 'onGTID', + ConstEventsNames::QUERY->value => 'onQuery', + ConstEventsNames::ROTATE->value => 'onRotate', + ConstEventsNames::XID->value => 'onXID', + ConstEventsNames::WRITE->value => 'onWrite', + ConstEventsNames::MARIADB_GTID->value => 'onMariaDbGtid', + ConstEventsNames::FORMAT_DESCRIPTION->value => 'onFormatDescription', + ConstEventsNames::HEARTBEAT->value => 'onHeartbeat', ]; } @@ -42,10 +43,6 @@ public function onUpdate(UpdateRowsDTO $event): void $this->allEvents($event); } - protected function allEvents(EventDTO $event): void - { - } - public function onTableMap(TableMapDTO $event): void { $this->allEvents($event); @@ -95,4 +92,8 @@ public function onHeartbeat(HeartbeatDTO $event): void { $this->allEvents($event); } -} \ No newline at end of file + + protected function allEvents(EventDTO $event): void + { + } +} diff --git a/src/MySQLReplication/Event/GtidEvent.php b/src/MySQLReplication/Event/GtidEvent.php index a023d02a..ae3fbb1a 100644 --- a/src/MySQLReplication/Event/GtidEvent.php +++ b/src/MySQLReplication/Event/GtidEvent.php @@ -1,26 +1,28 @@ binaryDataReader->readUInt8(); - $sid = unpack('H*', $this->binaryDataReader->read(16))[1]; + $commit_flag = $this->binaryDataReader->readUInt8() === 1; + $sid = BinaryDataReader::unpack('H*', $this->binaryDataReader->read(16))[1]; $gno = $this->binaryDataReader->readUInt64(); - $gtid = vsprintf('%s%s%s%s%s%s%s%s-%s%s%s%s-%s%s%s%s-%s%s%s%s-%s%s%s%s%s%s%s%s%s%s%s%s', str_split($sid)) . ':' . $gno; + $gtid = vsprintf( + '%s%s%s%s%s%s%s%s-%s%s%s%s-%s%s%s%s-%s%s%s%s-%s%s%s%s%s%s%s%s%s%s%s%s', + str_split($sid) + ) . ':' . $gno; - $this->eventInfo->getBinLogCurrent()->setGtid($gtid); + $this->eventInfo->binLogCurrent + ->setGtid($gtid); - return new GTIDLogDTO( - $this->eventInfo, - $commit_flag, - $gtid - ); + return new GTIDLogDTO($this->eventInfo, $commit_flag, $gtid); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/MariaDbGtidEvent.php b/src/MySQLReplication/Event/MariaDbGtidEvent.php index 3eb9ae91..ddb45122 100644 --- a/src/MySQLReplication/Event/MariaDbGtidEvent.php +++ b/src/MySQLReplication/Event/MariaDbGtidEvent.php @@ -1,4 +1,5 @@ binaryDataReader->readUInt32(); $flag = $this->binaryDataReader->readUInt8(); - $this->eventInfo->getBinLogCurrent()->setMariaDbGtid($mariaDbGtid); + $this->eventInfo->binLogCurrent + ->setMariaDbGtid($mariaDbGtid); - return new MariaDbGtidLogDTO( - $this->eventInfo, - $flag, - $domainId, - $mariaDbGtid - ); + return new MariaDbGtidLogDTO($this->eventInfo, $flag, $domainId, $mariaDbGtid); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/QueryEvent.php b/src/MySQLReplication/Event/QueryEvent.php index 88f6b318..e20675f9 100644 --- a/src/MySQLReplication/Event/QueryEvent.php +++ b/src/MySQLReplication/Event/QueryEvent.php @@ -1,4 +1,5 @@ binaryDataReader->advance($statusVarsLength); $schema = $this->binaryDataReader->read($schemaLength); $this->binaryDataReader->advance(1); - $query = $this->binaryDataReader->read($this->eventInfo->getSizeNoHeader() - 13 - $statusVarsLength - $schemaLength - 1); - - return new QueryDTO( - $this->eventInfo, - $schema, - $executionTime, - $query, - $threadId + $query = $this->binaryDataReader->read( + $this->eventInfo->getSizeNoHeader() - 13 - $statusVarsLength - $schemaLength - 1 ); + + return new QueryDTO($this->eventInfo, $schema, $executionTime, $query, $threadId); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/RotateEvent.php b/src/MySQLReplication/Event/RotateEvent.php index 0d68d863..8beac312 100644 --- a/src/MySQLReplication/Event/RotateEvent.php +++ b/src/MySQLReplication/Event/RotateEvent.php @@ -1,9 +1,9 @@ binaryDataReader->readUInt64(); + $binFilePos = $this->binaryDataReader->readUInt64(); $binFileName = $this->binaryDataReader->read( $this->eventInfo->getSizeNoHeader() - $this->getSizeToRemoveByVersion() ); - $this->eventInfo->getBinLogCurrent()->setBinLogPosition($binFilePos); - $this->eventInfo->getBinLogCurrent()->setBinFileName($binFileName); + $this->eventInfo->binLogCurrent + ->setBinLogPosition($binFilePos); + $this->eventInfo->binLogCurrent + ->setBinFileName($binFileName); - return new RotateDTO( - $this->eventInfo, - $binFilePos, - $binFileName - ); + return new RotateDTO($this->eventInfo, $binFilePos, $binFileName); } private function getSizeToRemoveByVersion(): int { - if (BinLogServerInfo::isMariaDb() && BinLogServerInfo::getRevision() <= 10) { + if ($this->binLogServerInfo->versionRevision <= 10 && $this->binLogServerInfo->isMariaDb()) { return 0; } return 8; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/RowEvent/ColumnDTO.php b/src/MySQLReplication/Event/RowEvent/ColumnDTO.php index 6767c72d..a0cad6d5 100644 --- a/src/MySQLReplication/Event/RowEvent/ColumnDTO.php +++ b/src/MySQLReplication/Event/RowEvent/ColumnDTO.php @@ -4,52 +4,29 @@ namespace MySQLReplication\Event\RowEvent; +use JsonSerializable; use MySQLReplication\BinaryDataReader\BinaryDataReader; use MySQLReplication\Definitions\ConstFieldType; use MySQLReplication\Repository\FieldDTO; -class ColumnDTO +readonly class ColumnDTO implements JsonSerializable { - private $fieldDTO; - private $maxLength; - private $size; - private $fsp; - private $lengthSize; - private $precision; - private $decimals; - private $bits; - private $bytes; - private $type; - public function __construct( - FieldDTO $fieldDTO, - int $type, - int $maxLength, - int $size, - int $fsp, - int $lengthSize, - int $precision, - int $decimals, - int $bits, - int $bytes + public FieldDTO $fieldDTO, + public int $type, + public int $maxLength, + public int $size, + public int $fsp, + public int $lengthSize, + public int $precision, + public int $decimals, + public int $bits, + public int $bytes ) { - $this->fieldDTO = $fieldDTO; - $this->type = $type; - $this->maxLength = $maxLength; - $this->size = $size; - $this->fsp = $fsp; - $this->lengthSize = $lengthSize; - $this->precision = $precision; - $this->decimals = $decimals; - $this->bits = $bits; - $this->bytes = $bytes; } - public static function make( - int $columnType, - FieldDTO $fieldDTO, - BinaryDataReader $binaryDataReader - ): self { + public static function make(int $columnType, FieldDTO $fieldDTO, BinaryDataReader $binaryDataReader): self + { $maxLength = 0; $size = 0; $fsp = 0; @@ -111,65 +88,15 @@ public static function make( ); } - public function getFieldDTO(): FieldDTO - { - return $this->fieldDTO; - } - - public function getMaxLength(): int - { - return $this->maxLength; - } - - public function getSize(): int - { - return $this->size; - } - - public function getFsp(): int - { - return $this->fsp; - } - - public function getLengthSize(): int - { - return $this->lengthSize; - } - - public function getPrecision(): int - { - return $this->precision; - } - - public function getDecimals(): int - { - return $this->decimals; - } - - public function getBits(): int - { - return $this->bits; - } - - public function getBytes(): int - { - return $this->bytes; - } - - public function getType(): int - { - return $this->type; - } - public function getName(): string { - return $this->fieldDTO->getColumnName(); + return $this->fieldDTO->columnName; } public function getEnumValues(): array { if ($this->type === ConstFieldType::ENUM) { - return explode(',', str_replace(['enum(', ')', '\''], '', $this->fieldDTO->getColumnType())); + return explode(',', str_replace(['enum(', ')', '\''], '', $this->fieldDTO->columnType)); } return []; @@ -178,7 +105,7 @@ public function getEnumValues(): array public function getSetValues(): array { if ($this->type === ConstFieldType::SET) { - return explode(',', str_replace(['set(', ')', '\''], '', $this->fieldDTO->getColumnType())); + return explode(',', str_replace(['set(', ')', '\''], '', $this->fieldDTO->columnType)); } return []; @@ -186,11 +113,16 @@ public function getSetValues(): array public function isUnsigned(): bool { - return !(stripos($this->fieldDTO->getColumnType(), 'unsigned') === false); + return !(stripos($this->fieldDTO->columnType, 'unsigned') === false); } public function isPrimary(): bool { - return $this->fieldDTO->getColumnKey() === 'PRI'; + return $this->fieldDTO->columnKey === 'PRI'; + } + + public function jsonSerialize(): array + { + return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/RowEvent/ColumnDTOCollection.php b/src/MySQLReplication/Event/RowEvent/ColumnDTOCollection.php index d5373187..2ea25e3f 100644 --- a/src/MySQLReplication/Event/RowEvent/ColumnDTOCollection.php +++ b/src/MySQLReplication/Event/RowEvent/ColumnDTOCollection.php @@ -1,10 +1,19 @@ + */ +class ColumnDTOCollection extends ArrayCollection implements JsonSerializable { -} \ No newline at end of file + public function jsonSerialize(): array + { + return $this->toArray(); + } +} diff --git a/src/MySQLReplication/Event/RowEvent/RowEvent.php b/src/MySQLReplication/Event/RowEvent/RowEvent.php index 729cf46f..66d04af1 100644 --- a/src/MySQLReplication/Event/RowEvent/RowEvent.php +++ b/src/MySQLReplication/Event/RowEvent/RowEvent.php @@ -1,4 +1,5 @@ repository = $repository; - $this->cache = $cache; + parent::__construct($eventInfo, $binaryDataReader, $binLogServerInfo); } /** - * This describe the structure of a table. + * This describes the structure of a table. * It's send before a change append on a table. - * A end user of the lib should have no usage of this - * @throws BinaryDataReaderException - * @throws InvalidArgumentException + * An end user of the lib should have no usage of this */ public function makeTableMapDTO(): ?TableMapDTO { @@ -320,7 +312,7 @@ public function makeTableMapDTO(): ?TableMapDTO $data['schema_length'] = $this->binaryDataReader->readUInt8(); $data['schema_name'] = $this->binaryDataReader->read($data['schema_length']); - if (Config::checkDataBasesOnly($data['schema_name'])) { + if ($this->config->checkDataBasesOnly($data['schema_name'])) { return null; } @@ -328,7 +320,7 @@ public function makeTableMapDTO(): ?TableMapDTO $data['table_length'] = $this->binaryDataReader->readUInt8(); $data['table_name'] = $this->binaryDataReader->read($data['table_length']); - if (Config::checkTablesOnly($data['table_name'])) { + if ($this->config->checkTablesOnly($data['table_name'])) { return null; } @@ -336,8 +328,8 @@ public function makeTableMapDTO(): ?TableMapDTO $data['columns_amount'] = (int)$this->binaryDataReader->readCodedBinary(); $data['column_types'] = $this->binaryDataReader->read($data['columns_amount']); - if ($this->cache->has($data['table_id'])) { - return new TableMapDTO($this->eventInfo, $this->cache->get($data['table_id'])); + if ($this->tableMapCache->has($data['table_id'])) { + return new TableMapDTO($this->eventInfo, $this->tableMapCache->get($data['table_id'])); } $this->binaryDataReader->readCodedBinary(); @@ -356,9 +348,8 @@ public function makeTableMapDTO(): ?TableMapDTO $type = ConstFieldType::IGNORE; } - /** @var FieldDTO $fieldDTO */ $fieldDTO = $fieldDTOCollection->offsetGet($offset); - if (null !== $fieldDTO) { + if ($fieldDTO) { $columnDTOCollection->set($offset, ColumnDTO::make($type, $fieldDTO, $this->binaryDataReader)); } } @@ -372,78 +363,94 @@ public function makeTableMapDTO(): ?TableMapDTO $columnDTOCollection ); - $this->cache->set($data['table_id'], $tableMap); + $this->tableMapCache->set($data['table_id'], $tableMap); return new TableMapDTO($this->eventInfo, $tableMap); } - /** - * @throws BinaryDataReaderException - * @throws InvalidArgumentException - * @throws JsonBinaryDecoderException - * @throws MySQLReplicationException - */ public function makeWriteRowsDTO(): ?WriteRowsDTO { - if (!$this->rowInit()) { + $this->currentTableMap = $this->findTableMap(); + if ($this->currentTableMap === null) { return null; } $values = $this->getValues(); - return new WriteRowsDTO( - $this->eventInfo, - $this->currentTableMap, - count($values), - $values - ); + return new WriteRowsDTO($this->eventInfo, $this->currentTableMap, count($values), $values); } - /** - * @throws InvalidArgumentException - * @throws BinaryDataReaderException - */ - protected function rowInit(): bool + public function makeDeleteRowsDTO(): ?DeleteRowsDTO + { + $this->currentTableMap = $this->findTableMap(); + if ($this->currentTableMap === null) { + return null; + } + + $values = $this->getValues(); + + return new DeleteRowsDTO($this->eventInfo, $this->currentTableMap, count($values), $values); + } + + public function makeUpdateRowsDTO(): ?UpdateRowsDTO + { + $this->currentTableMap = $this->findTableMap(); + if ($this->currentTableMap === null) { + return null; + } + + $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->columnsAmount); + $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize); + $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize); + + $values = []; + while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) { + $values[] = [ + 'before' => $this->getColumnData($beforeBinaryData), + 'after' => $this->getColumnData($afterBinaryData), + ]; + } + + return new UpdateRowsDTO($this->eventInfo, $this->currentTableMap, count($values), $values); + } + + protected function findTableMap(): ?TableMap { $tableId = $this->binaryDataReader->readTableId(); $this->binaryDataReader->advance(2); if (in_array( - $this->eventInfo->getType(), [ - ConstEventType::DELETE_ROWS_EVENT_V2, - ConstEventType::WRITE_ROWS_EVENT_V2, - ConstEventType::UPDATE_ROWS_EVENT_V2 - ], true + $this->eventInfo->type, + [ + ConstEventType::DELETE_ROWS_EVENT_V2->value, + ConstEventType::WRITE_ROWS_EVENT_V2->value, + ConstEventType::UPDATE_ROWS_EVENT_V2->value, + ], + true )) { $this->binaryDataReader->read((int)($this->binaryDataReader->readUInt16() / 8)); } $this->binaryDataReader->readCodedBinary(); - if ($this->cache->has($tableId)) { - /** @var TableMap $tableMap */ - $this->currentTableMap = $this->cache->get($tableId); - - return true; + if ($this->tableMapCache->has($tableId)) { + return $this->tableMapCache->get($tableId); } - return false; + $this->logger->info('No table map found for table ID: ' . $tableId); + + return null; } - /** - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - * @throws MySQLReplicationException - */ protected function getValues(): array { // if we don't get columns from information schema we don't know how to assign them - if ($this->currentTableMap === null || $this->currentTableMap->getColumnDTOCollection()->isEmpty()) { + if ($this->currentTableMap === null || $this->currentTableMap->columnDTOCollection->isEmpty()) { return []; } $binaryData = $this->binaryDataReader->read( - $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount()) + $this->getColumnsBinarySize($this->currentTableMap->columnsAmount) ); $values = []; @@ -459,14 +466,9 @@ protected function getColumnsBinarySize(int $columnsAmount): int return (int)(($columnsAmount + 7) / 8); } - /** - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - * @throws MySQLReplicationException - */ protected function getColumnData(string $colsBitmap): array { - if (null === $this->currentTableMap) { + if ($this->currentTableMap === null) { throw new RuntimeException('Current table map is missing!'); } @@ -477,89 +479,91 @@ protected function getColumnData(string $colsBitmap): array $nullBitmap = $this->binaryDataReader->read($this->getColumnsBinarySize($this->bitCount($colsBitmap))); $nullBitmapIndex = 0; - foreach ($this->currentTableMap->getColumnDTOCollection() as $i => $columnDTO) { + foreach ($this->currentTableMap->columnDTOCollection as $i => $columnDTO) { $name = $columnDTO->getName(); - $type = $columnDTO->getType(); + $type = $columnDTO->type; - if (0 === $this->bitGet($colsBitmap, $i)) { + if ($this->bitGet($colsBitmap, $i) === 0) { $values[$name] = null; continue; } if ($this->checkNull($nullBitmap, $nullBitmapIndex)) { $values[$name] = null; - } else if ($type === ConstFieldType::IGNORE) { - $this->binaryDataReader->advance($columnDTO->getLengthSize()); + } elseif ($type === ConstFieldType::IGNORE) { + $this->binaryDataReader->advance($columnDTO->lengthSize); $values[$name] = null; - } else if ($type === ConstFieldType::TINY) { + } elseif ($type === ConstFieldType::TINY) { if ($columnDTO->isUnsigned()) { $values[$name] = $this->binaryDataReader->readUInt8(); } else { $values[$name] = $this->binaryDataReader->readInt8(); } - } else if ($type === ConstFieldType::SHORT) { + } elseif ($type === ConstFieldType::SHORT) { if ($columnDTO->isUnsigned()) { $values[$name] = $this->binaryDataReader->readUInt16(); } else { $values[$name] = $this->binaryDataReader->readInt16(); } - } else if ($type === ConstFieldType::LONG) { + } elseif ($type === ConstFieldType::LONG) { if ($columnDTO->isUnsigned()) { $values[$name] = $this->binaryDataReader->readUInt32(); } else { $values[$name] = $this->binaryDataReader->readInt32(); } - } else if ($type === ConstFieldType::LONGLONG) { + } elseif ($type === ConstFieldType::LONGLONG) { if ($columnDTO->isUnsigned()) { $values[$name] = $this->binaryDataReader->readUInt64(); } else { $values[$name] = $this->binaryDataReader->readInt64(); } - } else if ($type === ConstFieldType::INT24) { + } elseif ($type === ConstFieldType::INT24) { if ($columnDTO->isUnsigned()) { $values[$name] = $this->binaryDataReader->readUInt24(); } else { $values[$name] = $this->binaryDataReader->readInt24(); } - } else if ($type === ConstFieldType::FLOAT) { + } elseif ($type === ConstFieldType::FLOAT) { // http://dev.mysql.com/doc/refman/5.7/en/floating-point-types.html FLOAT(7,4) $values[$name] = round($this->binaryDataReader->readFloat(), 4); - } else if ($type === ConstFieldType::DOUBLE) { + } elseif ($type === ConstFieldType::DOUBLE) { $values[$name] = $this->binaryDataReader->readDouble(); - } else if ($type === ConstFieldType::VARCHAR || $type === ConstFieldType::STRING) { - $values[$name] = $columnDTO->getMaxLength() > 255 ? $this->getString(2) : $this->getString(1); - } else if ($type === ConstFieldType::NEWDECIMAL) { + } elseif ($type === ConstFieldType::VARCHAR || $type === ConstFieldType::STRING) { + $values[$name] = $columnDTO->maxLength > 255 ? $this->getString(2) : $this->getString(1); + } elseif ($type === ConstFieldType::NEWDECIMAL) { $values[$name] = $this->getDecimal($columnDTO); - } else if ($type === ConstFieldType::BLOB) { - $values[$name] = $this->getString($columnDTO->getLengthSize()); - } else if ($type === ConstFieldType::DATETIME) { + } elseif ($type === ConstFieldType::BLOB) { + $values[$name] = $this->getString($columnDTO->lengthSize); + } elseif ($type === ConstFieldType::DATETIME) { $values[$name] = $this->getDatetime(); - } else if ($type === ConstFieldType::DATETIME2) { + } elseif ($type === ConstFieldType::DATETIME2) { $values[$name] = $this->getDatetime2($columnDTO); - } else if ($type === ConstFieldType::TIMESTAMP) { + } elseif ($type === ConstFieldType::TIMESTAMP) { $values[$name] = date('Y-m-d H:i:s', $this->binaryDataReader->readUInt32()); - } else if ($type === ConstFieldType::TIME) { + } elseif ($type === ConstFieldType::TIME) { $values[$name] = $this->getTime(); - } else if ($type === ConstFieldType::TIME2) { + } elseif ($type === ConstFieldType::TIME2) { $values[$name] = $this->getTime2($columnDTO); - } else if ($type === ConstFieldType::TIMESTAMP2) { + } elseif ($type === ConstFieldType::TIMESTAMP2) { $values[$name] = $this->getTimestamp2($columnDTO); - } else if ($type === ConstFieldType::DATE) { + } elseif ($type === ConstFieldType::DATE) { $values[$name] = $this->getDate(); - } else if ($type === ConstFieldType::YEAR) { + } elseif ($type === ConstFieldType::YEAR) { // https://dev.mysql.com/doc/refman/5.7/en/year.html $year = $this->binaryDataReader->readUInt8(); - $values[$name] = 0 === $year ? null : 1900 + $year; - } else if ($type === ConstFieldType::ENUM) { + $values[$name] = $year === 0 ? null : 1900 + $year; + } elseif ($type === ConstFieldType::ENUM) { $values[$name] = $this->getEnum($columnDTO); - } else if ($type === ConstFieldType::SET) { + } elseif ($type === ConstFieldType::SET) { $values[$name] = $this->getSet($columnDTO); - } else if ($type === ConstFieldType::BIT) { + } elseif ($type === ConstFieldType::BIT) { $values[$name] = $this->getBit($columnDTO); - } else if ($type === ConstFieldType::GEOMETRY) { - $values[$name] = $this->getString($columnDTO->getLengthSize()); - } else if ($type === ConstFieldType::JSON) { - $values[$name] = JsonBinaryDecoderService::makeJsonBinaryDecoder($this->getString($columnDTO->getLengthSize()))->parseToString(); + } elseif ($type === ConstFieldType::GEOMETRY) { + $values[$name] = $this->getString($columnDTO->lengthSize); + } elseif ($type === ConstFieldType::JSON) { + $values[$name] = JsonBinaryDecoderService::makeJsonBinaryDecoder( + $this->getString($columnDTO->lengthSize) + )->parseToString(); } else { throw new MySQLReplicationException('Unknown row type: ' . $type); } @@ -605,9 +609,6 @@ protected function checkNull(string $nullBitmap, int $position): int return $this->getBitFromBitmap($nullBitmap, $position) & (1 << ($position % 8)); } - /** - * @throws BinaryDataReaderException - */ protected function getString(int $size): string { return $this->binaryDataReader->readLengthString($size); @@ -616,17 +617,16 @@ protected function getString(int $size): string /** * Read MySQL's new decimal format introduced in MySQL 5 * https://dev.mysql.com/doc/refman/5.6/en/precision-math-decimal-characteristics.html - * @throws BinaryDataReaderException */ protected function getDecimal(ColumnDTO $columnDTO): string { $digitsPerInteger = 9; $compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4]; - $integral = $columnDTO->getPrecision() - $columnDTO->getDecimals(); + $integral = $columnDTO->precision - $columnDTO->decimals; $unCompIntegral = (int)($integral / $digitsPerInteger); - $unCompFractional = (int)($columnDTO->getDecimals() / $digitsPerInteger); + $unCompFractional = (int)($columnDTO->decimals / $digitsPerInteger); $compIntegral = $integral - ($unCompIntegral * $digitsPerInteger); - $compFractional = $columnDTO->getDecimals() - ($unCompFractional * $digitsPerInteger); + $compFractional = $columnDTO->decimals - ($unCompFractional * $digitsPerInteger); $value = $this->binaryDataReader->readUInt8(); if (0 !== ($value & 0x80)) { @@ -662,23 +662,18 @@ protected function getDecimal(ColumnDTO $columnDTO): string $res .= sprintf('%0' . $compFractional . 'd', $value); } - return bcmul($res, '1', $columnDTO->getDecimals()); + return bcmul($res, '1', $columnDTO->decimals); } protected function getDatetime(): ?string { $value = $this->binaryDataReader->readUInt64(); // nasty mysql 0000-00-00 dates - if ('0' === $value) { + if ($value === '0') { return null; } - $date = DateTime::createFromFormat('YmdHis', $value)->format('Y-m-d H:i:s'); - if (array_sum(DateTime::getLastErrors()) > 0) { - return null; - } - - return $date; + return $this->dateFormatter($value); } /** @@ -692,8 +687,6 @@ protected function getDatetime(): ?string * --------------------------- * 40 bits = 5 bytes * - * @throws BinaryDataReaderException - * * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html */ protected function getDatetime2(ColumnDTO $columnDTO): ?string @@ -710,39 +703,54 @@ protected function getDatetime2(ColumnDTO $columnDTO): ?string $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40); $fsp = $this->getFSP($columnDTO); + $formattedDate = $this->dateFormatter( + $year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second + ); + if ($formattedDate) { + return $formattedDate . $fsp; + } + + return null; + } + + protected function dateFormatter(string $date): ?string + { try { - $date = new DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second); - } catch (Exception $exception) { - return null; + $dateTime = new DateTime($date); + } catch (Exception) { + $dateTime = DateTime::createFromFormat('YmdHis', $date); + if ($dateTime === false) { + return null; + } } - if (array_sum(DateTime::getLastErrors()) > 0) { + + $formattedDate = $dateTime->format('Y-m-d H:i:s'); + if (DateTime::getLastErrors() !== false) { return null; } - return $date->format('Y-m-d H:i:s') . $fsp; + return $formattedDate; } /** - * @throws BinaryDataReaderException * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html */ protected function getFSP(ColumnDTO $columnDTO): string { $read = 0; $time = ''; - $fsp = $columnDTO->getFsp(); + $fsp = $columnDTO->fsp; if ($fsp === 1 || $fsp === 2) { $read = 1; - } else if ($fsp === 3 || $fsp === 4) { + } elseif ($fsp === 3 || $fsp === 4) { $read = 2; - } else if ($fsp === 5 || $fsp === 6) { + } elseif ($fsp === 5 || $fsp === 6) { $read = 3; } if ($read > 0) { $microsecond = $this->binaryDataReader->readIntBeBySize($read); if ($fsp % 2) { $microsecond = (int)($microsecond / 10); - } $time = $microsecond * (10 ** (6 - $fsp)); } @@ -753,7 +761,7 @@ protected function getFSP(ColumnDTO $columnDTO): string protected function getTime(): string { $data = $this->binaryDataReader->readUInt24(); - if (0 === $data) { + if ($data === 0) { return '00:00:00'; } @@ -761,7 +769,7 @@ protected function getTime(): string } /** - * TIME encoding for non fractional 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) @@ -769,8 +777,6 @@ protected function getTime(): string * 6 bits second (0-59) * --------------------- * 24 bits = 3 bytes - * - * @throws BinaryDataReaderException */ protected function getTime2(ColumnDTO $columnDTO): string { @@ -780,17 +786,16 @@ protected function getTime2(ColumnDTO $columnDTO): string $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24); $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24); - return (new DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($columnDTO); + return (new DateTime()) + ->setTime($hour, $minute, $second) + ->format('H:i:s') . $this->getFSP($columnDTO); } - /** - * @throws BinaryDataReaderException - */ protected function getTimestamp2(ColumnDTO $columnDTO): string { - $datetime = (string)date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be()); + $datetime = date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be()); $fsp = $this->getFSP($columnDTO); - if ('' !== $fsp) { + if ($fsp !== '') { $datetime .= '.' . $fsp; } @@ -800,7 +805,7 @@ protected function getTimestamp2(ColumnDTO $columnDTO): string protected function getDate(): ?string { $time = $this->binaryDataReader->readUInt24(); - if (0 === $time) { + if ($time === 0) { return null; } @@ -811,15 +816,13 @@ protected function getDate(): ?string return null; } - return (new DateTime())->setDate($year, $month, $day)->format('Y-m-d'); + return (new DateTime())->setDate($year, $month, $day) + ->format('Y-m-d'); } - /** - * @throws BinaryDataReaderException - */ protected function getEnum(ColumnDTO $columnDTO): string { - $value = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()) - 1; + $value = $this->binaryDataReader->readUIntBySize($columnDTO->size) - 1; // check if given value exists in enums, if there not existing enum mysql returns empty string. if (array_key_exists($value, $columnDTO->getEnumValues())) { @@ -829,16 +832,13 @@ protected function getEnum(ColumnDTO $columnDTO): string return ''; } - /** - * @throws BinaryDataReaderException - */ protected function getSet(ColumnDTO $columnDTO): array { // we read set columns as a bitmap telling us which options are enabled - $bit_mask = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()); + $bitMask = $this->binaryDataReader->readUIntBySize($columnDTO->size); $sets = []; foreach ($columnDTO->getSetValues() as $k => $item) { - if ($bit_mask & (2 ** $k)) { + if ($bitMask & (2 ** $k)) { $sets[] = $item; } } @@ -849,15 +849,15 @@ protected function getSet(ColumnDTO $columnDTO): array protected function getBit(ColumnDTO $columnDTO): string { $res = ''; - for ($byte = 0; $byte < $columnDTO->getBytes(); ++$byte) { - $current_byte = ''; + for ($byte = 0; $byte < $columnDTO->bytes; ++$byte) { + $currentByte = ''; $data = $this->binaryDataReader->readUInt8(); - if (0 === $byte) { - if (1 === $columnDTO->getBytes()) { - $end = $columnDTO->getBits(); + if ($byte === 0) { + if ($columnDTO->bytes === 1) { + $end = $columnDTO->bits; } else { - $end = $columnDTO->getBits() % 8; - if (0 === $end) { + $end = $columnDTO->bits % 8; + if ($end === 0) { $end = 8; } } @@ -867,69 +867,14 @@ protected function getBit(ColumnDTO $columnDTO): string for ($bit = 0; $bit < $end; ++$bit) { if ($data & (1 << $bit)) { - $current_byte .= '1'; + $currentByte .= '1'; } else { - $current_byte .= '0'; + $currentByte .= '0'; } - } - $res .= strrev($current_byte); + $res .= strrev($currentByte); } return $res; } - - /** - * @throws InvalidArgumentException - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - * @throws MySQLReplicationException - */ - public function makeDeleteRowsDTO(): ?DeleteRowsDTO - { - if (!$this->rowInit()) { - return null; - } - - $values = $this->getValues(); - - return new DeleteRowsDTO( - $this->eventInfo, - $this->currentTableMap, - count($values), - $values - ); - } - - /** - * @throws InvalidArgumentException - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - * @throws MySQLReplicationException - */ - public function makeUpdateRowsDTO(): ?UpdateRowsDTO - { - if (!$this->rowInit()) { - return null; - } - - $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount()); - $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize); - $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize); - - $values = []; - while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) { - $values[] = [ - 'before' => $this->getColumnData($beforeBinaryData), - 'after' => $this->getColumnData($afterBinaryData) - ]; - } - - return new UpdateRowsDTO( - $this->eventInfo, - $this->currentTableMap, - count($values), - $values - ); - } } diff --git a/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php b/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php index 2fc4eafe..cc904a7e 100644 --- a/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php +++ b/src/MySQLReplication/Event/RowEvent/RowEventBuilder.php @@ -1,46 +1,46 @@ repository = $repository; - $this->cache = $cache; } - public function withPackage(BinaryDataReader $package): void + public function withBinaryDataReader(BinaryDataReader $binaryDataReader): void { - $this->package = $package; + $this->binaryDataReader = $binaryDataReader; } public function build(): RowEvent { return new RowEvent( $this->repository, - $this->package, + $this->binaryDataReader, $this->eventInfo, - $this->cache + new TableMapCache($this->cache), + $this->config, + $this->binLogServerInfo, + $this->logger ); } @@ -48,4 +48,4 @@ public function withEventInfo(EventInfo $eventInfo): void { $this->eventInfo = $eventInfo; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/RowEvent/RowEventFactory.php b/src/MySQLReplication/Event/RowEvent/RowEventFactory.php index 06ef80b5..ca1b8c67 100644 --- a/src/MySQLReplication/Event/RowEvent/RowEventFactory.php +++ b/src/MySQLReplication/Event/RowEvent/RowEventFactory.php @@ -1,29 +1,24 @@ rowEventBuilder = new RowEventBuilder($repository, $cache); } - public function makeRowEvent(BinaryDataReader $package, EventInfo $eventInfo): RowEvent + public function makeRowEvent(BinaryDataReader $binaryDataReader, EventInfo $eventInfo): RowEvent { - $this->rowEventBuilder->withPackage($package); + $this->rowEventBuilder->withBinaryDataReader($binaryDataReader); $this->rowEventBuilder->withEventInfo($eventInfo); return $this->rowEventBuilder->build(); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/RowEvent/TableMap.php b/src/MySQLReplication/Event/RowEvent/TableMap.php index 9b38e95e..5ceffadb 100644 --- a/src/MySQLReplication/Event/RowEvent/TableMap.php +++ b/src/MySQLReplication/Event/RowEvent/TableMap.php @@ -1,62 +1,24 @@ database = $database; - $this->table = $table; - $this->tableId = $tableId; - $this->columnsAmount = $columnsAmount; - $this->columnDTOCollection = $columnDTOCollection; - } - - public function getDatabase(): string - { - return $this->database; - } - - public function getTable(): string - { - return $this->table; - } - - public function getTableId(): string - { - return $this->tableId; - } - - public function getColumnsAmount(): int - { - return $this->columnsAmount; - } - - /** - * @return ColumnDTOCollection|ColumnDTO[] - */ - public function getColumnDTOCollection(): ColumnDTOCollection - { - return $this->columnDTOCollection; } - public function jsonSerialize() + public function jsonSerialize(): array { return get_object_vars($this); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Event/RowEvent/TableMapCache.php b/src/MySQLReplication/Event/RowEvent/TableMapCache.php new file mode 100644 index 00000000..541b092a --- /dev/null +++ b/src/MySQLReplication/Event/RowEvent/TableMapCache.php @@ -0,0 +1,32 @@ +cache->get($tableId); + return $tableMap; + } + + public function has(string $tableId): bool + { + return $this->cache->has($tableId); + } + + public function set(string $tableId, TableMap $tableMap): void + { + $this->cache->set($tableId, $tableMap); + } +} diff --git a/src/MySQLReplication/Event/XidEvent.php b/src/MySQLReplication/Event/XidEvent.php index 14777909..41a4a8b6 100644 --- a/src/MySQLReplication/Event/XidEvent.php +++ b/src/MySQLReplication/Event/XidEvent.php @@ -1,4 +1,5 @@ eventInfo, - $this->binaryDataReader->readUInt64() - ); + return new XidDTO($this->eventInfo, $this->binaryDataReader->readUInt64()); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Exception/MySQLReplicationException.php b/src/MySQLReplication/Exception/MySQLReplicationException.php index 09d20bed..a4eaf1ee 100644 --- a/src/MySQLReplication/Exception/MySQLReplicationException.php +++ b/src/MySQLReplication/Exception/MySQLReplicationException.php @@ -1,50 +1,16 @@ intervals)); foreach ($this->intervals as $interval) { + $buffer .= BinaryDataReader::pack64bit((int)$interval[0]); if (count($interval) !== 1) { - $buffer .= BinaryDataReader::pack64bit((int)$interval[0]); $buffer .= BinaryDataReader::pack64bit((int)$interval[1]); } else { - $buffer .= BinaryDataReader::pack64bit((int)$interval[0]); $buffer .= BinaryDataReader::pack64bit($interval[0] + 1); } } @@ -46,6 +47,6 @@ public function getEncoded(): string public function getEncodedLength(): int { - return (40 * count($this->intervals)); + return 40 * count($this->intervals); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Gtid/GtidCollection.php b/src/MySQLReplication/Gtid/GtidCollection.php index b51d6a11..fd3cbf05 100644 --- a/src/MySQLReplication/Gtid/GtidCollection.php +++ b/src/MySQLReplication/Gtid/GtidCollection.php @@ -1,4 +1,5 @@ + */ class GtidCollection extends ArrayCollection { - /** - * @throws GtidException - */ - public static function makeCollectionFromString(string $gtids): GtidCollection + public static function makeCollectionFromString(string $gtids): self { $collection = new self(); foreach (array_filter(explode(',', $gtids)) as $gtid) { @@ -24,7 +25,6 @@ public static function makeCollectionFromString(string $gtids): GtidCollection public function getEncodedLength(): int { $l = 8; - /** @var Gtid $gtid */ foreach ($this->toArray() as $gtid) { $l += $gtid->getEncodedLength(); } @@ -35,11 +35,10 @@ public function getEncodedLength(): int public function getEncoded(): string { $s = BinaryDataReader::pack64bit($this->count()); - /** @var Gtid $gtid */ foreach ($this->toArray() as $gtid) { $s .= $gtid->getEncoded(); } return $s; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Gtid/GtidException.php b/src/MySQLReplication/Gtid/GtidException.php index f3da2548..bfa50f95 100644 --- a/src/MySQLReplication/Gtid/GtidException.php +++ b/src/MySQLReplication/Gtid/GtidException.php @@ -1,4 +1,5 @@ jsonString .= var_export($bool, true); } - public function formatValueNumeric(int $val): void + public function formatValueNumeric(int|string|null|float|bool $val): void { $this->jsonString .= $val; } - public function formatValue($val): void + public function formatValue(int|string|null|float|bool $val): void { $this->jsonString .= '"' . self::escapeJsonString($val) . '"'; } - /** - * Some characters needs to be escaped - * @see http://www.json.org/ - * @see https://stackoverflow.com/questions/1048487/phps-json-encode-does-not-escape-all-json-control-characters - */ - private static function escapeJsonString($value): string - { - return str_replace( - ["\\", '/', '"', "\n", "\r", "\t", "\x08", "\x0c"], - ["\\\\", "\\/", "\\\"", "\\n", "\\r", "\\t", "\\f", "\\b"], - (string)$value - ); - } - public function formatEndObject(): void { $this->jsonString .= '}'; @@ -75,4 +62,18 @@ public function getJsonString(): string { return $this->jsonString; } -} \ No newline at end of file + + /** + * Some characters need to be escaped + * @see http://www.json.org/ + * @see https://stackoverflow.com/questions/1048487/phps-json-encode-does-not-escape-all-json-control-characters + */ + private static function escapeJsonString(int|string|null|float|bool $value): string + { + return str_replace( + ['\\', '/', '"', "\n", "\r", "\t", "\x08", "\x0c"], + ['\\\\', '\\/', '\\"', '\\n', '\\r', '\\t', '\\f', '\\b'], + (string)$value + ); + } +} diff --git a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php index a44dee12..d669ef46 100644 --- a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php +++ b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderService.php @@ -1,4 +1,5 @@ binaryDataReader = $binaryDataReader; - $this->jsonBinaryDecoderFormatter = $jsonBinaryDecoderFormatter; $this->dataLength = $this->binaryDataReader->getBinaryDataLength(); } public static function makeJsonBinaryDecoder(string $data): self { - return new self( - new BinaryDataReader($data), - new JsonBinaryDecoderFormatter() - ); + return new self(new BinaryDataReader($data), new JsonBinaryDecoderFormatter()); } - /** - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - */ public function parseToString(): string { // Sometimes, we can insert a NULL JSON even we set the JSON field as NOT NULL. // If we meet this case, we can return a 'null' value. - if($this->binaryDataReader->getBinaryDataLength() === 0) { + if ($this->binaryDataReader->getBinaryDataLength() === 0) { return 'null'; } $this->parseJson($this->binaryDataReader->readUInt8()); @@ -84,80 +92,66 @@ public function parseToString(): string return $this->jsonBinaryDecoderFormatter->getJsonString(); } - /** - * @throws JsonBinaryDecoderException - * @throws BinaryDataReaderException - */ private function parseJson(int $type): void { $results = []; - if (self::SMALL_OBJECT === $type) { + if ($type === self::SMALL_OBJECT) { $results[self::OBJECT] = $this->parseArrayOrObject(self::OBJECT, self::SMALL_OFFSET_SIZE); - } else if (self::LARGE_OBJECT === $type) { + } elseif ($type === self::LARGE_OBJECT) { $results[self::OBJECT] = $this->parseArrayOrObject(self::OBJECT, self::LARGE_OFFSET_SIZE); - } else if (self::SMALL_ARRAY === $type) { + } elseif ($type === self::SMALL_ARRAY) { $results[self::ARRAY] = $this->parseArrayOrObject(self::ARRAY, self::SMALL_OFFSET_SIZE); - } else if (self::LARGE_ARRAY === $type) { + } elseif ($type === self::LARGE_ARRAY) { $results[self::ARRAY] = $this->parseArrayOrObject(self::ARRAY, self::LARGE_OFFSET_SIZE); } else { $results[self::SCALAR][] = [ 'name' => null, - 'value' => $this->parseScalar($type) + 'value' => $this->parseScalar($type), ]; } $this->parseToJson($results); } - /** - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - */ private function parseToJson(array $results): void { foreach ($results as $dataType => $entities) { - if (self::OBJECT === $dataType) { + if ($dataType === self::OBJECT) { $this->jsonBinaryDecoderFormatter->formatBeginObject(); - } else if (self::ARRAY === $dataType) { + } elseif ($dataType === self::ARRAY) { $this->jsonBinaryDecoderFormatter->formatBeginArray(); } foreach ($entities as $i => $entity) { if ($dataType === self::SCALAR) { - - if (null === $entity['value']->getValue()) { + if ($entity['value']->value === null) { $this->jsonBinaryDecoderFormatter->formatValue('null'); - } else if (is_bool($entity['value']->getValue())) { - $this->jsonBinaryDecoderFormatter->formatValueBool($entity['value']->getValue()); + } elseif (is_bool($entity['value']->value)) { + $this->jsonBinaryDecoderFormatter->formatValueBool($entity['value']->value); } else { - $this->jsonBinaryDecoderFormatter->formatValue($entity['value']->getValue()); + $this->jsonBinaryDecoderFormatter->formatValue($entity['value']->value); } continue; } - if ($i !== 0) { $this->jsonBinaryDecoderFormatter->formatNextEntry(); } - if (null !== $entity['name']) { + if ($entity['name'] !== null) { $this->jsonBinaryDecoderFormatter->formatName($entity['name']); } $this->assignValues($entity['value']); } - if (self::OBJECT === $dataType) { + if ($dataType === self::OBJECT) { $this->jsonBinaryDecoderFormatter->formatEndObject(); - } else if (self::ARRAY === $dataType) { + } elseif ($dataType === self::ARRAY) { $this->jsonBinaryDecoderFormatter->formatEndArray(); } } } - /** - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - */ private function parseArrayOrObject(int $type, int $intSize): array { $large = $intSize === self::LARGE_OFFSET_SIZE; @@ -217,7 +211,7 @@ private function parseArrayOrObject(int $type, int $intSize): array for ($i = 0; $i !== $elementCount; ++$i) { $results[] = [ 'name' => $keys[$i] ?? null, - 'value' => $entries[$i] + 'value' => $entries[$i], ]; } @@ -239,10 +233,6 @@ private static function valueEntrySize(bool $large): int return $large ? self::VALUE_ENTRY_SIZE_LARGE : self::VALUE_ENTRY_SIZE_SMALL; } - /** - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - */ private function getOffsetOrInLinedValue(int $bytes, int $intSize, int $valueEntrySize): JsonBinaryDecoderValue { $type = $this->binaryDataReader->readUInt8(); @@ -252,7 +242,7 @@ private function getOffsetOrInLinedValue(int $bytes, int $intSize, int $valueEnt // In binlog format, JSON arrays are fixed width elements, even though type value can be smaller. // In order to properly process this case, we need to move cursor to the next element, which is on position 1 + $valueEntrySize (1 is length of type) - if($type === self::UINT16 || $type === self::INT16) { + if ($type === self::UINT16 || $type === self::INT16) { $readNextBytes = $valueEntrySize - 2 - 1; $this->binaryDataReader->read($readNextBytes); } @@ -272,39 +262,30 @@ private function getOffsetOrInLinedValue(int $bytes, int $intSize, int $valueEnt private static function isInLinedType(int $type, int $intSize): bool { - switch ($type) { - case self::LITERAL: - case self::INT16: - case self::UINT16: - return true; - case self::INT32: - case self::UINT32: - return self::LARGE_OFFSET_SIZE === $intSize; - default: - return false; - } + return match ($type) { + self::LITERAL, self::INT16, self::UINT16 => true, + self::INT32, self::UINT32 => $intSize === self::LARGE_OFFSET_SIZE, + default => false, + }; } - /** - * @throws JsonBinaryDecoderException - */ private function parseScalar(int $type): JsonBinaryDecoderValue { - if (self::LITERAL === $type) { + if ($type === self::LITERAL) { $data = $this->readLiteral(); - } else if (self::INT16 === $type) { + } elseif ($type === self::INT16) { $data = $this->binaryDataReader->readInt16(); - } else if (self::INT32 === $type) { + } elseif ($type === self::INT32) { $data = ($this->binaryDataReader->readInt32()); - } else if (self::INT64 === $type) { + } elseif ($type === self::INT64) { $data = $this->binaryDataReader->readInt64(); - } else if (self::UINT16 === $type) { + } elseif ($type === self::UINT16) { $data = ($this->binaryDataReader->readUInt16()); - } else if (self::UINT64 === $type) { + } elseif ($type === self::UINT64) { $data = ($this->binaryDataReader->readUInt64()); - } else if (self::DOUBLE === $type) { + } elseif ($type === self::DOUBLE) { $data = ($this->binaryDataReader->readDouble()); - } else if (self::STRING === $type) { + } elseif ($type === self::STRING) { $data = ($this->binaryDataReader->read($this->readVariableInt())); } /** * else if (self::OPAQUE === $type) @@ -325,13 +306,13 @@ private function parseScalar(int $type): JsonBinaryDecoderValue private function readLiteral(): ?bool { $literal = ord($this->binaryDataReader->read(BinaryDataReader::UNSIGNED_SHORT_LENGTH)); - if (self::LITERAL_NULL === $literal) { + if ($literal === self::LITERAL_NULL) { return null; } - if (self::LITERAL_TRUE === $literal) { + if ($literal === self::LITERAL_TRUE) { return true; } - if (self::LITERAL_FALSE === $literal) { + if ($literal === self::LITERAL_FALSE) { return false; } @@ -355,27 +336,23 @@ private function readVariableInt(): int return $len; } - /** - * @throws JsonBinaryDecoderException - * @throws BinaryDataReaderException - */ private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue): void { - if (false === $jsonBinaryDecoderValue->isIsResolved()) { - $this->ensureOffset($jsonBinaryDecoderValue->getOffset()); - $this->parseJson($jsonBinaryDecoderValue->getType()); - } else if (null === $jsonBinaryDecoderValue->getValue()) { + if ($jsonBinaryDecoderValue->isResolved === false) { + $this->ensureOffset($jsonBinaryDecoderValue->offset); + $this->parseJson($jsonBinaryDecoderValue->type); + } elseif ($jsonBinaryDecoderValue->value === null) { $this->jsonBinaryDecoderFormatter->formatValueNull(); - } else if (is_bool($jsonBinaryDecoderValue->getValue())) { - $this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue()); - } else if (is_numeric($jsonBinaryDecoderValue->getValue())) { - $this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue()); + } elseif (is_bool($jsonBinaryDecoderValue->value)) { + $this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->value); + } elseif (is_numeric($jsonBinaryDecoderValue->value)) { + $this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->value); } } private function ensureOffset(?int $ensureOffset): void { - if (null === $ensureOffset) { + if ($ensureOffset === null) { return; } $pos = $this->binaryDataReader->getReadBytes(); diff --git a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php index 34c4fff5..89805284 100644 --- a/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php +++ b/src/MySQLReplication/JsonBinaryDecoder/JsonBinaryDecoderValue.php @@ -1,44 +1,16 @@ isResolved = $isResolved; - $this->value = $value; - $this->type = $type; - $this->offset = $offset; - } - - public function getOffset(): ?int - { - return $this->offset; - } - - public function getValue() - { - return $this->value; - } - - public function isIsResolved(): bool - { - return $this->isResolved; - } - - public function getType(): int - { - return $this->type; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/MySQLReplicationFactory.php b/src/MySQLReplication/MySQLReplicationFactory.php index a9e9c835..59e96042 100644 --- a/src/MySQLReplication/MySQLReplicationFactory.php +++ b/src/MySQLReplication/MySQLReplicationFactory.php @@ -1,89 +1,83 @@ validate(); - if (null === $repository) { + if ($repository === null) { $this->connection = DriverManager::getConnection( [ - 'user' => Config::getUser(), - 'password' => Config::getPassword(), - 'host' => Config::getHost(), - 'port' => Config::getPort(), + 'user' => $config->user, + 'password' => $config->password, + 'host' => $config->host, + 'port' => $config->port, 'driver' => 'pdo_mysql', - 'charset' => Config::getCharset() + 'charset' => $config->charset, ] ); $repository = new MySQLRepository($this->connection); } - if (null === $cache) { - $cache = new ArrayCache(); - } + + $cache = $cache ?: new ArrayCache($config->tableCacheSize); + $logger = $logger ?: new NullLogger(); + $socket = $socket ?: new Socket(); $this->eventDispatcher = $eventDispatcher ?: new EventDispatcher(); - if (null === $socket) { - $socket = new Socket(); - } + $this->binLogSocketConnect = new BinLogSocketConnect($repository, $socket, $logger, $config); $this->event = new Event( - new BinLogSocketConnect( - $repository, - $socket - ), + $this->binLogSocketConnect, new RowEventFactory( - $repository, - $cache + new RowEventBuilder( + $repository, + $cache, + $config, + $this->binLogSocketConnect->getBinLogServerInfo(), + $logger + ) ), $this->eventDispatcher, - $cache + $cache, + $config, + $this->binLogSocketConnect->getBinLogServerInfo() ); } @@ -97,36 +91,26 @@ public function unregisterSubscriber(EventSubscriberInterface $eventSubscribers) $this->eventDispatcher->removeSubscriber($eventSubscribers); } - public function getDbConnection(): Connection + public function getDbConnection(): ?Connection { return $this->connection; } - /** - * @throws SocketException - * @throws JsonBinaryDecoderException - * @throws BinaryDataReaderException - * @throws BinLogException - * @throws InvalidArgumentException - * @throws MySQLReplicationException - */ public function run(): void { + /** @phpstan-ignore-next-line */ while (1) { $this->consume(); } } - /** - * @throws MySQLReplicationException - * @throws InvalidArgumentException - * @throws BinLogException - * @throws BinaryDataReaderException - * @throws JsonBinaryDecoderException - * @throws SocketException - */ public function consume(): void { $this->event->consume(); } + + public function getServerInfo(): BinLogServerInfo + { + return $this->binLogSocketConnect->getBinLogServerInfo(); + } } diff --git a/src/MySQLReplication/Repository/FieldDTO.php b/src/MySQLReplication/Repository/FieldDTO.php index 69752994..27bf5f26 100644 --- a/src/MySQLReplication/Repository/FieldDTO.php +++ b/src/MySQLReplication/Repository/FieldDTO.php @@ -1,31 +1,19 @@ columnName = $columnName; - $this->collationName = $collationName; - $this->characterSetName = $characterSetName; - $this->columnComment = $columnComment; - $this->columnType = $columnType; - $this->columnKey = $columnKey; } public static function makeDummy(int $index): self @@ -37,7 +25,7 @@ public static function makeDummy(int $index): self 'CHARACTER_SET_NAME' => null, 'COLUMN_COMMENT' => '', 'COLUMN_TYPE' => 'BLOB', - 'COLUMN_KEY' => '' + 'COLUMN_KEY' => '', ] ); } @@ -53,34 +41,4 @@ public static function makeFromArray(array $field): self $field['COLUMN_KEY'] ); } - - public function getColumnName(): string - { - return $this->columnName; - } - - public function getCollationName(): ?string - { - return $this->collationName; - } - - public function getCharacterSetName(): ?string - { - return $this->characterSetName; - } - - public function getColumnComment(): string - { - return $this->columnComment; - } - - public function getColumnType(): string - { - return $this->columnType; - } - - public function getColumnKey(): string - { - return $this->columnKey; - } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Repository/FieldDTOCollection.php b/src/MySQLReplication/Repository/FieldDTOCollection.php index af7e755e..1d719e8c 100644 --- a/src/MySQLReplication/Repository/FieldDTOCollection.php +++ b/src/MySQLReplication/Repository/FieldDTOCollection.php @@ -1,10 +1,14 @@ + */ class FieldDTOCollection extends ArrayCollection { public static function makeFromArray(array $fields): self @@ -16,4 +20,4 @@ public static function makeFromArray(array $fields): self return $collection; } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Repository/MasterStatusDTO.php b/src/MySQLReplication/Repository/MasterStatusDTO.php index 678774b0..1e20ecd0 100644 --- a/src/MySQLReplication/Repository/MasterStatusDTO.php +++ b/src/MySQLReplication/Repository/MasterStatusDTO.php @@ -1,36 +1,19 @@ position = $position; - $this->file = $file; } public static function makeFromArray(array $data): self { - return new self( - (int)$data['Position'], - (string)$data['File'] - ); - } - - public function getPosition(): int - { - return $this->position; - } - - public function getFile(): string - { - return $this->file; + return new self((string)$data['Position'], (string)$data['File']); } -} \ No newline at end of file +} diff --git a/src/MySQLReplication/Repository/MySQLRepository.php b/src/MySQLReplication/Repository/MySQLRepository.php index 5bcedf14..ee7a779f 100644 --- a/src/MySQLReplication/Repository/MySQLRepository.php +++ b/src/MySQLReplication/Repository/MySQLRepository.php @@ -1,4 +1,5 @@ connection = $connection; + public function __construct( + private Connection $connection + ) { } public function __destruct() @@ -42,25 +41,16 @@ public function getFields(string $database, string $table): FieldDTOCollection ORDINAL_POSITION '; - return FieldDTOCollection::makeFromArray($this->getConnection()->fetchAllAssociative($sql, [$database, $table])); - } - - private function getConnection(): Connection - { - if (false === $this->ping($this->connection)) { - $this->connection->close(); - $this->connection->connect(); - } - - return $this->connection; + return FieldDTOCollection::makeFromArray( + $this->getConnection() + ->fetchAllAssociative($sql, [$database, $table]) + ); } - /** - * @throws Exception - */ public function isCheckSum(): bool { - $res = $this->getConnection()->fetchAssociative('SHOW GLOBAL VARIABLES LIKE "BINLOG_CHECKSUM"'); + $res = $this->getConnection() + ->fetchAssociative('SHOW GLOBAL VARIABLES LIKE "BINLOG_CHECKSUM"'); return isset($res['Value']) && $res['Value'] !== 'NONE'; } @@ -68,24 +58,20 @@ public function isCheckSum(): bool public function getVersion(): string { $r = ''; - $versions = $this->getConnection()->fetchAllAssociative('SHOW VARIABLES LIKE "version%"'); - if (is_array($versions) && 0 !== count($versions)) { - foreach ($versions as $version) { - $r .= $version['Value']; - } + $versions = $this->getConnection() + ->fetchAllAssociative('SHOW VARIABLES LIKE "version%"'); + + foreach ($versions as $version) { + $r .= $version['Value']; } return $r; } - /** - * @inheritDoc - * @throws Exception - * @throws BinLogException - */ public function getMasterStatus(): MasterStatusDTO { - $data = $this->getConnection()->fetchAssociative('SHOW MASTER STATUS'); + $data = $this->getConnection() + ->fetchAssociative('SHOW MASTER STATUS'); if (empty($data)) { throw new BinLogException( MySQLReplicationException::BINLOG_NOT_ENABLED, @@ -101,8 +87,18 @@ public function ping(Connection $connection): bool try { $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); return true; - } catch (Exception $e) { + } catch (Exception) { return false; } } + + private function getConnection(): Connection + { + if ($this->ping($this->connection) === false) { + $this->connection->close(); + $this->connection->connect(); + } + + return $this->connection; + } } diff --git a/src/MySQLReplication/Repository/PingableConnection.php b/src/MySQLReplication/Repository/PingableConnection.php index c6e77ff7..90b42c85 100644 --- a/src/MySQLReplication/Repository/PingableConnection.php +++ b/src/MySQLReplication/Repository/PingableConnection.php @@ -1,4 +1,5 @@ isConnected()) { - socket_shutdown($this->socket); - socket_close($this->socket); - } - } - - public function isConnected(): bool - { - return is_resource($this->socket); + socket_shutdown($this->socket); + socket_close($this->socket); } public function connectToStream(string $host, int $port): void { - $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - if (!$this->socket) { + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if ($socket === false) { throw new SocketException( SocketException::SOCKET_UNABLE_TO_CREATE_MESSAGE . $this->getSocketErrorMessage(), SocketException::SOCKET_UNABLE_TO_CREATE_CODE ); } + $this->socket = $socket; socket_set_block($this->socket); socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1); @@ -37,16 +34,6 @@ public function connectToStream(string $host, int $port): void } } - private function getSocketErrorMessage(): string - { - return socket_strerror($this->getSocketErrorCode()); - } - - private function getSocketErrorCode(): int - { - return socket_last_error(); - } - public function readFromSocket(int $length): string { $received = socket_recv($this->socket, $buf, $length, MSG_WAITALL); @@ -55,7 +42,7 @@ public function readFromSocket(int $length): string } // http://php.net/manual/en/function.socket-recv.php#47182 - if (0 === $received) { + if ($received === 0) { throw new SocketException( SocketException::SOCKET_DISCONNECTED_MESSAGE, SocketException::SOCKET_DISCONNECTED_CODE @@ -74,4 +61,14 @@ public function writeToSocket(string $data): void ); } } -} \ No newline at end of file + + private function getSocketErrorMessage(): string + { + return socket_strerror($this->getSocketErrorCode()); + } + + private function getSocketErrorCode(): int + { + return socket_last_error(); + } +} diff --git a/src/MySQLReplication/Socket/SocketException.php b/src/MySQLReplication/Socket/SocketException.php index 869e04c6..6fd45a9e 100644 --- a/src/MySQLReplication/Socket/SocketException.php +++ b/src/MySQLReplication/Socket/SocketException.php @@ -1,4 +1,5 @@ currentEvent = $eventDTO; - } + protected Connection $connection; + + protected string $database = 'mysqlreplication_test'; + + protected ?EventDTO $currentEvent; + + protected ConfigBuilder $configBuilder; + + private TestEventSubscribers $testEventSubscribers; protected function setUp(): void { @@ -53,26 +38,44 @@ protected function setUp(): void $this->configBuilder = (new ConfigBuilder()) ->withUser('root') - ->withHost('127.0.0.1') + ->withHost('0.0.0.0') ->withPassword('root') ->withPort(3306) - ->withEventsIgnore([ConstEventType::GTID_LOG_EVENT]); + ->withEventsIgnore([ConstEventType::GTID_LOG_EVENT->value]); $this->connect(); + if ($this->mySQLReplicationFactory?->getServerInfo()->versionRevision >= 8 && $this->mySQLReplicationFactory?->getServerInfo()->isGeneric()) { + self::assertInstanceOf(RotateDTO::class, $this->getEvent()); + } self::assertInstanceOf(FormatDescriptionEventDTO::class, $this->getEvent()); self::assertInstanceOf(QueryDTO::class, $this->getEvent()); self::assertInstanceOf(QueryDTO::class, $this->getEvent()); } + protected function tearDown(): void + { + parent::tearDown(); + + $this->disconnect(); + } + + public function setEvent(EventDTO $eventDTO): void + { + $this->currentEvent = $eventDTO; + } + public function connect(): void { $this->mySQLReplicationFactory = new MySQLReplicationFactory($this->configBuilder->build()); $this->testEventSubscribers = new TestEventSubscribers($this); $this->mySQLReplicationFactory->registerSubscriber($this->testEventSubscribers); - $this->connection = $this->mySQLReplicationFactory->getDbConnection(); - + $connection = $this->mySQLReplicationFactory->getDbConnection(); + if ($connection === null) { + throw new RuntimeException('Connection not initialized'); + } + $this->connection = $connection; $this->connection->executeStatement('SET SESSION time_zone = "UTC"'); $this->connection->executeStatement('DROP DATABASE IF EXISTS ' . $this->database); $this->connection->executeStatement('CREATE DATABASE ' . $this->database); @@ -82,33 +85,33 @@ public function connect(): void protected function getEvent(): EventDTO { + if ($this->mySQLReplicationFactory === null) { + throw new RuntimeException('MySQLReplicationFactory not initialized'); + } + // events can be null lets us continue until we find event $this->currentEvent = null; - while (1) { + while ($this->currentEvent === null) { $this->mySQLReplicationFactory->consume(); - if (null !== $this->currentEvent) { - return $this->currentEvent; - } } - } - - protected function tearDown(): void - { - parent::tearDown(); - - $this->disconnect(); + /** @phpstan-ignore-next-line */ + return $this->currentEvent; } protected function disconnect(): void { + if ($this->mySQLReplicationFactory === null) { + return; + } $this->mySQLReplicationFactory->unregisterSubscriber($this->testEventSubscribers); $this->mySQLReplicationFactory = null; - $this->connection = null; } protected function checkForVersion(float $version): bool { - return (float)$this->connection->fetchOne('SELECT VERSION()') < $version; + /** @phpstan-ignore-next-line */ + return $this->mySQLReplicationFactory->getServerInfo() + ->versionRevision < $version; } protected function createAndInsertValue(string $createQuery, string $insertQuery): EventDTO diff --git a/tests/Integration/BasicTest.php b/tests/Integration/BasicTest.php index 43dcd6a1..c3b81cea 100644 --- a/tests/Integration/BasicTest.php +++ b/tests/Integration/BasicTest.php @@ -1,4 +1,5 @@ createAndInsertValue( 'CREATE TABLE test (id INT NOT NULL AUTO_INCREMENT, data VARCHAR (50) NOT NULL, PRIMARY KEY (id))', @@ -42,14 +40,11 @@ public function shouldGetDeleteEvent(): void /** @var DeleteRowsDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(DeleteRowsDTO::class, $event); - self::assertEquals(1, $event->getValues()[0]['id']); - self::assertEquals('Hello World', $event->getValues()[0]['data']); + self::assertEquals(1, $event->values[0]['id']); + self::assertEquals('Hello World', $event->values[0]['data']); } - /** - * @test - */ - public function shouldGetUpdateEvent(): void + public function testShouldGetUpdateEvent(): void { $this->createAndInsertValue( 'CREATE TABLE test (id INT NOT NULL AUTO_INCREMENT, data VARCHAR (50) NOT NULL, PRIMARY KEY (id))', @@ -65,16 +60,13 @@ public function shouldGetUpdateEvent(): void /** @var UpdateRowsDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(UpdateRowsDTO::class, $event); - self::assertEquals(1, $event->getValues()[0]['before']['id']); - self::assertEquals('Hello', $event->getValues()[0]['before']['data']); - self::assertEquals(2, $event->getValues()[0]['after']['id']); - self::assertEquals('World', $event->getValues()[0]['after']['data']); + self::assertEquals(1, $event->values[0]['before']['id']); + self::assertEquals('Hello', $event->values[0]['before']['data']); + self::assertEquals(2, $event->values[0]['after']['id']); + self::assertEquals('World', $event->values[0]['after']['data']); } - /** - * @test - */ - public function shouldGetWriteEventDropTable(): void + public function testShouldGetWriteEventDropTable(): void { $this->connection->executeStatement($createExpected = 'CREATE TABLE `test` (id INTEGER(11))'); $this->connection->executeStatement('INSERT INTO `test` VALUES (1)'); @@ -83,12 +75,12 @@ public function shouldGetWriteEventDropTable(): void /** @var QueryDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(QueryDTO::class, $event); - self::assertEquals($createExpected, $event->getQuery()); + self::assertEquals($createExpected, $event->query); /** @var QueryDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(QueryDTO::class, $event); - self::assertEquals('BEGIN', $event->getQuery()); + self::assertEquals('BEGIN', $event->query); /** @var TableMapDTO $event */ self::assertInstanceOf(TableMapDTO::class, $this->getEvent()); @@ -96,21 +88,18 @@ public function shouldGetWriteEventDropTable(): void /** @var WriteRowsDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(WriteRowsDTO::class, $event); - self::assertEquals([], $event->getValues()); - self::assertEquals(0, $event->getChangedRows()); + self::assertEquals([], $event->values); + self::assertEquals(0, $event->changedRows); self::assertInstanceOf(XidDTO::class, $this->getEvent()); /** @var QueryDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(QueryDTO::class, $event); - self::assertStringContainsString($dropExpected, $event->getQuery()); + self::assertStringContainsString($dropExpected, $event->query); } - /** - * @test - */ - public function shouldGetQueryEventCreateTable(): void + public function testShouldGetQueryEventCreateTable(): void { $this->connection->executeStatement( $createExpected = 'CREATE TABLE test (id INT NOT NULL AUTO_INCREMENT, data VARCHAR (50) NOT NULL, PRIMARY KEY (id))' @@ -119,18 +108,15 @@ public function shouldGetQueryEventCreateTable(): void /** @var QueryDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(QueryDTO::class, $event); - self::assertEquals($createExpected, $event->getQuery()); + self::assertEquals($createExpected, $event->query); } - /** - * @test - */ - public function shouldDropColumn(): void + public function testShouldDropColumn(): void { $this->disconnect(); $this->configBuilder->withEventsOnly( - [ConstEventType::WRITE_ROWS_EVENT_V1, ConstEventType::WRITE_ROWS_EVENT_V2] + [ConstEventType::WRITE_ROWS_EVENT_V1->value, ConstEventType::WRITE_ROWS_EVENT_V2->value] ); $this->connect(); @@ -143,39 +129,40 @@ public function shouldDropColumn(): void /** @var WriteRowsDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(WriteRowsDTO::class, $event); - self::assertEquals(['id' => 1, 'DROPPED_COLUMN_1' => null], $event->getValues()[0]); + self::assertEquals([ + 'id' => 1, + 'DROPPED_COLUMN_1' => null, + ], $event->values[0]); $event = $this->getEvent(); self::assertInstanceOf(WriteRowsDTO::class, $event); - self::assertEquals(['id' => 2], $event->getValues()[0]); + self::assertEquals([ + 'id' => 2, + ], $event->values[0]); } - /** - * @test - */ - public function shouldFilterEvents(): void + public function testShouldFilterEvents(): void { $this->disconnect(); - $this->configBuilder->withEventsOnly([ConstEventType::QUERY_EVENT]); + $this->configBuilder->withEventsOnly([ConstEventType::QUERY_EVENT->value]); $this->connect(); self::assertInstanceOf(QueryDTO::class, $this->getEvent()); self::assertInstanceOf(QueryDTO::class, $this->getEvent()); - $this->connection->executeStatement($createTableExpected = 'CREATE TABLE test (id INTEGER(11), data VARCHAR(50))'); + $this->connection->executeStatement( + $createTableExpected = 'CREATE TABLE test (id INTEGER(11), data VARCHAR(50))' + ); /** @var QueryDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(QueryDTO::class, $event); - self::assertEquals($createTableExpected, $event->getQuery()); + self::assertEquals($createTableExpected, $event->query); } - /** - * @test - */ - public function shouldFilterTables(): void + public function testShouldFilterTables(): void { $expectedTable = 'test_2'; $expectedValue = 'foobar'; @@ -184,7 +171,7 @@ public function shouldFilterTables(): void $this->configBuilder ->withEventsOnly( - [ConstEventType::WRITE_ROWS_EVENT_V1, ConstEventType::WRITE_ROWS_EVENT_V2] + [ConstEventType::WRITE_ROWS_EVENT_V1->value, ConstEventType::WRITE_ROWS_EVENT_V2->value] )->withTablesOnly([$expectedTable]); $this->connect(); @@ -205,20 +192,15 @@ public function shouldFilterTables(): void $event = $this->getEvent(); self::assertInstanceOf(WriteRowsDTO::class, $event); - self::assertEquals($expectedTable, $event->getTableMap()->getTable()); - self::assertEquals($expectedValue, $event->getValues()[0]['data']); + self::assertEquals($expectedTable, $event->tableMap->table); + self::assertEquals($expectedValue, $event->values[0]['data']); } - /** - * @test - */ - public function shouldTruncateTable(): void + public function testShouldTruncateTable(): void { $this->disconnect(); - $this->configBuilder->withEventsOnly( - [ConstEventType::QUERY_EVENT] - ); + $this->configBuilder->withEventsOnly([ConstEventType::QUERY_EVENT->value]); $this->connect(); @@ -230,19 +212,16 @@ public function shouldTruncateTable(): void $this->connection->executeStatement('TRUNCATE TABLE test_truncate_column'); $event = $this->getEvent(); - self::assertSame('CREATE TABLE test_truncate_column (id INTEGER(11), data VARCHAR(50))', $event->getQuery()); + self::assertSame('CREATE TABLE test_truncate_column (id INTEGER(11), data VARCHAR(50))', $event->query); $event = $this->getEvent(); - self::assertSame('BEGIN', $event->getQuery()); + self::assertSame('BEGIN', $event->query); $event = $this->getEvent(); - self::assertSame('TRUNCATE TABLE test_truncate_column', $event->getQuery()); + self::assertSame('TRUNCATE TABLE test_truncate_column', $event->query); } - /** - * @test - */ - public function shouldJsonSetPartialUpdateWithHoles(): void + public function testShouldJsonSetPartialUpdateWithHoles(): void { - if ($this->checkForVersion(5.7) || BinLogServerInfo::isMariaDb()) { + if ($this->checkForVersion(5.7) || $this->mySQLReplicationFactory?->getServerInfo()->isMariaDb()) { self::markTestIncomplete('Only for mysql 5.7 or higher'); } @@ -263,22 +242,16 @@ public function shouldJsonSetPartialUpdateWithHoles(): void $event = $this->getEvent(); self::assertInstanceOf(UpdateRowsDTO::class, $event); - self::assertEquals( - $expected, - $event->getValues()[0]['before']['j'] - ); + self::assertEquals($expected, $event->values[0]['before']['j']); self::assertEquals( '{"age":22,"addr":{"code":100,"detail":{"ab":"970785C8"}},"name":"Alice"}', - $event->getValues()[0]['after']['j'] + $event->values[0]['after']['j'] ); } - /** - * @test - */ - public function shouldJsonRemovePartialUpdateWithHoles(): void + public function testShouldJsonRemovePartialUpdateWithHoles(): void { - if ($this->checkForVersion(5.7) || BinLogServerInfo::isMariaDb()) { + if ($this->checkForVersion(5.7) || $this->mySQLReplicationFactory?->getServerInfo()->isMariaDb()) { self::markTestIncomplete('Only for mysql 5.7 or higher'); } @@ -299,22 +272,16 @@ public function shouldJsonRemovePartialUpdateWithHoles(): void $event = $this->getEvent(); self::assertInstanceOf(UpdateRowsDTO::class, $event); - self::assertEquals( - $expected, - $event->getValues()[0]['before']['j'] - ); + self::assertEquals($expected, $event->values[0]['before']['j']); self::assertEquals( '{"age":22,"addr":{"code":100,"detail":{}},"name":"Alice"}', - $event->getValues()[0]['after']['j'] + $event->values[0]['after']['j'] ); } - /** - * @test - */ - public function shouldJsonReplacePartialUpdateWithHoles(): void + public function testShouldJsonReplacePartialUpdateWithHoles(): void { - if ($this->checkForVersion(5.7) || BinLogServerInfo::isMariaDb()) { + if ($this->checkForVersion(5.7) || $this->mySQLReplicationFactory?->getServerInfo()->isMariaDb()) { self::markTestIncomplete('Only for mysql 5.7 or higher'); } @@ -335,20 +302,14 @@ public function shouldJsonReplacePartialUpdateWithHoles(): void $event = $this->getEvent(); self::assertInstanceOf(UpdateRowsDTO::class, $event); - self::assertEquals( - $expected, - $event->getValues()[0]['before']['j'] - ); + self::assertEquals($expected, $event->values[0]['before']['j']); self::assertEquals( '{"age":22,"addr":{"code":100,"detail":{"ab":"9707"}},"name":"Alice"}', - $event->getValues()[0]['after']['j'] + $event->values[0]['after']['j'] ); } - /** - * @test - */ - public function shouldRoteLog(): void + public function testShouldRotateLog(): void { $this->connection->executeStatement('FLUSH LOGS'); @@ -356,14 +317,14 @@ public function shouldRoteLog(): void self::assertMatchesRegularExpression( '/^[a-z-]+\.[\d]+$/', - $this->getEvent()->getEventInfo()->getBinLogCurrent()->getBinFileName() + $this->getEvent() + ->getEventInfo() + ->binLogCurrent + ->getBinFileName() ); } - /** - * @test - */ - public function shouldUseProvidedEventDispatcher(): void + public function testShouldUseProvidedEventDispatcher(): void { $this->disconnect(); @@ -381,7 +342,7 @@ public function shouldUseProvidedEventDispatcher(): void /** @var QueryDTO $event */ $event = $this->getEvent(); self::assertInstanceOf(QueryDTO::class, $event); - self::assertEquals($createExpected, $event->getQuery()); + self::assertEquals($createExpected, $event->query); } private function connectWithProvidedEventDispatcher(EventDispatcherInterface $eventDispatcher): void @@ -393,14 +354,22 @@ private function connectWithProvidedEventDispatcher(EventDispatcherInterface $ev $eventDispatcher ); - $this->connection = $this->mySQLReplicationFactory->getDbConnection(); + $connection = $this->mySQLReplicationFactory->getDbConnection(); + if ($connection === null) { + throw new RuntimeException('Connection not initialized'); + } + $this->connection = $connection; $this->connection->executeStatement('SET SESSION time_zone = "UTC"'); $this->connection->executeStatement('DROP DATABASE IF EXISTS ' . $this->database); $this->connection->executeStatement('CREATE DATABASE ' . $this->database); $this->connection->executeStatement('USE ' . $this->database); $this->connection->executeStatement('SET SESSION sql_mode = \'\';'); + if ($this->mySQLReplicationFactory->getServerInfo()->versionRevision >= 8 && $this->mySQLReplicationFactory->getServerInfo()->isGeneric()) { + self::assertInstanceOf(RotateDTO::class, $this->getEvent()); + } + self::assertInstanceOf(FormatDescriptionEventDTO::class, $this->getEvent()); self::assertInstanceOf(QueryDTO::class, $this->getEvent()); self::assertInstanceOf(QueryDTO::class, $this->getEvent()); diff --git a/tests/Integration/TestEventSubscribers.php b/tests/Integration/TestEventSubscribers.php index 11585052..586c62a3 100644 --- a/tests/Integration/TestEventSubscribers.php +++ b/tests/Integration/TestEventSubscribers.php @@ -9,15 +9,13 @@ class TestEventSubscribers extends EventSubscribers { - private $baseTest; - - public function __construct(BaseTest $baseTest) - { - $this->baseTest = $baseTest; + public function __construct( + private readonly BaseCase $baseTest + ) { } public function allEvents(EventDTO $event): void { $this->baseTest->setEvent($event); } -} \ No newline at end of file +} diff --git a/tests/Integration/TypesTest.php b/tests/Integration/TypesTest.php index 1d1d29bc..42a83cee 100644 --- a/tests/Integration/TypesTest.php +++ b/tests/Integration/TypesTest.php @@ -1,4 +1,5 @@ createAndInsertValue($create_query, $insert_query); - self::assertEquals(4.2, $event->getValues()[0]['test']); + self::assertEquals(4.2, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDecimalLongValues(): void + public function testShouldBeDecimalLongValues(): void { $create_query = 'CREATE TABLE test (test DECIMAL(20,10))'; $insert_query = 'INSERT INTO test VALUES(9000000123.123456)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertSame($expect = '9000000123.1234560000', $value = $event->getValues()[0]['test']); + self::assertSame($expect = '9000000123.1234560000', $value = $event->values[0]['test']); self::assertSame(strlen($expect), strlen($value)); } - /** - * @test - */ - public function shouldBeDecimalLongValues2(): void + public function testShouldBeDecimalLongValues2(): void { $create_query = 'CREATE TABLE test (test DECIMAL(20,10))'; $insert_query = 'INSERT INTO test VALUES(9000000123.0000012345)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('9000000123.0000012345', $event->getValues()[0]['test']); + self::assertEquals('9000000123.0000012345', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDecimalNegativeValues(): void + public function testShouldBeDecimalNegativeValues(): void { $create_query = 'CREATE TABLE test (test DECIMAL(20,10), test2 DECIMAL(11,4), test3 DECIMAL(40,30))'; $insert_query = 'INSERT INTO test VALUES(-42000.123456, -51.1234, -51.123456789098765432123456789)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('-42000.1234560000', $event->getValues()[0]['test']); - self::assertEquals('-51.1234', $event->getValues()[0]['test2']); - self::assertEquals('-51.123456789098765432123456789000', $event->getValues()[0]['test3']); + self::assertEquals('-42000.1234560000', $event->values[0]['test']); + self::assertEquals('-51.1234', $event->values[0]['test2']); + self::assertEquals('-51.123456789098765432123456789000', $event->values[0]['test3']); } - /** - * @test - */ - public function shouldBeDecimalTwoValues(): void + public function testShouldBeDecimalTwoValues(): void { $create_query = 'CREATE TABLE test ( test DECIMAL(2,1), test2 DECIMAL(20,10) )'; $insert_query = 'INSERT INTO test VALUES(4.2, 42000.123456)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('4.2', $event->getValues()[0]['test']); - self::assertEquals('42000.1234560000', $event->getValues()[0]['test2']); + self::assertEquals('4.2', $event->values[0]['test']); + self::assertEquals('42000.1234560000', $event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeDecimalZeroScale1(): void + public function testShouldBeDecimalZeroScale1(): void { $create_query = 'CREATE TABLE test (test DECIMAL(23,0))'; $insert_query = 'INSERT INTO test VALUES(10)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('10', $event->getValues()[0]['test']); + self::assertEquals('10', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDecimalZeroScale2(): void + public function testShouldBeDecimalZeroScale2(): void { $create_query = 'CREATE TABLE test (test DECIMAL(23,0))'; $insert_query = 'INSERT INTO test VALUES(12345678912345678912345)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('12345678912345678912345', $event->getValues()[0]['test']); + self::assertEquals('12345678912345678912345', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDecimalZeroScale3(): void + public function testShouldBeDecimalZeroScale3(): void { $create_query = 'CREATE TABLE test (test DECIMAL(23,0))'; $insert_query = 'INSERT INTO test VALUES(100000.0)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('100000', $event->getValues()[0]['test']); + self::assertEquals('100000', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDecimalZeroScale4(): void + public function testShouldBeDecimalZeroScale4(): void { $create_query = 'CREATE TABLE test (test DECIMAL(23,0))'; $insert_query = 'INSERT INTO test VALUES(-100000.0)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('-100000', $event->getValues()[0]['test']); + self::assertEquals('-100000', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDecimalZeroScale5(): void + public function testShouldBeDecimalZeroScale5(): void { $create_query = 'CREATE TABLE test (test DECIMAL(23,0))'; $insert_query = 'INSERT INTO test VALUES(-1234567891234567891234)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('-1234567891234567891234', $event->getValues()[0]['test']); + self::assertEquals('-1234567891234567891234', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeTinyInt(): void + public function testShouldBeTinyInt(): void { $create_query = 'CREATE TABLE test (id TINYINT UNSIGNED NOT NULL, test TINYINT)'; $insert_query = 'INSERT INTO test VALUES(255, -128)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(255, $event->getValues()[0]['id']); - self::assertEquals(-128, $event->getValues()[0]['test']); + self::assertEquals(255, $event->values[0]['id']); + self::assertEquals(-128, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeMapsToBooleanTrue(): void + public function testShouldBeMapsToBooleanTrue(): void { $create_query = 'CREATE TABLE test (id TINYINT UNSIGNED NOT NULL, test BOOLEAN)'; $insert_query = 'INSERT INTO test VALUES(1, TRUE)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(1, $event->getValues()[0]['id']); - self::assertEquals(1, $event->getValues()[0]['test']); + self::assertEquals(1, $event->values[0]['id']); + self::assertEquals(1, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeMapsToBooleanFalse(): void + public function testShouldBeMapsToBooleanFalse(): void { $create_query = 'CREATE TABLE test (id TINYINT UNSIGNED NOT NULL, test BOOLEAN)'; $insert_query = 'INSERT INTO test VALUES(1, FALSE)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(1, $event->getValues()[0]['id']); - self::assertEquals(0, $event->getValues()[0]['test']); + self::assertEquals(1, $event->values[0]['id']); + self::assertEquals(0, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeMapsToNone(): void + public function testShouldBeMapsToNone(): void { $create_query = 'CREATE TABLE test (id TINYINT UNSIGNED NOT NULL, test BOOLEAN)'; $insert_query = 'INSERT INTO test VALUES(1, NULL)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(1, $event->getValues()[0]['id']); - self::assertEquals(null, $event->getValues()[0]['test']); + self::assertEquals(1, $event->values[0]['id']); + self::assertEquals(null, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeMapsToShort(): void + public function testShouldBeMapsToShort(): void { $create_query = 'CREATE TABLE test (id SMALLINT UNSIGNED NOT NULL, test SMALLINT)'; $insert_query = 'INSERT INTO test VALUES(65535, -32768)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(65535, $event->getValues()[0]['id']); - self::assertEquals(-32768, $event->getValues()[0]['test']); + self::assertEquals(65535, $event->values[0]['id']); + self::assertEquals(-32768, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeLong(): void + public function testShouldBeLong(): void { $create_query = 'CREATE TABLE test (id INT UNSIGNED NOT NULL, test INT)'; $insert_query = 'INSERT INTO test VALUES(4294967295, -2147483648)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(4294967295, $event->getValues()[0]['id']); - self::assertEquals(-2147483648, $event->getValues()[0]['test']); + self::assertEquals(4294967295, $event->values[0]['id']); + self::assertEquals(-2147483648, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeFloat(): void + public function testShouldBeFloat(): void { $create_query = 'CREATE TABLE test (id FLOAT NOT NULL, test FLOAT)'; $insert_query = 'INSERT INTO test VALUES(42.42, -84.84)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(42.42, $event->getValues()[0]['id']); - self::assertEquals(-84.84, $event->getValues()[0]['test']); + self::assertEquals(42.42, $event->values[0]['id']); + self::assertEquals(-84.84, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDouble(): void + public function testShouldBeDouble(): void { $create_query = 'CREATE TABLE test (id DOUBLE NOT NULL, test DOUBLE)'; $insert_query = 'INSERT INTO test VALUES(42.42, -84.84)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(42.42, $event->getValues()[0]['id']); - self::assertEquals(-84.84, $event->getValues()[0]['test']); + self::assertEquals(42.42, $event->values[0]['id']); + self::assertEquals(-84.84, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeTimestamp(): void + public function testShouldBeTimestamp(): void { $create_query = 'CREATE TABLE test (test TIMESTAMP);'; $insert_query = 'INSERT INTO test VALUES("1984-12-03 12:33:07")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('1984-12-03 12:33:07', $event->getValues()[0]['test']); + self::assertEquals('1984-12-03 12:33:07', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeTimestampMySQL56(): void + public function testShouldBeTimestampMySQL56(): void { /* * https://mariadb.com/kb/en/library/microseconds-in-mariadb/ * MySQL 5.6 introduced microseconds using a slightly different implementation to MariaDB 5.3. * Since MariaDB 10.1, MariaDB has defaulted to the MySQL format ... */ - if (BinLogServerInfo::isMariaDb() && $this->checkForVersion(10.1)) { + if ($this->mySQLReplicationFactory?->getServerInfo()->isMariaDb() && $this->checkForVersion(10.1)) { self::markTestIncomplete('Only for mariadb 10.1 or higher'); } elseif ($this->checkForVersion(5.6)) { self::markTestIncomplete('Only for mysql 5.6 or higher'); @@ -304,213 +243,167 @@ public function shouldBeTimestampMySQL56(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('1984-12-03 12:33:07', $event->getValues()[0]['test0']); - self::assertEquals('1984-12-03 12:33:07.100000', $event->getValues()[0]['test1']); - self::assertEquals('1984-12-03 12:33:07.120000', $event->getValues()[0]['test2']); - self::assertEquals('1984-12-03 12:33:07.123000', $event->getValues()[0]['test3']); - self::assertEquals('1984-12-03 12:33:07.123400', $event->getValues()[0]['test4']); - self::assertEquals('1984-12-03 12:33:07.123450', $event->getValues()[0]['test5']); - self::assertEquals('1984-12-03 12:33:07.123456', $event->getValues()[0]['test6']); + self::assertEquals('1984-12-03 12:33:07', $event->values[0]['test0']); + self::assertEquals('1984-12-03 12:33:07.100000', $event->values[0]['test1']); + self::assertEquals('1984-12-03 12:33:07.120000', $event->values[0]['test2']); + self::assertEquals('1984-12-03 12:33:07.123000', $event->values[0]['test3']); + self::assertEquals('1984-12-03 12:33:07.123400', $event->values[0]['test4']); + self::assertEquals('1984-12-03 12:33:07.123450', $event->values[0]['test5']); + self::assertEquals('1984-12-03 12:33:07.123456', $event->values[0]['test6']); } - /** - * @test - */ - public function shouldBeLongLong(): void + public function testShouldBeLongLong(): void { $create_query = 'CREATE TABLE test (id BIGINT UNSIGNED NOT NULL, test BIGINT)'; $insert_query = 'INSERT INTO test VALUES(18446744073709551615, -9223372036854775808)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('18446744073709551615', $event->getValues()[0]['id']); - self::assertEquals('-9223372036854775808', $event->getValues()[0]['test']); + self::assertEquals('18446744073709551615', $event->values[0]['id']); + self::assertEquals('-9223372036854775808', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeInt24(): void + public function testShouldBeInt24(): void { $create_query = 'CREATE TABLE test (id MEDIUMINT UNSIGNED NOT NULL, test MEDIUMINT, test2 MEDIUMINT, test3 MEDIUMINT, test4 MEDIUMINT, test5 MEDIUMINT)'; $insert_query = 'INSERT INTO test VALUES(16777215, 8388607, -8388608, 8, -8, 0)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(16777215, $event->getValues()[0]['id']); - self::assertEquals(8388607, $event->getValues()[0]['test']); - self::assertEquals(-8388608, $event->getValues()[0]['test2']); - self::assertEquals(8, $event->getValues()[0]['test3']); - self::assertEquals(-8, $event->getValues()[0]['test4']); - self::assertEquals(0, $event->getValues()[0]['test5']); + self::assertEquals(16777215, $event->values[0]['id']); + self::assertEquals(8388607, $event->values[0]['test']); + self::assertEquals(-8388608, $event->values[0]['test2']); + self::assertEquals(8, $event->values[0]['test3']); + self::assertEquals(-8, $event->values[0]['test4']); + self::assertEquals(0, $event->values[0]['test5']); } - /** - * @test - */ - public function shouldBeDate(): void + public function testShouldBeDate(): void { $create_query = 'CREATE TABLE test (test DATE);'; $insert_query = 'INSERT INTO test VALUES("1984-12-03")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('1984-12-03', $event->getValues()[0]['test']); + self::assertEquals('1984-12-03', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeZeroDate(): void + public function testShouldBeZeroDate(): void { $create_query = 'CREATE TABLE test (id INTEGER, test DATE, test2 DATE);'; $insert_query = 'INSERT INTO test (id, test2) VALUES(1, "0000-01-21")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); - self::assertNull($event->getValues()[0]['test2']); + self::assertNull($event->values[0]['test']); + self::assertNull($event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeZeroMonth(): void + public function testShouldBeZeroMonth(): void { $create_query = 'CREATE TABLE test (id INTEGER, test DATE, test2 DATE);'; $insert_query = 'INSERT INTO test (id, test2) VALUES(1, "2015-00-21")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); - self::assertNull($event->getValues()[0]['test2']); + self::assertNull($event->values[0]['test']); + self::assertNull($event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeZeroDay(): void + public function testShouldBeZeroDay(): void { $create_query = 'CREATE TABLE test (id INTEGER, test DATE, test2 DATE);'; $insert_query = 'INSERT INTO test (id, test2) VALUES(1, "2015-05-00")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); - self::assertNull($event->getValues()[0]['test2']); + self::assertNull($event->values[0]['test']); + self::assertNull($event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeTime(): void + public function testShouldBeTime(): void { $create_query = 'CREATE TABLE test (test TIME);'; $insert_query = 'INSERT INTO test VALUES("12:33:18")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('12:33:18', $event->getValues()[0]['test']); + self::assertEquals('12:33:18', $event->values[0]['test']); } - - /** - * @test - */ - public function shouldBeZeroTime(): void + public function testShouldBeZeroTime(): void { $create_query = 'CREATE TABLE test (id INTEGER, test TIME NOT NULL DEFAULT 0);'; $insert_query = 'INSERT INTO test (id) VALUES(1)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('00:00:00', $event->getValues()[0]['test']); + self::assertEquals('00:00:00', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeDateTime(): void + public function testShouldBeDateTime(): void { $create_query = 'CREATE TABLE test (test DATETIME);'; $insert_query = 'INSERT INTO test VALUES("1984-12-03 12:33:07")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('1984-12-03 12:33:07', $event->getValues()[0]['test']); + self::assertEquals('1984-12-03 12:33:07', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeZeroDateTime(): void + public function testShouldBeZeroDateTime(): void { $create_query = 'CREATE TABLE test (id INTEGER, test DATETIME NOT NULL DEFAULT 0);'; $insert_query = 'INSERT INTO test (id) VALUES(1)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); + self::assertNull($event->values[0]['test']); } - /** - * @test - */ - public function shouldBeBrokenDateTime(): void + public function testShouldBeBrokenDateTime(): void { $create_query = 'CREATE TABLE test (test DATETIME NOT NULL);'; $insert_query = 'INSERT INTO test VALUES("2013-00-00 00:00:00")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); + self::assertNull($event->values[0]['test']); } - /** - * @test - */ - public function shouldReturnNullOnZeroDateDateTime(): void + public function testShouldReturnNullOnZeroDateDateTime(): void { $create_query = 'CREATE TABLE test (test DATETIME NOT NULL);'; $insert_query = 'INSERT INTO test VALUES("0000-00-00 00:00:00")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); + self::assertNull($event->values[0]['test']); } - /** - * @test - */ - public function shouldBeYear(): void + public function testShouldBeYear(): void { $create_query = 'CREATE TABLE test (test YEAR(4), test2 YEAR, test3 YEAR)'; $insert_query = 'INSERT INTO test VALUES(1984, 1984, 0000)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(1984, $event->getValues()[0]['test']); - self::assertEquals(1984, $event->getValues()[0]['test2']); - self::assertNull($event->getValues()[0]['test3']); + self::assertEquals(1984, $event->values[0]['test']); + self::assertEquals(1984, $event->values[0]['test2']); + self::assertNull($event->values[0]['test3']); } - /** - * @test - */ - public function shouldBeVarChar(): void + public function testShouldBeVarChar(): void { $create_query = 'CREATE TABLE test (test VARCHAR(242)) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("Hello")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('Hello', $event->getValues()[0]['test']); + self::assertEquals('Hello', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBe1024CharsLongVarChar(): void + public function testShouldBe1024CharsLongVarChar(): void { $expected = str_repeat('-', 1024); @@ -519,13 +412,10 @@ public function shouldBe1024CharsLongVarChar(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals($expected, $event->getValues()[0]['test']); + self::assertEquals($expected, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeBit(): void + public function testShouldBeBit(): void { $create_query = 'CREATE TABLE test ( test BIT(6), @@ -544,20 +434,17 @@ public function shouldBeBit(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('100010', $event->getValues()[0]['test']); - self::assertEquals('1000101010111000', $event->getValues()[0]['test2']); - self::assertEquals('100010101101', $event->getValues()[0]['test3']); - self::assertEquals('101100111', $event->getValues()[0]['test4']); + self::assertEquals('100010', $event->values[0]['test']); + self::assertEquals('1000101010111000', $event->values[0]['test2']); + self::assertEquals('100010101101', $event->values[0]['test3']); + self::assertEquals('101100111', $event->values[0]['test4']); self::assertEquals( '1101011010110100100111100011010100010100101110111011101011011010', - $event->getValues()[0]['test5'] + $event->values[0]['test5'] ); } - /** - * @test - */ - public function shouldBeEnum(): void + public function testShouldBeEnum(): void { $create_query = 'CREATE TABLE test ( @@ -570,84 +457,68 @@ public function shouldBeEnum(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('ba', $event->getValues()[0]['test']); - self::assertEquals('a', $event->getValues()[0]['test2']); - self::assertEquals('', $event->getValues()[0]['test3']); + self::assertEquals('ba', $event->values[0]['test']); + self::assertEquals('a', $event->values[0]['test2']); + self::assertEquals('', $event->values[0]['test3']); } - /** - * @test - */ - public function shouldBeSet(): void + public function testShouldBeSet(): void { $create_query = 'CREATE TABLE test (test SET("a", "ba", "c"), test2 SET("a", "ba", "c")) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("ba,a,c", "a,c")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(['a', 'ba', 'c'], $event->getValues()[0]['test']); - self::assertEquals(['a', 'c'], $event->getValues()[0]['test2']); + self::assertEquals(['a', 'ba', 'c'], $event->values[0]['test']); + self::assertEquals(['a', 'c'], $event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeTinyBlob(): void + public function testShouldBeTinyBlob(): void { $create_query = 'CREATE TABLE test (test TINYBLOB, test2 TINYTEXT) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("Hello", "World")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('Hello', $event->getValues()[0]['test']); - self::assertEquals('World', $event->getValues()[0]['test2']); + self::assertEquals('Hello', $event->values[0]['test']); + self::assertEquals('World', $event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeMediumBlob(): void + public function testShouldBeMediumBlob(): void { $create_query = 'CREATE TABLE test (test MEDIUMBLOB, test2 MEDIUMTEXT) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("Hello", "World")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('Hello', $event->getValues()[0]['test']); - self::assertEquals('World', $event->getValues()[0]['test2']); + self::assertEquals('Hello', $event->values[0]['test']); + self::assertEquals('World', $event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeNullOnBooleanType(): void + public function testShouldBeNullOnBooleanType(): void { $create_query = 'CREATE TABLE test (test BOOLEAN);'; $insert_query = 'INSERT INTO test VALUES(NULL)'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); + self::assertNull($event->values[0]['test']); } - /** - * @test - */ - public function shouldBeLongBlob(): void + public function testShouldBeLongBlob(): void { $create_query = 'CREATE TABLE test (test LONGBLOB, test2 LONGTEXT) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("Hello", "World")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('Hello', $event->getValues()[0]['test']); - self::assertEquals('World', $event->getValues()[0]['test2']); + self::assertEquals('Hello', $event->values[0]['test']); + self::assertEquals('World', $event->values[0]['test2']); } /** * https://dev.mysql.com/doc/internals/en/mysql-packet.html * https://dev.mysql.com/doc/internals/en/sending-more-than-16mbyte.html - * */ public function shouldBeLongerTextThan16Mb(): void { @@ -659,42 +530,33 @@ public function shouldBeLongerTextThan16Mb(): void $insert_query = 'INSERT INTO test (data) VALUES ("' . $long_text_data . '")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals(strlen($long_text_data), strlen($event->getValues()[0]['data'])); + self::assertEquals(strlen($long_text_data), strlen($event->values[0]['data'])); $long_text_data = null; } - /** - * @test - */ - public function shouldBeBlob(): void + public function testShouldBeBlob(): void { $create_query = 'CREATE TABLE test (test BLOB, test2 TEXT) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("Hello", "World")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('Hello', $event->getValues()[0]['test']); - self::assertEquals('World', $event->getValues()[0]['test2']); + self::assertEquals('Hello', $event->values[0]['test']); + self::assertEquals('World', $event->values[0]['test2']); } - /** - * @test - */ - public function shouldBeString(): void + public function testShouldBeString(): void { $create_query = 'CREATE TABLE test (test CHAR(12)) CHARACTER SET latin1 COLLATE latin1_bin;'; $insert_query = 'INSERT INTO test VALUES("Hello")'; $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals('Hello', $event->getValues()[0]['test']); + self::assertEquals('Hello', $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeGeometry(): void + public function testShouldBeGeometry(): void { $prefix = 'ST_'; if ($this->checkForVersion(5.6)) { @@ -708,14 +570,11 @@ public function shouldBeGeometry(): void self::assertEquals( '000000000101000000000000000000f03f000000000000f03f', - bin2hex($event->getValues()[0]['test']) + bin2hex($event->values[0]['test']) ); } - /** - * @test - */ - public function shouldBeNull(): void + public function testShouldBeNull(): void { $create_query = 'CREATE TABLE test ( test TINYINT NULL DEFAULT NULL, @@ -743,17 +602,14 @@ public function shouldBeNull(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertNull($event->getValues()[0]['test']); - self::assertEquals(-128, $event->getValues()[0]['test2']); - self::assertNull($event->getValues()[0]['test3']); - self::assertEquals(42, $event->getValues()[0]['test7']); - self::assertEquals(84, $event->getValues()[0]['test20']); + self::assertNull($event->values[0]['test']); + self::assertEquals(-128, $event->values[0]['test2']); + self::assertNull($event->values[0]['test3']); + self::assertEquals(42, $event->values[0]['test7']); + self::assertEquals(84, $event->values[0]['test20']); } - /** - * @test - */ - public function shouldBeEncodedLatin1(): void + public function testShouldBeEncodedLatin1(): void { $this->connection->executeStatement('SET CHARSET latin1'); @@ -764,13 +620,10 @@ public function shouldBeEncodedLatin1(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals($string, $event->getValues()[0]['test']); + self::assertEquals($string, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeEncodedUTF8(): void + public function testShouldBeEncodedUTF8(): void { $this->connection->executeStatement('SET CHARSET utf8'); @@ -781,15 +634,12 @@ public function shouldBeEncodedUTF8(): void $event = $this->createAndInsertValue($create_query, $insert_query); - self::assertEquals($string, $event->getValues()[0]['test']); + self::assertEquals($string, $event->values[0]['test']); } - /** - * @test - */ - public function shouldBeJson(): void + public function testShouldBeJson(): void { - if ($this->checkForVersion(5.7) || BinLogServerInfo::isMariaDb()) { + if ($this->checkForVersion(5.7) || $this->mySQLReplicationFactory?->getServerInfo()->isMariaDb()) { self::markTestIncomplete('Only for mysql 5.7 or higher'); } @@ -841,7 +691,7 @@ public function shouldBeJson(): void $event = $this->createAndInsertValue($create_query, $insert_query); - $results = $event->getValues(); + $results = $event->values; self::assertEquals(null, $results[0]['j']); self::assertEquals('{"a":2}', $results[1]['j']); diff --git a/tests/Unit/BaseTest.php b/tests/Unit/BaseTest.php deleted file mode 100644 index 0d6eeb2e..00000000 --- a/tests/Unit/BaseTest.php +++ /dev/null @@ -1,11 +0,0 @@ -getBinaryRead(unpack('H*', $expected)[1])->read(52))); } - private function getBinaryRead($data): BinaryDataReader - { - return new BinaryDataReader($data); - } - - /** - * @test - */ - public function shouldReadCodedBinary(): void + public function testShouldReadCodedBinary(): void { self::assertSame(0, $this->getBinaryRead(pack('C', ''))->readCodedBinary()); self::assertNull($this->getBinaryRead(pack('C', BinaryDataReader::NULL_COLUMN))->readCodedBinary()); - self::assertSame(0, $this->getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_SHORT_COLUMN))->readCodedBinary()); - self::assertSame(0, $this->getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_INT24_COLUMN))->readCodedBinary()); + self::assertSame( + 0, + $this->getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_SHORT_COLUMN))->readCodedBinary() + ); + self::assertSame( + 0, + $this->getBinaryRead(pack('i', BinaryDataReader::UNSIGNED_INT24_COLUMN))->readCodedBinary() + ); } - /** - * @test - */ - public function shouldThrowErrorOnUnknownCodedBinary(): void + public function testShouldThrowErrorOnUnknownCodedBinary(): void { $this->expectException(BinaryDataReaderException::class); - $this->getBinaryRead(pack('i', 255))->readCodedBinary(); + $this->getBinaryRead(pack('i', 255)) + ->readCodedBinary(); } - public function dataProviderForUInt(): array + public static function dataProviderForUInt(): array { return [ [1, pack('c', 1), 1], @@ -55,38 +50,34 @@ public function dataProviderForUInt(): array [4, pack('I', 123123543), 123123543], [5, pack('CI', 71, 2570258120), 657986078791], [6, pack('v3', 2570258120, 2570258120, 2570258120), 7456176998088], - [7, pack('CSI', 66, 7890, 2570258120), 43121775657013826] + [7, pack('CSI', 66, 7890, 2570258120), 43121775657013826], ]; } - /** - * @test - */ - public function shouldReadReadUInt64(): void + public function testShouldReadReadUInt64(): void { - $this->assertSame('18374686483949813760', $this->getBinaryRead(pack('VV', 4278190080, 4278190080))->readUInt64()); + $this->assertSame( + '18374686483949813760', + $this->getBinaryRead(pack('VV', 4278190080, 4278190080)) + ->readUInt64() + ); } - /** - * @dataProvider dataProviderForUInt - * @test - */ - public function shouldReadUIntBySize($size, $data, $expected): void + #[DataProvider('dataProviderForUInt')] + public function testShouldReadUIntBySize(mixed $size, mixed $data, mixed $expected): void { self::assertSame($expected, $this->getBinaryRead($data)->readUIntBySize($size)); } - /** - * @test - */ - public function shouldThrowErrorOnReadUIntBySizeNotSupported(): void + public function testShouldThrowErrorOnReadUIntBySizeNotSupported(): void { $this->expectException(BinaryDataReaderException::class); - $this->getBinaryRead('')->readUIntBySize(32); + $this->getBinaryRead('') + ->readUIntBySize(32); } - public function dataProviderForBeInt(): array + public static function dataProviderForBeInt(): array { return [ [1, pack('c', 4), 4], @@ -97,130 +88,94 @@ public function dataProviderForBeInt(): array ]; } - /** - * @dataProvider dataProviderForBeInt - * @test - */ - public function shouldReadIntBeBySize(int $size, string $data, int $expected): void - { + #[DataProvider('dataProviderForBeInt')] public function testShouldReadIntBeBySize( + int $size, + string $data, + int $expected + ): void { self::assertSame($expected, $this->getBinaryRead($data)->readIntBeBySize($size)); } - /** - * @test - */ - public function shouldThrowErrorOnReadIntBeBySizeNotSupported(): void + public function testShouldThrowErrorOnReadIntBeBySizeNotSupported(): void { $this->expectException(BinaryDataReaderException::class); - $this->getBinaryRead('')->readIntBeBySize(666); + $this->getBinaryRead('') + ->readIntBeBySize(666); } - /** - * @test - */ - public function shouldReadInt16(): void + public function testShouldReadInt16(): void { $expected = 1000; self::assertSame($expected, $this->getBinaryRead(pack('s', $expected))->readInt16()); } - /** - * @test - */ - public function shouldUnreadAdvance(): void + public function testShouldUnreadAdvance(): void { $binaryDataReader = $this->getBinaryRead('123'); - self::assertEquals('123', $binaryDataReader->getData()); + self::assertEquals('123', $binaryDataReader->getBinaryData()); self::assertEquals(0, $binaryDataReader->getReadBytes()); $binaryDataReader->advance(2); - self::assertEquals('3', $binaryDataReader->getData()); + self::assertEquals('3', $binaryDataReader->getBinaryData()); self::assertEquals(2, $binaryDataReader->getReadBytes()); $binaryDataReader->unread('12'); - self::assertEquals('123', $binaryDataReader->getData()); + self::assertEquals('123', $binaryDataReader->getBinaryData()); self::assertEquals(0, $binaryDataReader->getReadBytes()); } - /** - * @test - */ - public function shouldReadInt24(): void + public function testShouldReadInt24(): void { self::assertSame(-6513508, $this->getBinaryRead(pack('C3', -100, -100, -100))->readInt24()); } - /** - * @test - */ - public function shouldReadInt64(): void + public function testShouldReadInt64(): void { self::assertSame('-72057589759737856', $this->getBinaryRead(pack('VV', 4278190080, 4278190080))->readInt64()); } - /** - * @test - */ - public function shouldReadLengthCodedPascalString(): void + public function testShouldReadLengthCodedPascalString(): void { $expected = 255; self::assertSame( $expected, - hexdec( - bin2hex( - $this->getBinaryRead(pack('cc', 1, $expected))->readLengthString(1) - ) - ) + hexdec(bin2hex($this->getBinaryRead(pack('cc', 1, $expected))->readLengthString(1))) ); } - /** - * @test - */ - public function shouldReadInt32(): void + public function testShouldReadInt32(): void { $expected = 777333; self::assertSame($expected, $this->getBinaryRead(pack('i', $expected))->readInt32()); } - - /** - * @test - */ - public function shouldReadFloat(): void + public function testShouldReadFloat(): void { $expected = 0.001; - self::assertSame($expected, $this->getBinaryRead(pack('f', $expected))->readFloat()); + // we need to add round as php have problem with precision in floats + self::assertSame($expected, round($this->getBinaryRead(pack('f', $expected))->readFloat(), 3)); } - /** - * @test - */ - public function shouldReadDouble(): void + public function testShouldReadDouble(): void { $expected = 1321312312.143567586; self::assertSame($expected, $this->getBinaryRead(pack('d', $expected))->readDouble()); } - /** - * @test - */ - public function shouldReadTableId(): void + public function testShouldReadTableId(): void { self::assertSame( '7456176998088', - $this->getBinaryRead(pack('v3', 2570258120, 2570258120, 2570258120))->readTableId() + $this->getBinaryRead(pack('v3', 2570258120, 2570258120, 2570258120)) + ->readTableId() ); } - /** - * @test - */ - public function shouldCheckIsCompleted(): void + public function testShouldCheckIsCompleted(): void { self::assertFalse($this->getBinaryRead('')->isComplete(1)); @@ -229,20 +184,19 @@ public function shouldCheckIsCompleted(): void self::assertTrue($r->isComplete(1)); } - /** - * @test - */ - public function shouldPack64bit(): void + public function testShouldPack64bit(): void { $expected = 9223372036854775807; self::assertSame((string)$expected, $this->getBinaryRead(BinaryDataReader::pack64bit($expected))->readInt64()); } - /** - * @test - */ - public function shouldGetBinaryDataLength(): void + public function testShouldGetBinaryDataLength(): void { self::assertSame(3, $this->getBinaryRead('foo')->getBinaryDataLength()); } -} \ No newline at end of file + + private function getBinaryRead(string $data): BinaryDataReader + { + return new BinaryDataReader($data); + } +} diff --git a/tests/Unit/Cache/ArrayCacheTest.php b/tests/Unit/Cache/ArrayCacheTest.php index 507fa0d1..ad121021 100644 --- a/tests/Unit/Cache/ArrayCacheTest.php +++ b/tests/Unit/Cache/ArrayCacheTest.php @@ -1,4 +1,5 @@ arrayCache = new ArrayCache(); } - /** - * @test - */ - public function shouldGet(): void + public function testShouldGet(): void { $this->arrayCache->set('foo', 'bar'); self::assertSame('bar', $this->arrayCache->get('foo')); } - /** - * @test - */ - public function shouldSet(): void + public function testShouldSet(): void { $this->arrayCache->set('foo', 'bar'); self::assertSame('bar', $this->arrayCache->get('foo')); } - /** - * @test - */ - public function shouldClearCacheOnSet(): void + public function testShouldClearCacheOnSet(): void { - (new ConfigBuilder())->withTableCacheSize(1)->build(); + (new ConfigBuilder())->withTableCacheSize(1) + ->build(); $this->arrayCache->set('foo', 'bar'); $this->arrayCache->set('foo', 'bar'); self::assertSame('bar', $this->arrayCache->get('foo')); } - /** - * @test - */ - public function shouldDelete(): void + public function testShouldDelete(): void { $this->arrayCache->set('foo', 'bar'); $this->arrayCache->delete('foo'); self::assertNull($this->arrayCache->get('foo')); } - /** - * @test - */ - public function shouldClear(): void + public function testShouldClear(): void { $this->arrayCache->set('foo', 'bar'); $this->arrayCache->set('foo1', 'bar1'); @@ -70,41 +57,43 @@ public function shouldClear(): void self::assertNull($this->arrayCache->get('foo')); } - /** - * @test - */ - public function shouldGetMultiple(): void + public function testShouldGetMultiple(): void { - $expect = ['foo' => 'bar', 'foo1' => 'bar1']; + $expect = [ + 'foo' => 'bar', + 'foo1' => 'bar1', + ]; $this->arrayCache->setMultiple($expect); - self::assertSame(['foo' => 'bar'], $this->arrayCache->getMultiple(['foo'])); + self::assertSame([ + 'foo' => 'bar', + ], $this->arrayCache->getMultiple(['foo'])); } - /** - * @test - */ - public function shouldSetMultiple(): void + public function testShouldSetMultiple(): void { - $expect = ['foo' => 'bar', 'foo1' => 'bar1']; + $expect = [ + 'foo' => 'bar', + 'foo1' => 'bar1', + ]; $this->arrayCache->setMultiple($expect); self::assertSame($expect, $this->arrayCache->getMultiple(['foo', 'foo1'])); } - /** - * @test - */ - public function shouldDeleteMultiple(): void + public function testShouldDeleteMultiple(): void { - $expect = ['foo' => 'bar', 'foo1' => 'bar1', 'foo2' => 'bar2']; + $expect = [ + 'foo' => 'bar', + 'foo1' => 'bar1', + 'foo2' => 'bar2', + ]; $this->arrayCache->setMultiple($expect); $this->arrayCache->deleteMultiple(['foo', 'foo1']); - self::assertSame(['foo2' => 'bar2'], $this->arrayCache->getMultiple(['foo2'])); + self::assertSame([ + 'foo2' => 'bar2', + ], $this->arrayCache->getMultiple(['foo2'])); } - /** - * @test - */ - public function shouldHas(): void + public function testShouldHas(): void { self::assertFalse($this->arrayCache->has('foo')); $this->arrayCache->set('foo', 'bar'); diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php index d0b81a30..9e137b18 100644 --- a/tests/Unit/Config/ConfigTest.php +++ b/tests/Unit/Config/ConfigTest.php @@ -1,4 +1,5 @@ '9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1-177592', 'slaveId' => 1, 'binLogFileName' => 'binfile1.bin', - 'binLogPosition' => 666, + 'binLogPosition' => '999', 'eventsOnly' => [], 'eventsIgnore' => [], 'tablesOnly' => ['test_table'], 'databasesOnly' => ['test_database'], 'mariaDbGtid' => '123:123', 'tableCacheSize' => 777, - 'custom' => [['random' => 'data']], + 'custom' => [ + [ + 'random' => 'data', + ], + ], 'heartbeatPeriod' => 69, + 'slaveUuid' => '6c27ed6d-7ee1-11e3-be39-6c626d957cff', ]; $config = new Config( @@ -51,132 +58,139 @@ public function shouldMakeConfig(): void $expected['databasesOnly'], $expected['tableCacheSize'], $expected['custom'], - $expected['heartbeatPeriod'] + $expected['heartbeatPeriod'], + $expected['slaveUuid'] ); - self::assertSame($expected['user'], $config::getUser()); - self::assertSame($expected['host'], $config::getHost()); - self::assertSame($expected['port'], $config::getPort()); - self::assertSame($expected['password'], $config::getPassword()); - self::assertSame($expected['charset'], $config::getCharset()); - self::assertSame($expected['gtid'], $config::getGtid()); - self::assertSame($expected['slaveId'], $config::getSlaveId()); - self::assertSame($expected['binLogFileName'], $config::getBinLogFileName()); - self::assertSame($expected['binLogPosition'], $config::getBinLogPosition()); - self::assertSame($expected['eventsOnly'], $config::getEventsOnly()); - self::assertSame($expected['eventsIgnore'], $config::getEventsIgnore()); - self::assertSame($expected['tablesOnly'], $config::getTablesOnly()); - self::assertSame($expected['mariaDbGtid'], $config::getMariaDbGtid()); - self::assertSame($expected['tableCacheSize'], $config::getTableCacheSize()); - self::assertSame($expected['custom'], $config::getCustom()); - self::assertSame($expected['heartbeatPeriod'], $config::getHeartbeatPeriod()); - self::assertSame($expected['databasesOnly'], $config::getDatabasesOnly()); - - $config::validate(); + self::assertSame($expected['user'], $config->user); + self::assertSame($expected['host'], $config->host); + self::assertSame($expected['port'], $config->port); + self::assertSame($expected['password'], $config->password); + self::assertSame($expected['charset'], $config->charset); + self::assertSame($expected['gtid'], $config->gtid); + self::assertSame($expected['slaveId'], $config->slaveId); + self::assertSame($expected['binLogFileName'], $config->binLogFileName); + self::assertSame($expected['binLogPosition'], $config->binLogPosition); + self::assertSame($expected['eventsOnly'], $config->eventsOnly); + self::assertSame($expected['eventsIgnore'], $config->eventsIgnore); + self::assertSame($expected['tablesOnly'], $config->tablesOnly); + self::assertSame($expected['mariaDbGtid'], $config->mariaDbGtid); + self::assertSame($expected['tableCacheSize'], $config->tableCacheSize); + self::assertSame($expected['custom'], $config->custom); + self::assertSame($expected['heartbeatPeriod'], $config->heartbeatPeriod); + self::assertSame($expected['databasesOnly'], $config->databasesOnly); + self::assertSame($expected['slaveUuid'], $config->slaveUuid); + + $config->validate(); } - /** - * @test - */ - public function shouldCheckDataBasesOnly(): void + public function testShouldCheckDataBasesOnly(): void { - (new ConfigBuilder())->withDatabasesOnly(['boo'])->build(); - self::assertTrue(Config::checkDataBasesOnly('foo')); + $config = (new ConfigBuilder())->withDatabasesOnly(['boo'])->build(); + self::assertTrue($config->checkDataBasesOnly('foo')); - (new ConfigBuilder())->withDatabasesOnly(['foo'])->build(); - self::assertFalse(Config::checkDataBasesOnly('foo')); + $config = (new ConfigBuilder())->withDatabasesOnly(['foo'])->build(); + self::assertFalse($config->checkDataBasesOnly('foo')); - (new ConfigBuilder())->withDatabasesOnly(['test'])->build(); - self::assertFalse(Config::checkDataBasesOnly('test')); + $config = (new ConfigBuilder())->withDatabasesOnly(['test'])->build(); + self::assertFalse($config->checkDataBasesOnly('test')); - (new ConfigBuilder())->withDatabasesOnly(['foo'])->build(); - self::assertTrue(Config::checkDataBasesOnly('bar')); + $config = (new ConfigBuilder())->withDatabasesOnly(['foo'])->build(); + self::assertTrue($config->checkDataBasesOnly('bar')); } - /** - * @test - */ - public function shouldCheckTablesOnly(): void + public function testShouldCheckTablesOnly(): void { - self::assertFalse(Config::checkTablesOnly('foo')); + $config = (new ConfigBuilder())->build(); + self::assertFalse($config->checkTablesOnly('foo')); - (new ConfigBuilder())->withTablesOnly(['foo'])->build(); - self::assertFalse(Config::checkTablesOnly('foo')); + $config = (new ConfigBuilder())->withTablesOnly(['foo'])->build(); + self::assertFalse($config->checkTablesOnly('foo')); - (new ConfigBuilder())->withTablesOnly(['test'])->build(); - self::assertFalse(Config::checkTablesOnly('test')); + $config = (new ConfigBuilder())->withTablesOnly(['test'])->build(); + self::assertFalse($config->checkTablesOnly('test')); - (new ConfigBuilder())->withTablesOnly(['foo'])->build(); - self::assertTrue(Config::checkTablesOnly('bar')); + $config = (new ConfigBuilder())->withTablesOnly(['foo'])->build(); + self::assertTrue($config->checkTablesOnly('bar')); } - /** - * @test - */ - public function shouldCheckEvent(): void + public function testShouldCheckEvent(): void { - self::assertTrue(Config::checkEvent(1)); + $config = (new ConfigBuilder())->build(); + self::assertTrue($config->checkEvent(1)); - (new ConfigBuilder())->withEventsOnly([2])->build(); - self::assertTrue(Config::checkEvent(2)); + $config = (new ConfigBuilder())->withEventsOnly([2])->build(); + self::assertTrue($config->checkEvent(2)); - (new ConfigBuilder())->withEventsOnly([3])->build(); - self::assertFalse(Config::checkEvent(4)); + $config = (new ConfigBuilder())->withEventsOnly([3])->build(); + self::assertFalse($config->checkEvent(4)); - (new ConfigBuilder())->withEventsIgnore([4])->build(); - self::assertFalse(Config::checkEvent(4)); + $config = (new ConfigBuilder())->withEventsIgnore([4])->build(); + self::assertFalse($config->checkEvent(4)); } - public function shouldCheckHeartbeatPeriodProvider(): array + public static function shouldCheckHeartbeatPeriodProvider(): array { - return [ - [0], - [0.0], - [0.001], - [4294967], - [2], - ]; + return [[0], [0.0], [0.001], [4294967], [2]]; } - /** - * @test - * @dataProvider shouldCheckHeartbeatPeriodProvider - */ - public function shouldCheckHeartbeatPeriod($heartbeatPeriod): void - { - $config = (new ConfigBuilder())->withHeartbeatPeriod($heartbeatPeriod)->build(); - $config::validate(); + #[DataProvider('shouldCheckHeartbeatPeriodProvider')] public function testShouldCheckHeartbeatPeriod( + int|float $heartbeatPeriod + ): void { + $config = (new ConfigBuilder())->withHeartbeatPeriod($heartbeatPeriod) + ->build(); + $config->validate(); - self::assertSame((float)$heartbeatPeriod, $config::getHeartbeatPeriod()); + self::assertEquals($heartbeatPeriod, $config->heartbeatPeriod); } - public function shouldValidateProvider(): array + public static function shouldValidateProvider(): array { return [ ['host', 'aaa', ConfigException::IP_ERROR_MESSAGE, ConfigException::IP_ERROR_CODE], ['port', -1, ConfigException::PORT_ERROR_MESSAGE, ConfigException::PORT_ERROR_CODE], ['slaveId', -1, ConfigException::SLAVE_ID_ERROR_MESSAGE, ConfigException::SLAVE_ID_ERROR_CODE], ['gtid', '-1', ConfigException::GTID_ERROR_MESSAGE, ConfigException::GTID_ERROR_CODE], - ['binLogPosition', -1, ConfigException::BIN_LOG_FILE_POSITION_ERROR_MESSAGE, ConfigException::BIN_LOG_FILE_POSITION_ERROR_CODE], - ['tableCacheSize', -1, ConfigException::TABLE_CACHE_SIZE_ERROR_MESSAGE, ConfigException::TABLE_CACHE_SIZE_ERROR_CODE], - ['heartbeatPeriod', 4294968, ConfigException::HEARTBEAT_PERIOD_ERROR_MESSAGE, ConfigException::HEARTBEAT_PERIOD_ERROR_CODE], - ['heartbeatPeriod', -1, ConfigException::HEARTBEAT_PERIOD_ERROR_MESSAGE, ConfigException::HEARTBEAT_PERIOD_ERROR_CODE], + [ + 'binLogPosition', + '-1', + ConfigException::BIN_LOG_FILE_POSITION_ERROR_MESSAGE, + ConfigException::BIN_LOG_FILE_POSITION_ERROR_CODE, + ], + [ + 'tableCacheSize', + -1, + ConfigException::TABLE_CACHE_SIZE_ERROR_MESSAGE, + ConfigException::TABLE_CACHE_SIZE_ERROR_CODE, + ], + [ + 'heartbeatPeriod', + 4294968, + ConfigException::HEARTBEAT_PERIOD_ERROR_MESSAGE, + ConfigException::HEARTBEAT_PERIOD_ERROR_CODE, + ], + [ + 'heartbeatPeriod', + -1, + ConfigException::HEARTBEAT_PERIOD_ERROR_MESSAGE, + ConfigException::HEARTBEAT_PERIOD_ERROR_CODE, + ], ]; } - /** - * @test - * @dataProvider shouldValidateProvider - */ - public function shouldValidate(string $configKey, $configValue, string $expectedMessage, int $expectedCode): void - { + #[DataProvider('shouldValidateProvider')] + public function testShouldValidate( + string $configKey, + mixed $configValue, + string $expectedMessage, + int $expectedCode + ): void { $this->expectException(ConfigException::class); $this->expectExceptionMessage($expectedMessage); $this->expectExceptionCode($expectedCode); /** @var Config $config */ $config = (new ConfigBuilder())->{'with' . strtoupper($configKey)}($configValue)->build(); - $config::validate(); + $config->validate(); } - -} \ No newline at end of file +} diff --git a/tests/Unit/Event/RowEvent/TableMapTest.php b/tests/Unit/Event/RowEvent/TableMapTest.php index 485c2e1f..01cb80a5 100644 --- a/tests/Unit/Event/RowEvent/TableMapTest.php +++ b/tests/Unit/Event/RowEvent/TableMapTest.php @@ -6,14 +6,11 @@ use MySQLReplication\Event\RowEvent\ColumnDTOCollection; use MySQLReplication\Event\RowEvent\TableMap; -use MySQLReplication\Tests\Unit\BaseTest; +use PHPUnit\Framework\TestCase; -class TableMapTest extends BaseTest +class TableMapTest extends TestCase { - /** - * @test - */ - public function shouldMakeTableMap(): void + public function testShouldMakeTableMap(): void { $expected = [ 'database' => 'foo', @@ -23,7 +20,6 @@ public function shouldMakeTableMap(): void 'columnDTOCollection' => new ColumnDTOCollection(), ]; - $tableMap = new TableMap( $expected['database'], $expected['table'], @@ -32,14 +28,14 @@ public function shouldMakeTableMap(): void $expected['columnDTOCollection'] ); - self::assertSame($expected['database'], $tableMap->getDatabase()); - self::assertSame($expected['table'], $tableMap->getTable()); - self::assertSame($expected['tableId'], $tableMap->getTableId()); - self::assertSame($expected['columnsAmount'], $tableMap->getColumnsAmount()); - self::assertSame($expected['columnDTOCollection'], $tableMap->getColumnDTOCollection()); + self::assertSame($expected['database'], $tableMap->database); + self::assertSame($expected['table'], $tableMap->table); + self::assertSame($expected['tableId'], $tableMap->tableId); + self::assertSame($expected['columnsAmount'], $tableMap->columnsAmount); + self::assertSame($expected['columnDTOCollection'], $tableMap->columnDTOCollection); self::assertInstanceOf(\JsonSerializable::class, $tableMap); /** @noinspection JsonEncodingApiUsageInspection */ self::assertSame(json_encode($expected), json_encode($tableMap)); } -} \ No newline at end of file +} diff --git a/tests/Unit/Gtid/GtidCollectionTest.php b/tests/Unit/Gtid/GtidCollectionTest.php index ac3c7893..919a1123 100644 --- a/tests/Unit/Gtid/GtidCollectionTest.php +++ b/tests/Unit/Gtid/GtidCollectionTest.php @@ -6,17 +6,13 @@ use MySQLReplication\Gtid\Gtid; use MySQLReplication\Gtid\GtidCollection; -use MySQLReplication\Gtid\GtidException; -use MySQLReplication\Tests\Unit\BaseTest; +use PHPUnit\Framework\TestCase; -class GtidCollectionTest extends BaseTest +class GtidCollectionTest extends TestCase { - /** - * @var GtidCollection - */ - private $gtidCollection; + private GtidCollection $gtidCollection; - public function setUp(): void + protected function setUp(): void { parent::setUp(); @@ -26,18 +22,12 @@ public function setUp(): void $this->gtidCollection->add(new Gtid('BBBBBBBB-CCCC-FFFF-DDDD-AAAAAAAAAAAA:1')); } - /** - * @test - */ - public function shouldGetEncodedLength(): void + public function testShouldGetEncodedLength(): void { self::assertSame(88, $this->gtidCollection->getEncodedLength()); } - /** - * @test - */ - public function shouldGetEncoded(): void + public function testShouldGetEncoded(): void { self::assertSame( '02000000000000009b1c8d182a7611e5a26b000c2976f3f301000000000000000100000000000000b8b5020000000000bbbbbbbbccccffffddddaaaaaaaaaaaa010000000000000001000000000000000200000000000000', @@ -45,11 +35,7 @@ public function shouldGetEncoded(): void ); } - /** - * @test - * @throws GtidException - */ - public function shouldCreateCollection(): void + public function testShouldCreateCollection(): void { self::assertCount(1, GtidCollection::makeCollectionFromString('9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1-177592')); } diff --git a/tests/Unit/Gtid/GtidTest.php b/tests/Unit/Gtid/GtidTest.php index ecd4fbfc..34b8dad3 100644 --- a/tests/Unit/Gtid/GtidTest.php +++ b/tests/Unit/Gtid/GtidTest.php @@ -1,4 +1,5 @@ getGtid('9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1-177592')->getEncoded())); - self::assertSame('9b1c8d182a7611e5a26b000c2976f3f3010000000000000001000000000000000200000000000000', bin2hex($this->getGtid('9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1')->getEncoded())); - } - - /** - * @throws GtidException - */ - private function getGtid(string $data): Gtid + public function testShouldGetEncoded(): void { - return new Gtid($data); + self::assertSame( + '9b1c8d182a7611e5a26b000c2976f3f301000000000000000100000000000000b8b5020000000000', + bin2hex($this->getGtid('9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1-177592')->getEncoded()) + ); + self::assertSame( + '9b1c8d182a7611e5a26b000c2976f3f3010000000000000001000000000000000200000000000000', + bin2hex($this->getGtid('9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1')->getEncoded()) + ); } - /** - * @test - */ - public function shouldGetEncodedLength(): void + public function testShouldGetEncodedLength(): void { self::assertSame(40, $this->getGtid('9b1c8d18-2a76-11e5-a26b-000c2976f3f3:1-177592')->getEncodedLength()); } - /** - * @test - */ - public function shouldThrowErrorOnIncrrectGtid(): void + public function testShouldThrowErrorOnIncrrectGtid(): void { $this->expectException(GtidException::class); $this->expectExceptionMessage(GtidException::INCORRECT_GTID_MESSAGE); @@ -47,4 +37,9 @@ public function shouldThrowErrorOnIncrrectGtid(): void $this->getGtid('not gtid'); } -} \ No newline at end of file + + private function getGtid(string $data): Gtid + { + return new Gtid($data); + } +} diff --git a/tests/Unit/Repository/MySQLRepositoryTest.php b/tests/Unit/Repository/MySQLRepositoryTest.php index 4fcc0454..5ad88ac9 100644 --- a/tests/Unit/Repository/MySQLRepositoryTest.php +++ b/tests/Unit/Repository/MySQLRepositoryTest.php @@ -1,4 +1,5 @@ connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock(); - $this->connection->method('getDatabasePlatform')->willReturn(new MySQLPlatform()); + $this->connection->method('getDatabasePlatform') + ->willReturn(new MySQLPlatform()); $this->mySQLRepositoryTest = new MySQLRepository($this->connection); } - /** - * @test - */ - public function shouldGetFields(): void + public function testShouldGetFields(): void { $expected = [ [ @@ -46,51 +40,55 @@ public function shouldGetFields(): void 'CHARACTER_SET_NAME' => 'charname', 'COLUMN_COMMENT' => 'colcommnet', 'COLUMN_TYPE' => 'coltype', - 'COLUMN_KEY' => 'colkey' - ] + 'COLUMN_KEY' => 'colkey', + ], ]; - $this->connection->method('fetchAllAssociative')->willReturn($expected); + $this->connection->method('fetchAllAssociative') + ->willReturn($expected); - self::assertEquals(FieldDTOCollection::makeFromArray($expected), $this->mySQLRepositoryTest->getFields('foo', 'bar')); + self::assertEquals( + FieldDTOCollection::makeFromArray($expected), + $this->mySQLRepositoryTest->getFields('foo', 'bar') + ); } - /** - * @test - */ - public function shouldIsCheckSum(): void + public function testShouldIsCheckSum(): void { self::assertFalse($this->mySQLRepositoryTest->isCheckSum()); - $this->connection->method('fetchAssociative')->willReturnOnConsecutiveCalls( - ['Value' => 'CRC32'], - ['Value' => 'NONE'] - ); + $this->connection->method('fetchAssociative') + ->willReturnOnConsecutiveCalls([ + 'Value' => 'CRC32', + ], [ + 'Value' => 'NONE', + ]); self::assertTrue($this->mySQLRepositoryTest->isCheckSum()); self::assertFalse($this->mySQLRepositoryTest->isCheckSum()); } - /** - * @test - */ - public function shouldGetVersion(): void + public function testShouldGetVersion(): void { $expected = [ - ['Value' => 'foo'], - ['Value' => 'bar'], - ['Value' => '123'], + [ + 'Value' => 'foo', + ], + [ + 'Value' => 'bar', + ], + [ + 'Value' => '123', + ], ]; - $this->connection->method('fetchAllAssociative')->willReturn($expected); + $this->connection->method('fetchAllAssociative') + ->willReturn($expected); self::assertEquals('foobar123', $this->mySQLRepositoryTest->getVersion()); } - /** - * @test - */ - public function shouldGetMasterStatus(): void + public function testShouldGetMasterStatus(): void { $expected = [ 'File' => 'mysql-bin.000002', @@ -100,31 +98,19 @@ public function shouldGetMasterStatus(): void 'Executed_Gtid_Set' => '041de05f-a36a-11e6-bc73-000c2976f3f3:1-8023', ]; - $this->connection->method('fetchAssociative')->willReturn($expected); + $this->connection->method('fetchAssociative') + ->willReturn($expected); self::assertEquals(MasterStatusDTO::makeFromArray($expected), $this->mySQLRepositoryTest->getMasterStatus()); } - /** - * @test - */ - public function shouldDestroy(): void - { - $this->mySQLRepositoryTest = null; - self::assertTrue(true); - } - - /** - * @test - */ - public function shouldReconnect(): void + public function testShouldReconnect(): void { // just to cover private getConnection - $this->connection->method('executeQuery')->willReturnCallback( - static function () { + $this->connection->method('executeQuery') + ->willReturnCallback(static function () { throw new Exception(''); - } - ); + }); $this->mySQLRepositoryTest->isCheckSum(); self::assertTrue(true); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1bb43b88..72d22984 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,7 @@ -