Update DatabaseMysql and VMXTemplate, fix some notices

master
vitalif 2013-04-09 15:14:26 +00:00
parent 5b8968e5e5
commit c10676624d
4 changed files with 264 additions and 87 deletions

View File

@ -3,7 +3,8 @@
/**
* Very stable interface for MySQL - object-oriented at last :)
* Select builder is inspired by MediaWiki's one.
* (c) Vitaliy Filippov, 2012
* Also usable for querying SphinxQL.
* (c) Vitaliy Filippov, 2012-2013
*/
if (!defined('MS_HASH'))
@ -31,6 +32,8 @@ class DatabaseMysql implements Database
var $tableNames = array();
var $queryLogFile;
var $reconnect = true;
var $autoBegin;
var $ondestroy = 'commit';
var $queryCount = 0;
var $link;
@ -46,8 +49,11 @@ class DatabaseMysql implements Database
* dbname DB name to use
* username Username
* password Password
* reconnect Whether to reconnect on idle timeout [true]
* tableNames Table name mappings (virtual => real)
* queryLogFile Path to query log file
* reconnect Whether to reconnect on idle timeout [true]
* autoBegin Whether to automatically begin a transaction of first query [false]
* ondestroy commit/rollback/none during destruction [commit]
*/
function __construct($options)
{
@ -59,7 +65,10 @@ class DatabaseMysql implements Database
'username' => '',
'password' => '',
'reconnect' => true,
'tableNames' => array(),
'queryLogFile' => '',
'autoBegin' => false,
'ondestroy' => 'commit',
);
$options += $defOpts;
if ($options['socket'])
@ -72,6 +81,16 @@ class DatabaseMysql implements Database
}
}
function __destruct()
{
$o = $this->ondestroy;
if (($o === 'commit' || $o === 'rollback') && $this->transactions)
{
$this->transactions = array(false);
$this->$o();
}
}
function connect()
{
if ($this->socket !== NULL)
@ -96,6 +115,10 @@ class DatabaseMysql implements Database
else
{
$this->link->set_charset('utf8');
if ($this->autoBegin)
{
$this->begin();
}
}
}
@ -109,6 +132,18 @@ class DatabaseMysql implements Database
return "`".str_replace("`", "``", $name)."`";
}
function quoteInto($str, $params)
{
$i = 0;
$r = '';
while (($p = strpos($str, '?')) !== false)
{
$r .= substr($str, 0, $p) . $this->quote($params[$i++]);
$str = substr($str, $p+1);
}
return $r.$str;
}
function quote($value)
{
if ($value === NULL)
@ -124,26 +159,88 @@ class DatabaseMysql implements Database
function query($sql, $fetchMode = MYSQLI_STORE_RESULT)
{
if (!$this->link)
{
$this->connect();
}
$this->queryCount++;
if ($this->queryLogFile)
{
file_put_contents($this->queryLogFile, date("[Y-m-d H:i:s] ").$sql."\n", FILE_APPEND);
}
$r = $this->link->query($sql, $fetchMode);
if (!$r)
{
if ($this->link->errno == 2006 && $this->reconnect && !$this->transactions)
{
// "MySQL server has gone away"
$this->connect();
$r = $this->link->query($sql, $fetchMode);
if (!$r && $this->link->errno == 2006)
{
$this->link = false;
}
}
if (!$r)
{
throw new DatabaseException($this->link->error . "\nQuery: $sql", $this->link->errno);
}
}
return $r;
}
function multi_select(array $queries, $format = 0)
{
if (!$this->link)
{
$this->connect();
}
$r = $this->link->query($sql, $fetchMode);
$this->queryCount += count($queries);
$log = '';
foreach ($queries as &$sql)
{
if (!is_string($sql))
{
$sql = $this->select_builder($sql[0], $sql[1], $sql[2], @$sql[3]);
}
$log .= date("[Y-m-d H:i:s] ").$sql."\n";
}
unset($sql);
if ($this->queryLogFile)
{
file_put_contents($this->queryLogFile, $log, FILE_APPEND);
}
$sql = implode('; ', $queries);
$r = $this->link->multi_query($sql);
if (!$r)
{
if ($this->link->errno == 2006 && $this->reconnect)
if ($this->link->errno == 2006 && $this->reconnect && !$this->transactions)
{
// "MySQL server has gone away"
$this->link = false;
$this->connect();
$r = $this->link->multi_query($sql);
if (!$r && $this->link->errno == 2006)
{
$this->link = false;
}
}
if (!$r)
{
throw new DatabaseException($this->link->error, $this->link->errno);
}
throw new DatabaseException($this->link->error, $this->link->errno);
}
return $r;
$results = array();
$i = 0;
foreach ($queries as $k => $q)
{
if ($i++)
{
$this->link->next_result();
}
$r = $this->link->store_result();
$results[$k] = $this->fetch_all($r, $format);
}
return $results;
}
/**
@ -220,6 +317,8 @@ class DatabaseMysql implements Database
* 'conditional expression',
* 'field_name' => 'value',
* 'field_name' => array('one', 'of', 'values'),
* 'field_name < ?' => 'value',
* 'field_name < DATE_SUB(?, ?)' => array('arg1', 'arg2'),
* 'field1,field2' => array(array(1, 2), array(3, 4)),
* )
*/
@ -231,7 +330,21 @@ class DatabaseMysql implements Database
foreach ($where as $k => $v)
{
if (ctype_digit("$k"))
$wh[] = $v;
{
if (is_array($v))
{
$str = array_shift($v);
$wh[] = $this->quoteInto($str, $v);
}
else
{
$wh[] = $v;
}
}
elseif (($p = strrpos($k, '?')) !== false)
{
$wh[] = $this->quoteInto($k, (array)$v);
}
elseif (is_array($v))
{
if (!$v)
@ -240,7 +353,7 @@ class DatabaseMysql implements Database
}
else
{
if (is_array($v[0]))
if (is_array(reset($v)))
foreach ($v as &$l)
$l = "(" . implode(",", array_map(array($this, 'quote'), $l)) . ")";
else
@ -248,15 +361,22 @@ class DatabaseMysql implements Database
$wh[] = "$k IN (" . implode(",", $v) . ")";
}
}
elseif (preg_match('/^-?\d+(\.\d+)?$/s', $v)) // int/float
$wh[] = "$k=$v";
elseif ($v !== NULL)
$wh[] = "$k=".$this->quote($v);
else
$wh[] = "$k IS NULL";
}
if (!$wh)
$where = '1';
else
if (!$this->username && !$this->password && !$this->dbname)
{
// Sphinx supports neither brackets nor OR operator as of 2.0.6-release O_o
$where = join(' AND ', $wh);
}
elseif ($where)
$where = '(' . join(') AND (', $wh) . ')';
else
$where = '';
return $where;
}
@ -275,8 +395,11 @@ class DatabaseMysql implements Database
* 'FOR UPDATE' or 'LOCK IN SHARE MODE'
* 'GROUP BY' => array($groupby_field1 => 'ASC', $groupby_field2 => 'DESC')
* 'ORDER BY' => array($orderby_field1 => 'ASC', $orderby_field2 => 'DESC')
* 'LIMIT' => array($limit, $offset) or array($limit) or just $limit
* 'LIMIT' => array($offset, $limit) or array($limit) or just $limit
* 'OFFSET' => $offset, for the case when 'LIMIT' is just $limit
*
* Sphinx Search extensions:
* 'WITHIN GROUP ORDER BY' => array($orderby_field => 'ASC')
*/
function select_builder($tables, $fields, $where, $options = NULL)
{
@ -304,40 +427,23 @@ class DatabaseMysql implements Database
$sql .= 'SQL_NO_CACHE ';
elseif (isset($options['CACHE']) || isset($options['SQL_CACHE']))
$sql .= 'SQL_CACHE ';
$sql .= "$fields FROM $tables WHERE $where";
if (isset($options['GROUP BY']))
$sql .= "$fields FROM $tables";
if ($where)
{
$g = $options['GROUP BY'];
if (is_array($g))
{
$g1 = array();
foreach ($g as $k => $v)
{
if (ctype_digit("$k"))
$g1[] = $v;
else
$g1[] = "$k $v";
}
$g = join(',', $g1);
}
$sql .= " GROUP BY $g";
$sql .= " WHERE $where";
}
if (isset($options['ORDER BY']))
if (!empty($options['GROUP BY']) && $options['GROUP BY'] !== '0')
{
$g = $options['ORDER BY'];
if (is_array($g))
{
$g1 = array();
foreach ($g as $k => $v)
{
if (ctype_digit("$k"))
$g1[] = $v;
else
$g1[] = "$k $v";
}
$g = join(',', $g1);
}
$sql .= " ORDER BY $g";
$sql .= " GROUP BY ".$this->order_option($options['GROUP BY']);
}
if (!empty($options['ORDER BY']) && $options['GROUP BY'] !== '0')
{
$sql .= " ORDER BY ".$this->order_option($options['ORDER BY']);
}
if (!empty($options['WITHIN GROUP ORDER BY']) && $options['WITHIN GROUP GROUP BY'] !== '0')
{
// Sphinx Search extension
$sql .= " WITHIN GROUP ORDER BY ".$this->order_option($options['WITHIN GROUP ORDER BY']);
}
$sql .= $this->limit_option($options);
if (isset($options['FOR UPDATE']))
@ -347,10 +453,30 @@ class DatabaseMysql implements Database
return $sql;
}
/**
* Handles ORDER BY or GROUP BY options
*/
protected function order_option($g)
{
if (is_array($g))
{
$g1 = array();
foreach ($g as $k => $v)
{
if (ctype_digit("$k"))
$g1[] = $v;
else
$g1[] = "$k $v";
}
$g = join(',', $g1);
}
return $g;
}
/**
* Handles a single LIMIT or LIMIT and OFFSET options.
*/
function limit_option($options)
protected function limit_option($options)
{
if (isset($options['LIMIT']))
{
@ -400,7 +526,7 @@ class DatabaseMysql implements Database
else
$table = (isset($this->tableNames[$v[1]]) ? $this->quoteId($this->tableNames[$v[1]]) : $v[1]) . ' ' . $k;
if ($t)
$t .= " $join JOIN $table ON ".$this->where_builder($v[2]);
$t .= " $join JOIN $table ON ".($this->where_builder($v[2]) ?: '1=1');
else
$t = $table;
continue;
@ -448,7 +574,7 @@ class DatabaseMysql implements Database
*/
function select($tables, $fields = '*', $where = 1, $options = NULL, $format = 0)
{
if (is_int($fields) || is_string($fields) && ctype_digit($fields))
if (is_int($fields))
{
$sql = $tables;
$format = $fields;
@ -457,10 +583,16 @@ class DatabaseMysql implements Database
$sql = $this->select_builder($tables, $fields, $where, $options);
if ($format & MS_RESULT)
return $this->query($sql, MYSQLI_USE_RESULT);
$res = $this->query($sql);
return $this->fetch_all($res, $format);
}
function fetch_all($res, $format = 0)
{
if ($format & MS_LIST)
$r = $this->get_rows($sql);
$r = $this->get_rows($res);
else
$r = $this->get_assocs($sql);
$r = $this->get_assocs($res);
if (!$r)
$r = array();
if ($format & MS_ROW)
@ -478,6 +610,7 @@ class DatabaseMysql implements Database
}
elseif ($format & MS_COL)
{
$k = false;
foreach ($r as $i => $v)
{
if (!$k)
@ -507,7 +640,7 @@ class DatabaseMysql implements Database
function delete($tables, $where, $options = NULL)
{
$tables = $this->tables_builder($tables);
$where = $this->where_builder($where);
$where = $this->where_builder($where) ?: '1=1';
$sql = "DELETE FROM $tables WHERE $where";
$sql .= $this->limit_option($options);
$this->query($sql);
@ -518,8 +651,9 @@ class DatabaseMysql implements Database
* Builds an INSERT query.
* @param string $table Table name to insert rows to.
* @param array $rows Array of table rows to be inserted.
* @param boolean $onduplicatekey If true, include
* ON DUPLICATE KEY UPDATE column=VALUES(column) for all columns.
* @param boolean $onduplicatekey If true, create MySQL-specific "UPSERT" query using
* INSERT .. ON DUPLICATE KEY UPDATE column=VALUES(column) for all columns.
* @param boolean $replace If true, use REPLACE instead of INSERT
*/
function insert_builder($table, $rows, $onduplicatekey = false, $replace = false)
{
@ -535,8 +669,9 @@ class DatabaseMysql implements Database
$rs[] = $this->quote($r[$k]);
$r = "(".implode(",", $rs).")";
}
$sphinx = !$this->username && !$this->password && !$this->dbname;
foreach ($key as &$k)
if (strpos($k, '`') === false)
if (strpos($k, '`') === false && (!$sphinx || $k !== 'id'))
$k = $this->quoteId($k);
$sql = ($replace ? "REPLACE" : "INSERT").
" INTO $table (".implode(",",$key).") VALUES ".implode(",",$rows);
@ -592,21 +727,19 @@ class DatabaseMysql implements Database
return false;
if (!is_array(@$rows[0]))
$rows = array($rows);
$sql = $this->insert_builder($table, $rows, true, !empty($options['replace']));
$sql = $this->insert_builder($table, $rows, empty($options['REPLACE']), !empty($options['REPLACE']));
}
else
{
$sql = array();
if (!is_array($rows))
$rows = array($rows);
foreach ($rows as $k => $v)
foreach ((array)$rows as $k => $v)
{
if (!ctype_digit("$k"))
$sql[] = $k.'='.$this->quote($v);
else
$sql[] = $v;
}
$where = $this->where_builder($where);
$where = $this->where_builder($where) ?: '1=1';
$sql = 'UPDATE ' . $this->tables_builder($table) . ' SET ' . implode(', ', $sql) . ' WHERE ' . $where;
$sql .= $this->limit_option($options);
}
@ -615,10 +748,16 @@ class DatabaseMysql implements Database
return false;
}
function get_rows($sql)
function replace($table, $rows, $where = NULL, $options = array())
{
$options['REPLACE'] = true;
return $this->update($table, $rows, $where, $options);
}
protected function get_rows($res)
{
$r = array();
if ($res = $this->query($sql))
if ($res)
{
while ($row = $res->fetch_row())
$r[] = $row;
@ -627,10 +766,10 @@ class DatabaseMysql implements Database
return $r;
}
function get_assocs($sql)
protected function get_assocs($res)
{
$r = array();
if ($res = $this->query($sql))
if ($res)
{
while ($row = $res->fetch_assoc())
$r[] = $row;

View File

@ -8,7 +8,9 @@
# Homepage: http://yourcmc.ru/wiki/VMX::Template
# Author: Vitaliy Filippov, 2006-2013
# $Id$
# $Id: template.php 1394 2013-04-09 15:01:39Z vitalif $
# TODO for perl version - rewrite it and prevent auto-vivification on a.b
class VMXTemplateState
{
@ -48,7 +50,7 @@ class VMXTemplate
static $Mon, $mon, $Wday;
static $cache_type = NULL;
static $cache = array();
static $safe_tags = '<div> <span> <a> <b> <i> <u> <p> <h1> <h2> <h3> <h4> <h5> <h6> <strike> <strong> <small> <big> <blink> <center> <ol> <pre> <sub> <sup> <font> <br> <table> <tr> <td> <th> <tbody> <tfoot> <thead> <tt> <ul> <li> <em> <img> <marquee>';
static $safe_tags = '<div> <blockquote> <span> <a> <b> <i> <u> <p> <h1> <h2> <h3> <h4> <h5> <h6> <strike> <strong> <small> <big> <blink> <center> <ol> <pre> <sub> <sup> <font> <br> <table> <tr> <td> <th> <tbody> <tfoot> <thead> <tt> <ul> <li> <em> <img> <marquee>';
// Timestamp format constants
const TS_UNIX = 0;
@ -281,8 +283,7 @@ class VMXTemplate
}
if (!class_exists($class) || !isset($class::$version) || $class::$version < self::CODE_VERSION)
{
// Cache file from some older version - reset it
$this->options->error("Please, clear template cache path after upgrading VMX::Template", true);
$this->options->error("MD5 collision :) file=$fn, cache=$file", true);
$this->failed[$fn] = true;
return NULL;
}
@ -440,8 +441,8 @@ class VMXTemplate
*/
static function filter_strip_space(&$text)
{
$text = preg_replace('/^[ \t\v]+/m', '', $text);
$text = preg_replace('/[ \t\v]+$/m', '', $text);
$text = preg_replace('/^[ \t]+/m', '', $text);
$text = preg_replace('/[ \t]+$/m', '', $text);
}
/*** Function implementations ***/
@ -615,6 +616,22 @@ class VMXTemplate
return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 0, 1);
}
// Select one of 3 plural forms for russian language
static function plural_ru($count, $one, $few, $many)
{
$sto = $count % 100;
if ($sto >= 10 && $sto <= 20)
return $many;
switch ($count % 10)
{
case 1: return $one;
case 2:
case 3:
case 4: return $few;
}
return $many;
}
// Limited-edition timestamp parser
static function timestamp($ts = 0, $format = 0)
{
@ -628,7 +645,7 @@ class VMXTemplate
{
// TS_UNIX or Epoch
if (!$ts)
$ts = time;
$ts = time();
}
elseif (preg_match('/^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$/s', $ts, $m))
{
@ -804,16 +821,17 @@ class VMXTemplateParser
var $tokens, $tokpos, $tokline, $ptr;
// Possible tokens consisting of special characters
static $chartokens = '+ - = * / % ! , . < > ( ) { } [ ] | .. || && == != <= >= =>';
static $chartokens = '# + - = * / % ! , . < > ( ) { } [ ] & .. || && == != <= >= =>';
// ops_and: ops_eq | ops_eq "&&" ops_and | ops_eq "AND" ops_and
// ops_and: ops_bitand | ops_bitand "&&" ops_and | ops_bitand "AND" ops_and
// ops_bitand: ops_eq | ops_eq "&" ops_bitand
// ops_eq: ops_cmp | ops_cmp "==" ops_cmp | ops_cmp "!=" ops_cmp
// ops_cmp: ops_add | ops_add '<' ops_add | ops_add '>' ops_add | ops_add "<=" ops_add | ops_add ">=" ops_add
// ops_add: ops_mul | ops_mul '+' ops_add | ops_mul '-' ops_add
// ops_mul: exp_neg | exp_neg '*' ops_mul | exp_neg '/' ops_mul | exp_neg '%' ops_mul
static $ops = array(
'or' => array(array('||', '$or', '$xor'), 'and', true),
'and' => array(array('&&', '$and'), 'eq', true),
'and' => array(array('&&', '$and'), 'bitand', true),
'bitand' => array(array('&'), 'eq', true),
'eq' => array(array('==', '!='), 'cmp', false),
'cmp' => array(array('<', '>', '<=', '>='), 'add', false),
'add' => array(array('+', '-'), 'mul', true),
@ -1093,8 +1111,8 @@ class VMXTemplateParser
}
$text = "Unexpected $tok, expected ";
if (count($expected) > 1)
$text .= "one of '";
$text .= implode("', '", $expected)."'";
$text .= "one of ";
$text .= "'".implode("', '", $expected)."'";
$this->raise($text);
}
@ -1200,10 +1218,23 @@ class VMXTemplateParser
try
{
// Try to parse from here, skip invalid parts
$r = $this->$handler();
$this->consume($this->eod);
// Add newline count from code fragment
$this->lineno += substr_count($this->code, "\n", $pos, $this->pos-$pos);
if ($this->tok() == '#')
{
// Comment!
$this->pos = strpos($this->code, $this->eod, $this->pos);
if ($this->pos === false)
{
throw new VMXTemplateParseException($this->eod . ' not found');
}
$this->pos += strlen($this->eod);
}
else
{
$r = $this->$handler();
$this->consume($this->eod);
// Add newline count from code fragment
$this->lineno += substr_count($this->code, "\n", $pos, $this->pos-$pos);
}
}
catch (VMXTemplateParseException $e)
{
@ -1502,6 +1533,7 @@ $varref = array_pop(\$stack);";
if ($this->tok() == ',')
$this->ptr++;
}
$this->ptr++;
}
$code = false;
if ($this->tok() == '=')
@ -1842,7 +1874,7 @@ $varref_index = \$stack[count(\$stack)-1]++;";
}
$r = "\$this->parent->call_block($parts[0], $args, \"".addslashes($this->errorinfo())."\")";
}
if (count($parts) == 1)
elseif (count($parts) == 1)
{
$fn = strtolower($parts[0]);
if (isset(self::$functions[$fn]))
@ -2005,6 +2037,7 @@ $varref_index = \$stack[count(\$stack)-1]++;";
function function_strip($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; }
/* удаление "небезопасных" HTML-тегов */
/* TODO: м.б исправлять некорректную разметку? */
function function_strip_unsafe($e) { return "strip_tags($e, self::\$safe_tags)"; }
/* заменить \n на <br /> */
@ -2036,10 +2069,9 @@ $varref_index = \$stack[count(\$stack)-1]++;";
}
/* strftime */
function function_strftime($fmt, $date, $time = '')
function function_strftime($fmt, $date)
{
$e = $time ? "($date).' '.($time)" : $date;
return "strftime($fmt, self::timestamp($e))";
return "strftime($fmt, self::timestamp($date))";
}
/* ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что. */
@ -2050,6 +2082,12 @@ $varref_index = \$stack[count(\$stack)-1]++;";
return "self::" . ($this->options->use_utf8 ? "mb_" : "") . "strlimit(".join(",", $a).")";
}
/* выбор правильной формы множественного числа для русского языка */
function function_plural_ru($count, $one, $few, $many)
{
return "self::plural_ru($count, $one, $few, $many)";
}
/** Массивы и хеши **/
/* создание хеша */

View File

@ -343,7 +343,7 @@ class OLAP
}
$vars = array('build' => 1);
if ($this->group_fields[0]['type'] == 'graph')
if ($this->group_fields && $this->group_fields[0]['type'] == 'graph')
{
$vars['graphs'] = $this->build_graphs($this->group_fields, $data, $tdkeys);
$vars['groups'] = $this->tpl_jsgraphs($vars['graphs']);
@ -682,7 +682,7 @@ class OLAP
$i = count($tdhead);
foreach ($cells as &$v)
{
if (!$rows[$i])
if (empty($rows[$i]))
$rows[$i] = array();
$rows[$i] = array_merge($rows[$i], $v);
$i++;

View File

@ -236,7 +236,7 @@ function doPlot(logarithmic)
<!-- SET o = 0 -->
<!-- FOR d = graph.desc -->
<!--# Описание графика (поле: значение, ..., только значения, не равные значениям предыдущего графика) -->
<!-- SET o = or(o, not(graph_index), ne(get(get(get(get(graphs,sub(graph_index,1)),'desc'),d_index),'value'),d.value)) -->
<!-- SET o = o || !graph_index || graphs[graph_index-1].desc[d_index].value != d.value -->
<!-- IF o -->
<p style="margin-left: {mul(d_index,20)}px">{d.field}<!-- IF d.func --> ({lc d.func})<!-- END -->: {d.value}</p>
<!-- END -->
@ -271,7 +271,7 @@ function doPlot(logarithmic)
<!-- IF table.desc -->
<!-- SET o = 0 -->
<!-- FOR d = table.desc -->
<!-- SET o = or(o, not(table_index), ne(get(get(get(get(tables,sub(table_index,1)),'desc'),d_index),'value'),d.value)) -->
<!-- SET o = o || !table_index || tables[table_index-1].desc[d_index].value != d.value -->
<!-- IF o -->
<p style="margin-left: {mul(d_index,20)}px">{d.field}<!-- IF d.func --> ({lc d.func})<!-- END -->: {d.value}</p>
<!-- END -->