PHP Unit testing work for DB::IO
Also various clean ups for DB::IO - fix PGSQL array to PHP - add bool/literal escape to SQL - fix literal escape to call correct php array - move functions to correct place
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -341,13 +341,13 @@ class IO
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $db_connection_closed = false;
|
protected $db_connection_closed = false;
|
||||||
// sub include with the database functions
|
// sub include with the database functions
|
||||||
/** @var \CoreLibs\DB\SQL\PgSQL */
|
/** @var \CoreLibs\DB\SQL\PgSQL if we have other DB types we need to add them here */
|
||||||
private $db_functions;
|
private $db_functions;
|
||||||
// endless loop protection
|
// endless loop protection
|
||||||
/** @var int */
|
/** @var int */
|
||||||
private $MAX_QUERY_CALL;
|
private $MAX_QUERY_CALL;
|
||||||
/** @var int */
|
/** @var int */
|
||||||
private const DEFAULT_MAX_QUERY_CALL = 20; // default
|
public const DEFAULT_MAX_QUERY_CALL = 20; // default
|
||||||
/** @var array<mixed> */
|
/** @var array<mixed> */
|
||||||
private $query_called = [];
|
private $query_called = [];
|
||||||
// error string
|
// error string
|
||||||
@@ -449,26 +449,16 @@ class IO
|
|||||||
'50' => 'Setting max query call to -1 will disable loop protection '
|
'50' => 'Setting max query call to -1 will disable loop protection '
|
||||||
. 'for all subsequent runs',
|
. 'for all subsequent runs',
|
||||||
'51' => 'Max query call needs to be set to at least 1',
|
'51' => 'Max query call needs to be set to at least 1',
|
||||||
'60' => 'table not found for reading meta data',
|
'60' => 'table not found for reading meta data'
|
||||||
'100' => 'No database sql layer object could be loaded'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// based on $this->db_type
|
// load the core DB functions wrapper class
|
||||||
// here we need to load the db pgsql include one
|
if (($db_functions = $this->__loadDBFunctions()) === null) {
|
||||||
// How can we do this dynamic? eg for non PgSQL
|
// abort
|
||||||
// OTOH this whole class is so PgSQL specific
|
die('<!-- Cannot load db functions calss for: ' . $this->db_type . ' -->');
|
||||||
// that non PgSQL doesn't make much sense anymore
|
|
||||||
if ($this->db_type == 'pgsql') {
|
|
||||||
$this->db_functions = new \CoreLibs\DB\SQL\PgSQL();
|
|
||||||
} else {
|
|
||||||
// abort error
|
|
||||||
$this->__dbError(10);
|
|
||||||
$this->db_connection_closed = true;
|
|
||||||
}
|
|
||||||
if (!is_object($this->db_functions)) {
|
|
||||||
$this->__dbError(100);
|
|
||||||
$this->db_connection_closed = true;
|
|
||||||
}
|
}
|
||||||
|
// write to internal one, once OK
|
||||||
|
$this->db_functions = $db_functions;
|
||||||
|
|
||||||
// connect to DB
|
// connect to DB
|
||||||
if (!$this->__connectToDB()) {
|
if (!$this->__connectToDB()) {
|
||||||
@@ -489,6 +479,33 @@ class IO
|
|||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
// *************************************************************
|
// *************************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* based on $this->db_type
|
||||||
|
* here we need to load the db pgsql include one
|
||||||
|
* How can we do this dynamic? eg for non PgSQL
|
||||||
|
* OTOH this whole class is so PgSQL specific
|
||||||
|
* that non PgSQL doesn't make much sense anymore
|
||||||
|
*
|
||||||
|
* @return \CoreLibs\DB\SQL\PgSQL|null DB functions object or false on error
|
||||||
|
*/
|
||||||
|
private function __loadDBFunctions()
|
||||||
|
{
|
||||||
|
$db_functions = null;
|
||||||
|
switch ($this->db_type) {
|
||||||
|
// list of valid DB function objects
|
||||||
|
case 'pgsql':
|
||||||
|
$db_functions = new \CoreLibs\DB\SQL\PgSQL();
|
||||||
|
break;
|
||||||
|
// if non set or none matching abort
|
||||||
|
default:
|
||||||
|
// abort error
|
||||||
|
$this->__dbError(10);
|
||||||
|
$this->db_connection_closed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $db_functions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* internal connection function. Used to connect to the DB if there is no connection done yet.
|
* internal connection function. Used to connect to the DB if there is no connection done yet.
|
||||||
* Called before any execute
|
* Called before any execute
|
||||||
@@ -1235,73 +1252,6 @@ class IO
|
|||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* only for postgres. pretty formats an age or datetime difference string
|
|
||||||
* @param string $age age or datetime difference
|
|
||||||
* @param bool $show_micro micro on off (default false)
|
|
||||||
* @return string Y/M/D/h/m/s formatted string (like TimeStringFormat)
|
|
||||||
*/
|
|
||||||
public function dbTimeFormat(string $age, bool $show_micro = false): string
|
|
||||||
{
|
|
||||||
$matches = [];
|
|
||||||
// in string (datetime diff): 1786 days 22:11:52.87418
|
|
||||||
// or (age): 4 years 10 mons 21 days 12:31:11.87418
|
|
||||||
// also -09:43:54.781021 or without - prefix
|
|
||||||
preg_match("/(.*)?(\d{2}):(\d{2}):(\d{2})(\.(\d+))/", $age, $matches);
|
|
||||||
|
|
||||||
$prefix = $matches[1] != '-' ? $matches[1] : '';
|
|
||||||
$hour = $matches[2] != '00' ? preg_replace('/^0/', '', $matches[2]) : '';
|
|
||||||
$minutes = $matches[3] != '00' ? preg_replace('/^0/', '', $matches[3]) : '';
|
|
||||||
$seconds = $matches[4] != '00' ? preg_replace('/^0/', '', $matches[4]) : '';
|
|
||||||
$milliseconds = $matches[6];
|
|
||||||
|
|
||||||
return $prefix
|
|
||||||
. (!empty($hour) && is_string($hour) ? $hour . 'h ' : '')
|
|
||||||
. (!empty($minutes) && is_string($minutes) ? $minutes . 'm ' : '')
|
|
||||||
. (!empty($seconds) && is_string($seconds) ? $seconds . 's' : '')
|
|
||||||
. ($show_micro && !empty($milliseconds) ? ' ' . $milliseconds . 'ms' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear up any data for valid DB insert
|
|
||||||
* @param int|float|string $value to escape data
|
|
||||||
* @param string $kbn escape trigger type
|
|
||||||
* @return string escaped value
|
|
||||||
*/
|
|
||||||
public function dbSqlEscape($value, string $kbn = '')
|
|
||||||
{
|
|
||||||
switch ($kbn) {
|
|
||||||
case 'i':
|
|
||||||
$value = $value === '' ? 'NULL' : intval($value);
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
$value = $value === '' ? 'NULL' : floatval($value);
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
$value = $value === '' ? 'NULL' : "'" . $this->dbEscapeString($value) . "'";
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
$value = $value === '' ? 'NULL' : "'" . $this->dbEscapeString($value) . "'";
|
|
||||||
break;
|
|
||||||
case 'i2':
|
|
||||||
$value = $value === '' ? 0 : intval($value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return (string)$value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this is only needed for Postgresql. Converts postgresql arrays to PHP
|
|
||||||
* @param string $text input text to parse to an array
|
|
||||||
* @return array<mixed> PHP array of the parsed data
|
|
||||||
*/
|
|
||||||
public function dbArrayParse(string $text): array
|
|
||||||
{
|
|
||||||
$output = [];
|
|
||||||
$__db_array_parse = $this->db_functions->__dbArrayParse($text, $output);
|
|
||||||
return is_array($__db_array_parse) ? $__db_array_parse : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ***************************
|
// ***************************
|
||||||
// DEBUG DATA DUMP
|
// DEBUG DATA DUMP
|
||||||
// ***************************
|
// ***************************
|
||||||
@@ -1354,6 +1304,16 @@ class IO
|
|||||||
return $this->db_functions->__dbEscapeLiteral($string);
|
return $this->db_functions->__dbEscapeLiteral($string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* string escape for column and table names
|
||||||
|
* @param string $string string to escape
|
||||||
|
* @return string escaped string
|
||||||
|
*/
|
||||||
|
public function dbEscapeIdentifier($string): string
|
||||||
|
{
|
||||||
|
return $this->db_functions->__dbEscapeIdentifier($string);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* neutral function to escape a bytea for DB writing
|
* neutral function to escape a bytea for DB writing
|
||||||
* @param string $bytea bytea to escape
|
* @param string $bytea bytea to escape
|
||||||
@@ -1364,6 +1324,45 @@ class IO
|
|||||||
return $this->db_functions->__dbEscapeBytea($bytea);
|
return $this->db_functions->__dbEscapeBytea($bytea);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear up any data for valid DB insert
|
||||||
|
* @param int|float|string $value to escape data
|
||||||
|
* @param string $kbn escape trigger type
|
||||||
|
* @return string escaped value
|
||||||
|
*/
|
||||||
|
public function dbSqlEscape($value, string $kbn = '')
|
||||||
|
{
|
||||||
|
switch ($kbn) {
|
||||||
|
case 'i':
|
||||||
|
$value = $value === '' ? 'NULL' : intval($value);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
$value = $value === '' ? 'NULL' : floatval($value);
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
$value = $value === '' ? 'NULL' : "'" . $this->dbEscapeString($value) . "'";
|
||||||
|
break;
|
||||||
|
case 'tl':
|
||||||
|
$value = $value === '' ? 'NULL' : $this->dbEscapeLiteral($value);
|
||||||
|
break;
|
||||||
|
// what is d?
|
||||||
|
case 'd':
|
||||||
|
$value = $value === '' ? 'NULL' : "'" . $this->dbEscapeString($value) . "'";
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
$value = $value === '' ? 'NULL' : "'" . $this->dbBoolean($value, true) . "'";
|
||||||
|
break;
|
||||||
|
case 'i2':
|
||||||
|
$value = $value === '' ? 0 : intval($value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (string)$value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************
|
||||||
|
// DATA READ/WRITE CONVERSION
|
||||||
|
// ***************************
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if the input is a single char 't' or 'f
|
* if the input is a single char 't' or 'f
|
||||||
* it will return the boolean value instead
|
* it will return the boolean value instead
|
||||||
@@ -1397,6 +1396,73 @@ class IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ***************************
|
||||||
|
// DATA READ CONVERSION
|
||||||
|
// ***************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only for postgres. pretty formats an age or datetime difference string
|
||||||
|
* @param string $interval Age or interval/datetime difference
|
||||||
|
* @param bool $show_micro micro on off (default false)
|
||||||
|
* @return string Y/M/D/h/m/s formatted string (like timeStringFormat)
|
||||||
|
*/
|
||||||
|
public function dbTimeFormat(string $interval, bool $show_micro = false): string
|
||||||
|
{
|
||||||
|
$matches = [];
|
||||||
|
// in string (datetime diff): 1786 days 22:11:52.87418
|
||||||
|
// or (age): 4 years 10 mons 21 days 12:31:11.87418
|
||||||
|
// also -09:43:54.781021 or without - prefix
|
||||||
|
// can have missing parts, but day/time must be fully set
|
||||||
|
preg_match(
|
||||||
|
"/^(-)?((\d+) year[s]? ?)?((\d+) mon[s]? ?)?((\d+) day[s]? ?)?"
|
||||||
|
. "((\d{1,2}):(\d{1,2}):(\d{1,2}))?(\.(\d+))?$/",
|
||||||
|
$interval,
|
||||||
|
$matches
|
||||||
|
);
|
||||||
|
|
||||||
|
// prefix (-)
|
||||||
|
$prefix = $matches[1] ?? '';
|
||||||
|
// date, years (2, 3), month (4, 5), days (6, 7)
|
||||||
|
$years = $matches[2] ?? '';
|
||||||
|
$months = $matches[4] ?? '';
|
||||||
|
$days = $matches[6] ?? '';
|
||||||
|
// time (8), hour (9), min (10), sec (11)
|
||||||
|
$hour = $matches[9] ?? '';
|
||||||
|
$minutes = $matches[10] ?? '';
|
||||||
|
$seconds = $matches[11] ?? '';
|
||||||
|
// micro second block (12), ms (13)
|
||||||
|
$milliseconds = $matches[13] ?? '';
|
||||||
|
|
||||||
|
// clean up, hide entries that have 00 in the time group
|
||||||
|
$hour = $hour != '00' ? preg_replace('/^0/', '', $hour) : '';
|
||||||
|
$minutes = $minutes != '00' ? preg_replace('/^0/', '', $minutes) : '';
|
||||||
|
$seconds = $seconds != '00' ? preg_replace('/^0/', '', $seconds) : '';
|
||||||
|
|
||||||
|
// strip any leading or trailing spaces
|
||||||
|
$time_string = trim(
|
||||||
|
$prefix . $years . $months . $days
|
||||||
|
. (!empty($hour) && is_string($hour) ? $hour . 'h ' : '')
|
||||||
|
. (!empty($minutes) && is_string($minutes) ? $minutes . 'm ' : '')
|
||||||
|
. (!empty($seconds) && is_string($seconds) ? $seconds . 's ' : '')
|
||||||
|
. ($show_micro && !empty($milliseconds) ? $milliseconds . 'ms' : '')
|
||||||
|
);
|
||||||
|
// if the return string is empty, return 0s instead
|
||||||
|
return empty($time_string) ? '0s' : $time_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this is only needed for Postgresql. Converts postgresql arrays to PHP
|
||||||
|
* Recommended to rather user 'array_to_json' instead and convet JSON in PHP
|
||||||
|
* @param string $text input text to parse to an array
|
||||||
|
* @return array<mixed> PHP array of the parsed data
|
||||||
|
* @deprecated Recommended to use 'array_to_json' in PostgreSQL instead
|
||||||
|
*/
|
||||||
|
public function dbArrayParse(string $text): array
|
||||||
|
{
|
||||||
|
$__db_array_parse = $this->db_functions->__dbArrayParse($text);
|
||||||
|
return is_array($__db_array_parse) ? $__db_array_parse : [];
|
||||||
|
}
|
||||||
|
|
||||||
// ***************************
|
// ***************************
|
||||||
// TABLE META DATA READ
|
// TABLE META DATA READ
|
||||||
// ***************************
|
// ***************************
|
||||||
|
|||||||
@@ -530,7 +530,21 @@ class PgSQL
|
|||||||
if ($this->dbh === false || is_bool($this->dbh)) {
|
if ($this->dbh === false || is_bool($this->dbh)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return pg_escape_string($this->dbh, (string)$string);
|
return pg_escape_literal($this->dbh, (string)$string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrapper for pg_escape_identifier
|
||||||
|
* Only used for table names, column names
|
||||||
|
* @param string $string any string
|
||||||
|
* @return string escaped string
|
||||||
|
*/
|
||||||
|
public function __dbEscapeIdentifier(string $string): string
|
||||||
|
{
|
||||||
|
if ($this->dbh === false || is_bool($this->dbh)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return pg_escape_identifier($this->dbh, (string)$string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -576,37 +590,64 @@ class PgSQL
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* NOTE: it is recommended to convert arrays to json and parse the json in
|
||||||
|
* PHP itself, array_to_json(array)
|
||||||
|
* This is a fallback for old PostgreSQL versions
|
||||||
* postgresql array to php array
|
* postgresql array to php array
|
||||||
* @param string $text array text from PostgreSQL
|
* https://stackoverflow.com/a/27964420
|
||||||
* @param array<mixed> $output (internal) recursive pass on for nested arrays
|
* @param string $array_text Array text from PostgreSQL
|
||||||
* @param bool|int $limit (internal) max limit to not overshoot
|
* @param int $start Start string position
|
||||||
* the end, start with false
|
* @param int|null $end End string position from recursive call
|
||||||
* @param integer $offset (internal) shift offset for {}
|
* @return null|array<mixed> PHP type array
|
||||||
* @return array<mixed>|int converted PHP array, interal recusrive int position
|
|
||||||
*/
|
*/
|
||||||
public function __dbArrayParse($text, &$output, $limit = false, $offset = 1)
|
public function __dbArrayParse(
|
||||||
{
|
string $array_text,
|
||||||
if (false === $limit) {
|
int $start = 0,
|
||||||
$limit = strlen($text) - 1;
|
?int &$end = null
|
||||||
$output = [];
|
): ?array {
|
||||||
|
if (empty($array_text) || $array_text[0] != '{') {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
if ('{}' != $text) {
|
$return = [];
|
||||||
do {
|
$string = false;
|
||||||
if ('{' != $text[$offset]) {
|
$quote = '';
|
||||||
preg_match("/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/", $text, $match, 0, $offset);
|
$len = strlen($array_text);
|
||||||
$offset += strlen($match[0]);
|
$v = '';
|
||||||
$output[] = '"' != $match[1][0] ?
|
// start from offset
|
||||||
$match[1] :
|
for ($array_pos = $start + 1; $array_pos < $len; $array_pos += 1) {
|
||||||
stripcslashes(substr($match[1], 1, -1));
|
$ch = $array_text[$array_pos];
|
||||||
if ('},' == $match[3]) {
|
// check wher ein the string are we
|
||||||
return $offset;
|
// end, one down
|
||||||
}
|
if (!$string && $ch == '}') {
|
||||||
} else {
|
if ($v !== '' || !empty($return)) {
|
||||||
$offset = $this->__dbArrayParse($text, $output, $limit, $offset + 1);
|
$return[] = $v;
|
||||||
}
|
}
|
||||||
} while ($limit > $offset);
|
$end = $array_pos;
|
||||||
|
break;
|
||||||
|
// open new array, jump recusrive up
|
||||||
|
} elseif (!$string && $ch == '{') {
|
||||||
|
// full string + poff set and end
|
||||||
|
$v = $this->__dbArrayParse($array_text, $array_pos, $array_pos);
|
||||||
|
// next array element
|
||||||
|
} elseif (!$string && $ch == ',') {
|
||||||
|
$return[] = $v;
|
||||||
|
$v = '';
|
||||||
|
// flag that this is a string
|
||||||
|
} elseif (!$string && ($ch == '"' || $ch == "'")) {
|
||||||
|
$string = true;
|
||||||
|
$quote = $ch;
|
||||||
|
// quoted string
|
||||||
|
} elseif ($string && $ch == $quote && $array_text[$array_pos - 1] == "\\") {
|
||||||
|
$v = substr($v, 0, -1) . $ch;
|
||||||
|
} elseif ($string && $ch == $quote && $array_text[$array_pos - 1] != "\\") {
|
||||||
|
$string = false;
|
||||||
|
} else {
|
||||||
|
// build string
|
||||||
|
$v .= $ch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $output;
|
|
||||||
|
return $return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user