SqlserverCompiler.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Database;
  17. use Cake\Database\Expression\FunctionExpression;
  18. /**
  19. * Responsible for compiling a Query object into its SQL representation
  20. * for SQL Server
  21. *
  22. * @internal
  23. */
  24. class SqlserverCompiler extends QueryCompiler
  25. {
  26. /**
  27. * SQLserver does not support ORDER BY in UNION queries.
  28. *
  29. * @var bool
  30. */
  31. protected $_orderedUnion = false;
  32. /**
  33. * @inheritDoc
  34. */
  35. protected $_templates = [
  36. 'delete' => 'DELETE',
  37. 'where' => ' WHERE %s',
  38. 'group' => ' GROUP BY %s ',
  39. 'order' => ' %s',
  40. 'offset' => ' OFFSET %s ROWS',
  41. 'epilog' => ' %s',
  42. ];
  43. /**
  44. * @inheritDoc
  45. */
  46. protected $_selectParts = [
  47. 'select', 'from', 'join', 'where', 'group', 'having', 'order', 'offset',
  48. 'limit', 'union', 'epilog',
  49. ];
  50. /**
  51. * Generates the INSERT part of a SQL query
  52. *
  53. * To better handle concurrency and low transaction isolation levels,
  54. * we also include an OUTPUT clause so we can ensure we get the inserted
  55. * row's data back.
  56. *
  57. * @param array $parts The parts to build
  58. * @param \Cake\Database\Query $query The query that is being compiled
  59. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  60. * @return string
  61. */
  62. protected function _buildInsertPart(array $parts, Query $query, ValueBinder $generator): string
  63. {
  64. $table = $parts[0];
  65. $columns = $this->_stringifyExpressions($parts[1], $generator);
  66. $modifiers = $this->_buildModifierPart($query->clause('modifier'), $query, $generator);
  67. return sprintf(
  68. 'INSERT%s INTO %s (%s) OUTPUT INSERTED.*',
  69. $modifiers,
  70. $table,
  71. implode(', ', $columns)
  72. );
  73. }
  74. /**
  75. * Generates the LIMIT part of a SQL query
  76. *
  77. * @param int $limit the limit clause
  78. * @param \Cake\Database\Query $query The query that is being compiled
  79. * @return string
  80. */
  81. protected function _buildLimitPart(int $limit, Query $query): string
  82. {
  83. if ($query->clause('offset') === null) {
  84. return '';
  85. }
  86. return sprintf(' FETCH FIRST %d ROWS ONLY', $limit);
  87. }
  88. /**
  89. * Helper function used to build the string representation of a HAVING clause,
  90. * it constructs the field list taking care of aliasing and
  91. * converting expression objects to string.
  92. *
  93. * @param array $parts list of fields to be transformed to string
  94. * @param \Cake\Database\Query $query The query that is being compiled
  95. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  96. * @return string
  97. */
  98. protected function _buildHavingPart($parts, $query, $generator)
  99. {
  100. $selectParts = $query->clause('select');
  101. foreach ($selectParts as $selectKey => $selectPart) {
  102. if (!$selectPart instanceof FunctionExpression) {
  103. continue;
  104. }
  105. foreach ($parts as $k => $p) {
  106. if (!is_string($p)) {
  107. continue;
  108. }
  109. preg_match_all(
  110. '/\b' . trim($selectKey, '[]') . '\b/i',
  111. $p,
  112. $matches
  113. );
  114. if (empty($matches[0])) {
  115. continue;
  116. }
  117. $parts[$k] = preg_replace(
  118. ['/\[|\]/', '/\b' . trim($selectKey, '[]') . '\b/i'],
  119. ['', $selectPart->sql($generator)],
  120. $p
  121. );
  122. }
  123. }
  124. return sprintf('HAVING %s', implode(', ', $parts));
  125. }
  126. }