diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index bd145ffb..bb24f270 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -38,6 +38,7 @@ namespace tests; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; use CoreLibs\Logging\Logger\Level; +use CoreLibs\DB\Options\Convert; /** * Test class for DB\IO + DB\SQL\PgSQL @@ -4557,6 +4558,176 @@ final class CoreLibsDBIOTest extends TestCase $db->dbClose(); } + // testing auto convert + + /** + * Undocumented function + * + * @covers ::dbSetConvertFlag + * @testdox Check convert type works + * + * @return void + */ + public function testConvertType(): void + { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + $bytea_data = $db->dbEscapeBytea( + file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'CoreLibsDBIOTest.php') ?: '' + ); + $query_insert = <<dbExecParams( + $query_insert, + [ + 'CONVERT_TYPE_TEST', + 1, 1.5, 'varchar', 'varchar literla', + json_encode(['json', 'a', 1, true, 'sub' => ['b', 'c']]), + json_encode(['jsonb', 'a', 1, true, 'sub' => ['b', 'c']]), + $bytea_data, date('Y-m-d H:i:s'), date('Y-m-d'), date('H:m:s'), + '{1,2,3}', '{"a","b","c"}' + ] + ); + $type_layout = [ + 'uid' => 'string', + 'row_int' => 'int', + 'row_numeric' => 'float', + 'row_varchar' => 'string', + 'row_varchar_literal' => 'string', + 'row_json' => 'json', + 'row_jsonb' => 'json', + 'row_bytea' => 'bytea', + 'row_timestamp' => 'string', + 'row_date' => 'string', + 'row_interval' => 'string', + 'row_array_int' => 'string', + 'row_array_varchar' => 'string' + ]; + $query_select = <<dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + // all hast to be string + foreach ($res as $key => $value) { + $this->assertIsString($value, 'Aseert string for column: ' . $key); + } + // convert base only + $db->dbSetConvertFlag(Convert::on); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + $db->dbSetConvertFlag(Convert::numeric); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + case 'float': + $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + $db->dbSetConvertFlag(Convert::json); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + case 'float': + $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + break; + case 'json': + case 'jsonb': + $this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + $db->dbSetConvertFlag(Convert::bytea); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + case 'float': + $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + break; + case 'json': + case 'jsonb': + $this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); + break; + case 'bytea': + // for hex types it must not start with \x + $this->assertStringStartsNotWith( + '\x', + $value, + 'Aseert bytes not starts with \x for column: ' . $key . '/' . $name + ); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + } + // - internal read data (post exec) // dbGetNumRows, dbGetNumFields, dbGetFieldNames, // dbGetQuery, dbGetQueryHash, dbGetDbh @@ -4588,7 +4759,7 @@ final class CoreLibsDBIOTest extends TestCase . "('Foxtrott', 'Tango', 789, '1982-10-15') ", null, // - 2, + 3, 4, ['row_varchar', 'row_varchar_literal', 'row_int', 'row_date'], ['varchar', 'varchar', 'int4', 'date'], @@ -4705,9 +4876,16 @@ final class CoreLibsDBIOTest extends TestCase $db->dbExecParams($query, $params); } + $this->assertInstanceOf( + 'PgSql\Result', + $db->dbGetCursor(), + 'Failed assert dbGetCursor' + ); + $this->assertEquals( $compare_query ?? $query, - $db->dbGetQuery() + $db->dbGetQuery(), + 'Failed assert dbGetQuery' ); $this->assertEquals( // perhaps move that somewhere else? @@ -4720,7 +4898,8 @@ final class CoreLibsDBIOTest extends TestCase ($params === null ? $db->dbGetQueryHash($query) : $db->dbGetQueryHash($query, $params) - ) + ), + 'Failed assertdbGetQueryHash ' ); $this->assertEquals( $expected_rows, @@ -4742,6 +4921,35 @@ final class CoreLibsDBIOTest extends TestCase $db->dbGetFieldTypes(), 'Failed assert dbGetFieldTypes' ); + // check FieldNameTypes matches + $this->assertEquals( + array_combine( + $expected_col_names, + $expected_col_types + ), + $db->dbGetFieldNameTypes(), + 'Failed assert dbGetFieldNameTypes' + ); + // check pos matches name + // name matches type + // pos matches type + foreach ($expected_col_names as $pos => $name) { + $this->assertEquals( + $name, + $db->dbGetFieldName($pos), + 'Failed assert dbGetFieldName: ' . $pos . ' => ' . $name + ); + $this->assertEquals( + $expected_col_types[$pos], + $db->dbGetFieldType($name), + 'Failed assert dbGetFieldType: ' . $name . ' => ' . $expected_col_types[$pos] + ); + $this->assertEquals( + $expected_col_types[$pos], + $db->dbGetFieldType($pos), + 'Failed assert dbGetFieldType: ' . $pos . ' => ' . $expected_col_types[$pos] + ); + } $dbh = $db->dbGetDbh(); $this->assertIsObject( $dbh diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index adb6e8b9..bff2aeeb 100644 --- a/www/admin/class_test.db.php +++ b/www/admin/class_test.db.php @@ -38,7 +38,9 @@ print ""; print "" . $PAGE_NAME . ""; print ""; print '
Class Test Master
'; +print '
Class Test DB Types
'; print '
Class Test DB dbReturn
'; +print '
Class Test DB Single Aciont
'; print '

' . $PAGE_NAME . '

'; print "LOGFILE NAME: " . $db->log->getLogFile() . "
"; @@ -170,10 +172,10 @@ print "EOM READ OF PREVIOUS INSERTED: " . print_r($db->dbReturnRow($query), true print "LAST ERROR: " . $db->dbGetLastError() . "
"; print "
"; $query = <<dbReturnRowParams( @@ -334,10 +336,10 @@ $status = $db->dbExecParams($query_insert, $query_params); echo "*
"; echo "EOM STRING WITH MORE THAN 10 PARAMETERS: " . Support::printToString($query_params) . " |
" - . " |
" - . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | " - . "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | " - . "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true) + . "QUERY: " . $db->dbGetQuery() . " |
" + . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |
" + . "RETURNING EXT:
" . print_r($db->dbGetReturningExt(), true) . "
|
" + . "RETURNING RETURN:
" . print_r($db->dbGetReturningArray(), true) . "
|
" . "ERROR: " . $db->dbGetLastError(true) . "
"; echo "
"; // binary insert tests @@ -355,10 +357,10 @@ $status = $db->dbExec($query); $__last_insert_id = $db->dbGetInsertPK(); print "BINARY DATA INSERT: " . Support::printToString($status) . " |
" - . " |
" - . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | " - . "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | " - . "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true) + . "QUERY: " . $db->dbGetQuery() . " |
" + . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |
" + . "RETURNING EXT:
" . print_r($db->dbGetReturningExt(), true) . "
|
" + . "RETURNING RETURN:
" . print_r($db->dbGetReturningArray(), true) . "
|
" . "ERROR: " . $db->dbGetLastError(true) . "
"; echo "*
"; @@ -369,7 +371,11 @@ INSERT INTO binary_test ( $1, $2, $3 ) SQL; -$status = $db->dbExecParams($query, [$filename, $rand_bin_uid, $binary_data]); +// $binary_data is dbEscapedBytea! +$uniqid = \CoreLibs\Create\Uids::uniqIdShort(); +$status = $db->dbExecParams($query, [ + 'class_test.db.php', $uniqid, $binary_data +]); $__last_insert_id = $db->dbGetInsertPK(); print "BINARY DATA INSERT PARAMS: " . Support::printToString($status) . " |
" @@ -378,7 +384,23 @@ print "BINARY DATA INSERT PARAMS: " . "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | " . "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true) . "ERROR: " . $db->dbGetLastError(true) . "
"; - +print "BINARY READER: "; +$query = <<dbReturnRowParams($query, [$uniqid]); +if (is_array($res)) { + var_dump($res); + $file = $db->dbUnescapeBytea($res['binary_data']); + // var_dump($file); +} else { + print "Execute error"; +} echo "
"; // returning test with multiple entries @@ -578,6 +600,44 @@ while ( } echo "
"; +print "DB RETURN PARAMS LIKE
"; +$q = <<dbReturnParams($q, ['%string%'])) +) { + print "ROW:
" . print_r($res, true) . "

"; +} +echo "
"; + +print "DB RETURN PARAMS ANY
"; +$q = <<dbReturnParams($q, [$query_value])) +) { + print "ROW:
" . print_r($res, true) . "

"; +} +echo "
"; + +print "COMPOSITE ELEMENT READ
"; +$res = $db->dbReturnRow("SELECT item, count, (item).name, (item).price, (item).supplier_id FROM on_hand"); +print "ROW:
" . print_r($res) . "
"; +var_dump($res); +print "Field Name/Types:
" . print_r($db->dbGetFieldNameTypes(), true) . "
"; +echo "
"; + // NOTE: try to replacate connection still exists if script is run a second time // open pg bouncer connection $db_pgb = new CoreLibs\DB\IO($DB_CONFIG['test_pgbouncer'] ?? [], $log); diff --git a/www/admin/class_test.db.single.php b/www/admin/class_test.db.single.php index e288cd7a..03cb6efe 100644 --- a/www/admin/class_test.db.single.php +++ b/www/admin/class_test.db.single.php @@ -35,7 +35,6 @@ print ""; print "" . $PAGE_NAME . ""; print ""; print ''; -print ''; print '

' . $PAGE_NAME . '

'; print "LOGFILE NAME: " . $db->log->getLogFile() . "
"; @@ -54,38 +53,62 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) { print "NO DB HANDLER
"; } -// params > 10 for debug -// error catcher -$query_insert = <<dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); $query_params = [ - 'col 1', 'col 2', 'col 3', 'col 4', 'col 5', 'col 6', 'col 7', 'col 8', - 'col 9', 'col 10', 'col 11', 'col 12', null + $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 = <<dbExecParams($query_insert, $query_params); -echo "*
"; -echo "EOM STRING WITH MORE THAN 10 PARAMETERS: " - . Support::printToString($query_params) . " |
" - . " |
" - . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | " - // . "RETURNING EXT: " . Support::printToString($db->dbGetReturningExt()) . " | " - . "RETURNING RETURN: " . Support::printToString($db->dbGetReturningArray()) - . "ERROR: " . $db->dbGetLastError(true) . "
"; -echo "
"; +$query_select = <<dbReturnRowParams($query_select, [$uniqid]); +if (is_array($res)) { + var_dump($res); +} print ""; diff --git a/www/admin/class_test.db.types.php b/www/admin/class_test.db.types.php new file mode 100644 index 00000000..1b26ed50 --- /dev/null +++ b/www/admin/class_test.db.types.php @@ -0,0 +1,171 @@ + 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 COLUMN TYPES'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +print '

' . $PAGE_NAME . '

'; + +print "LOGFILE NAME: " . $db->log->getLogFile() . "
"; +print "LOGFILE ID: " . $db->log->getLogFileId() . "
"; +print "DBINFO: " . $db->dbInfo() . "
"; +// DB client encoding +print "DB client encoding: " . $db->dbGetEncoding() . "
"; +print "DB search path: " . $db->dbGetSchema() . "
"; + +$to_db_version = '15.2'; +print "VERSION DB: " . $db->dbVersion() . "
"; +print "SERVER ENCODING: " . $db->dbVersionInfo('server_encoding') . "
"; +if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) { + print "ALL OUTPUT [TEST]:
" . print_r(pg_version($dbh), true) . "

"; +} else { + print "NO DB HANDLER
"; +} + +print "TRUNCATE test_foo
"; +$db->dbExec("TRUNCATE test_foo"); + +/* $q = <<dbExecParams($q); +pg_query($db->dbGetDbh(), $q); +$q = <<dbExecParams($q, ['D', '{"(a b,1,1.5)","(c,3,4.5)"}']); +$db->dbExecParams($q, ['D', '{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}']); + */ +$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 = <<dbExecParams($query_insert, $query_params); +echo "*
"; +echo "INSERT ALL COLUMN TYPES: " + . Support::printToString($query_params) . " |
" + . "QUERY: " . $db->dbGetQuery() . " |
" + . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |
" + . "RETURNING EXT:
" . print_r($db->dbGetReturningExt(), true) . "
|
" + . "RETURNING RETURN:
" . print_r($db->dbGetReturningArray(), true) . "
 |
" + . "ERROR: " . $db->dbGetLastError(true) . "
"; +echo "
"; + +$query_select = <<dbReturnRowParams($query_select, [$uniqid]); +// auto switch: +// ^int +// bool +// with flags: +// json(b) => array +// bytes => string? or resource? +// numeric => float (can have precision cut) +$pos = 0; +$name = ''; +if (is_array($res)) { + $cursor = $db->dbGetCursor(); + var_dump($res); + print "Field Name/Types:
" . print_r($db->dbGetFieldNameTypes(), true) . "
"; + print "Get type for: 'number_a':" . $db->dbGetFieldType('number_a') . "
"; + print "Get type for: 0: " . $db->dbGetFieldType(0) . "
"; + print "Get name for: 0: " . $db->dbGetFieldName(0) . "
"; +} + +$db->dbSetConvertFlag(Convert::on); +$db->dbSetConvertFlag(Convert::json); +$db->dbSetConvertFlag(Convert::numeric); +$db->dbSetConvertFlag(Convert::bytea); +$res = $db->dbReturnRowParams($query_select, [$uniqid]); +if (is_array($res)) { + var_dump($res); +} + +print ""; + +// __END__ diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 8f0e58aa..91e61713 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -69,6 +69,7 @@ print ""; // 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.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', diff --git a/www/configs/config.db.php b/www/configs/config.db.php index 1bc7a6bf..4e676c02 100644 --- a/www/configs/config.db.php +++ b/www/configs/config.db.php @@ -22,6 +22,7 @@ $DB_CONFIG = [ 'db_type' => 'pgsql', 'db_encoding' => '', 'db_ssl' => 'allow', // allow, disable, require, prefer + // 'db_convert_type' => ['json'] // on, json, numeric, bytea ], // same as above, but uses pg bouncer 'test_pgbouncer' => [ diff --git a/www/layout/admin/javascript/edit.jq.js b/www/layout/admin/javascript/edit.jq.js index fe0774bb..157cd736 100644 --- a/www/layout/admin/javascript/edit.jq.js +++ b/www/layout/admin/javascript/edit.jq.js @@ -575,7 +575,7 @@ function actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-v */ function actionIndicatorShow(loc, overlay = true) { - // console.log('Indicator: SHOW [%s]', loc); + // console.log('{Indicator}: SHOW [%s]', loc); if (!$('#indicator').is(':visible')) { if (!$('#indicator').hasClass('progress')) { $('#indicator').addClass('progress'); @@ -597,7 +597,7 @@ function actionIndicatorShow(loc, overlay = true) */ function actionIndicatorHide(loc, overlay = true) { - // console.log('Indicator: HIDE [%s]', loc); + // console.log('{Indicator}: HIDE [%s]', loc); $('#indicator').hide(); if (overlay === true) { overlayBoxHide(); @@ -677,7 +677,7 @@ function ClearCall() // eslint-disable-line no-unused-vars */ function showActionIndicator(loc) // eslint-disable-line no-unused-vars { - // console.log('Indicator: SHOW [%s]', loc); + // console.log('{Indicator}: SHOW [%s]', loc); // check if indicator element exists if ($('#indicator').length == 0) { var el = document.createElement('div'); @@ -715,7 +715,7 @@ function showActionIndicator(loc) // eslint-disable-line no-unused-vars */ function hideActionIndicator(loc) // eslint-disable-line no-unused-vars { - // console.log('Indicator: HIDE [%s]', loc); + // console.log('{Indicator}: HIDE [%s]', loc); // check if indicator is visible if ($('#indicator').is(':visible')) { // hide indicator diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index fb8c4993..47ab9ce3 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -259,6 +259,8 @@ namespace CoreLibs\DB; use CoreLibs\Create\Hash; use CoreLibs\Debug\Support; use CoreLibs\Create\Uids; +use CoreLibs\Convert\Json; +use CoreLibs\DB\Options\Convert; // below no ignore is needed if we want to use PgSql interface checks with PHP 8.0 // as main system. Currently all @var sets are written as object @@ -328,6 +330,17 @@ class IO private string $db_type; /** @var string ssl flag (for postgres only), disable, allow, prefer, require */ private string $db_ssl; + /** @var array flag for converting types from settings */ + private array $db_convert_type = []; + // convert type settings + // 0: OFF (CONVERT_OFF) + // >0: ON + // 1: convert intN/bool (CONVERT_ON) + // 2: convert json/jsonb to array (CONVERT_JSON) + // 4: convert numeric/floatN to float (CONVERT_NUMERIC) + // 8: convert bytea to string data (CONVERT_BYTEA) + /** @var int convert type settings as bit mask, 0 for off, anything >2 will aways set 1 too */ + private int $convert_type = Convert::off->value; // FOR BELOW: (This should be private and only readable through some method) // cursor array for cached readings /** @var array extended cursoers string index with content */ @@ -343,6 +356,8 @@ class IO private array $field_names = []; /** @var array field type names */ private array $field_types = []; + /** @var array field name to type */ + private array $field_name_types = []; /** @var array always return as array, even if only one */ private array $insert_id_arr = []; /** @var string primary key name for insert recovery from insert_id_arr */ @@ -404,17 +419,8 @@ class IO ) { // attach logger $this->log = $log; - // sets the names (for connect/reconnect) - $this->db_name = $db_config['db_name'] ?? ''; - $this->db_user = $db_config['db_user'] ?? ''; - $this->db_pwd = $db_config['db_pass'] ?? ''; - $this->db_host = $db_config['db_host'] ?? ''; - $this->db_port = !empty($db_config['db_port']) ? (int)$db_config['db_port'] : 5432; - // do not set to 'public' if not set, because the default is already public - $this->db_schema = !empty($db_config['db_schema']) ? $db_config['db_schema'] : ''; - $this->db_encoding = !empty($db_config['db_encoding']) ? $db_config['db_encoding'] : ''; - $this->db_type = $db_config['db_type'] ?? ''; - $this->db_ssl = !empty($db_config['db_ssl']) ? $db_config['db_ssl'] : 'allow'; + // set the config options + $this->__setConfigOptions($db_config); // set debug, either via global var, or from config, else set to false $this->dbSetDebug( // set if logging level is Debug @@ -499,6 +505,72 @@ class IO // PRIVATE METHODS // ************************************************************* + /** + * Setup DB config and options + * + * @param array> $db_config + * @return bool + */ + private function __setConfigOptions(array $db_config): bool + { + // sets the names (for connect/reconnect) + $this->db_name = $db_config['db_name'] ?? ''; + $this->db_user = $db_config['db_user'] ?? ''; + $this->db_pwd = $db_config['db_pass'] ?? ''; + $this->db_host = $db_config['db_host'] ?? ''; + // port + $this->db_port = !empty($db_config['db_port']) ? + (int)$db_config['db_port'] : 5432; + if ($this->db_port < 0 || $this->db_port > 65535) { + $this->db_port = 5432; + } + // do not set to 'public' if not set, because the default is already public + $this->db_schema = !empty($db_config['db_schema']) ? + $db_config['db_schema'] : ''; + $this->db_encoding = !empty($db_config['db_encoding']) ? + $db_config['db_encoding'] : ''; + // db type + $this->db_type = $db_config['db_type'] ?? ''; + if (!in_array($this->db_type, ['pgsql'])) { + $this->db_type = 'pgsql'; + } + // ssl setting + $this->db_ssl = !empty($db_config['db_ssl']) ? + $db_config['db_ssl'] : 'allow'; + if (!in_array($this->db_ssl, ['allow', 'disable', 'require', 'prefer'])) { + $this->db_ssl = 'allow'; + } + // trigger convert type + // ['on', 'json', 'numeric', 'bytea'] allowed + // if on is not set but other valid than on is assumed + foreach ($db_config['db_convert_type'] ?? [] as $db_convert_type) { + if (!in_array($db_convert_type, ['on', 'json', 'numeric', 'bytea'])) { + continue; + } + $this->db_convert_type[] = $db_convert_type; + switch ($db_convert_type) { + case 'on': + $this->convert_type |= Convert::on->value; + break; + case 'json': + $this->convert_type |= Convert::on->value; + $this->convert_type |= Convert::json->value; + break; + case 'numeric': + $this->convert_type |= Convert::on->value; + $this->convert_type |= Convert::numeric->value; + break; + case 'bytea': + $this->convert_type |= Convert::on->value; + $this->convert_type |= Convert::bytea->value; + break; + } + } + + // return status true: ok, false: options error + return true; + } + /** * based on $this->db_type * here we need to load the db pgsql include one @@ -715,7 +787,12 @@ class IO $this->log->warning($debug_id . ' :' . $prefix . $error_string); break; default: - $this->log->debug($debug_id, $error_string, $prefix); + // used named arguments so we can easy change the order of debug + $this->log->debug( + group_id: $debug_id, + message: $error_string, + prefix: $prefix + ); break; } } @@ -918,11 +995,8 @@ class IO if (is_bool($row)) { return false; } - // only do if array, else pass through row (can be false) - if ( - !is_array($row) || - empty($this->to_encoding) - ) { + // do not do anything if no to encoding is set + if (empty($this->to_encoding)) { return $row; } // go through each row and convert the encoding if needed @@ -944,6 +1018,67 @@ class IO return $row; } + /** + * Convert column content to the type in the name/pos field + * Note that on default it will only convert types that 100% map to PHP + * - intN + * - bool + * everything else will stay string. + * Fruther flags in the conert_type allow to convert: + * - json/jsonb to array + * - bytea to string + * Dangerous convert: + * - numeric/float to float (precision can be lost) + * + * @param array|false $row + * @return array|false + */ + private function __dbConvertType(array|false $row): array|false + { + if (is_bool($row)) { + return false; + } + // if convert type is not turned on + if (!$this->convert_type) { + return $row; + } + foreach ($row as $key => $value) { + // always bool/int + if ( + $this->dbGetFieldType($key) != 'interval' && + str_starts_with($this->dbGetFieldType($key), 'int') + ) { + $row[$key] = (int)$value; + } + if ($this->dbGetFieldType($key) == 'bool') { + $row[$key] = $this->dbBoolean($value); + } + if ( + $this->convert_type & Convert::json->value && + str_starts_with($this->dbGetFieldType($key), 'json') + ) { + $row[$key] = Json::jsonConvertToArray($value); + } + if ( + $this->convert_type & Convert::numeric->value && + ( + str_starts_with($this->dbGetFieldType($key), 'numeric') || + str_starts_with($this->dbGetFieldType($key), 'float') + // $this->dbGetFieldType($key) == 'real' + ) + ) { + $row[$key] = (float)$value; + } + if ( + $this->convert_type & Convert::bytea->value && + $this->dbGetFieldType($key) == 'bytea' + ) { + $row[$key] = $this->dbUnescapeBytea($value); + } + } + return $row; + } + /** * for debug purpose replaces $1, $2, etc with actual data * @@ -1204,7 +1339,7 @@ class IO ) { $this->returning_id = true; } - // $this->debug('DB IO', 'Q: '.$this->query.', RETURN: '.$this->returning_id); + // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // for DEBUG, only on first time ;) $this->__dbDebug( 'db', @@ -1250,6 +1385,7 @@ class IO /** * runs post execute for rows affected, field names, inserted primary key, etc + * * @return bool true on success or false if an error occured */ private function __dbPostExec(): bool @@ -1275,11 +1411,18 @@ class IO $this->field_names = []; for ($i = 0; $i < $this->num_fields; $i++) { $this->field_names[] = $this->db_functions->__dbFieldName($this->cursor, $i) ?: ''; + // if (!empty($this->field_names[$i])) + // $this->field_name_types[$this->field_names[$i]] = null; } $this->field_types = []; for ($i = 0; $i < $this->num_fields; $i++) { $this->field_types[] = $this->db_functions->__dbFieldType($this->cursor, $i) ?: ''; } + // combined array + $this->field_name_types = array_combine( + $this->field_names, + $this->field_types + ); } elseif ($this->__checkQueryForInsert($this->query)) { // if not select do here // count affected rows @@ -1790,8 +1933,8 @@ class IO * * @param string|bool|int $string 't' / 'f' or any string, or bool true/false * @param bool $rev do reverse (bool to string) - * @return bool|string correct php bool true/false - * or postgresql 't'/'f' + * @return bool|string [default=false]: corretc postgresql -> php, + * true: convert php to postgresql */ public function dbBoolean(string|bool|int $string, bool $rev = false): bool|string { @@ -1996,6 +2139,10 @@ class IO 'data' => [], // field names as array 'field_names' => [], + // field types as array (pos in field names is pos here) + 'field_types' => [], + // name to type assoc array (from field names and field types) + 'field_name_types' => [], // number of fields (field names) 'num_fields' => 0, // number of rows that will be maximum returned @@ -2159,6 +2306,12 @@ class IO ); } $this->field_types = $this->cursor_ext[$query_hash]['field_types']; + // combined name => type + $this->cursor_ext[$query_hash]['field_name_types'] = array_combine( + $this->field_names, + $this->field_types + ); + $this->field_name_types = $this->cursor_ext[$query_hash]['field_name_types']; // reset first call var $first_call = false; // reset the internal pos counter @@ -2181,9 +2334,11 @@ class IO !is_int($this->cursor_ext[$query_hash]['cursor']) ) { $return = $this->__dbConvertEncoding( - $this->db_functions->__dbFetchArray( - $this->cursor_ext[$query_hash]['cursor'], - $this->db_functions->__dbResultType($assoc_only) + $this->__dbConvertType( + $this->db_functions->__dbFetchArray( + $this->cursor_ext[$query_hash]['cursor'], + $this->db_functions->__dbResultType($assoc_only) + ) ) ); $this->cursor_ext[$query_hash]['log'][] = 'DB Reading data: ' @@ -2375,9 +2530,11 @@ class IO return false; } return $this->__dbConvertEncoding( - $this->db_functions->__dbFetchArray( - $cursor, - $this->db_functions->__dbResultType($assoc_only) + $this->__dbConvertType( + $this->db_functions->__dbFetchArray( + $cursor, + $this->db_functions->__dbResultType($assoc_only) + ) ) ); } @@ -2476,6 +2633,20 @@ class IO return $rows; } + // *************************** + // CURSOR RETURN + // *************************** + + /** + * Get current set cursor or false if not set or error + * + * @return \PgSql\Result|false + */ + public function dbGetCursor(): \PgSql\Result|false + { + return $this->cursor; + } + // *************************** // CURSOR EXT CACHE RESET // *************************** @@ -3214,6 +3385,42 @@ class IO return $this->db_debug; } + /** + * Undocumented function + * + * @param Convert $convert + * @return void + */ + public function dbSetConvertFlag(Convert $convert): void + { + $this->convert_type |= $convert->value; + } + + /** + * Undocumented function + * + * @param Convert $convert + * @return void + */ + public function dbUnsetConvertFlag(Convert $convert): void + { + $this->convert_type &= ~$convert->value; + } + + /** + * Undocumented function + * + * @param Convert $convert + * @return bool + */ + public function dbGetConvertFlag(Convert $convert): bool + { + if ($this->convert_type & $convert->value) { + return true; + } + return false; + } + /** * set max query calls, set to -1 to disable loop * protection. this will generate a warning @@ -3365,12 +3572,11 @@ class IO * Alternative use dbSetEcnoding to trigger encoding change on the DB side * Set to empty string to turn off * @param string $encoding PHP Valid encoding to set - * @return string Current set encoding + * @return void */ - public function dbSetToEncoding(string $encoding): string + public function dbSetToEncoding(string $encoding): void { $this->to_encoding = $encoding; - return $this->to_encoding; } /** @@ -3606,6 +3812,45 @@ class IO return $this->field_types; } + /** + * Get the field name to type connection list + * + * @return array + */ + public function dbGetFieldNameTypes(): array + { + return $this->field_name_types; + } + + /** + * Get the field name for a position + * + * @param int $pos Position number in query + * @return false|string Field name or false for not found + */ + public function dbGetFieldName(int $pos): false|string + { + return $this->field_names[$pos] ?? false; + } + + /** + * Return a field type for a field name or pos, + * will return false if field is not found in list + * + * @param string|int $name_pos Field name or pos to get the type for + * @return false|string Either the field type or + * false for not found in list + */ + public function dbGetFieldType(int|string $name_pos): false|string + { + if (is_numeric($name_pos)) { + $field_type = $this->field_types[$name_pos] ?? false; + } else { + $field_type = $this->field_name_types[$name_pos] ?? false; + } + return $field_type; + } + /** * Returns the value for given key in statement * Will write error if statemen id does not exist diff --git a/www/lib/CoreLibs/DB/Options/Convert.php b/www/lib/CoreLibs/DB/Options/Convert.php new file mode 100644 index 00000000..7eeb2f8b --- /dev/null +++ b/www/lib/CoreLibs/DB/Options/Convert.php @@ -0,0 +1,58 @@ + self::off, + 'On', 'on', 'ON', 'convert_on', 'CONVERT_ON' => self::on, + 'Json', 'json', 'JSON', 'convert_json', 'CONVERT_JSON' => self::json, + 'Numeric', 'numeric', 'NUMERIC', 'convert_numeric', 'CONVERT_NUMERIC' => self::numeric, + 'Bytea', 'bytea', 'BYTEA', 'convert_bytea', 'CONVERT_BYTEA' => self::bytea, + default => self::off, + }; + } + + /** + * Get internal name from int value + * + * @param int $value + * @return self + */ + public static function fromValue(int $value): self + { + return self::from($value); + } +} + +// __END__ diff --git a/www/vendor/composer/autoload_classmap.php b/www/vendor/composer/autoload_classmap.php index 04c4c89c..f950e063 100644 --- a/www/vendor/composer/autoload_classmap.php +++ b/www/vendor/composer/autoload_classmap.php @@ -39,6 +39,7 @@ return array( 'CoreLibs\\Create\\Uids' => $baseDir . '/lib/CoreLibs/Create/Uids.php', 'CoreLibs\\DB\\Extended\\ArrayIO' => $baseDir . '/lib/CoreLibs/DB/Extended/ArrayIO.php', 'CoreLibs\\DB\\IO' => $baseDir . '/lib/CoreLibs/DB/IO.php', + 'CoreLibs\\DB\\Options\\Convert' => $baseDir . '/lib/CoreLibs/DB/Options/Convert.php', 'CoreLibs\\DB\\SQL\\Interface\\SqlFunctions' => $baseDir . '/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php', 'CoreLibs\\DB\\SQL\\PgSQL' => $baseDir . '/lib/CoreLibs/DB/SQL/PgSQL.php', 'CoreLibs\\Debug\\FileWriter' => $baseDir . '/lib/CoreLibs/Debug/FileWriter.php', diff --git a/www/vendor/composer/autoload_static.php b/www/vendor/composer/autoload_static.php index 095f108a..050fd1ef 100644 --- a/www/vendor/composer/autoload_static.php +++ b/www/vendor/composer/autoload_static.php @@ -90,6 +90,7 @@ class ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9 'CoreLibs\\Create\\Uids' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Uids.php', 'CoreLibs\\DB\\Extended\\ArrayIO' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Extended/ArrayIO.php', 'CoreLibs\\DB\\IO' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/IO.php', + 'CoreLibs\\DB\\Options\\Convert' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Options/Convert.php', 'CoreLibs\\DB\\SQL\\Interface\\SqlFunctions' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php', 'CoreLibs\\DB\\SQL\\PgSQL' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/SQL/PgSQL.php', 'CoreLibs\\Debug\\FileWriter' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/FileWriter.php',