Completed
Push — master ( 352f8c...a4ea01 )
by diego
39s
created

Mysqldump::exportTables()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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