"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/lib/Drupal/Core/Database/Query/Merge.php" (26 Nov 2020, 12713 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Merge.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\Core\Database\Query;
    4 
    5 use Drupal\Core\Database\Database;
    6 use Drupal\Core\Database\Connection;
    7 use Drupal\Core\Database\IntegrityConstraintViolationException;
    8 
    9 /**
   10  * General class for an abstracted MERGE query operation.
   11  *
   12  * An ANSI SQL:2003 compatible database would run the following query:
   13  *
   14  * @code
   15  * MERGE INTO table_name_1 USING table_name_2 ON (condition)
   16  *   WHEN MATCHED THEN
   17  *   UPDATE SET column1 = value1 [, column2 = value2 ...]
   18  *   WHEN NOT MATCHED THEN
   19  *   INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...
   20  * @endcode
   21  *
   22  * Other databases (most notably MySQL, PostgreSQL and SQLite) will emulate
   23  * this statement by running a SELECT and then INSERT or UPDATE.
   24  *
   25  * By default, the two table names are identical and they are passed into the
   26  * the constructor. table_name_2 can be specified by the
   27  * MergeQuery::conditionTable() method. It can be either a string or a
   28  * subquery.
   29  *
   30  * The condition is built exactly like SelectQuery or UpdateQuery conditions,
   31  * the UPDATE query part is built similarly like an UpdateQuery and finally the
   32  * INSERT query part is built similarly like an InsertQuery. However, both
   33  * UpdateQuery and InsertQuery has a fields method so
   34  * MergeQuery::updateFields() and MergeQuery::insertFields() needs to be called
   35  * instead. MergeQuery::fields() can also be called which calls both of these
   36  * methods as the common case is to use the same column-value pairs for both
   37  * INSERT and UPDATE. However, this is not mandatory. Another convenient
   38  * wrapper is MergeQuery::key() which adds the same column-value pairs to the
   39  * condition and the INSERT query part.
   40  *
   41  * Several methods (key(), fields(), insertFields()) can be called to set a
   42  * key-value pair for the INSERT query part. Subsequent calls for the same
   43  * fields override the earlier ones. The same is true for UPDATE and key(),
   44  * fields() and updateFields().
   45  */
   46 class Merge extends Query implements ConditionInterface {
   47 
   48   use QueryConditionTrait;
   49 
   50   /**
   51    * Returned by execute() if an INSERT query has been executed.
   52    */
   53   const STATUS_INSERT = 1;
   54 
   55   /**
   56    * Returned by execute() if an UPDATE query has been executed.
   57    */
   58   const STATUS_UPDATE = 2;
   59 
   60   /**
   61    * The table to be used for INSERT and UPDATE.
   62    *
   63    * @var string
   64    */
   65   protected $table;
   66 
   67   /**
   68    * The table or subquery to be used for the condition.
   69    *
   70    * @var string
   71    */
   72   protected $conditionTable;
   73 
   74   /**
   75    * An array of fields on which to insert.
   76    *
   77    * @var array
   78    */
   79   protected $insertFields = [];
   80 
   81   /**
   82    * An array of fields which should be set to their database-defined defaults.
   83    *
   84    * Used on INSERT.
   85    *
   86    * @var array
   87    */
   88   protected $defaultFields = [];
   89 
   90   /**
   91    * An array of values to be inserted.
   92    *
   93    * @var string
   94    */
   95   protected $insertValues = [];
   96 
   97   /**
   98    * An array of fields that will be updated.
   99    *
  100    * @var array
  101    */
  102   protected $updateFields = [];
  103 
  104   /**
  105    * Array of fields to update to an expression in case of a duplicate record.
  106    *
  107    * This variable is a nested array in the following format:
  108    * @code
  109    * <some field> => array(
  110    *  'condition' => <condition to execute, as a string>,
  111    *  'arguments' => <array of arguments for condition, or NULL for none>,
  112    * );
  113    * @endcode
  114    *
  115    * @var array
  116    */
  117   protected $expressionFields = [];
  118 
  119   /**
  120    * Flag indicating whether an UPDATE is necessary.
  121    *
  122    * @var bool
  123    */
  124   protected $needsUpdate = FALSE;
  125 
  126   /**
  127    * Constructs a Merge object.
  128    *
  129    * @param \Drupal\Core\Database\Connection $connection
  130    *   A Connection object.
  131    * @param string $table
  132    *   Name of the table to associate with this query.
  133    * @param array $options
  134    *   Array of database options.
  135    */
  136   public function __construct(Connection $connection, $table, array $options = []) {
  137     $options['return'] = Database::RETURN_AFFECTED;
  138     parent::__construct($connection, $options);
  139     $this->table = $table;
  140     $this->conditionTable = $table;
  141     $this->condition = $this->connection->condition('AND');
  142   }
  143 
  144   /**
  145    * Sets the table or subquery to be used for the condition.
  146    *
  147    * @param $table
  148    *   The table name or the subquery to be used. Use a Select query object to
  149    *   pass in a subquery.
  150    *
  151    * @return $this
  152    *   The called object.
  153    */
  154   protected function conditionTable($table) {
  155     $this->conditionTable = $table;
  156     return $this;
  157   }
  158 
  159   /**
  160    * Adds a set of field->value pairs to be updated.
  161    *
  162    * @param $fields
  163    *   An associative array of fields to write into the database. The array keys
  164    *   are the field names and the values are the values to which to set them.
  165    *
  166    * @return $this
  167    *   The called object.
  168    */
  169   public function updateFields(array $fields) {
  170     $this->updateFields = $fields;
  171     $this->needsUpdate = TRUE;
  172     return $this;
  173   }
  174 
  175   /**
  176    * Specifies fields to be updated as an expression.
  177    *
  178    * Expression fields are cases such as counter = counter + 1. This method
  179    * takes precedence over MergeQuery::updateFields() and its wrappers,
  180    * MergeQuery::key() and MergeQuery::fields().
  181    *
  182    * @param $field
  183    *   The field to set.
  184    * @param $expression
  185    *   The field will be set to the value of this expression. This parameter
  186    *   may include named placeholders.
  187    * @param $arguments
  188    *   If specified, this is an array of key/value pairs for named placeholders
  189    *   corresponding to the expression.
  190    *
  191    * @return $this
  192    *   The called object.
  193    */
  194   public function expression($field, $expression, array $arguments = NULL) {
  195     $this->expressionFields[$field] = [
  196       'expression' => $expression,
  197       'arguments' => $arguments,
  198     ];
  199     $this->needsUpdate = TRUE;
  200     return $this;
  201   }
  202 
  203   /**
  204    * Adds a set of field->value pairs to be inserted.
  205    *
  206    * @param $fields
  207    *   An array of fields on which to insert. This array may be indexed or
  208    *   associative. If indexed, the array is taken to be the list of fields.
  209    *   If associative, the keys of the array are taken to be the fields and
  210    *   the values are taken to be corresponding values to insert. If a
  211    *   $values argument is provided, $fields must be indexed.
  212    * @param $values
  213    *   An array of fields to insert into the database. The values must be
  214    *   specified in the same order as the $fields array.
  215    *
  216    * @return $this
  217    *   The called object.
  218    */
  219   public function insertFields(array $fields, array $values = []) {
  220     if ($values) {
  221       $fields = array_combine($fields, $values);
  222     }
  223     $this->insertFields = $fields;
  224     return $this;
  225   }
  226 
  227   /**
  228    * Specifies fields for which the database-defaults should be used.
  229    *
  230    * If you want to force a given field to use the database-defined default,
  231    * not NULL or undefined, use this method to instruct the database to use
  232    * default values explicitly. In most cases this will not be necessary
  233    * unless you are inserting a row that is all default values, as you cannot
  234    * specify no values in an INSERT query.
  235    *
  236    * Specifying a field both in fields() and in useDefaults() is an error
  237    * and will not execute.
  238    *
  239    * @param $fields
  240    *   An array of values for which to use the default values
  241    *   specified in the table definition.
  242    *
  243    * @return $this
  244    *   The called object.
  245    */
  246   public function useDefaults(array $fields) {
  247     $this->defaultFields = $fields;
  248     return $this;
  249   }
  250 
  251   /**
  252    * Sets common field-value pairs in the INSERT and UPDATE query parts.
  253    *
  254    * This method should only be called once. It may be called either
  255    * with a single associative array or two indexed arrays. If called
  256    * with an associative array, the keys are taken to be the fields
  257    * and the values are taken to be the corresponding values to set.
  258    * If called with two arrays, the first array is taken as the fields
  259    * and the second array is taken as the corresponding values.
  260    *
  261    * @param $fields
  262    *   An array of fields to insert, or an associative array of fields and
  263    *   values. The keys of the array are taken to be the fields and the values
  264    *   are taken to be corresponding values to insert.
  265    * @param $values
  266    *   An array of values to set into the database. The values must be
  267    *   specified in the same order as the $fields array.
  268    *
  269    * @return $this
  270    *   The called object.
  271    */
  272   public function fields(array $fields, array $values = []) {
  273     if ($values) {
  274       $fields = array_combine($fields, $values);
  275     }
  276     foreach ($fields as $key => $value) {
  277       $this->insertFields[$key] = $value;
  278       $this->updateFields[$key] = $value;
  279     }
  280     $this->needsUpdate = TRUE;
  281     return $this;
  282   }
  283 
  284   /**
  285    * Sets the key fields to be used as conditions for this query.
  286    *
  287    * This method should only be called once. It may be called either
  288    * with a single associative array or two indexed arrays. If called
  289    * with an associative array, the keys are taken to be the fields
  290    * and the values are taken to be the corresponding values to set.
  291    * If called with two arrays, the first array is taken as the fields
  292    * and the second array is taken as the corresponding values.
  293    *
  294    * The fields are copied to the condition of the query and the INSERT part.
  295    * If no other method is called, the UPDATE will become a no-op.
  296    *
  297    * @param $fields
  298    *   An array of fields to set, or an associative array of fields and values.
  299    * @param $values
  300    *   An array of values to set into the database. The values must be
  301    *   specified in the same order as the $fields array.
  302    *
  303    * @return $this
  304    */
  305   public function keys(array $fields, array $values = []) {
  306     if ($values) {
  307       $fields = array_combine($fields, $values);
  308     }
  309     foreach ($fields as $key => $value) {
  310       $this->insertFields[$key] = $value;
  311       $this->condition($key, $value);
  312     }
  313     return $this;
  314   }
  315 
  316   /**
  317    * Sets a single key field to be used as condition for this query.
  318    *
  319    * Same as \Drupal\Core\Database\Query\Merge::keys() but offering a signature
  320    * that is more natural for the case of a single key.
  321    *
  322    * @param string $field
  323    *   The name of the field to set.
  324    * @param mixed $value
  325    *   The value to set into the database.
  326    *
  327    * @return $this
  328    *
  329    * @see \Drupal\Core\Database\Query\Merge::keys()
  330    */
  331   public function key($field, $value = NULL) {
  332     // @todo D9: Remove this backwards-compatibility shim.
  333     if (is_array($field)) {
  334       $this->keys($field, isset($value) ? $value : []);
  335     }
  336     else {
  337       $this->keys([$field => $value]);
  338     }
  339     return $this;
  340   }
  341 
  342   /**
  343    * Implements PHP magic __toString method to convert the query to a string.
  344    *
  345    * In the degenerate case, there is no string-able query as this operation
  346    * is potentially two queries.
  347    *
  348    * @return string
  349    *   The prepared query statement.
  350    */
  351   public function __toString() {
  352   }
  353 
  354   public function execute() {
  355     // Default options for merge queries.
  356     $this->queryOptions += [
  357       'throw_exception' => TRUE,
  358     ];
  359 
  360     try {
  361       if (!count($this->condition)) {
  362         throw new InvalidMergeQueryException('Invalid merge query: no conditions');
  363       }
  364       $select = $this->connection->select($this->conditionTable)
  365         ->condition($this->condition);
  366       $select->addExpression('1');
  367       if (!$select->execute()->fetchField()) {
  368         try {
  369           $insert = $this->connection->insert($this->table)->fields($this->insertFields);
  370           if ($this->defaultFields) {
  371             $insert->useDefaults($this->defaultFields);
  372           }
  373           $insert->execute();
  374           return self::STATUS_INSERT;
  375         }
  376         catch (IntegrityConstraintViolationException $e) {
  377           // The insert query failed, maybe it's because a racing insert query
  378           // beat us in inserting the same row. Retry the select query, if it
  379           // returns a row, ignore the error and continue with the update
  380           // query below.
  381           if (!$select->execute()->fetchField()) {
  382             throw $e;
  383           }
  384         }
  385       }
  386       if ($this->needsUpdate) {
  387         $update = $this->connection->update($this->table)
  388           ->fields($this->updateFields)
  389           ->condition($this->condition);
  390         if ($this->expressionFields) {
  391           foreach ($this->expressionFields as $field => $data) {
  392             $update->expression($field, $data['expression'], $data['arguments']);
  393           }
  394         }
  395         $update->execute();
  396         return self::STATUS_UPDATE;
  397       }
  398     }
  399     catch (\Exception $e) {
  400       if ($this->queryOptions['throw_exception']) {
  401         throw $e;
  402       }
  403       else {
  404         return NULL;
  405       }
  406     }
  407   }
  408 
  409 }