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

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