Completed
Push — master ( d4994c...f8f609 )
by diego
01:26
created

TypeAdapterMysql::parseColumnType()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 6
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
        $stmt = "SELECT " . implode(",", $colStmt) . " FROM `$tableName`";
866
867
        if ($this->dumpSettings['where']) {
868
            $stmt .= " WHERE {$this->dumpSettings['where']}";
869
        }
870
        $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...
871
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
872
873
        foreach ($resultSet as $row) {
874
            $vals = $this->escape($tableName, $row);
875
            if ($onlyOnce || !$this->dumpSettings['extended-insert']) {
876
877
                if ($this->dumpSettings['complete-insert']) {
878
                    $lineSize += $this->compressManager->write(
879
                        "INSERT INTO `$tableName` (" .
880
                        implode(", ", $colStmt) .
881
                        ") VALUES (" . implode(",", $vals) . ")"
882
                    );
883
                } else {
884
                    $lineSize += $this->compressManager->write(
885
                        "INSERT INTO `$tableName` VALUES (" . implode(",", $vals) . ")"
886
                    );
887
                }
888
                $onlyOnce = false;
889
            } else {
890
                $lineSize += $this->compressManager->write(",(" . implode(",", $vals) . ")");
891
            }
892
            if (($lineSize > $this->dumpSettings['net_buffer_length']) ||
893
                    !$this->dumpSettings['extended-insert']) {
894
                $onlyOnce = true;
895
                $lineSize = $this->compressManager->write(";" . PHP_EOL);
896
            }
897
        }
898
        $resultSet->closeCursor();
899
900
        if (!$onlyOnce) {
901
            $this->compressManager->write(";" . PHP_EOL);
902
        }
903
904
        $this->endListValues($tableName);
905
    }
906
907
    /**
908
     * Table rows extractor, append information prior to dump
909
     *
910
     * @param string $tableName  Name of table to export
911
     *
912
     * @return null
913
     */
914
    function prepareListValues($tableName)
915
    {
916
        if (!$this->dumpSettings['skip-comments']) {
917
            $this->compressManager->write(
918
                "--" . PHP_EOL .
919
                "-- Dumping data for table `$tableName`" .  PHP_EOL .
920
                "--" . PHP_EOL . PHP_EOL
921
            );
922
        }
923
924
        if ($this->dumpSettings['single-transaction']) {
925
            $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...
926
            $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...
927
        }
928
929
        if ($this->dumpSettings['lock-tables']) {
930
            $this->typeAdapter->lock_table($tableName);
931
        }
932
933
        if ($this->dumpSettings['add-locks']) {
934
            $this->compressManager->write(
935
                $this->typeAdapter->start_add_lock_table($tableName)
936
            );
937
        }
938
939
        if ($this->dumpSettings['disable-keys']) {
940
            $this->compressManager->write(
941
                $this->typeAdapter->start_add_disable_keys($tableName)
942
            );
943
        }
944
945
        // Disable autocommit for faster reload
946
        if ($this->dumpSettings['no-autocommit']) {
947
            $this->compressManager->write(
948
                $this->typeAdapter->start_disable_autocommit()
949
            );
950
        }
951
952
        return;
953
    }
954
955
    /**
956
     * Table rows extractor, close locks and commits after dump
957
     *
958
     * @param string $tableName  Name of table to export
959
     *
960
     * @return null
961
     */
962
    function endListValues($tableName)
963
    {
964
        if ($this->dumpSettings['disable-keys']) {
965
            $this->compressManager->write(
966
                $this->typeAdapter->end_add_disable_keys($tableName)
967
            );
968
        }
969
970
        if ($this->dumpSettings['add-locks']) {
971
            $this->compressManager->write(
972
                $this->typeAdapter->end_add_lock_table($tableName)
973
            );
974
        }
975
976
        if ($this->dumpSettings['single-transaction']) {
977
            $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...
978
        }
979
980
        if ($this->dumpSettings['lock-tables']) {
981
            $this->typeAdapter->unlock_table($tableName);
982
        }
983
984
        // Commit to enable autocommit
985
        if ($this->dumpSettings['no-autocommit']) {
986
            $this->compressManager->write(
987
                $this->typeAdapter->end_disable_autocommit()
988
            );
989
        }
990
991
        $this->compressManager->write(PHP_EOL);
992
993
        return;
994
    }
995
996
    /**
997
     * Build SQL List of all columns on current table
998
     *
999
     * @param string $tableName  Name of table to get columns
1000
     *
1001
     * @return string SQL sentence with columns
1002
     */
1003
    function getColumnStmt($tableName)
1004
    {
1005
        $colStmt = array();
1006
        foreach($this->tableColumnTypes[$tableName] as $colName => $colType) {
1007
            if ($colType['type'] == 'bit' && $this->dumpSettings['hex-blob']) {
1008
                $colStmt[] = "LPAD(HEX(`${colName}`),2,'0') AS `${colName}`";
1009
            } else if ($colType['is_blob'] && $this->dumpSettings['hex-blob']) {
1010
                $colStmt[] = "HEX(`${colName}`) AS `${colName}`";
1011
            } else if ($colType['is_virtual']) {
1012
                $this->dumpSettings['complete-insert'] = true;
1013
                continue;
1014
            } else {
1015
                $colStmt[] = "`${colName}`";
1016
            }
1017
        }
1018
1019
        return $colStmt;
1020
    }
1021
}
1022
1023
/**
1024
 * Enum with all available compression methods
1025
 *
1026
 */
1027
abstract class CompressMethod
1028
{
1029
    public static $enums = array(
1030
        "None",
1031
        "Gzip",
1032
        "Bzip2"
1033
    );
1034
1035
    /**
1036
     * @param string $c
1037
     * @return boolean
1038
     */
1039
    public static function isValid($c)
1040
    {
1041
        return in_array($c, self::$enums);
1042
    }
1043
}
1044
1045
abstract class CompressManagerFactory
1046
{
1047
    /**
1048
     * @param string $c
1049
     * @return CompressBzip2|CompressGzip|CompressNone
1050
     */
1051 View Code Duplication
    public static function create($c)
1052
    {
1053
        $c = ucfirst(strtolower($c));
1054
        if (! CompressMethod::isValid($c)) {
1055
            throw new Exception("Compression method ($c) is not defined yet");
1056
        }
1057
1058
        $method =  __NAMESPACE__ . "\\" . "Compress" . $c;
1059
1060
        return new $method;
1061
    }
1062
}
1063
1064 View Code Duplication
class CompressBzip2 extends CompressManagerFactory
1065
{
1066
    private $fileHandler = null;
1067
1068
    public function __construct()
1069
    {
1070
        if (! function_exists("bzopen")) {
1071
            throw new Exception("Compression is enabled, but bzip2 lib is not installed or configured properly");
1072
        }
1073
    }
1074
1075
    /**
1076
     * @param string $filename
1077
     */
1078
    public function open($filename)
1079
    {
1080
        $this->fileHandler = bzopen($filename, "w");
1081
        if (false === $this->fileHandler) {
1082
            throw new Exception("Output file is not writable");
1083
        }
1084
1085
        return true;
1086
    }
1087
1088
    public function write($str)
1089
    {
1090
        if (false === ($bytesWritten = bzwrite($this->fileHandler, $str))) {
1091
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1092
        }
1093
        return $bytesWritten;
1094
    }
1095
1096
    public function close()
1097
    {
1098
        return bzclose($this->fileHandler);
1099
    }
1100
}
1101
1102 View Code Duplication
class CompressGzip extends CompressManagerFactory
1103
{
1104
    private $fileHandler = null;
1105
1106
    public function __construct()
1107
    {
1108
        if (! function_exists("gzopen")) {
1109
            throw new Exception("Compression is enabled, but gzip lib is not installed or configured properly");
1110
        }
1111
    }
1112
1113
    /**
1114
     * @param string $filename
1115
     */
1116
    public function open($filename)
1117
    {
1118
        $this->fileHandler = gzopen($filename, "wb");
1119
        if (false === $this->fileHandler) {
1120
            throw new Exception("Output file is not writable");
1121
        }
1122
1123
        return true;
1124
    }
1125
1126
    public function write($str)
1127
    {
1128
        if (false === ($bytesWritten = gzwrite($this->fileHandler, $str))) {
1129
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1130
        }
1131
        return $bytesWritten;
1132
    }
1133
1134
    public function close()
1135
    {
1136
        return gzclose($this->fileHandler);
1137
    }
1138
}
1139
1140
class CompressNone extends CompressManagerFactory
1141
{
1142
    private $fileHandler = null;
1143
1144
    /**
1145
     * @param string $filename
1146
     */
1147
    public function open($filename)
1148
    {
1149
        $this->fileHandler = fopen($filename, "wb");
1150
        if (false === $this->fileHandler) {
1151
            throw new Exception("Output file is not writable");
1152
        }
1153
1154
        return true;
1155
    }
1156
1157
    public function write($str)
1158
    {
1159
        if (false === ($bytesWritten = fwrite($this->fileHandler, $str))) {
1160
            throw new Exception("Writting to file failed! Probably, there is no more free space left?");
1161
        }
1162
        return $bytesWritten;
1163
    }
1164
1165
    public function close()
1166
    {
1167
        return fclose($this->fileHandler);
1168
    }
1169
}
1170
1171
/**
1172
 * Enum with all available TypeAdapter implementations
1173
 *
1174
 */
1175
abstract class TypeAdapter
1176
{
1177
    public static $enums = array(
1178
        "Sqlite",
1179
        "Mysql"
1180
    );
1181
1182
    /**
1183
     * @param string $c
1184
     * @return boolean
1185
     */
1186
    public static function isValid($c)
1187
    {
1188
        return in_array($c, self::$enums);
1189
    }
1190
}
1191
1192
/**
1193
 * TypeAdapter Factory
1194
 *
1195
 */
1196
abstract class TypeAdapterFactory
1197
{
1198
    /**
1199
     * @param string $c Type of database factory to create (Mysql, Sqlite,...)
1200
     * @param PDO $dbHandler
1201
     */
1202 View Code Duplication
    public static function create($c, $dbHandler = null)
1203
    {
1204
        $c = ucfirst(strtolower($c));
1205
        if (! TypeAdapter::isValid($c)) {
1206
            throw new Exception("Database type support for ($c) not yet available");
1207
        }
1208
        $method =  __NAMESPACE__ . "\\" . "TypeAdapter" . $c;
1209
        return new $method($dbHandler);
1210
    }
1211
1212
    /**
1213
     * function databases Add sql to create and use database
1214
     * @todo make it do something with sqlite
1215
     */
1216
    public function databases()
1217
    {
1218
        return "";
1219
    }
1220
1221
    public function show_create_table($tableName)
1222
    {
1223
        return "SELECT tbl_name as 'Table', sql as 'Create Table' " .
1224
            "FROM sqlite_master " .
1225
            "WHERE type='table' AND tbl_name='$tableName'";
1226
    }
1227
1228
    /**
1229
     * function create_table Get table creation code from database
1230
     * @todo make it do something with sqlite
1231
     */
1232
    public function create_table($row, $dumpSettings)
1233
    {
1234
        return "";
1235
    }
1236
1237
    public function show_create_view($viewName)
1238
    {
1239
        return "SELECT tbl_name as 'View', sql as 'Create View' " .
1240
            "FROM sqlite_master " .
1241
            "WHERE type='view' AND tbl_name='$viewName'";
1242
    }
1243
1244
    /**
1245
     * function create_view Get view creation code from database
1246
     * @todo make it do something with sqlite
1247
     */
1248
    public function create_view($row)
1249
    {
1250
        return "";
1251
    }
1252
1253
    /**
1254
     * function show_create_trigger Get trigger creation code from database
1255
     * @todo make it do something with sqlite
1256
     */
1257
    public function show_create_trigger($triggerName)
1258
    {
1259
        return "";
1260
    }
1261
1262
    /**
1263
     * function create_trigger Modify trigger code, add delimiters, etc
1264
     * @todo make it do something with sqlite
1265
     */
1266
    public function create_trigger($triggerName)
1267
    {
1268
        return "";
1269
    }
1270
1271
    /**
1272
     * function create_procedure Modify procedure code, add delimiters, etc
1273
     * @todo make it do something with sqlite
1274
     */
1275
    public function create_procedure($procedureName, $dumpSettings)
1276
    {
1277
        return "";
1278
    }
1279
1280
    public function show_tables()
1281
    {
1282
        return "SELECT tbl_name FROM sqlite_master WHERE type='table'";
1283
    }
1284
1285
    public function show_views()
1286
    {
1287
        return "SELECT tbl_name FROM sqlite_master WHERE type='view'";
1288
    }
1289
1290
    public function show_triggers()
1291
    {
1292
        return "SELECT name FROM sqlite_master WHERE type='trigger'";
1293
    }
1294
1295
    public function show_columns()
1296
    {
1297
        if (func_num_args() != 1) {
1298
            return "";
1299
        }
1300
1301
        $args = func_get_args();
1302
1303
        return "pragma table_info(${args[0]})";
1304
    }
1305
1306
    public function show_procedures()
1307
    {
1308
        return "";
1309
    }
1310
1311
    public function show_events()
1312
    {
1313
        return "";
1314
    }
1315
1316
    public function setup_transaction()
1317
    {
1318
        return "";
1319
    }
1320
1321
    public function start_transaction()
1322
    {
1323
        return "BEGIN EXCLUSIVE";
1324
    }
1325
1326
    public function commit_transaction()
1327
    {
1328
        return "COMMIT";
1329
    }
1330
1331
    public function lock_table()
1332
    {
1333
        return "";
1334
    }
1335
1336
    public function unlock_table()
1337
    {
1338
        return "";
1339
    }
1340
1341
    public function start_add_lock_table()
1342
    {
1343
        return PHP_EOL;
1344
    }
1345
1346
    public function end_add_lock_table()
1347
    {
1348
        return PHP_EOL;
1349
    }
1350
1351
    public function start_add_disable_keys()
1352
    {
1353
        return PHP_EOL;
1354
    }
1355
1356
    public function end_add_disable_keys()
1357
    {
1358
        return PHP_EOL;
1359
    }
1360
1361
    public function start_disable_foreign_keys_check()
1362
    {
1363
        return PHP_EOL;
1364
    }
1365
1366
    public function end_disable_foreign_keys_check()
1367
    {
1368
        return PHP_EOL;
1369
    }
1370
1371
    public function add_drop_database()
1372
    {
1373
        return PHP_EOL;
1374
    }
1375
1376
    public function add_drop_trigger()
1377
    {
1378
        return PHP_EOL;
1379
    }
1380
1381
    public function drop_table()
1382
    {
1383
        return PHP_EOL;
1384
    }
1385
1386
    public function drop_view()
1387
    {
1388
        return PHP_EOL;
1389
    }
1390
1391
    /**
1392
     * Decode column metadata and fill info structure.
1393
     * type, is_numeric and is_blob will always be available.
1394
     *
1395
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1396
     * @return array
1397
     */
1398
    public function parseColumnType($colType)
1399
    {
1400
        return array();
1401
    }
1402
1403
    public function backup_parameters()
1404
    {
1405
        return PHP_EOL;
1406
    }
1407
1408
    public function restore_parameters()
1409
    {
1410
        return PHP_EOL;
1411
    }
1412
}
1413
1414
class TypeAdapterPgsql extends TypeAdapterFactory
1415
{
1416
}
1417
1418
class TypeAdapterDblib extends TypeAdapterFactory
1419
{
1420
}
1421
1422
class TypeAdapterSqlite extends TypeAdapterFactory
1423
{
1424
}
1425
1426
class TypeAdapterMysql extends TypeAdapterFactory
1427
{
1428
1429
    private $dbHandler = null;
1430
1431
    // Numerical Mysql types
1432
    public $mysqlTypes = array(
1433
        'numerical' => array(
1434
            'bit',
1435
            'tinyint',
1436
            'smallint',
1437
            'mediumint',
1438
            'int',
1439
            'integer',
1440
            'bigint',
1441
            'real',
1442
            'double',
1443
            'float',
1444
            'decimal',
1445
            'numeric'
1446
        ),
1447
        'blob' => array(
1448
            'tinyblob',
1449
            'blob',
1450
            'mediumblob',
1451
            'longblob',
1452
            'binary',
1453
            'varbinary',
1454
            'bit',
1455
            'geometry', /* http://bugs.mysql.com/bug.php?id=43544 */
1456
            'point',
1457
            'linestring',
1458
            'polygon',
1459
            'multipoint',
1460
            'multilinestring',
1461
            'multipolygon',
1462
            'geometrycollection',
1463
        )
1464
    );
1465
1466
    public function __construct ($dbHandler)
1467
    {
1468
        $this->dbHandler = $dbHandler;
1469
    }
1470
1471
    public function databases()
1472
    {
1473
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1474
        $args = func_get_args();
1475
        $databaseName = $args[0];
1476
1477
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'character_set_database';");
1478
        $characterSet = $resultSet->fetchColumn(1);
1479
        $resultSet->closeCursor();
1480
1481
        $resultSet = $this->dbHandler->query("SHOW VARIABLES LIKE 'collation_database';");
1482
        $collationDb = $resultSet->fetchColumn(1);
1483
        $resultSet->closeCursor();
1484
        $ret = "";
1485
1486
        $ret .= "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `${databaseName}`".
1487
            " /*!40100 DEFAULT CHARACTER SET ${characterSet} " .
1488
            " COLLATE ${collationDb} */;" . PHP_EOL . PHP_EOL .
1489
            "USE `${databaseName}`;" . PHP_EOL . PHP_EOL;
1490
1491
        return $ret;
1492
    }
1493
1494
    public function show_create_table($tableName)
1495
    {
1496
        return "SHOW CREATE TABLE `$tableName`";
1497
    }
1498
1499
    public function show_create_view($viewName)
1500
    {
1501
        return "SHOW CREATE VIEW `$viewName`";
1502
    }
1503
1504
    public function show_create_trigger($triggerName)
1505
    {
1506
        return "SHOW CREATE TRIGGER `$triggerName`";
1507
    }
1508
1509
    public function show_create_procedure($procedureName)
1510
    {
1511
        return "SHOW CREATE PROCEDURE `$procedureName`";
1512
    }
1513
1514
    public function show_create_event($eventName)
1515
    {
1516
        return "SHOW CREATE EVENT `$eventName`";
1517
    }
1518
1519
    public function create_table( $row, $dumpSettings )
1520
    {
1521
        if ( !isset($row['Create Table']) ) {
1522
            throw new Exception("Error getting table code, unknown output");
1523
        }
1524
1525
        $createTable = $row['Create Table'];
1526
        if ( $dumpSettings['reset-auto-increment'] ) {
1527
            $match = "/AUTO_INCREMENT=[0-9]+/s";
1528
            $replace = "";
1529
            $createTable = preg_replace($match, $replace, $createTable);
1530
        }
1531
1532
        $ret = "/*!40101 SET @saved_cs_client     = @@character_set_client */;" . PHP_EOL .
1533
            "/*!40101 SET character_set_client = " . $dumpSettings['default-character-set'] . " */;" . PHP_EOL .
1534
            $createTable . ";" . PHP_EOL .
1535
            "/*!40101 SET character_set_client = @saved_cs_client */;" . PHP_EOL .
1536
            PHP_EOL;
1537
        return $ret;
1538
    }
1539
1540
    public function create_view($row)
1541
    {
1542
        $ret = "";
1543
        if (!isset($row['Create View'])) {
1544
                throw new Exception("Error getting view structure, unknown output");
1545
        }
1546
1547
        $triggerStmt = $row['Create View'];
1548
1549
        $triggerStmtReplaced1 = str_replace(
1550
            "CREATE ALGORITHM",
1551
            "/*!50001 CREATE ALGORITHM",
1552
            $triggerStmt
1553
        );
1554
        $triggerStmtReplaced2 = str_replace(
1555
            " DEFINER=",
1556
            " */" . PHP_EOL . "/*!50013 DEFINER=",
1557
            $triggerStmtReplaced1
1558
        );
1559
        $triggerStmtReplaced3 = str_replace(
1560
            " VIEW ",
1561
            " */" . PHP_EOL . "/*!50001 VIEW ",
1562
            $triggerStmtReplaced2
1563
        );
1564
        if (false === $triggerStmtReplaced1 ||
1565
            false === $triggerStmtReplaced2 ||
1566
            false === $triggerStmtReplaced3) {
1567
            $triggerStmtReplaced = $triggerStmt;
1568
        } else {
1569
            $triggerStmtReplaced = $triggerStmtReplaced3 . " */;";
1570
        }
1571
1572
        $ret .= $triggerStmtReplaced . PHP_EOL . PHP_EOL;
1573
        return $ret;
1574
    }
1575
1576
    public function create_trigger($row)
1577
    {
1578
        $ret = "";
1579
        if (!isset($row['SQL Original Statement'])) {
1580
            throw new Exception("Error getting trigger code, unknown output");
1581
        }
1582
1583
        $triggerStmt = $row['SQL Original Statement'];
1584
        $triggerStmtReplaced = str_replace(
1585
            "CREATE DEFINER",
1586
            "/*!50003 CREATE*/ /*!50017 DEFINER",
1587
            $triggerStmt
1588
        );
1589
        $triggerStmtReplaced = str_replace(
1590
            " TRIGGER",
1591
            "*/ /*!50003 TRIGGER",
1592
            $triggerStmtReplaced
1593
        );
1594
        if ( false === $triggerStmtReplaced ) {
1595
            $triggerStmtReplaced = $triggerStmt . " /* ";
1596
        }
1597
1598
        $ret .= "DELIMITER ;;" . PHP_EOL .
1599
            $triggerStmtReplaced . " */ ;;" . PHP_EOL .
1600
            "DELIMITER ;" . PHP_EOL . PHP_EOL;
1601
        return $ret;
1602
    }
1603
1604
    public function create_procedure($row, $dumpSettings)
1605
    {
1606
        $ret = "";
1607
        if (!isset($row['Create Procedure'])) {
1608
            throw new Exception("Error getting procedure code, unknown output. " .
1609
                "Please check 'https://bugs.mysql.com/bug.php?id=14564'");
1610
        }
1611
        $procedureStmt = $row['Create Procedure'];
1612
1613
        $ret .= "/*!50003 DROP PROCEDURE IF EXISTS `" .
1614
            $row['Procedure'] . "` */;" . PHP_EOL .
1615
            "/*!40101 SET @saved_cs_client     = @@character_set_client */;" . PHP_EOL .
1616
            "/*!40101 SET character_set_client = " . $dumpSettings['default-character-set'] . " */;" . PHP_EOL .
1617
            "DELIMITER ;;" . PHP_EOL .
1618
            $procedureStmt . " ;;" . PHP_EOL .
1619
            "DELIMITER ;" . PHP_EOL .
1620
            "/*!40101 SET character_set_client = @saved_cs_client */;" . PHP_EOL . PHP_EOL;
1621
1622
        return $ret;
1623
    }
1624
1625
    public function create_event($row)
1626
    {
1627
        $ret = "";
1628
        if ( !isset($row['Create Event']) ) {
1629
            throw new Exception("Error getting event code, unknown output. " .
1630
                "Please check 'http://stackoverflow.com/questions/10853826/mysql-5-5-create-event-gives-syntax-error'");
1631
        }
1632
        $eventName = $row['Event'];
1633
        $eventStmt = $row['Create Event'];
1634
        $sqlMode = $row['sql_mode'];
1635
1636
        $eventStmtReplaced = str_replace(
1637
            "CREATE DEFINER",
1638
            "/*!50106 CREATE*/ /*!50117 DEFINER",
1639
            $eventStmt
1640
        );
1641
        $eventStmtReplaced = str_replace(
1642
            " EVENT ",
1643
            "*/ /*!50106 EVENT ",
1644
            $eventStmtReplaced
1645
        );
1646
1647
        if ( false === $eventStmtReplaced ) {
1648
            $eventStmtReplaced = $eventStmt . " /* ";
1649
        }
1650
1651
        $ret .= "/*!50106 SET @save_time_zone= @@TIME_ZONE */ ;" . PHP_EOL .
1652
            "/*!50106 DROP EVENT IF EXISTS `" . $eventName . "` */;" . PHP_EOL .
1653
            "DELIMITER ;;" . PHP_EOL .
1654
            "/*!50003 SET @saved_cs_client      = @@character_set_client */ ;;" . PHP_EOL .
1655
            "/*!50003 SET @saved_cs_results     = @@character_set_results */ ;;" . PHP_EOL .
1656
            "/*!50003 SET @saved_col_connection = @@collation_connection */ ;;" . PHP_EOL .
1657
            "/*!50003 SET character_set_client  = utf8 */ ;;" . PHP_EOL .
1658
            "/*!50003 SET character_set_results = utf8 */ ;;" . PHP_EOL .
1659
            "/*!50003 SET collation_connection  = utf8_general_ci */ ;;" . PHP_EOL .
1660
            "/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;;" . PHP_EOL .
1661
            "/*!50003 SET sql_mode              = '" . $sqlMode . "' */ ;;" . PHP_EOL .
1662
            "/*!50003 SET @saved_time_zone      = @@time_zone */ ;;" . PHP_EOL .
1663
            "/*!50003 SET time_zone             = 'SYSTEM' */ ;;" . PHP_EOL .
1664
            $eventStmtReplaced . " */ ;;" . PHP_EOL .
1665
            "/*!50003 SET time_zone             = @saved_time_zone */ ;;" . PHP_EOL .
1666
            "/*!50003 SET sql_mode              = @saved_sql_mode */ ;;" . PHP_EOL .
1667
            "/*!50003 SET character_set_client  = @saved_cs_client */ ;;" . PHP_EOL .
1668
            "/*!50003 SET character_set_results = @saved_cs_results */ ;;" . PHP_EOL .
1669
            "/*!50003 SET collation_connection  = @saved_col_connection */ ;;" . PHP_EOL .
1670
            "DELIMITER ;" . PHP_EOL .
1671
            "/*!50106 SET TIME_ZONE= @save_time_zone */ ;" . PHP_EOL . PHP_EOL;
1672
            // Commented because we are doing this in restore_parameters()
1673
            // "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL . PHP_EOL;
1674
1675
        return $ret;
1676
    }
1677
1678 View Code Duplication
    public function show_tables()
1679
    {
1680
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1681
        $args = func_get_args();
1682
        return "SELECT TABLE_NAME AS tbl_name " .
1683
            "FROM INFORMATION_SCHEMA.TABLES " .
1684
            "WHERE TABLE_TYPE='BASE TABLE' AND TABLE_SCHEMA='${args[0]}'";
1685
    }
1686
1687 View Code Duplication
    public function show_views()
1688
    {
1689
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1690
        $args = func_get_args();
1691
        return "SELECT TABLE_NAME AS tbl_name " .
1692
            "FROM INFORMATION_SCHEMA.TABLES " .
1693
            "WHERE TABLE_TYPE='VIEW' AND TABLE_SCHEMA='${args[0]}'";
1694
    }
1695
1696
    public function show_triggers()
1697
    {
1698
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1699
        $args = func_get_args();
1700
        return "SHOW TRIGGERS FROM `${args[0]}`;";
1701
    }
1702
1703
    public function show_columns()
1704
    {
1705
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1706
        $args = func_get_args();
1707
        return "SHOW COLUMNS FROM `${args[0]}`;";
1708
    }
1709
1710 View Code Duplication
    public function show_procedures()
1711
    {
1712
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1713
        $args = func_get_args();
1714
        return "SELECT SPECIFIC_NAME AS procedure_name " .
1715
            "FROM INFORMATION_SCHEMA.ROUTINES " .
1716
            "WHERE ROUTINE_TYPE='PROCEDURE' AND ROUTINE_SCHEMA='${args[0]}'";
1717
    }
1718
1719
    /**
1720
     * Get query string to ask for names of events from current database.
1721
     *
1722
     * @param string Name of database
1723
     * @return string
1724
     */
1725 View Code Duplication
    public function show_events()
1726
    {
1727
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1728
        $args = func_get_args();
1729
        return "SELECT EVENT_NAME AS event_name " .
1730
            "FROM INFORMATION_SCHEMA.EVENTS " .
1731
            "WHERE EVENT_SCHEMA='${args[0]}'";
1732
    }
1733
1734
    public function setup_transaction()
1735
    {
1736
        return "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
1737
    }
1738
1739
    public function start_transaction()
1740
    {
1741
        return "START TRANSACTION";
1742
    }
1743
1744
    public function commit_transaction()
1745
    {
1746
        return "COMMIT";
1747
    }
1748
1749
    public function lock_table()
1750
    {
1751
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1752
        $args = func_get_args();
1753
        return $this->dbHandler->exec("LOCK TABLES `${args[0]}` READ LOCAL");
1754
1755
    }
1756
1757
    public function unlock_table()
1758
    {
1759
        return $this->dbHandler->exec("UNLOCK TABLES");
1760
    }
1761
1762
    public function start_add_lock_table()
1763
    {
1764
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1765
        $args = func_get_args();
1766
        return "LOCK TABLES `${args[0]}` WRITE;" . PHP_EOL;
1767
    }
1768
1769
    public function end_add_lock_table()
1770
    {
1771
        return "UNLOCK TABLES;" . PHP_EOL;
1772
    }
1773
1774 View Code Duplication
    public function start_add_disable_keys()
1775
    {
1776
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1777
        $args = func_get_args();
1778
        return "/*!40000 ALTER TABLE `${args[0]}` DISABLE KEYS */;" .
1779
            PHP_EOL;
1780
    }
1781
1782 View Code Duplication
    public function end_add_disable_keys()
1783
    {
1784
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1785
        $args = func_get_args();
1786
        return "/*!40000 ALTER TABLE `${args[0]}` ENABLE KEYS */;" .
1787
            PHP_EOL;
1788
    }
1789
1790
    public function start_disable_autocommit()
1791
    {
1792
        return "SET autocommit=0;" . PHP_EOL;
1793
    }
1794
1795
    public function end_disable_autocommit()
1796
    {
1797
        return "COMMIT;" . PHP_EOL;
1798
    }
1799
1800 View Code Duplication
    public function add_drop_database()
1801
    {
1802
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1803
        $args = func_get_args();
1804
        return "/*!40000 DROP DATABASE IF EXISTS `${args[0]}`*/;" .
1805
            PHP_EOL . PHP_EOL;
1806
    }
1807
1808
    public function add_drop_trigger()
1809
    {
1810
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1811
        $args = func_get_args();
1812
        return "DROP TRIGGER IF EXISTS `${args[0]}`;" . PHP_EOL;
1813
    }
1814
1815
    public function drop_table()
1816
    {
1817
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1818
        $args = func_get_args();
1819
        return "DROP TABLE IF EXISTS `${args[0]}`;" . PHP_EOL;
1820
    }
1821
1822 View Code Duplication
    public function drop_view()
1823
    {
1824
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1825
        $args = func_get_args();
1826
        return "DROP TABLE IF EXISTS `${args[0]}`;" . PHP_EOL .
1827
                "/*!50001 DROP VIEW IF EXISTS `${args[0]}`*/;" . PHP_EOL;
1828
    }
1829
1830
    public function getDatabaseHeader()
1831
    {
1832
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1833
        $args = func_get_args();
1834
        return "--" . PHP_EOL .
1835
            "-- Current Database: `${args[0]}`" . PHP_EOL .
1836
            "--" . PHP_EOL . PHP_EOL;
1837
    }
1838
1839
    /**
1840
     * Decode column metadata and fill info structure.
1841
     * type, is_numeric and is_blob will always be available.
1842
     *
1843
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1844
     * @return array
1845
     */
1846
    public function parseColumnType($colType)
1847
    {
1848
        $colInfo = array();
1849
        $colParts = explode(" ", $colType['Type']);
1850
1851
        if($fparen = strpos($colParts[0], "("))
1852
        {
1853
            $colInfo['type'] = substr($colParts[0], 0, $fparen);
1854
            $colInfo['length']  = str_replace(")", "", substr($colParts[0], $fparen+1));
1855
            $colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
1856
        }
1857
        else
1858
        {
1859
            $colInfo['type'] = $colParts[0];
1860
        }
1861
        $colInfo['is_numeric'] = in_array($colInfo['type'], $this->mysqlTypes['numerical']);
1862
        $colInfo['is_blob'] = in_array($colInfo['type'], $this->mysqlTypes['blob']);
1863
        $colInfo['is_virtual'] = strpos($colType['Extra'], "STORED GENERATED") === false ? false : true;
1864
        return $colInfo;
1865
    }
1866
1867
    public function backup_parameters()
1868
    {
1869
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1870
        $args = func_get_args();
1871
        $dumpSettings = $args[0];
1872
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" . PHP_EOL .
1873
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" . PHP_EOL .
1874
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" . PHP_EOL .
1875
            "/*!40101 SET NAMES " . $dumpSettings['default-character-set'] . " */;" . PHP_EOL;
1876
1877
        if (false === $dumpSettings['skip-tz-utc']) {
1878
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;" . PHP_EOL .
1879
                "/*!40103 SET TIME_ZONE='+00:00' */;" . PHP_EOL;
1880
        }
1881
1882
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;" . PHP_EOL .
1883
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;" . PHP_EOL .
1884
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;" . PHP_EOL .
1885
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;" . PHP_EOL .PHP_EOL;
1886
1887
        return $ret;
1888
    }
1889
1890
    public function restore_parameters()
1891
    {
1892
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1893
        $args = func_get_args();
1894
        $dumpSettings = $args[0];
1895
        $ret = "";
1896
1897
        if (false === $dumpSettings['skip-tz-utc']) {
1898
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL;
1899
        }
1900
1901
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;" . PHP_EOL .
1902
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;" . PHP_EOL .
1903
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;" . PHP_EOL .
1904
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;" . PHP_EOL .
1905
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;" . PHP_EOL .
1906
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;" . PHP_EOL .
1907
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;" . PHP_EOL . PHP_EOL;
1908
1909
        return $ret;
1910
    }
1911
1912
    /**
1913
     * Check number of parameters passed to function, useful when inheriting.
1914
     * Raise exception if unexpected.
1915
     *
1916
     * @param integer $num_args
1917
     * @param integer $expected_num_args
1918
     * @param string $method_name
1919
     */
1920
    private function check_parameters($num_args, $expected_num_args, $method_name)
1921
    {
1922
        if ( $num_args != $expected_num_args ) {
1923
            throw new Exception("Unexpected parameter passed to $method_name");
1924
        }
1925
        return;
1926
    }
1927
}
1928