From cde29c03621869f9f0b00f9a8038a27cf50e09fb Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 3 Jun 2022 11:03:06 +0900 Subject: [PATCH] Update DB\IO dbReturn and update connected tests Switch the code point for these below for logic reasons CLEAR_CACHE 1 => 2 (clear cache AFTER END read) READ_NEW 2 => 1 (clear cache BEFORE first read) in dbReturn cursor ext array: remove firstcall entry because it is not needed add new: - cache_flag: $cache method call number - assoc_flag: the assoc read flag from the method call - cached: if there is data cached in the cursor ext array this is true - finished: true if the last read was false - db_read_finished: if true the db read has fiinished (read_rows = num_rows) - read_finished: if true the current read (cache or db) via pos = num_rows is done - log_pos: sequential number for each call with the same query hash - log: array with current actions done in the last read Update DB IO class test with all cursor, cursor ext, read single step, read in loop, read again, etc tests --- 4dev/tests/CoreLibsDBIOTest.php | 884 ++++++++++++++++++++++----- www/admin/class_test.db.dbReturn.php | 40 +- www/lib/CoreLibs/DB/IO.php | 154 +++-- 3 files changed, 875 insertions(+), 203 deletions(-) diff --git a/4dev/tests/CoreLibsDBIOTest.php b/4dev/tests/CoreLibsDBIOTest.php index 2fd95db0..7a6c8179 100644 --- a/4dev/tests/CoreLibsDBIOTest.php +++ b/4dev/tests/CoreLibsDBIOTest.php @@ -203,6 +203,37 @@ final class CoreLibsDBIOTest extends TestCase // print_r(self::$db_config); } + /** + * For all Warning and Error checks in all tests + * DB Warning/Error checks + * + * @param \CoreLibs\DB\IO $db + * @param string $warning + * @param string $error + * @return array + */ + private function subAssertErrorTest( + \CoreLibs\DB\IO $db, + string $warning, + string $error + ): array { + // get last error/warnings + $last_warning = $db->dbGetLastWarning(); + $last_error = $db->dbGetLastError(); + // if string for warning or error is not empty check + $this->assertEquals( + $warning, + $last_warning, + 'Assert query warning' + ); + $this->assertEquals( + $error, + $last_error, + 'Assert query warning' + ); + return [$last_warning, $last_error]; + } + // - connected version test // dbVerions, dbVersionNum, dbVersionInfo, dbVersionInfoParameters, // dbCompareVersion @@ -387,28 +418,15 @@ final class CoreLibsDBIOTest extends TestCase */ public function testDbVerson(string $input, bool $expected): void { - // connect to valid DB - // $db = new \CoreLibs\DB\IO( - // self::$db_config['valid'], - // self::$log - // ); - /** @var \CoreLibs\DB\IO&MockObject $db_io_mock */ $db_io_mock = $this->createPartialMock(\CoreLibs\DB\IO::class, ['dbVersion']); $db_io_mock->method('dbVersion')->willReturn('13.6.0'); - // print "DB VERSION: " . $db->dbVersion() . "\n"; - // print "DB Mock: " . $stub->dbVersion() . "\n"; - // print "TEST: " . ($stub->dbCompareVersion('=13.1.0') ? 'YES' : 'NO') . "\n"; - // print "TEST: " . ($stub->dbCompareVersion('=13.6.0') ? 'YES' : 'NO') . "\n"; - $this->assertEquals( $expected, // $db->dbCompareVersion($input) $db_io_mock->dbCompareVersion($input) ); - - // print "IT HAS TO BE 13.1.0: " . $stub->dbVersion() . "\n"; } // - connect to DB test (dbGetConnectionStatus) @@ -1564,14 +1582,10 @@ final class CoreLibsDBIOTest extends TestCase @$db->dbExec($query) : @$db->dbExec($query, $pk_name) ); - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); } else { $result = $pk_name === null ? $db->dbExec($query) : $db->dbExec($query, $pk_name); - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); // if PHP or newer, must be Object PgSql\Result if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { $this->assertIsObject( @@ -1602,8 +1616,6 @@ final class CoreLibsDBIOTest extends TestCase $db->dbExec($query) : $db->dbExec($query, $pk_name) ); - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); // check query called matching $current_count = $db->dbGetQueryCalled($query); $this->assertEquals( @@ -1618,14 +1630,7 @@ final class CoreLibsDBIOTest extends TestCase } // if string for warning or error is not empty check - $this->assertEquals( - $warning, - $last_warning - ); - $this->assertEquals( - $error, - $last_error - ); + $this->subAssertErrorTest($db, $warning, $error); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); @@ -1749,18 +1754,8 @@ final class CoreLibsDBIOTest extends TestCase $db->dbReturnRow($query, $flag_assoc) ); // get last error/warnings - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); - // print "ER: " . $last_error . "/" . $last_warning . "\n"; // if string for warning or error is not empty check - $this->assertEquals( - $warning, - $last_warning - ); - $this->assertEquals( - $error, - $last_error - ); + $this->subAssertErrorTest($db, $warning, $error); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); @@ -1886,17 +1881,8 @@ final class CoreLibsDBIOTest extends TestCase $db->dbReturnArray($query, $flag_assoc) ); // get last error/warnings - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); // if string for warning or error is not empty check - $this->assertEquals( - $warning, - $last_warning - ); - $this->assertEquals( - $error, - $last_error - ); + $this->subAssertErrorTest($db, $warning, $error); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); @@ -1918,80 +1904,619 @@ final class CoreLibsDBIOTest extends TestCase $insert_query = "INSERT INTO table_with_primary_key (row_int, uid) VALUES " . "(1, 'A'), (2, 'B')"; $read_query = "SELECT row_int, uid FROM table_with_primary_key"; + $row_a = [ + 'row_int' => 1, + 0 => 1, + 'uid' => 'A', + 1 => 'A' + ]; + $row_a_assoc = [ + 'row_int' => 1, + 'uid' => 'A', + ]; + $row_b = [ + 'row_int' => 2, + 0 => 2, + 'uid' => 'B', + 1 => 'B' + ]; + $row_b_assoc = [ + 'row_int' => 2, + 'uid' => 'B', + ]; // 0: read query // 1: reset flag, null for default // 2: assoc flag, null for default - // 3: expected return - // 4: read first, read all flag - // 5: read all check array - // 6: warning - // 7: error - // 8: insert data + // 3: expected return (cursor_ext/data) + // 4: step through, or normal loop read + // 5: cursor ext compare array + // 6: first only, extended cursor (for each step) + // 7: warning + // 8: error + // 9: insert data return [ - 'valid select' => [ + // *** READ STEP BY STEP + // default cache: USE_CACHE + 'valid select, default cache settings' => [ $read_query, null, null, - [ - 'row_int' => 1, - 0 => 1, - 'uid' => 'A', - 1 => 'A' - ], + $row_a, true, - [], + // check cursor_ext + [ + // if <8.1 check against resource + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + // extended cursor per step (first only true) + [ + // second row + [ + 'data' => [ + 0 => $row_b, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], '', '', $insert_query ], - 'valid select, default cache, assoc only' => [ + 'valid select, use cache, assoc only' => [ $read_query, \CoreLibs\DB\IO::USE_CACHE, true, - [ - 'row_int' => 1, - 'uid' => 'A', - ], + $row_a_assoc, true, - [], + // is same as default + [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], '', '', $insert_query ], - 'empty select' => [ + 'valid select, read new, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::READ_NEW, + true, + $row_a_assoc, + true, + [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + 'valid select, clear cache, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::CLEAR_CACHE, + true, + $row_a_assoc, + true, + [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [ + 0 => $row_a_assoc, + 1 => $row_b_assoc, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => true, + 'cached' => true, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + 'valid select, no cache, assoc only' => [ + $read_query, + \CoreLibs\DB\IO::NO_CACHE, + true, + $row_a_assoc, + true, + [ + 'cursor' => 'PgSql\Result', + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 1, + 'query' => $read_query, + 'read_rows' => 1, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => false, + 'read_finished' => false, + 'db_read_finished' => false, + ], + [ + // second row + [ + 'data' => [ + 0 => $row_b_assoc, + ], + 'cursor' => [ + 'cursor' => 'PgSql\Result', + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 2, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => false, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ], + // end row, false + [ + 'data' => false, + 'cursor' => [ + 'cursor' => 1, + 'data' => [], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => true, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ] + ] + ], + '', + '', + $insert_query + ], + // *** READ STEP BY STEP, ERROR TRIGGER + 'empty select error' => [ '', null, null, false, true, [], + [], '', '11', $insert_query, ], - 'insert query' => [ + 'insert query error' => [ $insert_query, null, null, false, true, [], + [], '', '17', $insert_query ], + // *** READ AS LOOP // from here on a complex read all full tests - 'valid select, full read' => [ + 'valid select, full read DEFAULT CACHE' => [ $read_query, null, null, - [ - 'row_int' => 1, - 0 => 1, - 'uid' => 'A', - 1 => 'A' - ], + [$row_a, $row_b,], false, + [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::USE_CACHE, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + // READ_NEW + 'valid select, full read READ NEW' => [ + $read_query, + \CoreLibs\DB\IO::READ_NEW, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + 0 => $row_a, + 1 => $row_b, + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::READ_NEW, + 'assoc_flag' => false, + 'cached' => true, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + // CLEAR_CACHE + 'valid select, full read CLEAR CACHE' => [ + $read_query, + \CoreLibs\DB\IO::CLEAR_CACHE, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::CLEAR_CACHE, + 'assoc_flag' => false, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], + [], + '', + '', + $insert_query + ], + 'valid select, full read NO CACHE' => [ + $read_query, + \CoreLibs\DB\IO::NO_CACHE, + null, + [$row_a, $row_b,], + false, + [ + 'cursor' => 1, + 'data' => [ + ], + 'field_names' => [ + 'row_int', + 'uid' + ], + 'num_fields' => 2, + 'num_rows' => 2, + 'pos' => 0, + 'query' => $read_query, + 'read_rows' => 2, + 'cache_flag' => \CoreLibs\DB\IO::NO_CACHE, + 'assoc_flag' => false, + 'cached' => false, + 'finished' => true, + 'read_finished' => true, + 'db_read_finished' => true, + ], [], '', '', @@ -2001,7 +2526,65 @@ final class CoreLibsDBIOTest extends TestCase } /** - * Undocumented function + * dbReturn cursor extended checks + * + * @param \CoreLibs\DB\IO $db + * @param string $query + * @param array $cursor_ext_checks + * @return void + */ + private function subAssertCursorExtTestDbReturnFunction( + \CoreLibs\DB\IO $db, + string $query, + array $cursor_ext_checks, + ): void { + // cursor check + if ( + empty($db->dbGetLastWarning()) && + empty($db->dbGetLastError()) && + count($cursor_ext_checks) + ) { + $cursor_ext = $db->dbGetCursorExt($query); + foreach ($cursor_ext_checks as $key => $expected) { + if ($key != 'cursor') { + $this->assertEquals( + $expected, + $cursor_ext[$key], + 'assert equal cursor ext for ' . $key + ); + } else { + // for int, it has to be one + // else depends on PHP version, either object or REsource + if (is_int($expected)) { + $this->assertEquals( + 1, + $cursor_ext[$key], + 'assert equal cursor ext cursor int 1' + ); + } elseif (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { + $this->assertIsObject( + $cursor_ext[$key], + 'assert is object cursor ext cursor' + ); + // also check that this is correct instance type + $this->assertInstanceOf( + $expected, + $cursor_ext[$key], + 'assert is instance of cursor ext cursor' + ); + } else { + $this->assertIsResource( + $cursor_ext[$key], + 'assert is resource cursor ext cursor' + ); + } + } + } + } + } + + /** + * dbReturn Function Test * * @covers ::dbReturn * @covers ::dbCacheReset @@ -2009,26 +2592,28 @@ final class CoreLibsDBIOTest extends TestCase * @covers ::dbCursorPos * @covers ::dbCursorNumRows * @dataProvider dbReturnProvider - * @testdox dbReturn $query and cache $flag_cache and assoc $flag_assoc with $expected (Warning: $warning/Error: $error) [$_dataName] + * @testdox dbReturn Read Frist $read_first_only only and cache $flag_cache and assoc $flag_assoc with (Warning: $warning/Error: $error) [$_dataName] * * @param string $query * @param integer|null $flag_cache * @param boolean|null $flag_assoc * @param array|bool $expected * @param bool $read_first_only - * @param array $cursor_ext_checks + * @param array $cursor_ext_checks + * @param array $cursor_ext_checks_step * @param string $warning * @param string $error * @param string $insert_data * @return void */ - public function testDbReturn( + public function testDbReturnFunction( string $query, ?int $flag_cache, ?bool $flag_assoc, $expected, bool $read_first_only, array $cursor_ext_checks, + array $cursor_ext_checks_step, string $warning, string $error, string $insert_data @@ -2045,7 +2630,7 @@ final class CoreLibsDBIOTest extends TestCase // all checks below if ($read_first_only === true) { // simple assert first read, then discard result - // compare + // compare data $this->assertEquals( $expected, $flag_cache === null && $flag_assoc === null ? @@ -2053,24 +2638,17 @@ final class CoreLibsDBIOTest extends TestCase ($flag_assoc === null ? $db->dbReturn($query, $flag_cache) : $db->dbReturn($query, $flag_cache, $flag_assoc) - ) - ); - // get last error/warnings - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); - // if string for warning or error is not empty check - $this->assertEquals( - $warning, - $last_warning - ); - $this->assertEquals( - $error, - $last_error + ), + 'Assert dbReturn first only equal' ); + $this->subAssertErrorTest($db, $warning, $error); + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); + // extended checks. read until end of data and check per steck cursor data + foreach ($cursor_ext_checks_step as $_cursor_ext_checks) { + // each step matches a read step + } } else { - // all tests here have valid returns already, error checks not needed // read all, and then do result compare - // cursor ext data checks (field names, rows, pos, data) // do cache reset test $data = []; $pos = 0; @@ -2089,14 +2667,18 @@ final class CoreLibsDBIOTest extends TestCase // check cursor pos $this->assertEquals( $pos, - $db->dbGetCursorPos($query) + $db->dbGetCursorPos($query), + 'Assert dbReturn pos' ); } // does count match for returned data and the cursor num rows $this->assertEquals( count($data), - $db->dbGetCursorNumRows($query) + $db->dbGetCursorNumRows($query), + 'Assert dbReturn num rows' ); + // run cursor ext checks after first run + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); // does data match // try get cursor data for non existing, must be null $this->assertNull( @@ -2105,8 +2687,66 @@ final class CoreLibsDBIOTest extends TestCase // does reset data work, query cursor must be null $db->dbCacheReset($query); $this->assertNull( - $db->dbGetCursorExt($query) + $db->dbGetCursorExt($query), + 'Assert dbReturn reset cache' ); + // New run after reset + // for read all, test that two reads result in same data + $data = []; + $pos = 0; + while ( + is_array( + $res = $flag_cache === null && $flag_assoc === null ? + $db->dbReturn($query) : + ($flag_assoc === null ? + $db->dbReturn($query, $flag_cache) : + $db->dbReturn($query, $flag_cache, $flag_assoc) + ) + ) + ) { + $data[] = $res; + $pos++; + // check cursor pos + $this->assertEquals( + $pos, + $db->dbGetCursorPos($query), + 'Assert dbReturn double read 1 pos: ' . $flag_cache + ); + } + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); + $data_second = []; + $pos_second = 0; + while ( + is_array( + $res = $flag_cache === null && $flag_assoc === null ? + $db->dbReturn($query) : + ($flag_assoc === null ? + $db->dbReturn($query, $flag_cache) : + $db->dbReturn($query, $flag_cache, $flag_assoc) + ) + ) + ) { + $data_second[] = $res; + $pos_second++; + // check cursor pos + $this->assertEquals( + $pos_second, + $db->dbGetCursorPos($query), + 'Assert dbReturn double read 2 pos: ' . $flag_cache + ); + } + $this->assertEquals( + $data, + $data_second, + 'Assert first and second run are equal: return data' + ); + $this->assertEquals( + $pos, + $pos_second, + 'Assert first and second run are equal: count' + ); + // run cursor ext checks after second run + $this->subAssertCursorExtTestDbReturnFunction($db, $query, $cursor_ext_checks); } // reset all data @@ -2376,8 +3016,6 @@ final class CoreLibsDBIOTest extends TestCase $db->dbPrepare($stm_name, $query) : $db->dbPrepare($stm_name, $query, $pk_name); } - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); // if result type, or if forced bool if (is_string($expected_prepare) && $expected_prepare == 'result') { // if PHP or newer, must be Object PgSql\Result @@ -2402,22 +3040,13 @@ final class CoreLibsDBIOTest extends TestCase ); } // error/warning check - $this->assertEquals( - $warning_prepare, - $last_warning, - ); - $this->assertEquals( - $error_prepare, - $last_error, - ); + $this->subAssertErrorTest($db, $warning_prepare, $error_prepare); // for non fail prepare test exec // check test result $execute_result = $query_data === null ? $db->dbExecute($stm_name) : $db->dbExecute($stm_name, $query_data); - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); if ($expected_execute == 'result') { // if PHP or newer, must be Object PgSql\Result if (\CoreLibs\Check\PhpVersion::checkPHPVersion('8.1')) { @@ -2442,14 +3071,7 @@ final class CoreLibsDBIOTest extends TestCase ); } // error/warning check - $this->assertEquals( - $warning_execute, - $last_warning, - ); - $this->assertEquals( - $error_execute, - $last_error, - ); + $this->subAssertErrorTest($db, $warning_execute, $error_execute); // now check test result if expected return is result if ( $expected_execute == 'result' && @@ -3540,21 +4162,12 @@ final class CoreLibsDBIOTest extends TestCase $result_exec = $pk_name === null ? $db->dbExecAsync($query) : $db->dbExecAsync($query, $pk_name); - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); $this->assertEquals( $expected_exec, $result_exec ); // error/warning check - $this->assertEquals( - $warning_exec, - $last_warning, - ); - $this->assertEquals( - $error_exec, - $last_error, - ); + $this->subAssertErrorTest($db, $warning_exec, $error_exec); $run = 1; // first loop check @@ -3567,8 +4180,6 @@ final class CoreLibsDBIOTest extends TestCase } $run++; } - $last_warning = $db->dbGetLastWarning(); - $last_error = $db->dbGetLastError(); // check after final if ($expected_final == 'result') { // post end check @@ -3594,14 +4205,7 @@ final class CoreLibsDBIOTest extends TestCase ); } // error/warning check - $this->assertEquals( - $warning_final, - $last_warning, - ); - $this->assertEquals( - $error_final, - $last_error, - ); + $this->subAssertErrorTest($db, $warning_final, $error_final); // reset all data $db->dbExec("TRUNCATE table_with_primary_key"); diff --git a/www/admin/class_test.db.dbReturn.php b/www/admin/class_test.db.dbReturn.php index aa4e2440..d51cbb12 100644 --- a/www/admin/class_test.db.dbReturn.php +++ b/www/admin/class_test.db.dbReturn.php @@ -66,7 +66,7 @@ $q_db_ret = "SELECT * FROM test_db_return ORDER BY uid"; RunningTime::hrRunningTime(); -$cache_flag = 'USE_CACHE'; +$cache_flag = 'USE_CACHE (0)'; print "dbReturn '" . $cache_flag . "'/Default: " . $q_db_ret . "
"; // SINGLE read on multi row return // Do twice @@ -84,24 +84,7 @@ for ($i = 1; $i <= 6; $i++) { // reset all read data $db->dbCacheReset($q_db_ret); echo "
"; -$cache_flag = 'CLEAR_CACHE'; -print "dbReturn '" . $cache_flag . "': " . $q_db_ret . "
"; -// NO CACHE -for ($i = 1; $i <= 6; $i++) { - $res = $db->dbReturn($q_db_ret, $db::CLEAR_CACHE); - print $i . ") " . $cache_flag . ": " - . "res: " . (is_bool($res) ? - "Bool: " . $db->log->prBl($res) : - (is_array($res) ? - "Array: " . $db->log->prBl(is_array($res)) : '{-}') - ) . ", " - . "cursor_ext:
" . Support::printAr($db->dbGetCursorExt($q_db_ret)) . "
"; - print "Run time: " . RunningTime::hrRunningTime() . "
"; -} -// reset all read data -$db->dbCacheReset($q_db_ret); -echo "
"; -$cache_flag = 'READ_NEW'; +$cache_flag = 'READ_NEW (1)'; print "dbReturn '" . $cache_flag . "': " . $q_db_ret . "
"; // NO CACHE for ($i = 1; $i <= 6; $i++) { @@ -118,7 +101,24 @@ for ($i = 1; $i <= 6; $i++) { // reset all read data $db->dbCacheReset($q_db_ret); echo "
"; -$cache_flag = 'NO_CACHE'; +$cache_flag = 'CLEAR_CACHE (2)'; +print "dbReturn '" . $cache_flag . "': " . $q_db_ret . "
"; +// NO CACHE +for ($i = 1; $i <= 6; $i++) { + $res = $db->dbReturn($q_db_ret, $db::CLEAR_CACHE); + print $i . ") " . $cache_flag . ": " + . "res: " . (is_bool($res) ? + "Bool: " . $db->log->prBl($res) : + (is_array($res) ? + "Array: " . $db->log->prBl(is_array($res)) : '{-}') + ) . ", " + . "cursor_ext:
" . Support::printAr($db->dbGetCursorExt($q_db_ret)) . "
"; + print "Run time: " . RunningTime::hrRunningTime() . "
"; +} +// reset all read data +$db->dbCacheReset($q_db_ret); +echo "
"; +$cache_flag = 'NO_CACHE (3)'; print "dbReturn '" . $cache_flag . "': " . $q_db_ret . "
"; // NO CACHE for ($i = 1; $i <= 6; $i++) { diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index d79c8a7d..df9c9d10 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -263,15 +263,15 @@ use CoreLibs\Create\Uids; class IO { // 0: normal read, store in cache - // 1: read cache, clean at the end - // 2: read new, clean at end + // 1: read new, keep at end, clean before new run + // 2: read new, clean at the end (temporary cache) // 3: never cache /** @var int */ public const USE_CACHE = 0; /** @var int */ - public const CLEAR_CACHE = 1; + public const READ_NEW = 1; /** @var int */ - public const READ_NEW = 2; + public const CLEAR_CACHE = 2; /** @var int */ public const NO_CACHE = 3; /** @var string */ @@ -1682,18 +1682,18 @@ class IO * data, or with reset flag set calls data from DB again * NOTE on $cache param: * - if set to 0, if same query run again, will read from cache - * - if set to 1, at the end of the query (last row returned), - * the stored array will be deleted ... - * - if set to 2, the data will be read new and cached + * - if set to 1, the data will be read new and cached, cache reset a new run * (wheres 1 reads cache AND destroys at end of read) + * - if set to 2, at the end of the query (last row returned), + * the stored array will be deleted ... * - if set to 3, after EACH row, the data will be reset, * no caching is done except for basic (count, etc) * @param string $query Query string * @param int $cache reset status: default: USE_CACHE * USE_CACHE/0: normal read from cache on second run - * CLEAR_CACHE/1: read cache, clean at the end - * READ_NEW/2: read new, clean at end - * NO_CACHE/3: never cache [default] + * READ_NEW/1: write to cache, clean before new run + * CLEAR_CACHE/2: write cache, clean after finished + * NO_CACHE/3: don't write cache * @param bool $assoc_only True to only returned the named and not * index position ones * @return array|bool return array data or false on error/end @@ -1714,17 +1714,44 @@ class IO // pre declare array if (!isset($this->cursor_ext[$query_hash])) { $this->cursor_ext[$query_hash] = [ + // cursor, null: unset, 1: finished read/cache, 2: object/resource reading 'cursor' => null, + // cached data 'data' => [], + // field names as array 'field_names' => [], - 'firstcall' => 0, + // number of fields (field names) 'num_fields' => 0, + // number of rows that will be maximum returned 'num_rows' => 0, - 'pos' => 0, - 'query' => '', + // how many rows have been read from db 'read_rows' => 0, + // current read pos (db/cache), 0 on last read (finished) + 'pos' => 0, + // the query used in this call + 'query' => '', + // cache flag from method call + 'cache_flag' => $cache, + // flag if we only have assoc data + 'assoc_flag' => $assoc_only, + // flag if we have cache data stored at the moment + 'cached' => false, + // when fetch array or cache read returns false + // in loop read that means dbReturn retuns false without erro + 'finished' => false, + // read from cache/db (pos == rows) + 'read_finished' => false, + // read from db only (read == rows) + 'db_read_finished' => false, + // for debug + 'log_pos' => 1, // how many times called overall + 'log' => [], // current run log ]; + } else { + $this->cursor_ext[$query_hash]['log_pos'] ++; } + // reset log for each read + $this->cursor_ext[$query_hash]['log'] = []; // set the query $this->cursor_ext[$query_hash]['query'] = $query; // before doing ANYTHING check if query is "SELECT ..." everything else does not work @@ -1732,20 +1759,25 @@ class IO $this->__dbError(17, false, $this->cursor_ext[$query_hash]['query']); return false; } + // set first call to false + $first_call = false; // init return als false $return = false; // if it is a call with reset in it we reset the cursor, // so we get an uncached return // but only for the FIRST call (pos == 0) if ($cache > self::USE_CACHE && !$this->cursor_ext[$query_hash]['pos']) { + $this->cursor_ext[$query_hash]['log'][] = 'Reset cursor'; $this->cursor_ext[$query_hash]['cursor'] = null; + if ($cache == self::READ_NEW) { + $this->cursor_ext[$query_hash]['log'][] = 'Cache reset'; + $this->cursor_ext[$query_hash]['data'] = []; + } } - // $this->debug('MENU', 'Reset: '.$reset.', Cursor: ' - // .$this->cursor_ext[$query_hash]['cursor'].', Pos: '.$this->cursor_ext[$query_hash]['pos'] - // .', Query: '.$query); // if no cursor yet, execute if (!$this->cursor_ext[$query_hash]['cursor']) { + $this->cursor_ext[$query_hash]['log'][] = 'No cursor'; // for DEBUG, print out each query executed if ($this->db_debug) { $this->__dbDebug('db', $this->cursor_ext[$query_hash]['query'], 'dbReturn', 'Q'); @@ -1773,13 +1805,14 @@ class IO $this->__dbError(13, $this->cursor_ext[$query_hash]['cursor']); return false; } else { - $this->cursor_ext[$query_hash]['firstcall'] = 1; + $first_call = true; } } // only go if NO cursor exists // if cursor exists ... if ($this->cursor_ext[$query_hash]['cursor']) { - if ($this->cursor_ext[$query_hash]['firstcall'] == 1) { + if ($first_call === true) { + $this->cursor_ext[$query_hash]['log'][] = 'First call'; // count the rows returned (if select) $this->cursor_ext[$query_hash]['num_rows'] = $this->db_functions->__dbNumRows($this->cursor_ext[$query_hash]['cursor']); @@ -1799,35 +1832,51 @@ class IO ); } $this->field_names = $this->cursor_ext[$query_hash]['field_names']; - // reset first call vars - $this->cursor_ext[$query_hash]['firstcall'] = 0; + // reset first call var + $first_call = false; // reset the internal pos counter $this->cursor_ext[$query_hash]['pos'] = 0; // reset the global (for cache) read counter $this->cursor_ext[$query_hash]['read_rows'] = 0; + // reset read finished flag + $this->cursor_ext[$query_hash]['finished'] = false; + $this->cursor_ext[$query_hash]['read_finished'] = false; + $this->cursor_ext[$query_hash]['db_read_finished'] = false; + // set cursor ccached flag based on cache flag + if ($cache < self::NO_CACHE) { + $this->cursor_ext[$query_hash]['cached'] = true; + } } - // read data for further work ... but only if necessarry + // main database read if not all read and we have an active cursor if ( - $this->cursor_ext[$query_hash]['read_rows'] == - $this->cursor_ext[$query_hash]['num_rows'] + $this->cursor_ext[$query_hash]['read_rows'] != + $this->cursor_ext[$query_hash]['num_rows'] && + !is_int($this->cursor_ext[$query_hash]['cursor']) ) { - $return = false; - } else { $return = $this->__dbConvertEncoding( $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: ' + . (is_bool($return) ? 'EOF' : 'DATA'); // if returned is NOT an array, abort to false if (!is_array($return)) { $return = false; } } - // check if cached call or reset call ... + // read from cache, or do partial cache set and caching if (!$return && $cache == self::USE_CACHE) { // check if end of output ... - if ($this->cursor_ext[$query_hash]['pos'] >= $this->cursor_ext[$query_hash]['num_rows']) { + if ( + $this->cursor_ext[$query_hash]['pos'] >= + $this->cursor_ext[$query_hash]['num_rows'] + ) { + $this->cursor_ext[$query_hash]['log'][] = 'USE CACHE, end'; + // finish read + $this->cursor_ext[$query_hash]['finished'] = true; + // reset pos for next read $this->cursor_ext[$query_hash]['pos'] = 0; // if not reset given, set the cursor to true, so in a cached // call on a different page we don't get problems from @@ -1835,36 +1884,55 @@ class IO $this->cursor_ext[$query_hash]['cursor'] = 1; $return = false; } else { + $this->cursor_ext[$query_hash]['log'][] = 'USE CACHE, read data'; + $this->cursor_ext[$query_hash]['read_finished'] = false; + $this->cursor_ext[$query_hash]['finished'] = false; // cached data read $return = $this->__dbReturnCacheRead($query_hash, $assoc_only); + if ( + $this->cursor_ext[$query_hash]['pos'] == + $this->cursor_ext[$query_hash]['num_rows'] + ) { + $this->cursor_ext[$query_hash]['log'][] = 'USE CACHE, all cache rows read'; + $this->cursor_ext[$query_hash]['read_finished'] = true; + } } } else { - // return row, if last && reset, then unset the hole hash array - if ( - !$return && - ($cache == self::CLEAR_CACHE || $cache == self::NO_CACHE) && - $this->cursor_ext[$query_hash]['pos'] - ) { - // unset data block only - $this->cursor_ext[$query_hash]['data'] = []; - $this->cursor_ext[$query_hash]['pos'] = 0; - } elseif ( - !$return && $cache == self::READ_NEW && - $this->cursor_ext[$query_hash]['pos'] - ) { - // at end of read reset pos & set cursor to 1 - // (so it does not get lost in session transfer) + // return row, if last && reset, then unset the whole hash array + if (!$return && $this->cursor_ext[$query_hash]['pos']) { $this->cursor_ext[$query_hash]['pos'] = 0; $this->cursor_ext[$query_hash]['cursor'] = 1; - $return = false; + $this->cursor_ext[$query_hash]['finished'] = true; + // for clear cache, clear cache, else only write log info + if ($cache == self::CLEAR_CACHE) { + $this->cursor_ext[$query_hash]['log'][] = 'CLEAR CACHE, end'; + // unset data block only + $this->cursor_ext[$query_hash]['data'] = []; + $this->cursor_ext[$query_hash]['cached'] = false; + } elseif ($cache == self::READ_NEW) { + $this->cursor_ext[$query_hash]['log'][] = 'READ NEW, end'; + } elseif ($cache == self::NO_CACHE) { + $this->cursor_ext[$query_hash]['log'][] = 'NO CACHE, end'; + } } // if something found, write data into hash array if ($return) { + $this->cursor_ext[$query_hash]['log'][] = 'Return Data'; // internal position counter $this->cursor_ext[$query_hash]['pos'] ++; $this->cursor_ext[$query_hash]['read_rows'] ++; + // read is finished + if ( + $this->cursor_ext[$query_hash]['read_rows'] == + $this->cursor_ext[$query_hash]['num_rows'] + ) { + $this->cursor_ext[$query_hash]['log'][] = 'Return data all db rows read'; + $this->cursor_ext[$query_hash]['db_read_finished'] = true; + $this->cursor_ext[$query_hash]['read_finished'] = true; + } // if reset is < NO_CACHE level caching is done, else no if ($cache < self::NO_CACHE) { + $this->cursor_ext[$query_hash]['log'][] = 'Cache Data'; // why was this here? // $temp = []; // foreach ($return as $field_name => $data) {