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

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