vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php line 980

Open in your IDE?
  1. <?php
  2. namespace Doctrine\DBAL;
  3. use Closure;
  4. use Doctrine\Common\EventManager;
  5. use Doctrine\DBAL\Cache\ArrayStatement;
  6. use Doctrine\DBAL\Cache\CacheException;
  7. use Doctrine\DBAL\Cache\QueryCacheProfile;
  8. use Doctrine\DBAL\Cache\ResultCacheStatement;
  9. use Doctrine\DBAL\Driver\Connection as DriverConnection;
  10. use Doctrine\DBAL\Driver\PDO\Statement as PDODriverStatement;
  11. use Doctrine\DBAL\Driver\PingableConnection;
  12. use Doctrine\DBAL\Driver\ResultStatement;
  13. use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
  14. use Doctrine\DBAL\Exception\ConnectionLost;
  15. use Doctrine\DBAL\Exception\InvalidArgumentException;
  16. use Doctrine\DBAL\Exception\NoKeyValue;
  17. use Doctrine\DBAL\Platforms\AbstractPlatform;
  18. use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
  19. use Doctrine\DBAL\Query\QueryBuilder;
  20. use Doctrine\DBAL\Schema\AbstractSchemaManager;
  21. use Doctrine\DBAL\Types\Type;
  22. use Doctrine\Deprecations\Deprecation;
  23. use PDO;
  24. use Throwable;
  25. use Traversable;
  26. use function array_key_exists;
  27. use function array_shift;
  28. use function assert;
  29. use function func_get_args;
  30. use function implode;
  31. use function is_int;
  32. use function is_string;
  33. use function key;
  34. /**
  35.  * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
  36.  * events, transaction isolation levels, configuration, emulated transaction nesting,
  37.  * lazy connecting and more.
  38.  *
  39.  * @psalm-import-type Params from DriverManager
  40.  * @psalm-consistent-constructor
  41.  */
  42. class Connection implements DriverConnection
  43. {
  44.     /**
  45.      * Constant for transaction isolation level READ UNCOMMITTED.
  46.      *
  47.      * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
  48.      */
  49.     public const TRANSACTION_READ_UNCOMMITTED TransactionIsolationLevel::READ_UNCOMMITTED;
  50.     /**
  51.      * Constant for transaction isolation level READ COMMITTED.
  52.      *
  53.      * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
  54.      */
  55.     public const TRANSACTION_READ_COMMITTED TransactionIsolationLevel::READ_COMMITTED;
  56.     /**
  57.      * Constant for transaction isolation level REPEATABLE READ.
  58.      *
  59.      * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
  60.      */
  61.     public const TRANSACTION_REPEATABLE_READ TransactionIsolationLevel::REPEATABLE_READ;
  62.     /**
  63.      * Constant for transaction isolation level SERIALIZABLE.
  64.      *
  65.      * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
  66.      */
  67.     public const TRANSACTION_SERIALIZABLE TransactionIsolationLevel::SERIALIZABLE;
  68.     /**
  69.      * Represents an array of ints to be expanded by Doctrine SQL parsing.
  70.      */
  71.     public const PARAM_INT_ARRAY ParameterType::INTEGER self::ARRAY_PARAM_OFFSET;
  72.     /**
  73.      * Represents an array of strings to be expanded by Doctrine SQL parsing.
  74.      */
  75.     public const PARAM_STR_ARRAY ParameterType::STRING self::ARRAY_PARAM_OFFSET;
  76.     /**
  77.      * Offset by which PARAM_* constants are detected as arrays of the param type.
  78.      */
  79.     public const ARRAY_PARAM_OFFSET 100;
  80.     /**
  81.      * The wrapped driver connection.
  82.      *
  83.      * @var \Doctrine\DBAL\Driver\Connection|null
  84.      */
  85.     protected $_conn;
  86.     /** @var Configuration */
  87.     protected $_config;
  88.     /** @var EventManager */
  89.     protected $_eventManager;
  90.     /** @var ExpressionBuilder */
  91.     protected $_expr;
  92.     /**
  93.      * The current auto-commit mode of this connection.
  94.      *
  95.      * @var bool
  96.      */
  97.     private $autoCommit true;
  98.     /**
  99.      * The transaction nesting level.
  100.      *
  101.      * @var int
  102.      */
  103.     private $transactionNestingLevel 0;
  104.     /**
  105.      * The currently active transaction isolation level or NULL before it has been determined.
  106.      *
  107.      * @var int|null
  108.      */
  109.     private $transactionIsolationLevel;
  110.     /**
  111.      * If nested transactions should use savepoints.
  112.      *
  113.      * @var bool
  114.      */
  115.     private $nestTransactionsWithSavepoints false;
  116.     /**
  117.      * The parameters used during creation of the Connection instance.
  118.      *
  119.      * @var array<string,mixed>
  120.      * @phpstan-var array<string,mixed>
  121.      * @psalm-var Params
  122.      */
  123.     private $params;
  124.     /**
  125.      * The database platform object used by the connection or NULL before it's initialized.
  126.      *
  127.      * @var AbstractPlatform|null
  128.      */
  129.     private $platform;
  130.     /**
  131.      * The schema manager.
  132.      *
  133.      * @var AbstractSchemaManager|null
  134.      */
  135.     protected $_schemaManager;
  136.     /**
  137.      * The used DBAL driver.
  138.      *
  139.      * @var Driver
  140.      */
  141.     protected $_driver;
  142.     /**
  143.      * Flag that indicates whether the current transaction is marked for rollback only.
  144.      *
  145.      * @var bool
  146.      */
  147.     private $isRollbackOnly false;
  148.     /** @var int */
  149.     protected $defaultFetchMode FetchMode::ASSOCIATIVE;
  150.     /**
  151.      * Initializes a new instance of the Connection class.
  152.      *
  153.      * @internal The connection can be only instantiated by the driver manager.
  154.      *
  155.      * @param array<string,mixed> $params       The connection parameters.
  156.      * @param Driver              $driver       The driver to use.
  157.      * @param Configuration|null  $config       The configuration, optional.
  158.      * @param EventManager|null   $eventManager The event manager, optional.
  159.      * @psalm-param Params $params
  160.      * @phpstan-param array<string,mixed> $params
  161.      *
  162.      * @throws Exception
  163.      */
  164.     public function __construct(
  165.         array $params,
  166.         Driver $driver,
  167.         ?Configuration $config null,
  168.         ?EventManager $eventManager null
  169.     ) {
  170.         $this->_driver $driver;
  171.         $this->params  $params;
  172.         if (isset($params['pdo'])) {
  173.             Deprecation::trigger(
  174.                 'doctrine/dbal',
  175.                 'https://github.com/doctrine/dbal/pull/3554',
  176.                 'Passing a user provided PDO instance directly to Doctrine is deprecated.'
  177.             );
  178.             if (! $params['pdo'] instanceof PDO) {
  179.                 throw Exception::invalidPdoInstance();
  180.             }
  181.             $this->_conn $params['pdo'];
  182.             $this->_conn->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDODriverStatement::class, []]);
  183.             unset($this->params['pdo']);
  184.         }
  185.         if (isset($params['platform'])) {
  186.             if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
  187.                 throw Exception::invalidPlatformType($params['platform']);
  188.             }
  189.             $this->platform $params['platform'];
  190.         }
  191.         // Create default config and event manager if none given
  192.         if (! $config) {
  193.             $config = new Configuration();
  194.         }
  195.         if (! $eventManager) {
  196.             $eventManager = new EventManager();
  197.         }
  198.         $this->_config       $config;
  199.         $this->_eventManager $eventManager;
  200.         $this->_expr = new Query\Expression\ExpressionBuilder($this);
  201.         $this->autoCommit $config->getAutoCommit();
  202.     }
  203.     /**
  204.      * Gets the parameters used during instantiation.
  205.      *
  206.      * @internal
  207.      *
  208.      * @return array<string,mixed>
  209.      * @psalm-return Params
  210.      * @phpstan-return array<string,mixed>
  211.      */
  212.     public function getParams()
  213.     {
  214.         return $this->params;
  215.     }
  216.     /**
  217.      * Gets the name of the database this Connection is connected to.
  218.      *
  219.      * @return string
  220.      */
  221.     public function getDatabase()
  222.     {
  223.         return $this->_driver->getDatabase($this);
  224.     }
  225.     /**
  226.      * Gets the hostname of the currently connected database.
  227.      *
  228.      * @deprecated
  229.      *
  230.      * @return string|null
  231.      */
  232.     public function getHost()
  233.     {
  234.         Deprecation::trigger(
  235.             'doctrine/dbal',
  236.             'https://github.com/doctrine/dbal/issues/3580',
  237.             'Connection::getHost() is deprecated, get the database server host from application config ' .
  238.             'or as a last resort from internal Connection::getParams() API.'
  239.         );
  240.         return $this->params['host'] ?? null;
  241.     }
  242.     /**
  243.      * Gets the port of the currently connected database.
  244.      *
  245.      * @deprecated
  246.      *
  247.      * @return mixed
  248.      */
  249.     public function getPort()
  250.     {
  251.         Deprecation::trigger(
  252.             'doctrine/dbal',
  253.             'https://github.com/doctrine/dbal/issues/3580',
  254.             'Connection::getPort() is deprecated, get the database server port from application config ' .
  255.             'or as a last resort from internal Connection::getParams() API.'
  256.         );
  257.         return $this->params['port'] ?? null;
  258.     }
  259.     /**
  260.      * Gets the username used by this connection.
  261.      *
  262.      * @deprecated
  263.      *
  264.      * @return string|null
  265.      */
  266.     public function getUsername()
  267.     {
  268.         Deprecation::trigger(
  269.             'doctrine/dbal',
  270.             'https://github.com/doctrine/dbal/issues/3580',
  271.             'Connection::getUsername() is deprecated, get the username from application config ' .
  272.             'or as a last resort from internal Connection::getParams() API.'
  273.         );
  274.         return $this->params['user'] ?? null;
  275.     }
  276.     /**
  277.      * Gets the password used by this connection.
  278.      *
  279.      * @deprecated
  280.      *
  281.      * @return string|null
  282.      */
  283.     public function getPassword()
  284.     {
  285.         Deprecation::trigger(
  286.             'doctrine/dbal',
  287.             'https://github.com/doctrine/dbal/issues/3580',
  288.             'Connection::getPassword() is deprecated, get the password from application config ' .
  289.             'or as a last resort from internal Connection::getParams() API.'
  290.         );
  291.         return $this->params['password'] ?? null;
  292.     }
  293.     /**
  294.      * Gets the DBAL driver instance.
  295.      *
  296.      * @return Driver
  297.      */
  298.     public function getDriver()
  299.     {
  300.         return $this->_driver;
  301.     }
  302.     /**
  303.      * Gets the Configuration used by the Connection.
  304.      *
  305.      * @return Configuration
  306.      */
  307.     public function getConfiguration()
  308.     {
  309.         return $this->_config;
  310.     }
  311.     /**
  312.      * Gets the EventManager used by the Connection.
  313.      *
  314.      * @return EventManager
  315.      */
  316.     public function getEventManager()
  317.     {
  318.         return $this->_eventManager;
  319.     }
  320.     /**
  321.      * Gets the DatabasePlatform for the connection.
  322.      *
  323.      * @return AbstractPlatform
  324.      *
  325.      * @throws Exception
  326.      */
  327.     public function getDatabasePlatform()
  328.     {
  329.         if ($this->platform === null) {
  330.             $this->platform $this->detectDatabasePlatform();
  331.             $this->platform->setEventManager($this->_eventManager);
  332.         }
  333.         return $this->platform;
  334.     }
  335.     /**
  336.      * Gets the ExpressionBuilder for the connection.
  337.      *
  338.      * @return ExpressionBuilder
  339.      */
  340.     public function getExpressionBuilder()
  341.     {
  342.         return $this->_expr;
  343.     }
  344.     /**
  345.      * Establishes the connection with the database.
  346.      *
  347.      * @return bool TRUE if the connection was successfully established, FALSE if
  348.      *              the connection is already open.
  349.      */
  350.     public function connect()
  351.     {
  352.         if ($this->_conn !== null) {
  353.             return false;
  354.         }
  355.         $driverOptions $this->params['driverOptions'] ?? [];
  356.         $user          $this->params['user'] ?? null;
  357.         $password      $this->params['password'] ?? null;
  358.         $this->_conn $this->_driver->connect($this->params$user$password$driverOptions);
  359.         $this->transactionNestingLevel 0;
  360.         if ($this->autoCommit === false) {
  361.             $this->beginTransaction();
  362.         }
  363.         if ($this->_eventManager->hasListeners(Events::postConnect)) {
  364.             $eventArgs = new Event\ConnectionEventArgs($this);
  365.             $this->_eventManager->dispatchEvent(Events::postConnect$eventArgs);
  366.         }
  367.         return true;
  368.     }
  369.     /**
  370.      * Detects and sets the database platform.
  371.      *
  372.      * Evaluates custom platform class and version in order to set the correct platform.
  373.      *
  374.      * @throws Exception If an invalid platform was specified for this connection.
  375.      */
  376.     private function detectDatabasePlatform(): AbstractPlatform
  377.     {
  378.         $version $this->getDatabasePlatformVersion();
  379.         if ($version !== null) {
  380.             assert($this->_driver instanceof VersionAwarePlatformDriver);
  381.             return $this->_driver->createDatabasePlatformForVersion($version);
  382.         }
  383.         return $this->_driver->getDatabasePlatform();
  384.     }
  385.     /**
  386.      * Returns the version of the related platform if applicable.
  387.      *
  388.      * Returns null if either the driver is not capable to create version
  389.      * specific platform instances, no explicit server version was specified
  390.      * or the underlying driver connection cannot determine the platform
  391.      * version without having to query it (performance reasons).
  392.      *
  393.      * @return string|null
  394.      *
  395.      * @throws Throwable
  396.      */
  397.     private function getDatabasePlatformVersion()
  398.     {
  399.         // Driver does not support version specific platforms.
  400.         if (! $this->_driver instanceof VersionAwarePlatformDriver) {
  401.             return null;
  402.         }
  403.         // Explicit platform version requested (supersedes auto-detection).
  404.         if (isset($this->params['serverVersion'])) {
  405.             return $this->params['serverVersion'];
  406.         }
  407.         // If not connected, we need to connect now to determine the platform version.
  408.         if ($this->_conn === null) {
  409.             try {
  410.                 $this->connect();
  411.             } catch (Throwable $originalException) {
  412.                 if (empty($this->params['dbname'])) {
  413.                     throw $originalException;
  414.                 }
  415.                 // The database to connect to might not yet exist.
  416.                 // Retry detection without database name connection parameter.
  417.                 $params $this->params;
  418.                 unset($this->params['dbname']);
  419.                 try {
  420.                     $this->connect();
  421.                 } catch (Throwable $fallbackException) {
  422.                     // Either the platform does not support database-less connections
  423.                     // or something else went wrong.
  424.                     throw $originalException;
  425.                 } finally {
  426.                     $this->params $params;
  427.                 }
  428.                 $serverVersion $this->getServerVersion();
  429.                 // Close "temporary" connection to allow connecting to the real database again.
  430.                 $this->close();
  431.                 return $serverVersion;
  432.             }
  433.         }
  434.         return $this->getServerVersion();
  435.     }
  436.     /**
  437.      * Returns the database server version if the underlying driver supports it.
  438.      *
  439.      * @return string|null
  440.      */
  441.     private function getServerVersion()
  442.     {
  443.         $connection $this->getWrappedConnection();
  444.         // Automatic platform version detection.
  445.         if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
  446.             return $connection->getServerVersion();
  447.         }
  448.         // Unable to detect platform version.
  449.         return null;
  450.     }
  451.     /**
  452.      * Returns the current auto-commit mode for this connection.
  453.      *
  454.      * @see    setAutoCommit
  455.      *
  456.      * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
  457.      */
  458.     public function isAutoCommit()
  459.     {
  460.         return $this->autoCommit === true;
  461.     }
  462.     /**
  463.      * Sets auto-commit mode for this connection.
  464.      *
  465.      * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
  466.      * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
  467.      * the method commit or the method rollback. By default, new connections are in auto-commit mode.
  468.      *
  469.      * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
  470.      * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
  471.      *
  472.      * @see   isAutoCommit
  473.      *
  474.      * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
  475.      *
  476.      * @return void
  477.      */
  478.     public function setAutoCommit($autoCommit)
  479.     {
  480.         $autoCommit = (bool) $autoCommit;
  481.         // Mode not changed, no-op.
  482.         if ($autoCommit === $this->autoCommit) {
  483.             return;
  484.         }
  485.         $this->autoCommit $autoCommit;
  486.         // Commit all currently active transactions if any when switching auto-commit mode.
  487.         if ($this->_conn === null || $this->transactionNestingLevel === 0) {
  488.             return;
  489.         }
  490.         $this->commitAll();
  491.     }
  492.     /**
  493.      * Sets the fetch mode.
  494.      *
  495.      * @deprecated Use one of the fetch- or iterate-related methods.
  496.      *
  497.      * @param int $fetchMode
  498.      *
  499.      * @return void
  500.      */
  501.     public function setFetchMode($fetchMode)
  502.     {
  503.         Deprecation::trigger(
  504.             'doctrine/dbal',
  505.             'https://github.com/doctrine/dbal/pull/4019',
  506.             'Default Fetch Mode configuration is deprecated, use explicit Connection::fetch*() APIs instead.'
  507.         );
  508.         $this->defaultFetchMode $fetchMode;
  509.     }
  510.     /**
  511.      * Prepares and executes an SQL query and returns the first row of the result
  512.      * as an associative array.
  513.      *
  514.      * @deprecated Use fetchAssociative()
  515.      *
  516.      * @param string                                                               $sql    SQL query
  517.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  518.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  519.      *
  520.      * @return array<string, mixed>|false False is returned if no rows are found.
  521.      *
  522.      * @throws Exception
  523.      */
  524.     public function fetchAssoc($sql, array $params = [], array $types = [])
  525.     {
  526.         Deprecation::trigger(
  527.             'doctrine/dbal',
  528.             'https://github.com/doctrine/dbal/pull/4019',
  529.             'Connection::fetchAssoc() is deprecated, use Connection::fetchAssociative() API instead.'
  530.         );
  531.         return $this->executeQuery($sql$params$types)->fetch(FetchMode::ASSOCIATIVE);
  532.     }
  533.     /**
  534.      * Prepares and executes an SQL query and returns the first row of the result
  535.      * as a numerically indexed array.
  536.      *
  537.      * @deprecated Use fetchNumeric()
  538.      *
  539.      * @param string                                                               $sql    SQL query
  540.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  541.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  542.      *
  543.      * @return array<int, mixed>|false False is returned if no rows are found.
  544.      */
  545.     public function fetchArray($sql, array $params = [], array $types = [])
  546.     {
  547.         Deprecation::trigger(
  548.             'doctrine/dbal',
  549.             'https://github.com/doctrine/dbal/pull/4019',
  550.             'Connection::fetchArray() is deprecated, use Connection::fetchNumeric() API instead.'
  551.         );
  552.         return $this->executeQuery($sql$params$types)->fetch(FetchMode::NUMERIC);
  553.     }
  554.     /**
  555.      * Prepares and executes an SQL query and returns the value of a single column
  556.      * of the first row of the result.
  557.      *
  558.      * @deprecated Use fetchOne() instead.
  559.      *
  560.      * @param string                                                               $sql    SQL query
  561.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  562.      * @param int                                                                  $column 0-indexed column number
  563.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  564.      *
  565.      * @return mixed|false False is returned if no rows are found.
  566.      *
  567.      * @throws Exception
  568.      */
  569.     public function fetchColumn($sql, array $params = [], $column 0, array $types = [])
  570.     {
  571.         Deprecation::trigger(
  572.             'doctrine/dbal',
  573.             'https://github.com/doctrine/dbal/pull/4019',
  574.             'Connection::fetchColumn() is deprecated, use Connection::fetchOne() API instead.'
  575.         );
  576.         return $this->executeQuery($sql$params$types)->fetchColumn($column);
  577.     }
  578.     /**
  579.      * Prepares and executes an SQL query and returns the first row of the result
  580.      * as an associative array.
  581.      *
  582.      * @param string                                                               $query  SQL query
  583.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  584.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  585.      *
  586.      * @return array<string, mixed>|false False is returned if no rows are found.
  587.      *
  588.      * @throws Exception
  589.      */
  590.     public function fetchAssociative(string $query, array $params = [], array $types = [])
  591.     {
  592.         try {
  593.             $stmt $this->ensureForwardCompatibilityStatement(
  594.                 $this->executeQuery($query$params$types)
  595.             );
  596.             return $stmt->fetchAssociative();
  597.         } catch (Throwable $e) {
  598.             $this->handleExceptionDuringQuery($e$query$params$types);
  599.         }
  600.     }
  601.     /**
  602.      * Prepares and executes an SQL query and returns the first row of the result
  603.      * as a numerically indexed array.
  604.      *
  605.      * @param string                                                               $query  SQL query
  606.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  607.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  608.      *
  609.      * @return array<int, mixed>|false False is returned if no rows are found.
  610.      *
  611.      * @throws Exception
  612.      */
  613.     public function fetchNumeric(string $query, array $params = [], array $types = [])
  614.     {
  615.         try {
  616.             $stmt $this->ensureForwardCompatibilityStatement(
  617.                 $this->executeQuery($query$params$types)
  618.             );
  619.             return $stmt->fetchNumeric();
  620.         } catch (Throwable $e) {
  621.             $this->handleExceptionDuringQuery($e$query$params$types);
  622.         }
  623.     }
  624.     /**
  625.      * Prepares and executes an SQL query and returns the value of a single column
  626.      * of the first row of the result.
  627.      *
  628.      * @param string                                                               $query  SQL query
  629.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  630.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  631.      *
  632.      * @return mixed|false False is returned if no rows are found.
  633.      *
  634.      * @throws Exception
  635.      */
  636.     public function fetchOne(string $query, array $params = [], array $types = [])
  637.     {
  638.         try {
  639.             $stmt $this->ensureForwardCompatibilityStatement(
  640.                 $this->executeQuery($query$params$types)
  641.             );
  642.             return $stmt->fetchOne();
  643.         } catch (Throwable $e) {
  644.             $this->handleExceptionDuringQuery($e$query$params$types);
  645.         }
  646.     }
  647.     /**
  648.      * Whether an actual connection to the database is established.
  649.      *
  650.      * @return bool
  651.      */
  652.     public function isConnected()
  653.     {
  654.         return $this->_conn !== null;
  655.     }
  656.     /**
  657.      * Checks whether a transaction is currently active.
  658.      *
  659.      * @return bool TRUE if a transaction is currently active, FALSE otherwise.
  660.      */
  661.     public function isTransactionActive()
  662.     {
  663.         return $this->transactionNestingLevel 0;
  664.     }
  665.     /**
  666.      * Adds condition based on the criteria to the query components
  667.      *
  668.      * @param array<string,mixed> $criteria   Map of key columns to their values
  669.      * @param string[]            $columns    Column names
  670.      * @param mixed[]             $values     Column values
  671.      * @param string[]            $conditions Key conditions
  672.      *
  673.      * @throws Exception
  674.      */
  675.     private function addCriteriaCondition(
  676.         array $criteria,
  677.         array &$columns,
  678.         array &$values,
  679.         array &$conditions
  680.     ): void {
  681.         $platform $this->getDatabasePlatform();
  682.         foreach ($criteria as $columnName => $value) {
  683.             if ($value === null) {
  684.                 $conditions[] = $platform->getIsNullExpression($columnName);
  685.                 continue;
  686.             }
  687.             $columns[]    = $columnName;
  688.             $values[]     = $value;
  689.             $conditions[] = $columnName ' = ?';
  690.         }
  691.     }
  692.     /**
  693.      * Executes an SQL DELETE statement on a table.
  694.      *
  695.      * Table expression and columns are not escaped and are not safe for user-input.
  696.      *
  697.      * @param string                                                               $table    Table name
  698.      * @param array<string, mixed>                                                 $criteria Deletion criteria
  699.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types    Parameter types
  700.      *
  701.      * @return int|string The number of affected rows.
  702.      *
  703.      * @throws Exception
  704.      */
  705.     public function delete($table, array $criteria, array $types = [])
  706.     {
  707.         if (empty($criteria)) {
  708.             throw InvalidArgumentException::fromEmptyCriteria();
  709.         }
  710.         $columns $values $conditions = [];
  711.         $this->addCriteriaCondition($criteria$columns$values$conditions);
  712.         return $this->executeStatement(
  713.             'DELETE FROM ' $table ' WHERE ' implode(' AND '$conditions),
  714.             $values,
  715.             is_string(key($types)) ? $this->extractTypeValues($columns$types) : $types
  716.         );
  717.     }
  718.     /**
  719.      * Closes the connection.
  720.      *
  721.      * @return void
  722.      */
  723.     public function close()
  724.     {
  725.         $this->_conn null;
  726.     }
  727.     /**
  728.      * Sets the transaction isolation level.
  729.      *
  730.      * @param int $level The level to set.
  731.      *
  732.      * @return int|string
  733.      */
  734.     public function setTransactionIsolation($level)
  735.     {
  736.         $this->transactionIsolationLevel $level;
  737.         return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
  738.     }
  739.     /**
  740.      * Gets the currently active transaction isolation level.
  741.      *
  742.      * @return int The current transaction isolation level.
  743.      */
  744.     public function getTransactionIsolation()
  745.     {
  746.         if ($this->transactionIsolationLevel === null) {
  747.             $this->transactionIsolationLevel $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
  748.         }
  749.         return $this->transactionIsolationLevel;
  750.     }
  751.     /**
  752.      * Executes an SQL UPDATE statement on a table.
  753.      *
  754.      * Table expression and columns are not escaped and are not safe for user-input.
  755.      *
  756.      * @param string                                                               $table    Table name
  757.      * @param array<string, mixed>                                                 $data     Column-value pairs
  758.      * @param array<string, mixed>                                                 $criteria Update criteria
  759.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types    Parameter types
  760.      *
  761.      * @return int|string The number of affected rows.
  762.      *
  763.      * @throws Exception
  764.      */
  765.     public function update($table, array $data, array $criteria, array $types = [])
  766.     {
  767.         $columns $values $conditions $set = [];
  768.         foreach ($data as $columnName => $value) {
  769.             $columns[] = $columnName;
  770.             $values[]  = $value;
  771.             $set[]     = $columnName ' = ?';
  772.         }
  773.         $this->addCriteriaCondition($criteria$columns$values$conditions);
  774.         if (is_string(key($types))) {
  775.             $types $this->extractTypeValues($columns$types);
  776.         }
  777.         $sql 'UPDATE ' $table ' SET ' implode(', '$set)
  778.                 . ' WHERE ' implode(' AND '$conditions);
  779.         return $this->executeStatement($sql$values$types);
  780.     }
  781.     /**
  782.      * Inserts a table row with specified data.
  783.      *
  784.      * Table expression and columns are not escaped and are not safe for user-input.
  785.      *
  786.      * @param string                                                               $table Table name
  787.      * @param array<string, mixed>                                                 $data  Column-value pairs
  788.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types
  789.      *
  790.      * @return int|string The number of affected rows.
  791.      *
  792.      * @throws Exception
  793.      */
  794.     public function insert($table, array $data, array $types = [])
  795.     {
  796.         if (empty($data)) {
  797.             return $this->executeStatement('INSERT INTO ' $table ' () VALUES ()');
  798.         }
  799.         $columns = [];
  800.         $values  = [];
  801.         $set     = [];
  802.         foreach ($data as $columnName => $value) {
  803.             $columns[] = $columnName;
  804.             $values[]  = $value;
  805.             $set[]     = '?';
  806.         }
  807.         return $this->executeStatement(
  808.             'INSERT INTO ' $table ' (' implode(', '$columns) . ')' .
  809.             ' VALUES (' implode(', '$set) . ')',
  810.             $values,
  811.             is_string(key($types)) ? $this->extractTypeValues($columns$types) : $types
  812.         );
  813.     }
  814.     /**
  815.      * Extract ordered type list from an ordered column list and type map.
  816.      *
  817.      * @param array<int, string>                                                   $columnList
  818.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
  819.      *
  820.      * @return array<int, int|string|Type|null>|array<string, int|string|Type|null>
  821.      */
  822.     private function extractTypeValues(array $columnList, array $types)
  823.     {
  824.         $typeValues = [];
  825.         foreach ($columnList as $columnIndex => $columnName) {
  826.             $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
  827.         }
  828.         return $typeValues;
  829.     }
  830.     /**
  831.      * Quotes a string so it can be safely used as a table or column name, even if
  832.      * it is a reserved name.
  833.      *
  834.      * Delimiting style depends on the underlying database platform that is being used.
  835.      *
  836.      * NOTE: Just because you CAN use quoted identifiers does not mean
  837.      * you SHOULD use them. In general, they end up causing way more
  838.      * problems than they solve.
  839.      *
  840.      * @param string $str The name to be quoted.
  841.      *
  842.      * @return string The quoted name.
  843.      */
  844.     public function quoteIdentifier($str)
  845.     {
  846.         return $this->getDatabasePlatform()->quoteIdentifier($str);
  847.     }
  848.     /**
  849.      * {@inheritDoc}
  850.      *
  851.      * @param mixed                $value
  852.      * @param int|string|Type|null $type
  853.      */
  854.     public function quote($value$type ParameterType::STRING)
  855.     {
  856.         $connection $this->getWrappedConnection();
  857.         [$value$bindingType] = $this->getBindingInfo($value$type);
  858.         return $connection->quote($value$bindingType);
  859.     }
  860.     /**
  861.      * Prepares and executes an SQL query and returns the result as an associative array.
  862.      *
  863.      * @deprecated Use fetchAllAssociative()
  864.      *
  865.      * @param string         $sql    The SQL query.
  866.      * @param mixed[]        $params The query parameters.
  867.      * @param int[]|string[] $types  The query parameter types.
  868.      *
  869.      * @return mixed[]
  870.      */
  871.     public function fetchAll($sql, array $params = [], $types = [])
  872.     {
  873.         return $this->executeQuery($sql$params$types)->fetchAll();
  874.     }
  875.     /**
  876.      * Prepares and executes an SQL query and returns the result as an array of numeric arrays.
  877.      *
  878.      * @param string                                                               $query  SQL query
  879.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  880.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  881.      *
  882.      * @return array<int,array<int,mixed>>
  883.      *
  884.      * @throws Exception
  885.      */
  886.     public function fetchAllNumeric(string $query, array $params = [], array $types = []): array
  887.     {
  888.         try {
  889.             $stmt $this->ensureForwardCompatibilityStatement(
  890.                 $this->executeQuery($query$params$types)
  891.             );
  892.             return $stmt->fetchAllNumeric();
  893.         } catch (Throwable $e) {
  894.             $this->handleExceptionDuringQuery($e$query$params$types);
  895.         }
  896.     }
  897.     /**
  898.      * Prepares and executes an SQL query and returns the result as an array of associative arrays.
  899.      *
  900.      * @param string                                                               $query  SQL query
  901.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  902.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  903.      *
  904.      * @return array<int,array<string,mixed>>
  905.      *
  906.      * @throws Exception
  907.      */
  908.     public function fetchAllAssociative(string $query, array $params = [], array $types = []): array
  909.     {
  910.         try {
  911.             $stmt $this->ensureForwardCompatibilityStatement(
  912.                 $this->executeQuery($query$params$types)
  913.             );
  914.             return $stmt->fetchAllAssociative();
  915.         } catch (Throwable $e) {
  916.             $this->handleExceptionDuringQuery($e$query$params$types);
  917.         }
  918.     }
  919.     /**
  920.      * Prepares and executes an SQL query and returns the result as an associative array with the keys
  921.      * mapped to the first column and the values mapped to the second column.
  922.      *
  923.      * @param string                                           $query  SQL query
  924.      * @param array<int, mixed>|array<string, mixed>           $params Query parameters
  925.      * @param array<int, int|string>|array<string, int|string> $types  Parameter types
  926.      *
  927.      * @return array<mixed,mixed>
  928.      *
  929.      * @throws Exception
  930.      */
  931.     public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array
  932.     {
  933.         $stmt $this->executeQuery($query$params$types);
  934.         $this->ensureHasKeyValue($stmt);
  935.         $data = [];
  936.         foreach ($stmt->fetchAll(FetchMode::NUMERIC) as [$key$value]) {
  937.             $data[$key] = $value;
  938.         }
  939.         return $data;
  940.     }
  941.     /**
  942.      * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped
  943.      * to the first column and the values being an associative array representing the rest of the columns
  944.      * and their values.
  945.      *
  946.      * @param string                                           $query  SQL query
  947.      * @param array<int, mixed>|array<string, mixed>           $params Query parameters
  948.      * @param array<int, int|string>|array<string, int|string> $types  Parameter types
  949.      *
  950.      * @return array<mixed,array<string,mixed>>
  951.      *
  952.      * @throws Exception
  953.      */
  954.     public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array
  955.     {
  956.         $stmt $this->executeQuery($query$params$types);
  957.         $data = [];
  958.         foreach ($stmt->fetchAll(FetchMode::ASSOCIATIVE) as $row) {
  959.             $data[array_shift($row)] = $row;
  960.         }
  961.         return $data;
  962.     }
  963.     /**
  964.      * Prepares and executes an SQL query and returns the result as an array of the first column values.
  965.      *
  966.      * @param string                                                               $query  SQL query
  967.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  968.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  969.      *
  970.      * @return array<int,mixed>
  971.      *
  972.      * @throws Exception
  973.      */
  974.     public function fetchFirstColumn(string $query, array $params = [], array $types = []): array
  975.     {
  976.         try {
  977.             $stmt $this->ensureForwardCompatibilityStatement(
  978.                 $this->executeQuery($query$params$types)
  979.             );
  980.             return $stmt->fetchFirstColumn();
  981.         } catch (Throwable $e) {
  982.             $this->handleExceptionDuringQuery($e$query$params$types);
  983.         }
  984.     }
  985.     /**
  986.      * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays.
  987.      *
  988.      * @param string                                                               $query  SQL query
  989.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  990.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  991.      *
  992.      * @return Traversable<int,array<int,mixed>>
  993.      *
  994.      * @throws Exception
  995.      */
  996.     public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable
  997.     {
  998.         try {
  999.             $stmt $this->ensureForwardCompatibilityStatement(
  1000.                 $this->executeQuery($query$params$types)
  1001.             );
  1002.             yield from $stmt->iterateNumeric();
  1003.         } catch (Throwable $e) {
  1004.             $this->handleExceptionDuringQuery($e$query$params$types);
  1005.         }
  1006.     }
  1007.     /**
  1008.      * Prepares and executes an SQL query and returns the result as an iterator over rows represented
  1009.      * as associative arrays.
  1010.      *
  1011.      * @param string                                                               $query  SQL query
  1012.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  1013.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1014.      *
  1015.      * @return Traversable<int,array<string,mixed>>
  1016.      *
  1017.      * @throws Exception
  1018.      */
  1019.     public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable
  1020.     {
  1021.         try {
  1022.             $stmt $this->ensureForwardCompatibilityStatement(
  1023.                 $this->executeQuery($query$params$types)
  1024.             );
  1025.             yield from $stmt->iterateAssociative();
  1026.         } catch (Throwable $e) {
  1027.             $this->handleExceptionDuringQuery($e$query$params$types);
  1028.         }
  1029.     }
  1030.     /**
  1031.      * Prepares and executes an SQL query and returns the result as an iterator with the keys
  1032.      * mapped to the first column and the values mapped to the second column.
  1033.      *
  1034.      * @param string                                           $query  SQL query
  1035.      * @param array<int, mixed>|array<string, mixed>           $params Query parameters
  1036.      * @param array<int, int|string>|array<string, int|string> $types  Parameter types
  1037.      *
  1038.      * @return Traversable<mixed,mixed>
  1039.      *
  1040.      * @throws Exception
  1041.      */
  1042.     public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable
  1043.     {
  1044.         $stmt $this->executeQuery($query$params$types);
  1045.         $this->ensureHasKeyValue($stmt);
  1046.         while (($row $stmt->fetch(FetchMode::NUMERIC)) !== false) {
  1047.             yield $row[0] => $row[1];
  1048.         }
  1049.     }
  1050.     /**
  1051.      * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped
  1052.      * to the first column and the values being an associative array representing the rest of the columns
  1053.      * and their values.
  1054.      *
  1055.      * @param string                                           $query  SQL query
  1056.      * @param array<int, mixed>|array<string, mixed>           $params Query parameters
  1057.      * @param array<int, int|string>|array<string, int|string> $types  Parameter types
  1058.      *
  1059.      * @return Traversable<mixed,array<string,mixed>>
  1060.      *
  1061.      * @throws Exception
  1062.      */
  1063.     public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable
  1064.     {
  1065.         $stmt $this->executeQuery($query$params$types);
  1066.         while (($row $stmt->fetch(FetchMode::ASSOCIATIVE)) !== false) {
  1067.             yield array_shift($row) => $row;
  1068.         }
  1069.     }
  1070.     /**
  1071.      * Prepares and executes an SQL query and returns the result as an iterator over the first column values.
  1072.      *
  1073.      * @param string                                                               $query  SQL query
  1074.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  1075.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1076.      *
  1077.      * @return Traversable<int,mixed>
  1078.      *
  1079.      * @throws Exception
  1080.      */
  1081.     public function iterateColumn(string $query, array $params = [], array $types = []): Traversable
  1082.     {
  1083.         try {
  1084.             $stmt $this->ensureForwardCompatibilityStatement(
  1085.                 $this->executeQuery($query$params$types)
  1086.             );
  1087.             yield from $stmt->iterateColumn();
  1088.         } catch (Throwable $e) {
  1089.             $this->handleExceptionDuringQuery($e$query$params$types);
  1090.         }
  1091.     }
  1092.     /**
  1093.      * Prepares an SQL statement.
  1094.      *
  1095.      * @param string $sql The SQL statement to prepare.
  1096.      *
  1097.      * @return Statement The prepared statement.
  1098.      *
  1099.      * @throws Exception
  1100.      */
  1101.     public function prepare($sql)
  1102.     {
  1103.         try {
  1104.             $stmt = new Statement($sql$this);
  1105.         } catch (Throwable $e) {
  1106.             $this->handleExceptionDuringQuery($e$sql);
  1107.         }
  1108.         $stmt->setFetchMode($this->defaultFetchMode);
  1109.         return $stmt;
  1110.     }
  1111.     /**
  1112.      * Executes an, optionally parametrized, SQL query.
  1113.      *
  1114.      * If the query is parametrized, a prepared statement is used.
  1115.      * If an SQLLogger is configured, the execution is logged.
  1116.      *
  1117.      * @param string                                                               $sql    SQL query
  1118.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  1119.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1120.      *
  1121.      * @return ForwardCompatibility\DriverStatement|ForwardCompatibility\DriverResultStatement
  1122.      *
  1123.      * The executed statement or the cached result statement if a query cache profile is used
  1124.      *
  1125.      * @throws Exception
  1126.      */
  1127.     public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp null)
  1128.     {
  1129.         if ($qcp !== null) {
  1130.             return $this->executeCacheQuery($sql$params$types$qcp);
  1131.         }
  1132.         $connection $this->getWrappedConnection();
  1133.         $logger $this->_config->getSQLLogger();
  1134.         if ($logger) {
  1135.             $logger->startQuery($sql$params$types);
  1136.         }
  1137.         try {
  1138.             if ($params) {
  1139.                 [$sql$params$types] = SQLParserUtils::expandListParameters($sql$params$types);
  1140.                 $stmt $connection->prepare($sql);
  1141.                 if ($types) {
  1142.                     $this->_bindTypedValues($stmt$params$types);
  1143.                     $stmt->execute();
  1144.                 } else {
  1145.                     $stmt->execute($params);
  1146.                 }
  1147.             } else {
  1148.                 $stmt $connection->query($sql);
  1149.             }
  1150.         } catch (Throwable $e) {
  1151.             $this->handleExceptionDuringQuery(
  1152.                 $e,
  1153.                 $sql,
  1154.                 $params,
  1155.                 $types
  1156.             );
  1157.         }
  1158.         $stmt->setFetchMode($this->defaultFetchMode);
  1159.         if ($logger) {
  1160.             $logger->stopQuery();
  1161.         }
  1162.         return $this->ensureForwardCompatibilityStatement($stmt);
  1163.     }
  1164.     /**
  1165.      * Executes a caching query.
  1166.      *
  1167.      * @param string                                                               $sql    SQL query
  1168.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  1169.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1170.      *
  1171.      * @return ForwardCompatibility\DriverResultStatement
  1172.      *
  1173.      * @throws CacheException
  1174.      */
  1175.     public function executeCacheQuery($sql$params$typesQueryCacheProfile $qcp)
  1176.     {
  1177.         $resultCache $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
  1178.         if ($resultCache === null) {
  1179.             throw CacheException::noResultDriverConfigured();
  1180.         }
  1181.         $connectionParams $this->params;
  1182.         unset($connectionParams['platform']);
  1183.         [$cacheKey$realKey] = $qcp->generateCacheKeys($sql$params$types$connectionParams);
  1184.         // fetch the row pointers entry
  1185.         $data $resultCache->fetch($cacheKey);
  1186.         if ($data !== false) {
  1187.             // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
  1188.             if (isset($data[$realKey])) {
  1189.                 $stmt = new ArrayStatement($data[$realKey]);
  1190.             } elseif (array_key_exists($realKey$data)) {
  1191.                 $stmt = new ArrayStatement([]);
  1192.             }
  1193.         }
  1194.         if (! isset($stmt)) {
  1195.             $stmt = new ResultCacheStatement(
  1196.                 $this->executeQuery($sql$params$types),
  1197.                 $resultCache,
  1198.                 $cacheKey,
  1199.                 $realKey,
  1200.                 $qcp->getLifetime()
  1201.             );
  1202.         }
  1203.         $stmt->setFetchMode($this->defaultFetchMode);
  1204.         return $this->ensureForwardCompatibilityStatement($stmt);
  1205.     }
  1206.     /**
  1207.      * @return ForwardCompatibility\Result
  1208.      */
  1209.     private function ensureForwardCompatibilityStatement(ResultStatement $stmt)
  1210.     {
  1211.         return ForwardCompatibility\Result::ensure($stmt);
  1212.     }
  1213.     /**
  1214.      * Executes an, optionally parametrized, SQL query and returns the result,
  1215.      * applying a given projection/transformation function on each row of the result.
  1216.      *
  1217.      * @deprecated
  1218.      *
  1219.      * @param string  $sql      The SQL query to execute.
  1220.      * @param mixed[] $params   The parameters, if any.
  1221.      * @param Closure $function The transformation function that is applied on each row.
  1222.      *                           The function receives a single parameter, an array, that
  1223.      *                           represents a row of the result set.
  1224.      *
  1225.      * @return mixed[] The projected result of the query.
  1226.      */
  1227.     public function project($sql, array $paramsClosure $function)
  1228.     {
  1229.         Deprecation::trigger(
  1230.             'doctrine/dbal',
  1231.             'https://github.com/doctrine/dbal/pull/3823',
  1232.             'Connection::project() is deprecated without replacement, implement data projections in your own code.'
  1233.         );
  1234.         $result = [];
  1235.         $stmt   $this->executeQuery($sql$params);
  1236.         while ($row $stmt->fetch()) {
  1237.             $result[] = $function($row);
  1238.         }
  1239.         $stmt->closeCursor();
  1240.         return $result;
  1241.     }
  1242.     /**
  1243.      * Executes an SQL statement, returning a result set as a Statement object.
  1244.      *
  1245.      * @deprecated Use {@link executeQuery()} instead.
  1246.      *
  1247.      * @return \Doctrine\DBAL\Driver\Statement
  1248.      *
  1249.      * @throws Exception
  1250.      */
  1251.     public function query()
  1252.     {
  1253.         Deprecation::trigger(
  1254.             'doctrine/dbal',
  1255.             'https://github.com/doctrine/dbal/pull/4163',
  1256.             'Connection::query() is deprecated, use Connection::executeQuery() instead.'
  1257.         );
  1258.         $connection $this->getWrappedConnection();
  1259.         $args func_get_args();
  1260.         $logger $this->_config->getSQLLogger();
  1261.         if ($logger) {
  1262.             $logger->startQuery($args[0]);
  1263.         }
  1264.         try {
  1265.             $statement $connection->query(...$args);
  1266.         } catch (Throwable $e) {
  1267.             $this->handleExceptionDuringQuery($e$args[0]);
  1268.         }
  1269.         $statement->setFetchMode($this->defaultFetchMode);
  1270.         if ($logger) {
  1271.             $logger->stopQuery();
  1272.         }
  1273.         return $statement;
  1274.     }
  1275.     /**
  1276.      * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
  1277.      * and returns the number of affected rows.
  1278.      *
  1279.      * This method supports PDO binding types as well as DBAL mapping types.
  1280.      *
  1281.      * @deprecated Use {@link executeStatement()} instead.
  1282.      *
  1283.      * @param string                                                               $sql    SQL statement
  1284.      * @param array<int, mixed>|array<string, mixed>                               $params Statement parameters
  1285.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1286.      *
  1287.      * @return int|string The number of affected rows.
  1288.      *
  1289.      * @throws Exception
  1290.      */
  1291.     public function executeUpdate($sql, array $params = [], array $types = [])
  1292.     {
  1293.         Deprecation::trigger(
  1294.             'doctrine/dbal',
  1295.             'https://github.com/doctrine/dbal/pull/4163',
  1296.             'Connection::executeUpdate() is deprecated, use Connection::executeStatement() instead.'
  1297.         );
  1298.         return $this->executeStatement($sql$params$types);
  1299.     }
  1300.     /**
  1301.      * Executes an SQL statement with the given parameters and returns the number of affected rows.
  1302.      *
  1303.      * Could be used for:
  1304.      *  - DML statements: INSERT, UPDATE, DELETE, etc.
  1305.      *  - DDL statements: CREATE, DROP, ALTER, etc.
  1306.      *  - DCL statements: GRANT, REVOKE, etc.
  1307.      *  - Session control statements: ALTER SESSION, SET, DECLARE, etc.
  1308.      *  - Other statements that don't yield a row set.
  1309.      *
  1310.      * This method supports PDO binding types as well as DBAL mapping types.
  1311.      *
  1312.      * @param string                                                               $sql    SQL statement
  1313.      * @param array<int, mixed>|array<string, mixed>                               $params Statement parameters
  1314.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1315.      *
  1316.      * @return int|string The number of affected rows.
  1317.      *
  1318.      * @throws Exception
  1319.      */
  1320.     public function executeStatement($sql, array $params = [], array $types = [])
  1321.     {
  1322.         $connection $this->getWrappedConnection();
  1323.         $logger $this->_config->getSQLLogger();
  1324.         if ($logger) {
  1325.             $logger->startQuery($sql$params$types);
  1326.         }
  1327.         try {
  1328.             if ($params) {
  1329.                 [$sql$params$types] = SQLParserUtils::expandListParameters($sql$params$types);
  1330.                 $stmt $connection->prepare($sql);
  1331.                 if ($types) {
  1332.                     $this->_bindTypedValues($stmt$params$types);
  1333.                     $stmt->execute();
  1334.                 } else {
  1335.                     $stmt->execute($params);
  1336.                 }
  1337.                 $result $stmt->rowCount();
  1338.             } else {
  1339.                 $result $connection->exec($sql);
  1340.             }
  1341.         } catch (Throwable $e) {
  1342.             $this->handleExceptionDuringQuery(
  1343.                 $e,
  1344.                 $sql,
  1345.                 $params,
  1346.                 $types
  1347.             );
  1348.         }
  1349.         if ($logger) {
  1350.             $logger->stopQuery();
  1351.         }
  1352.         return $result;
  1353.     }
  1354.     /**
  1355.      * Executes an SQL statement and return the number of affected rows.
  1356.      *
  1357.      * @deprecated Use {@link executeStatement()} instead.
  1358.      *
  1359.      * @param string $sql
  1360.      *
  1361.      * @return int|string The number of affected rows.
  1362.      *
  1363.      * @throws Exception
  1364.      */
  1365.     public function exec($sql)
  1366.     {
  1367.         Deprecation::trigger(
  1368.             'doctrine/dbal',
  1369.             'https://github.com/doctrine/dbal/pull/4163',
  1370.             'Connection::exec() is deprecated, use Connection::executeStatement() instead.'
  1371.         );
  1372.         $connection $this->getWrappedConnection();
  1373.         $logger $this->_config->getSQLLogger();
  1374.         if ($logger) {
  1375.             $logger->startQuery($sql);
  1376.         }
  1377.         try {
  1378.             $result $connection->exec($sql);
  1379.         } catch (Throwable $e) {
  1380.             $this->handleExceptionDuringQuery($e$sql);
  1381.         }
  1382.         if ($logger) {
  1383.             $logger->stopQuery();
  1384.         }
  1385.         return $result;
  1386.     }
  1387.     /**
  1388.      * Returns the current transaction nesting level.
  1389.      *
  1390.      * @return int The nesting level. A value of 0 means there's no active transaction.
  1391.      */
  1392.     public function getTransactionNestingLevel()
  1393.     {
  1394.         return $this->transactionNestingLevel;
  1395.     }
  1396.     /**
  1397.      * Fetches the SQLSTATE associated with the last database operation.
  1398.      *
  1399.      * @deprecated The error information is available via exceptions.
  1400.      *
  1401.      * @return string|null The last error code.
  1402.      */
  1403.     public function errorCode()
  1404.     {
  1405.         Deprecation::trigger(
  1406.             'doctrine/dbal',
  1407.             'https://github.com/doctrine/dbal/pull/3507',
  1408.             'Connection::errorCode() is deprecated, use getCode() or getSQLState() on Exception instead.'
  1409.         );
  1410.         return $this->getWrappedConnection()->errorCode();
  1411.     }
  1412.     /**
  1413.      * {@inheritDoc}
  1414.      *
  1415.      * @deprecated The error information is available via exceptions.
  1416.      */
  1417.     public function errorInfo()
  1418.     {
  1419.         Deprecation::trigger(
  1420.             'doctrine/dbal',
  1421.             'https://github.com/doctrine/dbal/pull/3507',
  1422.             'Connection::errorInfo() is deprecated, use getCode() or getSQLState() on Exception instead.'
  1423.         );
  1424.         return $this->getWrappedConnection()->errorInfo();
  1425.     }
  1426.     /**
  1427.      * Returns the ID of the last inserted row, or the last value from a sequence object,
  1428.      * depending on the underlying driver.
  1429.      *
  1430.      * Note: This method may not return a meaningful or consistent result across different drivers,
  1431.      * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
  1432.      * columns or sequences.
  1433.      *
  1434.      * @param string|null $name Name of the sequence object from which the ID should be returned.
  1435.      *
  1436.      * @return string|int|false A string representation of the last inserted ID.
  1437.      */
  1438.     public function lastInsertId($name null)
  1439.     {
  1440.         return $this->getWrappedConnection()->lastInsertId($name);
  1441.     }
  1442.     /**
  1443.      * Executes a function in a transaction.
  1444.      *
  1445.      * The function gets passed this Connection instance as an (optional) parameter.
  1446.      *
  1447.      * If an exception occurs during execution of the function or transaction commit,
  1448.      * the transaction is rolled back and the exception re-thrown.
  1449.      *
  1450.      * @param Closure $func The function to execute transactionally.
  1451.      *
  1452.      * @return mixed The value returned by $func
  1453.      *
  1454.      * @throws Throwable
  1455.      */
  1456.     public function transactional(Closure $func)
  1457.     {
  1458.         $this->beginTransaction();
  1459.         try {
  1460.             $res $func($this);
  1461.             $this->commit();
  1462.             return $res;
  1463.         } catch (Throwable $e) {
  1464.             $this->rollBack();
  1465.             throw $e;
  1466.         }
  1467.     }
  1468.     /**
  1469.      * Sets if nested transactions should use savepoints.
  1470.      *
  1471.      * @param bool $nestTransactionsWithSavepoints
  1472.      *
  1473.      * @return void
  1474.      *
  1475.      * @throws ConnectionException
  1476.      */
  1477.     public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
  1478.     {
  1479.         if ($this->transactionNestingLevel 0) {
  1480.             throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
  1481.         }
  1482.         if (! $this->getDatabasePlatform()->supportsSavepoints()) {
  1483.             throw ConnectionException::savepointsNotSupported();
  1484.         }
  1485.         $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
  1486.     }
  1487.     /**
  1488.      * Gets if nested transactions should use savepoints.
  1489.      *
  1490.      * @return bool
  1491.      */
  1492.     public function getNestTransactionsWithSavepoints()
  1493.     {
  1494.         return $this->nestTransactionsWithSavepoints;
  1495.     }
  1496.     /**
  1497.      * Returns the savepoint name to use for nested transactions are false if they are not supported
  1498.      * "savepointFormat" parameter is not set
  1499.      *
  1500.      * @return mixed A string with the savepoint name or false.
  1501.      */
  1502.     protected function _getNestedTransactionSavePointName()
  1503.     {
  1504.         return 'DOCTRINE2_SAVEPOINT_' $this->transactionNestingLevel;
  1505.     }
  1506.     /**
  1507.      * {@inheritDoc}
  1508.      */
  1509.     public function beginTransaction()
  1510.     {
  1511.         $connection $this->getWrappedConnection();
  1512.         ++$this->transactionNestingLevel;
  1513.         $logger $this->_config->getSQLLogger();
  1514.         if ($this->transactionNestingLevel === 1) {
  1515.             if ($logger) {
  1516.                 $logger->startQuery('"START TRANSACTION"');
  1517.             }
  1518.             $connection->beginTransaction();
  1519.             if ($logger) {
  1520.                 $logger->stopQuery();
  1521.             }
  1522.         } elseif ($this->nestTransactionsWithSavepoints) {
  1523.             if ($logger) {
  1524.                 $logger->startQuery('"SAVEPOINT"');
  1525.             }
  1526.             $this->createSavepoint($this->_getNestedTransactionSavePointName());
  1527.             if ($logger) {
  1528.                 $logger->stopQuery();
  1529.             }
  1530.         }
  1531.         return true;
  1532.     }
  1533.     /**
  1534.      * {@inheritDoc}
  1535.      *
  1536.      * @throws ConnectionException If the commit failed due to no active transaction or
  1537.      *                                            because the transaction was marked for rollback only.
  1538.      */
  1539.     public function commit()
  1540.     {
  1541.         if ($this->transactionNestingLevel === 0) {
  1542.             throw ConnectionException::noActiveTransaction();
  1543.         }
  1544.         if ($this->isRollbackOnly) {
  1545.             throw ConnectionException::commitFailedRollbackOnly();
  1546.         }
  1547.         $result true;
  1548.         $connection $this->getWrappedConnection();
  1549.         $logger $this->_config->getSQLLogger();
  1550.         if ($this->transactionNestingLevel === 1) {
  1551.             if ($logger) {
  1552.                 $logger->startQuery('"COMMIT"');
  1553.             }
  1554.             $result $connection->commit();
  1555.             if ($logger) {
  1556.                 $logger->stopQuery();
  1557.             }
  1558.         } elseif ($this->nestTransactionsWithSavepoints) {
  1559.             if ($logger) {
  1560.                 $logger->startQuery('"RELEASE SAVEPOINT"');
  1561.             }
  1562.             $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
  1563.             if ($logger) {
  1564.                 $logger->stopQuery();
  1565.             }
  1566.         }
  1567.         --$this->transactionNestingLevel;
  1568.         if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
  1569.             return $result;
  1570.         }
  1571.         $this->beginTransaction();
  1572.         return $result;
  1573.     }
  1574.     /**
  1575.      * Commits all current nesting transactions.
  1576.      */
  1577.     private function commitAll(): void
  1578.     {
  1579.         while ($this->transactionNestingLevel !== 0) {
  1580.             if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
  1581.                 // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
  1582.                 // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
  1583.                 $this->commit();
  1584.                 return;
  1585.             }
  1586.             $this->commit();
  1587.         }
  1588.     }
  1589.     /**
  1590.      * Cancels any database changes done during the current transaction.
  1591.      *
  1592.      * @return bool
  1593.      *
  1594.      * @throws ConnectionException If the rollback operation failed.
  1595.      */
  1596.     public function rollBack()
  1597.     {
  1598.         if ($this->transactionNestingLevel === 0) {
  1599.             throw ConnectionException::noActiveTransaction();
  1600.         }
  1601.         $connection $this->getWrappedConnection();
  1602.         $logger $this->_config->getSQLLogger();
  1603.         if ($this->transactionNestingLevel === 1) {
  1604.             if ($logger) {
  1605.                 $logger->startQuery('"ROLLBACK"');
  1606.             }
  1607.             $this->transactionNestingLevel 0;
  1608.             $connection->rollBack();
  1609.             $this->isRollbackOnly false;
  1610.             if ($logger) {
  1611.                 $logger->stopQuery();
  1612.             }
  1613.             if ($this->autoCommit === false) {
  1614.                 $this->beginTransaction();
  1615.             }
  1616.         } elseif ($this->nestTransactionsWithSavepoints) {
  1617.             if ($logger) {
  1618.                 $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
  1619.             }
  1620.             $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
  1621.             --$this->transactionNestingLevel;
  1622.             if ($logger) {
  1623.                 $logger->stopQuery();
  1624.             }
  1625.         } else {
  1626.             $this->isRollbackOnly true;
  1627.             --$this->transactionNestingLevel;
  1628.         }
  1629.         return true;
  1630.     }
  1631.     /**
  1632.      * Creates a new savepoint.
  1633.      *
  1634.      * @param string $savepoint The name of the savepoint to create.
  1635.      *
  1636.      * @return void
  1637.      *
  1638.      * @throws ConnectionException
  1639.      */
  1640.     public function createSavepoint($savepoint)
  1641.     {
  1642.         $platform $this->getDatabasePlatform();
  1643.         if (! $platform->supportsSavepoints()) {
  1644.             throw ConnectionException::savepointsNotSupported();
  1645.         }
  1646.         $this->getWrappedConnection()->exec($platform->createSavePoint($savepoint));
  1647.     }
  1648.     /**
  1649.      * Releases the given savepoint.
  1650.      *
  1651.      * @param string $savepoint The name of the savepoint to release.
  1652.      *
  1653.      * @return void
  1654.      *
  1655.      * @throws ConnectionException
  1656.      */
  1657.     public function releaseSavepoint($savepoint)
  1658.     {
  1659.         $platform $this->getDatabasePlatform();
  1660.         if (! $platform->supportsSavepoints()) {
  1661.             throw ConnectionException::savepointsNotSupported();
  1662.         }
  1663.         if (! $platform->supportsReleaseSavepoints()) {
  1664.             return;
  1665.         }
  1666.         $this->getWrappedConnection()->exec($platform->releaseSavePoint($savepoint));
  1667.     }
  1668.     /**
  1669.      * Rolls back to the given savepoint.
  1670.      *
  1671.      * @param string $savepoint The name of the savepoint to rollback to.
  1672.      *
  1673.      * @return void
  1674.      *
  1675.      * @throws ConnectionException
  1676.      */
  1677.     public function rollbackSavepoint($savepoint)
  1678.     {
  1679.         $platform $this->getDatabasePlatform();
  1680.         if (! $platform->supportsSavepoints()) {
  1681.             throw ConnectionException::savepointsNotSupported();
  1682.         }
  1683.         $this->getWrappedConnection()->exec($platform->rollbackSavePoint($savepoint));
  1684.     }
  1685.     /**
  1686.      * Gets the wrapped driver connection.
  1687.      *
  1688.      * @return DriverConnection
  1689.      */
  1690.     public function getWrappedConnection()
  1691.     {
  1692.         $this->connect();
  1693.         assert($this->_conn !== null);
  1694.         return $this->_conn;
  1695.     }
  1696.     /**
  1697.      * Gets the SchemaManager that can be used to inspect or change the
  1698.      * database schema through the connection.
  1699.      *
  1700.      * @return AbstractSchemaManager
  1701.      */
  1702.     public function getSchemaManager()
  1703.     {
  1704.         if ($this->_schemaManager === null) {
  1705.             $this->_schemaManager $this->_driver->getSchemaManager($this);
  1706.         }
  1707.         return $this->_schemaManager;
  1708.     }
  1709.     /**
  1710.      * Marks the current transaction so that the only possible
  1711.      * outcome for the transaction to be rolled back.
  1712.      *
  1713.      * @return void
  1714.      *
  1715.      * @throws ConnectionException If no transaction is active.
  1716.      */
  1717.     public function setRollbackOnly()
  1718.     {
  1719.         if ($this->transactionNestingLevel === 0) {
  1720.             throw ConnectionException::noActiveTransaction();
  1721.         }
  1722.         $this->isRollbackOnly true;
  1723.     }
  1724.     /**
  1725.      * Checks whether the current transaction is marked for rollback only.
  1726.      *
  1727.      * @return bool
  1728.      *
  1729.      * @throws ConnectionException If no transaction is active.
  1730.      */
  1731.     public function isRollbackOnly()
  1732.     {
  1733.         if ($this->transactionNestingLevel === 0) {
  1734.             throw ConnectionException::noActiveTransaction();
  1735.         }
  1736.         return $this->isRollbackOnly;
  1737.     }
  1738.     /**
  1739.      * Converts a given value to its database representation according to the conversion
  1740.      * rules of a specific DBAL mapping type.
  1741.      *
  1742.      * @param mixed  $value The value to convert.
  1743.      * @param string $type  The name of the DBAL mapping type.
  1744.      *
  1745.      * @return mixed The converted value.
  1746.      */
  1747.     public function convertToDatabaseValue($value$type)
  1748.     {
  1749.         return Type::getType($type)->convertToDatabaseValue($value$this->getDatabasePlatform());
  1750.     }
  1751.     /**
  1752.      * Converts a given value to its PHP representation according to the conversion
  1753.      * rules of a specific DBAL mapping type.
  1754.      *
  1755.      * @param mixed  $value The value to convert.
  1756.      * @param string $type  The name of the DBAL mapping type.
  1757.      *
  1758.      * @return mixed The converted type.
  1759.      */
  1760.     public function convertToPHPValue($value$type)
  1761.     {
  1762.         return Type::getType($type)->convertToPHPValue($value$this->getDatabasePlatform());
  1763.     }
  1764.     /**
  1765.      * Binds a set of parameters, some or all of which are typed with a PDO binding type
  1766.      * or DBAL mapping type, to a given statement.
  1767.      *
  1768.      * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
  1769.      *           raw PDOStatement instances.
  1770.      *
  1771.      * @param \Doctrine\DBAL\Driver\Statement                                      $stmt   Prepared statement
  1772.      * @param array<int, mixed>|array<string, mixed>                               $params Statement parameters
  1773.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1774.      *
  1775.      * @return void
  1776.      */
  1777.     private function _bindTypedValues($stmt, array $params, array $types)
  1778.     {
  1779.         // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  1780.         if (is_int(key($params))) {
  1781.             // Positional parameters
  1782.             $typeOffset array_key_exists(0$types) ? -0;
  1783.             $bindIndex  1;
  1784.             foreach ($params as $value) {
  1785.                 $typeIndex $bindIndex $typeOffset;
  1786.                 if (isset($types[$typeIndex])) {
  1787.                     $type                  $types[$typeIndex];
  1788.                     [$value$bindingType] = $this->getBindingInfo($value$type);
  1789.                     $stmt->bindValue($bindIndex$value$bindingType);
  1790.                 } else {
  1791.                     $stmt->bindValue($bindIndex$value);
  1792.                 }
  1793.                 ++$bindIndex;
  1794.             }
  1795.         } else {
  1796.             // Named parameters
  1797.             foreach ($params as $name => $value) {
  1798.                 if (isset($types[$name])) {
  1799.                     $type                  $types[$name];
  1800.                     [$value$bindingType] = $this->getBindingInfo($value$type);
  1801.                     $stmt->bindValue($name$value$bindingType);
  1802.                 } else {
  1803.                     $stmt->bindValue($name$value);
  1804.                 }
  1805.             }
  1806.         }
  1807.     }
  1808.     /**
  1809.      * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
  1810.      *
  1811.      * @param mixed                $value The value to bind.
  1812.      * @param int|string|Type|null $type  The type to bind (PDO or DBAL).
  1813.      *
  1814.      * @return array{mixed, int} [0] => the (escaped) value, [1] => the binding type.
  1815.      */
  1816.     private function getBindingInfo($value$type): array
  1817.     {
  1818.         if (is_string($type)) {
  1819.             $type Type::getType($type);
  1820.         }
  1821.         if ($type instanceof Type) {
  1822.             $value       $type->convertToDatabaseValue($value$this->getDatabasePlatform());
  1823.             $bindingType $type->getBindingType();
  1824.         } else {
  1825.             $bindingType $type ?? ParameterType::STRING;
  1826.         }
  1827.         return [$value$bindingType];
  1828.     }
  1829.     /**
  1830.      * Resolves the parameters to a format which can be displayed.
  1831.      *
  1832.      * @internal This is a purely internal method. If you rely on this method, you are advised to
  1833.      *           copy/paste the code as this method may change, or be removed without prior notice.
  1834.      *
  1835.      * @param array<int, mixed>|array<string, mixed>                               $params Query parameters
  1836.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1837.      *
  1838.      * @return array<int, int|string|Type|null>|array<string, int|string|Type|null>
  1839.      */
  1840.     public function resolveParams(array $params, array $types)
  1841.     {
  1842.         $resolvedParams = [];
  1843.         // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  1844.         if (is_int(key($params))) {
  1845.             // Positional parameters
  1846.             $typeOffset array_key_exists(0$types) ? -0;
  1847.             $bindIndex  1;
  1848.             foreach ($params as $value) {
  1849.                 $typeIndex $bindIndex $typeOffset;
  1850.                 if (isset($types[$typeIndex])) {
  1851.                     $type                       $types[$typeIndex];
  1852.                     [$value]                    = $this->getBindingInfo($value$type);
  1853.                     $resolvedParams[$bindIndex] = $value;
  1854.                 } else {
  1855.                     $resolvedParams[$bindIndex] = $value;
  1856.                 }
  1857.                 ++$bindIndex;
  1858.             }
  1859.         } else {
  1860.             // Named parameters
  1861.             foreach ($params as $name => $value) {
  1862.                 if (isset($types[$name])) {
  1863.                     $type                  $types[$name];
  1864.                     [$value]               = $this->getBindingInfo($value$type);
  1865.                     $resolvedParams[$name] = $value;
  1866.                 } else {
  1867.                     $resolvedParams[$name] = $value;
  1868.                 }
  1869.             }
  1870.         }
  1871.         return $resolvedParams;
  1872.     }
  1873.     /**
  1874.      * Creates a new instance of a SQL query builder.
  1875.      *
  1876.      * @return QueryBuilder
  1877.      */
  1878.     public function createQueryBuilder()
  1879.     {
  1880.         return new Query\QueryBuilder($this);
  1881.     }
  1882.     /**
  1883.      * Ping the server
  1884.      *
  1885.      * When the server is not available the method returns FALSE.
  1886.      * It is responsibility of the developer to handle this case
  1887.      * and abort the request or reconnect manually:
  1888.      *
  1889.      * @deprecated
  1890.      *
  1891.      * @return bool
  1892.      *
  1893.      * @example
  1894.      *
  1895.      *   if ($conn->ping() === false) {
  1896.      *      $conn->close();
  1897.      *      $conn->connect();
  1898.      *   }
  1899.      *
  1900.      * It is undefined if the underlying driver attempts to reconnect
  1901.      * or disconnect when the connection is not available anymore
  1902.      * as long it returns TRUE when a reconnect succeeded and
  1903.      * FALSE when the connection was dropped.
  1904.      */
  1905.     public function ping()
  1906.     {
  1907.         Deprecation::trigger(
  1908.             'doctrine/dbal',
  1909.             'https://github.com/doctrine/dbal/pull/4119',
  1910.             'Retry and reconnecting lost connections now happens automatically, ping() will be removed in DBAL 3.'
  1911.         );
  1912.         $connection $this->getWrappedConnection();
  1913.         if ($connection instanceof PingableConnection) {
  1914.             return $connection->ping();
  1915.         }
  1916.         try {
  1917.             $this->query($this->getDatabasePlatform()->getDummySelectSQL());
  1918.             return true;
  1919.         } catch (DBALException $e) {
  1920.             return false;
  1921.         }
  1922.     }
  1923.     /**
  1924.      * @internal
  1925.      *
  1926.      * @param array<int, mixed>|array<string, mixed>                               $params
  1927.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
  1928.      *
  1929.      * @psalm-return never-return
  1930.      *
  1931.      * @throws Exception
  1932.      */
  1933.     public function handleExceptionDuringQuery(Throwable $estring $sql, array $params = [], array $types = []): void
  1934.     {
  1935.         $this->throw(
  1936.             Exception::driverExceptionDuringQuery(
  1937.                 $this->_driver,
  1938.                 $e,
  1939.                 $sql,
  1940.                 $this->resolveParams($params$types)
  1941.             )
  1942.         );
  1943.     }
  1944.     /**
  1945.      * @internal
  1946.      *
  1947.      * @psalm-return never-return
  1948.      *
  1949.      * @throws Exception
  1950.      */
  1951.     public function handleDriverException(Throwable $e): void
  1952.     {
  1953.         $this->throw(
  1954.             Exception::driverException(
  1955.                 $this->_driver,
  1956.                 $e
  1957.             )
  1958.         );
  1959.     }
  1960.     /**
  1961.      * @internal
  1962.      *
  1963.      * @psalm-return never-return
  1964.      *
  1965.      * @throws Exception
  1966.      */
  1967.     private function throw(Exception $e): void
  1968.     {
  1969.         if ($e instanceof ConnectionLost) {
  1970.             $this->close();
  1971.         }
  1972.         throw $e;
  1973.     }
  1974.     private function ensureHasKeyValue(ResultStatement $stmt): void
  1975.     {
  1976.         $columnCount $stmt->columnCount();
  1977.         if ($columnCount 2) {
  1978.             throw NoKeyValue::fromColumnCount($columnCount);
  1979.         }
  1980.     }
  1981. }