Completed
Pull Request — master (#117)
by
unknown
04:57
created

Mysqldump::setDbHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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