SqlserverDialectTrait.php 5.8 KB

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