ValuesExpression.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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\Expression;
  16. use Cake\Database\Exception;
  17. use Cake\Database\ExpressionInterface;
  18. use Cake\Database\Query;
  19. use Cake\Database\TypeMapTrait;
  20. use Cake\Database\Type\ExpressionTypeCasterTrait;
  21. use Cake\Database\ValueBinder;
  22. /**
  23. * An expression object to contain values being inserted.
  24. *
  25. * Helps generate SQL with the correct number of placeholders and bind
  26. * values correctly into the statement.
  27. *
  28. * @internal
  29. */
  30. class ValuesExpression implements ExpressionInterface
  31. {
  32. use ExpressionTypeCasterTrait;
  33. use TypeMapTrait;
  34. /**
  35. * Array of values to insert.
  36. *
  37. * @var array
  38. */
  39. protected $_values = [];
  40. /**
  41. * List of columns to ensure are part of the insert.
  42. *
  43. * @var array
  44. */
  45. protected $_columns = [];
  46. /**
  47. * The Query object to use as a values expression
  48. *
  49. * @var \Cake\Database\Query|null
  50. */
  51. protected $_query = null;
  52. /**
  53. * Whether or not values have been casted to expressions
  54. * already.
  55. *
  56. * @var string
  57. */
  58. protected $_castedExpressions = false;
  59. /**
  60. * Constructor
  61. *
  62. * @param array $columns The list of columns that are going to be part of the values.
  63. * @param \Cake\Database\TypeMap $typeMap A dictionary of column -> type names
  64. */
  65. public function __construct(array $columns, $typeMap)
  66. {
  67. $this->_columns = $columns;
  68. $this->typeMap($typeMap);
  69. }
  70. /**
  71. * Add a row of data to be inserted.
  72. *
  73. * @param array|\Cake\Database\Query $data Array of data to append into the insert, or
  74. * a query for doing INSERT INTO .. SELECT style commands
  75. * @return void
  76. * @throws \Cake\Database\Exception When mixing array + Query data types.
  77. */
  78. public function add($data)
  79. {
  80. if ((count($this->_values) && $data instanceof Query) ||
  81. ($this->_query && is_array($data))
  82. ) {
  83. throw new Exception(
  84. 'You cannot mix subqueries and array data in inserts.'
  85. );
  86. }
  87. if ($data instanceof Query) {
  88. $this->query($data);
  89. return;
  90. }
  91. $this->_values[] = $data;
  92. $this->_castedExpressions = false;
  93. }
  94. /**
  95. * Sets the columns to be inserted.
  96. *
  97. * @param array $cols Array with columns to be inserted.
  98. * @return $this
  99. */
  100. public function setColumns($cols)
  101. {
  102. $this->_columns = $cols;
  103. $this->_castedExpressions = false;
  104. return $this;
  105. }
  106. /**
  107. * Gets the columns to be inserted.
  108. *
  109. * @return array
  110. */
  111. public function getColumns()
  112. {
  113. return $this->_columns;
  114. }
  115. /**
  116. * Sets the columns to be inserted. If no params are passed, then it returns
  117. * the currently stored columns.
  118. *
  119. * @deprecated 3.4.0 Use setColumns()/getColumns() instead.
  120. * @param array|null $cols Array with columns to be inserted.
  121. * @return array|$this
  122. */
  123. public function columns($cols = null)
  124. {
  125. if ($cols !== null) {
  126. return $this->setColumns($cols);
  127. }
  128. return $this->getColumns();
  129. }
  130. /**
  131. * Get the bare column names.
  132. *
  133. * Because column names could be identifier quoted, we
  134. * need to strip the identifiers off of the columns.
  135. *
  136. * @return array
  137. */
  138. protected function _columnNames()
  139. {
  140. $columns = [];
  141. foreach ($this->_columns as $col) {
  142. if (is_string($col)) {
  143. $col = trim($col, '`[]"');
  144. }
  145. $columns[] = $col;
  146. }
  147. return $columns;
  148. }
  149. /**
  150. * Sets the values to be inserted.
  151. *
  152. * @param array $values Array with values to be inserted.
  153. * @return $this
  154. */
  155. public function setValues($values)
  156. {
  157. $this->_values = $values;
  158. $this->_castedExpressions = false;
  159. return $this;
  160. }
  161. /**
  162. * Gets the values to be inserted.
  163. *
  164. * @return array
  165. */
  166. public function getValues()
  167. {
  168. if (!$this->_castedExpressions) {
  169. $this->_processExpressions();
  170. }
  171. return $this->_values;
  172. }
  173. /**
  174. * Sets the values to be inserted. If no params are passed, then it returns
  175. * the currently stored values
  176. *
  177. * @param array|null $values Array with values to be inserted.
  178. * @return array|$this
  179. */
  180. public function values($values = null)
  181. {
  182. if ($values !== null) {
  183. return $this->setValues($values);
  184. }
  185. return $this->getValues();
  186. }
  187. /**
  188. * Sets the query object to be used as the values expression to be evaluated
  189. * to insert records in the table.
  190. *
  191. * @param \Cake\Database\Query $query The query to set
  192. * @return $this
  193. */
  194. public function setQuery(Query $query)
  195. {
  196. $this->_query = $query;
  197. return $this;
  198. }
  199. /**
  200. * Gets the query object to be used as the values expression to be evaluated
  201. * to insert records in the table.
  202. *
  203. * @return \Cake\Database\Query
  204. */
  205. public function getQuery()
  206. {
  207. return $this->_query;
  208. }
  209. /**
  210. * Sets the query object to be used as the values expression to be evaluated
  211. * to insert records in the table. If no params are passed, then it returns
  212. * the currently stored query
  213. *
  214. * @deprecated 3.4.0 Use setQuery()/getQuery() instead.
  215. * @param \Cake\Database\Query|null $query The query to set
  216. * @return \Cake\Database\Query|null|$this
  217. */
  218. public function query(Query $query = null)
  219. {
  220. if ($query !== null) {
  221. return $this->setQuery($query);
  222. }
  223. return $this->getQuery();
  224. }
  225. /**
  226. * Convert the values into a SQL string with placeholders.
  227. *
  228. * @param \Cake\Database\ValueBinder $generator Placeholder generator object
  229. * @return string
  230. */
  231. public function sql(ValueBinder $generator)
  232. {
  233. if (empty($this->_values) && empty($this->_query)) {
  234. return '';
  235. }
  236. if (!$this->_castedExpressions) {
  237. $this->_processExpressions();
  238. }
  239. $i = 0;
  240. $columns = [];
  241. $columns = $this->_columnNames();
  242. $defaults = array_fill_keys($columns, null);
  243. $placeholders = [];
  244. $types = [];
  245. $typeMap = $this->typeMap();
  246. foreach ($defaults as $col => $v) {
  247. $types[$col] = $typeMap->type($col);
  248. }
  249. foreach ($this->_values as $row) {
  250. $row += $defaults;
  251. $rowPlaceholders = [];
  252. foreach ($columns as $column) {
  253. $value = $row[$column];
  254. if ($value instanceof ExpressionInterface) {
  255. $rowPlaceholders[] = '(' . $value->sql($generator) . ')';
  256. continue;
  257. }
  258. $placeholder = $generator->placeholder($i);
  259. $rowPlaceholders[] = $placeholder;
  260. $generator->bind($placeholder, $value, $types[$column]);
  261. }
  262. $placeholders[] = implode(', ', $rowPlaceholders);
  263. }
  264. if ($this->query()) {
  265. return ' ' . $this->query()->sql($generator);
  266. }
  267. return sprintf(' VALUES (%s)', implode('), (', $placeholders));
  268. }
  269. /**
  270. * Traverse the values expression.
  271. *
  272. * This method will also traverse any queries that are to be used in the INSERT
  273. * values.
  274. *
  275. * @param callable $visitor The visitor to traverse the expression with.
  276. * @return void
  277. */
  278. public function traverse(callable $visitor)
  279. {
  280. if ($this->_query) {
  281. return;
  282. }
  283. if (!$this->_castedExpressions) {
  284. $this->_processExpressions();
  285. }
  286. foreach ($this->_values as $v) {
  287. if ($v instanceof ExpressionInterface) {
  288. $v->traverse($visitor);
  289. }
  290. if (!is_array($v)) {
  291. continue;
  292. }
  293. foreach ($v as $column => $field) {
  294. if ($field instanceof ExpressionInterface) {
  295. $visitor($field);
  296. $field->traverse($visitor);
  297. }
  298. }
  299. }
  300. }
  301. /**
  302. * Converts values that need to be casted to expressions
  303. *
  304. * @return void
  305. */
  306. protected function _processExpressions()
  307. {
  308. $types = [];
  309. $typeMap = $this->typeMap();
  310. $columns = $this->_columnNames();
  311. foreach ($columns as $c) {
  312. if (!is_scalar($c)) {
  313. continue;
  314. }
  315. $types[$c] = $typeMap->type($c);
  316. }
  317. $types = $this->_requiresToExpressionCasting($types);
  318. if (empty($types)) {
  319. return;
  320. }
  321. foreach ($this->_values as $row => $values) {
  322. foreach ($types as $col => $type) {
  323. /* @var \Cake\Database\Type\ExpressionTypeInterface $type */
  324. $this->_values[$row][$col] = $type->toExpression($values[$col]);
  325. }
  326. }
  327. $this->_castedExpressions = true;
  328. }
  329. }