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