Exporter::process()   B
last analyzed

Complexity

Conditions 9
Paths 90

Size

Total Lines 71
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 9.0368

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 34
c 1
b 0
f 0
nc 90
nop 1
dl 0
loc 71
ccs 24
cts 26
cp 0.9231
crap 9.0368
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of dimtrovich/db-dumper".
5
 *
6
 * (c) 2024 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Dimtrovich\DbDumper;
13
14
use Dimtrovich\DbDumper\Exceptions\Exception;
15
use PDO;
16
17
/**
18
 * @method void onTableExport(callable(string $tableName, int $rowCount) $callback)
19
 */
20
class Exporter
21
{
22
    use Dumper;
0 ignored issues
show
introduced by
The trait Dimtrovich\DbDumper\Dumper requires some properties which are not provided by Dimtrovich\DbDumper\Exporter: $init_commands, $compress
Loading history...
23
24
    /**
25
     * List of registered tables
26
     */
27
    private array $tables = [];
28
29
    /**
30
     * List of columns types for tables [$tableName => [$column => $type]]
31
     */
32
    private array $tableColumnTypes = [];
33
34
    /**
35
     * List of registered views
36
     */
37
    private array $views = [];
38
39
    /**
40
     * List of registered triggers
41
     */
42
    private array $triggers = [];
43
44
    /**
45
     * List of registered procedures
46
     */
47
    private array $procedures = [];
48
49
    /**
50
     * List of registered functions
51
     */
52
    private array $functions = [];
53
54
    /**
55
     * List of registered events
56
     */
57
    private array $events = [];
58
59
    /**
60
     * @var callable
61
     */
62
    private $transformTableRowCallable;
63
64
    /**
65
     * Keyed on table name, with the value as the conditions.
66
     * e.g. - 'users' => 'date_registered > NOW() - INTERVAL 6 MONTH'
67
     */
68
    private array $tableWheres = [];
69
70
    private array $tableLimits = [];
71
72
    /**
73
     * Primary function, triggers dumping.
74
     *
75
     * @param string $filename Name of file to write sql dump to
76
     */
77
    public function process(string $filename = 'php://stdout')
78
    {
79
        // Create output file
80 2
        $this->compressor->open($filename);
81
82
        // Write some basic info to output file
83 2
        $this->compressor->write($this->getDumpFileHeader());
84
85
        // initiate a transaction at global level to create a consistent snapshot
86
        if ($this->option->single_transaction) {
87
            if ('' !== $setupTransaction = $this->adapter->setupTransaction()) {
88 2
                $this->pdo->exec($setupTransaction);
89
            }
90
            if ('' !== $startTransaction = $this->adapter->startTransaction()) {
91 2
                $this->pdo->exec($startTransaction);
92
            }
93
        }
94
95
        // Store server settings and use sanner defaults to dump
96 2
        $this->compressor->write($this->adapter->backupParameters());
97
98
        if ($this->option->databases) {
99 2
            $this->compressor->write($this->adapter->getDatabaseHeader($this->database));
100
101
            if ($this->option->add_drop_database) {
102
                $this->compressor->write($this->adapter->addDropDatabase($this->database));
103
            }
104
        }
105
106
        // Get table, view, trigger, procedures, functions and events structures from database.
107 2
        $this->getDatabaseStructureTables();
108 2
        $this->getDatabaseStructureViews();
109 2
        $this->getDatabaseStructureTriggers();
110 2
        $this->getDatabaseStructureProcedures();
111 2
        $this->getDatabaseStructureFunctions();
112 2
        $this->getDatabaseStructureEvents();
113
114
        if ($this->option->databases) {
115 2
            $this->compressor->write($this->adapter->databases($this->database));
116
        }
117
118
        // If there still are some tables/views in include-tables array,
119
        // that means that some tables or views weren't found.
120
        // Give proper error and exit.
121
        // This check will be removed once include-tables supports regexps.
122
        if ($this->option->include_tables !== []) {
123 2
            $name = implode(',', $this->option->include_tables);
124
125
            throw Exception::tableNotFound($name);
126
        }
127
128 2
        $this->exportTables();
129 2
        $this->exportTriggers();
130 2
        $this->exportFunctions();
131 2
        $this->exportProcedures();
132 2
        $this->exportViews();
133 2
        $this->exportEvents();
134
135
        // Restore saved parameters.
136 2
        $this->compressor->write($this->adapter->restoreParameters());
137
138
        // end transaction
139
        if ($this->option->single_transaction) {
140 2
            $this->pdo->exec($this->adapter->commitTransaction());
141
        }
142
143
        // Write some stats to output file.
144 2
        $this->compressor->write($this->getDumpFileFooter());
145
146
        // Close output file.
147 2
        $this->compressor->close();
148
    }
149
150
    /**
151
     * Keyed by table name, with the value as the conditions:
152
     * e.g. 'users' => 'date_registered > NOW() - INTERVAL 6 MONTH AND deleted=0'
153
     */
154
    public function setTableWheres(array $tableWheres)
155
    {
156 2
        $this->tableWheres = $tableWheres;
157
    }
158
159
    /**
160
     * @return bool|mixed
161
     */
162
    public function getTableWhere(string $tableName)
163
    {
164
        if (! empty($this->tableWheres[$tableName])) {
165 2
            return $this->tableWheres[$tableName];
166
        }
167
        if ($this->option->where !== '') {
168 2
            return $this->option->where;
169
        }
170
171 2
        return false;
172
    }
173
174
    /**
175
     * Sets a WHERE condition for a specific table during the export process.
176
     */
177
    public function where(string $table, string $condition): self
178
    {
179 2
        $this->tableWheres[$table] = $condition;
180
181 2
        return $this;
182
    }
183
184
    /**
185
     * Keyed by table name, with the value as the numeric limit:
186
     * e.g. 'users' => 3000
187
     */
188
    public function setTableLimits(array $tableLimits)
189
    {
190 2
        $this->tableLimits = $tableLimits;
191
    }
192
193
    /**
194
     * Returns the LIMIT for the table.
195
     * Must be numeric to be returned.
196
     *
197
     * @return false|int
198
     */
199
    public function getTableLimit(string $tableName)
200
    {
201
        if (! isset($this->tableLimits[$tableName])) {
202 2
            return false;
203
        }
204
205 2
        $limit = $this->tableLimits[$tableName];
206
207
        if (! is_numeric($limit)) {
208 2
            return false;
209
        }
210
211 2
        return $limit;
212
    }
213
214
    /**
215
     * Sets a LIMIT condition for a specific table during the export process.
216
     */
217
    public function limit(string $table, int $limit): self
218
    {
219 2
        $this->tableLimits[$table] = $limit;
220
221 2
        return $this;
222
    }
223
224
    /**
225
     * Returns header for dump file.
226
     */
227
    private function getDumpFileHeader(): string
228
    {
229 2
        $header = '';
230
231
        if (! $this->option->skip_comments) {
232 2
            $divider = str_repeat('----------------------------------------------------', 2);
233
234
            // Some info about software, source and time
235
            $header = '-- ' . $divider . PHP_EOL . '-- ' . PHP_EOL .
236
                    '-- Database Backup Manager' . PHP_EOL .
237
                    '-- This backup was created automatically by the Dimtrovich Db-Dumper. A simplest PHP Database Backup Manager' . PHP_EOL .
238
                    '-- © ' . date('Y') . ' Dimitri Sitchet Tomkeu' . PHP_EOL .
239
                    '-- https://github.com/dimtrovich/php-db-dumper' . PHP_EOL .
240 2
                    '-- ' . PHP_EOL;
241
            if ($this->driver !== 'sqlite') {
242 2
                $header .= '-- Host: ' . $this->pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS) . PHP_EOL;
243
            }
244
            $header .= "-- Database: {$this->database}" . PHP_EOL .
245 2
                        '-- Server version: ' . $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION) . ' Driver: ' . $this->driver . PHP_EOL;
246
247
            if (! $this->option->skip_dump_date) {
248 2
                $header .= '-- ' . PHP_EOL . '-- Generated on: ' . date('r') . PHP_EOL;
249
            }
250
            if ('' !== $this->option->message) {
251 2
                $header .= '-- ' . PHP_EOL . trim($this->option->message) . PHP_EOL;
252
            }
253
254 2
            $header .= '-- ' . PHP_EOL . $divider . PHP_EOL . PHP_EOL;
255
        }
256
257 2
        return $header;
258
    }
259
260
    /**
261
     * Returns footer for dump file.
262
     */
263
    private function getDumpFileFooter()
264
    {
265 2
        $footer = '';
266
267
        if (! $this->option->skip_comments) {
268 2
            $footer .= '-- Dump completed';
269
270
            if (! $this->option->skip_dump_date) {
271 2
                $footer .= ' on: ' . date('r');
272
            }
273
274 2
            $footer .= PHP_EOL;
275
        }
276
277 2
        return $footer;
278
    }
279
280
    /**
281
     * Reads table names from database.
282
     * Fills $this->tables array so they will be dumped later.
283
     */
284
    private function getDatabaseStructureTables()
285
    {
286 2
        $tables = $this->pdo->query($this->adapter->showTables($this->database));
287
288
        // Listing all tables from database
289
        if ($this->option->include_tables === []) {
290
            // include all tables for now, blacklisting happens later
291
            foreach ($tables as $row) {
292 2
                $this->tables[] = current($row);
293
            }
294
        } else {
295
            // include only the tables mentioned in include-tables
296
            foreach ($tables as $row) {
297
                if (in_array(current($row), $this->option->include_tables, true)) {
298
                    $this->tables[] = current($row);
299
                    $elem           = array_search(current($row), $this->option->include_tables, true);
300
                    unset($this->option->include_tables[$elem]);
301
                }
302
            }
303
        }
304
    }
305
306
    /**
307
     * Reads view names from database.
308
     * Fills $this->tables array so they will be dumped later.
309
     */
310
    private function getDatabaseStructureViews()
311
    {
312 2
        $views = $this->pdo->query($this->adapter->showViews($this->database));
313
314
        // Listing all views from database
315
        if ($this->option->include_views === []) {
316
            // include all views for now, blacklisting happens later
317
            foreach ($views as $row) {
318 2
                $this->views[] = current($row);
319
            }
320
        } else {
321
            // include only the tables mentioned in include-tables
322
            foreach ($views as $row) {
323
                if (in_array(current($row), $this->option->include_views, true)) {
324
                    $this->views[] = current($row);
325
                    $elem          = array_search(current($row), $this->option->include_views, true);
326
                    unset($this->option->include_views[$elem]);
327
                }
328
            }
329
        }
330
    }
331
332
    /**
333
     * Reads trigger names from database.
334
     * Fills $this->tables array so they will be dumped later.
335
     */
336
    private function getDatabaseStructureTriggers()
337
    {
338
        // Listing all triggers from database
339
        if ($this->option->skip_triggers) {
340
            foreach ($this->pdo->query($this->adapter->showTriggers($this->database)) as $row) {
341
                $this->triggers[] = $row['Trigger'];
342
            }
343
        }
344
    }
345
346
    /**
347
     * Reads procedure names from database.
348
     * Fills $this->tables array so they will be dumped later.
349
     */
350
    private function getDatabaseStructureProcedures(): void
351
    {
352
        // Listing all procedures from database
353
        if ($this->option->routines) {
354
            foreach ($this->pdo->query($this->adapter->showProcedures($this->database)) as $row) {
355
                $this->procedures[] = $row['procedure_name'];
356
            }
357
        }
358
    }
359
360
    /**
361
     * Reads functions names from database.
362
     * Fills $this->tables array so they will be dumped later.
363
     */
364
    private function getDatabaseStructureFunctions(): void
365
    {
366
        // Listing all functions from database
367
        if ($this->option->routines) {
368
            foreach ($this->pdo->query($this->adapter->showFunctions($this->database)) as $row) {
369
                $this->functions[] = $row['function_name'];
370
            }
371
        }
372
    }
373
374
    /**
375
     * Reads event names from database.
376
     * Fills $this->tables array so they will be dumped later.
377
     */
378
    private function getDatabaseStructureEvents(): void
379
    {
380
        // Listing all events from database
381
        if ($this->option->events) {
382
            foreach ($this->pdo->query($this->adapter->showEvents($this->database)) as $row) {
383
                $this->events[] = $row['event_name'];
384
            }
385
        }
386
    }
387
388
    /**
389
     * Compare if $table name matches with a definition inside $arr
390
     *
391
     * @param $arr array with strings or patterns
392
     */
393
    private function matches(string $table, array $arr): bool
394
    {
395 2
        $match = false;
396
397
        foreach ($arr as $pattern) {
398
            if ('/' !== $pattern[0]) {
399
                continue;
400
            }
401
            if (1 === preg_match($pattern, $table)) {
402
                $match = true;
403
            }
404
        }
405
406 2
        return in_array($table, $arr, true) || $match;
407
    }
408
409
    /**
410
     * Exports all the tables selected from database
411
     */
412
    private function exportTables()
413
    {
414
        // Exporting tables one by one
415
        foreach ($this->tables as $table) {
416
            if ($this->matches($table, $this->option->exclude_tables)) {
417
                continue;
418
            }
419
420 2
            $this->getTableStructure($table);
421
422
            if ([] === $this->option->no_data) { // don't break compatibility with old trigger
423 2
                $this->listValues($table);
424
            } elseif ([] !== $this->option->no_data || $this->matches($table, $this->option->no_data)) {
425
                continue;
426
            } else {
427
                $this->listValues($table);
428
            }
429
        }
430
    }
431
432
    /**
433
     * Exports all the views found in database
434
     */
435
    private function exportViews()
436
    {
437
        if (false === $this->option->no_create_info) {
438
            // Exporting views one by one
439
            foreach ($this->views as $view) {
440
                if ($this->matches($view, $this->option->exclude_tables)) {
441
                    continue;
442
                }
443
444
                $this->tableColumnTypes[$view] = $this->getTableColumnTypes($view);
445
                $this->getViewStructureTable($view);
446
            }
447
448
            foreach ($this->views as $view) {
449
                if ($this->matches($view, $this->option->exclude_tables)) {
450
                    continue;
451
                }
452
453
                $this->getViewStructureView($view);
454
            }
455
        }
456
    }
457
458
    /**
459
     * Exports all the triggers found in database
460
     */
461
    private function exportTriggers()
462
    {
463
        // Exporting triggers one by one
464
        foreach ($this->triggers as $trigger) {
465 2
            $this->getTriggerStructure($trigger);
466
        }
467
    }
468
469
    /**
470
     * Exports all the procedures found in database
471
     */
472
    private function exportProcedures()
473
    {
474
        // Exporting triggers one by one
475
        foreach ($this->procedures as $procedure) {
476 2
            $this->getProcedureStructure($procedure);
477
        }
478
    }
479
480
    /**
481
     * Exports all the functions found in database
482
     */
483
    private function exportFunctions()
484
    {
485
        // Exporting triggers one by one
486
        foreach ($this->functions as $function) {
487 2
            $this->getFunctionStructure($function);
488
        }
489
    }
490
491
    /**
492
     * Exports all the events found in database
493
     */
494
    private function exportEvents()
495
    {
496
        // Exporting triggers one by one
497
        foreach ($this->events as $event) {
498 2
            $this->getEventStructure($event);
499
        }
500
    }
501
502
    /**
503
     * Table structure extractor
504
     */
505
    private function getTableStructure(string $tableName)
506
    {
507
        if (! $this->option->no_create_info) {
508 2
            $ret = '';
509
510
            if (! $this->option->skip_comments) {
511
                $ret = '--' . PHP_EOL .
512
                    "-- Table structure for table `{$tableName}`" . PHP_EOL .
513 2
                    '--' . PHP_EOL . PHP_EOL;
514
            }
515
516 2
            $stmt = $this->adapter->showCreateTable($tableName);
517
518
            foreach ($this->pdo->query($stmt) as $r) {
519 2
                $this->compressor->write($ret);
520
521
                if ($this->option->add_drop_table) {
522 2
                    $this->compressor->write($this->adapter->dropTable($tableName));
523
                }
524
525 2
                $this->compressor->write($this->adapter->createTable($r));
526
527 2
                break;
528
            }
529
        }
530
531 2
        $this->tableColumnTypes[$tableName] = $this->getTableColumnTypes($tableName);
532
    }
533
534
    /**
535
     * Store column types to create data dumps and for Stand-In tables
536
     *
537
     * @return array type column types detailed
538
     */
539
    private function getTableColumnTypes(string $tableName): array
540
    {
541 2
        $columnTypes = [];
542
543
        $columns = $this->pdo->query(
544
            $this->adapter->showColumns($tableName)
545 2
        );
546 2
        $columns->setFetchMode(PDO::FETCH_ASSOC);
547
548
        foreach ($columns as $key => $col) {
549 2
            $field = $col['Field'] ?? ($col['name'] ?? '');
550
            if ($field === '') {
551
                continue; // skip if field name is empty (MySQL 8.0+ returns empty name for computed columns)
552
            }
553
554 2
            $types               = $this->adapter->parseColumnType($col);
555
            $columnTypes[$field] = [
556
                'is_numeric' => $types['is_numeric'],
557
                'is_blob'    => $types['is_blob'],
558
                'type'       => $types['type'],
559
                'type_sql'   => $types['type_sql'] ?? $col['Type'],
560
                'is_virtual' => $types['is_virtual'],
561 2
            ];
562
        }
563
564 2
        return $columnTypes;
565
    }
566
567
    /**
568
     * View structure extractor, create table (avoids cyclic references)
569
     */
570
    private function getViewStructureTable(string $viewName): void
571
    {
572
        if (! $this->option->skip_comments) {
573
            $ret = '--' . PHP_EOL .
574
                "-- Stand-In structure for view `{$viewName}`" . PHP_EOL .
575
                '--' . PHP_EOL . PHP_EOL;
576
577
            $this->compressor->write($ret);
578
        }
579
580
        $stmt = $this->adapter->showCreateView($viewName);
581
582
        // create views as tables, to resolve dependencies
583
        foreach ($this->pdo->query($stmt) as $r) {
584
            if ($this->option->add_drop_table) {
585
                $this->compressor->write($this->adapter->dropView($viewName));
586
            }
587
588
            $this->compressor->write($this->createStandInTable($viewName));
589
            break;
590
        }
591
    }
592
593
    /**
594
     * Write a create table statement for the table Stand-In, show create
595
     * table would return a create algorithm when used on a view
596
     */
597
    public function createStandInTable(string $viewName): string
598
    {
599
        $ret = [];
600
601
        foreach ($this->tableColumnTypes[$viewName] as $k => $v) {
602
            $ret[] = "`{$k}` {$v['type_sql']}";
603
        }
604
605
        $ret = implode(PHP_EOL . ',', $ret);
606
607
        return "CREATE TABLE IF NOT EXISTS `{$viewName}` (" .
608
            PHP_EOL . $ret . PHP_EOL . ');' . PHP_EOL;
609
    }
610
611
    /**
612
     * View structure extractor, create view
613
     */
614
    private function getViewStructureView(string $viewName): void
615
    {
616
        if (! $this->option->skip_comments) {
617
            $ret = '--' . PHP_EOL .
618
                "-- View structure for view `{$viewName}`" . PHP_EOL .
619
                '--' . PHP_EOL . PHP_EOL;
620
            $this->compressor->write($ret);
621
        }
622
623
        $stmt = $this->adapter->showCreateView($viewName);
624
625
        // create views, to resolve dependencies
626
        // replacing tables with views
627
        foreach ($this->pdo->query($stmt) as $r) {
628
            // because we must replace table with view, we should delete it
629
            $this->compressor->write($this->adapter->dropView($viewName));
630
            $this->compressor->write($this->adapter->createView($r));
631
632
            break;
633
        }
634
    }
635
636
    /**
637
     * Trigger structure extractor
638
     */
639
    private function getTriggerStructure(string $triggerName): void
640
    {
641
        $stmt = $this->adapter->showCreateTrigger($triggerName);
642
643
        foreach ($this->pdo->query($stmt) as $r) {
644
            if ($this->option->add_drop_trigger) {
645
                $this->compressor->write($this->adapter->addDropTrigger($triggerName));
646
            }
647
648
            $this->compressor->write($this->adapter->createTrigger($r));
649
650
            return;
651
        }
652
    }
653
654
    /**
655
     * Procedure structure extractor
656
     */
657
    private function getProcedureStructure(string $procedureName)
658
    {
659
        if (! $this->option->skip_comments) {
660
            $ret = '--' . PHP_EOL .
661
                "-- Dumping routines for database '" . $this->database . "'" . PHP_EOL .
662
                '--' . PHP_EOL . PHP_EOL;
663
            $this->compressor->write($ret);
664
        }
665
666
        $stmt = $this->adapter->showCreateProcedure($procedureName);
667
668
        foreach ($this->pdo->query($stmt) as $r) {
669
            $this->compressor->write($this->adapter->createProcedure($r));
670
671
            return;
672
        }
673
    }
674
675
    /**
676
     * Function structure extractor
677
     */
678
    private function getFunctionStructure(string $functionName)
679
    {
680
        if (! $this->option->skip_comments) {
681
            $ret = '--' . PHP_EOL .
682
                "-- Dumping routines for database '" . $this->database . "'" . PHP_EOL .
683
                '--' . PHP_EOL . PHP_EOL;
684
685
            $this->compressor->write($ret);
686
        }
687
688
        $stmt = $this->adapter->showCreateFunction($functionName);
689
690
        foreach ($this->pdo->query($stmt) as $r) {
691
            $this->compressor->write($this->adapter->createFunction($r));
692
693
            return;
694
        }
695
    }
696
697
    /**
698
     * Event structure extractor
699
     */
700
    private function getEventStructure(string $eventName)
701
    {
702
        if (! $this->option->skip_comments) {
703
            $ret = '--' . PHP_EOL .
704
                "-- Dumping events for database '" . $this->database . "'" . PHP_EOL .
705
                '--' . PHP_EOL . PHP_EOL;
706
707
            $this->compressor->write($ret);
708
        }
709
710
        $stmt = $this->adapter->showCreateEvent($eventName);
711
712
        foreach ($this->pdo->query($stmt) as $r) {
713
            $this->compressor->write($this->adapter->createEvent($r));
714
715
            return;
716
        }
717
    }
718
719
    /**
720
     * Prepare values for output
721
     *
722
     * @param array $row Associative array of column names and values to be quoted
723
     */
724
    private function prepareColumnValues(string $tableName, array $row): array
725
    {
726 2
        $ret         = [];
727 2
        $columnTypes = $this->tableColumnTypes[$tableName];
728
729
        if ($this->transformTableRowCallable !== null) {
730 2
            $row = ($this->transformTableRowCallable)($tableName, $row);
731
        }
732
733
        foreach ($row as $colName => $colValue) {
734 2
            $ret[] = $this->escape($colValue, $columnTypes[$colName]);
735
        }
736
737 2
        return $ret;
738
    }
739
740
    /**
741
     * Escape values with quotes when needed
742
     *
743
     * @param mixed $colValue
744
     * @param mixed $colType
745
     */
746
    private function escape($colValue, $colType)
747
    {
748
        if (null === $colValue) {
749 2
            return 'NULL';
750
        }
751
        if ($this->option->hex_blob && $colType['is_blob']) {
752
            if ($colType['type'] === 'bit' || ! empty($colValue)) {
753
                return "0x{$colValue}";
754
            }
755
756
            return "''";
757
        }
758
        if ($colType['is_numeric']) {
759 2
            return $colValue;
760
        }
761
762 2
        return $this->pdo->quote($colValue);
763
    }
764
765
    /**
766
     * Set a callable that will be used to transform table rows
767
     */
768
    public function transformTableRow(callable $callable)
769
    {
770
        $this->transformTableRowCallable = $callable;
771
    }
772
773
    /**
774
     * Table rows extractor
775
     */
776
    private function listValues(string $tableName)
777
    {
778 2
        $this->prepareListValues($tableName);
779
780 2
        $onlyOnce = true;
781
782
        // colStmt is used to form a query to obtain row values
783 2
        $colStmt = $this->getColumnStmt($tableName);
784
785
        // colNames is used to get the name of the columns when using complete-insert
786
        if ($this->option->complete_insert) {
787 2
            $colNames = $this->getColumnNames($tableName);
788
        }
789
790 2
        $stmt = 'SELECT ' . implode(',', $colStmt) . " FROM `{$tableName}`";
791
792
        // Table specific conditions override the default 'where'
793 2
        $condition = $this->getTableWhere($tableName);
794
795
        if ($condition) {
796
            $stmt .= " WHERE {$condition}";
797
        }
798
799 2
        $limit = $this->getTableLimit($tableName);
800
801
        if ($limit !== false) {
802
            $stmt .= " LIMIT {$limit}";
803
        }
804
805 2
        $resultSet = $this->pdo->query($stmt);
806 2
        $resultSet->setFetchMode(PDO::FETCH_ASSOC);
807
808 2
        $ignore = $this->option->insert_ignore ? '  IGNORE' : '';
809
810 2
        $count = 0;
811 2
        $line  = '';
812
813
        foreach ($resultSet as $row) {
814 2
            $count++;
815 2
            $vals = $this->prepareColumnValues($tableName, $row);
816
            if ($onlyOnce || ! $this->option->extended_insert) {
817
                if ($this->option->complete_insert) {
818
                    $line .= "INSERT{$ignore} INTO `{$tableName}` (" .
819
                        implode(', ', $colNames) .
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $colNames does not seem to be defined for all execution paths leading up to this point.
Loading history...
820
                        ') VALUES (' . implode(',', $vals) . ')';
821
                } else {
822 2
                    $line .= "INSERT{$ignore} INTO `{$tableName}` VALUES (" . implode(',', $vals) . ')';
823
                }
824 2
                $onlyOnce = false;
825
            } else {
826 2
                $line .= ',(' . implode(',', $vals) . ')';
827
            }
828
829
            if ((strlen($line) > $this->option->net_buffer_length)
830
                    || ! $this->option->extended_insert) {
831
                $onlyOnce = true;
832
                $this->compressor->write($line . ';' . PHP_EOL);
833
                $line = '';
834
            }
835
        }
836
837 2
        $resultSet->closeCursor();
838
839
        if ('' !== $line) {
840 2
            $this->compressor->write($line . ';' . PHP_EOL);
841
        }
842
843 2
        $this->endListValues($tableName, $count);
844
845 2
        $this->event->emit('table.export', $tableName, $count);
846
    }
847
848
    /**
849
     * Table rows extractor, append information prior to dump
850
     */
851
    public function prepareListValues(string $tableName)
852
    {
853
        if (! $this->option->skip_comments) {
854
            $this->compressor->write(
855
                '--' . PHP_EOL .
856
                "-- Dumping data for table `{$tableName}`" . PHP_EOL .
857
                '--' . PHP_EOL . PHP_EOL
858 2
            );
859
        }
860
861
        if ($this->option->lock_tables && ! $this->option->single_transaction) {
862 2
            $this->adapter->lockTable($tableName);
863
        }
864
865
        if ($this->option->add_locks) {
866
            $this->compressor->write(
867
                $this->adapter->startAddLockTable($tableName)
868 2
            );
869
        }
870
871
        if ($this->option->disable_keys) {
872
            $this->compressor->write(
873
                $this->adapter->startAddDisableKeys($tableName)
874 2
            );
875
        }
876
877
        // Disable autocommit for faster reload
878
        if ($this->option->no_autocommit) {
879
            $this->compressor->write(
880
                $this->adapter->startDisableAutocommit()
881 2
            );
882
        }
883
    }
884
885
    /**
886
     * Table rows extractor, close locks and commits after dump
887
     *
888
     * @param int $count Number of rows inserted.
889
     */
890
    public function endListValues(string $tableName, int $count = 0)
891
    {
892
        if ($this->option->disable_keys) {
893
            $this->compressor->write(
894
                $this->adapter->endAddDisableKeys($tableName)
895 2
            );
896
        }
897
898
        if ($this->option->add_locks) {
899 2
            $this->compressor->write($this->adapter->endAddLockTable($tableName));
900
        }
901
902
        if ($this->option->lock_tables && ! $this->option->single_transaction) {
903 2
            $this->adapter->unlockTable($tableName);
904
        }
905
906
        // Commit to enable autocommit
907
        if ($this->option->no_autocommit) {
908
            $this->compressor->write(
909
                $this->adapter->endDisableAutocommit()
910 2
            );
911
        }
912
913 2
        $this->compressor->write(PHP_EOL);
914
915
        if (! $this->option->skip_comments) {
916
            $this->compressor->write(
917
                '-- Dumped table `' . $tableName . "` with {$count} row(s)" . PHP_EOL .
918
                '--' . PHP_EOL . PHP_EOL
919 2
            );
920
        }
921
    }
922
923
    /**
924
     * Build SQL List of all columns on current table which will be used for selecting
925
     *
926
     * @return array SQL sentence with columns for select
927
     */
928
    public function getColumnStmt(string $tableName): array
929
    {
930 2
        $colStmt = [];
931
932
        foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) {
933
            if ($colType['is_virtual']) {
934
                $this->option->complete_insert = true;
935
936
                continue;
937
            }
938
            if ($colType['type'] === 'bit' && $this->option->hex_blob) {
939 2
                $colStmt[] = "LPAD(HEX(`{$colName}`),2,'0') AS `{$colName}`";
940
            } elseif ($colType['type'] === 'double' && PHP_VERSION_ID > 80100) {
941 2
                $colStmt[] = sprintf('CONCAT(`%s`) AS `%s`', $colName, $colName);
942
            } elseif ($colType['is_blob'] && $this->option->hex_blob) {
943 2
                $colStmt[] = "HEX(`{$colName}`) AS `{$colName}`";
944
            } else {
945 2
                $colStmt[] = "`{$colName}`";
946
            }
947
        }
948
949 2
        return $colStmt;
950
    }
951
952
    /**
953
     * Build SQL List of all columns on current table which will be used for inserting
954
     *
955
     * @return array columns for sql sentence for insert
956
     */
957
    public function getColumnNames(string $tableName): array
958
    {
959
        $colNames = [];
960
961
        foreach ($this->tableColumnTypes[$tableName] as $colName => $colType) {
962
            if ($colType['is_virtual']) {
963
                $this->option->complete_insert = true;
964
965
                continue;
966
            }
967
            $colNames[] = "`{$colName}`";
968
        }
969
970
        return $colNames;
971
    }
972
}
973