From 8fecbf8af0d037abe75fa47f08d5140b0e4a213b Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 01:33:46 +0300 Subject: [PATCH 01/11] feat: Multi Result Set --- WebFiori/Database/MultiResultSet.php | 150 ++++++++++++++++++ tests/MultiResultSetTest.php | 227 +++++++++++++++++++++++++++ tests/phpunit.xml | 3 + tests/phpunit10.xml | 5 +- 4 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 WebFiori/Database/MultiResultSet.php create mode 100644 tests/MultiResultSetTest.php diff --git a/WebFiori/Database/MultiResultSet.php b/WebFiori/Database/MultiResultSet.php new file mode 100644 index 00000000..59ce88fe --- /dev/null +++ b/WebFiori/Database/MultiResultSet.php @@ -0,0 +1,150 @@ +cursorPos = 0; + $this->resultSets = []; + + foreach ($resultSets as $resultData) { + $this->addResultSet($resultData); + } + } + + /** + * Add a result set to the collection. + * + * @param array|ResultSet $resultData Array of records or ResultSet object + */ + public function addResultSet($resultData): void { + if ($resultData instanceof ResultSet) { + $this->resultSets[] = $resultData; + } else { + $this->resultSets[] = new ResultSet($resultData); + } + } + + /** + * Get the number of result sets. + * + * @return int Number of result sets + */ + public function count(): int { + return count($this->resultSets); + } + + /** + * Get the current ResultSet object. + * + * @return ResultSet|null Current ResultSet or null if invalid position + */ + #[ReturnTypeWillChange] + public function current() { + return $this->valid() ? $this->resultSets[$this->cursorPos] : null; + } + + /** + * Get a specific result set by index. + * + * @param int $index Index of the result set + * @return ResultSet|null ResultSet at the specified index or null if not found + */ + public function getResultSet(int $index): ?ResultSet { + return isset($this->resultSets[$index]) ? $this->resultSets[$index] : null; + } + + /** + * Get all result sets. + * + * @return ResultSet[] Array of all ResultSet objects + */ + public function getResultSets(): array { + return $this->resultSets; + } + + /** + * Get the current cursor position. + * + * @return int Current position + */ + #[ReturnTypeWillChange] + public function key() { + return $this->cursorPos; + } + + /** + * Move to the next result set. + */ + #[ReturnTypeWillChange] + public function next(): void { + $this->cursorPos++; + } + + /** + * Reset cursor to the first result set. + */ + #[ReturnTypeWillChange] + public function rewind(): void { + $this->cursorPos = 0; + } + + /** + * Get total number of records across all result sets. + * + * @return int Total number of records + */ + public function getTotalRecordCount(): int { + $total = 0; + foreach ($this->resultSets as $resultSet) { + $total += $resultSet->getRowsCount(); + } + return $total; + } + + /** + * Check if current position is valid. + * + * @return bool True if current position is valid + */ + public function valid(): bool { + return $this->cursorPos >= 0 && $this->cursorPos < count($this->resultSets); + } +} diff --git a/tests/MultiResultSetTest.php b/tests/MultiResultSetTest.php new file mode 100644 index 00000000..c37a98f7 --- /dev/null +++ b/tests/MultiResultSetTest.php @@ -0,0 +1,227 @@ +assertEquals(0, $multiResult->count()); + $this->assertEquals(0, $multiResult->getTotalRecordCount()); + } + + /** + * @test + */ + public function testConstructorWithData() { + $data = [ + [['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Jane']], + [['count' => 5]], + [['status' => 'success']] + ]; + + $multiResult = new MultiResultSet($data); + $this->assertEquals(3, $multiResult->count()); + $this->assertEquals(4, $multiResult->getTotalRecordCount()); // 2 + 1 + 1 + } + + /** + * @test + */ + public function testAddResultSet() { + $multiResult = new MultiResultSet(); + $multiResult->addResultSet([['id' => 1, 'name' => 'Test']]); + + $this->assertEquals(1, $multiResult->count()); + $this->assertEquals(1, $multiResult->getTotalRecordCount()); + } + + /** + * @test + */ + public function testGetResultSet() { + $data = [ + [['id' => 1, 'name' => 'John']], + [['count' => 5]] + ]; + + $multiResult = new MultiResultSet($data); + + $firstResult = $multiResult->getResultSet(0); + $this->assertInstanceOf(ResultSet::class, $firstResult); + $this->assertEquals(1, $firstResult->getRowsCount()); + + $secondResult = $multiResult->getResultSet(1); + $this->assertInstanceOf(ResultSet::class, $secondResult); + $this->assertEquals(1, $secondResult->getRowsCount()); + + $invalidResult = $multiResult->getResultSet(5); + $this->assertNull($invalidResult); + } + + /** + * @test + */ + public function testGetResultSets() { + $data = [ + [['id' => 1]], + [['id' => 2]] + ]; + + $multiResult = new MultiResultSet($data); + $resultSets = $multiResult->getResultSets(); + + $this->assertIsArray($resultSets); + $this->assertEquals(2, count($resultSets)); + $this->assertInstanceOf(ResultSet::class, $resultSets[0]); + $this->assertInstanceOf(ResultSet::class, $resultSets[1]); + } + + /** + * @test + */ + public function testIterator() { + $data = [ + [['id' => 1, 'name' => 'First']], + [['id' => 2, 'name' => 'Second']], + [['id' => 3, 'name' => 'Third']] + ]; + + $multiResult = new MultiResultSet($data); + + $count = 0; + foreach ($multiResult as $index => $resultSet) { + $this->assertEquals($count, $index); + $this->assertInstanceOf(ResultSet::class, $resultSet); + $this->assertEquals(1, $resultSet->getRowsCount()); + $count++; + } + + $this->assertEquals(3, $count); + } + + /** + * @test + */ + public function testIteratorMethods() { + $data = [ + [['id' => 1]], + [['id' => 2]] + ]; + + $multiResult = new MultiResultSet($data); + + // Test rewind + $multiResult->rewind(); + $this->assertEquals(0, $multiResult->key()); + $this->assertTrue($multiResult->valid()); + + // Test current + $current = $multiResult->current(); + $this->assertInstanceOf(ResultSet::class, $current); + + // Test next + $multiResult->next(); + $this->assertEquals(1, $multiResult->key()); + $this->assertTrue($multiResult->valid()); + + // Test beyond bounds + $multiResult->next(); + $this->assertEquals(2, $multiResult->key()); + $this->assertFalse($multiResult->valid()); + $this->assertNull($multiResult->current()); + } + + /** + * @test + */ + public function testGetTotalRecordCount() { + $data = [ + [['id' => 1], ['id' => 2], ['id' => 3]], // 3 records + [['count' => 10]], // 1 record + [], // 0 records + [['status' => 'ok'], ['status' => 'error']] // 2 records + ]; + + $multiResult = new MultiResultSet($data); + $this->assertEquals(6, $multiResult->getTotalRecordCount()); // 3 + 1 + 0 + 2 + } + + /** + * @test + */ + public function testConstructorWithMixedData() { + $resultSet1 = new ResultSet([['id' => 1, 'name' => 'John']]); + $resultSet2 = new ResultSet([['count' => 5]]); + + $data = [ + $resultSet1, // ResultSet object + [['id' => 2, 'name' => 'Jane']], // Array data + $resultSet2, // Another ResultSet object + [['status' => 'success']] // More array data + ]; + + $multiResult = new MultiResultSet($data); + $this->assertEquals(4, $multiResult->count()); + $this->assertEquals(4, $multiResult->getTotalRecordCount()); // 1 + 1 + 1 + 1 + + // Verify first result set (was already ResultSet) + $first = $multiResult->getResultSet(0); + $this->assertInstanceOf(ResultSet::class, $first); + $this->assertEquals(1, $first->getRowsCount()); + $this->assertEquals('John', $first->getRows()[0]['name']); + + // Verify second result set (was array, converted to ResultSet) + $second = $multiResult->getResultSet(1); + $this->assertInstanceOf(ResultSet::class, $second); + $this->assertEquals(1, $second->getRowsCount()); + $this->assertEquals('Jane', $second->getRows()[0]['name']); + } + + /** + * @test + */ + public function testAddResultSetWithMixedTypes() { + $multiResult = new MultiResultSet(); + + // Add array data + $multiResult->addResultSet([['id' => 1, 'name' => 'Test']]); + $this->assertEquals(1, $multiResult->count()); + + // Add ResultSet object + $resultSet = new ResultSet([['count' => 5], ['count' => 10]]); + $multiResult->addResultSet($resultSet); + $this->assertEquals(2, $multiResult->count()); + $this->assertEquals(3, $multiResult->getTotalRecordCount()); // 1 + 2 + + // Verify the added ResultSet object + $addedResultSet = $multiResult->getResultSet(1); + $this->assertInstanceOf(ResultSet::class, $addedResultSet); + $this->assertEquals(2, $addedResultSet->getRowsCount()); + $this->assertEquals(5, $addedResultSet->getRows()[0]['count']); + } + + /** + * @test + */ + public function testCountable() { + $multiResult = new MultiResultSet(); + $this->assertEquals(0, count($multiResult)); + + $multiResult->addResultSet([['id' => 1]]); + $this->assertEquals(1, count($multiResult)); + + $multiResult->addResultSet([['id' => 2]]); + $this->assertEquals(2, count($multiResult)); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 8ffba418..6c836d70 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -26,5 +26,8 @@ ./WebFiori/Tests/Database/Schema + + ./MultiResultSetTest.php + diff --git a/tests/phpunit10.xml b/tests/phpunit10.xml index 8f0d9c06..e8dea8ee 100644 --- a/tests/phpunit10.xml +++ b/tests/phpunit10.xml @@ -21,7 +21,9 @@ ./WebFiori/Tests/Database/Schema - + + ./MultiResultSetTest.php + @@ -29,7 +31,6 @@ ../WebFiori/Database/MySql ../WebFiori/Database ../WebFiori/Database/Schema - From 3c275de161ef4444a1242f9916d5a74a0ef2d68b Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 02:08:00 +0300 Subject: [PATCH 02/11] feat: mult-results --- WebFiori/Database/Connection.php | 8 +- WebFiori/Database/Database.php | 4 +- WebFiori/Database/MsSql/MSSQLConnection.php | 82 +++++-- WebFiori/Database/MySql/MySQLConnection.php | 152 ++++++------ .../Database/MsSql/MSSQLMultiResultTest.php | 217 ++++++++++++++++++ .../Database/MultiResultIntegrationTest.php | 117 ++++++++++ .../Database/MySql/MySQLMultiResultTest.php | 163 +++++++++++++ tests/phpunit.xml | 6 + tests/phpunit10.xml | 9 + 9 files changed, 656 insertions(+), 102 deletions(-) create mode 100644 tests/WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php create mode 100644 tests/WebFiori/Tests/Database/MultiResultIntegrationTest.php create mode 100644 tests/WebFiori/Tests/Database/MySql/MySQLMultiResultTest.php diff --git a/WebFiori/Database/Connection.php b/WebFiori/Database/Connection.php index d33864f5..a41f0faa 100644 --- a/WebFiori/Database/Connection.php +++ b/WebFiori/Database/Connection.php @@ -46,7 +46,7 @@ abstract class Connection { /** * The result set which contains fetched data. * - * @var ResultSet + * @var ResultSet|MultiResultSet */ private $resultSet; /** @@ -140,7 +140,7 @@ public function getLastQuery() { /** * Returns last result set. * - * @return ResultSet|null The result set. If the result set is not set, the + * @return ResultSet|MultiResultSet|null The result set. If the result set is not set, the * method will return null. * */ @@ -189,10 +189,10 @@ public function setLastQuery(AbstractQuery $query) { /** * Sets result set. * - * @param ResultSet $result An object that represents result set. + * @param ResultSet|MultiResultSet $result An object that represents result set. * */ - public function setResultSet(ResultSet $result) { + public function setResultSet(ResultSet|MultiResultSet $result) { $this->resultSet = $result; } } diff --git a/WebFiori/Database/Database.php b/WebFiori/Database/Database.php index 5b6d4c29..26d34a64 100644 --- a/WebFiori/Database/Database.php +++ b/WebFiori/Database/Database.php @@ -334,7 +334,7 @@ public function enablePerformanceMonitoring(): void { *
  • An error has occurred while executing the query.
  • * * - * @return ResultSet|null If the last executed query was a select, show or + * @return ResultSet|MultiRsultSet|null If the last executed query was a select, show or * describe query, the method will return an object of type 'ResultSet' that * holds fetched records. Other than that, the method will return null. * @@ -447,7 +447,7 @@ public function getLastQuery() : string { * Returns the last result set which was generated from executing a query such * as a 'select' query. * - * @return ResultSet|null The last result set. If no result set is available, + * @return ResultSet|MultiResultSet|null The last result set. If no result set is available, * the method will return null. */ public function getLastResultSet() { diff --git a/WebFiori/Database/MsSql/MSSQLConnection.php b/WebFiori/Database/MsSql/MSSQLConnection.php index 1bc91dfc..22753279 100644 --- a/WebFiori/Database/MsSql/MSSQLConnection.php +++ b/WebFiori/Database/MsSql/MSSQLConnection.php @@ -13,6 +13,7 @@ use WebFiori\Database\AbstractQuery; use WebFiori\Database\Connection; +use WebFiori\Database\MultiResultSet; use WebFiori\Database\ConnectionInfo; use WebFiori\Database\DatabaseException; use WebFiori\Database\ResultSet; @@ -235,51 +236,84 @@ private function runInsertQuery() { return $this->checkInsertOrUpdateResult($r); } private function runOtherQuery() { - $sql = $this->getLastQuery()->getQuery(); - $queryBulder = $this->getLastQuery(); - - $r = sqlsrv_query($this->link, $sql, $queryBulder->getBindings()); + $query = $this->getLastQuery(); + $sql = $query->getQuery(); + $r = sqlsrv_query($this->link, $sql, $query->getBindings()); if (!is_resource($r)) { $this->setSqlErr(); - return false; + } + + // Collect all result sets + $allResults = []; + + // First result set + if (sqlsrv_has_rows($r)) { + $data = []; + while ($row = sqlsrv_fetch_array($r, SQLSRV_FETCH_ASSOC)) { + $data[] = $row; + } + $allResults[] = $data; } else { - - - if (sqlsrv_has_rows($r)) { - $data = []; - while ($row = sqlsrv_fetch_array($r, SQLSRV_FETCH_ASSOC)) { - $data[] = $row; - } - $this->setResultSet(new ResultSet($data)); + $allResults[] = []; + } + + // Additional result sets + while (sqlsrv_next_result($r)) { + $data = []; + while ($row = sqlsrv_fetch_array($r, SQLSRV_FETCH_ASSOC)) { + $data[] = $row; } - + $allResults[] = $data; + } + + // Set result + if (count($allResults) > 1) { + $this->setResultSet(new MultiResultSet($allResults)); + } else { + $this->setResultSet(new ResultSet($allResults[0])); } return true; } private function runSelectQuery() { - $queryBulder = $this->getLastQuery(); - $sql = $queryBulder->getQuery(); + $query = $this->getLastQuery(); + $sql = $query->getQuery(); + $r = sqlsrv_query($this->link, $sql, $query->getBindings()); - $r = sqlsrv_query($this->link, $sql, $queryBulder->getBindings()); + if (!is_resource($r)) { + $this->setSqlErr(); + return false; + } + // Collect all result sets + $allResults = []; + + // First result set + $data = []; + while ($row = sqlsrv_fetch_array($r, SQLSRV_FETCH_ASSOC)) { + $data[] = $row; + } + $allResults[] = $data; - if (is_resource($r)) { + // Additional result sets + while (sqlsrv_next_result($r)) { $data = []; - while ($row = sqlsrv_fetch_array($r, SQLSRV_FETCH_ASSOC)) { $data[] = $row; } - $this->setResultSet(new ResultSet($data)); + $allResults[] = $data; + } - return true; + // Set result + if (count($allResults) > 1) { + $this->setResultSet(new MultiResultSet($allResults)); } else { - $this->setSqlErr(); - - return false; + $this->setResultSet(new ResultSet($allResults[0])); } + + return true; } private function runUpdateQuery() { $params = $this->getLastQuery()->getBindings(); diff --git a/WebFiori/Database/MySql/MySQLConnection.php b/WebFiori/Database/MySql/MySQLConnection.php index 104a5b8f..288c690a 100644 --- a/WebFiori/Database/MySql/MySQLConnection.php +++ b/WebFiori/Database/MySql/MySQLConnection.php @@ -13,6 +13,7 @@ use mysqli; use mysqli_stmt; +use WebFiori\Database\MultiResultSet; use WebFiori\Database\AbstractQuery; use WebFiori\Database\Connection; use WebFiori\Database\ConnectionInfo; @@ -273,110 +274,117 @@ private function runInsertQuery() { return $this->chechInsertOrUpdateResult($r); } private function runOtherQuery() { - $query = $this->getLastQuery()->getQuery(); - $sql = $this->getLastQuery()->getQuery(); $params = $this->getLastQuery()->getBindings()['bind']; $values = array_merge($this->getLastQuery()->getBindings()['values']); - $r = false; - + // Execute query if (count($values) != 0 && !empty($params)) { - // Count the number of ? placeholders in the SQL - $paramCount = substr_count($sql, '?'); - - // Only use prepared statements if parameter counts match - if ($paramCount == count($values) && strlen($params) == count($values)) { - $sqlStatement = mysqli_prepare($this->link, $sql); - $sqlStatement->bind_param($params, ...$values); - $sqlStatement->execute(); - $r = $sqlStatement->get_result(); - - if ($sqlStatement) { - mysqli_stmt_close($sqlStatement); - } - } else { - // Fall back to regular query if there's a mismatch - $r = mysqli_query($this->link, $sql); - } + $stmt = mysqli_prepare($this->link, $sql); + mysqli_stmt_bind_param($stmt, $params, ...$values); + mysqli_stmt_execute($stmt); + $r = mysqli_stmt_get_result($stmt); + mysqli_stmt_close($stmt); } else { - $r = mysqli_query($this->link, $query); + $r = mysqli_query($this->link, $sql); } if (!$r) { $this->setErrMessage($this->link->error); $this->setErrCode($this->link->errno); return false; + } + + // Collect all result sets + $allResults = []; + + // First result set + if (is_object($r) && method_exists($r, 'fetch_assoc')) { + $rows = mysqli_fetch_all($r, MYSQLI_ASSOC); + $allResults[] = $rows; + mysqli_free_result($r); } else { - $this->setErrMessage('NO ERRORS'); - $this->setErrCode(0); - $this->getLastQuery()->setIsBlobInsertOrUpdate(false); - - // Handle result sets - - if (gettype($r) == 'object' && method_exists($r, 'fetch_assoc')) { - $data = []; - while ($row = $r->fetch_assoc()) { - $data[] = $row; - } - $this->setResultSet(new ResultSet($data)); - mysqli_free_result($r); - } - - // Clean up any additional result sets (for stored procedures) - while (mysqli_more_results($this->link)) { - mysqli_next_result($this->link); - if ($result = mysqli_store_result($this->link)) { - mysqli_free_result($result); - } + $allResults[] = []; + } + + // Additional result sets + while (mysqli_more_results($this->link)) { + mysqli_next_result($this->link); + if ($result = mysqli_store_result($this->link)) { + $rows = mysqli_fetch_all($result, MYSQLI_ASSOC); + $allResults[] = $rows; + mysqli_free_result($result); } } + // Set result + if (count($allResults) > 1) { + $this->setResultSet(new MultiResultSet($allResults)); + } else { + $this->setResultSet(new ResultSet($allResults[0])); + } + + $this->setErrCode(0); return true; } + /** + * Get the mysqli link for testing purposes. + * + * @return mysqli The mysqli connection link + */ + public function getMysqliLink() { + return $this->link; + } + private function runSelectQuery() { $sql = $this->getLastQuery()->getQuery(); $params = $this->getLastQuery()->getBindings()['bind']; $values = array_merge($this->getLastQuery()->getBindings()['values']); + // Execute query if (count($values) != 0) { - $sqlStatement = mysqli_prepare($this->link, $sql); - $sqlStatement->bind_param($params, ...$values); - $r = $sqlStatement->execute(); - - if ($r) { - $r = mysqli_stmt_get_result($sqlStatement); - } - - if ($sqlStatement) { - mysqli_stmt_close($sqlStatement); - } + $stmt = mysqli_prepare($this->link, $sql); + mysqli_stmt_bind_param($stmt, $params, ...$values); + mysqli_stmt_execute($stmt); + $r = mysqli_stmt_get_result($stmt); + mysqli_stmt_close($stmt); } else { - $r = mysqli_query($this->link, $this->getLastQuery()->getQuery()); + $r = mysqli_query($this->link, $sql); } + if (!$r) { + $this->setErrMessage($this->link->error); + $this->setErrCode($this->link->errno); + return false; + } - - if ($r) { - $this->setErrCode(0); - $rows = []; - - if (function_exists('mysqli_fetch_all')) { - $rows = mysqli_fetch_all($r, MYSQLI_ASSOC); - } else { - while ($row = $r->fetch_assoc()) { - $rows[] = $row; - } + // Collect all result sets + $allResults = []; + + // First result set + $rows = mysqli_fetch_all($r, MYSQLI_ASSOC); + $allResults[] = $rows; + mysqli_free_result($r); + + // Additional result sets + while (mysqli_more_results($this->link)) { + mysqli_next_result($this->link); + if ($result = mysqli_store_result($this->link)) { + $rows = mysqli_fetch_all($result, MYSQLI_ASSOC); + $allResults[] = $rows; + mysqli_free_result($result); } - $this->setResultSet(new ResultSet($rows)); + } - return true; + // Set result + if (count($allResults) > 1) { + $this->setResultSet(new MultiResultSet($allResults)); } else { - $this->setErrMessage($this->link->error); - $this->setErrCode($this->link->errno); - - return false; + $this->setResultSet(new ResultSet($allResults[0])); } + + $this->setErrCode(0); + return true; } private function runUpdateQuery() { $sqlStatement = mysqli_prepare($this->link, $this->getLastQuery()->getQuery()); diff --git a/tests/WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php b/tests/WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php new file mode 100644 index 00000000..a2af3452 --- /dev/null +++ b/tests/WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php @@ -0,0 +1,217 @@ +raw("SELECT 1 as num, 'first' as label; SELECT 2 as num, 'second' as label")->execute(); + + if ($result instanceof MultiResultSet) { + $this->assertEquals(2, $result->count()); + + // Check first result set + $firstResult = $result->getResultSet(0); + $this->assertInstanceOf(ResultSet::class, $firstResult); + $this->assertEquals(1, $firstResult->getRowsCount()); + $this->assertEquals(1, $firstResult->getRows()[0]['num']); + $this->assertEquals('first', $firstResult->getRows()[0]['label']); + + // Check second result set + $secondResult = $result->getResultSet(1); + $this->assertInstanceOf(ResultSet::class, $secondResult); + $this->assertEquals(1, $secondResult->getRowsCount()); + $this->assertEquals(2, $secondResult->getRows()[0]['num']); + $this->assertEquals('second', $secondResult->getRows()[0]['label']); + } else { + // If single result, verify it has data from first result set + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertGreaterThan(0, $result->getRowsCount()); + } + + } catch (\Exception $e) { + $this->markTestSkipped('MSSQL multi-select test failed: ' . $e->getMessage()); + } + } + + /** + * @test + */ + public function testStoredProcedureMultiResult() { + $schema = new MSSQLTestSchema(); + + try { + // Clean up first + $schema->raw("IF OBJECT_ID('GetMultiResults', 'P') IS NOT NULL DROP PROCEDURE GetMultiResults")->execute(); + + // Create stored procedure that returns multiple result sets + $createProc = "CREATE PROCEDURE GetMultiResults + AS + BEGIN + SELECT 'users' as table_name, 1 as count; + SELECT 'result' as status, 'success' as message; + END"; + $schema->raw($createProc)->execute(); + + // Execute stored procedure using our implementation + $result = $schema->raw("EXEC GetMultiResults")->execute(); + + if ($result instanceof MultiResultSet) { + $this->assertEquals(2, $result->count()); + + // Check first result set + $firstResult = $result->getResultSet(0); + $this->assertInstanceOf(ResultSet::class, $firstResult); + $this->assertEquals(1, $firstResult->getRowsCount()); + $this->assertEquals('users', $firstResult->getRows()[0]['table_name']); + $this->assertEquals(1, $firstResult->getRows()[0]['count']); + + // Check second result set + $secondResult = $result->getResultSet(1); + $this->assertInstanceOf(ResultSet::class, $secondResult); + $this->assertEquals(1, $secondResult->getRowsCount()); + $this->assertEquals('result', $secondResult->getRows()[0]['status']); + $this->assertEquals('success', $secondResult->getRows()[0]['message']); + } else { + // If single result, verify it has data from first result set + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertGreaterThan(0, $result->getRowsCount()); + } + + } catch (\Exception $e) { + $this->markTestSkipped('MSSQL stored procedure test failed: ' . $e->getMessage()); + } finally { + // Clean up + try { + $schema->raw("IF OBJECT_ID('GetMultiResults', 'P') IS NOT NULL DROP PROCEDURE GetMultiResults")->execute(); + } catch (\Exception $e) { + // Ignore cleanup errors + } + } + } + + /** + * @test + */ + public function testMultipleInsertWithSelect() { + $schema = new MSSQLTestSchema(); + + try { + // Create temp table for testing + $schema->raw("IF OBJECT_ID('tempdb..#TestMulti') IS NOT NULL DROP TABLE #TestMulti")->execute(); + $schema->raw("CREATE TABLE #TestMulti (id INT, name NVARCHAR(50))")->execute(); + + // Multi-statement: Insert data then select it + $result = $schema->raw(" + INSERT INTO #TestMulti VALUES (1, 'Test1'), (2, 'Test2'); + SELECT * FROM #TestMulti; + SELECT COUNT(*) as total FROM #TestMulti + ")->execute(); + + if ($result instanceof MultiResultSet) { + $this->assertGreaterThanOrEqual(2, $result->count()); + + // Should have at least the SELECT results + $selectResult = null; + $countResult = null; + + for ($i = 0; $i < $result->count(); $i++) { + $rs = $result->getResultSet($i); + if ($rs->getRowsCount() > 0) { + $firstRow = $rs->getRows()[0]; + if (isset($firstRow['id']) && isset($firstRow['name'])) { + $selectResult = $rs; + } elseif (isset($firstRow['total'])) { + $countResult = $rs; + } + } + } + + if ($selectResult) { + $this->assertEquals(2, $selectResult->getRowsCount()); + $this->assertEquals('Test1', $selectResult->getRows()[0]['name']); + } + + if ($countResult) { + $this->assertEquals(2, $countResult->getRows()[0]['total']); + } + } else { + // Single result - should still have some data + $this->assertInstanceOf(ResultSet::class, $result); + } + + } catch (\Exception $e) { + $this->markTestSkipped('MSSQL multi-insert test failed: ' . $e->getMessage()); + } finally { + // Clean up + try { + $schema->raw("IF OBJECT_ID('tempdb..#TestMulti') IS NOT NULL DROP TABLE #TestMulti")->execute(); + } catch (\Exception $e) { + // Ignore cleanup errors + } + } + } + + /** + * @test + */ + public function testSingleQueryStillWorksAsResultSet() { + $schema = new MSSQLTestSchema(); + + // Execute single query - should return ResultSet, not MultiResultSet + $result = $schema->raw("SELECT 'test' as value")->execute(); + + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertNotInstanceOf(MultiResultSet::class, $result); + $this->assertEquals(1, $result->getRowsCount()); + $this->assertEquals('test', $result->getRows()[0]['value']); + } + + /** + * @test + */ + public function testBackwardCompatibilityMaintained() { + $schema = new MSSQLTestSchema(); + + // Test various single queries still work + $result1 = $schema->raw("SELECT 1 as num")->execute(); + $this->assertInstanceOf(ResultSet::class, $result1); + $this->assertEquals(1, $result1->getRows()[0]['num']); + + $result2 = $schema->raw("SELECT 'hello' as greeting, 'world' as target")->execute(); + $this->assertInstanceOf(ResultSet::class, $result2); + $this->assertEquals('hello', $result2->getRows()[0]['greeting']); + $this->assertEquals('world', $result2->getRows()[0]['target']); + } + + /** + * @test + */ + public function testEmptyResultSetHandling() { + $schema = new MSSQLTestSchema(); + + // Query that returns no rows + $result = $schema->raw("SELECT 1 as num WHERE 1=0")->execute(); + + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertEquals(0, $result->getRowsCount()); + $this->assertEquals([], $result->getRows()); + } +} diff --git a/tests/WebFiori/Tests/Database/MultiResultIntegrationTest.php b/tests/WebFiori/Tests/Database/MultiResultIntegrationTest.php new file mode 100644 index 00000000..7be79773 --- /dev/null +++ b/tests/WebFiori/Tests/Database/MultiResultIntegrationTest.php @@ -0,0 +1,117 @@ +raw("DROP PROCEDURE IF EXISTS TestMultiResult")->execute(); + $schema->raw("CREATE PROCEDURE TestMultiResult() BEGIN SELECT 1 as id, 'first' as name; SELECT 2 as id, 'second' as name; END")->execute(); + + // Execute the stored procedure + $result = $schema->raw("CALL TestMultiResult()")->execute(); + + // Verify we get a MultiResultSet when multiple results are returned + if ($result instanceof MultiResultSet) { + $this->assertEquals(2, $result->count()); + $this->assertEquals('first', $result->getResultSet(0)->getRows()[0]['name']); + $this->assertEquals('second', $result->getResultSet(1)->getRows()[0]['name']); + + // Test iteration + $names = []; + foreach ($result as $resultSet) { + if ($resultSet->getRowsCount() > 0) { + $names[] = $resultSet->getRows()[0]['name']; + } + } + $this->assertEquals(['first', 'second'], $names); + } else { + // If single result, just verify it's a ResultSet + $this->assertInstanceOf(ResultSet::class, $result); + } + + } catch (\Exception $e) { + $this->markTestSkipped('MySQL stored procedure test failed: ' . $e->getMessage()); + } finally { + try { + $schema->raw("DROP PROCEDURE IF EXISTS TestMultiResult")->execute(); + } catch (\Exception $e) { + // Ignore cleanup errors + } + } + } + + /** + * @test + */ + public function testMSSQLStoredProcedureReturnsMultiResultSet() { + $schema = new MSSQLTestSchema(); + + try { + // Clean up and create a simple stored procedure + $schema->raw("IF OBJECT_ID('TestMultiResult', 'P') IS NOT NULL DROP PROCEDURE TestMultiResult")->execute(); + $schema->raw("CREATE PROCEDURE TestMultiResult AS BEGIN SELECT 1 as id, 'first' as name; SELECT 2 as id, 'second' as name; END")->execute(); + + // Execute the stored procedure + $result = $schema->raw("EXEC TestMultiResult")->execute(); + + // Verify we get a MultiResultSet when multiple results are returned + if ($result instanceof MultiResultSet) { + $this->assertEquals(2, $result->count()); + $this->assertEquals('first', $result->getResultSet(0)->getRows()[0]['name']); + $this->assertEquals('second', $result->getResultSet(1)->getRows()[0]['name']); + + // Test getTotalRecordCount + $this->assertEquals(2, $result->getTotalRecordCount()); + } else { + // If single result, just verify it's a ResultSet + $this->assertInstanceOf(ResultSet::class, $result); + } + + } catch (\Exception $e) { + $this->markTestSkipped('MSSQL stored procedure test failed: ' . $e->getMessage()); + } finally { + try { + $schema->raw("IF OBJECT_ID('TestMultiResult', 'P') IS NOT NULL DROP PROCEDURE TestMultiResult")->execute(); + } catch (\Exception $e) { + // Ignore cleanup errors + } + } + } + + /** + * @test + */ + public function testSingleQueryStillReturnsSingleResultSet() { + $mysqlSchema = new MySQLTestSchema(); + $mssqlSchema = new MSSQLTestSchema(); + + // Test MySQL single query + $mysqlResult = $mysqlSchema->raw("SELECT 'single' as type")->execute(); + $this->assertInstanceOf(ResultSet::class, $mysqlResult); + $this->assertNotInstanceOf(MultiResultSet::class, $mysqlResult); + + // Test MSSQL single query + $mssqlResult = $mssqlSchema->raw("SELECT 'single' as type")->execute(); + $this->assertInstanceOf(ResultSet::class, $mssqlResult); + $this->assertNotInstanceOf(MultiResultSet::class, $mssqlResult); + } +} diff --git a/tests/WebFiori/Tests/Database/MySql/MySQLMultiResultTest.php b/tests/WebFiori/Tests/Database/MySql/MySQLMultiResultTest.php new file mode 100644 index 00000000..afa6d3ae --- /dev/null +++ b/tests/WebFiori/Tests/Database/MySql/MySQLMultiResultTest.php @@ -0,0 +1,163 @@ +getConnection(); + $link = $connection->getMysqliLink(); + + // Execute multi-select query using mysqli_multi_query + $sql = "SELECT 1 as num, 'first' as label; SELECT 2 as num, 'second' as label"; + $success = mysqli_multi_query($link, $sql); + + if ($success) { + // Collect all results manually to verify our implementation would work + $allResults = []; + + // First result + if ($result = mysqli_store_result($link)) { + $rows = mysqli_fetch_all($result, MYSQLI_ASSOC); + $allResults[] = $rows; + mysqli_free_result($result); + } + + // Additional results + while (mysqli_more_results($link)) { + mysqli_next_result($link); + if ($result = mysqli_store_result($link)) { + $rows = mysqli_fetch_all($result, MYSQLI_ASSOC); + $allResults[] = $rows; + mysqli_free_result($result); + } + } + + // Verify we got multiple result sets + $this->assertEquals(2, count($allResults)); + $this->assertEquals(1, $allResults[0][0]['num']); + $this->assertEquals('first', $allResults[0][0]['label']); + $this->assertEquals(2, $allResults[1][0]['num']); + $this->assertEquals('second', $allResults[1][0]['label']); + } else { + $this->markTestSkipped('MySQL multi-query not supported or enabled'); + } + } + + /** + * @test + */ + public function testStoredProcedureMultiResult() { + $schema = new MySQLTestSchema(); + + try { + // Clean up first + $schema->raw("DROP PROCEDURE IF EXISTS GetMultiResults")->execute(); + + // Create stored procedure that returns multiple result sets + $createProc = "CREATE PROCEDURE GetMultiResults() + BEGIN + SELECT 'users' as table_name, 1 as count; + SELECT 'result' as status, 'success' as message; + END"; + $schema->raw($createProc)->execute(); + + // Execute stored procedure using our implementation + $result = $schema->raw("CALL GetMultiResults()")->execute(); + + if ($result instanceof MultiResultSet) { + $this->assertEquals(2, $result->count()); + + // Check first result set + $firstResult = $result->getResultSet(0); + $this->assertInstanceOf(ResultSet::class, $firstResult); + $this->assertEquals(1, $firstResult->getRowsCount()); + $this->assertEquals('users', $firstResult->getRows()[0]['table_name']); + $this->assertEquals(1, $firstResult->getRows()[0]['count']); + + // Check second result set + $secondResult = $result->getResultSet(1); + $this->assertInstanceOf(ResultSet::class, $secondResult); + $this->assertEquals(1, $secondResult->getRowsCount()); + $this->assertEquals('result', $secondResult->getRows()[0]['status']); + $this->assertEquals('success', $secondResult->getRows()[0]['message']); + } else { + // If single result, verify it has data from first result set + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertGreaterThan(0, $result->getRowsCount()); + } + + } catch (\Exception $e) { + $this->markTestSkipped('Stored procedure test failed: ' . $e->getMessage()); + } finally { + // Clean up + try { + $schema->raw("DROP PROCEDURE IF EXISTS GetMultiResults")->execute(); + } catch (\Exception $e) { + // Ignore cleanup errors + } + } + } + + /** + * @test + */ + public function testSingleQueryStillWorksAsResultSet() { + $schema = new MySQLTestSchema(); + + // Execute single query - should return ResultSet, not MultiResultSet + $result = $schema->raw("SELECT 'test' as value")->execute(); + + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertNotInstanceOf(MultiResultSet::class, $result); + $this->assertEquals(1, $result->getRowsCount()); + $this->assertEquals('test', $result->getRows()[0]['value']); + } + + /** + * @test + */ + public function testBackwardCompatibilityMaintained() { + $schema = new MySQLTestSchema(); + + // Test various single queries still work + $result1 = $schema->raw("SELECT 1 as num")->execute(); + $this->assertInstanceOf(ResultSet::class, $result1); + $this->assertEquals(1, $result1->getRows()[0]['num']); + + $result2 = $schema->raw("SELECT 'hello' as greeting, 'world' as target")->execute(); + $this->assertInstanceOf(ResultSet::class, $result2); + $this->assertEquals('hello', $result2->getRows()[0]['greeting']); + $this->assertEquals('world', $result2->getRows()[0]['target']); + } + + /** + * @test + */ + public function testEmptyResultSetHandling() { + $schema = new MySQLTestSchema(); + + // Query that returns no rows + $result = $schema->raw("SELECT 1 as num WHERE 1=0")->execute(); + + $this->assertInstanceOf(ResultSet::class, $result); + $this->assertEquals(0, $result->getRowsCount()); + $this->assertEquals([], $result->getRows()); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 6c836d70..86072381 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -20,9 +20,15 @@ ./WebFiori/Tests/Database/MySql + + ./WebFiori/Tests/Database/MySql/MySQLMultiResultTest.php + ./WebFiori/Tests/Database/MsSql + + ./WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php + ./WebFiori/Tests/Database/Schema diff --git a/tests/phpunit10.xml b/tests/phpunit10.xml index e8dea8ee..613e258e 100644 --- a/tests/phpunit10.xml +++ b/tests/phpunit10.xml @@ -15,9 +15,18 @@ ./WebFiori/Tests/Database/MySql + + ./WebFiori/Tests/Database/MySql/MySQLMultiResultTest.php + ./WebFiori/Tests/Database/MsSql + + ./WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php + + + ./WebFiori/Tests/Database/MultiResultIntegrationTest.php + ./WebFiori/Tests/Database/Schema From 46de7ba4e138c03412645ad64a622eed1915b699 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 17:07:19 +0300 Subject: [PATCH 03/11] Update Connection.php --- WebFiori/Database/Connection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WebFiori/Database/Connection.php b/WebFiori/Database/Connection.php index a41f0faa..7da4ff9e 100644 --- a/WebFiori/Database/Connection.php +++ b/WebFiori/Database/Connection.php @@ -121,10 +121,10 @@ public function getLastErrCode() { /** * Returns the last message at which that was generated by executing a query. * - * @return string The last message at which that was generated by executing a query. + * @return string|null The last message at which that was generated by executing a query. * */ - public function getLastErrMessage() : string { + public function getLastErrMessage() : ?string { return $this->lastErrMsg; } /** @@ -158,7 +158,7 @@ public abstract function rollBack(?string $name = null); * query object. If set, it should execute it. * */ - public abstract function runQuery(?AbstractQuery $query = null); + public abstract function runQuery(?AbstractQuery $query = null) : bool; /** * Sets error code at which that was generated by executing last query. * From 637f9db2385ee2a32e8bcca508b6f40eb9756b4f Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 17:07:28 +0300 Subject: [PATCH 04/11] Update MSSQLConnection.php --- WebFiori/Database/MsSql/MSSQLConnection.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/WebFiori/Database/MsSql/MSSQLConnection.php b/WebFiori/Database/MsSql/MSSQLConnection.php index 22753279..3491849c 100644 --- a/WebFiori/Database/MsSql/MSSQLConnection.php +++ b/WebFiori/Database/MsSql/MSSQLConnection.php @@ -177,13 +177,14 @@ public function rollBack(?string $name = null) { * Execute MSSQL query. * * @param AbstractQuery $query A query builder that has the generated MSSQL - * query. + /** + * Execute a query and return execution status. * - * @return bool If the query successfully executed, the method will return - * true. Other than that, the method will return false. + * @param AbstractQuery|null $query The query to execute. If null, uses the last set query. * + * @return bool True if the query executed successfully, false if there were errors. */ - public function runQuery(?AbstractQuery $query = null) { + public function runQuery(?AbstractQuery $query = null): bool { $this->addToExecuted($query->getQuery()); $this->setLastQuery($query); From 90307c40147dbd81387c8a05e2bbcb33af82cb8d Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 17:07:39 +0300 Subject: [PATCH 05/11] Update MySQLConnection.php --- WebFiori/Database/MySql/MySQLConnection.php | 44 ++++++++++++--------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/WebFiori/Database/MySql/MySQLConnection.php b/WebFiori/Database/MySql/MySQLConnection.php index 288c690a..b306a62a 100644 --- a/WebFiori/Database/MySql/MySQLConnection.php +++ b/WebFiori/Database/MySql/MySQLConnection.php @@ -172,12 +172,14 @@ public function rollBack(?string $name = null) { * * @param AbstractQuery $query A query builder that has the generated MySQL * query. + /** + * Execute a query and return execution status. * - * @return bool If the query successfully executed, the method will return - * true. Other than that, the method will return true. + * @param AbstractQuery|null $query The query to execute. If null, uses the last set query. * + * @return bool True if the query executed successfully, false if there were errors. */ - public function runQuery(?AbstractQuery $query = null) { + public function runQuery(?AbstractQuery $query = null): bool { $this->setLastQuery($query); if ($query instanceof MySQLQuery && !$query->isBlobInsertOrUpdate() && !$this->isCollationSet) { @@ -202,21 +204,23 @@ public function runQuery(?AbstractQuery $query = null) { $this->addToExecuted($query->getQuery()); try { + $result = false; if ($qType == 'insert') { - return $this->runInsertQuery(); + $result = $this->runInsertQuery(); } else if ($qType == 'update') { - return $this->runUpdateQuery(); + $result = $this->runUpdateQuery(); } else if ($qType == 'select' || $qType == 'show' || $qType == 'describe') { - return $this->runSelectQuery(); + $result = $this->runSelectQuery(); } else { - return $this->runOtherQuery(); + $result = $this->runOtherQuery(); } + $query->resetBinding(); + return $result; } catch (\Exception $ex) { $this->setErrCode($ex->getCode()); $this->setErrMessage($ex->getMessage()); throw new DatabaseException($ex->getCode().' - '.$ex->getMessage(), $ex->getCode(), $this->getLastQuery()->getQuery(), $ex); } - $query->resetBinding(); } private function chechInsertOrUpdateResult($r) { $retVal = false; @@ -277,19 +281,25 @@ private function runOtherQuery() { $sql = $this->getLastQuery()->getQuery(); $params = $this->getLastQuery()->getBindings()['bind']; $values = array_merge($this->getLastQuery()->getBindings()['values']); - + $successExec = false; + $r = null; // Execute query if (count($values) != 0 && !empty($params)) { - $stmt = mysqli_prepare($this->link, $sql); - mysqli_stmt_bind_param($stmt, $params, ...$values); - mysqli_stmt_execute($stmt); - $r = mysqli_stmt_get_result($stmt); - mysqli_stmt_close($stmt); + $paramCount = substr_count($sql, '?'); + if ($paramCount == count($values) && strlen($params) == count($values)) { + $stmt = mysqli_prepare($this->link, $sql); + mysqli_stmt_bind_param($stmt, $params, ...$values); + $successExec = mysqli_stmt_execute($stmt); + $r = mysqli_stmt_get_result($stmt); + mysqli_stmt_close($stmt); + } else { + $r = mysqli_query($this->link, $sql); + } } else { $r = mysqli_query($this->link, $sql); } - if (!$r) { + if (($r === null || $r === false) && !$successExec) { $this->setErrMessage($this->link->error); $this->setErrCode($this->link->errno); return false; @@ -303,8 +313,6 @@ private function runOtherQuery() { $rows = mysqli_fetch_all($r, MYSQLI_ASSOC); $allResults[] = $rows; mysqli_free_result($r); - } else { - $allResults[] = []; } // Additional result sets @@ -320,7 +328,7 @@ private function runOtherQuery() { // Set result if (count($allResults) > 1) { $this->setResultSet(new MultiResultSet($allResults)); - } else { + } else if (count($allResults) == 1) { $this->setResultSet(new ResultSet($allResults[0])); } From 5e319f46a1267e9573f05209d68043b444b26d33 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 18:41:54 +0300 Subject: [PATCH 06/11] Update SchemaAdvancedTest.php --- tests/WebFiori/Tests/Database/Schema/SchemaAdvancedTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/WebFiori/Tests/Database/Schema/SchemaAdvancedTest.php b/tests/WebFiori/Tests/Database/Schema/SchemaAdvancedTest.php index b7da8274..8a391fa5 100644 --- a/tests/WebFiori/Tests/Database/Schema/SchemaAdvancedTest.php +++ b/tests/WebFiori/Tests/Database/Schema/SchemaAdvancedTest.php @@ -35,6 +35,11 @@ public function testApplyAndRollbackSequence() { try { $runner = new SchemaRunner($this->getConnectionInfo()); $runner->registerAll([TestMigration::class, TestSeeder::class]); + try { + $runner->dropSchemaTable(); + } catch (DatabaseException $ex) { + // Table might not exist, ignore + } $runner->createSchemaTable(); // Just test that we have changes registered From 79c2e308bf8c92cf3cb6808494d7378a0d6145d2 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 18:42:16 +0300 Subject: [PATCH 07/11] Update Connection.php --- WebFiori/Database/Connection.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WebFiori/Database/Connection.php b/WebFiori/Database/Connection.php index 7da4ff9e..16b9cbc0 100644 --- a/WebFiori/Database/Connection.php +++ b/WebFiori/Database/Connection.php @@ -27,7 +27,7 @@ abstract class Connection { private $executedQueries; /** * - * @var string + * @var string|int * */ private $lastErrCode; @@ -62,6 +62,7 @@ abstract class Connection { public function __construct(ConnectionInfo $connInfo) { $this->connParams = $connInfo; $this->executedQueries = []; + $this->lastErrCode = 0; if (!$this->connect()) { throw new DatabaseException('Unable to connect to database: '.$this->getLastErrCode().' - '.$this->getLastErrMessage(), $this->getLastErrCode()); @@ -115,7 +116,7 @@ public function getExecutedQueries() : array { * @return int|string Last error code at which that was generated by executing last query. * */ - public function getLastErrCode() { + public function getLastErrCode() : string|int { return $this->lastErrCode; } /** @@ -165,7 +166,7 @@ public abstract function runQuery(?AbstractQuery $query = null) : bool; * @param int|string $code An integer value or any code that represents error code. * */ - public function setErrCode($code) { + public function setErrCode(string|int $code) { $this->lastErrCode = $code; } /** From 3705e5d97782fb3318c665cff289c433c65ad682 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 18:43:09 +0300 Subject: [PATCH 08/11] chore: Configurations Update --- .gitignore | 1 + tests/phpunit.xml | 1 + tests/phpunit10.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2fb5d744..74b3132e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ tests/webfiori/database/tests/mysql/UserEntity.php *.Identifier /tests/.phpunit.cache .php-cs-fixer.cache +/.vscode diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 86072381..4e6dad03 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -25,6 +25,7 @@ ./WebFiori/Tests/Database/MsSql + ./WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php ./WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php diff --git a/tests/phpunit10.xml b/tests/phpunit10.xml index 613e258e..ca2c7bd0 100644 --- a/tests/phpunit10.xml +++ b/tests/phpunit10.xml @@ -20,6 +20,7 @@ ./WebFiori/Tests/Database/MsSql + ./WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php ./WebFiori/Tests/Database/MsSql/MSSQLMultiResultTest.php From b7652c3b55427efb11d10ef4c5bb48452d2e3ec0 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 19:04:28 +0300 Subject: [PATCH 09/11] Update Connection.php --- WebFiori/Database/Connection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/WebFiori/Database/Connection.php b/WebFiori/Database/Connection.php index 16b9cbc0..83e8bd8a 100644 --- a/WebFiori/Database/Connection.php +++ b/WebFiori/Database/Connection.php @@ -63,6 +63,7 @@ public function __construct(ConnectionInfo $connInfo) { $this->connParams = $connInfo; $this->executedQueries = []; $this->lastErrCode = 0; + $this->lastErrMsg = 'NO ERROR'; if (!$this->connect()) { throw new DatabaseException('Unable to connect to database: '.$this->getLastErrCode().' - '.$this->getLastErrMessage(), $this->getLastErrCode()); From f08cfa80f67dc987b9077741fea693e0572eb007 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 19:04:50 +0300 Subject: [PATCH 10/11] Update MSSQLConnection.php --- WebFiori/Database/MsSql/MSSQLConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebFiori/Database/MsSql/MSSQLConnection.php b/WebFiori/Database/MsSql/MSSQLConnection.php index 3491849c..6cd65179 100644 --- a/WebFiori/Database/MsSql/MSSQLConnection.php +++ b/WebFiori/Database/MsSql/MSSQLConnection.php @@ -226,7 +226,7 @@ private function runInsertQuery() { $sql = $this->getLastQuery()->getQuery(); if ($insertBuilder === null) { - return false; + return $this->runOtherQuery(); } $params = $insertBuilder->getQueryParams(); From 93768f34acd4f2287c09bae63d6b041e53738878 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 4 Nov 2025 19:05:09 +0300 Subject: [PATCH 11/11] Update MySQLConnection.php --- WebFiori/Database/MySql/MySQLConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebFiori/Database/MySql/MySQLConnection.php b/WebFiori/Database/MySql/MySQLConnection.php index b306a62a..a6085b9a 100644 --- a/WebFiori/Database/MySql/MySQLConnection.php +++ b/WebFiori/Database/MySql/MySQLConnection.php @@ -252,7 +252,7 @@ private function runInsertQuery() { $insertBuilder = $this->getLastQuery()->getInsertBuilder(); if ($insertBuilder === null) { - return false; + return $this->runOtherQuery(); } $sqlStatement = mysqli_prepare($this->link, $insertBuilder->getQuery());