Completed
Pull Request — master (#131)
by
unknown
01:32
created

TypeAdapterMysql::parseColumnType()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 13
nc 6
nop 1
1
<?php
2
/**
3
 * Mysqldump File Doc Comment
4
 *
5
 * PHP version 5
6
 *
7
 * @category Library
8
 * @package  Ifsnop\Mysqldump
9
 * @author   Michael J. Calkins <[email protected]>
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
 * Mysqldump Class Doc Comment
24
 *
25
 * @category Library
26
 * @package  Ifsnop\Mysqldump
27
 * @author   Michael J. Calkins <[email protected]>
28
 * @author   Diego Torres <[email protected]>
29
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
30
 * @link     https://github.com/ifsnop/mysqldump-php
31
 *
32
 */
33
class Mysqldump
34
{
35
36
    // Same as mysqldump
37
    const MAXLINESIZE = 1000000;
38
39
    // Available compression methods as constants
40
    const GZIP = 'Gzip';
41
    const BZIP2 = 'Bzip2';
42
    const NONE = 'None';
43
44
    // Available connection strings
45
    const UTF8 = 'utf8';
46
    const UTF8MB4 = 'utf8mb4';
47
48
    /**
49
    * Database username
50
    * @var string
51
    */
52
    public $user;
53
    /**
54
    * Database password
55
    * @var string
56
    */
57
    public $pass;
58
    /**
59
    * Connection string for PDO
60
    * @var string
61
    */
62
    public $dsn;
63
    /**
64
    * Destination filename, defaults to stdout
65
    * @var string
66
    */
67
    public $fileName = 'php://output';
68
69
    // Internal stuff
70
    private $tables = array();
71
    private $views = array();
72
    private $triggers = array();
73
    private $procedures = array();
74
    private $events = array();
75
    private $dbHandler = null;
76
    private $dbType;
77
    private $compressManager;
78
    private $typeAdapter;
79
    private $dumpSettings = array();
80
    private $pdoSettings = array();
81
    private $version;
82
    private $tableColumnTypes = array();
83
    /**
84
    * database name, parsed from dsn
85
    * @var string
86
    */
87
    private $dbName;
88
    /**
89
    * host name, parsed from dsn
90
    * @var string
91
    */
92
    private $host;
93
    /**
94
    * dsn string parsed as an array
95
    * @var array
96
    */
97
    private $dsnArray = array();
98
99
    /**
100
     * Constructor of Mysqldump. Note that in the case of an SQLite database
101
     * connection, the filename must be in the $db parameter.
102
     *
103
     * @param string $dsn        PDO DSN connection string
104
     * @param string $user       SQL account username
105
     * @param string $pass       SQL account password
106
     * @param array  $dumpSettings SQL database settings
107
     * @param array  $pdoSettings  PDO configured attributes
108
     */
109
    public function __construct(
110
        $dsn = '',
111
        $user = '',
112
        $pass = '',
113
        $dumpSettings = array(),
114
        $pdoSettings = array()
115
    ) {
116
        $dumpSettingsDefault = array(
117
            'include-tables' => array(),
118
            'exclude-tables' => array(),
119
            'compress' => Mysqldump::NONE,
120
            'init_commands' => array(),
121
            'no-data' => array(),
122
            'reset-auto-increment' => false,
123
            'add-drop-database' => false,
124
            'add-drop-table' => false,
125
            'add-drop-trigger' => true,
126
            'add-locks' => true,
127
            'complete-insert' => false,
128
            'databases' => false,
129
            'default-character-set' => Mysqldump::UTF8,
130
            'disable-keys' => true,
131
            'extended-insert' => true,
132
            'events' => false,
133
            'hex-blob' => true, /* faster than escaped content */
134
            'net_buffer_length' => self::MAXLINESIZE,
135
            'no-autocommit' => true,
136
            'no-create-info' => false,
137
            'lock-tables' => true,
138
            'routines' => false,
139
            'single-transaction' => true,
140
            'skip-triggers' => false,
141
            'skip-tz-utc' => false,
142
            'skip-comments' => false,
143
            'skip-dump-date' => false,
144
            'skip-definer' => false,
145
            'where' => '',
146
            /* deprecated */
147
            'disable-foreign-keys-check' => true
148
        );
149
150
        $pdoSettingsDefault = array(
151
            PDO::ATTR_PERSISTENT => true,
152
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
153
            PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false
154
        );
155
156
        $this->user = $user;
157
        $this->pass = $pass;
158
        $this->parseDsn($dsn);
159
        $this->pdoSettings = self::array_replace_recursive($pdoSettingsDefault, $pdoSettings);
160
        $this->dumpSettings = self::array_replace_recursive($dumpSettingsDefault, $dumpSettings);
161
162
        $this->dumpSettings['init_commands'][] = "SET NAMES " . $this->dumpSettings['default-character-set'];
163
164
        if (false === $this->dumpSettings['skip-tz-utc']) {
165
            $this->dumpSettings['init_commands'][] = "SET TIME_ZONE='+00:00'";
166
        }
167
168
        $diff = array_diff(array_keys($this->dumpSettings), array_keys($dumpSettingsDefault));
169
        if (count($diff)>0) {
170
            throw new Exception("Unexpected value in dumpSettings: (" . implode(",", $diff) . ")");
171
        }
172
173
        if ( !is_array($this->dumpSettings['include-tables']) ||
174
            !is_array($this->dumpSettings['exclude-tables']) ) {
175
            throw new Exception("Include-tables and exclude-tables should be arrays");
176
        }
177
178
        // Dump the same views as tables, mimic mysqldump behaviour
179
        $this->dumpSettings['include-views'] = $this->dumpSettings['include-tables'];
180
181
        // Create a new compressManager to manage compressed output
182
        $this->compressManager = CompressManagerFactory::create($this->dumpSettings['compress']);
183
    }
184
185
    /**
186
     * Destructor of Mysqldump. Unsets dbHandlers and database objects.
187
     *
188
     */
189
    public function __destruct()
190
    {
191
        $this->dbHandler = null;
192
    }
193
194
    /**
195
     * Custom array_replace_recursive to be used if PHP < 5.3
196
     * Replaces elements from passed arrays into the first array recursively
197
     *
198
     * @param array $array1 The array in which elements are replaced
199
     * @param array $array2 The array from which elements will be extracted
200
     *
201
     * @return array Returns an array, or NULL if an error occurs.
202
     */
203
    public static function array_replace_recursive($array1, $array2)
204
    {
205
        if (function_exists('array_replace_recursive')) {
206
            return array_replace_recursive($array1, $array2);
207
        }
208
209
        foreach ($array2 as $key => $value) {
210
            if (is_array($value)) {
211
                $array1[$key] = self::array_replace_recursive($array1[$key], $value);
212
            } else {
213
                $array1[$key] = $value;
214
            }
215
        }
216
        return $array1;
217
    }
218
219
    /**
220
     * Parse DSN string and extract dbname value
221
     * Several examples of a DSN string
222
     *   mysql:host=localhost;dbname=testdb
223
     *   mysql:host=localhost;port=3307;dbname=testdb
224
     *   mysql:unix_socket=/tmp/mysql.sock;dbname=testdb
225
     *
226
     * @param string $dsn dsn string to parse
227
     */
228
    private function parseDsn($dsn)
229
    {
230
        if (empty($dsn) || (false === ($pos = strpos($dsn, ":")))) {
231
            throw new Exception("Empty DSN string");
232
        }
233
234
        $this->dsn = $dsn;
235
        $this->dbType = strtolower(substr($dsn, 0, $pos));
236
237
        if (empty($this->dbType)) {
238
            throw new Exception("Missing database type from DSN string");
239
        }
240
241
        $dsn = substr($dsn, $pos + 1);
242
243
        foreach(explode(";", $dsn) as $kvp) {
244
            $kvpArr = explode("=", $kvp);
245
            $this->dsnArray[strtolower($kvpArr[0])] = $kvpArr[1];
246
        }
247
248
        if (empty($this->dsnArray['host']) &&
249
            empty($this->dsnArray['unix_socket'])) {
250
            throw new Exception("Missing host from DSN string");
251
        }
252
        $this->host = (!empty($this->dsnArray['host'])) ?
253
            $this->dsnArray['host'] :
254
            $this->dsnArray['unix_socket'];
255
256
        if (empty($this->dsnArray['dbname'])) {
257
            throw new Exception("Missing database name from DSN string");
258
        }
259
260
        $this->dbName = $this->dsnArray['dbname'];
261
262
        return true;
263
    }
264
265
    /**
266
     * Connect with PDO
267
     *
268
     * @return null
269
     */
270
    private function connect()
271
    {
272
        // Connecting with PDO
273
        try {
274
            switch ($this->dbType) {
275
                case 'sqlite':
276
                    $this->dbHandler = @new PDO("sqlite:" . $this->dbName, null, null, $this->pdoSettings);
277
                    break;
278
                case 'mysql':
279
                case 'pgsql':
280
                case 'dblib':
281
                    $this->dbHandler = @new PDO(
282
                        $this->dsn,
283
                        $this->user,
284
                        $this->pass,
285
                        $this->pdoSettings
286
                    );
287
                    // Execute init commands once connected
288
                    foreach($this->dumpSettings['init_commands'] as $stmt) {
289
                        $this->dbHandler->exec($stmt);
290
                    }
291
                    // Store server version
292
                    $this->version = $this->dbHandler->getAttribute(PDO::ATTR_SERVER_VERSION);
293
                    break;
294
                default:
295
                    throw new Exception("Unsupported database type (" . $this->dbType . ")");
296
            }
297
        } catch (PDOException $e) {
298
            throw new Exception(
299
                "Connection to " . $this->dbType . " failed with message: " .
300
                $e->getMessage()
301
            );
302
        }
303
304
        if ( is_null($this->dbHandler) ) {
305
            throw new Exception("Connection to ". $this->dbType . "failed");
306
        }
307
308
        $this->dbHandler->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL);
309
        $this->typeAdapter = TypeAdapterFactory::create($this->dbType, $this->dbHandler, $this->dumpSettings);
310
    }
311
312
    /**
313
     * Main call
314
     *
315
     * @param string $filename  Name of file to write sql dump to
316
     * @return null
317
     */
318
    public function start($filename = '')
319
    {
320
        // Output file can be redefined here
321
        if (!empty($filename)) {
322
            $this->fileName = $filename;
323
        }
324
325
        // Connect to database
326
        $this->connect();
327
328
        // Create output file
329
        $this->compressManager->open($this->fileName);
330
331
        // Write some basic info to output file
332
        $this->compressManager->write($this->getDumpFileHeader());
333
334
        // Store server settings and use sanner defaults to dump
335
        $this->compressManager->write(
336
            $this->typeAdapter->backup_parameters()
337
        );
338
339
        if ($this->dumpSettings['databases']) {
340
            $this->compressManager->write(
341
                $this->typeAdapter->getDatabaseHeader($this->dbName)
342
            );
343
            if ($this->dumpSettings['add-drop-database']) {
344
                $this->compressManager->write(
345
                    $this->typeAdapter->add_drop_database($this->dbName)
346
                );
347
            }
348
        }
349
350
        // Get table, view and trigger structures from database
351
        $this->getDatabaseStructure();
352
353
        if ($this->dumpSettings['databases']) {
354
            $this->compressManager->write(
355
                $this->typeAdapter->databases($this->dbName)
356
            );
357
        }
358
359
        // If there still are some tables/views in include-tables array,
360
        // that means that some tables or views weren't found.
361
        // Give proper error and exit.
362
        // This check will be removed once include-tables supports regexps
363
        if (0 < count($this->dumpSettings['include-tables'])) {
364
            $name = implode(",", $this->dumpSettings['include-tables']);
365
            throw new Exception("Table (" . $name . ") not found in database");
366
        }
367
368
        $this->exportTables();
369
        $this->exportTriggers();
370
        $this->exportViews();
371
        $this->exportProcedures();
372
        $this->exportEvents();
373
374
        // Restore saved parameters
375
        $this->compressManager->write(
376
            $this->typeAdapter->restore_parameters()
377
        );
378
        // Write some stats to output file
379
        $this->compressManager->write($this->getDumpFileFooter());
380
        // Close output file
381
        $this->compressManager->close();
382
    }
383
384
    /**
385
     * Returns header for dump file
386
     *
387
     * @return string
388
     */
389
    private function getDumpFileHeader()
390
    {
391
        $header = '';
392
        if ( !$this->dumpSettings['skip-comments'] ) {
393
            // Some info about software, source and time
394
            $header = "-- mysqldump-php https://github.com/ifsnop/mysqldump-php" . PHP_EOL .
395
                    "--" . PHP_EOL .
396
                    "-- Host: {$this->host}\tDatabase: {$this->dbName}" . PHP_EOL .
397
                    "-- ------------------------------------------------------" . PHP_EOL;
398
399
            if ( !empty($this->version) ) {
400
                $header .= "-- Server version \t" . $this->version . PHP_EOL;
401
            }
402
403
            if ( !$this->dumpSettings['skip-dump-date'] ) {
404
                $header .= "-- Date: " . date('r') . PHP_EOL . PHP_EOL;
405
            }
406
        }
407
        return $header;
408
    }
409
410
    /**
411
     * Returns footer for dump file
412
     *
413
     * @return string
414
     */
415
    private function getDumpFileFooter()
416
    {
417
        $footer = '';
418
        if (!$this->dumpSettings['skip-comments']) {
419
            $footer .= '-- Dump completed';
420
            if (!$this->dumpSettings['skip-dump-date']) {
421
                $footer .= ' on: ' . date('r');
422
            }
423
            $footer .= PHP_EOL;
424
        }
425
426
        return $footer;
427
    }
428
429
    /**
430
     * Reads table and views names from database.
431
     * Fills $this->tables array so they will be dumped later.
432
     *
433
     * @return null
434
     */
435
    private function getDatabaseStructure()
436
    {
437
        // Listing all tables from database
438 View Code Duplication
        if (empty($this->dumpSettings['include-tables'])) {
439
            // include all tables for now, blacklisting happens later
440
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
441
                array_push($this->tables, current($row));
442
            }
443
        } else {
444
            // include only the tables mentioned in include-tables
445
            foreach ($this->dbHandler->query($this->typeAdapter->show_tables($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
446
                if (in_array(current($row), $this->dumpSettings['include-tables'], true)) {
447
                    array_push($this->tables, current($row));
448
                    $elem = array_search(
449
                        current($row),
450
                        $this->dumpSettings['include-tables']
451
                    );
452
                    unset($this->dumpSettings['include-tables'][$elem]);
453
                }
454
            }
455
        }
456
457
        // Listing all views from database
458 View Code Duplication
        if (empty($this->dumpSettings['include-views'])) {
459
            // include all views for now, blacklisting happens later
460
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
461
                array_push($this->views, current($row));
462
            }
463
        } else {
464
            // include only the tables mentioned in include-tables
465
            foreach ($this->dbHandler->query($this->typeAdapter->show_views($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
466
                if (in_array(current($row), $this->dumpSettings['include-views'], true)) {
467
                    array_push($this->views, current($row));
468
                    $elem = array_search(
469
                        current($row),
470
                        $this->dumpSettings['include-views']
471
                    );
472
                    unset($this->dumpSettings['include-views'][$elem]);
473
                }
474
            }
475
        }
476
477
        // Listing all triggers from database
478 View Code Duplication
        if (false === $this->dumpSettings['skip-triggers']) {
479
            foreach ($this->dbHandler->query($this->typeAdapter->show_triggers($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
480
                array_push($this->triggers, $row['Trigger']);
481
            }
482
        }
483
484
        // Listing all procedures from database
485 View Code Duplication
        if ($this->dumpSettings['routines']) {
486
            foreach ($this->dbHandler->query($this->typeAdapter->show_procedures($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
487
                array_push($this->procedures, $row['procedure_name']);
488
            }
489
        }
490
491
        // Listing all events from database
492 View Code Duplication
        if ($this->dumpSettings['events']) {
493
            foreach ($this->dbHandler->query($this->typeAdapter->show_events($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
494
                array_push($this->events, $row['event_name']);
495
            }
496
        }
497
    }
498
499
    /**
500
     * Compare if $table name matches with a definition inside $arr
501
     * @param $table string
502
     * @param $arr array with strings or patterns
503
     * @return bool
504
     */
505
    private function matches($table, $arr) {
506
        $match = false;
507
508
        foreach ($arr as $pattern) {
509
            if ( '/' != $pattern[0] ) {
510
                continue;
511
            }
512
            if ( 1 == preg_match($pattern, $table) ) {
513
                $match = true;
514
            }
515
        }
516
517
        return in_array($table, $arr) || $match;
518
    }
519
520
    /**
521
     * Exports all the tables selected from database
522
     *
523
     * @return null
524
     */
525
    private function exportTables()
526
    {
527
        // Exporting tables one by one
528
        foreach ($this->tables as $table) {
529
            if ( $this->matches($table, $this->dumpSettings['exclude-tables']) ) {
530
                continue;
531
            }
532
            $this->getTableStructure($table);
533
            if ( false === $this->dumpSettings['no-data'] ) { // don't break compatibility with old trigger
534
                $this->listValues($table);
535
            } else if ( true === $this->dumpSettings['no-data']
536
                 || $this->matches($table, $this->dumpSettings['no-data']) ) {
537
                continue;
538
            } else {
539
                $this->listValues($table);
540
            }
541
        }
542
    }
543
544
    /**
545
     * Exports all the views found in database
546
     *
547
     * @return null
548
     */
549
    private function exportViews()
550
    {
551
        if (false === $this->dumpSettings['no-create-info']) {
552
            // Exporting views one by one
553
            foreach ($this->views as $view) {
554
                if ( $this->matches($view, $this->dumpSettings['exclude-tables']) ) {
555
                    continue;
556
                }
557
                $this->tableColumnTypes[$view] = $this->getTableColumnTypes($view);
558
                $this->getViewStructureTable($view);
559
            }
560
            foreach ($this->views as $view) {
561
                if ( $this->matches($view, $this->dumpSettings['exclude-tables']) ) {
562
                    continue;
563
                }
564
                $this->getViewStructureView($view);
565
            }
566
        }
567
    }
568
569
    /**
570
     * Exports all the triggers found in database
571
     *
572
     * @return null
573
     */
574
    private function exportTriggers()
575
    {
576
        // Exporting triggers one by one
577
        foreach ($this->triggers as $trigger) {
578
            $this->getTriggerStructure($trigger);
579
        }
580
    }
581
582
    /**
583
     * Exports all the procedures found in database
584
     *
585
     * @return null
586
     */
587
    private function exportProcedures()
588
    {
589
        // Exporting triggers one by one
590
        foreach ($this->procedures as $procedure) {
591
            $this->getProcedureStructure($procedure);
592
        }
593
    }
594
595
    /**
596
     * Exports all the events found in database
597
     *
598
     * @return null
599
     */
600
    private function exportEvents()
601
    {
602
        // Exporting triggers one by one
603
        foreach ($this->events as $event) {
604
            $this->getEventStructure($event);
605
        }
606
    }
607
608
    /**
609
     * Table structure extractor
610
     *
611
     * @todo move specific mysql code to typeAdapter
612
     * @param string $tableName  Name of table to export
613
     * @return null
614
     */
615
    private function getTableStructure($tableName)
616
    {
617
        if (!$this->dumpSettings['no-create-info']) {
618
            $ret = '';
619
            if (!$this->dumpSettings['skip-comments']) {
620
                $ret = "--" . PHP_EOL .
621
                    "-- Table structure for table `$tableName`" . PHP_EOL .
622
                    "--" . PHP_EOL . PHP_EOL;
623
            }
624
            $stmt = $this->typeAdapter->show_create_table($tableName);
625
            foreach ($this->dbHandler->query($stmt) as $r) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
626
                $this->compressManager->write($ret);
627
                if ($this->dumpSettings['add-drop-table']) {
628
                    $this->compressManager->write(
629
                        $this->typeAdapter->drop_table($tableName)
630
                    );
631
                }
632
                $this->compressManager->write(
633
                    $this->typeAdapter->create_table($r)
634
                );
635
                break;
636
            }
637
        }
638
        $this->tableColumnTypes[$tableName] = $this->getTableColumnTypes($tableName);
639
        return;
640
    }
641
642
    /**
643
     * Store column types to create data dumps and for Stand-In tables
644
     *
645
     * @param string $tableName  Name of table to export
646
     * @return array type column types detailed
647
     */
648
649
    private function getTableColumnTypes($tableName) {
650
        $columnTypes = array();
651
        $columns = $this->dbHandler->query(
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
652
            $this->typeAdapter->show_columns($tableName)
653
        );
654
        $columns->setFetchMode(PDO::FETCH_ASSOC);
655
656
        foreach($columns as $key => $col) {
657
            $types = $this->typeAdapter->parseColumnType($col);
658
            $columnTypes[$col['Field']] = array(
659
                'is_numeric'=> $types['is_numeric'],
660
                'is_blob' => $types['is_blob'],
661
                'type' => $types['type'],
662
                'type_sql' => $col['Type'],
663
                'is_virtual' => $types['is_virtual']
664
            );
665
        }
666
667
        return $columnTypes;
668
    }
669
670
    /**
671
     * View structure extractor, create table (avoids cyclic references)
672
     *
673
     * @todo move mysql specific code to typeAdapter
674
     * @param string $viewName  Name of view to export
675
     * @return null
676
     */
677 View Code Duplication
    private function getViewStructureTable($viewName)
678
    {
679
        if (!$this->dumpSettings['skip-comments']) {
680
            $ret = "--" . PHP_EOL .
681
                "-- Stand-In structure for view `${viewName}`" . PHP_EOL .
682
                "--" . PHP_EOL . PHP_EOL;
683
            $this->compressManager->write($ret);
684
        }
685
        $stmt = $this->typeAdapter->show_create_view($viewName);
686
687
        // create views as tables, to resolve dependencies
688
        foreach ($this->dbHandler->query($stmt) as $r) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
689
            if ($this->dumpSettings['add-drop-table']) {
690
                $this->compressManager->write(
691
                    $this->typeAdapter->drop_view($viewName)
692
                );
693
            }
694
695
            $this->compressManager->write(
696
                $this->createStandInTable($viewName)
697
            );
698
            break;
699
        }
700
    }
701
702
    /**
703
     * Write a create table statement for the table Stand-In, show create
704
     * table would return a create algorithm when used on a view
705
     *
706
     * @param string $viewName  Name of view to export
707
     * @return string create statement
708
     */
709
    function createStandInTable($viewName) {
710
        $ret = array();
711
        foreach($this->tableColumnTypes[$viewName] as $k => $v) {
712
            $ret[] = "`${k}` ${v['type_sql']}";
713
        }
714
        $ret = implode(PHP_EOL . ",", $ret);
715
716
        $ret = "CREATE TABLE IF NOT EXISTS `$viewName` (" .
717
            PHP_EOL . $ret . PHP_EOL . ");" . PHP_EOL;
718
719
        return $ret;
720
    }
721
722
    /**
723
     * View structure extractor, create view
724
     *
725
     * @todo move mysql specific code to typeAdapter
726
     * @param string $viewName  Name of view to export
727
     * @return null
728
     */
729 View Code Duplication
    private function getViewStructureView($viewName)
730
    {
731
        if (!$this->dumpSettings['skip-comments']) {
732
            $ret = "--" . PHP_EOL .
733
                "-- View 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, to resolve dependencies
740
        // replacing tables with views
741
        foreach ($this->dbHandler->query($stmt) as $r) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
742
            // because we must replace table with view, we should delete it
743
            $this->compressManager->write(
744
                $this->typeAdapter->drop_view($viewName)
745
            );
746
            $this->compressManager->write(
747
                $this->typeAdapter->create_view($r)
748
            );
749
            break;
750
        }
751
    }
752
753
    /**
754
     * Trigger structure extractor
755
     *
756
     * @param string $triggerName  Name of trigger to export
757
     * @return null
758
     */
759
    private function getTriggerStructure($triggerName)
760
    {
761
        $stmt = $this->typeAdapter->show_create_trigger($triggerName);
762
        foreach ($this->dbHandler->query($stmt) as $r) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
763
            if ($this->dumpSettings['add-drop-trigger']) {
764
                $this->compressManager->write(
765
                    $this->typeAdapter->add_drop_trigger($triggerName)
766
                );
767
            }
768
            $this->compressManager->write(
769
                $this->typeAdapter->create_trigger($r)
770
            );
771
            return;
772
        }
773
    }
774
775
    /**
776
     * Procedure structure extractor
777
     *
778
     * @param string $procedureName  Name of procedure to export
779
     * @return null
780
     */
781 View Code Duplication
    private function getProcedureStructure($procedureName)
782
    {
783
        if (!$this->dumpSettings['skip-comments']) {
784
            $ret = "--" . PHP_EOL .
785
                "-- Dumping routines for database '" . $this->dbName . "'" . PHP_EOL .
786
                "--" . PHP_EOL . PHP_EOL;
787
            $this->compressManager->write($ret);
788
        }
789
        $stmt = $this->typeAdapter->show_create_procedure($procedureName);
790
        foreach ($this->dbHandler->query($stmt) as $r) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
791
            $this->compressManager->write(
792
                $this->typeAdapter->create_procedure($r)
793
            );
794
            return;
795
        }
796
    }
797
798
    /**
799
     * Event structure extractor
800
     *
801
     * @param string $eventName  Name of event to export
802
     * @return null
803
     */
804 View Code Duplication
    private function getEventStructure($eventName)
805
    {
806
        if (!$this->dumpSettings['skip-comments']) {
807
            $ret = "--" . PHP_EOL .
808
                "-- Dumping events for database '" . $this->dbName . "'" . PHP_EOL .
809
                "--" . PHP_EOL . PHP_EOL;
810
            $this->compressManager->write($ret);
811
        }
812
        $stmt = $this->typeAdapter->show_create_event($eventName);
813
        foreach ($this->dbHandler->query($stmt) as $r) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
814
            $this->compressManager->write(
815
                $this->typeAdapter->create_event($r)
816
            );
817
            return;
818
        }
819
    }
820
821
    /**
822
     * Escape values with quotes when needed
823
     *
824
     * @param string $tableName Name of table which contains rows
825
     * @param array $row Associative array of column names and values to be quoted
826
     *
827
     * @return string
828
     */
829
    private function escape($tableName, $row)
830
    {
831
        $ret = array();
832
        $columnTypes = $this->tableColumnTypes[$tableName];
833
        foreach ($row as $colName => $colValue) {
834
            if (is_null($colValue)) {
835
                $ret[] = "NULL";
836
            } elseif ($this->dumpSettings['hex-blob'] && $columnTypes[$colName]['is_blob']) {
837
                if ($columnTypes[$colName]['type'] == 'bit' || !empty($colValue)) {
838
                    $ret[] = "0x${colValue}";
839
                } else {
840
                    $ret[] = "''";
841
                }
842
            } elseif ($columnTypes[$colName]['is_numeric']) {
843
                $ret[] = $colValue;
844
            } else {
845
                $ret[] = $this->dbHandler->quote($colValue);
0 ignored issues
show
Bug introduced by
The method quote cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
846
            }
847
        }
848
        return $ret;
849
    }
850
851
    /**
852
     * Table rows extractor
853
     *
854
     * @param string $tableName  Name of table to export
855
     *
856
     * @return null
857
     */
858
    private function listValues($tableName)
859
    {
860
        $this->prepareListValues($tableName);
861
862
        $onlyOnce = true;
863
        $lineSize = 0;
864
865
        $colStmt = $this->getColumnStmt($tableName);
866
        $stmt = "SELECT " . implode(",", $colStmt) . " FROM `$tableName`";
867
868
        if ($this->dumpSettings['where']) {
869
            $stmt .= " WHERE {$this->dumpSettings['where']}";
870
        }
871
        $resultSet = $this->dbHandler->query($stmt);
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
872
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
873
874
        foreach ($resultSet as $row) {
875
            $vals = $this->escape($tableName, $row);
876
            if ($onlyOnce || !$this->dumpSettings['extended-insert']) {
877
878
                if ($this->dumpSettings['complete-insert']) {
879
                    $lineSize += $this->compressManager->write(
880
                        "INSERT INTO `$tableName` (" .
881
                        implode(", ", $colStmt) .
882
                        ") VALUES (" . implode(",", $vals) . ")"
883
                    );
884
                } else {
885
                    $lineSize += $this->compressManager->write(
886
                        "INSERT INTO `$tableName` VALUES (" . implode(",", $vals) . ")"
887
                    );
888
                }
889
                $onlyOnce = false;
890
            } else {
891
                $lineSize += $this->compressManager->write(",(" . implode(",", $vals) . ")");
892
            }
893
            if (($lineSize > $this->dumpSettings['net_buffer_length']) ||
894
                    !$this->dumpSettings['extended-insert']) {
895
                $onlyOnce = true;
896
                $lineSize = $this->compressManager->write(";" . PHP_EOL);
897
            }
898
        }
899
        $resultSet->closeCursor();
900
901
        if (!$onlyOnce) {
902
            $this->compressManager->write(";" . PHP_EOL);
903
        }
904
905
        $this->endListValues($tableName);
906
    }
907
908
    /**
909
     * Table rows extractor, append information prior to dump
910
     *
911
     * @param string $tableName  Name of table to export
912
     *
913
     * @return null
914
     */
915
    function prepareListValues($tableName)
916
    {
917
        if (!$this->dumpSettings['skip-comments']) {
918
            $this->compressManager->write(
919
                "--" . PHP_EOL .
920
                "-- Dumping data for table `$tableName`" .  PHP_EOL .
921
                "--" . PHP_EOL . PHP_EOL
922
            );
923
        }
924
925
        if ($this->dumpSettings['single-transaction']) {
926
            $this->dbHandler->exec($this->typeAdapter->setup_transaction());
0 ignored issues
show
Bug introduced by
The method exec cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
927
            $this->dbHandler->exec($this->typeAdapter->start_transaction());
0 ignored issues
show
Bug introduced by
The method exec cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
928
        }
929
930
        if ($this->dumpSettings['lock-tables']) {
931
            $this->typeAdapter->lock_table($tableName);
932
        }
933
934
        if ($this->dumpSettings['add-locks']) {
935
            $this->compressManager->write(
936
                $this->typeAdapter->start_add_lock_table($tableName)
937
            );
938
        }
939
940
        if ($this->dumpSettings['disable-keys']) {
941
            $this->compressManager->write(
942
                $this->typeAdapter->start_add_disable_keys($tableName)
943
            );
944
        }
945
946
        // Disable autocommit for faster reload
947
        if ($this->dumpSettings['no-autocommit']) {
948
            $this->compressManager->write(
949
                $this->typeAdapter->start_disable_autocommit()
950
            );
951
        }
952
953
        return;
954
    }
955
956
    /**
957
     * Table rows extractor, close locks and commits after dump
958
     *
959
     * @param string $tableName  Name of table to export
960
     *
961
     * @return null
962
     */
963
    function endListValues($tableName)
964
    {
965
        if ($this->dumpSettings['disable-keys']) {
966
            $this->compressManager->write(
967
                $this->typeAdapter->end_add_disable_keys($tableName)
968
            );
969
        }
970
971
        if ($this->dumpSettings['add-locks']) {
972
            $this->compressManager->write(
973
                $this->typeAdapter->end_add_lock_table($tableName)
974
            );
975
        }
976
977
        if ($this->dumpSettings['single-transaction']) {
978
            $this->dbHandler->exec($this->typeAdapter->commit_transaction());
0 ignored issues
show
Bug introduced by
The method exec cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1849
        // MySQL reference: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
1850
        $colInfo['is_virtual'] = strpos($colType['Extra'], "VIRTUAL GENERATED") !== false || strpos($colType['Extra'], "STORED GENERATED") !== false;
1851
      
1852
        return $colInfo;
1853
    }
1854
1855
    public function backup_parameters()
1856
    {
1857
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" . PHP_EOL .
1858
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" . PHP_EOL .
1859
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" . PHP_EOL .
1860
            "/*!40101 SET NAMES " . $this->dumpSettings['default-character-set'] . " */;" . PHP_EOL;
1861
1862
        if (false === $this->dumpSettings['skip-tz-utc']) {
1863
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;" . PHP_EOL .
1864
                "/*!40103 SET TIME_ZONE='+00:00' */;" . PHP_EOL;
1865
        }
1866
1867
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;" . PHP_EOL .
1868
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;" . PHP_EOL .
1869
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;" . PHP_EOL .
1870
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;" . PHP_EOL .PHP_EOL;
1871
1872
        return $ret;
1873
    }
1874
1875
    public function restore_parameters()
1876
    {
1877
        $ret = "";
1878
1879
        if (false === $this->dumpSettings['skip-tz-utc']) {
1880
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL;
1881
        }
1882
1883
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;" . PHP_EOL .
1884
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;" . PHP_EOL .
1885
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;" . PHP_EOL .
1886
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;" . PHP_EOL .
1887
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;" . PHP_EOL .
1888
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;" . PHP_EOL .
1889
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;" . PHP_EOL . PHP_EOL;
1890
1891
        return $ret;
1892
    }
1893
1894
    /**
1895
     * Check number of parameters passed to function, useful when inheriting.
1896
     * Raise exception if unexpected.
1897
     *
1898
     * @param integer $num_args
1899
     * @param integer $expected_num_args
1900
     * @param string $method_name
1901
     */
1902
    private function check_parameters($num_args, $expected_num_args, $method_name)
1903
    {
1904
        if ( $num_args != $expected_num_args ) {
1905
            throw new Exception("Unexpected parameter passed to $method_name");
1906
        }
1907
        return;
1908
    }
1909
}
1910