vendor/doctrine/dbal/src/Connection.php line 347

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