SqlserverDialectTrait.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Database\Dialect;
  16. use Cake\Database\Dialect\TupleComparisonTranslatorTrait;
  17. use Cake\Database\Expression\FunctionExpression;
  18. use Cake\Database\Expression\OrderByExpression;
  19. use Cake\Database\Expression\UnaryExpression;
  20. use Cake\Database\Query;
  21. use Cake\Database\SqlDialectTrait;
  22. use Cake\Database\SqlserverCompiler;
  23. use PDO;
  24. /**
  25. * Contains functions that encapsulates the SQL dialect used by SQLServer,
  26. * including query translators and schema introspection.
  27. *
  28. * @internal
  29. */
  30. trait SqlserverDialectTrait {
  31. use SqlDialectTrait;
  32. use TupleComparisonTranslatorTrait;
  33. /**
  34. * String used to start a database identifier quoting to make it safe
  35. *
  36. * @var string
  37. */
  38. protected $_startQuote = '[';
  39. /**
  40. * String used to end a database identifier quoting to make it safe
  41. *
  42. * @var string
  43. */
  44. protected $_endQuote = ']';
  45. /**
  46. * Modify the limit/offset to TSQL
  47. *
  48. * @param \Cake\Database\Query $query The query to translate
  49. * @return \Cake\Database\Query The modified query
  50. */
  51. protected function _selectQueryTranslator($query) {
  52. $limit = $query->clause('limit');
  53. $offset = $query->clause('offset');
  54. if ($limit && $offset === null) {
  55. $query->modifier(['_auto_top_' => sprintf('TOP %d', $limit)]);
  56. }
  57. if ($offset !== null && !$query->clause('order')) {
  58. $query->order($query->newExpr()->add('SELECT NULL'));
  59. }
  60. if ($this->_version() < 11 && $offset !== null) {
  61. return $this->_pagingSubquery($query, $limit, $offset);
  62. }
  63. return $query;
  64. }
  65. /**
  66. * Get the version of SQLserver we are connected to.
  67. *
  68. * @return int
  69. */
  70. public function _version() {
  71. return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
  72. }
  73. /**
  74. * Generate a paging subquery for older versions of SQLserver.
  75. *
  76. * Prior to SQLServer 2012 there was no equivalent to LIMIT OFFSET, so a subquery must
  77. * be used.
  78. *
  79. * @param \Cake\Database\Query $original The query to wrap in a subquery.
  80. * @param int $limit The number of rows to fetch.
  81. * @param int $offset The number of rows to offset.
  82. * @return \Cake\Database\Query Modified query object.
  83. */
  84. protected function _pagingSubquery($original, $limit, $offset) {
  85. $field = '_cake_paging_._cake_page_rownum_';
  86. $query = clone $original;
  87. $order = $query->clause('order') ?: new OrderByExpression('NULL');
  88. $query->select([
  89. '_cake_page_rownum_' => new UnaryExpression('ROW_NUMBER() OVER', $order)
  90. ])->limit(null)
  91. ->offset(null)
  92. ->order([], true);
  93. $outer = new Query($query->connection());
  94. $outer->select('*')
  95. ->from(['_cake_paging_' => $query]);
  96. if ($offset) {
  97. $outer->where(["$field >" => $offset]);
  98. }
  99. if ($limit) {
  100. $outer->where(["$field <=" => (int)$offset + (int)$limit]);
  101. }
  102. // Decorate the original query as that is what the
  103. // end developer will be calling execute() on originally.
  104. $original->decorateResults(function ($row) {
  105. if (isset($row['_cake_page_rownum_'])) {
  106. unset($row['_cake_page_rownum_']);
  107. }
  108. return $row;
  109. });
  110. return $outer;
  111. }
  112. /**
  113. * Returns a dictionary of expressions to be transformed when compiling a Query
  114. * to SQL. Array keys are method names to be called in this class
  115. *
  116. * @return array
  117. */
  118. protected function _expressionTranslators() {
  119. $namespace = 'Cake\Database\Expression';
  120. return [
  121. $namespace . '\FunctionExpression' => '_transformFunctionExpression',
  122. $namespace . '\TupleComparison' => '_transformTupleComparison'
  123. ];
  124. }
  125. /**
  126. * Receives a FunctionExpression and changes it so that it conforms to this
  127. * SQL dialect.
  128. *
  129. * @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL.
  130. * @return void
  131. */
  132. protected function _transformFunctionExpression(FunctionExpression $expression) {
  133. switch ($expression->name()) {
  134. case 'CONCAT':
  135. // CONCAT function is expressed as exp1 + exp2
  136. $expression->name('')->type(' +');
  137. break;
  138. case 'DATEDIFF':
  139. $hasDay = false;
  140. $visitor = function($value) use (&$hasDay){
  141. if ($value === 'day') {
  142. $hasDay = true;
  143. }
  144. return $value;
  145. };
  146. $expression->iterateParts($visitor);
  147. if (!$hasDay) {
  148. $expression->add(['day' => 'literal'], [], true);
  149. }
  150. break;
  151. case 'CURRENT_DATE':
  152. $time = new FunctionExpression('GETUTCDATE');
  153. $expression->name('CONVERT')->add(['date' => 'literal', $time]);
  154. break;
  155. case 'CURRENT_TIME':
  156. $time = new FunctionExpression('GETUTCDATE');
  157. $expression->name('CONVERT')->add(['time' => 'literal', $time]);
  158. break;
  159. case 'NOW':
  160. $expression->name('GETUTCDATE');
  161. break;
  162. }
  163. }
  164. /**
  165. * Get the schema dialect.
  166. *
  167. * Used by Cake\Schema package to reflect schema and
  168. * generate schema.
  169. *
  170. * @return \Cake\Database\Schema\MysqlSchema
  171. */
  172. public function schemaDialect() {
  173. return new \Cake\Database\Schema\SqlserverSchema($this);
  174. }
  175. /**
  176. * Returns a SQL snippet for creating a new transaction savepoint
  177. *
  178. * @param string $name save point name
  179. * @return string
  180. */
  181. public function savePointSQL($name) {
  182. return 'SAVE TRANSACTION t' . $name;
  183. }
  184. /**
  185. * Returns a SQL snippet for releasing a previously created save point
  186. *
  187. * @param string $name save point name
  188. * @return string
  189. */
  190. public function releaseSavePointSQL($name) {
  191. return 'COMMIT TRANSACTION t' . $name;
  192. }
  193. /**
  194. * Returns a SQL snippet for rollbacking a previously created save point
  195. *
  196. * @param string $name save point name
  197. * @return string
  198. */
  199. public function rollbackSavePointSQL($name) {
  200. return 'ROLLBACK TRANSACTION t' . $name;
  201. }
  202. /**
  203. * {@inheritDoc}
  204. *
  205. * @return \Cake\Database\SqlserverCompiler
  206. */
  207. public function newCompiler() {
  208. return new SqlserverCompiler();
  209. }
  210. }