Commit a22e8db0 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Database: Moved implementation from PDO to mysqli. Reason: mysqli_num needed...

Database: Moved implementation from PDO to mysqli. Reason: mysqli_num needed for `report`, but PDO can't fetch column names than.
parent 7b18a4b4
...@@ -50,6 +50,7 @@ const ROW_EXPECT_0 = "expect_0"; ...@@ -50,6 +50,7 @@ const ROW_EXPECT_0 = "expect_0";
const ROW_EXPECT_1 = "expect_1"; const ROW_EXPECT_1 = "expect_1";
const ROW_EXPECT_0_1 = "expect_0_1"; const ROW_EXPECT_0_1 = "expect_0_1";
const ROW_EXPECT_GE_1 = "expect_ge_1"; const ROW_EXPECT_GE_1 = "expect_ge_1";
const ROW_KEYS = "keys";
// KeyValueParser // KeyValueParser
const IF_VALUE_EMPTY_COPY_KEY = 'if_value_empty_copy_key'; const IF_VALUE_EMPTY_COPY_KEY = 'if_value_empty_copy_key';
...@@ -97,7 +98,7 @@ const ERROR_UNKNOWN_MODE = 1032; ...@@ -97,7 +98,7 @@ const ERROR_UNKNOWN_MODE = 1032;
const ERROR_NOT_IMPLEMENTED = 1033; const ERROR_NOT_IMPLEMENTED = 1033;
const ERROR_RESERVED_KEY_NAME = 1034; const ERROR_RESERVED_KEY_NAME = 1034;
const ERROR_VALUE_HAS_NO_KEY = 1035; const ERROR_VALUE_HAS_NO_KEY = 1035;
const ERROR_DB_EXECUTE = 1036;
const ERROR_COLUMN_NOT_FOUND_IN_TABLE = 1037; const ERROR_COLUMN_NOT_FOUND_IN_TABLE = 1037;
const ERROR_MISSING_HIDDEN_FIELD_IN_SIP = 1038; const ERROR_MISSING_HIDDEN_FIELD_IN_SIP = 1038;
const ERROR_KEY_EXIST_IN_STORE = 1039; const ERROR_KEY_EXIST_IN_STORE = 1039;
...@@ -112,6 +113,13 @@ const ERROR_GET_STORE_ZERO = 1047; ...@@ -112,6 +113,13 @@ const ERROR_GET_STORE_ZERO = 1047;
const ERROR_SET_STORE_ZERO = 1048; const ERROR_SET_STORE_ZERO = 1048;
const ERROR_MISSING_FORMELEMENT = 1049; const ERROR_MISSING_FORMELEMENT = 1049;
const ERROR_DB_EXECUTE = 1050;
const ERROR_DB_PREPARE = 1051;
const ERROR_DB_BIND = 1052;
const ERROR_DB_QUERY = 1053;
const ERROR_CLOSE_MYSQLI_RESULT = 1054;
const ERROR_CLOSE_MYSQLI_STMT = 1055;
// DB Errors // DB Errors
//const ERROR_DB_QUERY_SIMPLE = 2000; //const ERROR_DB_QUERY_SIMPLE = 2000;
...@@ -182,7 +190,6 @@ const SYSTEM_DBPW = 'DBPW'; ...@@ -182,7 +190,6 @@ const SYSTEM_DBPW = 'DBPW';
const SYSTEM_DB = 'DB'; const SYSTEM_DB = 'DB';
const SYSTEM_TESTDB = 'TESTDB'; const SYSTEM_TESTDB = 'TESTDB';
const SYSTEM_SESSIONNAME = 'SESSIONNAME'; const SYSTEM_SESSIONNAME = 'SESSIONNAME';
const SYSTEM_DBH = 'dataBaseHandle';
// Information for: Log / Debug / Exception // Information for: Log / Debug / Exception
const SYSTEM_SQL_RAW = 'sqlRaw'; // Type: SANATIZE_ALL / String. SQL Query (before substitute). Useful for error reporting. const SYSTEM_SQL_RAW = 'sqlRaw'; // Type: SANATIZE_ALL / String. SQL Query (before substitute). Useful for error reporting.
const SYSTEM_SQL_FINAL = 'sqlFinal'; // Type: SANATIZE_ALL / String. SQL Query (after substitute). Useful for error reporting. const SYSTEM_SQL_FINAL = 'sqlFinal'; // Type: SANATIZE_ALL / String. SQL Query (after substitute). Useful for error reporting.
......
...@@ -9,19 +9,20 @@ ...@@ -9,19 +9,20 @@
namespace qfq; namespace qfq;
use qfq; use qfq;
use qfq\DbException;
use qfq\Store; use qfq\Store;
use qfq\CodeException; use qfq\CodeException;
use qfq\UserException; use qfq\UserException;
use qfq\DbException;
use qfq\Support; use qfq\Support;
require_once(__DIR__ . '/../qfq/exceptions/UserException.php'); require_once(__DIR__ . '/exceptions/UserException.php');
require_once(__DIR__ . '/../qfq/exceptions/CodeException.php'); require_once(__DIR__ . '/exceptions/CodeException.php');
require_once(__DIR__ . '/../qfq/exceptions/DbException.php'); require_once(__DIR__ . '/exceptions/DbException.php');
require_once(__DIR__ . '/../qfq/store/Store.php'); require_once(__DIR__ . '/store/Store.php');
require_once(__DIR__ . '/../qfq/helper/Support.php'); require_once(__DIR__ . '/helper/Support.php');
require_once(__DIR__ . '/helper/BindParam.php');
/** /**
* Class Database * Class Database
...@@ -29,15 +30,26 @@ require_once(__DIR__ . '/../qfq/helper/Support.php'); ...@@ -29,15 +30,26 @@ require_once(__DIR__ . '/../qfq/helper/Support.php');
*/ */
class Database { class Database {
/**
* @var \PDO
*/
public $pdo = null;
/** /**
* @var Store * @var Store
*/ */
private $store = null; private $store = null;
private $stmt = null;
/**
* @var \mysqli
*/
private $mysqli = null;
/**
* @var \mysqli_stmt
*/
private $mysqli_stmt = null;
/**
* @var \mysqli_result
*/
private $mysqli_result = null;
/** /**
* Returns current data base handle from Store[System][SYSTEM_DBH]. * Returns current data base handle from Store[System][SYSTEM_DBH].
...@@ -49,32 +61,33 @@ class Database { ...@@ -49,32 +61,33 @@ class Database {
public function __construct() { public function __construct() {
$this->store = Store::getInstance(); $this->store = Store::getInstance();
$this->pdo = $this->store->getVar(SYSTEM_DBH, STORE_SYSTEM); if ($this->mysqli === null) {
$this->mysqli = $this->dbConnect();
if ($this->pdo === false) {
$this->pdo = $this->dbConnect();
$this->store->setVar(SYSTEM_DBH, $this->pdo, STORE_SYSTEM);
} }
} }
/** /**
* If not * Open mysqli database connection if not already done.
* @return \PDO *
* @return \mysqli
* @throws UserException * @throws UserException
*/ */
private function dbConnect() { private function dbConnect() {
$mysqli = null;
$dbuser = $this->store->getVar(SYSTEM_DBUSER, STORE_SYSTEM); $dbuser = $this->store->getVar(SYSTEM_DBUSER, STORE_SYSTEM);
$dbserver = $this->store->getVar(SYSTEM_DBSERVER, STORE_SYSTEM); $dbserver = $this->store->getVar(SYSTEM_DBSERVER, STORE_SYSTEM);
$dbpw = $this->store->getVar(SYSTEM_DBPW, STORE_SYSTEM); $dbpw = $this->store->getVar(SYSTEM_DBPW, STORE_SYSTEM);
$db = $this->store->getVar(SYSTEM_DB, STORE_SYSTEM); $db = $this->store->getVar(SYSTEM_DB, STORE_SYSTEM);
try { $mysqli = new \mysqli($dbserver, $dbuser, $dbpw, $db);
$pdo = new \PDO("mysql:host=" . $dbserver . ";dbname=" . $db, $dbuser, $dbpw, array(\PDO::ATTR_PERSISTENT => true));
} catch (\Exception $e) { if ($mysqli->connect_error) {
throw new UserException ("Error open Database 'mysql:host=" . $dbserver . ";dbname=" . $db . ";dbuser=" . $dbuser . "'': " . $e->getMessage(), ERROR_OPEN_DATABASE); throw new UserException ("Error open Database 'mysql:host=" . $dbserver . ";dbname=" . $db . ";dbuser=" . $dbuser . "'': " . $mysqli->connect_errno . PHP_EOL . $mysqli->connect_error, ERROR_OPEN_DATABASE);
} }
return $pdo; return $mysqli;
} }
/** /**
...@@ -86,11 +99,11 @@ class Database { ...@@ -86,11 +99,11 @@ class Database {
* has never been called prior a call to this method, false is returned. * has never been called prior a call to this method, false is returned.
*/ */
public function getRowCount() { public function getRowCount() {
if ($this->stmt == null) { if ($this->mysqli_result == null) {
return false; return false;
} }
return $this->stmt->rowCount(); return $this->mysqli_result->num_rows;
} }
/** /**
...@@ -134,11 +147,9 @@ class Database { ...@@ -134,11 +147,9 @@ class Database {
* @param string $table name of the table * @param string $table name of the table
* *
* @param string $columnName name of the column * @param string $columnName name of the column
*
* @return array the definition of the column as retrieved by Database::getTableDefinition(). * @return array the definition of the column as retrieved by Database::getTableDefinition().
* *
* @throws UserException * @throws \qfq\DbException
*
*/ */
private function getFieldDefinitionFromTable($table, $columnName) { private function getFieldDefinitionFromTable($table, $columnName) {
$tableDefinition = $this->getTableDefinition($table); $tableDefinition = $this->getTableDefinition($table);
...@@ -185,7 +196,9 @@ class Database { ...@@ -185,7 +196,9 @@ class Database {
* @throws \qfq\CodeException * @throws \qfq\CodeException
* @throws \qfq\DbException * @throws \qfq\DbException
*/ */
public function sql($sql, $mode = ROW_REGULAR, array $parameterArray = array(), $specificMessage = '') { public function sql($sql, $mode = ROW_REGULAR, array $parameterArray = array(), $specificMessage = '', array &$keys = array()) {
$result = array();
$this->closeMysqliStmt();
// CR often forgets to specify the $mode and use prepared statement with parameter instead. // CR often forgets to specify the $mode and use prepared statement with parameter instead.
if (is_array($mode)) if (is_array($mode))
...@@ -200,52 +213,68 @@ class Database { ...@@ -200,52 +213,68 @@ class Database {
throw new DbException($specificMessage . "No idea why this error happens - please take some time and check this: $sql", ERROR_DB_GENERIC_CHECK); throw new DbException($specificMessage . "No idea why this error happens - please take some time and check this: $sql", ERROR_DB_GENERIC_CHECK);
} }
if ($mode === ROW_IMPLODE_ALL) {
return $this->fetchAll(ROW_IMPLODE_ALL);
}
if ($mode === ROW_REGULAR) {
return $this->fetchAll();
}
switch ($mode) { switch ($mode) {
case ROW_IMPLODE_ALL: case ROW_IMPLODE_ALL:
return $this->fetchAll(ROW_IMPLODE_ALL); $result = $this->fetchAll($mode);
break; break;
case ROW_KEYS:
case ROW_REGULAR: case ROW_REGULAR:
return $this->fetchAll(); $result = $this->fetchAll($mode, $keys);
break; break;
case ROW_EXPECT_0: case ROW_EXPECT_0:
if ($count === 0) if ($count === 0)
return array(); $result = array();
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS); else
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
break; break;
case ROW_EXPECT_1: case ROW_EXPECT_1:
if ($count === 1) if ($count === 1)
return $this->fetchOne(); $result = $this->fetchAll($mode)[0];
throw new DbException($specificMessage . "Expected one row, got 0 or more than 1: $sql", ERROR_DB_COUNT_DO_NOT_MATCH); else
throw new DbException($specificMessage . "Expected one row, got 0 or more than 1: $sql", ERROR_DB_COUNT_DO_NOT_MATCH);
break; break;
case ROW_EXPECT_0_1: case ROW_EXPECT_0_1:
if ($count === 1) if ($count === 1)
return $this->fetchOne(); $result = $this->fetchAll($mode)[0];
if ($count === 0) elseif ($count === 0)
return array(); $result = array();
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS); else
throw new DbException($specificMessage . "Expected no row, got $count rows: $sql", ERROR_DB_TOO_MANY_ROWS);
break; break;
// case ROW_EXPECT_GE_0:
// if ($count === 0)
// return array();
// return $this->fetchAll();
// break;
case ROW_EXPECT_GE_1: case ROW_EXPECT_GE_1:
if ($count > 0) if ($count > 0)
return $this->fetchAll(); $result = $this->fetchAll($mode);
throw new DbException($specificMessage . "Expected at least one row, got nothing: $sql", ERROR_DB_TOO_FEW_ROWS); else
throw new DbException($specificMessage . "Expected at least one row, got nothing: $sql", ERROR_DB_TOO_FEW_ROWS);
break; break;
default: default:
throw new DbException($specificMessage . "Unknown mode: $mode", ERROR_UNKNOWN_MODE); throw new DbException($specificMessage . "Unknown mode: $mode", ERROR_UNKNOWN_MODE);
} }
$this->closeMysqliStmt();
return $result;
}
/**
* Close an optional open MySQLi Statement.
*
* @throws \qfq\DbException
*/
private function closeMysqliStmt() {
if ($this->mysqli_result !== null && $this->mysqli_result !== false) {
$this->mysqli_result->free_result();
}
if ($this->mysqli_stmt !== null && $this->mysqli_stmt !== false) {
$this->mysqli_stmt->free_result();
if (!$this->mysqli_stmt->close())
throw new DbException('Error closing mysqli_stmt' . ERROR_CLOSE_MYSQLI_STMT);
}
$this->mysqli_stmt = null;
$this->mysqli_result = null;
} }
/** /**
...@@ -259,24 +288,69 @@ class Database { ...@@ -259,24 +288,69 @@ class Database {
* @throws \qfq\DbException * @throws \qfq\DbException
* @throws \qfq\UserException * @throws \qfq\UserException
*/ */
public function prepareExecute($sql, array $parameterArray = array()) { private function prepareExecute($sql, array $parameterArray = array()) {
$result = null;
$this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM); $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
$this->store->setVar(SYSTEM_SQL_PARAM_ARRAY, $parameterArray, STORE_SYSTEM); $this->store->setVar(SYSTEM_SQL_PARAM_ARRAY, $parameterArray, STORE_SYSTEM);
// Logfile // Logfile
$this->dbLog($sql, $parameterArray); $this->dbLog($sql, $parameterArray);
$this->stmt = $this->pdo->prepare($sql); // Prepared Statement?
if (false === $this->stmt->execute($parameterArray)) { // if (count($parameterArray) === 0) {
throw new DbException($this->stmt->errorInfo()[2], ERROR_DB_EXECUTE); // if (false === ($result = $this->mysqli->query($sql))) {
// throw new DbException('[ mysqli: ' . $this->mysqli->errno . ' ] ' . $this->mysqli->error, ERROR_DB_QUERY);
// }
//
// } else {
if (false === ($this->mysqli_stmt = $this->mysqli->prepare($sql))) {
throw new DbException('[ mysqli: ' . $this->mysqli->errno . ' ] ' . $this->mysqli->error, ERROR_DB_PREPARE);
}
if (count($parameterArray) > 0) {
if (false === $this->fakeCallUserFunc($parameterArray)) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_BIND);
}
}
if (false === $this->mysqli_stmt->execute()) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_EXECUTE);
}
// }
$msg = '';
$count = 0;
$command = strtoupper(explode(' ', $sql, 2)[0]);
switch ($command) {
case 'SELECT':
case 'SHOW':
case 'DESCRIBE':
case 'EXPLAIN':
if (false === ($result = $this->mysqli_stmt->get_result())) {
throw new DbException('[ mysqli: ' . $this->mysqli_stmt->errno . ' ] ' . $this->mysqli_stmt->error, ERROR_DB_EXECUTE);
}
$this->mysqli_result = $result;
$count = $this->mysqli_result->num_rows;
$msg = 'Get rows: ' . $count;
break;
case 'INSERT':
$count = $this->mysqli->insert_id;
$msg = 'ID: ' . $count;
break;
case 'UPDATE':
case 'REPLACE':
case 'DELETE':
$count = $this->mysqli->affected_rows;
$msg = 'Affected rows: ' . $count;
break;
default:
break;
} }
$count = $this->stmt->rowCount();
$this->store->setVar(SYSTEM_SQL_COUNT, $count, STORE_SYSTEM); $this->store->setVar(SYSTEM_SQL_COUNT, $count, STORE_SYSTEM);
// Logfile // Logfile
$msg = (substr($sql, 0, 7) === 'INSERT ') ? 'ID: ' . $this->getLastInsertId() : 'Affected rows: ' . $count;
$this->dbLog($msg); $this->dbLog($msg);
return $count; return $count;
...@@ -313,12 +387,48 @@ class Database { ...@@ -313,12 +387,48 @@ class Database {
} }
/** /**
* Returns lastInsertId * @param $arr
*
* @return string
*/ */
public function getLastInsertId() { private function fakeCallUserFunc($arr) {
return $this->pdo->lastInsertId();
$type = '';
foreach ($arr as $value) {
if (is_int($value)) {
$type .= 'i';
} elseif (is_double($value)) {
$type .= 'd';
} else {
$type .= 's';
}
}
switch (count($arr)) {
case 0:
break;
case 1:
$this->mysqli_stmt->bind_param($type, $arr[0]);
break;
case 2:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1]);
break;
case 3:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2]);
break;
case 4:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3]);
break;
case 5:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3], $arr[4]);
break;
case 6:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3], $arr[4], $arr[5]);
break;
case 7:
$this->mysqli_stmt->bind_param($type, $arr[0], $arr[1], $arr[2], $arr[3], $arr[4], $arr[5], $arr[6]);
break;
default:
throw new DbException('Oops, too many parameter in prepared statement.', 0);
}
} }
/** /**
...@@ -330,43 +440,56 @@ class Database { ...@@ -330,43 +440,56 @@ class Database {
* All rows and all columns imploded to one string if $mode=IMPLODE_ALL * All rows and all columns imploded to one string if $mode=IMPLODE_ALL
* *
*/ */
public function fetchAll($mode = '') { private function fetchAll($mode = '', &$keys = array()) {
if ($this->stmt == null) { if ($this->mysqli_result == null || $this->mysqli_result == false) {
return false; return false;
} }
if ($this->stmt->rowCount() === 0) { if ($this->mysqli_result->num_rows === 0) {
return ($mode === ROW_IMPLODE_ALL) ? "" : array(); return ($mode === ROW_IMPLODE_ALL) ? "" : array();
} }
$str = ""; switch ($mode) {
if ($mode === ROW_IMPLODE_ALL) { case ROW_IMPLODE_ALL:
foreach ($this->stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $str = "";
$str .= implode($row); foreach ($this->mysqli_result->fetch_all(MYSQLI_ASSOC) as $row) {
} $str .= implode($row);
return $str; }
} else { return $str;
return $this->stmt->fetchAll(\PDO::FETCH_ASSOC); break;
case ROW_KEYS:
$keys = array();
for ($ii = 0; $ii < $this->mysqli_result->field_count; $ii++) {
$keys[$ii] = $this->mysqli_result->fetch_field_direct($ii)->name;
}
return $this->mysqli_result->fetch_all(MYSQLI_NUM);
break;
default:
return $this->mysqli_result->fetch_all(MYSQLI_ASSOC);
} }
} }
/** /**
* Fetch one row of the result as associative array. * @param $sql
* * @param array $keys
* @return null|mixed the first row of the result of Database::execute() as associative array. * @return array|bool
* If Database::execute() has never been called prior a call to this method, false is returned. * @throws \qfq\DbException
* Empty string is returned if the query didn't yield any rows.
*/ */
public function fetchOne() { public function sqlKeys($sql, array &$keys) {
if ($this->stmt == null) {
return false;
}
if ($this->stmt->rowCount() === 0) { return $this->sql($sql, ROW_KEYS, array(), '', $keys);
return array(); }
}
return $this->stmt->fetch(\PDO::FETCH_ASSOC); /**
* Returns lastInsertId
*
* @return string
*/
public function getLastInsertId() {
return $this->mysqli->insert_id;
} }
} }
\ No newline at end of file
...@@ -31,7 +31,7 @@ require_once(__DIR__ . '/../../qfq/Database.php'); ...@@ -31,7 +31,7 @@ require_once(__DIR__ . '/../../qfq/Database.php');
/** /**
* Class Store * Class Store
* @package qfq\store * @package qfq
*/ */
class Store