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 f622af6b..38fb51c1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,17 +11,34 @@ jobs: tests: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: - #mysql-versions: ['5.7', '8.0'] - mysql-versions: ['5.7'] - 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 @@ -48,8 +65,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: cd tests/scripts && ./create_users.sh 127.0.0.1 + + - name: Run test script + run: cd tests/scripts && ./test.sh 127.0.0.1 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/Dockerfile b/Dockerfile index e78671cb..75e08f3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,21 @@ -FROM php:8.1-alpine +ARG PHP_SHORT_VERSION -RUN apk --update add --no-cache \ - bash mysql-client \ - && rm -rf /var/cache/apk/* +FROM druidfi/php:7.4 as php-74 -RUN docker-php-ext-install mysqli pdo pdo_mysql +RUN sudo apk --update -X https://dl-cdn.alpinelinux.org/alpine/edge/testing --no-cache add php7-pdo php7-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/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 81d396c0..ce61f4f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,54 @@ +version: '3.7' + 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 + + php74: + container_name: "mysqldump-php-74" + image: mysqldump-php-tester:php-7.4 + build: + context: . + args: + PHP_SHORT_VERSION: "74" + volumes: + - .:/app + depends_on: + - db + - db2 + + php80: + container_name: "mysqldump-php-80" + image: mysqldump-php-tester:php-8.0 + build: + context: . + args: + PHP_SHORT_VERSION: "80" + volumes: + - .:/app + depends_on: + - db + - db2 - php: - container_name: "mysqldump-php" - image: tester:latest + php81: + container_name: "mysqldump-php-81" + image: mysqldump-php-tester:php-8.1 build: context: . + args: + PHP_SHORT_VERSION: "81" volumes: - .:/app depends_on: - db + - db2 diff --git a/src/DumpSettings.php b/src/DumpSettings.php new file mode 100644 index 00000000..37ab2a08 --- /dev/null +++ b/src/DumpSettings.php @@ -0,0 +1,165 @@ + [], + '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 getDefaultCharacterSet(): string + { + return $this->settings['default-character-set']; + } + + 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..0bc0e363 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,30 +25,20 @@ 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; 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; private TypeAdapterInterface $db; - private array $typeAdapters = [ - 'mysql' => TypeAdapterMysql::class, - ]; + private static string $adapterClass = TypeAdapterMysql::class; - private array $dumpSettings; + private DumpSettings $settings; private array $tableColumnTypes = []; private $transformTableRowCallable; private $transformColumnValueCallable; @@ -70,153 +60,27 @@ 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 */ public function __construct( - string $dsn = '', + string $dsn = '', ?string $user = null, ?string $pass = null, - array $dumpSettings = [], - array $pdoOptions = [] + 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 - ]; - - $pdoOptionsDefault = [ - PDO::ATTR_PERSISTENT => true, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ]; - + $this->dsn = $this->parseDsn($dsn); $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']); - } - - /** - * 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->dumpSettings['where']) { - return $this->dumpSettings['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; - } - - $limit = $this->tableLimits[$tableName]; - - if (!is_numeric($limit)) { - return false; - } - - return $limit; + $this->settings = new DumpSettings($settings); + $this->pdoOptions = $pdoOptions; } /** @@ -229,43 +93,37 @@ public function getTableLimit(string $tableName) * @param string $dsn dsn string to parse * @throws Exception */ - private function parseDsn(string $dsn): void + private function parseDsn(string $dsn): string { - if (empty($dsn) || (false === ($pos = strpos($dsn, ':')))) { + if (empty($dsn) || !($pos = strpos($dsn, ':'))) { throw new Exception('Empty DSN string'); } - $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'); } - 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 = []; + $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; } /** @@ -276,17 +134,31 @@ 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); } - $this->conn->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); + $this->db = $this->getAdapter(); + } - /** @var TypeAdapterInterface $typeAdapterClass */ - $typeAdapterClass = $this->typeAdapters[$this->dbType]; - $this->db = new $typeAdapterClass($this->conn, $this->dumpSettings); + public function getAdapter(): TypeAdapterInterface + { + return new self::$adapterClass($this->conn, $this->settings); + } + + private function write(string $data): int + { + return $this->io->write($data); } /** @@ -307,22 +179,25 @@ 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); // Write some basic info to output file - if (!$this->dumpSettings['skip-comments']) { - $this->io->write($this->getDumpFileHeader()); + if (!$this->settings->skipComments()) { + $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->dumpSettings['databases']) { - $this->io->write($this->db->getDatabaseHeader($this->dbName)); + if ($this->settings->isEnabled('databases')) { + $this->write($this->db->getDatabaseHeader($this->dbName)); - if ($this->dumpSettings['add-drop-database']) { - $this->io->write($this->db->addDropDatabase($this->dbName)); + if ($this->settings->isEnabled('add-drop-database')) { + $this->write($this->db->addDropDatabase($this->dbName)); } } @@ -334,14 +209,14 @@ public function start(?string $filename = '') $this->getDatabaseStructureFunctions(); $this->getDatabaseStructureEvents(); - if ($this->dumpSettings['databases']) { - $this->io->write($this->db->databases($this->dbName)); + if ($this->settings->isEnabled('databases')) { + $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 // 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); } @@ -354,11 +229,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->dumpSettings['skip-comments']) { - $this->io->write($this->getDumpFileFooter()); + if (!$this->settings->skipComments()) { + $this->write($this->getDumpFileFooter()); } // Close output file. @@ -384,7 +259,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 +273,7 @@ private function getDumpFileFooter(): string { $footer = '-- Dump completed'; - if (!$this->dumpSettings['skip-dump-date']) { + if (!$this->settings->skipDumpDate()) { $footer .= ' on: '.date('r'); } @@ -412,8 +287,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 +298,11 @@ 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); + // TODO should this be done in DumpSettings? + unset($includedTables[$elem]); } } } @@ -435,8 +313,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 +324,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 +339,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 +352,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 +365,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 +378,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 +412,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 +434,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 +446,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; } @@ -607,6 +487,7 @@ private function exportFunctions() /** * Exports all the events found in database. + * @throws Exception */ private function exportEvents() { @@ -619,31 +500,31 @@ 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); foreach ($this->conn->query($stmt) as $r) { - $this->io->write($ret); + $this->write($ret); - if ($this->dumpSettings['add-drop-table']) { - $this->io->write( - $this->db->dropTable($tableName) - ); + if ($this->settings->isEnabled('add-drop-table')) { + $this->write($this->db->dropTable($tableName)); } - $this->io->write($this->db->createTable($r)); + $this->write($this->db->createTable($r)); break; } @@ -682,29 +563,28 @@ 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) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = ( '--' . PHP_EOL . sprintf('-- Stand-In structure for view `%s`', $viewName) . PHP_EOL . '--' . PHP_EOL . PHP_EOL ); - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateView($viewName); // create views as tables, to resolve dependencies foreach ($this->conn->query($stmt) as $r) { - if ($this->dumpSettings['add-drop-table']) { - $this->io->write($this->db->dropView($viewName)); + if ($this->settings->isEnabled('add-drop-table')) { + $this->write($this->db->dropView($viewName)); } - $this->io->write($this->createStandInTable($viewName)); + $this->write($this->createStandInTable($viewName)); break; } @@ -717,7 +597,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 = []; @@ -736,13 +616,10 @@ 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) { - if (!$this->dumpSettings['skip-comments']) { + if (!$this->settings->skipComments()) { $ret = sprintf( "--". PHP_EOL. "-- View structure for view `%s`". PHP_EOL. @@ -750,7 +627,7 @@ private function getViewStructureView(string $viewName) $viewName ); - $this->io->write($ret); + $this->write($ret); } $stmt = $this->db->showCreateView($viewName); @@ -758,8 +635,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; } @@ -775,11 +652,11 @@ private function getTriggerStructure(string $triggerName) $stmt = $this->db->showCreateTrigger($triggerName); foreach ($this->conn->query($stmt) as $r) { - if ($this->dumpSettings['add-drop-trigger']) { - $this->io->write($this->db->addDropTrigger($triggerName)); + if ($this->settings->isEnabled('add-drop-trigger')) { + $this->write($this->db->addDropTrigger($triggerName)); } - $this->io->write($this->db->createTrigger($r)); + $this->write($this->db->createTrigger($r)); return; } @@ -792,17 +669,17 @@ 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; - $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; } @@ -815,17 +692,17 @@ 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; - $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; } @@ -839,17 +716,17 @@ 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; - $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; } @@ -888,7 +765,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 { @@ -901,22 +778,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. * @@ -934,7 +795,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); } @@ -944,50 +805,54 @@ 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); $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)) { - $lineSize += $this->io->write( - "INSERT$ignore INTO `$tableName` (". - implode(", ", $colNames). - ") VALUES (".implode(",", $values).")" - ); + $valueList = implode(',', $values); + + if ($onlyOnce || !$this->settings->isEnabled('extended-insert')) { + if ($this->settings->isEnabled('complete-insert') && count($colNames)) { + $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->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); + $lineSize = $this->write(';' . PHP_EOL); } } $resultSet->closeCursor(); if (!$onlyOnce) { - $this->io->write(";".PHP_EOL); + $this->write(';' . PHP_EOL); } $this->endListValues($tableName, $count); @@ -1002,36 +867,36 @@ 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->dumpSettings['skip-comments']) { - $this->io->write( + if (!$this->settings->skipComments()) { + $this->write( "--".PHP_EOL. "-- Dumping data for table `$tableName`".PHP_EOL. "--".PHP_EOL.PHP_EOL ); } - 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']) { - $this->io->write($this->db->startAddLockTable($tableName)); + if ($this->settings->isEnabled('add-locks')) { + $this->write($this->db->startAddLockTable($tableName)); } - if ($this->dumpSettings['disable-keys']) { - $this->io->write($this->db->startAddDisableKeys($tableName)); + if ($this->settings->isEnabled('disable-keys')) { + $this->write($this->db->startAddDisableKeys($tableName)); } // Disable autocommit for faster reload - if ($this->dumpSettings['no-autocommit']) { - $this->io->write($this->db->startDisableAutocommit()); + if ($this->settings->isEnabled('no-autocommit')) { + $this->write($this->db->startDisableAutocommit()); } } @@ -1041,33 +906,34 @@ 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->dumpSettings['disable-keys']) { - $this->io->write($this->db->endAddDisableKeys($tableName)); + if ($this->settings->isEnabled('disable-keys')) { + $this->write($this->db->endAddDisableKeys($tableName)); } - if ($this->dumpSettings['add-locks']) { - $this->io->write($this->db->endAddLockTable($tableName)); + if ($this->settings->isEnabled('add-locks')) { + $this->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']) { - $this->io->write($this->db->endDisableAutocommit()); + if ($this->settings->isEnabled('no-autocommit')) { + $this->write($this->db->endDisableAutocommit()); } - $this->io->write(PHP_EOL); + $this->write(PHP_EOL); - if (!$this->dumpSettings['skip-comments']) { - $this->io->write( + if (!$this->settings->skipComments()) { + $this->write( "-- Dumped table `".$tableName."` with $count row(s)".PHP_EOL. '--'.PHP_EOL.PHP_EOL ); @@ -1081,17 +947,17 @@ 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 = []; 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); } @@ -1107,13 +973,13 @@ 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 = []; foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) { if ($colType['is_virtual']) { - $this->dumpSettings['complete-insert'] = true; + $this->settings->setCompleteInsert(); } else { $colNames[] = sprintf('`%s`', $colName); } @@ -1122,11 +988,82 @@ 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 + * + * @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; + } + + /** + * 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; } } diff --git a/src/TypeAdapter/TypeAdapterMysql.php b/src/TypeAdapter/TypeAdapterMysql.php index a4c81156..9688008d 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->getDefaultCharacterSet() ." */;".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->getDefaultCharacterSet()." */;".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->getDefaultCharacterSet() ." */;".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/.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 diff --git a/tests/scripts/create_users.sh b/tests/scripts/create_users.sh index 2de59fce..ee84109e 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\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;" @@ -38,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;" diff --git a/tests/scripts/pdo_checks.php b/tests/scripts/pdo_checks.php new file mode 100644 index 00000000..c9d11099 --- /dev/null +++ b/tests/scripts/pdo_checks.php @@ -0,0 +1,38 @@ + true, + \PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, + \PDO::ATTR_STRINGIFY_FETCHES => 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 b6a146fd..1b038bcd 100644 --- a/tests/scripts/test.php +++ b/tests/scripts/test.php @@ -5,10 +5,11 @@ require __DIR__ . '/../../vendor/autoload.php'; +use Druidfi\Mysqldump\DumpSettings; use Druidfi\Mysqldump\Mysqldump; use Druidfi\Mysqldump\Compress\CompressManagerFactory; -$host = 'db'; +$host = $argv[1] ?? 'db'; // Get host name from test.sh $user = 'travis'; $dumpSettings = [ @@ -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"); diff --git a/tests/scripts/test.sh b/tests/scripts/test.sh index 75be3210..c64a3104 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" @@ -146,7 +152,9 @@ $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 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 printf "\nImport generated SQL dumps...\n\n" @@ -188,7 +196,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" @@ -209,7 +217,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" @@ -293,7 +301,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 @@ -329,7 +337,10 @@ 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" +else + echo -e "\nThere are errors. Exiting with code $retvalue" fi -echo -e "\nExiting with code $retvalue" exit $retvalue 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