vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php line 1853

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