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

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