namespace Friendica\Database;
+use Exception;
use Friendica\Core\Config\Cache;
use Friendica\Core\System;
+use Friendica\DI;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
/** @var PDO|mysqli */
protected $connection;
protected $driver;
+ private $emulate_prepares = false;
private $error = false;
private $errorno = 0;
private $affected_rows = 0;
protected $in_transaction = false;
protected $in_retrial = false;
+ protected $testmode = false;
private $relation = [];
public function __construct(Cache $configCache, Profiler $profiler, LoggerInterface $logger, array $server = [])
return false;
}
- if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
+ $this->emulate_prepares = (bool)$this->configCache->get('database', 'emulate_prepares');
+ $this->pdo_emulate_prepares = (bool)$this->configCache->get('database', 'pdo_emulate_prepares');
+
+ if (!$this->configCache->get('database', 'disable_pdo') && class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
$this->driver = 'pdo';
$connect = "mysql:host=" . $server . ";dbname=" . $db;
try {
$this->connection = @new PDO($connect, $user, $pass);
- $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
$this->connected = true;
} catch (PDOException $e) {
$this->connected = false;
return $this->connected;
}
+ public function setTestmode(bool $test)
+ {
+ $this->testmode = $test;
+ }
/**
* Sets the logger for DBA
*
{
$offset = 0;
foreach ($args AS $param => $value) {
- if (is_int($args[$param]) || is_float($args[$param])) {
+ if (is_int($args[$param]) || is_float($args[$param]) || is_bool($args[$param])) {
$replace = intval($args[$param]);
+ } elseif (is_null($args[$param])) {
+ $replace = 'NULL';
} else {
$replace = "'" . $this->escape($args[$param]) . "'";
}
$sql = "/*" . System::callstack() . " */ " . $sql;
}
+ $is_error = false;
$this->error = '';
$this->errorno = 0;
$this->affected_rows = 0;
switch ($this->driver) {
case 'pdo':
// If there are no arguments we use "query"
- if (count($args) == 0) {
- if (!$retval = $this->connection->query($sql)) {
+ if ($this->emulate_prepares || count($args) == 0) {
+ if (!$retval = $this->connection->query($this->replaceParameters($sql, $args))) {
$errorInfo = $this->connection->errorInfo();
$this->error = $errorInfo[2];
$this->errorno = $errorInfo[1];
$retval = false;
+ $is_error = true;
break;
}
$this->affected_rows = $retval->rowCount();
$this->error = $errorInfo[2];
$this->errorno = $errorInfo[1];
$retval = false;
+ $is_error = true;
break;
}
$this->error = $errorInfo[2];
$this->errorno = $errorInfo[1];
$retval = false;
+ $is_error = true;
} else {
$retval = $stmt;
$this->affected_rows = $retval->rowCount();
$can_be_prepared = in_array($command, ['select', 'update', 'insert', 'delete']);
// The fallback routine is called as well when there are no arguments
- if (!$can_be_prepared || (count($args) == 0)) {
+ if ($this->emulate_prepares || !$can_be_prepared || (count($args) == 0)) {
$retval = $this->connection->query($this->replaceParameters($sql, $args));
if ($this->connection->errno) {
$this->error = $this->connection->error;
$this->errorno = $this->connection->errno;
$retval = false;
+ $is_error = true;
} else {
if (isset($retval->num_rows)) {
$this->affected_rows = $retval->num_rows;
$this->error = $stmt->error;
$this->errorno = $stmt->errno;
$retval = false;
+ $is_error = true;
break;
}
$this->error = $this->connection->error;
$this->errorno = $this->connection->errno;
$retval = false;
+ $is_error = true;
} else {
$stmt->store_result();
$retval = $stmt;
break;
}
+ // See issue https://github.com/friendica/friendica/issues/8572
+ // Ensure that we always get an error message on an error.
+ if ($is_error && empty($this->errorno)) {
+ $this->errorno = -1;
+ }
+
+ if ($is_error && empty($this->error)) {
+ $this->error = 'Unknown database error';
+ }
+
// We are having an own error logging in the function "e"
if (($this->errorno != 0) && !$called_from_e) {
// We have to preserve the error code, somewhere in the logging it get lost
$error = $this->error;
$errorno = $this->errorno;
+ if ($this->testmode) {
+ throw new Exception(DI::l10n()->t('Database error %d "%s" at "%s"', $errorno, $error, $this->replaceParameters($sql, $args)));
+ }
+
$this->logger->error('DB Error', [
- 'code' => $this->errorno,
- 'error' => $this->error,
+ 'code' => $errorno,
+ 'error' => $error,
'callstack' => System::callstack(8),
'params' => $this->replaceParameters($sql, $args),
]);
// It doesn't make sense to continue when the database connection was lost
if ($this->in_retrial) {
$this->logger->notice('Giving up retrial because of database error', [
- 'code' => $this->errorno,
- 'error' => $this->error,
+ 'code' => $errorno,
+ 'error' => $error,
]);
} else {
$this->logger->notice('Couldn\'t reconnect after database error', [
- 'code' => $this->errorno,
- 'error' => $this->error,
+ 'code' => $errorno,
+ 'error' => $error,
]);
}
exit(1);
} else {
// We try it again
$this->logger->notice('Reconnected after database error', [
- 'code' => $this->errorno,
- 'error' => $this->error,
+ 'code' => $errorno,
+ 'error' => $error,
]);
$this->in_retrial = true;
$ret = $this->p($sql, $args);
$error = $this->error;
$errorno = $this->errorno;
+ if ($this->testmode) {
+ throw new Exception(DI::l10n()->t('Database error %d "%s" at "%s"', $errorno, $error, $this->replaceParameters($sql, $params)));
+ }
+
$this->logger->error('DB Error', [
- 'code' => $this->errorno,
- 'error' => $this->error,
+ 'code' => $errorno,
+ 'error' => $error,
'callstack' => System::callstack(8),
'params' => $this->replaceParameters($sql, $params),
]);
// A reconnect like in $this->p could be dangerous with modifications
if ($errorno == 2006) {
$this->logger->notice('Giving up because of database error', [
- 'code' => $this->errorno,
- 'error' => $this->error,
+ 'code' => $errorno,
+ 'error' => $error,
]);
exit(1);
}
$success = $this->e("LOCK TABLES " . DBA::buildTableString($table) . " WRITE");
if ($this->driver == 'pdo') {
- $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
}
if (!$success) {
$success = $this->e("UNLOCK TABLES");
if ($this->driver == 'pdo') {
- $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
$this->e("SET autocommit=1");
} else {
$this->connection->autocommit(true);
return (["list" => $statelist, "amount" => $processes]);
}
+ /**
+ * Fetch a database variable
+ *
+ * @param string $name
+ * @return string content
+ */
+ public function getVariable(string $name)
+ {
+ $result = $this->fetchFirst("SHOW GLOBAL VARIABLES WHERE `Variable_name` = ?", $name);
+ return $result['Value'] ?? null;
+ }
+
/**
* Checks if $array is a filled array with at least one entry.
*