Insert.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <?php
  2. namespace Drupal\Core\Database\Query;
  3. use Drupal\Core\Database\Database;
  4. /**
  5. * General class for an abstracted INSERT query.
  6. *
  7. * @ingroup database
  8. */
  9. class Insert extends Query implements \Countable {
  10. use InsertTrait;
  11. /**
  12. * A SelectQuery object to fetch the rows that should be inserted.
  13. *
  14. * @var \Drupal\Core\Database\Query\SelectInterface
  15. */
  16. protected $fromQuery;
  17. /**
  18. * Constructs an Insert object.
  19. *
  20. * @param \Drupal\Core\Database\Connection $connection
  21. * A Connection object.
  22. * @param string $table
  23. * Name of the table to associate with this query.
  24. * @param array $options
  25. * Array of database options.
  26. */
  27. public function __construct($connection, $table, array $options = []) {
  28. if (!isset($options['return'])) {
  29. $options['return'] = Database::RETURN_INSERT_ID;
  30. }
  31. parent::__construct($connection, $options);
  32. $this->table = $table;
  33. }
  34. /**
  35. * Sets the fromQuery on this InsertQuery object.
  36. *
  37. * @param \Drupal\Core\Database\Query\SelectInterface $query
  38. * The query to fetch the rows that should be inserted.
  39. *
  40. * @return \Drupal\Core\Database\Query\Insert
  41. * The called object.
  42. */
  43. public function from(SelectInterface $query) {
  44. $this->fromQuery = $query;
  45. return $this;
  46. }
  47. /**
  48. * Executes the insert query.
  49. *
  50. * @return
  51. * The last insert ID of the query, if one exists. If the query was given
  52. * multiple sets of values to insert, the return value is undefined. If no
  53. * fields are specified, this method will do nothing and return NULL. That
  54. * That makes it safe to use in multi-insert loops.
  55. */
  56. public function execute() {
  57. // If validation fails, simply return NULL. Note that validation routines
  58. // in preExecute() may throw exceptions instead.
  59. if (!$this->preExecute()) {
  60. return NULL;
  61. }
  62. // If we're selecting from a SelectQuery, finish building the query and
  63. // pass it back, as any remaining options are irrelevant.
  64. if (!empty($this->fromQuery)) {
  65. $sql = (string) $this;
  66. // The SelectQuery may contain arguments, load and pass them through.
  67. return $this->connection->query($sql, $this->fromQuery->getArguments(), $this->queryOptions);
  68. }
  69. $last_insert_id = 0;
  70. // Each insert happens in its own query in the degenerate case. However,
  71. // we wrap it in a transaction so that it is atomic where possible. On many
  72. // databases, such as SQLite, this is also a notable performance boost.
  73. $transaction = $this->connection->startTransaction();
  74. try {
  75. $sql = (string) $this;
  76. foreach ($this->insertValues as $insert_values) {
  77. $last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions);
  78. }
  79. }
  80. catch (\Exception $e) {
  81. // One of the INSERTs failed, rollback the whole batch.
  82. $transaction->rollBack();
  83. // Rethrow the exception for the calling code.
  84. throw $e;
  85. }
  86. // Re-initialize the values array so that we can re-use this query.
  87. $this->insertValues = [];
  88. // Transaction commits here where $transaction looses scope.
  89. return $last_insert_id;
  90. }
  91. /**
  92. * Implements PHP magic __toString method to convert the query to a string.
  93. *
  94. * @return string
  95. * The prepared statement.
  96. */
  97. public function __toString() {
  98. // Create a sanitized comment string to prepend to the query.
  99. $comments = $this->connection->makeComment($this->comments);
  100. // Default fields are always placed first for consistency.
  101. $insert_fields = array_merge($this->defaultFields, $this->insertFields);
  102. if (!empty($this->fromQuery)) {
  103. return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
  104. }
  105. // For simplicity, we will use the $placeholders array to inject
  106. // default keywords even though they are not, strictly speaking,
  107. // placeholders for prepared statements.
  108. $placeholders = [];
  109. $placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
  110. $placeholders = array_pad($placeholders, count($this->insertFields), '?');
  111. return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')';
  112. }
  113. /**
  114. * Preprocesses and validates the query.
  115. *
  116. * @return bool
  117. * TRUE if the validation was successful, FALSE if not.
  118. *
  119. * @throws \Drupal\Core\Database\Query\FieldsOverlapException
  120. * @throws \Drupal\Core\Database\Query\NoFieldsException
  121. */
  122. protected function preExecute() {
  123. // Confirm that the user did not try to specify an identical
  124. // field and default field.
  125. if (array_intersect($this->insertFields, $this->defaultFields)) {
  126. throw new FieldsOverlapException('You may not specify the same field to have a value and a schema-default value.');
  127. }
  128. if (!empty($this->fromQuery)) {
  129. // We have to assume that the used aliases match the insert fields.
  130. // Regular fields are added to the query before expressions, maintain the
  131. // same order for the insert fields.
  132. // This behavior can be overridden by calling fields() manually as only the
  133. // first call to fields() does have an effect.
  134. $this->fields(array_merge(array_keys($this->fromQuery->getFields()), array_keys($this->fromQuery->getExpressions())));
  135. }
  136. else {
  137. // Don't execute query without fields.
  138. if (count($this->insertFields) + count($this->defaultFields) == 0) {
  139. throw new NoFieldsException('There are no fields available to insert with.');
  140. }
  141. }
  142. // If no values have been added, silently ignore this query. This can happen
  143. // if values are added conditionally, so we don't want to throw an
  144. // exception.
  145. if (!isset($this->insertValues[0]) && count($this->insertFields) > 0 && empty($this->fromQuery)) {
  146. return FALSE;
  147. }
  148. return TRUE;
  149. }
  150. }