From 9cf2f312e543cd137696d3341c391e892679e1bf Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Mon, 22 Aug 2022 16:14:09 +0300 Subject: [PATCH 01/32] DumpSettings class --- src/DumpSettings.php | 160 ++++++++++++++++++ src/Mysqldump.php | 235 ++++++++++----------------- src/TypeAdapter/TypeAdapterMysql.php | 43 +++-- tests/scripts/test.php | 3 +- 4 files changed, 276 insertions(+), 165 deletions(-) create mode 100644 src/DumpSettings.php diff --git a/src/DumpSettings.php b/src/DumpSettings.php new file mode 100644 index 00000000..addca648 --- /dev/null +++ b/src/DumpSettings.php @@ -0,0 +1,160 @@ + [], + 'exclude-tables' => [], + 'include-views' => [], + 'compress' => 'None', + 'init_commands' => [], + 'no-data' => [], + 'if-not-exists' => false, + 'reset-auto-increment' => false, + 'add-drop-database' => false, + 'add-drop-table' => false, + 'add-drop-trigger' => true, + 'add-locks' => true, + 'complete-insert' => false, + 'databases' => false, + 'default-character-set' => self::UTF8, + 'disable-keys' => true, + 'extended-insert' => true, + 'events' => false, + 'hex-blob' => true, /* faster than escaped content */ + 'insert-ignore' => false, + 'net_buffer_length' => 1000000, + 'no-autocommit' => true, + 'no-create-info' => false, + 'lock-tables' => true, + 'routines' => false, + 'single-transaction' => true, + 'skip-triggers' => false, + 'skip-tz-utc' => false, + 'skip-comments' => false, + 'skip-dump-date' => false, + 'skip-definer' => false, + 'where' => '', + /* deprecated */ + 'disable-foreign-keys-check' => true + ]; + private array $settings; + + /** + * @throws Exception + */ + public function __construct(array $settings) + { + $this->settings = array_replace_recursive(self::$defaults, $settings); + + $this->settings['init_commands'][] = "SET NAMES " . $this->get('default-character-set'); + + if (false === $this->settings['skip-tz-utc']) { + $this->settings['init_commands'][] = "SET TIME_ZONE='+00:00'"; + } + + $diff = array_diff(array_keys($this->settings), array_keys(self::$defaults)); + + if (count($diff) > 0) { + throw new Exception("Unexpected value in dumpSettings: (" . implode(",", $diff) . ")"); + } + + if (!is_array($this->settings['include-tables']) || !is_array($this->settings['exclude-tables'])) { + throw new Exception('Include-tables and exclude-tables should be arrays'); + } + + // If no include-views is passed in, dump the same views as tables, mimic mysqldump behaviour. + if (!isset($settings['include-views'])) { + $this->settings['include-views'] = $this->settings['include-tables']; + } + } + + public function getCompressMethod(): string + { + return $this->settings['compress'] ?? CompressManagerFactory::NONE; + } + + public function getDefaults(): array + { + return self::$defaults; + } + + public function getExcludedTables(): array + { + return $this->settings['exclude-tables'] ?? []; + } + + public function getIncludedTables(): array + { + return $this->settings['include-tables'] ?? []; + } + + public function getIncludedViews(): array + { + return $this->settings['include-views'] ?? []; + } + + public function getInitCommands(): array + { + return $this->settings['init_commands'] ?? []; + } + + public function getNetBufferLength(): int + { + return $this->settings['net_buffer_length']; + } + + public function getNoData(): array + { + return $this->settings['no-data'] ?? []; + } + + public function isEnabled(string $option): bool + { + return isset($this->settings[$option]) && $this->settings[$option] === true; + } + + public function setCompleteInsert(bool $value = true) + { + $this->settings['complete-insert'] = $value; + } + + public function skipComments(): bool + { + return $this->isEnabled('skip-comments'); + } + + public function skipDefiner(): bool + { + return $this->isEnabled('skip-definer'); + } + + public function skipDumpDate(): bool + { + return $this->isEnabled('skip-dump-date'); + } + + public function skipTriggers(): bool + { + return $this->isEnabled('skip-triggers'); + } + + public function skipTzUtc(): bool + { + return $this->isEnabled('skip-tz-utc'); + } + + public function get(string $option): string + { + return (string) $this->settings[$option]; + } +} diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 018b58dd..0570f400 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -3,7 +3,7 @@ /** * PHP version of mysqldump cli that comes with MySQL. * - * Tags: mysql mysqldump pdo php7 php5 database php sql mariadb mysql-backup. + * Tags: mysql mysqldump pdo php7 php8 database php sql mariadb mysql-backup. * * @category Library * @package Druidfi\Mysqldump @@ -25,13 +25,6 @@ class Mysqldump { - // Same as mysqldump. - const MAXLINESIZE = 1000000; - - // List of available connection strings. - const UTF8 = 'utf8'; - const UTF8MB4 = 'utf8mb4'; - // Database private string $dsn; private ?string $user; @@ -48,7 +41,7 @@ class Mysqldump 'mysql' => TypeAdapterMysql::class, ]; - private array $dumpSettings; + private DumpSettings $settings; private array $tableColumnTypes = []; private $transformTableRowCallable; private $transformColumnValueCallable; @@ -70,13 +63,12 @@ class Mysqldump private array $tableLimits = []; /** - * Constructor of Mysqldump. Note that in the case of an SQLite database - * connection, the filename must be in the $db parameter. + * Constructor of Mysqldump. * * @param string $dsn PDO DSN connection string * @param string|null $user SQL account username * @param string|null $pass SQL account password - * @param array $dumpSettings SQL database settings + * @param array $settings SQL database settings * @param array $pdoOptions PDO configured attributes * @throws Exception */ @@ -84,85 +76,29 @@ public function __construct( string $dsn = '', ?string $user = null, ?string $pass = null, - array $dumpSettings = [], + array $settings = [], array $pdoOptions = [] ) { - $dumpSettingsDefault = [ - 'include-tables' => [], - 'exclude-tables' => [], - 'include-views' => [], - 'compress' => CompressManagerFactory::NONE, - 'init_commands' => [], - 'no-data' => [], - 'if-not-exists' => false, - 'reset-auto-increment' => false, - 'add-drop-database' => false, - 'add-drop-table' => false, - 'add-drop-trigger' => true, - 'add-locks' => true, - 'complete-insert' => false, - 'databases' => false, - 'default-character-set' => self::UTF8, - 'disable-keys' => true, - 'extended-insert' => true, - 'events' => false, - 'hex-blob' => true, /* faster than escaped content */ - 'insert-ignore' => false, - 'net_buffer_length' => self::MAXLINESIZE, - 'no-autocommit' => true, - 'no-create-info' => false, - 'lock-tables' => true, - 'routines' => false, - 'single-transaction' => true, - 'skip-triggers' => false, - 'skip-tz-utc' => false, - 'skip-comments' => false, - 'skip-dump-date' => false, - 'skip-definer' => false, - 'where' => '', - /* deprecated */ - 'disable-foreign-keys-check' => true - ]; + $this->parseDsn($dsn); + + $this->user = $user; + $this->pass = $pass; + $this->settings = new DumpSettings($settings); $pdoOptionsDefault = [ PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]; - $this->user = $user; - $this->pass = $pass; - $this->parseDsn($dsn); - // This drops MYSQL dependency, only use the constant if it's defined. if ('mysql' === $this->dbType) { $pdoOptionsDefault[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false; } $this->pdoOptions = array_replace_recursive($pdoOptionsDefault, $pdoOptions); - $this->dumpSettings = array_replace_recursive($dumpSettingsDefault, $dumpSettings); - $this->dumpSettings['init_commands'][] = "SET NAMES ".$this->dumpSettings['default-character-set']; - - if (false === $this->dumpSettings['skip-tz-utc']) { - $this->dumpSettings['init_commands'][] = "SET TIME_ZONE='+00:00'"; - } - - $diff = array_diff(array_keys($this->dumpSettings), array_keys($dumpSettingsDefault)); - - if (count($diff) > 0) { - throw new Exception("Unexpected value in dumpSettings: (".implode(",", $diff).")"); - } - - if (!is_array($this->dumpSettings['include-tables']) || !is_array($this->dumpSettings['exclude-tables'])) { - throw new Exception('Include-tables and exclude-tables should be arrays'); - } - - // If no include-views is passed in, dump the same views as tables, mimic mysqldump behaviour. - if (!isset($dumpSettings['include-views'])) { - $this->dumpSettings['include-views'] = $this->dumpSettings['include-tables']; - } // Create a new compressManager to manage compressed output - $this->io = CompressManagerFactory::create($this->dumpSettings['compress']); + $this->io = CompressManagerFactory::create($this->settings->getCompressMethod()); } /** @@ -186,8 +122,8 @@ public function getTableWhere(string $tableName) { if (!empty($this->tableWheres[$tableName])) { return $this->tableWheres[$tableName]; - } elseif ($this->dumpSettings['where']) { - return $this->dumpSettings['where']; + } elseif ($this->settings->get('where')) { + return $this->settings->get('where'); } return false; @@ -286,7 +222,7 @@ private function connect() /** @var TypeAdapterInterface $typeAdapterClass */ $typeAdapterClass = $this->typeAdapters[$this->dbType]; - $this->db = new $typeAdapterClass($this->conn, $this->dumpSettings); + $this->db = new $typeAdapterClass($this->conn, $this->settings); } /** @@ -311,17 +247,17 @@ public function start(?string $filename = '') $this->io->open($destination); // Write some basic info to output file - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $this->io->write($this->getDumpFileHeader()); } // Store server settings and use saner defaults to dump $this->io->write($this->db->backupParameters()); - if ($this->dumpSettings['databases']) { + if ($this->settings->isEnabled('databases')) { $this->io->write($this->db->getDatabaseHeader($this->dbName)); - if ($this->dumpSettings['add-drop-database']) { + if ($this->settings->isEnabled('add-drop-database')) { $this->io->write($this->db->addDropDatabase($this->dbName)); } } @@ -334,14 +270,14 @@ public function start(?string $filename = '') $this->getDatabaseStructureFunctions(); $this->getDatabaseStructureEvents(); - if ($this->dumpSettings['databases']) { + if ($this->settings->isEnabled('databases')) { $this->io->write($this->db->databases($this->dbName)); } // If there still are some tables/views in include-tables array, that means that some tables or views weren't // found. Give proper error and exit. This check will be removed once include-tables supports regexps. - if (0 < count($this->dumpSettings['include-tables'])) { - $name = implode(',', $this->dumpSettings['include-tables']); + if (0 < count($this->settings->getIncludedTables())) { + $name = implode(',', $this->settings->getIncludedTables()); $message = sprintf("Table '%s' not found in database", $name); throw new Exception($message); } @@ -357,7 +293,7 @@ public function start(?string $filename = '') $this->io->write($this->db->restoreParameters()); // Write some stats to output file. - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $this->io->write($this->getDumpFileFooter()); } @@ -384,7 +320,7 @@ private function getDumpFileHeader(): string $header .= "-- Server version \t". $version . PHP_EOL; } - if (!$this->dumpSettings['skip-dump-date']) { + if (!$this->settings->skipDumpDate()) { $header .= "-- Date: ".date('r'). PHP_EOL . PHP_EOL; } @@ -398,7 +334,7 @@ private function getDumpFileFooter(): string { $footer = '-- Dump completed'; - if (!$this->dumpSettings['skip-dump-date']) { + if (!$this->settings->skipDumpDate()) { $footer .= ' on: '.date('r'); } @@ -412,8 +348,10 @@ private function getDumpFileFooter(): string */ private function getDatabaseStructureTables() { + $includedTables = $this->settings->getIncludedTables(); + // Listing all tables from database - if (empty($this->dumpSettings['include-tables'])) { + if (empty($includedTables)) { // include all tables for now, blacklisting happens later foreach ($this->conn->query($this->db->showTables($this->dbName)) as $row) { $this->tables[] = current($row); @@ -421,10 +359,10 @@ private function getDatabaseStructureTables() } else { // include only the tables mentioned in include-tables foreach ($this->conn->query($this->db->showTables($this->dbName)) as $row) { - if (in_array(current($row), $this->dumpSettings['include-tables'], true)) { + if (in_array(current($row), $includedTables, true)) { $this->tables[] = current($row); - $elem = array_search(current($row), $this->dumpSettings['include-tables']); - unset($this->dumpSettings['include-tables'][$elem]); + $elem = array_search(current($row), $includedTables); + unset($includedTables[$elem]); } } } @@ -435,8 +373,10 @@ private function getDatabaseStructureTables() */ private function getDatabaseStructureViews() { + $includedViews = $this->settings->getIncludedViews(); + // Listing all views from database - if (empty($this->dumpSettings['include-views'])) { + if (empty($includedViews)) { // include all views for now, blacklisting happens later foreach ($this->conn->query($this->db->showViews($this->dbName)) as $row) { $this->views[] = current($row); @@ -444,10 +384,10 @@ private function getDatabaseStructureViews() } else { // include only the tables mentioned in include-tables foreach ($this->conn->query($this->db->showViews($this->dbName)) as $row) { - if (in_array(current($row), $this->dumpSettings['include-views'], true)) { + if (in_array(current($row), $includedViews, true)) { $this->views[] = current($row); - $elem = array_search(current($row), $this->dumpSettings['include-views']); - unset($this->dumpSettings['include-views'][$elem]); + $elem = array_search(current($row), $includedViews); + unset($includedViews[$elem]); } } } @@ -459,7 +399,7 @@ private function getDatabaseStructureViews() private function getDatabaseStructureTriggers() { // Listing all triggers from database - if (false === $this->dumpSettings['skip-triggers']) { + if (!$this->settings->skipTriggers()) { foreach ($this->conn->query($this->db->showTriggers($this->dbName)) as $row) { $this->triggers[] = $row['Trigger']; } @@ -472,7 +412,7 @@ private function getDatabaseStructureTriggers() private function getDatabaseStructureProcedures() { // Listing all procedures from database - if ($this->dumpSettings['routines']) { + if ($this->settings->isEnabled('routines')) { foreach ($this->conn->query($this->db->showProcedures($this->dbName)) as $row) { $this->procedures[] = $row['procedure_name']; } @@ -485,7 +425,7 @@ private function getDatabaseStructureProcedures() private function getDatabaseStructureFunctions() { // Listing all functions from database - if ($this->dumpSettings['routines']) { + if ($this->settings->isEnabled('routines')) { foreach ($this->conn->query($this->db->showFunctions($this->dbName)) as $row) { $this->functions[] = $row['function_name']; } @@ -498,7 +438,7 @@ private function getDatabaseStructureFunctions() private function getDatabaseStructureEvents() { // Listing all events from database - if ($this->dumpSettings['events']) { + if ($this->settings->isEnabled('events')) { foreach ($this->conn->query($this->db->showEvents($this->dbName)) as $row) { $this->events[] = $row['event_name']; } @@ -532,16 +472,16 @@ private function exportTables() { // Exporting tables one by one foreach ($this->tables as $table) { - if ($this->matches($table, $this->dumpSettings['exclude-tables'])) { + if ($this->matches($table, $this->settings->getExcludedTables())) { continue; } $this->getTableStructure($table); + $no_data = $this->settings->isEnabled('no-data'); - if (false === $this->dumpSettings['no-data']) { // don't break compatibility with old trigger + if (!$no_data) { // don't break compatibility with old trigger $this->listValues($table); - } elseif (true === $this->dumpSettings['no-data'] - || $this->matches($table, $this->dumpSettings['no-data'])) { + } elseif ($no_data || $this->matches($table, $this->settings->getNoData())) { continue; } else { $this->listValues($table); @@ -554,10 +494,10 @@ private function exportTables() */ private function exportViews() { - if (false === $this->dumpSettings['no-create-info']) { + if (false === $this->settings->isEnabled('no-create-info')) { // Exporting views one by one foreach ($this->views as $view) { - if ($this->matches($view, $this->dumpSettings['exclude-tables'])) { + if ($this->matches($view, $this->settings->getExcludedTables())) { continue; } @@ -566,7 +506,7 @@ private function exportViews() } foreach ($this->views as $view) { - if ($this->matches($view, $this->dumpSettings['exclude-tables'])) { + if ($this->matches($view, $this->settings->getExcludedTables())) { continue; } @@ -619,17 +559,19 @@ private function exportEvents() * Table structure extractor. * * @param string $tableName Name of table to export - * @TODO move specific mysql code to typeAdapter */ private function getTableStructure(string $tableName) { - if (!$this->dumpSettings['no-create-info']) { + if (!$this->settings->isEnabled('no-create-info')) { $ret = ''; - if (!$this->dumpSettings['skip-comments']) { - $ret = "--".PHP_EOL. - "-- Table structure for table `$tableName`".PHP_EOL. - "--".PHP_EOL.PHP_EOL; + if (!$this->settings->skipComments()) { + $ret = sprintf( + "--".PHP_EOL. + "-- Table structure for table `%s`".PHP_EOL. + "--".PHP_EOL.PHP_EOL, + $tableName + ); } $stmt = $this->db->showCreateTable($tableName); @@ -637,10 +579,8 @@ private function getTableStructure(string $tableName) foreach ($this->conn->query($stmt) as $r) { $this->io->write($ret); - if ($this->dumpSettings['add-drop-table']) { - $this->io->write( - $this->db->dropTable($tableName) - ); + if ($this->settings->isEnabled('add-drop-table')) { + $this->io->write($this->db->dropTable($tableName)); } $this->io->write($this->db->createTable($r)); @@ -686,7 +626,7 @@ private function getTableColumnTypes(string $tableName): array */ private function getViewStructureTable(string $viewName) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = ( '--' . PHP_EOL . sprintf('-- Stand-In structure for view `%s`', $viewName) . PHP_EOL . @@ -700,7 +640,7 @@ private function getViewStructureTable(string $viewName) // create views as tables, to resolve dependencies foreach ($this->conn->query($stmt) as $r) { - if ($this->dumpSettings['add-drop-table']) { + if ($this->settings->isEnabled('add-drop-table')) { $this->io->write($this->db->dropView($viewName)); } @@ -742,7 +682,7 @@ public function createStandInTable(string $viewName): string */ private function getViewStructureView(string $viewName) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = sprintf( "--". PHP_EOL. "-- View structure for view `%s`". PHP_EOL. @@ -775,7 +715,7 @@ private function getTriggerStructure(string $triggerName) $stmt = $this->db->showCreateTrigger($triggerName); foreach ($this->conn->query($stmt) as $r) { - if ($this->dumpSettings['add-drop-trigger']) { + if ($this->settings->isEnabled('add-drop-trigger')) { $this->io->write($this->db->addDropTrigger($triggerName)); } @@ -792,7 +732,7 @@ private function getTriggerStructure(string $triggerName) */ private function getProcedureStructure(string $procedureName) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = "--".PHP_EOL. "-- Dumping routines for database '".$this->dbName."'".PHP_EOL. "--".PHP_EOL.PHP_EOL; @@ -815,7 +755,7 @@ private function getProcedureStructure(string $procedureName) */ private function getFunctionStructure(string $functionName) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = "--".PHP_EOL. "-- Dumping routines for database '".$this->dbName."'".PHP_EOL. "--".PHP_EOL.PHP_EOL; @@ -839,7 +779,7 @@ private function getFunctionStructure(string $functionName) */ private function getEventStructure(string $eventName) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = "--".PHP_EOL. "-- Dumping events for database '".$this->dbName."'".PHP_EOL. "--".PHP_EOL.PHP_EOL; @@ -888,7 +828,7 @@ private function escape(?string $colValue, array $colType) { if (is_null($colValue)) { return 'NULL'; - } elseif ($this->dumpSettings['hex-blob'] && $colType['is_blob']) { + } elseif ($this->settings->isEnabled('hex-blob') && $colType['is_blob']) { if ($colType['type'] == 'bit' || !empty($colValue)) { return sprintf('0x%s', $colValue); } else { @@ -934,7 +874,7 @@ private function listValues(string $tableName) $colStmt = $this->getColumnStmt($tableName); // colNames is used to get the name of the columns when using complete-insert - if ($this->dumpSettings['complete-insert']) { + if ($this->settings->isEnabled('complete-insert')) { $colNames = $this->getColumnNames($tableName); } @@ -956,14 +896,15 @@ private function listValues(string $tableName) $resultSet = $this->conn->query($stmt); $resultSet->setFetchMode(PDO::FETCH_ASSOC); - $ignore = $this->dumpSettings['insert-ignore'] ? ' IGNORE' : ''; - + $ignore = $this->settings->isEnabled('insert-ignore') ? ' IGNORE' : ''; $count = 0; + foreach ($resultSet as $row) { $count++; $values = $this->prepareColumnValues($tableName, $row); - if ($onlyOnce || !$this->dumpSettings['extended-insert']) { - if ($this->dumpSettings['complete-insert'] && count($colNames)) { + + if ($onlyOnce || !$this->settings->isEnabled('extended-insert')) { + if ($this->settings->isEnabled('complete-insert') && count($colNames)) { $lineSize += $this->io->write( "INSERT$ignore INTO `$tableName` (". implode(", ", $colNames). @@ -978,7 +919,8 @@ private function listValues(string $tableName) } else { $lineSize += $this->io->write(",(".implode(",", $values).")"); } - if (($lineSize > $this->dumpSettings['net_buffer_length']) || !$this->dumpSettings['extended-insert']) { + if (($lineSize > $this->settings->getNetBufferLength()) + || !$this->settings->isEnabled('extended-insert')) { $onlyOnce = true; $lineSize = $this->io->write(";".PHP_EOL); } @@ -1004,7 +946,7 @@ private function listValues(string $tableName) */ public function prepareListValues(string $tableName) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $this->io->write( "--".PHP_EOL. "-- Dumping data for table `$tableName`".PHP_EOL. @@ -1012,25 +954,25 @@ public function prepareListValues(string $tableName) ); } - if ($this->dumpSettings['single-transaction']) { + if ($this->settings->isEnabled('single-transaction')) { $this->conn->exec($this->db->setupTransaction()); $this->conn->exec($this->db->startTransaction()); } - if ($this->dumpSettings['lock-tables'] && !$this->dumpSettings['single-transaction']) { + if ($this->settings->isEnabled('lock-tables') && !$this->settings->isEnabled('single-transaction')) { $this->db->lockTable($tableName); } - if ($this->dumpSettings['add-locks']) { + if ($this->settings->isEnabled('add-locks')) { $this->io->write($this->db->startAddLockTable($tableName)); } - if ($this->dumpSettings['disable-keys']) { + if ($this->settings->isEnabled('disable-keys')) { $this->io->write($this->db->startAddDisableKeys($tableName)); } // Disable autocommit for faster reload - if ($this->dumpSettings['no-autocommit']) { + if ($this->settings->isEnabled('no-autocommit')) { $this->io->write($this->db->startDisableAutocommit()); } } @@ -1043,30 +985,31 @@ public function prepareListValues(string $tableName) */ public function endListValues(string $tableName, int $count = 0) { - if ($this->dumpSettings['disable-keys']) { + if ($this->settings->isEnabled('disable-keys')) { $this->io->write($this->db->endAddDisableKeys($tableName)); } - if ($this->dumpSettings['add-locks']) { + if ($this->settings->isEnabled('add-locks')) { $this->io->write($this->db->endAddLockTable($tableName)); } - if ($this->dumpSettings['single-transaction']) { + if ($this->settings->isEnabled('single-transaction')) { $this->conn->exec($this->db->commitTransaction()); } - if ($this->dumpSettings['lock-tables'] && !$this->dumpSettings['single-transaction']) { + if ($this->settings->isEnabled('lock-tables') + && !$this->settings->isEnabled('single-transaction')) { $this->db->unlockTable($tableName); } // Commit to enable autocommit - if ($this->dumpSettings['no-autocommit']) { + if ($this->settings->isEnabled('no-autocommit')) { $this->io->write($this->db->endDisableAutocommit()); } $this->io->write(PHP_EOL); - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $this->io->write( "-- Dumped table `".$tableName."` with $count row(s)".PHP_EOL. '--'.PHP_EOL.PHP_EOL @@ -1086,12 +1029,12 @@ public function getColumnStmt(string $tableName): array $colStmt = []; foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) { - if ($colType['type'] == 'bit' && $this->dumpSettings['hex-blob']) { + if ($colType['type'] == 'bit' && $this->settings->isEnabled('hex-blob')) { $colStmt[] = sprintf("LPAD(HEX(`%s`),2,'0') AS `%s`", $colName, $colName); - } elseif ($colType['is_blob'] && $this->dumpSettings['hex-blob']) { + } elseif ($colType['is_blob'] && $this->settings->isEnabled('hex-blob')) { $colStmt[] = sprintf("HEX(`%s`) AS `%s`", $colName, $colName); } elseif ($colType['is_virtual']) { - $this->dumpSettings['complete-insert'] = true; + $this->settings->setCompleteInsert(); } else { $colStmt[] = sprintf("`%s`", $colName); } @@ -1113,7 +1056,7 @@ public function getColumnNames(string $tableName): array foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) { if ($colType['is_virtual']) { - $this->dumpSettings['complete-insert'] = true; + $this->settings->setCompleteInsert(); } else { $colNames[] = sprintf('`%s`', $colName); } diff --git a/src/TypeAdapter/TypeAdapterMysql.php b/src/TypeAdapter/TypeAdapterMysql.php index a4c81156..791522b0 100644 --- a/src/TypeAdapter/TypeAdapterMysql.php +++ b/src/TypeAdapter/TypeAdapterMysql.php @@ -2,6 +2,7 @@ namespace Druidfi\Mysqldump\TypeAdapter; +use Druidfi\Mysqldump\DumpSettings; use Exception; use PDO; @@ -10,7 +11,7 @@ class TypeAdapterMysql implements TypeAdapterInterface const DEFINER_RE = 'DEFINER=`(?:[^`]|``)*`@`(?:[^`]|``)*`'; protected PDO $db; - protected array $dumpSettings = []; + protected DumpSettings $settings; // Numerical Mysql types public array $mysqlTypes = [ @@ -47,13 +48,13 @@ class TypeAdapterMysql implements TypeAdapterInterface ] ]; - public function __construct(PDO $conn, array $dumpSettings = []) + public function __construct(PDO $conn, DumpSettings $settings) { $this->db = $conn; - $this->dumpSettings = $dumpSettings; + $this->settings = $settings; // Execute init commands once connected - foreach ($this->dumpSettings['init_commands'] as $stmt) { + foreach ($this->settings->getInitCommands() as $stmt) { $this->db->exec($stmt); } } @@ -120,18 +121,18 @@ public function createTable(array $row): string } $createTable = $row['Create Table']; - if ($this->dumpSettings['reset-auto-increment']) { + if ($this->settings->isEnabled('reset-auto-increment')) { $match = "/AUTO_INCREMENT=[0-9]+/s"; $replace = ""; $createTable = preg_replace($match, $replace, $createTable); } - if ($this->dumpSettings['if-not-exists']) { + if ($this->settings->isEnabled('if-not-exists')) { $createTable = preg_replace('/^CREATE TABLE/', 'CREATE TABLE IF NOT EXISTS', $createTable); } return "/*!40101 SET @saved_cs_client = @@character_set_client */;".PHP_EOL. - "/*!40101 SET character_set_client = ".$this->dumpSettings['default-character-set']." */;".PHP_EOL. + "/*!40101 SET character_set_client = ".$this->settings->get('default-character-set')." */;".PHP_EOL. $createTable.";".PHP_EOL. "/*!40101 SET character_set_client = @saved_cs_client */;".PHP_EOL. PHP_EOL; @@ -150,7 +151,7 @@ public function createView(array $row): string $viewStmt = $row['Create View']; - $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50013 \2 */'.PHP_EOL; + $definerStr = $this->settings->skipDefiner() ? '' : '/*!50013 \2 */' . PHP_EOL; if ($viewStmtReplaced = preg_replace( '/^(CREATE(?:\s+ALGORITHM=(?:UNDEFINED|MERGE|TEMPTABLE))?)\s+(' @@ -178,7 +179,7 @@ public function createTrigger(array $row): string } $triggerStmt = $row['SQL Original Statement']; - $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50017 \2*/ '; + $definerStr = $this->settings->skipDefiner() ? '' : '/*!50017 \2*/ '; if ($triggerStmtReplaced = preg_replace( '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(TRIGGER\s.*)$/s', '/*!50003 \1*/ '.$definerStr.'/*!50003 \3 */', @@ -191,6 +192,7 @@ public function createTrigger(array $row): string $ret .= "DELIMITER ;;".PHP_EOL. $triggerStmt.";;".PHP_EOL. "DELIMITER ;".PHP_EOL.PHP_EOL; + return $ret; } @@ -200,12 +202,15 @@ public function createTrigger(array $row): string public function createProcedure(array $row): string { $ret = ""; + if (!isset($row['Create Procedure'])) { throw new Exception("Error getting procedure code, unknown output. ". "Please check 'https://bugs.mysql.com/bug.php?id=14564'"); } + $procedureStmt = $row['Create Procedure']; - if ($this->dumpSettings['skip-definer']) { + + if ($this->settings->skipDefiner()) { if ($procedureStmtReplaced = preg_replace( '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(PROCEDURE\s.*)$/s', '\1 \3', @@ -219,7 +224,7 @@ public function createProcedure(array $row): string $ret .= "/*!50003 DROP PROCEDURE IF EXISTS `". $row['Procedure']."` */;".PHP_EOL. "/*!40101 SET @saved_cs_client = @@character_set_client */;".PHP_EOL. - "/*!40101 SET character_set_client = ".$this->dumpSettings['default-character-set']." */;".PHP_EOL. + "/*!40101 SET character_set_client = ".$this->settings->get('default-character-set')." */;".PHP_EOL. "DELIMITER ;;".PHP_EOL. $procedureStmt." ;;".PHP_EOL. "DELIMITER ;".PHP_EOL. @@ -244,7 +249,7 @@ public function createFunction(array $row): string $collationConnection = $row['collation_connection']; $sqlMode = $row['sql_mode']; - if ($this->dumpSettings['skip-definer']) { + if ($this->settings->skipDefiner()) { if ($functionStmtReplaced = preg_replace( '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(FUNCTION\s.*)$/s', '\1 \3', @@ -286,14 +291,16 @@ public function createFunction(array $row): string public function createEvent(array $row): string { $ret = ""; + if (!isset($row['Create Event'])) { throw new Exception("Error getting event code, unknown output. ". "Please check 'https://stackoverflow.com/questions/10853826/mysql-5-5-create-event-gives-syntax-error'"); } + $eventName = $row['Event']; $eventStmt = $row['Create Event']; $sqlMode = $row['sql_mode']; - $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50117 \2*/ '; + $definerStr = $this->settings->skipDefiner() ? '' : '/*!50117 \2*/ '; if ($eventStmtReplaced = preg_replace( '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(EVENT .*)$/', @@ -520,14 +527,14 @@ public function backupParameters(): string $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;".PHP_EOL. "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;".PHP_EOL. "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;".PHP_EOL. - "/*!40101 SET NAMES ".$this->dumpSettings['default-character-set']." */;".PHP_EOL; + "/*!40101 SET NAMES ".$this->settings->get('default-character-set')." */;".PHP_EOL; - if (false === $this->dumpSettings['skip-tz-utc']) { + if (false === $this->settings->skipTzUtc()) { $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;".PHP_EOL. "/*!40103 SET TIME_ZONE='+00:00' */;".PHP_EOL; } - if ($this->dumpSettings['no-autocommit']) { + if ($this->settings->isEnabled('no-autocommit')) { $ret .= "/*!40101 SET @OLD_AUTOCOMMIT=@@AUTOCOMMIT */;".PHP_EOL; } @@ -543,11 +550,11 @@ public function restoreParameters(): string { $ret = ""; - if (false === $this->dumpSettings['skip-tz-utc']) { + if (!$this->settings->skipTzUtc()) { $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;".PHP_EOL; } - if ($this->dumpSettings['no-autocommit']) { + if ($this->settings->isEnabled('no-autocommit')) { $ret .= "/*!40101 SET AUTOCOMMIT=@OLD_AUTOCOMMIT */;".PHP_EOL; } diff --git a/tests/scripts/test.php b/tests/scripts/test.php index b6a146fd..91909183 100644 --- a/tests/scripts/test.php +++ b/tests/scripts/test.php @@ -5,6 +5,7 @@ require __DIR__ . '/../../vendor/autoload.php'; +use Druidfi\Mysqldump\DumpSettings; use Druidfi\Mysqldump\Mysqldump; use Druidfi\Mysqldump\Compress\CompressManagerFactory; @@ -47,7 +48,7 @@ $dump->start("output/mysqldump-php_test001_complete.sql"); print "Create dump with PHP: mysql-php_test002.sql" . PHP_EOL; - $dumpSettings['default-character-set'] = Mysqldump::UTF8MB4; + $dumpSettings['default-character-set'] = DumpSettings::UTF8MB4; $dumpSettings['complete-insert'] = true; $dump = new Mysqldump("mysql:host=$host;dbname=test002", $user, "", $dumpSettings); $dump->start("output/mysqldump-php_test002.sql"); From fcdac62e9700792a8806597c6917202c7777415f Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Mon, 22 Aug 2022 16:22:42 +0300 Subject: [PATCH 02/32] Small fixes and refactorings --- src/Mysqldump.php | 55 +++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 0570f400..d7705b56 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -138,7 +138,7 @@ public function setTableLimits(array $tableLimits) } /** - * Returns the LIMIT for the table. Must be numeric to be returned. + * Returns the LIMIT for the table. Must be numeric to be returned. */ public function getTableLimit(string $tableName) { @@ -146,13 +146,7 @@ public function getTableLimit(string $tableName) return false; } - $limit = $this->tableLimits[$tableName]; - - if (!is_numeric($limit)) { - return false; - } - - return $limit; + return is_numeric($this->tableLimits[$tableName]) ? $this->tableLimits[$tableName] : false; } /** @@ -547,6 +541,7 @@ private function exportFunctions() /** * Exports all the events found in database. + * @throws Exception */ private function exportEvents() { @@ -622,7 +617,6 @@ private function getTableColumnTypes(string $tableName): array * View structure extractor, create table (avoids cyclic references). * * @param string $viewName Name of view to export - * @TODO move mysql specific code to typeAdapter */ private function getViewStructureTable(string $viewName) { @@ -676,9 +670,6 @@ public function createStandInTable(string $viewName): string /** * View structure extractor, create view. - * - * @TODO move mysql specific code to typeAdapter - * @param string $viewName Name of view to export */ private function getViewStructureView(string $viewName) { @@ -841,22 +832,6 @@ private function escape(?string $colValue, array $colType) return $this->conn->quote($colValue); } - /** - * Set a callable that will be used to transform table rows. - */ - public function setTransformTableRowHook(callable $callable) - { - $this->transformTableRowCallable = $callable; - } - - /** - * Set a callable that will be used to report dump information. - */ - public function setInfoHook(callable $callable) - { - $this->infoCallable = $callable; - } - /** * Table rows extractor. * @@ -884,13 +859,11 @@ private function listValues(string $tableName) $condition = $this->getTableWhere($tableName); if ($condition) { - $stmt .= " WHERE {$condition}"; + $stmt .= sprintf(' WHERE %s', $condition); } - $limit = $this->getTableLimit($tableName); - - if ($limit !== false) { - $stmt .= " LIMIT {$limit}"; + if ($limit = $this->getTableLimit($tableName)) { + $stmt .= sprintf(' LIMIT %d', $limit); } $resultSet = $this->conn->query($stmt); @@ -1072,4 +1045,20 @@ public function addTypeAdapter(string $type, string $className) { $this->typeAdapters[$type] = $className; } + + /** + * Set a callable that will be used to transform table rows. + */ + public function setTransformTableRowHook(callable $callable) + { + $this->transformTableRowCallable = $callable; + } + + /** + * Set a callable that will be used to report dump information. + */ + public function setInfoHook(callable $callable) + { + $this->infoCallable = $callable; + } } From adbf6b66a94fc005d1ec9ae713b9958a8ea14dc1 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Mon, 22 Aug 2022 16:27:33 +0300 Subject: [PATCH 03/32] getDefaultCharacterSet() --- src/DumpSettings.php | 5 +++++ src/TypeAdapter/TypeAdapterMysql.php | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/DumpSettings.php b/src/DumpSettings.php index addca648..37ab2a08 100644 --- a/src/DumpSettings.php +++ b/src/DumpSettings.php @@ -83,6 +83,11 @@ public function getCompressMethod(): string return $this->settings['compress'] ?? CompressManagerFactory::NONE; } + public function getDefaultCharacterSet(): string + { + return $this->settings['default-character-set']; + } + public function getDefaults(): array { return self::$defaults; diff --git a/src/TypeAdapter/TypeAdapterMysql.php b/src/TypeAdapter/TypeAdapterMysql.php index 791522b0..9688008d 100644 --- a/src/TypeAdapter/TypeAdapterMysql.php +++ b/src/TypeAdapter/TypeAdapterMysql.php @@ -132,7 +132,7 @@ public function createTable(array $row): string } return "/*!40101 SET @saved_cs_client = @@character_set_client */;".PHP_EOL. - "/*!40101 SET character_set_client = ".$this->settings->get('default-character-set')." */;".PHP_EOL. + "/*!40101 SET character_set_client = ". $this->settings->getDefaultCharacterSet() ." */;".PHP_EOL. $createTable.";".PHP_EOL. "/*!40101 SET character_set_client = @saved_cs_client */;".PHP_EOL. PHP_EOL; @@ -224,7 +224,7 @@ public function createProcedure(array $row): string $ret .= "/*!50003 DROP PROCEDURE IF EXISTS `". $row['Procedure']."` */;".PHP_EOL. "/*!40101 SET @saved_cs_client = @@character_set_client */;".PHP_EOL. - "/*!40101 SET character_set_client = ".$this->settings->get('default-character-set')." */;".PHP_EOL. + "/*!40101 SET character_set_client = ".$this->settings->getDefaultCharacterSet()." */;".PHP_EOL. "DELIMITER ;;".PHP_EOL. $procedureStmt." ;;".PHP_EOL. "DELIMITER ;".PHP_EOL. @@ -527,7 +527,7 @@ public function backupParameters(): string $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;".PHP_EOL. "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;".PHP_EOL. "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;".PHP_EOL. - "/*!40101 SET NAMES ".$this->settings->get('default-character-set')." */;".PHP_EOL; + "/*!40101 SET NAMES ". $this->settings->getDefaultCharacterSet() ." */;".PHP_EOL; if (false === $this->settings->skipTzUtc()) { $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;".PHP_EOL. From ee876f43d1166b793d571202406caf31c247cc32 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Mon, 22 Aug 2022 16:39:03 +0300 Subject: [PATCH 04/32] Method visibility --- src/Mysqldump.php | 109 +++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index d7705b56..441da05a 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -31,7 +31,7 @@ class Mysqldump private ?string $pass; private string $host; private string $dbName; - private ?PDO $conn = null; + private PDO $conn; private array $pdoOptions; private string $dbType = ''; private CompressInterface $io; @@ -101,54 +101,6 @@ public function __construct( $this->io = CompressManagerFactory::create($this->settings->getCompressMethod()); } - /** - * Get table column types. - */ - protected function tableColumnTypes(): array - { - return $this->tableColumnTypes; - } - - /** - * Keyed by table name, with the value as the conditions: - * e.g. 'users' => 'date_registered > NOW() - INTERVAL 6 MONTH AND deleted=0' - */ - public function setTableWheres(array $tableWheres) - { - $this->tableWheres = $tableWheres; - } - - public function getTableWhere(string $tableName) - { - if (!empty($this->tableWheres[$tableName])) { - return $this->tableWheres[$tableName]; - } elseif ($this->settings->get('where')) { - return $this->settings->get('where'); - } - - return false; - } - - /** - * Keyed by table name, with the value as the numeric limit: e.g. 'users' => 3000 - */ - public function setTableLimits(array $tableLimits) - { - $this->tableLimits = $tableLimits; - } - - /** - * Returns the LIMIT for the table. Must be numeric to be returned. - */ - public function getTableLimit(string $tableName) - { - if (!isset($this->tableLimits[$tableName])) { - return false; - } - - return is_numeric($this->tableLimits[$tableName]) ? $this->tableLimits[$tableName] : false; - } - /** * Parse DSN string and extract dbname value * Several examples of a DSN string @@ -356,6 +308,7 @@ private function getDatabaseStructureTables() if (in_array(current($row), $includedTables, true)) { $this->tables[] = current($row); $elem = array_search(current($row), $includedTables); + // TODO should this be done in DumpSettings? unset($includedTables[$elem]); } } @@ -651,7 +604,7 @@ private function getViewStructureTable(string $viewName) * @param string $viewName Name of view to export * @return string create statement */ - public function createStandInTable(string $viewName): string + private function createStandInTable(string $viewName): string { $ret = []; @@ -917,7 +870,7 @@ private function listValues(string $tableName) * * @param string $tableName Name of table to export */ - public function prepareListValues(string $tableName) + private function prepareListValues(string $tableName) { if (!$this->settings->skipComments()) { $this->io->write( @@ -956,7 +909,7 @@ public function prepareListValues(string $tableName) * @param string $tableName Name of table to export. * @param integer $count Number of rows inserted. */ - public function endListValues(string $tableName, int $count = 0) + private function endListValues(string $tableName, int $count = 0) { if ($this->settings->isEnabled('disable-keys')) { $this->io->write($this->db->endAddDisableKeys($tableName)); @@ -997,7 +950,7 @@ public function endListValues(string $tableName, int $count = 0) * * @return array SQL sentence with columns for select */ - public function getColumnStmt(string $tableName): array + protected function getColumnStmt(string $tableName): array { $colStmt = []; @@ -1023,7 +976,7 @@ public function getColumnStmt(string $tableName): array * * @return array columns for sql sentence for insert */ - public function getColumnNames(string $tableName): array + private function getColumnNames(string $tableName): array { $colNames = []; @@ -1038,6 +991,54 @@ public function getColumnNames(string $tableName): array return $colNames; } + /** + * Get table column types. + */ + protected function tableColumnTypes(): array + { + return $this->tableColumnTypes; + } + + /** + * Keyed by table name, with the value as the conditions: + * e.g. 'users' => 'date_registered > NOW() - INTERVAL 6 MONTH AND deleted=0' + */ + public function setTableWheres(array $tableWheres) + { + $this->tableWheres = $tableWheres; + } + + public function getTableWhere(string $tableName) + { + if (!empty($this->tableWheres[$tableName])) { + return $this->tableWheres[$tableName]; + } elseif ($this->settings->get('where')) { + return $this->settings->get('where'); + } + + return false; + } + + /** + * Keyed by table name, with the value as the numeric limit: e.g. 'users' => 3000 + */ + public function setTableLimits(array $tableLimits) + { + $this->tableLimits = $tableLimits; + } + + /** + * Returns the LIMIT for the table. Must be numeric to be returned. + */ + public function getTableLimit(string $tableName) + { + if (!isset($this->tableLimits[$tableName])) { + return false; + } + + return is_numeric($this->tableLimits[$tableName]) ? $this->tableLimits[$tableName] : false; + } + /** * Add TypeAdapter */ From 0229e03784b691e960768d5ebb668e6ba93954a4 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 07:55:11 +0300 Subject: [PATCH 05/32] Expose local db ports (with random number) --- docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 81d396c0..4c758727 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,10 +3,14 @@ services: db: container_name: "mysqldump-php-db" image: druidfi/mysql:5.7-drupal + ports: + - 3306 db2: container_name: "mysqldump-php-db2" image: druidfi/mysql:8.0-drupal + ports: + - 3306 php: container_name: "mysqldump-php" From ef254700aa54503e59df7fcb057dba56bc557c45 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 07:56:05 +0300 Subject: [PATCH 06/32] Add comment for PDO::ATTR_ORACLE_NULLS & PDO::NULL_NATURAL --- src/Mysqldump.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 441da05a..1edad65f 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -164,6 +164,7 @@ private function connect() throw new Exception($message); } + // Don't convert empty strings to SQL NULL values on data fetches. $this->conn->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); /** @var TypeAdapterInterface $typeAdapterClass */ From 8ac28973270cbbae597cdc148a9aec0034492f9f Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 07:59:52 +0300 Subject: [PATCH 07/32] Mysql 5.7 + php 7.5 & 8.0 should pass --- .github/workflows/tests.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f622af6b..02ac044e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,8 @@ jobs: matrix: #mysql-versions: ['5.7', '8.0'] mysql-versions: ['5.7'] - php-versions: ['7.4', '8.0', '8.1'] + php-versions: ['7.4', '8.0'] + #php-versions: ['7.4', '8.0', '8.1'] services: db: @@ -48,8 +49,8 @@ jobs: - name: Run PHPunit tests run: vendor/bin/phpunit -# - name: Create user and databases for testing -# run: ./tests/scripts/create_users.sh 127.0.0.1 -# -# - name: Run test script -# run: ./tests/scripts/test.sh 127.0.0.1 + - name: Create user and databases for testing + run: ./tests/scripts/create_users.sh 127.0.0.1 + + - name: Run test script + run: ./tests/scripts/test.sh 127.0.0.1 From c494bf0ddaa0c88fe48bd1133e1185cfcb87fe26 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 08:02:28 +0300 Subject: [PATCH 08/32] run test.sh in it's folder --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 02ac044e..1e4e5f7d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,7 +50,7 @@ jobs: run: vendor/bin/phpunit - name: Create user and databases for testing - run: ./tests/scripts/create_users.sh 127.0.0.1 + run: cd tests/scripts && ./create_users.sh 127.0.0.1 - name: Run test script - run: ./tests/scripts/test.sh 127.0.0.1 + run: cd tests/scripts && ./test.sh 127.0.0.1 From 308f82f0a38583ed99580caeb4912c340b3b7ad6 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 08:43:48 +0300 Subject: [PATCH 09/32] More output to tests --- tests/scripts/create_users.sh | 2 ++ tests/scripts/test.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/scripts/create_users.sh b/tests/scripts/create_users.sh index 2de59fce..71d27723 100755 --- a/tests/scripts/create_users.sh +++ b/tests/scripts/create_users.sh @@ -9,6 +9,8 @@ major=`$MYSQL_CMD -e "SELECT @@version\G" | grep version |awk '{print $2}' | awk medium=`$MYSQL_CMD -e "SELECT @@version\G" | grep version |awk '{print $2}' | awk -F"." '{print $2}'` minor=`$MYSQL_CMD -e "SELECT @@version\G" | grep version |awk '{print $2}' | awk -F"." '{print $3}'` +printf "\nCreating users in MySQL server version $major.$medium.$minor on host '$HOST' with user '$USER'\n" + $MYSQL_CMD -e "CREATE USER IF NOT EXISTS '$USER'@'%';" $MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS test001;" $MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS test002;" diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index 75be3210..ba7ef81c 100755 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -329,6 +329,8 @@ if [[ $retvalue -eq 0 ]]; then rm output/*.checksum 2> /dev/null rm output/*.filtered.sql 2> /dev/null rm output/mysqldump* 2> /dev/null + + echo -e "\nAll tests were successfully" fi echo -e "\nExiting with code $retvalue" From b7573ff89b1a42ee248f73b1c349c135e1ccb056 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 08:44:06 +0300 Subject: [PATCH 10/32] Local matrix of PHP versions --- Dockerfile | 4 +++- README.md | 7 +++++-- docker-compose.yml | 39 ++++++++++++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index e78671cb..7d96c776 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM php:8.1-alpine +ARG PHP_VERSION + +FROM php:${PHP_VERSION}-alpine RUN apk --update add --no-cache \ bash mysql-client \ diff --git a/README.md b/README.md index 209dc27c..7ea2b90e 100644 --- a/README.md +++ b/README.md @@ -297,8 +297,11 @@ Local setup for tests: ``` docker-compose up -d --build -docker-compose exec php /app/tests/scripts/create_users.sh -docker-compose exec -w /app/tests/scripts php ./test.sh +docker-compose exec php74 /app/tests/scripts/create_users.sh +docker-compose exec php74 /app/tests/scripts/create_users.sh db2 +docker-compose exec -w /app/tests/scripts php74 ./test.sh +docker-compose exec -w /app/tests/scripts php80 ./test.sh +docker-compose exec -w /app/tests/scripts php81 ./test.sh ``` ## Bugs (from mysqldump, not from mysqldump-php) diff --git a/docker-compose.yml b/docker-compose.yml index 4c758727..2edd290a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,52 @@ services: db: - container_name: "mysqldump-php-db" + container_name: "mysqldump-php-mysql-57" image: druidfi/mysql:5.7-drupal ports: - 3306 db2: - container_name: "mysqldump-php-db2" + container_name: "mysqldump-php-mysql-80" image: druidfi/mysql:8.0-drupal ports: - 3306 - php: - container_name: "mysqldump-php" - image: tester:latest + php74: + container_name: "mysqldump-php-74" + image: mysqldump-php-tester:php-7.4 build: context: . + args: + PHP_VERSION: "7.4" volumes: - .:/app depends_on: - db + - db2 + + php80: + container_name: "mysqldump-php-80" + image: mysqldump-php-tester:php-8.0 + build: + context: . + args: + PHP_VERSION: "8.0" + volumes: + - .:/app + depends_on: + - db + - db2 + + php81: + container_name: "mysqldump-php-81" + image: mysqldump-php-tester:php-8.1 + build: + context: . + args: + PHP_VERSION: "8.1" + volumes: + - .:/app + depends_on: + - db + - db2 From 60d42995b3a96dac1a3d7a3ab921b6e08ca344cb Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 09:45:34 +0300 Subject: [PATCH 11/32] No need for mysqli --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7d96c776..3d2fb3f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN apk --update add --no-cache \ bash mysql-client \ && rm -rf /var/cache/apk/* -RUN docker-php-ext-install mysqli pdo pdo_mysql +RUN docker-php-ext-install pdo pdo_mysql WORKDIR /app From 6c76aeb001c7f69d2b2d22001c499d7c88c6ebb4 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 11:41:49 +0300 Subject: [PATCH 12/32] Change test output --- tests/scripts/test.php | 2 ++ tests/scripts/test.sh | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/scripts/test.php b/tests/scripts/test.php index 91909183..7adc6428 100644 --- a/tests/scripts/test.php +++ b/tests/scripts/test.php @@ -32,6 +32,8 @@ 'where' => '' ]; +print "PHP version is ". phpversion() . PHP_EOL; + try { // do nothing test print "Create dump with PHP: mysql-php_test000.sql" . PHP_EOL; diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index ba7ef81c..3db44a45 100755 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -331,7 +331,8 @@ if [[ $retvalue -eq 0 ]]; then rm output/mysqldump* 2> /dev/null echo -e "\nAll tests were successfully" +else + echo -e "\nThere are errors. Exiting with code $retvalue" fi -echo -e "\nExiting with code $retvalue" exit $retvalue From 8c8c50d9f5b1ace2fb305deb1871d516a3903948 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 12:37:00 +0300 Subject: [PATCH 13/32] Add readability to PDO options --- src/Mysqldump.php | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 1edad65f..25c0dd3a 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -37,9 +37,7 @@ class Mysqldump private CompressInterface $io; private TypeAdapterInterface $db; - private array $typeAdapters = [ - 'mysql' => TypeAdapterMysql::class, - ]; + private static string $adapterClass = TypeAdapterMysql::class; private DumpSettings $settings; private array $tableColumnTypes = []; @@ -85,17 +83,11 @@ public function __construct( $this->pass = $pass; $this->settings = new DumpSettings($settings); - $pdoOptionsDefault = [ + $this->pdoOptions = array_replace_recursive([ PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ]; - - // This drops MYSQL dependency, only use the constant if it's defined. - if ('mysql' === $this->dbType) { - $pdoOptionsDefault[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false; - } - - $this->pdoOptions = array_replace_recursive($pdoOptionsDefault, $pdoOptions); + PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, + ], $pdoOptions); // Create a new compressManager to manage compressed output $this->io = CompressManagerFactory::create($this->settings->getCompressMethod()); @@ -124,11 +116,6 @@ private function parseDsn(string $dsn): void throw new Exception('Missing database type from DSN string'); } - if (!isset($this->typeAdapters[$this->dbType])) { - $message = sprintf("There is no adapter for type '%s'", $this->dbType); - throw new Exception($message); - } - $dsn = substr($dsn, $pos + 1); $dsnArray = []; @@ -167,9 +154,12 @@ private function connect() // Don't convert empty strings to SQL NULL values on data fetches. $this->conn->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); - /** @var TypeAdapterInterface $typeAdapterClass */ - $typeAdapterClass = $this->typeAdapters[$this->dbType]; - $this->db = new $typeAdapterClass($this->conn, $this->settings); + $this->db = $this->getAdapter(); + } + + public function getAdapter(): TypeAdapterInterface + { + return new self::$adapterClass($this->conn, $this->settings); } /** @@ -1042,10 +1032,17 @@ public function getTableLimit(string $tableName) /** * Add TypeAdapter + * + * @throws Exception */ - public function addTypeAdapter(string $type, string $className) + public function addTypeAdapter(string $adapterClassName) { - $this->typeAdapters[$type] = $className; + if (!is_a($adapterClassName, TypeAdapterInterface::class, true)) { + $message = sprintf('Adapter %s is not instance of %s', $adapterClassName, TypeAdapterInterface::class); + throw new Exception($message); + } + + self::$adapterClass = $adapterClassName; } /** From 345a269d6296e8777c3e5cfc05b995538ed09f10 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 12:50:35 +0300 Subject: [PATCH 14/32] Add compatibility with some linux distros --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 2edd290a..899157b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3.7' + services: db: From c84bb6d2152c04c276234c9d6b715ec8cfca959f Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 13:02:17 +0300 Subject: [PATCH 15/32] Get hostname from test.sh to test.php --- tests/scripts/test.php | 2 +- tests/scripts/test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/scripts/test.php b/tests/scripts/test.php index 7adc6428..fe7e4da6 100644 --- a/tests/scripts/test.php +++ b/tests/scripts/test.php @@ -9,7 +9,7 @@ use Druidfi\Mysqldump\Mysqldump; use Druidfi\Mysqldump\Compress\CompressManagerFactory; -$host = 'db'; +$host = $argv[1] ?? 'db'; // Get host name from test.sh $user = 'travis'; $dumpSettings = [ diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index 3db44a45..2bdd775c 100755 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -146,7 +146,7 @@ $MYSQLDUMP_CMD test001 \ errCode=$?; ret[((index++))]=$errCode printf "\nRun mysqldump with PHP:\n\n" -php test.php || { echo "ERROR running test.php" && exit -1; } +php test.php $HOST || { echo "ERROR running test.php" && exit -1; } errCode=$?; ret[((index++))]=$errCode printf "\nImport generated SQL dumps...\n\n" From fc8df4c236d35ac3c0eccd3d4889b5833f5c6425 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 13:21:03 +0300 Subject: [PATCH 16/32] Try with Ubuntu 18.05 --- .github/workflows/tests.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1e4e5f7d..69f2fac7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,14 +11,12 @@ jobs: tests: - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 strategy: matrix: - #mysql-versions: ['5.7', '8.0'] - mysql-versions: ['5.7'] - php-versions: ['7.4', '8.0'] - #php-versions: ['7.4', '8.0', '8.1'] + mysql-versions: ['5.7', '8.0'] + php-versions: ['7.4', '8.0', '8.1'] services: db: From b86aebe9149d7b65c67f34779ea8b5dc033d8a7b Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 13:31:43 +0300 Subject: [PATCH 17/32] Alternative source dump for test010 and MySQL 8 --- tests/scripts/test.sh | 14 ++++++--- tests/scripts/test010.8.src.sql | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/scripts/test010.8.src.sql diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index 2bdd775c..ba1d347b 100755 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -61,16 +61,22 @@ printf "Import test009.src.sql" $MYSQL_CMD < test009.src.sql && echo " - done."; errCode=$?; ret[((index++))]=$errCode if [[ $errCode -ne 0 ]]; then echo "error test001.src.sql"; fi -printf "Import test010.src.sql" -$MYSQL_CMD < test010.src.sql && echo " - done."; errCode=$?; ret[((index++))]=$errCode -if [[ $errCode -ne 0 ]]; then echo "error test010.src.sql"; fi +if [[ $major -eq 5 && $medium -ge 7 ]]; then + printf "Import test010.src.sql" + $MYSQL_CMD < test010.src.sql && echo " - done."; errCode=$?; ret[((index++))]=$errCode + if [[ $errCode -ne 0 ]]; then echo "error test010.src.sql"; fi +else + printf "Import test010.8.src.sql" + $MYSQL_CMD < test010.8.src.sql && echo " - done."; errCode=$?; ret[((index++))]=$errCode + if [[ $errCode -ne 0 ]]; then echo "error test010.8.src.sql"; fi +fi if [[ $major -eq 5 && $medium -ge 7 ]]; then printf "Import test011.src.sql" # test virtual column support, with simple inserts forced to complete (a) and complete inserts (b) $MYSQL_CMD < test011.src.sql && echo " - done."; errCode=$?; ret[((index++))]=$errCode else - echo "test011 disabled, only valid for mysql server version > 5.7.0" + echo "test011 disabled, only valid for mysql server version 5.7.x" fi printf "Import test012.src.sql" diff --git a/tests/scripts/test010.8.src.sql b/tests/scripts/test010.8.src.sql new file mode 100644 index 00000000..265a032f --- /dev/null +++ b/tests/scripts/test010.8.src.sql @@ -0,0 +1,56 @@ +DROP DATABASE IF EXISTS `test010`; +CREATE DATABASE `test010`; +USE `test010`; + +-- MySQL dump 10.13 Distrib 5.7.17, for Linux (x86_64) +-- +-- Host: localhost Database: test010 +-- ------------------------------------------------------ +-- Server version 5.7.17 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Dumping events for database 'test010' +-- +/*!50106 SET @save_time_zone= @@TIME_ZONE */ ; +/*!50106 DROP EVENT IF EXISTS `e_test010` */; +DELIMITER ;; +/*!50003 SET @saved_cs_client = @@character_set_client */ ;; +/*!50003 SET @saved_cs_results = @@character_set_results */ ;; +/*!50003 SET @saved_col_connection = @@collation_connection */ ;; +/*!50003 SET character_set_client = utf8 */ ;; +/*!50003 SET character_set_results = utf8 */ ;; +/*!50003 SET collation_connection = utf8_general_ci */ ;; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ;; +/*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ;; +/*!50003 SET @saved_time_zone = @@time_zone */ ;; +/*!50003 SET time_zone = 'SYSTEM' */ ;; +/*!50106 CREATE*/ /*!50117 DEFINER=`travis`@`%`*/ /*!50106 EVENT `e_test010` ON SCHEDULE AT '2030-01-01 23:59:00' ON COMPLETION NOT PRESERVE ENABLE DO INSERT INTO test010.test VALUES (NOW()) */ ;; +/*!50003 SET time_zone = @saved_time_zone */ ;; +/*!50003 SET sql_mode = @saved_sql_mode */ ;; +/*!50003 SET character_set_client = @saved_cs_client */ ;; +/*!50003 SET character_set_results = @saved_cs_results */ ;; +/*!50003 SET collation_connection = @saved_col_connection */ ;; +DELIMITER ; +/*!50106 SET TIME_ZONE= @save_time_zone */ ; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2017-03-20 21:52:21 From 78e73382be081f2020ef464436131d914824ca02 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 13:53:04 +0300 Subject: [PATCH 18/32] Use druidfi/php images locally for testing --- Dockerfile | 21 +++++++++++++++------ docker-compose.yml | 6 +++--- tests/scripts/create_users.sh | 2 +- tests/scripts/test.sh | 6 +++--- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3d2fb3f8..75e08f3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,21 @@ -ARG PHP_VERSION +ARG PHP_SHORT_VERSION -FROM php:${PHP_VERSION}-alpine +FROM druidfi/php:7.4 as php-74 -RUN apk --update add --no-cache \ - bash mysql-client \ - && rm -rf /var/cache/apk/* +RUN sudo apk --update -X https://dl-cdn.alpinelinux.org/alpine/edge/testing --no-cache add php7-pdo php7-pdo_mysql -RUN docker-php-ext-install pdo pdo_mysql +FROM druidfi/php:8.0 as php-80 + +RUN sudo apk --update --no-cache add php8-pdo php8-pdo_mysql + +FROM druidfi/php:8.1 as php-81 + +RUN sudo apk --update --no-cache add php81-pdo php81-pdo_mysql + +FROM php-${PHP_SHORT_VERSION} + +RUN sudo apk --update add --no-cache bash mysql-client \ + && sudo rm -rf /var/cache/apk/* WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index 899157b7..ce61f4f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: build: context: . args: - PHP_VERSION: "7.4" + PHP_SHORT_VERSION: "74" volumes: - .:/app depends_on: @@ -33,7 +33,7 @@ services: build: context: . args: - PHP_VERSION: "8.0" + PHP_SHORT_VERSION: "80" volumes: - .:/app depends_on: @@ -46,7 +46,7 @@ services: build: context: . args: - PHP_VERSION: "8.1" + PHP_SHORT_VERSION: "81" volumes: - .:/app depends_on: diff --git a/tests/scripts/create_users.sh b/tests/scripts/create_users.sh index 71d27723..7c1170cb 100755 --- a/tests/scripts/create_users.sh +++ b/tests/scripts/create_users.sh @@ -9,7 +9,7 @@ major=`$MYSQL_CMD -e "SELECT @@version\G" | grep version |awk '{print $2}' | awk medium=`$MYSQL_CMD -e "SELECT @@version\G" | grep version |awk '{print $2}' | awk -F"." '{print $2}'` minor=`$MYSQL_CMD -e "SELECT @@version\G" | grep version |awk '{print $2}' | awk -F"." '{print $3}'` -printf "\nCreating users in MySQL server version $major.$medium.$minor on host '$HOST' with user '$USER'\n" +printf "\nCreating users in MySQL server version $major.$medium.$minor on host '$HOST' with user '$USER'\n\n" $MYSQL_CMD -e "CREATE USER IF NOT EXISTS '$USER'@'%';" $MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS test001;" diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index ba1d347b..46552ce9 100755 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -194,7 +194,7 @@ if [[ $major -eq 5 && $medium -ge 7 ]]; then # test virtual column support, with simple inserts forced to complete (a) and complete inserts (b) cat test011.src.sql | egrep "INSERT|GENERATED" > output/test011.filtered.sql && echo "Created test011.filtered.sql" else - echo "test011 disabled, only valid for mysql server version > 5.7.0" + echo "test011 disabled, only valid for mysql server version 5.7.x" fi cat output/mysqldump_test001.sql | grep ^INSERT > output/mysqldump_test001.filtered.sql && echo "Created mysqldump_test001.filtered.sql" @@ -215,7 +215,7 @@ if [[ $major -eq 5 && $medium -ge 7 ]]; then cat output/mysqldump-php_test011a.sql | egrep "INSERT|GENERATED" > output/mysqldump-php_test011a.filtered.sql && echo "Created mysqldump-php_test011a.filtered.sql" cat output/mysqldump-php_test011b.sql | egrep "INSERT|GENERATED" > output/mysqldump-php_test011b.filtered.sql && echo "Created mysqldump-php_test011b.filtered.sql" else - echo "test011 disabled, only valid for mysql server version > 5.7.0" + echo "test011 disabled, only valid for mysql server version 5.7.x" fi cat output/mysqldump-php_test012.sql | grep -E -e '50001 (CREATE|VIEW)' -e '50013 DEFINER' -e 'CREATE.*TRIGGER' -e 'FUNCTION' -e 'PROCEDURE' > output/mysqldump-php_test012.filtered.sql && echo "Created mysqldump-php_test012.filtered.sql" @@ -299,7 +299,7 @@ if [[ $major -eq 5 && $medium -ge 7 ]]; then errCode=$?; ret[((index++))]=$errCode if [[ $errCode -ne 0 ]]; then echo -e "\n$test\n"; fi else - echo test011 disabled, only valid for mysql server version > 5.7.0 + echo "test011 disabled, only valid for mysql server version 5.7.x" fi # Test create views, events, trigger From 3f960708e8e3537bfe75b06161dc18e29e89edd6 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 14:02:45 +0300 Subject: [PATCH 19/32] I guess this was not needed --- tests/scripts/create_users.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/scripts/create_users.sh b/tests/scripts/create_users.sh index 7c1170cb..ee84109e 100755 --- a/tests/scripts/create_users.sh +++ b/tests/scripts/create_users.sh @@ -40,8 +40,6 @@ fi if [[ $major -eq 5 && $medium -ge 7 ]]; then $MYSQL_CMD -e "use mysql; update user set authentication_string=PASSWORD('') where User='$USER'; update user set plugin='mysql_native_password';" -elif [[ $major -eq 8 ]]; then - $MYSQL_CMD -e "ALTER USER '$USER'@'localhost' IDENTIFIED WITH caching_sha2_password BY '';" fi $MYSQL_CMD -e "FLUSH PRIVILEGES;" From be37c5760f67673747ed25969b253ec113618439 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 14:06:06 +0300 Subject: [PATCH 20/32] Don't cancel jobs --- .github/renovate.json | 19 ------------------- .github/workflows/tests.yml | 1 + 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 .github/renovate.json diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 0aad8202..00000000 --- a/.github/renovate.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "baseBranches": [ - "main" - ], - "extends": [ - "config:base", - ":preserveSemverRanges", - ":disableRateLimiting" - ], - "dependencyDashboard": true, - "rangeStrategy": "replace", - "separateMajorMinor": true, - "separateMinorPatch": true, - "separateMultipleMajor": true, - "timezone": "Europe/Helsinki", - "vulnerabilityAlerts": { - "labels": ["security"] - } -} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 69f2fac7..e6c04aae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,6 +14,7 @@ jobs: runs-on: ubuntu-18.04 strategy: + fail-fast: false matrix: mysql-versions: ['5.7', '8.0'] php-versions: ['7.4', '8.0', '8.1'] From 17c2503f62618511a7957e2101c516d42d8652e2 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 14:14:17 +0300 Subject: [PATCH 21/32] Try running different ubuntu per mysql version --- .github/workflows/tests.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e6c04aae..a25ed524 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,17 +11,21 @@ jobs: tests: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - mysql-versions: ['5.7', '8.0'] + include: + - os: ubuntu-18.04 + mysql-version: 5.7 + - os: ubuntu-latest + mysql-version: 8.0 php-versions: ['7.4', '8.0', '8.1'] services: db: - image: druidfi/mysql:${{ matrix.mysql-versions }}-drupal + image: druidfi/mysql:${{ matrix.mysql-version }}-drupal ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 From 29dc2d2b3eb747c89ae1d9d6fa548c6f53de7e46 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 14:16:28 +0300 Subject: [PATCH 22/32] Explicit list --- .github/workflows/tests.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a25ed524..28394c79 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,9 +19,22 @@ jobs: include: - os: ubuntu-18.04 mysql-version: 5.7 + php-version: 7.4 + - os: ubuntu-18.04 + mysql-version: 5.7 + php-version: 8.0 + - os: ubuntu-18.04 + mysql-version: 5.7 + php-version: 8.1 + - os: ubuntu-latest + mysql-version: 8.0 + php-version: 7.4 + - os: ubuntu-latest + mysql-version: 8.0 + php-version: 8.0 - os: ubuntu-latest mysql-version: 8.0 - php-versions: ['7.4', '8.0', '8.1'] + php-version: 8.1 services: db: @@ -41,7 +54,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-versions }} + php-version: ${{ matrix.php-version }} - name: Validate composer.json run: composer validate From 39ff7af697595599b68b566e0d11be9dfbde5e41 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 14:19:51 +0300 Subject: [PATCH 23/32] Revert back --- .github/workflows/tests.yml | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 28394c79..e6c04aae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,34 +11,17 @@ jobs: tests: - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: - include: - - os: ubuntu-18.04 - mysql-version: 5.7 - php-version: 7.4 - - os: ubuntu-18.04 - mysql-version: 5.7 - php-version: 8.0 - - os: ubuntu-18.04 - mysql-version: 5.7 - php-version: 8.1 - - os: ubuntu-latest - mysql-version: 8.0 - php-version: 7.4 - - os: ubuntu-latest - mysql-version: 8.0 - php-version: 8.0 - - os: ubuntu-latest - mysql-version: 8.0 - php-version: 8.1 + mysql-versions: ['5.7', '8.0'] + php-versions: ['7.4', '8.0', '8.1'] services: db: - image: druidfi/mysql:${{ matrix.mysql-version }}-drupal + image: druidfi/mysql:${{ matrix.mysql-versions }}-drupal ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 @@ -54,7 +37,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-version }} + php-version: ${{ matrix.php-versions }} - name: Validate composer.json run: composer validate From 8153957c2f0436ed22cd7057de5ddabecf8707a4 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 15:18:11 +0300 Subject: [PATCH 24/32] Refactor --- src/Mysqldump.php | 106 +++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 25c0dd3a..3b1b43a5 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -33,7 +33,6 @@ class Mysqldump private string $dbName; private PDO $conn; private array $pdoOptions; - private string $dbType = ''; private CompressInterface $io; private TypeAdapterInterface $db; @@ -71,11 +70,11 @@ class Mysqldump * @throws Exception */ public function __construct( - string $dsn = '', + string $dsn = '', ?string $user = null, ?string $pass = null, - array $settings = [], - array $pdoOptions = [] + array $settings = [], + array $pdoOptions = [] ) { $this->parseDsn($dsn); @@ -110,9 +109,9 @@ private function parseDsn(string $dsn): void } $this->dsn = $dsn; - $this->dbType = strtolower(substr($dsn, 0, $pos)); + $dbType = strtolower(substr($dsn, 0, $pos)); - if (empty($this->dbType)) { + if (empty($dbType)) { throw new Exception('Missing database type from DSN string'); } @@ -162,6 +161,11 @@ public function getAdapter(): TypeAdapterInterface return new self::$adapterClass($this->conn, $this->settings); } + private function write(string $data): void + { + $this->io->write($data); + } + /** * Primary function, triggers dumping. * @@ -185,17 +189,17 @@ public function start(?string $filename = '') // Write some basic info to output file if (!$this->settings->skipComments()) { - $this->io->write($this->getDumpFileHeader()); + $this->write($this->getDumpFileHeader()); } // Store server settings and use saner defaults to dump - $this->io->write($this->db->backupParameters()); + $this->write($this->db->backupParameters()); if ($this->settings->isEnabled('databases')) { - $this->io->write($this->db->getDatabaseHeader($this->dbName)); + $this->write($this->db->getDatabaseHeader($this->dbName)); if ($this->settings->isEnabled('add-drop-database')) { - $this->io->write($this->db->addDropDatabase($this->dbName)); + $this->write($this->db->addDropDatabase($this->dbName)); } } @@ -208,7 +212,7 @@ public function start(?string $filename = '') $this->getDatabaseStructureEvents(); if ($this->settings->isEnabled('databases')) { - $this->io->write($this->db->databases($this->dbName)); + $this->write($this->db->databases($this->dbName)); } // If there still are some tables/views in include-tables array, that means that some tables or views weren't @@ -227,11 +231,11 @@ public function start(?string $filename = '') $this->exportEvents(); // Restore saved parameters. - $this->io->write($this->db->restoreParameters()); + $this->write($this->db->restoreParameters()); // Write some stats to output file. if (!$this->settings->skipComments()) { - $this->io->write($this->getDumpFileFooter()); + $this->write($this->getDumpFileFooter()); } // Close output file. @@ -516,13 +520,13 @@ private function getTableStructure(string $tableName) $stmt = $this->db->showCreateTable($tableName); foreach ($this->conn->query($stmt) as $r) { - $this->io->write($ret); + $this->write($ret); if ($this->settings->isEnabled('add-drop-table')) { - $this->io->write($this->db->dropTable($tableName)); + $this->write($this->db->dropTable($tableName)); } - $this->io->write($this->db->createTable($r)); + $this->write($this->db->createTable($r)); break; } @@ -571,7 +575,7 @@ private function getViewStructureTable(string $viewName) '--' . PHP_EOL . PHP_EOL ); - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateView($viewName); @@ -579,10 +583,10 @@ private function getViewStructureTable(string $viewName) // create views as tables, to resolve dependencies foreach ($this->conn->query($stmt) as $r) { if ($this->settings->isEnabled('add-drop-table')) { - $this->io->write($this->db->dropView($viewName)); + $this->write($this->db->dropView($viewName)); } - $this->io->write($this->createStandInTable($viewName)); + $this->write($this->createStandInTable($viewName)); break; } @@ -625,7 +629,7 @@ private function getViewStructureView(string $viewName) $viewName ); - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateView($viewName); @@ -633,8 +637,8 @@ private function getViewStructureView(string $viewName) // Create views, to resolve dependencies replacing tables with views foreach ($this->conn->query($stmt) as $r) { // Because we must replace table with view, we should delete it - $this->io->write($this->db->dropView($viewName)); - $this->io->write($this->db->createView($r)); + $this->write($this->db->dropView($viewName)); + $this->write($this->db->createView($r)); break; } @@ -651,10 +655,10 @@ private function getTriggerStructure(string $triggerName) foreach ($this->conn->query($stmt) as $r) { if ($this->settings->isEnabled('add-drop-trigger')) { - $this->io->write($this->db->addDropTrigger($triggerName)); + $this->write($this->db->addDropTrigger($triggerName)); } - $this->io->write($this->db->createTrigger($r)); + $this->write($this->db->createTrigger($r)); return; } @@ -671,13 +675,13 @@ private function getProcedureStructure(string $procedureName) $ret = "--".PHP_EOL. "-- Dumping routines for database '".$this->dbName."'".PHP_EOL. "--".PHP_EOL.PHP_EOL; - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateProcedure($procedureName); foreach ($this->conn->query($stmt) as $r) { - $this->io->write($this->db->createProcedure($r)); + $this->write($this->db->createProcedure($r)); return; } @@ -694,13 +698,13 @@ private function getFunctionStructure(string $functionName) $ret = "--".PHP_EOL. "-- Dumping routines for database '".$this->dbName."'".PHP_EOL. "--".PHP_EOL.PHP_EOL; - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateFunction($functionName); foreach ($this->conn->query($stmt) as $r) { - $this->io->write($this->db->createFunction($r)); + $this->write($this->db->createFunction($r)); return; } @@ -718,13 +722,13 @@ private function getEventStructure(string $eventName) $ret = "--".PHP_EOL. "-- Dumping events for database '".$this->dbName."'".PHP_EOL. "--".PHP_EOL.PHP_EOL; - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateEvent($eventName); foreach ($this->conn->query($stmt) as $r) { - $this->io->write($this->db->createEvent($r)); + $this->write($this->db->createEvent($r)); return; } @@ -819,34 +823,38 @@ private function listValues(string $tableName) foreach ($resultSet as $row) { $count++; $values = $this->prepareColumnValues($tableName, $row); + $valueList = implode(',', $values); if ($onlyOnce || !$this->settings->isEnabled('extended-insert')) { if ($this->settings->isEnabled('complete-insert') && count($colNames)) { - $lineSize += $this->io->write( - "INSERT$ignore INTO `$tableName` (". - implode(", ", $colNames). - ") VALUES (".implode(",", $values).")" - ); + $lineSize += $this->write(sprintf( + 'INSERT%s INTO `%s` (%s) VALUES (%s)', + $ignore, + $tableName, + implode(', ', $colNames), + $valueList + )); } else { - $lineSize += $this->io->write( - "INSERT$ignore INTO `$tableName` VALUES (".implode(",", $values).")" + $lineSize += $this->write( + sprintf('INSERT%s INTO `%s` VALUES (%s)', $ignore, $tableName, $valueList) ); } $onlyOnce = false; } else { - $lineSize += $this->io->write(",(".implode(",", $values).")"); + $lineSize += $this->write(sprintf(',(%s)', $valueList)); } + if (($lineSize > $this->settings->getNetBufferLength()) || !$this->settings->isEnabled('extended-insert')) { $onlyOnce = true; - $lineSize = $this->io->write(";".PHP_EOL); + $lineSize = $this->write(';' . PHP_EOL); } } $resultSet->closeCursor(); if (!$onlyOnce) { - $this->io->write(";".PHP_EOL); + $this->write(';' . PHP_EOL); } $this->endListValues($tableName, $count); @@ -864,7 +872,7 @@ private function listValues(string $tableName) private function prepareListValues(string $tableName) { if (!$this->settings->skipComments()) { - $this->io->write( + $this->write( "--".PHP_EOL. "-- Dumping data for table `$tableName`".PHP_EOL. "--".PHP_EOL.PHP_EOL @@ -881,16 +889,16 @@ private function prepareListValues(string $tableName) } if ($this->settings->isEnabled('add-locks')) { - $this->io->write($this->db->startAddLockTable($tableName)); + $this->write($this->db->startAddLockTable($tableName)); } if ($this->settings->isEnabled('disable-keys')) { - $this->io->write($this->db->startAddDisableKeys($tableName)); + $this->write($this->db->startAddDisableKeys($tableName)); } // Disable autocommit for faster reload if ($this->settings->isEnabled('no-autocommit')) { - $this->io->write($this->db->startDisableAutocommit()); + $this->write($this->db->startDisableAutocommit()); } } @@ -903,11 +911,11 @@ private function prepareListValues(string $tableName) private function endListValues(string $tableName, int $count = 0) { if ($this->settings->isEnabled('disable-keys')) { - $this->io->write($this->db->endAddDisableKeys($tableName)); + $this->write($this->db->endAddDisableKeys($tableName)); } if ($this->settings->isEnabled('add-locks')) { - $this->io->write($this->db->endAddLockTable($tableName)); + $this->write($this->db->endAddLockTable($tableName)); } if ($this->settings->isEnabled('single-transaction')) { @@ -921,13 +929,13 @@ private function endListValues(string $tableName, int $count = 0) // Commit to enable autocommit if ($this->settings->isEnabled('no-autocommit')) { - $this->io->write($this->db->endDisableAutocommit()); + $this->write($this->db->endDisableAutocommit()); } - $this->io->write(PHP_EOL); + $this->write(PHP_EOL); if (!$this->settings->skipComments()) { - $this->io->write( + $this->write( "-- Dumped table `".$tableName."` with $count row(s)".PHP_EOL. '--'.PHP_EOL.PHP_EOL ); From 494a595f8b1d6cc9eea910ae66eb128a857bef2f Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 15:21:18 +0300 Subject: [PATCH 25/32] Correct return type for write (int) --- src/Mysqldump.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 3b1b43a5..f33ae7de 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -161,9 +161,9 @@ public function getAdapter(): TypeAdapterInterface return new self::$adapterClass($this->conn, $this->settings); } - private function write(string $data): void + private function write(string $data): int { - $this->io->write($data); + return $this->io->write($data); } /** From 9159b37845e50dd53e67c46706e3536291126c37 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 15:33:41 +0300 Subject: [PATCH 26/32] New attempt on matrix os changes --- .github/workflows/tests.yml | 27 ++++++++++++++++++++++----- src/Mysqldump.php | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e6c04aae..c754806f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,17 +11,34 @@ jobs: tests: - runs-on: ubuntu-18.04 + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - mysql-versions: ['5.7', '8.0'] - php-versions: ['7.4', '8.0', '8.1'] + include: + - os: ubuntu-18.04 + mysql-version: 5.7 + php-version: 7.4 + - os: ubuntu-18.04 + mysql-version: 5.7 + php-version: 8.0 + - os: ubuntu-18.04 + mysql-version: 5.7 + php-version: 8.1 + - os: ubuntu-latest + mysql-version: 8.0 + php-version: 7.4 + - os: ubuntu-latest + mysql-version: 8.0 + php-version: 8.0 + - os: ubuntu-latest + mysql-version: 8.0 + php-version: 8.1 services: db: - image: druidfi/mysql:${{ matrix.mysql-versions }}-drupal + image: druidfi/mysql:${{ matrix.mysql-version }}-drupal ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 @@ -37,7 +54,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-versions }} + php-version: ${{ matrix.php-version }} - name: Validate composer.json run: composer validate diff --git a/src/Mysqldump.php b/src/Mysqldump.php index f33ae7de..e9478779 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -104,7 +104,7 @@ public function __construct( */ private function parseDsn(string $dsn): void { - if (empty($dsn) || (false === ($pos = strpos($dsn, ':')))) { + if (empty($dsn) || !($pos = strpos($dsn, ':'))) { throw new Exception('Empty DSN string'); } From 278e2d0dee9ac2918c9bdff23220033b9bd892d5 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 15:38:21 +0300 Subject: [PATCH 27/32] quote mysql-version --- .github/workflows/tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c754806f..59fb9fa0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,22 +18,22 @@ jobs: matrix: include: - os: ubuntu-18.04 - mysql-version: 5.7 + mysql-version: '5.7' php-version: 7.4 - os: ubuntu-18.04 - mysql-version: 5.7 + mysql-version: '5.7' php-version: 8.0 - os: ubuntu-18.04 - mysql-version: 5.7 + mysql-version: '5.7' php-version: 8.1 - os: ubuntu-latest - mysql-version: 8.0 + mysql-version: '8.0' php-version: 7.4 - os: ubuntu-latest - mysql-version: 8.0 + mysql-version: '8.0' php-version: 8.0 - os: ubuntu-latest - mysql-version: 8.0 + mysql-version: '8.0' php-version: 8.1 services: From 687066f1c37c80b322063834fcab91b044cc802b Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 15:47:31 +0300 Subject: [PATCH 28/32] quote also php-version --- .github/workflows/tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 59fb9fa0..38fb51c1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,22 +19,22 @@ jobs: include: - os: ubuntu-18.04 mysql-version: '5.7' - php-version: 7.4 + php-version: '7.4' - os: ubuntu-18.04 mysql-version: '5.7' - php-version: 8.0 + php-version: '8.0' - os: ubuntu-18.04 mysql-version: '5.7' - php-version: 8.1 + php-version: '8.1' - os: ubuntu-latest mysql-version: '8.0' - php-version: 7.4 + php-version: '7.4' - os: ubuntu-latest mysql-version: '8.0' - php-version: 8.0 + php-version: '8.0' - os: ubuntu-latest mysql-version: '8.0' - php-version: 8.1 + php-version: '8.1' services: db: From 9db633cdf91d8be4404b645caaf019abb4a95652 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 16:07:38 +0300 Subject: [PATCH 29/32] Add PDO checks --- tests/scripts/pdo_checks.php | 35 +++++++++++++++++++++++++++++++++++ tests/scripts/test.php | 2 -- tests/scripts/test.sh | 2 ++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/scripts/pdo_checks.php diff --git a/tests/scripts/pdo_checks.php b/tests/scripts/pdo_checks.php new file mode 100644 index 00000000..9d7edb92 --- /dev/null +++ b/tests/scripts/pdo_checks.php @@ -0,0 +1,35 @@ + true, +]); + +$q = $db->query('SELECT * FROM test000'); +$q->setFetchMode(\PDO::FETCH_ASSOC); + +foreach ($q as $result) { + if ($result['col15'] === $expected_double) { + echo "Success: Double value is the expected!" . PHP_EOL; + $ret = 0; + } else { + echo "Fail: double value is not expected..." . PHP_EOL; + echo "Expected: " . $expected_double . PHP_EOL; + echo "Actual: " . $result['col15'] . PHP_EOL; + $ret = 1; + } +} + +echo PHP_EOL; + +exit($ret); diff --git a/tests/scripts/test.php b/tests/scripts/test.php index fe7e4da6..1b038bcd 100644 --- a/tests/scripts/test.php +++ b/tests/scripts/test.php @@ -32,8 +32,6 @@ 'where' => '' ]; -print "PHP version is ". phpversion() . PHP_EOL; - try { // do nothing test print "Create dump with PHP: mysql-php_test000.sql" . PHP_EOL; diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index 46552ce9..c64a3104 100755 --- a/tests/scripts/test.sh +++ b/tests/scripts/test.sh @@ -152,6 +152,8 @@ $MYSQLDUMP_CMD test001 \ errCode=$?; ret[((index++))]=$errCode printf "\nRun mysqldump with PHP:\n\n" +php pdo_checks.php $HOST || { echo "ERROR running pdo_checks.php" && exit -1; } +errCode=$?; ret[((index++))]=$errCode php test.php $HOST || { echo "ERROR running test.php" && exit -1; } errCode=$?; ret[((index++))]=$errCode From 58fc8c3078336e11a035faeed74fe2473f963eae Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 16:30:56 +0300 Subject: [PATCH 30/32] Combine .gitignore files --- .gitignore | 2 ++ tests/scripts/.gitignore | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 tests/scripts/.gitignore diff --git a/.gitignore b/.gitignore index 26762b99..da63043c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,7 @@ **/*.checksum /composer.lock /composer.phar +/tests/scripts/output/*.checksum +/tests/scripts/output/*.sql /vendor/ /.phpunit.result.cache diff --git a/tests/scripts/.gitignore b/tests/scripts/.gitignore deleted file mode 100644 index ac53d56b..00000000 --- a/tests/scripts/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -output/*.sql -output/*.checksum From 3dda2d5bdbefe0bded631932a461b37b452a51f4 Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 16:36:27 +0300 Subject: [PATCH 31/32] Clean constructor --- src/Mysqldump.php | 26 +++++++++++++------------- tests/scripts/pdo_checks.php | 3 +++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index e9478779..8a3603d0 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -81,15 +81,7 @@ public function __construct( $this->user = $user; $this->pass = $pass; $this->settings = new DumpSettings($settings); - - $this->pdoOptions = array_replace_recursive([ - PDO::ATTR_PERSISTENT => true, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, - ], $pdoOptions); - - // Create a new compressManager to manage compressed output - $this->io = CompressManagerFactory::create($this->settings->getCompressMethod()); + $this->pdoOptions = $pdoOptions; } /** @@ -144,15 +136,20 @@ private function parseDsn(string $dsn): void private function connect() { try { - $this->conn = new PDO($this->dsn, $this->user, $this->pass, $this->pdoOptions); + $options = array_replace_recursive([ + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + // Don't convert empty strings to SQL NULL values on data fetches. + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, + ], $this->pdoOptions); + + $this->conn = new PDO($this->dsn, $this->user, $this->pass, $options); } catch (PDOException $e) { $message = sprintf("Connection to %s failed with message: %s", $this->host, $e->getMessage()); throw new Exception($message); } - // Don't convert empty strings to SQL NULL values on data fetches. - $this->conn->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); - $this->db = $this->getAdapter(); } @@ -184,6 +181,9 @@ public function start(?string $filename = '') // Connect to database $this->connect(); + // Create a new compressManager to manage compressed output + $this->io = CompressManagerFactory::create($this->settings->getCompressMethod()); + // Create output file $this->io->open($destination); diff --git a/tests/scripts/pdo_checks.php b/tests/scripts/pdo_checks.php index 9d7edb92..c9d11099 100644 --- a/tests/scripts/pdo_checks.php +++ b/tests/scripts/pdo_checks.php @@ -12,6 +12,9 @@ print "PDO check: double field" . PHP_EOL . PHP_EOL; $db = new \PDO("mysql:host=$host;dbname=test001", $user, null, [ + \PDO::ATTR_PERSISTENT => true, + \PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, \PDO::ATTR_STRINGIFY_FETCHES => true, ]); From 540a34f2294b97e8126d2d407056f888c4b5ea5e Mon Sep 17 00:00:00 2001 From: Marko Korhonen Date: Tue, 23 Aug 2022 16:46:36 +0300 Subject: [PATCH 32/32] Refactor parseDsn method --- src/Mysqldump.php | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Mysqldump.php b/src/Mysqldump.php index 8a3603d0..0bc0e363 100644 --- a/src/Mysqldump.php +++ b/src/Mysqldump.php @@ -76,8 +76,7 @@ public function __construct( array $settings = [], array $pdoOptions = [] ) { - $this->parseDsn($dsn); - + $this->dsn = $this->parseDsn($dsn); $this->user = $user; $this->pass = $pass; $this->settings = new DumpSettings($settings); @@ -94,38 +93,37 @@ public function __construct( * @param string $dsn dsn string to parse * @throws Exception */ - private function parseDsn(string $dsn): void + private function parseDsn(string $dsn): string { if (empty($dsn) || !($pos = strpos($dsn, ':'))) { throw new Exception('Empty DSN string'); } - $this->dsn = $dsn; $dbType = strtolower(substr($dsn, 0, $pos)); if (empty($dbType)) { throw new Exception('Missing database type from DSN string'); } - $dsn = substr($dsn, $pos + 1); - $dsnArray = []; + $data = []; - foreach (explode(';', $dsn) as $kvp) { - $kvpArr = explode('=', $kvp); - $dsnArray[strtolower($kvpArr[0])] = $kvpArr[1]; + foreach (explode(';', substr($dsn, $pos + 1)) as $kvp) { + list($param, $value) = explode('=', $kvp); + $data[strtolower($param)] = $value; } - if (empty($dsnArray['host']) && empty($dsnArray['unix_socket'])) { + if (empty($data['host']) && empty($data['unix_socket'])) { throw new Exception('Missing host from DSN string'); } - $this->host = (!empty($dsnArray['host'])) ? $dsnArray['host'] : $dsnArray['unix_socket']; - - if (empty($dsnArray['dbname'])) { + if (empty($data['dbname'])) { throw new Exception('Missing database name from DSN string'); } - $this->dbName = $dsnArray['dbname']; + $this->host = (!empty($data['host'])) ? $data['host'] : $data['unix_socket']; + $this->dbName = $data['dbname']; + + return $dsn; } /**