QueryCompiler.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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\Query;
  17. use Cake\Database\ValueBinder;
  18. /**
  19. * Responsible for compiling a Query object into its SQL representation
  20. *
  21. * @internal
  22. */
  23. class QueryCompiler
  24. {
  25. /**
  26. * List of sprintf templates that will be used for compiling the SQL for
  27. * this query. There are some clauses that can be built as just as the
  28. * direct concatenation of the internal parts, those are listed here.
  29. *
  30. * @var array
  31. */
  32. protected $_templates = [
  33. 'delete' => 'DELETE',
  34. 'update' => 'UPDATE %s',
  35. 'where' => ' WHERE %s',
  36. 'group' => ' GROUP BY %s ',
  37. 'having' => ' HAVING %s ',
  38. 'order' => ' %s',
  39. 'limit' => ' LIMIT %s',
  40. 'offset' => ' OFFSET %s',
  41. 'epilog' => ' %s'
  42. ];
  43. /**
  44. * The list of query clauses to traverse for generating a SELECT statement
  45. *
  46. * @var array
  47. */
  48. protected $_selectParts = [
  49. 'select', 'from', 'join', 'where', 'group', 'having', 'order', 'limit',
  50. 'offset', 'union', 'epilog'
  51. ];
  52. /**
  53. * The list of query clauses to traverse for generating an UPDATE statement
  54. *
  55. * @var array
  56. */
  57. protected $_updateParts = ['update', 'set', 'where', 'epilog'];
  58. /**
  59. * The list of query clauses to traverse for generating a DELETE statement
  60. *
  61. * @var array
  62. */
  63. protected $_deleteParts = ['delete', 'from', 'where', 'epilog'];
  64. /**
  65. * The list of query clauses to traverse for generating an INSERT statement
  66. *
  67. * @var array
  68. */
  69. protected $_insertParts = ['insert', 'values', 'epilog'];
  70. /**
  71. * Returns the SQL representation of the provided query after generating
  72. * the placeholders for the bound values using the provided generator
  73. *
  74. * @param \Cake\Database\Query $query The query that is being compiled
  75. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  76. * @return \Closure
  77. */
  78. public function compile(Query $query, ValueBinder $generator)
  79. {
  80. $sql = '';
  81. $type = $query->type();
  82. $query->traverse(
  83. $this->_sqlCompiler($sql, $query, $generator),
  84. $this->{'_' . $type . 'Parts'}
  85. );
  86. return $sql;
  87. }
  88. /**
  89. * Returns a callable object that can be used to compile a SQL string representation
  90. * of this query.
  91. *
  92. * @param string &$sql initial sql string to append to
  93. * @param \Cake\Database\Query $query The query that is being compiled
  94. * @param \Cake\Database\ValueBinder $generator The placeholder and value binder object
  95. * @return \Closure
  96. */
  97. protected function _sqlCompiler(&$sql, $query, $generator)
  98. {
  99. return function ($parts, $name) use (&$sql, $query, $generator) {
  100. if (!count($parts)) {
  101. return;
  102. }
  103. if ($parts instanceof ExpressionInterface) {
  104. $parts = [$parts->sql($generator)];
  105. }
  106. if (isset($this->_templates[$name])) {
  107. $parts = $this->_stringifyExpressions((array)$parts, $generator);
  108. return $sql .= sprintf($this->_templates[$name], implode(', ', $parts));
  109. }
  110. return $sql .= $this->{'_build' . ucfirst($name) . 'Part'}($parts, $query, $generator);
  111. };
  112. }
  113. /**
  114. * Helper function used to build the string representation of a SELECT clause,
  115. * it constructs the field list taking care of aliasing and
  116. * converting expression objects to string. This function also constructs the
  117. * DISTINCT clause for the query.
  118. *
  119. * @param array $parts list of fields to be transformed to string
  120. * @param \Cake\Database\Query $query The query that is being compiled
  121. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  122. * @return string
  123. */
  124. protected function _buildSelectPart($parts, $query, $generator)
  125. {
  126. $driver = $query->connection()->driver();
  127. $select = 'SELECT %s%s%s';
  128. $distinct = $query->clause('distinct');
  129. $modifiers = $query->clause('modifier') ?: null;
  130. $normalized = [];
  131. $parts = $this->_stringifyExpressions($parts, $generator);
  132. foreach ($parts as $k => $p) {
  133. if (!is_numeric($k)) {
  134. $p = $p . ' AS ' . $driver->quoteIdentifier($k);
  135. }
  136. $normalized[] = $p;
  137. }
  138. if ($distinct === true) {
  139. $distinct = 'DISTINCT ';
  140. }
  141. if (is_array($distinct)) {
  142. $distinct = $this->_stringifyExpressions($distinct, $generator);
  143. $distinct = sprintf('DISTINCT ON (%s) ', implode(', ', $distinct));
  144. }
  145. if ($modifiers !== null) {
  146. $modifiers = $this->_stringifyExpressions($modifiers, $generator);
  147. $modifiers = implode(' ', $modifiers) . ' ';
  148. }
  149. return sprintf($select, $distinct, $modifiers, implode(', ', $normalized));
  150. }
  151. /**
  152. * Helper function used to build the string representation of a FROM clause,
  153. * it constructs the tables list taking care of aliasing and
  154. * converting expression objects to string.
  155. *
  156. * @param array $parts list of tables to be transformed to string
  157. * @param \Cake\Database\Query $query The query that is being compiled
  158. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  159. * @return string
  160. */
  161. protected function _buildFromPart($parts, $query, $generator)
  162. {
  163. $select = ' FROM %s';
  164. $normalized = [];
  165. $parts = $this->_stringifyExpressions($parts, $generator);
  166. foreach ($parts as $k => $p) {
  167. if (!is_numeric($k)) {
  168. $p = $p . ' ' . $k;
  169. }
  170. $normalized[] = $p;
  171. }
  172. return sprintf($select, implode(', ', $normalized));
  173. }
  174. /**
  175. * Helper function used to build the string representation of multiple JOIN clauses,
  176. * it constructs the joins list taking care of aliasing and converting
  177. * expression objects to string in both the table to be joined and the conditions
  178. * to be used.
  179. *
  180. * @param array $parts list of joins to be transformed to string
  181. * @param \Cake\Database\Query $query The query that is being compiled
  182. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  183. * @return string
  184. */
  185. protected function _buildJoinPart($parts, $query, $generator)
  186. {
  187. $joins = '';
  188. foreach ($parts as $join) {
  189. if ($join['table'] instanceof ExpressionInterface) {
  190. $join['table'] = '(' . $join['table']->sql($generator) . ')';
  191. }
  192. $joins .= sprintf(' %s JOIN %s %s', $join['type'], $join['table'], $join['alias']);
  193. if (isset($join['conditions']) && count($join['conditions'])) {
  194. $joins .= sprintf(' ON %s', $join['conditions']->sql($generator));
  195. } else {
  196. $joins .= ' ON 1 = 1';
  197. }
  198. }
  199. return $joins;
  200. }
  201. /**
  202. * Helper function to generate SQL for SET expressions.
  203. *
  204. * @param array $parts List of keys & values to set.
  205. * @param \Cake\Database\Query $query The query that is being compiled
  206. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  207. * @return string
  208. */
  209. protected function _buildSetPart($parts, $query, $generator)
  210. {
  211. $set = [];
  212. foreach ($parts as $part) {
  213. if ($part instanceof ExpressionInterface) {
  214. $part = $part->sql($generator);
  215. }
  216. if ($part[0] === '(') {
  217. $part = substr($part, 1, -1);
  218. }
  219. $set[] = $part;
  220. }
  221. return ' SET ' . implode('', $set);
  222. }
  223. /**
  224. * Builds the SQL string for all the UNION clauses in this query, when dealing
  225. * with query objects it will also transform them using their configured SQL
  226. * dialect.
  227. *
  228. * @param array $parts list of queries to be operated with UNION
  229. * @param \Cake\Database\Query $query The query that is being compiled
  230. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  231. * @return string
  232. */
  233. protected function _buildUnionPart($parts, $query, $generator)
  234. {
  235. $parts = array_map(function ($p) use ($generator) {
  236. $p['query'] = $p['query']->sql($generator);
  237. $p['query'] = $p['query'][0] === '(' ? trim($p['query'], '()') : $p['query'];
  238. $prefix = $p['all'] ? 'ALL' : '';
  239. return sprintf('%s (%s)', $prefix, $p['query']);
  240. }, $parts);
  241. return sprintf("\nUNION %s", implode("\nUNION ", $parts));
  242. }
  243. /**
  244. * Builds the SQL fragment for INSERT INTO.
  245. *
  246. * @param array $parts The insert parts.
  247. * @param \Cake\Database\Query $query The query that is being compiled
  248. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  249. * @return string SQL fragment.
  250. */
  251. protected function _buildInsertPart($parts, $query, $generator)
  252. {
  253. $table = $parts[0];
  254. $columns = $this->_stringifyExpressions($parts[1], $generator);
  255. return sprintf('INSERT INTO %s (%s)', $table, implode(', ', $columns));
  256. }
  257. /**
  258. * Builds the SQL fragment for INSERT INTO.
  259. *
  260. * @param array $parts The values parts.
  261. * @param \Cake\Database\Query $query The query that is being compiled
  262. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  263. * @return string SQL fragment.
  264. */
  265. protected function _buildValuesPart($parts, $query, $generator)
  266. {
  267. return implode('', $this->_stringifyExpressions($parts, $generator));
  268. }
  269. /**
  270. * Helper function used to covert ExpressionInterface objects inside an array
  271. * into their string representation.
  272. *
  273. * @param array $expressions list of strings and ExpressionInterface objects
  274. * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
  275. * @return array
  276. */
  277. protected function _stringifyExpressions($expressions, $generator)
  278. {
  279. $result = [];
  280. foreach ($expressions as $k => $expression) {
  281. if ($expression instanceof ExpressionInterface) {
  282. $value = $expression->sql($generator);
  283. $expression = '(' . $value . ')';
  284. }
  285. $result[$k] = $expression;
  286. }
  287. return $result;
  288. }
  289. }