Пыха.РуГотовые решенияgoDBИсходный код библиотеки

Исходный код библиотеки


<?php
/**
 * Библиотека для работы с базой данных MySQL
 * 
 * @package   goDB
 * @version   1.3.2 (31 января 2011)
 * @link      http://pyha.ru/go/godb/
 * @author    Григорьев Олег aka vasa_c (http://blgo.ru/blog/)
 * @copyright &copy; Григорьев Олег & PyhaTeam, 2007-2010
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL
 * @uses      php_mysqli (http://php.net/mysqli)
 */

class goDB extends mysqli implements goDBI
{

  /*** PUBLIC: ***/
    
    /**
     * Конструктор.
     *
     * Отличия от конструктора mysqli (http://php.net/manual/en/mysqli.connect.php):
     * 1. Исключение при ошибке подключения
     * 2. Дополнительный формат вызова: один аргумент - массив параметров
     * 3. Установка кодировки
     *
     * @link http://pyha.ru/go/godb/connect/
     *
     * @throws goDBExceptionConnect
     *         не подключиться или не выбрать базу
     *
     * @param mixed $host [optional]
     *        хост для подключения (возможен вариант "host:port")
     *        либо массив всех параметров
     * @param string $username [optional]
     *        имя пользователя mysql
     * @param string $passwd [optional]
     *        пароль пользователя mysql
     * @param string $dbname [optional]
     *        имя базы данных
     * @param int $port [optional]
     *        порт для подключения
     *        в случае указания аргументом и в строке $host используется аргумент
     * @param string $socket [optional]
     *        mysql-сокет для подключения
     */
    public function __construct($host = null, $username = null, $passwd = null, $dbname = null, $port = null, $socket = null) {       
    	if (is_array($host)) {
            $config = $host;
            $fields = array(
                'host', 'username', 'passwd', 'dbname', 'port', 
                'socket', 'charset', 'debug', 'prefix',
            );
            foreach ($fields as $field) {
                $$field = isset($config[$field]) ? $config[$field] : null;
            }
    		$this->setPrefix($prefix);
   			$this->setDebug($debug);
    	}
    	if (!$port) {
        	$host = explode(':', $host, 2);
        	$port = empty($host[1]) ? null : $host[1];        
        	$host = $host[0];    	
    	}
        @parent::__construct($host, $username, $passwd, $dbname, $port, $socket);
        if (mysqli_connect_errno()) {
            throw new goDBExceptionConnect(mysqli_connect_error(), mysqli_connect_errno());
        }
        if (!empty($charset)) {
            $this->set_charset($charset);
        }
    }

    /**
     * Выполнение запроса к базе
     *
     * @link http://pyha.ru/go/godb/query/
     *
     * @throws goDBExceptionQuery
     *         ошибка при запросе
     * @throws goDBExceptionData
     *         ошибочный шаблон или входные данные
     * @throws goDBExceptionFetch
     *         неизвестный или неожиданный формат представления
     * 
     * @param string $pattern
     *        sql-запрос или строка-шаблон с плейсхолдерами
     * @param array $data [optional]
     *        массив входных данных
     * @param string $fetch [optional]
     *        требуемый формат представления результата
     * @param string $prefix [optional]
     *        префикс имен таблиц
     * @return mixed
     *         результат запроса в заданном формате
     */
    public function query($pattern, $data = null, $fetch = null, $prefix = null) {        
		self::$qQuery++;
    	$query = $this->makeQuery($pattern, $data, $prefix);
        if ($this->queryWrapper) {
            $query = call_user_func_array($this->queryWrapper, array($query));
            if (!$query) {
                return false;
            }
        }
        $this->toDebug($query);
        if ($this->transactionFailed) {
            return false;
        }
        $result = parent::query($query, MYSQLI_STORE_RESULT);
        if ($this->errno) {
            if (is_object($result)) {
                $result->free();
            }
            throw new goDBExceptionQuery($query, $this->errno, $this->error);
        }
        $return = $this->fetch($result, $fetch);
        if ((is_object($result)) and ($result !== $return) and (!$this->isiterator)) {
            $result->free();
        }
        return $return;
    }

    /**
     * Выполнение запроса, путём вызова объекта, как метода
     *
     * @example $result = $db($pattern, $data, $fetch);
     * PHP >5.3
     */
    public function __invoke($pattern, $data = null, $fetch = null, $prefix = null) {
        return $this->query($pattern, $data, $fetch, $prefix);
    }

    /**
     * Старт транзакции
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @param bool $saveAC [optional]
     *        сохранять ли значение autocommit
     * @return int
     *         новый уровень вложенности
     */
    public function transactionBegin($saveAC = false) {
        $this->transactionLevel++;
        if ($this->transactionLevel > 1) {
            return $this->transactionLevel;
        }
        if ($saveAC) {
            $this->transactionACStored = $this->getAutocommit();
        } else {
            $this->transactionACStored = true;
        }
        parent::autocommit(false);
        $this->transactionFailed = false;
        return $this->transactionLevel;
    }

    /**
     * Подтверждение правильности данной транзакции
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @return bool
     *         произошло ли сохранение или это вложенная транзакция
     */
    public function transactionCommit() {
        if (!$this->inTransaction()) {
            return false;
        }
        $this->transactionLevel--;
        if ($this->transactionLevel > 0) {
            return false;
        }
        if (!$this->transactionFailed) {
            parent::commit();
        } else {
            parent::rollback();
            $this->transactionFailed = false;
        }
        parent::autocommit($this->transactionACStored);
        return true;
    }

    /**
     * Откат транзакции
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @throws goDBExceptionTransactionRollback
     *
     * @param bool $throw [optional]
     *        false - тихий откат
     *        true  - с выбросом исключения
     */
    public function transactionRollback($throw = false) {
        if (!$this->inTransaction()) {
            return false;
        }
        if ($throw) {
            $this->transactionLevel = 0;
            parent::rollback();
            parent::autocommit($this->transactionACStored);
            $this->transactionFailed = false;
            throw new goDBExceptionTransactionRollback();
        } else {
            $this->transactionLevel--;
            if ($this->transactionLevel == 0) {
                parent::rollback();
                parent::autocommit($this->transactionACStored);
                $this->transactionFailed = false;
                return true;
            }
            $this->transactionFailed = true;
        }
        return true;
    }

    /**
     * Выполнение функции внутри транзакции

     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @throws любые исключения из функции,
     *         кроме goDBExceptionTransactionRollback на верхнем уровне
     *
     * @param callback $callback
     *        функция, вызываемая внутри транзакции
     * @param bool $saveAC [optional]
     *        сохранять ли значение автокоммита
     */
    public function transactionRun($callback, $saveAC = false) {
        $level = $this->transactionBegin($saveAC);
        try {
            $result = call_user_func($callback);
            if ($result) {
                $this->transactionCommit();
            } else {
                $this->transactionRollback();
            }
            return $result;
        } catch (goDBExceptionTransactionRollback $e) {
            return false;
        } catch (goDBExceptionQuery $e) {
            $this->transactionRollback();
            throw $e;
        }
    }

    /**
     * Узнать уровень вложенности транзакции
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @return int
     *         уровень вложенности (1 - верхний, 0 - вне транзакции)
     */
    public function transactionLevel() {
        return $this->transactionLevel;
    }

    /**
     * Узнать, не отмечена ли траназкция уже, как провалившаяся
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @return bool
     */
    public function transactionFailed() {
        return $this->transactionFailed;
    }

    /**
     * Узнать, находимся ли мы внутри транзакции
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @return bool
     */
    public function inTransaction() {
        return ($this->transactionLevel > 0);
    }

    /**
     * Закрыть транзакцию, вне зависимости от уровня
     *
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @param bool $commit [optional]
     *        коммитить или откатывать
     */
    public function transactionClose($commit = false) {
        if (!$this->inTransaction()) {
            return false;
        }
        $this->transactionLevel = 1;
        if ($commit) {
            return $this->transactionCommit();
        } else {
            return $this->transactionRollback();
        }
    }

    /**
     * Переопределение mysqli::autocommit()
     *
     * @link http://ru.php.net/manual/en/mysqli.autocommit.php
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @param bool $mode
     *
     * @return bool
     */
    public function autocommit($mode) {
        if (!$this->inTransaction()) {
            return parent::autocommit($mode);
        }
        return true;
    }

    /**
     * Переопределение mysqli::commit()
     *
     * @link http://ru.php.net/manual/en/mysqli.commit.php
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @return bool
     */
    public function commit() {
        if (!$this->inTransaction()) {
            return parent::commit();
        }
        return true;
    }

    /**
     * Переопределение mysqli::commit()
     *
     * @link http://ru.php.net/manual/en/mysqli.rollback.php
     * @link http://pyha.ru/go/godb/transaction/
     *
     * @return bool
     */
    public function rollback() {
        if (!$this->inTransaction()) {
            return parent::rollback();
        }
        $this->transactionLevel++;
        return $this->transactionRollback();
    }

    /**
     * Получить значение автокоммита
     *
     * @return bool
     */
    public function getAutocommit() {
        return $this->query('SELECT @@autocommit', null, 'bool');
    }


    /**
     * Мультизапрос
     *
     * @link http://pyha.ru/go/godb/multi/
     *
     * @param mixed $patterns
     * @param array $datas [optional]
     * @param mixed $fetches [optional]
     * @param bool $transaction [optional]
     * @return array
     */
    public function multiQuery($patterns, $datas = null, $fetches = null, $transaction = true) {
        if ($this->transactionFailed) {
            return false;
        }
        $args = $this->argsMultiQuery($patterns, $datas, $fetches);
        if (!$args) {
            return array();
        }
        $queries = $args['queries'];
        $last    = $args['last'];
        $multi   = array();
        foreach ($queries as $q) {
            $multi[] = $this->makeQuery($q[0], $q[1]).';';
        }
        $multi = implode("\n", $multi);
        $this->toDebug("\n".$multi."\n");
        if ($transaction) {
            $this->transactionBegin();
        }
        $this->multi_query($multi);
        try {
            $results = $this->multiFetch($queries, $fetches, $last, $transaction);
        } catch (goDBExceptionFetch $e) {
            /* Ошибка разбора - требуется вытащить все оставшиеся результаты и закомитить */
            while ($this->more_results()) {
                $this->next_result();
            }
            if ($transaction) {
                $this->transactionCommit();
            }
            throw $e;
        }
        if ($transaction) {
            $this->transactionCommit();
        }
        return $results;
    }

    /**
     * Создание подготовленного выражения
     *
     * @override mysqli::prepare()
     *
     * @link http://pyha.ru/go/godb/prepare/
     * @link http://ru.php.net/manual/en/mysqli.prepare.php
     *
     * @param string $query
     *        шаблон подготовленного запроса
     * @param bool $godb
     *        использовать godb-стиль (иначе - mysqli)
     * @return goDBPrepare
     *         или mysqli_stmt
     */
    public function prepare($query, $godb = false) {
        if (!$godb) {
            return parent::prepare($query);
        }
        return (new goDBPrepare($this, $query));
    }

    /**
     * Создание и выполнение подготовленного выражения
     *
     * @link http://pyha.ru/go/godb/prepare/
     *
     * @param string $query
     *        шаблон запроса или имя именованного выражения
     * @param array $data
     *        входные данные
     * @param string $fetch
     *        формат разбора
     */
    public function prepareExecute($query, $data, $fetch, $cache = true) {
        if (!$cache) {
            if (empty($query)) {
                return false;
            }
            if ($query[0] == '#') {
                throw new goDBExceptionPrepareNamed('Cannot create prepare by one name');
            }
            $prepare = new goDBPrepare($this, $query);
            $result  = $prepare->execute($data, $fetch);
            $prepare->close();
            return $result;
        }
        $prepare = $this->getPrepare($query, true);
        if (!$prepare) {
            throw new goDBExceptionPrepareNamed('Prepare "'.$query.'" not found');
        }
        if (is_string($cache)) {
            if ($cache[0] != '#') {
                $cache = '#'.$cache;
            }
            $this->cachePrepare[$cache] = $prepare;
        }
        return $prepare->execute($data, $fetch);
    }

    /**
     * Создание именованного подготовленного выражения
     *
     * @param string $name
     *        имя выражения
     * @param string $query
     *        шаблон выражения
     * @return goDBPrepare

     *         созданное подготовленное выражение
     */
    public function prepareNamed($name, $query) {
        if (empty($name)) {
            return false;
        }
        if ($name[0] != '#') {
            $name = '#'.$name;
        }
        $prepare = new goDBPrepare($this, $query, true);
        $this->cachePrepare[$name]  = $prepare;
        $this->cachePrepare[$query] = $prepare;
        return $prepare;
    }

    /**
     * Чтение подготовленного выражения из кэша
     *
     * @param string $query
     *        шаблон выражения или имя для именованного
     * @param bool $create [optional]
     *        создавать, если в кэше нет
     * @return goDBPrepare
     */
    public function getPrepare($query, $create = false) {
        if (empty($query)) {
            return false;
        }
        if (isset($this->cachePrepare[$query])) {
            return $this->cachePrepare[$query];
        }
        if ($query[0] == '#') {
            if ($create) {
                throw new goDBExceptionPrepareNamed('Cannot create prepare by one name');
            }
            return false;
        }
        $name = '#'.$query;
        if (isset($this->cachePrepare[$name])) {
            return $this->cachePrepare[$name];
        }
        if (!$create) {
            return false;
        }
        $prepare = new goDBPrepare($this, $query);
        $this->cachePrepare[$query] = $prepare;
        return $prepare;
    }

    /**
     * Получить объект ссылку
     *
     * @link http://pyha.ru/go/godb/godblink/
     *
     * @return goDBI
     */
    public function getLinkObject() {
        return (new goDBLink($this));
    }

    /**
     * Формирование запроса
     *
     * @link http://pyha.ru/go/godb/query/#ph-list
     *
     * @throws goDBExceptionData
     *         ошибочный шаблон или несответствие ему входных данных
     *
     * @param string $pattern
     *        строка-шаблон с плейсхолдерами
     * @param array $data
     *        массив входных данных
     * @param string $prefix [optional]
     *        префикс таблиц
     */
    public function makeQuery($pattern, $data, $prefix = null) {
        $prefix = ($prefix === null) ? $this->tablePrefix : $prefix;    
		$query  = preg_replace('~{(.*?)}~', '`'.preg_quote($prefix).'\1`', $pattern);
		if (!$data) {
			return $query;
		}
        $this->_prefix  = $prefix;
        $this->_data    = $data;
        $this->_dataCnt = 0;
        $this->_phType  = null;
        $query = preg_replace_callback('~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i', Array($this, '_makeQuery'), $query);
        if (($this->_phType == 'r') && ($this->_dataCnt < count($data))) {
            throw new goDBExceptionDataMuch('It is too much data');
        }
        return $query;
    }
    
    /**
     * Разбор результата в нужном формате
     *
     * @link http://pyha.ru/go/godb/fetch/
     *
     * @throws goDBExceptionFetch
     *         неизвестный или неожиданный формат представления
     *
     * @param mysqli_result $result
     *        результат запроса
     * @param string $fetch
     *        требуемый формат представления
     * @return mixed
     *         результат в требуемом формате
     */
    public function fetch($result, $fetch) {
        $fetch   = explode(':', $fetch, 2);
        $options = isset($fetch[1]) ? $fetch[1] : '';
        $fetch   = strtolower($fetch[0]);
        $this->isiterator = false;
        switch ($fetch) {
            case null:
            case 'no':
                return $result;
            case 'true':
                return true;
            case 'id':
                return $this->insert_id;
            case 'ar':
                return $this->affected_rows;
        }
        if (!is_object($result)) {
            $this->checkFetch($fetch);
            throw new goDBExceptionFetchUnexpected($fetch);
        }
        switch ($fetch) {
            case 'assoc':
                $return = array();
                while ($row = $result->fetch_assoc()) {
                    $return[] = $row;
                }
                return $return;
            case 'row':
                $return = array();
                while ($row = $result->fetch_row()) {
                    $return[] = $row;
                }
                return $return;
            case 'col':
                $return = array();
                while ($row = $result->fetch_row()) {
                    $return[] = $row[0];
                }
                return $return;
            case 'object':
                $return = array();
                while ($row = $result->fetch_object()) {
                    $return[] = $row;
                }
                return $return;
            case 'vars':
                $return = array();
                while ($row = $result->fetch_row()) {
                    $return[$row[0]] = isset($row[1]) ? $row[1] : $row[0];
                }
                return $return;
            case 'kassoc':
                $return = array();
                $key    = $options;
                while ($row = $result->fetch_assoc()) {
                    if (!$key) {
                        reset($row);
                        $key = key($row);
                    }
                    $return[$row[$key]] = $row;
                }
                return $return;
            case 'iassoc':
                $this->isiterator = true;
                return new goDBResultAssoc($result);
            case 'irow':
                $this->isiterator = true;            
                return new goDBResultRow($result);
            case 'icol':
                $this->isiterator = true;            
                return new goDBResultCol($result);
            case 'iobject':
                $this->isiterator = true;            
                return new goDBResultObject($result);
        }

        $num = $result->num_rows;
        if ($fetch == 'num') {
            return $num;
        }
        if ($num == 0) {
            $this->checkFetch($fetch, 'one');
            return false;
        }

        switch ($fetch) {
            case 'rowassoc':
                return $result->fetch_assoc();
            case 'rowrow':
                return $result->fetch_row();
            case 'rowobject':
                return $result->fetch_object();
            case 'el':
                $r = $result->fetch_row();
                return $r[0];
            case 'bool':
                $r = $result->fetch_row();
                return (bool)$r[0];
        }

        throw new goDBExceptionFetchUnknown($fetch);
    }

    /**
     * Установка префикса таблиц
     *
     * @link http://pyha.ru/go/godb/etc/
     *
     * @param string $prefix
     */
    public function setPrefix($prefix) {
        $this->tablePrefix = $prefix;
        return true;
    }

    /**
     * Получить текущий префикс
     * 
     * @return string
     */
    public function getPrefix() {
        return $this->tablePrefix;
    }

    /**
     * Установить значение отладки
     *
     * @link http://pyha.ru/go/godb/etc/
     *
     * @param mixed $debug
     *        true:     вывод в поток
     *        false:    отключение отладки
     *        callback: вызов указанной функции
     */
    public function setDebug($debug = true) {
        $this->queryDebug = $debug;
        return true;
    }

    /**
     * Получить значение отладки
     *
     * @return mixed
     */
    public function getDebug() {
        return $this->queryDebug;
    }

    /**
     * Декорирование query()
     *
     * @link http://pyha.ru/go/godb/etc/
     *
     * @param callback $wrapper
     *        функция-декоратор
     */
    public function queryDecorated($wrapper) {
    	$this->queryWrapper = $wrapper;
    	return true;
    }

    /**
     * Получить декоратор запроса
     *
     * @return callback
     */
    public function getQueryDecorator() {
        return $this->queryWrapper;
    }
    
    /**
     * Количество записей в таблице, удволетворяющих условию
     * 
     * @param string $table
     *        имя таблицы (используется префикс)
     * @param string $where [optional]
     *        условие WHERE (с плейсхолдерами). Нет - вся таблица.
     * @param array $data [optional]
     *        данные для WHERE
     * @return int
     *         количество записей удовлетворяющих условию
     */
    public function countRows($table, $where = null, $data = null) {
    	$where   = $where ? ('WHERE '.$where) : '';
    	$pattern = 'SELECT COUNT(*) FROM {'.$table.'} '.$where;
    	return $this->query($pattern, $data, 'el');
    }    
 
  /*** STATIC: ***/

    /**
     * Имя по умолчанию в пространстве имён
     * @const string
     */
    const baseName = 'base';
      
    /**
     * Создание базы и сохранение в пространстве имен
     *
     * Возможно указание как всех аргументов метода,
     * так и указание единственного - конфигурационного массива
     *
     * @link http://pyha.ru/go/godb/namespace/
     *
     * @throws goDBExceptionDBAlready
     *         заданное имя уже существует
     * @throws goDBExceptionConnect
     *         ошибка подключения, если не используется отложенное подключение
     * 
     * @param mixed $host
     *        хост - возможно указание порта через ":"
     *        либо конфигурационный массив
     * @param string $username [optional]
     *        имя пользователя базы
     * @param string $passwd [optional]
     *        пароль пользователя базы
     * @param string $dbname [optional]
     *        имя базы данных
     * @param string $name [optional]
     *        наименование данной базы в пространстве имён
     * @param bool $post [optional]
     *        использовать ли отложенное подключение
     * @return mixed
     *         объект базы данные если не используется отложенное подключение
     */
    public static function makeDB($host, $username = null, $passwd = null, $dbname = null, $name = null, $post = false) {
    	if (is_array($host)) {
    		$name = isset($host['name']) ? $host['name'] : self::baseName;
            if (isset($host['link'])) {
                self::assocDB($name, $host['link']);
                return true;
            }
    		$post = isset($host['postmake']) ? $host['postmake'] : false;
    	} elseif (!$name) {
            $name = self::baseName;
        }
        if (isset(self::$dbList[$name])) {
            throw new goDBExceptionDBAlready($name);
        }
        if (!$post) {
            self::$dbList[$name] = new self($host, $username, $passwd, $dbname);        
        } else {
            self::$dbList[$name] = Array($host, $username, $passwd, $dbname);
        }
        return self::$dbList[$name];
    }

    /**
     * Сохранить базу в пространстве имен
     *
     * @throws goDBExceptionAlready
     *         имя занято
     * 
     * @param goDB $db
     * @param string $name [optional]
     */
    public static function setDB(goDB $db, $name = self::baseName) {
        if (isset(self::$dbList[$name])) {
            throw new goDBExceptionDBAlready($name);
        }
        self::$dbList[$name] = $db;
        return true;
    }
    
    /**
     * Ассоциация с базой
     *
     * @throws goDBExceptionDBAlready
     *         имя новой занято
     * @throws goDBExceptionDBNotFound
     *         целевая база отсутствует
     * 
     * @param string $one
     *        новая база
     * @param string $two
     *        та, с которой ассоциируется
     */
    public static function assocDB($one, $two) {
    	if (isset(self::$dbList[$one])) {
    		throw new goDBExceptionDBAlready($one);
    	}
    	if (!isset(self::$dbList[$two])) {
    		throw new goDBExceptionDBNotFound($two);
    	}
    	self::$dbList[$one] = $two;
    	return true;
    }

    /**
     * Получить базу из пространства имен
     *
     * @throws goDBExceptionDBNotFound
     *         нет базы с таким именем
     * @throws goDBExceptionConnect
     *         может произойти ошибка при отложенном подключении
     * 
     * @param string $name
     * @return goDB
     */
    public static function getDB($name = self::baseName) {
        if (!isset(self::$dbList[$name])) {
            throw new goDBExceptionDBNotFound($name);
        }
        if (is_array(self::$dbList[$name])) {
            $prm = self::$dbList[$name];        
            self::$dbList[$name] = new self($prm[0], $prm[1], $prm[2], $prm[3]);
        } elseif (!is_object(self::$dbList[$name])) {
        	self::$dbList[$name] = self::getDB(self::$dbList[$name]);
        }
        return self::$dbList[$name];
    }

    /**
     * Делегирование запроса к нужному объекту БД из пространства имён
     *
     * @throws goDBException
     *         - нет такой базы
     *         - ошибка отложенного подключения
     *         - ошибки шаблона и данных
     *         - ошиочный формат разбора
     *         - ошибочный запрос
     *
     * @param string $pattern [optional]
     * @param array  $data [optional]
     * @param string $fetch [optional]
     * @param string $prefix [optional]
     * @param string $name [optional]
     * @return mixed
     */
    public static function queryDB($pattern, $data = null, $fetch = null, $prefix = null, $name = self::baseName) {
        return self::getDB($name)->query($pattern, $data, $fetch, $prefix);
    }      
    
	/**
	 * Получить количество запросов через данный класс
	 *
	 * @return int
	 */
    public static function getQQuery() {
    	return self::$qQuery;
    }
    
  /*** PRIVATE: ***/

    /**
     * Вспомагательная функция для формирования запроса
     *
     * @param array $matches
     * @return string
     */
    private function _makeQuery($matches) {
        if (!isset($matches[1])) {
            /* Простой регулярный (не именованный) плейсхолдер "?" */
            $placeholder = '';
            $type        = 'r';
        } else {
            /* Плейсхолдер с указанием типа и (или) именованный */
            $placeholder = strtolower($matches[1]);
            if ($placeholder == '?') {
                return '?';
            }
            if (isset($matches[2])) {
                /* Именованный плейсхолдер */
                if (isset($matches[3])) {
                    $type = 'n';
                    $name = $matches[3];
                    if (empty($name)) {
                        throw new goDBExceptionDataPlaceholder($matches[0]);
                    }
                } else {
                    /* ":" поставлена, а имя не указано */
                    throw new goDBExceptionDataPlaceholder($matches[0]);
                }
            } else {
                /* Регулярный плейсхолдер */
                $type = 'r';
            }
        }
        if ($type == 'r') {
            /* Для регулярного плейсхолдера индекс - очередной номер */
            $name = $this->_dataCnt;
            $this->_dataCnt++;
        }
        if (!$this->_phType) {
            /* Тип плейсхолдеров ещё не определён */
            $this->_phType = $type;
        } elseif ($this->_phType != $type) {
            /* Неоднородные плейсхолдеры в одном запроса */
            throw new goDBExceptionDataMixed('regularly and named placeholder in a query');
        }
        if (!array_key_exists($name, $this->_data)) {
            /* Нет такого индекса */
            throw new goDBExceptionDataNotEnough('It is not enough data');
        }
        $value = $this->_data[$name];

        switch ($placeholder) {
            case '':
            case 'string':
                return '"'.$this->real_escape_string($value).'"';
            case 'n':
            case 'null':
                return is_null($value) ? 'NULL' : '"'.$this->real_escape_string($value).'"';
            case 'i':
            case 'int':
                return (0 + $value);
            case 'bool':
                return $value ? '"1"' : '"0"';
            case 'in':
            case 'ni':
            case 'int-null':
                return is_null($value) ? 'NULL' : (0 + $value);
            case 'l':
            case 'list':
            case 'a':
                foreach ($value as &$e) {
                    $e = is_null($e) ? 'NULL' : '"'.$this->real_escape_string($e).'"';
                }
                return implode(',', $value);
            case 'li':
            case 'list-int':
            case 'ai':
            case 'ia':
                foreach ($value as &$e) {
                    $e = is_null($e) ? 'NULL' : (0 + $e);
                }
                return implode(',', $value);
            case 's':
            case 'set':
                $set = array();
                foreach ($value as $col => $val) {
                    $val   = is_null($val) ? 'NULL' : '"'.$this->real_escape_string($val).'"';
                    $set[] = '`'.$col.'`='.$val;
                }
                return implode(',', $set);
            case 'v':
            case 'values':
                $valueses = array();
                foreach ($value as $vs) {
                    $values = array();
                    foreach ($vs as $v) {
                        $values[] = is_null($v) ? 'NULL' : '"'.$this->real_escape_string($v).'"';
                    }
                    $valueses[] = '('.implode(',', $values).')';
                }
                return implode(',', $valueses);
            case 'e':
            case 'escape':
                return $this->real_escape_string($value);
            case 'q':
            case 'query':
                return $value;
            case 't':
            case 'table':
                return '`'.$this->_prefix.$value.'`';
            case 'c':
            case 'col':
                if (is_array($value)) {
                    return '`'.$this->_prefix.$value[0].'`.`'.$value[1].'`';
                }
                return '`'.$value.'`';
            default:
                throw new goDBExceptionDataPlaceholder($matches[0]);
        }
    }

    /**
     * Проверка формата разбора
     *
     * @throws goDBExceptionFetchUnknown
     *         неизвестный формат
     *
     * @param string $fetch
     *        формат разбора
     * @param string $groups [optional]
     *        в какой группе искать
     *        не указана - во всех
     */
    private function checkFetch($fetch, $group = null) {
        if ($group) {
            if (in_array($fetch, self::$listFetchs[$group])) {
                return true;
            }
        } else {
            foreach (self::$listFetchs as $fetchs) {
                if (in_array($fetch, $fetchs)) {
                    return true;
                }
            }
        }
        throw new goDBExceptionFetchUnknown($fetch);
    }

    /**
     * Приведение различных форматов multiQuery к одному
     *
     * @param mixed $patterns
     * @param mixed $datas
     * @param mixed $fetches
     * @return array
     *     "queries": массив запросов виде (pattern, data, fetch)
     *     "last":    last-fetch если указан
     */
    private function argsMultiQuery($patterns, $datas, $fetches) {
        $queries = array();
        if (is_array($patterns)) {
            if (!isset($patterns[0])) {
                return false;
            }
            if (is_array($patterns[0])) {
                $queries = $patterns;
            } else {
                $countP = count($patterns);
                $countD = count($datas);
                if ($countP != $countD) {
                    throw new goDBExceptionMulti('multi patterns != datas');
                }
                for ($i = 0; $i < $countP; $i++) {
                    $queries[] = array($patterns[$i], $datas[$i]);
                }
            }
        } else {
            foreach ($datas as $data) {
                $queries[] = array($patterns, $data);
            }
        }
        $last = null;
        if ($fetches) {
            if (is_array($fetches)) {
                $countQ = count($queries);
                $countF = count($fetches);
                if ($countQ != $countF) {
                    throw new goDBExceptionMulti('multi queries != fetches');
                }
                for ($i = 0; $i < $countQ; $i++) {
                    $queries[$i][2] = $fetches[$i];
                }
            } else {
                $f = explode(':', $fetches, 2);
                if (($f[0] == 'last') && (isset($f[1]))) {
                    $last = $f[1];
                } else {
                    foreach ($queries as &$q) {
                        $q[2] = $fetches;
                    }
                }
            }
        } else {            
            foreach ($queries as &$q) {
                $q[2] = isset($q[2]) ? $q[2] : 'true';
            }
        }
        return array(
            'queries' => $queries,
            'last'    => $last,
        );
    }

    /**
     * Разбор результатов мультизапроса
     *
     * @throws goDBExceptionFetch
     *
     * @param array $queries
     * @param mixed $fetches
     * @param bool $last
     * @param bool $transaction
     * @return array
     */
    private function multiFetch($queries, $fetches, $last, $transaction) {
        $results = array();
        $notlast = count($queries);
        foreach ($queries as $q) {
            $notlast--;
            $result = $this->store_result();
            if ($this->errno) {
                if ($transaction) {
                    $this->transactionRollback();
                }
                throw new goDBExceptionQuery($q[0], $this->errno, $this->error);
            }
            if (!$last) {
                $fetch = $q[2];
                $r = $this->fetch($result, $fetch);
                $results[] = $r;
                if (is_object($result) && ($result !== $r)) {
                    $result->free();
                }
            } elseif (!$notlast) {
                $r = $this->fetch($result, $last);
                $results = $r;
                if (is_object($result) && ($result !== $r)) {
                    $result->free();
                }
            } else {
                if (is_object($result)) {
                    $result->free();
                }
            }
            if ($this->more_results()) {
                $this->next_result();
            } elseif ($notlast) {
                if ($transaction) {
                    $this->transactionRollback();
                }
                throw new goDBExceptionMulti('multi results < queires');
            }
        }
        if ($this->more_results()) {
            if ($transaction) {
                $this->transactionRollback();
            }
            throw new goDBExceptionMulti('multi results > queires');
        }
        return $results;
    }

    /**
     * Вывод отладочной информации
     * 
     * @param string $message
     */
    private function toDebug($message) {
        if (!$this->queryDebug) {
            return false;
        }
       	if ($this->queryDebug === true) {
           	echo '<pre>'.htmlspecialchars($message).'</pre>';
       	} else {
       		call_user_func($this->queryDebug, $message);
       	}
        return true;
    }

    /**
     * Деструктор объекта goDB
     * Чистка кэшей и т.п.
     */
    public function __destruct() {
        foreach ($this->cachePrepare as $p) {
            $p->close();
        }
        $this->cachePrepare = null;
    }
  
  /*** VARS: ***/
    
   /**
     * Префикс таблиц по умолчанию
     *
     * @var string
     */
    protected $tablePrefix = '';

    /**
     * Разрешение отладки
     *
     * @var mixed
     */
    protected $queryDebug = false;

    /**
     * Список баз данных
     *
     * @var array
     */
    protected static $dbList = Array();
    
    /**
     * Количество запросов через класс
     *
     * @var int
     */
    protected static $qQuery = 0;

    /**
     * Текущий уровень вложенности транзакций
     *
     * @var int
     */
    protected $transactionLevel = 0;

    /**
     * Сохранённое значение автокоммита при старте транзакции
     *
     * @var bool
     */
    protected $transactionACStored;

    /**
     * Пометка текущей транзакции, как ошибочной при тихом откате
     *
     * @var bool
     */
    protected $transactionFailed = false;

    /**
     * Кэш подготовленных выражений
     *
     * Запросы хранятся как "query" => goDBPrepare
     * Именованные как "#name" => goDBPrepare
     *
     * @var array
     */
    protected $cachePrepare = array();

    /**
     * Список всех форматов представления результата по группам
     * @var array
     */
    private static $listFetchs = array(
        /* Возвращающие множество записей */
        'many' => array(
            'assoc', 'row', 'col', 'object', 'kassoc',
            'iassoc', 'irow', 'icol', 'iobject',
            'vars', 'num',
        ),
        /* Возвращаюшие одну запись */
        'one' => array(
            'rowassoc', 'rowrow', 'rowobject', 'el', 'bool',
        ),
        /* Другие */
        'other' => array(
            'no', 'id', 'ar',
        ),
    );

    /**
     * Декоратор query()
     *
     * @var callback
     */
    protected $queryWrapper;

    /* Вспомагательная фигня */
    private $_data;
    private $_dataCnt;
    private $_phType;
    private $_prefix;
    
    private $isiterator;
}


/****************************************
 * 
 * Иерархия исключений при работе с библиотекой
 *
 ****************************************/

interface goDBException {}

abstract class goDBRuntimeException extends RuntimeException implements goDBException {}
abstract class goDBLogicException extends LogicException implements goDBException {}


/**
 * Ошибка подключения
 */
class goDBExceptionConnect extends goDBRuntimeException {}

/**
 * Ошибка в запросе
 */
class goDBExceptionQuery extends goDBLogicException
{
    public function __construct($query, $errno, $error) {
		$msg = 'DB Error. Query="'.$query.'" error = '.$errno.' "'.$error.'"';
		parent::__construct($msg, $errno);
        $this->query = $query;
        $this->errno = $errno;
        $this->error = $error;
    }

    public function query() {return $this->query;}
    public function errno() {return $this->errno;}
    public function error() {return $this->error;}

    public function __toString() {
        return htmlspecialchars($this->getMessage());
    }

    private $query, $errno, $error;
}

/**
 * Несоответствие количество плейсхолдеров и входных данных
 */
abstract class goDBExceptionData extends goDBLogicException {}

/**
 * Данных больше плейсхолдеров
 */
class goDBExceptionDataMuch extends goDBExceptionData {}

/**
 * Данных меньше плейсхолдеров
 */
class goDBExceptionDataNotEnough extends goDBExceptionData {}

/**
 * Неизвестный плейсхолдер
 */
class goDBExceptionDataPlaceholder extends goDBExceptionData {
    public function __construct($placeholder, $code = null) {
        $message = 'Unknown placeholder "'.$placeholder.'"';
        parent::__construct($message, $code);
    }
}

/**
 * В запросе используются именованные плейсхолдеры наравне с регулярными
 */
class goDBExceptionDataMixed extends goDBExceptionData {}

/**
 * Неверный fetch
 */
abstract class goDBExceptionFetch extends goDBLogicException {}

/**
 * Неверный fetch - неизвестный
 */
class goDBExceptionFetchUnknown extends goDBExceptionFetch {
    public function __construct($fetch, $code = null) {
        $message = 'Unknown fetch "'.$fetch.'"';
        parent::__construct($message, $code);
    }
}

/**
 * Неверный fetch - неожиданный (assoc после INSERT, например)
 */
class goDBExceptionFetchUnexpected extends goDBExceptionFetch {
    public function __construct($fetch, $code = null) {
        $message = 'Unexpected fetch "'.$fetch.'" for this query';
        parent::__construct($message, $code);
    }
}

/**
 * Исключения, связанные с транзакциями
 */
abstract class goDBExceptionTransaction extends goDBLogicException {}
class goDBExceptionTransactionRollback extends goDBExceptionTransaction {}

class goDBExceptionMulti extends goDBLogicException {}

abstract class goDBExceptionPrepare extends goDBLogicException {}
class goDBExceptionPrepareCreate extends goDBExceptionPrepare {}
class goDBExceptionPrepareNamed extends goDBExceptionPrepare {}


/**
 * Проблемы со списком баз данных в пространстве имен DB
 */
abstract class goDBExceptionDB extends goDBLogicException {}
class goDBExceptionDBAlready  extends goDBExceptionDB {}
class goDBExceptionDBNotFound extends goDBExceptionDB {}

/**
 * Результат выполнения запроса (итератор)
 *
 */
abstract class goDBResult implements Iterator, ArrayAccess, Countable {

    public function __construct($result) {
        $this->result  = $result;
        $this->numRows = $result->num_rows;
    }

    public function __destruct() {
        if (is_resource($this->result)) {
            $this->result->free();
        }
    }

    public function rewind() {
        $this->count = 0;
        return true;
    }

    public function current() {
        $this->result->data_seek($this->count);
        return $this->getEl();
    }

    public function key() {
        return $this->count;
    }

    public function next() {
        $this->count++;
        return $this->current();
    }

    public function valid() {
        if ($this->count >= $this->numRows) {
            return false;
        }
        return true;
    }

    public function count() {
        return $this->numRows;
    }

    public function get($num, $index = false)  {
        if ($num >= $this->numRows) {
            return null;
        }
        $this->result->data_seek($num);
        $r = $this->getEl();
        if ($index === false) {
            return $r;
        }
        if (!is_array($r)) {
            return null;
        }
        if (!isset($r[$index])) {
            return null;
        }
        return $r[$index];
    }

    public function offsetGet($offset) {
        return $this->get($offset);
    }
    public function offsetSet($offset, $value) {
        return false;
    }
    public function offsetExists($offset) {
        return (($offset >= 0) && ($offset < $this->numRows));
    }
    public function offsetUnset($offset) {
        return false;
    }


    abstract protected function getEl();

    protected $result, $numRows, $count = 0;
}

class goDBResultRow extends goDBResult
{
    protected function getEl() {
        return $this->result->fetch_row();
    }
}

class goDBResultAssoc extends goDBResult
{
    protected function getEl()  {
        return $this->result->fetch_assoc();
    }
}

class goDBResultCol extends goDBResult
{
    protected function getEl() {
        $r = $this->result->fetch_row();
        return $r[0];
    }
}

class goDBResultObject extends goDBResult
{
    protected function getEl() {
        return $this->result->fetch_object();        
    }
}

/**
 * Класс подготовленных выражений
 *
 * @link http://pyha.ru/go/godb/prepare/
 */
class goDBPrepare
{

    /**
     * Конструктор
     *
     * @param goDB $db
     *        объект базы для которой создаётся
     * @param string $query
     *        шаблон запроса
     * @param bool $lazy [optional]
     *        использовать ли отложенное создание
     */
    public function __construct(goDB $db, $query, $lazy = false) {
        $this->db = $db;
        $this->makeQuery($query);
        if (!$lazy) {
            $this->makeSTMT();
        }
    }

    /**
     * Деструктор
     * Уничтожение выражения
     */
    public function __destruct() {
        $this->close();
    }

    /**
     * Выполнение запроса
     *
     * @throws goDBExceptionQuery
     *
     * @param array $data
     *        набор входных данных
     * @param string $fetch
     *        формат представления результата
     * @return mixed
     *         результат в требуемом формате
     */
    public function execute($data = null, $fetch = null) {
        if ($this->closed) {
            return false;
        }
        if ($this->db->transactionFailed()) {
            return false;
        }
        if (!$this->stmt) {
            $this->makeSTMT();
        }
        $stmt = $this->stmt;
        $data = $data ? $data : array();
        $len = count($data);
        if ($len != $this->paramsCount) {
            if ($len < $this->paramsCount) {
                throw new goDBExceptionDataNotEnough();
            } else {
                throw new goDBExceptionDataMuch();
            }
        }
        if ($this->paramsCount > 0) {
            $params = array($this->types);
            foreach ($data as &$d) {
                $params[] = &$d;
            }
            call_user_func_array(array($stmt, 'bind_param'), $params);
        }
        $stmt->execute();
        if ($stmt->errno) {
            throw new goDBExceptionQuery('[PREPARE] '.$this->query, $stmt->errno, $stmt->error);
        }
        if ($this->fields) {
            $rR = array();
            $rA = array();
            $num = 0;
            foreach ($this->fields as $field) {
                $rA[$field] = null;
                $rR[$num] = &$rA[$field];
                $num++;
            }
            call_user_func_array(array($stmt, 'bind_result'), $rR);
            $result = $this->fetch($rR, $rA, $fetch);
            unset($rR);
            unset($rA);
        } else {
            $result = $this->fetch(true, true, $fetch);
        }
        $stmt->free_result();
        unset($params);
        unset($results);
        return $result;
    }

    /**
     * Выполнение запроса вызовом объекта
     *
     * @example $result = $prepare($data, $fetch);
     *
     * @uses PHP 5.3
     *
     * @param array $data
     * @param string $fetch
     * @return mixed
     */
    public function __invoke($data = null, $fetch = null) {
        return $this->execute($data, $fetch);
    }

    /**
     * Закрытие подключения
     */
    public function close() {
        if ($this->closed) {
            return false;
        }
        if ($this->stmt) {
            $this->stmt->close();
            $this->stmt = null;
        }
        $this->closed = true;
        return true;
    }

    /**
     * Получить объект mysqli_stmt для данного выражения
     *
     * @return mysqli_stmt
     */
    public function getSTMT() {
        if ($this->closed) {
            return false;
        }
        if (!$this->stmt) {
            $this->makeSTMT();
        }
        return $this->stmt;
    }

    /**
     * Получить строку с типами плейсхолдеров
     *
     * @return string
     */
    public function getTypes() {
        return $this->types;
    }

    /**
     * Закрыто ли уже выражение
     *
     * @return bool
     */
    public function isClosed() {
        return $this->closed;
    }

    /**
     * Получение шаблона запроса
     *
     * @return string
     */
    public function getQuery() {
        return $this->query;
    }

    /**
     * Создание подготовленного выражения mysqli
     */
    private function makeSTMT() {
        $this->stmt = $this->db->prepare($this->query);
        if (!$this->stmt) {
            $message = 
                'Error prepare "'.$this->query.'"; '.
                    '#'.$this->db->errno.
                    ': "'.$this->db->error.'"';
            throw new goDBExceptionPrepareCreate($message);
        }
        if ($this->stmt->param_count != $this->paramsCount) {
            $message = 'prepare "'.$this->query.'" error param_count';
            throw new goDBExceptionPrepareCreate($message);
        }
        $meta = $this->stmt->result_metadata();
        if ($meta) {
            $fields = $meta->fetch_fields();
            $this->fields = array();
            foreach ($fields as $field) {
                $this->fields[] = $field->name;
            }
            $meta->free();
        } else {
            $this->fields = false;
        }
        return true;
    }

    /**
     * Разбор шаблона goDB на шаблон mysqli и строку типов
     */
    private function makeQuery($pattern) {
        $reg = '~\?([idsb])?;?~';
        if (!preg_match_all($reg, $pattern, $matches, PREG_SET_ORDER)) {
            $this->query = $pattern;
            $this->types = '';
            $this->paramsCount = 0;
            return true;
        }
        $this->paramsCount = count($matches);
        $t = array();
        foreach ($matches as $match) {
            if (isset($match[1])) {
                $t[] = $match[1];
            } else {
                $t[] = 's';
            }
        }
        $this->types = implode('', $t);
        if ($this->paramsCount > 0) {
            $pattern = preg_replace($reg, '?', $pattern);
        }
        $this->query = $pattern;
        return true;
    }

    /**
     * Разбор результата
     * @param array $row
     * @param array $assoc
     * @param string $fetch
     * @return mixed
     */
    private function fetch($row, $assoc, $fetch) {
        $fetch   = explode(':', $fetch, 2);
        $options = isset($fetch[1]) ? $fetch[1] : '';
        $fetch   = strtolower($fetch[0]);
        $stmt    = $this->stmt;
        switch ($fetch) {
            case null:
                return true;
            case 'no':
                throw new goDBExceptionFetchUnexpected($fetch);
            case 'true':
                return true;
            case 'id':
                return $stmt->insert_id;
            case 'ar':
                return $stmt->affected_rows;
        }
        if (!is_array($row)) {
            throw new goDBExceptionFetchUnexpected($fetch);
        }
        switch ($fetch) {
            case 'assoc':
            case 'iassoc':
                $result = array();
                while ($stmt->fetch()) {
                    $result[] = $this->aCopy($assoc);
                }
                return $result;
            case 'row':
            case 'irow':
                $result = array();
                while ($stmt->fetch()) {
                    $result[] = $this->aCopy($row);
                }
                return $result;
            case 'col':
            case 'icol':
                $result = array();
                while ($stmt->fetch()) {
                    $result[] = $row[0];
                }
                return $result;
            case 'object':
            case 'iobject':
                $result = array();
                while ($stmt->fetch()) {
                    $result[] = (object)$this->aCopy($assoc);
                }
                return $result;
            case 'vars':
                $result = array();
                while ($stmt->fetch()) {
                    $return[$row[0]] = isset($row[1]) ? $row[1] : $row[0];
                }
                return $return;
            case 'kassoc':
                $result = array();
                $key    = $options;
                while ($stmt->fetch()) {
                    if (!$key) {
                        $key = key($assoc);
                    }
                    $result[$assoc[$key]] = $this->aCopy($assoc);
                }
                return $result;
        }
        
        if ($fetch == 'num') {
            $num = 0;
            while ($stmt->fetch()) {
                $num++;
            }
            return $num;
        }

        if (!$stmt->fetch()) {
            return false;
        }

        switch ($fetch) {
            case 'rowassoc':
                return $this->aCopy($assoc);
            case 'rowrow':
                return $this->aCopy($row);
            case 'rowobject':
                return (object)$this->aCopy($assoc);
            case 'el':                
                return $row[0];
            case 'bool':                
                return (bool)$row[0];
        }

        throw new goDBExceptionFetchUnknown($fetch);
    }

    private function aCopy($A) {
        $r = array();
        foreach ($A as $k => $v) {
            $r[$k] = $v;
        }
        return $r;
    }

    /**
     * Связанный объект базы
     * @var goDB
     */
    private $db;

    /**
     * Шаблон запроса
     * @var string
     */
    private $query;

    /**
     * Строка типов
     * @var string
     */
    private $types;

    /**
     * Количество входных параметров
     * @var int
     */
    private $paramsCount;

    /**
     * Подготовленное выражение mysqli
     * @var mysqli_stmt
     */
    private $stmt;

    /**
     * Закрыто ли подключение
     * @var bool
     */
    private $closed = false;

    /**
     * Имена возвращаемых полей
     * @var array
     */
    private $fields;
}

interface goDBI {}

/**
 * Объект-ссылка.
 * Разделяет тоже подключение, но может иметь другие настройки.
 */
class goDBLink implements goDBI {

    /**
     * Конструктор
     * 
     * @param goDBI $db
     *        объект, на который нужно сделать ссылку
     */
    public function __construct(goDBI $db) {
        $this->db      = $db;
        $this->prefix  = $db->getPrefix();
        $this->debug   = $db->getDebug();
        $this->wrapper = $db->getQueryDecorator();
    }

    /**
     * Получить исходный объект
     * @return goDB
     */
    public function godbObject() {
        return $this->db;
    }

    public function __get($name) {
        return $this->db->$name;
    }
    public function __set($name, $value) {
        $this->db->$name = $value;
    }
    public function __call($name, $arguments) {
        return call_user_func_array(array($this->db, $name), $arguments);
    }

    /**
     * @override
     * @param string $pattern
     * @param array $data [optional]
     * @param string $fetch [optional]
     * @param string $prefix [optional]
     */
    public function query($pattern, $data = null, $fetch = null, $prefix = null) {
        $prefix  = $prefix ? $prefix : $this->prefix;
        $debug   = $this->db->getDebug();
        $wrapper = $this->db->getQueryDecorator();
        $this->db->setDebug($this->debug);
        $this->db->queryDecorated($this->wrapper);
        try {
            $result  = $this->db->query($pattern, $data, $fetch, $prefix);
        } catch (Exception $e) {
            $this->db->setDebug($debug);
            $this->db->queryDecorated($wrapper);
            throw $e;
        }
        $this->db->setDebug($debug);
        $this->db->queryDecorated($wrapper);
        return $result;
    }
    
    public function __invoke($pattern, $data = null, $fetch = null, $prefix = null) {
        return $this->query($pattern, $data, $fetch, $prefix);
    }    

    /**
     * @override
     * @param string $pattern
     * @param array $data [optional]
     * @param string $prefix [optional]
     */
    public function makeQuery($pattern, $data = null, $prefix = null) {
        $prefix = $prefix ? $prefix : $this->prefix;
        return $this->db->makeQuery($pattern, $data, $prefix);
    }



    public function setPrefix($prefix) {
        $this->prefix = $prefix;
        return true;
    }
    public function getPrefix() {
        return $this->prefix;
    }
    public function setDebug($debug = true) {
        $this->debug = $debug;
        return true;
    }
    public function getDebug() {
        return $this->debug;
    }
    public function queryDecorated($wrapper) {
    	$this->wrapper = $wrapper;
    	return true;
    }
    public function getQueryDecorator() {
        return $this->wrapper;
    }

    /**
     * Исходный объект
     * @var goDB
     */
    private $db;

    private $prefix, $debug, $wrapper;
}
			
© Григорьев Олег aka vasa_c, 2006—2010