Connection.php 70 KB

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