GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( ecd2dc...aafa57 )
by Robert
18:49
created

QueryBuilder::normalizeTableRowData()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 0
cts 13
cp 0
rs 8.2222
c 0
b 0
f 0
cc 7
eloc 8
nc 2
nop 3
crap 56
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\mssql;
9
10
use yii\base\InvalidParamException;
11
use yii\base\NotSupportedException;
12
use yii\db\Expression;
13
14
/**
15
 * QueryBuilder is the query builder for MS SQL Server databases (version 2008 and above).
16
 *
17
 * @author Timur Ruziev <[email protected]>
18
 * @since 2.0
19
 */
20
class QueryBuilder extends \yii\db\QueryBuilder
21
{
22
    /**
23
     * @var array mapping from abstract column types (keys) to physical column types (values).
24
     */
25
    public $typeMap = [
26
        Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY',
27
        Schema::TYPE_UPK => 'int IDENTITY PRIMARY KEY',
28
        Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY',
29
        Schema::TYPE_UBIGPK => 'bigint IDENTITY PRIMARY KEY',
30
        Schema::TYPE_CHAR => 'nchar(1)',
31
        Schema::TYPE_STRING => 'nvarchar(255)',
32
        Schema::TYPE_TEXT => 'nvarchar(max)',
33
        Schema::TYPE_SMALLINT => 'smallint',
34
        Schema::TYPE_INTEGER => 'int',
35
        Schema::TYPE_BIGINT => 'bigint',
36
        Schema::TYPE_FLOAT => 'float',
37
        Schema::TYPE_DOUBLE => 'float',
38
        Schema::TYPE_DECIMAL => 'decimal',
39
        Schema::TYPE_DATETIME => 'datetime',
40
        Schema::TYPE_TIMESTAMP => 'datetime',
41
        Schema::TYPE_TIME => 'time',
42
        Schema::TYPE_DATE => 'date',
43
        Schema::TYPE_BINARY => 'varbinary(max)',
44
        Schema::TYPE_BOOLEAN => 'bit',
45
        Schema::TYPE_MONEY => 'decimal(19,4)',
46
    ];
47
48
    /**
49
     * @inheritdoc
50
     */
51
    protected $likeEscapingReplacements = [
52
        '%' => '\%',
53
        '_' => '\_',
54
        '[' => '\[',
55
        ']' => '\]',
56
        '\\' => '\\\\',
57
    ];
58
    /**
59
     * @inheritdoc
60
     */
61
    protected $likeEscapeCharacter = '\\';
62
63
    /**
64
     * @inheritdoc
65
     */
66
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset)
67
    {
68
        if (!$this->hasOffset($offset) && !$this->hasLimit($limit)) {
69
            $orderBy = $this->buildOrderBy($orderBy);
70
            return $orderBy === '' ? $sql : $sql . $this->separator . $orderBy;
71
        }
72
73
        if ($this->isOldMssql()) {
74
            return $this->oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset);
75
        } else {
76
            return $this->newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset);
77
        }
78
    }
79
80
    /**
81
     * Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2012 or newer.
82
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
83
     * @param array $orderBy the order by columns. See [[\yii\db\Query::orderBy]] for more details on how to specify this parameter.
84
     * @param int $limit the limit number. See [[\yii\db\Query::limit]] for more details.
85
     * @param int $offset the offset number. See [[\yii\db\Query::offset]] for more details.
86
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
87
     */
88
    protected function newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset)
89
    {
90
        $orderBy = $this->buildOrderBy($orderBy);
91
        if ($orderBy === '') {
92
            // ORDER BY clause is required when FETCH and OFFSET are in the SQL
93
            $orderBy = 'ORDER BY (SELECT NULL)';
94
        }
95
        $sql .= $this->separator . $orderBy;
96
97
        // http://technet.microsoft.com/en-us/library/gg699618.aspx
98
        $offset = $this->hasOffset($offset) ? $offset : '0';
99
        $sql .= $this->separator . "OFFSET $offset ROWS";
100
        if ($this->hasLimit($limit)) {
101
            $sql .= $this->separator . "FETCH NEXT $limit ROWS ONLY";
102
        }
103
104
        return $sql;
105
    }
106
107
    /**
108
     * Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2005 to 2008.
109
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
110
     * @param array $orderBy the order by columns. See [[\yii\db\Query::orderBy]] for more details on how to specify this parameter.
111
     * @param int $limit the limit number. See [[\yii\db\Query::limit]] for more details.
112
     * @param int $offset the offset number. See [[\yii\db\Query::offset]] for more details.
113
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
114
     */
115
    protected function oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset)
116
    {
117
        $orderBy = $this->buildOrderBy($orderBy);
118
        if ($orderBy === '') {
119
            // ROW_NUMBER() requires an ORDER BY clause
120
            $orderBy = 'ORDER BY (SELECT NULL)';
121
        }
122
123
        $sql = preg_replace('/^([\s(])*SELECT(\s+DISTINCT)?(?!\s*TOP\s*\()/i', "\\1SELECT\\2 rowNum = ROW_NUMBER() over ($orderBy),", $sql);
124
125
        if ($this->hasLimit($limit)) {
126
            $sql = "SELECT TOP $limit * FROM ($sql) sub";
127
        } else {
128
            $sql = "SELECT * FROM ($sql) sub";
129
        }
130
        if ($this->hasOffset($offset)) {
131
            $sql .= $this->separator . "WHERE rowNum > $offset";
132
        }
133
134
        return $sql;
135
    }
136
137
    /**
138
     * Builds a SQL statement for renaming a DB table.
139
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
140
     * @param string $newName the new table name. The name will be properly quoted by the method.
141
     * @return string the SQL statement for renaming a DB table.
142
     */
143
    public function renameTable($oldName, $newName)
144
    {
145
        return 'sp_rename ' . $this->db->quoteTableName($oldName) . ', ' . $this->db->quoteTableName($newName);
146
    }
147
148
    /**
149
     * Builds a SQL statement for renaming a column.
150
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
151
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
152
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
153
     * @return string the SQL statement for renaming a DB column.
154
     */
155
    public function renameColumn($table, $oldName, $newName)
156
    {
157
        $table = $this->db->quoteTableName($table);
158
        $oldName = $this->db->quoteColumnName($oldName);
159
        $newName = $this->db->quoteColumnName($newName);
160
        return "sp_rename '{$table}.{$oldName}', {$newName}, 'COLUMN'";
161
    }
162
163
    /**
164
     * Builds a SQL statement for changing the definition of a column.
165
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
166
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
167
     * @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column type (if any)
168
     * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
169
     * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
170
     * @return string the SQL statement for changing the definition of a column.
171
     */
172
    public function alterColumn($table, $column, $type)
173
    {
174
        $type = $this->getColumnType($type);
175
        $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
176
            . $this->db->quoteColumnName($column) . ' '
177
            . $this->getColumnType($type);
178
179
        return $sql;
180
    }
181
182
    /**
183
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
184
     * The sequence will be reset such that the primary key of the next new row inserted
185
     * will have the specified value or 1.
186
     * @param string $tableName the name of the table whose primary key sequence will be reset
187
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
188
     * the next new row's primary key will have a value 1.
189
     * @return string the SQL statement for resetting sequence
190
     * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
191
     */
192
    public function resetSequence($tableName, $value = null)
193
    {
194
        $table = $this->db->getTableSchema($tableName);
195
        if ($table !== null && $table->sequenceName !== null) {
196
            $tableName = $this->db->quoteTableName($tableName);
197
            if ($value === null) {
198
                $key = $this->db->quoteColumnName(reset($table->primaryKey));
0 ignored issues
show
Security Bug introduced by
It seems like reset($table->primaryKey) targeting reset() can also be of type false; however, yii\db\Connection::quoteColumnName() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
199
                $value = "(SELECT COALESCE(MAX({$key}),0) FROM {$tableName})+1";
200
            } else {
201
                $value = (int) $value;
202
            }
203
204
            return "DBCC CHECKIDENT ('{$tableName}', RESEED, {$value})";
205
        } elseif ($table === null) {
206
            throw new InvalidParamException("Table not found: $tableName");
207
        } else {
208
            throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
209
        }
210
    }
211
212
    /**
213
     * Builds a SQL statement for enabling or disabling integrity check.
214
     * @param bool $check whether to turn on or off the integrity check.
215
     * @param string $schema the schema of the tables.
216
     * @param string $table the table name.
217
     * @return string the SQL statement for checking integrity
218
     */
219
    public function checkIntegrity($check = true, $schema = '', $table = '')
220
    {
221
        $enable = $check ? 'CHECK' : 'NOCHECK';
222
        $schema = $schema ? $schema : $this->db->getSchema()->defaultSchema;
223
        $tableNames = $this->db->getTableSchema($table) ? [$table] : $this->db->getSchema()->getTableNames($schema);
224
        $viewNames = $this->db->getSchema()->getViewNames($schema);
0 ignored issues
show
Documentation Bug introduced by
The method getViewNames does not exist on object<yii\db\Schema>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
225
        $tableNames = array_diff($tableNames, $viewNames);
226
        $command = '';
227
228
        foreach ($tableNames as $tableName) {
229
            $tableName = $this->db->quoteTableName("{$schema}.{$tableName}");
230
            $command .= "ALTER TABLE $tableName $enable CONSTRAINT ALL; ";
231
        }
232
233
        return $command;
234
    }
235
236
    /**
237
     * @inheritdoc
238
     * @since 2.0.8
239
     */
240
    public function addCommentOnColumn($table, $column, $comment)
241
    {
242
        return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table',  @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}";
243
    }
244
245
    /**
246
     * @inheritdoc
247
     * @since 2.0.8
248
     */
249
    public function addCommentOnTable($table, $comment)
250
    {
251
        return "sp_updateextendedproperty @name = N'MS_Description', @value = {$this->db->quoteValue($comment)}, @level1type = N'Table',  @level1name = {$this->db->quoteTableName($table)}";
252
    }
253
254
    /**
255
     * @inheritdoc
256
     * @since 2.0.8
257
     */
258
    public function dropCommentFromColumn($table, $column)
259
    {
260
        return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table',  @level1name = {$this->db->quoteTableName($table)}, @level2type = N'Column', @level2name = {$this->db->quoteColumnName($column)}";
261
    }
262
263
    /**
264
     * @inheritdoc
265
     * @since 2.0.8
266
     */
267
    public function dropCommentFromTable($table)
268
    {
269
        return "sp_dropextendedproperty @name = N'MS_Description', @level1type = N'Table',  @level1name = {$this->db->quoteTableName($table)}";
270
    }
271
272
    /**
273
     * Returns an array of column names given model name
274
     *
275
     * @param string $modelClass name of the model class
276
     * @return array|null array of column names
277
     */
278
    protected function getAllColumnNames($modelClass = null)
279
    {
280
        if (!$modelClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $modelClass of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
281
            return null;
282
        }
283
        /* @var $model \yii\db\ActiveRecord */
284
        $model = new $modelClass;
285
        $schema = $model->getTableSchema();
286
        return array_keys($schema->columns);
287
    }
288
289
    /**
290
     * @var bool whether MSSQL used is old.
291
     */
292
    private $_oldMssql;
293
294
    /**
295
     * @return bool whether the version of the MSSQL being used is older than 2012.
296
     * @throws \yii\base\InvalidConfigException
297
     * @throws \yii\db\Exception
298
     */
299
    protected function isOldMssql()
300
    {
301
        if ($this->_oldMssql === null) {
302
            $pdo = $this->db->getSlavePdo();
303
            $version = explode('.', $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION));
304
            $this->_oldMssql = $version[0] < 11;
305
        }
306
        return $this->_oldMssql;
307
    }
308
309
    /**
310
     * @inheritdoc
311
     * @throws NotSupportedException if `$columns` is an array
312
     */
313
    protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
314
    {
315
        if (is_array($columns)) {
316
            throw new NotSupportedException(__METHOD__ . ' is not supported by MSSQL.');
317
        }
318
        return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
319
    }
320
321
    /**
322
     * Builds SQL for IN condition
323
     *
324
     * @param string $operator
325
     * @param array $columns
326
     * @param array $values
327
     * @param array $params
328
     * @return string SQL
329
     */
330
    protected function buildCompositeInCondition($operator, $columns, $values, &$params)
331
    {
332
        $quotedColumns = [];
333
        foreach ($columns as $i => $column) {
334
            $quotedColumns[$i] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
335
        }
336
        $vss = [];
337
        foreach ($values as $value) {
338
            $vs = [];
339
            foreach ($columns as $i => $column) {
340
                if (isset($value[$column])) {
341
                    $phName = self::PARAM_PREFIX . count($params);
342
                    $params[$phName] = $value[$column];
343
                    $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
344
                } else {
345
                    $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
346
                }
347
            }
348
            $vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
349
        }
350
351
        return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
352
    }
353
354
    /**
355
     * @inheritdoc
356
     * @since 2.0.8
357
     */
358
    public function selectExists($rawSql)
359
    {
360
        return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END';
361
    }
362
363
    /**
364
     * Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary.
365
     * @param string $table the table that data will be saved into.
366
     * @param array $columns the column data (name => value) to be saved into the table.
367
     * @return array normalized columns
368
     */
369
    private function normalizeTableRowData($table, $columns, &$params)
370
    {
371
        if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) {
372
            $columnSchemas = $tableSchema->columns;
373
            foreach ($columns as $name => $value) {
374
                // @see https://github.com/yiisoft/yii2/issues/12599
375
                if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && $columnSchemas[$name]->dbType === 'varbinary' && is_string($value)) {
376
                    $phName = self::PARAM_PREFIX . count($params);
377
                    $columns[$name] = new Expression("CONVERT(VARBINARY, $phName)", [$phName => $value]);
378
                }
379
            }
380
        }
381
        return $columns;
382
    }
383
384
    /**
385
     * @inheritdoc
386
     */
387
    public function insert($table, $columns, &$params)
388
    {
389
        return parent::insert($table, $this->normalizeTableRowData($table, $columns, $params), $params);
390
    }
391
392
    /**
393
     * @inheritdoc
394
     */
395
    public function update($table, $columns, $condition, &$params)
396
    {
397
        return parent::update($table, $this->normalizeTableRowData($table, $columns, $params), $condition, $params);
398
    }
399
}
400