Passed
Push — main ( acbf0f...7dd0c5 )
by Thierry
01:56
created

DbDumpTrait::dumpDataRow()   B

Complexity

Conditions 9
Paths 50

Size

Total Lines 28
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 23
c 1
b 0
f 0
nc 50
nop 4
dl 0
loc 28
rs 8.0555
1
<?php
2
3
namespace Lagdo\DbAdmin\DbAdmin\Traits;
4
5
use Lagdo\DbAdmin\Driver\Db\StatementInterface;
6
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
7
8
use function strlen;
9
use function in_array;
10
use function preg_match;
11
use function str_replace;
12
use function implode;
13
use function is_numeric;
14
use function array_keys;
15
use function array_map;
16
17
trait DbDumpTrait
18
{
19
    /**
20
     * The dump options
21
     *
22
     * @var array
23
     */
24
    private $options;
25
26
    /**
27
     * The queries generated by the dump
28
     *
29
     * @var array
30
     */
31
    private $queries = [];
32
33
    // Temp vars for data dumps
34
    private $insert = '';
35
    private $buffer = '';
36
    private $suffix = '';
37
    private $views = [];
38
    private $fkeys = [];
39
40
    /**
41
     * Print CSV row
42
     *
43
     * @param array  $row
44
     *
45
     * @return void
46
     */
47
    private function dumpCsv(array $row)
48
    {
49
        // From functions.inc.php
50
        foreach ($row as $key => $val) {
51
            if (preg_match('~["\n,;\t]|^0|\.\d*0$~', $val) || $val === '') {
52
                $row[$key] = '"' . str_replace('"', '""', $val) . '"';
53
            }
54
        }
55
        $separator = $this->options['format'] == 'csv' ? ',' :
56
            ($this->options['format'] == 'tsv' ? "\t" : ';');
57
        $this->queries[] = implode($separator, $row);
58
    }
59
60
    /**
61
     * Convert a value to string
62
     *
63
     * @param mixed  $val
64
     * @param object $field
65
     *
66
     * @return string
67
     */
68
    private function convertToString($val, $field): string
69
    {
70
        // From functions.inc.php
71
        if ($val === null) {
72
            return 'NULL';
73
        }
74
        return $this->driver->unconvertField($field, preg_match($this->driver->numberRegex(), $field->type) &&
75
        !preg_match('~\[~', $field->fullType) && is_numeric($val) ?
76
            $val : $this->driver->quote(($val === false ? 0 : $val)));
77
    }
78
79
    /**
80
     * Export table structure
81
     *
82
     * @param string $table
83
     * @param string $style
84
     * @param int    $tableType       0 table, 1 view, 2 temporary view table
85
     *
86
     * @return null prints data
87
     */
88
    private function dumpTableOrView(string $table, string $style, int $tableType = 0)
89
    {
90
        // From adminer.inc.php
91
        if ($this->options['format'] != 'sql') {
92
            $this->queries[] = "\xef\xbb\xbf"; // UTF-8 byte order mark
93
            if ($style) {
94
                $this->dumpCsv(array_keys($this->driver->fields($table)));
95
            }
96
            return;
97
        }
98
99
        if ($tableType == 2) {
100
            $fields = [];
101
            foreach ($this->driver->fields($table) as $name => $field) {
102
                $fields[] = $this->driver->escapeId($name) . ' ' . $field->fullType;
103
            }
104
            $create = 'CREATE TABLE ' . $this->driver->table($table) . ' (' . implode(', ', $fields) . ')';
105
        } else {
106
            $create = $this->driver->sqlForCreateTable($table, $this->options['autoIncrement'], $style);
107
        }
108
        $this->driver->setUtf8mb4($create);
109
        if ($style && $create) {
110
            if ($style == 'DROP+CREATE' || $tableType == 1) {
111
                $this->queries[] = 'DROP ' . ($tableType == 2 ? 'VIEW' : 'TABLE') .
112
                    ' IF EXISTS ' . $this->driver->table($table) . ';';
113
            }
114
            if ($tableType == 1) {
115
                $create = $this->admin->removeDefiner($create);
116
            }
117
            $this->queries[] = $create . ';';
118
        }
119
    }
120
121
    /**
122
     * @param array $row
123
     * @param StatementInterface $statement
124
     *
125
     * @return array
126
     */
127
    private function getDataRowKeys(array $row, StatementInterface $statement): array
128
    {
129
        $values = [];
130
        $keys = [];
131
        foreach ($row as $val) {
132
            $field = $statement->fetchField();
133
            $keys[] = $field->name();
134
            $key = $this->driver->escapeId($field->name());
135
            $values[] = "$key = VALUES($key)";
136
        }
137
        $this->suffix = ";\n";
138
        if ($this->options['data_style'] == 'INSERT+UPDATE') {
139
            $this->suffix = "\nON DUPLICATE KEY UPDATE " . implode(', ', $values) . ";\n";
140
        }
141
        return $keys;
142
    }
143
144
    /**
145
     * @param string $table
146
     * @param array $fields
147
     * @param array $row
148
     * @param array $keys
149
     *
150
     * @return void
151
     */
152
    private function dumpDataRow(string $table, array $fields, array $row, array $keys)
153
    {
154
        if ($this->options['format'] != 'sql') {
155
            if ($this->options['data_style'] == 'table') {
156
                $this->dumpCsv($keys);
157
                $this->options['data_style'] = 'INSERT';
158
            }
159
            $this->dumpCsv($row);
160
        } else {
161
            if (!$this->insert) {
162
                $this->insert = 'INSERT INTO ' . $this->driver->table($table) . ' (' .
163
                    implode(', ', array_map(function ($key) {
164
                        return $this->driver->escapeId($key);
165
                    }, $keys)) . ') VALUES';
166
            }
167
            foreach ($row as $key => $val) {
168
                $field = $fields[$key];
169
                $row[$key] = $this->convertToString($val, $field);
170
            }
171
            $max_packet = ($this->driver->jush() == 'sqlite' ? 0 : 1048576); // default, minimum is 1024
172
            $s = ($max_packet ? "\n" : ' ') . '(' . implode(",\t", $row) . ')';
173
            if (!$this->buffer) {
174
                $this->buffer = $this->insert . $s;
175
            } elseif (strlen($this->buffer) + 4 + strlen($s) + strlen($this->suffix) < $max_packet) { // 4 - length specification
176
                $this->buffer .= ",$s";
177
            } else {
178
                $this->queries[] = $this->buffer . $this->suffix;
179
                $this->buffer = $this->insert . $s;
180
            }
181
        }
182
    }
183
184
    /** Export table data
185
     *
186
     * @param string $table
187
     * @param string $query
188
     *
189
     * @return null prints data
190
     */
191
    private function dumpData(string $table, string $query)
192
    {
193
        $fields = [];
194
        if ($this->options['data_style']) {
195
            if ($this->options['format'] == 'sql') {
196
                if ($this->options['data_style'] == 'TRUNCATE+INSERT') {
197
                    $this->queries[] = $this->driver->sqlForTruncateTable($table) . ";\n";
198
                }
199
                $fields = $this->driver->fields($table);
200
            }
201
            $statement = $this->driver->query($query); // 1 - MYSQLI_USE_RESULT //! enum and set as numbers
202
            if ($statement) {
203
                $this->insert = '';
204
                $this->buffer = '';
205
                $this->suffix = '';
206
                $keys = [];
207
                $fetch_function = ($table != '' ? 'fetchAssoc' : 'fetchRow');
208
                while ($row = $statement->$fetch_function()) {
209
                    if (empty($keys)) {
210
                        $keys = $this->getDataRowKeys($row, $statement);
211
                    }
212
                    $this->dumpDataRow($table, $fields, $row, $keys);
213
                }
214
                if ($this->buffer) {
215
                    $this->queries[] = $this->buffer . $this->suffix;
216
                }
217
            } elseif ($this->options['format'] == 'sql') {
218
                $this->queries[] = '-- ' . str_replace("\n", ' ', $this->driver->error()) . "\n";
219
            }
220
        }
221
    }
222
223
    /**
224
     * @param string $table
225
     * @param TableEntity $tableStatus
226
     * @param bool $dumpTable
227
     * @param bool $dumpData
228
     *
229
     * @return void
230
     */
231
    private function dumpTable(string $table, TableEntity $tableStatus, bool $dumpTable, bool $dumpData)
232
    {
233
        if ($dumpTable || $dumpData) {
234
            $this->dumpTableOrView($table, ($dumpTable ? $this->options['table_style'] : ''));
235
            if ($dumpData) {
236
                $fields = $this->driver->fields($table);
237
                $query = 'SELECT *' . $this->driver->convertFields($fields, $fields) .
238
                    ' FROM ' . $this->driver->table($table);
239
                $this->dumpData($table, $query);
240
            }
241
            if ($this->options['is_sql'] && $this->options['triggers'] && $dumpTable &&
242
                ($triggers = $this->driver->sqlForCreateTrigger($table))) {
243
                $this->queries[] = 'DELIMITER ;';
244
                $this->queries[] = $triggers;
245
                $this->queries[] = 'DELIMITER ;';
246
            }
247
            if ($this->options['is_sql']) {
248
                $this->queries[] = '';
249
            }
250
        }
251
    }
252
253
    /**
254
     * Dump tables
255
     *
256
     * @param string $database      The database name
257
     *
258
     * @return void
259
     */
260
    private function dumpTables(string $database)
261
    {
262
        $dbDumpTable = $this->tables['list'] === '*' && in_array($database, $this->databases['list']);
263
        $dbDumpData = in_array($database, $this->databases['data']);
264
        $this->views = []; // View names
265
        $this->fkeys = []; // Table names for foreign keys
266
        $dbTables = $this->driver->tableStatuses(true);
267
        foreach ($dbTables as $table => $tableStatus) {
268
            $isView = $this->driver->isView($tableStatus);
269
            if ($isView) {
270
                // The views will be dumped after the tables
271
                $this->views[] = $table;
272
                continue;
273
            }
274
            $this->fkeys[] = $table;
275
            $dumpTable = $dbDumpTable || in_array($table, $this->tables['list']);
276
            $dumpData = $dbDumpData || in_array($table, $this->tables['data']);
277
            $this->dumpTable($table, $tableStatus, $dumpTable, $dumpData);
278
        }
279
    }
280
281
    /**
282
     * @return void
283
     */
284
    private function dumpViewsAndFKeys()
285
    {
286
        // Add FKs after creating tables (except in MySQL which uses SET FOREIGN_KEY_CHECKS=0)
287
        if ($this->driver->support('fkeys_sql')) {
288
            foreach ($this->fkeys as $table) {
289
                $this->queries[] = $this->driver->sqlForForeignKeys($table);
290
            }
291
        }
292
        // Dump the views after all the tables
293
        foreach ($this->views as $view) {
294
            $this->dumpTableOrView($view, $this->options['table_style'], 1);
295
        }
296
    }
297
}
298