382 lines
10 KiB
PHP
Executable File
382 lines
10 KiB
PHP
Executable File
<?php
|
|
|
|
class DB
|
|
{
|
|
|
|
static $dataCache = array();
|
|
static $link = false;
|
|
static $lastQuery = '';
|
|
static $keyDef = array();
|
|
static $affectedRows = 0;
|
|
static $writeOps = 0;
|
|
static $readOps = 0;
|
|
static $track_changes_in_tables = array();
|
|
|
|
static function isConnected()
|
|
{
|
|
return(self::$link instanceof mysqli);
|
|
}
|
|
|
|
static function connect()
|
|
{
|
|
if(self::$link) return;
|
|
if(!cfg('db/user')) die('database not configured');
|
|
Profiler::Log('DB::Connect() start');
|
|
self::$link = mysqli_connect(cfg('db/host'), cfg('db/user'), cfg('db/password'), cfg('db/database'), ini_get("mysqli.default_port"), cfg('db/socket')) or
|
|
critical('The database connection to server '.cfg('db/user').'@'.cfg('db/host').
|
|
' could not be established (code: '.@mysqli_connect_errno(self::$link).': '.@mysqli_connect_error(self::$link).')');
|
|
#if(mysqli_character_set_name(self::$link) != 'utf8')
|
|
mysqli_set_charset(self::$link, 'utf8mb4');
|
|
self::$track_changes_in_tables = cfg('track/changes');
|
|
Profiler::Log('DB::Connect() done');
|
|
}
|
|
|
|
static function Update($table, $searchCriteria, $updateFields)
|
|
{
|
|
self::$writeOps++;
|
|
DB::Query('UPDATE #'.DB::Safe($table).'
|
|
SET '.DB::MakeSetList($updateFields).'
|
|
WHERE '.DB::MakeSetList($searchCriteria, ' AND '));
|
|
}
|
|
|
|
static function GetCached($query, $parameters = null)
|
|
{
|
|
$cacheKey = 'dbq-'.md5($query.json_encode($parameters));
|
|
$result = Cache::Get($cacheKey);
|
|
if(!$result)
|
|
{
|
|
$result = self::Get($query, $parameters);
|
|
Cache::Set($cacheKey, $result);
|
|
}
|
|
return($result);
|
|
}
|
|
|
|
# get a list of datasets matching the $query
|
|
static function Get($query, $parameters = null, $keyByField = null)
|
|
{
|
|
self::$readOps++;
|
|
$result = array();
|
|
|
|
$query = self::ParseQueryParams($query, $parameters);
|
|
|
|
$lines = mysqli_query(self::$link, $query) or critical(mysqli_error(self::$link).' {query: '.$query.' }');
|
|
|
|
while ($line = mysqli_fetch_array($lines, MYSQLI_ASSOC))
|
|
{
|
|
if ($keyByField !== null && isset($line[$keyByField]))
|
|
$result[$line[$keyByField]] = $line;
|
|
else
|
|
$result[] = $line;
|
|
}
|
|
mysqli_free_result($lines);
|
|
|
|
Profiler::Log('DB::Get('.substr($query, 0, 40).'...)');
|
|
return $result;
|
|
}
|
|
|
|
# gets a list of keys for the table
|
|
static function Keys($tablename)
|
|
{
|
|
if(isset(self::$keyDef[$tablename]))
|
|
return(self::$keyDef[$tablename]);
|
|
|
|
self::$readOps++;
|
|
$result = array();
|
|
$sql = 'SHOW KEYS FROM `'.$tablename.'`';
|
|
$res = mysqli_query(self::$link, $sql) or critical('Cannot get keys // '.mysqli_error(self::$link));
|
|
|
|
while ($row = @mysqli_fetch_assoc($res))
|
|
{
|
|
if ($row['Key_name']=='PRIMARY')
|
|
array_push($result, $row['Column_name']);
|
|
}
|
|
|
|
Profiler::Log('DB::Keys('.$tablename.') REBUILD KEY CACHE');
|
|
self::$keyDef[$tablename] = $result;
|
|
return($result);
|
|
}
|
|
|
|
# get column info for $table
|
|
static function Info($table)
|
|
{
|
|
self::$readOps++;
|
|
$result = array('fields' => array(), 'info' => array());
|
|
foreach(self::Get('SHOW FULL COLUMNS FROM #'.$table) as $fld)
|
|
{
|
|
$ds = array();
|
|
foreach($fld as $k => $v)
|
|
{
|
|
$k = strtolower($k);
|
|
if($k == 'comment')
|
|
{
|
|
$p = explode(',', $v);
|
|
$v = false;
|
|
if(sizeof($p) > 0) foreach($p as $pi)
|
|
{
|
|
$pk = trim(nibble('=', $pi));
|
|
$ds['_'.$pk] = trim($pi);
|
|
}
|
|
}
|
|
if($v)
|
|
$ds[$k] = $v;
|
|
}
|
|
$ds['caption'] = first($ds['_title'], $ds['field']);
|
|
$result['fields'][$ds['field']] = $ds;
|
|
}
|
|
return($result);
|
|
}
|
|
|
|
# updates/creates the $dataset in the $tablename
|
|
static function Insert($tablename, $dataset)
|
|
{
|
|
self::$writeOps++;
|
|
$query='INSERT INTO '.$tablename.' ('.DB::MakeNamesList($dataset).
|
|
') VALUES('.DB::MakeValuesList($dataset).')';
|
|
|
|
mysqli_query(self::$link, $query) or critical(mysqli_error(self::$link).'{ '.$query.' }');
|
|
self::$affectedRows += mysqli_affected_rows(self::$link);
|
|
return(mysqli_insert_id(self::$link));
|
|
}
|
|
|
|
# updates/creates the $dataset in the $tablename
|
|
static function Commit($tablename, &$dataset)
|
|
{
|
|
self::$writeOps++;
|
|
$keynames = self::Keys($tablename);
|
|
$keyname = $keynames[0];
|
|
$keyvalue = $dataset[$keyname];
|
|
Profiler::Log('DB::Commit('.$tablename.', '.$dataset[$keyname].') start');
|
|
|
|
$cache_entry = $tablename.':'.$keyname.':'.$keyvalue;
|
|
unset(self::$dataCache[$cache_entry]);
|
|
|
|
$query='REPLACE INTO '.$tablename.' ('.DB::MakeNamesList($dataset).
|
|
') VALUES('.DB::MakeValuesList($dataset).');';
|
|
|
|
# keeping this around just in case, but performance seems the same:
|
|
# $query='INSERT INTO '.$tablename.' ('.DB::MakeNamesList($dataset).
|
|
# ') VALUES('.DB::MakeValuesList($dataset).')
|
|
# ON DUPLICATE KEY UPDATE '.DB::MakeSetList($dataset).';';
|
|
|
|
mysqli_query(self::$link, $query) or critical(mysqli_error(self::$link).' { '.$query.' }');
|
|
$dataset[$keyname] = first($dataset[$keyname], mysqli_insert_id(self::$link));
|
|
self::$dataCache[$cache_entry] = $dataset;
|
|
|
|
Profiler::Log('DB::Commit('.$tablename.', '.$dataset[$keyname].') done');
|
|
return($dataset[$keyname]);
|
|
}
|
|
|
|
static function GetRowsMatch($table, $matchOptions, $fillIfEmpty = true)
|
|
{
|
|
self::$readOps++;
|
|
$where = array('1');
|
|
foreach($matchOptions as $k => $v)
|
|
$where[] = '('.$k.'="'.DB::Safe($v).'")';
|
|
$iwhere = implode(' AND ', $where);
|
|
$query = 'SELECT * FROM '.($table).
|
|
' WHERE '.$iwhere;
|
|
$resultDS = self::GetRowWithQuery($query);
|
|
if ($fillIfEmpty && sizeof($resultDS) == 0)
|
|
foreach($matchOptions as $k => $v)
|
|
$resultDS[$k] = $v;
|
|
Profiler::Log('DB::GetRowsMatch('.$table.') done');
|
|
return($resultDS);
|
|
}
|
|
|
|
# from table $tablename, get dataset with key $keyvalue
|
|
static function GetRow($tablename, $keyvalue, $keyname = '', $options = array())
|
|
{
|
|
if($keyvalue == '0') return(array());
|
|
$fields = @$options['fields'];
|
|
$fields = first($fields, '*');
|
|
if (!self::$link) return(array());
|
|
|
|
if ($keyname == '')
|
|
{
|
|
$keynames = self::Keys($tablename);
|
|
$keyname = $keynames[0];
|
|
}
|
|
|
|
$cache_entry = $tablename.':'.$keyname.':'.$keyvalue;
|
|
if(isset(self::$dataCache[$cache_entry])) return(self::$dataCache[$cache_entry]);
|
|
|
|
$join = isset($options['join']) ? ' '.$options['join'] : '';
|
|
$query = 'SELECT '.$fields.' FROM '.$tablename.$join.' WHERE '.$keyname.'="'.DB::Safe($keyvalue).'";';
|
|
$queryResult = mysqli_query(self::$link, $query) or critical(mysqli_error(self::$link).' { Query: "'.$query.'" }');
|
|
self::$readOps++;
|
|
|
|
if ($line = @mysqli_fetch_array($queryResult, MYSQLI_ASSOC))
|
|
{
|
|
mysqli_free_result($queryResult);
|
|
self::$dataCache[$cache_entry] = $line;
|
|
Profiler::Log('DB::GetRow('.$tablename.', '.$keyvalue.')');
|
|
return($line);
|
|
}
|
|
else
|
|
$result = array();
|
|
|
|
Profiler::Log('DB::GetRow('.$tablename.', '.$keyvalue.') #fail');
|
|
return($result);
|
|
}
|
|
|
|
static function RemoveRow($tablename, $keyvalue, $keyname = null)
|
|
{
|
|
if ($keyname == null)
|
|
{
|
|
$keynames = self::Keys($tablename);
|
|
$keyname = $keynames[0];
|
|
}
|
|
$res = (mysqli_query(self::$link, 'DELETE FROM '.$tablename.' WHERE '.$keyname.'="'.
|
|
DB::Safe($keyvalue).'";')
|
|
or critical(' Cannot remove dataset // '.mysqli_error(self::$link)));
|
|
Profiler::Log('DB::RemoveRow('.$tablename.', '.$keyvalue.') done');
|
|
self::$affectedRows += mysqli_affected_rows(self::$link);
|
|
self::$writeOps++;
|
|
return($res);
|
|
}
|
|
|
|
// retrieve dataset identified by SQL $query
|
|
static function GetRowWithQuery($query, $parameters = null)
|
|
{
|
|
$query = self::ParseQueryParams($query, $parameters);
|
|
|
|
$queryResult = mysqli_query(self::$link, $query);
|
|
|
|
if(!$queryResult)
|
|
return(critical('Error getting data // '.mysqli_error(self::$link).'{ '.$query.' }'));
|
|
|
|
if ($line = mysqli_fetch_array($queryResult, MYSQLI_ASSOC))
|
|
{
|
|
$result = $line;
|
|
mysqli_free_result($queryResult);
|
|
}
|
|
else
|
|
$result = array();
|
|
|
|
Profiler::Log('DB::GetRowWithQuery('.$query.')');
|
|
self::$readOps++;
|
|
return($result);
|
|
}
|
|
|
|
# execute a simple update $query
|
|
static function Query($query, $parameters = null)
|
|
{
|
|
$query = self::parseQueryParams($query, $parameters);
|
|
if (substr($query, -1, 1) == ';')
|
|
$query = substr($query, 0, -1);
|
|
$result = (mysqli_query(self::$link, $query)
|
|
or critical(' Query error // '.mysqli_error(self::$link)));
|
|
Profiler::Log('DB::Query('.$query.') done');
|
|
self::$affectedRows += mysqli_affected_rows(self::$link);
|
|
self::$writeOps++;
|
|
return($result);
|
|
}
|
|
|
|
# create a comma-separated list of keys in $dataset
|
|
static function MakeNamesList(&$dataset)
|
|
{
|
|
$result = '';
|
|
if (sizeof($dataset) > 0)
|
|
foreach (array_keys($dataset) as $k)
|
|
{
|
|
if ($k!='')
|
|
$result = $result.','.$k;
|
|
}
|
|
return(substr($result, 1));
|
|
}
|
|
|
|
# make a name-value list for UPDATE-queries
|
|
static function MakeValuesList(&$dataset)
|
|
{
|
|
$result = '';
|
|
if (sizeof($dataset) > 0)
|
|
foreach ($dataset as $k => $v)
|
|
{
|
|
if ($k!='')
|
|
$result = $result.',"'.DB::safe($v).'"';
|
|
}
|
|
return(substr($result, 1));
|
|
}
|
|
|
|
static function MakeSetList(&$dataset, $concat = ', ')
|
|
{
|
|
$result = array();
|
|
if (sizeof($dataset) > 0) foreach ($dataset as $k => $v)
|
|
{
|
|
if(substr($k, -1) == '+' || substr($k, -1) == '-')
|
|
{
|
|
$op = substr($k, -1);
|
|
$k = substr($k, 0, -1);
|
|
$result[] = $k.' = '.$k.' '.$op.' "'.DB::safe($v).'"';
|
|
}
|
|
else
|
|
{
|
|
$result[] = $k.' = "'.DB::safe($v).'"';
|
|
}
|
|
}
|
|
return(implode($concat, $result));
|
|
}
|
|
|
|
static function ParseQueryParams($query, $parameters = null)
|
|
{
|
|
if ($parameters != null)
|
|
{
|
|
$pctr = 0;
|
|
$result = '';
|
|
for($a = 0; $a < strlen($query); $a++)
|
|
{
|
|
$chr = substr($query, $a, 1);
|
|
if ($chr == '?')
|
|
{
|
|
$result .= '"'.DB::Safe($parameters[$pctr]).'"';
|
|
$pctr++;
|
|
}
|
|
else if ($chr == '&')
|
|
{
|
|
$result .= ''.intval($parameters[$pctr]).'';
|
|
$pctr++;
|
|
}
|
|
else if ($chr == ':')
|
|
{
|
|
$paramName = '';
|
|
$a += 1;
|
|
$pFormat = 'string';
|
|
if($query[$a] == ':')
|
|
{
|
|
$pFormat = 'number';
|
|
$a += 1;
|
|
}
|
|
while(!ctype_space($chr = substr($query, $a, 1)) && $a < strlen($query))
|
|
{
|
|
$paramName .= $chr;
|
|
$a += 1;
|
|
}
|
|
if($pFormat == 'number')
|
|
$result .= ' '.($parameters[$paramName]+0).' ';
|
|
else
|
|
$result .= ' "'.DB::Safe($parameters[$paramName]).'" ';
|
|
}
|
|
else
|
|
$result .= $chr;
|
|
}
|
|
}
|
|
else
|
|
$result = $query;
|
|
$q = str_replace('#', cfg('db/prefix'), $result);
|
|
self::$lastQuery = $q;
|
|
return($q);
|
|
}
|
|
|
|
static function Safe($raw)
|
|
{
|
|
if(!self::$link)
|
|
return(addslashes($raw));
|
|
else
|
|
return(mysqli_real_escape_string(self::$link, $raw));
|
|
}
|
|
|
|
}
|
|
|
|
DB::connect();
|
|
|