Passed
Push — master ( 11a4ac...376676 )
by diego
03:11
created

TypeAdapterMysql   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 470
rs 6
c 0
b 0
f 0
wmc 55

37 Methods

Rating   Name   Duplication   Size   Complexity  
A unlock_table() 0 3 1
A databases() 0 21 1
A end_add_lock_table() 0 3 1
A show_events() 0 7 1
A show_create_view() 0 3 1
A start_add_disable_keys() 0 6 1
A drop_table() 0 5 1
A show_create_procedure() 0 3 1
A check_parameters() 0 6 2
A commit_transaction() 0 3 1
A start_transaction() 0 3 1
A show_procedures() 0 7 1
A create_table() 0 19 3
A restore_parameters() 0 17 2
A show_triggers() 0 5 1
B create_trigger() 0 22 4
A add_drop_database() 0 6 1
A start_add_lock_table() 0 5 1
A drop_view() 0 6 1
A end_disable_autocommit() 0 3 1
A show_create_event() 0 3 1
A show_tables() 0 7 1
A show_columns() 0 5 1
B create_view() 0 23 4
A start_disable_autocommit() 0 3 1
B create_event() 0 46 4
A parseColumnType() 0 20 4
A backup_parameters() 0 18 2
A getDatabaseHeader() 0 7 1
A end_add_disable_keys() 0 6 1
A show_create_trigger() 0 3 1
A show_create_table() 0 3 1
A show_views() 0 7 1
A create_procedure() 0 19 2
A add_drop_trigger() 0 5 1
A lock_table() 0 5 1
A setup_transaction() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TypeAdapterMysql often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TypeAdapterMysql, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * PHP version of mysqldump cli that comes with MySQL
5
 *
6
 * mysql mysqldump pdo php7 php5 database php sql
7
 *
8
 * @category Library
9
 * @package  Ifsnop\Mysqldump
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
 * PHP version of mysqldump cli that comes with MySQL
24
 *
25
 * mysql mysqldump pdo php7 php5 database php sql
26
 *
27
 * @category Library
28
 * @package  Ifsnop\Mysqldump
29
 * @author   Diego Torres <[email protected]>
30
 * @license  http://www.gnu.org/copyleft/gpl.html GNU General Public License
31
 * @link     https://github.com/ifsnop/mysqldump-php
32
 *
33
 */
34
class Mysqldump
35
{
36
37
    // Same as mysqldump
38
    const MAXLINESIZE = 1000000;
39
40
    // Available compression methods as constants
41
    const GZIP = 'Gzip';
42
    const BZIP2 = 'Bzip2';
43
    const NONE = 'None';
44
45
    // Available connection strings
46
    const UTF8 = 'utf8';
47
    const UTF8MB4 = 'utf8mb4';
48
49
    /**
50
     * Database username
51
     * @var string
52
     */
53
    public $user;
54
    /**
55
     * Database password
56
     * @var string
57
     */
58
    public $pass;
59
    /**
60
     * Connection string for PDO
61
     * @var string
62
     */
63
    public $dsn;
64
    /**
65
     * Destination filename, defaults to stdout
66
     * @var string
67
     */
68
    public $fileName = 'php://output';
69
70
    // Internal stuff
71
    private $tables = array();
72
    private $views = array();
73
    private $triggers = array();
74
    private $procedures = array();
75
    private $events = array();
76
    private $dbHandler = null;
77
    private $dbType = "";
78
    private $compressManager;
79
    private $typeAdapter;
80
    private $dumpSettings = array();
81
    private $pdoSettings = array();
82
    private $version;
83
    private $tableColumnTypes = array();
84
    /**
85
     * database name, parsed from dsn
86
     * @var string
87
     */
88
    private $dbName;
89
    /**
90
     * host name, parsed from dsn
91
     * @var string
92
     */
93
    private $host;
94
    /**
95
     * dsn string parsed as an array
96
     * @var array
97
     */
98
    private $dsnArray = array();
99
100
    /**
101
     * Constructor of Mysqldump. Note that in the case of an SQLite database
102
     * connection, the filename must be in the $db parameter.
103
     *
104
     * @param string $dsn        PDO DSN connection string
105
     * @param string $user       SQL account username
106
     * @param string $pass       SQL account password
107
     * @param array  $dumpSettings SQL database settings
108
     * @param array  $pdoSettings  PDO configured attributes
109
     */
110
    public function __construct(
111
        $dsn = '',
112
        $user = '',
113
        $pass = '',
114
        $dumpSettings = array(),
115
        $pdoSettings = array()
116
    ) {
117
        $dumpSettingsDefault = array(
118
            'include-tables' => array(),
119
            'exclude-tables' => array(),
120
            'compress' => Mysqldump::NONE,
121
            'init_commands' => array(),
122
            'no-data' => array(),
123
            'reset-auto-increment' => false,
124
            'add-drop-database' => false,
125
            'add-drop-table' => false,
126
            'add-drop-trigger' => true,
127
            'add-locks' => true,
128
            'complete-insert' => false,
129
            'databases' => false,
130
            'default-character-set' => Mysqldump::UTF8,
131
            'disable-keys' => true,
132
            'extended-insert' => true,
133
            'events' => false,
134
            'hex-blob' => true, /* faster than escaped content */
135
            'net_buffer_length' => self::MAXLINESIZE,
136
            'no-autocommit' => true,
137
            'no-create-info' => false,
138
            'lock-tables' => true,
139
            'routines' => false,
140
            'single-transaction' => true,
141
            'skip-triggers' => false,
142
            'skip-tz-utc' => false,
143
            'skip-comments' => false,
144
            'skip-dump-date' => false,
145
            'skip-definer' => false,
146
            'where' => '',
147
            /* deprecated */
148
            'disable-foreign-keys-check' => true
149
        );
150
151
        $pdoSettingsDefault = array(
152
            PDO::ATTR_PERSISTENT => true,
153
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
154
        );
155
156
        $this->user = $user;
157
        $this->pass = $pass;
158
        $this->parseDsn($dsn);
159
160
        // this drops MYSQL dependency, only use the constant if it's defined
161
        if ( "mysql" === $this->dbType ) {
0 ignored issues
show
introduced by
The condition 'mysql' === $this->dbType is always false.
Loading history...
162
            $pdoSettingsDefault[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false;
163
        }
164
165
        $this->pdoSettings = self::array_replace_recursive($pdoSettingsDefault, $pdoSettings);
166
        $this->dumpSettings = self::array_replace_recursive($dumpSettingsDefault, $dumpSettings);
167
        $this->dumpSettings['init_commands'][] = "SET NAMES " . $this->dumpSettings['default-character-set'];
168
169
        if (false === $this->dumpSettings['skip-tz-utc']) {
170
            $this->dumpSettings['init_commands'][] = "SET TIME_ZONE='+00:00'";
171
        }
172
173
        $diff = array_diff(array_keys($this->dumpSettings), array_keys($dumpSettingsDefault));
174
        if (count($diff)>0) {
175
            throw new Exception("Unexpected value in dumpSettings: (" . implode(",", $diff) . ")");
176
        }
177
178
        if ( !is_array($this->dumpSettings['include-tables']) ||
179
            !is_array($this->dumpSettings['exclude-tables']) ) {
180
            throw new Exception("Include-tables and exclude-tables should be arrays");
181
        }
182
183
        // Dump the same views as tables, mimic mysqldump behaviour
184
        $this->dumpSettings['include-views'] = $this->dumpSettings['include-tables'];
185
186
        // Create a new compressManager to manage compressed output
187
        $this->compressManager = CompressManagerFactory::create($this->dumpSettings['compress']);
188
    }
189
190
    /**
191
     * Destructor of Mysqldump. Unsets dbHandlers and database objects.
192
     *
193
     */
194
    public function __destruct()
195
    {
196
        $this->dbHandler = null;
197
    }
198
199
    /**
200
     * Custom array_replace_recursive to be used if PHP < 5.3
201
     * Replaces elements from passed arrays into the first array recursively
202
     *
203
     * @param array $array1 The array in which elements are replaced
204
     * @param array $array2 The array from which elements will be extracted
205
     *
206
     * @return array Returns an array, or NULL if an error occurs.
207
     */
208
    public static function array_replace_recursive($array1, $array2)
209
    {
210
        if (function_exists('array_replace_recursive')) {
211
            return array_replace_recursive($array1, $array2);
212
        }
213
214
        foreach ($array2 as $key => $value) {
215
            if (is_array($value)) {
216
                $array1[$key] = self::array_replace_recursive($array1[$key], $value);
217
            } else {
218
                $array1[$key] = $value;
219
            }
220
        }
221
        return $array1;
222
    }
223
224
    /**
225
     * Parse DSN string and extract dbname value
226
     * Several examples of a DSN string
227
     *   mysql:host=localhost;dbname=testdb
228
     *   mysql:host=localhost;port=3307;dbname=testdb
229
     *   mysql:unix_socket=/tmp/mysql.sock;dbname=testdb
230
     *
231
     * @param string $dsn dsn string to parse
232
     */
233
    private function parseDsn($dsn)
234
    {
235
        if (empty($dsn) || (false === ($pos = strpos($dsn, ":")))) {
236
            throw new Exception("Empty DSN string");
237
        }
238
239
        $this->dsn = $dsn;
240
        $this->dbType = strtolower(substr($dsn, 0, $pos));
241
242
        if (empty($this->dbType)) {
243
            throw new Exception("Missing database type from DSN string");
244
        }
245
246
        $dsn = substr($dsn, $pos + 1);
247
248
        foreach(explode(";", $dsn) as $kvp) {
249
            $kvpArr = explode("=", $kvp);
250
            $this->dsnArray[strtolower($kvpArr[0])] = $kvpArr[1];
251
        }
252
253
        if (empty($this->dsnArray['host']) &&
254
            empty($this->dsnArray['unix_socket'])) {
255
            throw new Exception("Missing host from DSN string");
256
        }
257
        $this->host = (!empty($this->dsnArray['host'])) ?
258
            $this->dsnArray['host'] :
259
            $this->dsnArray['unix_socket'];
260
261
        if (empty($this->dsnArray['dbname'])) {
262
            throw new Exception("Missing database name from DSN string");
263
        }
264
265
        $this->dbName = $this->dsnArray['dbname'];
266
267
        // safety check
268
        if ( !is_string($this->dbType) ) {
0 ignored issues
show
introduced by
The condition is_string($this->dbType) is always true.
Loading history...
269
            throw new Exception("Invalid database type definition in DSN string");
270
        }
271
272
        return true;
273
    }
274
275
    /**
276
     * Connect with PDO
277
     *
278
     * @return null
279
     */
280
    private function connect()
281
    {
282
        // Connecting with PDO
283
        try {
284
            switch ($this->dbType) {
285
                case 'sqlite':
286
                    $this->dbHandler = @new PDO("sqlite:" . $this->dbName, null, null, $this->pdoSettings);
287
                    break;
288
                case 'mysql':
289
                case 'pgsql':
290
                case 'dblib':
291
                    $this->dbHandler = @new PDO(
292
                        $this->dsn,
293
                        $this->user,
294
                        $this->pass,
295
                        $this->pdoSettings
296
                    );
297
                    // Execute init commands once connected
298
                    foreach($this->dumpSettings['init_commands'] as $stmt) {
299
                        $this->dbHandler->exec($stmt);
300
                    }
301
                    // Store server version
302
                    $this->version = $this->dbHandler->getAttribute(PDO::ATTR_SERVER_VERSION);
303
                    break;
304
                default:
305
                    throw new Exception("Unsupported database type (" . $this->dbType . ")");
0 ignored issues
show
Bug introduced by
Are you sure $this->dbType of type mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1751
     * @return string
1752
     */
1753
    public function show_events()
1754
    {
1755
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1756
        $args = func_get_args();
1757
        return "SELECT EVENT_NAME AS event_name " .
1758
            "FROM INFORMATION_SCHEMA.EVENTS " .
1759
            "WHERE EVENT_SCHEMA='${args[0]}'";
1760
    }
1761
1762
    public function setup_transaction()
1763
    {
1764
        return "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
1765
    }
1766
1767
    public function start_transaction()
1768
    {
1769
        return "START TRANSACTION";
1770
    }
1771
1772
    public function commit_transaction()
1773
    {
1774
        return "COMMIT";
1775
    }
1776
1777
    public function lock_table()
1778
    {
1779
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1780
        $args = func_get_args();
1781
        return $this->dbHandler->exec("LOCK TABLES `${args[0]}` READ LOCAL");
1782
1783
    }
1784
1785
    public function unlock_table()
1786
    {
1787
        return $this->dbHandler->exec("UNLOCK TABLES");
1788
    }
1789
1790
    public function start_add_lock_table()
1791
    {
1792
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1793
        $args = func_get_args();
1794
        return "LOCK TABLES `${args[0]}` WRITE;" . PHP_EOL;
1795
    }
1796
1797
    public function end_add_lock_table()
1798
    {
1799
        return "UNLOCK TABLES;" . PHP_EOL;
1800
    }
1801
1802
    public function start_add_disable_keys()
1803
    {
1804
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1805
        $args = func_get_args();
1806
        return "/*!40000 ALTER TABLE `${args[0]}` DISABLE KEYS */;" .
1807
            PHP_EOL;
1808
    }
1809
1810
    public function end_add_disable_keys()
1811
    {
1812
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1813
        $args = func_get_args();
1814
        return "/*!40000 ALTER TABLE `${args[0]}` ENABLE KEYS */;" .
1815
            PHP_EOL;
1816
    }
1817
1818
    public function start_disable_autocommit()
1819
    {
1820
        return "SET autocommit=0;" . PHP_EOL;
1821
    }
1822
1823
    public function end_disable_autocommit()
1824
    {
1825
        return "COMMIT;" . PHP_EOL;
1826
    }
1827
1828
    public function add_drop_database()
1829
    {
1830
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1831
        $args = func_get_args();
1832
        return "/*!40000 DROP DATABASE IF EXISTS `${args[0]}`*/;" .
1833
            PHP_EOL . PHP_EOL;
1834
    }
1835
1836
    public function add_drop_trigger()
1837
    {
1838
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1839
        $args = func_get_args();
1840
        return "DROP TRIGGER IF EXISTS `${args[0]}`;" . PHP_EOL;
1841
    }
1842
1843
    public function drop_table()
1844
    {
1845
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1846
        $args = func_get_args();
1847
        return "DROP TABLE IF EXISTS `${args[0]}`;" . PHP_EOL;
1848
    }
1849
1850
    public function drop_view()
1851
    {
1852
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1853
        $args = func_get_args();
1854
        return "DROP TABLE IF EXISTS `${args[0]}`;" . PHP_EOL .
1855
                "/*!50001 DROP VIEW IF EXISTS `${args[0]}`*/;" . PHP_EOL;
1856
    }
1857
1858
    public function getDatabaseHeader()
1859
    {
1860
        $this->check_parameters(func_num_args(), $expected_num_args = 1, __METHOD__);
1861
        $args = func_get_args();
1862
        return "--" . PHP_EOL .
1863
            "-- Current Database: `${args[0]}`" . PHP_EOL .
1864
            "--" . PHP_EOL . PHP_EOL;
1865
    }
1866
1867
    /**
1868
     * Decode column metadata and fill info structure.
1869
     * type, is_numeric and is_blob will always be available.
1870
     *
1871
     * @param array $colType Array returned from "SHOW COLUMNS FROM tableName"
1872
     * @return array
1873
     */
1874
    public function parseColumnType($colType)
1875
    {
1876
        $colInfo = array();
1877
        $colParts = explode(" ", $colType['Type']);
1878
1879
        if($fparen = strpos($colParts[0], "(")) {
1880
            $colInfo['type'] = substr($colParts[0], 0, $fparen);
1881
            $colInfo['length']  = str_replace(")", "", substr($colParts[0], $fparen+1));
1882
            $colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
1883
        } else {
1884
            $colInfo['type'] = $colParts[0];
1885
        }
1886
        $colInfo['is_numeric'] = in_array($colInfo['type'], $this->mysqlTypes['numerical']);
1887
        $colInfo['is_blob'] = in_array($colInfo['type'], $this->mysqlTypes['blob']);
1888
        // for virtual columns that are of type 'Extra', column type
1889
        // could by "STORED GENERATED" or "VIRTUAL GENERATED"
1890
        // MySQL reference: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
1891
        $colInfo['is_virtual'] = strpos($colType['Extra'], "VIRTUAL GENERATED") !== false || strpos($colType['Extra'], "STORED GENERATED") !== false;
1892
1893
        return $colInfo;
1894
    }
1895
1896
    public function backup_parameters()
1897
    {
1898
        $ret = "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" . PHP_EOL .
1899
            "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" . PHP_EOL .
1900
            "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" . PHP_EOL .
1901
            "/*!40101 SET NAMES " . $this->dumpSettings['default-character-set'] . " */;" . PHP_EOL;
1902
1903
        if (false === $this->dumpSettings['skip-tz-utc']) {
1904
            $ret .= "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;" . PHP_EOL .
1905
                "/*!40103 SET TIME_ZONE='+00:00' */;" . PHP_EOL;
1906
        }
1907
1908
        $ret .= "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;" . PHP_EOL .
1909
            "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;" . PHP_EOL .
1910
            "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;" . PHP_EOL .
1911
            "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;" . PHP_EOL .PHP_EOL;
1912
1913
        return $ret;
1914
    }
1915
1916
    public function restore_parameters()
1917
    {
1918
        $ret = "";
1919
1920
        if (false === $this->dumpSettings['skip-tz-utc']) {
1921
            $ret .= "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;" . PHP_EOL;
1922
        }
1923
1924
        $ret .= "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;" . PHP_EOL .
1925
            "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;" . PHP_EOL .
1926
            "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;" . PHP_EOL .
1927
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;" . PHP_EOL .
1928
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;" . PHP_EOL .
1929
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;" . PHP_EOL .
1930
            "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;" . PHP_EOL . PHP_EOL;
1931
1932
        return $ret;
1933
    }
1934
1935
    /**
1936
     * Check number of parameters passed to function, useful when inheriting.
1937
     * Raise exception if unexpected.
1938
     *
1939
     * @param integer $num_args
1940
     * @param integer $expected_num_args
1941
     * @param string $method_name
1942
     */
1943
    private function check_parameters($num_args, $expected_num_args, $method_name)
1944
    {
1945
        if ( $num_args != $expected_num_args ) {
1946
            throw new Exception("Unexpected parameter passed to $method_name");
1947
        }
1948
        return;
1949
    }
1950
}
1951