ValuesExpression.php 10 KB

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