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

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