You are here

class Merge in Drupal driver for SQL Server and SQL Azure 8.2

Same name and namespace in other branches
  1. 8 drivers/lib/Drupal/Driver/Database/sqlsrv/Merge.php \Drupal\Driver\Database\sqlsrv\Merge
  2. 3.0.x drivers/lib/Drupal/Driver/Database/sqlsrv/Merge.php \Drupal\Driver\Database\sqlsrv\Merge

Hierarchy

Expanded class hierarchy of Merge

File

drivers/lib/Drupal/Driver/Database/sqlsrv/Merge.php, line 8

Namespace

Drupal\Driver\Database\sqlsrv
View source
class Merge extends QueryMerge {

  /**
   * Returned by execute() no
   * records have been affected.
   */
  const STATUS_NONE = -1;

  /**
   * The database connection
   *
   * @var Connection
   */
  protected $connection;

  /**
   * Keep track of the number of
   * placeholders in the compiled query
   *
   * @var int
   */
  protected $totalPlaceholders;

  /**
   * {@inheritdoc}
   */
  public function execute() {
    if (!count($this->condition)) {
      throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
    }

    // Check that the table does exist.
    if (!$this->connection
      ->schema()
      ->tableExists($this->table)) {
      throw new \Drupal\Core\Database\SchemaObjectDoesNotExistException("Table {$this->table} does not exist.");
    }

    // Retrieve query options.
    $options = $this->queryOptions;

    // Keep a reference to the blobs.
    $blobs = [];

    // Fetch the list of blobs and sequences used on that table.
    $columnInformation = $this->connection
      ->schema()
      ->getTableIntrospection($this->table);

    // Find out if there is an identity field set in this insert.
    $this->setIdentity = !empty($columnInformation['identity']) && in_array($columnInformation['identity'], array_keys($this->insertFields));

    // Initialize placeholder count.
    $max_placeholder = 0;

    // Stringify the query
    $query = $this
      ->__toString();

    // Build the query, ensure that we have retries for concurrency control
    $options['integrityretry'] = true;
    if ($this->totalPlaceholders >= 2100) {
      $options['insecure'] = true;
    }
    $stmt = $this->connection
      ->prepareQuery($query, $options);

    // Build the arguments: 1. condition.
    $arguments = $this->condition
      ->arguments();
    $stmt
      ->BindArguments($arguments);

    // 2. When matched part.
    $fields = $this->updateFields;
    $stmt
      ->BindExpressions($this->expressionFields, $fields);
    $stmt
      ->BindValues($fields, $blobs, ':db_merge_placeholder_', $columnInformation, $max_placeholder);

    // 3. When not matched part.
    $stmt
      ->BindValues($this->insertFields, $blobs, ':db_merge_placeholder_', $columnInformation, $max_placeholder);

    // 4. Run the query, this will return UPDATE or INSERT
    $this->connection
      ->query($stmt, []);
    $result = null;
    foreach ($stmt as $value) {
      $result = $value->{'$action'};
    }
    switch ($result) {
      case 'UPDATE':
        return static::STATUS_UPDATE;
      case 'INSERT':
        return static::STATUS_INSERT;
      default:
        if (!empty($this->expressionFields)) {
          throw new InvalidMergeQueryException(t('Invalid merge query: no results.'));
        }
        else {
          return static::STATUS_NONE;
        }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function __toString() {

    // Initialize placeholder count.
    $max_placeholder = 0;
    $max_placeholder_conditions = 0;
    $query = [];

    // Enable direct insertion to identity columns if necessary.
    if (!empty($this->setIdentity)) {
      $query[] = 'SET IDENTITY_INSERT {' . $this->table . '} ON;';
    }
    $query[] = 'MERGE INTO {' . $this->table . '} _target';

    // 1. Condition part.
    $this->condition
      ->compile($this->connection, $this);
    $key_conditions = [];
    $template_item = [];
    $conditions = $this
      ->conditions();
    unset($conditions['#conjunction']);
    foreach ($conditions as $condition) {
      $key_conditions[] = '_target.' . $this->connection
        ->escapeField($condition['field']) . ' = ' . '_source.' . $this->connection
        ->escapeField($condition['field']);
      $template_item[] = ':db_condition_placeholder_' . $max_placeholder_conditions++ . ' AS ' . $this->connection
        ->escapeField($condition['field']);
    }
    $query[] = 'USING (SELECT ' . implode(', ', $template_item) . ') _source ' . PHP_EOL . 'ON ' . implode(' AND ', $key_conditions);

    // 2. "When matched" part.
    // Expressions take priority over literal fields, so we process those first
    // and remove any literal fields that conflict.
    $fields = $this->updateFields;
    $update_fields = [];
    foreach ($this->expressionFields as $field => $data) {
      $update_fields[] = $field . '=' . $data['expression'];
      unset($fields[$field]);
    }
    foreach ($fields as $field => $value) {
      $update_fields[] = $this->connection
        ->quoteIdentifier($field) . '=:db_merge_placeholder_' . $max_placeholder++;
    }
    if (!empty($update_fields)) {
      $query[] = 'WHEN MATCHED THEN UPDATE SET ' . implode(', ', $update_fields);
    }

    // 3. "When not matched" part.
    if ($this->insertFields) {

      // Build the list of placeholders.
      $placeholders = [];
      $insertFieldsCount = count($this->insertFields);
      for ($i = 0; $i < $insertFieldsCount; ++$i) {
        $placeholders[] = ':db_merge_placeholder_' . $max_placeholder++;
      }
      $query[] = 'WHEN NOT MATCHED THEN INSERT (' . implode(', ', $this->connection
        ->quoteIdentifiers(array_keys($this->insertFields))) . ') VALUES (' . implode(', ', $placeholders) . ')';
    }
    else {
      $query[] = 'WHEN NOT MATCHED THEN INSERT DEFAULT VALUES';
    }

    // Return information about the query.
    $query[] = 'OUTPUT $action;';
    $this->totalPlaceholders = $max_placeholder + $max_placeholder_conditions;
    return implode(PHP_EOL, $query);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Merge::$conditionTable protected property The table or subquery to be used for the condition.
Merge::$connection protected property The database connection Overrides Query::$connection
Merge::$defaultFields protected property An array of fields which should be set to their database-defined defaults.
Merge::$expressionFields protected property Array of fields to update to an expression in case of a duplicate record.
Merge::$insertFields protected property An array of fields on which to insert.
Merge::$insertValues protected property An array of values to be inserted.
Merge::$needsUpdate protected property Flag indicating whether an UPDATE is necessary.
Merge::$table protected property The table to be used for INSERT and UPDATE.
Merge::$totalPlaceholders protected property Keep track of the number of placeholders in the compiled query
Merge::$updateFields protected property An array of fields that will be updated.
Merge::conditionTable protected function Sets the table or subquery to be used for the condition.
Merge::execute public function Runs the query against the database. Overrides Merge::execute
Merge::expression public function Specifies fields to be updated as an expression.
Merge::fields public function Sets common field-value pairs in the INSERT and UPDATE query parts.
Merge::insertFields public function Adds a set of field->value pairs to be inserted.
Merge::key public function Sets a single key field to be used as condition for this query.
Merge::keys public function Sets the key fields to be used as conditions for this query.
Merge::STATUS_INSERT constant Returned by execute() if an INSERT query has been executed.
Merge::STATUS_NONE constant Returned by execute() no records have been affected.
Merge::STATUS_UPDATE constant Returned by execute() if an UPDATE query has been executed.
Merge::updateFields public function Adds a set of field->value pairs to be updated.
Merge::useDefaults public function Specifies fields for which the database-defaults should be used.
Merge::__construct public function Constructs a Merge object. Overrides Query::__construct
Merge::__toString public function Implements PHP magic __toString method to convert the query to a string. Overrides Merge::__toString
Query::$comments protected property An array of comments that can be prepended to a query.
Query::$connectionKey protected property The key of the connection object.
Query::$connectionTarget protected property The target of the connection object.
Query::$nextPlaceholder protected property The placeholder counter.
Query::$queryOptions protected property The query options to pass on to the connection object.
Query::$uniqueIdentifier protected property A unique identifier for this query object.
Query::comment public function Adds a comment to the query.
Query::getComments public function Returns a reference to the comments array for the query.
Query::nextPlaceholder public function Gets the next placeholder value for this query object. Overrides PlaceholderInterface::nextPlaceholder
Query::uniqueIdentifier public function Returns a unique identifier for this object. Overrides PlaceholderInterface::uniqueIdentifier
Query::__clone public function Implements the magic __clone function. 1
Query::__sleep public function Implements the magic __sleep function to disconnect from the database.
Query::__wakeup public function Implements the magic __wakeup function to reconnect to the database.
QueryConditionTrait::$condition protected property The condition object for this query.
QueryConditionTrait::alwaysFalse public function
QueryConditionTrait::andConditionGroup public function
QueryConditionTrait::arguments public function 1
QueryConditionTrait::compile public function 1
QueryConditionTrait::compiled public function 1
QueryConditionTrait::condition public function
QueryConditionTrait::conditionGroupFactory public function
QueryConditionTrait::conditions public function
QueryConditionTrait::exists public function
QueryConditionTrait::isNotNull public function
QueryConditionTrait::isNull public function
QueryConditionTrait::notExists public function
QueryConditionTrait::orConditionGroup public function
QueryConditionTrait::where public function