silverstripe-framework/tests/php/ORM/DatabaseTest.php

297 lines
11 KiB
PHP
Raw Normal View History

<?php
2016-10-14 03:30:05 +02:00
namespace SilverStripe\ORM\Tests;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\Connect\MySQLDatabase;
2016-06-23 01:37:22 +02:00
use SilverStripe\MSSQL\MSSQLDatabase;
use SilverStripe\Dev\SapphireTest;
2016-10-14 03:30:05 +02:00
use Exception;
use SilverStripe\ORM\Tests\DatabaseTest\MyObject;
2016-06-23 01:37:22 +02:00
/**
* @skipUpgrade
*/
class DatabaseTest extends SapphireTest
{
protected static $extra_dataobjects = [
MyObject::class,
];
protected $usesDatabase = true;
public function testDontRequireField()
{
$schema = DB::get_schema();
$this->assertArrayHasKey(
'MyField',
$schema->fieldList('DatabaseTest_MyObject')
);
$schema->dontRequireField('DatabaseTest_MyObject', 'MyField');
$this->assertArrayHasKey(
'_obsolete_MyField',
$schema->fieldList('DatabaseTest_MyObject'),
'Field is renamed to _obsolete_<fieldname> through dontRequireField()'
);
static::resetDBSchema(true);
}
public function testRenameField()
{
$schema = DB::get_schema();
$schema->clearCachedFieldlist();
$schema->renameField('DatabaseTest_MyObject', 'MyField', 'MyRenamedField');
$this->assertArrayHasKey(
'MyRenamedField',
$schema->fieldList('DatabaseTest_MyObject'),
'New fieldname is set through renameField()'
);
$this->assertArrayNotHasKey(
'MyField',
$schema->fieldList('DatabaseTest_MyObject'),
'Old fieldname isnt preserved through renameField()'
);
static::resetDBSchema(true);
}
public function testMySQLCreateTableOptions()
{
if (!(DB::get_conn() instanceof MySQLDatabase)) {
$this->markTestSkipped('MySQL only');
}
$ret = DB::query(
sprintf(
'SHOW TABLE STATUS WHERE "Name" = \'%s\'',
'DatabaseTest_MyObject'
)
)->first();
$this->assertEquals(
$ret['Engine'],
'InnoDB',
"MySQLDatabase tables can be changed to InnoDB through DataObject::\$create_table_options"
);
}
function testIsSchemaUpdating()
{
$schema = DB::get_schema();
$this->assertFalse($schema->isSchemaUpdating(), 'Before the transaction the flag is false.');
// Test complete schema update
$test = $this;
$schema->schemaUpdate(
function () use ($test, $schema) {
$test->assertTrue($schema->isSchemaUpdating(), 'During the transaction the flag is true.');
}
);
$this->assertFalse($schema->isSchemaUpdating(), 'After the transaction the flag is false.');
// Test cancelled schema update
$schema->schemaUpdate(
function () use ($test, $schema) {
$schema->cancelSchemaUpdate();
$test->assertFalse($schema->doesSchemaNeedUpdating(), 'After cancelling the transaction the flag is false');
}
);
}
public function testSchemaUpdateChecking()
{
$schema = DB::get_schema();
// Initially, no schema changes necessary
$test = $this;
$schema->schemaUpdate(
function () use ($test, $schema) {
$test->assertFalse($schema->doesSchemaNeedUpdating());
// If we make a change, then the schema will need updating
$schema->transCreateTable("TestTable");
$test->assertTrue($schema->doesSchemaNeedUpdating());
// If we make cancel the change, then schema updates are no longer necessary
$schema->cancelSchemaUpdate();
$test->assertFalse($schema->doesSchemaNeedUpdating());
}
);
}
public function testHasTable()
{
$this->assertTrue(DB::get_schema()->hasTable('DatabaseTest_MyObject'));
$this->assertFalse(DB::get_schema()->hasTable('asdfasdfasdf'));
}
public function testGetAndReleaseLock()
{
$db = DB::get_conn();
if (!$db->supportsLocks()) {
return $this->markTestSkipped('Tested database doesn\'t support application locks');
}
$this->assertTrue(
$db->getLock('DatabaseTest'),
'Can aquire lock'
);
// $this->assertFalse($db->getLock('DatabaseTest'), 'Can\'t repeatedly aquire the same lock');
$this->assertTrue(
$db->getLock('DatabaseTest'),
'The same lock can be aquired multiple times in the same connection'
);
$this->assertTrue(
$db->getLock('DatabaseTestOtherLock'),
'Can aquire different lock'
);
$db->releaseLock('DatabaseTestOtherLock');
// Release potentially stacked locks from previous getLock() invocations
$db->releaseLock('DatabaseTest');
$db->releaseLock('DatabaseTest');
$this->assertTrue(
$db->getLock('DatabaseTest'),
'Can aquire lock after releasing it'
);
$db->releaseLock('DatabaseTest');
}
public function testCanLock()
{
$db = DB::get_conn();
if (!$db->supportsLocks()) {
return $this->markTestSkipped('Database doesn\'t support locks');
}
if ($db instanceof MSSQLDatabase) {
return $this->markTestSkipped('MSSQLDatabase doesn\'t support inspecting locks');
}
$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock before first aquiring one');
$db->getLock('DatabaseTest');
$this->assertFalse($db->canLock('DatabaseTest'), 'Can\'t lock after aquiring one');
$db->releaseLock('DatabaseTest');
$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock again after releasing it');
}
public function testFieldTypes()
{
// Scaffold some data
$obj = new MyObject();
$obj->MyField = "value";
$obj->MyInt = 5;
$obj->MyFloat = 6.0;
// Note: in non-PDO SQLite, whole numbers of a decimal field will be returned as integers rather than floats
$obj->MyDecimal = 7.1;
$obj->MyBoolean = true;
$obj->write();
$record = DB::prepared_query(
'SELECT * FROM "DatabaseTest_MyObject" WHERE "ID" = ?',
[ $obj->ID ]
)->record();
// IDs and ints are returned as ints
$this->assertInternalType('int', $record['ID'], 'Primary key should be integer');
$this->assertInternalType('int', $record['MyInt'], 'DBInt fields should be integer');
$this->assertInternalType('float', $record['MyFloat'], 'DBFloat fields should be float');
$this->assertInternalType('float', $record['MyDecimal'], 'DBDecimal fields should be float');
// Booleans are returned as ints  we follow MySQL's lead
$this->assertInternalType('int', $record['MyBoolean'], 'DBBoolean fields should be int');
// Strings and enums are returned as strings
$this->assertInternalType('string', $record['MyField'], 'DBVarchar fields should be string');
$this->assertInternalType('string', $record['ClassName'], 'DBEnum fields should be string');
// Dates are returned as strings
$this->assertInternalType('string', $record['Created'], 'DBDatetime fields should be string');
2019-03-08 04:15:49 +01:00
// Ensure that the same is true when calling a query a second time (cached prepared statement)
$record = DB::prepared_query(
'SELECT * FROM "DatabaseTest_MyObject" WHERE "ID" = ?',
[ $obj->ID ]
)->record();
// IDs and ints are returned as ints
$this->assertInternalType('int', $record['ID'], 'Primary key should be integer (2nd call)');
$this->assertInternalType('int', $record['MyInt'], 'DBInt fields should be integer (2nd call)');
$this->assertInternalType('float', $record['MyFloat'], 'DBFloat fields should be float (2nd call)');
$this->assertInternalType('float', $record['MyDecimal'], 'DBDecimal fields should be float (2nd call)');
// Booleans are returned as ints  we follow MySQL's lead
$this->assertInternalType('int', $record['MyBoolean'], 'DBBoolean fields should be int (2nd call)');
// Strings and enums are returned as strings
$this->assertInternalType('string', $record['MyField'], 'DBVarchar fields should be string (2nd call)');
$this->assertInternalType('string', $record['ClassName'], 'DBEnum fields should be string (2nd call)');
// Dates are returned as strings
$this->assertInternalType('string', $record['Created'], 'DBDatetime fields should be string (2nd call)');
// Ensure that the same is true when using non-prepared statements
$record = DB::query('SELECT * FROM "DatabaseTest_MyObject" WHERE "ID" = ' . (int)$obj->ID)->record();
// IDs and ints are returned as ints
2019-03-08 04:15:49 +01:00
$this->assertInternalType('int', $record['ID'], 'Primary key should be integer (non-prepared)');
$this->assertInternalType('int', $record['MyInt'], 'DBInt fields should be integer (non-prepared)');
2019-03-08 04:15:49 +01:00
$this->assertInternalType('float', $record['MyFloat'], 'DBFloat fields should be float (non-prepared)');
$this->assertInternalType('float', $record['MyDecimal'], 'DBDecimal fields should be float (non-prepared)');
// Booleans are returned as ints  we follow MySQL's lead
2019-03-08 04:15:49 +01:00
$this->assertInternalType('int', $record['MyBoolean'], 'DBBoolean fields should be int (non-prepared)');
// Strings and enums are returned as strings
2019-03-08 04:15:49 +01:00
$this->assertInternalType('string', $record['MyField'], 'DBVarchar fields should be string (non-prepared)');
$this->assertInternalType('string', $record['ClassName'], 'DBEnum fields should be string (non-prepared)');
// Dates are returned as strings
2019-03-08 04:15:49 +01:00
$this->assertInternalType('string', $record['Created'], 'DBDatetime fields should be string (non-prepared)');
// Booleans selected directly are ints
$result = DB::query('SELECT TRUE')->first();
$this->assertInternalType('int', reset($result));
}
/**
* Test that repeated iteration of a query returns all records.
* See https://github.com/silverstripe/silverstripe-framework/issues/9097
*/
public function testRepeatedIteration()
{
$inputData = ['one', 'two', 'three', 'four'];
foreach ($inputData as $i => $text) {
$x = new MyObject();
$x->MyField = $text;
$x->MyInt = $i;
$x->write();
}
$query = DB::query('SELECT "MyInt", "MyField" FROM "DatabaseTest_MyObject" ORDER BY "MyInt"');
$this->assertEquals($inputData, $query->map());
$this->assertEquals($inputData, $query->map());
}
}