Completed
Push — master ( 24286d...ce1cee )
by diego
04:59 queued 02:47
created

Mysqldump::parseDsn()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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