Passed
Push — php8 ( bd975b...94eefc )
by Doug
13:01
created

PDODatabase::query()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Database Access Layer.
6
 * @author Doug Wright
7
 */
8
9
namespace DVDoug\DB;
10
11
/**
12
 * PDO-backed database connection (common parts).
13
 * @author Doug Wright
14
 */
15
abstract class PDODatabase extends \PDO implements DatabaseInterface
16
{
17
    /**
18
     * Character to use when quoting identifiers.
19
     */
20
    public const IDENTIFIER_OPENQUOTE = '"';
21
22
    /**
23
     * Character to use when quoting identifiers.
24
     */
25
    public const IDENTIFIER_CLOSEQUOTE = '"';
26
27
    /**
28
     * Constructor.
29
     * @param string $aDSN
30
     * @param string $aUsername
31
     * @param string $aPassword
32
     */
33 40
    public function __construct($aDSN, $aUsername, $aPassword, array $aDriverOptions = null)
34
    {
35 40
        parent::__construct($aDSN, $aUsername, $aPassword, $aDriverOptions);
36 40
        $this->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
37 40
        $this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, ['\DVDoug\DB\PDOStatement']);
38 40
    }
39
40
    /**
41
     * Returns the ID of the last inserted row or sequence value.
42
     * @param string $aName name of the sequence object (if any) from which the ID should be returned
43
     */
44
    public function getLastInsertId($aName = null): string
45 20
    {
46
        return parent::lastInsertID($aName);
47 20
    }
48
49
    /**
50
     * Escapes/quotes a parameter for use in a query.
51
     *
52
     * @param mixed $aParam     the parameter to be quoted
53
     * @param int   $aParamType data type hint for drivers
54
     *
55
     * @throws \RuntimeException
56
     * @return string            a quoted string that is theoretically safe to pass into an SQL statement
57
     */
58
    public function escape($aParam, $aParamType = DatabaseInterface::PARAM_IS_STR)
59
    {
60
        switch ($aParamType) {
61
            case self::PARAM_IS_INT:
62
                if (is_int($aParam) || ctype_digit($aParam)) {
63
                    return (int) $aParam;
64
                } else {
65
                    throw new \RuntimeException("Parameter {$aParam} is not an integer");
66
                }
67
                break;
68 12
69
            default:
70
                return parent::quote($aParam, $aParamType);
71 12
        }
72 8
    }
73 4
74
    /**
75 4
     * Adds appropriate quotes to an identifier so it can be safely used in an SQL statement.
76
     * @param mixed $aIdentifier the parameter to be quoted
77
     */
78
    public function quoteIdentifier($aIdentifier): string
79
    {
80 8
        return static::IDENTIFIER_OPENQUOTE . $aIdentifier . static::IDENTIFIER_CLOSEQUOTE;
81
    }
82
83
    /**
84
     * Get MySQL table definition.
85
     * @param string $aDatabase       database/schema name
86
     * @param string $aTable          table name
87
     * @param bool   $aSkipUnusedCols whether to skip unused columns
88 20
     */
89
    public function getMySQLTableDef($aDatabase, $aTable, $aSkipUnusedCols = true): string
90 20
    {
91
        $table = strtolower($aTable);
92
93
        $columns = $this->getTableColumns($aDatabase, $aTable);
94
95
        $colDefs = [];
96
97
        foreach ($columns as $columnName => $column) {
98
            if ($aSkipUnusedCols && $column->getDistinctValueCount() <= 1) {
99 8
                unset($columns[$columnName]);
100
                continue;
101 8
            }
102
103 8
            $colDefs[] = $column->getMySQLColumnDef();
104
        }
105 8
106
        $primaryKey = $this->getPrimaryKey($aDatabase, $aTable);
107 8
        $indexes = $this->getIndexes($aDatabase, $aTable);
108 8
109
        $tableDef = "CREATE TABLE `{$table}` (" . "\n";
110
        $tableDef .= implode(',' . "\n", $colDefs);
111
112
        if ($primaryKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $primaryKey of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
113 8
            $length = 0;
114
            foreach ($primaryKey as $primaryCol) {
115
                $length += $columns[$primaryCol]->getLength();
116 8
            }
117 8
            if ($length <= 191) { //skip index if too long for MySQL
118
                $tableDef .= ',' . "\n" . "\n";
119 8
                $tableDef .= 'PRIMARY KEY (';
120 8
                $tableDef .= implode(', ' . "\n", array_map(function ($c) {return '`' . strtolower($c) . '`'; }, $primaryKey));
121
                $tableDef .= ')';
122 8
            }
123
        }
124
125
        if ($indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
126
            foreach ($indexes as $indexName => $indexColumns) {
127
                $length = 0;
128
                foreach ($indexColumns as &$col) {
129
                    if (!in_array($col, array_keys($columns))) { //skip index if it includes a skipped column
130
                        continue 2;
131
                    }
132
133
                    $length += $columns[$col]->getLength();
134
                    if ($length > 191) { //skip index if too long for MySQL
135 8
                        continue 2;
136
                    }
137
138
                    if (preg_match('/(BLOB|TEXT)$/', $columns[$col]->getMySQLType())) {
139
                        continue 2;
140
                    }
141
                }
142
                $tableDef .= ',' . "\n";
143
                $tableDef .= 'KEY `' . strtolower($indexName) . '` (';
144
                $tableDef .= implode(', ', array_map(function ($c) {return '`' . strtolower($c) . '`'; }, $indexColumns));
145
                $tableDef .= ')';
146
            }
147
        }
148
        $tableDef .= ') ENGINE=InnoDB ROW_FORMAT=COMPRESSED';
149
150
        return $tableDef;
151
    }
152
153
    /**
154
     * Get Oracle table definition.
155
     * @param string $aDatabase       database/schema name
156
     * @param string $aTable          table name
157
     * @param bool   $aSkipUnusedCols whether to skip unused columns
158 8
     */
159
    public function getOracleTableDef($aDatabase, $aTable, $aSkipUnusedCols = true): string
160 8
    {
161
        $table = strtolower($aTable);
162
163
        $columns = $this->getTableColumns($aDatabase, $aTable);
164
165
        $colDefs = [];
166
167
        foreach ($columns as $columnName => $column) {
168
            if ($aSkipUnusedCols && $column->getDistinctValueCount() <= 1) {
169 8
                unset($columns[$columnName]);
170
                continue;
171 8
            }
172
173 8
            $colDefs[] = $column->getOracleColumnDef();
174
        }
175 8
176
        $primaryKey = $this->getPrimaryKey($aDatabase, $aTable);
177 8
        $indexes = $this->getIndexes($aDatabase, $aTable);
178 8
179
        $tableDef = "CREATE TABLE `{$table}` (" . "\n";
180
        $tableDef .= implode(',' . "\n", $colDefs);
181
182
        if ($primaryKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $primaryKey of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
183 8
            $tableDef .= ',' . "\n" . "\n";
184
            $tableDef .= 'PRIMARY KEY (';
185
            $tableDef .= implode(', ' . "\n", array_map(function ($c) {return '"' . strtolower($c) . '"'; }, $primaryKey));
186 8
            $tableDef .= ')';
187 8
        }
188
189 8
        if ($indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
190 8
            foreach ($indexes as $indexName => $indexColumns) {
191
                foreach ($indexColumns as &$col) {
192 8
                    if (!in_array($col, array_keys($columns))) { //skip index if it includes a skipped column
193
                        continue 2;
194
                    }
195
                }
196
                $tableDef .= ',' . "\n";
197
                $tableDef .= 'KEY `' . strtolower($indexName) . '` (';
198
                $tableDef .= implode(', ', array_map(function ($c) {return '"' . strtolower($c) . '"'; }, $indexColumns));
199 8
                $tableDef .= ')';
200
            }
201
        }
202
        $tableDef .= ')';
203
204
        return $tableDef;
205
    }
206
}
207