Passed
Push — 4 ( dcdc25...6fc25e )
by
unknown
07:34
created

DataListTest::testChunkedFetch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 28
nc 1
nop 0
dl 0
loc 43
rs 9.472
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Core\Injector\InjectorNotFoundException;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\ORM\DataList;
10
use SilverStripe\ORM\DataQuery;
11
use SilverStripe\ORM\DB;
12
use SilverStripe\ORM\Filterable;
13
use SilverStripe\ORM\Filters\ExactMatchFilter;
14
use SilverStripe\ORM\Tests\DataObjectTest\DataListQueryCounter;
15
use SilverStripe\ORM\Tests\DataObjectTest\Fixture;
16
use SilverStripe\ORM\Tests\DataObjectTest\Bracket;
17
use SilverStripe\ORM\Tests\DataObjectTest\EquipmentCompany;
18
use SilverStripe\ORM\Tests\DataObjectTest\Fan;
19
use SilverStripe\ORM\Tests\DataObjectTest\Player;
20
use SilverStripe\ORM\Tests\DataObjectTest\Sortable;
21
use SilverStripe\ORM\Tests\DataObjectTest\Staff;
22
use SilverStripe\ORM\Tests\DataObjectTest\SubTeam;
23
use SilverStripe\ORM\Tests\DataObjectTest\Team;
24
use SilverStripe\ORM\Tests\DataObjectTest\TeamComment;
25
use SilverStripe\ORM\Tests\DataObjectTest\ValidatedObject;
26
use SilverStripe\ORM\Tests\ManyManyListTest\Category;
27
28
/**
29
 * @skipUpgrade
30
 */
31
class DataListTest extends SapphireTest
32
{
33
34
    // Borrow the model from DataObjectTest
35
    protected static $fixture_file = 'DataObjectTest.yml';
36
37
    public static function getExtraDataObjects()
38
    {
39
        return array_merge(
40
            DataObjectTest::$extra_data_objects,
41
            ManyManyListTest::$extra_data_objects
42
        );
43
    }
44
45
46
    public function testFilterDataObjectByCreatedDate()
47
    {
48
        // create an object to test with
49
        $obj1 = new ValidatedObject();
50
        $obj1->Name = 'test obj 1';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\D...ectTest\ValidatedObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
51
        $obj1->write();
52
        $this->assertTrue($obj1->isInDB());
53
54
        // reload the object from the database and reset its Created timestamp to a known value
55
        $obj1 = ValidatedObject::get()->filter(['Name' => 'test obj 1'])->first();
56
        $this->assertTrue(is_object($obj1));
57
        $this->assertEquals('test obj 1', $obj1->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
58
        $obj1->Created = '2013-01-01 00:00:00';
59
        $obj1->write();
60
61
        // reload the object again and make sure that our Created date was properly persisted
62
        $obj1 = ValidatedObject::get()->filter(['Name' => 'test obj 1'])->first();
63
        $this->assertTrue(is_object($obj1));
64
        $this->assertEquals('test obj 1', $obj1->Name);
65
        $this->assertEquals('2013-01-01 00:00:00', $obj1->Created);
66
67
        // now save a second object to the DB with an automatically-set Created value
68
        $obj2 = new ValidatedObject();
69
        $obj2->Name = 'test obj 2';
70
        $obj2->write();
71
        $this->assertTrue($obj2->isInDB());
72
73
        // and a third object
74
        $obj3 = new ValidatedObject();
75
        $obj3->Name = 'test obj 3';
76
        $obj3->write();
77
        $this->assertTrue($obj3->isInDB());
78
79
        // now test the filtering based on Created timestamp
80
        $list = ValidatedObject::get()
81
            ->filter(['Created:GreaterThan' => '2013-02-01 00:00:00'])
82
            ->toArray();
83
        $this->assertEquals(2, count($list));
84
    }
85
86
    public function testSubtract()
87
    {
88
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
89
        $subtractList = TeamComment::get()->filter('ID', $comment1->ID);
90
        $fullList = TeamComment::get();
91
        $newList = $fullList->subtract($subtractList);
92
        $this->assertEquals(2, $newList->Count(), 'List should only contain two objects after subtraction');
93
    }
94
95
    /**
96
     * @expectedException \InvalidArgumentException
97
     */
98
    public function testSubtractBadDataclassThrowsException()
99
    {
100
        $teamsComments = TeamComment::get();
101
        $teams = Team::get();
102
        $teamsComments->subtract($teams);
103
    }
104
105
    public function testListCreationSortAndLimit()
106
    {
107
        // By default, a DataList will contain all items of that class
108
        $list = TeamComment::get()->sort('ID');
109
110
        // We can iterate on the DataList
111
        $names = [];
112
        foreach ($list as $item) {
113
            $names[] = $item->Name;
114
        }
115
        $this->assertEquals(['Joe', 'Bob', 'Phil'], $names);
116
117
        // If we don't want to iterate, we can extract a single column from the list with column()
118
        $this->assertEquals(['Joe', 'Bob', 'Phil'], $list->column('Name'));
119
120
        // We can sort a list
121
        $list = $list->sort('Name');
122
        $this->assertEquals(['Bob', 'Joe', 'Phil'], $list->column('Name'));
123
124
        // We can also restrict the output to a range
125
        $this->assertEquals(['Joe', 'Phil'], $list->limit(2, 1)->column('Name'));
126
    }
127
128
    public function testLimitAndOffset()
129
    {
130
        $list = TeamComment::get();
131
        $check = $list->limit(3);
132
133
        $this->assertEquals(3, $check->count());
134
135
        $check = $list->limit(1);
136
        $this->assertEquals(1, $check->count());
137
138
        $check = $list->limit(1, 1);
139
        $this->assertEquals(1, $check->count());
140
141
        $check = $list->limit(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $limit of SilverStripe\ORM\DataList::limit(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
        $check = $list->limit(/** @scrutinizer ignore-type */ false);
Loading history...
142
        $this->assertEquals(3, $check->count());
143
144
        $check = $list->limit(null);
145
        $this->assertEquals(3, $check->count());
146
147
        $check = $list->limit(null, 2);
148
        $this->assertEquals(1, $check->count());
149
150
        // count()/first()/last() methods may alter limit/offset, so run the query and manually check the count
151
        $check = $list->limit(null, 1)->toArray();
152
        $this->assertEquals(2, count($check));
153
    }
154
155
    public function testDistinct()
156
    {
157
        $list = TeamComment::get();
158
        $this->assertContains('SELECT DISTINCT', $list->dataQuery()->sql($params), 'Query is set as distinct by default');
159
160
        $list = $list->distinct(false);
161
        $this->assertNotContains('SELECT DISTINCT', $list->dataQuery()->sql($params), 'Query does not contain distinct');
162
163
        $list = $list->distinct(true);
164
        $this->assertContains('SELECT DISTINCT', $list->dataQuery()->sql($params), 'Query contains distinct');
165
    }
166
167
    public function testDataClass()
168
    {
169
        $list = TeamComment::get();
170
        $this->assertEquals(DataObjectTest\TeamComment::class, $list->dataClass());
171
    }
172
173
    public function testDataClassCaseInsensitive()
174
    {
175
        $list = DataList::create(strtolower(DataObjectTest\TeamComment::class));
176
        $this->assertTrue($list->exists());
177
    }
178
179
    public function testClone()
180
    {
181
        $list = TeamComment::get();
182
        $this->assertEquals($list, clone($list));
183
    }
184
185
    public function testSql()
186
    {
187
        $db = DB::get_conn();
188
        $list = TeamComment::get();
189
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
190
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
191
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
192
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
193
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL '
194
            . 'THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
195
            . $db->quoteString(DataObjectTest\TeamComment::class)
196
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment"'
197
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
198
        $this->assertSQLEquals($expected, $list->sql($parameters));
199
    }
200
201
    public function testInnerJoin()
202
    {
203
        $db = DB::get_conn();
204
205
        $list = TeamComment::get();
206
        $list = $list->innerJoin(
207
            'DataObjectTest_Team',
208
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
209
            'Team'
210
        );
211
212
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
213
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
214
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
215
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
216
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL'
217
            . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
218
            . $db->quoteString(DataObjectTest\TeamComment::class)
219
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" INNER JOIN '
220
            . '"DataObjectTest_Team" AS "Team" ON "DataObjectTest_Team"."ID" = '
221
            . '"DataObjectTest_TeamComment"."TeamID"'
222
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
223
224
225
        $this->assertSQLEquals($expected, $list->sql($parameters));
226
        $this->assertEmpty($parameters);
227
    }
228
229
    public function testInnerJoinParameterised()
230
    {
231
        $db = DB::get_conn();
232
233
        $list = TeamComment::get();
234
        $list = $list->innerJoin(
235
            'DataObjectTest_Team',
236
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID" '
237
            . 'AND "DataObjectTest_Team"."Title" LIKE ?',
238
            'Team',
239
            20,
240
            ['Team%']
241
        );
242
243
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
244
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
245
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
246
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
247
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL'
248
            . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
249
            . $db->quoteString(DataObjectTest\TeamComment::class)
250
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" INNER JOIN '
251
            . '"DataObjectTest_Team" AS "Team" ON "DataObjectTest_Team"."ID" = '
252
            . '"DataObjectTest_TeamComment"."TeamID" '
253
            . 'AND "DataObjectTest_Team"."Title" LIKE ?'
254
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
255
256
        $this->assertSQLEquals($expected, $list->sql($parameters));
257
        $this->assertEquals(['Team%'], $parameters);
258
    }
259
260
    public function testLeftJoin()
261
    {
262
        $db = DB::get_conn();
263
264
        $list = TeamComment::get();
265
        $list = $list->leftJoin(
266
            'DataObjectTest_Team',
267
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"',
268
            'Team'
269
        );
270
271
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
272
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
273
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
274
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
275
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL '
276
            . 'THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
277
            . $db->quoteString(DataObjectTest\TeamComment::class)
278
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" LEFT JOIN "DataObjectTest_Team" '
279
            . 'AS "Team" ON "DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"'
280
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
281
282
283
        $this->assertSQLEquals($expected, $list->sql($parameters));
284
        $this->assertEmpty($parameters);
285
    }
286
287
    public function testLeftJoinParameterised()
288
    {
289
        $db = DB::get_conn();
290
291
        $list = TeamComment::get();
292
        $list = $list->leftJoin(
293
            'DataObjectTest_Team',
294
            '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID" '
295
            . 'AND "DataObjectTest_Team"."Title" LIKE ?',
296
            'Team',
297
            20,
298
            ['Team%']
299
        );
300
301
        $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", '
302
            . '"DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Created", '
303
            . '"DataObjectTest_TeamComment"."Name", "DataObjectTest_TeamComment"."Comment", '
304
            . '"DataObjectTest_TeamComment"."TeamID", "DataObjectTest_TeamComment"."ID", '
305
            . 'CASE WHEN "DataObjectTest_TeamComment"."ClassName" IS NOT NULL'
306
            . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '
307
            . $db->quoteString(DataObjectTest\TeamComment::class)
308
            . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" LEFT JOIN '
309
            . '"DataObjectTest_Team" AS "Team" ON "DataObjectTest_Team"."ID" = '
310
            . '"DataObjectTest_TeamComment"."TeamID" '
311
            . 'AND "DataObjectTest_Team"."Title" LIKE ?'
312
            . ' ORDER BY "DataObjectTest_TeamComment"."Name" ASC';
313
314
        $this->assertSQLEquals($expected, $list->sql($parameters));
315
        $this->assertEquals(['Team%'], $parameters);
316
    }
317
318
    public function testToNestedArray()
319
    {
320
        $list = TeamComment::get()->sort('ID');
321
        $nestedArray = $list->toNestedArray();
322
        $expected = [
323
            0=>
324
            [
325
                'ClassName'=>DataObjectTest\TeamComment::class,
326
                'Name'=>'Joe',
327
                'Comment'=>'This is a team comment by Joe',
328
                'TeamID'=> $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1')->TeamID,
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
329
            ],
330
            1=>
331
            [
332
                'ClassName'=>DataObjectTest\TeamComment::class,
333
                'Name'=>'Bob',
334
                'Comment'=>'This is a team comment by Bob',
335
                'TeamID'=> $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2')->TeamID,
336
            ],
337
            2=>
338
            [
339
                'ClassName'=>DataObjectTest\TeamComment::class,
340
                'Name'=>'Phil',
341
                'Comment'=>'Phil is a unique guy, and comments on team2',
342
                'TeamID'=> $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3')->TeamID,
343
            ],
344
        ];
345
        $this->assertEquals(3, count($nestedArray));
346
        $this->assertEquals($expected[0]['Name'], $nestedArray[0]['Name']);
347
        $this->assertEquals($expected[1]['Comment'], $nestedArray[1]['Comment']);
348
        $this->assertEquals($expected[2]['TeamID'], $nestedArray[2]['TeamID']);
349
    }
350
351
    public function testMap()
352
    {
353
        $map = TeamComment::get()->map()->toArray();
354
        $expected = [
355
            $this->idFromFixture(DataObjectTest\TeamComment::class, 'comment1') => 'Joe',
356
            $this->idFromFixture(DataObjectTest\TeamComment::class, 'comment2') => 'Bob',
357
            $this->idFromFixture(DataObjectTest\TeamComment::class, 'comment3') => 'Phil'
358
        ];
359
360
        $this->assertEquals($expected, $map);
361
        $otherMap = TeamComment::get()->map('Name', 'TeamID')->toArray();
362
        $otherExpected = [
363
            'Joe' => $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1')->TeamID,
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
364
            'Bob' => $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2')->TeamID,
365
            'Phil' => $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3')->TeamID
366
        ];
367
368
        $this->assertEquals($otherExpected, $otherMap);
369
    }
370
371
    public function testAmbiguousAggregate()
372
    {
373
        // Test that we avoid ambiguity error when a field exists on two joined tables
374
        // Fetch the sponsors in a round-about way to simulate this
375
        $teamID = $this->idFromFixture(DataObjectTest\Team::class, 'team2');
376
        $sponsors = EquipmentCompany::get()->filter('SponsoredTeams.ID', $teamID);
377
        $this->assertNotNull($sponsors->Max('ID'));
378
        $this->assertNotNull($sponsors->Min('ID'));
379
        $this->assertNotNull($sponsors->Avg('ID'));
380
        $this->assertNotNull($sponsors->Sum('ID'));
381
382
        // Test non-orm many_many_extraFields
383
        $company = $this->objFromFixture(EquipmentCompany::class, 'equipmentcompany1');
384
        $this->assertNotNull($company->SponsoredTeams()->Max('SponsorFee'));
0 ignored issues
show
Bug introduced by
The method SponsoredTeams() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

384
        $this->assertNotNull($company->/** @scrutinizer ignore-call */ SponsoredTeams()->Max('SponsorFee'));
Loading history...
385
        $this->assertNotNull($company->SponsoredTeams()->Min('SponsorFee'));
386
        $this->assertNotNull($company->SponsoredTeams()->Avg('SponsorFee'));
387
        $this->assertNotNull($company->SponsoredTeams()->Sum('SponsorFee'));
388
    }
389
390
    public function testEach()
391
    {
392
        $list = TeamComment::get();
393
394
        $count = 0;
395
        $list->each(
396
            function ($item) use (&$count) {
397
                $count++;
398
                $this->assertInstanceOf(TeamComment::class, $item);
399
            }
400
        );
401
402
        $this->assertEquals($count, $list->count());
403
    }
404
405
    public function testWhere()
406
    {
407
        // We can use raw SQL queries with where.  This is only recommended for advanced uses;
408
        // if you can, you should use filter().
409
        $list = TeamComment::get();
410
411
        // where() returns a new DataList, like all the other modifiers, so it can be chained.
412
        $list2 = $list->where('"Name" = \'Joe\'');
413
        $this->assertEquals(['This is a team comment by Joe'], $list2->column('Comment'));
414
415
        // The where() clauses are chained together with AND
416
        $list3 = $list2->where('"Name" = \'Bob\'');
417
        $this->assertEquals([], $list3->column('Comment'));
418
    }
419
420
    /**
421
     * Test DataList->byID()
422
     */
423
    public function testByID()
424
    {
425
        // We can get a single item by ID.
426
        $id = $this->idFromFixture(DataObjectTest\Team::class, 'team2');
427
        $team = Team::get()->byID($id);
428
429
        // byID() returns a DataObject, rather than a DataList
430
        $this->assertInstanceOf(DataObjectTest\Team::class, $team);
431
        $this->assertEquals('Team 2', $team->Title);
432
433
        // Assert that filtering on ID searches by the base table, not the child table field
434
        $query = SubTeam::get()->filter('ID', 4)->sql($parameters);
435
        $this->assertContains('WHERE ("DataObjectTest_Team"."ID" = ?)', $query);
436
        $this->assertNotContains('WHERE ("DataObjectTest_SubTeam"."ID" = ?)', $query);
437
    }
438
439
    public function testByIDs()
440
    {
441
        $knownIDs = $this->allFixtureIDs(DataObjectTest\Player::class);
442
        $removedID = array_pop($knownIDs);
443
        $filteredPlayers = Player::get()->byIDs($knownIDs);
444
        foreach ($filteredPlayers as $player) {
445
            $this->assertContains($player->ID, $knownIDs);
446
            $this->assertNotEquals($removedID, $player->ID);
447
        }
448
    }
449
450
    /**
451
     * Test DataList->removeByID()
452
     */
453
    public function testRemoveByID()
454
    {
455
        $list = Team::get();
456
        $id = $this->idFromFixture(DataObjectTest\Team::class, 'team2');
457
458
        $this->assertNotNull($list->byID($id));
459
        $list->removeByID($id);
460
        $this->assertNull($list->byID($id));
461
    }
462
463
    /**
464
     * Test DataList->canSortBy()
465
     */
466
    public function testCanSortBy()
467
    {
468
        // Basic check
469
        $team = Team::get();
470
        $this->assertTrue($team->canSortBy("Title"));
471
        $this->assertFalse($team->canSortBy("SomethingElse"));
472
473
        // Subclasses
474
        $subteam = SubTeam::get();
475
        $this->assertTrue($subteam->canSortBy("Title"));
476
        $this->assertTrue($subteam->canSortBy("SubclassDatabaseField"));
477
    }
478
479
    public function testDataListArrayAccess()
480
    {
481
        $list = Team::get()->sort('Title');
482
483
        // We can use array access to refer to single items in the DataList, as if it were an array
484
        $this->assertEquals("Subteam 1", $list[0]->Title);
485
        $this->assertEquals("Subteam 3", $list[2]->Title);
486
        $this->assertEquals("Team 2", $list[4]->Title);
487
    }
488
489
    public function testFind()
490
    {
491
        $list = Team::get();
492
        $record = $list->find('Title', 'Team 1');
493
        $this->assertEquals($this->idFromFixture(DataObjectTest\Team::class, 'team1'), $record->ID);
494
    }
495
496
    public function testFindById()
497
    {
498
        $list = Team::get();
499
        $record = $list->find('ID', $this->idFromFixture(DataObjectTest\Team::class, 'team1'));
500
        $this->assertEquals('Team 1', $record->Title);
501
        // Test that you can call it twice on the same list
502
        $record = $list->find('ID', $this->idFromFixture(DataObjectTest\Team::class, 'team2'));
503
        $this->assertEquals('Team 2', $record->Title);
504
    }
505
506
    public function testSimpleSort()
507
    {
508
        $list = TeamComment::get();
509
        $list = $list->sort('Name');
510
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
511
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
512
    }
513
514
    public function testSimpleSortOneArgumentASC()
515
    {
516
        $list = TeamComment::get();
517
        $list = $list->sort('Name ASC');
518
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
519
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
520
    }
521
522
    public function testSimpleSortOneArgumentDESC()
523
    {
524
        $list = TeamComment::get();
525
        $list = $list->sort('Name DESC');
526
        $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
527
        $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
528
    }
529
530
    public function testSortOneArgumentMultipleColumns()
531
    {
532
        $list = TeamComment::get();
533
        $list = $list->sort('TeamID ASC, Name DESC');
534
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
535
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
536
    }
537
538
    public function testSimpleSortASC()
539
    {
540
        $list = TeamComment::get();
541
        $list = $list->sort('Name', 'asc');
542
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
543
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
544
    }
545
546
    public function testSimpleSortDESC()
547
    {
548
        $list = TeamComment::get();
549
        $list = $list->sort('Name', 'desc');
550
        $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
551
        $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
552
    }
553
554
    public function testSortWithArraySyntaxSortASC()
555
    {
556
        $list = TeamComment::get();
557
        $list = $list->sort(['Name'=>'asc']);
558
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
559
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
560
    }
561
562
    public function testSortWithArraySyntaxSortDESC()
563
    {
564
        $list = TeamComment::get();
565
        $list = $list->sort(['Name'=>'desc']);
566
        $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
567
        $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob');
568
    }
569
570
    public function testSortWithMultipleArraySyntaxSort()
571
    {
572
        $list = TeamComment::get();
573
        $list = $list->sort(['TeamID'=>'asc','Name'=>'desc']);
574
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
575
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
576
    }
577
578
    public function testSortWithCompositeSyntax()
579
    {
580
        // Phil commented on team with founder surname "Aaron"
581
        $list = TeamComment::get();
582
        $list = $list->sort('Team.Founder.Surname', 'asc');
583
        $this->assertEquals('Phil', $list->first()->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
584
        $list = $list->sort('Team.Founder.Surname', 'desc');
585
        $this->assertEquals('Phil', $list->last()->Name);
586
    }
587
588
    /**
589
     * @expectedException \InvalidArgumentException
590
     * @expectedExceptionMessage Fans is not a linear relation on model SilverStripe\ORM\Tests\DataObjectTest\Player
591
     */
592
    public function testSortInvalidParameters()
593
    {
594
        $list = Team::get();
595
        $list->sort('Founder.Fans.Surname'); // Can't sort on has_many
596
    }
597
598
    public function testSortNumeric()
599
    {
600
        $list = Sortable::get();
601
        $list1 = $list->sort('Sort', 'ASC');
602
        $this->assertEquals(
603
            [
604
            -10,
605
            -2,
606
            -1,
607
            0,
608
            1,
609
            2,
610
            10
611
            ],
612
            $list1->column('Sort')
613
        );
614
    }
615
616
    public function testSortMixedCase()
617
    {
618
        $list = Sortable::get();
619
        $list1 = $list->sort('Name', 'ASC');
620
        $this->assertEquals(
621
            [
622
            'Bob',
623
            'bonny',
624
            'jane',
625
            'John',
626
            'sam',
627
            'Steve',
628
            'steven'
629
            ],
630
            $list1->column('Name')
631
        );
632
    }
633
634
    /**
635
     * Test DataList->canFilterBy()
636
     */
637
    public function testCanFilterBy()
638
    {
639
        // Basic check
640
        $team = Team::get();
641
        $this->assertTrue($team->canFilterBy("Title"));
642
        $this->assertFalse($team->canFilterBy("SomethingElse"));
643
644
        // Has one
645
        $this->assertTrue($team->canFilterBy("CaptainID"));
646
        $this->assertTrue($team->canFilterBy("Captain.ShirtNumber"));
647
        $this->assertFalse($team->canFilterBy("SomethingElse.ShirtNumber"));
648
        $this->assertFalse($team->canFilterBy("Captain.SomethingElse"));
649
        $this->assertTrue($team->canFilterBy("Captain.FavouriteTeam.Captain.ShirtNumber"));
650
651
        // Has many
652
        $this->assertTrue($team->canFilterBy("Fans.Name"));
653
        $this->assertFalse($team->canFilterBy("SomethingElse.Name"));
654
        $this->assertFalse($team->canFilterBy("Fans.SomethingElse"));
655
656
        // Many many
657
        $this->assertTrue($team->canFilterBy("Players.FirstName"));
658
        $this->assertFalse($team->canFilterBy("SomethingElse.FirstName"));
659
        $this->assertFalse($team->canFilterBy("Players.SomethingElse"));
660
661
        // Subclasses
662
        $subteam = SubTeam::get();
663
        $this->assertTrue($subteam->canFilterBy("Title"));
664
        $this->assertTrue($subteam->canFilterBy("SubclassDatabaseField"));
665
    }
666
667
    /**
668
     * $list->filter('Name', 'bob'); // only bob in the list
669
     */
670
    public function testSimpleFilter()
671
    {
672
        $list = Team::get();
673
        $list = $list->filter('Title', 'Team 2');
674
        $this->assertEquals(1, $list->count());
675
        $this->assertEquals('Team 2', $list->first()->Title, 'List should only contain Team 2');
676
        $this->assertEquals('Team 2', $list->last()->Title, 'Last should only contain Team 2');
677
    }
678
679
    public function testSimpleFilterEndsWith()
680
    {
681
        $list = TeamComment::get();
682
        $list = $list->filter('Name:EndsWith', 'b');
683
        $this->assertEquals(1, $list->count());
684
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
685
    }
686
687
    public function testSimpleFilterExactMatchFilter()
688
    {
689
        $list = TeamComment::get();
690
        $list = $list->filter('Name:ExactMatch', 'Bob');
691
        $this->assertEquals(1, $list->count());
692
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
693
    }
694
695
    public function testSimpleFilterGreaterThanFilter()
696
    {
697
        $list = TeamComment::get();
698
        $list = $list->filter('TeamID:GreaterThan', $this->idFromFixture(DataObjectTest\Team::class, 'team1'));
699
        $this->assertEquals(1, $list->count());
700
        $this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
701
    }
702
703
    public function testSimpleFilterGreaterThanOrEqualFilter()
704
    {
705
        $list = TeamComment::get();
706
        $list = $list->filter(
707
            'TeamID:GreaterThanOrEqual',
708
            $this->idFromFixture(DataObjectTest\Team::class, 'team1')
709
        )->sort("ID");
710
        $this->assertEquals(3, $list->count());
711
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
712
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
713
    }
714
715
    public function testSimpleFilterLessThanFilter()
716
    {
717
        $list = TeamComment::get();
718
        $list = $list->filter(
719
            'TeamID:LessThan',
720
            $this->idFromFixture(DataObjectTest\Team::class, 'team2')
721
        )->sort('Name');
722
        $this->assertEquals(2, $list->count());
723
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
724
        $this->assertEquals('Joe', $list->Last()->Name, 'Last comment should be from Joe');
725
    }
726
727
    public function testSimpleFilterLessThanOrEqualFilter()
728
    {
729
        $list = TeamComment::get();
730
        $list = $list->filter(
731
            'TeamID:LessThanOrEqual',
732
            $this->idFromFixture(DataObjectTest\Team::class, 'team1')
733
        )->sort('ID');
734
        $this->assertEquals(2, $list->count());
735
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
736
        $this->assertEquals('Bob', $list->Last()->Name, 'Last comment should be from Bob');
737
    }
738
739
    public function testSimplePartialMatchFilter()
740
    {
741
        $list = TeamComment::get();
742
        $list = $list->filter('Name:PartialMatch', 'o')->sort('Name');
743
        $this->assertEquals(2, $list->count());
744
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
745
        $this->assertEquals('Joe', $list->last()->Name, 'First comment should be from Joe');
746
    }
747
748
    public function testSimpleFilterStartsWith()
749
    {
750
        $list = TeamComment::get();
751
        $list = $list->filter('Name:StartsWith', 'B');
752
        $this->assertEquals(1, $list->count());
753
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
754
    }
755
756
    public function testSimpleFilterWithNonExistingComparisator()
757
    {
758
        $this->expectException(InjectorNotFoundException::class);
759
        $this->expectExceptionMessageRegExp('/Class "?DataListFilter.Bogus"? does not exist/');
760
761
        $list = TeamComment::get();
762
        $list->filter('Comment:Bogus', 'team comment');
763
    }
764
765
    /**
766
     * Invalid modifiers are treated as failed filter construction
767
     */
768
    public function testInvalidModifier()
769
    {
770
        $this->expectException(InjectorNotFoundException::class);
771
        $this->expectExceptionMessageRegExp('/Class "?DataListFilter.invalidmodifier"? does not exist/');
772
773
        $list = TeamComment::get();
774
        $list->filter('Comment:invalidmodifier', 'team comment');
775
    }
776
777
    /**
778
     * $list->filter('Name', ['aziz', 'bob']); // aziz and bob in list
779
     */
780
    public function testSimpleFilterWithMultiple()
781
    {
782
        $list = TeamComment::get();
783
        $list = $list->filter('Name', ['Bob','Phil']);
784
        $list = $list->sort('Name', 'ASC');
785
        $this->assertEquals(2, $list->count());
786
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
787
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
788
    }
789
790
    public function testMultipleFilterWithNoMatch()
791
    {
792
        $list = TeamComment::get();
793
        $list = $list->filter(['Name'=>'Bob', 'Comment'=>'Phil is a unique guy, and comments on team2']);
794
        $this->assertEquals(0, $list->count());
795
    }
796
797
    /**
798
     *  $list->filter(['Name'=>'bob, 'Age'=>21]); // bob with the age 21
799
     */
800
    public function testFilterMultipleArray()
801
    {
802
        $list = TeamComment::get();
803
        $list = $list->filter(['Name'=>'Bob', 'Comment'=>'This is a team comment by Bob']);
804
        $list = $list->sort('Name', 'ASC');
805
        $this->assertEquals(1, $list->count());
806
        $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
807
    }
808
809
    public function testFilterMultipleWithTwoMatches()
810
    {
811
        $list = TeamComment::get();
812
        $list = $list->filter(['TeamID'=>$this->idFromFixture(DataObjectTest\Team::class, 'team1')]);
813
        $this->assertEquals(2, $list->count());
814
    }
815
816
    public function testFilterMultipleWithArrayFilter()
817
    {
818
        $list = TeamComment::get();
819
        $list = $list->filter(['Name'=>['Bob','Phil']]);
820
        $list = $list->sort('Name', 'ASC');
821
        $this->assertEquals(2, $list->count(), 'There should be two comments');
822
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
823
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
824
    }
825
826
    public function testFilterMultipleWithArrayFilterAndModifiers()
827
    {
828
        $list = TeamComment::get();
829
        $list = $list->filter(['Name:StartsWith'=>['Bo', 'Jo']]);
830
        $list = $list->sort('Name', 'ASC');
831
        $this->assertEquals(2, $list->count());
832
        $this->assertEquals('Bob', $list->first()->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
833
        $this->assertEquals('Joe', $list->last()->Name);
834
    }
835
836
    /**
837
     * $list->filter(['Name'=>['aziz','bob'], 'Age'=>[21, 43]]);
838
     */
839
    public function testFilterArrayInArray()
840
    {
841
        $list = TeamComment::get();
842
        $list = $list->filter(
843
            [
844
            'Name'=>['Bob','Phil'],
845
            'TeamID'=>[$this->idFromFixture(DataObjectTest\Team::class, 'team1')]]
846
        );
847
        $this->assertEquals(1, $list->count(), 'There should be one comment');
848
        $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
849
    }
850
851
    public function testFilterWithModifiers()
852
    {
853
        $list = TeamComment::get();
854
        $nocaseList = $list->filter('Name:nocase', 'bob');
855
        $this->assertEquals(1, $nocaseList->count(), 'There should be one comment');
856
        $caseList = $list->filter('Name:case', 'bob');
857
        $this->assertEquals(0, $caseList->count(), 'There should be no comments');
858
        $gtList = $list->filter(
859
            'TeamID:GreaterThan:not',
860
            $this->idFromFixture(DataObjectTest\Team::class, 'team1')
861
        );
862
        $this->assertEquals(2, $gtList->count());
863
    }
864
865
    /**
866
     * Test that a filter correctly aliases relationships that share common classes
867
     */
868
    public function testFilterSharedRelationalClasses()
869
    {
870
        /** @var Bracket $final1 */
871
        $final1 = $this->objFromFixture(Bracket::class, 'final');
872
        $prefinal1 = $this->objFromFixture(Bracket::class, 'prefinal1');
873
        $prefinal2 = $this->objFromFixture(Bracket::class, 'prefinal2');
874
        $semifinal1 = $this->objFromFixture(Bracket::class, 'semifinal1');
875
        $team2 = $this->objFromFixture(Team::class, 'team2');
876
877
        // grand child can be found from parent
878
        $found = Bracket::get()->filter('Next.Next.Title', $final1->Title);
879
        $this->assertListEquals(
880
            [['Title' => $semifinal1->Title]],
881
            $found
882
        );
883
884
        // grand child can be found from child
885
        $found = Bracket::get()->filter('Next.Title', $prefinal1->Title);
886
        $this->assertListEquals(
887
            [['Title' => $semifinal1->Title]],
888
            $found
889
        );
890
891
        // child can be found from parent
892
        $found = Bracket::get()->filter('Next.Title', $final1->Title);
893
        $this->assertListEquals(
894
            [
895
                ['Title' => $prefinal1->Title],
896
                ['Title' => $prefinal2->Title]
897
            ],
898
            $found
899
        );
900
901
        // Complex filter, get brackets where the following bracket was won by team 1
902
        // Note: Includes results from multiple levels
903
        $found = Bracket::get()->filter('Next.Winner.Title', $team2->Title);
904
        $this->assertListEquals(
905
            [
906
                ['Title' => $prefinal1->Title],
907
                ['Title' => $prefinal2->Title],
908
                ['Title' => $semifinal1->Title]
909
            ],
910
            $found
911
        );
912
    }
913
914
    public function testFilterOnImplicitJoinWithSharedInheritance()
915
    {
916
        $list = DataObjectTest\RelationChildFirst::get()->filter([
917
            'ManyNext.ID' => [
918
                $this->idFromFixture(DataObjectTest\RelationChildSecond::class, 'test1'),
919
                $this->idFromFixture(DataObjectTest\RelationChildSecond::class, 'test2'),
920
            ],
921
        ]);
922
        $this->assertEquals(2, $list->count());
923
        $ids = $list->column('ID');
924
        $this->assertContains($this->idFromFixture(DataObjectTest\RelationChildFirst::class, 'test1'), $ids);
925
        $this->assertContains($this->idFromFixture(DataObjectTest\RelationChildFirst::class, 'test2'), $ids);
926
    }
927
928
    public function testFilterAny()
929
    {
930
        $list = TeamComment::get();
931
        $list = $list->filterAny('Name', 'Bob');
932
        $this->assertEquals(1, $list->count());
933
    }
934
935
    public function testFilterAnyWithRelation()
936
    {
937
        $list = Player::get();
938
        $list = $list->filterAny([
939
            'Teams.Title:StartsWith' => 'Team',
940
            'ID:GreaterThan' => 0,
941
        ]);
942
        $this->assertCount(4, $list);
943
    }
944
945
    public function testFilterAnyWithTwoGreaterThanFilters()
946
    {
947
948
        for ($i=1; $i<=3; $i++) {
949
            $f = new Fixture();
950
            $f->MyDecimal = $i;
0 ignored issues
show
Bug Best Practice introduced by
The property MyDecimal does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
951
            $f->write();
952
953
            $f = new Fixture();
954
            $f->MyInt = $i;
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
955
            $f->write();
956
        }
957
958
        $list = Fixture::get()->filterAny([
959
            'MyDecimal:GreaterThan' => 1, // 2 records
960
            'MyInt:GreaterThan' => 2, // 1 record
961
        ]);
962
963
        $this->assertCount(3, $list);
964
    }
965
966
    public function testFilterAnyMultipleArray()
967
    {
968
        $list = TeamComment::get();
969
        $list = $list->filterAny(['Name'=>'Bob', 'Comment'=>'This is a team comment by Bob']);
970
        $this->assertEquals(1, $list->count());
971
        $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
972
    }
973
974
    public function testFilterAnyOnFilter()
975
    {
976
        $list = TeamComment::get();
977
        $list = $list->filter(
978
            [
979
            'TeamID'=>$this->idFromFixture(DataObjectTest\Team::class, 'team1')
980
            ]
981
        );
982
        $list = $list->filterAny(
983
            [
984
            'Name'=>['Phil', 'Joe'],
985
            'Comment'=>'This is a team comment by Bob'
986
            ]
987
        );
988
        $list = $list->sort('Name');
989
        $this->assertEquals(2, $list->count());
990
        $this->assertEquals(
991
            'Bob',
992
            $list->offsetGet(0)->Name,
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
993
            'Results should include comments from Bob, matched by comment and team'
994
        );
995
        $this->assertEquals(
996
            'Joe',
997
            $list->offsetGet(1)->Name,
998
            'Results should include comments by Joe, matched by name and team (not by comment)'
999
        );
1000
1001
        $list = TeamComment::get();
1002
        $list = $list->filter(
1003
            [
1004
            'TeamID'=>$this->idFromFixture(DataObjectTest\Team::class, 'team1')
1005
            ]
1006
        );
1007
        $list = $list->filterAny(
1008
            [
1009
            'Name'=>['Phil', 'Joe'],
1010
            'Comment'=>'This is a team comment by Bob'
1011
            ]
1012
        );
1013
        $list = $list->sort('Name');
1014
        $list = $list->filter(['Name' => 'Bob']);
1015
        $this->assertEquals(1, $list->count());
1016
        $this->assertEquals(
1017
            'Bob',
1018
            $list->offsetGet(0)->Name,
1019
            'Results should include comments from Bob, matched by name and team'
1020
        );
1021
    }
1022
1023
    public function testFilterAnyMultipleWithArrayFilter()
1024
    {
1025
        $list = TeamComment::get();
1026
        $list = $list->filterAny(['Name'=>['Bob','Phil']]);
1027
        $this->assertEquals(2, $list->count(), 'There should be two comments');
1028
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1029
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
1030
    }
1031
1032
    public function testFilterAnyArrayInArray()
1033
    {
1034
        $list = TeamComment::get();
1035
        $list = $list->filterAny(
1036
            [
1037
            'Name'=>['Bob','Phil'],
1038
            'TeamID'=>[$this->idFromFixture(DataObjectTest\Team::class, 'team1')]]
1039
        )
1040
            ->sort('Name');
1041
        $this->assertEquals(3, $list->count());
1042
        $this->assertEquals(
1043
            'Bob',
1044
            $list->offsetGet(0)->Name,
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1045
            'Results should include comments from Bob, matched by name and team'
1046
        );
1047
        $this->assertEquals(
1048
            'Joe',
1049
            $list->offsetGet(1)->Name,
1050
            'Results should include comments by Joe, matched by team (not by name)'
1051
        );
1052
        $this->assertEquals(
1053
            'Phil',
1054
            $list->offsetGet(2)->Name,
1055
            'Results should include comments from Phil, matched by name (even if he\'s not in Team1)'
1056
        );
1057
    }
1058
1059
    public function testFilterOnJoin()
1060
    {
1061
        $list = TeamComment::get()
1062
            ->leftJoin(
1063
                'DataObjectTest_Team',
1064
                '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"'
1065
            )->filter([
1066
                'Title' => 'Team 1'
1067
            ]);
1068
1069
        $this->assertEquals(2, $list->count());
1070
        $values = $list->column('Name');
1071
        $this->assertEquals(array_intersect($values, ['Joe', 'Bob']), $values);
1072
    }
1073
1074
    public function testFilterOnImplicitJoin()
1075
    {
1076
        // Many to many
1077
        $list = Team::get()
1078
            ->filter('Players.FirstName', ['Captain', 'Captain 2']);
1079
1080
        $this->assertEquals(2, $list->count());
1081
1082
        // Has many
1083
        $list = Team::get()
1084
            ->filter('Comments.Name', ['Joe', 'Phil']);
1085
1086
        $this->assertEquals(2, $list->count());
1087
1088
        // Has one
1089
        $list = Player::get()
1090
            ->filter('FavouriteTeam.Title', 'Team 1');
1091
1092
        $this->assertEquals(1, $list->count());
1093
        $this->assertEquals('007', $list->first()->ShirtNumber);
0 ignored issues
show
Bug Best Practice introduced by
The property ShirtNumber does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1094
    }
1095
1096
    /**
1097
     * @expectedException \InvalidArgumentException
1098
     * @expectedExceptionMessage MascotAnimal is not a relation on model SilverStripe\ORM\Tests\DataObjectTest\Team
1099
     */
1100
    public function testFilterOnInvalidRelation()
1101
    {
1102
        // Filter on missing relation 'MascotAnimal'
1103
        Team::get()
1104
            ->filter('MascotAnimal.Name', 'Richard')
1105
            ->toArray();
1106
    }
1107
1108
    public function testFilterAndExcludeById()
1109
    {
1110
        $id = $this->idFromFixture(SubTeam::class, 'subteam1');
1111
        $list = SubTeam::get()->filter('ID', $id);
1112
        $this->assertEquals($id, $list->first()->ID);
1113
1114
        $list = SubTeam::get();
1115
        $this->assertEquals(3, count($list));
1116
        $this->assertEquals(2, count($list->exclude('ID', $id)));
1117
    }
1118
1119
    /**
1120
     * @skipUpgrade
1121
     */
1122
    public function testFilterByNull()
1123
    {
1124
        $list = Fan::get();
1125
        // Force DataObjectTest_Fan/fan5::Email to empty string
1126
        $fan5id = $this->idFromFixture(Fan::class, 'fan5');
1127
        DB::prepared_query("UPDATE \"DataObjectTest_Fan\" SET \"Email\" = '' WHERE \"ID\" = ?", [$fan5id]);
1128
1129
        // Filter by null email
1130
        $nullEmails = $list->filter('Email', null);
1131
        $this->assertListEquals(
1132
            [
1133
            [
1134
                'Name' => 'Stephen',
1135
            ],
1136
            [
1137
                'Name' => 'Mitch',
1138
            ]
1139
            ],
1140
            $nullEmails
1141
        );
1142
1143
        // Filter by non-null
1144
        $nonNullEmails = $list->filter('Email:not', null);
1145
        $this->assertListEquals(
1146
            [
1147
            [
1148
                'Name' => 'Damian',
1149
                'Email' => '[email protected]',
1150
            ],
1151
            [
1152
                'Name' => 'Richard',
1153
                'Email' => '[email protected]',
1154
            ],
1155
            [
1156
                'Name' => 'Hamish',
1157
            ]
1158
            ],
1159
            $nonNullEmails
1160
        );
1161
1162
        // Filter by empty only
1163
        $emptyOnly = $list->filter('Email', '');
1164
        $this->assertListEquals(
1165
            [
1166
            [
1167
                'Name' => 'Hamish',
1168
            ]
1169
            ],
1170
            $emptyOnly
1171
        );
1172
1173
        // Non-empty only. This should include null values, since ExactMatchFilter works around
1174
        // the caveat that != '' also excludes null values in ANSI SQL-92 behaviour.
1175
        $nonEmptyOnly = $list->filter('Email:not', '');
1176
        $this->assertListEquals(
1177
            [
1178
            [
1179
                'Name' => 'Damian',
1180
                'Email' => '[email protected]',
1181
            ],
1182
            [
1183
                'Name' => 'Richard',
1184
                'Email' => '[email protected]',
1185
            ],
1186
            [
1187
                'Name' => 'Stephen',
1188
            ],
1189
            [
1190
                'Name' => 'Mitch',
1191
            ]
1192
            ],
1193
            $nonEmptyOnly
1194
        );
1195
1196
        // Filter by many including null, empty string, and non-empty
1197
        $items1 = $list->filter('Email', [null, '', '[email protected]']);
1198
        $this->assertListEquals(
1199
            [
1200
            [
1201
                'Name' => 'Damian',
1202
                'Email' => '[email protected]',
1203
            ],
1204
            [
1205
                'Name' => 'Stephen',
1206
            ],
1207
            [
1208
                'Name' => 'Mitch',
1209
            ],
1210
            [
1211
                'Name' => 'Hamish',
1212
            ]
1213
            ],
1214
            $items1
1215
        );
1216
1217
        // Filter exclusion of above list
1218
        $items2 = $list->filter('Email:not', [null, '', '[email protected]']);
1219
        $this->assertListEquals(
1220
            [
1221
            [
1222
                'Name' => 'Richard',
1223
                'Email' => '[email protected]',
1224
            ],
1225
            ],
1226
            $items2
1227
        );
1228
1229
        // Filter by many including empty string and non-empty
1230
        $items3 = $list->filter('Email', ['', '[email protected]']);
1231
        $this->assertListEquals(
1232
            [
1233
            [
1234
                'Name' => 'Damian',
1235
                'Email' => '[email protected]',
1236
            ],
1237
            [
1238
                'Name' => 'Hamish',
1239
            ]
1240
            ],
1241
            $items3
1242
        );
1243
1244
        // Filter by many including empty string and non-empty
1245
        // This also relies no the workaround for null comparison as in the $nonEmptyOnly test
1246
        $items4 = $list->filter('Email:not', ['', '[email protected]']);
1247
        $this->assertListEquals(
1248
            [
1249
            [
1250
                'Name' => 'Richard',
1251
                'Email' => '[email protected]',
1252
            ],
1253
            [
1254
                'Name' => 'Stephen',
1255
            ],
1256
            [
1257
                'Name' => 'Mitch',
1258
            ]
1259
            ],
1260
            $items4
1261
        );
1262
1263
        // Filter by many including empty string and non-empty
1264
        // The extra null check isn't necessary, but check that this doesn't fail
1265
        $items5 = $list->filterAny(
1266
            [
1267
            'Email:not' => ['', '[email protected]'],
1268
            'Email' => null
1269
            ]
1270
        );
1271
        $this->assertListEquals(
1272
            [
1273
            [
1274
                'Name' => 'Richard',
1275
                'Email' => '[email protected]',
1276
            ],
1277
            [
1278
                'Name' => 'Stephen',
1279
            ],
1280
            [
1281
                'Name' => 'Mitch',
1282
            ]
1283
            ],
1284
            $items5
1285
        );
1286
1287
        // Filter by null or empty values
1288
        $items6 = $list->filter('Email', [null, '']);
1289
        $this->assertListEquals(
1290
            [
1291
            [
1292
                'Name' => 'Stephen',
1293
            ],
1294
            [
1295
                'Name' => 'Mitch',
1296
            ],
1297
            [
1298
                'Name' => 'Hamish',
1299
            ]
1300
            ],
1301
            $items6
1302
        );
1303
    }
1304
1305
    /**
1306
     * Test null checks with case modifiers
1307
     */
1308
    public function testFilterByNullCase()
1309
    {
1310
        // Test with case (case/nocase both use same code path)
1311
        // Test with and without null, and with inclusion/exclusion permutations
1312
        $list = Fan::get();
1313
1314
        // Only an explicit NOT NULL should include null values
1315
        $items6 = $list->filter('Email:not:case', [null, '', '[email protected]']);
1316
        $this->assertSQLContains(' AND "DataObjectTest_Fan"."Email" IS NOT NULL', $items6->sql());
1317
1318
        // These should all include values where Email IS NULL
1319
        $items7 = $list->filter('Email:nocase', [null, '', '[email protected]']);
1320
        $this->assertSQLContains(' OR "DataObjectTest_Fan"."Email" IS NULL', $items7->sql());
1321
        $items8 = $list->filter('Email:not:case', ['', '[email protected]']);
1322
        $this->assertSQLContains(' OR "DataObjectTest_Fan"."Email" IS NULL', $items8->sql());
1323
1324
        // These should not contain any null checks at all
1325
        $items9 = $list->filter('Email:nocase', ['', '[email protected]']);
1326
        $this->assertSQLNotContains('"DataObjectTest_Fan"."Email" IS NULL', $items9->sql());
1327
        $this->assertSQLNotContains('"DataObjectTest_Fan"."Email" IS NOT NULL', $items9->sql());
1328
    }
1329
1330
    public function testAggregateDBName()
1331
    {
1332
        $filter = new ExactMatchFilter(
1333
            'Comments.Count()'
1334
        );
1335
        $filter->apply(new DataQuery(DataObjectTest\Team::class));
1336
        $this->assertEquals('COUNT("comments_DataObjectTest_TeamComment"."ID")', $filter->getDBName());
1337
1338
        foreach (['Comments.Max(ID)', 'Comments.Max( ID )', 'Comments.Max(  ID)'] as $name) {
1339
            $filter = new ExactMatchFilter($name);
1340
            $filter->apply(new DataQuery(DataObjectTest\Team::class));
1341
            $this->assertEquals('MAX("comments_DataObjectTest_TeamComment"."ID")', $filter->getDBName());
1342
        }
1343
    }
1344
1345
    public function testAggregateFilterExceptions()
1346
    {
1347
        $ex = null;
1348
        try {
1349
            $filter = new ExactMatchFilter('Comments.Max( This will not parse! )');
0 ignored issues
show
Unused Code introduced by
The assignment to $filter is dead and can be removed.
Loading history...
1350
        } catch (\Exception $e) {
1351
            $ex = $e;
1352
        }
1353
        $this->assertInstanceOf(\InvalidArgumentException::class, $ex);
1354
        $this->assertRegExp('/Malformed/', $ex->getMessage());
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1354
        $this->assertRegExp('/Malformed/', $ex->/** @scrutinizer ignore-call */ getMessage());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1355
1356
1357
        $filter = new ExactMatchFilter('Comments.Max(NonExistentColumn)');
1358
        $filter->setModel(new DataObjectTest\Team());
1359
        $ex = null;
1360
        try {
1361
            $name = $filter->getDBName();
0 ignored issues
show
Unused Code introduced by
The assignment to $name is dead and can be removed.
Loading history...
1362
        } catch (\Exception $e) {
1363
            $ex = $e;
1364
        }
1365
        $this->assertInstanceOf(\InvalidArgumentException::class, $ex);
1366
        $this->assertRegExp('/Invalid column/', $ex->getMessage());
1367
    }
1368
1369
    public function testAggregateFilters()
1370
    {
1371
        $teams = Team::get()->filter('Comments.Count()', 2);
1372
1373
        $team1 = $this->objFromFixture(Team::class, 'team1');
1374
        $team2 = $this->objFromFixture(Team::class, 'team2');
1375
        $team3 = $this->objFromFixture(Team::class, 'team3');
1376
        $team4 = $this->objFromFixture(SubTeam::class, 'subteam1');
1377
        $team5 = $this->objFromFixture(SubTeam::class, 'subteam2_with_player_relation');
1378
        $team6 = $this->objFromFixture(SubTeam::class, 'subteam3_with_empty_fields');
1379
1380
        $company1 = $this->objFromFixture(EquipmentCompany::class, 'equipmentcompany1');
1381
        $company2 = $this->objFromFixture(EquipmentCompany::class, 'equipmentcompany2');
1382
1383
        $company1->CurrentStaff()->add(Staff::create(['Salary' => 3])->write());
0 ignored issues
show
Bug introduced by
The method CurrentStaff() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1383
        $company1->/** @scrutinizer ignore-call */ 
1384
                   CurrentStaff()->add(Staff::create(['Salary' => 3])->write());
Loading history...
1384
        $company1->CurrentStaff()->add(Staff::create(['Salary' => 5])->write());
1385
        $company2->CurrentStaff()->add(Staff::create(['Salary' => 4])->write());
1386
1387
        $this->assertCount(1, $teams);
1388
        $this->assertEquals($team1->ID, $teams->first()->ID);
1389
1390
        $teams = Team::get()->filter('Comments.Count()', [1,2]);
1391
1392
        $this->assertCount(2, $teams);
1393
        foreach ([$team1, $team2] as $expectedTeam) {
1394
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1395
        }
1396
1397
        $teams = Team::get()->filter('Comments.Count():GreaterThan', 1);
1398
1399
        $this->assertCount(1, $teams);
1400
        $this->assertContains(
1401
            $this->objFromFixture(Team::class, 'team1')->ID,
1402
            $teams->column('ID')
1403
        );
1404
1405
        $teams = Team::get()->filter('Comments.Count():LessThan', 2);
1406
1407
        $this->assertCount(5, $teams);
1408
        foreach ([$team2, $team3, $team4, $team5, $team6] as $expectedTeam) {
1409
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1410
        }
1411
1412
        $teams = Team::get()->filter('Comments.Count():GreaterThanOrEqual', 1);
1413
1414
        $this->assertCount(2, $teams);
1415
        foreach ([$team1, $team2] as $expectedTeam) {
1416
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1417
        }
1418
1419
        $teams = Team::get()->filter('Comments.Count():LessThanOrEqual', 1);
1420
1421
        $this->assertCount(5, $teams);
1422
        foreach ([$team2, $team3, $team4, $team5, $team6] as $expectedTeam) {
1423
            $this->assertContains($expectedTeam->ID, $teams->column('ID'));
1424
        }
1425
1426
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Max(Salary)', 5);
1427
        $this->assertCount(1, $companies);
1428
        $this->assertEquals($company1->ID, $companies->first()->ID);
1429
1430
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Min(Salary)', 3);
1431
        $this->assertCount(1, $companies);
1432
        $this->assertEquals($company1->ID, $companies->first()->ID);
1433
1434
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Max(Salary):GreaterThan', 3);
1435
        $this->assertCount(2, $companies);
1436
        foreach ([$company1, $company2] as $expectedTeam) {
1437
            $this->assertContains($expectedTeam->ID, $companies->column('ID'));
1438
        }
1439
1440
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary)', 8);
1441
        $this->assertCount(1, $companies);
1442
        $this->assertEquals($company1->ID, $companies->first()->ID);
1443
1444
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary):LessThan', 7);
1445
        $this->assertCount(1, $companies);
1446
        $this->assertEquals($company2->ID, $companies->first()->ID);
1447
1448
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary):GreaterThan', 100);
1449
        $this->assertCount(0, $companies);
1450
1451
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Sum(Salary):GreaterThan', 7);
1452
        $this->assertCount(1, $companies);
1453
        $this->assertEquals($company1->ID, $companies->first()->ID);
1454
1455
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Avg(Salary)', 4);
1456
        $this->assertCount(2, $companies);
1457
        foreach ([$company1, $company2] as $expectedTeam) {
1458
            $this->assertContains($expectedTeam->ID, $companies->column('ID'));
1459
        }
1460
1461
        $companies = EquipmentCompany::get()->filter('CurrentStaff.Avg(Salary):LessThan', 10);
1462
        $this->assertCount(2, $companies);
1463
        foreach ([$company1, $company2] as $expectedTeam) {
1464
            $this->assertContains($expectedTeam->ID, $companies->column('ID'));
1465
        }
1466
    }
1467
1468
    /**
1469
     * $list = $list->filterByCallback(function($item, $list) { return $item->Age == 21; })
1470
     */
1471
    public function testFilterByCallback()
1472
    {
1473
        $team1ID = $this->idFromFixture(DataObjectTest\Team::class, 'team1');
1474
        $list = TeamComment::get();
1475
        $list = $list->filterByCallback(
1476
            function ($item, $list) use ($team1ID) {
0 ignored issues
show
Unused Code introduced by
The parameter $list is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1476
            function ($item, /** @scrutinizer ignore-unused */ $list) use ($team1ID) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1477
                return $item->TeamID == $team1ID;
1478
            }
1479
        );
1480
1481
        $result = $list->column('Name');
1482
        $expected = array_intersect($result, ['Joe', 'Bob']);
1483
1484
        $this->assertEquals(2, $list->count());
1485
        $this->assertEquals($expected, $result, 'List should only contain comments from Team 1 (Joe and Bob)');
1486
        $this->assertTrue($list instanceof Filterable, 'The List should be of type SS_Filterable');
1487
    }
1488
1489
    /**
1490
     * $list->exclude('Name', 'bob'); // exclude bob from list
1491
     */
1492
    public function testSimpleExclude()
1493
    {
1494
        $list = TeamComment::get();
1495
        $list = $list->exclude('Name', 'Bob');
1496
        $list = $list->sort('Name');
1497
        $this->assertEquals(2, $list->count());
1498
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1499
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
1500
    }
1501
    //
1502
    /**
1503
     * $list->exclude('Name', ['aziz', 'bob']); // exclude aziz and bob from list
1504
     */
1505
    public function testSimpleExcludeWithMultiple()
1506
    {
1507
        $list = TeamComment::get();
1508
        $list = $list->exclude('Name', ['Joe','Phil']);
1509
        $this->assertEquals(1, $list->count());
1510
        $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1511
    }
1512
1513
    /**
1514
     * $list->exclude(['Name'=>'bob, 'Age'=>21]); // negative version
1515
     */
1516
    public function testMultipleExcludeWithMiss()
1517
    {
1518
        $list = TeamComment::get();
1519
        $list = $list->exclude(['Name'=>'Bob', 'Comment'=>'Does not match any comments']);
1520
        $this->assertEquals(3, $list->count());
1521
    }
1522
1523
    /**
1524
     * $list->exclude(['Name'=>'bob, 'Age'=>21]); // exclude bob that has Age 21
1525
     */
1526
    public function testMultipleExclude()
1527
    {
1528
        $list = TeamComment::get();
1529
        $list = $list->exclude(['Name'=>'Bob', 'Comment'=>'This is a team comment by Bob']);
1530
        $this->assertEquals(2, $list->count());
1531
    }
1532
1533
    /**
1534
     * Test doesn't exclude if only matches one
1535
     * $list->exclude(['Name'=>'bob, 'Age'=>21]); // exclude bob that has Age 21
1536
     */
1537
    public function testMultipleExcludeMultipleMatches()
1538
    {
1539
        $list = TeamComment::get();
1540
        $list = $list->exclude(['Name'=>'Bob', 'Comment'=>'Phil is a unique guy, and comments on team2']);
1541
        $this->assertCount(3, $list);
1542
    }
1543
1544
    /**
1545
     * // exclude only those that match both
1546
     */
1547
    public function testMultipleExcludeArraysMultipleMatches()
1548
    {
1549
        $list = TeamComment::get();
1550
        $list = $list->exclude([
1551
            'Name'=> ['Bob', 'Phil'],
1552
            'Comment'=> [
1553
                'This is a team comment by Bob',
1554
                'Phil is a unique guy, and comments on team2'
1555
            ]
1556
        ]);
1557
        $this->assertListEquals([['Name' => 'Joe']], $list);
1558
    }
1559
1560
    /**
1561
     * Exclude only which matches both params
1562
     */
1563
    public function testMultipleExcludeArraysMultipleMatchesOneMiss()
1564
    {
1565
        $list = TeamComment::get();
1566
        $list = $list->exclude([
1567
            'Name' => ['Bob', 'Phil'],
1568
            'Comment' => [
1569
                'Does not match any comments',
1570
                'Phil is a unique guy, and comments on team2'
1571
            ]
1572
        ]);
1573
        $list = $list->sort('Name');
1574
        $this->assertListEquals(
1575
            [
1576
                ['Name' => 'Bob'],
1577
                ['Name' => 'Joe'],
1578
            ],
1579
            $list
1580
        );
1581
    }
1582
1583
    /**
1584
     * Test that if an exclude() is applied to a filter(), the filter() is still preserved.
1585
     */
1586
    public function testExcludeOnFilter()
1587
    {
1588
        $list = TeamComment::get();
1589
        $list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2');
1590
        $list = $list->exclude('Name', 'Bob');
1591
1592
        $sql = $list->sql($parameters);
1593
        $this->assertSQLContains(
1594
            'WHERE ("DataObjectTest_TeamComment"."Comment" = ?) AND (("DataObjectTest_TeamComment"."Name" != ? '
1595
            . 'OR "DataObjectTest_TeamComment"."Name" IS NULL))',
1596
            $sql
1597
        );
1598
        $this->assertEquals(['Phil is a unique guy, and comments on team2', 'Bob'], $parameters);
1599
        $this->assertListEquals([['Name' => 'Phil']], $list);
1600
    }
1601
1602
    /**
1603
     * Test that if a complicated exclude() is applied to a filter(), the filter() is still preserved.
1604
     */
1605
    public function testComplicatedExcludeOnFilter()
1606
    {
1607
        $list = TeamComment::get();
1608
        $list = $list->filter('Name', ['Phil', 'Bob']);
1609
        $list = $list->exclude('Name', ['Bob', 'Joe']);
1610
1611
        $sql = $list->sql($parameters);
1612
        $this->assertSQLContains(
1613
            'WHERE ("DataObjectTest_TeamComment"."Name" IN (?, ?)) AND (("DataObjectTest_TeamComment"."Name" NOT IN (?, ?) '
1614
            . 'OR "DataObjectTest_TeamComment"."Name" IS NULL))',
1615
            $sql
1616
        );
1617
        $this->assertEquals(['Phil', 'Bob', 'Bob', 'Joe'], $parameters);
1618
        $this->assertListEquals([['Name' => 'Phil']], $list);
1619
    }
1620
1621
    /**
1622
     * Test that if a very complicated exclude() is applied to a filter(), the filter() is still preserved.
1623
     */
1624
    public function testVeryComplicatedExcludeOnFilter()
1625
    {
1626
        $list = TeamComment::get();
1627
        $list = $list->filter('Name', ['Phil', 'Bob']);
1628
        $list = $list->exclude([
1629
            'Name' => ['Joe', 'Phil'],
1630
            'Comment' => ['Matches no comments', 'Not a matching comment']
1631
        ]);
1632
1633
        $sql = $list->sql($parameters);
1634
        $this->assertSQLContains(
1635
            'WHERE ("DataObjectTest_TeamComment"."Name" IN (?, ?)) '
1636
            . 'AND (("DataObjectTest_TeamComment"."Name" NOT IN (?, ?) '
1637
            . 'OR "DataObjectTest_TeamComment"."Name" IS NULL) '
1638
            . 'OR ("DataObjectTest_TeamComment"."Comment" NOT IN (?, ?) '
1639
            . 'OR "DataObjectTest_TeamComment"."Comment" IS NULL))',
1640
            $sql
1641
        );
1642
        $this->assertEquals(['Phil', 'Bob', 'Joe', 'Phil', 'Matches no comments', 'Not a matching comment'], $parameters);
1643
        $list = $list->sort('Name');
1644
        $this->assertListEquals(
1645
            [
1646
                ['Name' => 'Bob'],
1647
                ['Name' => 'Phil'],
1648
            ],
1649
            $list
1650
        );
1651
    }
1652
1653
    public function testExcludeWithSearchFilter()
1654
    {
1655
        $list = TeamComment::get();
1656
        $list = $list->exclude('Name:LessThan', 'Bob');
1657
1658
        $sql = $list->sql($parameters);
1659
        $this->assertSQLContains('WHERE (("DataObjectTest_TeamComment"."Name" >= ?))', $sql);
1660
        $this->assertEquals(['Bob'], $parameters);
1661
    }
1662
1663
    /**
1664
     * Test that Bob and Phil are excluded (one match each)
1665
     */
1666
    public function testExcludeAny()
1667
    {
1668
        $list = TeamComment::get();
1669
        $list = $list->excludeAny([
1670
            'Name' => 'Bob',
1671
            'Comment' => 'Phil is a unique guy, and comments on team2'
1672
        ]);
1673
        $this->assertListEquals([['Name' => 'Joe']], $list);
1674
    }
1675
1676
    /**
1677
     * Test that Bob and Phil are excluded by Name
1678
     */
1679
    public function testExcludeAnyArrays()
1680
    {
1681
        $list = TeamComment::get();
1682
        $list = $list->excludeAny([
1683
            'Name' => ['Bob', 'Phil'],
1684
            'Comment' => 'No matching comments'
1685
        ]);
1686
        $this->assertListEquals([['Name' => 'Joe']], $list);
1687
    }
1688
1689
    /**
1690
     * Test that Bob is excluded by Name, Phil by comment
1691
     */
1692
    public function testExcludeAnyMultiArrays()
1693
    {
1694
        $list = TeamComment::get();
1695
        $list = $list->excludeAny([
1696
            'Name' => ['Bob', 'Fred'],
1697
            'Comment' => ['No matching comments', 'Phil is a unique guy, and comments on team2']
1698
        ]);
1699
        $this->assertListEquals([['Name' => 'Joe']], $list);
1700
    }
1701
1702
    /**
1703
     * Test exact match filter with empty array items
1704
     *
1705
     * @expectedException \InvalidArgumentException
1706
     * @expectedExceptionMessage Cannot filter "DataObjectTest_TeamComment"."Name" against an empty set
1707
     */
1708
    public function testEmptyFilter()
1709
    {
1710
        $list = TeamComment::get();
1711
        $list->exclude('Name', []);
1712
    }
1713
1714
    /**
1715
     * $list->exclude(['Name'=>'bob, 'Age'=>[21, 43]]); // exclude bob with Age 21 or 43
1716
     */
1717
    public function testMultipleExcludeWithMultipleThatCheersEitherTeam()
1718
    {
1719
        $list = TeamComment::get();
1720
        $list = $list->exclude(
1721
            ['Name'=>'Bob', 'TeamID'=>[
1722
            $this->idFromFixture(DataObjectTest\Team::class, 'team1'),
1723
            $this->idFromFixture(DataObjectTest\Team::class, 'team2')
1724
            ]]
1725
        );
1726
        $list = $list->sort('Name');
1727
        $this->assertEquals(2, $list->count());
1728
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1729
        $this->assertEquals('Phil', $list->last()->Name, 'First comment should be from Phil');
1730
    }
1731
1732
    /**
1733
     * $list->exclude(['Name'=>'bob, 'Age'=>[21, 43]]); // negative version
1734
     */
1735
    public function testMultipleExcludeWithMultipleThatCheersOnNonExistingTeam()
1736
    {
1737
        $list = TeamComment::get();
1738
        $list = $list->exclude(['Name'=>'Bob', 'TeamID'=>[3]]);
1739
        $this->assertEquals(3, $list->count());
1740
    }
1741
1742
    /**
1743
     * $list->exclude(['Name'=>['bob','phil'], 'Age'=>[21, 43]]); //negative version
1744
     */
1745
    public function testMultipleExcludeWithNoExclusion()
1746
    {
1747
        $list = TeamComment::get();
1748
        $list = $list->exclude(
1749
            [
1750
            'Name'=>['Bob','Joe'],
1751
            'Comment' => 'Phil is a unique guy, and comments on team2']
1752
        );
1753
        $this->assertEquals(3, $list->count());
1754
    }
1755
1756
    /**
1757
     *  $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
1758
     */
1759
    public function testMultipleExcludeWithTwoArray()
1760
    {
1761
        $list = TeamComment::get();
1762
        $list = $list->exclude(
1763
            ['Name' => ['Bob','Joe'], 'TeamID' => [
1764
            $this->idFromFixture(DataObjectTest\Team::class, 'team1'),
1765
            $this->idFromFixture(DataObjectTest\Team::class, 'team2')
1766
            ]]
1767
        );
1768
        $this->assertEquals(1, $list->count());
1769
        $this->assertEquals('Phil', $list->last()->Name, 'Only comment should be from Phil');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1770
    }
1771
1772
    /**
1773
     *  $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
1774
     */
1775
    public function testMultipleExcludeWithTwoArrayOneTeam()
1776
    {
1777
        $list = TeamComment::get();
1778
        $list = $list->exclude(
1779
            [
1780
            'Name' => ['Bob', 'Phil'],
1781
            'TeamID' => [$this->idFromFixture(DataObjectTest\Team::class, 'team1')]]
1782
        );
1783
        $list = $list->sort('Name');
1784
        $this->assertEquals(2, $list->count());
1785
        $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1786
        $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil');
1787
    }
1788
1789
    /**
1790
     *
1791
     */
1792
    public function testSortByRelation()
1793
    {
1794
        $list = TeamComment::get();
1795
        $list = $list->sort(['Team.Title' => 'DESC']);
1796
        $this->assertEquals(3, $list->count());
1797
        $this->assertEquals(
1798
            $this->idFromFixture(DataObjectTest\Team::class, 'team2'),
1799
            $list->first()->TeamID,
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1800
            'First comment should be for Team 2'
1801
        );
1802
        $this->assertEquals(
1803
            $this->idFromFixture(DataObjectTest\Team::class, 'team1'),
1804
            $list->last()->TeamID,
1805
            'Last comment should be for Team 1'
1806
        );
1807
    }
1808
1809
    public function testReverse()
1810
    {
1811
        $list = TeamComment::get();
1812
        $list = $list->sort('Name');
1813
        $list = $list->reverse();
1814
1815
        $this->assertEquals('Bob', $list->last()->Name, 'Last comment should be from Bob');
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1816
        $this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
1817
    }
1818
1819
    public function testSortByComplexExpression()
1820
    {
1821
        // Test an expression with both spaces and commas. This test also tests that column() can be called
1822
        // with a complex sort expression, so keep using column() below
1823
        $teamClass = Convert::raw2sql(SubTeam::class);
1824
        $list = Team::get()->sort(
1825
            'CASE WHEN "DataObjectTest_Team"."ClassName" = \'' . $teamClass . '\' THEN 0 ELSE 1 END, "Title" DESC'
1826
        );
1827
        $this->assertEquals(
1828
            [
1829
            'Subteam 3',
1830
            'Subteam 2',
1831
            'Subteam 1',
1832
            'Team 3',
1833
            'Team 2',
1834
            'Team 1',
1835
            ],
1836
            $list->column("Title")
1837
        );
1838
    }
1839
1840
    public function testShuffle()
1841
    {
1842
        $list = Team::get()->shuffle();
1843
1844
        $this->assertSQLContains(DB::get_conn()->random() . ' AS "_SortColumn', $list->dataQuery()->sql());
1845
    }
1846
1847
    public function testColumnFailureInvalidColumn()
1848
    {
1849
        $this->expectException(InvalidArgumentException::class);
1850
1851
        Category::get()->column('ObviouslyInvalidColumn');
1852
    }
1853
1854
    public function testColumnFailureInvalidTable()
1855
    {
1856
        $this->expectException(InvalidArgumentException::class);
1857
1858
        $columnName = null;
1859
        Category::get()
1860
            ->applyRelation('Products.ID', $columnName)
1861
            ->column('"ObviouslyInvalidTable"."ID"');
1862
    }
1863
1864
    public function testColumnFromRelatedTable()
1865
    {
1866
        $columnName = null;
1867
        $productTitles = Category::get()
1868
            ->applyRelation('Products.Title', $columnName)
1869
            ->column($columnName);
1870
1871
        $productTitles = array_diff($productTitles, [null]);
1872
        sort($productTitles);
1873
1874
        $this->assertEquals([
1875
            'Product A',
1876
            'Product B',
1877
        ], $productTitles);
1878
    }
1879
1880
    public function testChunkedFetch()
1881
    {
1882
        $expectedIDs = Team::get()->map('ID', 'ID')->toArray();
1883
        $expectedSize = sizeof($expectedIDs);
1884
1885
        $dataQuery = new DataListQueryCounter(Team::class);
1886
        $this->chunkTester(
1887
            $expectedIDs,
1888
            Team::get()->setDataQuery($dataQuery)->chunkedFetch(),
1889
            $dataQuery,
1890
            1
1891
        );
1892
1893
        $dataQuery = new DataListQueryCounter(Team::class);
1894
        $this->chunkTester(
1895
            $expectedIDs,
1896
            Team::get()->setDataQuery($dataQuery)->chunkedFetch(1),
1897
            $dataQuery,
1898
            $expectedSize+1
1899
        );
1900
1901
        $dataQuery = new DataListQueryCounter(Team::class);
1902
        $this->chunkTester(
1903
            $expectedIDs,
1904
            Team::get()->setDataQuery($dataQuery)->chunkedFetch($expectedSize),
1905
            $dataQuery,
1906
            2
1907
        );
1908
1909
        $dataQuery = new DataListQueryCounter(Team::class);
1910
        $this->chunkTester(
1911
            $expectedIDs,
1912
            Team::get()->setDataQuery($dataQuery)->chunkedFetch($expectedSize-1),
1913
            $dataQuery,
1914
            2
1915
        );
1916
1917
        $dataQuery = new DataListQueryCounter(Team::class);
1918
        $this->chunkTester(
1919
            $expectedIDs,
1920
            Team::get()->setDataQuery($dataQuery)->chunkedFetch($expectedSize+1),
1921
            $dataQuery,
1922
            1
1923
        );
1924
    }
1925
1926
    public function testFilteredChunk()
1927
    {
1928
        $dataQuery = new DataListQueryCounter(Team::class);
1929
        $this->chunkTester(
1930
            Team::get()->filter('ClassName', Team::class)->map('ID', 'ID')->toArray(),
1931
            Team::get()->setDataQuery($dataQuery)->filter('ClassName', Team::class)->chunkedFetch(),
1932
            $dataQuery,
1933
            1
1934
        );
1935
    }
1936
1937
    public function testSortedChunk()
1938
    {
1939
        $dataQuery = new DataListQueryCounter(Team::class);
1940
        $this->chunkTester(
1941
            Team::get()->sort('ID', 'Desc')->map('ID', 'ID')->toArray(),
1942
            Team::get()->setDataQuery($dataQuery)->sort('ID', 'Desc')->chunkedFetch(),
1943
            $dataQuery,
1944
            1
1945
        );
1946
    }
1947
1948
    public function testEmptyChunk()
1949
    {
1950
        $dataQuery = new DataListQueryCounter(Team::class);
1951
        $this->chunkTester(
1952
            [],
1953
            Team::get()->setDataQuery($dataQuery)->filter('ClassName', 'non-sense')->chunkedFetch(),
1954
            $dataQuery,
1955
            1
1956
        );
1957
    }
1958
1959
    public function testInvalidChunkSize()
1960
    {
1961
        $this->expectException(InvalidArgumentException::class);
1962
        foreach (Team::get()->chunkedFetch(0) as $item) {
1963
            // You don't get the error until you iterate over the list
1964
        };
1965
    }
1966
1967
    /**
1968
     * Loop over a chunck list and make sure it matches our expected results
1969
     * @param int[] $expectedIDs
1970
     * @param iterable $chunkList
1971
     */
1972
    private function chunkTester(
1973
        array $expectedIDs,
1974
        iterable $chunkList,
1975
        DataListQueryCounter $dataQuery,
1976
        int $expectedQueryCount
1977
    ) {
1978
        foreach ($chunkList as $chunkedTeam) {
1979
            $this->assertInstanceOf(
1980
                Team::class,
1981
                $chunkedTeam,
1982
                'Chunk return the correct type of data object'
1983
            );
1984
1985
            $expectedID = array_shift($expectedIDs);
1986
1987
            $this->assertEquals(
1988
                $expectedID,
1989
                $chunkedTeam->ID,
1990
                'chunk returns the same results in the same order as the regular iterator'
1991
            );
1992
        }
1993
1994
        $this->assertEmpty($expectedIDs, 'chunk returns all the results that the regular iterator does');
1995
        $this->assertEquals($expectedQueryCount, $dataQuery->getCount());
1996
    }
1997
}
1998