Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Db.php 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Exception;
  11. use Piwik\Db\Adapter;
  12. use Piwik\Tracker;
  13. /**
  14. * Contains SQL related helper functions for Piwik's MySQL database.
  15. *
  16. * Plugins should always use this class to execute SQL against the database.
  17. *
  18. * ### Examples
  19. *
  20. * $rows = Db::fetchAll("SELECT col1, col2 FROM mytable WHERE thing = ?", array('thingvalue'));
  21. * foreach ($rows as $row) {
  22. * doSomething($row['col1'], $row['col2']);
  23. * }
  24. *
  25. * $value = Db::fetchOne("SELECT MAX(col1) FROM mytable");
  26. * doSomethingElse($value);
  27. *
  28. * Db::query("DELETE FROM mytable WHERE id < ?", array(23));
  29. *
  30. * @api
  31. */
  32. class Db
  33. {
  34. private static $connection = null;
  35. /**
  36. * Returns the database connection and creates it if it hasn't been already.
  37. *
  38. * @return \Piwik\Tracker\Db|\Piwik\Db\AdapterInterface|\Piwik\Db
  39. */
  40. public static function get()
  41. {
  42. if (SettingsServer::isTrackerApiRequest()) {
  43. return Tracker::getDatabase();
  44. }
  45. if (self::$connection === null) {
  46. self::createDatabaseObject();
  47. }
  48. return self::$connection;
  49. }
  50. public static function getDatabaseConfig($dbConfig = null)
  51. {
  52. $config = Config::getInstance();
  53. if (is_null($dbConfig)) {
  54. $dbConfig = $config->database;
  55. }
  56. /**
  57. * Triggered before a database connection is established.
  58. *
  59. * This event can be used to change the settings used to establish a connection.
  60. *
  61. * @param array *$dbInfos Reference to an array containing database connection info,
  62. * including:
  63. *
  64. * - **host**: The host name or IP address to the MySQL database.
  65. * - **username**: The username to use when connecting to the
  66. * database.
  67. * - **password**: The password to use when connecting to the
  68. * database.
  69. * - **dbname**: The name of the Piwik MySQL database.
  70. * - **port**: The MySQL database port to use.
  71. * - **adapter**: either `'PDO\MYSQL'` or `'MYSQLI'`
  72. * - **type**: The MySQL engine to use, for instance 'InnoDB'
  73. */
  74. Piwik::postEvent('Db.getDatabaseConfig', array(&$dbConfig));
  75. $dbConfig['profiler'] = $config->Debug['enable_sql_profiler'];
  76. return $dbConfig;
  77. }
  78. /**
  79. * Connects to the database.
  80. *
  81. * Shouldn't be called directly, use {@link get()} instead.
  82. *
  83. * @param array|null $dbConfig Connection parameters in an array. Defaults to the `[database]`
  84. * INI config section.
  85. */
  86. public static function createDatabaseObject($dbConfig = null)
  87. {
  88. $dbConfig = self::getDatabaseConfig($dbConfig);
  89. $db = @Adapter::factory($dbConfig['adapter'], $dbConfig);
  90. self::$connection = $db;
  91. }
  92. /**
  93. * Disconnects and destroys the database connection.
  94. *
  95. * For tests.
  96. */
  97. public static function destroyDatabaseObject()
  98. {
  99. DbHelper::disconnectDatabase();
  100. self::$connection = null;
  101. }
  102. /**
  103. * Executes an unprepared SQL query. Recommended for DDL statements like `CREATE`,
  104. * `DROP` and `ALTER`. The return value is DBMS-specific. For MySQLI, it returns the
  105. * number of rows affected. For PDO, it returns a
  106. * [Zend_Db_Statement](http://framework.zend.com/manual/1.12/en/zend.db.statement.html) object.
  107. *
  108. * @param string $sql The SQL query.
  109. * @throws \Exception If there is an error in the SQL.
  110. * @return integer|\Zend_Db_Statement
  111. */
  112. public static function exec($sql)
  113. {
  114. /** @var \Zend_Db_Adapter_Abstract $db */
  115. $db = self::get();
  116. $profiler = $db->getProfiler();
  117. $q = $profiler->queryStart($sql, \Zend_Db_Profiler::INSERT);
  118. try {
  119. self::logSql(__FUNCTION__, $sql);
  120. $return = self::get()->exec($sql);
  121. } catch (Exception $ex) {
  122. self::logExtraInfoIfDeadlock($ex);
  123. throw $ex;
  124. }
  125. $profiler->queryEnd($q);
  126. return $return;
  127. }
  128. /**
  129. * Executes an SQL query and returns the [Zend_Db_Statement](http://framework.zend.com/manual/1.12/en/zend.db.statement.html)
  130. * for the query.
  131. *
  132. * This method is meant for non-query SQL statements like `INSERT` and `UPDATE. If you want to fetch
  133. * data from the DB you should use one of the fetch... functions.
  134. *
  135. * @param string $sql The SQL query.
  136. * @param array $parameters Parameters to bind in the query, eg, `array(param1 => value1, param2 => value2)`.
  137. * @throws \Exception If there is a problem with the SQL or bind parameters.
  138. * @return \Zend_Db_Statement
  139. */
  140. public static function query($sql, $parameters = array())
  141. {
  142. try {
  143. self::logSql(__FUNCTION__, $sql, $parameters);
  144. return self::get()->query($sql, $parameters);
  145. } catch (Exception $ex) {
  146. self::logExtraInfoIfDeadlock($ex);
  147. throw $ex;
  148. }
  149. }
  150. /**
  151. * Executes an SQL `SELECT` statement and returns all fetched rows from the result set.
  152. *
  153. * @param string $sql The SQL query.
  154. * @param array $parameters Parameters to bind in the query, eg, `array(param1 => value1, param2 => value2)`.
  155. * @throws \Exception If there is a problem with the SQL or bind parameters.
  156. * @return array The fetched rows, each element is an associative array mapping column names
  157. * with column values.
  158. */
  159. public static function fetchAll($sql, $parameters = array())
  160. {
  161. try {
  162. self::logSql(__FUNCTION__, $sql, $parameters);
  163. return self::get()->fetchAll($sql, $parameters);
  164. } catch (Exception $ex) {
  165. self::logExtraInfoIfDeadlock($ex);
  166. throw $ex;
  167. }
  168. }
  169. /**
  170. * Executes an SQL `SELECT` statement and returns the first row of the result set.
  171. *
  172. * @param string $sql The SQL query.
  173. * @param array $parameters Parameters to bind in the query, eg, `array(param1 => value1, param2 => value2)`.
  174. * @throws \Exception If there is a problem with the SQL or bind parameters.
  175. * @return array The fetched row, each element is an associative array mapping column names
  176. * with column values.
  177. */
  178. public static function fetchRow($sql, $parameters = array())
  179. {
  180. try {
  181. self::logSql(__FUNCTION__, $sql, $parameters);
  182. return self::get()->fetchRow($sql, $parameters);
  183. } catch (Exception $ex) {
  184. self::logExtraInfoIfDeadlock($ex);
  185. throw $ex;
  186. }
  187. }
  188. /**
  189. * Executes an SQL `SELECT` statement and returns the first column value of the first
  190. * row in the result set.
  191. *
  192. * @param string $sql The SQL query.
  193. * @param array $parameters Parameters to bind in the query, eg, `array(param1 => value1, param2 => value2)`.
  194. * @throws \Exception If there is a problem with the SQL or bind parameters.
  195. * @return string
  196. */
  197. public static function fetchOne($sql, $parameters = array())
  198. {
  199. try {
  200. self::logSql(__FUNCTION__, $sql, $parameters);
  201. return self::get()->fetchOne($sql, $parameters);
  202. } catch (Exception $ex) {
  203. self::logExtraInfoIfDeadlock($ex);
  204. throw $ex;
  205. }
  206. }
  207. /**
  208. * Executes an SQL `SELECT` statement and returns the entire result set indexed by the first
  209. * selected field.
  210. *
  211. * @param string $sql The SQL query.
  212. * @param array $parameters Parameters to bind in the query, eg, `array(param1 => value1, param2 => value2)`.
  213. * @throws \Exception If there is a problem with the SQL or bind parameters.
  214. * @return array eg,
  215. * ```
  216. * array('col1value1' => array('col2' => '...', 'col3' => ...),
  217. * 'col1value2' => array('col2' => '...', 'col3' => ...))
  218. * ```
  219. */
  220. public static function fetchAssoc($sql, $parameters = array())
  221. {
  222. try {
  223. self::logSql(__FUNCTION__, $sql, $parameters);
  224. return self::get()->fetchAssoc($sql, $parameters);
  225. } catch (Exception $ex) {
  226. self::logExtraInfoIfDeadlock($ex);
  227. throw $ex;
  228. }
  229. }
  230. /**
  231. * Deletes all desired rows in a table, while using a limit. This function will execute many
  232. * DELETE queries until there are no more rows to delete.
  233. *
  234. * Use this function when you need to delete many thousands of rows from a table without
  235. * locking the table for too long.
  236. *
  237. * **Example**
  238. *
  239. * // delete all visit rows whose ID is less than a certain value, 100000 rows at a time
  240. * $idVisit = // ...
  241. * Db::deleteAllRows(Common::prefixTable('log_visit'), "WHERE idvisit <= ?", "idvisit ASC", 100000, array($idVisit));
  242. *
  243. * @param string $table The name of the table to delete from. Must be prefixed (see {@link Piwik\Common::prefixTable()}).
  244. * @param string $where The where clause of the query. Must include the WHERE keyword.
  245. * @param $orderBy The column to order by and the order by direction, eg, `idvisit ASC`.
  246. * @param int $maxRowsPerQuery The maximum number of rows to delete per `DELETE` query.
  247. * @param array $parameters Parameters to bind for each query.
  248. * @return int The total number of rows deleted.
  249. */
  250. public static function deleteAllRows($table, $where, $orderBy, $maxRowsPerQuery = 100000, $parameters = array())
  251. {
  252. $orderByClause = $orderBy ? "ORDER BY $orderBy" : "";
  253. $sql = "DELETE FROM $table
  254. $where
  255. $orderByClause
  256. LIMIT " . (int)$maxRowsPerQuery;
  257. // delete rows w/ a limit
  258. $totalRowsDeleted = 0;
  259. do {
  260. $rowsDeleted = self::query($sql, $parameters)->rowCount();
  261. $totalRowsDeleted += $rowsDeleted;
  262. } while ($rowsDeleted >= $maxRowsPerQuery);
  263. return $totalRowsDeleted;
  264. }
  265. /**
  266. * Runs an `OPTIMIZE TABLE` query on the supplied table or tables.
  267. *
  268. * Tables will only be optimized if the `[General] enable_sql_optimize_queries` INI config option is
  269. * set to **1**.
  270. *
  271. * @param string|array $tables The name of the table to optimize or an array of tables to optimize.
  272. * Table names must be prefixed (see {@link Piwik\Common::prefixTable()}).
  273. * @return \Zend_Db_Statement
  274. */
  275. public static function optimizeTables($tables)
  276. {
  277. $optimize = Config::getInstance()->General['enable_sql_optimize_queries'];
  278. if (empty($optimize)) {
  279. return;
  280. }
  281. if (empty($tables)) {
  282. return false;
  283. }
  284. if (!is_array($tables)) {
  285. $tables = array($tables);
  286. }
  287. // filter out all InnoDB tables
  288. $myisamDbTables = array();
  289. foreach (Db::fetchAll("SHOW TABLE STATUS") as $row) {
  290. if (strtolower($row['Engine']) == 'myisam'
  291. && in_array($row['Name'], $tables)
  292. ) {
  293. $myisamDbTables[] = $row['Name'];
  294. }
  295. }
  296. if (empty($myisamDbTables)) {
  297. return false;
  298. }
  299. // optimize the tables
  300. return self::query("OPTIMIZE TABLE " . implode(',', $myisamDbTables));
  301. }
  302. /**
  303. * Drops the supplied table or tables.
  304. *
  305. * @param string|array $tables The name of the table to drop or an array of table names to drop.
  306. * Table names must be prefixed (see {@link Piwik\Common::prefixTable()}).
  307. * @return \Zend_Db_Statement
  308. */
  309. public static function dropTables($tables)
  310. {
  311. if (!is_array($tables)) {
  312. $tables = array($tables);
  313. }
  314. return self::query("DROP TABLE `" . implode('`,`', $tables) . "`");
  315. }
  316. /**
  317. * Drops all tables
  318. */
  319. public static function dropAllTables()
  320. {
  321. $tablesAlreadyInstalled = DbHelper::getTablesInstalled();
  322. self::dropTables($tablesAlreadyInstalled);
  323. }
  324. /**
  325. * Get columns information from table
  326. *
  327. * @param string|array $table The name of the table you want to get the columns definition for.
  328. * @return \Zend_Db_Statement
  329. */
  330. public static function getColumnNamesFromTable($table)
  331. {
  332. $columns = self::fetchAll("SHOW COLUMNS FROM `" . $table . "`");
  333. $columnNames = array();
  334. foreach ($columns as $column) {
  335. $columnNames[] = $column['Field'];
  336. }
  337. return $columnNames;
  338. }
  339. /**
  340. * Locks the supplied table or tables.
  341. *
  342. * **NOTE:** Piwik does not require the `LOCK TABLES` privilege to be available. Piwik
  343. * should still work if it has not been granted.
  344. *
  345. * @param string|array $tablesToRead The table or tables to obtain 'read' locks on. Table names must
  346. * be prefixed (see {@link Piwik\Common::prefixTable()}).
  347. * @param string|array $tablesToWrite The table or tables to obtain 'write' locks on. Table names must
  348. * be prefixed (see {@link Piwik\Common::prefixTable()}).
  349. * @return \Zend_Db_Statement
  350. */
  351. public static function lockTables($tablesToRead, $tablesToWrite = array())
  352. {
  353. if (!is_array($tablesToRead)) {
  354. $tablesToRead = array($tablesToRead);
  355. }
  356. if (!is_array($tablesToWrite)) {
  357. $tablesToWrite = array($tablesToWrite);
  358. }
  359. $lockExprs = array();
  360. foreach ($tablesToWrite as $table) {
  361. $lockExprs[] = $table . " WRITE";
  362. }
  363. foreach ($tablesToRead as $table) {
  364. $lockExprs[] = $table . " READ";
  365. }
  366. return self::exec("LOCK TABLES " . implode(', ', $lockExprs));
  367. }
  368. /**
  369. * Releases all table locks.
  370. *
  371. * **NOTE:** Piwik does not require the `LOCK TABLES` privilege to be available. Piwik
  372. * should still work if it has not been granted.
  373. *
  374. * @return \Zend_Db_Statement
  375. */
  376. public static function unlockAllTables()
  377. {
  378. return self::exec("UNLOCK TABLES");
  379. }
  380. /**
  381. * Performs a `SELECT` statement on a table one chunk at a time and returns the first
  382. * successfully fetched value.
  383. *
  384. * This function will execute a query on one set of rows in a table. If nothing
  385. * is fetched, it will execute the query on the next set of rows and so on until
  386. * the query returns a value.
  387. *
  388. * This function will break up a `SELECT into several smaller `SELECT`s and
  389. * should be used when performing a `SELECT` that can take a long time to finish.
  390. * Using several smaller `SELECT`s will ensure that the table will not be locked
  391. * for too long.
  392. *
  393. * **Example**
  394. *
  395. * // find the most recent visit that is older than a certain date
  396. * $dateStart = // ...
  397. * $sql = "SELECT idvisit
  398. * FROM $logVisit
  399. * WHERE '$dateStart' > visit_last_action_time
  400. * AND idvisit <= ?
  401. * AND idvisit > ?
  402. * ORDER BY idvisit DESC
  403. * LIMIT 1";
  404. *
  405. * // since visits
  406. * return Db::segmentedFetchFirst($sql, $maxIdVisit, 0, -self::$selectSegmentSize);
  407. *
  408. * @param string $sql The SQL to perform. The last two conditions of the `WHERE`
  409. * expression must be as follows: `'id >= ? AND id < ?'` where
  410. * **id** is the int id of the table.
  411. * @param int $first The minimum ID to loop from.
  412. * @param int $last The maximum ID to loop to.
  413. * @param int $step The maximum number of rows to scan in one query.
  414. * @param array $params Parameters to bind in the query, eg, `array(param1 => value1, param2 => value2)`
  415. *
  416. * @return string
  417. */
  418. public static function segmentedFetchFirst($sql, $first, $last, $step, $params = array())
  419. {
  420. $result = false;
  421. if ($step > 0) {
  422. for ($i = $first; $result === false && $i <= $last; $i += $step) {
  423. $result = self::fetchOne($sql, array_merge($params, array($i, $i + $step)));
  424. }
  425. } else {
  426. for ($i = $first; $result === false && $i >= $last; $i += $step) {
  427. $result = self::fetchOne($sql, array_merge($params, array($i, $i + $step)));
  428. }
  429. }
  430. return $result;
  431. }
  432. /**
  433. * Performs a `SELECT` on a table one chunk at a time and returns an array
  434. * of every fetched value.
  435. *
  436. * This function will break up a `SELECT` query into several smaller queries by
  437. * using only a limited number of rows at a time. It will accumulate the results
  438. * of each smaller query and return the result.
  439. *
  440. * This function should be used when performing a `SELECT` that can
  441. * take a long time to finish. Using several smaller queries will ensure that
  442. * the table will not be locked for too long.
  443. *
  444. * @param string $sql The SQL to perform. The last two conditions of the `WHERE`
  445. * expression must be as follows: `'id >= ? AND id < ?'` where
  446. * **id** is the int id of the table.
  447. * @param int $first The minimum ID to loop from.
  448. * @param int $last The maximum ID to loop to.
  449. * @param int $step The maximum number of rows to scan in one query.
  450. * @param array $params Parameters to bind in the query, `array(param1 => value1, param2 => value2)`
  451. * @return array An array of primitive values.
  452. */
  453. public static function segmentedFetchOne($sql, $first, $last, $step, $params = array())
  454. {
  455. $result = array();
  456. if ($step > 0) {
  457. for ($i = $first; $i <= $last; $i += $step) {
  458. $result[] = self::fetchOne($sql, array_merge($params, array($i, $i + $step)));
  459. }
  460. } else {
  461. for ($i = $first; $i >= $last; $i += $step) {
  462. $result[] = self::fetchOne($sql, array_merge($params, array($i, $i + $step)));
  463. }
  464. }
  465. return $result;
  466. }
  467. /**
  468. * Performs a SELECT on a table one chunk at a time and returns an array
  469. * of every fetched row.
  470. *
  471. * This function will break up a `SELECT` query into several smaller queries by
  472. * using only a limited number of rows at a time. It will accumulate the results
  473. * of each smaller query and return the result.
  474. *
  475. * This function should be used when performing a `SELECT` that can
  476. * take a long time to finish. Using several smaller queries will ensure that
  477. * the table will not be locked for too long.
  478. *
  479. * @param string $sql The SQL to perform. The last two conditions of the `WHERE`
  480. * expression must be as follows: `'id >= ? AND id < ?'` where
  481. * **id** is the int id of the table.
  482. * @param int $first The minimum ID to loop from.
  483. * @param int $last The maximum ID to loop to.
  484. * @param int $step The maximum number of rows to scan in one query.
  485. * @param array $params Parameters to bind in the query, array( param1 => value1, param2 => value2)
  486. * @return array An array of rows that includes the result set of every smaller
  487. * query.
  488. */
  489. public static function segmentedFetchAll($sql, $first, $last, $step, $params = array())
  490. {
  491. $result = array();
  492. if ($step > 0) {
  493. for ($i = $first; $i <= $last; $i += $step) {
  494. $currentParams = array_merge($params, array($i, $i + $step));
  495. $result = array_merge($result, self::fetchAll($sql, $currentParams));
  496. }
  497. } else {
  498. for ($i = $first; $i >= $last; $i += $step) {
  499. $currentParams = array_merge($params, array($i, $i + $step));
  500. $result = array_merge($result, self::fetchAll($sql, $currentParams));
  501. }
  502. }
  503. return $result;
  504. }
  505. /**
  506. * Performs a `UPDATE` or `DELETE` statement on a table one chunk at a time.
  507. *
  508. * This function will break up a query into several smaller queries by
  509. * using only a limited number of rows at a time.
  510. *
  511. * This function should be used when executing a non-query statement will
  512. * take a long time to finish. Using several smaller queries will ensure that
  513. * the table will not be locked for too long.
  514. *
  515. * @param string $sql The SQL to perform. The last two conditions of the `WHERE`
  516. * expression must be as follows: `'id >= ? AND id < ?'` where
  517. * **id** is the int id of the table.
  518. * @param int $first The minimum ID to loop from.
  519. * @param int $last The maximum ID to loop to.
  520. * @param int $step The maximum number of rows to scan in one query.
  521. * @param array $params Parameters to bind in the query, `array(param1 => value1, param2 => value2)`
  522. */
  523. public static function segmentedQuery($sql, $first, $last, $step, $params = array())
  524. {
  525. if ($step > 0) {
  526. for ($i = $first; $i <= $last; $i += $step) {
  527. $currentParams = array_merge($params, array($i, $i + $step));
  528. self::query($sql, $currentParams);
  529. }
  530. } else {
  531. for ($i = $first; $i >= $last; $i += $step) {
  532. $currentParams = array_merge($params, array($i, $i + $step));
  533. self::query($sql, $currentParams);
  534. }
  535. }
  536. }
  537. /**
  538. * Returns `true` if a table in the database, `false` if otherwise.
  539. *
  540. * @param string $tableName The name of the table to check for. Must be prefixed.
  541. * @return bool
  542. */
  543. public static function tableExists($tableName)
  544. {
  545. return self::query("SHOW TABLES LIKE ?", $tableName)->rowCount() > 0;
  546. }
  547. /**
  548. * Attempts to get a named lock. This function uses a timeout of 1s, but will
  549. * retry a set number of times.
  550. *
  551. * @param string $lockName The lock name.
  552. * @param int $maxRetries The max number of times to retry.
  553. * @return bool `true` if the lock was obtained, `false` if otherwise.
  554. */
  555. public static function getDbLock($lockName, $maxRetries = 30)
  556. {
  557. /*
  558. * the server (e.g., shared hosting) may have a low wait timeout
  559. * so instead of a single GET_LOCK() with a 30 second timeout,
  560. * we use a 1 second timeout and loop, to avoid losing our MySQL
  561. * connection
  562. */
  563. $sql = 'SELECT GET_LOCK(?, 1)';
  564. $db = self::get();
  565. while ($maxRetries > 0) {
  566. if ($db->fetchOne($sql, array($lockName)) == '1') {
  567. return true;
  568. }
  569. $maxRetries--;
  570. }
  571. return false;
  572. }
  573. /**
  574. * Releases a named lock.
  575. *
  576. * @param string $lockName The lock name.
  577. * @return bool `true` if the lock was released, `false` if otherwise.
  578. */
  579. public static function releaseDbLock($lockName)
  580. {
  581. $sql = 'SELECT RELEASE_LOCK(?)';
  582. $db = self::get();
  583. return $db->fetchOne($sql, array($lockName)) == '1';
  584. }
  585. /**
  586. * Cached result of isLockprivilegeGranted function.
  587. *
  588. * Public so tests can simulate the situation where the lock tables privilege isn't granted.
  589. *
  590. * @var bool
  591. * @ignore
  592. */
  593. public static $lockPrivilegeGranted = null;
  594. /**
  595. * Checks whether the database user is allowed to lock tables.
  596. *
  597. * @return bool
  598. */
  599. public static function isLockPrivilegeGranted()
  600. {
  601. if (is_null(self::$lockPrivilegeGranted)) {
  602. try {
  603. Db::lockTables(Common::prefixTable('log_visit'));
  604. Db::unlockAllTables();
  605. self::$lockPrivilegeGranted = true;
  606. } catch (Exception $ex) {
  607. self::$lockPrivilegeGranted = false;
  608. }
  609. }
  610. return self::$lockPrivilegeGranted;
  611. }
  612. private static function logExtraInfoIfDeadlock($ex)
  613. {
  614. if (self::get()->isErrNo($ex, 1213)) {
  615. $deadlockInfo = self::fetchAll("SHOW ENGINE INNODB STATUS");
  616. // log using exception so backtrace appears in log output
  617. Log::debug(new Exception("Encountered deadlock: " . print_r($deadlockInfo, true)));
  618. }
  619. }
  620. private static function logSql($functionName, $sql, $parameters = array())
  621. {
  622. // NOTE: at the moment we dont log bind in order to avoid sensitive information leaks
  623. Log::verbose("Db::%s() executing SQL:\n%s", $functionName, $sql);
  624. }
  625. }