This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "quickfw".
The branch, master has been updated
via 936d9fccedd497c7283587ebc88c6758252a06f7 (commit)
via faa3d7492d8b959f6d57b1865e177765c64b9fb9 (commit)
from 60693af34d92bea06925807c109a096a5b16895d (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 936d9fccedd497c7283587ebc88c6758252a06f7
Author: Ivan1986 <iva...@li...>
Date: Tue Aug 24 15:33:45 2010 +0400
remove ErrorStack
diff --git a/QFW/config.php b/QFW/config.php
index 6b10fe4..a6aa01a 100644
--- a/QFW/config.php
+++ b/QFW/config.php
@@ -34,7 +34,6 @@ $config['redirection']=array(
*/
$config['QFW'] = array(
'release' => false, /* статус проекта на данном хосте - отладка и всякие быстрые компиляции */
- 'ErrorStack' => false, /* вывод стека вызовов в сообщении об ошибке в БД */
'cacheSessions' => false, /* Хранить сессии в кеше, не использовать стандартный механизм */
'autoload' => false, /* включить автолоад false|true|string */
'auto404' => false, /* не перенаправлять на дефолтовый контроллер все запросы */
diff --git a/application/default.php b/application/default.php
index 19f25b9..0cecf94 100644
--- a/application/default.php
+++ b/application/default.php
@@ -82,7 +82,6 @@ $config['cache'] = array(
*/
$config['QFW'] = array(
'release' => false, /* статус проекта на данном хосте - отладка и всякие быстрые компиляции */
- 'ErrorStack' => false, /* вывод стека вызовов в сообщении об ошибке в БД */
'cacheSessions' => false, /* Хранить сессии в кеше, не использовать стандартный механизм */
'autoload' => true, /* включить автолоад false|true|string */
'auto404' => false, /* не перенаправлять на дефолтовый контроллер все запросы */
commit faa3d7492d8b959f6d57b1865e177765c64b9fb9
Author: Ivan1986 <iva...@li...>
Date: Tue Aug 24 15:32:32 2010 +0400
update Dbsimple
diff --git a/lib/DbSimple/Connect.php b/lib/DbSimple/Connect.php
index 0552859..b3dec28 100644
--- a/lib/DbSimple/Connect.php
+++ b/lib/DbSimple/Connect.php
@@ -120,20 +120,13 @@ class DbSimple_Connect
}
/**
- * Функция обработки ошибок - выводит сообщение об ошибке на тестовом
- * на продакшене показывает 404 и пишит в sql.log
+ * Функция обработки ошибок - стандартный обработчик
* Все вызовы без @ прекращают выполнение скрипта
*/
public function errorHandler($msg, $info)
{
// Если использовалась @, ничего не делать.
if (!error_reporting()) return;
- if (QFW::$config['QFW']['release'])
- {
- require_once LIBPATH.'/Log.php';
- Log::log('SQL Error - '.$msg,'sql');
- QFW::$router->show404();
- }
// Выводим подробную информацию об ошибке.
echo "SQL Error: $msg<br><pre>";
print_r($info);
@@ -156,15 +149,17 @@ class DbSimple_Connect
$prev = $this->errorHandler;
$this->errorHandler = $handler;
if ($this->DbSimple)
- return $this->DbSimple->setErrorHandler($handler);
+ $this->DbSimple->setErrorHandler($handler);
+ return $prev;
}
/** @var callback обработчик ошибок */
private $errorHandler = null;
/**
- * array parseDSN(string $dsn)
* Разбирает строку DSN в массив параметров подключения к базе
+ *
+ * @param array parseDSN(string $dsn)
*/
protected function parseDSN($dsn)
{
diff --git a/lib/DbSimple/Generic.php b/lib/DbSimple/Database.php
similarity index 91%
copy from lib/DbSimple/Generic.php
copy to lib/DbSimple/Database.php
index 532746c..112b444 100644
--- a/lib/DbSimple/Generic.php
+++ b/lib/DbSimple/Database.php
@@ -1,6 +1,7 @@
<?php
+
/**
- * DbSimple_Generic: universal database connected by DSN.
+ * DbSimple_Database: Base class for all databases.
* (C) Dk Lab, http://en.dklab.ru
*
* This library is free software; you can redistribute it and/or
@@ -9,7 +10,7 @@
* version 2.1 of the License, or (at your option) any later version.
* See http://www.gnu.org/copyleft/lesser.html
*
- * Use class DbSimple_Connect if you don't know
+ * Use static DbSimple_Generic::connect($dsn) call if you don't know
* database type and parameters, but have its DSN.
*
* Additional keys can be added by appending a URI query string to the
@@ -32,10 +33,9 @@
* initial author: Tomas V.V.Cox <co...@id...>.
*
* Contains 3 classes:
- * - DbSimple_Generic: database factory class
- * - DbSimple_Generic_Database: common database methods
- * - DbSimple_Generic_Blob: common BLOB support
- * - DbSimple_Generic_LastError: error reporting and tracking
+ * - DbSimple_Database: common database methods
+ * - DbSimple_Blob: common BLOB support
+ * - DbSimple_LastError: error reporting and tracking
*
* Special result-set fields:
* - ARRAY_KEY* ("*" means "anything")
@@ -55,17 +55,34 @@
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/
* @author Ivan Borzenkov, http://forum.dklab.ru/users/Ivan1986/
*
- * @version 2.x $Id: Generic.php 226 2007-09-17 21:00:15Z dk $
+ * @version 2.x $Id$
+ */
+
+/**
+ * Use this constant as placeholder value to skip optional SQL block [...].
*/
+if (!defined('DBSIMPLE_SKIP'))
+ define('DBSIMPLE_SKIP', log(0));
/**
+ * Names of special columns in result-set which is used
+ * as array key (or karent key in forest-based resultsets) in
+ * resulting hash.
+ */
+if (!defined('DBSIMPLE_ARRAY_KEY'))
+ define('DBSIMPLE_ARRAY_KEY', 'ARRAY_KEY'); // hash-based resultset support
+if (!defined('DBSIMPLE_PARENT_KEY'))
+ define('DBSIMPLE_PARENT_KEY', 'PARENT_KEY'); // forrest-based resultset support
+
+/**
+ *
* Base class for all databases.
* Can create transactions and new BLOBs, parse DSNs.
*
* Logger is COMMON for multiple transactions.
* Error handler is private for each transaction and database.
*/
-abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
+abstract class DbSimple_Database extends DbSimple_LastError
{
/**
* Public methods.
@@ -213,6 +230,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
return $this->_performEscape($s, $isIdent);
}
+
/**
* DbSimple_SubQuery subquery(string $query [, $arg1] [,$arg2] ...)
* Выполняет разворачивание плейсхолдеров без коннекта к базе
@@ -226,6 +244,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
return new DbSimple_SubQuery($args);
}
+
/**
* callback setLogger(callback $logger)
* Set query logger called before each query is executed.
@@ -388,6 +407,26 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
/**
+ * array parseDSN(mixed $dsn)
+ * Parse a data source name.
+ * See parse_url() for details.
+ */
+ protected function parseDSN($dsn)
+ {
+ if (is_array($dsn)) return $dsn;
+ $parsed = @parse_url($dsn);
+ if (!$parsed) return null;
+ $params = null;
+ if (!empty($parsed['query'])) {
+ parse_str($parsed['query'], $params);
+ $parsed += $params;
+ }
+ $parsed['dsn'] = $dsn;
+ return $parsed;
+ }
+
+
+ /**
* array _query($query, &$total)
* See _performQuery().
*/
@@ -419,6 +458,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$result = isset($cacheData['result']) ? $cacheData['result'] : null;
$rows = isset($cacheData['rows']) ? $cacheData['rows'] : null;
+
$cache_params = $this->attributes['CACHE'];
// Calculating cache time to live
@@ -436,8 +476,8 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$m = null;
preg_match($re, $cache_params, $m);
$ttl = (isset($m[3])?$m[3]:0)
- + (isset($m[2])?$m[2]:0) * 60
- + (isset($m[1])?$m[1]:0) * 3600;
+ + (isset($m[2])?$m[2]:0) * 60
+ + (isset($m[1])?$m[1]:0) * 3600;
// Cutting out time param - now there are just fields for uniqKey or nothing
$cache_params = trim(preg_replace($re, '', $cache_params, 1));
@@ -448,7 +488,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$dummy = null;
// There is no need in query, cos' needle in $this->attributes['CACHE']
$this->_transformQuery($dummy, 'UNIQ_KEY');
- $uniq_key = call_user_func_array(array(&$this, 'select'), array($dummy));
+ $uniq_key = call_user_func(array(&$this, 'select'), $dummy);
$uniq_key = md5(serialize($uniq_key));
}
// Check TTL?
@@ -463,14 +503,14 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
else $cache_it = true;
}
- if (false === $rows || true === $cache_it)
- {
+ if (false === $rows || true === $cache_it) {
$this->_logQuery($query);
// Run the query (counting time).
$qStart = microtime(true);
$result = $this->_performQuery($query);
$fetchTime = $firstFetchTime = 0;
+
if (is_resource($result)) {
$rows = array();
// Fetch result row by row.
@@ -493,10 +533,14 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows);
// Prepare BLOB objects if needed.
- if (is_array($rows) && !empty($this->attributes['BLOB_OBJ']))
- foreach ($this->_performGetBlobFieldNames($result) as $name)
- foreach ($rows as $r=>$v)
- $rows[$r][$name] = $this->_performNewBlob($v[$name]);
+ if (is_array($rows) && !empty($this->attributes['BLOB_OBJ'])) {
+ $blobFieldNames = $this->_performGetBlobFieldNames($result);
+ foreach ($blobFieldNames as $name) {
+ for ($r = count($rows)-1; $r>=0; $r--) {
+ $rows[$r][$name] =& $this->_performNewBlob($rows[$r][$name]);
+ }
+ }
+ }
// Transform resulting rows.
$result = $this->_transformResult($rows);
@@ -559,14 +603,13 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
return $options;
case 'UNIQ_KEY':
$q = $this->attributes['CACHE'];
- $i = 0;
- $query = " -- UNIQ_KEY\n";
+ $query = array();
while(preg_match('/(\w+)\.\w+/sx', $q, $m)) {
- if($i > 0)$query .= "\nUNION\n";
- $query .= 'SELECT MAX('.$m[0].') AS M, COUNT(*) AS C FROM '.$m[1];
+ $query[] = 'SELECT MAX('.$m[0].') AS M, COUNT(*) AS C FROM '.$m[1];
$q = substr($q, strlen($m[0]));
- $i++;
}
+ $query = " -- UNIQ_KEY\n".
+ join("\nUNION\n", $query);
return true;
}
// No such transform.
@@ -631,13 +674,13 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
# Comment.
-- [^\r\n]*
)
- |
+ |
(?>
# DB-specifics.
' . trim($this->_performGetPlaceholderIgnoreRe()) . '
)
)
- |
+ |
(?>
# Optional blocks
\{
@@ -645,7 +688,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
( (?> (?>(\??)[^{}]+) | (?R) )* ) #1
\}
)
- |
+ |
(?>
# Placeholder
(\?) ( [_dsafn&|\#]? ) #2 #3
@@ -658,7 +701,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
);
return $query;
}
-
+
static $join = array(
'|' => array('inner' => ' AND ', 'outer' => ') OR (',),
'&' => array('inner' => ' OR ', 'outer' => ') AND (',),
@@ -720,7 +763,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
if ($v instanceof DbSimple_SubQuery)
$v = $v->get($this->_placeholderNativeArgs);
else
- $v = $v === null? 'NULL' : $this->escape($v);
+ $v = $v === null? 'NULL' : $this->escape($v);
if (!is_int($k)) {
$k = $this->escape($k, true);
$parts[] = "$prefix$k=$v";
@@ -741,17 +784,16 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
{
if ($value instanceof DbSimple_SubQuery)
return $value->get($this->_placeholderNativeArgs);
- $value = $this->_addPrefix2Table($value);
- return $this->escape($value, true);
+ return $this->escape($this->_addPrefix2Table($value), true);
}
$parts = array();
- foreach ($value as $table => $identifiers) {
- if (!is_array($identifiers)) $identifiers = array($identifiers);
+ foreach ($value as $table => $identifiers)
+ {
+ if (!is_array($identifiers))
+ $identifiers = array($identifiers);
$prefix = '';
- if (!is_int($table)) {
- $table = $this->_addPrefix2Table($table);
- $prefix = $this->escape($table, true) . '.';
- }
+ if (!is_int($table))
+ $prefix = $this->escape($this->_addPrefix2Table($table), true) . '.';
foreach ($identifiers as $identifier)
if ($identifier instanceof DbSimple_SubQuery)
$parts[] = $identifier->get($this->_placeholderNativeArgs);
@@ -767,26 +809,23 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
return empty($value)? 'NULL' : intval($value);
}
+ // Native arguments are not processed.
+ if ($this->_placeholderNativeArgs !== null) {
+ $this->_placeholderNativeArgs[] = $value;
+ return $this->_performGetNativePlaceholderMarker(count($this->_placeholderNativeArgs) - 1);
+ }
+
// In non-native mode arguments are quoted.
if ($value === null) return 'NULL';
-
switch ($type) {
+ case '':
+ if (!is_scalar($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_SCALAR';
+ return $this->escape($value);
case 'd':
return intval($value);
case 'f':
return str_replace(',', '.', floatval($value));
}
-
- // нативные плейсхолдеры необходимо обработать после числовых из-за бага в PDO
- // Native arguments are not processed.
- if ($this->_placeholderNativeArgs !== null) {
- $this->_placeholderNativeArgs[] = $value;
- return $this->_performGetNativePlaceholderMarker(count($this->_placeholderNativeArgs) - 1);
- }
-
- if (!is_scalar($value))
- return 'DBSIMPLE_ERROR_VALUE_NOT_SCALAR';
-
// By default - escape as string.
return $this->escape($value);
}
@@ -797,7 +836,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$prev = $this->_placeholderNoValueFound;
if ($this->_placeholderNativeArgs !== null)
$prevPh = $this->_placeholderNativeArgs;
-
+
// Проверка на {? } - условный блок
$skip = false;
if ($m[2]=='?')
@@ -805,12 +844,12 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$skip = array_pop($this->_placeholderArgs) === DBSIMPLE_SKIP;
$block[0] = ' ';
}
-
+
$block = $this->_expandOptionalBlock($block);
-
+
if ($skip)
$block = '';
-
+
if ($this->_placeholderNativeArgs !== null)
if ($this->_placeholderNoValueFound)
$this->_placeholderNativeArgs = $prevPh;
@@ -822,6 +861,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
return $m[0];
}
+
/**
* Заменяет ?_ на текущий префикс
*
@@ -835,6 +875,13 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
return $table;
}
+
+ /**
+ * Разбирает опциональный блок - условие |
+ *
+ * @param string $block блок, который нужно разобрать
+ * @return string что получается в результате разбора блока
+ */
private function _expandOptionalBlock($block)
{
$alts = array();
@@ -871,7 +918,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
}
return $r;
}
-
+
/**
* void _setLastError($code, $msg, $query)
@@ -911,7 +958,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
*/
private function _transformResult($rows)
{
- // Результат не массив - выходим
+ // is not array
if (!is_array($rows) || !$rows)
return $rows;
@@ -1059,8 +1106,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$this->_statistics['count']++;
// If no logger, economize CPU resources and actually log nothing.
- if (!$this->_logger)
- return;
+ if (!$this->_logger) return;
$dt = round($queryTime * 1000);
$firstFetchTime = round($firstFetchTime*1000);
@@ -1101,6 +1147,7 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
$this->_logQuery($log, true);
}
+
// Identifiers prefix (used for ?_ placeholder).
private $_identPrefix = '';
@@ -1121,16 +1168,16 @@ abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
/**
* When string representation of row (in characters) is greater than this,
* row data will not be logged.
- */
+ */
private $MAX_LOG_ROW_LEN = 128;
}
/**
- * Интерфейс BLOB.
- * Описывает функции обекта типа BLOB - ему должны соответствовать классы, возвращаемые в качестве BLOB полей.
+ * Database BLOB.
+ * Can read blob chunk by chunk, write data to BLOB.
*/
-interface DbSimple_Generic_Blob
+interface DbSimple_Blob
{
/**
* string read(int $length)
@@ -1155,9 +1202,9 @@ interface DbSimple_Generic_Blob
* Closes the blob. Return its ID. No other way to obtain this ID!
*/
public function close();
-
}
+
/**
* Класс для хранения подзапроса - результата выполнения функции
* DbSimple_Generic_Database::subquery
@@ -1166,8 +1213,7 @@ interface DbSimple_Generic_Blob
class DbSimple_SubQuery
{
private $query=array();
-
-
+
public function __construct(array $q)
{
$this->query = $q;
@@ -1185,18 +1231,17 @@ class DbSimple_SubQuery
$ph = array_merge($ph, array_slice($this->query,1,null,true));
return $this->query[0];
}
-
}
+
/**
* Support for error tracking.
* Can hold error messages, error queries and build proper stacktraces.
*/
-abstract class DbSimple_Generic_LastError
+abstract class DbSimple_LastError
{
public $error = null;
public $errmsg = null;
- private $showStack = false;
private $errorHandler = null;
private $ignoresInTraceRe = 'DbSimple_.*::.* | call_user_func.*';
@@ -1204,7 +1249,7 @@ abstract class DbSimple_Generic_LastError
* abstract void _logQuery($query)
* Must be overriden in derived class.
*/
- protected abstract function _logQuery($query);
+ abstract protected function _logQuery($query);
/**
* void _resetLastError()
@@ -1223,29 +1268,16 @@ abstract class DbSimple_Generic_LastError
protected function _setLastError($code, $msg, $query)
{
$context = "unknown";
+ if ($t = $this->findLibraryCaller()) {
+ $context = (isset($t['file'])? $t['file'] : '?') . ' line ' . (isset($t['line'])? $t['line'] : '?');
+ }
$this->error = array(
'code' => $code,
'message' => rtrim($msg),
'query' => $query,
- 'context' => '',
+ 'context' => $context,
);
- if ($t = $this->findLibraryCaller($this->showStack))
- {
- if (!$this->showStack)
- $this->error['context'] = (isset($t['file'])? $t['file'] : '?') .
- ' line ' . (isset($t['line'])? $t['line'] : '?');
- else
- {
- $this->error['context'] = (isset($t[0]['file'])? $t[0]['file'] : '?') .
- ' line ' . (isset($t[0]['line'])? $t[0]['line'] : '?');
- $this->error['stack'] = array();
- foreach($t as $f)
- $this->error['stack'][] = (isset($f['class']) ? $f['class'].$f['type'] : '').
- (isset($f['function']) ? $f['function'] : '?').'() '.
- (isset($f['file'])? $f['file'] : '?') . ':' . (isset($f['line'])? $f['line'] : '?');
- }
- }
- $this->errmsg = rtrim($msg) . ($this->error['context']? ' at '.$this->error['context'] : '');
+ $this->errmsg = rtrim($msg) . ($context? " at $context" : "");
$this->_logQuery(" -- error #".$code.": ".preg_replace('/(\r?\n)+/s', ' ', $this->errmsg));
@@ -1258,21 +1290,21 @@ abstract class DbSimple_Generic_LastError
/**
- * callback setErrorHandler(callback $handler, bool $stack)
+ * callback setErrorHandler(callback $handler)
* Set new error handler called on database errors.
* Handler gets 3 arguments:
* - error message
* - full error context information (last query etc.)
*/
- public function setErrorHandler($handler, $stack=false)
+ public function setErrorHandler($handler)
{
$prev = $this->errorHandler;
$this->errorHandler = $handler;
- $this->showStack = $stack;
// In case of setting first error handler for already existed
// error - call the handler now (usual after connect()).
- if (!$prev && $this->error)
+ if (!$prev && $this->error) {
call_user_func($this->errorHandler, $this->errmsg, $this->error);
+ }
return $prev;
}
@@ -1291,12 +1323,12 @@ abstract class DbSimple_Generic_LastError
* Return part of stacktrace before calling first library method.
* Used in debug purposes (query logging etc.).
*/
- protected function findLibraryCaller($all = false)
+ protected function findLibraryCaller()
{
$caller = call_user_func(
array(&$this, 'debug_backtrace_smart'),
$this->ignoresInTraceRe,
- !$all
+ true
);
return $caller;
}
@@ -1355,4 +1387,4 @@ abstract class DbSimple_Generic_LastError
}
}
-?>
\ No newline at end of file
+?>
diff --git a/lib/DbSimple/Generic.php b/lib/DbSimple/Generic.php
index 532746c..43dbe14 100644
--- a/lib/DbSimple/Generic.php
+++ b/lib/DbSimple/Generic.php
@@ -9,7 +9,7 @@
* version 2.1 of the License, or (at your option) any later version.
* See http://www.gnu.org/copyleft/lesser.html
*
- * Use class DbSimple_Connect if you don't know
+ * Use static DbSimple_Generic::connect($dsn) call if you don't know
* database type and parameters, but have its DSN.
*
* Additional keys can be added by appending a URI query string to the
@@ -53,1306 +53,93 @@
*
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/
- * @author Ivan Borzenkov, http://forum.dklab.ru/users/Ivan1986/
*
- * @version 2.x $Id: Generic.php 226 2007-09-17 21:00:15Z dk $
+ * @version 2.x $Id$
*/
/**
- * Base class for all databases.
- * Can create transactions and new BLOBs, parse DSNs.
- *
- * Logger is COMMON for multiple transactions.
- * Error handler is private for each transaction and database.
+ * Use this constant as placeholder value to skip optional SQL block [...].
*/
-abstract class DbSimple_Generic_Database extends DbSimple_Generic_LastError
-{
- /**
- * Public methods.
- */
-
- /**
- * object blob($blob_id)
- * Create new blob
- */
- public function blob($blob_id = null)
- {
- $this->_resetLastError();
- return $this->_performNewBlob($blob_id);
- }
-
- /**
- * void transaction($mode)
- * Create new transaction.
- */
- public function transaction($mode=null)
- {
- $this->_resetLastError();
- $this->_logQuery('-- START TRANSACTION '.$mode);
- return $this->_performTransaction($mode);
- }
-
- /**
- * mixed commit()
- * Commit the transaction.
- */
- public function commit()
- {
- $this->_resetLastError();
- $this->_logQuery('-- COMMIT');
- return $this->_performCommit();
- }
-
- /**
- * mixed rollback()
- * Rollback the transaction.
- */
- public function rollback()
- {
- $this->_resetLastError();
- $this->_logQuery('-- ROLLBACK');
- return $this->_performRollback();
- }
-
- /**
- * mixed select(string $query [, $arg1] [,$arg2] ...)
- * Execute query and return the result.
- */
- public function select($query)
- {
- $args = func_get_args();
- $total = false;
- return $this->_query($args, $total);
- }
-
- /**
- * mixed selectPage(int &$total, string $query [, $arg1] [,$arg2] ...)
- * Execute query and return the result.
- * Total number of found rows (independent to LIMIT) is returned in $total
- * (in most cases second query is performed to calculate $total).
- */
- public function selectPage(&$total, $query)
- {
- $args = func_get_args();
- array_shift($args);
- $total = true;
- return $this->_query($args, $total);
- }
-
- /**
- * hash selectRow(string $query [, $arg1] [,$arg2] ...)
- * Return the first row of query result.
- * On errors return false and set last error.
- * If no one row found, return array()! It is useful while debugging,
- * because PHP DOES NOT generates notice on $row['abc'] if $row === null
- * or $row === false (but, if $row is empty array, notice is generated).
- */
- public function selectRow()
- {
- $args = func_get_args();
- $total = false;
- $rows = $this->_query($args, $total);
- if (!is_array($rows)) return $rows;
- if (!count($rows)) return array();
- reset($rows);
- return current($rows);
- }
-
- /**
- * array selectCol(string $query [, $arg1] [,$arg2] ...)
- * Return the first column of query result as array.
- */
- public function selectCol()
- {
- $args = func_get_args();
- $total = false;
- $rows = $this->_query($args, $total);
- if (!is_array($rows)) return $rows;
- $this->_shrinkLastArrayDimensionCallback($rows);
- return $rows;
- }
-
- /**
- * scalar selectCell(string $query [, $arg1] [,$arg2] ...)
- * Return the first cell of the first column of query result.
- * If no one row selected, return null.
- */
- public function selectCell()
- {
- $args = func_get_args();
- $total = false;
- $rows = $this->_query($args, $total);
- if (!is_array($rows)) return $rows;
- if (!count($rows)) return null;
- reset($rows);
- $row = current($rows);
- if (!is_array($row)) return $row;
- reset($row);
- return current($row);
- }
-
- /**
- * mixed query(string $query [, $arg1] [,$arg2] ...)
- * Alias for select(). May be used for INSERT or UPDATE queries.
- */
- public function query()
- {
- $args = func_get_args();
- $total = false;
- return $this->_query($args, $total);
- }
-
- /**
- * string escape(mixed $s, bool $isIdent=false)
- * Enclose the string into database quotes correctly escaping
- * special characters. If $isIdent is true, value quoted as identifier
- * (e.g.: `value` in MySQL, "value" in Firebird, [value] in MSSQL).
- */
- public function escape($s, $isIdent=false)
- {
- return $this->_performEscape($s, $isIdent);
- }
-
- /**
- * DbSimple_SubQuery subquery(string $query [, $arg1] [,$arg2] ...)
- * Выполняет разворачивание плейсхолдеров без коннекта к базе
- * Нужно для сложных запросов, состоящих из кусков, которые полезно сохранить
- *
- */
- public function subquery()
- {
- $args = func_get_args();
- $this->_expandPlaceholders($args,$this->_placeholderNativeArgs !== null);
- return new DbSimple_SubQuery($args);
- }
-
- /**
- * callback setLogger(callback $logger)
- * Set query logger called before each query is executed.
- * Returns previous logger.
- */
- public function setLogger($logger)
- {
- $prev = $this->_logger;
- $this->_logger = $logger;
- return $prev;
- }
-
- /**
- * callback setCacher(callback $cacher)
- * Set cache mechanism called during each query if specified.
- * Returns previous handler.
- */
- public function setCacher(Zend_Cache_Backend_Interface $cacher=null)
- {
- $prev = $this->_cacher;
- $this->_cacher = $cacher;
- return $prev;
- }
-
- /**
- * string setIdentPrefix($prx)
- * Set identifier prefix used for $_ placeholder.
- */
- public function setIdentPrefix($prx)
- {
- $old = $this->_identPrefix;
- if ($prx !== null) $this->_identPrefix = $prx;
- return $old;
- }
-
- /**
- * string setCachePrefix($prx)
- * Set cache prefix used in key caclulation.
- */
- public function setCachePrefix($prx)
- {
- $old = $this->_cachePrefix;
- if ($prx !== null) $this->_cachePrefix = $prx;
- return $old;
- }
-
- /**
- * Задает имя класса строки
- *
- * <br>для следующего запроса каждая строка будет
- * заменена классом, конструктору которого передается
- * массив поле=>значение для этой строки
- *
- * @param string $name имя класса
- * @return DbSimple_Generic_Database указатель на себя
- */
- public function setClassName($name)
- {
- $this->_className = $name;
- return $this;
- }
-
- /**
- * array getStatistics()
- * Returns various statistical information.
- */
- public function getStatistics()
- {
- return $this->_statistics;
- }
-
-
- /**
- * string _performEscape(mixed $s, bool $isIdent=false)
- */
- abstract protected function _performEscape($s, $isIdent=false);
-
- /**
- * object _performNewBlob($id)
- *
- * Returns new blob object.
- */
- abstract protected function _performNewBlob($id=null);
-
- /**
- * list _performGetBlobFieldNames($resultResource)
- * Get list of all BLOB field names in result-set.
- */
- abstract protected function _performGetBlobFieldNames($result);
-
- /**
- * mixed _performTransformQuery(array &$query, string $how)
- *
- * Transform query different way specified by $how.
- * May return some information about performed transform.
- */
- abstract protected function _performTransformQuery(&$queryMain, $how);
-
-
- /**
- * resource _performQuery($arrayQuery)
- * Must return:
- * - For SELECT queries: ID of result-set (PHP resource).
- * - For other queries: query status (scalar).
- * - For error queries: false (and call _setLastError()).
- */
- abstract protected function _performQuery($arrayQuery);
-
- /**
- * mixed _performFetch($resultResource)
- * Fetch ONE NEXT row from result-set.
- * Must return:
- * - For SELECT queries: all the rows of the query (2d arrray).
- * - For INSERT queries: ID of inserted row.
- * - For UPDATE queries: number of updated rows.
- * - For other queries: query status (scalar).
- * - For error queries: false (and call _setLastError()).
- */
- abstract protected function _performFetch($result);
-
- /**
- * mixed _performTransaction($mode)
- * Start new transaction.
- */
- abstract protected function _performTransaction($mode=null);
-
- /**
- * mixed _performCommit()
- * Commit the transaction.
- */
- abstract protected function _performCommit();
-
- /**
- * mixed _performRollback()
- * Rollback the transaction.
- */
- abstract protected function _performRollback();
-
- /**
- * string _performGetPlaceholderIgnoreRe()
- * Return regular expression which matches ignored query parts.
- * This is needed to skip placeholder replacement inside comments, constants etc.
- */
- protected function _performGetPlaceholderIgnoreRe()
- {
- return '';
- }
-
- /**
- * Returns marker for native database placeholder. E.g. in FireBird it is '?',
- * in PostgreSQL - '$1', '$2' etc.
- *
- * @param int $n Number of native placeholder from the beginning of the query (begins from 0!).
- * @return string String representation of native placeholder marker (by default - '?').
- */
- protected function _performGetNativePlaceholderMarker($n)
- {
- return '?';
- }
-
-
- /**
- * array _query($query, &$total)
- * See _performQuery().
- */
- private function _query($query, &$total)
- {
- $this->_resetLastError();
-
- // Fetch query attributes.
- $this->attributes = $this->_transformQuery($query, 'GET_ATTRIBUTES');
-
- // Modify query if needed for total counting.
- if ($total)
- $this->_transformQuery($query, 'CALC_TOTAL');
-
- $rows = false;
- $cache_it = false;
- // Кешер у нас либо null либо соответствует Zend интерфейсу
- if (!empty($this->attributes['CACHE']) && $this->_cacher)
- {
-
- $hash = $this->_cachePrefix . md5(serialize($query));
- // Getting data from cache if possible
- $fetchTime = $firstFetchTime = 0;
- $qStart = microtime(true);
- $cacheData = unserialize($this->_cacher->load($hash));
- $queryTime = microtime(true) - $qStart;
-
- $invalCache = isset($cacheData['invalCache']) ? $cacheData['invalCache'] : null;
- $result = isset($cacheData['result']) ? $cacheData['result'] : null;
- $rows = isset($cacheData['rows']) ? $cacheData['rows'] : null;
-
- $cache_params = $this->attributes['CACHE'];
-
- // Calculating cache time to live
- $re = '/
- (?>
- ([0-9]+) #1 - hours
- h)? [ \t]*
- (?>
- ([0-9]+) #2 - minutes
- m)? [ \t]*
- (?>
- ([0-9]+) #3 - seconds
- s?)? (,)?
- /sx';
- $m = null;
- preg_match($re, $cache_params, $m);
- $ttl = (isset($m[3])?$m[3]:0)
- + (isset($m[2])?$m[2]:0) * 60
- + (isset($m[1])?$m[1]:0) * 3600;
- // Cutting out time param - now there are just fields for uniqKey or nothing
- $cache_params = trim(preg_replace($re, '', $cache_params, 1));
-
- $uniq_key = null;
-
- // UNIQ_KEY calculation
- if (!empty($cache_params)) {
- $dummy = null;
- // There is no need in query, cos' needle in $this->attributes['CACHE']
- $this->_transformQuery($dummy, 'UNIQ_KEY');
- $uniq_key = call_user_func_array(array(&$this, 'select'), array($dummy));
- $uniq_key = md5(serialize($uniq_key));
- }
- // Check TTL?
- $ok = empty($ttl) || $cacheData;
-
- // Invalidate cache?
- if ($ok && $uniq_key == $invalCache) {
- $this->_logQuery($query);
- $this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows);
-
- }
- else $cache_it = true;
- }
-
- if (false === $rows || true === $cache_it)
- {
- $this->_logQuery($query);
-
- // Run the query (counting time).
- $qStart = microtime(true);
- $result = $this->_performQuery($query);
- $fetchTime = $firstFetchTime = 0;
- if (is_resource($result)) {
- $rows = array();
- // Fetch result row by row.
- $fStart = microtime(true);
- $row = $this->_performFetch($result);
- $firstFetchTime = microtime(true) - $fStart;
- if ($row !== false) {
- $rows[] = $row;
- while ($row=$this->_performFetch($result)) {
- $rows[] = $row;
- }
- }
- $fetchTime = microtime(true) - $fStart;
- } else {
- $rows = $result;
- }
- $queryTime = microtime(true) - $qStart;
-
- // Log query statistics.
- $this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows);
-
- // Prepare BLOB objects if needed.
- if (is_array($rows) && !empty($this->attributes['BLOB_OBJ']))
- foreach ($this->_performGetBlobFieldNames($result) as $name)
- foreach ($rows as $r=>$v)
- $rows[$r][$name] = $this->_performNewBlob($v[$name]);
-
- // Transform resulting rows.
- $result = $this->_transformResult($rows);
-
- // Storing data in cache
- if ($cache_it && $this->_cacher)
- {
- $this->_cacher->save(
- serialize(array(
- 'invalCache' => $uniq_key,
- 'result' => $result,
- 'rows' => $rows
- )),
- $hash,
- array(),
- $ttl==0?false:$ttl
- );
- }
-
- }
- // Count total number of rows if needed.
- if (is_array($result) && $total) {
- $this->_transformQuery($query, 'GET_TOTAL');
- $total = call_user_func_array(array(&$this, 'selectCell'), $query);
- }
-
- if ($this->_className)
- {
- foreach($result as $k=>$v)
- $result[$k] = new $this->_className($v);
- $this->_className = '';
- }
-
- return $result;
- }
-
-
- /**
- * mixed _transformQuery(array &$query, string $how)
- *
- * Transform query different way specified by $how.
- * May return some information about performed transform.
- */
- private function _transformQuery(&$query, $how)
- {
- // Do overriden transformation.
- $result = $this->_performTransformQuery($query, $how);
- if ($result === true) return $result;
- // Common transformations.
- switch ($how) {
- case 'GET_ATTRIBUTES':
- // Extract query attributes.
- $options = array();
- $q = $query[0];
- $m = null;
- while (preg_match('/^ \s* -- [ \t]+ (\w+): ([^\r\n]+) [\r\n]* /sx', $q, $m)) {
- $options[$m[1]] = trim($m[2]);
- $q = substr($q, strlen($m[0]));
- }
- return $options;
- case 'UNIQ_KEY':
- $q = $this->attributes['CACHE'];
- $i = 0;
- $query = " -- UNIQ_KEY\n";
- while(preg_match('/(\w+)\.\w+/sx', $q, $m)) {
- if($i > 0)$query .= "\nUNION\n";
- $query .= 'SELECT MAX('.$m[0].') AS M, COUNT(*) AS C FROM '.$m[1];
- $q = substr($q, strlen($m[0]));
- $i++;
- }
- return true;
- }
- // No such transform.
- $this->_setLastError(-1, "No such transform type: $how", $query);
- }
-
-
- /**
- * void _expandPlaceholders(array &$queryAndArgs, bool $useNative=false)
- * Replace placeholders by quoted values.
- * Modify $queryAndArgs.
- */
- protected function _expandPlaceholders(&$queryAndArgs, $useNative=false)
- {
- $cacheCode = null;
- if ($this->_logger) {
- // Serialize is much faster than placeholder expansion. So use caching.
- $cacheCode = md5(serialize($queryAndArgs) . '|' . $useNative . '|' . $this->_identPrefix);
- if (isset($this->_placeholderCache[$cacheCode])) {
- $queryAndArgs = $this->_placeholderCache[$cacheCode];
- return;
- }
- }
-
- if (!is_array($queryAndArgs)) {
- $queryAndArgs = array($queryAndArgs);
- }
-
- $this->_placeholderNativeArgs = $useNative? array() : null;
- $this->_placeholderArgs = array_reverse($queryAndArgs);
-
- $query = array_pop($this->_placeholderArgs); // array_pop is faster than array_shift
-
- // Do all the work.
- $this->_placeholderNoValueFound = false;
- $query = $this->_expandPlaceholdersFlow($query);
-
- if ($useNative) {
- array_unshift($this->_placeholderNativeArgs, $query);
- $queryAndArgs = $this->_placeholderNativeArgs;
- } else {
- $queryAndArgs = array($query);
- }
-
- if ($cacheCode) {
- $this->_placeholderCache[$cacheCode] = $queryAndArgs;
- }
- }
-
-
- /**
- * Do real placeholder processing.
- * Imply that all interval variables (_placeholder_*) already prepared.
- * May be called recurrent!
- */
- private function _expandPlaceholdersFlow($query)
- {
- $re = '{
- (?>
- # Ignored chunks.
- (?>
- # Comment.
- -- [^\r\n]*
- )
- |
- (?>
- # DB-specifics.
- ' . trim($this->_performGetPlaceholderIgnoreRe()) . '
- )
- )
- |
- (?>
- # Optional blocks
- \{
- # Use "+" here, not "*"! Else nested blocks are not processed well.
- ( (?> (?>(\??)[^{}]+) | (?R) )* ) #1
- \}
- )
- |
- (?>
- # Placeholder
- (\?) ( [_dsafn&|\#]? ) #2 #3
- )
- }sx';
- $query = preg_replace_callback(
- $re,
- array(&$this, '_expandPlaceholdersCallback'),
- $query
- );
- return $query;
- }
-
- static $join = array(
- '|' => array('inner' => ' AND ', 'outer' => ') OR (',),
- '&' => array('inner' => ' OR ', 'outer' => ') AND (',),
- 'a' => array('inner' => ', ', 'outer' => '), (',),
- );
-
- /**
- * string _expandPlaceholdersCallback(list $m)
- * Internal function to replace placeholders (see preg_replace_callback).
- */
- private function _expandPlaceholdersCallback($m)
- {
- // Placeholder.
- if (!empty($m[3])) {
- $type = $m[4];
-
- // Idenifier prefix.
- if ($type == '_') {
- return $this->_identPrefix;
- }
-
- // Value-based placeholder.
- if (!$this->_placeholderArgs) return 'DBSIMPLE_ERROR_NO_VALUE';
- $value = array_pop($this->_placeholderArgs);
-
- // Skip this value?
- if ($value === DBSIMPLE_SKIP) {
- $this->_placeholderNoValueFound = true;
- return '';
- }
-
- // First process guaranteed non-native placeholders.
- switch ($type) {
- case 's':
- if (!($value instanceof DbSimple_SubQuery))
- return 'DBSIMPLE_ERROR_VALUE_NOT_SUBQUERY';
- return $value->get($this->_placeholderNativeArgs);
- case '|':
- case '&':
- case 'a':
- if (!$value) $this->_placeholderNoValueFound = true;
- if (!is_array($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_ARRAY';
- $parts = array();
- $multi = array(); //массив для двойной вложенности
- $mult = $type!='a' || is_int(key($value)) && is_array(current($value));
- foreach ($value as $prefix => $field) {
- //превращаем $value в двумерный нуменованный массив
- if (!is_array($field)) {
- $field = array($prefix => $field);
- $prefix = 0;
- }
- $prefix = is_int($prefix) ? '' :
- $this->escape($this->_addPrefix2Table($prefix), true) . '.';
- //для мультиинсерта очищаем ключи - их быть не может по синтаксису
- if ($mult && $type=='a')
- $field = array_values($field);
- foreach ($field as $k => $v)
- {
- if ($v instanceof DbSimple_SubQuery)
- $v = $v->get($this->_placeholderNativeArgs);
- else
- $v = $v === null? 'NULL' : $this->escape($v);
- if (!is_int($k)) {
- $k = $this->escape($k, true);
- $parts[] = "$prefix$k=$v";
- } else {
- $parts[] = $v;
- }
- }
- if ($mult)
- {
- $multi[] = join(self::$join[$type]['inner'], $parts);
- $parts = array();
- }
- }
- return $mult ? join(self::$join[$type]['outer'], $multi) : join(', ', $parts);
- case '#':
- // Identifier.
- if (!is_array($value))
- {
- if ($value instanceof DbSimple_SubQuery)
- return $value->get($this->_placeholderNativeArgs);
- $value = $this->_addPrefix2Table($value);
- return $this->escape($value, true);
- }
- $parts = array();
- foreach ($value as $table => $identifiers) {
- if (!is_array($identifiers)) $identifiers = array($identifiers);
- $prefix = '';
- if (!is_int($table)) {
- $table = $this->_addPrefix2Table($table);
- $prefix = $this->escape($table, true) . '.';
- }
- foreach ($identifiers as $identifier)
- if ($identifier instanceof DbSimple_SubQuery)
- $parts[] = $identifier->get($this->_placeholderNativeArgs);
- elseif (!is_string($identifier))
- return 'DBSIMPLE_ERROR_ARRAY_VALUE_NOT_STRING';
- else
- $parts[] = $prefix . ($identifier=='*' ? '*' :
- $this->escape($this->_addPrefix2Table($identifier), true));
- }
- return join(', ', $parts);
- case 'n':
- // NULL-based placeholder.
- return empty($value)? 'NULL' : intval($value);
- }
-
- // In non-native mode arguments are quoted.
- if ($value === null) return 'NULL';
-
- switch ($type) {
- case 'd':
- return intval($value);
- case 'f':
- return str_replace(',', '.', floatval($value));
- }
-
- // нативные плейсхолдеры необходимо обработать после числовых из-за бага в PDO
- // Native arguments are not processed.
- if ($this->_placeholderNativeArgs !== null) {
- $this->_placeholderNativeArgs[] = $value;
- return $this->_performGetNativePlaceholderMarker(count($this->_placeholderNativeArgs) - 1);
- }
-
- if (!is_scalar($value))
- return 'DBSIMPLE_ERROR_VALUE_NOT_SCALAR';
-
- // By default - escape as string.
- return $this->escape($value);
- }
-
- // Optional block.
- if (isset($m[1]) && strlen($block=$m[1]))
- {
- $prev = $this->_placeholderNoValueFound;
- if ($this->_placeholderNativeArgs !== null)
- $prevPh = $this->_placeholderNativeArgs;
-
- // Проверка на {? } - условный блок
- $skip = false;
- if ($m[2]=='?')
- {
- $skip = array_pop($this->_placeholderArgs) === DBSIMPLE_SKIP;
- $block[0] = ' ';
- }
-
- $block = $this->_expandOptionalBlock($block);
-
- if ($skip)
- $block = '';
-
- if ($this->_placeholderNativeArgs !== null)
- if ($this->_placeholderNoValueFound)
- $this->_placeholderNativeArgs = $prevPh;
- $this->_placeholderNoValueFound = $prev; // recurrent-safe
- return $block;
- }
-
- // Default: skipped part of the string.
- return $m[0];
- }
-
- /**
- * Заменяет ?_ на текущий префикс
- *
- * @param string $table имя таблицы
- * @return string имя таблицы
- */
- private function _addPrefix2Table($table)
- {
- if (substr($table, 0, 2) == '?_')
- $table = $this->_identPrefix . substr($table, 2);
- return $table;
- }
-
- private function _expandOptionalBlock($block)
- {
- $alts = array();
- $alt = '';
- $sub=0;
- $exp = explode('|',$block);
- // Оптимизация, так как в большинстве случаев | не используется
- if (count($exp)==1)
- $alts=$exp;
- else
- foreach ($exp as $v)
- {
- // Реализуем автоматный магазин для нахождения нужной скобки
- // На суммарную парность скобок проверять нет необходимости - об этом заботится регулярка
- $sub+=substr_count($v,'{');
- $sub-=substr_count($v,'}');
- if ($sub>0)
- $alt.=$v.'|';
- else
- {
- $alts[]=$alt.$v;
- $alt='';
- }
- }
- $r='';
- foreach ($alts as $block)
- {
- $this->_placeholderNoValueFound = false;
- $block = $this->_expandPlaceholdersFlow($block);
- // Необходимо пройти все блоки, так как если пропустить оставшиесь,
- // то это нарушит порядок подставляемых значений
- if ($this->_placeholderNoValueFound == false && $r=='')
- $r = ' '.$block.' ';
- }
- return $r;
- }
-
-
- /**
- * void _setLastError($code, $msg, $query)
- * Set last database error context.
- * Aditionally expand placeholders.
- */
- protected function _setLastError($code, $msg, $query)
- {
- if (is_array($query)) {
- $this->_expandPlaceholders($query, false);
- $query = $query[0];
- }
- return parent::_setLastError($code, $msg, $query);
- }
-
-
- /**
- * Convert SQL field-list to COUNT(...) clause
- * (e.g. 'DISTINCT a AS aa, b AS bb' -> 'COUNT(DISTINCT a, b)').
- */
- private function _fieldList2Count($fields)
- {
- $m = null;
- if (preg_match('/^\s* DISTINCT \s* (.*)/sx', $fields, $m)) {
- $fields = $m[1];
- $fields = preg_replace('/\s+ AS \s+ .*? (?=,|$)/sx', '', $fields);
- return "COUNT(DISTINCT $fields)";
- } else {
- return 'COUNT(*)';
- }
- }
-
-
- /**
- * array _transformResult(list $rows)
- * Transform resulting rows to various formats.
- */
- private function _transformResult($rows)
- {
- // Результат не массив - выходим
- if (!is_array($rows) || !$rows)
- return $rows;
-
- // Find ARRAY_KEY* AND PARENT_KEY fields in field list.
- $pk = null;
- $ak = array();
- foreach (array_keys(current($rows)) as $fieldName)
- if (0 == strncasecmp($fieldName, DBSIMPLE_ARRAY_KEY, strlen(DBSIMPLE_ARRAY_KEY)))
- $ak[] = $fieldName;
- elseif (0 == strncasecmp($fieldName, DBSIMPLE_PARENT_KEY, strlen(DBSIMPLE_PARENT_KEY)))
- $pk = $fieldName;
-
- if (!$ak)
- return $rows;
-
- natsort($ak); // sort ARRAY_KEY* using natural comparision
- // Tree-based array? Fields: ARRAY_KEY, PARENT_KEY
- if ($pk !== null)
- return $this->_transformResultToForest($rows, $ak[0], $pk);
- // Key-based array? Fields: ARRAY_KEY.
- return $this->_transformResultToHash($rows, $ak);
- }
-
-
- /**
- * Converts rowset to key-based array.
- *
- * @param array $rows Two-dimensional array of resulting rows.
- * @param array $ak List of ARRAY_KEY* field names.
- * @return array Transformed array.
- */
- private function _transformResultToHash(array $rows, array $arrayKeys)
- {
- $result = array();
- foreach ($rows as $row) {
- // Iterate over all of ARRAY_KEY* fields and build array dimensions.
- $current =& $result;
- foreach ($arrayKeys as $ak) {
- $key = $row[$ak];
- unset($row[$ak]); // remove ARRAY_KEY* field from result row
- if ($key !== null) {
- $current =& $current[$key];
- } else {
- // IF ARRAY_KEY field === null, use array auto-indices.
- $tmp = array();
- $current[] =& $tmp;
- $current =& $tmp;
- unset($tmp); // we use $tmp, because don't know the value of auto-index
- }
- }
- $current = $row; // save the row in last dimension
- }
- return $result;
- }
-
-
- /**
- * Converts rowset to the forest.
- *
- * @param array $rows Two-dimensional array of resulting rows.
- * @param string $idName Name of ID field.
- * @param string $pidName Name of PARENT_ID field.
- * @return array Transformed array (tree).
- */
- private function _transformResultToForest(array $rows, $idName, $pidName)
- {
- $children = array(); // children of each ID
- $ids = array();
- // Collect who are children of whom.
- foreach ($rows as $i=>$r) {
- $row =& $rows[$i];
- $id = $row[$idName];
- if ($id === null) {
- // Rows without an ID are totally invalid and makes the result tree to
- // be empty (because PARENT_ID = null means "a root of the tree"). So
- // skip them totally.
- continue;
- }
- $pid = $row[$pidName];
- if ($id == $pid) $pid = null;
- $children[$pid][$id] =& $row;
- if (!isset($children[$id])) $children[$id] = array();
- $row['childNodes'] =& $children[$id];
- $ids[$id] = true;
- }
- // Root elements are elements with non-found PIDs.
- $forest = array();
- foreach ($rows as $i=>$r) {
- $row =& $rows[$i];
- $id = $row[$idName];
- $pid = $row[$pidName];
- if ($pid == $id) $pid = null;
- if (!isset($ids[$pid])) {
- $forest[$row[$idName]] =& $row;
- }
- unset($row[$idName]);
- unset($row[$pidName]);
- }
- return $forest;
- }
-
-
- /**
- * Replaces the last array in a multi-dimensional array $V by its first value.
- * Used for selectCol(), when we need to transform (N+1)d resulting array
- * to Nd array (column).
- */
- private function _shrinkLastArrayDimensionCallback(&$v)
- {
- if (!$v) return;
- reset($v);
- if (!is_array($firstCell = current($v))) {
- $v = $firstCell;
- } else {
- array_walk($v, array(&$this, '_shrinkLastArrayDimensionCallback'));
- }
- }
-
-
- /**
- * void _logQuery($query, $noTrace=false)
- * Must be called on each query.
- * If $noTrace is true, library caller is not solved (speed improvement).
- */
- protected function _logQuery($query, $noTrace=false)
- {
- if (!$this->_logger) return;
- $this->_expandPlaceholders($query, false);
- $args = array();
- $args[] =& $this;
- $args[] = $query[0];
- $args[] = $noTrace? null : $this->findLibraryCaller();
- return call_user_func_array($this->_logger, $args);
- }
-
-
- /**
- * void _logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows)
- * Log information about performed query statistics.
- */
- private function _logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows)
- {
- // Always increment counters.
- $this->_statistics['time'] += $queryTime;
- $this->_statistics['count']++;
-
- // If no logger, economize CPU resources and actually log nothing.
- if (!$this->_logger)
- return;
-
- $dt = round($queryTime * 1000);
- $firstFetchTime = round($firstFetchTime*1000);
- $tailFetchTime = round($fetchTime * 1000) - $firstFetchTime;
- $log = " -- ";
- if ($firstFetchTime + $tailFetchTime) {
- $log = sprintf(" -- %d ms = %d+%d".($tailFetchTime? "+%d" : ""), $dt, $dt-$firstFetchTime-$tailFetchTime, $firstFetchTime, $tailFetchTime);
- } else {
- $log = sprintf(" -- %d ms", $dt);
- }
- $log .= "; returned ";
-
- if (!is_array($rows)) {
- $log .= $this->escape($rows);
- } else {
- $detailed = null;
- if (count($rows) == 1) {
- $len = 0;
- $values = array();
- foreach ($rows[0] as $k=>$v) {
- $len += strlen($v);
- if ($len > $this->MAX_LOG_ROW_LEN) {
- break;
- }
- $values[] = $v === null? 'NULL' : $this->escape($v);
- }
- if ($len <= $this->MAX_LOG_ROW_LEN) {
- $detailed = "(" . preg_replace("/\r?\n/", "\\n", join(', ', $values)) . ")";
- }
- }
- if ($detailed) {
- $log .= $detailed;
- } else {
- $log .= count($rows). " row(s)";
- }
- }
-
- $this->_logQuery($log, true);
- }
-
- // Identifiers prefix (used for ?_ placeholder).
- private $_identPrefix = '';
-
- // Queries statistics.
- private $_statistics = array(
- 'time' => 0,
- 'count' => 0,
- );
-
- private $_cachePrefix = '';
- private $_className = '';
-
- private $_logger = null;
- private $_cacher = null;
- private $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array();
- private $_placeholderNoValueFound;
-
- /**
- * When string representation of row (in characters) is greater than this,
- * row data will not be logged.
- */
- private $MAX_LOG_ROW_LEN = 128;
-}
-
+if (!defined('DBSIMPLE_SKIP'))
+ define('DBSIMPLE_SKIP', log(0));
/**
- * Интерфейс BLOB.
- * Описывает функции обекта типа BLOB - ему должны соответствовать классы, возвращаемые в качестве BLOB полей.
+ * Names of special columns in result-set which is used
+ * as array key (or karent key in forest-based resultsets) in
+ * resulting hash.
*/
-interface DbSimple_Generic_Blob
-{
- /**
- * string read(int $length)
- * Returns following $length bytes from the blob.
- */
- public function read($len);
-
- /**
- * string write($data)
- * Appends data to blob.
- */
- public function write($data);
-
- /**
- * int length()
- * Returns length of the blob.
- */
- public function length();
+if (!defined('DBSIMPLE_ARRAY_KEY'))
+ define('DBSIMPLE_ARRAY_KEY', 'ARRAY_KEY'); // hash-based resultset support
+if (!defined('DBSIMPLE_PARENT_KEY'))
+ define('DBSIMPLE_PARENT_KEY', 'PARENT_KEY'); // forrest-based resultset support
- /**
- * blobid close()
- * Closes the blob. Return its ID. No other way to obtain this ID!
- */
- public function close();
-
-}
/**
- * Класс для хранения подзапроса - результата выполнения функции
- * DbSimple_Generic_Database::subquery
- *
+ * DbSimple factory.
*/
-class DbSimple_SubQuery
+class DbSimple_Generic
{
- private $query=array();
-
-
- public function __construct(array $q)
- {
- $this->query = $q;
- }
-
/**
- * Возвращает сам запрос и добавляет плейсхолдеры в массив переданный по ссылке
+ * DbSimple_Generic connect(mixed $dsn)
*
- * @param &array|null - ссылка на массив плейсхолдеров
- * @return string
- */
- public function get(&$ph)
- {
- if ($ph !== null)
- $ph = array_merge($ph, array_slice($this->query,1,null,true));
- return $this->query[0];
- }
-
-}
-
-/**
- * Support for error tracking.
- * Can hold error messages, error queries and build proper stacktraces.
- */
-abstract class DbSimple_Generic_LastError
-{
- public $error = null;
- public $errmsg = null;
- private $showStack = false;
- private $errorHandler = null;
- private $ignoresInTraceRe = 'DbSimple_.*::.* | call_user_func.*';
-
- /**
- * abstract void _logQuery($query)
- * Must be overriden in derived class.
- */
- protected abstract function _logQuery($query);
-
- /**
- * void _resetLastError()
- * Reset the last error. Must be called on correct queries.
- */
- protected function _resetLastError()
- {
- $this->error = $this->errmsg = null;
- }
-
- /**
- * void _setLastError(int $code, string $message, string $query)
- * Fill $this->error property with error information. Error context
- * (code initiated the query outside DbSimple) is assigned automatically.
- */
- protected function _setLastError($code, $msg, $query)
- {
- $context = "unknown";
- $this->error = array(
- 'code' => $code,
- 'message' => rtrim($msg),
- 'query' => $query,
- 'context' => '',
- );
- if ($t = $this->findLibraryCaller($this->showStack))
- {
- if (!$this->showStack)
- $this->error['context'] = (isset($t['file'])? $t['file'] : '?') .
- ' line ' . (isset($t['line'])? $t['line'] : '?');
- else
- {
- $this->error['context'] = (isset($t[0]['file'])? $t[0]['file'] : '?') .
- ' line ' . (isset($t[0]['line'])? $t[0]['line'] : '?');
- $this->error['stack'] = array();
- foreach($t as $f)
- $this->error['stack'][] = (isset($f['class']) ? $f['class'].$f['type'] : '').
- (isset($f['function']) ? $f['function'] : '?').'() '.
- (isset($f['file'])? $f['file'] : '?') . ':' . (isset($f['line'])? $f['line'] : '?');
+ * Universal static function to connect ANY database using DSN syntax.
+ * Choose database driver according to DSN. Return new instance
+ * of this driver.
+ */
+ function& connect($dsn)
+ {
+ // Load database driver and create its instance.
+ $parsed = DbSimple_Generic::parseDSN($dsn);
+ if (!$parsed) {
+ $dummy = null;
+ return $dummy;
+ }
+ $class = 'DbSimple_'.ucfirst($parsed['scheme']);
+ if (!class_exists($class)) {
+ $file = str_replace('_', '/', $class) . ".php";
+ // Try to load library file from standard include_path.
+ if ($f = @fopen($file, "r", true)) {
+ fclose($f);
+ require_once($file);
+ } else {
+ // Wrong include_path; try to load from current directory.
+ $base = basename($file);
+ $dir = dirname(__FILE__);
+ if (@is_file($path = "$dir/$base")) {
+ require_once($path);
+ } else {
+ trigger_error("Error loading database driver: no file $file in include_path; no file $base in $dir", E_USER_ERROR);
+ return null;
+ }
}
}
- $this->errmsg = rtrim($msg) . ($this->error['context']? ' at '.$this->error['context'] : '');
-
- $this->_logQuery(" -- error #".$code.": ".preg_replace('/(\r?\n)+/s', ' ', $this->errmsg));
-
- if (is_callable($this->errorHandler)) {
- call_user_func($this->errorHandler, $this->errmsg, $this->error);
+ $object =& new $class($parsed);
+ if (isset($parsed['ident_prefix'])) {
+ $object->setIdentPrefix($parsed['ident_prefix']);
}
-
- return false;
+ $object->setCachePrefix(md5(serialize($parsed['dsn'])));
+ return $object;
}
/**
- * callback setErrorHandler(callback $handler, bool $stack)
- * Set new error handler called on database errors.
- * Handler gets 3 arguments:
- * - error message
- * - full error context information (last query etc.)
+ * array parseDSN(mixed $dsn)
+ * Parse a data source name.
+ * See parse_url() for details.
*/
- public function setErrorHandler($handler, $stack=false)
+ function parseDSN($dsn)
{
- $prev = $this->errorHandler;
- $this->errorHandler = $handler;
- $this->showStack = $stack;
- // In case of setting first error handler for already existed
- // error - call the handler now (usual after connect()).
- if (!$prev && $this->error)
- call_user_func($this->errorHandler, $this->errmsg, $this->error);
- return $prev;
- }
-
- /**
- * void addIgnoreInTrace($reName)
- * Add regular expression matching ClassName::functionName or functionName.
- * Matched stack frames will be ignored in stack traces passed to query logger.
- */
- public function addIgnoreInTrace($name)
- {
- $this->ignoresInTraceRe .= "|" . $name;
- }
-
- /**
- * array of array findLibraryCaller()
- * Return part of stacktrace before calling first library method.
- * Used in debug purposes (query logging etc.).
- */
- protected function findLibraryCaller($all = false)
- {
- $caller = call_user_func(
- array(&$this, 'debug_backtrace_smart'),
- $this->ignoresInTraceRe,
- !$all
- );
- return $caller;
- }
-
- /**
- * array debug_backtrace_smart($ignoresRe=null, $returnCaller=false)
- *
- * Return stacktrace. Correctly work with call_user_func*
- * (totally skip them correcting caller references).
- * If $returnCaller is true, return only first matched caller,
- * not all stacktrace.
- *
- * @version 2.03
- */
- private function debug_backtrace_smart($ignoresRe=null, $returnCaller=false)
- {
- $trace = debug_backtrace();
-
- if ($ignoresRe !== null)
- $ignoresRe = "/^(?>{$ignoresRe})$/six";
- $smart = array();
- $framesSeen = 0;
- for ($i=0, $n=count($trace); $i<$n; $i++) {
- $t = $trace[$i];
- if (!$t) continue;
-
- // Next frame.
- $next = isset($trace[$i+1])? $trace[$i+1] : null;
-
- // Dummy frame before call_user_func* frames.
- if (!isset($t['file'])) {
- $t['over_function'] = $trace[$i+1]['function'];
- $t = $t + $trace[$i+1];
- $trace[$i+1] = null; // skip call_user_func on next iteration
- $next = isset($trace[$i+2])? $trace[$i+2] : null; // Correct Next frame.
- }
-
- // Skip myself frame.
- if (++$framesSeen < 2) continue;
-
- // 'class' and 'function' field of next frame define where
- // this frame function situated. Skip frames for functions
- // situated in ignored places.
- if ($ignoresRe && $next) {
- // Name of function "inside which" frame was generated.
- $frameCaller = (isset($next['class'])? $next['class'].'::' : '') . (isset($next['function'])? $next['function'] : '');
- if (preg_match($ignoresRe, $frameCaller)) continue;
- }
-
- // On each iteration we consider ability to add PREVIOUS frame
- // to $smart stack.
- if ($returnCaller) return $t;
- $smart[] = $t;
+ if (is_array($dsn)) return $dsn;
+ $parsed = @parse_url($dsn);
+ if (!$parsed) return null;
+ $params = null;
+ if (!empty($parsed['query'])) {
+ parse_str($parsed['query'], $params);
+ $parsed += $params;
}
- return $smart;
+ $parsed['dsn'] = $dsn;
+ return $parsed;
}
-
}
-?>
\ No newline at end of file
+
+?>
diff --git a/lib/DbSimple/Ibase.php b/lib/DbSimple/Ibase.php
index 2dc45fd..8fe15a0 100644
--- a/lib/DbSimple/Ibase.php
+++ b/lib/DbSimple/Ibase.php
@@ -14,9 +14,9 @@
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/
*
- * @version 2.x $Id: Ibase.php 221 2007-07-27 23:24:35Z dk $
+ * @version 2.x $Id$
*/
-require_once dirname(__FILE__).'/Generic.php';
+require_once...
[truncated message content] |