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.

1475 lines
38 KiB

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