Subversion

lib

Compare Path:  Rev
With Path: Rev

This comparison shows the changes necessary to convert path /DbSimple/tags (Rev 227) TO /DbSimple/tags (Rev 248)

Reverse comparison

2.39/test/DbSimple/connect.php
New file

@@ -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();
}
?>


2.39/t/DbSimple/Postgresql/030_last_insert_id.phpt
New file

@@ -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',
),
)


2.39/t/DbSimple/Postgresql/dsn.txt
New file

@@ -0,0 +1 @@
postgresql://test:test@localhost/test


2.39/t/DbSimple/Postgresql/040_insert_rule.phpt
New file

@@ -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',
),
)


2.39/t/DbSimple/Postgresql/020_cell_placeholder.phpt
New file

@@ -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',
)

\ No newline at end of file


2.39/t/DbSimple/Postgresql/010_native_pholder.phpt
New file

@@ -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\')',
)


2.39/t/DbSimple/Mysql/dsn.txt
New file

@@ -0,0 +1 @@
mysql://test:test@localhost/test


2.39/t/DbSimple/Mysql/010_cell_placeholder.phpt
New file

@@ -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',
)

\ No newline at end of file


2.39/t/DbSimple/init.php
New file

@@ -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";
}
?>

\ No newline at end of file


2.39/t/DbSimple/.htaccess
New file

@@ -0,0 +1,2 @@
AddHandler application/x-httpd-php phpt
php_flag output_buffering On


2.39/t/DbSimple/Generic/060_cache_lite.phpt
New file

@@ -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',
)

\ No newline at end of file


2.39/t/DbSimple/Generic/070_nested_blocks.phpt
New file

@@ -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'


2.39/t/DbSimple/Generic/010_array_key.phpt
New file

@@ -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',
),
),
)


2.39/t/DbSimple/Generic/dsn.txt
New file

@@ -0,0 +1 @@
Mysql


2.39/t/DbSimple/Generic/040_select_col_multi.phpt
New file

@@ -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',
),
)


2.39/t/DbSimple/Generic/050_select_row.phpt
New file

@@ -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',
)

\ No newline at end of file


2.39/t/DbSimple/Generic/080_table_ident_pholder.phpt
New file

@@ -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'


2.39/t/DbSimple/Generic/040_select_col.phpt
New file

@@ -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 (
)


2.39/t/DbSimple/Generic/030_select_cell.phpt
New file

@@ -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'

\ No newline at end of file


2.39/t/DbSimple/Generic/020_parent_key.phpt
New file

@@ -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 (
),
),
),
),
),
),
)

\ No newline at end of file


2.39/t/DbSimple/Generic/021_sys_alias_ci.phpt
New file

@@ -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 (
),
),
),
),
),
),
)


2.39/t/Cache/Lite.php
New file

@@ -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);
}
}
}
?>


2.39/t/Cache/Lite/File.php
New file

@@ -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;
}
}
?>


2.39/t/Cache/Lite/Output.php
New file

@@ -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);
}
}
?>


2.39/t/Cache/Lite/Function.php
New file

@@ -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;
}
}
?>


2.39/lib/DbSimple/Generic.php
New file

@@ -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;
}
}
?>

\ No newline at end of file

Property changes :

Name: svn:keywords
+ Id


2.39/lib/DbSimple/Ibase.php
New file

@@ -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()