Passed
Push — master ( 8d0634...ad514c )
by diego
02:44
created

Mysqldump::getDatabaseStructureTriggers()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 0
dl 0
loc 9
rs 9.9666
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, trigger, procedures and events
356
        // structures from database
357
        $this->getDatabaseStructureTables();
358
        $this->getDatabaseStructureViews();
359
        $this->getDatabaseStructureTriggers();
360
        $this->getDatabaseStructureProcedures();
361
        $this->getDatabaseStructureEvents();
362
363
        if ($this->dumpSettings['databases']) {
364
            $this->compressManager->write(
365
                $this->typeAdapter->databases($this->dbName)
366
            );
367
        }
368
369
        // If there still are some tables/views in include-tables array,
370
        // that means that some tables or views weren't found.
371
        // Give proper error and exit.
372
        // This check will be removed once include-tables supports regexps
373
        if (0 < count($this->dumpSettings['include-tables'])) {
374
            $name = implode(",", $this->dumpSettings['include-tables']);
375
            throw new Exception("Table (".$name.") not found in database");
376
        }
377
378
        $this->exportTables();
379
        $this->exportTriggers();
380
        $this->exportViews();
381
        $this->exportProcedures();
382
        $this->exportEvents();
383
384
        // Restore saved parameters
385
        $this->compressManager->write(
386
            $this->typeAdapter->restore_parameters()
387
        );
388
        // Write some stats to output file
389
        $this->compressManager->write($this->getDumpFileFooter());
390
        // Close output file
391
        $this->compressManager->close();
392
    }
393
394
    /**
395
     * Returns header for dump file
396
     *
397
     * @return string
398
     */
399
    private function getDumpFileHeader()
400
    {
401
        $header = '';
402
        if (!$this->dumpSettings['skip-comments']) {
403
            // Some info about software, source and time
404
            $header = "-- mysqldump-php https://github.com/ifsnop/mysqldump-php".PHP_EOL.
405
                    "--".PHP_EOL.
406
                    "-- Host: {$this->host}\tDatabase: {$this->dbName}".PHP_EOL.
407
                    "-- ------------------------------------------------------".PHP_EOL;
408
409
            if (!empty($this->version)) {
410
                $header .= "-- Server version \t".$this->version.PHP_EOL;
411
            }
412
413
            if (!$this->dumpSettings['skip-dump-date']) {
414
                $header .= "-- Date: ".date('r').PHP_EOL.PHP_EOL;
415
            }
416
        }
417
        return $header;
418
    }
419
420
    /**
421
     * Returns footer for dump file
422
     *
423
     * @return string
424
     */
425
    private function getDumpFileFooter()
426
    {
427
        $footer = '';
428
        if (!$this->dumpSettings['skip-comments']) {
429
            $footer .= '-- Dump completed';
430
            if (!$this->dumpSettings['skip-dump-date']) {
431
                $footer .= ' on: '.date('r');
432
            }
433
            $footer .= PHP_EOL;
434
        }
435
436
        return $footer;
437
    }
438
439
    /**
440
     * Reads table names from database.
441
     * Fills $this->tables array so they will be dumped later.
442
     *
443
     * @return null
444
     */
445
    function getDatabaseStructureTables()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
446
    {
447
        // Listing all tables from database
448
        if (empty($this->dumpSettings['include-tables'])) {
449
            // include all tables for now, blacklisting happens later
450
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
451
                array_push($this->tables, current($row));
452
            }
453
        } else {
454
            // include only the tables mentioned in include-tables
455
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
456
                if (in_array(current($row), $this->dumpSettings['include-tables'], true)) {
457
                    array_push($this->tables, current($row));
458
                    $elem = array_search(
459
                        current($row),
460
                        $this->dumpSettings['include-tables']
461
                    );
462
                    unset($this->dumpSettings['include-tables'][$elem]);
463
                }
464
            }
465
        }
466
        return;
467
    }
468
469
    /**
470
     * Reads view names from database.
471
     * Fills $this->tables array so they will be dumped later.
472
     *
473
     * @return null
474
     */
475
    function getDatabaseStructureViews()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
476
    {
477
        // Listing all views from database
478
        if (empty($this->dumpSettings['include-views'])) {
479
            // include all views for now, blacklisting happens later
480
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
481
                array_push($this->views, current($row));
482
            }
483
        } else {
484
            // include only the tables mentioned in include-tables
485
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
486
                if (in_array(current($row), $this->dumpSettings['include-views'], true)) {
487
                    array_push($this->views, current($row));
488
                    $elem = array_search(
489
                        current($row),
490
                        $this->dumpSettings['include-views']
491
                    );
492
                    unset($this->dumpSettings['include-views'][$elem]);
493
                }
494
            }
495
        }
496
        return;
497
    }
498
499
    /**
500
     * Reads trigger names from database.
501
     * Fills $this->tables array so they will be dumped later.
502
     *
503
     * @return null
504
     */
505
506
    function getDatabaseStructureTriggers()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
507
    {
508
        // Listing all triggers from database
509
        if (false === $this->dumpSettings['skip-triggers']) {
510
            foreach ($this->dbHandler->query($this->typeAdapter->show_triggers($this->dbName)) as $row) {
511
                array_push($this->triggers, $row['Trigger']);
512
            }
513
        }
514
        return;
515
    }
516
517
    /**
518
     * Reads procedure names from database.
519
     * Fills $this->tables array so they will be dumped later.
520
     *
521
     * @return null
522
     */
523
    function getDatabaseStructureProcedures()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
524
    {
525
        // Listing all procedures from database
526
        if ($this->dumpSettings['routines']) {
527
            foreach ($this->dbHandler->query($this->typeAdapter->show_procedures($this->dbName)) as $row) {
528
                array_push($this->procedures, $row['procedure_name']);
529
            }
530
        }
531
        return;
532
    }
533
534
    /**
535
     * Reads event names from database.
536
     * Fills $this->tables array so they will be dumped later.
537
     *
538
     * @return null
539
     */
540
    function getDatabaseStructureEvents()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

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