Completed
Pull Request — master (#128)
by
unknown
04:46
created

Mysqldump::getColumnNames()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
/**
3
 * Mysqldump File Doc Comment
4
 *
5
 * PHP version 5
6
 *
7
 * @category Library
8
 * @package  Ifsnop\Mysqldump
9
 * @author   Michael J. Calkins <[email protected]>
10
 * @author   Diego Torres <[email protected]>
11
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
12
 * @link     https://github.com/ifsnop/mysqldump-php
13
 *
14
 */
15
16
namespace Ifsnop\Mysqldump;
17
18
use Exception;
19
use PDO;
20
use PDOException;
21
22
/**
23
 * Mysqldump Class Doc Comment
24
 *
25
 * @category Library
26
 * @package  Ifsnop\Mysqldump
27
 * @author   Michael J. Calkins <[email protected]>
28
 * @author   Diego Torres <[email protected]>
29
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
30
 * @link     https://github.com/ifsnop/mysqldump-php
31
 *
32
 */
33
class Mysqldump
34
{
35
36
    // Same as mysqldump
37
    const MAXLINESIZE = 1000000;
38
39
    // Available compression methods as constants
40
    const GZIP = 'Gzip';
41
    const BZIP2 = 'Bzip2';
42
    const NONE = 'None';
43
44
    // Available connection strings
45
    const UTF8 = 'utf8';
46
    const UTF8MB4 = 'utf8mb4';
47
48
    /**
49
    * Database username
50
    * @var string
51
    */
52
    public $user;
53
    /**
54
    * Database password
55
    * @var string
56
    */
57
    public $pass;
58
    /**
59
    * Connection string for PDO
60
    * @var string
61
    */
62
    public $dsn;
63
    /**
64
    * Destination filename, defaults to stdout
65
    * @var string
66
    */
67
    public $fileName = 'php://output';
68
69
    // Internal stuff
70
    private $tables = array();
71
    private $views = array();
72
    private $triggers = array();
73
    private $procedures = array();
74
    private $events = array();
75
    private $dbHandler = null;
76
    private $dbType;
77
    private $compressManager;
78
    private $typeAdapter;
79
    private $dumpSettings = array();
80
    private $pdoSettings = array();
81
    private $version;
82
    private $tableColumnTypes = array();
83
    /**
84
    * database name, parsed from dsn
85
    * @var string
86
    */
87
    private $dbName;
88
    /**
89
    * host name, parsed from dsn
90
    * @var string
91
    */
92
    private $host;
93
    /**
94
    * dsn string parsed as an array
95
    * @var array
96
    */
97
    private $dsnArray = array();
98
99
    /**
100
     * Constructor of Mysqldump. Note that in the case of an SQLite database
101
     * connection, the filename must be in the $db parameter.
102
     *
103
     * @param string $dsn        PDO DSN connection string
104
     * @param string $user       SQL account username
105
     * @param string $pass       SQL account password
106
     * @param array  $dumpSettings SQL database settings
107
     * @param array  $pdoSettings  PDO configured attributes
108
     */
109
    public function __construct(
110
        $dsn = '',
111
        $user = '',
112
        $pass = '',
113
        $dumpSettings = array(),
114
        $pdoSettings = array()
115
    ) {
116
        $dumpSettingsDefault = array(
117
            'include-tables' => array(),
118
            'exclude-tables' => array(),
119
            'compress' => Mysqldump::NONE,
120
            'init_commands' => array(),
121
            'no-data' => array(),
122
            'reset-auto-increment' => false,
123
            'add-drop-database' => false,
124
            'add-drop-table' => false,
125
            'add-drop-trigger' => true,
126
            'add-locks' => true,
127
            'complete-insert' => false,
128
            'databases' => false,
129
            'default-character-set' => Mysqldump::UTF8,
130
            'disable-keys' => true,
131
            'extended-insert' => true,
132
            'events' => false,
133
            'hex-blob' => true, /* faster than escaped content */
134
            'net_buffer_length' => self::MAXLINESIZE,
135
            'no-autocommit' => true,
136
            'no-create-info' => false,
137
            'lock-tables' => true,
138
            'routines' => false,
139
            'single-transaction' => true,
140
            'skip-triggers' => false,
141
            'skip-tz-utc' => false,
142
            'skip-comments' => false,
143
            'skip-dump-date' => false,
144
            'where' => '',
145
            /* deprecated */
146
            'disable-foreign-keys-check' => true
147
        );
148
149
        $pdoSettingsDefault = array(
150
            PDO::ATTR_PERSISTENT => true,
151
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
152
            PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false
153
        );
154
155
        $this->user = $user;
156
        $this->pass = $pass;
157
        $this->parseDsn($dsn);
158
        $this->pdoSettings = self::array_replace_recursive($pdoSettingsDefault, $pdoSettings);
159
        $this->dumpSettings = self::array_replace_recursive($dumpSettingsDefault, $dumpSettings);
160
161
        $this->dumpSettings['init_commands'][] = "SET NAMES " . $this->dumpSettings['default-character-set'];
162
163
        if (false === $this->dumpSettings['skip-tz-utc']) {
164
            $this->dumpSettings['init_commands'][] = "SET TIME_ZONE='+00:00'";
165
        }
166
167
        $diff = array_diff(array_keys($this->dumpSettings), array_keys($dumpSettingsDefault));
168
        if (count($diff)>0) {
169
            throw new Exception("Unexpected value in dumpSettings: (" . implode(",", $diff) . ")");
170
        }
171
172
        if ( !is_array($this->dumpSettings['include-tables']) ||
173
            !is_array($this->dumpSettings['exclude-tables']) ) {
174
            throw new Exception("Include-tables and exclude-tables should be arrays");
175
        }
176
177
        // Dump the same views as tables, mimic mysqldump behaviour
178
        $this->dumpSettings['include-views'] = $this->dumpSettings['include-tables'];
179
180
        // Create a new compressManager to manage compressed output
181
        $this->compressManager = CompressManagerFactory::create($this->dumpSettings['compress']);
182
    }
183
184
    /**
185
     * Destructor of Mysqldump. Unsets dbHandlers and database objects.
186
     *
187
     */
188
    public function __destruct()
189
    {
190
        $this->dbHandler = null;
191
    }
192
193
    /**
194
     * Custom array_replace_recursive to be used if PHP < 5.3
195
     * Replaces elements from passed arrays into the first array recursively
196
     *
197
     * @param array $array1 The array in which elements are replaced
198
     * @param array $array2 The array from which elements will be extracted
199
     *
200
     * @return array Returns an array, or NULL if an error occurs.
201
     */
202
    public static function array_replace_recursive($array1, $array2)
203
    {
204
        if (function_exists('array_replace_recursive')) {
205
            return array_replace_recursive($array1, $array2);
206
        }
207
208
        foreach ($array2 as $key => $value) {
209
            if (is_array($value)) {
210
                $array1[$key] = self::array_replace_recursive($array1[$key], $value);
211
            } else {
212
                $array1[$key] = $value;
213
            }
214
        }
215
        return $array1;
216
    }
217
218
    /**
219
     * Parse DSN string and extract dbname value
220
     * Several examples of a DSN string
221
     *   mysql:host=localhost;dbname=testdb
222
     *   mysql:host=localhost;port=3307;dbname=testdb
223
     *   mysql:unix_socket=/tmp/mysql.sock;dbname=testdb
224
     *
225
     * @param string $dsn dsn string to parse
226
     */
227
    private function parseDsn($dsn)
228
    {
229
        if (empty($dsn) || (false === ($pos = strpos($dsn, ":")))) {
230
            throw new Exception("Empty DSN string");
231
        }
232
233
        $this->dsn = $dsn;
234
        $this->dbType = strtolower(substr($dsn, 0, $pos));
235
236
        if (empty($this->dbType)) {
237
            throw new Exception("Missing database type from DSN string");
238
        }
239
240
        $dsn = substr($dsn, $pos + 1);
241
242
        foreach(explode(";", $dsn) as $kvp) {
243
            $kvpArr = explode("=", $kvp);
244
            $this->dsnArray[strtolower($kvpArr[0])] = $kvpArr[1];
245
        }
246
247
        if (empty($this->dsnArray['host']) &&
248
            empty($this->dsnArray['unix_socket'])) {
249
            throw new Exception("Missing host from DSN string");
250
        }
251
        $this->host = (!empty($this->dsnArray['host'])) ?
252
            $this->dsnArray['host'] :
253
            $this->dsnArray['unix_socket'];
254
255
        if (empty($this->dsnArray['dbname'])) {
256
            throw new Exception("Missing database name from DSN string");
257
        }
258
259
        $this->dbName = $this->dsnArray['dbname'];
260
261
        return true;
262
    }
263
264
    /**
265
     * Connect with PDO
266
     *
267
     * @return null
268
     */
269
    private function connect()
270
    {
271
        // Connecting with PDO
272
        try {
273
            switch ($this->dbType) {
274
                case 'sqlite':
275
                    $this->dbHandler = @new PDO("sqlite:" . $this->dbName, null, null, $this->pdoSettings);
276
                    break;
277
                case 'mysql':
278
                case 'pgsql':
279
                case 'dblib':
280
                    $this->dbHandler = @new PDO(
281
                        $this->dsn,
282
                        $this->user,
283
                        $this->pass,
284
                        $this->pdoSettings
285
                    );
286
                    // Execute init commands once connected
287
                    foreach($this->dumpSettings['init_commands'] as $stmt) {
288
                        $this->dbHandler->exec($stmt);
289
                    }
290
                    // Store server version
291
                    $this->version = $this->dbHandler->getAttribute(PDO::ATTR_SERVER_VERSION);
292
                    break;
293
                default:
294
                    throw new Exception("Unsupported database type (" . $this->dbType . ")");
295
            }
296
        } catch (PDOException $e) {
297
            throw new Exception(
298
                "Connection to " . $this->dbType . " failed with message: " .
299
                $e->getMessage()
300
            );
301
        }
302
303
        if ( is_null($this->dbHandler) ) {
304
            throw new Exception("Connection to ". $this->dbType . "failed");
305
        }
306
307
        $this->dbHandler->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL);
308
        $this->typeAdapter = TypeAdapterFactory::create($this->dbType, $this->dbHandler);
309
    }
310
311
    /**
312
     * Main call
313
     *
314
     * @param string $filename  Name of file to write sql dump to
315
     * @return null
316
     */
317
    public function start($filename = '')
318
    {
319
        // Output file can be redefined here
320
        if (!empty($filename)) {
321
            $this->fileName = $filename;
322
        }
323
324
        // Connect to database
325
        $this->connect();
326
327
        // Create output file
328
        $this->compressManager->open($this->fileName);
329
330
        // Write some basic info to output file
331
        $this->compressManager->write($this->getDumpFileHeader());
332
333
        // Store server settings and use sanner defaults to dump
334
        $this->compressManager->write(
335
            $this->typeAdapter->backup_parameters($this->dumpSettings)
336
        );
337
338
        if ($this->dumpSettings['databases']) {
339
            $this->compressManager->write(
340
                $this->typeAdapter->getDatabaseHeader($this->dbName)
341
            );
342
            if ($this->dumpSettings['add-drop-database']) {
343
                $this->compressManager->write(
344
                    $this->typeAdapter->add_drop_database($this->dbName)
345
                );
346
            }
347
        }
348
349
        // Get table, view and trigger structures from database
350
        $this->getDatabaseStructure();
351
352
        if ($this->dumpSettings['databases']) {
353
            $this->compressManager->write(
354
                $this->typeAdapter->databases($this->dbName)
355
            );
356
        }
357
358
        // If there still are some tables/views in include-tables array,
359
        // that means that some tables or views weren't found.
360
        // Give proper error and exit.
361
        // This check will be removed once include-tables supports regexps
362
        if (0 < count($this->dumpSettings['include-tables'])) {
363
            $name = implode(",", $this->dumpSettings['include-tables']);
364
            throw new Exception("Table (" . $name . ") not found in database");
365
        }
366
367
        $this->exportTables();
368
        $this->exportViews();
369
        $this->exportTriggers();
370
        $this->exportProcedures();
371
        $this->exportEvents();
372
373
        // Restore saved parameters
374
        $this->compressManager->write(
375
            $this->typeAdapter->restore_parameters($this->dumpSettings)
376
        );
377
        // Write some stats to output file
378
        $this->compressManager->write($this->getDumpFileFooter());
379
        // Close output file
380
        $this->compressManager->close();
381
    }
382
383
    /**
384
     * Returns header for dump file
385
     *
386
     * @return string
387
     */
388
    private function getDumpFileHeader()
389
    {
390
        $header = '';
391
        if ( !$this->dumpSettings['skip-comments'] ) {
392
            // Some info about software, source and time
393
            $header = "-- mysqldump-php https://github.com/ifsnop/mysqldump-php" . PHP_EOL .
394
                    "--" . PHP_EOL .
395
                    "-- Host: {$this->host}\tDatabase: {$this->dbName}" . PHP_EOL .
396
                    "-- ------------------------------------------------------" . PHP_EOL;
397
398
            if ( !empty($this->version) ) {
399
                $header .= "-- Server version \t" . $this->version . PHP_EOL;
400
            }
401
402
            if ( !$this->dumpSettings['skip-dump-date'] ) {
403
                $header .= "-- Date: " . date('r') . PHP_EOL . PHP_EOL;
404
            }
405
        }
406
        return $header;
407
    }
408
409
    /**
410
     * Returns footer for dump file
411
     *
412
     * @return string
413
     */
414
    private function getDumpFileFooter()
415
    {
416
        $footer = '';
417
        if (!$this->dumpSettings['skip-comments']) {
418
            $footer .= '-- Dump completed';
419
            if (!$this->dumpSettings['skip-dump-date']) {
420
                $footer .= ' on: ' . date('r');
421
            }
422
            $footer .= PHP_EOL;
423
        }
424
425
        return $footer;
426
    }
427
428
    /**
429
     * Reads table and views names from database.
430
     * Fills $this->tables array so they will be dumped later.
431
     *
432
     * @return null
433
     */
434
    private function getDatabaseStructure()
435
    {
436
        // Listing all tables from database
437 View Code Duplication
        if (empty($this->dumpSettings['include-tables'])) {
438
            // include all tables for now, blacklisting happens later
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
                array_push($this->tables, current($row));
441
            }
442
        } else {
443
            // include only the tables mentioned in include-tables
444
            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...
445
                if (in_array(current($row), $this->dumpSettings['include-tables'], true)) {
446
                    array_push($this->tables, current($row));
447
                    $elem = array_search(
448
                        current($row),
449
                        $this->dumpSettings['include-tables']
450
                    );
451
                    unset($this->dumpSettings['include-tables'][$elem]);
452
                }
453
            }
454
        }
455
456
        // Listing all views from database
457 View Code Duplication
        if (empty($this->dumpSettings['include-views'])) {
458
            // include all views for now, blacklisting happens later
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
                array_push($this->views, current($row));
461
            }
462
        } else {
463
            // include only the tables mentioned in include-tables
464
            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...
465
                if (in_array(current($row), $this->dumpSettings['include-views'], true)) {
466
                    array_push($this->views, current($row));
467
                    $elem = array_search(
468
                        current($row),
469
                        $this->dumpSettings['include-views']
470
                    );
471
                    unset($this->dumpSettings['include-views'][$elem]);
472
                }
473
            }
474
        }
475
476
        // Listing all triggers from database
477 View Code Duplication
        if (false === $this->dumpSettings['skip-triggers']) {
478
            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...
479
                array_push($this->triggers, $row['Trigger']);
480
            }
481
        }
482
483
        // Listing all procedures from database
484 View Code Duplication
        if ($this->dumpSettings['routines']) {
485
            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...
486
                array_push($this->procedures, $row['procedure_name']);
487
            }
488
        }
489
490
        // Listing all events from database
491 View Code Duplication
        if ($this->dumpSettings['events']) {
492
            foreach ($this->dbHandler->query($this->typeAdapter->show_events($this->dbName)) as $row) {
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->dbHandler (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1882
        $colInfo['is_virtual'] = strpos($colType['Extra'], "STORED GENERATED") === false ? false : true;
1883
1884
        return $colInfo;
1885
    }
1886
1887
    public function backup_parameters()
1888
    {
1889
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1890
        $args = func_get_args();
1891
        $dumpSettings = $args[0];
1892
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" . PHP_EOL .
1893
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" . PHP_EOL .
1894
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" . PHP_EOL .
1895
            "/*!40101 SET NAMES " . $dumpSettings['default-character-set'] . " */;" . PHP_EOL;
1896
1897
        if (false === $dumpSettings['skip-tz-utc']) {
1898
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;" . PHP_EOL .
1899
                "/*!40103 SET TIME_ZONE='+00:00' */;" . PHP_EOL;
1900
        }
1901
1902
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;" . PHP_EOL .
1903
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;" . PHP_EOL .
1904
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;" . PHP_EOL .
1905
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;" . PHP_EOL .PHP_EOL;
1906
1907
        return $ret;
1908
    }
1909
1910
    public function restore_parameters()
1911
    {
1912
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1913
        $args = func_get_args();
1914
        $dumpSettings = $args[0];
1915
        $ret = "";
1916
1917
        if (false === $dumpSettings['skip-tz-utc']) {
1918
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL;
1919
        }
1920
1921
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;" . PHP_EOL .
1922
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;" . PHP_EOL .
1923
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;" . PHP_EOL .
1924
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;" . PHP_EOL .
1925
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;" . PHP_EOL .
1926
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;" . PHP_EOL .
1927
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;" . PHP_EOL . PHP_EOL;
1928
1929
        return $ret;
1930
    }
1931
1932
    /**
1933
     * Check number of parameters passed to function, useful when inheriting.
1934
     * Raise exception if unexpected.
1935
     *
1936
     * @param integer $num_args
1937
     * @param integer $expected_num_args
1938
     * @param string $method_name
1939
     */
1940
    private function check_parameters($num_args, $expected_num_args, $method_name)
1941
    {
1942
        if ( $num_args != $expected_num_args ) {
1943
            throw new Exception("Unexpected parameter passed to $method_name");
1944
        }
1945
        return;
1946
    }
1947
}
1948