Passed
Pull Request — 4.4 (#9098)
by Sam
07:26
created

DatabaseTest   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 139
c 3
b 1
f 0
dl 0
loc 280
rs 10
wmc 15

10 Methods

Rating   Name   Duplication   Size   Complexity  
A testHasTable() 0 4 1
A testGetAndReleaseLock() 0 33 2
A testIsSchemaUpdating() 0 20 1
A testSchemaUpdateChecking() 0 17 1
A testCanLock() 0 17 3
A testRenameField() 0 20 1
A testMySQLCreateTableOptions() 0 17 2
A testDontRequireField() 0 17 1
A testFieldTypes() 0 84 1
A testRepeatedIteration() 0 15 2
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\ORM\DB;
6
use SilverStripe\ORM\Connect\MySQLDatabase;
7
use SilverStripe\MSSQL\MSSQLDatabase;
0 ignored issues
show
Bug introduced by
The type SilverStripe\MSSQL\MSSQLDatabase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use SilverStripe\Dev\SapphireTest;
9
use Exception;
10
use SilverStripe\ORM\Tests\DatabaseTest\MyObject;
11
12
/**
13
 * @skipUpgrade
14
*/
15
class DatabaseTest extends SapphireTest
16
{
17
18
    protected static $extra_dataobjects = array(
19
        MyObject::class,
20
    );
21
22
    protected $usesDatabase = true;
23
24
    public function testDontRequireField()
25
    {
26
        $schema = DB::get_schema();
27
        $this->assertArrayHasKey(
28
            'MyField',
29
            $schema->fieldList('DatabaseTest_MyObject')
30
        );
31
32
        $schema->dontRequireField('DatabaseTest_MyObject', 'MyField');
33
34
        $this->assertArrayHasKey(
35
            '_obsolete_MyField',
36
            $schema->fieldList('DatabaseTest_MyObject'),
37
            'Field is renamed to _obsolete_<fieldname> through dontRequireField()'
38
        );
39
40
        static::resetDBSchema(true);
41
    }
42
43
    public function testRenameField()
44
    {
45
        $schema = DB::get_schema();
46
47
        $schema->clearCachedFieldlist();
48
49
        $schema->renameField('DatabaseTest_MyObject', 'MyField', 'MyRenamedField');
50
51
        $this->assertArrayHasKey(
52
            'MyRenamedField',
53
            $schema->fieldList('DatabaseTest_MyObject'),
54
            'New fieldname is set through renameField()'
55
        );
56
        $this->assertArrayNotHasKey(
57
            'MyField',
58
            $schema->fieldList('DatabaseTest_MyObject'),
59
            'Old fieldname isnt preserved through renameField()'
60
        );
61
62
        static::resetDBSchema(true);
63
    }
64
65
    public function testMySQLCreateTableOptions()
66
    {
67
        if (!(DB::get_conn() instanceof MySQLDatabase)) {
68
            $this->markTestSkipped('MySQL only');
69
        }
70
71
72
        $ret = DB::query(
73
            sprintf(
74
                'SHOW TABLE STATUS WHERE "Name" = \'%s\'',
75
                'DatabaseTest_MyObject'
76
            )
77
        )->first();
78
        $this->assertEquals(
79
            $ret['Engine'],
80
            'InnoDB',
81
            "MySQLDatabase tables can be changed to InnoDB through DataObject::\$create_table_options"
82
        );
83
    }
84
85
    function testIsSchemaUpdating()
86
    {
87
        $schema = DB::get_schema();
88
89
        $this->assertFalse($schema->isSchemaUpdating(), 'Before the transaction the flag is false.');
90
91
        // Test complete schema update
92
        $test = $this;
93
        $schema->schemaUpdate(
94
            function () use ($test, $schema) {
95
                $test->assertTrue($schema->isSchemaUpdating(), 'During the transaction the flag is true.');
96
            }
97
        );
98
        $this->assertFalse($schema->isSchemaUpdating(), 'After the transaction the flag is false.');
99
100
        // Test cancelled schema update
101
        $schema->schemaUpdate(
102
            function () use ($test, $schema) {
103
                $schema->cancelSchemaUpdate();
104
                $test->assertFalse($schema->doesSchemaNeedUpdating(), 'After cancelling the transaction the flag is false');
105
            }
106
        );
107
    }
108
109
    public function testSchemaUpdateChecking()
110
    {
111
        $schema = DB::get_schema();
112
113
        // Initially, no schema changes necessary
114
        $test = $this;
115
        $schema->schemaUpdate(
116
            function () use ($test, $schema) {
117
                $test->assertFalse($schema->doesSchemaNeedUpdating());
118
119
                // If we make a change, then the schema will need updating
120
                $schema->transCreateTable("TestTable");
121
                $test->assertTrue($schema->doesSchemaNeedUpdating());
122
123
                // If we make cancel the change, then schema updates are no longer necessary
124
                $schema->cancelSchemaUpdate();
125
                $test->assertFalse($schema->doesSchemaNeedUpdating());
126
            }
127
        );
128
    }
129
130
    public function testHasTable()
131
    {
132
        $this->assertTrue(DB::get_schema()->hasTable('DatabaseTest_MyObject'));
133
        $this->assertFalse(DB::get_schema()->hasTable('asdfasdfasdf'));
134
    }
135
136
    public function testGetAndReleaseLock()
137
    {
138
        $db = DB::get_conn();
139
140
        if (!$db->supportsLocks()) {
141
            return $this->markTestSkipped('Tested database doesn\'t support application locks');
142
        }
143
144
        $this->assertTrue(
145
            $db->getLock('DatabaseTest'),
146
            'Can aquire lock'
147
        );
148
        // $this->assertFalse($db->getLock('DatabaseTest'), 'Can\'t repeatedly aquire the same lock');
149
        $this->assertTrue(
150
            $db->getLock('DatabaseTest'),
151
            'The same lock can be aquired multiple times in the same connection'
152
        );
153
154
        $this->assertTrue(
155
            $db->getLock('DatabaseTestOtherLock'),
156
            'Can aquire different lock'
157
        );
158
        $db->releaseLock('DatabaseTestOtherLock');
159
160
        // Release potentially stacked locks from previous getLock() invocations
161
        $db->releaseLock('DatabaseTest');
162
        $db->releaseLock('DatabaseTest');
163
164
        $this->assertTrue(
165
            $db->getLock('DatabaseTest'),
166
            'Can aquire lock after releasing it'
167
        );
168
        $db->releaseLock('DatabaseTest');
169
    }
170
171
    public function testCanLock()
172
    {
173
        $db = DB::get_conn();
174
175
        if (!$db->supportsLocks()) {
176
            return $this->markTestSkipped('Database doesn\'t support locks');
177
        }
178
179
        if ($db instanceof MSSQLDatabase) {
180
            return $this->markTestSkipped('MSSQLDatabase doesn\'t support inspecting locks');
181
        }
182
183
        $this->assertTrue($db->canLock('DatabaseTest'), 'Can lock before first aquiring one');
184
        $db->getLock('DatabaseTest');
185
        $this->assertFalse($db->canLock('DatabaseTest'), 'Can\'t lock after aquiring one');
186
        $db->releaseLock('DatabaseTest');
187
        $this->assertTrue($db->canLock('DatabaseTest'), 'Can lock again after releasing it');
188
    }
189
190
    public function testFieldTypes()
191
    {
192
        // Scaffold some data
193
        $obj = new MyObject();
194
        $obj->MyField = "value";
0 ignored issues
show
Bug Best Practice introduced by
The property MyField does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
195
        $obj->MyInt = 5;
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
196
        $obj->MyFloat = 6.0;
0 ignored issues
show
Bug Best Practice introduced by
The property MyFloat does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
197
198
        // Note: in non-PDO SQLite, whole numbers of a decimal field will be returned as integers rather than floats
199
        $obj->MyDecimal = 7.1;
0 ignored issues
show
Bug Best Practice introduced by
The property MyDecimal does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
200
        $obj->MyBoolean = true;
0 ignored issues
show
Bug Best Practice introduced by
The property MyBoolean does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
201
        $obj->write();
202
203
        $record = DB::prepared_query(
204
            'SELECT * FROM "DatabaseTest_MyObject" WHERE "ID" = ?',
205
            [ $obj->ID ]
206
        )->record();
207
208
        // IDs and ints are returned as ints
209
        $this->assertInternalType('int', $record['ID'], 'Primary key should be integer');
210
        $this->assertInternalType('int', $record['MyInt'], 'DBInt fields should be integer');
211
212
        $this->assertInternalType('float', $record['MyFloat'], 'DBFloat fields should be float');
213
        $this->assertInternalType('float', $record['MyDecimal'], 'DBDecimal fields should be float');
214
215
        // Booleans are returned as ints – we follow MySQL's lead
216
        $this->assertInternalType('int', $record['MyBoolean'], 'DBBoolean fields should be int');
217
218
        // Strings and enums are returned as strings
219
        $this->assertInternalType('string', $record['MyField'], 'DBVarchar fields should be string');
220
        $this->assertInternalType('string', $record['ClassName'], 'DBEnum fields should be string');
221
222
        // Dates are returned as strings
223
        $this->assertInternalType('string', $record['Created'], 'DBDatetime fields should be string');
224
225
226
        // Ensure that the same is true when calling a query a second time (cached prepared statement)
227
228
        $record = DB::prepared_query(
229
            'SELECT * FROM "DatabaseTest_MyObject" WHERE "ID" = ?',
230
            [ $obj->ID ]
231
        )->record();
232
233
        // IDs and ints are returned as ints
234
        $this->assertInternalType('int', $record['ID'], 'Primary key should be integer (2nd call)');
235
        $this->assertInternalType('int', $record['MyInt'], 'DBInt fields should be integer (2nd call)');
236
237
        $this->assertInternalType('float', $record['MyFloat'], 'DBFloat fields should be float (2nd call)');
238
        $this->assertInternalType('float', $record['MyDecimal'], 'DBDecimal fields should be float (2nd call)');
239
240
        // Booleans are returned as ints – we follow MySQL's lead
241
        $this->assertInternalType('int', $record['MyBoolean'], 'DBBoolean fields should be int (2nd call)');
242
243
        // Strings and enums are returned as strings
244
        $this->assertInternalType('string', $record['MyField'], 'DBVarchar fields should be string (2nd call)');
245
        $this->assertInternalType('string', $record['ClassName'], 'DBEnum fields should be string (2nd call)');
246
247
        // Dates are returned as strings
248
        $this->assertInternalType('string', $record['Created'], 'DBDatetime fields should be string (2nd call)');
249
250
251
        // Ensure that the same is true when using non-prepared statements
252
        $record = DB::query('SELECT * FROM "DatabaseTest_MyObject" WHERE "ID" = ' . (int)$obj->ID)->record();
253
254
        // IDs and ints are returned as ints
255
        $this->assertInternalType('int', $record['ID'], 'Primary key should be integer (non-prepared)');
256
        $this->assertInternalType('int', $record['MyInt'], 'DBInt fields should be integer (non-prepared)');
257
258
        $this->assertInternalType('float', $record['MyFloat'], 'DBFloat fields should be float (non-prepared)');
259
        $this->assertInternalType('float', $record['MyDecimal'], 'DBDecimal fields should be float (non-prepared)');
260
261
        // Booleans are returned as ints – we follow MySQL's lead
262
        $this->assertInternalType('int', $record['MyBoolean'], 'DBBoolean fields should be int (non-prepared)');
263
264
        // Strings and enums are returned as strings
265
        $this->assertInternalType('string', $record['MyField'], 'DBVarchar fields should be string (non-prepared)');
266
        $this->assertInternalType('string', $record['ClassName'], 'DBEnum fields should be string (non-prepared)');
267
268
        // Dates are returned as strings
269
        $this->assertInternalType('string', $record['Created'], 'DBDatetime fields should be string (non-prepared)');
270
271
        // Booleans selected directly are ints
272
        $result = DB::query('SELECT TRUE')->first();
273
        $this->assertInternalType('int', reset($result));
274
    }
275
276
    /**
277
     * Test that repeated iteration of a query returns all records.
278
     * See https://github.com/silverstripe/silverstripe-framework/issues/9097
279
     */
280
    public function testRepeatedIteration()
281
    {
282
        $inputData = ['one', 'two', 'three', 'four'];
283
284
        foreach ($inputData as $i => $text) {
285
            $x = new MyObject();
286
            $x->MyField = $text;
0 ignored issues
show
Bug Best Practice introduced by
The property MyField does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
287
            $x->MyInt = $i;
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\Tests\DatabaseTest\MyObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
288
            $x->write();
289
        }
290
291
        $query = DB::query('SELECT "MyInt", "MyField" FROM "DatabaseTest_MyObject" ORDER BY "MyInt"');
292
293
        $this->assertEquals($inputData, $query->map());
294
        $this->assertEquals($inputData, $query->map());
295
    }
296
}
297