Passed
Branch master (11a4ac)
by diego
02:42
created

Mysqldump::exportProcedures()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * PHP version of mysqldump cli that comes with MySQL
5
 *
6
 * mysql mysqldump pdo php7 php5 database php sql
7
 *
8
 * @category Library
9
 * @package  Ifsnop\Mysqldump
10
 * @author   Diego Torres <[email protected]>
11
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
12
 * @link     https://github.com/ifsnop/mysqldump-php
13
 *
14
 */
15
16
namespace Ifsnop\Mysqldump;
17
18
use Exception;
19
use PDO;
20
use PDOException;
21
22
/**
23
 * PHP version of mysqldump cli that comes with MySQL
24
 *
25
 * mysql mysqldump pdo php7 php5 database php sql
26
 *
27
 * @category Library
28
 * @package  Ifsnop\Mysqldump
29
 * @author   Diego Torres <[email protected]>
30
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
31
 * @link     https://github.com/ifsnop/mysqldump-php
32
 *
33
 */
34
class Mysqldump
35
{
36
37
    // Same as mysqldump
38
    const MAXLINESIZE = 1000000;
39
40
    // Available compression methods as constants
41
    const GZIP = 'Gzip';
42
    const BZIP2 = 'Bzip2';
43
    const NONE = 'None';
44
45
    // Available connection strings
46
    const UTF8 = 'utf8';
47
    const UTF8MB4 = 'utf8mb4';
48
49
    /**
50
    * Database username
51
    * @var string
52
    */
53
    public $user;
54
    /**
55
    * Database password
56
    * @var string
57
    */
58
    public $pass;
59
    /**
60
    * Connection string for PDO
61
    * @var string
62
    */
63
    public $dsn;
64
    /**
65
    * Destination filename, defaults to stdout
66
    * @var string
67
    */
68
    public $fileName = 'php://output';
69
70
    // Internal stuff
71
    private $tables = array();
72
    private $views = array();
73
    private $triggers = array();
74
    private $procedures = array();
75
    private $events = array();
76
    private $dbHandler = null;
77
    private $dbType;
78
    private $compressManager;
79
    private $typeAdapter;
80
    private $dumpSettings = array();
81
    private $pdoSettings = array();
82
    private $version;
83
    private $tableColumnTypes = array();
84
    /**
85
    * database name, parsed from dsn
86
    * @var string
87
    */
88
    private $dbName;
89
    /**
90
    * host name, parsed from dsn
91
    * @var string
92
    */
93
    private $host;
94
    /**
95
    * dsn string parsed as an array
96
    * @var array
97
    */
98
    private $dsnArray = array();
99
100
    /**
101
     * Constructor of Mysqldump. Note that in the case of an SQLite database
102
     * connection, the filename must be in the $db parameter.
103
     *
104
     * @param string $dsn        PDO DSN connection string
105
     * @param string $user       SQL account username
106
     * @param string $pass       SQL account password
107
     * @param array  $dumpSettings SQL database settings
108
     * @param array  $pdoSettings  PDO configured attributes
109
     */
110
    public function __construct(
111
        $dsn = '',
112
        $user = '',
113
        $pass = '',
114
        $dumpSettings = array(),
115
        $pdoSettings = array()
116
    ) {
117
        $dumpSettingsDefault = array(
118
            'include-tables' => array(),
119
            'exclude-tables' => array(),
120
            'compress' => Mysqldump::NONE,
121
            'init_commands' => array(),
122
            'no-data' => array(),
123
            'reset-auto-increment' => false,
124
            'add-drop-database' => false,
125
            'add-drop-table' => false,
126
            'add-drop-trigger' => true,
127
            'add-locks' => true,
128
            'complete-insert' => false,
129
            'databases' => false,
130
            'default-character-set' => Mysqldump::UTF8,
131
            'disable-keys' => true,
132
            'extended-insert' => true,
133
            'events' => false,
134
            'hex-blob' => true, /* faster than escaped content */
135
            'net_buffer_length' => self::MAXLINESIZE,
136
            'no-autocommit' => true,
137
            'no-create-info' => false,
138
            'lock-tables' => true,
139
            'routines' => false,
140
            'single-transaction' => true,
141
            'skip-triggers' => false,
142
            'skip-tz-utc' => false,
143
            'skip-comments' => false,
144
            'skip-dump-date' => false,
145
            'skip-definer' => false,
146
            'where' => '',
147
            /* deprecated */
148
            'disable-foreign-keys-check' => true
149
        );
150
151
        $pdoSettingsDefault = array(
152
            PDO::ATTR_PERSISTENT => true,
153
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
154
        );
155
156
        $this->user = $user;
157
        $this->pass = $pass;
158
        $this->parseDsn($dsn);
159
160
        // this drops MYSQL dependency, only use the constant if it's defined
161
        if ( "mysql" == $this->dbType ) {
0 ignored issues
show
introduced by
The condition 'mysql' == $this->dbType is always false.
Loading history...
162
            $pdoSettingsDefault[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false;
163
        }
164
165
        $this->pdoSettings = self::array_replace_recursive($pdoSettingsDefault, $pdoSettings);
166
        $this->dumpSettings = self::array_replace_recursive($dumpSettingsDefault, $dumpSettings);
167
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));
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'] :
260
            $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 and trigger structures from database
357
        $this->getDatabaseStructure();
358
359
        if ($this->dumpSettings['databases']) {
360
            $this->compressManager->write(
361
                $this->typeAdapter->databases($this->dbName)
362
            );
363
        }
364
365
        // If there still are some tables/views in include-tables array,
366
        // that means that some tables or views weren't found.
367
        // Give proper error and exit.
368
        // This check will be removed once include-tables supports regexps
369
        if (0 < count($this->dumpSettings['include-tables'])) {
370
            $name = implode(",", $this->dumpSettings['include-tables']);
371
            throw new Exception("Table (" . $name . ") not found in database");
372
        }
373
374
        $this->exportTables();
375
        $this->exportTriggers();
376
        $this->exportViews();
377
        $this->exportProcedures();
378
        $this->exportEvents();
379
380
        // Restore saved parameters
381
        $this->compressManager->write(
382
            $this->typeAdapter->restore_parameters()
383
        );
384
        // Write some stats to output file
385
        $this->compressManager->write($this->getDumpFileFooter());
386
        // Close output file
387
        $this->compressManager->close();
388
    }
389
390
    /**
391
     * Returns header for dump file
392
     *
393
     * @return string
394
     */
395
    private function getDumpFileHeader()
396
    {
397
        $header = '';
398
        if ( !$this->dumpSettings['skip-comments'] ) {
399
            // Some info about software, source and time
400
            $header = "-- mysqldump-php https://github.com/ifsnop/mysqldump-php" . PHP_EOL .
401
                    "--" . PHP_EOL .
402
                    "-- Host: {$this->host}\tDatabase: {$this->dbName}" . PHP_EOL .
403
                    "-- ------------------------------------------------------" . PHP_EOL;
404
405
            if ( !empty($this->version) ) {
406
                $header .= "-- Server version \t" . $this->version . PHP_EOL;
407
            }
408
409
            if ( !$this->dumpSettings['skip-dump-date'] ) {
410
                $header .= "-- Date: " . date('r') . PHP_EOL . PHP_EOL;
411
            }
412
        }
413
        return $header;
414
    }
415
416
    /**
417
     * Returns footer for dump file
418
     *
419
     * @return string
420
     */
421
    private function getDumpFileFooter()
422
    {
423
        $footer = '';
424
        if (!$this->dumpSettings['skip-comments']) {
425
            $footer .= '-- Dump completed';
426
            if (!$this->dumpSettings['skip-dump-date']) {
427
                $footer .= ' on: ' . date('r');
428
            }
429
            $footer .= PHP_EOL;
430
        }
431
432
        return $footer;
433
    }
434
435
    /**
436
     * Reads table and views names from database.
437
     * Fills $this->tables array so they will be dumped later.
438
     *
439
     * @return null
440
     */
441
    private function getDatabaseStructure()
442
    {
443
        // Listing all tables from database
444
        if (empty($this->dumpSettings['include-tables'])) {
445
            // include all tables for now, blacklisting happens later
446
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
447
                array_push($this->tables, current($row));
448
            }
449
        } else {
450
            // include only the tables mentioned in include-tables
451
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
452
                if (in_array(current($row), $this->dumpSettings['include-tables'], true)) {
453
                    array_push($this->tables, current($row));
454
                    $elem = array_search(
455
                        current($row),
456
                        $this->dumpSettings['include-tables']
457
                    );
458
                    unset($this->dumpSettings['include-tables'][$elem]);
459
                }
460
            }
461
        }
462
463
        // Listing all views from database
464
        if (empty($this->dumpSettings['include-views'])) {
465
            // include all views for now, blacklisting happens later
466
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
467
                array_push($this->views, current($row));
468
            }
469
        } else {
470
            // include only the tables mentioned in include-tables
471
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
472
                if (in_array(current($row), $this->dumpSettings['include-views'], true)) {
473
                    array_push($this->views, current($row));
474
                    $elem = array_search(
475
                        current($row),
476
                        $this->dumpSettings['include-views']
477
                    );
478
                    unset($this->dumpSettings['include-views'][$elem]);
479
                }
480
            }
481
        }
482
483
        // Listing all triggers from database
484
        if (false === $this->dumpSettings['skip-triggers']) {
485
            foreach ($this->dbHandler->query($this->typeAdapter->show_triggers($this->dbName)) as $row) {
486
                array_push($this->triggers, $row['Trigger']);
487
            }
488
        }
489
490
        // Listing all procedures from database
491
        if ($this->dumpSettings['routines']) {
492
            foreach ($this->dbHandler->query($this->typeAdapter->show_procedures($this->dbName)) as $row) {
493
                array_push($this->procedures, $row['procedure_name']);
494
            }
495
        }
496
497
        // Listing all events from database
498
        if ($this->dumpSettings['events']) {
499
            foreach ($this->dbHandler->query($this->typeAdapter->show_events($this->dbName)) as $row) {
500
                array_push($this->events, $row['event_name']);
501
            }
502
        }
503
    }
504
505
    /**
506
     * Compare if $table name matches with a definition inside $arr
507
     * @param $table string
508
     * @param $arr array with strings or patterns
509
     * @return bool
510
     */
511
    private function matches($table, $arr) {
512
        $match = false;
513
514
        foreach ($arr as $pattern) {
515
            if ( '/' != $pattern[0] ) {
516
                continue;
517
            }
518
            if ( 1 == preg_match($pattern, $table) ) {
519
                $match = true;
520
            }
521
        }
522
523
        return in_array($table, $arr) || $match;
524
    }
525
526
    /**
527
     * Exports all the tables selected from database
528
     *
529
     * @return null
530
     */
531
    private function exportTables()
532
    {
533
        // Exporting tables one by one
534
        foreach ($this->tables as $table) {
535
            if ( $this->matches($table, $this->dumpSettings['exclude-tables']) ) {
536
                continue;
537
            }
538
            $this->getTableStructure($table);
539
            if ( false === $this->dumpSettings['no-data'] ) { // don't break compatibility with old trigger
540
                $this->listValues($table);
541
            } else if ( true === $this->dumpSettings['no-data']
542
                 || $this->matches($table, $this->dumpSettings['no-data']) ) {
543
                continue;
544
            } else {
545
                $this->listValues($table);
546
            }
547
        }
548
    }
549
550
    /**
551
     * Exports all the views found in database
552
     *
553
     * @return null
554
     */
555
    private function exportViews()
556
    {
557
        if (false === $this->dumpSettings['no-create-info']) {
558
            // Exporting views one by one
559
            foreach ($this->views as $view) {
560
                if ( $this->matches($view, $this->dumpSettings['exclude-tables']) ) {
561
                    continue;
562
                }
563
                $this->tableColumnTypes[$view] = $this->getTableColumnTypes($view);
564
                $this->getViewStructureTable($view);
565
            }
566
            foreach ($this->views as $view) {
567
                if ( $this->matches($view, $this->dumpSettings['exclude-tables']) ) {
568
                    continue;
569
                }
570
                $this->getViewStructureView($view);
571
            }
572
        }
573
    }
574
575
    /**
576
     * Exports all the triggers found in database
577
     *
578
     * @return null
579
     */
580
    private function exportTriggers()
581
    {
582
        // Exporting triggers one by one
583
        foreach ($this->triggers as $trigger) {
584
            $this->getTriggerStructure($trigger);
585
        }
586
    }
587
588
    /**
589
     * Exports all the procedures found in database
590
     *
591
     * @return null
592
     */
593
    private function exportProcedures()
594
    {
595
        // Exporting triggers one by one
596
        foreach ($this->procedures as $procedure) {
597
            $this->getProcedureStructure($procedure);
598
        }
599
    }
600
601
    /**
602
     * Exports all the events found in database
603
     *
604
     * @return null
605
     */
606
    private function exportEvents()
607
    {
608
        // Exporting triggers one by one
609
        foreach ($this->events as $event) {
610
            $this->getEventStructure($event);
611
        }
612
    }
613
614
    /**
615
     * Table structure extractor
616
     *
617
     * @todo move specific mysql code to typeAdapter
618
     * @param string $tableName  Name of table to export
619
     * @return null
620
     */
621
    private function getTableStructure($tableName)
622
    {
623
        if (!$this->dumpSettings['no-create-info']) {
624
            $ret = '';
625
            if (!$this->dumpSettings['skip-comments']) {
626
                $ret = "--" . PHP_EOL .
627
                    "-- Table structure for table `$tableName`" . PHP_EOL .
628
                    "--" . PHP_EOL . PHP_EOL;
629
            }
630
            $stmt = $this->typeAdapter->show_create_table($tableName);
631
            foreach ($this->dbHandler->query($stmt) as $r) {
632
                $this->compressManager->write($ret);
633
                if ($this->dumpSettings['add-drop-table']) {
634
                    $this->compressManager->write(
635
                        $this->typeAdapter->drop_table($tableName)
636
                    );
637
                }
638
                $this->compressManager->write(
639
                    $this->typeAdapter->create_table($r)
640
                );
641
                break;
642
            }
643
        }
644
        $this->tableColumnTypes[$tableName] = $this->getTableColumnTypes($tableName);
645
        return;
646
    }
647
648
    /**
649
     * Store column types to create data dumps and for Stand-In tables
650
     *
651
     * @param string $tableName  Name of table to export
652
     * @return array type column types detailed
653
     */
654
655
    private function getTableColumnTypes($tableName) {
656
        $columnTypes = array();
657
        $columns = $this->dbHandler->query(
658
            $this->typeAdapter->show_columns($tableName)
659
        );
660
        $columns->setFetchMode(PDO::FETCH_ASSOC);
661
662
        foreach($columns as $key => $col) {
663
            $types = $this->typeAdapter->parseColumnType($col);
664
            $columnTypes[$col['Field']] = array(
665
                'is_numeric'=> $types['is_numeric'],
666
                'is_blob' => $types['is_blob'],
667
                'type' => $types['type'],
668
                'type_sql' => $col['Type'],
669
                'is_virtual' => $types['is_virtual']
670
            );
671
        }
672
673
        return $columnTypes;
674
    }
675
676
    /**
677
     * View structure extractor, create table (avoids cyclic references)
678
     *
679
     * @todo move mysql specific code to typeAdapter
680
     * @param string $viewName  Name of view to export
681
     * @return null
682
     */
683
    private function getViewStructureTable($viewName)
684
    {
685
        if (!$this->dumpSettings['skip-comments']) {
686
            $ret = "--" . PHP_EOL .
687
                "-- Stand-In structure for view `${viewName}`" . PHP_EOL .
688
                "--" . PHP_EOL . PHP_EOL;
689
            $this->compressManager->write($ret);
690
        }
691
        $stmt = $this->typeAdapter->show_create_view($viewName);
692
693
        // create views as tables, to resolve dependencies
694
        foreach ($this->dbHandler->query($stmt) as $r) {
695
            if ($this->dumpSettings['add-drop-table']) {
696
                $this->compressManager->write(
697
                    $this->typeAdapter->drop_view($viewName)
698
                );
699
            }
700
701
            $this->compressManager->write(
702
                $this->createStandInTable($viewName)
703
            );
704
            break;
705
        }
706
    }
707
708
    /**
709
     * Write a create table statement for the table Stand-In, show create
710
     * table would return a create algorithm when used on a view
711
     *
712
     * @param string $viewName  Name of view to export
713
     * @return string create statement
714
     */
715
    function createStandInTable($viewName) {
716
        $ret = array();
717
        foreach($this->tableColumnTypes[$viewName] as $k => $v) {
718
            $ret[] = "`${k}` ${v['type_sql']}";
719
        }
720
        $ret = implode(PHP_EOL . ",", $ret);
721
722
        $ret = "CREATE TABLE IF NOT EXISTS `$viewName` (" .
723
            PHP_EOL . $ret . PHP_EOL . ");" . PHP_EOL;
724
725
        return $ret;
726
    }
727
728
    /**
729
     * View structure extractor, create view
730
     *
731
     * @todo move mysql specific code to typeAdapter
732
     * @param string $viewName  Name of view to export
733
     * @return null
734
     */
735
    private function getViewStructureView($viewName)
736
    {
737
        if (!$this->dumpSettings['skip-comments']) {
738
            $ret = "--" . PHP_EOL .
739
                "-- View structure for view `${viewName}`" . PHP_EOL .
740
                "--" . PHP_EOL . PHP_EOL;
741
            $this->compressManager->write($ret);
742
        }
743
        $stmt = $this->typeAdapter->show_create_view($viewName);
744
745
        // create views, to resolve dependencies
746
        // replacing tables with views
747
        foreach ($this->dbHandler->query($stmt) as $r) {
748
            // because we must replace table with view, we should delete it
749
            $this->compressManager->write(
750
                $this->typeAdapter->drop_view($viewName)
751
            );
752
            $this->compressManager->write(
753
                $this->typeAdapter->create_view($r)
754
            );
755
            break;
756
        }
757
    }
758
759
    /**
760
     * Trigger structure extractor
761
     *
762
     * @param string $triggerName  Name of trigger to export
763
     * @return null
764
     */
765
    private function getTriggerStructure($triggerName)
766
    {
767
        $stmt = $this->typeAdapter->show_create_trigger($triggerName);
768
        foreach ($this->dbHandler->query($stmt) as $r) {
769
            if ($this->dumpSettings['add-drop-trigger']) {
770
                $this->compressManager->write(
771
                    $this->typeAdapter->add_drop_trigger($triggerName)
772
                );
773
            }
774
            $this->compressManager->write(
775
                $this->typeAdapter->create_trigger($r)
776
            );
777
            return;
778
        }
779
    }
780
781
    /**
782
     * Procedure structure extractor
783
     *
784
     * @param string $procedureName  Name of procedure to export
785
     * @return null
786
     */
787
    private function getProcedureStructure($procedureName)
788
    {
789
        if (!$this->dumpSettings['skip-comments']) {
790
            $ret = "--" . PHP_EOL .
791
                "-- Dumping routines for database '" . $this->dbName . "'" . PHP_EOL .
792
                "--" . PHP_EOL . PHP_EOL;
793
            $this->compressManager->write($ret);
794
        }
795
        $stmt = $this->typeAdapter->show_create_procedure($procedureName);
796
        foreach ($this->dbHandler->query($stmt) as $r) {
797
            $this->compressManager->write(
798
                $this->typeAdapter->create_procedure($r)
799
            );
800
            return;
801
        }
802
    }
803
804
    /**
805
     * Event structure extractor
806
     *
807
     * @param string $eventName  Name of event to export
808
     * @return null
809
     */
810
    private function getEventStructure($eventName)
811
    {
812
        if (!$this->dumpSettings['skip-comments']) {
813
            $ret = "--" . PHP_EOL .
814
                "-- Dumping events for database '" . $this->dbName . "'" . PHP_EOL .
815
                "--" . PHP_EOL . PHP_EOL;
816
            $this->compressManager->write($ret);
817
        }
818
        $stmt = $this->typeAdapter->show_create_event($eventName);
819
        foreach ($this->dbHandler->query($stmt) as $r) {
820
            $this->compressManager->write(
821
                $this->typeAdapter->create_event($r)
822
            );
823
            return;
824
        }
825
    }
826
827
    /**
828
     * Prepare values for output
829
     *
830
     * @param string $tableName Name of table which contains rows
831
     * @param array $row Associative array of column names and values to be
832
     *   quoted
833
     *
834
     * @return array
835
     */
836
    private function prepareColumnValues($tableName, $row)
837
    {
838
        $ret = [];
839
        $columnTypes = $this->tableColumnTypes[$tableName];
840
        foreach ($row as $colName => $colValue) {
841
            $colValue = $this->hookTransformColumnValue($tableName, $colName, $colValue);
842
            $ret[] = $this->escape($colValue, $columnTypes[$colName]);
843
        }
844
845
        return $ret;
846
    }
847
848
    /**
849
     * Escape values with quotes when needed
850
     *
851
     * @param string $tableName Name of table which contains rows
852
     * @param array $row Associative array of column names and values to be quoted
853
     *
854
     * @return string
855
     */
856
    private function escape($colValue, $colType)
857
    {
858
        if (is_null($colValue)) {
859
            return "NULL";
860
        } elseif ($this->dumpSettings['hex-blob'] && $colType['is_blob']) {
861
            if ($colType['type'] == 'bit' || !empty($colValue)) {
862
                return "0x${colValue}";
863
            } else {
864
                return "''";
865
            }
866
        } elseif ($colType['is_numeric']) {
867
            return $colValue;
868
        }
869
870
        return $this->dbHandler->quote($colValue);
871
    }
872
873
    /**
874
     * Give extending classes an opportunity to transform column values
875
     * 
876
     * @param string $tableName Name of table which contains rows
877
     * @param string $colName Name of the column in question
878
     * @param string $colValue Value of the column in question
879
     * 
880
     * @return string
881
     *
882
     */
883
    protected function hookTransformColumnValue($tableName, $colName, $colValue)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

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

883
    protected function hookTransformColumnValue(/** @scrutinizer ignore-unused */ $tableName, $colName, $colValue)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $colName is not used and could be removed. ( Ignorable by Annotation )

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

883
    protected function hookTransformColumnValue($tableName, /** @scrutinizer ignore-unused */ $colName, $colValue)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
884
    {
885
      return $colValue;
886
    }
887
888
    /**
889
     * Table rows extractor
890
     *
891
     * @param string $tableName  Name of table to export
892
     *
893
     * @return null
894
     */
895
    private function listValues($tableName)
896
    {
897
        $this->prepareListValues($tableName);
898
899
        $onlyOnce = true;
900
        $lineSize = 0;
901
902
        $colStmt = $this->getColumnStmt($tableName);
903
        $stmt = "SELECT " . implode(",", $colStmt) . " FROM `$tableName`";
0 ignored issues
show
Bug introduced by
$colStmt of type string is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

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

903
        $stmt = "SELECT " . implode(",", /** @scrutinizer ignore-type */ $colStmt) . " FROM `$tableName`";
Loading history...
904
905
        if ($this->dumpSettings['where']) {
906
            $stmt .= " WHERE {$this->dumpSettings['where']}";
907
        }
908
        $resultSet = $this->dbHandler->query($stmt);
909
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
910
911
        foreach ($resultSet as $row) {
912
            $vals = $this->prepareColumnValues($tableName, $row);
913
            if ($onlyOnce || !$this->dumpSettings['extended-insert']) {
914
915
                if ($this->dumpSettings['complete-insert']) {
916
                    $lineSize += $this->compressManager->write(
917
                        "INSERT INTO `$tableName` (" .
918
                        implode(", ", $colStmt) .
919
                        ") VALUES (" . implode(",", $vals) . ")"
920
                    );
921
                } else {
922
                    $lineSize += $this->compressManager->write(
923
                        "INSERT INTO `$tableName` VALUES (" . implode(",", $vals) . ")"
924
                    );
925
                }
926
                $onlyOnce = false;
927
            } else {
928
                $lineSize += $this->compressManager->write(",(" . implode(",", $vals) . ")");
929
            }
930
            if (($lineSize > $this->dumpSettings['net_buffer_length']) ||
931
                    !$this->dumpSettings['extended-insert']) {
932
                $onlyOnce = true;
933
                $lineSize = $this->compressManager->write(";" . PHP_EOL);
934
            }
935
        }
936
        $resultSet->closeCursor();
937
938
        if (!$onlyOnce) {
939
            $this->compressManager->write(";" . PHP_EOL);
940
        }
941
942
        $this->endListValues($tableName);
943
    }
944
945
    /**
946
     * Table rows extractor, append information prior to dump
947
     *
948
     * @param string $tableName  Name of table to export
949
     *
950
     * @return null
951
     */
952
    function prepareListValues($tableName)
953
    {
954
        if (!$this->dumpSettings['skip-comments']) {
955
            $this->compressManager->write(
956
                "--" . PHP_EOL .
957
                "-- Dumping data for table `$tableName`" .  PHP_EOL .
958
                "--" . PHP_EOL . PHP_EOL
959
            );
960
        }
961
962
        if ($this->dumpSettings['single-transaction']) {
963
            $this->dbHandler->exec($this->typeAdapter->setup_transaction());
964
            $this->dbHandler->exec($this->typeAdapter->start_transaction());
965
        }
966
967
        if ($this->dumpSettings['lock-tables']) {
968
            $this->typeAdapter->lock_table($tableName);
969
        }
970
971
        if ($this->dumpSettings['add-locks']) {
972
            $this->compressManager->write(
973
                $this->typeAdapter->start_add_lock_table($tableName)
974
            );
975
        }
976
977
        if ($this->dumpSettings['disable-keys']) {
978
            $this->compressManager->write(
979
                $this->typeAdapter->start_add_disable_keys($tableName)
980
            );
981
        }
982
983
        // Disable autocommit for faster reload
984
        if ($this->dumpSettings['no-autocommit']) {
985
            $this->compressManager->write(
986
                $this->typeAdapter->start_disable_autocommit()
987
            );
988
        }
989
990
        return;
991
    }
992
993
    /**
994
     * Table rows extractor, close locks and commits after dump
995
     *
996
     * @param string $tableName  Name of table to export
997
     *
998
     * @return null
999
     */
1000
    function endListValues($tableName)
1001
    {
1002
        if ($this->dumpSettings['disable-keys']) {
1003
            $this->compressManager->write(
1004
                $this->typeAdapter->end_add_disable_keys($tableName)
1005
            );
1006
        }
1007
1008
        if ($this->dumpSettings['add-locks']) {
1009
            $this->compressManager->write(
1010
                $this->typeAdapter->end_add_lock_table($tableName)
1011
            );
1012
        }
1013
1014
        if ($this->dumpSettings['single-transaction']) {
1015
            $this->dbHandler->exec($this->typeAdapter->commit_transaction());
1016
        }
1017
1018
        if ($this->dumpSettings['lock-tables']) {
1019
            $this->typeAdapter->unlock_table($tableName);
1020
        }
1021
1022
        // Commit to enable autocommit
1023
        if ($this->dumpSettings['no-autocommit']) {
1024
            $this->compressManager->write(
1025
                $this->typeAdapter->end_disable_autocommit()
1026
            );
1027
        }
1028
1029
        $this->compressManager->write(PHP_EOL);
1030
1031
        return;
1032
    }
1033
1034
    /**
1035
     * Build SQL List of all columns on current table
1036
     *
1037
     * @param string $tableName  Name of table to get columns
1038
     *
1039
     * @return string SQL sentence with columns
1040
     */
1041
    function getColumnStmt($tableName)
1042
    {
1043
        $colStmt = array();
1044
        foreach($this->tableColumnTypes[$tableName] as $colName => $colType) {
1045
            if ($colType['type'] == 'bit' && $this->dumpSettings['hex-blob']) {
1046
                $colStmt[] = "LPAD(HEX(`${colName}`),2,'0') AS `${colName}`";
1047
            } else if ($colType['is_blob'] && $this->dumpSettings['hex-blob']) {
1048
                $colStmt[] = "HEX(`${colName}`) AS `${colName}`";
1049
            } else if ($colType['is_virtual']) {
1050
                $this->dumpSettings['complete-insert'] = true;
1051
                continue;
1052
            } else {
1053
                $colStmt[] = "`${colName}`";
1054
            }
1055
        }
1056
1057
        return $colStmt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $colStmt returns the type array|string[] which is incompatible with the documented return type string.
Loading history...
1058
    }
1059
}
1060
1061
/**
1062
 * Enum with all available compression methods
1063
 *
1064
 */
1065
abstract class CompressMethod
1066
{
1067
    public static $enums = array(
1068
        "None",
1069
        "Gzip",
1070
        "Bzip2"
1071
    );
1072
1073
    /**
1074
     * @param string $c
1075
     * @return boolean
1076
     */
1077
    public static function isValid($c)
1078
    {
1079
        return in_array($c, self::$enums);
1080
    }
1081
}
1082
1083
abstract class CompressManagerFactory
1084
{
1085
    /**
1086
     * @param string $c
1087
     * @return CompressBzip2|CompressGzip|CompressNone
1088
     */
1089
    public static function create($c)
1090
    {
1091
        $c = ucfirst(strtolower($c));
1092
        if (! CompressMethod::isValid($c)) {
1093
            throw new Exception("Compression method ($c) is not defined yet");
1094
        }
1095
1096
        $method =  __NAMESPACE__ . "\\" . "Compress" . $c;
1097
1098
        return new $method;
1099
    }
1100
}
1101
1102
class CompressBzip2 extends CompressManagerFactory
1103
{
1104
    private $fileHandler = null;
1105
1106
    public function __construct()
1107
    {
1108
        if (! function_exists("bzopen")) {
1109
            throw new Exception("Compression is enabled, but bzip2 lib is not installed or configured properly");
1110
        }
1111
    }
1112
1113
    /**
1114
     * @param string $filename
1115
     */
1116
    public function open($filename)
1117
    {
1118
        $this->fileHandler = bzopen($filename, "w");
1119
        if (false === $this->fileHandler) {
1120
            throw new Exception("Output file is not writable");
1121
        }
1122
1123
        return true;
1124
    }
1125
1126
    public function write($str)
1127
    {
1128
        if (false === ($bytesWritten = bzwrite($this->fileHandler, $str))) {
1129
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1130
        }
1131
        return $bytesWritten;
1132
    }
1133
1134
    public function close()
1135
    {
1136
        return bzclose($this->fileHandler);
1137
    }
1138
}
1139
1140
class CompressGzip extends CompressManagerFactory
1141
{
1142
    private $fileHandler = null;
1143
1144
    public function __construct()
1145
    {
1146
        if (! function_exists("gzopen")) {
1147
            throw new Exception("Compression is enabled, but gzip lib is not installed or configured properly");
1148
        }
1149
    }
1150
1151
    /**
1152
     * @param string $filename
1153
     */
1154
    public function open($filename)
1155
    {
1156
        $this->fileHandler = gzopen($filename, "wb");
1157
        if (false === $this->fileHandler) {
1158
            throw new Exception("Output file is not writable");
1159
        }
1160
1161
        return true;
1162
    }
1163
1164
    public function write($str)
1165
    {
1166
        if (false === ($bytesWritten = gzwrite($this->fileHandler, $str))) {
1167
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1168
        }
1169
        return $bytesWritten;
1170
    }
1171
1172
    public function close()
1173
    {
1174
        return gzclose($this->fileHandler);
1175
    }
1176
}
1177
1178
class CompressNone extends CompressManagerFactory
1179
{
1180
    private $fileHandler = null;
1181
1182
    /**
1183
     * @param string $filename
1184
     */
1185
    public function open($filename)
1186
    {
1187
        $this->fileHandler = fopen($filename, "wb");
1188
        if (false === $this->fileHandler) {
1189
            throw new Exception("Output file is not writable");
1190
        }
1191
1192
        return true;
1193
    }
1194
1195
    public function write($str)
1196
    {
1197
        if (false === ($bytesWritten = fwrite($this->fileHandler, $str))) {
1198
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1199
        }
1200
        return $bytesWritten;
1201
    }
1202
1203
    public function close()
1204
    {
1205
        return fclose($this->fileHandler);
1206
    }
1207
}
1208
1209
/**
1210
 * Enum with all available TypeAdapter implementations
1211
 *
1212
 */
1213
abstract class TypeAdapter
1214
{
1215
    public static $enums = array(
1216
        "Sqlite",
1217
        "Mysql"
1218
    );
1219
1220
    /**
1221
     * @param string $c
1222
     * @return boolean
1223
     */
1224
    public static function isValid($c)
1225
    {
1226
        return in_array($c, self::$enums);
1227
    }
1228
}
1229
1230
/**
1231
 * TypeAdapter Factory
1232
 *
1233
 */
1234
abstract class TypeAdapterFactory
1235
{
1236
    protected $dbHandler = null;
1237
    protected $dumpSettings = array();
1238
1239
    /**
1240
     * @param string $c Type of database factory to create (Mysql, Sqlite,...)
1241
     * @param PDO $dbHandler
1242
     */
1243
    public static function create($c, $dbHandler = null, $dumpSettings = array())
1244
    {
1245
        $c = ucfirst(strtolower($c));
1246
        if (! TypeAdapter::isValid($c)) {
1247
            throw new Exception("Database type support for ($c) not yet available");
1248
        }
1249
        $method =  __NAMESPACE__ . "\\" . "TypeAdapter" . $c;
1250
        return new $method($dbHandler, $dumpSettings);
1251
    }
1252
1253
    public function __construct($dbHandler = null, $dumpSettings = array())
1254
    {
1255
        $this->dbHandler = $dbHandler;
1256
        $this->dumpSettings = $dumpSettings;
1257
    }
1258
1259
    /**
1260
     * function databases Add sql to create and use database
1261
     * @todo make it do something with sqlite
1262
     */
1263
    public function databases()
1264
    {
1265
        return "";
1266
    }
1267
1268
    public function show_create_table($tableName)
1269
    {
1270
        return "SELECT tbl_name as 'Table', sql as 'Create Table' " .
1271
            "FROM sqlite_master " .
1272
            "WHERE type='table' AND tbl_name='$tableName'";
1273
    }
1274
1275
    /**
1276
     * function create_table Get table creation code from database
1277
     * @todo make it do something with sqlite
1278
     */
1279
    public function create_table($row)
1280
    {
1281
        return "";
1282
    }
1283
1284
    public function show_create_view($viewName)
1285
    {
1286
        return "SELECT tbl_name as 'View', sql as 'Create View' " .
1287
            "FROM sqlite_master " .
1288
            "WHERE type='view' AND tbl_name='$viewName'";
1289
    }
1290
1291
    /**
1292
     * function create_view Get view creation code from database
1293
     * @todo make it do something with sqlite
1294
     */
1295
    public function create_view($row)
1296
    {
1297
        return "";
1298
    }
1299
1300
    /**
1301
     * function show_create_trigger Get trigger creation code from database
1302
     * @todo make it do something with sqlite
1303
     */
1304
    public function show_create_trigger($triggerName)
1305
    {
1306
        return "";
1307
    }
1308
1309
    /**
1310
     * function create_trigger Modify trigger code, add delimiters, etc
1311
     * @todo make it do something with sqlite
1312
     */
1313
    public function create_trigger($triggerName)
1314
    {
1315
        return "";
1316
    }
1317
1318
    /**
1319
     * function create_procedure Modify procedure code, add delimiters, etc
1320
     * @todo make it do something with sqlite
1321
     */
1322
    public function create_procedure($procedureName)
1323
    {
1324
        return "";
1325
    }
1326
1327
    public function show_tables()
1328
    {
1329
        return "SELECT tbl_name FROM sqlite_master WHERE type='table'";
1330
    }
1331
1332
    public function show_views()
1333
    {
1334
        return "SELECT tbl_name FROM sqlite_master WHERE type='view'";
1335
    }
1336
1337
    public function show_triggers()
1338
    {
1339
        return "SELECT name FROM sqlite_master WHERE type='trigger'";
1340
    }
1341
1342
    public function show_columns()
1343
    {
1344
        if (func_num_args() != 1) {
1345
            return "";
1346
        }
1347
1348
        $args = func_get_args();
1349
1350
        return "pragma table_info(${args[0]})";
1351
    }
1352
1353
    public function show_procedures()
1354
    {
1355
        return "";
1356
    }
1357
1358
    public function show_events()
1359
    {
1360
        return "";
1361
    }
1362
1363
    public function setup_transaction()
1364
    {
1365
        return "";
1366
    }
1367
1368
    public function start_transaction()
1369
    {
1370
        return "BEGIN EXCLUSIVE";
1371
    }
1372
1373
    public function commit_transaction()
1374
    {
1375
        return "COMMIT";
1376
    }
1377
1378
    public function lock_table()
1379
    {
1380
        return "";
1381
    }
1382
1383
    public function unlock_table()
1384
    {
1385
        return "";
1386
    }
1387
1388
    public function start_add_lock_table()
1389
    {
1390
        return PHP_EOL;
1391
    }
1392
1393
    public function end_add_lock_table()
1394
    {
1395
        return PHP_EOL;
1396
    }
1397
1398
    public function start_add_disable_keys()
1399
    {
1400
        return PHP_EOL;
1401
    }
1402
1403
    public function end_add_disable_keys()
1404
    {
1405
        return PHP_EOL;
1406
    }
1407
1408
    public function start_disable_foreign_keys_check()
1409
    {
1410
        return PHP_EOL;
1411
    }
1412
1413
    public function end_disable_foreign_keys_check()
1414
    {
1415
        return PHP_EOL;
1416
    }
1417
1418
    public function add_drop_database()
1419
    {
1420
        return PHP_EOL;
1421
    }
1422
1423
    public function add_drop_trigger()
1424
    {
1425
        return PHP_EOL;
1426
    }
1427
1428
    public function drop_table()
1429
    {
1430
        return PHP_EOL;
1431
    }
1432
1433
    public function drop_view()
1434
    {
1435
        return PHP_EOL;
1436
    }
1437
1438
    /**
1439
     * Decode column metadata and fill info structure.
1440
     * type, is_numeric and is_blob will always be available.
1441
     *
1442
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1443
     * @return array
1444
     */
1445
    public function parseColumnType($colType)
1446
    {
1447
        return array();
1448
    }
1449
1450
    public function backup_parameters()
1451
    {
1452
        return PHP_EOL;
1453
    }
1454
1455
    public function restore_parameters()
1456
    {
1457
        return PHP_EOL;
1458
    }
1459
}
1460
1461
class TypeAdapterPgsql extends TypeAdapterFactory
1462
{
1463
}
1464
1465
class TypeAdapterDblib extends TypeAdapterFactory
1466
{
1467
}
1468
1469
class TypeAdapterSqlite extends TypeAdapterFactory
1470
{
1471
}
1472
1473
class TypeAdapterMysql extends TypeAdapterFactory
1474
{
1475
    const DEFINER_RE = 'DEFINER=`(?:[^`]|``)*`@`(?:[^`]|``)*`';
1476
1477
1478
    // Numerical Mysql types
1479
    public $mysqlTypes = array(
1480
        'numerical' => array(
1481
            'bit',
1482
            'tinyint',
1483
            'smallint',
1484
            'mediumint',
1485
            'int',
1486
            'integer',
1487
            'bigint',
1488
            'real',
1489
            'double',
1490
            'float',
1491
            'decimal',
1492
            'numeric'
1493
        ),
1494
        'blob' => array(
1495
            'tinyblob',
1496
            'blob',
1497
            'mediumblob',
1498
            'longblob',
1499
            'binary',
1500
            'varbinary',
1501
            'bit',
1502
            'geometry', /* http://bugs.mysql.com/bug.php?id=43544 */
1503
            'point',
1504
            'linestring',
1505
            'polygon',
1506
            'multipoint',
1507
            'multilinestring',
1508
            'multipolygon',
1509
            'geometrycollection',
1510
        )
1511
    );
1512
1513
    public function databases()
1514
    {
1515
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1516
        $args = func_get_args();
1517
        $databaseName = $args[0];
1518
1519
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'character_set_database';");
1520
        $characterSet = $resultSet->fetchColumn(1);
1521
        $resultSet->closeCursor();
1522
1523
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'collation_database';");
1524
        $collationDb = $resultSet->fetchColumn(1);
1525
        $resultSet->closeCursor();
1526
        $ret = "";
1527
1528
        $ret .= "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `${databaseName}`".
1529
            " /*!40100 DEFAULT CHARACTER SET ${characterSet} " .
1530
            " COLLATE ${collationDb} */;" . PHP_EOL . PHP_EOL .
1531
            "USE `${databaseName}`;" . PHP_EOL . PHP_EOL;
1532
1533
        return $ret;
1534
    }
1535
1536
    public function show_create_table($tableName)
1537
    {
1538
        return "SHOW CREATE TABLE `$tableName`";
1539
    }
1540
1541
    public function show_create_view($viewName)
1542
    {
1543
        return "SHOW CREATE VIEW `$viewName`";
1544
    }
1545
1546
    public function show_create_trigger($triggerName)
1547
    {
1548
        return "SHOW CREATE TRIGGER `$triggerName`";
1549
    }
1550
1551
    public function show_create_procedure($procedureName)
1552
    {
1553
        return "SHOW CREATE PROCEDURE `$procedureName`";
1554
    }
1555
1556
    public function show_create_event($eventName)
1557
    {
1558
        return "SHOW CREATE EVENT `$eventName`";
1559
    }
1560
1561
    public function create_table( $row)
1562
    {
1563
        if ( !isset($row['Create Table']) ) {
1564
            throw new Exception("Error getting table code, unknown output");
1565
        }
1566
1567
        $createTable = $row['Create Table'];
1568
        if ( $this->dumpSettings['reset-auto-increment'] ) {
1569
            $match = "/AUTO_INCREMENT=[0-9]+/s";
1570
            $replace = "";
1571
            $createTable = preg_replace($match, $replace, $createTable);
1572
        }
1573
1574
        $ret = "/*!40101 SET @saved_cs_client     = @@character_set_client */;" . PHP_EOL .
1575
            "/*!40101 SET character_set_client = " . $this->dumpSettings['default-character-set'] . " */;" . PHP_EOL .
1576
            $createTable . ";" . PHP_EOL .
1577
            "/*!40101 SET character_set_client = @saved_cs_client */;" . PHP_EOL .
1578
            PHP_EOL;
1579
        return $ret;
1580
    }
1581
1582
    public function create_view($row)
1583
    {
1584
        $ret = "";
1585
        if (!isset($row['Create View'])) {
1586
                throw new Exception("Error getting view structure, unknown output");
1587
        }
1588
1589
        $viewStmt = $row['Create View'];
1590
1591
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50013 \2 */'.PHP_EOL;
1592
1593
        if ($viewStmtReplaced = preg_replace(
1594
            '/^(CREATE(?:\s+ALGORITHM=(?:UNDEFINED|MERGE|TEMPTABLE))?)\s+('
1595
            .self::DEFINER_RE.'(?:\s+SQL SECURITY DEFINER|INVOKER)?)?\s+(VIEW .+)$/',
1596
            '/*!50001 \1 */'.PHP_EOL.$definerStr.'/*!50001 \3 */',
1597
            $viewStmt,
1598
            1
1599
        )) {
1600
            $viewStmt = $viewStmtReplaced;
1601
        };
1602
1603
        $ret .= $viewStmt . ';' . PHP_EOL . PHP_EOL;
1604
        return $ret;
1605
    }
1606
1607
    public function create_trigger($row)
1608
    {
1609
        $ret = "";
1610
        if (!isset($row['SQL Original Statement'])) {
1611
            throw new Exception("Error getting trigger code, unknown output");
1612
        }
1613
1614
        $triggerStmt = $row['SQL Original Statement'];
1615
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50017 \2*/ ';
1616
        if ($triggerStmtReplaced = preg_replace(
1617
            '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(TRIGGER\s.*)$/s',
1618
            '/*!50003 \1*/ '.$definerStr.'/*!50003 \3 */',
1619
            $triggerStmt,
1620
            1
1621
        )) {
1622
            $triggerStmt = $triggerStmtReplaced;
1623
        }
1624
1625
        $ret .= "DELIMITER ;;" . PHP_EOL .
1626
            $triggerStmt . ";;" . PHP_EOL .
1627
            "DELIMITER ;" . PHP_EOL . PHP_EOL;
1628
        return $ret;
1629
    }
1630
1631
    public function create_procedure($row)
1632
    {
1633
        $ret = "";
1634
        if (!isset($row['Create Procedure'])) {
1635
            throw new Exception("Error getting procedure code, unknown output. " .
1636
                "Please check 'https://bugs.mysql.com/bug.php?id=14564'");
1637
        }
1638
        $procedureStmt = $row['Create Procedure'];
1639
1640
        $ret .= "/*!50003 DROP PROCEDURE IF EXISTS `" .
1641
            $row['Procedure'] . "` */;" . PHP_EOL .
1642
            "/*!40101 SET @saved_cs_client     = @@character_set_client */;" . PHP_EOL .
1643
            "/*!40101 SET character_set_client = " . $this->dumpSettings['default-character-set'] . " */;" . PHP_EOL .
1644
            "DELIMITER ;;" . PHP_EOL .
1645
            $procedureStmt . " ;;" . PHP_EOL .
1646
            "DELIMITER ;" . PHP_EOL .
1647
            "/*!40101 SET character_set_client = @saved_cs_client */;" . PHP_EOL . PHP_EOL;
1648
1649
        return $ret;
1650
    }
1651
1652
    public function create_event($row)
1653
    {
1654
        $ret = "";
1655
        if ( !isset($row['Create Event']) ) {
1656
            throw new Exception("Error getting event code, unknown output. " .
1657
                "Please check 'http://stackoverflow.com/questions/10853826/mysql-5-5-create-event-gives-syntax-error'");
1658
        }
1659
        $eventName = $row['Event'];
1660
        $eventStmt = $row['Create Event'];
1661
        $sqlMode = $row['sql_mode'];
1662
        $definerStr = $this->dumpSettings['skip-definer'] ? '' : '/*!50117 \2*/ ';
1663
1664
        if ($eventStmtReplaced = preg_replace(
1665
            '/^(CREATE)\s+('.self::DEFINER_RE.')?\s+(EVENT .*)$/',
1666
            '/*!50106 \1*/ '.$definerStr.'/*!50106 \3 */',
1667
            $eventStmt,
1668
            1
1669
        )) {
1670
            $eventStmt = $eventStmtReplaced;
1671
        }
1672
1673
        $ret .= "/*!50106 SET @save_time_zone= @@TIME_ZONE */ ;" . PHP_EOL .
1674
            "/*!50106 DROP EVENT IF EXISTS `" . $eventName . "` */;" . PHP_EOL .
1675
            "DELIMITER ;;" . PHP_EOL .
1676
            "/*!50003 SET @saved_cs_client      = @@character_set_client */ ;;" . PHP_EOL .
1677
            "/*!50003 SET @saved_cs_results     = @@character_set_results */ ;;" . PHP_EOL .
1678
            "/*!50003 SET @saved_col_connection = @@collation_connection */ ;;" . PHP_EOL .
1679
            "/*!50003 SET character_set_client  = utf8 */ ;;" . PHP_EOL .
1680
            "/*!50003 SET character_set_results = utf8 */ ;;" . PHP_EOL .
1681
            "/*!50003 SET collation_connection  = utf8_general_ci */ ;;" . PHP_EOL .
1682
            "/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;;" . PHP_EOL .
1683
            "/*!50003 SET sql_mode              = '" . $sqlMode . "' */ ;;" . PHP_EOL .
1684
            "/*!50003 SET @saved_time_zone      = @@time_zone */ ;;" . PHP_EOL .
1685
            "/*!50003 SET time_zone             = 'SYSTEM' */ ;;" . PHP_EOL .
1686
            $eventStmt . " ;;" . PHP_EOL .
1687
            "/*!50003 SET time_zone             = @saved_time_zone */ ;;" . PHP_EOL .
1688
            "/*!50003 SET sql_mode              = @saved_sql_mode */ ;;" . PHP_EOL .
1689
            "/*!50003 SET character_set_client  = @saved_cs_client */ ;;" . PHP_EOL .
1690
            "/*!50003 SET character_set_results = @saved_cs_results */ ;;" . PHP_EOL .
1691
            "/*!50003 SET collation_connection  = @saved_col_connection */ ;;" . PHP_EOL .
1692
            "DELIMITER ;" . PHP_EOL .
1693
            "/*!50106 SET TIME_ZONE= @save_time_zone */ ;" . PHP_EOL . PHP_EOL;
1694
            // Commented because we are doing this in restore_parameters()
1695
            // "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL . PHP_EOL;
1696
1697
        return $ret;
1698
    }
1699
1700
    public function show_tables()
1701
    {
1702
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1703
        $args = func_get_args();
1704
        return "SELECT TABLE_NAME AS tbl_name " .
1705
            "FROM INFORMATION_SCHEMA.TABLES " .
1706
            "WHERE TABLE_TYPE='BASE TABLE' AND TABLE_SCHEMA='${args[0]}'";
1707
    }
1708
1709
    public function show_views()
1710
    {
1711
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1712
        $args = func_get_args();
1713
        return "SELECT TABLE_NAME AS tbl_name " .
1714
            "FROM INFORMATION_SCHEMA.TABLES " .
1715
            "WHERE TABLE_TYPE='VIEW' AND TABLE_SCHEMA='${args[0]}'";
1716
    }
1717
1718
    public function show_triggers()
1719
    {
1720
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1721
        $args = func_get_args();
1722
        return "SHOW TRIGGERS FROM `${args[0]}`;";
1723
    }
1724
1725
    public function show_columns()
1726
    {
1727
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1728
        $args = func_get_args();
1729
        return "SHOW COLUMNS FROM `${args[0]}`;";
1730
    }
1731
1732
    public function show_procedures()
1733
    {
1734
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1735
        $args = func_get_args();
1736
        return "SELECT SPECIFIC_NAME AS procedure_name " .
1737
            "FROM INFORMATION_SCHEMA.ROUTINES " .
1738
            "WHERE ROUTINE_TYPE='PROCEDURE' AND ROUTINE_SCHEMA='${args[0]}'";
1739
    }
1740
1741
    /**
1742
     * Get query string to ask for names of events from current database.
1743
     *
1744
     * @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...
1745
     * @return string
1746
     */
1747
    public function show_events()
1748
    {
1749
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1750
        $args = func_get_args();
1751
        return "SELECT EVENT_NAME AS event_name " .
1752
            "FROM INFORMATION_SCHEMA.EVENTS " .
1753
            "WHERE EVENT_SCHEMA='${args[0]}'";
1754
    }
1755
1756
    public function setup_transaction()
1757
    {
1758
        return "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
1759
    }
1760
1761
    public function start_transaction()
1762
    {
1763
        return "START TRANSACTION";
1764
    }
1765
1766
    public function commit_transaction()
1767
    {
1768
        return "COMMIT";
1769
    }
1770
1771
    public function lock_table()
1772
    {
1773
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1774
        $args = func_get_args();
1775
        return $this->dbHandler->exec("LOCK TABLES `${args[0]}` READ LOCAL");
1776
1777
    }
1778
1779
    public function unlock_table()
1780
    {
1781
        return $this->dbHandler->exec("UNLOCK TABLES");
1782
    }
1783
1784
    public function start_add_lock_table()
1785
    {
1786
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1787
        $args = func_get_args();
1788
        return "LOCK TABLES `${args[0]}` WRITE;" . PHP_EOL;
1789
    }
1790
1791
    public function end_add_lock_table()
1792
    {
1793
        return "UNLOCK TABLES;" . PHP_EOL;
1794
    }
1795
1796
    public function start_add_disable_keys()
1797
    {
1798
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1799
        $args = func_get_args();
1800
        return "/*!40000 ALTER TABLE `${args[0]}` DISABLE KEYS */;" .
1801
            PHP_EOL;
1802
    }
1803
1804
    public function end_add_disable_keys()
1805
    {
1806
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1807
        $args = func_get_args();
1808
        return "/*!40000 ALTER TABLE `${args[0]}` ENABLE KEYS */;" .
1809
            PHP_EOL;
1810
    }
1811
1812
    public function start_disable_autocommit()
1813
    {
1814
        return "SET autocommit=0;" . PHP_EOL;
1815
    }
1816
1817
    public function end_disable_autocommit()
1818
    {
1819
        return "COMMIT;" . PHP_EOL;
1820
    }
1821
1822
    public function add_drop_database()
1823
    {
1824
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1825
        $args = func_get_args();
1826
        return "/*!40000 DROP DATABASE IF EXISTS `${args[0]}`*/;" .
1827
            PHP_EOL . PHP_EOL;
1828
    }
1829
1830
    public function add_drop_trigger()
1831
    {
1832
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1833
        $args = func_get_args();
1834
        return "DROP TRIGGER IF EXISTS `${args[0]}`;" . PHP_EOL;
1835
    }
1836
1837
    public function drop_table()
1838
    {
1839
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1840
        $args = func_get_args();
1841
        return "DROP TABLE IF EXISTS `${args[0]}`;" . PHP_EOL;
1842
    }
1843
1844
    public function drop_view()
1845
    {
1846
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1847
        $args = func_get_args();
1848
        return "DROP TABLE IF EXISTS `${args[0]}`;" . PHP_EOL .
1849
                "/*!50001 DROP VIEW IF EXISTS `${args[0]}`*/;" . PHP_EOL;
1850
    }
1851
1852
    public function getDatabaseHeader()
1853
    {
1854
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1855
        $args = func_get_args();
1856
        return "--" . PHP_EOL .
1857
            "-- Current Database: `${args[0]}`" . PHP_EOL .
1858
            "--" . PHP_EOL . PHP_EOL;
1859
    }
1860
1861
    /**
1862
     * Decode column metadata and fill info structure.
1863
     * type, is_numeric and is_blob will always be available.
1864
     *
1865
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1866
     * @return array
1867
     */
1868
    public function parseColumnType($colType)
1869
    {
1870
        $colInfo = array();
1871
        $colParts = explode(" ", $colType['Type']);
1872
1873
        if($fparen = strpos($colParts[0], "("))
1874
        {
1875
            $colInfo['type'] = substr($colParts[0], 0, $fparen);
1876
            $colInfo['length']  = str_replace(")", "", substr($colParts[0], $fparen+1));
1877
            $colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
1878
        }
1879
        else
1880
        {
1881
            $colInfo['type'] = $colParts[0];
1882
        }
1883
        $colInfo['is_numeric'] = in_array($colInfo['type'], $this->mysqlTypes['numerical']);
1884
        $colInfo['is_blob'] = in_array($colInfo['type'], $this->mysqlTypes['blob']);
1885
        // for virtual 'Extra' -> "STORED GENERATED" OR "VIRTUAL GENERATED"
1886
        // MySQL reference: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
1887
        $colInfo['is_virtual'] = strpos($colType['Extra'], "VIRTUAL GENERATED") !== false || strpos($colType['Extra'], "STORED GENERATED") !== false;
1888
1889
        return $colInfo;
1890
    }
1891
1892
    public function backup_parameters()
1893
    {
1894
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" . PHP_EOL .
1895
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" . PHP_EOL .
1896
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" . PHP_EOL .
1897
            "/*!40101 SET NAMES " . $this->dumpSettings['default-character-set'] . " */;" . PHP_EOL;
1898
1899
        if (false === $this->dumpSettings['skip-tz-utc']) {
1900
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;" . PHP_EOL .
1901
                "/*!40103 SET TIME_ZONE='+00:00' */;" . PHP_EOL;
1902
        }
1903
1904
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;" . PHP_EOL .
1905
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;" . PHP_EOL .
1906
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;" . PHP_EOL .
1907
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;" . PHP_EOL .PHP_EOL;
1908
1909
        return $ret;
1910
    }
1911
1912
    public function restore_parameters()
1913
    {
1914
        $ret = "";
1915
1916
        if (false === $this->dumpSettings['skip-tz-utc']) {
1917
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL;
1918
        }
1919
1920
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;" . PHP_EOL .
1921
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;" . PHP_EOL .
1922
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;" . PHP_EOL .
1923
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;" . PHP_EOL .
1924
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;" . PHP_EOL .
1925
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;" . PHP_EOL .
1926
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;" . PHP_EOL . PHP_EOL;
1927
1928
        return $ret;
1929
    }
1930
1931
    /**
1932
     * Check number of parameters passed to function, useful when inheriting.
1933
     * Raise exception if unexpected.
1934
     *
1935
     * @param integer $num_args
1936
     * @param integer $expected_num_args
1937
     * @param string $method_name
1938
     */
1939
    private function check_parameters($num_args, $expected_num_args, $method_name)
1940
    {
1941
        if ( $num_args != $expected_num_args ) {
1942
            throw new Exception("Unexpected parameter passed to $method_name");
1943
        }
1944
        return;
1945
    }
1946
}
1947