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.

1393 lines
35KB

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