Connection.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Database;
  17. use Cake\Cache\Cache;
  18. use Cake\Core\App;
  19. use Cake\Core\Retry\CommandRetry;
  20. use Cake\Database\Exception\MissingConnectionException;
  21. use Cake\Database\Exception\MissingDriverException;
  22. use Cake\Database\Exception\MissingExtensionException;
  23. use Cake\Database\Exception\NestedTransactionRollbackException;
  24. use Cake\Database\Log\LoggedQuery;
  25. use Cake\Database\Log\LoggingStatement;
  26. use Cake\Database\Log\QueryLogger;
  27. use Cake\Database\Query\DeleteQuery;
  28. use Cake\Database\Query\InsertQuery;
  29. use Cake\Database\Query\SelectQuery;
  30. use Cake\Database\Query\UpdateQuery;
  31. use Cake\Database\Retry\ReconnectStrategy;
  32. use Cake\Database\Schema\CachedCollection;
  33. use Cake\Database\Schema\Collection as SchemaCollection;
  34. use Cake\Database\Schema\CollectionInterface as SchemaCollectionInterface;
  35. use Cake\Datasource\ConnectionInterface;
  36. use Cake\Log\Engine\BaseLog;
  37. use Cake\Log\Log;
  38. use Psr\Log\LoggerInterface;
  39. use Psr\SimpleCache\CacheInterface;
  40. use RuntimeException;
  41. use Throwable;
  42. use function Cake\Core\deprecationWarning;
  43. /**
  44. * Represents a connection with a database server.
  45. */
  46. class Connection implements ConnectionInterface
  47. {
  48. use TypeConverterTrait;
  49. /**
  50. * Contains the configuration params for this connection.
  51. *
  52. * @var array<string, mixed>
  53. */
  54. protected $_config;
  55. /**
  56. * @var \Cake\Database\DriverInterface
  57. */
  58. protected DriverInterface $readDriver;
  59. /**
  60. * @var \Cake\Database\DriverInterface
  61. */
  62. protected DriverInterface $writeDriver;
  63. /**
  64. * Contains how many nested transactions have been started.
  65. *
  66. * @var int
  67. */
  68. protected $_transactionLevel = 0;
  69. /**
  70. * Whether a transaction is active in this connection.
  71. *
  72. * @var bool
  73. */
  74. protected $_transactionStarted = false;
  75. /**
  76. * Whether this connection can and should use savepoints for nested
  77. * transactions.
  78. *
  79. * @var bool
  80. */
  81. protected $_useSavePoints = false;
  82. /**
  83. * Whether to log queries generated during this connection.
  84. *
  85. * @var bool
  86. */
  87. protected $_logQueries = false;
  88. /**
  89. * Logger object instance.
  90. *
  91. * @var \Psr\Log\LoggerInterface|null
  92. */
  93. protected $_logger;
  94. /**
  95. * Cacher object instance.
  96. *
  97. * @var \Psr\SimpleCache\CacheInterface|null
  98. */
  99. protected $cacher;
  100. /**
  101. * The schema collection object
  102. *
  103. * @var \Cake\Database\Schema\CollectionInterface|null
  104. */
  105. protected $_schemaCollection;
  106. /**
  107. * NestedTransactionRollbackException object instance, will be stored if
  108. * the rollback method is called in some nested transaction.
  109. *
  110. * @var \Cake\Database\Exception\NestedTransactionRollbackException|null
  111. */
  112. protected $nestedTransactionRollbackException;
  113. /**
  114. * Constructor.
  115. *
  116. * ### Available options:
  117. *
  118. * - `driver` Sort name or FCQN for driver.
  119. * - `log` Boolean indicating whether to use query logging.
  120. * - `name` Connection name.
  121. * - `cacheMetaData` Boolean indicating whether metadata (datasource schemas) should be cached.
  122. * If set to a string it will be used as the name of cache config to use.
  123. * - `cacheKeyPrefix` Custom prefix to use when generation cache keys. Defaults to connection name.
  124. *
  125. * @param array<string, mixed> $config Configuration array.
  126. */
  127. public function __construct(array $config)
  128. {
  129. $this->_config = $config;
  130. [self::ROLE_READ => $this->readDriver, self::ROLE_WRITE => $this->writeDriver] = $this->createDrivers($config);
  131. if (!empty($config['log'])) {
  132. $this->enableQueryLogging((bool)$config['log']);
  133. }
  134. }
  135. /**
  136. * Creates read and write drivers.
  137. *
  138. * @param array $config Connection config
  139. * @return array<string, \Cake\Database\DriverInterface>
  140. * @psalm-return array{read: \Cake\Database\DriverInterface, write: \Cake\Database\DriverInterface}
  141. */
  142. protected function createDrivers(array $config): array
  143. {
  144. $driver = $config['driver'] ?? '';
  145. if (!is_string($driver)) {
  146. /** @var \Cake\Database\DriverInterface $driver */
  147. if (!$driver->enabled()) {
  148. throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $this->configName()]);
  149. }
  150. // Legacy support for setting instance instead of driver class
  151. return [self::ROLE_READ => $driver, self::ROLE_WRITE => $driver];
  152. }
  153. /** @var class-string<\Cake\Database\DriverInterface>|null $driverClass */
  154. $driverClass = App::className($driver, 'Database/Driver');
  155. if ($driverClass === null) {
  156. throw new MissingDriverException(['driver' => $driver, 'connection' => $this->configName()]);
  157. }
  158. $sharedConfig = array_diff_key($config, array_flip([
  159. 'name',
  160. 'driver',
  161. 'log',
  162. 'cacheMetaData',
  163. 'cacheKeyPrefix',
  164. ]));
  165. $writeConfig = $config['write'] ?? [] + $sharedConfig;
  166. $readConfig = $config['read'] ?? [] + $sharedConfig;
  167. if ($readConfig == $writeConfig) {
  168. $readDriver = $writeDriver = new $driverClass(['_role' => self::ROLE_WRITE] + $writeConfig);
  169. } else {
  170. $readDriver = new $driverClass(['_role' => self::ROLE_READ] + $readConfig);
  171. $writeDriver = new $driverClass(['_role' => self::ROLE_WRITE] + $writeConfig);
  172. }
  173. if (!$writeDriver->enabled()) {
  174. throw new MissingExtensionException(['driver' => get_class($writeDriver), 'name' => $this->configName()]);
  175. }
  176. return [self::ROLE_READ => $readDriver, self::ROLE_WRITE => $writeDriver];
  177. }
  178. /**
  179. * Destructor
  180. *
  181. * Disconnects the driver to release the connection.
  182. */
  183. public function __destruct()
  184. {
  185. if ($this->_transactionStarted && class_exists(Log::class)) {
  186. Log::warning('The connection is going to be closed but there is an active transaction.');
  187. }
  188. }
  189. /**
  190. * @inheritDoc
  191. */
  192. public function config(): array
  193. {
  194. return $this->_config;
  195. }
  196. /**
  197. * @inheritDoc
  198. */
  199. public function configName(): string
  200. {
  201. return $this->_config['name'] ?? '';
  202. }
  203. /**
  204. * Returns the connection role: read or write.
  205. *
  206. * @return string
  207. */
  208. public function role(): string
  209. {
  210. return preg_match('/:read$/', $this->configName()) === 1 ? static::ROLE_READ : static::ROLE_WRITE;
  211. }
  212. /**
  213. * Sets the driver instance. If a string is passed it will be treated
  214. * as a class name and will be instantiated.
  215. *
  216. * @param \Cake\Database\DriverInterface|string $driver The driver instance to use.
  217. * @param array<string, mixed> $config Config for a new driver.
  218. * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing.
  219. * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing.
  220. * @return $this
  221. * @deprecated 4.4.0 Setting the driver is deprecated. Use the connection config instead.
  222. */
  223. public function setDriver($driver, $config = [])
  224. {
  225. deprecationWarning('Setting the driver is deprecated. Use the connection config instead.');
  226. $driver = $this->createDriver($driver, $config);
  227. $this->readDriver = $this->writeDriver = $driver;
  228. return $this;
  229. }
  230. /**
  231. * Creates driver from name, class name or instance.
  232. *
  233. * @param \Cake\Database\DriverInterface|string $name Driver name, class name or instance.
  234. * @param array $config Driver config if $name is not an instance.
  235. * @return \Cake\Database\DriverInterface
  236. * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing.
  237. * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing.
  238. */
  239. protected function createDriver($name, array $config): DriverInterface
  240. {
  241. $driver = $name;
  242. if (is_string($driver)) {
  243. /** @psalm-var class-string<\Cake\Database\DriverInterface>|null $className */
  244. $className = App::className($driver, 'Database/Driver');
  245. if ($className === null) {
  246. throw new MissingDriverException(['driver' => $driver, 'connection' => $this->configName()]);
  247. }
  248. $driver = new $className(['_role' => self::ROLE_WRITE] + $config);
  249. }
  250. if (!$driver->enabled()) {
  251. throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $this->configName()]);
  252. }
  253. return $driver;
  254. }
  255. /**
  256. * Get the retry wrapper object that is allows recovery from server disconnects
  257. * while performing certain database actions, such as executing a query.
  258. *
  259. * @return \Cake\Core\Retry\CommandRetry The retry wrapper
  260. */
  261. public function getDisconnectRetry(): CommandRetry
  262. {
  263. return new CommandRetry(new ReconnectStrategy($this));
  264. }
  265. /**
  266. * Gets the driver instance.
  267. *
  268. * @param string $role Connection role ('read' or 'write')
  269. * @return \Cake\Database\DriverInterface
  270. */
  271. public function getDriver(string $role = self::ROLE_WRITE): DriverInterface
  272. {
  273. assert($role === self::ROLE_READ || $role === self::ROLE_WRITE);
  274. return $role === self::ROLE_READ ? $this->readDriver : $this->writeDriver;
  275. }
  276. /**
  277. * Connects to the configured database.
  278. *
  279. * @throws \Cake\Database\Exception\MissingConnectionException If database connection could not be established.
  280. * @return bool true, if the connection was already established or the attempt was successful.
  281. */
  282. public function connect(): bool
  283. {
  284. deprecationWarning(
  285. 'If you cannot use automatic connection management, use $connection->getDriver()->connect() instead.'
  286. );
  287. $connected = true;
  288. foreach ([self::ROLE_READ, self::ROLE_WRITE] as $role) {
  289. try {
  290. $connected = $connected && $this->getDriver($role)->connect();
  291. } catch (MissingConnectionException $e) {
  292. throw $e;
  293. } catch (Throwable $e) {
  294. throw new MissingConnectionException(
  295. [
  296. 'driver' => App::shortName(get_class($this->getDriver($role)), 'Database/Driver'),
  297. 'reason' => $e->getMessage(),
  298. ],
  299. null,
  300. $e
  301. );
  302. }
  303. }
  304. return $connected;
  305. }
  306. /**
  307. * Disconnects from database server.
  308. *
  309. * @return void
  310. * @deprecated 4.5.0 Use getDriver()->disconnect() instead.
  311. */
  312. public function disconnect(): void
  313. {
  314. deprecationWarning(
  315. 'If you cannot use automatic connection management, use $connection->getDriver()->disconnect() instead.'
  316. );
  317. $this->getDriver(self::ROLE_READ)->disconnect();
  318. $this->getDriver(self::ROLE_WRITE)->disconnect();
  319. }
  320. /**
  321. * Returns whether connection to database server was already established.
  322. *
  323. * @return bool
  324. * @deprecated 4.5.0 Use getDriver()->isConnected() instead.
  325. */
  326. public function isConnected(): bool
  327. {
  328. deprecationWarning('Use $connection->getDriver()->isConnected() instead.');
  329. return $this->getDriver(self::ROLE_READ)->isConnected() && $this->getDriver(self::ROLE_WRITE)->isConnected();
  330. }
  331. /**
  332. * Prepares a SQL statement to be executed.
  333. *
  334. * @param \Cake\Database\Query|string $query The SQL to convert into a prepared statement.
  335. * @return \Cake\Database\StatementInterface
  336. * @deprecated 4.5.0 Use getDriver()->prepare() instead.
  337. */
  338. public function prepare($query): StatementInterface
  339. {
  340. $role = $query instanceof Query ? $query->getConnectionRole() : self::ROLE_WRITE;
  341. return $this->getDisconnectRetry()->run(function () use ($query, $role) {
  342. $statement = $this->getDriver($role)->prepare($query);
  343. if ($this->_logQueries) {
  344. $statement = $this->_newLogger($statement);
  345. }
  346. return $statement;
  347. });
  348. }
  349. /**
  350. * Executes a query using $params for interpolating values and $types as a hint for each
  351. * those params.
  352. *
  353. * @param string $sql SQL to be executed and interpolated with $params
  354. * @param array $params list or associative array of params to be interpolated in $sql as values
  355. * @param array $types list or associative array of types to be used for casting values in query
  356. * @return \Cake\Database\StatementInterface executed statement
  357. */
  358. public function execute(string $sql, array $params = [], array $types = []): StatementInterface
  359. {
  360. return $this->getDisconnectRetry()->run(function () use ($sql, $params, $types) {
  361. $statement = $this->prepare($sql);
  362. if (!empty($params)) {
  363. $statement->bind($params, $types);
  364. }
  365. $statement->execute();
  366. return $statement;
  367. });
  368. }
  369. /**
  370. * Compiles a Query object into a SQL string according to the dialect for this
  371. * connection's driver
  372. *
  373. * @param \Cake\Database\Query $query The query to be compiled
  374. * @param \Cake\Database\ValueBinder $binder Value binder
  375. * @return string
  376. */
  377. public function compileQuery(Query $query, ValueBinder $binder): string
  378. {
  379. deprecationWarning('Use getDriver()->compileQuery() instead.');
  380. return $this->getDriver($query->getConnectionRole())->compileQuery($query, $binder)[1];
  381. }
  382. /**
  383. * Executes the provided query after compiling it for the specific driver
  384. * dialect and returns the executed Statement object.
  385. *
  386. * @param \Cake\Database\Query $query The query to be executed
  387. * @return \Cake\Database\StatementInterface executed statement
  388. */
  389. public function run(Query $query): StatementInterface
  390. {
  391. return $this->getDisconnectRetry()->run(function () use ($query) {
  392. $statement = $this->prepare($query);
  393. $query->getValueBinder()->attachTo($statement);
  394. $statement->execute();
  395. return $statement;
  396. });
  397. }
  398. /**
  399. * Create a new SelectQuery instance for this connection.
  400. *
  401. * @param \Cake\Database\ExpressionInterface|callable|array|string $fields fields to be added to the list.
  402. * @param array|string $table The table or list of tables to query.
  403. * @param array<string, string> $types Associative array containing the types to be used for casting.
  404. * @return \Cake\Database\Query\SelectQuery
  405. */
  406. public function selectQuery(
  407. $fields = [],
  408. $table = [],
  409. array $types = []
  410. ): SelectQuery {
  411. $query = new SelectQuery($this);
  412. if ($table) {
  413. $query->from($table);
  414. }
  415. if ($fields) {
  416. $query->select($fields, false);
  417. }
  418. $query->setDefaultTypes($types);
  419. return $query;
  420. }
  421. /**
  422. * Executes a SQL statement and returns the Statement object as result.
  423. *
  424. * @param string $sql The SQL query to execute.
  425. * @return \Cake\Database\StatementInterface
  426. */
  427. public function query(string $sql): StatementInterface
  428. {
  429. deprecationWarning('Use either `selectQuery`, `insertQuery`, `deleteQuery`, `updateQuery` instead.');
  430. return $this->getDisconnectRetry()->run(function () use ($sql) {
  431. $statement = $this->prepare($sql);
  432. $statement->execute();
  433. return $statement;
  434. });
  435. }
  436. /**
  437. * Create a new Query instance for this connection.
  438. *
  439. * @return \Cake\Database\Query
  440. */
  441. public function newQuery(): Query
  442. {
  443. deprecationWarning(
  444. 'As of 4.5.0, using newQuery() is deprecated. Instead, use `insertQuery()`, ' .
  445. '`deleteQuery()`, `selectQuery()` or `updateQuery()`. The query objects ' .
  446. 'returned by these methods will emit deprecations that will become fatal errors in 5.0.' .
  447. 'See https://book.cakephp.org/4/en/appendices/4-5-migration-guide.html for more information.'
  448. );
  449. return new Query($this);
  450. }
  451. /**
  452. * Sets a Schema\Collection object for this connection.
  453. *
  454. * @param \Cake\Database\Schema\CollectionInterface $collection The schema collection object
  455. * @return $this
  456. */
  457. public function setSchemaCollection(SchemaCollectionInterface $collection)
  458. {
  459. $this->_schemaCollection = $collection;
  460. return $this;
  461. }
  462. /**
  463. * Gets a Schema\Collection object for this connection.
  464. *
  465. * @return \Cake\Database\Schema\CollectionInterface
  466. */
  467. public function getSchemaCollection(): SchemaCollectionInterface
  468. {
  469. if ($this->_schemaCollection !== null) {
  470. return $this->_schemaCollection;
  471. }
  472. if (!empty($this->_config['cacheMetadata'])) {
  473. return $this->_schemaCollection = new CachedCollection(
  474. new SchemaCollection($this),
  475. empty($this->_config['cacheKeyPrefix']) ? $this->configName() : $this->_config['cacheKeyPrefix'],
  476. $this->getCacher()
  477. );
  478. }
  479. return $this->_schemaCollection = new SchemaCollection($this);
  480. }
  481. /**
  482. * Executes an INSERT query on the specified table.
  483. *
  484. * @param string $table the table to insert values in
  485. * @param array $values values to be inserted
  486. * @param array<int|string, string> $types Array containing the types to be used for casting
  487. * @return \Cake\Database\StatementInterface
  488. */
  489. public function insert(string $table, array $values, array $types = []): StatementInterface
  490. {
  491. return $this->getDisconnectRetry()->run(function () use ($table, $values, $types) {
  492. return $this->insertQuery($table, $values, $types)->execute();
  493. });
  494. }
  495. /**
  496. * Create a new InsertQuery instance for this connection.
  497. *
  498. * @param string|null $table The table to insert rows into.
  499. * @param array $values Associative array of column => value to be inserted.
  500. * @param array<int|string, string> $types Associative array containing the types to be used for casting.
  501. * @return \Cake\Database\Query\InsertQuery
  502. */
  503. public function insertQuery(?string $table = null, array $values = [], array $types = []): InsertQuery
  504. {
  505. $query = new InsertQuery($this);
  506. if ($table) {
  507. $query->into($table);
  508. }
  509. if ($values) {
  510. $columns = array_keys($values);
  511. $query->insert($columns, $types)
  512. ->values($values);
  513. }
  514. return $query;
  515. }
  516. /**
  517. * Executes an UPDATE statement on the specified table.
  518. *
  519. * @param string $table the table to update rows from
  520. * @param array $values values to be updated
  521. * @param array $conditions conditions to be set for update statement
  522. * @param array<string> $types list of associative array containing the types to be used for casting
  523. * @return \Cake\Database\StatementInterface
  524. */
  525. public function update(string $table, array $values, array $conditions = [], array $types = []): StatementInterface
  526. {
  527. return $this->getDisconnectRetry()->run(function () use ($table, $values, $conditions, $types) {
  528. return $this->updateQuery($table, $values, $conditions, $types)->execute();
  529. });
  530. }
  531. /**
  532. * Create a new UpdateQuery instance for this connection.
  533. *
  534. * @param \Cake\Database\ExpressionInterface|string|null $table The table to update rows of.
  535. * @param array $values Values to be updated.
  536. * @param array $conditions Conditions to be set for the update statement.
  537. * @param array<string, string> $types Associative array containing the types to be used for casting.
  538. * @return \Cake\Database\Query\UpdateQuery
  539. */
  540. public function updateQuery(
  541. $table = null,
  542. array $values = [],
  543. array $conditions = [],
  544. array $types = []
  545. ): UpdateQuery {
  546. $query = new UpdateQuery($this);
  547. if ($table) {
  548. $query->update($table);
  549. }
  550. if ($values) {
  551. $query->set($values, $types);
  552. }
  553. if ($conditions) {
  554. $query->where($conditions, $types);
  555. }
  556. return $query;
  557. }
  558. /**
  559. * Executes a DELETE statement on the specified table.
  560. *
  561. * @param string $table the table to delete rows from
  562. * @param array $conditions conditions to be set for delete statement
  563. * @param array<string> $types list of associative array containing the types to be used for casting
  564. * @return \Cake\Database\StatementInterface
  565. */
  566. public function delete(string $table, array $conditions = [], array $types = []): StatementInterface
  567. {
  568. return $this->getDisconnectRetry()->run(function () use ($table, $conditions, $types) {
  569. return $this->deleteQuery($table, $conditions, $types)->execute();
  570. });
  571. }
  572. /**
  573. * Create a new DeleteQuery instance for this connection.
  574. *
  575. * @param string|null $table The table to delete rows from.
  576. * @param array $conditions Conditions to be set for the delete statement.
  577. * @param array<string, string> $types Associative array containing the types to be used for casting.
  578. * @return \Cake\Database\Query\DeleteQuery
  579. */
  580. public function deleteQuery(?string $table = null, array $conditions = [], array $types = []): DeleteQuery
  581. {
  582. $query = new DeleteQuery($this);
  583. if ($table) {
  584. $query->from($table);
  585. }
  586. if ($conditions) {
  587. $query->where($conditions, $types);
  588. }
  589. return $query;
  590. }
  591. /**
  592. * Starts a new transaction.
  593. *
  594. * @return void
  595. */
  596. public function begin(): void
  597. {
  598. if (!$this->_transactionStarted) {
  599. if ($this->_logQueries) {
  600. $this->log('BEGIN');
  601. }
  602. $this->getDisconnectRetry()->run(function (): void {
  603. $this->getDriver()->beginTransaction();
  604. });
  605. $this->_transactionLevel = 0;
  606. $this->_transactionStarted = true;
  607. $this->nestedTransactionRollbackException = null;
  608. return;
  609. }
  610. $this->_transactionLevel++;
  611. if ($this->isSavePointsEnabled()) {
  612. $this->createSavePoint((string)$this->_transactionLevel);
  613. }
  614. }
  615. /**
  616. * Commits current transaction.
  617. *
  618. * @return bool true on success, false otherwise
  619. */
  620. public function commit(): bool
  621. {
  622. if (!$this->_transactionStarted) {
  623. return false;
  624. }
  625. if ($this->_transactionLevel === 0) {
  626. if ($this->wasNestedTransactionRolledback()) {
  627. /** @var \Cake\Database\Exception\NestedTransactionRollbackException $e */
  628. $e = $this->nestedTransactionRollbackException;
  629. $this->nestedTransactionRollbackException = null;
  630. throw $e;
  631. }
  632. $this->_transactionStarted = false;
  633. $this->nestedTransactionRollbackException = null;
  634. if ($this->_logQueries) {
  635. $this->log('COMMIT');
  636. }
  637. return $this->getDriver()->commitTransaction();
  638. }
  639. if ($this->isSavePointsEnabled()) {
  640. $this->releaseSavePoint((string)$this->_transactionLevel);
  641. }
  642. $this->_transactionLevel--;
  643. return true;
  644. }
  645. /**
  646. * Rollback current transaction.
  647. *
  648. * @param bool|null $toBeginning Whether the transaction should be rolled back to the
  649. * beginning of it. Defaults to false if using savepoints, or true if not.
  650. * @return bool
  651. */
  652. public function rollback(?bool $toBeginning = null): bool
  653. {
  654. if (!$this->_transactionStarted) {
  655. return false;
  656. }
  657. $useSavePoint = $this->isSavePointsEnabled();
  658. if ($toBeginning === null) {
  659. $toBeginning = !$useSavePoint;
  660. }
  661. if ($this->_transactionLevel === 0 || $toBeginning) {
  662. $this->_transactionLevel = 0;
  663. $this->_transactionStarted = false;
  664. $this->nestedTransactionRollbackException = null;
  665. if ($this->_logQueries) {
  666. $this->log('ROLLBACK');
  667. }
  668. $this->getDriver()->rollbackTransaction();
  669. return true;
  670. }
  671. $savePoint = $this->_transactionLevel--;
  672. if ($useSavePoint) {
  673. $this->rollbackSavepoint($savePoint);
  674. } elseif ($this->nestedTransactionRollbackException === null) {
  675. $this->nestedTransactionRollbackException = new NestedTransactionRollbackException();
  676. }
  677. return true;
  678. }
  679. /**
  680. * Enables/disables the usage of savepoints, enables only if driver the allows it.
  681. *
  682. * If you are trying to enable this feature, make sure you check
  683. * `isSavePointsEnabled()` to verify that savepoints were enabled successfully.
  684. *
  685. * @param bool $enable Whether save points should be used.
  686. * @return $this
  687. */
  688. public function enableSavePoints(bool $enable = true)
  689. {
  690. if ($enable === false) {
  691. $this->_useSavePoints = false;
  692. } else {
  693. $this->_useSavePoints = $this->getDriver()->supports(DriverInterface::FEATURE_SAVEPOINT);
  694. }
  695. return $this;
  696. }
  697. /**
  698. * Disables the usage of savepoints.
  699. *
  700. * @return $this
  701. */
  702. public function disableSavePoints()
  703. {
  704. $this->_useSavePoints = false;
  705. return $this;
  706. }
  707. /**
  708. * Returns whether this connection is using savepoints for nested transactions
  709. *
  710. * @return bool true if enabled, false otherwise
  711. */
  712. public function isSavePointsEnabled(): bool
  713. {
  714. return $this->_useSavePoints;
  715. }
  716. /**
  717. * Creates a new save point for nested transactions.
  718. *
  719. * @param string|int $name Save point name or id
  720. * @return void
  721. */
  722. public function createSavePoint($name): void
  723. {
  724. $this->execute($this->getDriver()->savePointSQL($name))->closeCursor();
  725. }
  726. /**
  727. * Releases a save point by its name.
  728. *
  729. * @param string|int $name Save point name or id
  730. * @return void
  731. */
  732. public function releaseSavePoint($name): void
  733. {
  734. $sql = $this->getDriver()->releaseSavePointSQL($name);
  735. if ($sql) {
  736. $this->execute($sql)->closeCursor();
  737. }
  738. }
  739. /**
  740. * Rollback a save point by its name.
  741. *
  742. * @param string|int $name Save point name or id
  743. * @return void
  744. */
  745. public function rollbackSavepoint($name): void
  746. {
  747. $this->execute($this->getDriver()->rollbackSavePointSQL($name))->closeCursor();
  748. }
  749. /**
  750. * Run driver specific SQL to disable foreign key checks.
  751. *
  752. * @return void
  753. */
  754. public function disableForeignKeys(): void
  755. {
  756. $this->getDisconnectRetry()->run(function (): void {
  757. $this->execute($this->getDriver()->disableForeignKeySQL())->closeCursor();
  758. });
  759. }
  760. /**
  761. * Run driver specific SQL to enable foreign key checks.
  762. *
  763. * @return void
  764. */
  765. public function enableForeignKeys(): void
  766. {
  767. $this->getDisconnectRetry()->run(function (): void {
  768. $this->execute($this->getDriver()->enableForeignKeySQL())->closeCursor();
  769. });
  770. }
  771. /**
  772. * Returns whether the driver supports adding or dropping constraints
  773. * to already created tables.
  774. *
  775. * @return bool true if driver supports dynamic constraints
  776. * @deprecated 4.3.0 Fixtures no longer dynamically drop and create constraints.
  777. */
  778. public function supportsDynamicConstraints(): bool
  779. {
  780. return $this->getDriver()->supportsDynamicConstraints();
  781. }
  782. /**
  783. * @inheritDoc
  784. */
  785. public function transactional(callable $callback)
  786. {
  787. $this->begin();
  788. try {
  789. $result = $callback($this);
  790. } catch (Throwable $e) {
  791. $this->rollback(false);
  792. throw $e;
  793. }
  794. if ($result === false) {
  795. $this->rollback(false);
  796. return false;
  797. }
  798. try {
  799. $this->commit();
  800. } catch (NestedTransactionRollbackException $e) {
  801. $this->rollback(false);
  802. throw $e;
  803. }
  804. return $result;
  805. }
  806. /**
  807. * Returns whether some nested transaction has been already rolled back.
  808. *
  809. * @return bool
  810. */
  811. protected function wasNestedTransactionRolledback(): bool
  812. {
  813. return $this->nestedTransactionRollbackException instanceof NestedTransactionRollbackException;
  814. }
  815. /**
  816. * @inheritDoc
  817. */
  818. public function disableConstraints(callable $callback)
  819. {
  820. return $this->getDisconnectRetry()->run(function () use ($callback) {
  821. $this->disableForeignKeys();
  822. try {
  823. $result = $callback($this);
  824. } finally {
  825. $this->enableForeignKeys();
  826. }
  827. return $result;
  828. });
  829. }
  830. /**
  831. * Checks if a transaction is running.
  832. *
  833. * @return bool True if a transaction is running else false.
  834. */
  835. public function inTransaction(): bool
  836. {
  837. return $this->_transactionStarted;
  838. }
  839. /**
  840. * Quotes value to be used safely in database query.
  841. *
  842. * This uses `PDO::quote()` and requires `supportsQuoting()` to work.
  843. *
  844. * @param mixed $value The value to quote.
  845. * @param \Cake\Database\TypeInterface|string|int $type Type to be used for determining kind of quoting to perform
  846. * @return string Quoted value
  847. * @deprecated 4.5.0 Use getDriver()->quote() instead.
  848. */
  849. public function quote($value, $type = 'string'): string
  850. {
  851. deprecationWarning('Use getDriver()->quote() instead.');
  852. [$value, $type] = $this->cast($value, $type);
  853. return $this->getDriver()->quote($value, $type);
  854. }
  855. /**
  856. * Checks if using `quote()` is supported.
  857. *
  858. * This is not required to use `quoteIdentifier()`.
  859. *
  860. * @return bool
  861. * @deprecated 4.5.0 Use getDriver()->supportsQuoting() instead.
  862. */
  863. public function supportsQuoting(): bool
  864. {
  865. deprecationWarning('Use getDriver()->supportsQuoting() instead.');
  866. return $this->getDriver()->supports(DriverInterface::FEATURE_QUOTE);
  867. }
  868. /**
  869. * Quotes a database identifier (a column name, table name, etc..) to
  870. * be used safely in queries without the risk of using reserved words.
  871. *
  872. * This does not require `supportsQuoting()` to work.
  873. *
  874. * @param string $identifier The identifier to quote.
  875. * @return string
  876. * @deprecated 4.5.0 Use getDriver()->quoteIdentifier() instead.
  877. */
  878. public function quoteIdentifier(string $identifier): string
  879. {
  880. deprecationWarning('Use getDriver()->quoteIdentifier() instead.');
  881. return $this->getDriver()->quoteIdentifier($identifier);
  882. }
  883. /**
  884. * Enables or disables metadata caching for this connection
  885. *
  886. * Changing this setting will not modify existing schema collections objects.
  887. *
  888. * @param string|bool $cache Either boolean false to disable metadata caching, or
  889. * true to use `_cake_model_` or the name of the cache config to use.
  890. * @return void
  891. */
  892. public function cacheMetadata($cache): void
  893. {
  894. $this->_schemaCollection = null;
  895. $this->_config['cacheMetadata'] = $cache;
  896. if (is_string($cache)) {
  897. $this->cacher = null;
  898. }
  899. }
  900. /**
  901. * @inheritDoc
  902. */
  903. public function setCacher(CacheInterface $cacher)
  904. {
  905. $this->cacher = $cacher;
  906. return $this;
  907. }
  908. /**
  909. * @inheritDoc
  910. */
  911. public function getCacher(): CacheInterface
  912. {
  913. if ($this->cacher !== null) {
  914. return $this->cacher;
  915. }
  916. $configName = $this->_config['cacheMetadata'] ?? '_cake_model_';
  917. if (!is_string($configName)) {
  918. $configName = '_cake_model_';
  919. }
  920. if (!class_exists(Cache::class)) {
  921. throw new RuntimeException(
  922. 'To use caching you must either set a cacher using Connection::setCacher()' .
  923. ' or require the cakephp/cache package in your composer config.'
  924. );
  925. }
  926. return $this->cacher = Cache::pool($configName);
  927. }
  928. /**
  929. * Enable/disable query logging
  930. *
  931. * @param bool $enable Enable/disable query logging
  932. * @return $this
  933. * @deprecated 4.5.0 Connection logging is moving to the driver in 5.x
  934. */
  935. public function enableQueryLogging(bool $enable = true)
  936. {
  937. $this->_logQueries = $enable;
  938. return $this;
  939. }
  940. /**
  941. * Disable query logging
  942. *
  943. * @return $this
  944. * @deprecated 4.5.0 Connection logging is moving to the driver in 5.x
  945. */
  946. public function disableQueryLogging()
  947. {
  948. $this->_logQueries = false;
  949. return $this;
  950. }
  951. /**
  952. * Check if query logging is enabled.
  953. *
  954. * @return bool
  955. * @deprecated 4.5.0 Connection logging is moving to the driver in 5.x
  956. */
  957. public function isQueryLoggingEnabled(): bool
  958. {
  959. return $this->_logQueries;
  960. }
  961. /**
  962. * Sets a logger
  963. *
  964. * @param \Psr\Log\LoggerInterface $logger Logger object
  965. * @return $this
  966. * @psalm-suppress ImplementedReturnTypeMismatch
  967. */
  968. public function setLogger(LoggerInterface $logger)
  969. {
  970. $this->_logger = $logger;
  971. return $this;
  972. }
  973. /**
  974. * Gets the logger object
  975. *
  976. * @return \Psr\Log\LoggerInterface logger instance
  977. */
  978. public function getLogger(): LoggerInterface
  979. {
  980. if ($this->_logger !== null) {
  981. return $this->_logger;
  982. }
  983. if (!class_exists(BaseLog::class)) {
  984. throw new RuntimeException(
  985. 'For logging you must either set a logger using Connection::setLogger()' .
  986. ' or require the cakephp/log package in your composer config.'
  987. );
  988. }
  989. return $this->_logger = new QueryLogger(['connection' => $this->configName()]);
  990. }
  991. /**
  992. * Logs a Query string using the configured logger object.
  993. *
  994. * @param string $sql string to be logged
  995. * @return void
  996. */
  997. public function log(string $sql): void
  998. {
  999. $query = new LoggedQuery();
  1000. $query->query = $sql;
  1001. $this->getLogger()->debug((string)$query, ['query' => $query]);
  1002. }
  1003. /**
  1004. * Returns a new statement object that will log the activity
  1005. * for the passed original statement instance.
  1006. *
  1007. * @param \Cake\Database\StatementInterface $statement the instance to be decorated
  1008. * @return \Cake\Database\Log\LoggingStatement
  1009. */
  1010. protected function _newLogger(StatementInterface $statement): LoggingStatement
  1011. {
  1012. $log = new LoggingStatement($statement, $this->getDriver());
  1013. $log->setLogger($this->getLogger());
  1014. return $log;
  1015. }
  1016. /**
  1017. * Returns an array that can be used to describe the internal state of this
  1018. * object.
  1019. *
  1020. * @return array<string, mixed>
  1021. */
  1022. public function __debugInfo(): array
  1023. {
  1024. $secrets = [
  1025. 'password' => '*****',
  1026. 'username' => '*****',
  1027. 'host' => '*****',
  1028. 'database' => '*****',
  1029. 'port' => '*****',
  1030. ];
  1031. $replace = array_intersect_key($secrets, $this->_config);
  1032. $config = $replace + $this->_config;
  1033. if (isset($config['read'])) {
  1034. /** @psalm-suppress PossiblyInvalidArgument */
  1035. $config['read'] = array_intersect_key($secrets, $config['read']) + $config['read'];
  1036. }
  1037. if (isset($config['write'])) {
  1038. /** @psalm-suppress PossiblyInvalidArgument */
  1039. $config['write'] = array_intersect_key($secrets, $config['write']) + $config['write'];
  1040. }
  1041. return [
  1042. 'config' => $config,
  1043. 'readDriver' => $this->readDriver,
  1044. 'writeDriver' => $this->writeDriver,
  1045. 'transactionLevel' => $this->_transactionLevel,
  1046. 'transactionStarted' => $this->_transactionStarted,
  1047. 'useSavePoints' => $this->_useSavePoints,
  1048. 'logQueries' => $this->_logQueries,
  1049. 'logger' => $this->_logger,
  1050. ];
  1051. }
  1052. }