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

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