Passed
Pull Request — master (#141)
by
unknown
03:12
created

Mysqldump::hookTransformColumnStmt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * PHP version of mysqldump cli that comes with MySQL
5
 *
6
 * mysql mysqldump pdo php7 php5 database php sql
7
 *
8
 * @category Library
9
 * @package  Ifsnop\Mysqldump
10
 * @author   Diego Torres <[email protected]>
11
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
12
 * @link     https://github.com/ifsnop/mysqldump-php
13
 *
14
 */
15
16
namespace Ifsnop\Mysqldump;
17
18
use Exception;
19
use PDO;
20
use PDOException;
21
22
/**
23
 * PHP version of mysqldump cli that comes with MySQL
24
 *
25
 * mysql mysqldump pdo php7 php5 database php sql
26
 *
27
 * @category Library
28
 * @package  Ifsnop\Mysqldump
29
 * @author   Diego Torres <[email protected]>
30
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
31
 * @link     https://github.com/ifsnop/mysqldump-php
32
 *
33
 */
34
class Mysqldump
35
{
36
37
    // Same as mysqldump
38
    const MAXLINESIZE = 1000000;
39
40
    // Available compression methods as constants
41
    const GZIP = 'Gzip';
42
    const BZIP2 = 'Bzip2';
43
    const NONE = 'None';
44
45
    // Available connection strings
46
    const UTF8 = 'utf8';
47
    const UTF8MB4 = 'utf8mb4';
48
49
    /**
50
     * Database username
51
     * @var string
52
     */
53
    public $user;
54
    /**
55
     * Database password
56
     * @var string
57
     */
58
    public $pass;
59
    /**
60
     * Connection string for PDO
61
     * @var string
62
     */
63
    public $dsn;
64
    /**
65
     * Destination filename, defaults to stdout
66
     * @var string
67
     */
68
    public $fileName = 'php://output';
69
70
    // Internal stuff
71
    private $tables = array();
72
    private $views = array();
73
    private $triggers = array();
74
    private $procedures = array();
75
    private $events = array();
76
    private $dbHandler = null;
77
    private $dbType = "";
78
    private $compressManager;
79
    private $typeAdapter;
80
    private $dumpSettings = array();
81
    private $pdoSettings = array();
82
    private $version;
83
    private $tableColumnTypes = array();
84
    /**
85
     * database name, parsed from dsn
86
     * @var string
87
     */
88
    private $dbName;
89
    /**
90
     * host name, parsed from dsn
91
     * @var string
92
     */
93
    private $host;
94
    /**
95
     * dsn string parsed as an array
96
     * @var array
97
     */
98
    private $dsnArray = array();
99
100
    /**
101
     * Constructor of Mysqldump. Note that in the case of an SQLite database
102
     * connection, the filename must be in the $db parameter.
103
     *
104
     * @param string $dsn        PDO DSN connection string
105
     * @param string $user       SQL account username
106
     * @param string $pass       SQL account password
107
     * @param array  $dumpSettings SQL database settings
108
     * @param array  $pdoSettings  PDO configured attributes
109
     */
110
    public function __construct(
111
        $dsn = '',
112
        $user = '',
113
        $pass = '',
114
        $dumpSettings = array(),
115
        $pdoSettings = array()
116
    ) {
117
        $dumpSettingsDefault = array(
118
            'include-tables' => array(),
119
            'exclude-tables' => array(),
120
            'compress' => Mysqldump::NONE,
121
            'init_commands' => array(),
122
            'no-data' => array(),
123
            'reset-auto-increment' => false,
124
            'add-drop-database' => false,
125
            'add-drop-table' => false,
126
            'add-drop-trigger' => true,
127
            'add-locks' => true,
128
            'complete-insert' => false,
129
            'databases' => false,
130
            'default-character-set' => Mysqldump::UTF8,
131
            'disable-keys' => true,
132
            'extended-insert' => true,
133
            'events' => false,
134
            'hex-blob' => true, /* faster than escaped content */
135
            'net_buffer_length' => self::MAXLINESIZE,
136
            'no-autocommit' => true,
137
            'no-create-info' => false,
138
            'lock-tables' => true,
139
            'routines' => false,
140
            'single-transaction' => true,
141
            'skip-triggers' => false,
142
            'skip-tz-utc' => false,
143
            'skip-comments' => false,
144
            'skip-dump-date' => false,
145
            'skip-definer' => false,
146
            'where' => '',
147
            /* deprecated */
148
            'disable-foreign-keys-check' => true
149
        );
150
151
        $pdoSettingsDefault = array(
152
            PDO::ATTR_PERSISTENT => true,
153
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
154
        );
155
156
        $this->user = $user;
157
        $this->pass = $pass;
158
        $this->parseDsn($dsn);
159
160
        // this drops MYSQL dependency, only use the constant if it's defined
161
        if ("mysql" === $this->dbType) {
0 ignored issues
show
introduced by
The condition 'mysql' === $this->dbType is always false.
Loading history...
162
            $pdoSettingsDefault[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false;
163
        }
164
165
        $this->pdoSettings = self::array_replace_recursive($pdoSettingsDefault, $pdoSettings);
166
        $this->dumpSettings = self::array_replace_recursive($dumpSettingsDefault, $dumpSettings);
167
        $this->dumpSettings['init_commands'][] = "SET NAMES ".$this->dumpSettings['default-character-set'];
168
169
        if (false === $this->dumpSettings['skip-tz-utc']) {
170
            $this->dumpSettings['init_commands'][] = "SET TIME_ZONE='+00:00'";
171
        }
172
173
        $diff = array_diff(array_keys($this->dumpSettings), array_keys($dumpSettingsDefault));
174
        if (count($diff) > 0) {
175
            throw new Exception("Unexpected value in dumpSettings: (".implode(",", $diff).")");
176
        }
177
178
        if (!is_array($this->dumpSettings['include-tables']) ||
179
            !is_array($this->dumpSettings['exclude-tables'])) {
180
            throw new Exception("Include-tables and exclude-tables should be arrays");
181
        }
182
183
        // Dump the same views as tables, mimic mysqldump behaviour
184
        $this->dumpSettings['include-views'] = $this->dumpSettings['include-tables'];
185
186
        // Create a new compressManager to manage compressed output
187
        $this->compressManager = CompressManagerFactory::create($this->dumpSettings['compress']);
188
    }
189
190
    /**
191
     * Destructor of Mysqldump. Unsets dbHandlers and database objects.
192
     *
193
     */
194
    public function __destruct()
195
    {
196
        $this->dbHandler = null;
197
    }
198
199
    /**
200
     * Custom array_replace_recursive to be used if PHP < 5.3
201
     * Replaces elements from passed arrays into the first array recursively
202
     *
203
     * @param array $array1 The array in which elements are replaced
204
     * @param array $array2 The array from which elements will be extracted
205
     *
206
     * @return array Returns an array, or NULL if an error occurs.
207
     */
208
    public static function array_replace_recursive($array1, $array2)
209
    {
210
        if (function_exists('array_replace_recursive')) {
211
            return array_replace_recursive($array1, $array2);
212
        }
213
214
        foreach ($array2 as $key => $value) {
215
            if (is_array($value)) {
216
                $array1[$key] = self::array_replace_recursive($array1[$key], $value);
217
            } else {
218
                $array1[$key] = $value;
219
            }
220
        }
221
        return $array1;
222
    }
223
224
    /**
225
     * Parse DSN string and extract dbname value
226
     * Several examples of a DSN string
227
     *   mysql:host=localhost;dbname=testdb
228
     *   mysql:host=localhost;port=3307;dbname=testdb
229
     *   mysql:unix_socket=/tmp/mysql.sock;dbname=testdb
230
     *
231
     * @param string $dsn dsn string to parse
232
     */
233
    private function parseDsn($dsn)
234
    {
235
        if (empty($dsn) || (false === ($pos = strpos($dsn, ":")))) {
236
            throw new Exception("Empty DSN string");
237
        }
238
239
        $this->dsn = $dsn;
240
        $this->dbType = strtolower(substr($dsn, 0, $pos));
241
242
        if (empty($this->dbType)) {
243
            throw new Exception("Missing database type from DSN string");
244
        }
245
246
        $dsn = substr($dsn, $pos + 1);
247
248
        foreach (explode(";", $dsn) as $kvp) {
249
            $kvpArr = explode("=", $kvp);
250
            $this->dsnArray[strtolower($kvpArr[0])] = $kvpArr[1];
251
        }
252
253
        if (empty($this->dsnArray['host']) &&
254
            empty($this->dsnArray['unix_socket'])) {
255
            throw new Exception("Missing host from DSN string");
256
        }
257
        $this->host = (!empty($this->dsnArray['host'])) ?
258
            $this->dsnArray['host'] : $this->dsnArray['unix_socket'];
259
260
        if (empty($this->dsnArray['dbname'])) {
261
            throw new Exception("Missing database name from DSN string");
262
        }
263
264
        $this->dbName = $this->dsnArray['dbname'];
265
266
        // safety check
267
        if (!is_string($this->dbType)) {
0 ignored issues
show
introduced by
The condition is_string($this->dbType) is always true.
Loading history...
268
            throw new Exception("Invalid database type definition in DSN string");
269
        }
270
271
        return true;
272
    }
273
274
    /**
275
     * Connect with PDO
276
     *
277
     * @return null
278
     */
279
    private function connect()
280
    {
281
        // Connecting with PDO
282
        try {
283
            switch ($this->dbType) {
284
                case 'sqlite':
285
                    $this->dbHandler = @new PDO("sqlite:".$this->dbName, null, null, $this->pdoSettings);
286
                    break;
287
                case 'mysql':
288
                case 'pgsql':
289
                case 'dblib':
290
                    $this->dbHandler = @new PDO(
291
                        $this->dsn,
292
                        $this->user,
293
                        $this->pass,
294
                        $this->pdoSettings
295
                    );
296
                    // Execute init commands once connected
297
                    foreach ($this->dumpSettings['init_commands'] as $stmt) {
298
                        $this->dbHandler->exec($stmt);
299
                    }
300
                    // Store server version
301
                    $this->version = $this->dbHandler->getAttribute(PDO::ATTR_SERVER_VERSION);
302
                    break;
303
                default:
304
                    throw new Exception("Unsupported database type (".$this->dbType.")");
0 ignored issues
show
Bug introduced by
Are you sure $this->dbType of type mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

304
                    throw new Exception("Unsupported database type ("./** @scrutinizer ignore-type */ $this->dbType.")");
Loading history...
305
            }
306
        } catch (PDOException $e) {
307
            throw new Exception(
308
                "Connection to ".$this->dbType." failed with message: ".
309
                $e->getMessage()
310
            );
311
        }
312
313
        if (is_null($this->dbHandler)) {
314
            throw new Exception("Connection to ".$this->dbType."failed");
315
        }
316
317
        $this->dbHandler->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL);
318
        $this->typeAdapter = TypeAdapterFactory::create($this->dbType, $this->dbHandler, $this->dumpSettings);
319
    }
320
321
    /**
322
     * Main call
323
     *
324
     * @param string $filename  Name of file to write sql dump to
325
     * @return null
326
     */
327
    public function start($filename = '')
328
    {
329
        // Output file can be redefined here
330
        if (!empty($filename)) {
331
            $this->fileName = $filename;
332
        }
333
334
        // Connect to database
335
        $this->connect();
336
337
        // Create output file
338
        $this->compressManager->open($this->fileName);
339
340
        // Write some basic info to output file
341
        $this->compressManager->write($this->getDumpFileHeader());
342
343
        // Store server settings and use sanner defaults to dump
344
        $this->compressManager->write(
345
            $this->typeAdapter->backup_parameters()
346
        );
347
348
        if ($this->dumpSettings['databases']) {
349
            $this->compressManager->write(
350
                $this->typeAdapter->getDatabaseHeader($this->dbName)
351
            );
352
            if ($this->dumpSettings['add-drop-database']) {
353
                $this->compressManager->write(
354
                    $this->typeAdapter->add_drop_database($this->dbName)
355
                );
356
            }
357
        }
358
359
        // Get table, view and trigger structures from database
360
        $this->getDatabaseStructure();
361
362
        if ($this->dumpSettings['databases']) {
363
            $this->compressManager->write(
364
                $this->typeAdapter->databases($this->dbName)
365
            );
366
        }
367
368
        // If there still are some tables/views in include-tables array,
369
        // that means that some tables or views weren't found.
370
        // Give proper error and exit.
371
        // This check will be removed once include-tables supports regexps
372
        if (0 < count($this->dumpSettings['include-tables'])) {
373
            $name = implode(",", $this->dumpSettings['include-tables']);
374
            throw new Exception("Table (".$name.") not found in database");
375
        }
376
377
        $this->exportTables();
378
        $this->exportTriggers();
379
        $this->exportViews();
380
        $this->exportProcedures();
381
        $this->exportEvents();
382
383
        // Restore saved parameters
384
        $this->compressManager->write(
385
            $this->typeAdapter->restore_parameters()
386
        );
387
        // Write some stats to output file
388
        $this->compressManager->write($this->getDumpFileFooter());
389
        // Close output file
390
        $this->compressManager->close();
391
    }
392
393
    /**
394
     * Returns header for dump file
395
     *
396
     * @return string
397
     */
398
    private function getDumpFileHeader()
399
    {
400
        $header = '';
401
        if (!$this->dumpSettings['skip-comments']) {
402
            // Some info about software, source and time
403
            $header = "-- mysqldump-php https://github.com/ifsnop/mysqldump-php".PHP_EOL.
404
                    "--".PHP_EOL.
405
                    "-- Host: {$this->host}\tDatabase: {$this->dbName}".PHP_EOL.
406
                    "-- ------------------------------------------------------".PHP_EOL;
407
408
            if (!empty($this->version)) {
409
                $header .= "-- Server version \t".$this->version.PHP_EOL;
410
            }
411
412
            if (!$this->dumpSettings['skip-dump-date']) {
413
                $header .= "-- Date: ".date('r').PHP_EOL.PHP_EOL;
414
            }
415
        }
416
        return $header;
417
    }
418
419
    /**
420
     * Returns footer for dump file
421
     *
422
     * @return string
423
     */
424
    private function getDumpFileFooter()
425
    {
426
        $footer = '';
427
        if (!$this->dumpSettings['skip-comments']) {
428
            $footer .= '-- Dump completed';
429
            if (!$this->dumpSettings['skip-dump-date']) {
430
                $footer .= ' on: '.date('r');
431
            }
432
            $footer .= PHP_EOL;
433
        }
434
435
        return $footer;
436
    }
437
438
    /**
439
     * Reads table and views names from database.
440
     * Fills $this->tables array so they will be dumped later.
441
     *
442
     * @return null
443
     */
444
    private function getDatabaseStructure()
445
    {
446
        // Listing all tables from database
447
        if (empty($this->dumpSettings['include-tables'])) {
448
            // include all tables for now, blacklisting happens later
449
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
450
                array_push($this->tables, current($row));
451
            }
452
        } else {
453
            // include only the tables mentioned in include-tables
454
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
455
                if (in_array(current($row), $this->dumpSettings['include-tables'], true)) {
456
                    array_push($this->tables, current($row));
457
                    $elem = array_search(
458
                        current($row),
459
                        $this->dumpSettings['include-tables']
460
                    );
461
                    unset($this->dumpSettings['include-tables'][$elem]);
462
                }
463
            }
464
        }
465
466
        // Listing all views from database
467
        if (empty($this->dumpSettings['include-views'])) {
468
            // include all views for now, blacklisting happens later
469
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
470
                array_push($this->views, current($row));
471
            }
472
        } else {
473
            // include only the tables mentioned in include-tables
474
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
475
                if (in_array(current($row), $this->dumpSettings['include-views'], true)) {
476
                    array_push($this->views, current($row));
477
                    $elem = array_search(
478
                        current($row),
479
                        $this->dumpSettings['include-views']
480
                    );
481
                    unset($this->dumpSettings['include-views'][$elem]);
482
                }
483
            }
484
        }
485
486
        // Listing all triggers from database
487
        if (false === $this->dumpSettings['skip-triggers']) {
488
            foreach ($this->dbHandler->query($this->typeAdapter->show_triggers($this->dbName)) as $row) {
489
                array_push($this->triggers, $row['Trigger']);
490
            }
491
        }
492
493
        // Listing all procedures from database
494
        if ($this->dumpSettings['routines']) {
495
            foreach ($this->dbHandler->query($this->typeAdapter->show_procedures($this->dbName)) as $row) {
496
                array_push($this->procedures, $row['procedure_name']);
497
            }
498
        }
499
500
        // Listing all events from database
501
        if ($this->dumpSettings['events']) {
502
            foreach ($this->dbHandler->query($this->typeAdapter->show_events($this->dbName)) as $row) {
503
                array_push($this->events, $row['event_name']);
504
            }
505
        }
506
    }
507
508
    /**
509
     * Compare if $table name matches with a definition inside $arr
510
     * @param $table string
511
     * @param $arr array with strings or patterns
512
     * @return bool
513
     */
514
    private function matches($table, $arr) {
515
        $match = false;
516
517
        foreach ($arr as $pattern) {
518
            if ('/' != $pattern[0]) {
519
                continue;
520
            }
521
            if (1 == preg_match($pattern, $table)) {
522
                $match = true;
523
            }
524
        }
525
526
        return in_array($table, $arr) || $match;
527
    }
528
529
    /**
530
     * Exports all the tables selected from database
531
     *
532
     * @return null
533
     */
534
    private function exportTables()
535
    {
536
        // Exporting tables one by one
537
        foreach ($this->tables as $table) {
538
            if ($this->matches($table, $this->dumpSettings['exclude-tables'])) {
539
                continue;
540
            }
541
            $this->getTableStructure($table);
542
            if (false === $this->dumpSettings['no-data']) { // don't break compatibility with old trigger
543
                $this->listValues($table);
544
            } else if (true === $this->dumpSettings['no-data']
545
                 || $this->matches($table, $this->dumpSettings['no-data'])) {
546
                continue;
547
            } else {
548
                $this->listValues($table);
549
            }
550
        }
551
    }
552
553
    /**
554
     * Exports all the views found in database
555
     *
556
     * @return null
557
     */
558
    private function exportViews()
559
    {
560
        if (false === $this->dumpSettings['no-create-info']) {
561
            // Exporting views one by one
562
            foreach ($this->views as $view) {
563
                if ($this->matches($view, $this->dumpSettings['exclude-tables'])) {
564
                    continue;
565
                }
566
                $this->tableColumnTypes[$view] = $this->getTableColumnTypes($view);
567
                $this->getViewStructureTable($view);
568
            }
569
            foreach ($this->views as $view) {
570
                if ($this->matches($view, $this->dumpSettings['exclude-tables'])) {
571
                    continue;
572
                }
573
                $this->getViewStructureView($view);
574
            }
575
        }
576
    }
577
578
    /**
579
     * Exports all the triggers found in database
580
     *
581
     * @return null
582
     */
583
    private function exportTriggers()
584
    {
585
        // Exporting triggers one by one
586
        foreach ($this->triggers as $trigger) {
587
            $this->getTriggerStructure($trigger);
588
        }
589
    }
590
591
    /**
592
     * Exports all the procedures found in database
593
     *
594
     * @return null
595
     */
596
    private function exportProcedures()
597
    {
598
        // Exporting triggers one by one
599
        foreach ($this->procedures as $procedure) {
600
            $this->getProcedureStructure($procedure);
601
        }
602
    }
603
604
    /**
605
     * Exports all the events found in database
606
     *
607
     * @return null
608
     */
609
    private function exportEvents()
610
    {
611
        // Exporting triggers one by one
612
        foreach ($this->events as $event) {
613
            $this->getEventStructure($event);
614
        }
615
    }
616
617
    /**
618
     * Table structure extractor
619
     *
620
     * @todo move specific mysql code to typeAdapter
621
     * @param string $tableName  Name of table to export
622
     * @return null
623
     */
624
    private function getTableStructure($tableName)
625
    {
626
        if (!$this->dumpSettings['no-create-info']) {
627
            $ret = '';
628
            if (!$this->dumpSettings['skip-comments']) {
629
                $ret = "--".PHP_EOL.
630
                    "-- Table structure for table `$tableName`".PHP_EOL.
631
                    "--".PHP_EOL.PHP_EOL;
632
            }
633
            $stmt = $this->typeAdapter->show_create_table($tableName);
634
            foreach ($this->dbHandler->query($stmt) as $r) {
635
                $this->compressManager->write($ret);
636
                if ($this->dumpSettings['add-drop-table']) {
637
                    $this->compressManager->write(
638
                        $this->typeAdapter->drop_table($tableName)
639
                    );
640
                }
641
                $this->compressManager->write(
642
                    $this->typeAdapter->create_table($r)
643
                );
644
                break;
645
            }
646
        }
647
        $this->tableColumnTypes[$tableName] = $this->getTableColumnTypes($tableName);
648
        return;
649
    }
650
651
    /**
652
     * Store column types to create data dumps and for Stand-In tables
653
     *
654
     * @param string $tableName  Name of table to export
655
     * @return array type column types detailed
656
     */
657
658
    private function getTableColumnTypes($tableName) {
659
        $columnTypes = array();
660
        $columns = $this->dbHandler->query(
661
            $this->typeAdapter->show_columns($tableName)
662
        );
663
        $columns->setFetchMode(PDO::FETCH_ASSOC);
664
665
        foreach ($columns as $key => $col) {
666
            $types = $this->typeAdapter->parseColumnType($col);
667
            $columnTypes[$col['Field']] = array(
668
                'is_numeric'=> $types['is_numeric'],
669
                'is_blob' => $types['is_blob'],
670
                'type' => $types['type'],
671
                'type_sql' => $col['Type'],
672
                'is_virtual' => $types['is_virtual']
673
            );
674
        }
675
676
        return $columnTypes;
677
    }
678
679
    /**
680
     * View structure extractor, create table (avoids cyclic references)
681
     *
682
     * @todo move mysql specific code to typeAdapter
683
     * @param string $viewName  Name of view to export
684
     * @return null
685
     */
686
    private function getViewStructureTable($viewName)
687
    {
688
        if (!$this->dumpSettings['skip-comments']) {
689
            $ret = "--".PHP_EOL.
690
                "-- Stand-In structure for view `${viewName}`".PHP_EOL.
691
                "--".PHP_EOL.PHP_EOL;
692
            $this->compressManager->write($ret);
693
        }
694
        $stmt = $this->typeAdapter->show_create_view($viewName);
695
696
        // create views as tables, to resolve dependencies
697
        foreach ($this->dbHandler->query($stmt) as $r) {
698
            if ($this->dumpSettings['add-drop-table']) {
699
                $this->compressManager->write(
700
                    $this->typeAdapter->drop_view($viewName)
701
                );
702
            }
703
704
            $this->compressManager->write(
705
                $this->createStandInTable($viewName)
706
            );
707
            break;
708
        }
709
    }
710
711
    /**
712
     * Write a create table statement for the table Stand-In, show create
713
     * table would return a create algorithm when used on a view
714
     *
715
     * @param string $viewName  Name of view to export
716
     * @return string create statement
717
     */
718
    function createStandInTable($viewName) {
719
        $ret = array();
720
        foreach ($this->tableColumnTypes[$viewName] as $k => $v) {
721
            $ret[] = "`${k}` ${v['type_sql']}";
722
        }
723
        $ret = implode(PHP_EOL.",", $ret);
724
725
        $ret = "CREATE TABLE IF NOT EXISTS `$viewName` (".
726
            PHP_EOL.$ret.PHP_EOL.");".PHP_EOL;
727
728
        return $ret;
729
    }
730
731
    /**
732
     * View structure extractor, create view
733
     *
734
     * @todo move mysql specific code to typeAdapter
735
     * @param string $viewName  Name of view to export
736
     * @return null
737
     */
738
    private function getViewStructureView($viewName)
739
    {
740
        if (!$this->dumpSettings['skip-comments']) {
741
            $ret = "--".PHP_EOL.
742
                "-- View structure for view `${viewName}`".PHP_EOL.
743
                "--".PHP_EOL.PHP_EOL;
744
            $this->compressManager->write($ret);
745
        }
746
        $stmt = $this->typeAdapter->show_create_view($viewName);
747
748
        // create views, to resolve dependencies
749
        // replacing tables with views
750
        foreach ($this->dbHandler->query($stmt) as $r) {
751
            // because we must replace table with view, we should delete it
752
            $this->compressManager->write(
753
                $this->typeAdapter->drop_view($viewName)
754
            );
755
            $this->compressManager->write(
756
                $this->typeAdapter->create_view($r)
757
            );
758
            break;
759
        }
760
    }
761
762
    /**
763
     * Trigger structure extractor
764
     *
765
     * @param string $triggerName  Name of trigger to export
766
     * @return null
767
     */
768
    private function getTriggerStructure($triggerName)
769
    {
770
        $stmt = $this->typeAdapter->show_create_trigger($triggerName);
771
        foreach ($this->dbHandler->query($stmt) as $r) {
772
            if ($this->dumpSettings['add-drop-trigger']) {
773
                $this->compressManager->write(
774
                    $this->typeAdapter->add_drop_trigger($triggerName)
775
                );
776
            }
777
            $this->compressManager->write(
778
                $this->typeAdapter->create_trigger($r)
779
            );
780
            return;
781
        }
782
    }
783
784
    /**
785
     * Procedure structure extractor
786
     *
787
     * @param string $procedureName  Name of procedure to export
788
     * @return null
789
     */
790
    private function getProcedureStructure($procedureName)
791
    {
792
        if (!$this->dumpSettings['skip-comments']) {
793
            $ret = "--".PHP_EOL.
794
                "-- Dumping routines for database '".$this->dbName."'".PHP_EOL.
795
                "--".PHP_EOL.PHP_EOL;
796
            $this->compressManager->write($ret);
797
        }
798
        $stmt = $this->typeAdapter->show_create_procedure($procedureName);
799
        foreach ($this->dbHandler->query($stmt) as $r) {
800
            $this->compressManager->write(
801
                $this->typeAdapter->create_procedure($r)
802
            );
803
            return;
804
        }
805
    }
806
807
    /**
808
     * Event structure extractor
809
     *
810
     * @param string $eventName  Name of event to export
811
     * @return null
812
     */
813
    private function getEventStructure($eventName)
814
    {
815
        if (!$this->dumpSettings['skip-comments']) {
816
            $ret = "--".PHP_EOL.
817
                "-- Dumping events for database '".$this->dbName."'".PHP_EOL.
818
                "--".PHP_EOL.PHP_EOL;
819
            $this->compressManager->write($ret);
820
        }
821
        $stmt = $this->typeAdapter->show_create_event($eventName);
822
        foreach ($this->dbHandler->query($stmt) as $r) {
823
            $this->compressManager->write(
824
                $this->typeAdapter->create_event($r)
825
            );
826
            return;
827
        }
828
    }
829
830
    /**
831
     * Prepare values for output
832
     *
833
     * @param string $tableName Name of table which contains rows
834
     * @param array $row Associative array of column names and values to be
835
     *   quoted
836
     *
837
     * @return array
838
     */
839
    private function prepareColumnValues($tableName, $row)
840
    {
841
        $ret = [];
842
        $columnTypes = $this->tableColumnTypes[$tableName];
843
        foreach ($row as $colName => $colValue) {
844
            $colValue = $this->hookTransformColumnValue($tableName, $colName, $colValue);
845
            $ret[] = $this->escape($colValue, $columnTypes[$colName]);
846
        }
847
848
        return $ret;
849
    }
850
851
    /**
852
     * Escape values with quotes when needed
853
     *
854
     * @param string $tableName Name of table which contains rows
855
     * @param array $row Associative array of column names and values to be quoted
856
     *
857
     * @return string
858
     */
859
    private function escape($colValue, $colType)
860
    {
861
        if (is_null($colValue)) {
862
            return "NULL";
863
        } elseif ($this->dumpSettings['hex-blob'] && $colType['is_blob']) {
864
            if ($colType['type'] == 'bit' || !empty($colValue)) {
865
                return "0x${colValue}";
866
            } else {
867
                return "''";
868
            }
869
        } elseif ($colType['is_numeric']) {
870
            return $colValue;
871
        }
872
873
        return $this->dbHandler->quote($colValue);
874
    }
875
876
    /**
877
     * Give extending classes an opportunity to transform column values
878
     *
879
     * @param string $tableName Name of table which contains rows
880
     * @param string $colName Name of the column in question
881
     * @param string $colValue Value of the column in question
882
     *
883
     * @return string
884
     */
885
    protected function hookTransformColumnValue(
886
        /** @scrutinizer ignore-unused */ $tableName,
887
        /** @scrutinizer ignore-unused */ $colName,
888
        $colValue)
889
    {
890
        return $colValue;
891
    }
892
893
    /**
894
     * Table rows extractor
895
     *
896
     * @param string $tableName  Name of table to export
897
     *
898
     * @return null
899
     */
900
    private function listValues($tableName)
901
    {
902
        $this->prepareListValues($tableName);
903
904
        $onlyOnce = true;
905
        $lineSize = 0;
906
907
        $colStmt = $this->getColumnStmt($tableName);
908
        $stmt = "SELECT ".implode(",", $colStmt)." FROM `$tableName`";
909
910
        if ($this->dumpSettings['where']) {
911
            $stmt .= " WHERE {$this->dumpSettings['where']}";
912
        }
913
        $resultSet = $this->dbHandler->query($stmt);
914
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
915
916
        foreach ($resultSet as $row) {
917
            $vals = $this->prepareColumnValues($tableName, $row);
918
            if ($onlyOnce || !$this->dumpSettings['extended-insert']) {
919
920
                if ($this->dumpSettings['complete-insert']) {
921
                    $lineSize += $this->compressManager->write(
922
                        "INSERT INTO `$tableName` (".
923
                        implode(", ", $colStmt).
924
                        ") VALUES (".implode(",", $vals).")"
925
                    );
926
                } else {
927
                    $lineSize += $this->compressManager->write(
928
                        "INSERT INTO `$tableName` VALUES (".implode(",", $vals).")"
929
                    );
930
                }
931
                $onlyOnce = false;
932
            } else {
933
                $lineSize += $this->compressManager->write(",(".implode(",", $vals).")");
934
            }
935
            if (($lineSize > $this->dumpSettings['net_buffer_length']) ||
936
                    !$this->dumpSettings['extended-insert']) {
937
                $onlyOnce = true;
938
                $lineSize = $this->compressManager->write(";".PHP_EOL);
939
            }
940
        }
941
        $resultSet->closeCursor();
942
943
        if (!$onlyOnce) {
944
            $this->compressManager->write(";".PHP_EOL);
945
        }
946
947
        $this->endListValues($tableName);
948
    }
949
950
    /**
951
     * Table rows extractor, append information prior to dump
952
     *
953
     * @param string $tableName  Name of table to export
954
     *
955
     * @return null
956
     */
957
    function prepareListValues($tableName)
958
    {
959
        if (!$this->dumpSettings['skip-comments']) {
960
            $this->compressManager->write(
961
                "--".PHP_EOL.
962
                "-- Dumping data for table `$tableName`".PHP_EOL.
963
                "--".PHP_EOL.PHP_EOL
964
            );
965
        }
966
967
        if ($this->dumpSettings['single-transaction']) {
968
            $this->dbHandler->exec($this->typeAdapter->setup_transaction());
969
            $this->dbHandler->exec($this->typeAdapter->start_transaction());
970
        }
971
972
        if ($this->dumpSettings['lock-tables']) {
973
            $this->typeAdapter->lock_table($tableName);
974
        }
975
976
        if ($this->dumpSettings['add-locks']) {
977
            $this->compressManager->write(
978
                $this->typeAdapter->start_add_lock_table($tableName)
979
            );
980
        }
981
982
        if ($this->dumpSettings['disable-keys']) {
983
            $this->compressManager->write(
984
                $this->typeAdapter->start_add_disable_keys($tableName)
985
            );
986
        }
987
988
        // Disable autocommit for faster reload
989
        if ($this->dumpSettings['no-autocommit']) {
990
            $this->compressManager->write(
991
                $this->typeAdapter->start_disable_autocommit()
992
            );
993
        }
994
995
        return;
996
    }
997
998
    /**
999
     * Table rows extractor, close locks and commits after dump
1000
     *
1001
     * @param string $tableName  Name of table to export
1002
     *
1003
     * @return null
1004
     */
1005
    function endListValues($tableName)
1006
    {
1007
        if ($this->dumpSettings['disable-keys']) {
1008
            $this->compressManager->write(
1009
                $this->typeAdapter->end_add_disable_keys($tableName)
1010
            );
1011
        }
1012
1013
        if ($this->dumpSettings['add-locks']) {
1014
            $this->compressManager->write(
1015
                $this->typeAdapter->end_add_lock_table($tableName)
1016
            );
1017
        }
1018
1019
        if ($this->dumpSettings['single-transaction']) {
1020
            $this->dbHandler->exec($this->typeAdapter->commit_transaction());
1021
        }
1022
1023
        if ($this->dumpSettings['lock-tables']) {
1024
            $this->typeAdapter->unlock_table($tableName);
1025
        }
1026
1027
        // Commit to enable autocommit
1028
        if ($this->dumpSettings['no-autocommit']) {
1029
            $this->compressManager->write(
1030
                $this->typeAdapter->end_disable_autocommit()
1031
            );
1032
        }
1033
1034
        $this->compressManager->write(PHP_EOL);
1035
1036
        return;
1037
    }
1038
1039
    /**
1040
     * Build SQL List of all columns on current table
1041
     *
1042
     * @param string $tableName  Name of table to get columns
1043
     *
1044
     * @return array SQL sentence with columns
1045
     */
1046
    function getColumnStmt($tableName)
1047
    {
1048
        $colStmt = array();
1049
        foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) {
1050
            if($hookColumnStmt = $this->hookTransformColumnStmt($tableName, $colName, $colType)) {
1051
                $colStmt[] = $hookColumnStmt;
1052
            } else if ($colType['type'] == 'bit' && $this->dumpSettings['hex-blob']) {
1053
                $colStmt[] = "LPAD(HEX(`${colName}`),2,'0') AS `${colName}`";
1054
            } else if ($colType['is_blob'] && $this->dumpSettings['hex-blob']) {
1055
                $colStmt[] = "HEX(`${colName}`) AS `${colName}`";
1056
            } else if ($colType['is_virtual']) {
1057
                $this->dumpSettings['complete-insert'] = true;
1058
                continue;
1059
            } else {
1060
                $colStmt[] = "`${colName}`";
1061
            }
1062
        }
1063
1064
        return $colStmt;
1065
    }
1066
1067
    /**
1068
     * Give extending classes an opportunity to transform column values
1069
     *
1070
     * @param string $tableName Name of table which contains rows
1071
     * @param string $colName Name of the column in question
1072
     * @param string $colType Type of the column in question
1073
     *
1074
     * @return string
1075
     */
1076
    function hookTransformColumnStmt(
1077
      /** @scrutinizer ignore-unused */ $tableName,
1078
      /** @scrutinizer ignore-unused */ $colName,
1079
      /** @scrutinizer ignore-unused */ $colType) {
1080
        return;
1081
    }
1082
}
1083
1084
/**
1085
 * Enum with all available compression methods
1086
 *
1087
 */
1088
abstract class CompressMethod
1089
{
1090
    public static $enums = array(
1091
        "None",
1092
        "Gzip",
1093
        "Bzip2"
1094
    );
1095
1096
    /**
1097
     * @param string $c
1098
     * @return boolean
1099
     */
1100
    public static function isValid($c)
1101
    {
1102
        return in_array($c, self::$enums);
1103
    }
1104
}
1105
1106
abstract class CompressManagerFactory
1107
{
1108
    /**
1109
     * @param string $c
1110
     * @return CompressBzip2|CompressGzip|CompressNone
1111
     */
1112
    public static function create($c)
1113
    {
1114
        $c = ucfirst(strtolower($c));
1115
        if (!CompressMethod::isValid($c)) {
1116
            throw new Exception("Compression method ($c) is not defined yet");
1117
        }
1118
1119
        $method = __NAMESPACE__."\\"."Compress".$c;
1120
1121
        return new $method;
1122
    }
1123
}
1124
1125
class CompressBzip2 extends CompressManagerFactory
1126
{
1127
    private $fileHandler = null;
1128
1129
    public function __construct()
1130
    {
1131
        if (!function_exists("bzopen")) {
1132
            throw new Exception("Compression is enabled, but bzip2 lib is not installed or configured properly");
1133
        }
1134
    }
1135
1136
    /**
1137
     * @param string $filename
1138
     */
1139
    public function open($filename)
1140
    {
1141
        $this->fileHandler = bzopen($filename, "w");
1142
        if (false === $this->fileHandler) {
1143
            throw new Exception("Output file is not writable");
1144
        }
1145
1146
        return true;
1147
    }
1148
1149
    public function write($str)
1150
    {
1151
        if (false === ($bytesWritten = bzwrite($this->fileHandler, $str))) {
1152
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1153
        }
1154
        return $bytesWritten;
1155
    }
1156
1157
    public function close()
1158
    {
1159
        return bzclose($this->fileHandler);
1160
    }
1161
}
1162
1163
class CompressGzip extends CompressManagerFactory
1164
{
1165
    private $fileHandler = null;
1166
1167
    public function __construct()
1168
    {
1169
        if (!function_exists("gzopen")) {
1170
            throw new Exception("Compression is enabled, but gzip lib is not installed or configured properly");
1171
        }
1172
    }
1173
1174
    /**
1175
     * @param string $filename
1176
     */
1177
    public function open($filename)
1178
    {
1179
        $this->fileHandler = gzopen($filename, "wb");
1180
        if (false === $this->fileHandler) {
1181
            throw new Exception("Output file is not writable");
1182
        }
1183
1184
        return true;
1185
    }
1186
1187
    public function write($str)
1188
    {
1189
        if (false === ($bytesWritten = gzwrite($this->fileHandler, $str))) {
1190
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1191
        }
1192
        return $bytesWritten;
1193
    }
1194
1195
    public function close()
1196
    {
1197
        return gzclose($this->fileHandler);
1198
    }
1199
}
1200
1201
class CompressNone extends CompressManagerFactory
1202
{
1203
    private $fileHandler = null;
1204
1205
    /**
1206
     * @param string $filename
1207
     */
1208
    public function open($filename)
1209
    {
1210
        $this->fileHandler = fopen($filename, "wb");
1211
        if (false === $this->fileHandler) {
1212
            throw new Exception("Output file is not writable");
1213
        }
1214
1215
        return true;
1216
    }
1217
1218
    public function write($str)
1219
    {
1220
        if (false === ($bytesWritten = fwrite($this->fileHandler, $str))) {
1221
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1222
        }
1223
        return $bytesWritten;
1224
    }
1225
1226
    public function close()
1227
    {
1228
        return fclose($this->fileHandler);
1229
    }
1230
}
1231
1232
/**
1233
 * Enum with all available TypeAdapter implementations
1234
 *
1235
 */
1236
abstract class TypeAdapter
1237
{
1238
    public static $enums = array(
1239
        "Sqlite",
1240
        "Mysql"
1241
    );
1242
1243
    /**
1244
     * @param string $c
1245
     * @return boolean
1246
     */
1247
    public static function isValid($c)
1248
    {
1249
        return in_array($c, self::$enums);
1250
    }
1251
}
1252
1253
/**
1254
 * TypeAdapter Factory
1255
 *
1256
 */
1257
abstract class TypeAdapterFactory
1258
{
1259
    protected $dbHandler = null;
1260
    protected $dumpSettings = array();
1261
1262
    /**
1263
     * @param string $c Type of database factory to create (Mysql, Sqlite,...)
1264
     * @param PDO $dbHandler
1265
     */
1266
    public static function create($c, $dbHandler = null, $dumpSettings = array())
1267
    {
1268
        $c = ucfirst(strtolower($c));
1269
        if (!TypeAdapter::isValid($c)) {
1270
            throw new Exception("Database type support for ($c) not yet available");
1271
        }
1272
        $method = __NAMESPACE__."\\"."TypeAdapter".$c;
1273
        return new $method($dbHandler, $dumpSettings);
1274
    }
1275
1276
    public function __construct($dbHandler = null, $dumpSettings = array())
1277
    {
1278
        $this->dbHandler = $dbHandler;
1279
        $this->dumpSettings = $dumpSettings;
1280
    }
1281
1282
    /**
1283
     * function databases Add sql to create and use database
1284
     * @todo make it do something with sqlite
1285
     */
1286
    public function databases()
1287
    {
1288
        return "";
1289
    }
1290
1291
    public function show_create_table($tableName)
1292
    {
1293
        return "SELECT tbl_name as 'Table', sql as 'Create Table' ".
1294
            "FROM sqlite_master ".
1295
            "WHERE type='table' AND tbl_name='$tableName'";
1296
    }
1297
1298
    /**
1299
     * function create_table Get table creation code from database
1300
     * @todo make it do something with sqlite
1301
     */
1302
    public function create_table($row)
1303
    {
1304
        return "";
1305
    }
1306
1307
    public function show_create_view($viewName)
1308
    {
1309
        return "SELECT tbl_name as 'View', sql as 'Create View' ".
1310
            "FROM sqlite_master ".
1311
            "WHERE type='view' AND tbl_name='$viewName'";
1312
    }
1313
1314
    /**
1315
     * function create_view Get view creation code from database
1316
     * @todo make it do something with sqlite
1317
     */
1318
    public function create_view($row)
1319
    {
1320
        return "";
1321
    }
1322
1323
    /**
1324
     * function show_create_trigger Get trigger creation code from database
1325
     * @todo make it do something with sqlite
1326
     */
1327
    public function show_create_trigger($triggerName)
1328
    {
1329
        return "";
1330
    }
1331
1332
    /**
1333
     * function create_trigger Modify trigger code, add delimiters, etc
1334
     * @todo make it do something with sqlite
1335
     */
1336
    public function create_trigger($triggerName)
1337
    {
1338
        return "";
1339
    }
1340
1341
    /**
1342
     * function create_procedure Modify procedure code, add delimiters, etc
1343
     * @todo make it do something with sqlite
1344
     */
1345
    public function create_procedure($procedureName)
1346
    {
1347
        return "";
1348
    }
1349
1350
    public function show_tables()
1351
    {
1352
        return "SELECT tbl_name FROM sqlite_master WHERE type='table'";
1353
    }
1354
1355
    public function show_views()
1356
    {
1357
        return "SELECT tbl_name FROM sqlite_master WHERE type='view'";
1358
    }
1359
1360
    public function show_triggers()
1361
    {
1362
        return "SELECT name FROM sqlite_master WHERE type='trigger'";
1363
    }
1364
1365
    public function show_columns()
1366
    {
1367
        if (func_num_args() != 1) {
1368
            return "";
1369
        }
1370
1371
        $args = func_get_args();
1372
1373
        return "pragma table_info(${args[0]})";
1374
    }
1375
1376
    public function show_procedures()
1377
    {
1378
        return "";
1379
    }
1380
1381
    public function show_events()
1382
    {
1383
        return "";
1384
    }
1385
1386
    public function setup_transaction()
1387
    {
1388
        return "";
1389
    }
1390
1391
    public function start_transaction()
1392
    {
1393
        return "BEGIN EXCLUSIVE";
1394
    }
1395
1396
    public function commit_transaction()
1397
    {
1398
        return "COMMIT";
1399
    }
1400
1401
    public function lock_table()
1402
    {
1403
        return "";
1404
    }
1405
1406
    public function unlock_table()
1407
    {
1408
        return "";
1409
    }
1410
1411
    public function start_add_lock_table()
1412
    {
1413
        return PHP_EOL;
1414
    }
1415
1416
    public function end_add_lock_table()
1417
    {
1418
        return PHP_EOL;
1419
    }
1420
1421
    public function start_add_disable_keys()
1422
    {
1423
        return PHP_EOL;
1424
    }
1425
1426
    public function end_add_disable_keys()
1427
    {
1428
        return PHP_EOL;
1429
    }
1430
1431
    public function start_disable_foreign_keys_check()
1432
    {
1433
        return PHP_EOL;
1434
    }
1435
1436
    public function end_disable_foreign_keys_check()
1437
    {
1438
        return PHP_EOL;
1439
    }
1440
1441
    public function add_drop_database()
1442
    {
1443
        return PHP_EOL;
1444
    }
1445
1446
    public function add_drop_trigger()
1447
    {
1448
        return PHP_EOL;
1449
    }
1450
1451
    public function drop_table()
1452
    {
1453
        return PHP_EOL;
1454
    }
1455
1456
    public function drop_view()
1457
    {
1458
        return PHP_EOL;
1459
    }
1460
1461
    /**
1462
     * Decode column metadata and fill info structure.
1463
     * type, is_numeric and is_blob will always be available.
1464
     *
1465
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1466
     * @return array
1467
     */
1468
    public function parseColumnType($colType)
1469
    {
1470
        return array();
1471
    }
1472
1473
    public function backup_parameters()
1474
    {
1475
        return PHP_EOL;
1476
    }
1477
1478
    public function restore_parameters()
1479
    {
1480
        return PHP_EOL;
1481
    }
1482
}
1483
1484
class TypeAdapterPgsql extends TypeAdapterFactory
1485
{
1486
}
1487
1488
class TypeAdapterDblib extends TypeAdapterFactory
1489
{
1490
}
1491
1492
class TypeAdapterSqlite extends TypeAdapterFactory
1493
{
1494
}
1495
1496
class TypeAdapterMysql extends TypeAdapterFactory
1497
{
1498
    const DEFINER_RE = 'DEFINER=`(?:[^`]|``)*`@`(?:[^`]|``)*`';
1499
1500
1501
    // Numerical Mysql types
1502
    public $mysqlTypes = array(
1503
        'numerical' => array(
1504
            'bit',
1505
            'tinyint',
1506
            'smallint',
1507
            'mediumint',
1508
            'int',
1509
            'integer',
1510
            'bigint',
1511
            'real',
1512
            'double',
1513
            'float',
1514
            'decimal',
1515
            'numeric'
1516
        ),
1517
        'blob' => array(
1518
            'tinyblob',
1519
            'blob',
1520
            'mediumblob',
1521
            'longblob',
1522
            'binary',
1523
            'varbinary',
1524
            'bit',
1525
            'geometry', /* http://bugs.mysql.com/bug.php?id=43544 */
1526
            'point',
1527
            'linestring',
1528
            'polygon',
1529
            'multipoint',
1530
            'multilinestring',
1531
            'multipolygon',
1532
            'geometrycollection',
1533
        )
1534
    );
1535
1536
    public function databases()
1537
    {
1538
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1539
        $args = func_get_args();
1540
        $databaseName = $args[0];
1541
1542
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'character_set_database';");
1543
        $characterSet = $resultSet->fetchColumn(1);
1544
        $resultSet->closeCursor();
1545
1546
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'collation_database';");
1547
        $collationDb = $resultSet->fetchColumn(1);
1548
        $resultSet->closeCursor();
1549
        $ret = "";
1550
1551
        $ret .= "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `${databaseName}`".
1552
            " /*!40100 DEFAULT CHARACTER SET ${characterSet} ".
1553
            " COLLATE ${collationDb} */;".PHP_EOL.PHP_EOL.
1554
            "USE `${databaseName}`;".PHP_EOL.PHP_EOL;
1555
1556
        return $ret;
1557
    }
1558
1559
    public function show_create_table($tableName)
1560
    {
1561
        return "SHOW CREATE TABLE `$tableName`";
1562
    }
1563
1564
    public function show_create_view($viewName)
1565
    {
1566
        return "SHOW CREATE VIEW `$viewName`";
1567
    }
1568
1569
    public function show_create_trigger($triggerName)
1570
    {
1571
        return "SHOW CREATE TRIGGER `$triggerName`";
1572
    }
1573
1574
    public function show_create_procedure($procedureName)
1575
    {
1576
        return "SHOW CREATE PROCEDURE `$procedureName`";
1577
    }
1578
1579
    public function show_create_event($eventName)
1580
    {
1581
        return "SHOW CREATE EVENT `$eventName`";
1582
    }
1583
1584
    public function create_table($row)
1585
    {
1586
        if (!isset($row['Create Table'])) {
1587
            throw new Exception("Error getting table code, unknown output");
1588
        }
1589
1590
        $createTable = $row['Create Table'];
1591
        if ($this->dumpSettings['reset-auto-increment']) {
1592
            $match = "/AUTO_INCREMENT=[0-9]+/s";
1593
            $replace = "";
1594
            $createTable = preg_replace($match, $replace, $createTable);
1595
        }
1596
1597
        $ret = "/*!40101 SET @saved_cs_client     = @@character_set_client */;".PHP_EOL.
1598
            "/*!40101 SET character_set_client = ".$this->dumpSettings['default-character-set']." */;".PHP_EOL.
1599
            $createTable.";".PHP_EOL.
1600
            "/*!40101 SET character_set_client = @saved_cs_client */;".PHP_EOL.
1601
            PHP_EOL;
1602
        return $ret;
1603
    }
1604
1605
    public function create_view($row)
1606
    {
1607
        $ret = "";
1608
        if (!isset($row['Create View'])) {
1609
                throw new Exception("Error getting view structure, unknown output");
1610
        }
1611
1612
        $viewStmt = $row['Create View'];
1613
1614
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50013 \2 */'.PHP_EOL;
1615
1616
        if ($viewStmtReplaced = preg_replace(
1617
            '/^(CREATE(?:\s+ALGORITHM=(?:UNDEFINED|MERGE|TEMPTABLE))?)\s+('
1618
            .self::DEFINER_RE.'(?:\s+SQL SECURITY DEFINER|INVOKER)?)?\s+(VIEW .+)$/',
1619
            '/*!50001 \1 */'.PHP_EOL.$definerStr.'/*!50001 \3 */',
1620
            $viewStmt,
1621
            1
1622
        )) {
1623
            $viewStmt = $viewStmtReplaced;
1624
        };
1625
1626
        $ret .= $viewStmt.';'.PHP_EOL.PHP_EOL;
1627
        return $ret;
1628
    }
1629
1630
    public function create_trigger($row)
1631
    {
1632
        $ret = "";
1633
        if (!isset($row['SQL Original Statement'])) {
1634
            throw new Exception("Error getting trigger code, unknown output");
1635
        }
1636
1637
        $triggerStmt = $row['SQL Original Statement'];
1638
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50017 \2*/ ';
1639
        if ($triggerStmtReplaced = preg_replace(
1640
            '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(TRIGGER\s.*)$/s',
1641
            '/*!50003 \1*/ '.$definerStr.'/*!50003 \3 */',
1642
            $triggerStmt,
1643
            1
1644
        )) {
1645
            $triggerStmt = $triggerStmtReplaced;
1646
        }
1647
1648
        $ret .= "DELIMITER ;;".PHP_EOL.
1649
            $triggerStmt.";;".PHP_EOL.
1650
            "DELIMITER ;".PHP_EOL.PHP_EOL;
1651
        return $ret;
1652
    }
1653
1654
    public function create_procedure($row)
1655
    {
1656
        $ret = "";
1657
        if (!isset($row['Create Procedure'])) {
1658
            throw new Exception("Error getting procedure code, unknown output. ".
1659
                "Please check 'https://bugs.mysql.com/bug.php?id=14564'");
1660
        }
1661
        $procedureStmt = $row['Create Procedure'];
1662
1663
        $ret .= "/*!50003 DROP PROCEDURE IF EXISTS `".
1664
            $row['Procedure']."` */;".PHP_EOL.
1665
            "/*!40101 SET @saved_cs_client     = @@character_set_client */;".PHP_EOL.
1666
            "/*!40101 SET character_set_client = ".$this->dumpSettings['default-character-set']." */;".PHP_EOL.
1667
            "DELIMITER ;;".PHP_EOL.
1668
            $procedureStmt." ;;".PHP_EOL.
1669
            "DELIMITER ;".PHP_EOL.
1670
            "/*!40101 SET character_set_client = @saved_cs_client */;".PHP_EOL.PHP_EOL;
1671
1672
        return $ret;
1673
    }
1674
1675
    public function create_event($row)
1676
    {
1677
        $ret = "";
1678
        if (!isset($row['Create Event'])) {
1679
            throw new Exception("Error getting event code, unknown output. ".
1680
                "Please check 'http://stackoverflow.com/questions/10853826/mysql-5-5-create-event-gives-syntax-error'");
1681
        }
1682
        $eventName = $row['Event'];
1683
        $eventStmt = $row['Create Event'];
1684
        $sqlMode = $row['sql_mode'];
1685
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50117 \2*/ ';
1686
1687
        if ($eventStmtReplaced = preg_replace(
1688
            '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(EVENT .*)$/',
1689
            '/*!50106 \1*/ '.$definerStr.'/*!50106 \3 */',
1690
            $eventStmt,
1691
            1
1692
        )) {
1693
            $eventStmt = $eventStmtReplaced;
1694
        }
1695
1696
        $ret .= "/*!50106 SET @save_time_zone= @@TIME_ZONE */ ;".PHP_EOL.
1697
            "/*!50106 DROP EVENT IF EXISTS `".$eventName."` */;".PHP_EOL.
1698
            "DELIMITER ;;".PHP_EOL.
1699
            "/*!50003 SET @saved_cs_client      = @@character_set_client */ ;;".PHP_EOL.
1700
            "/*!50003 SET @saved_cs_results     = @@character_set_results */ ;;".PHP_EOL.
1701
            "/*!50003 SET @saved_col_connection = @@collation_connection */ ;;".PHP_EOL.
1702
            "/*!50003 SET character_set_client  = utf8 */ ;;".PHP_EOL.
1703
            "/*!50003 SET character_set_results = utf8 */ ;;".PHP_EOL.
1704
            "/*!50003 SET collation_connection  = utf8_general_ci */ ;;".PHP_EOL.
1705
            "/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;;".PHP_EOL.
1706
            "/*!50003 SET sql_mode              = '".$sqlMode."' */ ;;".PHP_EOL.
1707
            "/*!50003 SET @saved_time_zone      = @@time_zone */ ;;".PHP_EOL.
1708
            "/*!50003 SET time_zone             = 'SYSTEM' */ ;;".PHP_EOL.
1709
            $eventStmt." ;;".PHP_EOL.
1710
            "/*!50003 SET time_zone             = @saved_time_zone */ ;;".PHP_EOL.
1711
            "/*!50003 SET sql_mode              = @saved_sql_mode */ ;;".PHP_EOL.
1712
            "/*!50003 SET character_set_client  = @saved_cs_client */ ;;".PHP_EOL.
1713
            "/*!50003 SET character_set_results = @saved_cs_results */ ;;".PHP_EOL.
1714
            "/*!50003 SET collation_connection  = @saved_col_connection */ ;;".PHP_EOL.
1715
            "DELIMITER ;".PHP_EOL.
1716
            "/*!50106 SET TIME_ZONE= @save_time_zone */ ;".PHP_EOL.PHP_EOL;
1717
            // Commented because we are doing this in restore_parameters()
1718
            // "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL . PHP_EOL;
1719
1720
        return $ret;
1721
    }
1722
1723
    public function show_tables()
1724
    {
1725
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1726
        $args = func_get_args();
1727
        return "SELECT TABLE_NAME AS tbl_name ".
1728
            "FROM INFORMATION_SCHEMA.TABLES ".
1729
            "WHERE TABLE_TYPE='BASE TABLE' AND TABLE_SCHEMA='${args[0]}'";
1730
    }
1731
1732
    public function show_views()
1733
    {
1734
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1735
        $args = func_get_args();
1736
        return "SELECT TABLE_NAME AS tbl_name ".
1737
            "FROM INFORMATION_SCHEMA.TABLES ".
1738
            "WHERE TABLE_TYPE='VIEW' AND TABLE_SCHEMA='${args[0]}'";
1739
    }
1740
1741
    public function show_triggers()
1742
    {
1743
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1744
        $args = func_get_args();
1745
        return "SHOW TRIGGERS FROM `${args[0]}`;";
1746
    }
1747
1748
    public function show_columns()
1749
    {
1750
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1751
        $args = func_get_args();
1752
        return "SHOW COLUMNS FROM `${args[0]}`;";
1753
    }
1754
1755
    public function show_procedures()
1756
    {
1757
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1758
        $args = func_get_args();
1759
        return "SELECT SPECIFIC_NAME AS procedure_name ".
1760
            "FROM INFORMATION_SCHEMA.ROUTINES ".
1761
            "WHERE ROUTINE_TYPE='PROCEDURE' AND ROUTINE_SCHEMA='${args[0]}'";
1762
    }
1763
1764
    /**
1765
     * Get query string to ask for names of events from current database.
1766
     *
1767
     * @param string Name of database
0 ignored issues
show
Bug introduced by
The type Ifsnop\Mysqldump\Name was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1768
     * @return string
1769
     */
1770
    public function show_events()
1771
    {
1772
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1773
        $args = func_get_args();
1774
        return "SELECT EVENT_NAME AS event_name ".
1775
            "FROM INFORMATION_SCHEMA.EVENTS ".
1776
            "WHERE EVENT_SCHEMA='${args[0]}'";
1777
    }
1778
1779
    public function setup_transaction()
1780
    {
1781
        return "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
1782
    }
1783
1784
    public function start_transaction()
1785
    {
1786
        return "START TRANSACTION";
1787
    }
1788
1789
    public function commit_transaction()
1790
    {
1791
        return "COMMIT";
1792
    }
1793
1794
    public function lock_table()
1795
    {
1796
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1797
        $args = func_get_args();
1798
        return $this->dbHandler->exec("LOCK TABLES `${args[0]}` READ LOCAL");
1799
1800
    }
1801
1802
    public function unlock_table()
1803
    {
1804
        return $this->dbHandler->exec("UNLOCK TABLES");
1805
    }
1806
1807
    public function start_add_lock_table()
1808
    {
1809
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1810
        $args = func_get_args();
1811
        return "LOCK TABLES `${args[0]}` WRITE;".PHP_EOL;
1812
    }
1813
1814
    public function end_add_lock_table()
1815
    {
1816
        return "UNLOCK TABLES;".PHP_EOL;
1817
    }
1818
1819
    public function start_add_disable_keys()
1820
    {
1821
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1822
        $args = func_get_args();
1823
        return "/*!40000 ALTER TABLE `${args[0]}` DISABLE KEYS */;".
1824
            PHP_EOL;
1825
    }
1826
1827
    public function end_add_disable_keys()
1828
    {
1829
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1830
        $args = func_get_args();
1831
        return "/*!40000 ALTER TABLE `${args[0]}` ENABLE KEYS */;".
1832
            PHP_EOL;
1833
    }
1834
1835
    public function start_disable_autocommit()
1836
    {
1837
        return "SET autocommit=0;".PHP_EOL;
1838
    }
1839
1840
    public function end_disable_autocommit()
1841
    {
1842
        return "COMMIT;".PHP_EOL;
1843
    }
1844
1845
    public function add_drop_database()
1846
    {
1847
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1848
        $args = func_get_args();
1849
        return "/*!40000 DROP DATABASE IF EXISTS `${args[0]}`*/;".
1850
            PHP_EOL.PHP_EOL;
1851
    }
1852
1853
    public function add_drop_trigger()
1854
    {
1855
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1856
        $args = func_get_args();
1857
        return "DROP TRIGGER IF EXISTS `${args[0]}`;".PHP_EOL;
1858
    }
1859
1860
    public function drop_table()
1861
    {
1862
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1863
        $args = func_get_args();
1864
        return "DROP TABLE IF EXISTS `${args[0]}`;".PHP_EOL;
1865
    }
1866
1867
    public function drop_view()
1868
    {
1869
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1870
        $args = func_get_args();
1871
        return "DROP TABLE IF EXISTS `${args[0]}`;".PHP_EOL.
1872
                "/*!50001 DROP VIEW IF EXISTS `${args[0]}`*/;".PHP_EOL;
1873
    }
1874
1875
    public function getDatabaseHeader()
1876
    {
1877
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1878
        $args = func_get_args();
1879
        return "--".PHP_EOL.
1880
            "-- Current Database: `${args[0]}`".PHP_EOL.
1881
            "--".PHP_EOL.PHP_EOL;
1882
    }
1883
1884
    /**
1885
     * Decode column metadata and fill info structure.
1886
     * type, is_numeric and is_blob will always be available.
1887
     *
1888
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1889
     * @return array
1890
     */
1891
    public function parseColumnType($colType)
1892
    {
1893
        $colInfo = array();
1894
        $colParts = explode(" ", $colType['Type']);
1895
1896
        if ($fparen = strpos($colParts[0], "(")) {
1897
            $colInfo['type'] = substr($colParts[0], 0, $fparen);
1898
            $colInfo['length'] = str_replace(")", "", substr($colParts[0], $fparen + 1));
1899
            $colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
1900
        } else {
1901
            $colInfo['type'] = $colParts[0];
1902
        }
1903
        $colInfo['is_numeric'] = in_array($colInfo['type'], $this->mysqlTypes['numerical']);
1904
        $colInfo['is_blob'] = in_array($colInfo['type'], $this->mysqlTypes['blob']);
1905
        // for virtual columns that are of type 'Extra', column type
1906
        // could by "STORED GENERATED" or "VIRTUAL GENERATED"
1907
        // MySQL reference: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
1908
        $colInfo['is_virtual'] = strpos($colType['Extra'], "VIRTUAL GENERATED") !== false || strpos($colType['Extra'], "STORED GENERATED") !== false;
1909
1910
        return $colInfo;
1911
    }
1912
1913
    public function backup_parameters()
1914
    {
1915
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;".PHP_EOL.
1916
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;".PHP_EOL.
1917
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;".PHP_EOL.
1918
            "/*!40101 SET NAMES ".$this->dumpSettings['default-character-set']." */;".PHP_EOL;
1919
1920
        if (false === $this->dumpSettings['skip-tz-utc']) {
1921
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;".PHP_EOL.
1922
                "/*!40103 SET TIME_ZONE='+00:00' */;".PHP_EOL;
1923
        }
1924
1925
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;".PHP_EOL.
1926
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;".PHP_EOL.
1927
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;".PHP_EOL.
1928
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;".PHP_EOL.PHP_EOL;
1929
1930
        return $ret;
1931
    }
1932
1933
    public function restore_parameters()
1934
    {
1935
        $ret = "";
1936
1937
        if (false === $this->dumpSettings['skip-tz-utc']) {
1938
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;".PHP_EOL;
1939
        }
1940
1941
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;".PHP_EOL.
1942
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;".PHP_EOL.
1943
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;".PHP_EOL.
1944
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;".PHP_EOL.
1945
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;".PHP_EOL.
1946
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;".PHP_EOL.
1947
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;".PHP_EOL.PHP_EOL;
1948
1949
        return $ret;
1950
    }
1951
1952
    /**
1953
     * Check number of parameters passed to function, useful when inheriting.
1954
     * Raise exception if unexpected.
1955
     *
1956
     * @param integer $num_args
1957
     * @param integer $expected_num_args
1958
     * @param string $method_name
1959
     */
1960
    private function check_parameters($num_args, $expected_num_args, $method_name)
1961
    {
1962
        if ($num_args != $expected_num_args) {
1963
            throw new Exception("Unexpected parameter passed to $method_name");
1964
        }
1965
        return;
1966
    }
1967
}
1968