Database::makeList() : Handle NULL when building 'IN' clause
[lhc/web/wiklou.git] / tests / phpunit / includes / db / DatabaseSQLTest.php
1 <?php
2
3 /**
4 * Test the abstract database layer
5 * This is a non DBMS depending test.
6 */
7 class DatabaseSQLTest extends MediaWikiTestCase {
8
9 /**
10 * @var DatabaseTestHelper
11 */
12 private $database;
13
14 protected function setUp() {
15 parent::setUp();
16 $this->database = new DatabaseTestHelper( __CLASS__ );
17 }
18
19 protected function assertLastSql( $sqlText ) {
20 $this->assertEquals(
21 $this->database->getLastSqls(),
22 $sqlText
23 );
24 }
25
26 /**
27 * @dataProvider provideSelect
28 * @covers DatabaseBase::select
29 */
30 public function testSelect( $sql, $sqlText ) {
31 $this->database->select(
32 $sql['tables'],
33 $sql['fields'],
34 isset( $sql['conds'] ) ? $sql['conds'] : array(),
35 __METHOD__,
36 isset( $sql['options'] ) ? $sql['options'] : array(),
37 isset( $sql['join_conds'] ) ? $sql['join_conds'] : array()
38 );
39 $this->assertLastSql( $sqlText );
40 }
41
42 public static function provideSelect() {
43 return array(
44 array(
45 array(
46 'tables' => 'table',
47 'fields' => array( 'field', 'alias' => 'field2' ),
48 'conds' => array( 'alias' => 'text' ),
49 ),
50 "SELECT field,field2 AS alias " .
51 "FROM table " .
52 "WHERE alias = 'text'"
53 ),
54 array(
55 array(
56 'tables' => 'table',
57 'fields' => array( 'field', 'alias' => 'field2' ),
58 'conds' => array( 'alias' => 'text' ),
59 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ),
60 ),
61 "SELECT field,field2 AS alias " .
62 "FROM table " .
63 "WHERE alias = 'text' " .
64 "ORDER BY field " .
65 "LIMIT 1"
66 ),
67 array(
68 array(
69 'tables' => array( 'table', 't2' => 'table2' ),
70 'fields' => array( 'tid', 'field', 'alias' => 'field2', 't2.id' ),
71 'conds' => array( 'alias' => 'text' ),
72 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ),
73 'join_conds' => array( 't2' => array(
74 'LEFT JOIN', 'tid = t2.id'
75 ) ),
76 ),
77 "SELECT tid,field,field2 AS alias,t2.id " .
78 "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
79 "WHERE alias = 'text' " .
80 "ORDER BY field " .
81 "LIMIT 1"
82 ),
83 array(
84 array(
85 'tables' => array( 'table', 't2' => 'table2' ),
86 'fields' => array( 'tid', 'field', 'alias' => 'field2', 't2.id' ),
87 'conds' => array( 'alias' => 'text' ),
88 'options' => array( 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ),
89 'join_conds' => array( 't2' => array(
90 'LEFT JOIN', 'tid = t2.id'
91 ) ),
92 ),
93 "SELECT tid,field,field2 AS alias,t2.id " .
94 "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
95 "WHERE alias = 'text' " .
96 "GROUP BY field HAVING COUNT(*) > 1 " .
97 "LIMIT 1"
98 ),
99 array(
100 array(
101 'tables' => array( 'table', 't2' => 'table2' ),
102 'fields' => array( 'tid', 'field', 'alias' => 'field2', 't2.id' ),
103 'conds' => array( 'alias' => 'text' ),
104 'options' => array(
105 'LIMIT' => 1,
106 'GROUP BY' => array( 'field', 'field2' ),
107 'HAVING' => array( 'COUNT(*) > 1', 'field' => 1 )
108 ),
109 'join_conds' => array( 't2' => array(
110 'LEFT JOIN', 'tid = t2.id'
111 ) ),
112 ),
113 "SELECT tid,field,field2 AS alias,t2.id " .
114 "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
115 "WHERE alias = 'text' " .
116 "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " .
117 "LIMIT 1"
118 ),
119 array(
120 array(
121 'tables' => array( 'table' ),
122 'fields' => array( 'alias' => 'field' ),
123 'conds' => array( 'alias' => array( 1, 2, 3, 4 ) ),
124 ),
125 "SELECT field AS alias " .
126 "FROM table " .
127 "WHERE alias IN ('1','2','3','4')"
128 ),
129 );
130 }
131
132 /**
133 * @dataProvider provideUpdate
134 * @covers DatabaseBase::update
135 */
136 public function testUpdate( $sql, $sqlText ) {
137 $this->database->update(
138 $sql['table'],
139 $sql['values'],
140 $sql['conds'],
141 __METHOD__,
142 isset( $sql['options'] ) ? $sql['options'] : array()
143 );
144 $this->assertLastSql( $sqlText );
145 }
146
147 public static function provideUpdate() {
148 return array(
149 array(
150 array(
151 'table' => 'table',
152 'values' => array( 'field' => 'text', 'field2' => 'text2' ),
153 'conds' => array( 'alias' => 'text' ),
154 ),
155 "UPDATE table " .
156 "SET field = 'text'" .
157 ",field2 = 'text2' " .
158 "WHERE alias = 'text'"
159 ),
160 array(
161 array(
162 'table' => 'table',
163 'values' => array( 'field = other', 'field2' => 'text2' ),
164 'conds' => array( 'id' => '1' ),
165 ),
166 "UPDATE table " .
167 "SET field = other" .
168 ",field2 = 'text2' " .
169 "WHERE id = '1'"
170 ),
171 array(
172 array(
173 'table' => 'table',
174 'values' => array( 'field = other', 'field2' => 'text2' ),
175 'conds' => '*',
176 ),
177 "UPDATE table " .
178 "SET field = other" .
179 ",field2 = 'text2'"
180 ),
181 );
182 }
183
184 /**
185 * @dataProvider provideDelete
186 * @covers DatabaseBase::delete
187 */
188 public function testDelete( $sql, $sqlText ) {
189 $this->database->delete(
190 $sql['table'],
191 $sql['conds'],
192 __METHOD__
193 );
194 $this->assertLastSql( $sqlText );
195 }
196
197 public static function provideDelete() {
198 return array(
199 array(
200 array(
201 'table' => 'table',
202 'conds' => array( 'alias' => 'text' ),
203 ),
204 "DELETE FROM table " .
205 "WHERE alias = 'text'"
206 ),
207 array(
208 array(
209 'table' => 'table',
210 'conds' => '*',
211 ),
212 "DELETE FROM table"
213 ),
214 );
215 }
216
217 /**
218 * @dataProvider provideUpsert
219 * @covers DatabaseBase::upsert
220 */
221 public function testUpsert( $sql, $sqlText ) {
222 $this->database->upsert(
223 $sql['table'],
224 $sql['rows'],
225 $sql['uniqueIndexes'],
226 $sql['set'],
227 __METHOD__
228 );
229 $this->assertLastSql( $sqlText );
230 }
231
232 public static function provideUpsert() {
233 return array(
234 array(
235 array(
236 'table' => 'upsert_table',
237 'rows' => array( 'field' => 'text', 'field2' => 'text2' ),
238 'uniqueIndexes' => array( 'field' ),
239 'set' => array( 'field' => 'set' ),
240 ),
241 "BEGIN; " .
242 "UPDATE upsert_table " .
243 "SET field = 'set' " .
244 "WHERE ((field = 'text')); " .
245 "INSERT IGNORE INTO upsert_table " .
246 "(field,field2) " .
247 "VALUES ('text','text2'); " .
248 "COMMIT"
249 ),
250 );
251 }
252
253 /**
254 * @dataProvider provideDeleteJoin
255 * @covers DatabaseBase::deleteJoin
256 */
257 public function testDeleteJoin( $sql, $sqlText ) {
258 $this->database->deleteJoin(
259 $sql['delTable'],
260 $sql['joinTable'],
261 $sql['delVar'],
262 $sql['joinVar'],
263 $sql['conds'],
264 __METHOD__
265 );
266 $this->assertLastSql( $sqlText );
267 }
268
269 public static function provideDeleteJoin() {
270 return array(
271 array(
272 array(
273 'delTable' => 'table',
274 'joinTable' => 'table_join',
275 'delVar' => 'field',
276 'joinVar' => 'field_join',
277 'conds' => array( 'alias' => 'text' ),
278 ),
279 "DELETE FROM table " .
280 "WHERE field IN (" .
281 "SELECT field_join FROM table_join WHERE alias = 'text'" .
282 ")"
283 ),
284 array(
285 array(
286 'delTable' => 'table',
287 'joinTable' => 'table_join',
288 'delVar' => 'field',
289 'joinVar' => 'field_join',
290 'conds' => '*',
291 ),
292 "DELETE FROM table " .
293 "WHERE field IN (" .
294 "SELECT field_join FROM table_join " .
295 ")"
296 ),
297 );
298 }
299
300 /**
301 * @dataProvider provideInsert
302 * @covers DatabaseBase::insert
303 */
304 public function testInsert( $sql, $sqlText ) {
305 $this->database->insert(
306 $sql['table'],
307 $sql['rows'],
308 __METHOD__,
309 isset( $sql['options'] ) ? $sql['options'] : array()
310 );
311 $this->assertLastSql( $sqlText );
312 }
313
314 public static function provideInsert() {
315 return array(
316 array(
317 array(
318 'table' => 'table',
319 'rows' => array( 'field' => 'text', 'field2' => 2 ),
320 ),
321 "INSERT INTO table " .
322 "(field,field2) " .
323 "VALUES ('text','2')"
324 ),
325 array(
326 array(
327 'table' => 'table',
328 'rows' => array( 'field' => 'text', 'field2' => 2 ),
329 'options' => 'IGNORE',
330 ),
331 "INSERT IGNORE INTO table " .
332 "(field,field2) " .
333 "VALUES ('text','2')"
334 ),
335 array(
336 array(
337 'table' => 'table',
338 'rows' => array(
339 array( 'field' => 'text', 'field2' => 2 ),
340 array( 'field' => 'multi', 'field2' => 3 ),
341 ),
342 'options' => 'IGNORE',
343 ),
344 "INSERT IGNORE INTO table " .
345 "(field,field2) " .
346 "VALUES " .
347 "('text','2')," .
348 "('multi','3')"
349 ),
350 );
351 }
352
353 /**
354 * @dataProvider provideInsertSelect
355 * @covers DatabaseBase::insertSelect
356 */
357 public function testInsertSelect( $sql, $sqlText ) {
358 $this->database->insertSelect(
359 $sql['destTable'],
360 $sql['srcTable'],
361 $sql['varMap'],
362 $sql['conds'],
363 __METHOD__,
364 isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : array(),
365 isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : array()
366 );
367 $this->assertLastSql( $sqlText );
368 }
369
370 public static function provideInsertSelect() {
371 return array(
372 array(
373 array(
374 'destTable' => 'insert_table',
375 'srcTable' => 'select_table',
376 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ),
377 'conds' => '*',
378 ),
379 "INSERT INTO insert_table " .
380 "(field_insert,field) " .
381 "SELECT field_select,field2 " .
382 "FROM select_table"
383 ),
384 array(
385 array(
386 'destTable' => 'insert_table',
387 'srcTable' => 'select_table',
388 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ),
389 'conds' => array( 'field' => 2 ),
390 ),
391 "INSERT INTO insert_table " .
392 "(field_insert,field) " .
393 "SELECT field_select,field2 " .
394 "FROM select_table " .
395 "WHERE field = '2'"
396 ),
397 array(
398 array(
399 'destTable' => 'insert_table',
400 'srcTable' => 'select_table',
401 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ),
402 'conds' => array( 'field' => 2 ),
403 'insertOptions' => 'IGNORE',
404 'selectOptions' => array( 'ORDER BY' => 'field' ),
405 ),
406 "INSERT IGNORE INTO insert_table " .
407 "(field_insert,field) " .
408 "SELECT field_select,field2 " .
409 "FROM select_table " .
410 "WHERE field = '2' " .
411 "ORDER BY field"
412 ),
413 );
414 }
415
416 /**
417 * @dataProvider provideReplace
418 * @covers DatabaseBase::replace
419 */
420 public function testReplace( $sql, $sqlText ) {
421 $this->database->replace(
422 $sql['table'],
423 $sql['uniqueIndexes'],
424 $sql['rows'],
425 __METHOD__
426 );
427 $this->assertLastSql( $sqlText );
428 }
429
430 public static function provideReplace() {
431 return array(
432 array(
433 array(
434 'table' => 'replace_table',
435 'uniqueIndexes' => array( 'field' ),
436 'rows' => array( 'field' => 'text', 'field2' => 'text2' ),
437 ),
438 "DELETE FROM replace_table " .
439 "WHERE ( field='text' ); " .
440 "INSERT INTO replace_table " .
441 "(field,field2) " .
442 "VALUES ('text','text2')"
443 ),
444 array(
445 array(
446 'table' => 'module_deps',
447 'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ),
448 'rows' => array(
449 'md_module' => 'module',
450 'md_skin' => 'skin',
451 'md_deps' => 'deps',
452 ),
453 ),
454 "DELETE FROM module_deps " .
455 "WHERE ( md_module='module' AND md_skin='skin' ); " .
456 "INSERT INTO module_deps " .
457 "(md_module,md_skin,md_deps) " .
458 "VALUES ('module','skin','deps')"
459 ),
460 array(
461 array(
462 'table' => 'module_deps',
463 'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ),
464 'rows' => array(
465 array(
466 'md_module' => 'module',
467 'md_skin' => 'skin',
468 'md_deps' => 'deps',
469 ), array(
470 'md_module' => 'module2',
471 'md_skin' => 'skin2',
472 'md_deps' => 'deps2',
473 ),
474 ),
475 ),
476 "DELETE FROM module_deps " .
477 "WHERE ( md_module='module' AND md_skin='skin' ); " .
478 "INSERT INTO module_deps " .
479 "(md_module,md_skin,md_deps) " .
480 "VALUES ('module','skin','deps'); " .
481 "DELETE FROM module_deps " .
482 "WHERE ( md_module='module2' AND md_skin='skin2' ); " .
483 "INSERT INTO module_deps " .
484 "(md_module,md_skin,md_deps) " .
485 "VALUES ('module2','skin2','deps2')"
486 ),
487 array(
488 array(
489 'table' => 'module_deps',
490 'uniqueIndexes' => array( 'md_module', 'md_skin' ),
491 'rows' => array(
492 array(
493 'md_module' => 'module',
494 'md_skin' => 'skin',
495 'md_deps' => 'deps',
496 ), array(
497 'md_module' => 'module2',
498 'md_skin' => 'skin2',
499 'md_deps' => 'deps2',
500 ),
501 ),
502 ),
503 "DELETE FROM module_deps " .
504 "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " .
505 "INSERT INTO module_deps " .
506 "(md_module,md_skin,md_deps) " .
507 "VALUES ('module','skin','deps'); " .
508 "DELETE FROM module_deps " .
509 "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " .
510 "INSERT INTO module_deps " .
511 "(md_module,md_skin,md_deps) " .
512 "VALUES ('module2','skin2','deps2')"
513 ),
514 array(
515 array(
516 'table' => 'module_deps',
517 'uniqueIndexes' => array(),
518 'rows' => array(
519 'md_module' => 'module',
520 'md_skin' => 'skin',
521 'md_deps' => 'deps',
522 ),
523 ),
524 "INSERT INTO module_deps " .
525 "(md_module,md_skin,md_deps) " .
526 "VALUES ('module','skin','deps')"
527 ),
528 );
529 }
530
531 /**
532 * @dataProvider provideNativeReplace
533 * @covers DatabaseBase::nativeReplace
534 */
535 public function testNativeReplace( $sql, $sqlText ) {
536 $this->database->nativeReplace(
537 $sql['table'],
538 $sql['rows'],
539 __METHOD__
540 );
541 $this->assertLastSql( $sqlText );
542 }
543
544 public static function provideNativeReplace() {
545 return array(
546 array(
547 array(
548 'table' => 'replace_table',
549 'rows' => array( 'field' => 'text', 'field2' => 'text2' ),
550 ),
551 "REPLACE INTO replace_table " .
552 "(field,field2) " .
553 "VALUES ('text','text2')"
554 ),
555 );
556 }
557
558 /**
559 * @dataProvider provideConditional
560 * @covers DatabaseBase::conditional
561 */
562 public function testConditional( $sql, $sqlText ) {
563 $this->assertEquals( trim( $this->database->conditional(
564 $sql['conds'],
565 $sql['true'],
566 $sql['false']
567 ) ), $sqlText );
568 }
569
570 public static function provideConditional() {
571 return array(
572 array(
573 array(
574 'conds' => array( 'field' => 'text' ),
575 'true' => 1,
576 'false' => 'NULL',
577 ),
578 "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)"
579 ),
580 array(
581 array(
582 'conds' => array( 'field' => 'text', 'field2' => 'anothertext' ),
583 'true' => 1,
584 'false' => 'NULL',
585 ),
586 "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)"
587 ),
588 array(
589 array(
590 'conds' => 'field=1',
591 'true' => 1,
592 'false' => 'NULL',
593 ),
594 "(CASE WHEN field=1 THEN 1 ELSE NULL END)"
595 ),
596 );
597 }
598
599 /**
600 * @dataProvider provideBuildConcat
601 * @covers DatabaseBase::buildConcat
602 */
603 public function testBuildConcat( $stringList, $sqlText ) {
604 $this->assertEquals( trim( $this->database->buildConcat(
605 $stringList
606 ) ), $sqlText );
607 }
608
609 public static function provideBuildConcat() {
610 return array(
611 array(
612 array( 'field', 'field2' ),
613 "CONCAT(field,field2)"
614 ),
615 array(
616 array( "'test'", 'field2' ),
617 "CONCAT('test',field2)"
618 ),
619 );
620 }
621
622 /**
623 * @dataProvider provideBuildLike
624 * @covers DatabaseBase::buildLike
625 */
626 public function testBuildLike( $array, $sqlText ) {
627 $this->assertEquals( trim( $this->database->buildLike(
628 $array
629 ) ), $sqlText );
630 }
631
632 public static function provideBuildLike() {
633 return array(
634 array(
635 'text',
636 "LIKE 'text'"
637 ),
638 array(
639 array( 'text', new LikeMatch( '%' ) ),
640 "LIKE 'text%'"
641 ),
642 array(
643 array( 'text', new LikeMatch( '%' ), 'text2' ),
644 "LIKE 'text%text2'"
645 ),
646 array(
647 array( 'text', new LikeMatch( '_' ) ),
648 "LIKE 'text_'"
649 ),
650 );
651 }
652
653 /**
654 * @dataProvider provideUnionQueries
655 * @covers DatabaseBase::unionQueries
656 */
657 public function testUnionQueries( $sql, $sqlText ) {
658 $this->assertEquals( trim( $this->database->unionQueries(
659 $sql['sqls'],
660 $sql['all']
661 ) ), $sqlText );
662 }
663
664 public static function provideUnionQueries() {
665 return array(
666 array(
667 array(
668 'sqls' => array( 'RAW SQL', 'RAW2SQL' ),
669 'all' => true,
670 ),
671 "(RAW SQL) UNION ALL (RAW2SQL)"
672 ),
673 array(
674 array(
675 'sqls' => array( 'RAW SQL', 'RAW2SQL' ),
676 'all' => false,
677 ),
678 "(RAW SQL) UNION (RAW2SQL)"
679 ),
680 array(
681 array(
682 'sqls' => array( 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ),
683 'all' => false,
684 ),
685 "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)"
686 ),
687 );
688 }
689
690 /**
691 * @covers DatabaseBase::commit
692 */
693 public function testTransactionCommit() {
694 $this->database->begin( __METHOD__ );
695 $this->database->commit( __METHOD__ );
696 $this->assertLastSql( 'BEGIN; COMMIT' );
697 }
698
699 /**
700 * @covers DatabaseBase::rollback
701 */
702 public function testTransactionRollback() {
703 $this->database->begin( __METHOD__ );
704 $this->database->rollback( __METHOD__ );
705 $this->assertLastSql( 'BEGIN; ROLLBACK' );
706 }
707
708 /**
709 * @covers DatabaseBase::dropTable
710 */
711 public function testDropTable() {
712 $this->database->setExistingTables( array( 'table' ) );
713 $this->database->dropTable( 'table', __METHOD__ );
714 $this->assertLastSql( 'DROP TABLE table' );
715 }
716
717 /**
718 * @covers DatabaseBase::dropTable
719 */
720 public function testDropNonExistingTable() {
721 $this->assertFalse(
722 $this->database->dropTable( 'non_existing', __METHOD__ )
723 );
724 }
725
726 /**
727 * @dataProvider provideMakeList
728 * @covers DatabaseBase::makeList
729 */
730 public function testMakeList( $list, $mode, $sqlText ) {
731 $this->assertEquals( trim( $this->database->makeList(
732 $list, $mode
733 ) ), $sqlText );
734 }
735
736 public static function provideMakeList() {
737 return array(
738 array(
739 array( 'value', 'value2' ),
740 LIST_COMMA,
741 "'value','value2'"
742 ),
743 array(
744 array( 'field', 'field2' ),
745 LIST_NAMES,
746 "field,field2"
747 ),
748 array(
749 array( 'field' => 'value', 'field2' => 'value2' ),
750 LIST_AND,
751 "field = 'value' AND field2 = 'value2'"
752 ),
753 array(
754 array( 'field' => null, "field2 != 'value2'" ),
755 LIST_AND,
756 "field IS NULL AND (field2 != 'value2')"
757 ),
758 array(
759 array( 'field' => array( 'value', null, 'value2' ), 'field2' => 'value2' ),
760 LIST_AND,
761 "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'"
762 ),
763 array(
764 array( 'field' => array( null ), 'field2' => null ),
765 LIST_AND,
766 "field IS NULL AND field2 IS NULL"
767 ),
768 array(
769 array( 'field' => 'value', 'field2' => 'value2' ),
770 LIST_OR,
771 "field = 'value' OR field2 = 'value2'"
772 ),
773 array(
774 array( 'field' => 'value', 'field2' => null ),
775 LIST_OR,
776 "field = 'value' OR field2 IS NULL"
777 ),
778 array(
779 array( 'field' => array( 'value', 'value2' ), 'field2' => array( 'value' ) ),
780 LIST_OR,
781 "field IN ('value','value2') OR field2 = 'value'"
782 ),
783 array(
784 array( 'field' => array( null, 'value', null, 'value2' ), "field2 != 'value2'" ),
785 LIST_OR,
786 "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')"
787 ),
788 array(
789 array( 'field' => 'value', 'field2' => 'value2' ),
790 LIST_SET,
791 "field = 'value',field2 = 'value2'"
792 ),
793 array(
794 array( 'field' => 'value', 'field2' => null ),
795 LIST_SET,
796 "field = 'value',field2 = NULL"
797 ),
798 array(
799 array( 'field' => 'value', "field2 != 'value2'" ),
800 LIST_SET,
801 "field = 'value',field2 != 'value2'"
802 ),
803 );
804 }
805 }