您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

270 行
10KB

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