![]() |
This comparison shows the changes necessary to convert path /DbSimple/tags (Rev 227) TO /DbSimple/tags (Rev 248)
@@ -0,0 +1,22 @@
<?php ## Ïîäêëþ÷åíèå ê ÁÄ. |
require_once "../../lib/config.php"; |
require_once "DbSimple/Generic.php"; |
|
// Ïîäêëþ÷àåìñÿ ê ÁÄ. |
$DATABASE = DbSimple_Generic::connect('mysql://test:test@localhost1/non-existed-db'); |
|
// Óñòàíàâëèâàåì îáðàáîò÷èê îøèáîê. |
$DATABASE->setErrorHandler('databaseErrorHandler'); |
|
// Êîä îáðàáîò÷èêà îøèáîê SQL. |
function databaseErrorHandler($message, $info) |
{ |
// Åñëè èñïîëüçîâàëàñü @, íè÷åãî íå äåëàòü. |
if (!error_reporting()) return; |
// Âûâîäèì ïîäðîáíóþ èíôîðìàöèþ îá îøèáêå. |
echo "SQL Error: $message<br><pre>"; |
print_r($info); |
echo "</pre>"; |
exit(); |
} |
?> |
@@ -0,0 +1,44 @@
--TEST-- |
PostgreSQL: returning of last inserted ID |
|
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id SERIAL, str VARCHAR(10)) WITH OIDS"); |
printr($DB->query("INSERT INTO test(str) VALUES ('test')"), "ID"); |
printr($DB->select("SELECT * FROM test"), "Result"); |
printr($DB->select("SELECT 1 AS a"), "Result"); |
} |
?> |
|
|
--SKIPIF-- |
<?php |
if (!is_callable('pg_connect')) print('skip pgsql extension not loaded'); |
?> |
|
|
--EXPECTF-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id SERIAL, str VARCHAR(10)) WITH OIDS' |
Query: 'INSERT INTO test(str) VALUES (\'test\')' |
ID: '%d' |
Query: 'SELECT * FROM test' |
Result: array ( |
0 => |
array ( |
'id' => '1', |
'str' => 'test', |
), |
) |
Query: 'SELECT 1 AS a' |
Result: array ( |
0 => |
array ( |
'a' => '1', |
), |
) |
@@ -0,0 +1 @@
postgresql://test:test@localhost/test |
@@ -0,0 +1,46 @@
--TEST-- |
PostgreSQL: returning result of RULE after INSERT |
|
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP RULE test_r ON test"); |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id SERIAL, str VARCHAR(10))"); |
$DB->query("CREATE RULE test_r AS ON INSERT TO test DO (SELECT 111 AS id)"); |
printr($DB->query("INSERT INTO test(str) VALUES ('test')"), "Rule generated"); |
printr($DB->query("SELECT * FROM test"), "Table content"); |
} |
?> |
|
|
--SKIPIF-- |
<?php |
if (!is_callable('pg_connect')) print('skip pgsql extension not loaded'); |
?> |
|
|
--EXPECT-- |
Query: 'DROP RULE test_r ON test' |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id SERIAL, str VARCHAR(10))' |
Query: 'CREATE RULE test_r AS ON INSERT TO test DO (SELECT 111 AS id)' |
Query: 'INSERT INTO test(str) VALUES (\'test\')' |
Rule generated: array ( |
0 => |
array ( |
'id' => '111', |
), |
) |
Query: 'SELECT * FROM test' |
Table content: array ( |
0 => |
array ( |
'id' => '1', |
'str' => 'test', |
), |
) |
|
@@ -0,0 +1,36 @@
--TEST-- |
PostgreSQL: ?# placeholder usage |
|
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
$row = array( |
'id' => 1, |
'str' => 'test' |
); |
|
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, str VARCHAR(10))"); |
$DB->query("INSERT INTO test(?#) VALUES(?a)", array_keys($row), array_values($row)); |
printr($DB->selectCol("SELECT ?# FROM test", 'id')); |
} |
?> |
|
|
--SKIPIF-- |
<?php |
if (!is_callable('pg_connect')) print('skip pgsql extension not loaded'); |
?> |
|
|
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, str VARCHAR(10))' |
Query: 'INSERT INTO test("id", "str") VALUES(\'1\', \'test\')' |
Query: 'SELECT "id" FROM test' |
array ( |
0 => '1', |
) |
@@ -0,0 +1,37 @@
--TEST-- |
Postgresql: native placeholders support |
|
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
$query = array("INSERT INTO test(id, pid, str) VALUES(?, ?, ?)", 10, 101, 'a'); |
$DB->_expandPlaceholders($query, true); |
printr($query, "With native placeholders"); |
|
$query = array("INSERT INTO test(id, pid, str) VALUES(?, ?, ?)", 10, 101, 'a'); |
$DB->_expandPlaceholders($query, false); |
printr($query, "Without native placeholders"); |
} |
?> |
|
|
--SKIPIF-- |
<?php |
if (!is_callable('pg_connect')) print('skip pgsql extension not loaded'); |
?> |
|
|
--EXPECT-- |
With native placeholders: array ( |
0 => 'INSERT INTO test(id, pid, str) VALUES($1, $2, $3)', |
1 => 10, |
2 => 101, |
3 => 'a', |
) |
Without native placeholders: array ( |
0 => 'INSERT INTO test(id, pid, str) VALUES(\'10\', \'101\', \'a\')', |
) |
|
@@ -0,0 +1 @@
mysql://test:test@localhost/test |
@@ -0,0 +1,29 @@
--TEST-- |
Mysql: ?# placeholder usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
$row = array( |
'id' => 1, |
'str' => 'test' |
); |
|
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, str VARCHAR(10))"); |
$DB->query("INSERT INTO test(?#) VALUES(?a)", array_keys($row), array_values($row)); |
printr($DB->selectCol("SELECT ?# FROM test", 'id')); |
} |
?> |
|
|
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, str VARCHAR(10))' |
Query: 'INSERT INTO test(`id`, `str`) VALUES(\'1\', \'test\')' |
Query: 'SELECT `id` FROM test' |
array ( |
0 => '1', |
) |
@@ -0,0 +1,55 @@
<?php |
$stack = debug_backtrace(); |
chdir(dirname(realpath($stack[0]['file']))); |
|
header("Content-type: text/plain"); |
include_once dirname(__FILE__) . "/../../lib/config.php"; |
ini_set("include_path", ini_get("include_path").PATH_SEPARATOR.dirname(__FILE__).'/..'); // for Cache_Lite |
include_once "DbSimple/Generic.php"; |
|
$DSN = array(); |
|
$dsnFile = "dsn.txt"; |
$dsnOwn = trim(@join("", file($dsnFile))); |
if (!$dsnOwn) die("Current directory must contain $dsnFile file!"); |
if ($dsnOwn == '*' || preg_match('/^\w+$/', $dsnOwn)) { |
$dir = dirname(__FILE__); |
$d = opendir($dir); |
while (false !== ($e = readdir($d))) { |
$full = realpath("$dir/$e"); |
if ($e == "." || $e == ".." || !is_dir($full) || $full == realpath(getcwd())) continue; |
if ($dsnOwn != '*' && strtolower($e) != strtolower($dsnOwn)) continue; |
$dsn = trim(@join("", file("$full/$dsnFile"))); |
if ($dsn) $DSN = array_merge($DSN, preg_split('/\s+/s', $dsn)); |
} |
} else { |
$DSN[] = $dsnOwn; |
} |
|
foreach ($DSN as $dsn) { |
$DB =& DbSimple_Generic::connect($dsn); |
$DB->setLogger('queryLogger'); |
$DB->setErrorHandler('errorHandler'); |
main($DB); |
} |
|
function queryLogger(&$DB, $query) |
{ |
if (preg_match('/^\s*--\s+(\d|error)/', $query)) return; |
printr($query, "Query"); |
} |
|
function errorHandler($msg, $error) |
{ |
if (!error_reporting()) return; |
printr($error['message'], "Error"); |
} |
|
// Debug human-readable output of any variable. |
function printr($value, $comment=null) |
{ |
if ($comment !== null) echo "$comment: "; |
var_export($value); |
echo "\n"; |
} |
?> |
@@ -0,0 +1,2 @@
AddHandler application/x-httpd-php phpt |
php_flag output_buffering On |
@@ -0,0 +1,42 @@
--TEST-- |
Generic: Cache_Lite usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
$query = " |
-- CACHE: 10 |
SELECT * FROM test |
"; |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, str) VALUES( 1, 'a')"); |
printr($DB->selectRow($query)); |
$DB->query("UPDATE test SET str='b' WHERE id=1"); |
printr($DB->selectRow($query)); |
} |
|
?> |
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, str) VALUES( 1, \'a\')' |
Query: ' |
-- CACHE: 10 |
SELECT * FROM test |
' |
array ( |
'id' => '1', |
'str' => 'a', |
) |
Query: 'UPDATE test SET str=\'b\' WHERE id=1' |
Query: ' |
-- CACHE: 10 |
SELECT * FROM test |
' |
array ( |
'id' => '1', |
'str' => 'a', |
) |
@@ -0,0 +1,22 @@
--TEST-- |
Generic: nested {}-blocks usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("SELECT * FROM t1 WHERE 1 { AND a = ?d } AND c = ?d", 1, 3); |
@$DB->query("SELECT * FROM t1 WHERE 1 { AND a = ?d } AND c = ?d", DBSIMPLE_SKIP, 3); |
@$DB->query("SELECT * FROM t1 WHERE 1 { AND a = ?d { AND b=?d } } AND c = ?d", 1, 2, 3); |
@$DB->query("SELECT * FROM t1 WHERE 1 { AND a = ?d { AND b=?d } } AND c = ?d", 1, DBSIMPLE_SKIP, 3); |
@$DB->query("SELECT * FROM t1 WHERE 1 { AND a = ?d { AND b=?d } } AND c = ?d", DBSIMPLE_SKIP, 2, 3); |
} |
|
?> |
--EXPECT-- |
Query: 'SELECT * FROM t1 WHERE 1 AND a = 1 AND c = 3' |
Query: 'SELECT * FROM t1 WHERE 1 AND c = 3' |
Query: 'SELECT * FROM t1 WHERE 1 AND a = 1 AND b=2 AND c = 3' |
Query: 'SELECT * FROM t1 WHERE 1 AND a = 1 AND c = 3' |
Query: 'SELECT * FROM t1 WHERE 1 AND c = 3' |
@@ -0,0 +1,98 @@
--TEST-- |
Generic: ARRAY_KEY* usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, pid INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(100, 10, 'a')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(101, 10, 'b')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(200, 20, 'x')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(201, 20, 'y')"); |
printr($DB->select("SELECT id AS ARRAY_KEY, str FROM test ORDER BY id")); |
printr($DB->select("SELECT id AS ARRAY_KEY_2, pid AS ARRAY_KEY_1, str FROM test ORDER BY id")); |
printr($DB->select("SELECT NULL AS ARRAY_KEY_2, pid AS ARRAY_KEY_1, str FROM test ORDER BY id")); |
} |
|
?> |
|
|
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, pid INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, pid, str) VALUES(100, 10, \'a\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(101, 10, \'b\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(200, 20, \'x\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(201, 20, \'y\')' |
Query: 'SELECT id AS ARRAY_KEY, str FROM test ORDER BY id' |
array ( |
100 => |
array ( |
'str' => 'a', |
), |
101 => |
array ( |
'str' => 'b', |
), |
200 => |
array ( |
'str' => 'x', |
), |
201 => |
array ( |
'str' => 'y', |
), |
) |
Query: 'SELECT id AS ARRAY_KEY_2, pid AS ARRAY_KEY_1, str FROM test ORDER BY id' |
array ( |
10 => |
array ( |
100 => |
array ( |
'str' => 'a', |
), |
101 => |
array ( |
'str' => 'b', |
), |
), |
20 => |
array ( |
200 => |
array ( |
'str' => 'x', |
), |
201 => |
array ( |
'str' => 'y', |
), |
), |
) |
Query: 'SELECT NULL AS ARRAY_KEY_2, pid AS ARRAY_KEY_1, str FROM test ORDER BY id' |
array ( |
10 => |
array ( |
0 => |
array ( |
'str' => 'a', |
), |
1 => |
array ( |
'str' => 'b', |
), |
), |
20 => |
array ( |
0 => |
array ( |
'str' => 'x', |
), |
1 => |
array ( |
'str' => 'y', |
), |
), |
) |
@@ -0,0 +1 @@
Mysql |
@@ -0,0 +1,48 @@
--TEST-- |
Generic: selectCol() usage with multi-dimensional array |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id1 INTEGER, id2 INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test VALUES( 1, 10, 'a')"); |
$DB->query("INSERT INTO test VALUES( 2, 20, 'b')"); |
$DB->query("INSERT INTO test VALUES( 2, 30, 'c')"); |
$DB->query("INSERT INTO test VALUES( 4, 40, 'd')"); |
printr($DB->selectCol("SELECT id1 AS ARRAY_KEY_1, str FROM test")); |
printr($DB->selectCol("SELECT id1 AS ARRAY_KEY_1, id2 AS ARRAY_KEY_2, str FROM test")); |
} |
|
?> |
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id1 INTEGER, id2 INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test VALUES( 1, 10, \'a\')' |
Query: 'INSERT INTO test VALUES( 2, 20, \'b\')' |
Query: 'INSERT INTO test VALUES( 2, 30, \'c\')' |
Query: 'INSERT INTO test VALUES( 4, 40, \'d\')' |
Query: 'SELECT id1 AS ARRAY_KEY_1, str FROM test' |
array ( |
1 => 'a', |
2 => 'c', |
4 => 'd', |
) |
Query: 'SELECT id1 AS ARRAY_KEY_1, id2 AS ARRAY_KEY_2, str FROM test' |
array ( |
1 => |
array ( |
10 => 'a', |
), |
2 => |
array ( |
20 => 'b', |
30 => 'c', |
), |
4 => |
array ( |
40 => 'd', |
), |
) |
@@ -0,0 +1,30 @@
--TEST-- |
Generic: selectRow(); usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, str) VALUES( 1, 'a')"); |
printr($DB->selectRow("SELECT * FROM test")); |
printr($DB->selectRow("SELECT str, id FROM test")); |
} |
|
?> |
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, str) VALUES( 1, \'a\')' |
Query: 'SELECT * FROM test' |
array ( |
'id' => '1', |
'str' => 'a', |
) |
Query: 'SELECT str, id FROM test' |
array ( |
'str' => 'a', |
'id' => '1', |
) |
@@ -0,0 +1,16 @@
--TEST-- |
Generic: identifier placeholderwith tablename |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("SELECT ?# FROM t1", array('a', 'b')); |
@$DB->query("SELECT ?# FROM t1", array('t1' => 'a', 'b')); |
} |
|
?> |
--EXPECT-- |
Query: 'SELECT `a`, `b` FROM t1' |
Query: 'SELECT `t1`.`a`, `b` FROM t1' |
@@ -0,0 +1,44 @@
--TEST-- |
Generic: selectCol() usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, str) VALUES( 1, 'a')"); |
$DB->query("INSERT INTO test(id, str) VALUES( 2, 'b')"); |
$DB->query("INSERT INTO test(id, str) VALUES( 3, 'c')"); |
$DB->query("INSERT INTO test(id, str) VALUES( 4, 'd')"); |
printr($DB->selectCol("SELECT str FROM test")); |
printr($DB->selectCol("SELECT str, id FROM test")); |
printr($DB->selectCol("SELECT str, id FROM test WHERE 1=0")); |
} |
|
?> |
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, str) VALUES( 1, \'a\')' |
Query: 'INSERT INTO test(id, str) VALUES( 2, \'b\')' |
Query: 'INSERT INTO test(id, str) VALUES( 3, \'c\')' |
Query: 'INSERT INTO test(id, str) VALUES( 4, \'d\')' |
Query: 'SELECT str FROM test' |
array ( |
0 => 'a', |
1 => 'b', |
2 => 'c', |
3 => 'd', |
) |
Query: 'SELECT str, id FROM test' |
array ( |
0 => 'a', |
1 => 'b', |
2 => 'c', |
3 => 'd', |
) |
Query: 'SELECT str, id FROM test WHERE 1=0' |
array ( |
) |
@@ -0,0 +1,28 @@
--TEST-- |
Generic: selectCell() usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, str) VALUES( 1, 'a')"); |
printr($DB->selectCell("SELECT id FROM test")); |
printr($DB->selectCell("SELECT str FROM test")); |
printr($DB->selectCell("SELECT id, str FROM test")); |
|
} |
|
?> |
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, str) VALUES( 1, \'a\')' |
Query: 'SELECT id FROM test' |
'1' |
Query: 'SELECT str FROM test' |
'a' |
Query: 'SELECT id, str FROM test' |
'1' |
@@ -0,0 +1,164 @@
--TEST-- |
Generic: ARRAY_KEY/PARENT_KEY usage |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, pid INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 1, NULL, 'a')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 2, 1, 'b')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 3, 1, 'c')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 4, 1, 'd')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 5, 2, 'e')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 6, 2, 'f')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 7, 2, 'g')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 8, 3, 'h')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 9, 3, 'i')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(10, 3, 'j')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(11, 4, 'k')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(12, 4, 'l')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(13, 4, 'm')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(14, 5, 'n')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(15, 5, 'o')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(16, 5, 'p')"); |
printr($DB->select("SELECT id AS ARRAY_KEY, pid AS PARENT_KEY, str FROM test")); |
} |
|
?> |
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, pid INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 1, NULL, \'a\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 2, 1, \'b\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 3, 1, \'c\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 4, 1, \'d\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 5, 2, \'e\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 6, 2, \'f\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 7, 2, \'g\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 8, 3, \'h\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 9, 3, \'i\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(10, 3, \'j\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(11, 4, \'k\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(12, 4, \'l\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(13, 4, \'m\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(14, 5, \'n\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(15, 5, \'o\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(16, 5, \'p\')' |
Query: 'SELECT id AS ARRAY_KEY, pid AS PARENT_KEY, str FROM test' |
array ( |
1 => |
array ( |
'str' => 'a', |
'childNodes' => |
array ( |
2 => |
array ( |
'str' => 'b', |
'childNodes' => |
array ( |
5 => |
array ( |
'str' => 'e', |
'childNodes' => |
array ( |
14 => |
array ( |
'str' => 'n', |
'childNodes' => |
array ( |
), |
), |
15 => |
array ( |
'str' => 'o', |
'childNodes' => |
array ( |
), |
), |
16 => |
array ( |
'str' => 'p', |
'childNodes' => |
array ( |
), |
), |
), |
), |
6 => |
array ( |
'str' => 'f', |
'childNodes' => |
array ( |
), |
), |
7 => |
array ( |
'str' => 'g', |
'childNodes' => |
array ( |
), |
), |
), |
), |
3 => |
array ( |
'str' => 'c', |
'childNodes' => |
array ( |
8 => |
array ( |
'str' => 'h', |
'childNodes' => |
array ( |
), |
), |
9 => |
array ( |
'str' => 'i', |
'childNodes' => |
array ( |
), |
), |
10 => |
array ( |
'str' => 'j', |
'childNodes' => |
array ( |
), |
), |
), |
), |
4 => |
array ( |
'str' => 'd', |
'childNodes' => |
array ( |
11 => |
array ( |
'str' => 'k', |
'childNodes' => |
array ( |
), |
), |
12 => |
array ( |
'str' => 'l', |
'childNodes' => |
array ( |
), |
), |
13 => |
array ( |
'str' => 'm', |
'childNodes' => |
array ( |
), |
), |
), |
), |
), |
), |
) |
@@ -0,0 +1,166 @@
--TEST-- |
Generic: ARRAY_KEY/PARENT_KEY case insensitivity |
--FILE-- |
<?php |
require_once dirname(__FILE__) . '/../init.php'; |
|
function main(&$DB) |
{ |
@$DB->query("DROP TABLE test"); |
$DB->query("CREATE TABLE test(id INTEGER, pid INTEGER, str VARCHAR(1))"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 1, NULL, 'a')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 2, 1, 'b')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 3, 1, 'c')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 4, 1, 'd')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 5, 2, 'e')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 6, 2, 'f')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 7, 2, 'g')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 8, 3, 'h')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES( 9, 3, 'i')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(10, 3, 'j')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(11, 4, 'k')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(12, 4, 'l')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(13, 4, 'm')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(14, 5, 'n')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(15, 5, 'o')"); |
$DB->query("INSERT INTO test(id, pid, str) VALUES(16, 5, 'p')"); |
printr($DB->select("SELECT id AS array_key, pid AS Parent_Key, str FROM test")); |
} |
|
?> |
|
--EXPECT-- |
Query: 'DROP TABLE test' |
Query: 'CREATE TABLE test(id INTEGER, pid INTEGER, str VARCHAR(1))' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 1, NULL, \'a\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 2, 1, \'b\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 3, 1, \'c\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 4, 1, \'d\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 5, 2, \'e\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 6, 2, \'f\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 7, 2, \'g\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 8, 3, \'h\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES( 9, 3, \'i\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(10, 3, \'j\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(11, 4, \'k\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(12, 4, \'l\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(13, 4, \'m\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(14, 5, \'n\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(15, 5, \'o\')' |
Query: 'INSERT INTO test(id, pid, str) VALUES(16, 5, \'p\')' |
Query: 'SELECT id AS array_key, pid AS Parent_Key, str FROM test' |
array ( |
1 => |
array ( |
'str' => 'a', |
'childNodes' => |
array ( |
2 => |
array ( |
'str' => 'b', |
'childNodes' => |
array ( |
5 => |
array ( |
'str' => 'e', |
'childNodes' => |
array ( |
14 => |
array ( |
'str' => 'n', |
'childNodes' => |
array ( |
), |
), |
15 => |
array ( |
'str' => 'o', |
'childNodes' => |
array ( |
), |
), |
16 => |
array ( |
'str' => 'p', |
'childNodes' => |
array ( |
), |
), |
), |
), |
6 => |
array ( |
'str' => 'f', |
'childNodes' => |
array ( |
), |
), |
7 => |
array ( |
'str' => 'g', |
'childNodes' => |
array ( |
), |
), |
), |
), |
3 => |
array ( |
'str' => 'c', |
'childNodes' => |
array ( |
8 => |
array ( |
'str' => 'h', |
'childNodes' => |
array ( |
), |
), |
9 => |
array ( |
'str' => 'i', |
'childNodes' => |
array ( |
), |
), |
10 => |
array ( |
'str' => 'j', |
'childNodes' => |
array ( |
), |
), |
), |
), |
4 => |
array ( |
'str' => 'd', |
'childNodes' => |
array ( |
11 => |
array ( |
'str' => 'k', |
'childNodes' => |
array ( |
), |
), |
12 => |
array ( |
'str' => 'l', |
'childNodes' => |
array ( |
), |
), |
13 => |
array ( |
'str' => 'm', |
'childNodes' => |
array ( |
), |
), |
), |
), |
), |
), |
) |
|
@@ -0,0 +1,824 @@
<?php |
|
/** |
* Fast, light and safe Cache Class |
* |
* Cache_Lite is a fast, light and safe cache system. It's optimized |
* for file containers. It is fast and safe (because it uses file |
* locking and/or anti-corruption tests). |
* |
* There are some examples in the 'docs/examples' file |
* Technical choices are described in the 'docs/technical' file |
* |
* Memory Caching is from an original idea of |
* Mike BENOIT <ipso@snappymail.ca> |
* |
* Nota : A chinese documentation (thanks to RainX <china_1982@163.com>) is |
* available at : |
* http://rainx.phpmore.com/manual/cache_lite.html |
* |
* @package Cache_Lite |
* @category Caching |
* @version $Id: Lite.php,v 1.45 2006/06/03 08:10:33 fab Exp $ |
* @author Fabien MARTY <fab@php.net> |
*/ |
|
define('CACHE_LITE_ERROR_RETURN', 1); |
define('CACHE_LITE_ERROR_DIE', 8); |
|
class Cache_Lite |
{ |
|
// --- Private properties --- |
|
/** |
* Directory where to put the cache files |
* (make sure to add a trailing slash) |
* |
* @var string $_cacheDir |
*/ |
var $_cacheDir = '/tmp/'; |
|
/** |
* Enable / disable caching |
* |
* (can be very usefull for the debug of cached scripts) |
* |
* @var boolean $_caching |
*/ |
var $_caching = true; |
|
/** |
* Cache lifetime (in seconds) |
* |
* If null, the cache is valid forever. |
* |
* @var int $_lifeTime |
*/ |
var $_lifeTime = 3600; |
|
/** |
* Enable / disable fileLocking |
* |
* (can avoid cache corruption under bad circumstances) |
* |
* @var boolean $_fileLocking |
*/ |
var $_fileLocking = true; |
|
/** |
* Timestamp of the last valid cache |
* |
* @var int $_refreshTime |
*/ |
var $_refreshTime; |
|
/** |
* File name (with path) |
* |
* @var string $_file |
*/ |
var $_file; |
|
/** |
* File name (without path) |
* |
* @var string $_fileName |
*/ |
var $_fileName; |
|
/** |
* Enable / disable write control (the cache is read just after writing to detect corrupt entries) |
* |
* Enable write control will lightly slow the cache writing but not the cache reading |
* Write control can detect some corrupt cache files but maybe it's not a perfect control |
* |
* @var boolean $_writeControl |
*/ |
var $_writeControl = true; |
|
/** |
* Enable / disable read control |
* |
* If enabled, a control key is embeded in cache file and this key is compared with the one |
* calculated after the reading. |
* |
* @var boolean $_writeControl |
*/ |
var $_readControl = true; |
|
/** |
* Type of read control (only if read control is enabled) |
* |
* Available values are : |
* 'md5' for a md5 hash control (best but slowest) |
* 'crc32' for a crc32 hash control (lightly less safe but faster, better choice) |
* 'strlen' for a length only test (fastest) |
* |
* @var boolean $_readControlType |
*/ |
var $_readControlType = 'crc32'; |
|
/** |
* Pear error mode (when raiseError is called) |
* |
* (see PEAR doc) |
* |
* @see setToDebug() |
* @var int $_pearErrorMode |
*/ |
var $_pearErrorMode = CACHE_LITE_ERROR_RETURN; |
|
/** |
* Current cache id |
* |
* @var string $_id |
*/ |
var $_id; |
|
/** |
* Current cache group |
* |
* @var string $_group |
*/ |
var $_group; |
|
/** |
* Enable / Disable "Memory Caching" |
* |
* NB : There is no lifetime for memory caching ! |
* |
* @var boolean $_memoryCaching |
*/ |
var $_memoryCaching = false; |
|
/** |
* Enable / Disable "Only Memory Caching" |
* (be carefull, memory caching is "beta quality") |
* |
* @var boolean $_onlyMemoryCaching |
*/ |
var $_onlyMemoryCaching = false; |
|
/** |
* Memory caching array |
* |
* @var array $_memoryCachingArray |
*/ |
var $_memoryCachingArray = array(); |
|
/** |
* Memory caching counter |
* |
* @var int $memoryCachingCounter |
*/ |
var $_memoryCachingCounter = 0; |
|
/** |
* Memory caching limit |
* |
* @var int $memoryCachingLimit |
*/ |
var $_memoryCachingLimit = 1000; |
|
/** |
* File Name protection |
* |
* if set to true, you can use any cache id or group name |
* if set to false, it can be faster but cache ids and group names |
* will be used directly in cache file names so be carefull with |
* special characters... |
* |
* @var boolean $fileNameProtection |
*/ |
var $_fileNameProtection = true; |
|
/** |
* Enable / disable automatic serialization |
* |
* it can be used to save directly datas which aren't strings |
* (but it's slower) |
* |
* @var boolean $_serialize |
*/ |
var $_automaticSerialization = false; |
|
/** |
* Disable / Tune the automatic cleaning process |
* |
* The automatic cleaning process destroy too old (for the given life time) |
* cache files when a new cache file is written. |
* 0 => no automatic cache cleaning |
* 1 => systematic cache cleaning |
* x (integer) > 1 => automatic cleaning randomly 1 times on x cache write |
* |
* @var int $_automaticCleaning |
*/ |
var $_automaticCleaningFactor = 0; |
|
/** |
* Nested directory level |
* |
* Set the hashed directory structure level. 0 means "no hashed directory |
* structure", 1 means "one level of directory", 2 means "two levels"... |
* This option can speed up Cache_Lite only when you have many thousands of |
* cache file. Only specific benchs can help you to choose the perfect value |
* for you. Maybe, 1 or 2 is a good start. |
* |
* @var int $_hashedDirectoryLevel |
*/ |
var $_hashedDirectoryLevel = 0; |
|
/** |
* Umask for hashed directory structure |
* |
* @var int $_hashedDirectoryUmask |
*/ |
var $_hashedDirectoryUmask = 0700; |
|
/** |
* API break for error handling in CACHE_LITE_ERROR_RETURN mode |
* |
* In CACHE_LITE_ERROR_RETURN mode, error handling was not good because |
* for example save() method always returned a boolean (a PEAR_Error object |
* would be better in CACHE_LITE_ERROR_RETURN mode). To correct this without |
* breaking the API, this option (false by default) can change this handling. |
* |
* @var boolean |
*/ |
var $_errorHandlingAPIBreak = false; |
|
// --- Public methods --- |
|
/** |
* Constructor |
* |
* $options is an assoc. Available options are : |
* $options = array( |
* 'cacheDir' => directory where to put the cache files (string), |
* 'caching' => enable / disable caching (boolean), |
* 'lifeTime' => cache lifetime in seconds (int), |
* 'fileLocking' => enable / disable fileLocking (boolean), |
* 'writeControl' => enable / disable write control (boolean), |
* 'readControl' => enable / disable read control (boolean), |
* 'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string), |
* 'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int), |
* 'memoryCaching' => enable / disable memory caching (boolean), |
* 'onlyMemoryCaching' => enable / disable only memory caching (boolean), |
* 'memoryCachingLimit' => max nbr of records to store into memory caching (int), |
* 'fileNameProtection' => enable / disable automatic file name protection (boolean), |
* 'automaticSerialization' => enable / disable automatic serialization (boolean), |
* 'automaticCleaningFactor' => distable / tune automatic cleaning process (int), |
* 'hashedDirectoryLevel' => level of the hashed directory system (int), |
* 'hashedDirectoryUmask' => umask for hashed directory structure (int), |
* 'errorHandlingAPIBreak' => API break for better error handling ? (boolean) |
* ); |
* |
* @param array $options options |
* @access public |
*/ |
function Cache_Lite($options = array(NULL)) |
{ |
foreach($options as $key => $value) { |
$this->setOption($key, $value); |
} |
} |
|
/** |
* Generic way to set a Cache_Lite option |
* |
* see Cache_Lite constructor for available options |
* |
* @var string $name name of the option |
* @var mixed $value value of the option |
* @access public |
*/ |
function setOption($name, $value) |
{ |
$availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode'); |
if (in_array($name, $availableOptions)) { |
$property = '_'.$name; |
$this->$property = $value; |
} |
} |
|
/** |
* Test if a cache is available and (if yes) return it |
* |
* @param string $id cache id |
* @param string $group name of the cache group |
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested |
* @return string data of the cache (else : false) |
* @access public |
*/ |
function get($id, $group = 'default', $doNotTestCacheValidity = false) |
{ |
$this->_id = $id; |
$this->_group = $group; |
$data = false; |
if ($this->_caching) { |
$this->_setRefreshTime(); |
$this->_setFileName($id, $group); |
clearstatcache(); |
if ($this->_memoryCaching) { |
if (isset($this->_memoryCachingArray[$this->_file])) { |
if ($this->_automaticSerialization) { |
return unserialize($this->_memoryCachingArray[$this->_file]); |
} |
return $this->_memoryCachingArray[$this->_file]; |
} |
if ($this->_onlyMemoryCaching) { |
return false; |
} |
} |
if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) { |
if (file_exists($this->_file)) { |
$data = $this->_read(); |
} |
} else { |
if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) { |
$data = $this->_read(); |
} |
} |
if (($data) and ($this->_memoryCaching)) { |
$this->_memoryCacheAdd($data); |
} |
if (($this->_automaticSerialization) and (is_string($data))) { |
$data = unserialize($data); |
} |
return $data; |
} |
return false; |
} |
|
/** |
* Save some data in a cache file |
* |
* @param string $data data to put in cache (can be another type than strings if automaticSerialization is on) |
* @param string $id cache id |
* @param string $group name of the cache group |
* @return boolean true if no problem (else : false or a PEAR_Error object) |
* @access public |
*/ |
function save($data, $id = NULL, $group = 'default') |
{ |
if ($this->_caching) { |
if ($this->_automaticSerialization) { |
$data = serialize($data); |
} |
if (isset($id)) { |
$this->_setFileName($id, $group); |
} |
if ($this->_memoryCaching) { |
$this->_memoryCacheAdd($data); |
if ($this->_onlyMemoryCaching) { |
return true; |
} |
} |
if ($this->_automaticCleaningFactor>0) { |
$rand = rand(1, $this->_automaticCleaningFactor); |
if ($rand==1) { |
$this->clean(false, 'old'); |
} |
} |
if ($this->_writeControl) { |
$res = $this->_writeAndControl($data); |
if (is_bool($res)) { |
if ($res) { |
return true; |
} |
// if $res if false, we need to invalidate the cache |
@touch($this->_file, time() - 2*abs($this->_lifeTime)); |
return false; |
} |
} else { |
$res = $this->_write($data); |
} |
if (is_object($res)) { |
// $res is a PEAR_Error object |
if (!($this->_errorHandlingAPIBreak)) { |
return false; // we return false (old API) |
} |
} |
return $res; |
} |
return false; |
} |
|
/** |
* Remove a cache file |
* |
* @param string $id cache id |
* @param string $group name of the cache group |
* @return boolean true if no problem |
* @access public |
*/ |
function remove($id, $group = 'default') |
{ |
$this->_setFileName($id, $group); |
if ($this->_memoryCaching) { |
if (isset($this->_memoryCachingArray[$this->_file])) { |
unset($this->_memoryCachingArray[$this->_file]); |
$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1; |
} |
if ($this->_onlyMemoryCaching) { |
return true; |
} |
} |
return $this->_unlink($this->_file); |
} |
|
/** |
* Clean the cache |
* |
* if no group is specified all cache files will be destroyed |
* else only cache files of the specified group will be destroyed |
* |
* @param string $group name of the cache group |
* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup', |
* 'callback_myFunction' |
* @return boolean true if no problem |
* @access public |
*/ |
function clean($group = false, $mode = 'ingroup') |
{ |
return $this->_cleanDir($this->_cacheDir, $group, $mode); |
} |
|
/** |
* Set to debug mode |
* |
* When an error is found, the script will stop and the message will be displayed |
* (in debug mode only). |
* |
* @access public |
*/ |
function setToDebug() |
{ |
$this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE); |
} |
|
/** |
* Set a new life time |
* |
* @param int $newLifeTime new life time (in seconds) |
* @access public |
*/ |
function setLifeTime($newLifeTime) |
{ |
$this->_lifeTime = $newLifeTime; |
$this->_setRefreshTime(); |
} |
|
/** |
* Save the state of the caching memory array into a cache file cache |
* |
* @param string $id cache id |
* @param string $group name of the cache group |
* @access public |
*/ |
function saveMemoryCachingState($id, $group = 'default') |
{ |
if ($this->_caching) { |
$array = array( |
'counter' => $this->_memoryCachingCounter, |
'array' => $this->_memoryCachingState |
); |
$data = serialize($array); |
$this->save($data, $id, $group); |
} |
} |
|
/** |
* Load the state of the caching memory array from a given cache file cache |
* |
* @param string $id cache id |
* @param string $group name of the cache group |
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested |
* @access public |
*/ |
function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false) |
{ |
if ($this->_caching) { |
if ($data = $this->get($id, $group, $doNotTestCacheValidity)) { |
$array = unserialize($data); |
$this->_memoryCachingCounter = $array['counter']; |
$this->_memoryCachingArray = $array['array']; |
} |
} |
} |
|
/** |
* Return the cache last modification time |
* |
* BE CAREFUL : THIS METHOD IS FOR HACKING ONLY ! |
* |
* @return int last modification time |
*/ |
function lastModified() |
{ |
return @filemtime($this->_file); |
} |
|
/** |
* Trigger a PEAR error |
* |
* To improve performances, the PEAR.php file is included dynamically. |
* The file is so included only when an error is triggered. So, in most |
* cases, the file isn't included and perfs are much better. |
* |
* @param string $msg error message |
* @param int $code error code |
* @access public |
*/ |
function raiseError($msg, $code) |
{ |
include_once('PEAR.php'); |
return PEAR::raiseError($msg, $code, $this->_pearErrorMode); |
} |
|
/** |
* Extend the life of a valid cache file |
* |
* see http://pear.php.net/bugs/bug.php?id=6681 |
* |
* @access public |
*/ |
function extendLife() |
{ |
@touch($this->_file); |
} |
|
// --- Private methods --- |
|
/** |
* Compute & set the refresh time |
* |
* @access private |
*/ |
function _setRefreshTime() |
{ |
if (is_null($this->_lifeTime)) { |
$this->_refreshTime = null; |
} else { |
$this->_refreshTime = time() - $this->_lifeTime; |
} |
} |
|
/** |
* Remove a file |
* |
* @param string $file complete file path and name |
* @return boolean true if no problem |
* @access private |
*/ |
function _unlink($file) |
{ |
if (!@unlink($file)) { |
return $this->raiseError('Cache_Lite : Unable to remove cache !', -3); |
} |
return true; |
} |
|
/** |
* Recursive function for cleaning cache file in the given directory |
* |
* @param string $dir directory complete path (with a trailing slash) |
* @param string $group name of the cache group |
* @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup', |
'callback_myFunction' |
* @return boolean true if no problem |
* @access private |
*/ |
function _cleanDir($dir, $group = false, $mode = 'ingroup') |
{ |
if ($this->_fileNameProtection) { |
$motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_'; |
} else { |
$motif = ($group) ? 'cache_'.$group.'_' : 'cache_'; |
} |
if ($this->_memoryCaching) { |
while (list($key, ) = each($this->_memoryCachingArray)) { |
if (strpos($key, $motif, 0)) { |
unset($this->_memoryCachingArray[$key]); |
$this->_memoryCachingCounter = $this->_memoryCachingCounter - 1; |
} |
} |
if ($this->_onlyMemoryCaching) { |
return true; |
} |
} |
if (!($dh = opendir($dir))) { |
return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4); |
} |
$result = true; |
while ($file = readdir($dh)) { |
if (($file != '.') && ($file != '..')) { |
if (substr($file, 0, 6)=='cache_') { |
$file2 = $dir . $file; |
if (is_file($file2)) { |
switch (substr($mode, 0, 9)) { |
case 'old': |
// files older than lifeTime get deleted from cache |
if (!is_null($this->_lifeTime)) { |
if ((mktime() - @filemtime($file2)) > $this->_lifeTime) { |
$result = ($result and ($this->_unlink($file2))); |
} |
} |
break; |
case 'notingrou': |
if (!strpos($file2, $motif, 0)) { |
$result = ($result and ($this->_unlink($file2))); |
} |
break; |
case 'callback_': |
$func = substr($mode, 9, strlen($mode) - 9); |
if ($func($file2, $group)) { |
$result = ($result and ($this->_unlink($file2))); |
} |
break; |
case 'ingroup': |
default: |
if (strpos($file2, $motif, 0)) { |
$result = ($result and ($this->_unlink($file2))); |
} |
break; |
} |
} |
if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) { |
$result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode))); |
} |
} |
} |
} |
return $result; |
} |
|
/** |
* Add some date in the memory caching array |
* |
* @param string $data data to cache |
* @access private |
*/ |
function _memoryCacheAdd($data) |
{ |
$this->_memoryCachingArray[$this->_file] = $data; |
if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) { |
list($key, ) = each($this->_memoryCachingArray); |
unset($this->_memoryCachingArray[$key]); |
} else { |
$this->_memoryCachingCounter = $this->_memoryCachingCounter + 1; |
} |
} |
|
/** |
* Make a file name (with path) |
* |
* @param string $id cache id |
* @param string $group name of the group |
* @access private |
*/ |
function _setFileName($id, $group) |
{ |
|
if ($this->_fileNameProtection) { |
$suffix = 'cache_'.md5($group).'_'.md5($id); |
} else { |
$suffix = 'cache_'.$group.'_'.$id; |
} |
$root = $this->_cacheDir; |
if ($this->_hashedDirectoryLevel>0) { |
$hash = md5($suffix); |
for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) { |
$root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/'; |
} |
} |
$this->_fileName = $suffix; |
$this->_file = $root.$suffix; |
} |
|
/** |
* Read the cache file and return the content |
* |
* @return string content of the cache file (else : false or a PEAR_Error object) |
* @access private |
*/ |
function _read() |
{ |
$fp = @fopen($this->_file, "rb"); |
if ($this->_fileLocking) @flock($fp, LOCK_SH); |
if ($fp) { |
clearstatcache(); |
$length = @filesize($this->_file); |
$mqr = get_magic_quotes_runtime(); |
set_magic_quotes_runtime(0); |
if ($this->_readControl) { |
$hashControl = @fread($fp, 32); |
$length = $length - 32; |
} |
if ($length) { |
$data = @fread($fp, $length); |
} else { |
$data = ''; |
} |
set_magic_quotes_runtime($mqr); |
if ($this->_fileLocking) @flock($fp, LOCK_UN); |
@fclose($fp); |
if ($this->_readControl) { |
$hashData = $this->_hash($data, $this->_readControlType); |
if ($hashData != $hashControl) { |
if (!(is_null($this->_lifeTime))) { |
@touch($this->_file, time() - 2*abs($this->_lifeTime)); |
} else { |
@unlink($this->_file); |
} |
return false; |
} |
} |
return $data; |
} |
return $this->raiseError('Cache_Lite : Unable to read cache !', -2); |
} |
|
/** |
* Write the given data in the cache file |
* |
* @param string $data data to put in cache |
* @return boolean true if ok (a PEAR_Error object else) |
* @access private |
*/ |
function _write($data) |
{ |
if ($this->_hashedDirectoryLevel > 0) { |
$hash = md5($this->_fileName); |
$root = $this->_cacheDir; |
for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) { |
$root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/'; |
if (!(@is_dir($root))) { |
@mkdir($root, $this->_hashedDirectoryUmask); |
} |
} |
} |
$fp = @fopen($this->_file, "wb"); |
if ($fp) { |
if ($this->_fileLocking) @flock($fp, LOCK_EX); |
if ($this->_readControl) { |
@fwrite($fp, $this->_hash($data, $this->_readControlType), 32); |
} |
$len = strlen($data); |
@fwrite($fp, $data, $len); |
if ($this->_fileLocking) @flock($fp, LOCK_UN); |
@fclose($fp); |
return true; |
} |
return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1); |
} |
|
/** |
* Write the given data in the cache file and control it just after to avoir corrupted cache entries |
* |
* @param string $data data to put in cache |
* @return boolean true if the test is ok (else : false or a PEAR_Error object) |
* @access private |
*/ |
function _writeAndControl($data) |
{ |
$result = $this->_write($data); |
if (is_object($result)) { |
return $result; #Â We return the PEAR_Error object |
} |
$dataRead = $this->_read(); |
if (is_object($dataRead)) { |
return $result; #Â We return the PEAR_Error object |
} |
if ((is_bool($dataRead)) && (!$dataRead)) { |
return false; |
} |
return ($dataRead==$data); |
} |
|
/** |
* Make a control key with the string containing datas |
* |
* @param string $data data |
* @param string $controlType type of control 'md5', 'crc32' or 'strlen' |
* @return string control key |
* @access private |
*/ |
function _hash($data, $controlType) |
{ |
switch ($controlType) { |
case 'md5': |
return md5($data); |
case 'crc32': |
return sprintf('% 32d', crc32($data)); |
case 'strlen': |
return sprintf('% 32d', strlen($data)); |
default: |
return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5); |
} |
} |
|
} |
|
?> |
@@ -0,0 +1,92 @@
<?php |
|
/** |
* This class extends Cache_Lite and offers a cache system driven by a master file |
* |
* With this class, cache validity is only dependent of a given file. Cache files |
* are valid only if they are older than the master file. It's a perfect way for |
* caching templates results (if the template file is newer than the cache, cache |
* must be rebuild...) or for config classes... |
* There are some examples in the 'docs/examples' file |
* Technical choices are described in the 'docs/technical' file |
* |
* @package Cache_Lite |
* @version $Id: File.php,v 1.3 2005/12/04 16:03:55 fab Exp $ |
* @author Fabien MARTY <fab@php.net> |
*/ |
|
require_once('Cache/Lite.php'); |
|
class Cache_Lite_File extends Cache_Lite |
{ |
|
// --- Private properties --- |
|
/** |
* Complete path of the file used for controlling the cache lifetime |
* |
* @var string $_masterFile |
*/ |
var $_masterFile = ''; |
|
/** |
* Masterfile mtime |
* |
* @var int $_masterFile_mtime |
*/ |
var $_masterFile_mtime = 0; |
|
// --- Public methods ---- |
|
/** |
* Constructor |
* |
* $options is an assoc. To have a look at availables options, |
* see the constructor of the Cache_Lite class in 'Cache_Lite.php' |
* |
* Comparing to Cache_Lite constructor, there is another option : |
* $options = array( |
* (...) see Cache_Lite constructor |
* 'masterFile' => complete path of the file used for controlling the cache lifetime(string) |
* ); |
* |
* @param array $options options |
* @access public |
*/ |
function Cache_Lite_File($options = array(NULL)) |
{ |
$options['lifetime'] = 0; |
$this->Cache_Lite($options); |
if (isset($options['masterFile'])) { |
$this->_masterFile = $options['masterFile']; |
} else { |
return $this->raiseError('Cache_Lite_File : masterFile option must be set !'); |
} |
if (!($this->_masterFile_mtime = @filemtime($this->_masterFile))) { |
return $this->raiseError('Cache_Lite_File : Unable to read masterFile : '.$this->_masterFile, -3); |
} |
} |
|
/** |
* Test if a cache is available and (if yes) return it |
* |
* @param string $id cache id |
* @param string $group name of the cache group |
* @return string data of the cache (or false if no cache available) |
* @access public |
*/ |
function get($id, $group = 'default') |
{ |
if ($data = parent::get($id, $group, true)) { |
if ($filemtime = $this->lastModified()) { |
if ($filemtime > $this->_masterFile_mtime) { |
return $data; |
} |
} |
} |
return false; |
} |
|
} |
|
?> |
@@ -0,0 +1,72 @@
<?php |
|
/** |
* This class extends Cache_Lite and uses output buffering to get the data to cache. |
* |
* There are some examples in the 'docs/examples' file |
* Technical choices are described in the 'docs/technical' file |
* |
* @package Cache_Lite |
* @version $Id: Output.php,v 1.4 2006/01/29 00:22:07 fab Exp $ |
* @author Fabien MARTY <fab@php.net> |
*/ |
|
require_once('Cache/Lite.php'); |
|
class Cache_Lite_Output extends Cache_Lite |
{ |
|
// --- Public methods --- |
|
/** |
* Constructor |
* |
* $options is an assoc. To have a look at availables options, |
* see the constructor of the Cache_Lite class in 'Cache_Lite.php' |
* |
* @param array $options options |
* @access public |
*/ |
function Cache_Lite_Output($options) |
{ |
$this->Cache_Lite($options); |
} |
|
/** |
* Start the cache |
* |
* @param string $id cache id |
* @param string $group name of the cache group |
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested |
* @return boolean true if the cache is hit (false else) |
* @access public |
*/ |
function start($id, $group = 'default', $doNotTestCacheValidity = false) |
{ |
$data = $this->get($id, $group, $doNotTestCacheValidity); |
if ($data !== false) { |
echo($data); |
return true; |
} |
ob_start(); |
ob_implicit_flush(false); |
return false; |
} |
|
/** |
* Stop the cache |
* |
* @access public |
*/ |
function end() |
{ |
$data = ob_get_contents(); |
ob_end_clean(); |
$this->save($data, $this->_id, $this->_group); |
echo($data); |
} |
|
} |
|
|
?> |
@@ -0,0 +1,211 @@
<?php |
|
/** |
* This class extends Cache_Lite and can be used to cache the result and output of functions/methods |
* |
* This class is completly inspired from Sebastian Bergmann's |
* PEAR/Cache_Function class. This is only an adaptation to |
* Cache_Lite |
* |
* There are some examples in the 'docs/examples' file |
* Technical choices are described in the 'docs/technical' file |
* |
* @package Cache_Lite |
* @version $Id: Function.php,v 1.10 2006/02/04 18:36:36 fab Exp $ |
* @author Sebastian BERGMANN <sb@sebastian-bergmann.de> |
* @author Fabien MARTY <fab@php.net> |
*/ |
|
require_once('Cache/Lite.php'); |
|
class Cache_Lite_Function extends Cache_Lite |
{ |
|
// --- Private properties --- |
|
/** |
* Default cache group for function caching |
* |
* @var string $_defaultGroup |
*/ |
var $_defaultGroup = 'Cache_Lite_Function'; |
|
/** |
* Don't cache the method call when its output contains the string "NOCACHE" |
* |
* if set to true, the output of the method will never be displayed (because the output is used |
* to control the cache) |
* |
* @var boolean $_dontCacheWhenTheOutputContainsNOCACHE |
*/ |
var $_dontCacheWhenTheOutputContainsNOCACHE = false; |
|
/** |
* Don't cache the method call when its result is false |
* |
* @var boolean $_dontCacheWhenTheResultIsFalse |
*/ |
var $_dontCacheWhenTheResultIsFalse = false; |
|
/** |
* Don't cache the method call when its result is null |
* |
* @var boolean $_dontCacheWhenTheResultIsNull |
*/ |
var $_dontCacheWhenTheResultIsNull = false; |
|
/** |
* Debug the Cache_Lite_Function caching process |
* |
* @var boolean $_debugCacheLiteFunction |
*/ |
var $_debugCacheLiteFunction = false; |
|
// --- Public methods ---- |
|
/** |
* Constructor |
* |
* $options is an assoc. To have a look at availables options, |
* see the constructor of the Cache_Lite class in 'Cache_Lite.php' |
* |
* Comparing to Cache_Lite constructor, there is another option : |
* $options = array( |
* (...) see Cache_Lite constructor |
* 'debugCacheLiteFunction' => (bool) debug the caching process, |
* 'defaultGroup' => default cache group for function caching (string), |
* 'dontCacheWhenTheOutputContainsNOCACHE' => (bool) don't cache when the function output contains "NOCACHE", |
* 'dontCacheWhenTheResultIsFalse' => (bool) don't cache when the function result is false, |
* 'dontCacheWhenTheResultIsNull' => (bool don't cache when the function result is null |
* ); |
* |
* @param array $options options |
* @access public |
*/ |
function Cache_Lite_Function($options = array(NULL)) |
{ |
$availableOptions = array('debugCacheLiteFunction', 'defaultGroup', 'dontCacheWhenTheOutputContainsNOCACHE', 'dontCacheWhenTheResultIsFalse', 'dontCacheWhenTheResultIsNull'); |
while (list($name, $value) = each($options)) { |
if (in_array($name, $availableOptions)) { |
$property = '_'.$name; |
$this->$property = $value; |
} |
} |
reset($options); |
$this->Cache_Lite($options); |
} |
|
/** |
* Calls a cacheable function or method (or not if there is already a cache for it) |
* |
* Arguments of this method are read with func_get_args. So it doesn't appear |
* in the function definition. Synopsis : |
* call('functionName', $arg1, $arg2, ...) |
* (arg1, arg2... are arguments of 'functionName') |
* |
* @return mixed result of the function/method |
* @access public |
*/ |
function call() |
{ |
$arguments = func_get_args(); |
$id = $this->_makeId($arguments); |
$data = $this->get($id, $this->_defaultGroup); |
if ($data !== false) { |
if ($this->_debugCacheLiteFunction) { |
echo "Cache hit !\n"; |
} |
$array = unserialize($data); |
$output = $array['output']; |
$result = $array['result']; |
} else { |
if ($this->_debugCacheLiteFunction) { |
echo "Cache missed !\n"; |
} |
ob_start(); |
ob_implicit_flush(false); |
$target = array_shift($arguments); |
if (is_array($target)) { |
// in this case, $target is for example array($obj, 'method') |
$object = $target[0]; |
$method = $target[1]; |
$result = call_user_func_array(array(&$object, $method), $arguments); |
} else { |
if (strstr($target, '::')) { // classname::staticMethod |
list($class, $method) = explode('::', $target); |
$result = call_user_func_array(array($class, $method), $arguments); |
} else if (strstr($target, '->')) { // object->method |
// use a stupid name ($objet_123456789 because) of problems where the object |
// name is the same as this var name |
list($object_123456789, $method) = explode('->', $target); |
global $$object_123456789; |
$result = call_user_func_array(array($$object_123456789, $method), $arguments); |
} else { // function |
$result = call_user_func_array($target, $arguments); |
} |
} |
$output = ob_get_contents(); |
ob_end_clean(); |
if ($this->_dontCacheWhenTheResultIsFalse) { |
if ((is_bool($result)) && (!($result))) { |
echo($output); |
return $result; |
} |
} |
if ($this->_dontCacheWhenTheResultIsNull) { |
if (is_null($result)) { |
echo($output); |
return $result; |
} |
} |
if ($this->_dontCacheWhenTheOutputContainsNOCACHE) { |
if (strpos($output, 'NOCACHE') > -1) { |
return $result; |
} |
} |
$array['output'] = $output; |
$array['result'] = $result; |
$this->save(serialize($array), $id, $this->_defaultGroup); |
} |
echo($output); |
return $result; |
} |
|
/** |
* Drop a cache file |
* |
* Arguments of this method are read with func_get_args. So it doesn't appear |
* in the function definition. Synopsis : |
* remove('functionName', $arg1, $arg2, ...) |
* (arg1, arg2... are arguments of 'functionName') |
* |
* @return boolean true if no problem |
* @access public |
*/ |
function drop() |
{ |
$id = $this->_makeId(func_get_args()); |
$this->remove($id, $this->_defaultGroup); |
} |
|
/** |
* Make an id for the cache |
* |
* @var array result of func_get_args for the call() or the remove() method |
* @return string id |
* @access private |
*/ |
function _makeId($arguments) |
{ |
$id = serialize($arguments); // Generate a cache id |
if (!$this->_fileNameProtection) { |
$id = md5($id); |
// if fileNameProtection is set to false, then the id has to be hashed |
// because it's a very bad file name in most cases |
} |
return $id; |
} |
|
} |
|
?> |
@@ -0,0 +1,1361 @@
<?php |
/** |
* DbSimple_Generic: universal database connected by DSN. |
* (C) Dk Lab, http://en.dklab.ru |
* |
* This library is free software; you can redistribute it and/or |
* modify it under the terms of the GNU Lesser General Public |
* License as published by the Free Software Foundation; either |
* version 2.1 of the License, or (at your option) any later version. |
* See http://www.gnu.org/copyleft/lesser.html |
* |
* 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 |
* end of the DSN. |
* |
* The format of the supplied DSN is in its fullest form: |
* phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true |
* |
* Most variations are allowed: |
* phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 |
* phptype://username:password@hostspec/database_name |
* phptype://username:password@hostspec |
* phptype://username@hostspec |
* phptype://hostspec/database |
* phptype://hostspec |
* phptype(dbsyntax) |
* phptype |
* |
* Parsing code is partially grabbed from PEAR DB class, |
* initial author: Tomas V.V.Cox <cox@idecnet.com>. |
* |
* Ñontains 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 |
* |
* Special result-set fields: |
* - ARRAY_KEY* ("*" means "anything") |
* - PARENT_KEY |
* |
* Transforms: |
* - GET_ATTRIBUTES |
* - CALC_TOTAL |
* - GET_TOTAL |
* - UNIQ_KEY |
* |
* Query attributes: |
* - BLOB_OBJ |
* - CACHE |
* |
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/ |
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/ |
* |
* @version 2.x $Id$ |
*/ |
|
/** |
* Use this constant as placeholder value to skip optional SQL block [...]. |
*/ |
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. |
*/ |
define('DBSIMPLE_ARRAY_KEY', 'ARRAY_KEY'); // hash-based resultset support |
define('DBSIMPLE_PARENT_KEY', 'PARENT_KEY'); // forrest-based resultset support |
|
|
/** |
* DbSimple factory. |
*/ |
class DbSimple_Generic |
{ |
/** |
* DbSimple_Generic connect(mixed $dsn) |
* |
* 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; |
} |
} |
} |
$object =& new $class($parsed); |
if (isset($parsed['ident_prefix'])) { |
$object->setIdentPrefix($parsed['ident_prefix']); |
} |
$object->setCachePrefix(md5(serialize($parsed['dsn']))); |
if (@fopen('Cache/Lite.php', 'r', true)) { |
$tmp_dirs = array( |
ini_get('session.save_path'), |
getenv("TEMP"), |
getenv("TMP"), |
getenv("TMPDIR"), |
'/tmp' |
); |
foreach ($tmp_dirs as $dir) { |
if (!$dir) continue; |
$fp = @fopen($testFile = $dir . '/DbSimple_' . md5(getmypid() . microtime()), 'w'); |
if ($fp) { |
fclose($fp); |
unlink($testFile); |
require_once 'Cache' . '/Lite.php'; // "." -> no phpEclipse notice |
$t =& new Cache_Lite(array('cacheDir' => $dir.'/', 'lifeTime' => null, 'automaticSerialization' => true)); |
$object->_cacher =& $t; |
break; |
} |
|
} |
} |
return $object; |
} |
|
|
/** |
* array parseDSN(mixed $dsn) |
* Parse a data source name. |
* See parse_url() for details. |
*/ |
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; |
} |
} |
|
|
/** |
* 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. |
*/ |
class DbSimple_Generic_Database extends DbSimple_Generic_LastError |
{ |
/** |
* Public methods. |
*/ |
|
/** |
* object blob($blob_id) |
* Create new blob |
*/ |
function blob($blob_id = null) |
{ |
$this->_resetLastError(); |
return $this->_performNewBlob($blob_id); |
} |
|
/** |
* void transaction($mode) |
* Create new transaction. |
*/ |
function transaction($mode=null) |
{ |
$this->_resetLastError(); |
$this->_logQuery('-- START TRANSACTION '.$mode); |
return $this->_performTransaction($mode); |
} |
|
/** |
* mixed commit() |
* Commit the transaction. |
*/ |
function commit() |
{ |
$this->_resetLastError(); |
$this->_logQuery('-- COMMIT'); |
return $this->_performCommit(); |
} |
|
/** |
* mixed rollback() |
* Rollback the transaction. |
*/ |
function rollback() |
{ |
$this->_resetLastError(); |
$this->_logQuery('-- ROLLBACK'); |
return $this->_performRollback(); |
} |
|
/** |
* mixed select(string $query [, $arg1] [,$arg2] ...) |
* Execute query and return the result. |
*/ |
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). |
*/ |
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 null 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). |
*/ |
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. |
*/ |
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. |
*/ |
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. |
*/ |
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). |
*/ |
function escape($s, $isIdent=false) |
{ |
return $this->_performEscape($s, $isIdent); |
} |
|
|
/** |
* callback setLogger(callback $logger) |
* Set query logger called before each query is executed. |
* Returns previous logger. |
*/ |
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. |
*/ |
function setCacher($cacher) |
{ |
$prev = $this->_cacher; |
$this->_cacher = $cacher; |
return $prev; |
} |
|
/** |
* string setIdentPrefix($prx) |
* Set identifier prefix used for $_ placeholder. |
*/ |
function setIdentPrefix($prx) |
{ |
$old = $this->_identPrefix; |
if ($prx !== null) $this->_identPrefix = $prx; |
return $old; |
} |
|
/** |
* string setIdentPrefix($prx) |
* Set cache prefix used in key caclulation. |
*/ |
function setCachePrefix($prx) |
{ |
$old = $this->_cachePrefix; |
if ($prx !== null) $this->_cachePrefix = $prx; |
return $old; |
} |
|
/** |
* array getStatistics() |
* Returns various statistical information. |
*/ |
function getStatistics() |
{ |
return $this->_statistics; |
} |
|
|
/** |
* Virtual protected methods |
*/ |
function ____________PROTECTED() {} // for phpEclipse outline |
|
|
/** |
* string _performEscape(mixed $s, bool $isIdent=false) |
*/ |
function _performEscape($s, $isIdent) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* object _performNewBlob($id) |
* |
* Returns new blob object. |
*/ |
function& _performNewBlob($id) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* list _performGetBlobFieldNames($resultResource) |
* Get list of all BLOB field names in result-set. |
*/ |
function _performGetBlobFieldNames($result) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* mixed _performTransformQuery(array &$query, string $how) |
* |
* Transform query different way specified by $how. |
* May return some information about performed transform. |
*/ |
function _performTransformQuery(&$queryMain, $how) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
|
/** |
* resource _performQuery($arrayQuery) |
* Must return: |
* - For SELECT queries: ID of result-set (PHP resource). |
* - For other queries: query status (scalar). |
* - For error queries: null (and call _setLastError()). |
*/ |
function _performQuery($arrayQuery) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* 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: null (and call _setLastError()). |
*/ |
function _performFetch($result) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* array _performTotal($arrayQuery) |
*/ |
function _performTotal($arrayQuery) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* mixed _performTransaction($mode) |
* Start new transaction. |
*/ |
function _performTransaction($mode=null) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* mixed _performCommit() |
* Commit the transaction. |
*/ |
function _performCommit() |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* mixed _performRollback() |
* Rollback the transaction. |
*/ |
function _performRollback() |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* string _performGetPlaceholderIgnoreRe() |
* Return regular expression which matches ignored query parts. |
* This is needed to skip placeholder replacement inside comments, constants etc. |
*/ |
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 - '?'). |
*/ |
function _performGetNativePlaceholderMarker($n) |
{ |
return '?'; |
} |
|
|
/** |
* Private methods. |
*/ |
function ____________PRIVATE() {} // for phpEclipse outline |
|
|
/** |
* array _query($query, &$total) |
* See _performQuery(). |
*/ |
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'); |
} |
$is_cacher_callable = (is_callable($this->_cacher) || (method_exists($this->_cacher, 'get') && method_exists($this->_cacher, 'save'))); |
$rows = null; |
$cache_it = false; |
if (!empty($this->attributes['CACHE']) && $is_cacher_callable) { |
|
$hash = $this->_cachePrefix . md5(serialize($query)); |
// Getting data from cache if possible |
$fetchTime = $firstFetchTime = 0; |
$qStart = $this->_microtime(); |
$cacheData = $this->_cache($hash); |
$queryTime = $this->_microtime() - $qStart; |
|
$storeTime = isset($cacheData['storeTime']) ? $cacheData['storeTime'] : null; |
$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]+) #2 - hours |
h)? [ \t]* |
( |
([0-9]+) #4 - minutes |
m)? [ \t]* |
( |
([0-9]+) #6 - seconds |
s?)? (,)? |
/sx'; |
$m = null; |
preg_match($re, $cache_params, $m); |
$ttl = @$m[6] + @$m[4] * 60 + @$m[2] * 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'), $dummy); |
$uniq_key = md5(serialize($uniq_key)); |
} |
// Check TTL? |
$ttl = empty($ttl) ? true : (int)$storeTime > (time() - $ttl); |
|
// Invalidate cache? |
if ($ttl && $uniq_key == $invalCache) { |
$this->_logQuery($query); |
$this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows); |
|
} |
else $cache_it = true; |
} |
|
if (null === $rows || true === $cache_it) { |
$this->_logQuery($query); |
|
// Run the query (counting time). |
$qStart = $this->_microtime(); |
$result = $this->_performQuery($query); |
$fetchTime = $firstFetchTime = 0; |
|
if (is_resource($result)) { |
$rows = array(); |
// Fetch result row by row. |
$fStart = $this->_microtime(); |
$row = $this->_performFetch($result); |
$firstFetchTime = $this->_microtime() - $fStart; |
if ($row !== null) { |
$rows[] = $row; |
while ($row=$this->_performFetch($result)) { |
$rows[] = $row; |
} |
} |
$fetchTime = $this->_microtime() - $fStart; |
} else { |
$rows = $result; |
} |
$queryTime = $this->_microtime() - $qStart; |
|
// Log query statistics. |
$this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows); |
|
// Prepare BLOB objects if needed. |
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); |
|
// Storing data in cache |
if ($cache_it && $is_cacher_callable) { |
$this->_cache( |
$hash, |
array( |
'storeTime' => time(), |
'invalCache' => $uniq_key, |
'result' => $result, |
'rows' => $rows |
) |
); |
} |
|
} |
// 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); |
} |
|
return $result; |
} |
|
|
/** |
* mixed _transformQuery(array &$query, string $how) |
* |
* Transform query different way specified by $how. |
* May return some information about performed transform. |
*/ |
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. |
*/ |
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! |
*/ |
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; |
} |
|
|
/** |
* string _expandPlaceholdersCallback(list $m) |
* Internal function to replace placeholders (see preg_replace_callback). |
*/ |
function _expandPlaceholdersCallback($m) |
{ |
// Placeholder. |
if (!empty($m[2])) { |
$type = $m[3]; |
|
// 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 'a': |
if (!$value) $this->_placeholderNoValueFound = true; |
if (!is_array($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_ARRAY'; |
$parts = array(); |
foreach ($value as $k=>$v) { |
$v = $v === null? 'NULL' : $this->escape($v); |
if (!is_int($k)) { |
$k = $this->escape($k, true); |
$parts[] = "$k=$v"; |
} else { |
$parts[] = $v; |
} |
} |
return join(', ', $parts); |
case "#": |
// Identifier. |
if (!is_array($value)) return $this->escape($value, true); |
$parts = array(); |
foreach ($value as $table => $identifier) { |
if (!is_string($identifier)) return 'DBSIMPLE_ERROR_ARRAY_VALUE_NOT_STRING'; |
$parts[] = (!is_int($table)? $this->escape($table, true) . '.' : '') . $this->escape($identifier, true); |
} |
return join(', ', $parts); |
case 'n': |
// NULL-based placeholder. |
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)); |
} |
// By default - escape as string. |
return $this->escape($value); |
} |
|
// Optional block. |
if (isset($m[1]) && strlen($block=$m[1])) { |
$prev = @$this->_placeholderNoValueFound; |
$block = $this->_expandPlaceholdersFlow($block); |
$block = $this->_placeholderNoValueFound? '' : ' ' . $block . ' '; |
$this->_placeholderNoValueFound = $prev; // recurrent-safe |
return $block; |
} |
|
// Default: skipped part of the string. |
return $m[0]; |
} |
|
|
/** |
* void _setLastError($code, $msg, $query) |
* Set last database error context. |
* Aditionally expand placeholders. |
*/ |
function _setLastError($code, $msg, $query) |
{ |
if (is_array($query)) { |
$this->_expandPlaceholders($query, false); |
$query = $query[0]; |
} |
return DbSimple_Generic_LastError::_setLastError($code, $msg, $query); |
} |
|
|
/** |
* Return microtime as float value. |
*/ |
function _microtime() |
{ |
$t = explode(" ", microtime()); |
return $t[0] + $t[1]; |
} |
|
|
/** |
* Convert SQL field-list to COUNT(...) clause |
* (e.g. 'DISTINCT a AS aa, b AS bb' -> 'COUNT(DISTINCT a, b)'). |
*/ |
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. |
*/ |
function _transformResult($rows) |
{ |
// Process ARRAY_KEY feature. |
if (is_array($rows) && $rows) { |
// Find ARRAY_KEY* AND PARENT_KEY fields in field list. |
$pk = null; |
$ak = array(); |
foreach (current($rows) as $fieldName => $dummy) { |
if (0 == strncasecmp($fieldName, DBSIMPLE_ARRAY_KEY, strlen(DBSIMPLE_ARRAY_KEY))) { |
$ak[] = $fieldName; |
} else if (0 == strncasecmp($fieldName, DBSIMPLE_PARENT_KEY, strlen(DBSIMPLE_PARENT_KEY))) { |
$pk = $fieldName; |
} |
} |
natsort($ak); // sort ARRAY_KEY* using natural comparision |
|
if ($ak) { |
// 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); |
} |
} |
return $rows; |
} |
|
|
/** |
* 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. |
*/ |
function _transformResultToHash($rows, $arrayKeys) |
{ |
$arrayKeys = (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). |
*/ |
function _transformResultToForest($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). |
*/ |
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). |
*/ |
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. |
*/ |
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); |
} |
|
/** |
* mixed _cache($hash, $result=null) |
* Calls cache mechanism if possible. |
*/ |
function _cache($hash, $result=null) |
{ |
if (is_callable($this->_cacher)) { |
return call_user_func($this->_cacher, $hash, $result); |
} else if (is_object($this->_cacher) && method_exists($this->_cacher, 'get') && method_exists($this->_cacher, 'save')) { |
if (null === $result) |
return $this->_cacher->get($hash); |
else |
$this->_cacher->save($result, $hash); |
} |
else return false; |
} |
|
|
/** |
* protected constructor(string $dsn) |
* |
* Prevent from direct creation of this object. |
*/ |
function DbSimple_Generic_Database() |
{ |
die("This is protected constructor! Do not instantiate directly at ".__FILE__." line ".__LINE__); |
} |
|
// Identifiers prefix (used for ?_ placeholder). |
var $_identPrefix = ''; |
|
// Queries statistics. |
var $_statistics = array( |
'time' => 0, |
'count' => 0, |
); |
|
var $_cachePrefix = ''; |
|
var $_logger = null; |
var $_cacher = null; |
var $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array(); |
var $_placeholderNoValueFound; |
|
/** |
* When string representation of row (in characters) is greater than this, |
* row data will not be logged. |
*/ |
var $MAX_LOG_ROW_LEN = 128; |
} |
|
|
/** |
* Database BLOB. |
* Can read blob chunk by chunk, write data to BLOB. |
*/ |
class DbSimple_Generic_Blob extends DbSimple_Generic_LastError |
{ |
/** |
* string read(int $length) |
* Returns following $length bytes from the blob. |
*/ |
function read($len) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* string write($data) |
* Appends data to blob. |
*/ |
function write($data) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* int length() |
* Returns length of the blob. |
*/ |
function length() |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
|
/** |
* blobid close() |
* Closes the blob. Return its ID. No other way to obtain this ID! |
*/ |
function close() |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); |
} |
} |
|
|
/** |
* Support for error tracking. |
* Can hold error messages, error queries and build proper stacktraces. |
*/ |
class DbSimple_Generic_LastError |
{ |
var $error = null; |
var $errmsg = null; |
var $errorHandler = null; |
var $ignoresInTraceRe = 'DbSimple_.*::.* | call_user_func.*'; |
|
/** |
* abstract void _logQuery($query) |
* Must be overriden in derived class. |
*/ |
function _logQuery($query) |
{ |
die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__);; |
} |
|
/** |
* void _resetLastError() |
* Reset the last error. Must be called on correct queries. |
*/ |
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. |
*/ |
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, |
); |
$this->errmsg = rtrim($msg) . ($context? " at $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); |
} |
|
return null; |
} |
|
|
/** |
* 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.) |
*/ |
function setErrorHandler($handler) |
{ |
$prev = $this->errorHandler; |
$this->errorHandler = $handler; |
// 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. |
*/ |
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.). |
*/ |
function findLibraryCaller() |
{ |
$caller = call_user_func( |
array(&$this, 'debug_backtrace_smart'), |
$this->ignoresInTraceRe, |
true |
); |
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 |
*/ |
function debug_backtrace_smart($ignoresRe=null, $returnCaller=false) |
{ |
if (!is_callable($tracer='debug_backtrace')) return array(); |
$trace = $tracer(); |
|
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 |
} |
|
// 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; |
} |
return $smart; |
} |
|
} |
?> |
Property changes :
Name: svn:keywords
+ Id
@@ -0,0 +1,290 @@
<?php |
/** |
* DbSimple_Ibase: Interbase/Firebird database. |
* (C) Dk Lab, http://en.dklab.ru |
* |
* This library is free software; you can redistribute it and/or |
* modify it under the terms of the GNU Lesser General Public |
* License as published by the Free Software Foundation; either |
* version 2.1 of the License, or (at your option) any later version. |
* See http://www.gnu.org/copyleft/lesser.html |
* |
* Placeholders are emulated because of logging purposes. |
* |
* @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/ |
* @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/ |
* |
* @version 2.x $Id$ |
*/ |
require_once dirname(__FILE__) . '/Generic.php'; |
|
/** |
* Best transaction parameters for script queries. |
* They never give us update conflicts (unlike others)! |
* Used by default. |
*/ |
define('IBASE_BEST_TRANSACTION', IBASE_COMMITTED + IBASE_WAIT + IBASE_REC_VERSION); |
define('IBASE_BEST_FETCH', IBASE_UNIXTIME); |
|
/** |
* Database class for Interbase/Firebird. |
*/ |
|
class DbSimple_Ibase extends DbSimple_Generic_Database |
{ |
var $DbSimple_Ibase_BEST_TRANSACTION = IBASE_BEST_TRANSACTION; |
var $DbSimple_Ibase_USE_NATIVE_PHOLDERS = true; |
var $fetchFlags = IBASE_BEST_FETCH; |
var $link; |
var $trans; |
var $prepareCache = array(); |
|
/** |
* constructor(string $dsn) |
* Connect to Interbase/Firebird. |
*/ |
function DbSimple_Ibase($dsn) |
{ |
$p = DbSimple_Generic::parseDSN($dsn); |
if (!is_callable('ibase_connect')) { |
return $this->_setLastError("-1", "Interbase/Firebird extension is not loaded", "ibase_connect"); |
} |
$ok = $this->link = ibase_connect( |
$p['host'] . (empty($p['port'])? "" : ":".$p['port']) .':'.preg_replace('{^/}s', '', $p['path']), |
$p['user'], |
$p['pass'], |
isset($p['CHARSET']) ? $p['CHARSET'] : 'win1251', |
isset($p['BUFFERS']) ? $p['BUFFERS'] : 0, |
isset($p['DIALECT']) ? $p['DIALECT'] : 3, |
isset($p['ROLE']) ? $p['ROLE'] : '' |
); |
if (isset($p['TRANSACTION'])) $this->DbSimple_Ibase_BEST_TRANSACTION = eval($p['TRANSACTION'].";"); |
$this->_resetLastError(); |
if (!$ok) return $this->_setDbError('ibase_connect()'); |
} |
|
function _performEscape($s, $isIdent=false) |
{ |
if (!$isIdent) |
return "'" . str_replace("'", "''", $s) . "'"; |
else |
return '"' . str_replace('"', '_', $s) . '"'; |
} |
|
function _performTransaction($parameters=null) |
{ |
if ($parameters === null) $parameters = $this->DbSimple_Ibase_BEST_TRANSACTION; |
$this->trans = @ibase_trans($parameters, $this->link); |
} |
|
function& _performNewBlob($blobid=null) |
{ |
$obj =& new DbSimple_Ibase_Blob($this, $blobid); |
return $obj; |
} |
|
function _performGetBlobFieldNames($result) |
{ |
$blobFields = array(); |
for ($i=ibase_num_fields($result)-1; $i>=0; $i--) { |
$info = ibase_field_info($result, $i); |
if ($info['type'] === "BLOB") $blobFields[] = $info['name']; |
} |
return $blobFields; |
} |
|
function _performGetPlaceholderIgnoreRe() |
{ |
return ' |
" (?> [^"\\\\]+|\\\\"|\\\\)* " | |
\' (?> [^\'\\\\]+|\\\\\'|\\\\)* \' | |
` (?> [^`]+ | ``)* ` | # backticks |
/\* .*? \*/ # comments |
'; |
} |
|
function _performCommit() |
{ |
if (!is_resource($this->trans)) return false; |
$result = @ibase_commit($this->trans); |
if (true === $result) { |
$this->trans = null; |
} |
return $result; |
} |
|
|
function _performRollback() |
{ |
if (!is_resource($this->trans)) return false; |
$result = @ibase_rollback($this->trans); |
if (true === $result) { |
$this->trans = null; |
} |
return $result; |
} |
|
function _performTransformQuery(&$queryMain, $how) |
{ |
// If we also need to calculate total number of found rows... |
switch ($how) { |
// Prepare total calculation (if possible) |
case 'CALC_TOTAL': |
// Not possible |
return true; |
|
// Perform total calculation. |
case 'GET_TOTAL': |
// TODO: GROUP BY ... -> COUNT(DISTINCT ...) |
$re = '/^ |
(?> -- [^\r\n]* | \s+)* |
(\s* SELECT \s+) #1 |
((?:FIRST \s+ \S+ \s+ (?:SKIP \s+ \S+ \s+)? )?) #2 |
(.*?) #3 |
(\s+ FROM \s+ .*?) #4 |
((?:\s+ ORDER \s+ BY \s+ .*)?) #5 |
$/six'; |
$m = null; |
if (preg_match($re, $queryMain[0], $m)) { |
$queryMain[0] = $m[1] . $this->_fieldList2Count($m[3]) . " AS C" . $m[4]; |
$skipHead = substr_count($m[2], '?'); |
if ($skipHead) array_splice($queryMain, 1, $skipHead); |
$skipTail = substr_count($m[5], '?'); |
if ($skipTail) array_splice($queryMain, -$skipTail); |
} |
return true; |
} |
|
return false; |
} |
|
function _performQuery($queryMain) |
{ |
$this->_lastQuery = $queryMain; |
$this->_expandPlaceholders($queryMain, $this->DbSimple_Ibase_USE_NATIVE_PHOLDERS); |
|
$hash = $queryMain[0]; |
|
if (!isset($this->prepareCache[$hash])) { |
$this->prepareCache[$hash] = @ibase_prepare((is_resource($this->trans) ? $this->trans : $this->link), $queryMain[0]); |
} else { |
// Prepare cache hit! |
} |
|
$prepared = $this->prepareCache[$hash]; |
if (!$prepared) return $this->_setDbError($queryMain[0]); |
$queryMain[0] = $prepared; |
$result = @call_user_func_array('ibase_execute', $queryMain); |
// ATTENTION!!! |
// WE MUST save prepared ID (stored in $prepared variable) somewhere |
// before returning $result because of ibase destructor. Now it is done |
// by $this->prepareCache. When variable $prepared goes out of scope, it |
// is destroyed, and memory for result also freed by PHP. Totally we |
// got "Invalud statement handle" error message. |
|
if ($result === false) return $this->_setDbError($queryMain[0]); |
if (!is_resource($result)) { |
// Non-SELECT queries return number of affected rows, SELECT - resource. |
return @ibase_affected_rows((is_resource($this->trans) ? $this->trans : $this->link)); |
} |
return $result; |
} |
|
function _performFetch($result) |
{ |
// Select fetch mode. |
$flags = $this->fetchFlags; |
if (empty($this->attributes['BLOB_OBJ'])) $flags = $flags | IBASE_TEXT; |
else $flags = $flags & ~IBASE_TEXT; |
|
$row = @ibase_fetch_assoc($result, $flags); |
if (ibase_errmsg()) return $this->_setDbError($this->_lastQuery); |
if ($row === false) return null; |
return $row; |
} |
|
|
function _setDbError($query) |
{ |
return $this->_setLastError(ibase_errcode(), ibase_errmsg(), $query); |
} |
|
} |
|
class DbSimple_Ibase_Blob extends DbSimple_Generic_Blob |
{ |
var $blob; // resourse link |
var $id; |
var $database; |
|
function DbSimple_Ibase_Blob(&$database, $id=null) |
{ |
$this->database =& $database; |
$this->id = $id; |
$this->blob = null; |
} |
|
function read($len) |
{ |
if ($this->id === false) return ''; // wr-only blob |
if (!($e=$this->_firstUse())) return $e; |
$data = @ibase_blob_get($this->blob, $len); |
if ($data === false) return $this->_setDbError('read'); |
return $data; |
} |
|
function write($data) |
{ |
if (!($e=$this->_firstUse())) return $e; |
$ok = @ibase_blob_add($this->blob, $data); |
if ($ok === false) return $this->_setDbError('add data to'); |
return true; |
} |
|
function close() |
{ |
if (!($e=$this->_firstUse())) return $e; |
if ($this->blob) { |
$id = @ibase_blob_close($this->blob); |
if ($id === false) return $this->_setDbError('close'); |
$this->blob = null; |
} else { |
$id = null; |
} |
return $this->id ? $this->id : $id; |
} |
|
function length() |