Verified Commit 870e5c1c authored by Dave Long's avatar Dave Long
Browse files

Issue #3410476 by mondrake, smustgrave: Add event for statement execution failure

parent cc2223a9
Loading
Loading
Loading
Loading
Loading
+3 −10
Original line number Diff line number Diff line
@@ -3,8 +3,7 @@
namespace Drupal\Core\Database;

use Composer\Autoload\ClassLoader;
use Drupal\Core\Database\Event\StatementExecutionEndEvent;
use Drupal\Core\Database\Event\StatementExecutionStartEvent;
use Drupal\Core\Database\Event\StatementEvent;
use Drupal\Core\Extension\DatabaseDriverList;
use Drupal\Core\Cache\NullBackend;

@@ -125,10 +124,7 @@ final public static function startLog($logging_key, $key = 'default') {
      // logging object associated with it.
      if (!empty(self::$connections[$key])) {
        foreach (self::$connections[$key] as $connection) {
          $connection->enableEvents([
            StatementExecutionStartEvent::class,
            StatementExecutionEndEvent::class,
          ]);
          $connection->enableEvents(StatementEvent::all());
          $connection->setLogger(self::$logs[$key]);
        }
      }
@@ -469,10 +465,7 @@ final protected static function openConnection($key, $target) {
    // If we have any active logging objects for this connection key, we need
    // to associate them with the connection we just opened.
    if (!empty(self::$logs[$key])) {
      $new_connection->enableEvents([
        StatementExecutionStartEvent::class,
        StatementExecutionEndEvent::class,
      ]);
      $new_connection->enableEvents(StatementEvent::all());
      $new_connection->setLogger(self::$logs[$key]);
    }

+26 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Database\Event;

/**
 * Enumeration of the statement related database events.
 */
enum StatementEvent: string {

  case ExecutionStart = StatementExecutionStartEvent::class;
  case ExecutionEnd = StatementExecutionEndEvent::class;
  case ExecutionFailure = StatementExecutionFailureEvent::class;

  /**
   * Returns an array with all statement related events.
   *
   * @return list<class-string<\Drupal\Core\Database\Event\DatabaseEvent>>
   *   An array with all statement related events.
   */
  public static function all(): array {
    return array_map(fn(self $case) => $case->value, self::cases());
  }

}
+53 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Core\Database\Event;

/**
 * Represents the failure of a statement execution as an event.
 */
class StatementExecutionFailureEvent extends StatementExecutionEndEvent {

  /**
   * Constructor.
   *
   * See 'Customizing database settings' in settings.php for an explanation of
   * the $key and $target connection values.
   *
   * @param int $statementObjectId
   *   The id of the StatementInterface object as returned by spl_object_id().
   * @param string $key
   *   The database connection key.
   * @param string $target
   *   The database connection target.
   * @param string $queryString
   *   The SQL statement string being executed, with placeholders.
   * @param array $args
   *   The placeholders' replacement values.
   * @param array $caller
   *   A normalized debug backtrace entry representing the last non-db method
   *   called.
   * @param float $startTime
   *   The time of the statement execution start.
   * @param string $exceptionClass
   *   The class of the exception that was thrown.
   * @param int|string $exceptionCode
   *   The code of the exception that was thrown.
   * @param string $exceptionMessage
   *   The message of the exception that was thrown.
   */
  public function __construct(
    int $statementObjectId,
    string $key,
    string $target,
    string $queryString,
    array $args,
    array $caller,
    float $startTime,
    public readonly string $exceptionClass,
    public readonly int|string $exceptionCode,
    public readonly string $exceptionMessage,
  ) {
    parent::__construct($statementObjectId, $key, $target, $queryString, $args, $caller, $startTime);
  }

}
+27 −8
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
namespace Drupal\Core\Database;

use Drupal\Core\Database\Event\StatementExecutionEndEvent;
use Drupal\Core\Database\Event\StatementExecutionFailureEvent;
use Drupal\Core\Database\Event\StatementExecutionStartEvent;

/**
@@ -111,15 +112,27 @@ public function execute($args = [], $options = []) {
      $this->connection->dispatchEvent($startEvent);
    }

    // Prepare the query.
    // Prepare and execute the statement.
    try {
      $statement = $this->getStatement($this->queryString, $args);
    if (!$statement) {
      $this->throwPDOException();
    }

      $return = $statement->execute($args);
    if (!$return) {
      $this->throwPDOException();
    }
    catch (\Exception $e) {
      if (isset($startEvent) && $this->connection->isEventEnabled(StatementExecutionFailureEvent::class)) {
        $this->connection->dispatchEvent(new StatementExecutionFailureEvent(
          $startEvent->statementObjectId,
          $startEvent->key,
          $startEvent->target,
          $startEvent->queryString,
          $startEvent->args,
          $startEvent->caller,
          $startEvent->time,
          get_class($e),
          $e->getCode(),
          $e->getMessage(),
        ));
      }
      throw $e;
    }

    // Fetch all the data from the reply, in order to release any lock as soon
@@ -150,8 +163,14 @@ public function execute($args = [], $options = []) {

  /**
   * Throw a PDO Exception based on the last PDO error.
   *
   * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is
   *   no replacement.
   *
   * @see https://www.drupal.org/node/3410663
   */
  protected function throwPDOException(): void {
    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3410663', E_USER_DEPRECATED);
    $error_info = $this->connection->errorInfo();
    // We rebuild a message formatted in the same way as PDO.
    $exception = new \PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
+22 −2
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
namespace Drupal\Core\Database;

use Drupal\Core\Database\Event\StatementExecutionEndEvent;
use Drupal\Core\Database\Event\StatementExecutionFailureEvent;
use Drupal\Core\Database\Event\StatementExecutionStartEvent;

// cSpell:ignore maxlen driverdata INOUT
@@ -108,8 +109,27 @@ public function execute($args = [], $options = []) {
      $this->connection->dispatchEvent($startEvent);
    }

    try {
      $return = $this->clientStatement->execute($args);
      $this->markResultsetIterable($return);
    }
    catch (\Exception $e) {
      if (isset($startEvent) && $this->connection->isEventEnabled(StatementExecutionFailureEvent::class)) {
        $this->connection->dispatchEvent(new StatementExecutionFailureEvent(
          $startEvent->statementObjectId,
          $startEvent->key,
          $startEvent->target,
          $startEvent->queryString,
          $startEvent->args,
          $startEvent->caller,
          $startEvent->time,
          get_class($e),
          $e->getCode(),
          $e->getMessage(),
        ));
      }
      throw $e;
    }

    if (isset($startEvent) && $this->connection->isEventEnabled(StatementExecutionEndEvent::class)) {
      $this->connection->dispatchEvent(new StatementExecutionEndEvent(
Loading