Passed
Pull Request — 4 (#10028)
by Steve
09:01
created

SQLSelectTest   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 847
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 495
dl 0
loc 847
rs 6
c 0
b 0
f 0
wmc 55

How to fix   Complexity   

Complex Class

Complex classes like SQLSelectTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SQLSelectTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\ORM\Connect\MySQLDatabase;
8
use SilverStripe\ORM\Queries\SQLSelect;
9
use SilverStripe\SQLite\SQLite3Database;
10
use SilverStripe\PostgreSQL\PostgreSQLDatabase;
11
use SilverStripe\Dev\Deprecation;
12
use SilverStripe\Dev\SapphireTest;
13
14
class SQLSelectTest extends SapphireTest
15
{
16
17
    protected static $fixture_file = 'SQLSelectTest.yml';
18
19
    protected static $extra_dataobjects = [
20
        SQLSelectTest\TestObject::class,
21
        SQLSelectTest\TestBase::class,
22
        SQLSelectTest\TestChild::class
23
    ];
24
25
    protected $oldDeprecation = null;
26
27
    protected function setUp(): void
28
    {
29
        parent::setUp();
30
        $this->oldDeprecation = Deprecation::dump_settings();
31
    }
32
33
    protected function tearDown(): void
34
    {
35
        Deprecation::restore_settings($this->oldDeprecation);
36
        parent::tearDown();
37
    }
38
39
    public function testCount()
40
    {
41
42
        //basic counting
43
        $qry = SQLSelectTest\TestObject::get()->dataQuery()->getFinalisedQuery();
44
        $ids = $this->allFixtureIDs(SQLSelectTest\TestObject::class);
45
        $count = $qry->count('"SQLSelectTest_DO"."ID"');
46
        $this->assertEquals(count($ids), $count);
47
        $this->assertInternalType('int', ($count);
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ';', expecting ',' or ')' on line 47 at column 49
Loading history...
48
        //test with `having`
49
        if (DB::get_conn() instanceof MySQLDatabase) {
50
            $qry->setSelect([
51
                'Date' => 'MAX("Date")',
52
                'Common' => '"Common"',
53
            ]);
54
            $qry->setGroupBy('"Common"');
55
            $qry->setHaving('"Date" > 2012-02-01');
56
            $count = $qry->count('"SQLSelectTest_DO"."ID"');
57
            $this->assertEquals(1, $count);
58
            $this->assertInternalType('int', ($count);
59
        }
60
    }
61
    public function testUnlimitedRowCount()
62
    {
63
        //basic counting
64
        $qry = SQLSelectTest\TestObject::get()->dataQuery()->getFinalisedQuery();
65
        $ids = $this->allFixtureIDs(SQLSelectTest\TestObject::class);
66
        $qry->setLimit(1);
67
        $count = $qry->unlimitedRowCount('"SQLSelectTest_DO"."ID"');
68
        $this->assertEquals(count($ids), $count);
69
        $this->assertInternalType('int', ($count);
70
        // Test without column - SQLSelect has different logic for this
71
        $count = $qry->unlimitedRowCount();
72
        $this->assertEquals(2, $count);
73
        $this->assertInternalType('int', ($count);
74
        //test with `having`
75
        if (DB::get_conn() instanceof MySQLDatabase) {
76
            $qry->setHaving('"Date" > 2012-02-01');
77
            $count = $qry->unlimitedRowCount('"SQLSelectTest_DO"."ID"');
78
            $this->assertEquals(1, $count);
79
            $this->assertInternalType('int', ($count);
80
        }
81
    }
82
83
    public function testEmptyQueryReturnsNothing()
84
    {
85
        $query = new SQLSelect();
86
        $this->assertSQLEquals('', $query->sql($parameters));
87
    }
88
89
    public function testSelectFromBasicTable()
90
    {
91
        $query = new SQLSelect();
92
        $query->setFrom('MyTable');
93
        $this->assertSQLEquals("SELECT * FROM MyTable", $query->sql($parameters));
94
        $query->addFrom('MyJoin');
95
        $this->assertSQLEquals("SELECT * FROM MyTable MyJoin", $query->sql($parameters));
96
    }
97
98
    public function testSelectFromUserSpecifiedFields()
99
    {
100
        $query = new SQLSelect();
101
        $query->setSelect(["Name", "Title", "Description"]);
102
        $query->setFrom("MyTable");
103
        $this->assertSQLEquals("SELECT Name, Title, Description FROM MyTable", $query->sql($parameters));
104
    }
105
106
    public function testSelectWithWhereClauseFilter()
107
    {
108
        $query = new SQLSelect();
109
        $query->setSelect(["Name","Meta"]);
110
        $query->setFrom("MyTable");
111
        $query->setWhere("Name = 'Name'");
112
        $query->addWhere("Meta = 'Test'");
113
        $this->assertSQLEquals(
114
            "SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test')",
115
            $query->sql($parameters)
116
        );
117
    }
118
119
    public function testSelectWithConstructorParameters()
120
    {
121
        $query = new SQLSelect(["Foo", "Bar"], "FooBarTable");
122
        $this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable", $query->sql($parameters));
123
        $query = new SQLSelect(["Foo", "Bar"], "FooBarTable", ["Foo = 'Boo'"]);
124
        $this->assertSQLEquals("SELECT Foo, Bar FROM FooBarTable WHERE (Foo = 'Boo')", $query->sql($parameters));
125
    }
126
127
    public function testSelectWithChainedMethods()
128
    {
129
        $query = new SQLSelect();
130
        $query->setSelect("Name", "Meta")->setFrom("MyTable")->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'");
131
        $this->assertSQLEquals(
132
            "SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test')",
133
            $query->sql($parameters)
134
        );
135
    }
136
137
    public function testCanSortBy()
138
    {
139
        $query = new SQLSelect();
140
        $query->setSelect("Name", "Meta")->setFrom("MyTable")->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'");
141
        $this->assertTrue($query->canSortBy('Name ASC'));
142
        $this->assertTrue($query->canSortBy('Name'));
143
    }
144
145
    /**
146
     * Test multiple order by SQL clauses.
147
     */
148
    public function testAddOrderBy()
149
    {
150
        $query = new SQLSelect();
151
        $query->setSelect('ID', "Title")->setFrom('Page')->addOrderBy('(ID % 2)  = 0', 'ASC')->addOrderBy('ID > 50', 'ASC');
152
        $this->assertSQLEquals(
153
            'SELECT ID, Title, (ID % 2)  = 0 AS "_SortColumn0", ID > 50 AS "_SortColumn1" FROM Page ORDER BY "_SortColumn0" ASC, "_SortColumn1" ASC',
154
            $query->sql($parameters)
155
        );
156
    }
157
158
    public function testSelectWithChainedFilterParameters()
159
    {
160
        $query = new SQLSelect();
161
        $query->setSelect(["Name","Meta"])->setFrom("MyTable");
162
        $query->setWhere("Name = 'Name'")->addWhere("Meta = 'Test'")->addWhere("Beta != 'Gamma'");
163
        $this->assertSQLEquals(
164
            "SELECT Name, Meta FROM MyTable WHERE (Name = 'Name') AND (Meta = 'Test') AND (Beta != 'Gamma')",
165
            $query->sql($parameters)
166
        );
167
    }
168
169
    public function testSelectWithLimitClause()
170
    {
171
        if (!(DB::get_conn() instanceof MySQLDatabase || DB::get_conn() instanceof SQLite3Database
172
            || DB::get_conn() instanceof PostgreSQLDatabase)
173
        ) {
174
            $this->markTestIncomplete();
175
        }
176
177
        $query = new SQLSelect();
178
        $query->setFrom("MyTable");
179
        $query->setLimit(99);
180
        $this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99", $query->sql($parameters));
181
182
        // array limit with start (MySQL specific)
183
        $query = new SQLSelect();
184
        $query->setFrom("MyTable");
185
        $query->setLimit(99, 97);
186
        $this->assertSQLEquals("SELECT * FROM MyTable LIMIT 99 OFFSET 97", $query->sql($parameters));
187
    }
188
189
    public function testSelectWithOrderbyClause()
190
    {
191
        $query = new SQLSelect();
192
        $query->setFrom("MyTable");
193
        $query->setOrderBy('MyName');
194
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC', $query->sql($parameters));
195
196
        $query = new SQLSelect();
197
        $query->setFrom("MyTable");
198
        $query->setOrderBy('MyName desc');
199
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $query->sql($parameters));
200
201
        $query = new SQLSelect();
202
        $query->setFrom("MyTable");
203
        $query->setOrderBy('MyName ASC, Color DESC');
204
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color DESC', $query->sql($parameters));
205
206
        $query = new SQLSelect();
207
        $query->setFrom("MyTable");
208
        $query->setOrderBy('MyName ASC, Color');
209
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color ASC', $query->sql($parameters));
210
211
        $query = new SQLSelect();
212
        $query->setFrom("MyTable");
213
        $query->setOrderBy(['MyName' => 'desc']);
214
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC', $query->sql($parameters));
215
216
        $query = new SQLSelect();
217
        $query->setFrom("MyTable");
218
        $query->setOrderBy(['MyName' => 'desc', 'Color']);
219
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY MyName DESC, Color ASC', $query->sql($parameters));
220
221
        $query = new SQLSelect();
222
        $query->setFrom("MyTable");
223
        $query->setOrderBy('implode("MyName","Color")');
224
        $this->assertSQLEquals(
225
            'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC',
226
            $query->sql($parameters)
227
        );
228
229
        $query = new SQLSelect();
230
        $query->setFrom("MyTable");
231
        $query->setOrderBy('implode("MyName","Color") DESC');
232
        $this->assertSQLEquals(
233
            'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" DESC',
234
            $query->sql($parameters)
235
        );
236
237
        $query = new SQLSelect();
238
        $query->setFrom("MyTable");
239
        $query->setOrderBy('RAND()');
240
        $this->assertSQLEquals(
241
            'SELECT *, RAND() AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC',
242
            $query->sql($parameters)
243
        );
244
245
        $query = new SQLSelect();
246
        $query->setFrom("MyTable");
247
        $query->addFrom('INNER JOIN SecondTable USING (ID)');
248
        $query->addFrom('INNER JOIN ThirdTable USING (ID)');
249
        $query->setOrderBy('MyName');
250
        $this->assertSQLEquals(
251
            'SELECT * FROM MyTable '
252
            . 'INNER JOIN SecondTable USING (ID) '
253
            . 'INNER JOIN ThirdTable USING (ID) '
254
            . 'ORDER BY MyName ASC',
255
            $query->sql($parameters)
256
        );
257
    }
258
259
    public function testNullLimit()
260
    {
261
        $query = new SQLSelect();
262
        $query->setFrom("MyTable");
263
        $query->setLimit(null);
264
265
        $this->assertSQLEquals(
266
            'SELECT * FROM MyTable',
267
            $query->sql($parameters)
268
        );
269
    }
270
271
    public function testZeroLimit()
272
    {
273
        $this->expectError();
274
        Deprecation::notification_version('4.3.0');
275
        $query = new SQLSelect();
276
        $query->setFrom("MyTable");
277
        $query->setLimit(0);
278
    }
279
280
    public function testZeroLimitWithOffset()
281
    {
282
        $this->expectError();
283
        Deprecation::notification_version('4.3.0');
284
        if (!(DB::get_conn() instanceof MySQLDatabase || DB::get_conn() instanceof SQLite3Database
285
            || DB::get_conn() instanceof PostgreSQLDatabase)
286
        ) {
287
            $this->markTestIncomplete();
288
        }
289
290
        $query = new SQLSelect();
291
        $query->setFrom("MyTable");
292
        $query->setLimit(0, 99);
293
    }
294
295
    public function testNegativeLimit()
296
    {
297
        $this->expectException(InvalidArgumentException::class);
298
        $query = new SQLSelect();
299
        $query->setLimit(-10);
300
    }
301
302
    public function testNegativeOffset()
303
    {
304
        $this->expectException(InvalidArgumentException::class);
305
        $query = new SQLSelect();
306
        $query->setLimit(1, -10);
307
    }
308
309
    public function testNegativeOffsetAndLimit()
310
    {
311
        $this->expectException(InvalidArgumentException::class);
312
        $query = new SQLSelect();
313
        $query->setLimit(-10, -10);
314
    }
315
316
    public function testReverseOrderBy()
317
    {
318
        $query = new SQLSelect();
319
        $query->setFrom('MyTable');
320
321
        // default is ASC
322
        $query->setOrderBy("Name");
323
        $query->reverseOrderBy();
324
325
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC', $query->sql($parameters));
326
327
        $query->setOrderBy("Name DESC");
328
        $query->reverseOrderBy();
329
330
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC', $query->sql($parameters));
331
332
        $query->setOrderBy(["Name" => "ASC"]);
333
        $query->reverseOrderBy();
334
335
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name DESC', $query->sql($parameters));
336
337
        $query->setOrderBy(["Name" => 'DESC', 'Color' => 'asc']);
338
        $query->reverseOrderBy();
339
340
        $this->assertSQLEquals('SELECT * FROM MyTable ORDER BY Name ASC, Color DESC', $query->sql($parameters));
341
342
        $query->setOrderBy('implode("MyName","Color") DESC');
343
        $query->reverseOrderBy();
344
345
        $this->assertSQLEquals(
346
            'SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY "_SortColumn0" ASC',
347
            $query->sql($parameters)
348
        );
349
    }
350
351
    public function testFiltersOnID()
352
    {
353
        $query = new SQLSelect();
354
        $query->setWhere("ID = 5");
355
        $this->assertTrue(
356
            $query->filtersOnID(),
357
            "filtersOnID() is true with simple unquoted column name"
358
        );
359
360
        $query = new SQLSelect();
361
        $query->setWhere('"ID" = 5');
362
        $this->assertTrue(
363
            $query->filtersOnID(),
364
            "filtersOnID() is true with simple quoted column name"
365
        );
366
367
        $query = new SQLSelect();
368
        $query->setWhere(['"ID"' => 4]);
369
        $this->assertTrue(
370
            $query->filtersOnID(),
371
            "filtersOnID() is true with parameterised quoted column name"
372
        );
373
374
        $query = new SQLSelect();
375
        $query->setWhere(['"ID" = ?' => 4]);
376
        $this->assertTrue(
377
            $query->filtersOnID(),
378
            "filtersOnID() is true with parameterised quoted column name"
379
        );
380
381
        $query = new SQLSelect();
382
        $query->setWhere('"ID" IN (5,4)');
383
        $this->assertTrue(
384
            $query->filtersOnID(),
385
            "filtersOnID() is true with WHERE ID IN"
386
        );
387
388
        $query = new SQLSelect();
389
        $query->setWhere(['"ID" IN ?' => [1,2]]);
390
        $this->assertTrue(
391
            $query->filtersOnID(),
392
            "filtersOnID() is true with parameterised WHERE ID IN"
393
        );
394
395
        $query = new SQLSelect();
396
        $query->setWhere("ID=5");
397
        $this->assertTrue(
398
            $query->filtersOnID(),
399
            "filtersOnID() is true with simple unquoted column name and no spaces in equals sign"
400
        );
401
402
        $query = new SQLSelect();
403
        $query->setWhere("Identifier = 5");
404
        $this->assertFalse(
405
            $query->filtersOnID(),
406
            "filtersOnID() is false with custom column name (starting with 'id')"
407
        );
408
409
        $query = new SQLSelect();
410
        $query->setWhere("ParentID = 5");
411
        $this->assertFalse(
412
            $query->filtersOnID(),
413
            "filtersOnID() is false with column name ending in 'ID'"
414
        );
415
416
        $query = new SQLSelect();
417
        $query->setWhere("MyTable.ID = 5");
418
        $this->assertTrue(
419
            $query->filtersOnID(),
420
            "filtersOnID() is true with table and column name"
421
        );
422
423
        $query = new SQLSelect();
424
        $query->setWhere("MyTable.ID = 5");
425
        $this->assertTrue(
426
            $query->filtersOnID(),
427
            "filtersOnID() is true with table and quoted column name "
428
        );
429
    }
430
431
    public function testFiltersOnFK()
432
    {
433
        $query = new SQLSelect();
434
        $query->setWhere("ID = 5");
435
        $this->assertFalse(
436
            $query->filtersOnFK(),
437
            "filtersOnFK() is true with simple unquoted column name"
438
        );
439
440
        $query = new SQLSelect();
441
        $query->setWhere("Identifier = 5");
442
        $this->assertFalse(
443
            $query->filtersOnFK(),
444
            "filtersOnFK() is false with custom column name (starting with 'id')"
445
        );
446
447
        $query = new SQLSelect();
448
        $query->setWhere("MyTable.ParentID = 5");
449
        $this->assertTrue(
450
            $query->filtersOnFK(),
451
            "filtersOnFK() is true with table and column name"
452
        );
453
454
        $query = new SQLSelect();
455
        $query->setWhere("MyTable.`ParentID`= 5");
456
        $this->assertTrue(
457
            $query->filtersOnFK(),
458
            "filtersOnFK() is true with table and quoted column name "
459
        );
460
    }
461
462
    public function testInnerJoin()
463
    {
464
        $query = new SQLSelect();
465
        $query->setFrom('MyTable');
466
        $query->addInnerJoin('MyOtherTable', 'MyOtherTable.ID = 2');
467
        $query->addLeftJoin('MyLastTable', 'MyOtherTable.ID = MyLastTable.ID');
468
469
        $this->assertSQLEquals(
470
            'SELECT * FROM MyTable ' .
471
            'INNER JOIN "MyOtherTable" ON MyOtherTable.ID = 2 ' .
472
            'LEFT JOIN "MyLastTable" ON MyOtherTable.ID = MyLastTable.ID',
473
            $query->sql($parameters)
474
        );
475
476
        $query = new SQLSelect();
477
        $query->setFrom('MyTable');
478
        $query->addInnerJoin('MyOtherTable', 'MyOtherTable.ID = 2', 'table1');
479
        $query->addLeftJoin('MyLastTable', 'MyOtherTable.ID = MyLastTable.ID', 'table2');
480
481
        $this->assertSQLEquals(
482
            'SELECT * FROM MyTable ' .
483
            'INNER JOIN "MyOtherTable" AS "table1" ON MyOtherTable.ID = 2 ' .
484
            'LEFT JOIN "MyLastTable" AS "table2" ON MyOtherTable.ID = MyLastTable.ID',
485
            $query->sql($parameters)
486
        );
487
    }
488
489
    public function testJoinSubSelect()
490
    {
491
492
        $query = new SQLSelect();
493
        $query->setFrom('MyTable');
494
        $query->addInnerJoin(
495
            '(SELECT * FROM MyOtherTable)',
496
            'Mot.MyTableID = MyTable.ID',
497
            'Mot'
498
        );
499
        $query->addLeftJoin(
500
            '(SELECT MyLastTable.MyOtherTableID, COUNT(1) as MyLastTableCount FROM MyLastTable '
501
            . 'GROUP BY MyOtherTableID)',
502
            'Mlt.MyOtherTableID = Mot.ID',
503
            'Mlt'
504
        );
505
        $query->setOrderBy('COALESCE(Mlt.MyLastTableCount, 0) DESC');
506
507
        $this->assertSQLEquals(
508
            'SELECT *, COALESCE(Mlt.MyLastTableCount, 0) AS "_SortColumn0" FROM MyTable ' .
509
            'INNER JOIN (SELECT * FROM MyOtherTable) AS "Mot" ON Mot.MyTableID = MyTable.ID ' .
510
            'LEFT JOIN (SELECT MyLastTable.MyOtherTableID, COUNT(1) as MyLastTableCount FROM MyLastTable '
511
            . 'GROUP BY MyOtherTableID) AS "Mlt" ON Mlt.MyOtherTableID = Mot.ID ' .
512
            'ORDER BY "_SortColumn0" DESC',
513
            $query->sql($parameters)
514
        );
515
    }
516
517
    public function testSetWhereAny()
518
    {
519
        $query = new SQLSelect();
520
        $query->setFrom('MyTable');
521
522
        $query->setWhereAny(
523
            [
524
            'Monkey' => 'Chimp',
525
            'Color' => 'Brown'
526
            ]
527
        );
528
        $sql = $query->sql($parameters);
529
        $this->assertSQLEquals("SELECT * FROM MyTable WHERE ((Monkey = ?) OR (Color = ?))", $sql);
530
        $this->assertEquals(['Chimp', 'Brown'], $parameters);
531
    }
532
533
    public function testSelectFirst()
534
    {
535
        // Test first from sequence
536
        $query = new SQLSelect();
537
        $query->setFrom('"SQLSelectTest_DO"');
538
        $query->setOrderBy('"Name"');
539
        $result = $query->firstRow()->execute();
540
541
        $records = [];
542
        foreach ($result as $row) {
543
            $records[] = $row;
544
        }
545
546
        $this->assertCount(1, $records);
547
        $this->assertEquals('Object 1', $records[0]['Name']);
548
549
        // Test first from empty sequence
550
        $query = new SQLSelect();
551
        $query->setFrom('"SQLSelectTest_DO"');
552
        $query->setOrderBy('"Name"');
553
        $query->setWhere(['"Name"' => 'Nonexistent Object']);
554
        $result = $query->firstRow()->execute();
555
556
        $records = [];
557
        foreach ($result as $row) {
558
            $records[] = $row;
559
        }
560
561
        $this->assertCount(0, $records);
562
563
        // Test that given the last item, the 'first' in this list matches the last
564
        $query = new SQLSelect();
565
        $query->setFrom('"SQLSelectTest_DO"');
566
        $query->setOrderBy('"Name"');
567
        $query->setLimit(1, 1);
568
        $result = $query->firstRow()->execute();
569
570
        $records = [];
571
        foreach ($result as $row) {
572
            $records[] = $row;
573
        }
574
575
        $this->assertCount(1, $records);
576
        $this->assertEquals('Object 2', $records[0]['Name']);
577
    }
578
579
    public function testSelectLast()
580
    {
581
        // Test last in sequence
582
        $query = new SQLSelect();
583
        $query->setFrom('"SQLSelectTest_DO"');
584
        $query->setOrderBy('"Name"');
585
        $result = $query->lastRow()->execute();
586
587
        $records = [];
588
        foreach ($result as $row) {
589
            $records[] = $row;
590
        }
591
592
        $this->assertCount(1, $records);
593
        $this->assertEquals('Object 2', $records[0]['Name']);
594
595
        // Test last from empty sequence
596
        $query = new SQLSelect();
597
        $query->setFrom('"SQLSelectTest_DO"');
598
        $query->setOrderBy('"Name"');
599
        $query->setWhere(["\"Name\" = 'Nonexistent Object'"]);
600
        $result = $query->lastRow()->execute();
601
602
        $records = [];
603
        foreach ($result as $row) {
604
            $records[] = $row;
605
        }
606
607
        $this->assertCount(0, $records);
608
609
        // Test that given the first item, the 'last' in this list matches the first
610
        $query = new SQLSelect();
611
        $query->setFrom('"SQLSelectTest_DO"');
612
        $query->setOrderBy('"Name"');
613
        $query->setLimit(1);
614
        $result = $query->lastRow()->execute();
615
616
        $records = [];
617
        foreach ($result as $row) {
618
            $records[] = $row;
619
        }
620
621
        $this->assertCount(1, $records);
622
        $this->assertEquals('Object 1', $records[0]['Name']);
623
    }
624
625
    /**
626
     * Tests aggregate() function
627
     */
628
    public function testAggregate()
629
    {
630
        $query = new SQLSelect('"Common"');
631
        $query->setFrom('"SQLSelectTest_DO"');
632
        $query->setGroupBy('"Common"');
633
634
        $queryClone = $query->aggregate('COUNT(*)', 'cnt');
635
        $result = $queryClone->execute();
636
        $this->assertEquals([2], $result->column('cnt'));
637
    }
638
639
    /**
640
     * Tests that an ORDER BY is only added if a LIMIT is set.
641
     */
642
    public function testAggregateNoOrderByIfNoLimit()
643
    {
644
        $query = new SQLSelect();
645
        $query->setFrom('"SQLSelectTest_DO"');
646
        $query->setOrderBy('Common');
647
        $query->setLimit([]);
648
649
        $aggregate = $query->aggregate('MAX("ID")');
650
        $limit = $aggregate->getLimit();
651
        $this->assertEquals([], $aggregate->getOrderBy());
652
        $this->assertEquals([], $limit);
653
654
        $query = new SQLSelect();
655
        $query->setFrom('"SQLSelectTest_DO"');
656
        $query->setOrderBy('Common');
657
        $query->setLimit(2);
658
659
        $aggregate = $query->aggregate('MAX("ID")');
660
        $limit = $aggregate->getLimit();
661
        $this->assertEquals(['Common' => 'ASC'], $aggregate->getOrderBy());
662
        $this->assertEquals(['start' => 0, 'limit' => 2], $limit);
663
    }
664
665
    /**
666
     * Test that "_SortColumn0" is added for an aggregate in the ORDER BY
667
     * clause, in combination with a LIMIT and GROUP BY clause.
668
     * For some databases, like MSSQL, this is a complicated scenario
669
     * because a subselect needs to be done to query paginated data.
670
     */
671
    public function testOrderByContainingAggregateAndLimitOffset()
672
    {
673
        $query = new SQLSelect();
674
        $query->setSelect(['"Name"', '"Meta"']);
675
        $query->setFrom('"SQLSelectTest_DO"');
676
        $query->setOrderBy(['MAX("Date")']);
677
        $query->setGroupBy(['"Name"', '"Meta"']);
678
        $query->setLimit('1', '1');
679
680
        $records = [];
681
        foreach ($query->execute() as $record) {
682
            $records[] = $record;
683
        }
684
685
        $this->assertCount(1, $records);
686
687
        $this->assertEquals('Object 2', $records[0]['Name']);
688
        $this->assertEquals('2012-05-01 09:00:00', $records['0']['_SortColumn0']);
689
    }
690
691
    /**
692
     * Test that multiple order elements are maintained in the given order
693
     */
694
    public function testOrderByMultiple()
695
    {
696
        if (DB::get_conn() instanceof MySQLDatabase) {
697
            $query = new SQLSelect();
698
            $query->setSelect(['"Name"', '"Meta"']);
699
            $query->setFrom('"SQLSelectTest_DO"');
700
            $query->setOrderBy(['MID("Name", 8, 1) DESC', '"Name" ASC']);
701
702
            $records = [];
703
            foreach ($query->execute() as $record) {
704
                $records[] = $record;
705
            }
706
707
            $this->assertCount(2, $records);
708
709
            $this->assertEquals('Object 2', $records[0]['Name']);
710
            $this->assertEquals('2', $records[0]['_SortColumn0']);
711
712
            $this->assertEquals('Object 1', $records[1]['Name']);
713
            $this->assertEquals('1', $records[1]['_SortColumn0']);
714
        }
715
    }
716
717
    public function testSelect()
718
    {
719
        $query = new SQLSelect('"Title"', '"MyTable"');
720
        $query->addSelect('"TestField"');
721
        $this->assertSQLEquals(
722
            'SELECT "Title", "TestField" FROM "MyTable"',
723
            $query->sql()
724
        );
725
726
        // Test replacement of select
727
        $query->setSelect(
728
            [
729
            'Field' => '"Field"',
730
            'AnotherAlias' => '"AnotherField"'
731
            ]
732
        );
733
        $this->assertSQLEquals(
734
            'SELECT "Field", "AnotherField" AS "AnotherAlias" FROM "MyTable"',
735
            $query->sql()
736
        );
737
738
        // Check that ' as ' selects don't get mistaken as aliases
739
        $query->addSelect(
740
            [
741
            'Relevance' => "MATCH (Title, MenuTitle) AGAINST ('Two as One')"
742
            ]
743
        );
744
        $this->assertSQLEquals(
745
            'SELECT "Field", "AnotherField" AS "AnotherAlias", MATCH (Title, MenuTitle) AGAINST (' .
746
            '\'Two as One\') AS "Relevance" FROM "MyTable"',
747
            $query->sql()
748
        );
749
    }
750
751
    /**
752
     * Test passing in a LIMIT with OFFSET clause string.
753
     */
754
    public function testLimitSetFromClauseString()
755
    {
756
        $query = new SQLSelect();
757
        $query->setSelect('*');
758
        $query->setFrom('"SQLSelectTest_DO"');
759
760
        $query->setLimit('20 OFFSET 10');
761
        $limit = $query->getLimit();
762
        $this->assertEquals(20, $limit['limit']);
763
        $this->assertEquals(10, $limit['start']);
764
    }
765
766
    public function testParameterisedInnerJoins()
767
    {
768
        $query = new SQLSelect();
769
        $query->setSelect(['"SQLSelectTest_DO"."Name"', '"SubSelect"."Count"']);
770
        $query->setFrom('"SQLSelectTest_DO"');
771
        $query->addInnerJoin(
772
            '(SELECT "Title", COUNT(*) AS "Count" FROM "SQLSelectTestBase" GROUP BY "Title" HAVING "Title" NOT LIKE ?)',
773
            '"SQLSelectTest_DO"."Name" = "SubSelect"."Title"',
774
            'SubSelect',
775
            20,
776
            ['%MyName%']
777
        );
778
        $query->addWhere(['"SQLSelectTest_DO"."Date" > ?' => '2012-08-08 12:00']);
779
780
        $this->assertSQLEquals(
781
            'SELECT "SQLSelectTest_DO"."Name", "SubSelect"."Count"
782
			FROM "SQLSelectTest_DO" INNER JOIN (SELECT "Title", COUNT(*) AS "Count" FROM "SQLSelectTestBase"
783
		   GROUP BY "Title" HAVING "Title" NOT LIKE ?) AS "SubSelect" ON "SQLSelectTest_DO"."Name" =
784
		   "SubSelect"."Title"
785
			WHERE ("SQLSelectTest_DO"."Date" > ?)',
786
            $query->sql($parameters)
787
        );
788
        $this->assertEquals(['%MyName%', '2012-08-08 12:00'], $parameters);
789
        $query->execute();
790
    }
791
792
    public function testParameterisedLeftJoins()
793
    {
794
        $query = new SQLSelect();
795
        $query->setSelect(['"SQLSelectTest_DO"."Name"', '"SubSelect"."Count"']);
796
        $query->setFrom('"SQLSelectTest_DO"');
797
        $query->addLeftJoin(
798
            '(SELECT "Title", COUNT(*) AS "Count" FROM "SQLSelectTestBase" GROUP BY "Title" HAVING "Title" NOT LIKE ?)',
799
            '"SQLSelectTest_DO"."Name" = "SubSelect"."Title"',
800
            'SubSelect',
801
            20,
802
            ['%MyName%']
803
        );
804
        $query->addWhere(['"SQLSelectTest_DO"."Date" > ?' => '2012-08-08 12:00']);
805
806
        $this->assertSQLEquals(
807
            'SELECT "SQLSelectTest_DO"."Name", "SubSelect"."Count"
808
			FROM "SQLSelectTest_DO" LEFT JOIN (SELECT "Title", COUNT(*) AS "Count" FROM "SQLSelectTestBase"
809
		   GROUP BY "Title" HAVING "Title" NOT LIKE ?) AS "SubSelect" ON "SQLSelectTest_DO"."Name" =
810
		   "SubSelect"."Title"
811
			WHERE ("SQLSelectTest_DO"."Date" > ?)',
812
            $query->sql($parameters)
813
        );
814
        $this->assertEquals(['%MyName%', '2012-08-08 12:00'], $parameters);
815
        $query->execute();
816
    }
817
818
    public function testBaseTableAliases()
819
    {
820
        $query = SQLSelect::create('*', ['"MyTableAlias"' => '"MyTable"']);
821
        $sql = $query->sql();
822
823
        $this->assertSQLEquals('SELECT * FROM "MyTable" AS "MyTableAlias"', $sql);
824
825
        $query = SQLSelect::create('*', ['MyTableAlias' => '"MyTable"']);
826
        $sql = $query->sql();
827
828
        $this->assertSQLEquals('SELECT * FROM "MyTable" AS "MyTableAlias"', $sql);
829
830
        $query = SQLSelect::create('*', ['"MyTableAlias"' => '"MyTable"']);
831
        $query->addLeftJoin('OtherTable', '"Thing" = "OtherThing"', 'OtherTableAlias');
832
        $sql = $query->sql();
833
834
        $this->assertSQLEquals(
835
            'SELECT *
836
              FROM "MyTable" AS "MyTableAlias"
837
              LEFT JOIN "OtherTable" AS "OtherTableAlias" ON "Thing" = "OtherThing"',
838
            $sql
839
        );
840
841
        $query = SQLSelect::create('*', [
842
            'MyTableAlias' => '"MyTable"',
843
            'ignoredAlias' => ', (SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin"'
844
        ]);
845
        $sql = $query->sql();
846
847
        $this->assertSQLEquals(
848
            'SELECT * FROM "MyTable" AS "MyTableAlias" , ' .
849
            '(SELECT * FROM "MyTable" where "something" = "whatever") as "CrossJoin"',
850
            $sql
851
        );
852
    }
853
}
854