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

@@ -38,9 +38,10 @@ 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><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.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 "LOGFILE NAME: " . $db->log->getLogFile() . "<br>";
@@ -549,11 +550,13 @@ print "</pre>";
print "<b>PREPARE QUERIES</b><br>";
// READ PREPARE
$q_prep = "SELECT test_foo_id, test, some_bool, string_a, number_a, "
. "number_a_numeric, some_time "
. "FROM test_foo "
. "WHERE test = $1 "
. "ORDER BY test_foo_id DESC LIMIT 5";
$q_prep = <<<SQL
SELECT test_foo_id, test, some_bool, string_a, number_a,
number_a_numeric, some_time
FROM test_foo
WHERE test = $1
ORDER BY test_foo_id DESC LIMIT 5
SQL;
if ($db->dbPrepare('sel_test_foo', $q_prep) === false) {
print "Error in sel_test_foo prepare<br>";
} else {
@@ -681,16 +684,27 @@ echo "<hr>";
$db_pgb = new CoreLibs\DB\IO($DB_CONFIG['test_pgbouncer'] ?? [], $log);
print "[PGB] DBINFO: " . $db_pgb->dbInfo() . "<br>";
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 {
print "[PGB] [1] pgb_sel_test_foo prepare OK<br>";
}
// second prepare
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 {
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 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
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-db-single';
$LOG_FILE_ID = 'classTest-db-query-placeholders';
ob_end_flush();
use CoreLibs\Debug\Support;
@@ -30,7 +30,7 @@ $log = new CoreLibs\Logging\Logging([
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB SINGLE';
$PAGE_NAME = 'TEST CLASS: DB QUERY PLACEHOLDERS';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
@@ -65,63 +65,6 @@ function testDBS(\CoreLibs\DB\IO $dbc): void
$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);
print "</body></html>";

View File

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