Completed
Pull Request — master (#117)
by
unknown
17:47
created

Mysqldump::connect()   D

Complexity

Conditions 9
Paths 27

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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