1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\mssql;
use yii\base\InvalidParamException;
/**
* QueryBuilder is the query builder for MS SQL Server databases (version 2008 and above).
*
* @author Timur Ruziev <resurtm@gmail.com>
* @since 2.0
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
/**
* @var array mapping from abstract column types (keys) to physical column types (values).
*/
public $typeMap = [
Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY',
Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY',
Schema::TYPE_STRING => 'varchar(255)',
Schema::TYPE_TEXT => 'text',
Schema::TYPE_SMALLINT => 'smallint',
Schema::TYPE_INTEGER => 'int',
Schema::TYPE_BIGINT => 'bigint',
Schema::TYPE_FLOAT => 'float',
Schema::TYPE_DECIMAL => 'decimal',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'binary(1)',
Schema::TYPE_BOOLEAN => 'bit',
Schema::TYPE_MONEY => 'decimal(19,4)',
];
/**
* @inheritdoc
*/
public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset)
{
if (!$this->hasOffset($offset) && !$this->hasLimit($limit)) {
$orderBy = $this->buildOrderBy($orderBy);
return $orderBy === '' ? $sql : $sql . $this->separator . $orderBy;
}
if ($this->isOldMssql()) {
return $this->oldbuildOrderByAndLimit($sql, $orderBy, $limit, $offset);
} else {
return $this->newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset);
}
}
/**
* Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2012 or newer.
* @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
* @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
* @param integer $limit the limit number. See [[Query::limit]] for more details.
* @param integer $offset the offset number. See [[Query::offset]] for more details.
* @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
*/
protected function newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset)
{
$orderBy = $this->buildOrderBy($orderBy);
if ($orderBy === '') {
// ORDER BY clause is required when FETCH and OFFSET are in the SQL
$orderBy = 'ORDER BY (SELECT NULL)';
}
$sql .= $this->separator . $orderBy;
// http://technet.microsoft.com/en-us/library/gg699618.aspx
$offset = $this->hasOffset($offset) ? $offset : '0';
$sql .= $this->separator . "OFFSET $offset ROWS";
if ($this->hasLimit($limit)) {
$sql .= $this->separator . "FETCH NEXT $limit ROWS ONLY";
}
return $sql;
}
/**
* Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2005 to 2008.
* @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
* @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
* @param integer $limit the limit number. See [[Query::limit]] for more details.
* @param integer $offset the offset number. See [[Query::offset]] for more details.
* @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
*/
protected function oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset)
{
$orderBy = $this->buildOrderBy($orderBy);
if ($orderBy === '') {
// ROW_NUMBER() requires an ORDER BY clause
$orderBy = 'ORDER BY (SELECT NULL)';
}
$sql = preg_replace('/^([\s(])*SELECT(\s+DISTINCT)?(?!\s*TOP\s*\()/i', "\\1SELECT\\2 rowNum = ROW_NUMBER() over ($orderBy),", $sql);
if ($this->hasLimit($limit)) {
$sql = "SELECT TOP $limit * FROM ($sql) sub";
} else {
$sql = "SELECT * FROM ($sql) sub";
}
if ($this->hasOffset($offset)) {
$sql .= $this->separator . "WHERE rowNum > $offset";
}
return $sql;
}
/**
* Builds a SQL statement for renaming a DB table.
* @param string $table the table to be renamed. The name will be properly quoted by the method.
* @param string $newName the new table name. The name will be properly quoted by the method.
* @return string the SQL statement for renaming a DB table.
*/
public function renameTable($table, $newName)
{
return "sp_rename '$table', '$newName'";
}
/**
* Builds a SQL statement for renaming a column.
* @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
* @param string $name the old name of the column. The name will be properly quoted by the method.
* @param string $newName the new name of the column. The name will be properly quoted by the method.
* @return string the SQL statement for renaming a DB column.
*/
public function renameColumn($table, $name, $newName)
{
return "sp_rename '$table.$name', '$newName', 'COLUMN'";
}
/**
* Builds a SQL statement for changing the definition of a column.
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
* @param string $column the name of the column to be changed. The name will be properly quoted by the method.
* @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
* @return string the SQL statement for changing the definition of a column.
*/
public function alterColumn($table, $column, $type)
{
$type = $this->getColumnType($type);
$sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
. $this->db->quoteColumnName($column) . ' '
. $this->getColumnType($type);
return $sql;
}
/**
* Builds a SQL statement for enabling or disabling integrity check.
* @param boolean $check whether to turn on or off the integrity check.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
* @return string the SQL statement for checking integrity
* @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
*/
public function checkIntegrity($check = true, $schema = '', $table = '')
{
if ($schema !== '') {
$table = "{$schema}.{$table}";
}
$table = $this->db->quoteTableName($table);
if ($this->db->getTableSchema($table) === null) {
throw new InvalidParamException("Table not found: $table");
}
$enable = $check ? 'CHECK' : 'NOCHECK';
return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL";
}
/**
* Returns an array of column names given model name
*
* @param string $modelClass name of the model class
* @return array|null array of column names
*/
protected function getAllColumnNames($modelClass = null)
{
if (!$modelClass) {
return null;
}
/* @var $model \yii\db\ActiveRecord */
$model = new $modelClass;
$schema = $model->getTableSchema();
$columns = array_keys($schema->columns);
return $columns;
}
/**
* @var boolean whether MSSQL used is old.
*/
private $_oldMssql;
/**
* @return boolean whether the version of the MSSQL being used is older than 2012.
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
protected function isOldMssql()
{
if ($this->_oldMssql === null) {
$pdo = $this->db->getSlavePdo();
$version = preg_split("/\./", $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION));
$this->_oldMssql = $version[0] < 11;
}
return $this->_oldMssql;
}
}