SqlDialectTrait.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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;
  16. use Cake\Database\Expression\Comparison;
  17. /**
  18. * Sql dialect trait
  19. */
  20. trait SqlDialectTrait
  21. {
  22. /**
  23. * Quotes a database identifier (a column name, table name, etc..) to
  24. * be used safely in queries without the risk of using reserved words
  25. *
  26. * @param string $identifier The identifier to quote.
  27. * @return string
  28. */
  29. public function quoteIdentifier($identifier)
  30. {
  31. $identifier = trim($identifier);
  32. if ($identifier === '*') {
  33. return '*';
  34. }
  35. if ($identifier === '') {
  36. return '';
  37. }
  38. // string
  39. if (preg_match('/^[\w-]+$/', $identifier)) {
  40. return $this->_startQuote . $identifier . $this->_endQuote;
  41. }
  42. if (preg_match('/^[\w-]+\.[^ \*]*$/', $identifier)) {
  43. // string.string
  44. $items = explode('.', $identifier);
  45. return $this->_startQuote . implode($this->_endQuote . '.' . $this->_startQuote, $items) . $this->_endQuote;
  46. }
  47. if (preg_match('/^[\w-]+\.\*$/', $identifier)) {
  48. // string.*
  49. return $this->_startQuote . str_replace('.*', $this->_endQuote . '.*', $identifier);
  50. }
  51. if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) {
  52. // Functions
  53. return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
  54. }
  55. // Alias.field AS thing
  56. if (preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+AS\s*([\w-]+)$/i', $identifier, $matches)) {
  57. return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]);
  58. }
  59. if (preg_match('/^[\w-_\s]*[\w-_]+/', $identifier)) {
  60. return $this->_startQuote . $identifier . $this->_endQuote;
  61. }
  62. return $identifier;
  63. }
  64. /**
  65. * Returns a callable function that will be used to transform a passed Query object.
  66. * This function, in turn, will return an instance of a Query object that has been
  67. * transformed to accommodate any specificities of the SQL dialect in use.
  68. *
  69. * @param string $type the type of query to be transformed
  70. * (select, insert, update, delete)
  71. * @return callable
  72. */
  73. public function queryTranslator($type)
  74. {
  75. return function ($query) use ($type) {
  76. if ($this->autoQuoting()) {
  77. $query = (new IdentifierQuoter($this))->quote($query);
  78. }
  79. $query = $this->{'_' . $type . 'QueryTranslator'}($query);
  80. $translators = $this->_expressionTranslators();
  81. if (!$translators) {
  82. return $query;
  83. }
  84. $query->traverseExpressions(function ($expression) use ($translators, $query) {
  85. foreach ($translators as $class => $method) {
  86. if ($expression instanceof $class) {
  87. $this->{$method}($expression, $query);
  88. }
  89. }
  90. });
  91. return $query;
  92. };
  93. }
  94. /**
  95. * Returns an associative array of methods that will transform Expression
  96. * objects to conform with the specific SQL dialect. Keys are class names
  97. * and values a method in this class.
  98. *
  99. * @return array
  100. */
  101. protected function _expressionTranslators()
  102. {
  103. return [];
  104. }
  105. /**
  106. * Apply translation steps to select queries.
  107. *
  108. * @param \Cake\Database\Query $query The query to translate
  109. * @return \Cake\Database\Query The modified query
  110. */
  111. protected function _selectQueryTranslator($query)
  112. {
  113. return $this->_transformDistinct($query);
  114. }
  115. /**
  116. * Returns the passed query after rewriting the DISTINCT clause, so that drivers
  117. * that do not support the "ON" part can provide the actual way it should be done
  118. *
  119. * @param \Cake\Database\Query $query The query to be transformed
  120. * @return \Cake\Database\Query
  121. */
  122. protected function _transformDistinct($query)
  123. {
  124. if (is_array($query->clause('distinct'))) {
  125. $query->group($query->clause('distinct'), true);
  126. $query->distinct(false);
  127. }
  128. return $query;
  129. }
  130. /**
  131. * Apply translation steps to delete queries.
  132. *
  133. * Chops out aliases on delete query conditions as most database dialects do not
  134. * support aliases in delete queries. This also removes aliases
  135. * in table names as they frequently don't work either.
  136. *
  137. * We are intentionally not supporting deletes with joins as they have even poorer support.
  138. *
  139. * @param \Cake\Database\Query $query The query to translate
  140. * @return \Cake\Database\Query The modified query
  141. */
  142. protected function _deleteQueryTranslator($query)
  143. {
  144. $hadAlias = false;
  145. $tables = [];
  146. foreach ($query->clause('from') as $alias => $table) {
  147. if (is_string($alias)) {
  148. $hadAlias = true;
  149. }
  150. $tables[] = $table;
  151. }
  152. if ($hadAlias) {
  153. $query->from($tables, true);
  154. }
  155. if (!$hadAlias) {
  156. return $query;
  157. }
  158. return $this->_removeAliasesFromConditions($query);
  159. }
  160. /**
  161. * Apply translation steps to update queries.
  162. *
  163. * Chops out aliases on update query conditions as not all database dialects do support
  164. * aliases in update queries.
  165. *
  166. * Just like for delete queries, joins are currently not supported for update queries.
  167. *
  168. * @param \Cake\Database\Query $query The query to translate
  169. * @return \Cake\Database\Query The modified query
  170. */
  171. protected function _updateQueryTranslator($query)
  172. {
  173. return $this->_removeAliasesFromConditions($query);
  174. }
  175. /**
  176. * Removes aliases from the `WHERE` clause of a query.
  177. *
  178. * @param \Cake\Database\Query $query The query to process.
  179. * @return \Cake\Database\Query The modified query.
  180. * @throws \RuntimeException In case the processed query contains any joins, as removing
  181. * aliases from the conditions can break references to the joined tables.
  182. */
  183. protected function _removeAliasesFromConditions($query)
  184. {
  185. if ($query->clause('join')) {
  186. throw new \RuntimeException(
  187. 'Aliases are being removed from conditions for UPDATE/DELETE queries, ' .
  188. 'this can break references to joined tables.'
  189. );
  190. }
  191. $conditions = $query->clause('where');
  192. if ($conditions) {
  193. $conditions->traverse(function ($condition) {
  194. if (!($condition instanceof Comparison)) {
  195. return $condition;
  196. }
  197. $field = $condition->getField();
  198. if ($field instanceof ExpressionInterface || strpos($field, '.') === false) {
  199. return $condition;
  200. }
  201. list(, $field) = explode('.', $field);
  202. $condition->setField($field);
  203. return $condition;
  204. });
  205. }
  206. return $query;
  207. }
  208. /**
  209. * Apply translation steps to insert queries.
  210. *
  211. * @param \Cake\Database\Query $query The query to translate
  212. * @return \Cake\Database\Query The modified query
  213. */
  214. protected function _insertQueryTranslator($query)
  215. {
  216. return $query;
  217. }
  218. /**
  219. * Returns a SQL snippet for creating a new transaction savepoint
  220. *
  221. * @param string $name save point name
  222. * @return string
  223. */
  224. public function savePointSQL($name)
  225. {
  226. return 'SAVEPOINT LEVEL' . $name;
  227. }
  228. /**
  229. * Returns a SQL snippet for releasing a previously created save point
  230. *
  231. * @param string $name save point name
  232. * @return string
  233. */
  234. public function releaseSavePointSQL($name)
  235. {
  236. return 'RELEASE SAVEPOINT LEVEL' . $name;
  237. }
  238. /**
  239. * Returns a SQL snippet for rollbacking a previously created save point
  240. *
  241. * @param string $name save point name
  242. * @return string
  243. */
  244. public function rollbackSavePointSQL($name)
  245. {
  246. return 'ROLLBACK TO SAVEPOINT LEVEL' . $name;
  247. }
  248. }