mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge branch '5.0' into 5
This commit is contained in:
commit
82e96068d2
@ -116,6 +116,12 @@ class MySQLStatement extends Query
|
||||
}
|
||||
yield $row;
|
||||
}
|
||||
|
||||
// Check for the method first since $this->statement isn't strongly typed
|
||||
if (method_exists($this->statement, 'data_seek')) {
|
||||
// Reset so the query can be iterated over again
|
||||
$this->statement->data_seek(0);
|
||||
}
|
||||
}
|
||||
|
||||
public function numRecords()
|
||||
|
@ -181,13 +181,20 @@ class MySQLiConnector extends DBConnector
|
||||
{
|
||||
$this->beforeQuery($sql);
|
||||
|
||||
$error = null;
|
||||
$handle = null;
|
||||
|
||||
try {
|
||||
// Benchmark query
|
||||
$handle = $this->dbConn->query($sql ?? '', MYSQLI_STORE_RESULT);
|
||||
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
$error = $e->getMessage();
|
||||
} finally {
|
||||
if (!$handle || $this->dbConn->error) {
|
||||
$this->databaseError($this->getLastError(), $errorLevel, $sql);
|
||||
$this->databaseError($error ?? $this->getLastError(), $errorLevel, $sql);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Some non-select queries return true on success
|
||||
return new MySQLQuery($this, $handle);
|
||||
|
@ -180,6 +180,9 @@ class SearchContext
|
||||
*/
|
||||
private function prepareQuery($sort, $limit, ?DataList $existingQuery): DataList
|
||||
{
|
||||
if ($limit === false) {
|
||||
$limit = null;
|
||||
}
|
||||
$query = null;
|
||||
if ($existingQuery) {
|
||||
if (!($existingQuery instanceof DataList)) {
|
||||
|
@ -312,6 +312,33 @@ class DatabaseTest extends SapphireTest
|
||||
$this->assertEquals($inputData, $select->execute()->map());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that repeated abstracted iteration of a query result with predicates returns all records.
|
||||
*/
|
||||
public function testRepeatedIterationWithPredicates()
|
||||
{
|
||||
$inputData = ['one', 'two', 'three', 'four'];
|
||||
|
||||
foreach ($inputData as $i => $text) {
|
||||
$x = new MyObject();
|
||||
$x->MyField = $text;
|
||||
$x->MyInt = $i;
|
||||
$x->write();
|
||||
}
|
||||
|
||||
// Note that by including a WHERE statement with predicates
|
||||
// with MySQL the result is in a MySQLStatement object rather than a MySQLQuery object.
|
||||
$select = SQLSelect::create(
|
||||
['"MyInt"', '"MyField"'],
|
||||
'"DatabaseTest_MyObject"',
|
||||
['MyInt IN (?,?,?,?,?)' => [0,1,2,3,4]],
|
||||
['"MyInt"']
|
||||
)->execute();
|
||||
|
||||
$this->assertEquals($inputData, $select->map());
|
||||
$this->assertEquals($inputData, $select->map());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that stopping iteration part-way through produces predictable results
|
||||
* on a subsequent iteration.
|
||||
@ -345,4 +372,45 @@ class DatabaseTest extends SapphireTest
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that stopping iteration part-way through produces predictable results even when we're using predicates
|
||||
* on a subsequent iteration.
|
||||
* This test is here to ensure consistency between implementations (e.g. mysql vs postgres, etc)
|
||||
*/
|
||||
public function testRepeatedPartialIterationWithPredicates()
|
||||
{
|
||||
$inputData = ['one', 'two', 'three', 'four'];
|
||||
|
||||
foreach ($inputData as $i => $text) {
|
||||
$x = new MyObject();
|
||||
$x->MyField = $text;
|
||||
$x->MyInt = $i;
|
||||
$x->write();
|
||||
}
|
||||
|
||||
// Note that by including a WHERE statement with predicates
|
||||
// with MySQL the result is in a MySQLStatement object rather than a MySQLQuery object.
|
||||
$query = SQLSelect::create(
|
||||
['"MyInt"', '"MyField"'],
|
||||
'"DatabaseTest_MyObject"',
|
||||
['MyInt IN (?,?,?,?,?)' => [0,1,2,3,4]],
|
||||
['"MyInt"']
|
||||
)->execute();
|
||||
|
||||
$i = 0;
|
||||
foreach ($query as $record) {
|
||||
$this->assertEquals($inputData[$i], $record['MyField']);
|
||||
$i++;
|
||||
if ($i > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue from where we left off, since we're using a Generator
|
||||
foreach ($query as $record) {
|
||||
$this->assertEquals($inputData[$i], $record['MyField']);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use mysqli_driver;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\Tests\MySQLiConnectorTest\MySQLiConnector;
|
||||
use SilverStripe\ORM\Connect\DatabaseException;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\Tests\MySQLiConnectorTest\MySQLiConnector;
|
||||
use SilverStripe\Tests\ORM\Utf8\Utf8TestHelper;
|
||||
|
||||
/**
|
||||
@ -13,21 +15,47 @@ use SilverStripe\Tests\ORM\Utf8\Utf8TestHelper;
|
||||
*/
|
||||
class MySQLiConnectorTest extends SapphireTest implements TestOnly
|
||||
{
|
||||
/** @var array project database settings configuration */
|
||||
private $config = [];
|
||||
|
||||
private function getConnector(?string $charset = null, ?string $collation = null, bool $selectDb = false)
|
||||
{
|
||||
$config = $this->config;
|
||||
|
||||
if ($charset) {
|
||||
$config['charset'] = $charset;
|
||||
}
|
||||
if ($collation) {
|
||||
$config['collation'] = $collation;
|
||||
}
|
||||
|
||||
$config['database'] = 'information_schema';
|
||||
|
||||
$connector = new MySQLiConnector();
|
||||
$connector->connect($config, $selectDb);
|
||||
|
||||
return $connector;
|
||||
}
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$config = DB::getConfig();
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
$this->markTestSkipped("The test only relevant for MySQL - but $config[type] is in use");
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider charsetProvider
|
||||
*/
|
||||
public function testConnectionCharsetControl($charset, $defaultCollation)
|
||||
{
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['database'] = 'information_schema';
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new MySQLiConnector();
|
||||
$connector->connect($config);
|
||||
$connector = $this->getConnector($charset);
|
||||
$connection = $connector->getMysqliConnection();
|
||||
|
||||
$cset = $connection->get_charset();
|
||||
@ -46,17 +74,7 @@ class MySQLiConnectorTest extends SapphireTest implements TestOnly
|
||||
*/
|
||||
public function testConnectionCollationControl($charset, $defaultCollation, $customCollation)
|
||||
{
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['collation'] = $customCollation;
|
||||
$config['database'] = 'information_schema';
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new MySQLiConnector();
|
||||
$connector->connect($config);
|
||||
$connector = $this->getConnector($charset, $customCollation);
|
||||
$connection = $connector->getMysqliConnection();
|
||||
|
||||
$cset = $connection->get_charset();
|
||||
@ -100,20 +118,7 @@ class MySQLiConnectorTest extends SapphireTest implements TestOnly
|
||||
|
||||
public function testUtf8mb4GeneralCollation()
|
||||
{
|
||||
$charset = 'utf8mb4';
|
||||
$collation = 'utf8mb4_general_ci';
|
||||
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['collation'] = $collation;
|
||||
$config['database'] = 'information_schema';
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new MySQLiConnector();
|
||||
$connector->connect($config, true);
|
||||
$connector = $this->getConnector('utf8mb4', 'utf8mb4_general_ci', true);
|
||||
$connection = $connector->getMysqliConnection();
|
||||
|
||||
$result = $connection->query(
|
||||
@ -126,20 +131,7 @@ class MySQLiConnectorTest extends SapphireTest implements TestOnly
|
||||
|
||||
public function testUtf8mb4UnicodeCollation()
|
||||
{
|
||||
$charset = 'utf8mb4';
|
||||
$collation = 'utf8mb4_unicode_ci';
|
||||
|
||||
$config = DB::getConfig();
|
||||
$config['charset'] = $charset;
|
||||
$config['collation'] = $collation;
|
||||
$config['database'] = 'information_schema';
|
||||
|
||||
if (strtolower(substr($config['type'] ?? '', 0, 5)) !== 'mysql') {
|
||||
return $this->markTestSkipped('The test only relevant for MySQL');
|
||||
}
|
||||
|
||||
$connector = new MySQLiConnector();
|
||||
$connector->connect($config, true);
|
||||
$connector = $this->getConnector('utf8mb4', 'utf8mb4_unicode_ci', true);
|
||||
$connection = $connector->getMysqliConnection();
|
||||
|
||||
$result = $connection->query(
|
||||
@ -150,4 +142,25 @@ class MySQLiConnectorTest extends SapphireTest implements TestOnly
|
||||
$this->assertEquals('rßt', $result[0][0]);
|
||||
$this->assertEquals('rst', $result[1][0]);
|
||||
}
|
||||
|
||||
public function testQueryThrowsDatabaseErrorOnMySQLiError()
|
||||
{
|
||||
$connector = $this->getConnector();
|
||||
$driver = new mysqli_driver();
|
||||
// The default with PHP >= 8.0
|
||||
$driver->report_mode = MYSQLI_REPORT_OFF;
|
||||
$this->expectException(DatabaseException::class);
|
||||
$connector = $this->getConnector(null, null, true);
|
||||
$connector->query('force an error with invalid SQL');
|
||||
}
|
||||
|
||||
public function testQueryThrowsDatabaseErrorOnMySQLiException()
|
||||
{
|
||||
$driver = new mysqli_driver();
|
||||
// The default since PHP 8.1 - https://www.php.net/manual/en/mysqli-driver.report-mode.php
|
||||
$driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;
|
||||
$this->expectException(DatabaseException::class);
|
||||
$connector = $this->getConnector(null, null, true);
|
||||
$connector->query('force an error with invalid SQL');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user