X-Git-Url: http://git.polytechnique.org/?a=blobdiff_plain;f=classes%2Fxdb.php;h=6239b4b1e913c7fb7151d06a3bd1a635df06f957;hb=refs%2Fheads%2Fcore%2Fmaster;hp=9bda36c1e1850ea780b873f1a0d2061c9b794fce;hpb=ed3f4d3e4b4bfb8f480f96ad7c6750d5ea5b0674;p=platal.git diff --git a/classes/xdb.php b/classes/xdb.php index 9bda36c..6239b4b 100644 --- a/classes/xdb.php +++ b/classes/xdb.php @@ -1,6 +1,6 @@ dbhost, $globals->dbuser, $globals->dbpwd, $globals->dbdb); + if ($globals->debug & DEBUG_BT) { + $bt = new PlBacktrace('MySQL'); + if (mysqli_connect_errno()) { + $bt->newEvent("MySQLI connection", 0, mysqli_connect_error()); + return false; + } + } + self::$mysqli->autocommit(true); + self::$mysqli->set_charset($globals->dbcharset); + return true; + } + + public static function prepare($args) + { + global $globals; + $query = array_map(Array('XDB', 'escape'), $args); + $query[0] = preg_replace('/#([a-z0-9]+)#/', $globals->dbprefix . '$1', $args[0]); + $query[0] = str_replace('%', '%%', $query[0]); + $query[0] = str_replace('{?}', '%s', $query[0]); return call_user_func_array('sprintf', $query); } - public static function _reformatQuery($query) + public static function reformatQuery($query) { - $query = preg_split("/\n\\s*/", $query); + $query = preg_split("/\n\\s*/", trim($query)); $length = 0; foreach ($query as $key=>$line) { $local = -2; - if (preg_match('/^([A-Z]+(?:\s+(?:JOIN|BY|FROM|INTO))?)\s+(.*)/', $line, $matches) + if (preg_match('/^([A-Z]+(?:\s+(?:JOIN|BY|FROM|INTO))?)\s+(.*)/u', $line, $matches) && $matches[1] != 'AND' && $matches[1] != 'OR') { $local = strlen($matches[1]); @@ -55,65 +74,318 @@ class XDB return $res; } - public static function _query($query) + public static function run($query) { global $globals; - if ($globals->debug & 1) { - $_res = mysql_query("EXPLAIN $query"); + if (!self::$mysqli && !self::connect()) { + header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error'); + Platal::page()->kill('Impossible de se connecter à la base de données.'); + exit; + } + + if ($globals->debug & DEBUG_BT) { $explain = array(); - while ($row = @mysql_fetch_assoc($_res)) { - $explain[] = $row; + if (strpos($query, 'FOUND_ROWS()') === false && strpos($query, 'AUTOCOMMIT') === false) { + $res = self::$mysqli->query("EXPLAIN $query"); + if ($res) { + while ($row = $res->fetch_assoc()) { + $explain[] = $row; + } + $res->free(); + } } - $trace_data = array('query' => XDB::_reformatQuery($query), 'explain' => $explain); - @mysql_free_result($_res); - $time_start = microtime(); + PlBacktrace::$bt['MySQL']->start(XDB::reformatQuery($query)); } - $res = mysql_query($query); - - if ($globals->debug & 1) { - list($ue, $se) = explode(" ", microtime()); - list($us, $ss) = explode(" ", $time_start); - $time = intval((($ue - $us) + ($se - $ss)) * 1000); - $trace_data['error'] = mysql_error(); - $trace_data['exectime'] = $time; - $trace_data['rows'] = @mysql_num_rows() ? mysql_num_rows() : mysql_affected_rows(); - $GLOBALS['XDB::trace_data'][] = $trace_data; - if (mysql_errno()) { - $GLOBALS['XDB::error'] = true; - } + $res = XDB::$mysqli->query($query); + + if ($globals->debug & DEBUG_BT) { + PlBacktrace::$bt['MySQL']->stop(@$res->num_rows ? $res->num_rows : self::$mysqli->affected_rows, + self::$mysqli->error, + $explain); } + if ($res === false) { + throw new XDBException(XDB::reformatQuery($query), XDB::$mysqli->error); + } return $res; } + private static function queryv($query) + { + return new XDBResult(self::prepare($query)); + } + public static function query() { - return new XOrgDBResult(XDB::_prepare(func_get_args())); + return self::queryv(func_get_args()); + } + + public static function rawQuery($query) + { + return new XDBResult($query); + } + + public static function format() + { + return self::prepare(func_get_args()); + } + + // Produce the SQL statement for setting/unsetting a flag + public static function changeFlag($fieldname, $flagname, $state) + { + if ($state) { + return XDB::format($fieldname . ' = CONCAT({?}, \',\', ' . $fieldname . ')', $flagname); + } else { + return XDB::format($fieldname . ' = REPLACE(' . $fieldname . ', {?}, \'\')', $flagname); + } + } + + // Produce the SQL statement representing an array + public static function formatArray(array $array) + { + return self::escape($array); + } + + const WILDCARD_EXACT = 0x00; + const WILDCARD_PREFIX = 0x01; + const WILDCARD_SUFFIX = 0x02; + const WILDCARD_CONTAINS = 0x03; // WILDCARD_PREFIX | WILDCARD_SUFFIX + + // Produce a valid XDB argument that get formatted as a wildcard + // according to the given mode. + // + // Example: + // XDB::query("SELECT * FROM table WHERE field {?}", XDB::wildcard($text, WILDCARD_EXACT)); + public static function wildcard($mode, $value) + { + return new XDBWildcard($value, $mode); + } + + // Returns the SQL statement for a wildcard search. + public static function formatWildcards($mode, $text) + { + return XDB::wildcard($mode, $text)->format(); + } + + // Returns a FIELD(blah, 3, 1, 2) for use in an order with custom orders + public static function formatCustomOrder($field, $values) + { + return 'FIELD( ' . $field . ', ' . implode(', ', array_map(array('XDB', 'escape'), $values)) . ')'; } public static function execute() { - return XDB::_query(XDB::_prepare(func_get_args())); + global $globals; + $args = func_get_args(); + if ($globals->mode != 'rw' && !strpos($args[0], 'logger')) { + return; + } + return self::run(XDB::prepare($args)); + } + + public static function rawExecute($query) + { + global $globals; + if ($globals->mode != 'rw') { + return; + } + return self::run($query); + } + + private static $inTransaction = false; + public static function startTransaction() + { + if (self::$inTransaction) { + throw new XDBException('START TRANSACTION', 'Already in a transaction'); + } + self::$inTransaction = true; + self::rawExecute('SET AUTOCOMMIT = 0'); + self::rawExecute('START TRANSACTION'); + } + + public static function commit() + { + self::rawExecute('COMMIT'); + self::rawExecute('SET AUTOCOMMIT = 1'); + self::$inTransaction = false; + } + + public static function rollback() + { + self::rawExecute('ROLLBACK'); + self::rawExecute('SET AUTOCOMMIT = 1'); + self::$inTransaction = false; + } + + public static function runTransactionV($callback, array $args) + { + self::startTransaction(); + try { + if (call_user_func_array($callback, $args)) { + self::commit(); + return true; + } else { + self::rollback(); + return false; + } + } catch (Exception $e) { + self::rollback(); + throw $e; + } + } + + /** This function takes a callback followed by the arguments to be passed to the callback + * as arguments. It starts a transaction and execute the callback. If the callback fails + * (return false or raise an exception), the transaction is rollbacked, if the callback + * succeeds (return true), the transaction is committed. + */ + public static function runTransaction() + { + $args = func_get_args(); + $cb = array_shift($args); + return self::runTransactionV($cb, $args); } public static function iterator() { - return new XOrgDBIterator(XDB::_prepare(func_get_args())); + return new XDBIterator(self::prepare(func_get_args())); + } + + public static function rawIterator($query) + { + return new XDBIterator($query); } public static function iterRow() { - return new XOrgDBIterator(XDB::_prepare(func_get_args()), MYSQL_NUM); + return new XDBIterator(self::prepare(func_get_args()), MYSQL_NUM); + } + + public static function rawIterRow($query) + { + return new XDBIterator($query, MYSQL_NUM); + } + + private static function findQuery($params, $default = array()) + { + for ($i = 0 ; $i < count($default) ; ++$i) { + $is_query = false; + foreach (array('insert', 'select', 'replace', 'delete', 'update') as $kwd) { + if (stripos($params[0], $kwd) !== false) { + $is_query = true; + break; + } + } + if ($is_query) { + break; + } else { + $default[$i] = array_shift($params); + } + } + return array($default, $params); + } + + /** Fetch all rows returned by the given query. + * This functions can take 2 optional arguments (cf XDBResult::fetchAllRow()). + * Optional arguments are given *before* the query. + */ + public static function fetchAllRow() + { + list($args, $query) = self::findQuery(func_get_args(), array(false, false)); + return self::queryv($query)->fetchAllRow($args[0], $args[1]); + } + + public static function rawFetchAllRow($query, $id = false, $keep_array = false) + { + return self::rawQuery($query)->fetchAllRow($id, $keep_array); + } + + /** Fetch all rows returned by the given query. + * This functions can take 2 optional arguments (cf XDBResult::fetchAllAssoc()). + * Optional arguments are given *before* the query. + */ + public static function fetchAllAssoc() + { + list($args, $query) = self::findQuery(func_get_args(), array(false, false)); + return self::queryv($query)->fetchAllAssoc($args[0], $args[1]); + } + + public static function rawFetchAllAssoc($query, $id = false, $keep_array = false) + { + return self::rawQuery($query)->fetchAllAssoc($id, $keep_array); + } + + public static function fetchOneCell() + { + list($args, $query) = self::findQuery(func_get_args()); + return self::queryv($query)->fetchOneCell(); + } + + public static function rawFetchOneCell($query) + { + return self::rawQuery($query)->fetchOneCell(); + } + + public static function fetchOneRow() + { + list($args, $query) = self::findQuery(func_get_args()); + return self::queryv($query)->fetchOneRow(); + } + + public static function rawFetchOneRow($query) + { + return self::rawQuery($query)->fetchOneRow(); + } + + public static function fetchOneAssoc() + { + list($args, $query) = self::findQuery(func_get_args()); + return self::queryv($query)->fetchOneAssoc(); + } + + public static function rawFetchOneAssoc($query) + { + return self::rawQuery($query)->fetchOneAssoc(); + } + + /** Fetch a column from the result of the given query. + * This functions can take 1 optional arguments (cf XDBResult::fetchColumn()). + * Optional arguments are given *before* the query. + */ + public static function fetchColumn() + { + list($args, $query) = self::findQuery(func_get_args(), array(0)); + return self::queryv($query)->fetchColumn($args[0]); + } + + public static function rawFetchColumn($query, $key = 0) + { + return self::rawQuery($query)->fetchColumn($key); } public static function insertId() { - return mysql_insert_id(); + return self::$mysqli->insert_id; + } + + public static function errno() + { + return self::$mysqli->errno; } - public static function _db_escape($var) + public static function error() + { + return self::$mysqli->error; + } + + public static function affectedRows() + { + return self::$mysqli->affected_rows; + } + + public static function escape($var) { switch (gettype($var)) { case 'boolean': @@ -131,95 +403,174 @@ class XDB return 'NULL'; case 'object': + if ($var instanceof XDBFormat) { + return $var->format(); + } else { + return "'".addslashes(serialize($var))."'"; + } + case 'array': - return "'".addslashes(serialize($var))."'"; + return '(' . implode(', ', array_map(array('XDB', 'escape'), $var)) . ')'; default: die(var_export($var, true).' is not a valid for a database entry'); } } +} - public static function trace_format(&$page, $template = 'database-debug.tpl') { - $page->assign('trace_data', @$GLOBALS['XDB::trace_data']); - $page->assign('db_error', @$GLOBALS['XDB::error']); - return $page->fetch($template); +class XDBException extends PlException +{ + public function __construct($query, $error) + { + if (strpos($query, 'INSERT') === false && strpos($query, 'UPDATE') === false + && strpos($query, 'REPLACE') === false && strpos($query, 'DELETE') === false) { + $text = 'Erreur lors de l\'interrogation de la base de données'; + } else { + $text = 'Erreur lors de l\'écriture dans la base de données'; + } + parent::__construct($text, $query . "\n" . $error); } } -class XOrgDBResult +interface XDBFormat { + public function format(); +} + +class XDBWildcard implements XDBFormat +{ + private $value; + private $mode; + + public function __construct($value, $mode) + { + $this->value = $value; + $this->mode = $mode; + } - var $_res; + public function format() + { + if ($this->mode == XDB::WILDCARD_EXACT) { + return XDB::format(' = {?}', $this->value); + } else { + $text = str_replace(array('%', '_'), array('\%', '\_'), $this->value); + if ($this->mode & XDB::WILDCARD_PREFIX) { + $text = $text . '%'; + } + if ($this->mode & XDB::WILDCARD_SUFFIX) { + $text = '%' . $text; + } + return XDB::format(" LIKE {?}", $text); + } + } +} + + +class XDBResult +{ + private $res; - function XOrgDBResult($query) + public function __construct($query) { - $this->_res = XDB::_query($query); + $this->res = XDB::run($query); } - function free() + public function free() { - mysql_free_result($this->_res); + if ($this->res) { + $this->res->free(); + } unset($this); } - function _fetchRow() + protected function fetchRow() { - return mysql_fetch_row($this->_res); + return $this->res ? $this->res->fetch_row() : null; } - function _fetchAssoc() + protected function fetchAssoc() { - return mysql_fetch_assoc($this->_res); + return $this->res ? $this->res->fetch_assoc() : null; } - function fetchAllRow() + public function fetchAllRow($id = false, $keep_array = false) { $result = Array(); - while ($result[] = mysql_fetch_row($this->_res)) { } - array_pop($result); + if (!$this->res) { + return $result; + } + while (($data = $this->res->fetch_row())) { + if ($id !== false) { + $key = $data[$id]; + unset($data[$id]); + if (!$keep_array && count($data) == 1) { + reset($data); + $result[$key] = current($data); + } else { + $result[$key] = $data; + } + } else { + $result[] = $data; + } + } $this->free(); return $result; } - function fetchAllAssoc() + public function fetchAllAssoc($id = false, $keep_array = false) { $result = Array(); - while ($result[] = mysql_fetch_assoc($this->_res)) { } - array_pop($result); + if (!$this->res) { + return $result; + } + while (($data = $this->res->fetch_assoc())) { + if ($id !== false) { + $key = $data[$id]; + unset($data[$id]); + if (!$keep_array && count($data) == 1) { + reset($data); + $result[$key] = current($data); + } else { + $result[$key] = $data; + } + } else { + $result[] = $data; + } + } $this->free(); return $result; } - function fetchOneAssoc() + public function fetchOneAssoc() { - $tmp = $this->_fetchAssoc(); + $tmp = $this->fetchAssoc(); $this->free(); return $tmp; } - function fetchOneRow() + public function fetchOneRow() { - $tmp = $this->_fetchRow(); + $tmp = $this->fetchRow(); $this->free(); return $tmp; } - function fetchOneCell() + public function fetchOneCell() { - $tmp = $this->_fetchRow(); + $tmp = $this->fetchRow(); $this->free(); return $tmp[0]; } - function fetchColumn($key = 0) + public function fetchColumn($key = 0) { $res = Array(); if (is_numeric($key)) { - while($tmp = $this->_fetchRow()) { + while($tmp = $this->fetchRow()) { $res[] = $tmp[$key]; } } else { - while($tmp = $this->_fetchAssoc()) { + while($tmp = $this->fetchAssoc()) { $res[] = $tmp[$key]; } } @@ -227,53 +578,99 @@ class XOrgDBResult return $res; } - function numRows() + public function fetchOneField() + { + return $this->res ? $this->res->fetch_field() : null; + } + + public function fetchFields() + { + $res = array(); + while ($res[] = $this->fetchOneField()); + return $res; + } + + public function numRows() + { + return $this->res ? $this->res->num_rows : 0; + } + + public function fieldCount() { - return mysql_num_rows($this->_res); + return $this->res ? $this->res->field_count : 0; } } -class XOrgDBIterator -{ - private $_result; - private $_pos; - private $_total; - private $_mode = MYSQL_ASSOC; - function __construct($query, $mode = MYSQL_ASSOC) +class XDBIterator extends XDBResult implements PlIterator +{ + private $result; + private $pos; + private $total; + private $fpos; + private $fields; + private $mode = MYSQL_ASSOC; + + public function __construct($query, $mode = MYSQL_ASSOC) { - $this->_result =& new XOrgDBResult($query); - $this->_pos = 0; - $this->_total = $this->_result->numRows(); - $this->_mode = $mode; + parent::__construct($query); + $this->pos = 0; + $this->total = $this->numRows(); + $this->fpost = 0; + $this->fields = $this->fieldCount(); + $this->mode = $mode; } - function next() + public function next() { - $this->_pos ++; - if ($this->_pos > $this->_total) { - $this->_result->free(); + $this->pos ++; + if ($this->pos > $this->total) { + $this->free(); unset($this); return null; } - return $this->_mode != MYSQL_ASSOC ? $this->_result->_fetchRow() : $this->_result->_fetchAssoc(); + return $this->mode != MYSQL_ASSOC ? $this->fetchRow() : $this->fetchAssoc(); + } + + public function first() + { + return $this->pos == 1; + } + + public function last() + { + return $this->pos == $this->total; + } + + public function total() + { + return $this->total; + } + + public function nextField() + { + $this->fpos++; + if ($this->fpos > $this->fields) { + return null; + } + return $this->fetchOneField(); } - function first() + public function firstField() { - return $this->_pos == 1; + return $this->fpos == 1; } - function last() + public function lastField() { - return $this->_last == $this->_total; + return $this->fpos == $this->fields; } - function total() + public function totalFields() { - return $this->_total; + return $this->fields; } } -// vim:set et sw=4 sts=4 sws=4 foldmethod=marker: +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker fenc=utf-8: ?>