<?php
/**
 * db functions
 *
 * @version:    0.1
 * @author:    alex 'alex_ez' yaroshevich <qfox@ya.ru>
 * @link:    http://src.qfox.ru/
 * @date:    11 sep 2007
 *
 */


/**
 * some thinkings about library and architecture:

 Считаю, что прецеденты, которые должны быть в библиотеке работы с БД, должны быть следующими:
 * Выборка списков
  - Плоского с идентификаторами
  - Плоского с тикерами
  - Вертикального с идентификаторами
  - Исторического списка с последними данными
  - Списка тикеров для составных таблиц (исторических, вертикальных)
  - Списка изменений для исторического атрибута (пока формат не ясен, да и много чего, но это, очевидно, нужно)
   и ко всем выборкам должна быть единообразная фильтрация
 * Выборка элементов
  - Плоского
  - Тикерного
  - Ветки деревянного
  - Вертикального
  - Исторического
 */

if( !defined'DB' ) ) return;

define'SQL_INSERT''insert' );
define'SQL_INSERT_REPLACE''replace' );
define'SQL_INSERT_IGNORE''insert ignore' );

class 
SQLException extends Exception
{
    private 
$query;
    
    function 
__construct($message$code 0$query null)
    {
        
$this->query $query;
        
parent::__construct($message$code);
    }
    
    public function 
__toString()
    {
        return 
"sql: [{$this->code}]: {$this->message} in '{$this->query}'.\n";
    }
}

class 
sql {

static private 
$keys = array( );
static private 
$fields = array( );
static private 
$tables = array( );

static private 
$plainTables = array( );
static private 
$plainIndex = array( );
static private 
$runtimeCache = array( );

static public function 
select$q )
{
    
$md5q md5$q );
    if( isset( 
self::$runtimeCache$md5q ] ) )
    {
//        self::$queries[] = "runtime-cache: ".$q;
        
return self::$runtimeCache$md5q ];
    }
    
    
$_last $q;
    
/*if( xdebug_call_function() != 'get' && xdebug_call_line() != 365 )
    {
        self::$lastquery = $_last; self::$queries[] = array( "Called @ ".xdebug_call_file().":".xdebug_call_line()." from ".xdebug_call_function(), $_last );
    }*/
    
self::$lastquery $_lastself::$queries[] = $_last;
    
    if( 
function_exists'getTick' ) )
    {
        
$timenow getTick( );
        
d'sql: '.$q );
    }
    
    
$resultSet mysql_query$qDB );
    if( !
$resultSet )
    {
        
self::$runtimeCache$md5q ] = null;
        throw new 
SQLExceptionmysql_error(), mysql_errno(), $q );
    }
    
    
$_r = array( );
    while( 
$row mysql_fetch_assoc$resultSet ) )
        
$_r[] = $row;
    
mysql_free_result$resultSet );
    
    if( 
function_exists'getTick' ) )
    {
        
d'sql finished for '.(getTick( ) - $timenow).': '.count($_r).' rows' );
        
        
self::$time += getTick( ) - $timenow;
    }
    
    
self::$runtimeCache$md5q ] = $_r;
    
    return 
$_r;
}

static public function 
exec$q )
{
    
self::$lastquery $qself::$queries[] = self::$lastquery;
    
    
$_r null;
    
$q trim$q );
    
$command substr$q0strpos$q' ' ) );
    
    
$timenow getTick( );
    switch( 
$command ) {
    case 
"select":
        
$_r self::select$q );
        break;
    
    case 
"update":
    case 
"delete":
    case 
"replace";
        
d'sqlexec: '.$q );
        
$resultSet mysql_query$qDB );
        if( 
mysql_errno() )
        {
            throw new 
SQLExceptionmysql_error(), mysql_errno(), $q );
        }
        
        
$_r = ( is_resource$resultSet ) )
            ? 
mysql_affected_rows$resultSet )
            : 
$resultSet;
        
        
d'sqlexec finished for '.(getTick( ) - $timenow).': '.$_r );
        
        
self::$time += getTick( ) - $timenow;
        break;
    
    case 
"insert":
        
$_r '';
        
d'sqlexec: '.$q );
        
$resultSet mysql_query$qDB );
        if( 
mysql_errno() )
        {
            throw new 
SQLExceptionmysql_error(), mysql_errno(), $q );
        }
        
        if( 
$resultSet )
        
$_r mysql_insert_idDB );
        
d'sqlexec finished for '.(getTick( ) - $timenow).': '.$_r );
        
        
self::$time += getTick( ) - $timenow;
        break;
    
    default:
        
$timenow getTick( );
        
d'sqlexec: '.$q );
        
$resultSet mysql_query$qDB );
        
$_r mysql_infoDB );
        
d'sqlexec finished for '.(getTick( ) - $timenow).': '.$_r );
        
        
self::$time += getTick( ) - $timenow;
    }
    
    return 
$_r;
}

static public function 
tableFields$table )
{
    if( 
array_key_exists$tableself::$fields ) )
        return 
self::$fields$table ];
    
    
$_fields self::select"show fields from `{$table}`;" );
    if( !
is_array$_fields ) ) return null;
    
    
$_r = array( );
    foreach( 
$_fields as $_field )
    {
        
$_type self::parseType$_field'Type' ] );
        
$_default $_field'Default' ];
        
        
$_r$_field'Field' ] ] =
                
$_field'Type' ]
            . 
'||' $_default
            
'||' $_type;
    }
    
    
self::$fields$table ] = $_r;
    return 
$_r;
}

static public function 
tableKeys$table$type null )
{
    
//d( 'sqlTableKeys. table: '.$table.', type: '.$type );
    
    
if( array_key_exists$tableself::$keys ) )
    {
        
$_r self::$keys$table ];
    }
    else
    {
        
$_keys self::select"show keys from `{$table}`;" );
        if( !
is_array$_keys ) ) return null;
        
        
$_r = array( );
        foreach( 
$_keys as $_key )
            
$_rstrtolower$_key'Key_name' ] ) . '-' $_key'Seq_in_index' ] ] = $_key'Column_name' ];
        
        
self::$keys$table ] = $_r;
    }
    
    
    
// if not set type or not set {$type} key
    
if( is_null$type ) || !_z$_r$type.'-1' ) )
        return 
$_r;
    
    
// if {$type} key is alone
    
if( !_z$_r$type.'-2' ) ) return $_r$type.'-1' ];
    
    
// else
    
$__r = array( );
    foreach( 
$_r as $k => $field )
    if( 
substr($k,0,-2) == $type )
        
$__r[] = $field;
    
    return 
$__r;
}


static public function 
modify$table$ids$object )
{
    if( !
$table ) return null;
    if( !
self::isTable$table ) ) return null;
    
    
$query "update `{$table}` \n";
    
$query.= "set \n";
    foreach( 
$object as $key => $value ) {
        
$query .= "`{$key}` = ";
        
$query .= is_null$value ) ? 'null' : ("'".mysql_real_escape_string$valueDB )."'");
        
$query .= ",\n";
        }
    
$query substr$query0, -)."\n";
    
    
    
// where directive
    
if( is_array$ids ) )
    {
        
$keyFields self::primaryFields$table );
        
$tableKeys array_values$keyFields );
        
$queryKeys array_keys$ids );
        
sort$tableKeys );
        
sort$queryKeys );
        
        if( 
serialize$tableKeys ) !== serialize$queryKeys ) )
            return 
null;
    } else {
        
$key self::primaryField$tabletrue );
        if( 
is_null$key ) ) return null;
        
$ids = array( $key => $ids );
    }
    
    
$where " where \n";
    foreach( 
$ids as $key => $value ) {
        
$where.="`{$key}` = ";
        
$where .= is_null$value ) ? 'null' : ("'".mysql_real_escape_string$valueDB )."'");
        
$where .= " and \n";
        }
    
$where substr$where0, -);
    
// /where
    
    
$query.= $where.";";
    
    
$_r self::exec$query );
    if( 
$_r === '' )
        echo 
"<pre>".$query." /*$_r*/\n</pre>";
    return 
$_r;
}

static public function 
remove$table$ids )
{
    if( !
$table ) return null;
    if( !
self::isTable$table ) ) return null;
    
    if( 
is_array$ids ) )
    {
        
$keyFields self::primaryFields$table );
        
$tableKeys array_values$keyFields );
        
$queryKeys array_keys$ids );
        
//asd( array( serialize( $tableKeys ), serialize( $queryKeys ) ) );
        
if( serialize$tableKeys ) !== serialize$queryKeys ) )
            return 
null;
    } else {
        
$key self::primaryField$tabletrue );
        if( 
is_null$key ) ) return null;
        
$ids = array( $key => $ids );
    }
    
    
$query "delete from `{$table}` \n";
    
$query.= " where \n";
    foreach( 
$ids as $key => $value ) {
        
$query.="`{$key}` = ";
        
$query .= is_null$value ) ? 'null' : ("'".mysql_real_escape_string$valueDB )."'");
        
$query .= " and \n";
        }
    
$query substr$query0, -).";";
//    asd( $query );
//    return;
    
$_r self::exec$query );
    if( 
$_r === '' )
        echo 
"<pre>".$query." /*$_r*/\n</pre>";
    return 
$_r;
}

static public function 
put$table$object$method SQL_INSERT )
{
    
$query "{$method} into `{$table}` ";
    
$query.= "set ";
    foreach( 
$object as $key => $value ) {
        
$query .= "`{$key}` = '".mysql_real_escape_string$valueDB )."',";
        }
    
$query substr$query0, -).";";
    
$_r self::exec$query );
    if( 
$_r === '' )
        echo 
"<pre>".$query." /*$_r*/\n</pre>";
    return 
$_r;
}


static public function 
get$table$conditions null$orderby null$limiting null )
{
    if( !
$table ) return null;
    if( !
self::isTable$table ) ) return null;
    
    
// cache!!! need_crash_test!!!
    
if( !is_null$__cr self::getFromCache$table$conditions$orderby$limiting ) ) )
    {
        return 
$__cr;
    }
    
    
// берем примари поля
    
$fields self::tableFields$table );
    
$_sqlPF self::primaryField$table );
    
    
/// where directive
    
$where 'true';
    if( 
is_scalar$conditions ) )
        
$conditions = array( self::primaryField$table ) => $conditions );
    elseif( !
is_null$conditions ) )
    {
        
$shortTableName self::tnName$table );
        foreach( 
$conditions as $k => $_v // fullnames
        
if( !_z$fields$k ) && !_z$fields$shortTableName.'_'.$k ) )
            unset( 
$conditions$k ] );
        elseif( !
_z$fields$k ] ) ) // shortnames
        
{
            
$conditions$shortTableName.'_'.$k ] = $conditions$k ];
            unset( 
$conditions$k ] );
        }
        
    }
    
    if( 
is_array$conditions ) )
    foreach( 
$conditions as $_f => $_v )
    {
        
$where .= " and ( ";
        if( 
is_array$_v ) )
        {
            
$where .= "`$_f` in ( ";
            
$_where "";
            
$_whereaddon "";
            foreach( 
$_v as $__v )
            if( 
is_null$__v ) )
                
$_whereaddon "or isnull( {$_f} )";
            else
                
$_where .= (is_numeric$__v ) ? intval($__v) : "'".mysql_real_escape_string$__v )."'").", ";
            
$where .= substr$_where0, -) . " ) " $_whereaddon;
            
$_where null;
        }
        else
            
$where .= "`$_f` = '".mysql_real_escape_string$_v )."'";
        
$where .= " )";
    }
    
    
/// orderby
    
if( is_array$orderby ) ) $orderby join', '$orderby );
    
$orderby __$orderby'1+' );
    
$order str_replace( array( '+''-' ), array( ' asc'' desc' ), $orderby );
    
    
$limit '10000';
    if( !
is_null$limiting ) )
    {
        list( 
$count,$offset ) = array_reverseexplode','','.$limiting ) );
        
$limit intval($offset).', '.intval($count);
    }
    
    
/// executing
    
$query "select * from ".$table." where ".$where." order by ".$order." limit ".$limit.";";
    
$_last $query;
    
//self::$lastquery = $_last; self::$queries[] = $_last;
    //array( "Called @ ".xdebug_call_file().":".xdebug_call_line()." from ".xdebug_call_function(), $_last, json_encode(array('table'=>$table,'conditions'=>$conditions,'orderby'=>$orderby,'limiting'=>$limiting)) );
    
$objects self::select$query );
    
    if( 
is_null$objects ) ) return null;
    if( !
count$objects ) ) return $objects;
    
    
/// fetching
    
$_r = array( );
    if( !
is_null$_sqlPF ) )
            foreach( 
$objects as $__k => $object )
            
$_r$object[$_sqlPF] ] =& $objects$__k ];
    
    else
            foreach( 
$objects as $__k => $object )
            
$_r[] =& $objects$__k ];
    
    return 
$_r;
}

//"update %s set id=LAST_INSERT_ID(id+1);";
//"create table %s (id int not null)";
//"insert into %s values (%s)";
//"drop table %s";


static public function tables$dbName null$wide false )
{
    
$_tbs self::select"show tables".(!is_null($dbName)?" from {$dbName}":"").";" );
    if( 
is_null$_tbs ) ) return null;
    
    
$_r = array( );
    foreach( 
$_tbs as $_tb )
    {
        
$_tbName reset$_tb );
        if( 
$wide )
        {
            
$_r$_tbName ] = array( );
            
$_r$_tbName ][ 'name' ] = $_tbName;
            
$_r$_tbName ][ 'fields' ] = self::tableFields$_tbName );
            
$_r$_tbName ][ 'keys' ] = self::tableKeys$_tbName );
        }
        else
        {
            
$_r[] = $_tbName;
        }
    }
    
    return 
$_r;
}

static public function 
isTable$table )
{
    
$table str_replace( array( ' ''`'"\t""\n""\r" ), ''$table );
    
    
$db null;
    if( 
strpos$table'.' ) )
        list( 
$db$table ) = explode'.'$table );
    
    
$tables self::tables$db );
    return 
in_array$table$tables );
}

static public function 
primaryField$table$strict false )
{
    
$_keys self::tableKeys$table'primary' );
    if( !
is_array$_keys ) ) return $_keys;
    if( 
$strict && !empty( $_keys ) ) return reset$_keys );
    return 
null;
}

static public function 
primaryFields$table )
{
    return 
self::tableKeys$table'primary' );
}



static public function 
tnName$table )
{
    list( , 
$_r ) = explode'_'$table );
    return 
$_r;
}

static public function 
tnType$table )
{
    list( , , 
$_r ) = explode'_'$table );
    return 
$_r;
}

static public function 
tnSpace$table )
{
    list( 
$_r ) = explode'_'$table );
    return 
$_r;
}

static public function 
fnTruncate$field$table )
{
    
$tn self::tnName$table );
    return 
str_replace( array( "id_".$tn$tn."_id"$tn."_" ), array( "id""parent_id""" ), $field );
}

static public function 
fnsTruncate$array$table )
{
    
$tn self::tnName$table );
    
$k array_keys$array );
    
$v array_values$array );
    
array_walk$kcreate_function'&$field,$key,$tn''$field = str_replace( array( "id_".$tn, $tn."_id", $tn."_" ), array( "id", "parent_id", "" ), $field );' ), $tn );
    
$_r array_combine$k$v );
    return 
$_r;
}





static public function 
parseType$sqlType )
{
    
$guesses = array(
        
'char' => 'string',
        
'int' => 'int''long' => 'int''decimal' => 'int',
        
'text' => 'text',
        );
    
    foreach( 
$guesses as $guess => $solution )
    if( !(
strpos$sqlType$guess ) === false) )
        return 
$solution;
    
    return 
'string';
}


/**
 * debug divizion
 *
 */
static private $lastquery null;
static private 
$queries = array( );
static private 
$time 0;

static public function 
lastQuery( )
{
    return 
self::$lastquery;
}

static public function 
queries( )
{
    return 
self::$queries;
}

static public function 
time( )
{
    return 
self::$time;
}

/**
 * caching divizion
 *
 */
static private $cache_structure null;
static private 
$cache_data null;

static private function 
cachingInit( )
{
    
self::$cache_structure DIR_CSH.'__sql_structure';
    
self::$cache_data DIR_CSH.'__sql_const';
}

static public function 
caching( )
{
    
self::cachingInit( );
    
    if( !
file_existsself::$cache_structure ) )
    {
        foreach( 
self::tables( ) as $table )
        {
            
self::tableKeys$table );
            
self::tableFields$table );
        }
        
        
file_put_contents(
            
self::$cache_structure,
            
serialize( array( 'keys' => sql::$keys'fields' => sql::$fields ) )
            );
    } else {
        
$data unserializefile_get_contentsself::$cache_structure ) );
        
self::$keys $data['keys'];
        
self::$fields $data['fields'];
        unset( 
$data );
    }
    
    
    
self::$plainTables = array( );
    
self::$plainIndex = array( );
    
$staticTables = array(
        
'fbc_module_plain_tb' => array( 'module_ticker' ),
        
'fbc_language_ref_tb' => array( 'language_ticker' ),
        
'fbc_page_wa_tb' => array( ),
        
'fbc_instance_wa_tb' => array( ),
        
'fbc_user_wa_tb' => array( ),
        );
    
    if( !
file_existsself::$cache_data ) )
    {
        foreach( 
$staticTables as $table => $_index )
        {
            
self::$plainTables$table ] = sql::get$table );
            foreach( 
$_index as $_field )
            {
                foreach( 
array_keysself::$plainTables$table ] ) as $id )
                {
                    
$v self::$plainTables$table ][ $id ][ trim($_field,'&') ];
                    
self::$plainIndex$table ][ $_field ][ $v ] = $id;
                }
            }
        }
        
/*var_dump( self::$plainTables );
        foreach( self::$plainIndex as $table => $index )
        {
            echo "$table:<br/>";
            var_dump( $index );
        }
        exit;*/
        
file_put_contentsself::$cache_dataserialize( array( self::$plainTablesself::$plainIndex ) ) );
    } else {
        list( 
self::$plainTablesself::$plainIndex ) = unserializefile_get_contentsself::$cache_data ) );
    }
}

function 
getFromCache$table$conditions null$orderby null$limiting null )
{
    if( !isset( 
self::$plainTables$table ] ) || !is_null$orderby ) || !is_null$limiting ) )
    {
        return 
null;
    }
    
    if( 
is_scalar$conditions ) )
    {
        return array( 
$conditions => self::$plainTables$table ][ $conditions ] );
    }
    elseif( 
is_null$conditions ) )
    {
        return 
self::$plainTables$table ];
    }
    elseif( 
count$conditions ) == )
    {
        
$v reset$conditions );
        
$k key$conditions );
        if( !isset( 
self::$plainIndex$table ][ $k ][ $v ] ) )
        {
            return 
null;
        }
        
        
$result = array( );
        
$ids self::$plainIndex$table ][ $k ][ $v ];
//        self::$queries[] = "from cache: $table, ".print_r( $conditions, 1 ).", $orderby, $limiting -- ".print_r( $ids, 1 );
        
if( is_array$ids ) )
        {
            foreach( 
$ids as $id )
            {
                
$result$id ] = self::$plainTables$table ][ $id ];
            }
            return 
$result;
        }
        return 
self::$plainTables$table ][ $ids ];
    }
}

}


// alts
function sqlQueries( )
{
    return 
sql::queries( );
}

?>