Passed
Pull Request — dev (#98)
by Wilmer
43:38 queued 28:30
created

QueryBuilderPDOMysql   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Test Coverage

Coverage 86.36%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 69
c 1
b 0
f 0
dl 0
loc 257
ccs 57
cts 66
cp 0.8636
rs 9.76
wmc 33

23 Methods

Rating   Name   Duplication   Size   Complexity  
A createIndex() 0 3 1
A checkIntegrity() 0 3 2
A addCheck() 0 3 1
A addCommentOnTable() 0 3 1
A dropCheck() 0 3 1
A dropPrimaryKey() 0 3 1
A command() 0 3 1
A dropUnique() 0 3 1
A __construct() 0 8 1
A addCommentOnColumn() 0 3 1
A dropCommentFromTable() 0 3 1
A dropForeignKey() 0 3 1
A getColumnType() 0 4 1
A buildLimit() 0 21 4
A dropCommentFromColumn() 0 3 1
A prepareInsertValues() 0 23 6
A schema() 0 3 1
A quoter() 0 3 1
A hasOffset() 0 5 2
A renameColumn() 0 3 1
A hasLimit() 0 4 1
A defaultTimeTypeMap() 0 6 1
A defaultExpressionBuilders() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Mysql\PDO;
6
7
use JsonException;
8
use Throwable;
9
use Yiisoft\Db\Command\CommandInterface;
10
use Yiisoft\Db\Exception\Exception;
11
use Yiisoft\Db\Exception\InvalidArgumentException;
12
use Yiisoft\Db\Exception\InvalidConfigException;
13
use Yiisoft\Db\Exception\NotSupportedException;
14
use Yiisoft\Db\Expression\Expression;
15
use Yiisoft\Db\Expression\ExpressionBuilder;
16
use Yiisoft\Db\Expression\JsonExpression;
17
use Yiisoft\Db\Mysql\Builder\JsonExpressionBuilder;
18
use Yiisoft\Db\Mysql\DDLQueryBuilder;
19
use Yiisoft\Db\Mysql\DMLQueryBuilder;
20
use Yiisoft\Db\Query\Query;
21
use Yiisoft\Db\Query\QueryBuilder;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Db\Query\QueryBuilder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Yiisoft\Db\Query\QueryInterface;
23
use Yiisoft\Db\Schema\ColumnSchemaBuilder;
24
use Yiisoft\Db\Schema\QuoterInterface;
25
use Yiisoft\Db\Schema\Schema;
26
use Yiisoft\Db\Schema\SchemaInterface;
27
28
use function array_merge;
29
use function ctype_digit;
30
use function reset;
31
32
/**
33
 * The class QueryBuilder is the query builder for Mysql databases.
34
 */
35
final class QueryBuilderPDOMysql extends QueryBuilder
36
{
37
    /**
38
     * @psalm-var string[] $typeMap Mapping from abstract column types (keys) to physical column types (values).
39
     */
40
    protected array $typeMap = [
41
        Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
42
        Schema::TYPE_UPK => 'int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',
43
        Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
44
        Schema::TYPE_UBIGPK => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',
45
        Schema::TYPE_CHAR => 'char(1)',
46
        Schema::TYPE_STRING => 'varchar(255)',
47
        Schema::TYPE_TEXT => 'text',
48
        Schema::TYPE_TINYINT => 'tinyint(3)',
49
        Schema::TYPE_SMALLINT => 'smallint(6)',
50
        Schema::TYPE_INTEGER => 'int(11)',
51
        Schema::TYPE_BIGINT => 'bigint(20)',
52
        Schema::TYPE_FLOAT => 'float',
53
        Schema::TYPE_DOUBLE => 'double',
54
        Schema::TYPE_DECIMAL => 'decimal(10,0)',
55
        Schema::TYPE_DATE => 'date',
56
        Schema::TYPE_BINARY => 'blob',
57
        Schema::TYPE_BOOLEAN => 'tinyint(1)',
58
        Schema::TYPE_MONEY => 'decimal(19,4)',
59
        Schema::TYPE_JSON => 'json',
60
    ];
61
62 342
    public function __construct(
63
        private CommandInterface $command,
64
        private QuoterInterface $quoter,
65
        private SchemaInterface $schema
66
    ) {
67 342
        $this->ddlBuilder = new DDLQueryBuilder($this);
0 ignored issues
show
Bug Best Practice introduced by
The property ddlBuilder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
68 342
        $this->dmlBuilder = new DMLQueryBuilder($this);
0 ignored issues
show
Bug Best Practice introduced by
The property dmlBuilder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
69 342
        parent::__construct($quoter, $schema);
70
    }
71
72
    /**
73
     * @throws NotSupportedException
74
     */
75 1
    public function addCheck(string $name, string $table, string $expression): string
76
    {
77 1
        throw new NotSupportedException(self::class . '::addCheck is not supported by MySQL.');
78
    }
79
80
    /**
81
     * @throws Exception|Throwable
82
     */
83 1
    public function addCommentOnColumn(string $table, string $column, string $comment): string
84
    {
85 1
        return $this->ddlBuilder->addCommentOnColumn($table, $column, $comment);
86
    }
87
88
    /**
89
     * @throws \Exception
90
     */
91 1
    public function addCommentOnTable(string $table, string $comment): string
92
    {
93 1
        return $this->ddlBuilder->addCommentOnTable($table, $comment);
94
    }
95
96 183
    public function buildLimit(Expression|int|null $limit, Expression|int|null $offset): string
97
    {
98 183
        $sql = '';
99
100 183
        if ($this->hasLimit($limit)) {
101 9
            $sql = 'LIMIT ' . (string) $limit;
102
103 9
            if ($this->hasOffset($offset)) {
104 9
                $sql .= ' OFFSET ' . (string) $offset;
105
            }
106 177
        } elseif ($this->hasOffset($offset)) {
107
            /**
108
             * limit is not optional in MySQL.
109
             *
110
             * http://stackoverflow.com/a/271650/1106908
111
             * http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240
112
             */
113
            $sql = "LIMIT $offset, 18446744073709551615"; // 2^64-1
114
        }
115
116 183
        return $sql;
117
    }
118
119 1
    public function checkIntegrity(string $schema = '', string $table = '', bool $check = true): string
120
    {
121 1
        return 'SET FOREIGN_KEY_CHECKS = ' . ($check ? 1 : 0);
122
    }
123
124 6
    public function createIndex(string $name, string $table, array|string $columns, bool $unique = false): string
125
    {
126 6
        return $this->ddlBuilder->createIndex($name, $table, $columns, $unique);
127
    }
128
129 4
    public function command(): CommandInterface
130
    {
131 4
        return $this->command;
132
    }
133
134
    /**
135
     * @throws NotSupportedException
136
     */
137 1
    public function dropCheck(string $name, string $table): string
138
    {
139 1
        throw new NotSupportedException(__METHOD__ . ' is not supported by MySQL.');
140
    }
141
142
    /**
143
     * @throws Exception|Throwable
144
     */
145 1
    public function dropCommentFromColumn(string $table, string $column): string
146
    {
147 1
        return $this->addCommentOnColumn($table, $column, '');
148
    }
149
150
    /**
151
     * @throws Exception
152
     */
153 1
    public function dropCommentFromTable(string $table): string
154
    {
155 1
        return $this->addCommentOnTable($table, '');
156
    }
157
158 2
    public function dropForeignKey(string $name, string $table): string
159
    {
160 2
        return $this->ddlBuilder->dropForeignKey($name, $table);
161
    }
162
163 2
    public function dropPrimaryKey(string $name, string $table): string
164
    {
165 2
        return $this->ddlBuilder->dropPrimaryKey($name, $table);
166
    }
167
168 2
    public function dropUnique(string $name, string $table): string
169
    {
170 2
        return $this->dropIndex($name, $table);
171
    }
172
173 12
    public function getColumnType(ColumnSchemaBuilder|string $type): string
174
    {
175 12
        $this->typeMap = array_merge($this->typeMap, $this->defaultTimeTypeMap());
176 12
        return parent::getColumnType($type);
177
    }
178
179
    /**
180
     * Prepares a `VALUES` part for an `INSERT` SQL statement.
181
     *
182
     * @param string $table the table that new rows will be inserted into.
183
     * @param array|QueryInterface $columns the column data (name => value) to be inserted into the table or instance of
184
     * {@see Query|Query} to perform INSERT INTO ... SELECT SQL statement.
185
     * @param array $params the binding parameters that will be generated by this method. They should be bound to the DB
186
     * command later.
187
     *
188
     * @throws Exception|InvalidArgumentException|InvalidConfigException|JsonException|NotSupportedException
189
     *
190
     * @return array array of column names, placeholders, values and params.
191
     */
192 48
    public function prepareInsertValues(string $table, QueryInterface|array $columns, array $params = []): array
193
    {
194
        /**
195
         * @var array $names
196
         * @var array $placeholders
197
         */
198 48
        [$names, $placeholders, $values, $params] = parent::prepareInsertValues($table, $columns, $params);
199
200 45
        if (!$columns instanceof Query && empty($names)) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Query\Query.
Loading history...
201
            $tableSchema = $this->schema->getTableSchema($table);
202
203
            if ($tableSchema !== null) {
204
                $columns = $tableSchema->getColumns();
205
                $columns = !empty($tableSchema->getPrimaryKey())
206
                    ? $tableSchema->getPrimaryKey() : [reset($columns)->getName()];
207
                foreach ($columns as $name) {
208
                    $names[] = $this->quoter->quoteColumnName($name);
209
                    $placeholders[] = 'DEFAULT';
210
                }
211
            }
212
        }
213
214 45
        return [$names, $placeholders, $values, $params];
215
    }
216
217 279
    public function quoter(): QuoterInterface
218
    {
219 279
        return $this->quoter;
220
    }
221
222 2
    public function renameColumn(string $table, string $oldName, string $newName): string
223
    {
224 2
        return $this->ddlBuilder->renameColumn($table, $oldName, $newName);
225
    }
226
227 142
    public function schema(): SchemaInterface
228
    {
229 142
        return $this->schema;
230
    }
231
232
    /**
233
     * Checks to see if the given limit is effective.
234
     *
235
     * @param mixed $limit the given limit.
236
     *
237
     * @return bool whether the limit is effective.
238
     */
239 183
    protected function hasLimit(mixed $limit): bool
240
    {
241
        /** In MySQL limit argument must be non-negative integer constant */
242 183
        return ctype_digit((string) $limit);
243
    }
244
245
    /**
246
     * Checks to see if the given offset is effective.
247
     *
248
     * @param mixed $offset the given offset.
249
     *
250
     * @return bool whether the offset is effective.
251
     */
252 183
    protected function hasOffset(mixed $offset): bool
253
    {
254
        /** In MySQL offset argument must be non-negative integer constant */
255 183
        $offset = (string) $offset;
256 183
        return ctype_digit($offset) && $offset !== '0';
257
    }
258
259
    /**
260
     * Contains array of default expression builders. Extend this method and override it, if you want to change default
261
     * expression builders for this query builder.
262
     *
263
     * @return array
264
     *
265
     * See {@see ExpressionBuilder} docs for details.
266
     */
267 342
    protected function defaultExpressionBuilders(): array
268
    {
269 342
        return array_merge(
270 342
            parent::defaultExpressionBuilders(),
271
            [
272
                JsonExpression::class => JsonExpressionBuilder::class,
273
            ]
274
        );
275
    }
276
277
    /**
278
     * Returns the map for default time type.
279
     *
280
     * If the version of MySQL is lower than 5.6.4, then the types will be without fractional seconds, otherwise with
281
     * fractional seconds.
282
     *
283
     * @return array
284
     * @psalm-return array<string, string>
285
     */
286
    private function defaultTimeTypeMap(): array
287
    {
288
        return [
289
            Schema::TYPE_DATETIME => 'datetime(0)',
290
            Schema::TYPE_TIMESTAMP => 'timestamp(0)',
291
            Schema::TYPE_TIME => 'time(0)',
292
        ];
293
    }
294
}
295