Connection.php 71 KB

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