Completed
Push — 4 ( 6b7842...1eb66e )
by Robbie
27s queued 18s
created

testExistsCreatesFunctionalQueries()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

454
        $query->sort(/** @scrutinizer ignore-type */ [
Loading history...
455
            '"manytestes_DataQueryTest_E"."SortOrder"',
456
            '"manytestis_DataQueryTest_I"."SortOrder"',
457
            '"SortOrder"',
458
        ]);
459
460
        $titles = $query->column('Name');
461
462
        $this->assertEquals('First', $titles[0]);
463
        $this->assertEquals('Second', $titles[1]);
464
        $this->assertEquals('Last', $titles[2]);
465
    }
466
467
    public function testExistsCreatesFunctionalQueries()
468
    {
469
        $this->assertTrue(ObjectE::get()->exists());
470
        $this->assertFalse(ObjectE::get()->where(['"Title" = ?' => 'Foo'])->exists());
471
        $this->assertTrue(ObjectE::get()->dataQuery()->groupby('"SortOrder"')->exists());
472
    }
473
}
474