Completed
Push — 4.3 ( d63e4b...4cfce3 )
by Maxime
39s queued 22s
created

DataQueryTest::testMultipleRelationSort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 16
rs 9.9
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\ORM\DataQuery;
6
use SilverStripe\ORM\DataObject;
7
use SilverStripe\ORM\DB;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\Security\Member;
10
11
/**
12
 * @skipUpgrade
13
 */
14
class DataQueryTest extends SapphireTest
15
{
16
17
    protected static $fixture_file = 'DataQueryTest.yml';
18
19
    protected static $extra_dataobjects = array(
20
        DataQueryTest\ObjectA::class,
21
        DataQueryTest\ObjectB::class,
22
        DataQueryTest\ObjectC::class,
23
        DataQueryTest\ObjectD::class,
24
        DataQueryTest\ObjectE::class,
25
        DataQueryTest\ObjectF::class,
26
        DataQueryTest\ObjectG::class,
27
        DataQueryTest\ObjectH::class,
28
        DataQueryTest\ObjectI::class,
29
        SQLSelectTest\TestObject::class,
30
        SQLSelectTest\TestBase::class,
31
        SQLSelectTest\TestChild::class,
32
    );
33
34
    public function testSortByJoinedFieldRetainsSourceInformation()
35
    {
36
        $bar = new DataQueryTest\ObjectC();
37
        $bar->Title = "Bar";
38
        $bar->write();
39
40
        $foo = new DataQueryTest\ObjectB();
41
        $foo->Title = "Foo";
42
        $foo->TestC = $bar->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property TestC does not exist on SilverStripe\ORM\Tests\DataQueryTest\ObjectB. Since you implemented __set, consider adding a @property annotation.
Loading history...
43
        $foo->write();
44
45
        $query = new DataQuery(DataQueryTest\ObjectB::class);
46
        $result = $query->leftJoin(
47
            'DataQueryTest_C',
48
            "\"DataQueryTest_B\".\"TestCID\" = \"DataQueryTest_B\".\"ID\""
49
        )->sort('"DataQueryTest_B"."Title"', 'ASC');
50
51
        $result = $result->execute()->record();
52
        $this->assertEquals('Foo', $result['Title']);
53
    }
54
55
    /**
56
     * Test the leftJoin() and innerJoin method of the DataQuery object
57
     */
58
    public function testJoins()
59
    {
60
        $dq = new DataQuery(Member::class);
61
        $dq->innerJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
62
        $this->assertSQLContains(
63
            "INNER JOIN \"Group_Members\" ON \"Group_Members\".\"MemberID\" = \"Member\".\"ID\"",
64
            $dq->sql($parameters)
65
        );
66
67
        $dq = new DataQuery(Member::class);
68
        $dq->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
69
        $this->assertSQLContains(
70
            "LEFT JOIN \"Group_Members\" ON \"Group_Members\".\"MemberID\" = \"Member\".\"ID\"",
71
            $dq->sql($parameters)
72
        );
73
    }
74
75
    public function testApplyRelation()
76
    {
77
        // Test applyRelation with two has_ones pointing to the same class
78
        $dq = new DataQuery(DataQueryTest\ObjectB::class);
79
        $dq->applyRelation('TestC');
80
        $this->assertTrue($dq->query()->isJoinedTo('testc_DataQueryTest_C'));
81
        $this->assertContains('"testc_DataQueryTest_C"."ID" = "DataQueryTest_B"."TestCID"', $dq->sql());
82
83
        $dq = new DataQuery(DataQueryTest\ObjectB::class);
84
        $dq->applyRelation('TestCTwo');
85
        $this->assertTrue($dq->query()->isJoinedTo('testctwo_DataQueryTest_C'));
86
        $this->assertContains('"testctwo_DataQueryTest_C"."ID" = "DataQueryTest_B"."TestCTwoID"', $dq->sql());
87
    }
88
89
    public function testApplyRelationDeepInheritance()
90
    {
91
        //test has_one relation
92
        $newDQ = new DataQuery(DataQueryTest\ObjectE::class);
93
        //apply a relation to a relation from an ancestor class
94
        $newDQ->applyRelation('TestA');
95
        $this->assertTrue($newDQ->query()->isJoinedTo('DataQueryTest_C'));
96
        $this->assertContains('"testa_DataQueryTest_A"."ID" = "DataQueryTest_C"."TestAID"', $newDQ->sql($params));
97
98
        //test many_many relation
99
100
        //test many_many with separate inheritance
101
        $newDQ = new DataQuery(DataQueryTest\ObjectC::class);
102
        $baseDBTable = DataObject::getSchema()->baseDataTable(DataQueryTest\ObjectC::class);
103
        $newDQ->applyRelation('ManyTestAs');
104
        //check we are "joined" to the DataObject's table (there is no distinction between FROM or JOIN clauses)
105
        $this->assertTrue($newDQ->query()->isJoinedTo($baseDBTable));
106
        //check we are explicitly selecting "FROM" the DO's table
107
        $this->assertContains("FROM \"$baseDBTable\"", $newDQ->sql());
108
109
        //test many_many with shared inheritance
110
        $newDQ = new DataQuery(DataQueryTest\ObjectE::class);
111
        $baseDBTable = DataObject::getSchema()->baseDataTable(DataQueryTest\ObjectE::class);
112
        //check we are "joined" to the DataObject's table (there is no distinction between FROM or JOIN clauses)
113
        $this->assertTrue($newDQ->query()->isJoinedTo($baseDBTable));
114
        //check we are explicitly selecting "FROM" the DO's table
115
        $this->assertContains("FROM \"$baseDBTable\"", $newDQ->sql(), 'The FROM clause is missing from the query');
116
        $newDQ->applyRelation('ManyTestGs');
117
        //confirm we are still joined to the base table
118
        $this->assertTrue($newDQ->query()->isJoinedTo($baseDBTable));
119
        //double check it is the "FROM" clause
120
        $this->assertContains("FROM \"$baseDBTable\"", $newDQ->sql(), 'The FROM clause has been removed from the query');
121
        //another (potentially less crude check) for checking "FROM" clause
122
        $fromTables = $newDQ->query()->getFrom();
123
        $this->assertEquals('"' . $baseDBTable . '"', $fromTables[$baseDBTable]);
124
    }
125
126
    public function testRelationReturn()
127
    {
128
        $dq = new DataQuery(DataQueryTest\ObjectC::class);
129
        $this->assertEquals(
130
            DataQueryTest\ObjectA::class,
131
            $dq->applyRelation('TestA'),
132
            'DataQuery::applyRelation should return the name of the related object.'
133
        );
134
        $this->assertEquals(
135
            DataQueryTest\ObjectA::class,
136
            $dq->applyRelation('TestAs'),
137
            'DataQuery::applyRelation should return the name of the related object.'
138
        );
139
        $this->assertEquals(
140
            DataQueryTest\ObjectA::class,
141
            $dq->applyRelation('ManyTestAs'),
142
            'DataQuery::applyRelation should return the name of the related object.'
143
        );
144
145
        $this->assertEquals(
146
            DataQueryTest\ObjectB::class,
147
            $dq->applyRelation('TestB'),
148
            'DataQuery::applyRelation should return the name of the related object.'
149
        );
150
        $this->assertEquals(
151
            DataQueryTest\ObjectB::class,
152
            $dq->applyRelation('TestBs'),
153
            'DataQuery::applyRelation should return the name of the related object.'
154
        );
155
        $this->assertEquals(
156
            DataQueryTest\ObjectB::class,
157
            $dq->applyRelation('ManyTestBs'),
158
            'DataQuery::applyRelation should return the name of the related object.'
159
        );
160
        $newDQ = new DataQuery(DataQueryTest\ObjectE::class);
161
        $this->assertEquals(
162
            DataQueryTest\ObjectA::class,
163
            $newDQ->applyRelation('TestA'),
164
            'DataQuery::applyRelation should return the name of the related object.'
165
        );
166
    }
167
168
    public function testRelationOrderWithCustomJoin()
169
    {
170
        $dataQuery = new DataQuery(DataQueryTest\ObjectB::class);
171
        $dataQuery->innerJoin('DataQueryTest_D', '"DataQueryTest_D"."RelationID" = "DataQueryTest_B"."ID"');
172
        $dataQuery->execute();
173
    }
174
175
    public function testDisjunctiveGroup()
176
    {
177
        $dq = new DataQuery(DataQueryTest\ObjectA::class);
178
179
        $dq->where('DataQueryTest_A.ID = 2');
180
        $subDq = $dq->disjunctiveGroup();
181
        $subDq->where('DataQueryTest_A.Name = \'John\'');
182
        $subDq->where('DataQueryTest_A.Name = \'Bob\'');
183
184
        $this->assertSQLContains(
185
            "WHERE (DataQueryTest_A.ID = 2) AND ((DataQueryTest_A.Name = 'John') OR (DataQueryTest_A.Name = 'Bob'))",
186
            $dq->sql($parameters)
187
        );
188
    }
189
190
    public function testConjunctiveGroup()
191
    {
192
        $dq = new DataQuery(DataQueryTest\ObjectA::class);
193
194
        $dq->where('DataQueryTest_A.ID = 2');
195
        $subDq = $dq->conjunctiveGroup();
196
        $subDq->where('DataQueryTest_A.Name = \'John\'');
197
        $subDq->where('DataQueryTest_A.Name = \'Bob\'');
198
199
        $this->assertSQLContains(
200
            "WHERE (DataQueryTest_A.ID = 2) AND ((DataQueryTest_A.Name = 'John') AND (DataQueryTest_A.Name = 'Bob'))",
201
            $dq->sql($parameters)
202
        );
203
    }
204
205
    /**
206
     * @todo Test paramaterised
207
     */
208
    public function testNestedGroups()
209
    {
210
        $dq = new DataQuery(DataQueryTest\ObjectA::class);
211
212
        $dq->where('DataQueryTest_A.ID = 2');
213
        $subDq = $dq->disjunctiveGroup();
214
        $subDq->where('DataQueryTest_A.Name = \'John\'');
215
        $subSubDq = $subDq->conjunctiveGroup();
216
        $subSubDq->where('DataQueryTest_A.Age = 18');
217
        $subSubDq->where('DataQueryTest_A.Age = 50');
218
        $subDq->where('DataQueryTest_A.Name = \'Bob\'');
219
220
        $this->assertSQLContains(
221
            "WHERE (DataQueryTest_A.ID = 2) AND ((DataQueryTest_A.Name = 'John') OR ((DataQueryTest_A.Age = 18) "
222
                . "AND (DataQueryTest_A.Age = 50)) OR (DataQueryTest_A.Name = 'Bob'))",
223
            $dq->sql($parameters)
224
        );
225
    }
226
227
    public function testEmptySubgroup()
228
    {
229
        $dq = new DataQuery(DataQueryTest\ObjectA::class);
230
        $dq->conjunctiveGroup();
231
232
        // Empty groups should have no where condition at all
233
        $this->assertSQLNotContains('WHERE', $dq->sql($parameters));
234
    }
235
236
    public function testSubgroupHandoff()
237
    {
238
        $dq = new DataQuery(DataQueryTest\ObjectA::class);
239
        $subDq = $dq->disjunctiveGroup();
240
241
        $orgDq = clone $dq;
242
243
        $subDq->sort('"DataQueryTest_A"."Name"');
244
        $orgDq->sort('"DataQueryTest_A"."Name"');
245
246
        $this->assertSQLEquals($dq->sql($parameters), $orgDq->sql($parameters));
247
248
        $subDq->limit(5, 7);
249
        $orgDq->limit(5, 7);
250
251
        $this->assertSQLEquals($dq->sql($parameters), $orgDq->sql($parameters));
252
    }
253
254
    public function testOrderByMultiple()
255
    {
256
        $dq = new DataQuery(SQLSelectTest\TestObject::class);
257
        $dq = $dq->sort('"Name" ASC, MID("Name", 8, 1) DESC');
258
        $this->assertContains(
259
            'ORDER BY "SQLSelectTest_DO"."Name" ASC, "_SortColumn0" DESC',
260
            $dq->sql($parameters)
261
        );
262
    }
263
264
    public function testDefaultSort()
265
    {
266
        $query = new DataQuery(DataQueryTest\ObjectE::class);
267
        $result = $query->column('Title');
268
        $this->assertEquals(array('First', 'Second', 'Last'), $result);
269
    }
270
271
    public function testDistinct()
272
    {
273
        $query = new DataQuery(DataQueryTest\ObjectE::class);
274
        $this->assertContains('SELECT DISTINCT', $query->sql($params), 'Query is set as distinct by default');
275
276
        $query = $query->distinct(false);
277
        $this->assertNotContains('SELECT DISTINCT', $query->sql($params), 'Query does not contain distinct');
278
279
        $query = $query->distinct(true);
280
        $this->assertContains('SELECT DISTINCT', $query->sql($params), 'Query contains distinct');
281
    }
282
283
    public function testComparisonClauseInt()
284
    {
285
        DB::query("INSERT INTO \"DataQueryTest_F\" (\"SortOrder\") VALUES (2)");
286
        $query = new DataQuery(DataQueryTest\ObjectF::class);
287
        $query->where(DB::get_conn()->comparisonClause('"SortOrder"', '2'));
288
        $this->assertGreaterThan(0, $query->count(), "Couldn't find SortOrder");
289
        static::resetDBSchema(true);
290
    }
291
292
    public function testComparisonClauseDateFull()
293
    {
294
        DB::query("INSERT INTO \"DataQueryTest_F\" (\"MyDate\") VALUES ('1988-03-04 06:30')");
295
        $query = new DataQuery(DataQueryTest\ObjectF::class);
296
        $query->where(DB::get_conn()->comparisonClause('"MyDate"', '1988-03-04%'));
297
        $this->assertGreaterThan(0, $query->count(), "Couldn't find MyDate");
298
        static::resetDBSchema(true);
299
    }
300
301
    public function testSurrogateFieldSort()
302
    {
303
        $query = new DataQuery(DataQueryTest\ObjectE::class);
304
        $query->sort(
305
            sprintf(
306
                '(case when "Title" = %s then 1 else 0 end)',
307
                DB::get_conn()->quoteString('Second')
308
            ),
309
            'DESC',
310
            true
311
        );
312
        $query->sort('SortOrder', 'ASC', false);
313
        $query->sort(
314
            sprintf(
315
                '(case when "Title" = %s then 0 else 1 end)',
316
                DB::get_conn()->quoteString('Fourth')
317
            ),
318
            'DESC',
319
            false
320
        );
321
        $this->assertEquals(
322
            $query->execute()->column('Title'),
323
            $query->column('Title')
324
        );
325
    }
326
327
    public function testComparisonClauseDateStartsWith()
328
    {
329
        DB::query("INSERT INTO \"DataQueryTest_F\" (\"MyDate\") VALUES ('1988-03-04 06:30')");
330
        $query = new DataQuery(DataQueryTest\ObjectF::class);
331
        $query->where(DB::get_conn()->comparisonClause('"MyDate"', '1988%'));
332
        $this->assertGreaterThan(0, $query->count(), "Couldn't find MyDate");
333
        static::resetDBSchema(true);
334
    }
335
336
    public function testComparisonClauseDateStartsPartial()
337
    {
338
        DB::query("INSERT INTO \"DataQueryTest_F\" (\"MyDate\") VALUES ('1988-03-04 06:30')");
339
        $query = new DataQuery(DataQueryTest\ObjectF::class);
340
        $query->where(DB::get_conn()->comparisonClause('"MyDate"', '%03-04%'));
341
        $this->assertGreaterThan(0, $query->count(), "Couldn't find MyDate");
342
        static::resetDBSchema(true);
343
    }
344
345
    public function testComparisonClauseTextCaseInsensitive()
346
    {
347
        DB::query("INSERT INTO \"DataQueryTest_F\" (\"MyString\") VALUES ('HelloWorld')");
348
        $query = new DataQuery(DataQueryTest\ObjectF::class);
349
        $query->where(DB::get_conn()->comparisonClause('"MyString"', 'helloworld'));
350
        $this->assertGreaterThan(0, $query->count(), "Couldn't find MyString");
351
        static::resetDBSchema(true);
352
    }
353
354
    public function testComparisonClauseTextCaseSensitive()
355
    {
356
        DB::query("INSERT INTO \"DataQueryTest_F\" (\"MyString\") VALUES ('HelloWorld')");
357
        $query = new DataQuery(DataQueryTest\ObjectF::class);
358
        $query->where(DB::get_conn()->comparisonClause('"MyString"', 'HelloWorld', false, false, true));
359
        $this->assertGreaterThan(0, $query->count(), "Couldn't find MyString");
360
361
        $query2 = new DataQuery(DataQueryTest\ObjectF::class);
362
        $query2->where(DB::get_conn()->comparisonClause('"MyString"', 'helloworld', false, false, true));
363
        $this->assertEquals(0, $query2->count(), "Found mystring. Shouldn't be able too.");
364
        static::resetDBSchema(true);
365
    }
366
367
    /**
368
     * Tests that getFinalisedQuery can include all tables
369
     */
370
    public function testConditionsIncludeTables()
371
    {
372
        // Including filter on parent table only doesn't pull in second
373
        $query = new DataQuery(DataQueryTest\ObjectC::class);
374
        $query->sort('"SortOrder"');
375
        $query->where(
376
            array(
377
            '"DataQueryTest_C"."Title" = ?' => array('First')
378
            )
379
        );
380
        $result = $query->getFinalisedQuery(array('Title'));
381
        $from = $result->getFrom();
382
        $this->assertContains('DataQueryTest_C', array_keys($from));
383
        $this->assertNotContains('DataQueryTest_E', array_keys($from));
384
385
        // Including filter on sub-table requires it
386
        $query = new DataQuery(DataQueryTest\ObjectC::class);
387
        $query->sort('"SortOrder"');
388
        $query->where(
389
            array(
390
            '"DataQueryTest_C"."Title" = ? OR "DataQueryTest_E"."SortOrder" > ?' => array(
391
                'First', 2
392
            )
393
            )
394
        );
395
        $result = $query->getFinalisedQuery(array('Title'));
396
        $from = $result->getFrom();
397
398
        // Check that including "SortOrder" prompted inclusion of DataQueryTest_E table
399
        $this->assertContains('DataQueryTest_C', array_keys($from));
400
        $this->assertContains('DataQueryTest_E', array_keys($from));
401
        $arrayResult = iterator_to_array($result->execute());
402
        $first = array_shift($arrayResult);
403
        $this->assertNotNull($first);
404
        $this->assertEquals('First', $first['Title']);
405
        $second = array_shift($arrayResult);
406
        $this->assertNotNull($second);
407
        $this->assertEquals('Last', $second['Title']);
408
        $this->assertEmpty(array_shift($arrayResult));
409
    }
410
411
    public function testColumnReturnsAllValues()
412
    {
413
        $first = new DataQueryTest\ObjectA();
414
        $first->Name = 'Bar';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataQueryTest\ObjectA. Since you implemented __set, consider adding a @property annotation.
Loading history...
415
        $first->write();
416
417
        $second = new DataQueryTest\ObjectA();
418
        $second->Name = 'Foo';
419
        $second->write();
420
421
        $third = new DataQueryTest\ObjectA();
422
        $third->Name = 'Bar';
423
        $third->write();
424
425
        $result = DataQueryTest\ObjectA::get()->column('Name');
426
        $this->assertEquals(['Bar', 'Foo', 'Bar'], $result);
427
    }
428
429
    public function testColumnUniqueReturnsAllValues()
430
    {
431
        $first = new DataQueryTest\ObjectA();
432
        $first->Name = 'Bar';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataQueryTest\ObjectA. Since you implemented __set, consider adding a @property annotation.
Loading history...
433
        $first->write();
434
435
        $second = new DataQueryTest\ObjectA();
436
        $second->Name = 'Foo';
437
        $second->write();
438
439
        $third = new DataQueryTest\ObjectA();
440
        $third->Name = 'Bar';
441
        $third->write();
442
443
        $result = DataQueryTest\ObjectA::get()->columnUnique('Name');
444
        $this->assertCount(2, $result);
445
        $this->assertContains('Bar', $result);
446
        $this->assertContains('Foo', $result);
447
    }
448
449
    /**
450
     * Tests that sorting against multiple relationships is working
451
     */
452
    public function testMultipleRelationSort()
453
    {
454
        $query = new DataQuery(DataQueryTest\ObjectH::class);
455
        $query->applyRelation('ManyTestEs');
456
        $query->applyRelation('ManyTestIs');
457
        $query->sort([
0 ignored issues
show
Bug introduced by
array('"manytestes_DataQ...Order"', '"SortOrder"') of type array<integer,string> is incompatible with the type string expected by parameter $sort of SilverStripe\ORM\DataQuery::sort(). ( Ignorable by Annotation )

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

457
        $query->sort(/** @scrutinizer ignore-type */ [
Loading history...
458
            '"manytestes_DataQueryTest_E"."SortOrder"',
459
            '"manytestis_DataQueryTest_I"."SortOrder"',
460
            '"SortOrder"',
461
        ]);
462
463
        $titles = $query->column('Name');
464
465
        $this->assertEquals('First', $titles[0]);
466
        $this->assertEquals('Second', $titles[1]);
467
        $this->assertEquals('Last', $titles[2]);
468
    }
469
}
470