Passed
Push — main ( 7dd0c5...b31323 )
by Thierry
01:58
created

DbDumpTrait::getCreateTableQuery()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 10
rs 10
1
<?php
2
3
namespace Lagdo\DbAdmin\DbAdmin\Traits;
4
5
use Lagdo\DbAdmin\Driver\Db\StatementInterface;
6
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
7
8
use function count;
9
use function strlen;
10
use function in_array;
11
use function preg_match;
12
use function str_replace;
13
use function implode;
14
use function is_numeric;
15
use function array_keys;
16
use function array_map;
17
18
trait DbDumpTrait
19
{
20
    /**
21
     * The dump options
22
     *
23
     * @var array
24
     */
25
    private $options;
26
27
    /**
28
     * The queries generated by the dump
29
     *
30
     * @var array
31
     */
32
    private $queries = [];
33
34
    // Temp vars for data dumps
35
    private $insert = '';
36
    private $buffer = '';
37
    private $suffix = '';
38
    private $views = [];
39
    private $fkeys = [];
40
41
    /**
42
     * Print CSV row
43
     *
44
     * @param array  $row
45
     *
46
     * @return void
47
     */
48
    private function dumpCsv(array $row)
49
    {
50
        // From functions.inc.php
51
        foreach ($row as $key => $val) {
52
            if (preg_match('~["\n,;\t]|^0|\.\d*0$~', $val) || $val === '') {
53
                $row[$key] = '"' . str_replace('"', '""', $val) . '"';
54
            }
55
        }
56
        $separator = $this->options['format'] === 'csv' ? ',' :
57
            ($this->options['format'] === 'tsv' ? "\t" : ';');
58
        $this->queries[] = implode($separator, $row);
59
    }
60
61
    /**
62
     * Convert a value to string
63
     *
64
     * @param mixed  $value
65
     * @param TableFieldEntity $field
66
     *
67
     * @return string
68
     */
69
    private function convertToString($value, TableFieldEntity $field): string
70
    {
71
        // From functions.inc.php
72
        if ($value === null) {
73
            return 'NULL';
74
        }
75
        if (!preg_match($this->driver->numberRegex(), $field->type) ||
76
            preg_match('~\[~', $field->fullType) && is_numeric($value)) {
77
            $value = $this->driver->quote(($value === false ? 0 : $value));
78
        }
79
        return $this->driver->unconvertField($field, $value);
80
    }
81
82
    /**
83
     * @param string $table
84
     * @param string $style
85
     * @param int $tableType
86
     *
87
     * @return string
88
     */
89
    private function getCreateTableQuery(string $table, string $style, int $tableType): string
90
    {
91
        if ($tableType !== 2) {
92
            return $this->driver->sqlForCreateTable($table, $this->options['autoIncrement'], $style);
93
        }
94
        $fields = [];
95
        foreach ($this->driver->fields($table) as $name => $field) {
96
            $fields[] = $this->driver->escapeId($name) . ' ' . $field->fullType;
97
        }
98
        return 'CREATE TABLE ' . $this->driver->table($table) . ' (' . implode(', ', $fields) . ')';
99
    }
100
101
    /**
102
     * Export table structure
103
     *
104
     * @param string $table
105
     * @param string $style
106
     * @param int    $tableType       0 table, 1 view, 2 temporary view table
107
     *
108
     * @return void
109
     */
110
    private function dumpTableOrView(string $table, string $style, int $tableType = 0)
111
    {
112
        // From adminer.inc.php
113
        if ($this->options['format'] !== 'sql') {
114
            $this->queries[] = "\xef\xbb\xbf"; // UTF-8 byte order mark
115
            if ($style) {
116
                $this->dumpCsv(array_keys($this->driver->fields($table)));
117
            }
118
            return;
119
        }
120
        if (!$style) {
121
            return;
122
        }
123
124
        $create = $this->getCreateTableQuery($table, $style, $tableType);
125
        $this->driver->setUtf8mb4($create);
126
        if (!$create) {
127
            return;
128
        }
129
        if ($style === 'DROP+CREATE' || $tableType === 1) {
130
            $this->queries[] = 'DROP ' . ($tableType === 2 ? 'VIEW' : 'TABLE') .
131
                ' IF EXISTS ' . $this->driver->table($table) . ';';
132
        }
133
        if ($tableType === 1) {
134
            $create = $this->admin->removeDefiner($create);
135
        }
136
        $this->queries[] = $create . ';';
137
    }
138
139
    /**
140
     * @param array $row
141
     * @param StatementInterface $statement
142
     *
143
     * @return array
144
     */
145
    private function getDataRowKeys(array $row, StatementInterface $statement): array
146
    {
147
        $values = [];
148
        $keys = [];
149
        // For is preferred to foreach because the values are not used.
150
        // foreach ($row as $val) {
151
        // }
152
        for ($i = 0; $i < count($row); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
153
            $field = $statement->fetchField();
154
            $keys[] = $field->name();
155
            $key = $this->driver->escapeId($field->name());
156
            $values[] = "$key = VALUES($key)";
157
        }
158
        $this->suffix = ";\n";
159
        if ($this->options['data_style'] === 'INSERT+UPDATE') {
160
            $this->suffix = "\nON DUPLICATE KEY UPDATE " . implode(', ', $values) . ";\n";
161
        }
162
        return $keys;
163
    }
164
165
    /**
166
     * @param array $row
167
     *
168
     * @return void
169
     */
170
    private function saveRowInBuffer(array $row)
171
    {
172
        $max_packet = ($this->driver->jush() === 'sqlite' ? 0 : 1048576); // default, minimum is 1024
173
        $s = ($max_packet ? "\n" : ' ') . '(' . implode(",\t", $row) . ')';
174
        if (!$this->buffer) {
175
            $this->buffer = $this->insert . $s;
176
            return;
177
        }
178
        if (strlen($this->buffer) + 4 + strlen($s) + strlen($this->suffix) < $max_packet) { // 4 - length specification
179
            $this->buffer .= ",$s";
180
            return;
181
        }
182
        $this->queries[] = $this->buffer . $this->suffix;
183
        $this->buffer = $this->insert . $s;
184
    }
185
186
    /**
187
     * @param string $table
188
     * @param array $fields
189
     * @param array $row
190
     * @param array $keys
191
     *
192
     * @return void
193
     */
194
    private function dumpRow(string $table, array $fields, array $row, array $keys)
195
    {
196
        if ($this->options['format'] !== 'sql') {
197
            if ($this->options['data_style'] === 'table') {
198
                $this->dumpCsv($keys);
199
                $this->options['data_style'] = 'INSERT';
200
            }
201
            $this->dumpCsv($row);
202
            return;
203
        }
204
        if (!$this->insert) {
205
            $this->insert = 'INSERT INTO ' . $this->driver->table($table) . ' (' .
206
                implode(', ', array_map(function ($key) {
207
                    return $this->driver->escapeId($key);
208
                }, $keys)) . ') VALUES';
209
        }
210
        foreach ($row as $key => $val) {
211
            $field = $fields[$key];
212
            $row[$key] = $this->convertToString($val, $field);
213
        }
214
        $this->saveRowInBuffer($row);
215
    }
216
217
    /** Export table data
218
     *
219
     * @param string $table
220
     * @param string $query
221
     *
222
     * @return void
223
     */
224
    private function dumpData(string $table, string $query)
225
    {
226
        if (!$this->options['data_style']) {
227
            return;
228
        }
229
        $fields = [];
230
        if ($this->options['format'] === 'sql') {
231
            if ($this->options['data_style'] === 'TRUNCATE+INSERT') {
232
                $this->queries[] = $this->driver->sqlForTruncateTable($table) . ";\n";
233
            }
234
            $fields = $this->driver->fields($table);
235
        }
236
        $statement = $this->driver->query($query); // 1 - MYSQLI_USE_RESULT //! enum and set as numbers
237
        if (!$statement) {
238
            if ($this->options['format'] === 'sql') {
239
                $this->queries[] = '-- ' . str_replace("\n", ' ', $this->driver->error()) . "\n";
240
            }
241
            return;
242
        }
243
        $this->insert = '';
244
        $this->buffer = '';
245
        $this->suffix = '';
246
        $keys = [];
247
        $fetch_function = ($table !== '' ? 'fetchAssoc' : 'fetchRow');
248
        while ($row = $statement->$fetch_function()) {
249
            if (empty($keys)) {
250
                $keys = $this->getDataRowKeys($row, $statement);
251
            }
252
            $this->dumpRow($table, $fields, $row, $keys);
253
        }
254
        if (($this->buffer)) {
255
            $this->queries[] = $this->buffer . $this->suffix;
256
        }
257
    }
258
259
    /**
260
     * @param string $table
261
     * @param bool $dumpTable
262
     * @param bool $dumpData
263
     *
264
     * @return void
265
     */
266
    private function dumpTable(string $table, bool $dumpTable, bool $dumpData)
267
    {
268
        if (!$dumpTable && !$dumpData) {
269
            return;
270
        }
271
        $this->dumpTableOrView($table, ($dumpTable ? $this->options['table_style'] : ''));
272
        if ($dumpData) {
273
            $fields = $this->driver->fields($table);
274
            $query = 'SELECT *' . $this->driver->convertFields($fields, $fields) .
275
                ' FROM ' . $this->driver->table($table);
276
            $this->dumpData($table, $query);
277
        }
278
        if ($this->options['is_sql'] && $this->options['triggers'] && $dumpTable &&
279
            ($triggers = $this->driver->sqlForCreateTrigger($table))) {
280
            $this->queries[] = 'DELIMITER ;';
281
            $this->queries[] = $triggers;
282
            $this->queries[] = 'DELIMITER ;';
283
        }
284
        if ($this->options['is_sql']) {
285
            $this->queries[] = '';
286
        }
287
    }
288
289
    /**
290
     * Dump tables
291
     *
292
     * @param string $database      The database name
293
     *
294
     * @return void
295
     */
296
    private function dumpTables(string $database)
297
    {
298
        $dbDumpTable = $this->tables['list'] === '*' && in_array($database, $this->databases['list']);
299
        $dbDumpData = in_array($database, $this->databases['data']);
300
        $this->views = []; // View names
301
        $this->fkeys = []; // Table names for foreign keys
302
        $dbTables = $this->driver->tableStatuses(true);
303
        foreach ($dbTables as $table => $tableStatus) {
304
            $isView = $this->driver->isView($tableStatus);
305
            if ($isView) {
306
                // The views will be dumped after the tables
307
                $this->views[] = $table;
308
                continue;
309
            }
310
            $this->fkeys[] = $table;
311
            $dumpTable = $dbDumpTable || in_array($table, $this->tables['list']);
312
            $dumpData = $dbDumpData || in_array($table, $this->tables['data']);
313
            $this->dumpTable($table, $dumpTable, $dumpData);
314
        }
315
    }
316
317
    /**
318
     * @return void
319
     */
320
    private function dumpViewsAndFKeys()
321
    {
322
        // Add FKs after creating tables (except in MySQL which uses SET FOREIGN_KEY_CHECKS=0)
323
        if ($this->driver->support('fkeys_sql')) {
324
            foreach ($this->fkeys as $table) {
325
                $this->queries[] = $this->driver->sqlForForeignKeys($table);
326
            }
327
        }
328
        // Dump the views after all the tables
329
        foreach ($this->views as $view) {
330
            $this->dumpTableOrView($view, $this->options['table_style'], 1);
331
        }
332
    }
333
}
334