Completed
Push — master ( 0cc82e...4ac1c9 )
by Doug
30:45 queued 29:12
created

PDODatabase::getOracleTableDef()   B

Complexity

Conditions 9
Paths 12

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 16.1088

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 28
c 1
b 0
f 0
nc 12
nop 3
dl 0
loc 46
ccs 15
cts 27
cp 0.5556
crap 16.1088
rs 8.0555
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
        self::setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
0 ignored issues
show
Bug Best Practice introduced by
The method PDO::setAttribute() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

36
        self::/** @scrutinizer ignore-call */ 
37
              setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
Loading history...
37 40
        self::setAttribute(\PDO::ATTR_STATEMENT_CLASS, ['\DVDoug\DB\PDOStatement']);
38 40
    }
39
40
    /**
41
     * Executes an SQL statement, returning the result set if any as a StatementInterface object.
42
     * @param  string                  $aSQL the SQL statement to execute
43
     * @return StatementInterface|bool
44
     */
45 20
    public function query($aSQL)
46
    {
47 20
        return parent::query($aSQL);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::query($aSQL) also could return the type PDOStatement which is incompatible with the documented return type DVDoug\DB\StatementInterface|boolean.
Loading history...
48
    }
49
50
    /**
51
     * Returns the ID of the last inserted row or sequence value.
52
     * @param  string $aName name of the sequence object (if any) from which the ID should be returned
53
     * @return string
54
     */
55
    public function getLastInsertId($aName = null)
56
    {
57
        return parent::lastInsertID($aName);
58
    }
59
60
    /**
61
     * Escapes/quotes a parameter for use in a query.
62
     *
63
     * @param mixed $aParam     the parameter to be quoted
64
     * @param int   $aParamType data type hint for drivers
65
     *
66
     * @throws \RuntimeException
67
     * @return string            a quoted string that is theoretically safe to pass into an SQL statement
68
     */
69 12
    public function escape($aParam, $aParamType = DatabaseInterface::PARAM_IS_STR)
70
    {
71
        switch ($aParamType) {
72 12
            case self::PARAM_IS_INT:
73 8
                if (is_int($aParam) || ctype_digit($aParam)) {
74 4
                    return (int) $aParam;
75
                } else {
76 4
                    throw new \RuntimeException("Parameter {$aParam} is not an integer");
77
                }
78
                break;
79
80
            default:
81 8
                return parent::quote($aParam, $aParamType);
82
        }
83
    }
84
85
    /**
86
     * Adds appropriate quotes to an identifier so it can be safely used in an SQL statement.
87
     * @param  mixed  $aIdentifier the parameter to be quoted
88
     * @return string
89
     */
90 20
    public function quoteIdentifier($aIdentifier)
91
    {
92 20
        return static::IDENTIFIER_OPENQUOTE . $aIdentifier . static::IDENTIFIER_CLOSEQUOTE;
93
    }
94
95
    /**
96
     * Get MySQL table definition.
97
     * @param  string $aDatabase       database/schema name
98
     * @param  string $aTable          table name
99
     * @param  bool   $aSkipUnusedCols whether to skip unused columns
100
     * @return string
101
     */
102 8
    public function getMySQLTableDef($aDatabase, $aTable, $aSkipUnusedCols = true)
103
    {
104 8
        $table = strtolower($aTable);
105
106 8
        $columns = $this->getTableColumns($aDatabase, $aTable);
107
108 8
        $colDefs = [];
109
110 8
        foreach ($columns as $columnName => $column) {
111 8
            if ($aSkipUnusedCols && $column->getDistinctValueCount() <= 1) {
112
                unset($columns[$columnName]);
113
                continue;
114
            }
115
116 8
            $colDefs[] = $column->getMySQLColumnDef();
117
        }
118
119 8
        $primaryKey = $this->getPrimaryKey($aDatabase, $aTable);
120 8
        $indexes = $this->getIndexes($aDatabase, $aTable);
121
122 8
        $tableDef = "CREATE TABLE `{$table}` (" . "\n";
123 8
        $tableDef .= implode(',' . "\n", $colDefs);
124
125 8
        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...
126
            $length = 0;
127
            foreach ($primaryKey as $primaryCol) {
128
                $length += $columns[$primaryCol]->getLength();
129
            }
130
            if ($length <= 191) { //skip index if too long for MySQL
131
                $tableDef .= ',' . "\n" . "\n";
132
                $tableDef .= 'PRIMARY KEY (';
133
                $tableDef .= implode(', ' . "\n", array_map(function ($c) {return '`' . strtolower($c) . '`'; }, $primaryKey));
134
                $tableDef .= ')';
135
            }
136
        }
137
138 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...
139
            foreach ($indexes as $indexName => $indexColumns) {
140
                $length = 0;
141
                foreach ($indexColumns as &$col) {
142
                    if (!in_array($col, array_keys($columns))) { //skip index if it includes a skipped column
143
                        continue 2;
144
                    }
145
146
                    $length += $columns[$col]->getLength();
147
                    if ($length > 191) { //skip index if too long for MySQL
148
                        continue 2;
149
                    }
150
151
                    if (preg_match('/(BLOB|TEXT)$/', $columns[$col]->getMySQLType())) {
152
                        continue 2;
153
                    }
154
                }
155
                $tableDef .= ',' . "\n";
156
                $tableDef .= 'KEY `' . strtolower($indexName) . '` (';
157
                $tableDef .= implode(', ', array_map(function ($c) {return '`' . strtolower($c) . '`'; }, $indexColumns));
158
                $tableDef .= ')';
159
            }
160
        }
161 8
        $tableDef .= ') ENGINE=InnoDB ROW_FORMAT=COMPRESSED';
162
163 8
        return $tableDef;
164
    }
165
166
    /**
167
     * Get Oracle table definition.
168
     * @param  string $aDatabase       database/schema name
169
     * @param  string $aTable          table name
170
     * @param  bool   $aSkipUnusedCols whether to skip unused columns
171
     * @return string
172
     */
173 8
    public function getOracleTableDef($aDatabase, $aTable, $aSkipUnusedCols = true)
174
    {
175 8
        $table = strtolower($aTable);
176
177 8
        $columns = $this->getTableColumns($aDatabase, $aTable);
178
179 8
        $colDefs = [];
180
181 8
        foreach ($columns as $columnName => $column) {
182 8
            if ($aSkipUnusedCols && $column->getDistinctValueCount() <= 1) {
183
                unset($columns[$columnName]);
184
                continue;
185
            }
186
187 8
            $colDefs[] = $column->getOracleColumnDef();
188
        }
189
190 8
        $primaryKey = $this->getPrimaryKey($aDatabase, $aTable);
191 8
        $indexes = $this->getIndexes($aDatabase, $aTable);
192
193 8
        $tableDef = "CREATE TABLE `{$table}` (" . "\n";
194 8
        $tableDef .= implode(',' . "\n", $colDefs);
195
196 8
        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...
197
            $tableDef .= ',' . "\n" . "\n";
198
            $tableDef .= 'PRIMARY KEY (';
199
            $tableDef .= implode(', ' . "\n", array_map(function ($c) {return '"' . strtolower($c) . '"'; }, $primaryKey));
200
            $tableDef .= ')';
201
        }
202
203 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...
204
            foreach ($indexes as $indexName => $indexColumns) {
205
                foreach ($indexColumns as &$col) {
206
                    if (!in_array($col, array_keys($columns))) { //skip index if it includes a skipped column
207
                        continue 2;
208
                    }
209
                }
210
                $tableDef .= ',' . "\n";
211
                $tableDef .= 'KEY `' . strtolower($indexName) . '` (';
212
                $tableDef .= implode(', ', array_map(function ($c) {return '"' . strtolower($c) . '"'; }, $indexColumns));
213
                $tableDef .= ')';
214
            }
215
        }
216 8
        $tableDef .= ')';
217
218 8
        return $tableDef;
219
    }
220
}
221