Passed
Pull Request — master (#147)
by
unknown
06:20
created

Mysqldump::setTransformColumnValueHook()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

301
                    throw new Exception("Unsupported database type ("./** @scrutinizer ignore-type */ $this->dbType.")");
Loading history...
302
            }
303
        } catch (PDOException $e) {
304
            throw new Exception(
305
                "Connection to ".$this->dbType." failed with message: ".
306
                $e->getMessage()
307
            );
308
        }
309
310
        if (is_null($this->dbHandler)) {
311
            throw new Exception("Connection to ".$this->dbType."failed");
312
        }
313
314
        $this->dbHandler->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL);
315
        $this->typeAdapter = TypeAdapterFactory::create($this->dbType, $this->dbHandler, $this->dumpSettings);
316
    }
317
318
    /**
319
     * Main call
320
     *
321
     * @param string $filename  Name of file to write sql dump to
322
     * @return null
323
     */
324
    public function start($filename = '')
325
    {
326
        // Output file can be redefined here
327
        if (!empty($filename)) {
328
            $this->fileName = $filename;
329
        }
330
331
        // Connect to database
332
        $this->connect();
333
334
        // Create output file
335
        $this->compressManager->open($this->fileName);
336
337
        // Write some basic info to output file
338
        $this->compressManager->write($this->getDumpFileHeader());
339
340
        // Store server settings and use sanner defaults to dump
341
        $this->compressManager->write(
342
            $this->typeAdapter->backup_parameters()
343
        );
344
345
        if ($this->dumpSettings['databases']) {
346
            $this->compressManager->write(
347
                $this->typeAdapter->getDatabaseHeader($this->dbName)
348
            );
349
            if ($this->dumpSettings['add-drop-database']) {
350
                $this->compressManager->write(
351
                    $this->typeAdapter->add_drop_database($this->dbName)
352
                );
353
            }
354
        }
355
356
        // Get table, view, trigger, procedures and events
357
        // structures from database
358
        $this->getDatabaseStructureTables();
359
        $this->getDatabaseStructureViews();
360
        $this->getDatabaseStructureTriggers();
361
        $this->getDatabaseStructureProcedures();
362
        $this->getDatabaseStructureEvents();
363
364
        if ($this->dumpSettings['databases']) {
365
            $this->compressManager->write(
366
                $this->typeAdapter->databases($this->dbName)
367
            );
368
        }
369
370
        // If there still are some tables/views in include-tables array,
371
        // that means that some tables or views weren't found.
372
        // Give proper error and exit.
373
        // This check will be removed once include-tables supports regexps
374
        if (0 < count($this->dumpSettings['include-tables'])) {
375
            $name = implode(",", $this->dumpSettings['include-tables']);
376
            throw new Exception("Table (".$name.") not found in database");
377
        }
378
379
        $this->exportTables();
380
        $this->exportTriggers();
381
        $this->exportViews();
382
        $this->exportProcedures();
383
        $this->exportEvents();
384
385
        // Restore saved parameters
386
        $this->compressManager->write(
387
            $this->typeAdapter->restore_parameters()
388
        );
389
        // Write some stats to output file
390
        $this->compressManager->write($this->getDumpFileFooter());
391
        // Close output file
392
        $this->compressManager->close();
393
    }
394
395
    /**
396
     * Returns header for dump file
397
     *
398
     * @return string
399
     */
400
    private function getDumpFileHeader()
401
    {
402
        $header = '';
403
        if (!$this->dumpSettings['skip-comments']) {
404
            // Some info about software, source and time
405
            $header = "-- mysqldump-php https://github.com/ifsnop/mysqldump-php".PHP_EOL.
406
                    "--".PHP_EOL.
407
                    "-- Host: {$this->host}\tDatabase: {$this->dbName}".PHP_EOL.
408
                    "-- ------------------------------------------------------".PHP_EOL;
409
410
            if (!empty($this->version)) {
411
                $header .= "-- Server version \t".$this->version.PHP_EOL;
412
            }
413
414
            if (!$this->dumpSettings['skip-dump-date']) {
415
                $header .= "-- Date: ".date('r').PHP_EOL.PHP_EOL;
416
            }
417
        }
418
        return $header;
419
    }
420
421
    /**
422
     * Returns footer for dump file
423
     *
424
     * @return string
425
     */
426
    private function getDumpFileFooter()
427
    {
428
        $footer = '';
429
        if (!$this->dumpSettings['skip-comments']) {
430
            $footer .= '-- Dump completed';
431
            if (!$this->dumpSettings['skip-dump-date']) {
432
                $footer .= ' on: '.date('r');
433
            }
434
            $footer .= PHP_EOL;
435
        }
436
437
        return $footer;
438
    }
439
440
    /**
441
     * Reads table names from database.
442
     * Fills $this->tables array so they will be dumped later.
443
     *
444
     * @return null
445
     */
446
    private function getDatabaseStructureTables()
447
    {
448
        // Listing all tables from database
449
        if (empty($this->dumpSettings['include-tables'])) {
450
            // include all tables for now, blacklisting happens later
451
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
452
                array_push($this->tables, current($row));
453
            }
454
        } else {
455
            // include only the tables mentioned in include-tables
456
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
457
                if (in_array(current($row), $this->dumpSettings['include-tables'], true)) {
458
                    array_push($this->tables, current($row));
459
                    $elem = array_search(
460
                        current($row),
461
                        $this->dumpSettings['include-tables']
462
                    );
463
                    unset($this->dumpSettings['include-tables'][$elem]);
464
                }
465
            }
466
        }
467
        return;
468
    }
469
470
    /**
471
     * Reads view names from database.
472
     * Fills $this->tables array so they will be dumped later.
473
     *
474
     * @return null
475
     */
476
    private function getDatabaseStructureViews()
477
    {
478
        // Listing all views from database
479
        if (empty($this->dumpSettings['include-views'])) {
480
            // include all views for now, blacklisting happens later
481
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
482
                array_push($this->views, current($row));
483
            }
484
        } else {
485
            // include only the tables mentioned in include-tables
486
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
487
                if (in_array(current($row), $this->dumpSettings['include-views'], true)) {
488
                    array_push($this->views, current($row));
489
                    $elem = array_search(
490
                        current($row),
491
                        $this->dumpSettings['include-views']
492
                    );
493
                    unset($this->dumpSettings['include-views'][$elem]);
494
                }
495
            }
496
        }
497
        return;
498
    }
499
500
    /**
501
     * Reads trigger names from database.
502
     * Fills $this->tables array so they will be dumped later.
503
     *
504
     * @return null
505
     */
506
    private function getDatabaseStructureTriggers()
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
    private function getDatabaseStructureProcedures()
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
    private function getDatabaseStructureEvents()
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
     * Set a callable that will will be used to transform column values.
921
     *
922
     * @param callable $callable
923
     *
924
     * @return void
925
     */
926
    public function setTransformColumnValueHook($callable)
927
    {
928
        $this->transformColumnValueCallable = $callable;
929
    }
930
931
    /**
932
     * Give extending classes an opportunity to transform column values
933
     *
934
     * @param string $tableName Name of table which contains rows
935
     * @param string $colName Name of the column in question
936
     * @param string $colValue Value of the column in question
937
     *
938
     * @return string
939
     */
940
    protected function hookTransformColumnValue($tableName, $colName, $colValue)
941
    {
942
        if (! $this->transformColumnValueCallable) {
943
            return $colValue;
944
        }
945
946
        return call_user_func_array($this->transformColumnValueCallable, array(
947
            $tableName,
948
            $colName,
949
            $colValue,
950
        ));
951
    }
952
953
    /**
954
     * Table rows extractor
955
     *
956
     * @param string $tableName  Name of table to export
957
     *
958
     * @return null
959
     */
960
    private function listValues($tableName)
961
    {
962
        $this->prepareListValues($tableName);
963
964
        $onlyOnce = true;
965
        $lineSize = 0;
966
967
        // colStmt is used to form a query to obtain row values
968
        $colStmt = $this->getColumnStmt($tableName);
969
        // colNames is used to get the name of the columns when using complete-insert
970
        if ($this->dumpSettings['complete-insert']) {
971
            $colNames = $this->getColumnNames($tableName);
972
        }
973
974
        $stmt = "SELECT ".implode(",", $colStmt)." FROM `$tableName`";
975
976
        if ($this->dumpSettings['where']) {
977
            $stmt .= " WHERE {$this->dumpSettings['where']}";
978
        }
979
        $resultSet = $this->dbHandler->query($stmt);
980
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
981
982
        $ignore = $this->dumpSettings['insert-ignore'] ? '  IGNORE' : '';
983
984
        foreach ($resultSet as $row) {
985
            $vals = $this->prepareColumnValues($tableName, $row);
986
            if ($onlyOnce || !$this->dumpSettings['extended-insert']) {
987
988
                if ($this->dumpSettings['complete-insert']) {
989
                    $lineSize += $this->compressManager->write(
990
                        "INSERT$ignore INTO `$tableName` (".
991
                        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...
992
                        ") VALUES (".implode(",", $vals).")"
993
                    );
994
                } else {
995
                    $lineSize += $this->compressManager->write(
996
                        "INSERT$ignore INTO `$tableName` VALUES (".implode(",", $vals).")"
997
                    );
998
                }
999
                $onlyOnce = false;
1000
            } else {
1001
                $lineSize += $this->compressManager->write(",(".implode(",", $vals).")");
1002
            }
1003
            if (($lineSize > $this->dumpSettings['net_buffer_length']) ||
1004
                    !$this->dumpSettings['extended-insert']) {
1005
                $onlyOnce = true;
1006
                $lineSize = $this->compressManager->write(";".PHP_EOL);
1007
            }
1008
        }
1009
        $resultSet->closeCursor();
1010
1011
        if (!$onlyOnce) {
1012
            $this->compressManager->write(";".PHP_EOL);
1013
        }
1014
1015
        $this->endListValues($tableName);
1016
    }
1017
1018
    /**
1019
     * Table rows extractor, append information prior to dump
1020
     *
1021
     * @param string $tableName  Name of table to export
1022
     *
1023
     * @return null
1024
     */
1025
    function prepareListValues($tableName)
1026
    {
1027
        if (!$this->dumpSettings['skip-comments']) {
1028
            $this->compressManager->write(
1029
                "--".PHP_EOL.
1030
                "-- Dumping data for table `$tableName`".PHP_EOL.
1031
                "--".PHP_EOL.PHP_EOL
1032
            );
1033
        }
1034
1035
        if ($this->dumpSettings['single-transaction']) {
1036
            $this->dbHandler->exec($this->typeAdapter->setup_transaction());
1037
            $this->dbHandler->exec($this->typeAdapter->start_transaction());
1038
        }
1039
1040
        if ($this->dumpSettings['lock-tables']) {
1041
            $this->typeAdapter->lock_table($tableName);
1042
        }
1043
1044
        if ($this->dumpSettings['add-locks']) {
1045
            $this->compressManager->write(
1046
                $this->typeAdapter->start_add_lock_table($tableName)
1047
            );
1048
        }
1049
1050
        if ($this->dumpSettings['disable-keys']) {
1051
            $this->compressManager->write(
1052
                $this->typeAdapter->start_add_disable_keys($tableName)
1053
            );
1054
        }
1055
1056
        // Disable autocommit for faster reload
1057
        if ($this->dumpSettings['no-autocommit']) {
1058
            $this->compressManager->write(
1059
                $this->typeAdapter->start_disable_autocommit()
1060
            );
1061
        }
1062
1063
        return;
1064
    }
1065
1066
    /**
1067
     * Table rows extractor, close locks and commits after dump
1068
     *
1069
     * @param string $tableName  Name of table to export
1070
     *
1071
     * @return null
1072
     */
1073
    function endListValues($tableName)
1074
    {
1075
        if ($this->dumpSettings['disable-keys']) {
1076
            $this->compressManager->write(
1077
                $this->typeAdapter->end_add_disable_keys($tableName)
1078
            );
1079
        }
1080
1081
        if ($this->dumpSettings['add-locks']) {
1082
            $this->compressManager->write(
1083
                $this->typeAdapter->end_add_lock_table($tableName)
1084
            );
1085
        }
1086
1087
        if ($this->dumpSettings['single-transaction']) {
1088
            $this->dbHandler->exec($this->typeAdapter->commit_transaction());
1089
        }
1090
1091
        if ($this->dumpSettings['lock-tables']) {
1092
            $this->typeAdapter->unlock_table($tableName);
1093
        }
1094
1095
        // Commit to enable autocommit
1096
        if ($this->dumpSettings['no-autocommit']) {
1097
            $this->compressManager->write(
1098
                $this->typeAdapter->end_disable_autocommit()
1099
            );
1100
        }
1101
1102
        $this->compressManager->write(PHP_EOL);
1103
1104
        return;
1105
    }
1106
1107
    /**
1108
     * Build SQL List of all columns on current table which will be used for selecting
1109
     *
1110
     * @param string $tableName  Name of table to get columns
1111
     *
1112
     * @return array SQL sentence with columns for select
1113
     */
1114
    function getColumnStmt($tableName)
1115
    {
1116
        $colStmt = array();
1117
        foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) {
1118
            if ($colType['type'] == 'bit' && $this->dumpSettings['hex-blob']) {
1119
                $colStmt[] = "LPAD(HEX(`${colName}`),2,'0') AS `${colName}`";
1120
            } else if ($colType['is_blob'] && $this->dumpSettings['hex-blob']) {
1121
                $colStmt[] = "HEX(`${colName}`) AS `${colName}`";
1122
            } else if ($colType['is_virtual']) {
1123
                $this->dumpSettings['complete-insert'] = true;
1124
                continue;
1125
            } else {
1126
                $colStmt[] = "`${colName}`";
1127
            }
1128
        }
1129
1130
        return $colStmt;
1131
    }
1132
1133
    /**
1134
     * Build SQL List of all columns on current table which will be used for inserting
1135
     *
1136
     * @param string $tableName  Name of table to get columns
1137
     *
1138
     * @return array columns for sql sentence for insert
1139
     */
1140
    function getColumnNames($tableName)
1141
    {
1142
        $colNames = array();
1143
        foreach($this->tableColumnTypes[$tableName] as $colName => $colType) {
1144
            if ($colType['is_virtual']) {
1145
                $this->dumpSettings['complete-insert'] = true;
1146
                continue;
1147
            } else {
1148
                $colNames[] = "`${colName}`";
1149
            }
1150
        }
1151
        return $colNames;
1152
    }
1153
}
1154
1155
/**
1156
 * Enum with all available compression methods
1157
 *
1158
 */
1159
abstract class CompressMethod
1160
{
1161
    public static $enums = array(
1162
        "None",
1163
        "Gzip",
1164
        "Bzip2"
1165
    );
1166
1167
    /**
1168
     * @param string $c
1169
     * @return boolean
1170
     */
1171
    public static function isValid($c)
1172
    {
1173
        return in_array($c, self::$enums);
1174
    }
1175
}
1176
1177
abstract class CompressManagerFactory
1178
{
1179
    /**
1180
     * @param string $c
1181
     * @return CompressBzip2|CompressGzip|CompressNone
1182
     */
1183
    public static function create($c)
1184
    {
1185
        $c = ucfirst(strtolower($c));
1186
        if (!CompressMethod::isValid($c)) {
1187
            throw new Exception("Compression method ($c) is not defined yet");
1188
        }
1189
1190
        $method = __NAMESPACE__."\\"."Compress".$c;
1191
1192
        return new $method;
1193
    }
1194
}
1195
1196
class CompressBzip2 extends CompressManagerFactory
1197
{
1198
    private $fileHandler = null;
1199
1200
    public function __construct()
1201
    {
1202
        if (!function_exists("bzopen")) {
1203
            throw new Exception("Compression is enabled, but bzip2 lib is not installed or configured properly");
1204
        }
1205
    }
1206
1207
    /**
1208
     * @param string $filename
1209
     */
1210
    public function open($filename)
1211
    {
1212
        $this->fileHandler = bzopen($filename, "w");
1213
        if (false === $this->fileHandler) {
1214
            throw new Exception("Output file is not writable");
1215
        }
1216
1217
        return true;
1218
    }
1219
1220
    public function write($str)
1221
    {
1222
        if (false === ($bytesWritten = bzwrite($this->fileHandler, $str))) {
1223
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1224
        }
1225
        return $bytesWritten;
1226
    }
1227
1228
    public function close()
1229
    {
1230
        return bzclose($this->fileHandler);
1231
    }
1232
}
1233
1234
class CompressGzip extends CompressManagerFactory
1235
{
1236
    private $fileHandler = null;
1237
1238
    public function __construct()
1239
    {
1240
        if (!function_exists("gzopen")) {
1241
            throw new Exception("Compression is enabled, but gzip lib is not installed or configured properly");
1242
        }
1243
    }
1244
1245
    /**
1246
     * @param string $filename
1247
     */
1248
    public function open($filename)
1249
    {
1250
        $this->fileHandler = gzopen($filename, "wb");
1251
        if (false === $this->fileHandler) {
1252
            throw new Exception("Output file is not writable");
1253
        }
1254
1255
        return true;
1256
    }
1257
1258
    public function write($str)
1259
    {
1260
        if (false === ($bytesWritten = gzwrite($this->fileHandler, $str))) {
1261
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1262
        }
1263
        return $bytesWritten;
1264
    }
1265
1266
    public function close()
1267
    {
1268
        return gzclose($this->fileHandler);
1269
    }
1270
}
1271
1272
class CompressNone extends CompressManagerFactory
1273
{
1274
    private $fileHandler = null;
1275
1276
    /**
1277
     * @param string $filename
1278
     */
1279
    public function open($filename)
1280
    {
1281
        $this->fileHandler = fopen($filename, "wb");
1282
        if (false === $this->fileHandler) {
1283
            throw new Exception("Output file is not writable");
1284
        }
1285
1286
        return true;
1287
    }
1288
1289
    public function write($str)
1290
    {
1291
        if (false === ($bytesWritten = fwrite($this->fileHandler, $str))) {
1292
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1293
        }
1294
        return $bytesWritten;
1295
    }
1296
1297
    public function close()
1298
    {
1299
        return fclose($this->fileHandler);
1300
    }
1301
}
1302
1303
/**
1304
 * Enum with all available TypeAdapter implementations
1305
 *
1306
 */
1307
abstract class TypeAdapter
1308
{
1309
    public static $enums = array(
1310
        "Sqlite",
1311
        "Mysql"
1312
    );
1313
1314
    /**
1315
     * @param string $c
1316
     * @return boolean
1317
     */
1318
    public static function isValid($c)
1319
    {
1320
        return in_array($c, self::$enums);
1321
    }
1322
}
1323
1324
/**
1325
 * TypeAdapter Factory
1326
 *
1327
 */
1328
abstract class TypeAdapterFactory
1329
{
1330
    protected $dbHandler = null;
1331
    protected $dumpSettings = array();
1332
1333
    /**
1334
     * @param string $c Type of database factory to create (Mysql, Sqlite,...)
1335
     * @param PDO $dbHandler
1336
     */
1337
    public static function create($c, $dbHandler = null, $dumpSettings = array())
1338
    {
1339
        $c = ucfirst(strtolower($c));
1340
        if (!TypeAdapter::isValid($c)) {
1341
            throw new Exception("Database type support for ($c) not yet available");
1342
        }
1343
        $method = __NAMESPACE__."\\"."TypeAdapter".$c;
1344
        return new $method($dbHandler, $dumpSettings);
1345
    }
1346
1347
    public function __construct($dbHandler = null, $dumpSettings = array())
1348
    {
1349
        $this->dbHandler = $dbHandler;
1350
        $this->dumpSettings = $dumpSettings;
1351
    }
1352
1353
    /**
1354
     * function databases Add sql to create and use database
1355
     * @todo make it do something with sqlite
1356
     */
1357
    public function databases()
1358
    {
1359
        return "";
1360
    }
1361
1362
    public function show_create_table($tableName)
1363
    {
1364
        return "SELECT tbl_name as 'Table', sql as 'Create Table' ".
1365
            "FROM sqlite_master ".
1366
            "WHERE type='table' AND tbl_name='$tableName'";
1367
    }
1368
1369
    /**
1370
     * function create_table Get table creation code from database
1371
     * @todo make it do something with sqlite
1372
     */
1373
    public function create_table($row)
1374
    {
1375
        return "";
1376
    }
1377
1378
    public function show_create_view($viewName)
1379
    {
1380
        return "SELECT tbl_name as 'View', sql as 'Create View' ".
1381
            "FROM sqlite_master ".
1382
            "WHERE type='view' AND tbl_name='$viewName'";
1383
    }
1384
1385
    /**
1386
     * function create_view Get view creation code from database
1387
     * @todo make it do something with sqlite
1388
     */
1389
    public function create_view($row)
1390
    {
1391
        return "";
1392
    }
1393
1394
    /**
1395
     * function show_create_trigger Get trigger creation code from database
1396
     * @todo make it do something with sqlite
1397
     */
1398
    public function show_create_trigger($triggerName)
1399
    {
1400
        return "";
1401
    }
1402
1403
    /**
1404
     * function create_trigger Modify trigger code, add delimiters, etc
1405
     * @todo make it do something with sqlite
1406
     */
1407
    public function create_trigger($triggerName)
1408
    {
1409
        return "";
1410
    }
1411
1412
    /**
1413
     * function create_procedure Modify procedure code, add delimiters, etc
1414
     * @todo make it do something with sqlite
1415
     */
1416
    public function create_procedure($procedureName)
1417
    {
1418
        return "";
1419
    }
1420
1421
    public function show_tables()
1422
    {
1423
        return "SELECT tbl_name FROM sqlite_master WHERE type='table'";
1424
    }
1425
1426
    public function show_views()
1427
    {
1428
        return "SELECT tbl_name FROM sqlite_master WHERE type='view'";
1429
    }
1430
1431
    public function show_triggers()
1432
    {
1433
        return "SELECT name FROM sqlite_master WHERE type='trigger'";
1434
    }
1435
1436
    public function show_columns()
1437
    {
1438
        if (func_num_args() != 1) {
1439
            return "";
1440
        }
1441
1442
        $args = func_get_args();
1443
1444
        return "pragma table_info(${args[0]})";
1445
    }
1446
1447
    public function show_procedures()
1448
    {
1449
        return "";
1450
    }
1451
1452
    public function show_events()
1453
    {
1454
        return "";
1455
    }
1456
1457
    public function setup_transaction()
1458
    {
1459
        return "";
1460
    }
1461
1462
    public function start_transaction()
1463
    {
1464
        return "BEGIN EXCLUSIVE";
1465
    }
1466
1467
    public function commit_transaction()
1468
    {
1469
        return "COMMIT";
1470
    }
1471
1472
    public function lock_table()
1473
    {
1474
        return "";
1475
    }
1476
1477
    public function unlock_table()
1478
    {
1479
        return "";
1480
    }
1481
1482
    public function start_add_lock_table()
1483
    {
1484
        return PHP_EOL;
1485
    }
1486
1487
    public function end_add_lock_table()
1488
    {
1489
        return PHP_EOL;
1490
    }
1491
1492
    public function start_add_disable_keys()
1493
    {
1494
        return PHP_EOL;
1495
    }
1496
1497
    public function end_add_disable_keys()
1498
    {
1499
        return PHP_EOL;
1500
    }
1501
1502
    public function start_disable_foreign_keys_check()
1503
    {
1504
        return PHP_EOL;
1505
    }
1506
1507
    public function end_disable_foreign_keys_check()
1508
    {
1509
        return PHP_EOL;
1510
    }
1511
1512
    public function add_drop_database()
1513
    {
1514
        return PHP_EOL;
1515
    }
1516
1517
    public function add_drop_trigger()
1518
    {
1519
        return PHP_EOL;
1520
    }
1521
1522
    public function drop_table()
1523
    {
1524
        return PHP_EOL;
1525
    }
1526
1527
    public function drop_view()
1528
    {
1529
        return PHP_EOL;
1530
    }
1531
1532
    /**
1533
     * Decode column metadata and fill info structure.
1534
     * type, is_numeric and is_blob will always be available.
1535
     *
1536
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1537
     * @return array
1538
     */
1539
    public function parseColumnType($colType)
1540
    {
1541
        return array();
1542
    }
1543
1544
    public function backup_parameters()
1545
    {
1546
        return PHP_EOL;
1547
    }
1548
1549
    public function restore_parameters()
1550
    {
1551
        return PHP_EOL;
1552
    }
1553
}
1554
1555
class TypeAdapterPgsql extends TypeAdapterFactory
1556
{
1557
}
1558
1559
class TypeAdapterDblib extends TypeAdapterFactory
1560
{
1561
}
1562
1563
class TypeAdapterSqlite extends TypeAdapterFactory
1564
{
1565
}
1566
1567
class TypeAdapterMysql extends TypeAdapterFactory
1568
{
1569
    const DEFINER_RE = 'DEFINER=`(?:[^`]|``)*`@`(?:[^`]|``)*`';
1570
1571
1572
    // Numerical Mysql types
1573
    public $mysqlTypes = array(
1574
        'numerical' => array(
1575
            'bit',
1576
            'tinyint',
1577
            'smallint',
1578
            'mediumint',
1579
            'int',
1580
            'integer',
1581
            'bigint',
1582
            'real',
1583
            'double',
1584
            'float',
1585
            'decimal',
1586
            'numeric'
1587
        ),
1588
        'blob' => array(
1589
            'tinyblob',
1590
            'blob',
1591
            'mediumblob',
1592
            'longblob',
1593
            'binary',
1594
            'varbinary',
1595
            'bit',
1596
            'geometry', /* http://bugs.mysql.com/bug.php?id=43544 */
1597
            'point',
1598
            'linestring',
1599
            'polygon',
1600
            'multipoint',
1601
            'multilinestring',
1602
            'multipolygon',
1603
            'geometrycollection',
1604
        )
1605
    );
1606
1607
    public function databases()
1608
    {
1609
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1610
        $args = func_get_args();
1611
        $databaseName = $args[0];
1612
1613
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'character_set_database';");
1614
        $characterSet = $resultSet->fetchColumn(1);
1615
        $resultSet->closeCursor();
1616
1617
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'collation_database';");
1618
        $collationDb = $resultSet->fetchColumn(1);
1619
        $resultSet->closeCursor();
1620
        $ret = "";
1621
1622
        $ret .= "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `${databaseName}`".
1623
            " /*!40100 DEFAULT CHARACTER SET ${characterSet} ".
1624
            " COLLATE ${collationDb} */;".PHP_EOL.PHP_EOL.
1625
            "USE `${databaseName}`;".PHP_EOL.PHP_EOL;
1626
1627
        return $ret;
1628
    }
1629
1630
    public function show_create_table($tableName)
1631
    {
1632
        return "SHOW CREATE TABLE `$tableName`";
1633
    }
1634
1635
    public function show_create_view($viewName)
1636
    {
1637
        return "SHOW CREATE VIEW `$viewName`";
1638
    }
1639
1640
    public function show_create_trigger($triggerName)
1641
    {
1642
        return "SHOW CREATE TRIGGER `$triggerName`";
1643
    }
1644
1645
    public function show_create_procedure($procedureName)
1646
    {
1647
        return "SHOW CREATE PROCEDURE `$procedureName`";
1648
    }
1649
1650
    public function show_create_event($eventName)
1651
    {
1652
        return "SHOW CREATE EVENT `$eventName`";
1653
    }
1654
1655
    public function create_table($row)
1656
    {
1657
        if (!isset($row['Create Table'])) {
1658
            throw new Exception("Error getting table code, unknown output");
1659
        }
1660
1661
        $createTable = $row['Create Table'];
1662
        if ($this->dumpSettings['reset-auto-increment']) {
1663
            $match = "/AUTO_INCREMENT=[0-9]+/s";
1664
            $replace = "";
1665
            $createTable = preg_replace($match, $replace, $createTable);
1666
        }
1667
1668
        $ret = "/*!40101 SET @saved_cs_client     = @@character_set_client */;".PHP_EOL.
1669
            "/*!40101 SET character_set_client = ".$this->dumpSettings['default-character-set']." */;".PHP_EOL.
1670
            $createTable.";".PHP_EOL.
1671
            "/*!40101 SET character_set_client = @saved_cs_client */;".PHP_EOL.
1672
            PHP_EOL;
1673
        return $ret;
1674
    }
1675
1676
    public function create_view($row)
1677
    {
1678
        $ret = "";
1679
        if (!isset($row['Create View'])) {
1680
                throw new Exception("Error getting view structure, unknown output");
1681
        }
1682
1683
        $viewStmt = $row['Create View'];
1684
1685
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50013 \2 */'.PHP_EOL;
1686
1687
        if ($viewStmtReplaced = preg_replace(
1688
            '/^(CREATE(?:\s+ALGORITHM=(?:UNDEFINED|MERGE|TEMPTABLE))?)\s+('
1689
            .self::DEFINER_RE.'(?:\s+SQL SECURITY DEFINER|INVOKER)?)?\s+(VIEW .+)$/',
1690
            '/*!50001 \1 */'.PHP_EOL.$definerStr.'/*!50001 \3 */',
1691
            $viewStmt,
1692
            1
1693
        )) {
1694
            $viewStmt = $viewStmtReplaced;
1695
        };
1696
1697
        $ret .= $viewStmt.';'.PHP_EOL.PHP_EOL;
1698
        return $ret;
1699
    }
1700
1701
    public function create_trigger($row)
1702
    {
1703
        $ret = "";
1704
        if (!isset($row['SQL Original Statement'])) {
1705
            throw new Exception("Error getting trigger code, unknown output");
1706
        }
1707
1708
        $triggerStmt = $row['SQL Original Statement'];
1709
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50017 \2*/ ';
1710
        if ($triggerStmtReplaced = preg_replace(
1711
            '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(TRIGGER\s.*)$/s',
1712
            '/*!50003 \1*/ '.$definerStr.'/*!50003 \3 */',
1713
            $triggerStmt,
1714
            1
1715
        )) {
1716
            $triggerStmt = $triggerStmtReplaced;
1717
        }
1718
1719
        $ret .= "DELIMITER ;;".PHP_EOL.
1720
            $triggerStmt.";;".PHP_EOL.
1721
            "DELIMITER ;".PHP_EOL.PHP_EOL;
1722
        return $ret;
1723
    }
1724
1725
    public function create_procedure($row)
1726
    {
1727
        $ret = "";
1728
        if (!isset($row['Create Procedure'])) {
1729
            throw new Exception("Error getting procedure code, unknown output. ".
1730
                "Please check 'https://bugs.mysql.com/bug.php?id=14564'");
1731
        }
1732
        $procedureStmt = $row['Create Procedure'];
1733
1734
        $ret .= "/*!50003 DROP PROCEDURE IF EXISTS `".
1735
            $row['Procedure']."` */;".PHP_EOL.
1736
            "/*!40101 SET @saved_cs_client     = @@character_set_client */;".PHP_EOL.
1737
            "/*!40101 SET character_set_client = ".$this->dumpSettings['default-character-set']." */;".PHP_EOL.
1738
            "DELIMITER ;;".PHP_EOL.
1739
            $procedureStmt." ;;".PHP_EOL.
1740
            "DELIMITER ;".PHP_EOL.
1741
            "/*!40101 SET character_set_client = @saved_cs_client */;".PHP_EOL.PHP_EOL;
1742
1743
        return $ret;
1744
    }
1745
1746
    public function create_event($row)
1747
    {
1748
        $ret = "";
1749
        if (!isset($row['Create Event'])) {
1750
            throw new Exception("Error getting event code, unknown output. ".
1751
                "Please check 'http://stackoverflow.com/questions/10853826/mysql-5-5-create-event-gives-syntax-error'");
1752
        }
1753
        $eventName = $row['Event'];
1754
        $eventStmt = $row['Create Event'];
1755
        $sqlMode = $row['sql_mode'];
1756
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50117 \2*/ ';
1757
1758
        if ($eventStmtReplaced = preg_replace(
1759
            '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(EVENT .*)$/',
1760
            '/*!50106 \1*/ '.$definerStr.'/*!50106 \3 */',
1761
            $eventStmt,
1762
            1
1763
        )) {
1764
            $eventStmt = $eventStmtReplaced;
1765
        }
1766
1767
        $ret .= "/*!50106 SET @save_time_zone= @@TIME_ZONE */ ;".PHP_EOL.
1768
            "/*!50106 DROP EVENT IF EXISTS `".$eventName."` */;".PHP_EOL.
1769
            "DELIMITER ;;".PHP_EOL.
1770
            "/*!50003 SET @saved_cs_client      = @@character_set_client */ ;;".PHP_EOL.
1771
            "/*!50003 SET @saved_cs_results     = @@character_set_results */ ;;".PHP_EOL.
1772
            "/*!50003 SET @saved_col_connection = @@collation_connection */ ;;".PHP_EOL.
1773
            "/*!50003 SET character_set_client  = utf8 */ ;;".PHP_EOL.
1774
            "/*!50003 SET character_set_results = utf8 */ ;;".PHP_EOL.
1775
            "/*!50003 SET collation_connection  = utf8_general_ci */ ;;".PHP_EOL.
1776
            "/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;;".PHP_EOL.
1777
            "/*!50003 SET sql_mode              = '".$sqlMode."' */ ;;".PHP_EOL.
1778
            "/*!50003 SET @saved_time_zone      = @@time_zone */ ;;".PHP_EOL.
1779
            "/*!50003 SET time_zone             = 'SYSTEM' */ ;;".PHP_EOL.
1780
            $eventStmt." ;;".PHP_EOL.
1781
            "/*!50003 SET time_zone             = @saved_time_zone */ ;;".PHP_EOL.
1782
            "/*!50003 SET sql_mode              = @saved_sql_mode */ ;;".PHP_EOL.
1783
            "/*!50003 SET character_set_client  = @saved_cs_client */ ;;".PHP_EOL.
1784
            "/*!50003 SET character_set_results = @saved_cs_results */ ;;".PHP_EOL.
1785
            "/*!50003 SET collation_connection  = @saved_col_connection */ ;;".PHP_EOL.
1786
            "DELIMITER ;".PHP_EOL.
1787
            "/*!50106 SET TIME_ZONE= @save_time_zone */ ;".PHP_EOL.PHP_EOL;
1788
            // Commented because we are doing this in restore_parameters()
1789
            // "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL . PHP_EOL;
1790
1791
        return $ret;
1792
    }
1793
1794
    public function show_tables()
1795
    {
1796
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1797
        $args = func_get_args();
1798
        return "SELECT TABLE_NAME AS tbl_name ".
1799
            "FROM INFORMATION_SCHEMA.TABLES ".
1800
            "WHERE TABLE_TYPE='BASE TABLE' AND TABLE_SCHEMA='${args[0]}'";
1801
    }
1802
1803
    public function show_views()
1804
    {
1805
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1806
        $args = func_get_args();
1807
        return "SELECT TABLE_NAME AS tbl_name ".
1808
            "FROM INFORMATION_SCHEMA.TABLES ".
1809
            "WHERE TABLE_TYPE='VIEW' AND TABLE_SCHEMA='${args[0]}'";
1810
    }
1811
1812
    public function show_triggers()
1813
    {
1814
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1815
        $args = func_get_args();
1816
        return "SHOW TRIGGERS FROM `${args[0]}`;";
1817
    }
1818
1819
    public function show_columns()
1820
    {
1821
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1822
        $args = func_get_args();
1823
        return "SHOW COLUMNS FROM `${args[0]}`;";
1824
    }
1825
1826
    public function show_procedures()
1827
    {
1828
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1829
        $args = func_get_args();
1830
        return "SELECT SPECIFIC_NAME AS procedure_name ".
1831
            "FROM INFORMATION_SCHEMA.ROUTINES ".
1832
            "WHERE ROUTINE_TYPE='PROCEDURE' AND ROUTINE_SCHEMA='${args[0]}'";
1833
    }
1834
1835
    /**
1836
     * Get query string to ask for names of events from current database.
1837
     *
1838
     * @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...
1839
     * @return string
1840
     */
1841
    public function show_events()
1842
    {
1843
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1844
        $args = func_get_args();
1845
        return "SELECT EVENT_NAME AS event_name ".
1846
            "FROM INFORMATION_SCHEMA.EVENTS ".
1847
            "WHERE EVENT_SCHEMA='${args[0]}'";
1848
    }
1849
1850
    public function setup_transaction()
1851
    {
1852
        return "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
1853
    }
1854
1855
    public function start_transaction()
1856
    {
1857
        return "START TRANSACTION";
1858
    }
1859
1860
    public function commit_transaction()
1861
    {
1862
        return "COMMIT";
1863
    }
1864
1865
    public function lock_table()
1866
    {
1867
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1868
        $args = func_get_args();
1869
        return $this->dbHandler->exec("LOCK TABLES `${args[0]}` READ LOCAL");
1870
1871
    }
1872
1873
    public function unlock_table()
1874
    {
1875
        return $this->dbHandler->exec("UNLOCK TABLES");
1876
    }
1877
1878
    public function start_add_lock_table()
1879
    {
1880
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1881
        $args = func_get_args();
1882
        return "LOCK TABLES `${args[0]}` WRITE;".PHP_EOL;
1883
    }
1884
1885
    public function end_add_lock_table()
1886
    {
1887
        return "UNLOCK TABLES;".PHP_EOL;
1888
    }
1889
1890
    public function start_add_disable_keys()
1891
    {
1892
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1893
        $args = func_get_args();
1894
        return "/*!40000 ALTER TABLE `${args[0]}` DISABLE KEYS */;".
1895
            PHP_EOL;
1896
    }
1897
1898
    public function end_add_disable_keys()
1899
    {
1900
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1901
        $args = func_get_args();
1902
        return "/*!40000 ALTER TABLE `${args[0]}` ENABLE KEYS */;".
1903
            PHP_EOL;
1904
    }
1905
1906
    public function start_disable_autocommit()
1907
    {
1908
        return "SET autocommit=0;".PHP_EOL;
1909
    }
1910
1911
    public function end_disable_autocommit()
1912
    {
1913
        return "COMMIT;".PHP_EOL;
1914
    }
1915
1916
    public function add_drop_database()
1917
    {
1918
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1919
        $args = func_get_args();
1920
        return "/*!40000 DROP DATABASE IF EXISTS `${args[0]}`*/;".
1921
            PHP_EOL.PHP_EOL;
1922
    }
1923
1924
    public function add_drop_trigger()
1925
    {
1926
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1927
        $args = func_get_args();
1928
        return "DROP TRIGGER IF EXISTS `${args[0]}`;".PHP_EOL;
1929
    }
1930
1931
    public function drop_table()
1932
    {
1933
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1934
        $args = func_get_args();
1935
        return "DROP TABLE IF EXISTS `${args[0]}`;".PHP_EOL;
1936
    }
1937
1938
    public function drop_view()
1939
    {
1940
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1941
        $args = func_get_args();
1942
        return "DROP TABLE IF EXISTS `${args[0]}`;".PHP_EOL.
1943
                "/*!50001 DROP VIEW IF EXISTS `${args[0]}`*/;".PHP_EOL;
1944
    }
1945
1946
    public function getDatabaseHeader()
1947
    {
1948
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1949
        $args = func_get_args();
1950
        return "--".PHP_EOL.
1951
            "-- Current Database: `${args[0]}`".PHP_EOL.
1952
            "--".PHP_EOL.PHP_EOL;
1953
    }
1954
1955
    /**
1956
     * Decode column metadata and fill info structure.
1957
     * type, is_numeric and is_blob will always be available.
1958
     *
1959
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1960
     * @return array
1961
     */
1962
    public function parseColumnType($colType)
1963
    {
1964
        $colInfo = array();
1965
        $colParts = explode(" ", $colType['Type']);
1966
1967
        if ($fparen = strpos($colParts[0], "(")) {
1968
            $colInfo['type'] = substr($colParts[0], 0, $fparen);
1969
            $colInfo['length'] = str_replace(")", "", substr($colParts[0], $fparen + 1));
1970
            $colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
1971
        } else {
1972
            $colInfo['type'] = $colParts[0];
1973
        }
1974
        $colInfo['is_numeric'] = in_array($colInfo['type'], $this->mysqlTypes['numerical']);
1975
        $colInfo['is_blob'] = in_array($colInfo['type'], $this->mysqlTypes['blob']);
1976
        // for virtual columns that are of type 'Extra', column type
1977
        // could by "STORED GENERATED" or "VIRTUAL GENERATED"
1978
        // MySQL reference: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
1979
        $colInfo['is_virtual'] = strpos($colType['Extra'], "VIRTUAL GENERATED") !== false || strpos($colType['Extra'], "STORED GENERATED") !== false;
1980
1981
        return $colInfo;
1982
    }
1983
1984
    public function backup_parameters()
1985
    {
1986
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;".PHP_EOL.
1987
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;".PHP_EOL.
1988
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;".PHP_EOL.
1989
            "/*!40101 SET NAMES ".$this->dumpSettings['default-character-set']." */;".PHP_EOL;
1990
1991
        if (false === $this->dumpSettings['skip-tz-utc']) {
1992
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;".PHP_EOL.
1993
                "/*!40103 SET TIME_ZONE='+00:00' */;".PHP_EOL;
1994
        }
1995
1996
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;".PHP_EOL.
1997
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;".PHP_EOL.
1998
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;".PHP_EOL.
1999
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;".PHP_EOL.PHP_EOL;
2000
2001
        return $ret;
2002
    }
2003
2004
    public function restore_parameters()
2005
    {
2006
        $ret = "";
2007
2008
        if (false === $this->dumpSettings['skip-tz-utc']) {
2009
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;".PHP_EOL;
2010
        }
2011
2012
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;".PHP_EOL.
2013
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;".PHP_EOL.
2014
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;".PHP_EOL.
2015
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;".PHP_EOL.
2016
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;".PHP_EOL.
2017
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;".PHP_EOL.
2018
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;".PHP_EOL.PHP_EOL;
2019
2020
        return $ret;
2021
    }
2022
2023
    /**
2024
     * Check number of parameters passed to function, useful when inheriting.
2025
     * Raise exception if unexpected.
2026
     *
2027
     * @param integer $num_args
2028
     * @param integer $expected_num_args
2029
     * @param string $method_name
2030
     */
2031
    private function check_parameters($num_args, $expected_num_args, $method_name)
2032
    {
2033
        if ($num_args != $expected_num_args) {
2034
            throw new Exception("Unexpected parameter passed to $method_name");
2035
        }
2036
        return;
2037
    }
2038
}
2039