395 lines
11 KiB
PHP
395 lines
11 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Very stable interface for MySQL - object-oriented at last :)
|
||
|
* Select builder is inspired by MediaWiki's one.
|
||
|
* (c) Vitaliy Filippov, 2012
|
||
|
*/
|
||
|
|
||
|
define('MS_HASH', 0);
|
||
|
define('MS_LIST', 1);
|
||
|
define('MS_ROW', 2);
|
||
|
define('MS_ONE', 2);
|
||
|
define('MS_COL', 4);
|
||
|
define('MS_VALUE', 6);
|
||
|
define('MS_RESULT', 8);
|
||
|
|
||
|
class DatabaseMysql
|
||
|
{
|
||
|
var $dbName;
|
||
|
var $link;
|
||
|
var $tableNames = array();
|
||
|
var $queryCount = 0;
|
||
|
var $queryLogFile;
|
||
|
|
||
|
function __construct($host, $username, $password, $dbname, $port = NULL, $socket = NULL)
|
||
|
{
|
||
|
if ($socket !== NULL)
|
||
|
{
|
||
|
$this->link = new mysqli($host, $username, $password, $dbname, $port, $socket);
|
||
|
}
|
||
|
elseif ($port !== NULL)
|
||
|
{
|
||
|
$this->link = new mysqli($host, $username, $password, $dbname, $port);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$this->link = new mysqli($host, $username, $password, $dbname);
|
||
|
}
|
||
|
$this->dbName = $dbname;
|
||
|
}
|
||
|
|
||
|
function getDBName()
|
||
|
{
|
||
|
return $this->dbName;
|
||
|
}
|
||
|
|
||
|
function quoteId($name)
|
||
|
{
|
||
|
return "`".str_replace("`", "``", $name)."`";
|
||
|
}
|
||
|
|
||
|
function quote($value)
|
||
|
{
|
||
|
if ($value === NULL)
|
||
|
return "NULL";
|
||
|
return "'" . $this->link->real_escape_string($s) . "'";
|
||
|
}
|
||
|
|
||
|
function query($sql, $fetchMode = MYSQLI_STORE_RESULT)
|
||
|
{
|
||
|
$this->queryCount++;
|
||
|
if ($this->queryLogFile)
|
||
|
{
|
||
|
file_put_contents($this->queryLogFile, date("[Y-m-d H:i:s] ").$sql."\n", FILE_APPEND);
|
||
|
}
|
||
|
return $this->link->query($sql, $fetchMode);
|
||
|
}
|
||
|
|
||
|
function where_builder($where)
|
||
|
{
|
||
|
if (!is_array($where))
|
||
|
return $where;
|
||
|
$wh = array();
|
||
|
foreach ($where as $k => $v)
|
||
|
{
|
||
|
if (ctype_digit($k))
|
||
|
$wh[] = $v;
|
||
|
elseif (is_array($v))
|
||
|
{
|
||
|
if (!$v)
|
||
|
{
|
||
|
throw new DatabaseException(E_DB_EMPTY_IN, "Error: empty array for '$k IN (...)', don't know what to do");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (is_array($v[0]))
|
||
|
foreach ($v as &$l)
|
||
|
$l = "(" . implode(",", array_map(array($this, 'quote'), $l)) . ")";
|
||
|
else
|
||
|
$v = array_map(array($this, 'quote'), $v);
|
||
|
$wh[] = "$k IN (" . implode(",", $v) . ")";
|
||
|
}
|
||
|
}
|
||
|
elseif ($v !== NULL)
|
||
|
$wh[] = "$k=".$this->quote($v);
|
||
|
else
|
||
|
$wh[] = "$k IS NULL";
|
||
|
}
|
||
|
if (!$wh)
|
||
|
$where = '1';
|
||
|
else
|
||
|
$where = '(' . join(') AND (', $wh) . ')';
|
||
|
return $where;
|
||
|
}
|
||
|
|
||
|
function select_builder($tables, $fields, $where, $options = NULL)
|
||
|
{
|
||
|
if (!$options)
|
||
|
$options = array();
|
||
|
else
|
||
|
{
|
||
|
foreach ($options as $k => $v)
|
||
|
if (ctype_digit($k))
|
||
|
$options[$v] = true;
|
||
|
}
|
||
|
if (is_array($fields))
|
||
|
$fields = join(',', $fields);
|
||
|
$where = $this->where_builder($where);
|
||
|
$tables = $this->tables_builder($tables);
|
||
|
$sql = 'SELECT ';
|
||
|
if ($options['CALC_FOUND_ROWS'] || $options['SQL_CALC_FOUND_ROWS'])
|
||
|
$sql .= 'SQL_CALC_FOUND_ROWS ';
|
||
|
if ($options['NO_CACHE'] || $options['SQL_NO_CACHE'])
|
||
|
$sql .= 'SQL_NO_CACHE ';
|
||
|
elseif ($options['CACHE'] || $options['SQL_CACHE'])
|
||
|
$sql .= 'SQL_CACHE ';
|
||
|
$sql .= "$fields FROM $tables WHERE $where";
|
||
|
if ($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";
|
||
|
}
|
||
|
if ($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";
|
||
|
}
|
||
|
if ($g = $options['LIMIT'])
|
||
|
{
|
||
|
if (is_array($g))
|
||
|
$g = join(',', $g);
|
||
|
if ($g)
|
||
|
$sql .= " LIMIT $g";
|
||
|
}
|
||
|
if ($options['FOR UPDATE'])
|
||
|
$sql .= ' FOR UPDATE';
|
||
|
elseif ($options['LOCK IN SHARE MODE'])
|
||
|
$sql .= ' LOCK IN SHARE MODE';
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
function tables_builder($tables)
|
||
|
{
|
||
|
if (is_array($tables))
|
||
|
{
|
||
|
$t = '';
|
||
|
foreach ($tables as $k => $v)
|
||
|
{
|
||
|
if (!ctype_digit($k))
|
||
|
{
|
||
|
if (is_array($v))
|
||
|
{
|
||
|
$join = substr(strtolower($v[0]), 0, 4);
|
||
|
if ($join == 'righ')
|
||
|
$join = 'RIGHT';
|
||
|
elseif ($join == 'left')
|
||
|
$join = 'LEFT';
|
||
|
else /* if (!$join || $join == 'inne' || $join == 'join') */
|
||
|
$join = 'INNER';
|
||
|
if (is_array($v[1])) // nested join (left join (A join B on ...) on ...)
|
||
|
$table = '('.$this->tables_builder($v[1]).')';
|
||
|
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]);
|
||
|
else
|
||
|
$t = $table;
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
$v = (isset($this->tableNames[$v]) ? $this->quoteId($this->tableNames[$v]) : $v) . ' ' . $k;
|
||
|
}
|
||
|
else
|
||
|
$v = (isset($this->tableNames[$v]) ? $this->quoteId($this->tableNames[$v]).' '.$v : $v);
|
||
|
if ($t)
|
||
|
$t .= " INNER JOIN $v";
|
||
|
else
|
||
|
$t = $v;
|
||
|
}
|
||
|
$tables = $t;
|
||
|
}
|
||
|
else
|
||
|
$tables = preg_replace_callback('/((?:,|JOIN)\s*`)([^`]+)/s', array($this, 'tables_builder_pregcb1'), $tables);
|
||
|
return $tables;
|
||
|
}
|
||
|
|
||
|
function tables_builder_pregcb1($m)
|
||
|
{
|
||
|
$t = $this->tableNames[$m[2]];
|
||
|
if (!$t)
|
||
|
$t = $m[2];
|
||
|
return $m[1].$t;
|
||
|
}
|
||
|
|
||
|
function select($tables, $fields = '*', $where = 1, $options = NULL, $format = 0)
|
||
|
{
|
||
|
if (!strcmp(intval($fields), "$fields"))
|
||
|
{
|
||
|
$sql = $tables;
|
||
|
$format = $fields;
|
||
|
}
|
||
|
else
|
||
|
$sql = $this->select_builder($tables, $fields, $where, $options);
|
||
|
if ($format & MS_RESULT)
|
||
|
return $this->query($sql, MYSQLI_USE_RESULT);
|
||
|
if ($format & MS_LIST)
|
||
|
$r = $this->get_rows($sql);
|
||
|
else
|
||
|
$r = $this->get_assocs($sql);
|
||
|
if (!$r)
|
||
|
$r = array();
|
||
|
if ($format & MS_ROW)
|
||
|
{
|
||
|
if ($format & MS_COL)
|
||
|
{
|
||
|
if (!count($r))
|
||
|
return NULL;
|
||
|
$r = $r[0];
|
||
|
$k = array_keys($r);
|
||
|
$r = $r[$k[0]];
|
||
|
}
|
||
|
else
|
||
|
$r = $r[0];
|
||
|
}
|
||
|
elseif ($format & MS_COL)
|
||
|
{
|
||
|
foreach ($r as $i => $v)
|
||
|
{
|
||
|
if (!$k)
|
||
|
{
|
||
|
$k = array_keys($v);
|
||
|
$k = $k[0];
|
||
|
}
|
||
|
$r[$i] = $v[$k];
|
||
|
}
|
||
|
}
|
||
|
return $r;
|
||
|
}
|
||
|
|
||
|
function found_rows()
|
||
|
{
|
||
|
return $this->select('SELECT FOUND_ROWS()', MS_VALUE);
|
||
|
}
|
||
|
|
||
|
function delete($tables, $where, $options = NULL)
|
||
|
{
|
||
|
$tables = $this->tables_builder($tables);
|
||
|
$where = $this->where_builder($where);
|
||
|
$sql = "DELETE FROM $tables WHERE $where";
|
||
|
if (is_array($options))
|
||
|
{
|
||
|
if ($g = $options['LIMIT'])
|
||
|
{
|
||
|
if (is_array($g))
|
||
|
$g = join(',', $g);
|
||
|
if ($g)
|
||
|
$sql .= " LIMIT $g";
|
||
|
}
|
||
|
}
|
||
|
$this->query($sql);
|
||
|
return $this->link->affected_rows;
|
||
|
}
|
||
|
|
||
|
function insert_builder($table, $rows, $onduplicatekey = false)
|
||
|
{
|
||
|
if ($this->tableNames[$table])
|
||
|
$table = $this->tableNames[$table];
|
||
|
$key = array_keys($rows[0]);
|
||
|
foreach ($rows as &$r)
|
||
|
{
|
||
|
$rs = array();
|
||
|
foreach ($key as &$k)
|
||
|
$rs[] = $this->quote($r[$k]);
|
||
|
$r = "(".implode(",", $rs).")";
|
||
|
}
|
||
|
foreach ($key as &$k)
|
||
|
if (strpos($k, '`') === false)
|
||
|
$k = $this->quoteId($k);
|
||
|
$sql = "INSERT INTO $table (".implode(",",$key).") VALUES ".implode(",",$rows);
|
||
|
if ($onduplicatekey)
|
||
|
{
|
||
|
foreach ($key as &$k)
|
||
|
$k = "$k=VALUES($k)";
|
||
|
$sql .= " ON DUPLICATE KEY UPDATE ".implode(",",$key);
|
||
|
}
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
function insert_row($table, $row)
|
||
|
{
|
||
|
$sql = $this->insert_builder($table, array($row));
|
||
|
if ($this->query($sql))
|
||
|
return $this->link->insert_id;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
function update($table, $rows, $where = NULL, $options = NULL)
|
||
|
{
|
||
|
if (!$rows)
|
||
|
return false;
|
||
|
if (is_null($where))
|
||
|
{
|
||
|
if (!is_array($rows))
|
||
|
return false;
|
||
|
if (!is_array($rows[0]))
|
||
|
$rows = array($rows);
|
||
|
$sql = $this->insert_builder($table, $rows, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$sql = array();
|
||
|
if (!is_array($rows))
|
||
|
$rows = array($rows);
|
||
|
foreach ($rows as $k => $v)
|
||
|
{
|
||
|
if (!ctype_digit($k))
|
||
|
$sql[] = $k.'='.$this->quote($v);
|
||
|
else
|
||
|
$sql[] = $v;
|
||
|
}
|
||
|
$where = $this->where_builder($where);
|
||
|
$sql = 'UPDATE ' . $this->tables_builder($table) . ' SET ' . implode(', ', $sql) . ' WHERE ' . $where;
|
||
|
if (is_array($options))
|
||
|
{
|
||
|
if ($g = $options['LIMIT'])
|
||
|
{
|
||
|
if (is_array($g))
|
||
|
$g = join(',', $g);
|
||
|
if ($g)
|
||
|
$sql .= " LIMIT $g";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ($this->query($sql))
|
||
|
return $this->link->affected_rows;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function get_rows($sql)
|
||
|
{
|
||
|
$r = array();
|
||
|
if ($res = $this->query($sql))
|
||
|
{
|
||
|
while ($row = $res->fetch_row())
|
||
|
$r[] = $row;
|
||
|
$res->free();
|
||
|
}
|
||
|
return $r;
|
||
|
}
|
||
|
|
||
|
function get_assocs($sql)
|
||
|
{
|
||
|
$r = array();
|
||
|
if ($res = $this->query($sql))
|
||
|
{
|
||
|
while ($row = $res->fetch_assoc())
|
||
|
$r[] = $row;
|
||
|
$res->free();
|
||
|
}
|
||
|
return $r;
|
||
|
}
|
||
|
}
|