Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
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.

1376 lines
35 KiB

11 years ago
5 years ago
11 years ago
4 years ago
4 years ago
4 years ago
11 years ago
11 years ago
  1. <?php
  2. use Friendica\Core\L10n;
  3. use Friendica\Core\System;
  4. use Friendica\Database\DBM;
  5. use Friendica\Database\DBStructure;
  6. use Friendica\Util\DateTimeFormat;
  7. /**
  8. * @class MySQL database class
  9. *
  10. * This class is for the low level database stuff that does driver specific things.
  11. */
  12. class dba {
  13. public static $connected = false;
  14. private static $_server_info = '';
  15. private static $db;
  16. private static $driver;
  17. private static $error = false;
  18. private static $errorno = 0;
  19. private static $affected_rows = 0;
  20. private static $in_transaction = false;
  21. private static $relation = [];
  22. public static function connect($serveraddr, $user, $pass, $db, $install = false) {
  23. if (!is_null(self::$db)) {
  24. return true;
  25. }
  26. $a = get_app();
  27. $stamp1 = microtime(true);
  28. $serveraddr = trim($serveraddr);
  29. $serverdata = explode(':', $serveraddr);
  30. $server = $serverdata[0];
  31. if (count($serverdata) > 1) {
  32. $port = trim($serverdata[1]);
  33. }
  34. $server = trim($server);
  35. $user = trim($user);
  36. $pass = trim($pass);
  37. $db = trim($db);
  38. if (!(strlen($server) && strlen($user))) {
  39. return false;
  40. }
  41. if ($install) {
  42. // server has to be a non-empty string that is not 'localhost' and not an IP
  43. if (strlen($server) && ($server !== 'localhost') && filter_var($server, FILTER_VALIDATE_IP) === false) {
  44. if (! dns_get_record($server, DNS_A + DNS_CNAME)) {
  45. self::$error = L10n::t('Cannot locate DNS info for database server \'%s\'', $server);
  46. return false;
  47. }
  48. }
  49. }
  50. if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
  51. self::$driver = 'pdo';
  52. $connect = "mysql:host=".$server.";dbname=".$db;
  53. if (isset($port)) {
  54. $connect .= ";port=".$port;
  55. }
  56. if (isset($a->config["system"]["db_charset"])) {
  57. $connect .= ";charset=".$a->config["system"]["db_charset"];
  58. }
  59. try {
  60. self::$db = @new PDO($connect, $user, $pass);
  61. self::$connected = true;
  62. } catch (PDOException $e) {
  63. }
  64. }
  65. if (!self::$connected && class_exists('mysqli')) {
  66. self::$driver = 'mysqli';
  67. self::$db = @new mysqli($server, $user, $pass, $db, $port);
  68. if (!mysqli_connect_errno()) {
  69. self::$connected = true;
  70. if (isset($a->config["system"]["db_charset"])) {
  71. self::$db->set_charset($a->config["system"]["db_charset"]);
  72. }
  73. }
  74. }
  75. // No suitable SQL driver was found.
  76. if (!self::$connected) {
  77. self::$db = null;
  78. }
  79. $a->save_timestamp($stamp1, "network");
  80. return self::$connected;
  81. }
  82. /**
  83. * @brief Returns the MySQL server version string
  84. *
  85. * This function discriminate between the deprecated mysql API and the current
  86. * object-oriented mysqli API. Example of returned string: 5.5.46-0+deb8u1
  87. *
  88. * @return string
  89. */
  90. public static function server_info() {
  91. if (self::$_server_info == '') {
  92. switch (self::$driver) {
  93. case 'pdo':
  94. self::$_server_info = self::$db->getAttribute(PDO::ATTR_SERVER_VERSION);
  95. break;
  96. case 'mysqli':
  97. self::$_server_info = self::$db->server_info;
  98. break;
  99. }
  100. }
  101. return self::$_server_info;
  102. }
  103. /**
  104. * @brief Returns the selected database name
  105. *
  106. * @return string
  107. */
  108. public static function database_name() {
  109. $ret = self::p("SELECT DATABASE() AS `db`");
  110. $data = self::inArray($ret);
  111. return $data[0]['db'];
  112. }
  113. /**
  114. * @brief Analyze a database query and log this if some conditions are met.
  115. *
  116. * @param string $query The database query that will be analyzed
  117. */
  118. private static function logIndex($query) {
  119. $a = get_app();
  120. if (empty($a->config["system"]["db_log_index"])) {
  121. return;
  122. }
  123. // Don't explain an explain statement
  124. if (strtolower(substr($query, 0, 7)) == "explain") {
  125. return;
  126. }
  127. // Only do the explain on "select", "update" and "delete"
  128. if (!in_array(strtolower(substr($query, 0, 6)), ["select", "update", "delete"])) {
  129. return;
  130. }
  131. $r = self::p("EXPLAIN ".$query);
  132. if (!DBM::is_result($r)) {
  133. return;
  134. }
  135. $watchlist = explode(',', $a->config["system"]["db_log_index_watch"]);
  136. $blacklist = explode(',', $a->config["system"]["db_log_index_blacklist"]);
  137. while ($row = dba::fetch($r)) {
  138. if ((intval($a->config["system"]["db_loglimit_index"]) > 0)) {
  139. $log = (in_array($row['key'], $watchlist) &&
  140. ($row['rows'] >= intval($a->config["system"]["db_loglimit_index"])));
  141. } else {
  142. $log = false;
  143. }
  144. if ((intval($a->config["system"]["db_loglimit_index_high"]) > 0) && ($row['rows'] >= intval($a->config["system"]["db_loglimit_index_high"]))) {
  145. $log = true;
  146. }
  147. if (in_array($row['key'], $blacklist) || ($row['key'] == "")) {
  148. $log = false;
  149. }
  150. if ($log) {
  151. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  152. @file_put_contents($a->config["system"]["db_log_index"], DateTimeFormat::utcNow()."\t".
  153. $row['key']."\t".$row['rows']."\t".$row['Extra']."\t".
  154. basename($backtrace[1]["file"])."\t".
  155. $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
  156. substr($query, 0, 2000)."\n", FILE_APPEND);
  157. }
  158. }
  159. }
  160. public static function escape($str) {
  161. switch (self::$driver) {
  162. case 'pdo':
  163. return substr(@self::$db->quote($str, PDO::PARAM_STR), 1, -1);
  164. case 'mysqli':
  165. return @self::$db->real_escape_string($str);
  166. }
  167. }
  168. public static function connected() {
  169. $connected = false;
  170. switch (self::$driver) {
  171. case 'pdo':
  172. $r = dba::p("SELECT 1");
  173. if (DBM::is_result($r)) {
  174. $row = dba::inArray($r);
  175. $connected = ($row[0]['1'] == '1');
  176. }
  177. break;
  178. case 'mysqli':
  179. $connected = self::$db->ping();
  180. break;
  181. }
  182. return $connected;
  183. }
  184. /**
  185. * @brief Replaces ANY_VALUE() function by MIN() function,
  186. * if the database server does not support ANY_VALUE().
  187. *
  188. * Considerations for Standard SQL, or MySQL with ONLY_FULL_GROUP_BY (default since 5.7.5).
  189. * ANY_VALUE() is available from MySQL 5.7.5 https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html
  190. * A standard fall-back is to use MIN().
  191. *
  192. * @param string $sql An SQL string without the values
  193. * @return string The input SQL string modified if necessary.
  194. */
  195. public static function any_value_fallback($sql) {
  196. $server_info = self::server_info();
  197. if (version_compare($server_info, '5.7.5', '<') ||
  198. (stripos($server_info, 'MariaDB') !== false)) {
  199. $sql = str_ireplace('ANY_VALUE(', 'MIN(', $sql);
  200. }
  201. return $sql;
  202. }
  203. /**
  204. * @brief beautifies the query - useful for "SHOW PROCESSLIST"
  205. *
  206. * This is safe when we bind the parameters later.
  207. * The parameter values aren't part of the SQL.
  208. *
  209. * @param string $sql An SQL string without the values
  210. * @return string The input SQL string modified if necessary.
  211. */
  212. public static function clean_query($sql) {
  213. $search = ["\t", "\n", "\r", " "];
  214. $replace = [' ', ' ', ' ', ' '];
  215. do {
  216. $oldsql = $sql;
  217. $sql = str_replace($search, $replace, $sql);
  218. } while ($oldsql != $sql);
  219. return $sql;
  220. }
  221. /**
  222. * @brief Replaces the ? placeholders with the parameters in the $args array
  223. *
  224. * @param string $sql SQL query
  225. * @param array $args The parameters that are to replace the ? placeholders
  226. * @return string The replaced SQL query
  227. */
  228. private static function replaceParameters($sql, $args) {
  229. $offset = 0;
  230. foreach ($args AS $param => $value) {
  231. if (is_int($args[$param]) || is_float($args[$param])) {
  232. $replace = intval($args[$param]);
  233. } else {
  234. $replace = "'".self::escape($args[$param])."'";
  235. }
  236. $pos = strpos($sql, '?', $offset);
  237. if ($pos !== false) {
  238. $sql = substr_replace($sql, $replace, $pos, 1);
  239. }
  240. $offset = $pos + strlen($replace);
  241. }
  242. return $sql;
  243. }
  244. /**
  245. * @brief Convert parameter array to an universal form
  246. * @param array $args Parameter array
  247. * @return array universalized parameter array
  248. */
  249. private static function getParam($args) {
  250. unset($args[0]);
  251. // When the second function parameter is an array then use this as the parameter array
  252. if ((count($args) > 0) && (is_array($args[1]))) {
  253. return $args[1];
  254. } else {
  255. return $args;
  256. }
  257. }
  258. /**
  259. * @brief Executes a prepared statement that returns data
  260. * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid);
  261. *
  262. * Please only use it with complicated queries.
  263. * For all regular queries please use dba::select or dba::exists
  264. *
  265. * @param string $sql SQL statement
  266. * @return bool|object statement object
  267. */
  268. public static function p($sql) {
  269. $a = get_app();
  270. $stamp1 = microtime(true);
  271. $params = self::getParam(func_get_args());
  272. // Renumber the array keys to be sure that they fit
  273. $i = 0;
  274. $args = [];
  275. foreach ($params AS $param) {
  276. // Avoid problems with some MySQL servers and boolean values. See issue #3645
  277. if (is_bool($param)) {
  278. $param = (int)$param;
  279. }
  280. $args[++$i] = $param;
  281. }
  282. if (!self::$connected) {
  283. return false;
  284. }
  285. if ((substr_count($sql, '?') != count($args)) && (count($args) > 0)) {
  286. // Question: Should we continue or stop the query here?
  287. logger('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), LOGGER_DEBUG);
  288. }
  289. $sql = self::clean_query($sql);
  290. $sql = self::any_value_fallback($sql);
  291. $orig_sql = $sql;
  292. if (x($a->config,'system') && x($a->config['system'], 'db_callstack')) {
  293. $sql = "/*".System::callstack()." */ ".$sql;
  294. }
  295. self::$error = '';
  296. self::$errorno = 0;
  297. self::$affected_rows = 0;
  298. // We have to make some things different if this function is called from "e"
  299. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
  300. if (isset($trace[1])) {
  301. $called_from = $trace[1];
  302. } else {
  303. // We use just something that is defined to avoid warnings
  304. $called_from = $trace[0];
  305. }
  306. // We are having an own error logging in the function "e"
  307. $called_from_e = ($called_from['function'] == 'e');
  308. switch (self::$driver) {
  309. case 'pdo':
  310. // If there are no arguments we use "query"
  311. if (count($args) == 0) {
  312. if (!$retval = self::$db->query($sql)) {
  313. $errorInfo = self::$db->errorInfo();
  314. self::$error = $errorInfo[2];
  315. self::$errorno = $errorInfo[1];
  316. $retval = false;
  317. break;
  318. }
  319. self::$affected_rows = $retval->rowCount();
  320. break;
  321. }
  322. if (!$stmt = self::$db->prepare($sql)) {
  323. $errorInfo = self::$db->errorInfo();
  324. self::$error = $errorInfo[2];
  325. self::$errorno = $errorInfo[1];
  326. $retval = false;
  327. break;
  328. }
  329. foreach ($args AS $param => $value) {
  330. $stmt->bindParam($param, $args[$param]);
  331. }
  332. if (!$stmt->execute()) {
  333. $errorInfo = $stmt->errorInfo();
  334. self::$error = $errorInfo[2];
  335. self::$errorno = $errorInfo[1];
  336. $retval = false;
  337. } else {
  338. $retval = $stmt;
  339. self::$affected_rows = $retval->rowCount();
  340. }
  341. break;
  342. case 'mysqli':
  343. // There are SQL statements that cannot be executed with a prepared statement
  344. $parts = explode(' ', $orig_sql);
  345. $command = strtolower($parts[0]);
  346. $can_be_prepared = in_array($command, ['select', 'update', 'insert', 'delete']);
  347. // The fallback routine is called as well when there are no arguments
  348. if (!$can_be_prepared || (count($args) == 0)) {
  349. $retval = self::$db->query(self::replaceParameters($sql, $args));
  350. if (self::$db->errno) {
  351. self::$error = self::$db->error;
  352. self::$errorno = self::$db->errno;
  353. $retval = false;
  354. } else {
  355. if (isset($retval->num_rows)) {
  356. self::$affected_rows = $retval->num_rows;
  357. } else {
  358. self::$affected_rows = self::$db->affected_rows;
  359. }
  360. }
  361. break;
  362. }
  363. $stmt = self::$db->stmt_init();
  364. if (!$stmt->prepare($sql)) {
  365. self::$error = $stmt->error;
  366. self::$errorno = $stmt->errno;
  367. $retval = false;
  368. break;
  369. }
  370. $params = '';
  371. $values = [];
  372. foreach ($args AS $param => $value) {
  373. if (is_int($args[$param])) {
  374. $params .= 'i';
  375. } elseif (is_float($args[$param])) {
  376. $params .= 'd';
  377. } elseif (is_string($args[$param])) {
  378. $params .= 's';
  379. } else {
  380. $params .= 'b';
  381. }
  382. $values[] = &$args[$param];
  383. }
  384. if (count($values) > 0) {
  385. array_unshift($values, $params);
  386. call_user_func_array([$stmt, 'bind_param'], $values);
  387. }
  388. if (!$stmt->execute()) {
  389. self::$error = self::$db->error;
  390. self::$errorno = self::$db->errno;
  391. $retval = false;
  392. } else {
  393. $stmt->store_result();
  394. $retval = $stmt;
  395. self::$affected_rows = $retval->affected_rows;
  396. }
  397. break;
  398. }
  399. // We are having an own error logging in the function "e"
  400. if ((self::$errorno != 0) && !$called_from_e) {
  401. // We have to preserve the error code, somewhere in the logging it get lost
  402. $error = self::$error;
  403. $errorno = self::$errorno;
  404. logger('DB Error '.self::$errorno.': '.self::$error."\n".
  405. System::callstack(8)."\n".self::replaceParameters($sql, $params));
  406. self::$error = $error;
  407. self::$errorno = $errorno;
  408. }
  409. $a->save_timestamp($stamp1, 'database');
  410. if (x($a->config,'system') && x($a->config['system'], 'db_log')) {
  411. $stamp2 = microtime(true);
  412. $duration = (float)($stamp2 - $stamp1);
  413. if (($duration > $a->config["system"]["db_loglimit"])) {
  414. $duration = round($duration, 3);
  415. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  416. @file_put_contents($a->config["system"]["db_log"], DateTimeFormat::utcNow()."\t".$duration."\t".
  417. basename($backtrace[1]["file"])."\t".
  418. $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
  419. substr(self::replaceParameters($sql, $args), 0, 2000)."\n", FILE_APPEND);
  420. }
  421. }
  422. return $retval;
  423. }
  424. /**
  425. * @brief Executes a prepared statement like UPDATE or INSERT that doesn't return data
  426. *
  427. * Please use dba::delete, dba::insert, dba::update, ... instead
  428. *
  429. * @param string $sql SQL statement
  430. * @return boolean Was the query successfull? False is returned only if an error occurred
  431. */
  432. public static function e($sql) {
  433. $a = get_app();
  434. $stamp = microtime(true);
  435. $params = self::getParam(func_get_args());
  436. // In a case of a deadlock we are repeating the query 20 times
  437. $timeout = 20;
  438. do {
  439. $stmt = self::p($sql, $params);
  440. if (is_bool($stmt)) {
  441. $retval = $stmt;
  442. } elseif (is_object($stmt)) {
  443. $retval = true;
  444. } else {
  445. $retval = false;
  446. }
  447. self::close($stmt);
  448. } while ((self::$errorno == 1213) && (--$timeout > 0));
  449. if (self::$errorno != 0) {
  450. // We have to preserve the error code, somewhere in the logging it get lost
  451. $error = self::$error;
  452. $errorno = self::$errorno;
  453. logger('DB Error '.self::$errorno.': '.self::$error."\n".
  454. System::callstack(8)."\n".self::replaceParameters($sql, $params));
  455. self::$error = $error;
  456. self::$errorno = $errorno;
  457. }
  458. $a->save_timestamp($stamp, "database_write");
  459. return $retval;
  460. }
  461. /**
  462. * @brief Check if data exists
  463. *
  464. * @param string $table Table name
  465. * @param array $condition array of fields for condition
  466. *
  467. * @return boolean Are there rows for that condition?
  468. */
  469. public static function exists($table, $condition) {
  470. if (empty($table)) {
  471. return false;
  472. }
  473. $fields = [];
  474. reset($condition);
  475. $first_key = key($condition);
  476. if (!is_int($first_key)) {
  477. $fields = [$first_key];
  478. }
  479. $stmt = self::select($table, $fields, $condition, ['limit' => 1]);
  480. if (is_bool($stmt)) {
  481. $retval = $stmt;
  482. } else {
  483. $retval = (self::num_rows($stmt) > 0);
  484. }
  485. self::close($stmt);
  486. return $retval;
  487. }
  488. /**
  489. * Fetches the first row
  490. *
  491. * Please use dba::selectFirst or dba::exists whenever this is possible.
  492. *
  493. * @brief Fetches the first row
  494. * @param string $sql SQL statement
  495. * @return array first row of query
  496. */
  497. public static function fetch_first($sql) {
  498. $params = self::getParam(func_get_args());
  499. $stmt = self::p($sql, $params);
  500. if (is_bool($stmt)) {
  501. $retval = $stmt;
  502. } else {
  503. $retval = self::fetch($stmt);
  504. }
  505. self::close($stmt);
  506. return $retval;
  507. }
  508. /**
  509. * @brief Returns the number of affected rows of the last statement
  510. *
  511. * @return int Number of rows
  512. */
  513. public static function affected_rows() {
  514. return self::$affected_rows;
  515. }
  516. /**
  517. * @brief Returns the number of columns of a statement
  518. *
  519. * @param object Statement object
  520. * @return int Number of columns
  521. */
  522. public static function columnCount($stmt) {
  523. if (!is_object($stmt)) {
  524. return 0;
  525. }
  526. switch (self::$driver) {
  527. case 'pdo':
  528. return $stmt->columnCount();
  529. case 'mysqli':
  530. return $stmt->field_count;
  531. }
  532. return 0;
  533. }
  534. /**
  535. * @brief Returns the number of rows of a statement
  536. *
  537. * @param PDOStatement|mysqli_result|mysqli_stmt Statement object
  538. * @return int Number of rows
  539. */
  540. public static function num_rows($stmt) {
  541. if (!is_object($stmt)) {
  542. return 0;
  543. }
  544. switch (self::$driver) {
  545. case 'pdo':
  546. return $stmt->rowCount();
  547. case 'mysqli':
  548. return $stmt->num_rows;
  549. }
  550. return 0;
  551. }
  552. /**
  553. * @brief Fetch a single row
  554. *
  555. * @param mixed $stmt statement object
  556. * @return array current row
  557. */
  558. public static function fetch($stmt) {
  559. $a = get_app();
  560. $stamp1 = microtime(true);
  561. $columns = [];
  562. if (!is_object($stmt)) {
  563. return false;
  564. }
  565. switch (self::$driver) {
  566. case 'pdo':
  567. $columns = $stmt->fetch(PDO::FETCH_ASSOC);
  568. break;
  569. case 'mysqli':
  570. if (get_class($stmt) == 'mysqli_result') {
  571. $columns = $stmt->fetch_assoc();
  572. break;
  573. }
  574. // This code works, but is slow
  575. // Bind the result to a result array
  576. $cols = [];
  577. $cols_num = [];
  578. for ($x = 0; $x < $stmt->field_count; $x++) {
  579. $cols[] = &$cols_num[$x];
  580. }
  581. call_user_func_array([$stmt, 'bind_result'], $cols);
  582. if (!$stmt->fetch()) {
  583. return false;
  584. }
  585. // The slow part:
  586. // We need to get the field names for the array keys
  587. // It seems that there is no better way to do this.
  588. $result = $stmt->result_metadata();
  589. $fields = $result->fetch_fields();
  590. foreach ($cols_num AS $param => $col) {
  591. $columns[$fields[$param]->name] = $col;
  592. }
  593. }
  594. $a->save_timestamp($stamp1, 'database');
  595. return $columns;
  596. }
  597. /**
  598. * @brief Insert a row into a table
  599. *
  600. * @param string $table Table name
  601. * @param array $param parameter array
  602. * @param bool $on_duplicate_update Do an update on a duplicate entry
  603. *
  604. * @return boolean was the insert successfull?
  605. */
  606. public static function insert($table, $param, $on_duplicate_update = false) {
  607. if (empty($table) || empty($param)) {
  608. logger('Table and fields have to be set');
  609. return false;
  610. }
  611. $sql = "INSERT INTO `".self::escape($table)."` (`".implode("`, `", array_keys($param))."`) VALUES (".
  612. substr(str_repeat("?, ", count($param)), 0, -2).")";
  613. if ($on_duplicate_update) {
  614. $sql .= " ON DUPLICATE KEY UPDATE `".implode("` = ?, `", array_keys($param))."` = ?";
  615. $values = array_values($param);
  616. $param = array_merge_recursive($values, $values);
  617. }
  618. return self::e($sql, $param);
  619. }
  620. /**
  621. * @brief Fetch the id of the last insert command
  622. *
  623. * @return integer Last inserted id
  624. */
  625. public static function lastInsertId() {
  626. switch (self::$driver) {
  627. case 'pdo':
  628. $id = self::$db->lastInsertId();
  629. break;
  630. case 'mysqli':
  631. $id = self::$db->insert_id;
  632. break;
  633. }
  634. return $id;
  635. }
  636. /**
  637. * @brief Locks a table for exclusive write access
  638. *
  639. * This function can be extended in the future to accept a table array as well.
  640. *
  641. * @param string $table Table name
  642. *
  643. * @return boolean was the lock successful?
  644. */
  645. public static function lock($table) {
  646. // See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
  647. self::e("SET autocommit=0");
  648. $success = self::e("LOCK TABLES `".self::escape($table)."` WRITE");
  649. if (!$success) {
  650. self::e("SET autocommit=1");
  651. } else {
  652. self::$in_transaction = true;
  653. }
  654. return $success;
  655. }
  656. /**
  657. * @brief Unlocks all locked tables
  658. *
  659. * @return boolean was the unlock successful?
  660. */
  661. public static function unlock() {
  662. // See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
  663. self::e("COMMIT");
  664. $success = self::e("UNLOCK TABLES");
  665. self::e("SET autocommit=1");
  666. self::$in_transaction = false;
  667. return $success;
  668. }
  669. /**
  670. * @brief Starts a transaction
  671. *
  672. * @return boolean Was the command executed successfully?
  673. */
  674. public static function transaction() {
  675. if (!self::e('COMMIT')) {
  676. return false;
  677. }
  678. if (!self::e('START TRANSACTION')) {
  679. return false;
  680. }
  681. self::$in_transaction = true;
  682. return true;
  683. }
  684. /**
  685. * @brief Does a commit
  686. *
  687. * @return boolean Was the command executed successfully?
  688. */
  689. public static function commit() {
  690. if (!self::e('COMMIT')) {
  691. return false;
  692. }
  693. self::$in_transaction = false;
  694. return true;
  695. }
  696. /**
  697. * @brief Does a rollback
  698. *
  699. * @return boolean Was the command executed successfully?
  700. */
  701. public static function rollback() {
  702. if (!self::e('ROLLBACK')) {
  703. return false;
  704. }
  705. self::$in_transaction = false;
  706. return true;
  707. }
  708. /**
  709. * @brief Build the array with the table relations
  710. *
  711. * The array is build from the database definitions in DBStructure.php
  712. *
  713. * This process must only be started once, since the value is cached.
  714. */
  715. private static function buildRelationData() {
  716. $definition = DBStructure::definition();
  717. foreach ($definition AS $table => $structure) {
  718. foreach ($structure['fields'] AS $field => $field_struct) {
  719. if (isset($field_struct['relation'])) {
  720. foreach ($field_struct['relation'] AS $rel_table => $rel_field) {
  721. self::$relation[$rel_table][$rel_field][$table][] = $field;
  722. }
  723. }
  724. }
  725. }
  726. }
  727. /**
  728. * @brief Delete a row from a table
  729. *
  730. * @param string $table Table name
  731. * @param array $conditions Field condition(s)
  732. * @param boolean $in_process Internal use: Only do a commit after the last delete
  733. * @param array $callstack Internal use: prevent endless loops
  734. *
  735. * @return boolean|array was the delete successful? When $in_process is set: deletion data
  736. */
  737. public static function delete($table, array $conditions, $in_process = false, array &$callstack = [])
  738. {
  739. if (empty($table) || empty($conditions)) {
  740. logger('Table and conditions have to be set');
  741. return false;
  742. }
  743. $commands = [];
  744. // Create a key for the loop prevention
  745. $key = $table . ':' . implode(':', array_keys($conditions)) . ':' . implode(':', $conditions);
  746. // We quit when this key already exists in the callstack.
  747. if (isset($callstack[$key])) {
  748. return $commands;
  749. }
  750. $callstack[$key] = true;
  751. $table = self::escape($table);
  752. $commands[$key] = ['table' => $table, 'conditions' => $conditions];
  753. // To speed up the whole process we cache the table relations
  754. if (count(self::$relation) == 0) {
  755. self::buildRelationData();
  756. }
  757. // Is there a relation entry for the table?
  758. if (isset(self::$relation[$table])) {
  759. // We only allow a simple "one field" relation.
  760. $field = array_keys(self::$relation[$table])[0];
  761. $rel_def = array_values(self::$relation[$table])[0];
  762. // Create a key for preventing double queries
  763. $qkey = $field . '-' . $table . ':' . implode(':', array_keys($conditions)) . ':' . implode(':', $conditions);
  764. // When the search field is the relation field, we don't need to fetch the rows
  765. // This is useful when the leading record is already deleted in the frontend but the rest is done in the backend
  766. if ((count($conditions) == 1) && ($field == array_keys($conditions)[0])) {
  767. foreach ($rel_def AS $rel_table => $rel_fields) {
  768. foreach ($rel_fields AS $rel_field) {
  769. $retval = self::delete($rel_table, [$rel_field => array_values($conditions)[0]], true, $callstack);
  770. $commands = array_merge($commands, $retval);
  771. }
  772. }
  773. // We quit when this key already exists in the callstack.
  774. } elseif (!isset($callstack[$qkey])) {
  775. $callstack[$qkey] = true;
  776. // Fetch all rows that are to be deleted
  777. $data = self::select($table, [$field], $conditions);
  778. while ($row = self::fetch($data)) {
  779. // Now we accumulate the delete commands
  780. $retval = self::delete($table, [$field => $row[$field]], true, $callstack);
  781. $commands = array_merge($commands, $retval);
  782. }
  783. self::close($data);
  784. // Since we had split the delete command we don't need the original command anymore
  785. unset($commands[$key]);
  786. }
  787. }
  788. if (!$in_process) {
  789. // Now we finalize the process
  790. $do_transaction = !self::$in_transaction;
  791. if ($do_transaction) {
  792. self::transaction();
  793. }
  794. $compacted = [];
  795. $counter = [];
  796. foreach ($commands AS $command) {
  797. $conditions = $command['conditions'];
  798. reset($conditions);
  799. $first_key = key($conditions);
  800. $condition_string = self::buildCondition($conditions);
  801. if ((count($command['conditions']) > 1) || is_int($first_key)) {
  802. $sql = "DELETE FROM `" . $command['table'] . "`" . $condition_string;
  803. logger(self::replaceParameters($sql, $conditions), LOGGER_DATA);
  804. if (!self::e($sql, $conditions)) {
  805. if ($do_transaction) {
  806. self::rollback();
  807. }
  808. return false;
  809. }
  810. } else {
  811. $key_table = $command['table'];
  812. $key_condition = array_keys($command['conditions'])[0];
  813. $value = array_values($command['conditions'])[0];
  814. // Split the SQL queries in chunks of 100 values
  815. // We do the $i stuff here to make the code better readable
  816. $i = $counter[$key_table][$key_condition];
  817. if (count($compacted[$key_table][$key_condition][$i]) > 100) {
  818. ++$i;
  819. }
  820. $compacted[$key_table][$key_condition][$i][$value] = $value;
  821. $counter[$key_table][$key_condition] = $i;
  822. }
  823. }
  824. foreach ($compacted AS $table => $values) {
  825. foreach ($values AS $field => $field_value_list) {
  826. foreach ($field_value_list AS $field_values) {
  827. $sql = "DELETE FROM `" . $table . "` WHERE `" . $field . "` IN (" .
  828. substr(str_repeat("?, ", count($field_values)), 0, -2) . ");";
  829. logger(self::replaceParameters($sql, $field_values), LOGGER_DATA);
  830. if (!self::e($sql, $field_values)) {
  831. if ($do_transaction) {
  832. self::rollback();
  833. }
  834. return false;
  835. }
  836. }
  837. }
  838. }
  839. if ($do_transaction) {
  840. self::commit();
  841. }
  842. return true;
  843. }
  844. return $commands;
  845. }
  846. /**
  847. * @brief Updates rows
  848. *
  849. * Updates rows in the database. When $old_fields is set to an array,
  850. * the system will only do an update if the fields in that array changed.
  851. *
  852. * Attention:
  853. * Only the values in $old_fields are compared.
  854. * This is an intentional behaviour.
  855. *
  856. * Example:
  857. * We include the timestamp field in $fields but not in $old_fields.
  858. * Then the row will only get the new timestamp when the other fields had changed.
  859. *
  860. * When $old_fields is set to a boolean value the system will do this compare itself.
  861. * When $old_fields is set to "true" the system will do an insert if the row doesn't exists.
  862. *
  863. * Attention:
  864. * Only set $old_fields to a boolean value when you are sure that you will update a single row.
  865. * When you set $old_fields to "true" then $fields must contain all relevant fields!
  866. *
  867. * @param string $table Table name
  868. * @param array $fields contains the fields that are updated
  869. * @param array $condition condition array with the key values
  870. * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate)
  871. *
  872. * @return boolean was the update successfull?
  873. */
  874. public static function update($table, $fields, $condition, $old_fields = []) {
  875. if (empty($table) || empty($fields) || empty($condition)) {
  876. logger('Table, fields and condition have to be set');
  877. return false;
  878. }
  879. $table = self::escape($table);
  880. $condition_string = self::buildCondition($condition);
  881. if (is_bool($old_fields)) {
  882. $do_insert = $old_fields;
  883. $old_fields = self::selectFirst($table, [], $condition);
  884. if (is_bool($old_fields)) {
  885. if ($do_insert) {
  886. $values = array_merge($condition, $fields);
  887. return self::insert($table, $values, $do_insert);
  888. }
  889. $old_fields = [];
  890. }
  891. }
  892. $do_update = (count($old_fields) == 0);
  893. foreach ($old_fields AS $fieldname => $content) {
  894. if (isset($fields[$fieldname])) {
  895. if ($fields[$fieldname] == $content) {
  896. unset($fields[$fieldname]);
  897. } else {
  898. $do_update = true;
  899. }
  900. }
  901. }
  902. if (!$do_update || (count($fields) == 0)) {
  903. return true;
  904. }
  905. $sql = "UPDATE `".$table."` SET `".
  906. implode("` = ?, `", array_keys($fields))."` = ?".$condition_string;
  907. $params1 = array_values($fields);
  908. $params2 = array_values($condition);
  909. $params = array_merge_recursive($params1, $params2);
  910. return self::e($sql, $params);
  911. }
  912. /**
  913. * Retrieve a single record from a table and returns it in an associative array
  914. *
  915. * @brief Retrieve a single record from a table
  916. * @param string $table
  917. * @param array $fields
  918. * @param array $condition
  919. * @param array $params
  920. * @return bool|array
  921. * @see dba::select
  922. */
  923. public static function selectFirst($table, array $fields = [], array $condition = [], $params = [])
  924. {
  925. $params['limit'] = 1;
  926. $result = self::select($table, $fields, $condition, $params);
  927. if (is_bool($result)) {
  928. return $result;
  929. } else {
  930. $row = self::fetch($result);
  931. self::close($result);
  932. return $row;
  933. }
  934. }
  935. /**
  936. * @brief Select rows from a table
  937. *
  938. * @param string $table Table name
  939. * @param array $fields Array of selected fields, empty for all
  940. * @param array $condition Array of fields for condition
  941. * @param array $params Array of several parameters
  942. *
  943. * @return boolean|object
  944. *
  945. * Example:
  946. * $table = "item";
  947. * $fields = array("id", "uri", "uid", "network");
  948. *
  949. * $condition = array("uid" => 1, "network" => 'dspr');
  950. * or:
  951. * $condition = array("`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr');
  952. *
  953. * $params = array("order" => array("id", "received" => true), "limit" => 10);
  954. *
  955. * $data = dba::select($table, $fields, $condition, $params);
  956. */
  957. public static function select($table, array $fields = [], array $condition = [], array $params = [])
  958. {
  959. if ($table == '') {
  960. return false;
  961. }
  962. $table = self::escape($table);
  963. if (count($fields) > 0) {
  964. $select_fields = "`" . implode("`, `", array_values($fields)) . "`";
  965. } else {
  966. $select_fields = "*";
  967. }
  968. $condition_string = self::buildCondition($condition);
  969. $order_string = '';
  970. if (isset($params['order'])) {
  971. $order_string = " ORDER BY ";
  972. foreach ($params['order'] AS $fields => $order) {
  973. if (!is_int($fields)) {
  974. $order_string .= "`" . $fields . "` " . ($order ? "DESC" : "ASC") . ", ";
  975. } else {
  976. $order_string .= "`" . $order . "`, ";
  977. }
  978. }
  979. $order_string = substr($order_string, 0, -2);
  980. }
  981. $limit_string = '';
  982. if (isset($params['limit']) && is_int($params['limit'])) {
  983. $limit_string = " LIMIT " . $params['limit'];
  984. }
  985. if (isset($params['limit']) && is_array($params['limit'])) {
  986. $limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]);
  987. }
  988. $sql = "SELECT " . $select_fields . " FROM `" . $table . "`" . $condition_string . $order_string . $limit_string;
  989. $result = self::p($sql, $condition);
  990. return $result;
  991. }
  992. /**
  993. * @brief Counts the rows from a table satisfying the provided condition
  994. *
  995. * @param string $table Table name
  996. * @param array $condition array of fields for condition
  997. *
  998. * @return int
  999. *
  1000. * Example:
  1001. * $table = "item";
  1002. *
  1003. * $condition = ["uid" => 1, "network" => 'dspr'];
  1004. * or:
  1005. * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
  1006. *
  1007. * $count = dba::count($table, $condition);
  1008. */
  1009. public static function count($table, array $condition = [])
  1010. {
  1011. if ($table == '') {
  1012. return false;
  1013. }
  1014. $condition_string = self::buildCondition($condition);
  1015. $sql = "SELECT COUNT(*) AS `count` FROM `".$table."`".$condition_string;
  1016. $row = self::fetch_first($sql, $condition);
  1017. return $row['count'];
  1018. }
  1019. /**
  1020. * @brief Returns the SQL condition string built from the provided condition array
  1021. *
  1022. * This function operates with two modes.
  1023. * - Supplied with a filed/value associative array, it builds simple strict
  1024. * equality conditions linked by AND.
  1025. * - Supplied with a flat list, the first element is the condition string and
  1026. * the following arguments are the values to be interpolated
  1027. *
  1028. * $condition = ["uid" => 1, "network" => 'dspr'];
  1029. * or:
  1030. * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
  1031. *
  1032. * In either case, the provided array is left with the parameters only
  1033. *
  1034. * @param array $condition
  1035. * @return string
  1036. */
  1037. private static function buildCondition(array &$condition = [])
  1038. {
  1039. $condition_string = '';
  1040. if (count($condition) > 0) {
  1041. reset($condition);
  1042. $first_key = key($condition);
  1043. if (is_int($first_key)) {
  1044. $condition_string = " WHERE ".array_shift($condition);
  1045. } else {
  1046. $new_values = [];
  1047. $condition_string = "";
  1048. foreach ($condition as $field => $value) {
  1049. if ($condition_string != "") {
  1050. $condition_string .= " AND ";
  1051. }
  1052. if (is_array($value)) {
  1053. $new_values = array_merge($new_values, array_values($value));
  1054. $placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
  1055. $condition_string .= "`" . $field . "` IN (" . $placeholders . ")";
  1056. } else {
  1057. $new_values[$field] = $value;
  1058. $condition_string .= "`" . $field . "` = ?";
  1059. }
  1060. }
  1061. $condition_string = " WHERE " . $condition_string;
  1062. $condition = $new_values;
  1063. }
  1064. }
  1065. return $condition_string;
  1066. }
  1067. /**
  1068. * @brief Fills an array with data from a query
  1069. *
  1070. * @param object $stmt statement object
  1071. * @return array Data array
  1072. */
  1073. public static function inArray($stmt, $do_close = true) {
  1074. if (is_bool($stmt)) {
  1075. return $stmt;
  1076. }
  1077. $data = [];
  1078. while ($row = self::fetch($stmt)) {
  1079. $data[] = $row;
  1080. }
  1081. if ($do_close) {
  1082. self::close($stmt);
  1083. }
  1084. return $data;
  1085. }
  1086. /**
  1087. * @brief Returns the error number of the last query
  1088. *
  1089. * @return string Error number (0 if no error)
  1090. */
  1091. public static function errorNo() {
  1092. return self::$errorno;
  1093. }
  1094. /**
  1095. * @brief Returns the error message of the last query
  1096. *
  1097. * @return string Error message ('' if no error)
  1098. */
  1099. public static function errorMessage() {
  1100. return self::$error;
  1101. }
  1102. /**
  1103. * @brief Closes the current statement
  1104. *
  1105. * @param object $stmt statement object
  1106. * @return boolean was the close successful?
  1107. */
  1108. public static function close($stmt) {
  1109. $a = get_app();
  1110. $stamp1 = microtime(true);
  1111. if (!is_object($stmt)) {
  1112. return false;
  1113. }
  1114. switch (self::$driver) {
  1115. case 'pdo':
  1116. $ret = $stmt->closeCursor();
  1117. break;
  1118. case 'mysqli':
  1119. $stmt->free_result();
  1120. $ret = $stmt->close();
  1121. break;
  1122. }
  1123. $a->save_timestamp($stamp1, 'database');
  1124. return $ret;
  1125. }
  1126. }
  1127. function dbesc($str) {
  1128. if (dba::$connected) {
  1129. return(dba::escape($str));
  1130. } else {
  1131. return(str_replace("'","\\'",$str));
  1132. }
  1133. }
  1134. /**
  1135. * @brief execute SQL query with printf style args - deprecated
  1136. *
  1137. * Please use the dba:: functions instead:
  1138. * dba::select, dba::exists, dba::insert
  1139. * dba::delete, dba::update, dba::p, dba::e
  1140. *
  1141. * @param $args Query parameters (1 to N parameters of different types)
  1142. * @return array|bool Query array
  1143. */
  1144. function q($sql) {
  1145. $args = func_get_args();
  1146. unset($args[0]);
  1147. if (!dba::$connected) {
  1148. return false;
  1149. }
  1150. $sql = dba::clean_query($sql);
  1151. $sql = dba::any_value_fallback($sql);
  1152. $stmt = @vsprintf($sql, $args);
  1153. $ret = dba::p($stmt);
  1154. if (is_bool($ret)) {
  1155. return $ret;
  1156. }
  1157. $columns = dba::columnCount($ret);
  1158. $data = dba::inArray($ret);
  1159. if ((count($data) == 0) && ($columns == 0)) {
  1160. return true;
  1161. }
  1162. return $data;
  1163. }
  1164. function dba_timer() {
  1165. return microtime(true);
  1166. }