Completed
Push — master ( 30b471...767c34 )
by Todd
03:41
created

MySqlDb::alterTableDb()   C

Complexity

Conditions 8
Paths 96

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 8.026

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 25
cts 27
cp 0.9259
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 22
nc 96
nop 2
crap 8.026
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Db\Drivers;
9
10
use Garden\Db\Db;
11
use Garden\Db\Identifier;
12
use Garden\Db\Literal;
13
use PDO;
14
15
/**
16
 * A {@link Db} class for connecting to MySQL.
17
 */
18
class MySqlDb extends Db {
19
    const MYSQL_DATE_FORMAT = 'Y-m-d H:i:s';
20
21
    /**
22
     * @var string
23
     */
24
    protected $dbname;
25
26
    protected static $map = [
27
        Db::OP_GT => '>',
28
        Db::OP_GTE => '>=',
29
        Db::OP_LT => '<',
30
        Db::OP_LTE => '<=',
31
        Db::OP_LIKE => 'like',
32
        Db::OP_AND => 'and',
33
        Db::OP_OR => 'or',
34
    ];
35
36
    /**
37
     * {@inheritdoc}
38
     */
39 6
    protected function dropTableDb($table, array $options = []) {
40
        $sql = 'drop table '.
41 6
            ($options[Db::OPTION_IGNORE] ? 'if exists ' : '').
42 6
            $this->prefixTable($table);
43
44 6
        $this->queryDefine($sql);
45 6
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 8
    protected function fetchTableDefDb($table) {
51 8
        $columns = $this->fetchColumnDefsDb($table);
52
53 8
        if (empty($columns)) {
54
            // A table with no columns does not exist.
55 6
            return null;
56
        }
57
58 8
        $indexes = $this->fetchIndexesDb($table);
59
60
        $tableDef = [
61 8
            'name' => $table,
62 8
            'columns' => $columns,
63
            'indexes' => $indexes
64 8
        ];
65
66 8
        return $tableDef;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 5
    protected function fetchColumnDefsDb($table) {
73 5
        $rows = $this->get(
74 5
            new Identifier('information_schema', 'COLUMNS'),
75
            [
76 5
                'TABLE_SCHEMA' => $this->getDbName(),
77 5
                'TABLE_NAME' => $this->prefixTable($table, false)
78 5
            ],
79
            [
80 5
                Db::OPTION_FETCH_MODE => PDO::FETCH_ASSOC,
81 5
                'order' => ['TABLE_NAME', 'ORDINAL_POSITION']
82 5
            ]
83 5
        );
84
85 5
        $columns = [];
86 5
        foreach ($rows as $row) {
87 5
            $columnType = $row['COLUMN_TYPE'];
88 5
            if ($columnType === 'tinyint(1)') {
89 1
                $columnType = 'bool';
90 1
            }
91 5
            $column = Db::typeDef($columnType);
92 5
            if ($column === null) {
93
                throw new \Exception("Unknown type '$columnType'.", 500);
94
            }
95
96 5
            $column['allowNull'] = strcasecmp($row['IS_NULLABLE'], 'YES') === 0;
97
98 5
            if (($default = $row['COLUMN_DEFAULT']) !== null) {
99 4
                $column['default'] = $this->forceType($default, $column['type']);
100 4
            }
101
102 5
            if ($row['EXTRA'] === 'auto_increment') {
103 1
                $column['autoIncrement'] = true;
104 1
            }
105
106 5
            if ($row['COLUMN_KEY'] === 'PRI') {
107 3
                $column['primary'] = true;
108 3
            }
109
110 5
            $columns[$row['COLUMN_NAME']] = $column;
111 5
        }
112
113 5
        return $columns;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 72
    public function get($table, array $where, array $options = []) {
120 72
        $sql = $this->buildSelect($table, $where, $options);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
121 72
        $result = $this->query($sql, [], $options);
122 72
        return $result;
123
    }
124
125
    /**
126
     * Build a sql select statement.
127
     *
128
     * @param string|Identifier $table The name of the main table.
129
     * @param array $where The where filter.
130
     * @param array $options An array of additional query options.
131
     * @return string Returns the select statement as a string.
132
     * @see Db::get()
133
     */
134 76
    protected function buildSelect($table, array $where, array $options = []) {
135 76
        $options += ['limit' => 0];
136
137 76
        $sql = '';
138
139
        // Build the select clause.
140 76
        if (!empty($options['columns'])) {
141 11
            $columns = array();
142 11
            foreach ($options['columns'] as $value) {
143 11
                $columns[] = $this->escape($value);
144 11
            }
145 11
            $sql .= 'select '.implode(', ', $columns);
146 11
        } else {
147 72
            $sql .= "select *";
148
        }
149
150
        // Build the from clause.
151 76
        if ($table instanceof Literal) {
152
            $table = $table->getValue($this);
153
        } else {
154 76
            $table = $this->prefixTable($table);
155
        }
156 76
        $sql .= "\nfrom $table";
157
158
        // Build the where clause.
159 76
        $whereString = $this->buildWhere($where, Db::OP_AND);
160 76
        if ($whereString) {
161 49
            $sql .= "\nwhere ".$whereString;
162 49
        }
163
164
        // Build the order.
165 76
        if (!empty($options['order'])) {
166 40
            $orders = [];
167 40
            foreach ($options['order'] as $column) {
168 40
                if ($column[0] === '-') {
169 2
                    $order = $this->escape(substr($column, 1)).' desc';
170 2
                } else {
171 40
                    $order = $this->escape($column);
172
                }
173 40
                $orders[] = $order;
174 40
            }
175 40
            $sql .= "\norder by ".implode(', ', $orders);
176 40
        }
177
178
        // Build the limit, offset.
179 76
        if (!empty($options['limit'])) {
180 28
            $limit = (int)$options['limit'];
181 28
            $sql .= "\nlimit $limit";
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
182 28
        }
183
184 76
        if (!empty($options['offset'])) {
185 1
            $sql .= ' offset '.((int)$options['offset']);
186 76
        } elseif (isset($options['page'])) {
187
            $offset = $options['limit'] * ($options['page'] - 1);
188
            $sql .= ' offset '.$offset;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
189
        }
190
191 76
        return $sql;
192
    }
193
194
    /**
195
     * Build a where clause from a where array.
196
     *
197
     * @param array $where There where string.
198
     * This is an array in the form `['column' => 'value']` with more advanced options for non-equality comparisons.
199
     * @param string $op The logical operator to join multiple field comparisons.
200
     * @return string The where string.
201
     */
202 78
    protected function buildWhere($where, $op = Db::OP_AND) {
203 78
        $map = static::$map;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
204 78
        $strop = $map[$op];
205
206 78
        $result = '';
207 78
        foreach ($where as $column => $value) {
208 51
            $btcolumn = $this->escape($column);
209
210 51
            if (is_array($value)) {
211 30
                if (is_numeric($column)) {
212
                    // This is a bracketed expression.
213 4
                    $result .= (empty($result) ? '' : "\n  $strop ").
214 4
                        "(\n  ".
215 4
                        $this->buildWhere($value, $op).
216 4
                        "\n  )";
217 30
                } elseif (in_array($column, [Db::OP_AND, Db::OP_OR])) {
218
                    // This is an AND/OR expression.
219 4
                    $result .= (empty($result) ? '' : "\n  $strop ").
220 4
                        "(\n  ".
221 4
                        $this->buildWhere($value, $column).
222 4
                        "\n  )";
223 4
                } else {
224 26
                    if (isset($value[0])) {
225
                        // This is a short in syntax.
226 2
                        $value = [Db::OP_IN => $value];
227 2
                    }
228
229 26
                    foreach ($value as $vop => $rval) {
230 26
                        if ($result) {
231 4
                            $result .= "\n  $strop ";
232 4
                        }
233
234
                        switch ($vop) {
235 26
                            case Db::OP_AND:
236 26
                            case Db::OP_OR:
237 4
                                if (is_numeric($column)) {
238
                                    $innerWhere = $rval;
239
                                } else {
240 4
                                    $innerWhere = [$column => $rval];
241
                                }
242
                                $result .= "(\n  ".
243 4
                                    $this->buildWhere($innerWhere, $vop).
0 ignored issues
show
Bug introduced by
It seems like $innerWhere defined by $rval on line 238 can also be of type null; however, Garden\Db\Drivers\MySqlDb::buildWhere() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
244 4
                                    "\n  )";
245 4
                                break;
246 26
                            case Db::OP_EQ:
247 6
                                if ($rval === null) {
248
                                    $result .= "$btcolumn is null";
249 6
                                } elseif (is_array($rval)) {
250 2
                                    $result .= "$btcolumn in ".$this->bracketList($rval);
251 2
                                } else {
252 4
                                    $result .= "$btcolumn = ".$this->quote($rval);
253
                                }
254 6
                                break;
255 22
                            case Db::OP_GT:
256 22
                            case Db::OP_GTE:
257 22
                            case Db::OP_LT:
258 22
                            case Db::OP_LTE:
259 12
                                $result .= "$btcolumn {$map[$vop]} ".$this->quote($rval);
260 12
                                break;
261 10
                            case Db::OP_LIKE:
262
                                $result .= $this->buildLike($btcolumn, $rval);
263
                                break;
264 10
                            case Db::OP_IN:
265
                                // Quote the in values.
266 4
                                $rval = array_map([$this, 'quote'], (array)$rval);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
267 4
                                $result .= "$btcolumn in (".implode(', ', $rval).')';
268 4
                                break;
269 6
                            case Db::OP_NEQ:
270 6
                                if ($rval === null) {
271 2
                                    $result .= "$btcolumn is not null";
272 6
                                } elseif (is_array($rval)) {
273 2
                                    $result .= "$btcolumn not in ".$this->bracketList($rval);
274 2
                                } else {
275 2
                                    $result .= "$btcolumn <> ".$this->quote($rval);
276
                                }
277 6
                                break;
278
                        }
279 26
                    }
280
                }
281 30
            } else {
282 25
                if ($result) {
283 9
                    $result .= "\n  $strop ";
284 9
                }
285
286
                // This is just an equality operator.
287 25
                if ($value === null) {
288 2
                    $result .= "$btcolumn is null";
289 2
                } else {
290 23
                    $result .= "$btcolumn = ".$this->quote($value);
291
                }
292
            }
293 78
        }
294 78
        return $result;
295
    }
296
297
    /**
298
     * Build a like expression.
299
     *
300
     * @param string $column The column name.
301
     * @param mixed $value The right-hand value.
302
     * @return string Returns the like expression.
303
     * @internal param bool $quotevals Whether or not to quote the values.
304
     */
305
    protected function buildLike($column, $value) {
306
        return "$column like ".$this->quote($value);
307
    }
308
309
    /**
310
     * Convert an array into a bracketed list suitable for MySQL clauses.
311
     *
312
     * @param array $row The row to expand.
313
     * @param string $quote The quotes to surroud the items with. There are two special cases.
314
     * ' (single quote)
315
     * : The row will be passed through {@link PDO::quote()}.
316
     * ` (backticks)
317
     * : The row will be passed through {@link MySqlDb::backtick()}.
318
     * @return string Returns the bracket list.
319
     */
320 72
    public function bracketList($row, $quote = "'") {
321
        switch ($quote) {
322 72
            case "'":
323 59
                $row = array_map([$this, 'quote'], $row);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
324 59
                $quote = '';
325 59
                break;
326 72
            case '`':
327 72
                $row = array_map([$this, 'escape'], $row);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
328 72
                $quote = '';
329 72
                break;
330
        }
331
332 72
        return "($quote".implode("$quote, $quote", $row)."$quote)";
333
    }
334
335
336
    /**
337
     * Get the current database name.
338
     *
339
     * @return mixed
340
     */
341 5
    private function getDbName() {
342 5
        if (!isset($this->dbname)) {
343
            $this->dbname = $this->getPDO()->query('select database()')->fetchColumn();
344
        }
345 5
        return $this->dbname;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 17
    protected function nativeDbType(array $type) {
352 17
        static $translations = ['bool' => 'tinyint(1)', 'byte' => 'tinyint', 'short' => 'smallint', 'long' => 'bigint'];
353
354
        // Translate the dbtype to a MySQL native type.
355 17
        if (isset($translations[$type['dbtype']])) {
356 1
            $type['dbtype'] = $translations[$type['dbtype']];
357 1
        }
358
359
        // Unsigned is represented differently in MySQL.
360 17
        $unsigned = !empty($type['unsigned']);
361 17
        unset ($type['unsigned']);
362
363 17
        $dbType = static::dbType($type).($unsigned ? ' unsigned' : '');
364
365 17
        return $dbType;
366
    }
367
368
    /**
369
     * Parse a column type string and return it in a way that is suitable for a create/alter table statement.
370
     *
371
     * @param string $typeString The string to parse.
372
     * @return string Returns a canonical string.
0 ignored issues
show
Documentation introduced by
Should the return type not be string[]|null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
373
     */
374
    protected function columnTypeString($typeString) {
375
        $type = null;
376
377
        if (substr($typeString, 0, 4) === 'enum') {
378
            // This is an enum which will come in as an array.
379
            if (preg_match_all("`'([^']+)'`", $typeString, $matches)) {
380
                $type = $matches[1];
381
            }
382
        } else {
383
            if (preg_match('`([a-z]+)\s*(?:\((\d+(?:\s*,\s*\d+)*)\))?\s*(unsigned)?`', $typeString, $matches)) {
384
                //         var_dump($matches);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
385
                $str = $matches[1];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
386
                $length = self::val(2, $matches);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
387
                $unsigned = self::val(3, $matches);
388
389
                if (substr($str, 0, 1) == 'u') {
390
                    $unsigned = true;
391
                    $str = substr($str, 1);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
392
                }
393
394
                // Remove the length from types without real lengths.
395
                if (in_array($str, array('tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double'))) {
396
                    $length = null;
397
                }
398
399
                $type = $str;
400
                if ($length) {
401
                    $length = str_replace(' ', '', $length);
402
                    $type .= "($length)";
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
403
                }
404
                if ($unsigned) {
405
                    $type .= ' unsigned';
406
                }
407
            }
408
        }
409
410
        if (!$type) {
411
            debug_print_backtrace();
412
            trigger_error("Couldn't parse type $typeString", E_USER_ERROR);
413
        }
414
415
        return $type;
416
    }
417
418
    /**
419
     * Get the indexes from the database.
420
     *
421
     * @param string $table The name of the table to get the indexes for.
422
     * @return array|null
423
     */
424 4
    protected function fetchIndexesDb($table = '') {
425 4
        $stm = $this->get(
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
426 4
            new Identifier('information_schema', 'STATISTICS'),
427
            [
428 4
                'TABLE_SCHEMA' => $this->getDbName(),
429 4
                'TABLE_NAME' => $this->prefixTable($table, false)
430 4
            ],
431
            [
432
                'columns' => [
433 4
                    'INDEX_NAME',
434 4
                    'COLUMN_NAME',
435
                    'NON_UNIQUE'
436 4
                ],
437 4
                'order' => ['INDEX_NAME', 'SEQ_IN_INDEX']
438 4
            ]
439 4
        );
440 4
        $indexRows = $stm->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
441
442 4
        $indexes = [];
443 4
        foreach ($indexRows as $indexName => $columns) {
444
            $index = [
445 4
                'type' => null,
446 4
                'columns' => array_column($columns, 'COLUMN_NAME'),
447
                'name' => $indexName
448 4
            ];
449
450 4
            if ($indexName === 'PRIMARY') {
451 2
                $index['type'] = Db::INDEX_PK;
452 2
            } else {
453 2
                $index['type'] = $columns[0]['NON_UNIQUE'] ? Db::INDEX_IX : Db::INDEX_UNIQUE;
454
            }
455 4
            $indexes[] = $index;
456 4
        }
457
458 4
        return $indexes;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464
    protected function fetchTableNamesDb() {
465
        // Get the table names.
466
        $tables = $this->get(
467
            new Identifier('information_schema', 'TABLES'),
468
            [
469
                'TABLE_SCHEMA' => $this->getDbName(),
470
                'TABLE_NAME' => [Db::OP_LIKE => $this->escapeLike($this->getPx()).'%']
471
            ],
472
            [
473
                'columns' => ['TABLE_NAME'],
474
                'fetchMode' => PDO::FETCH_ASSOC
475
            ]
476
        );
477
478
        return $tables->fetchAll(PDO::FETCH_COLUMN);
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484 15
    public function insert($table, array $row, array $options = []) {
485 15
        $sql = $this->buildInsert($table, $row, $options);
486 15
        $id = $this->queryID($sql, [], $options);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
487 15
        if (is_numeric($id)) {
488 15
            return (int)$id;
489
        } else {
490
            return $id;
491
        }
492
    }
493
494
    /**
495
     * Build an insert statement.
496
     *
497
     * @param string|Identifier $table The name of the table to insert to.
498
     * @param array $row The row to insert.
499
     * @param array $options An array of options for the insert. See {@link Db::insert} for the options.
500
     * @return string Returns the the sql string of the insert statement.
501
     */
502 32
    protected function buildInsert($table, array $row, $options = []) {
503 32
        if (self::val(Db::OPTION_UPSERT, $options)) {
504 2
            return $this->buildUpsert($table, $row, $options);
0 ignored issues
show
Bug introduced by
It seems like $table defined by parameter $table on line 502 can also be of type object<Garden\Db\Identifier>; however, Garden\Db\Drivers\MySqlDb::buildUpsert() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
505 32
        } elseif (self::val(Db::OPTION_IGNORE, $options)) {
506 2
            $sql = 'insert ignore ';
507 32
        } elseif (self::val(Db::OPTION_REPLACE, $options)) {
508 2
            $sql = 'replace ';
509 2
        } else {
510 30
            $sql = 'insert ';
511
        }
512 32
        $sql .= $this->prefixTable($table);
513
514
        // Add the list of values.
515
        $sql .=
516 32
            "\n".$this->bracketList(array_keys($row), '`').
517 32
            "\nvalues".$this->bracketList($row, "'");
518
519 32
        return $sql;
520
    }
521
522
    /**
523
     * Build an upsert statement.
524
     *
525
     * An upsert statement is an insert on duplicate key statement in MySQL.
526
     *
527
     * @param string $table The name of the table to update.
528
     * @param array $row The row to insert or update.
529
     * @param array $options An array of additional query options.
530
     * @return string Returns the upsert statement as a string.
531
     */
532 2
    protected function buildUpsert($table, array $row, $options = []) {
533
        // Build the initial insert statement first.
534 2
        unset($options[Db::OPTION_UPSERT]);
535 2
        $sql = $this->buildInsert($table, $row, $options);
536
537
        // Add the duplicate key stuff.
538 2
        $updates = [];
539 2
        foreach ($row as $key => $value) {
540 2
            $updates[] = $this->escape($key).' = values('.$this->escape($key).')';
541 2
        }
542 2
        $sql .= "\non duplicate key update ".implode(', ', $updates);
543
544 2
        return $sql;
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550 54
    public function load($table, $rows, array $options = []) {
551 54
        $count = 0;
552 54
        $first = true;
553 54
        $spec = [];
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
554 54
        $stmt = null;
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
555
556
        // Loop over the rows and insert them with the statement.
557 54
        foreach ($rows as $row) {
558 44
            if ($first) {
559
                // Build the insert statement from the first row.
560 44
                foreach ($row as $key => $value) {
561 44
                    $spec[$key] = new Literal($this->paramName($key));
562 44
                }
563
564 44
                $sql = $this->buildInsert($table, $spec, $options);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
565 44
                $stmt = $this->getPDO()->prepare($sql);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
566 44
                $first = false;
567 44
            }
568
569 44
            $stmt->execute($row);
570 44
            $count += $stmt->rowCount();
571 54
        }
572
573 54
        return $count;
574
    }
575
576
    /**
577
     * Make a valid PDO parameter name from a string.
578
     *
579
     * This method replaces invalid placeholder characters with underscores.
580
     *
581
     * @param string $name The name to replace.
582
     * @return string
583
     */
584 44
    protected function paramName($name) {
585 44
        $result = ':'.preg_replace('`[^a-zA-Z0-9_]`', '_', $name);
586 44
        return $result;
587
    }
588
589
    /**
590
     * {@inheritdoc}
591
     */
592 6
    public function update($table, array $set, array $where, array $options = []) {
593 6
        $sql = $this->buildUpdate($table, $set, $where, $options);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
594 6
        $result = $this->queryModify($sql, [], $options);
595
596 6
        return $result;
597
    }
598
599
    /**
600
     * Build a sql update statement.
601
     *
602
     * @param string|Identifier $table The name of the table to update.
603
     * @param array $set An array of columns to set.
604
     * @param array $where The where filter.
605
     * @param array $options Additional options for the query.
606
     * @return string Returns the update statement as a string.
607
     */
608 2
    protected function buildUpdate($table, array $set, array $where, array $options = []) {
609
        $sql = 'update '.
610 2
            (self::val(Db::OPTION_IGNORE, $options) ? 'ignore ' : '').
611 2
            $this->prefixTable($table).
612 2
            "\nset\n  ";
613
614 2
        $parts = [];
615 2
        foreach ($set as $key => $value) {
616 2
            $quotedKey = $this->escape($key);
617
618 2
            $parts[] = $quotedKey.' = '.$this->quote($value);
619 2
        }
620 2
        $sql .= implode(",\n  ", $parts);
621
622 2
        if (!empty($where)) {
623 2
            $sql .= "\nwhere ".$this->buildWhere($where, Db::OP_AND);
624 2
        }
625
626 2
        return $sql;
627
    }
628
629
    /**
630
     * {@inheritdoc}
631
     */
632 30
    public function delete($table, array $where, array $options = []) {
633 30
        if (self::val(Db::OPTION_TRUNCATE, $options)) {
634
            if (!empty($where)) {
635
                throw new \InvalidArgumentException("You cannot truncate $table with a where filter.", 500);
636
            }
637
            $sql = 'truncate table '.$this->prefixTable($table);
638
        } else {
639 30
            $sql = 'delete from '.$this->prefixTable($table);
640
641 30
            if (!empty($where)) {
642
                $sql .= "\nwhere ".$this->buildWhere($where);
643
            }
644
        }
645 30
        return $this->queryModify($sql, [], $options);
646
    }
647
648
    /**
649
     * {@inheritdoc}
650
     */
651 17
    protected function createTableDb(array $tableDef, array $options = []) {
652 17
        $table = $tableDef['name'];
653
654
        // The table doesn't exist so this is a create table.
655 17
        $parts = array();
656 17
        foreach ($tableDef['columns'] as $name => $cdef) {
657 17
            $parts[] = $this->columnDefString($name, $cdef);
658 17
        }
659
660 17
        foreach (self::val('indexes', $tableDef, []) as $index) {
661 17
            $indexDef = $this->indexDefString($table, $index);
662 17
            if (!empty($indexDef)) {
663 16
                $parts[] = $indexDef;
664 16
            }
665 17
        }
666
667 17
        $tableName = $this->prefixTable($table);
668 17
        $sql = "create table $tableName (\n  ".
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
669 17
            implode(",\n  ", $parts).
670 17
            "\n)";
671
672 17
        if (self::val('collate', $options)) {
673
            $sql .= "\n collate {$options['collate']}";
674
        }
675
676 17
        $this->queryDefine($sql, $options);
677 17
    }
678
679
    /**
680
     * Construct a column definition string from an array defintion.
681
     *
682
     * @param string $name The name of the column.
683
     * @param array $cdef The column definition.
684
     * @return string Returns a string representing the column definition.
685
     */
686 17
    protected function columnDefString($name, array $cdef) {
687 17
        $result = $this->escape($name).' '.$this->nativeDbType($cdef);
688
689 17
        if (!self::val('allowNull', $cdef)) {
690 16
            $result .= ' not null';
691 16
        }
692
693 17
        if (isset($cdef['default'])) {
694 14
            $result .= ' default '.$this->quote($cdef['default']);
695 14
        }
696
697 17
        if (self::val('autoIncrement', $cdef)) {
698 10
            $result .= ' auto_increment';
699 10
        }
700
701 17
        return $result;
702
    }
703
704
    /**
705
     * Return the SDL string that defines an index.
706
     *
707
     * @param string $table The name of the table that the index is on.
708
     * @param array $def The index definition. This definition should have the following keys.
709
     *
710
     * columns
711
     * : An array of columns in the index.
712
     * type
713
     * : One of "index", "unique", or "primary".
714
     * @return null|string Returns the index string or null if the index is not correct.
715
     */
716 17
    protected function indexDefString($table, array $def) {
717 17
        $indexName = $this->escape($this->buildIndexName($table, $def));
718 17
        switch (self::val('type', $def, Db::INDEX_IX)) {
719 17
            case Db::INDEX_IX:
720 11
                return "index $indexName ".$this->bracketList($def['columns'], '`');
721 15
            case Db::INDEX_UNIQUE:
722 1
                return "unique $indexName ".$this->bracketList($def['columns'], '`');
723 15
            case Db::INDEX_PK:
724 14
                return "primary key ".$this->bracketList($def['columns'], '`');
725 2
        }
726 2
        return null;
727
    }
728
729
    /**
730
     * {@inheritdoc}
731
     */
732 4
    protected function alterTableDb(array $alterDef, array $options = []) {
733 4
        $table = $alterDef['name'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
734 4
        $columnOrders = $this->getColumnOrders($alterDef['def']['columns']);
735 4
        $parts = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
736
737
        // Add the columns and indexes.
738 4
        foreach ($alterDef['add']['columns'] as $cname => $cdef) {
739
            // Figure out the order of the column.
740 2
            $pos = self::val($cname, $columnOrders, '');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
741 2
            $parts[] = 'add '.$this->columnDefString($cname, $cdef).$pos;
742 4
        }
743 4
        foreach ($alterDef['add']['indexes'] as $ixdef) {
744 3
            $parts[] = 'add '.$this->indexDefString($table, $ixdef);
745 4
        }
746
747
        // Alter the columns.
748 4
        foreach ($alterDef['alter']['columns'] as $cname => $cdef) {
749 2
            $parts[] = 'modify '.$this->columnDefString($cname, $cdef);
750 4
        }
751
752
        // Drop the columns and indexes.
753 4
        foreach ($alterDef['drop']['columns'] as $cname => $_) {
754
            $parts[] = 'drop '.$this->escape($cname);
755 4
        }
756 4
        foreach ($alterDef['drop']['indexes'] as $ixdef) {
757 2
            $parts[] = 'drop index '.$this->escape($ixdef['name']);
758 4
        }
759
760 4
        if (empty($parts)) {
761
            return false;
762
        }
763
764
        $sql = 'alter '.
765 4
            (self::val(Db::OPTION_IGNORE, $options) ? 'ignore ' : '').
766 4
            'table '.$this->prefixTable($table)."\n  ".
767 4
            implode(",\n  ", $parts);
768
769 4
        $this->queryDefine($sql, $options);
770 4
    }
771
772
    /**
773
     * Get an array of column orders so that added columns can be slotted into their correct spot.
774
     *
775
     * @param array $cdefs An array of column definitions.
776
     * @return array Returns an array of column orders suitable for an `alter table` statement.
777
     */
778 4
    private function getColumnOrders($cdefs) {
779 4
        $orders = array_flip(array_keys($cdefs));
780
781 4
        $prev = ' first';
782 4
        foreach ($orders as $cname => &$value) {
783 4
            $value = $prev;
784 4
            $prev = ' after '.$this->escape($cname);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
785 4
        }
786 4
        return $orders;
787
    }
788
789
    /**
790
     * Force a value into the appropriate php type based on its SQL type.
791
     *
792
     * @param mixed $value The value to force.
793
     * @param string $type The sqlite type name.
794
     * @return mixed Returns $value cast to the appropriate type.
795
     */
796 4
    protected function forceType($value, $type) {
797 4
        $type = strtolower($type);
798
799 4
        if ($type === 'null') {
800
            return null;
801 4
        } elseif ($type === 'boolean') {
802 1
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
803 3
        } elseif (in_array($type, ['int', 'integer', 'tinyint', 'smallint',
804 3
            'mediumint', 'bigint', 'unsigned big int', 'int2', 'int8', 'boolean'])) {
805 3
            return filter_var($value, FILTER_VALIDATE_INT);
806
        } elseif (in_array($type, ['real', 'double', 'double precision', 'float',
807
            'numeric', 'number', 'decimal(10,5)'])) {
808
            return filter_var($value, FILTER_VALIDATE_FLOAT);
809
        } else {
810
            return (string)$value;
811
        }
812
    }
813
814 40
    public function quote($value, $column = '') {
815 40
        if (is_bool($value)) {
816 1
            return (string)(int)$value;
817
        } else {
818 40
            return parent::quote($value, $column);
819
        }
820
    }
821
}
822