Update DB\IO with auto query placeholder rewrite and better error logging

All errors have context that is used to add query, params, etc info
for logging into the DB.
Avoid double logging for PostGreSQL direct errors as those will be
logged now in context to the actual error log
Remove error: 16 missing/empty dbh has this is handled with error 14
in the connect method.

Auto convert ?, :named to $numbered, default off. Activate with
'db_convert_placeholder' flag or method dbSetConvertPlaceholder.
Converted result data for single queries in dbGetPlaceholderConverted
or in the cursor_ext array in placeholer_converted key

Do not auto translate debug queries with placeholder values in query
but keep them in the array in the context array. If needed
'db_debug_replace_placeholder' can be set to show prepared query
with placeholder replaced in the context

New methods:
public function dbSetConvertPlaceholder(bool $flag): void
public function dbGetConvertPlaceholder(): bool
public function dbSetConvertPlaceholderTarget(string $target): bool
public function dbGetConvertPlaceholderTarget(): string
public function dbSetDebugReplacePlaceholder(bool $flag): void
public function dbGetDebugReplacePlaceholder(): bool
public function dbGetPlaceholderConverted(): array

Chagned to public:
public function dbCheckQueryForSelect(string $query): bool
public function dbCheckQueryForInsert(string $query, bool $pure = false): bool
public function dbCheckQueryForUpdate(string $query): bool
This commit is contained in:
Clemens Schwaighofer
2023-10-16 14:43:55 +09:00
parent c46125aef1
commit 89e3888bf8
7 changed files with 947 additions and 398 deletions

View File

@@ -232,7 +232,7 @@ final class CoreLibsDBIOTest extends TestCase
$this->assertEquals( $this->assertEquals(
$error, $error,
$last_error, $last_error,
'Assert query warning' 'Assert query error'
); );
return [$last_warning, $last_error]; return [$last_warning, $last_error];
} }
@@ -251,8 +251,6 @@ final class CoreLibsDBIOTest extends TestCase
*/ */
public function testDbVersion(): void public function testDbVersion(): void
{ {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -276,8 +274,6 @@ final class CoreLibsDBIOTest extends TestCase
*/ */
public function testDbVersionNumeric(): void public function testDbVersionNumeric(): void
{ {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -306,8 +302,6 @@ final class CoreLibsDBIOTest extends TestCase
*/ */
public function testDbVersionInfoParameters(): void public function testDbVersionInfoParameters(): void
{ {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -365,8 +359,6 @@ final class CoreLibsDBIOTest extends TestCase
*/ */
public function testDbVersionInfo(string $parameter, string $expected): void public function testDbVersionInfo(string $parameter, string $expected): void
{ {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -1592,8 +1584,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error, string $error,
bool $run_many_times = false bool $run_many_times = false
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -1832,8 +1822,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error, string $error,
string $insert_data string $insert_data
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -2002,8 +1990,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error, string $error,
string $insert_data string $insert_data
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -3069,8 +3055,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error, string $error,
string $insert_data string $insert_data
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -3465,7 +3449,7 @@ final class CoreLibsDBIOTest extends TestCase
$read_query, $read_query,
null, null,
null, null,
// // warning: 20
true, '20', '', true, '20', '',
// //
'result', '', '', 'result', '', '',
@@ -3482,6 +3466,31 @@ final class CoreLibsDBIOTest extends TestCase
'returning_id' => false, 'returning_id' => false,
], ],
], ],
// prepare with different statement name
'prepare query with same statement name, different query' => [
'double_error',
$read_query,
// primary key
null,
// arguments (none)
null,
// expected return false, warning: no, error: 26
false, '', '26',
// return expected, warning, error
'', '', '',
// dummy query for second prepare with wrong query
$read_query . ' WHERE uid = $3',
[],
//
$insert_query,
//
[
'pk_name' => '',
'count' => 0,
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'returning_id' => false,
],
],
// insert wrong data count compared to needed (execute 23) // insert wrong data count compared to needed (execute 23)
'wrong parmeter count' => [ 'wrong parmeter count' => [
'wrong_param_count', 'wrong_param_count',
@@ -3554,8 +3563,6 @@ final class CoreLibsDBIOTest extends TestCase
string $insert_data, string $insert_data,
array $prepare_cursor, array $prepare_cursor,
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -3575,6 +3582,9 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbPrepare($stm_name, $query) : $db->dbPrepare($stm_name, $query) :
$db->dbPrepare($stm_name, $query, $pk_name); $db->dbPrepare($stm_name, $query, $pk_name);
} }
if ($error_prepare == '26') {
$prepare_result = $db->dbPrepare($stm_name, $expected_data_query);
}
// if result type, or if forced bool // if result type, or if forced bool
if (is_string($expected_prepare) && $expected_prepare == 'result') { if (is_string($expected_prepare) && $expected_prepare == 'result') {
// if PHP or newer, must be Object PgSql\Result // if PHP or newer, must be Object PgSql\Result
@@ -3597,66 +3607,68 @@ final class CoreLibsDBIOTest extends TestCase
// for non fail prepare test exec // for non fail prepare test exec
// check test result // check test result
$execute_result = $query_data === null ? if (!$error_prepare) {
$db->dbExecute($stm_name) : $execute_result = $query_data === null ?
$db->dbExecute($stm_name, $query_data); $db->dbExecute($stm_name) :
if ($expected_execute == 'result') { $db->dbExecute($stm_name, $query_data);
// if PHP or newer, must be Object PgSql\Result if ($expected_execute == 'result') {
$this->assertIsObject( // if PHP or newer, must be Object PgSql\Result
$execute_result $this->assertIsObject(
); $execute_result
// also check that this is correct instance type );
$this->assertInstanceOf( // also check that this is correct instance type
'PgSql\Result', $this->assertInstanceOf(
$execute_result 'PgSql\Result',
); $execute_result
// if this is an select use dbFetchArray to get data and test );
} else { // if this is an select use dbFetchArray to get data and test
$this->assertEquals( } else {
$expected_execute, $this->assertEquals(
$execute_result $expected_execute,
); $execute_result
} );
// error/warning check }
$this->subAssertErrorTest($db, $warning_execute, $error_execute); // error/warning check
// now check test result if expected return is result $this->subAssertErrorTest($db, $warning_execute, $error_execute);
if ( // now check test result if expected return is result
$expected_execute == 'result' && if (
!empty($expected_data_query) $expected_execute == 'result' &&
) { !empty($expected_data_query)
// $expected_data_query ) {
// $expected_data // $expected_data_query
$rows = $db->dbReturnArray($expected_data_query); // $expected_data
$this->assertEquals( $rows = $db->dbReturnArray($expected_data_query);
$expected_data, $this->assertEquals(
$rows $expected_data,
); $rows
} );
if ( }
$expected_execute == 'result' && if (
$execute_result !== false && $expected_execute == 'result' &&
empty($expected_data_query) && $execute_result !== false &&
count($expected_data) empty($expected_data_query) &&
) { count($expected_data)
// compare previously read data to compare data ) {
$compare_data = []; // compare previously read data to compare data
// read in the query data $compare_data = [];
while (is_array($row = $db->dbFetchArray($execute_result, true))) { // read in the query data
$compare_data[] = $row; while (is_array($row = $db->dbFetchArray($execute_result, true))) {
$compare_data[] = $row;
}
$this->assertEquals(
$expected_data,
$compare_data
);
} }
$this->assertEquals(
$expected_data,
$compare_data
);
}
// check dbGetPrepareCursorValue // check dbGetPrepareCursorValue
foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) {
$this->assertEquals( $this->assertEquals(
$prepare_cursor[$key], $prepare_cursor[$key],
$db->dbGetPrepareCursorValue($stm_name, $key), $db->dbGetPrepareCursorValue($stm_name, $key),
'Prepared cursor: ' . $key . ': failed assertion' 'Prepared cursor: ' . $key . ': failed assertion'
); );
}
} }
// reset all data // reset all data
@@ -3844,8 +3856,6 @@ final class CoreLibsDBIOTest extends TestCase
string $expected_get_var, string $expected_get_var,
string $expected_get_db string $expected_get_db
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config[$connection], self::$db_config[$connection],
self::$log self::$log
@@ -3910,7 +3920,10 @@ final class CoreLibsDBIOTest extends TestCase
// 'main::run::run::run::run::run::run::run::runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall // 'main::run::run::run::run::run::run::run::runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall
'source' => "/^(include::)?main::(run::)+runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall$/", 'source' => "/^(include::)?main::(run::)+runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall$/",
'pg_error' => '', 'pg_error' => '',
'msg' => '', 'message' => '',
'context' => [
'max_calls' => 0
]
] ]
], ],
'trigger warning' => [ 'trigger warning' => [
@@ -3943,8 +3956,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error_id, string $error_id,
array $expected_history array $expected_history
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -3970,7 +3981,7 @@ final class CoreLibsDBIOTest extends TestCase
foreach ($expected_history as $key => $value) { foreach ($expected_history as $key => $value) {
// check if starts with / because this is regex (timestamp) // check if starts with / because this is regex (timestamp)
// if (substr($expected_2, 0, 1) == '/) { // if (substr($expected_2, 0, 1) == '/) {
if (strpos($value, '/') === 0) { if (!is_array($value) && strpos($value, '/') === 0) {
// this is regex // this is regex
$this->assertMatchesRegularExpression( $this->assertMatchesRegularExpression(
$value, $value,
@@ -4058,8 +4069,6 @@ final class CoreLibsDBIOTest extends TestCase
bool $expected_set_flag, bool $expected_set_flag,
string $expected_get_encoding string $expected_get_encoding
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config[$connection], self::$db_config[$connection],
self::$log self::$log
@@ -4141,8 +4150,6 @@ final class CoreLibsDBIOTest extends TestCase
?string $encoding_php, ?string $encoding_php,
string $text string $text
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config[$connection], self::$db_config[$connection],
self::$log self::$log
@@ -4272,8 +4279,6 @@ final class CoreLibsDBIOTest extends TestCase
string $table, string $table,
string $primary_key string $primary_key
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -4330,7 +4335,7 @@ final class CoreLibsDBIOTest extends TestCase
// NOTE if there are different INSERTS before the primary keys // NOTE if there are different INSERTS before the primary keys
// will not match anymore. Must be updated by hand // will not match anymore. Must be updated by hand
// IMPORTANT: if this is stand alone the primary key will not match and fail // IMPORTANT: if this is stand alone the primary key will not match and fail
$table_with_primary_key_id = 68; $table_with_primary_key_id = 70;
// 0: query + returning // 0: query + returning
// 1: params // 1: params
// 1: pk name for db exec // 1: pk name for db exec
@@ -4530,8 +4535,6 @@ final class CoreLibsDBIOTest extends TestCase
array|string|int|null $expected_ret_ext, array|string|int|null $expected_ret_ext,
array $expected_ret_arr array $expected_ret_arr
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -4875,8 +4878,6 @@ final class CoreLibsDBIOTest extends TestCase
array $expected_col_names, array $expected_col_names,
array $expected_col_types array $expected_col_types
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log
@@ -5030,6 +5031,147 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbClose(); $db->dbClose();
} }
// query placeholder convert
public function queryPlaceholderReplaceProvider(): array
{
// WHERE row_varchar = $1
return [
'select, no change' => [
'query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
SQL,
'params' => [],
'found' => 0,
'expected_query' => '',
'expected_params' => [],
],
'select, params ?' => [
'query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = ?
SQL,
'params' => ['string a'],
'found' => 1,
'expected_query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = $1
SQL,
'expected_params' => ['string a'],
],
'select, params :' => [
'query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = :row_varchar
SQL,
'params' => [':row_varchar' => 'string a'],
'found' => 1,
'expected_query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = $1
SQL,
'expected_params' => ['string a'],
]
];
}
/**
* test query string with placeholders convert
*
* @dataProvider queryPlaceholderReplaceProvider
* @testdox Query replacement test [$_dataName]
*
* @param string $query
* @param array $params
* @param string $expected_query
* @param array $expected_params
* @return void
*/
public function testQueryPlaceholderReplace(
string $query,
array $params,
int $expected_found,
string $expected_query,
array $expected_params
): void {
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$db->dbSetConvertPlaceholder(true);
//
if ($db->dbCheckQueryForSelect($query)) {
$res = $db->dbReturnRowParams($query, $params);
$converted = $db->dbGetPlaceholderConverted();
} else {
$db->dbExecParams($query, $params);
$converted = $db->dbGetPlaceholderConverted();
}
$this->assertEquals(
$expected_found,
$converted['found'],
'Found not equal'
);
$this->assertEquals(
$expected_query,
$converted['query'],
'Query not equal'
);
$this->assertEquals(
$expected_params,
$converted['params'],
'Params not equal'
);
}
/**
* test exception for placeholder convert
* -> internally converted to error
*
* @testdox Query Replace error tests
*
* @return void
*/
public function testQueryPlaceholderReplaceException(): void
{
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$db->dbSetConvertPlaceholder(true);
$db->dbExecParams(
<<<SQL
SELECT foo FROM bar
WHERE a = ? and b = :bname
SQL,
['a', 'b']
);
$this->assertEquals(
200,
$db->dbGetLastError()
);
// catch unset, for :names
$db->dbExecParams(
<<<SQL
SELECT foo FROM bar
WHERE a = :aname and b = :bname
SQL,
[':foo' => 'a', ':bname' => 'b']
);
$this->assertEquals(
210,
$db->dbGetLastError()
);
// TODO: other way around for to pdo
}
// TODO implement below checks // TODO implement below checks
// - complex write sets // - complex write sets
// dbWriteData, dbWriteDataExt // dbWriteData, dbWriteDataExt
@@ -5158,8 +5300,6 @@ final class CoreLibsDBIOTest extends TestCase
string $warning_final, string $warning_final,
string $error_final string $error_final
): void { ): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config['valid'], self::$db_config['valid'],
self::$log self::$log

View File

@@ -38,9 +38,10 @@ print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>"; print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>"; print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>'; print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><a href="class_test.db.type.php">Class Test DB Types</a></div>'; print '<div><a href="class_test.db.type.php">Class Test DB row type convert to PHP type</a></div>';
print '<div><a href="class_test.db.query-placeholder.php">Class Test DB Query Placeholder convert</a></div>';
print '<div><a href="class_test.db.dbReturn.php">Class Test DB dbReturn</a></div>'; print '<div><a href="class_test.db.dbReturn.php">Class Test DB dbReturn</a></div>';
print '<div><a href="class_test.db.single.php">Class Test DB Single Aciont</a></div>'; print '<div><a href="class_test.db.single.php">Class Test DB Single Query tests</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>'; print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $db->log->getLogFile() . "<br>"; print "LOGFILE NAME: " . $db->log->getLogFile() . "<br>";
@@ -549,11 +550,13 @@ print "</pre>";
print "<b>PREPARE QUERIES</b><br>"; print "<b>PREPARE QUERIES</b><br>";
// READ PREPARE // READ PREPARE
$q_prep = "SELECT test_foo_id, test, some_bool, string_a, number_a, " $q_prep = <<<SQL
. "number_a_numeric, some_time " SELECT test_foo_id, test, some_bool, string_a, number_a,
. "FROM test_foo " number_a_numeric, some_time
. "WHERE test = $1 " FROM test_foo
. "ORDER BY test_foo_id DESC LIMIT 5"; WHERE test = $1
ORDER BY test_foo_id DESC LIMIT 5
SQL;
if ($db->dbPrepare('sel_test_foo', $q_prep) === false) { if ($db->dbPrepare('sel_test_foo', $q_prep) === false) {
print "Error in sel_test_foo prepare<br>"; print "Error in sel_test_foo prepare<br>";
} else { } else {
@@ -681,16 +684,27 @@ echo "<hr>";
$db_pgb = new CoreLibs\DB\IO($DB_CONFIG['test_pgbouncer'] ?? [], $log); $db_pgb = new CoreLibs\DB\IO($DB_CONFIG['test_pgbouncer'] ?? [], $log);
print "[PGB] DBINFO: " . $db_pgb->dbInfo() . "<br>"; print "[PGB] DBINFO: " . $db_pgb->dbInfo() . "<br>";
if ($db->dbPrepare('pgb_sel_test_foo', $q_prep) === false) { if ($db->dbPrepare('pgb_sel_test_foo', $q_prep) === false) {
print "[PGB] [1] Error in pgb_sel_test_foo prepare<br>"; print "[PGB] [1] Warning in pgb_sel_test_foo prepare<br>";
} else { } else {
print "[PGB] [1] pgb_sel_test_foo prepare OK<br>"; print "[PGB] [1] pgb_sel_test_foo prepare OK<br>";
} }
// second prepare // second prepare
if ($db->dbPrepare('pgb_sel_test_foo', $q_prep) === false) { if ($db->dbPrepare('pgb_sel_test_foo', $q_prep) === false) {
print "[PGB] [2] Error in pgb_sel_test_foo prepare<br>"; print "[PGB] [2] Warning in pgb_sel_test_foo prepare<br>";
} else { } else {
print "[PGB] [2] pgb_sel_test_foo prepare OK<br>"; print "[PGB] [2] pgb_sel_test_foo prepare OK<br>";
} }
// same statment name, different query
if (
$db->dbPrepare('pgb_sel_test_foo', <<<SQL
SELECT * FROM test_foo WHERE test = $1
ORDER BY test_foo_id DESC LIMIT 5
SQL) === false
) {
print "[PGB] [3] Error in pgb_sel_test_foo prepare<br>";
} else {
print "[PGB] [3] pgb_sel_test_foo prepare OK<br>";
}
$db_pgb->dbClose(); $db_pgb->dbClose();
# db write class test # db write class test

View File

@@ -0,0 +1,208 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
// turn on all error reporting
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', true);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-db-query-placeholder';
ob_end_flush();
use CoreLibs\Debug\Support;
use CoreLibs\DB\Support\ConvertPlaceholder;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// db connection and attach logger
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB QUERY PLACEHOLDER';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $db->log->getLogFile() . "<br>";
print "LOGFILE ID: " . $db->log->getLogFileId() . "<br>";
print "DBINFO: " . $db->dbInfo() . "<br>";
// DB client encoding
print "DB client encoding: " . $db->dbGetEncoding() . "<br>";
print "DB search path: " . $db->dbGetSchema() . "<br>";
$to_db_version = '15.2';
print "VERSION DB: " . $db->dbVersion() . "<br>";
print "SERVER ENCODING: " . $db->dbVersionInfo('server_encoding') . "<br>";
if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
print "ALL OUTPUT [TEST]: <pre>" . print_r(pg_version($dbh), true) . "</pre><br>";
} else {
print "NO DB HANDLER<br>";
}
// turn on debug replace for placeholders
$db->dbSetDebugReplacePlaceholder(true);
print "<b>TRUNCATE test_foo</b><br>";
$db->dbExec("TRUNCATE test_foo");
$uniqid = \CoreLibs\Create\Uids::uniqIdShort();
$binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
$query_params = [
$uniqid,
true,
'STRING A',
2,
2.5,
1,
date('H:m:s'),
date('Y-m-d H:i:s'),
json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]),
null,
'{"a", "b"}',
'{1,2}',
'{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}',
'("Text", 4, 6.3)',
$binary_data
];
$query_insert = <<<SQL
INSERT INTO test_foo (
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_int_1,
array_composite,
composite_item,
some_binary
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12,
$13,
$14,
$15
)
RETURNING
test_foo_id,
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_int_1,
array_composite,
composite_item,
some_binary
SQL;
$status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>";
echo "INSERT ALL COLUMN TYPES: "
. Support::printToString($query_params) . " |<br>"
. "QUERY: " . $db->dbGetQuery() . " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |<br>"
. "RETURNING EXT: <pre>" . print_r($db->dbGetReturningExt(), true) . "</pre> |<br>"
. "RETURNING RETURN: <pre>" . print_r($db->dbGetReturningArray(), true) . "<pre> |<br>"
. "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<hr>";
// convert placeholder tests
// ? -> $n
// :name -> $n
// other way around (just visual)
$test_queries = [
'skip' => [
'query' => <<<SQL
SELECT test, string_a, number_a
FROM test_foo
SQL,
'params' => [],
'direction' => 'pg',
],
'a?' => [
'query' => <<<SQL
INSERT INTO test_foo (
test, string_a, number_a
) VALUES (
?, ?, ?
)
SQL,
'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234],
'direction' => 'pg',
],
'b:' => [
'query' => <<<SQL
INSERT INTO test_foo (
test, string_a, number_a
) VALUES (
:test, :string_a, :number_a
)
SQL,
'params' => [
':test' => \CoreLibs\Create\Uids::uniqIdShort(),
':string_a' => 'string B-1',
':number_a' => 5678
],
'direction' => 'pg',
],
];
$db->dbSetConvertPlaceholder(true);
foreach ($test_queries as $info => $data) {
$query = $data['query'];
$params = $data['params'];
$direction = $data['direction'];
// print "[$info] Convert: "
// . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction))
// . "<br>";
if ($db->dbCheckQueryForSelect($query)) {
$row = $db->dbReturnRowParams($query, $params);
print "[$info] SELECT: " . Support::prAr($row) . "<br>";
} else {
$db->dbExecParams($query, $params);
}
print "[$info] " . Support::printAr($db->dbGetPlaceholderConverted()) . "<br>";
echo "<hr>";
}
echo "dbReturn read: <br>";
while (
is_array($res = $db->dbReturnParams(
<<<SQL
SELECT test, string_a, number_a
FROM test_foo
WHERE string_a = ?
SQL,
['string A-1']
))
) {
print "RES: " . Support::prAr($res) . "<br>";
}
print "CursorExt: " . Support::prAr($db->dbGetCursorExt(<<<SQL
SELECT test, string_a, number_a
FROM test_foo
WHERE string_a = ?
SQL, ['string A-1']));
$res = $db->dbReturnRowParams(<<<SQL
SELECT test, string_a, number_a
FROM test_foo
WHERE string_a = $1
SQL, []);
print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "<br>";
print "</body></html>";
$db->log->debug('DEBUGEND', '==================================== [END]');
// __END__

View File

@@ -16,7 +16,7 @@ define('USE_DATABASE', true);
// sample config // sample config
require 'config.php'; require 'config.php';
// define log file id // define log file id
$LOG_FILE_ID = 'classTest-db-single'; $LOG_FILE_ID = 'classTest-db-query-placeholders';
ob_end_flush(); ob_end_flush();
use CoreLibs\Debug\Support; use CoreLibs\Debug\Support;
@@ -30,7 +30,7 @@ $log = new CoreLibs\Logging\Logging([
$db = new CoreLibs\DB\IO(DB_CONFIG, $log); $db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>'); $db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB SINGLE'; $PAGE_NAME = 'TEST CLASS: DB QUERY PLACEHOLDERS';
print "<!DOCTYPE html>"; print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>"; print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>"; print "<body>";
@@ -65,63 +65,6 @@ function testDBS(\CoreLibs\DB\IO $dbc): void
$dbc->dbReturnRow("SELECT test FROM test_foo LIMIT 1"); $dbc->dbReturnRow("SELECT test FROM test_foo LIMIT 1");
} }
$uniqid = \CoreLibs\Create\Uids::uniqIdShort();
$binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
$query_params = [
$uniqid,
true,
'STRING A',
2,
2.5,
1,
date('H:m:s'),
date('Y-m-d H:m:s'),
json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]),
null,
'{"a", "b"}',
'{1,2}',
'{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}',
'("Text", 4, 6.3)',
$binary_data
];
$query_insert = <<<SQL
INSERT INTO test_foo (
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_int_1,
array_composite,
composite_item,
some_binary
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12,
$13,
$14,
$15
)
SQL;
$status = $db->dbExecParams($query_insert, $query_params);
$query_select = <<<SQL
SELECT
test_foo_id,
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
number_real, number_double, number_serial,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_char_2, array_int_1, array_int_2, array_composite,
composite_item, (composite_item).*
some_binary
FROM
test_foo
WHERE
test = $1;
SQL;
$res = $db->dbReturnRowParams($query_select, [$uniqid]);
if (is_array($res)) {
var_dump($res);
}
testDBS($db); testDBS($db);
print "</body></html>"; print "</body></html>";

View File

@@ -69,9 +69,10 @@ print "<body>";
// key: file name, value; name // key: file name, value; name
$test_files = [ $test_files = [
'class_test.db.php' => 'Class Test: DB', 'class_test.db.php' => 'Class Test: DB',
'class_test.db.types.php' => 'Class Test: DB COLUMN TYPES', 'class_test.db.types.php' => 'Class Test: DB column type convert',
'class_test.db.single.php' => 'Class Test: DB SINGLE', 'class_test.db.query-placeholder.php' => 'Class Test: DB query placeholder convert',
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.db.single.php' => 'Class Test: DB single query tests',
'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS',
'class_test.check.colors.php' => 'Class Test: CHECK COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS',
'class_test.mime.php' => 'Class Test: MIME', 'class_test.mime.php' => 'Class Test: MIME',

File diff suppressed because it is too large Load Diff

View File

@@ -24,10 +24,12 @@ class ConvertPlaceholder
* *
* If the convert_to is either pg or pdo, nothing will be changed * If the convert_to is either pg or pdo, nothing will be changed
* *
* found has -1 if an error occoured in the preg_match_all call
*
* @param string $query Query with placeholders to convert * @param string $query Query with placeholders to convert
* @param array<mixed> $params The parameters that are used for the query, and will be updated * @param array<mixed> $params The parameters that are used for the query, and will be updated
* @param string $convert_to Either pdo or pg, will be converted to lower case for check * @param string $convert_to Either pdo or pg, will be converted to lower case for check
* @return array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int|false,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} * @return array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>}
* @throws \OutOfRangeException 200 * @throws \OutOfRangeException 200
*/ */
public static function convertPlaceholderInQuery( public static function convertPlaceholderInQuery(
@@ -61,14 +63,27 @@ class ConvertPlaceholder
// 2: ? question mark // 2: ? question mark
// 3: $n numbered // 3: $n numbered
$found = preg_match_all($pattern, $query, $matches, PREG_UNMATCHED_AS_NULL); $found = preg_match_all($pattern, $query, $matches, PREG_UNMATCHED_AS_NULL);
// if false or null set to -1
// || $found === null
if ($found === false) {
$found = -1;
}
/** @var array<string> 1: named */ /** @var array<string> 1: named */
$named_matches = array_filter($matches[1]); $named_matches = array_filter($matches[1]);
/** @var array<string> 2: open ? */ /** @var array<string> 2: open ? */
$qmark_matches = array_filter($matches[2]); $qmark_matches = array_filter($matches[2]);
/** @var array<string> 3: $n matches */ /** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]); $numbered_matches = array_filter($matches[3]);
// count matches
$count_named = count($named_matches);
$count_qmark = count($qmark_matches);
$count_numbered = count($numbered_matches);
// throw if mixed // throw if mixed
if (count($named_matches) && count($qmark_matches) && count($numbered_matches)) { if (
($count_named && $count_qmark) ||
($count_named && $count_numbered) ||
($count_qmark && $count_numbered)
) {
throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200); throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200);
} }
// rebuild // rebuild
@@ -77,7 +92,7 @@ class ConvertPlaceholder
$query_new = ''; $query_new = '';
$params_new = []; $params_new = [];
$params_lookup = []; $params_lookup = [];
if (count($named_matches) && $convert_to == 'pg') { if ($count_named && $convert_to == 'pg') {
$type = 'named'; $type = 'named';
$matches_return = $named_matches; $matches_return = $named_matches;
// only check for :named // only check for :named
@@ -113,7 +128,7 @@ class ConvertPlaceholder
}, },
$query $query
); );
} elseif (count($qmark_matches) && $convert_to == 'pg') { } elseif ($count_qmark && $convert_to == 'pg') {
$type = 'question_mark'; $type = 'question_mark';
$matches_return = $qmark_matches; $matches_return = $qmark_matches;
// order and data stays the same // order and data stays the same
@@ -143,7 +158,7 @@ class ConvertPlaceholder
$query $query
); );
// for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove // for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove
} elseif (count($numbered_matches) && $convert_to == 'pdo') { } elseif ($count_numbered && $convert_to == 'pdo') {
// convert numbered to named // convert numbered to named
$type = 'numbered'; $type = 'numbered';
$matches_return = $numbered_matches; $matches_return = $numbered_matches;
@@ -190,8 +205,8 @@ class ConvertPlaceholder
], ],
// type found, empty if nothing was done // type found, empty if nothing was done
'type' => $type, 'type' => $type,
// int|null: found, not found; false: problem // int: found, not found; -1: problem (set from false)
'found' => $found, 'found' => (int)$found,
'matches' => $matches_return, 'matches' => $matches_return,
// old to new lookup check // old to new lookup check
'params_lookup' => $params_lookup, 'params_lookup' => $params_lookup,