Passed
Push — master ( 0678c0...9d00a5 )
by diego
02:42
created

Mysqldump::getEventStructure()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 1
dl 0
loc 14
rs 9.7998
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
            'insert-ignore' => false,
136
            'net_buffer_length' => self::MAXLINESIZE,
137
            'no-autocommit' => true,
138
            'no-create-info' => false,
139
            'lock-tables' => true,
140
            'routines' => false,
141
            'single-transaction' => true,
142
            'skip-triggers' => false,
143
            'skip-tz-utc' => false,
144
            'skip-comments' => false,
145
            'skip-dump-date' => false,
146
            'skip-definer' => false,
147
            'where' => '',
148
            /* deprecated */
149
            'disable-foreign-keys-check' => true
150
        );
151
152
        $pdoSettingsDefault = array(
153
            PDO::ATTR_PERSISTENT => true,
154
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
155
        );
156
157
        $this->user = $user;
158
        $this->pass = $pass;
159
        $this->parseDsn($dsn);
160
161
        // this drops MYSQL dependency, only use the constant if it's defined
162
        if ("mysql" === $this->dbType) {
0 ignored issues
show
introduced by
The condition 'mysql' === $this->dbType is always false.
Loading history...
163
            $pdoSettingsDefault[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false;
164
        }
165
166
        $this->pdoSettings = self::array_replace_recursive($pdoSettingsDefault, $pdoSettings);
167
        $this->dumpSettings = self::array_replace_recursive($dumpSettingsDefault, $dumpSettings);
168
        $this->dumpSettings['init_commands'][] = "SET NAMES ".$this->dumpSettings['default-character-set'];
169
170
        if (false === $this->dumpSettings['skip-tz-utc']) {
171
            $this->dumpSettings['init_commands'][] = "SET TIME_ZONE='+00:00'";
172
        }
173
174
        $diff = array_diff(array_keys($this->dumpSettings), array_keys($dumpSettingsDefault));
175
        if (count($diff) > 0) {
176
            throw new Exception("Unexpected value in dumpSettings: (".implode(",", $diff).")");
177
        }
178
179
        if (!is_array($this->dumpSettings['include-tables']) ||
180
            !is_array($this->dumpSettings['exclude-tables'])) {
181
            throw new Exception("Include-tables and exclude-tables should be arrays");
182
        }
183
184
        // Dump the same views as tables, mimic mysqldump behaviour
185
        $this->dumpSettings['include-views'] = $this->dumpSettings['include-tables'];
186
187
        // Create a new compressManager to manage compressed output
188
        $this->compressManager = CompressManagerFactory::create($this->dumpSettings['compress']);
189
    }
190
191
    /**
192
     * Destructor of Mysqldump. Unsets dbHandlers and database objects.
193
     *
194
     */
195
    public function __destruct()
196
    {
197
        $this->dbHandler = null;
198
    }
199
200
    /**
201
     * Custom array_replace_recursive to be used if PHP < 5.3
202
     * Replaces elements from passed arrays into the first array recursively
203
     *
204
     * @param array $array1 The array in which elements are replaced
205
     * @param array $array2 The array from which elements will be extracted
206
     *
207
     * @return array Returns an array, or NULL if an error occurs.
208
     */
209
    public static function array_replace_recursive($array1, $array2)
210
    {
211
        if (function_exists('array_replace_recursive')) {
212
            return array_replace_recursive($array1, $array2);
213
        }
214
215
        foreach ($array2 as $key => $value) {
216
            if (is_array($value)) {
217
                $array1[$key] = self::array_replace_recursive($array1[$key], $value);
218
            } else {
219
                $array1[$key] = $value;
220
            }
221
        }
222
        return $array1;
223
    }
224
225
    /**
226
     * Parse DSN string and extract dbname value
227
     * Several examples of a DSN string
228
     *   mysql:host=localhost;dbname=testdb
229
     *   mysql:host=localhost;port=3307;dbname=testdb
230
     *   mysql:unix_socket=/tmp/mysql.sock;dbname=testdb
231
     *
232
     * @param string $dsn dsn string to parse
233
     */
234
    private function parseDsn($dsn)
235
    {
236
        if (empty($dsn) || (false === ($pos = strpos($dsn, ":")))) {
237
            throw new Exception("Empty DSN string");
238
        }
239
240
        $this->dsn = $dsn;
241
        $this->dbType = strtolower(substr($dsn, 0, $pos)); // always returns a string
242
243
        if (empty($this->dbType)) {
244
            throw new Exception("Missing database type from DSN string");
245
        }
246
247
        $dsn = substr($dsn, $pos + 1);
248
249
        foreach (explode(";", $dsn) as $kvp) {
250
            $kvpArr = explode("=", $kvp);
251
            $this->dsnArray[strtolower($kvpArr[0])] = $kvpArr[1];
252
        }
253
254
        if (empty($this->dsnArray['host']) &&
255
            empty($this->dsnArray['unix_socket'])) {
256
            throw new Exception("Missing host from DSN string");
257
        }
258
        $this->host = (!empty($this->dsnArray['host'])) ?
259
            $this->dsnArray['host'] : $this->dsnArray['unix_socket'];
260
261
        if (empty($this->dsnArray['dbname'])) {
262
            throw new Exception("Missing database name from DSN string");
263
        }
264
265
        $this->dbName = $this->dsnArray['dbname'];
266
267
        return true;
268
    }
269
270
    /**
271
     * Connect with PDO
272
     *
273
     * @return null
274
     */
275
    private function connect()
276
    {
277
        // Connecting with PDO
278
        try {
279
            switch ($this->dbType) {
280
                case 'sqlite':
281
                    $this->dbHandler = @new PDO("sqlite:".$this->dbName, null, null, $this->pdoSettings);
282
                    break;
283
                case 'mysql':
284
                case 'pgsql':
285
                case 'dblib':
286
                    $this->dbHandler = @new PDO(
287
                        $this->dsn,
288
                        $this->user,
289
                        $this->pass,
290
                        $this->pdoSettings
291
                    );
292
                    // Execute init commands once connected
293
                    foreach ($this->dumpSettings['init_commands'] as $stmt) {
294
                        $this->dbHandler->exec($stmt);
295
                    }
296
                    // Store server version
297
                    $this->version = $this->dbHandler->getAttribute(PDO::ATTR_SERVER_VERSION);
298
                    break;
299
                default:
300
                    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

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