MediaWiki  1.32.5
DatabaseSQLTest.php
Go to the documentation of this file.
1 <?php
2 
6 use Wikimedia\TestingAccessWrapper;
10 
15 class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
16 
17  use MediaWikiCoversValidator;
18  use PHPUnit4And6Compat;
19 
21  private $database;
22 
23  protected function setUp() {
24  parent::setUp();
25  $this->database = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => true ] );
26  }
27 
28  protected function assertLastSql( $sqlText ) {
29  $this->assertEquals(
30  $sqlText,
31  $this->database->getLastSqls()
32  );
33  }
34 
35  protected function assertLastSqlDb( $sqlText, DatabaseTestHelper $db ) {
36  $this->assertEquals( $sqlText, $db->getLastSqls() );
37  }
38 
52  public function testSelect( $sql, $sqlText ) {
53  $this->database->select(
54  $sql['tables'],
55  $sql['fields'],
56  $sql['conds'] ?? [],
57  __METHOD__,
58  $sql['options'] ?? [],
59  $sql['join_conds'] ?? []
60  );
61  $this->assertLastSql( $sqlText );
62  }
63 
64  public static function provideSelect() {
65  return [
66  [
67  [
68  'tables' => 'table',
69  'fields' => [ 'field', 'alias' => 'field2' ],
70  'conds' => [ 'alias' => 'text' ],
71  ],
72  "SELECT field,field2 AS alias " .
73  "FROM table " .
74  "WHERE alias = 'text'"
75  ],
76  [
77  [
78  'tables' => 'table',
79  'fields' => [ 'field', 'alias' => 'field2' ],
80  'conds' => 'alias = \'text\'',
81  ],
82  "SELECT field,field2 AS alias " .
83  "FROM table " .
84  "WHERE alias = 'text'"
85  ],
86  [
87  [
88  'tables' => 'table',
89  'fields' => [ 'field', 'alias' => 'field2' ],
90  'conds' => [],
91  ],
92  "SELECT field,field2 AS alias " .
93  "FROM table"
94  ],
95  [
96  [
97  'tables' => 'table',
98  'fields' => [ 'field', 'alias' => 'field2' ],
99  'conds' => '',
100  ],
101  "SELECT field,field2 AS alias " .
102  "FROM table"
103  ],
104  [
105  [
106  'tables' => 'table',
107  'fields' => [ 'field', 'alias' => 'field2' ],
108  'conds' => '0', // T188314
109  ],
110  "SELECT field,field2 AS alias " .
111  "FROM table " .
112  "WHERE 0"
113  ],
114  [
115  [
116  // 'tables' with space prepended indicates pre-escaped table name
117  'tables' => ' table LEFT JOIN table2',
118  'fields' => [ 'field' ],
119  'conds' => [ 'field' => 'text' ],
120  ],
121  "SELECT field FROM table LEFT JOIN table2 WHERE field = 'text'"
122  ],
123  [
124  [
125  // Empty 'tables' is allowed
126  'tables' => '',
127  'fields' => [ 'SPECIAL_QUERY()' ],
128  ],
129  "SELECT SPECIAL_QUERY()"
130  ],
131  [
132  [
133  'tables' => 'table',
134  'fields' => [ 'field', 'alias' => 'field2' ],
135  'conds' => [ 'alias' => 'text' ],
136  'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
137  ],
138  "SELECT field,field2 AS alias " .
139  "FROM table " .
140  "WHERE alias = 'text' " .
141  "ORDER BY field " .
142  "LIMIT 1"
143  ],
144  [
145  [
146  'tables' => [ 'table', 't2' => 'table2' ],
147  'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
148  'conds' => [ 'alias' => 'text' ],
149  'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
150  'join_conds' => [ 't2' => [
151  'LEFT JOIN', 'tid = t2.id'
152  ] ],
153  ],
154  "SELECT tid,field,field2 AS alias,t2.id " .
155  "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
156  "WHERE alias = 'text' " .
157  "ORDER BY field " .
158  "LIMIT 1"
159  ],
160  [
161  [
162  'tables' => [ 'table', 't2' => 'table2' ],
163  'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
164  'conds' => [ 'alias' => 'text' ],
165  'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ],
166  'join_conds' => [ 't2' => [
167  'LEFT JOIN', 'tid = t2.id'
168  ] ],
169  ],
170  "SELECT tid,field,field2 AS alias,t2.id " .
171  "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
172  "WHERE alias = 'text' " .
173  "GROUP BY field HAVING COUNT(*) > 1 " .
174  "LIMIT 1"
175  ],
176  [
177  [
178  'tables' => [ 'table', 't2' => 'table2' ],
179  'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ],
180  'conds' => [ 'alias' => 'text' ],
181  'options' => [
182  'LIMIT' => 1,
183  'GROUP BY' => [ 'field', 'field2' ],
184  'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ]
185  ],
186  'join_conds' => [ 't2' => [
187  'LEFT JOIN', 'tid = t2.id'
188  ] ],
189  ],
190  "SELECT tid,field,field2 AS alias,t2.id " .
191  "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
192  "WHERE alias = 'text' " .
193  "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " .
194  "LIMIT 1"
195  ],
196  [
197  [
198  'tables' => [ 'table' ],
199  'fields' => [ 'alias' => 'field' ],
200  'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ],
201  ],
202  "SELECT field AS alias " .
203  "FROM table " .
204  "WHERE alias IN ('1','2','3','4')"
205  ],
206  [
207  [
208  'tables' => 'table',
209  'fields' => [ 'field' ],
210  'options' => [ 'USE INDEX' => [ 'table' => 'X' ] ],
211  ],
212  // No-op by default
213  "SELECT field FROM table"
214  ],
215  [
216  [
217  'tables' => 'table',
218  'fields' => [ 'field' ],
219  'options' => [ 'IGNORE INDEX' => [ 'table' => 'X' ] ],
220  ],
221  // No-op by default
222  "SELECT field FROM table"
223  ],
224  [
225  [
226  'tables' => 'table',
227  'fields' => [ 'field' ],
228  'options' => [ 'DISTINCT' ],
229  ],
230  "SELECT DISTINCT field FROM table"
231  ],
232  [
233  [
234  'tables' => 'table',
235  'fields' => [ 'field' ],
236  'options' => [ 'LOCK IN SHARE MODE' ],
237  ],
238  "SELECT field FROM table LOCK IN SHARE MODE"
239  ],
240  [
241  [
242  'tables' => 'table',
243  'fields' => [ 'field' ],
244  'options' => [ 'EXPLAIN' => true ],
245  ],
246  'EXPLAIN SELECT field FROM table'
247  ],
248  [
249  [
250  'tables' => 'table',
251  'fields' => [ 'field' ],
252  'options' => [ 'FOR UPDATE' ],
253  ],
254  "SELECT field FROM table FOR UPDATE"
255  ],
256  ];
257  }
258 
263  public function testLockForUpdate( $sql, $sqlText ) {
264  $this->database->startAtomic( __METHOD__ );
265  $this->database->lockForUpdate(
266  $sql['tables'],
267  $sql['conds'] ?? [],
268  __METHOD__,
269  $sql['options'] ?? [],
270  $sql['join_conds'] ?? []
271  );
272  $this->database->endAtomic( __METHOD__ );
273 
274  $this->assertLastSql( "BEGIN; $sqlText; COMMIT" );
275  }
276 
277  public static function provideLockForUpdate() {
278  return [
279  [
280  [
281  'tables' => [ 'table' ],
282  'conds' => [ 'field' => [ 1, 2, 3, 4 ] ],
283  ],
284  "SELECT COUNT(*) AS rowcount FROM " .
285  "(SELECT 1 FROM table WHERE field IN ('1','2','3','4') " .
286  "FOR UPDATE) tmp_count"
287  ],
288  [
289  [
290  'tables' => [ 'table', 't2' => 'table2' ],
291  'conds' => [ 'field' => 'text' ],
292  'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
293  'join_conds' => [ 't2' => [
294  'LEFT JOIN', 'tid = t2.id'
295  ] ],
296  ],
297  "SELECT COUNT(*) AS rowcount FROM " .
298  "(SELECT 1 FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
299  "WHERE field = 'text' ORDER BY field LIMIT 1 FOR UPDATE) tmp_count"
300  ],
301  [
302  [
303  'tables' => 'table',
304  ],
305  "SELECT COUNT(*) AS rowcount FROM " .
306  "(SELECT 1 FROM table FOR UPDATE) tmp_count"
307  ],
308  ];
309  }
310 
317  public function testSelectRowCount( $sql, $sqlText ) {
318  $this->database->selectRowCount(
319  $sql['tables'],
320  $sql['field'],
321  $sql['conds'] ?? [],
322  __METHOD__,
323  $sql['options'] ?? [],
324  $sql['join_conds'] ?? []
325  );
326  $this->assertLastSql( $sqlText );
327  }
328 
329  public static function provideSelectRowCount() {
330  return [
331  [
332  [
333  'tables' => 'table',
334  'field' => [ '*' ],
335  'conds' => [ 'field' => 'text' ],
336  ],
337  "SELECT COUNT(*) AS rowcount FROM " .
338  "(SELECT 1 FROM table WHERE field = 'text' ) tmp_count"
339  ],
340  [
341  [
342  'tables' => 'table',
343  'field' => [ 'column' ],
344  'conds' => [ 'field' => 'text' ],
345  ],
346  "SELECT COUNT(*) AS rowcount FROM " .
347  "(SELECT 1 FROM table WHERE field = 'text' AND (column IS NOT NULL) ) tmp_count"
348  ],
349  [
350  [
351  'tables' => 'table',
352  'field' => [ 'alias' => 'column' ],
353  'conds' => [ 'field' => 'text' ],
354  ],
355  "SELECT COUNT(*) AS rowcount FROM " .
356  "(SELECT 1 FROM table WHERE field = 'text' AND (column IS NOT NULL) ) tmp_count"
357  ],
358  [
359  [
360  'tables' => 'table',
361  'field' => [ 'alias' => 'column' ],
362  'conds' => '',
363  ],
364  "SELECT COUNT(*) AS rowcount FROM " .
365  "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count"
366  ],
367  [
368  [
369  'tables' => 'table',
370  'field' => [ 'alias' => 'column' ],
371  'conds' => false,
372  ],
373  "SELECT COUNT(*) AS rowcount FROM " .
374  "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count"
375  ],
376  [
377  [
378  'tables' => 'table',
379  'field' => [ 'alias' => 'column' ],
380  'conds' => null,
381  ],
382  "SELECT COUNT(*) AS rowcount FROM " .
383  "(SELECT 1 FROM table WHERE (column IS NOT NULL) ) tmp_count"
384  ],
385  [
386  [
387  'tables' => 'table',
388  'field' => [ 'alias' => 'column' ],
389  'conds' => '1',
390  ],
391  "SELECT COUNT(*) AS rowcount FROM " .
392  "(SELECT 1 FROM table WHERE (1) AND (column IS NOT NULL) ) tmp_count"
393  ],
394  [
395  [
396  'tables' => 'table',
397  'field' => [ 'alias' => 'column' ],
398  'conds' => '0',
399  ],
400  "SELECT COUNT(*) AS rowcount FROM " .
401  "(SELECT 1 FROM table WHERE (0) AND (column IS NOT NULL) ) tmp_count"
402  ],
403  ];
404  }
405 
412  public function testUpdate( $sql, $sqlText ) {
413  $this->database->update(
414  $sql['table'],
415  $sql['values'],
416  $sql['conds'],
417  __METHOD__,
418  $sql['options'] ?? []
419  );
420  $this->assertLastSql( $sqlText );
421  }
422 
423  public static function provideUpdate() {
424  return [
425  [
426  [
427  'table' => 'table',
428  'values' => [ 'field' => 'text', 'field2' => 'text2' ],
429  'conds' => [ 'alias' => 'text' ],
430  ],
431  "UPDATE table " .
432  "SET field = 'text'" .
433  ",field2 = 'text2' " .
434  "WHERE alias = 'text'"
435  ],
436  [
437  [
438  'table' => 'table',
439  'values' => [ 'field = other', 'field2' => 'text2' ],
440  'conds' => [ 'id' => '1' ],
441  ],
442  "UPDATE table " .
443  "SET field = other" .
444  ",field2 = 'text2' " .
445  "WHERE id = '1'"
446  ],
447  [
448  [
449  'table' => 'table',
450  'values' => [ 'field = other', 'field2' => 'text2' ],
451  'conds' => '*',
452  ],
453  "UPDATE table " .
454  "SET field = other" .
455  ",field2 = 'text2'"
456  ],
457  ];
458  }
459 
464  public function testDelete( $sql, $sqlText ) {
465  $this->database->delete(
466  $sql['table'],
467  $sql['conds'],
468  __METHOD__
469  );
470  $this->assertLastSql( $sqlText );
471  }
472 
473  public static function provideDelete() {
474  return [
475  [
476  [
477  'table' => 'table',
478  'conds' => [ 'alias' => 'text' ],
479  ],
480  "DELETE FROM table " .
481  "WHERE alias = 'text'"
482  ],
483  [
484  [
485  'table' => 'table',
486  'conds' => '*',
487  ],
488  "DELETE FROM table"
489  ],
490  ];
491  }
492 
497  public function testUpsert( $sql, $sqlText ) {
498  $this->database->upsert(
499  $sql['table'],
500  $sql['rows'],
501  $sql['uniqueIndexes'],
502  $sql['set'],
503  __METHOD__
504  );
505  $this->assertLastSql( $sqlText );
506  }
507 
508  public static function provideUpsert() {
509  return [
510  [
511  [
512  'table' => 'upsert_table',
513  'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
514  'uniqueIndexes' => [ 'field' ],
515  'set' => [ 'field' => 'set' ],
516  ],
517  "BEGIN; " .
518  "UPDATE upsert_table " .
519  "SET field = 'set' " .
520  "WHERE ((field = 'text')); " .
521  "INSERT IGNORE INTO upsert_table " .
522  "(field,field2) " .
523  "VALUES ('text','text2'); " .
524  "COMMIT"
525  ],
526  ];
527  }
528 
533  public function testDeleteJoin( $sql, $sqlText ) {
534  $this->database->deleteJoin(
535  $sql['delTable'],
536  $sql['joinTable'],
537  $sql['delVar'],
538  $sql['joinVar'],
539  $sql['conds'],
540  __METHOD__
541  );
542  $this->assertLastSql( $sqlText );
543  }
544 
545  public static function provideDeleteJoin() {
546  return [
547  [
548  [
549  'delTable' => 'table',
550  'joinTable' => 'table_join',
551  'delVar' => 'field',
552  'joinVar' => 'field_join',
553  'conds' => [ 'alias' => 'text' ],
554  ],
555  "DELETE FROM table " .
556  "WHERE field IN (" .
557  "SELECT field_join FROM table_join WHERE alias = 'text'" .
558  ")"
559  ],
560  [
561  [
562  'delTable' => 'table',
563  'joinTable' => 'table_join',
564  'delVar' => 'field',
565  'joinVar' => 'field_join',
566  'conds' => '*',
567  ],
568  "DELETE FROM table " .
569  "WHERE field IN (" .
570  "SELECT field_join FROM table_join " .
571  ")"
572  ],
573  ];
574  }
575 
581  public function testInsert( $sql, $sqlText ) {
582  $this->database->insert(
583  $sql['table'],
584  $sql['rows'],
585  __METHOD__,
586  $sql['options'] ?? []
587  );
588  $this->assertLastSql( $sqlText );
589  }
590 
591  public static function provideInsert() {
592  return [
593  [
594  [
595  'table' => 'table',
596  'rows' => [ 'field' => 'text', 'field2' => 2 ],
597  ],
598  "INSERT INTO table " .
599  "(field,field2) " .
600  "VALUES ('text','2')"
601  ],
602  [
603  [
604  'table' => 'table',
605  'rows' => [ 'field' => 'text', 'field2' => 2 ],
606  'options' => 'IGNORE',
607  ],
608  "INSERT IGNORE INTO table " .
609  "(field,field2) " .
610  "VALUES ('text','2')"
611  ],
612  [
613  [
614  'table' => 'table',
615  'rows' => [
616  [ 'field' => 'text', 'field2' => 2 ],
617  [ 'field' => 'multi', 'field2' => 3 ],
618  ],
619  'options' => 'IGNORE',
620  ],
621  "INSERT IGNORE INTO table " .
622  "(field,field2) " .
623  "VALUES " .
624  "('text','2')," .
625  "('multi','3')"
626  ],
627  ];
628  }
629 
635  public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) {
636  $this->database->insertSelect(
637  $sql['destTable'],
638  $sql['srcTable'],
639  $sql['varMap'],
640  $sql['conds'],
641  __METHOD__,
642  $sql['insertOptions'] ?? [],
643  $sql['selectOptions'] ?? [],
644  $sql['selectJoinConds'] ?? []
645  );
646  $this->assertLastSql( $sqlTextNative );
647 
648  $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] );
649  $dbWeb->forceNextResult( [
650  array_flip( array_keys( $sql['varMap'] ) )
651  ] );
652  $dbWeb->insertSelect(
653  $sql['destTable'],
654  $sql['srcTable'],
655  $sql['varMap'],
656  $sql['conds'],
657  __METHOD__,
658  $sql['insertOptions'] ?? [],
659  $sql['selectOptions'] ?? [],
660  $sql['selectJoinConds'] ?? []
661  );
662  $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, 'BEGIN', $sqlInsert, 'COMMIT' ] ), $dbWeb );
663  }
664 
665  public static function provideInsertSelect() {
666  return [
667  [
668  [
669  'destTable' => 'insert_table',
670  'srcTable' => 'select_table',
671  'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
672  'conds' => '*',
673  ],
674  "INSERT INTO insert_table " .
675  "(field_insert,field) " .
676  "SELECT field_select,field2 " .
677  "FROM select_table",
678  "SELECT field_select AS field_insert,field2 AS field " .
679  "FROM select_table FOR UPDATE",
680  "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
681  ],
682  [
683  [
684  'destTable' => 'insert_table',
685  'srcTable' => 'select_table',
686  'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
687  'conds' => [ 'field' => 2 ],
688  ],
689  "INSERT INTO insert_table " .
690  "(field_insert,field) " .
691  "SELECT field_select,field2 " .
692  "FROM select_table " .
693  "WHERE field = '2'",
694  "SELECT field_select AS field_insert,field2 AS field FROM " .
695  "select_table WHERE field = '2' FOR UPDATE",
696  "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
697  ],
698  [
699  [
700  'destTable' => 'insert_table',
701  'srcTable' => 'select_table',
702  'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
703  'conds' => [ 'field' => 2 ],
704  'insertOptions' => 'IGNORE',
705  'selectOptions' => [ 'ORDER BY' => 'field' ],
706  ],
707  "INSERT IGNORE INTO insert_table " .
708  "(field_insert,field) " .
709  "SELECT field_select,field2 " .
710  "FROM select_table " .
711  "WHERE field = '2' " .
712  "ORDER BY field",
713  "SELECT field_select AS field_insert,field2 AS field " .
714  "FROM select_table WHERE field = '2' ORDER BY field FOR UPDATE",
715  "INSERT IGNORE INTO insert_table (field_insert,field) VALUES ('0','1')"
716  ],
717  [
718  [
719  'destTable' => 'insert_table',
720  'srcTable' => [ 'select_table1', 'select_table2' ],
721  'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ],
722  'conds' => [ 'field' => 2 ],
723  'insertOptions' => [ 'NO_AUTO_COLUMNS' ],
724  'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ],
725  'selectJoinConds' => [
726  'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ],
727  ],
728  ],
729  "INSERT INTO insert_table " .
730  "(field_insert,field) " .
731  "SELECT field_select,field2 " .
732  "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " .
733  "WHERE field = '2' " .
734  "ORDER BY field",
735  "SELECT field_select AS field_insert,field2 AS field " .
736  "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " .
737  "WHERE field = '2' ORDER BY field FOR UPDATE",
738  "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
739  ],
740  ];
741  }
742 
743  public function testInsertSelectBatching() {
744  $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] );
745  $rows = [];
746  for ( $i = 0; $i <= 25000; $i++ ) {
747  $rows[] = [ 'field' => $i ];
748  }
749  $dbWeb->forceNextResult( $rows );
750  $dbWeb->insertSelect(
751  'insert_table',
752  'select_table',
753  [ 'field' => 'field2' ],
754  '*',
755  __METHOD__
756  );
757  $this->assertLastSqlDb( implode( '; ', [
758  'SELECT field2 AS field FROM select_table FOR UPDATE',
759  'BEGIN',
760  "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 0, 9999 ) ) . "')",
761  "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 10000, 19999 ) ) . "')",
762  "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 20000, 25000 ) ) . "')",
763  'COMMIT'
764  ] ), $dbWeb );
765  }
766 
771  public function testReplace( $sql, $sqlText ) {
772  $this->database->replace(
773  $sql['table'],
774  $sql['uniqueIndexes'],
775  $sql['rows'],
776  __METHOD__
777  );
778  $this->assertLastSql( $sqlText );
779  }
780 
781  public static function provideReplace() {
782  return [
783  [
784  [
785  'table' => 'replace_table',
786  'uniqueIndexes' => [ 'field' ],
787  'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
788  ],
789  "BEGIN; DELETE FROM replace_table " .
790  "WHERE (field = 'text'); " .
791  "INSERT INTO replace_table " .
792  "(field,field2) " .
793  "VALUES ('text','text2'); COMMIT"
794  ],
795  [
796  [
797  'table' => 'module_deps',
798  'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
799  'rows' => [
800  'md_module' => 'module',
801  'md_skin' => 'skin',
802  'md_deps' => 'deps',
803  ],
804  ],
805  "BEGIN; DELETE FROM module_deps " .
806  "WHERE (md_module = 'module' AND md_skin = 'skin'); " .
807  "INSERT INTO module_deps " .
808  "(md_module,md_skin,md_deps) " .
809  "VALUES ('module','skin','deps'); COMMIT"
810  ],
811  [
812  [
813  'table' => 'module_deps',
814  'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ],
815  'rows' => [
816  [
817  'md_module' => 'module',
818  'md_skin' => 'skin',
819  'md_deps' => 'deps',
820  ], [
821  'md_module' => 'module2',
822  'md_skin' => 'skin2',
823  'md_deps' => 'deps2',
824  ],
825  ],
826  ],
827  "BEGIN; DELETE FROM module_deps " .
828  "WHERE (md_module = 'module' AND md_skin = 'skin'); " .
829  "INSERT INTO module_deps " .
830  "(md_module,md_skin,md_deps) " .
831  "VALUES ('module','skin','deps'); " .
832  "DELETE FROM module_deps " .
833  "WHERE (md_module = 'module2' AND md_skin = 'skin2'); " .
834  "INSERT INTO module_deps " .
835  "(md_module,md_skin,md_deps) " .
836  "VALUES ('module2','skin2','deps2'); COMMIT"
837  ],
838  [
839  [
840  'table' => 'module_deps',
841  'uniqueIndexes' => [ 'md_module', 'md_skin' ],
842  'rows' => [
843  [
844  'md_module' => 'module',
845  'md_skin' => 'skin',
846  'md_deps' => 'deps',
847  ], [
848  'md_module' => 'module2',
849  'md_skin' => 'skin2',
850  'md_deps' => 'deps2',
851  ],
852  ],
853  ],
854  "BEGIN; DELETE FROM module_deps " .
855  "WHERE (md_module = 'module') OR (md_skin = 'skin'); " .
856  "INSERT INTO module_deps " .
857  "(md_module,md_skin,md_deps) " .
858  "VALUES ('module','skin','deps'); " .
859  "DELETE FROM module_deps " .
860  "WHERE (md_module = 'module2') OR (md_skin = 'skin2'); " .
861  "INSERT INTO module_deps " .
862  "(md_module,md_skin,md_deps) " .
863  "VALUES ('module2','skin2','deps2'); COMMIT"
864  ],
865  [
866  [
867  'table' => 'module_deps',
868  'uniqueIndexes' => [],
869  'rows' => [
870  'md_module' => 'module',
871  'md_skin' => 'skin',
872  'md_deps' => 'deps',
873  ],
874  ],
875  "BEGIN; INSERT INTO module_deps " .
876  "(md_module,md_skin,md_deps) " .
877  "VALUES ('module','skin','deps'); COMMIT"
878  ],
879  ];
880  }
881 
886  public function testNativeReplace( $sql, $sqlText ) {
887  $this->database->nativeReplace(
888  $sql['table'],
889  $sql['rows'],
890  __METHOD__
891  );
892  $this->assertLastSql( $sqlText );
893  }
894 
895  public static function provideNativeReplace() {
896  return [
897  [
898  [
899  'table' => 'replace_table',
900  'rows' => [ 'field' => 'text', 'field2' => 'text2' ],
901  ],
902  "REPLACE INTO replace_table " .
903  "(field,field2) " .
904  "VALUES ('text','text2')"
905  ],
906  ];
907  }
908 
913  public function testConditional( $sql, $sqlText ) {
914  $this->assertEquals( trim( $this->database->conditional(
915  $sql['conds'],
916  $sql['true'],
917  $sql['false']
918  ) ), $sqlText );
919  }
920 
921  public static function provideConditional() {
922  return [
923  [
924  [
925  'conds' => [ 'field' => 'text' ],
926  'true' => 1,
927  'false' => 'NULL',
928  ],
929  "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)"
930  ],
931  [
932  [
933  'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ],
934  'true' => 1,
935  'false' => 'NULL',
936  ],
937  "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)"
938  ],
939  [
940  [
941  'conds' => 'field=1',
942  'true' => 1,
943  'false' => 'NULL',
944  ],
945  "(CASE WHEN field=1 THEN 1 ELSE NULL END)"
946  ],
947  ];
948  }
949 
954  public function testBuildConcat( $stringList, $sqlText ) {
955  $this->assertEquals( trim( $this->database->buildConcat(
956  $stringList
957  ) ), $sqlText );
958  }
959 
960  public static function provideBuildConcat() {
961  return [
962  [
963  [ 'field', 'field2' ],
964  "CONCAT(field,field2)"
965  ],
966  [
967  [ "'test'", 'field2' ],
968  "CONCAT('test',field2)"
969  ],
970  ];
971  }
972 
978  public function testBuildLike( $array, $sqlText ) {
979  $this->assertEquals( trim( $this->database->buildLike(
980  $array
981  ) ), $sqlText );
982  }
983 
984  public static function provideBuildLike() {
985  return [
986  [
987  'text',
988  "LIKE 'text' ESCAPE '`'"
989  ],
990  [
991  [ 'text', new LikeMatch( '%' ) ],
992  "LIKE 'text%' ESCAPE '`'"
993  ],
994  [
995  [ 'text', new LikeMatch( '%' ), 'text2' ],
996  "LIKE 'text%text2' ESCAPE '`'"
997  ],
998  [
999  [ 'text', new LikeMatch( '_' ) ],
1000  "LIKE 'text_' ESCAPE '`'"
1001  ],
1002  [
1003  'more_text',
1004  "LIKE 'more`_text' ESCAPE '`'"
1005  ],
1006  [
1007  [ 'C:\\Windows\\', new LikeMatch( '%' ) ],
1008  "LIKE 'C:\\Windows\\%' ESCAPE '`'"
1009  ],
1010  [
1011  [ 'accent`_test`', new LikeMatch( '%' ) ],
1012  "LIKE 'accent```_test``%' ESCAPE '`'"
1013  ],
1014  ];
1015  }
1016 
1021  public function testUnionQueries( $sql, $sqlText ) {
1022  $this->assertEquals( trim( $this->database->unionQueries(
1023  $sql['sqls'],
1024  $sql['all']
1025  ) ), $sqlText );
1026  }
1027 
1028  public static function provideUnionQueries() {
1029  return [
1030  [
1031  [
1032  'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
1033  'all' => true,
1034  ],
1035  "(RAW SQL) UNION ALL (RAW2SQL)"
1036  ],
1037  [
1038  [
1039  'sqls' => [ 'RAW SQL', 'RAW2SQL' ],
1040  'all' => false,
1041  ],
1042  "(RAW SQL) UNION (RAW2SQL)"
1043  ],
1044  [
1045  [
1046  'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ],
1047  'all' => false,
1048  ],
1049  "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)"
1050  ],
1051  ];
1052  }
1053 
1058  public function testUnionConditionPermutations( $params, $expect ) {
1059  if ( isset( $params['unionSupportsOrderAndLimit'] ) ) {
1060  $this->database->setUnionSupportsOrderAndLimit( $params['unionSupportsOrderAndLimit'] );
1061  }
1062 
1063  $sql = trim( $this->database->unionConditionPermutations(
1064  $params['table'],
1065  $params['vars'],
1066  $params['permute_conds'],
1067  $params['extra_conds'] ?? '',
1068  'FNAME',
1069  $params['options'] ?? [],
1070  $params['join_conds'] ?? []
1071  ) );
1072  $this->assertEquals( $expect, $sql );
1073  }
1074 
1075  public static function provideUnionConditionPermutations() {
1076  // phpcs:disable Generic.Files.LineLength
1077  return [
1078  [
1079  [
1080  'table' => [ 'table1', 'table2' ],
1081  'vars' => [ 'field1', 'alias' => 'field2' ],
1082  'permute_conds' => [
1083  'field3' => [ 1, 2, 3 ],
1084  'duplicates' => [ 4, 5, 4 ],
1085  'empty' => [],
1086  'single' => [ 0 ],
1087  ],
1088  'extra_conds' => 'table2.bar > 23',
1089  'options' => [
1090  'ORDER BY' => [ 'field1', 'alias' ],
1091  'INNER ORDER BY' => [ 'field1', 'field2' ],
1092  'LIMIT' => 100,
1093  ],
1094  'join_conds' => [
1095  'table2' => [ 'JOIN', 'table1.foo_id = table2.foo_id' ],
1096  ],
1097  ],
1098  "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " .
1099  "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " .
1100  "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " .
1101  "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " .
1102  "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " .
1103  "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) " .
1104  "ORDER BY field1,alias LIMIT 100"
1105  ],
1106  [
1107  [
1108  'table' => 'foo',
1109  'vars' => [ 'foo_id' ],
1110  'permute_conds' => [
1111  'bar' => [ 1, 2, 3 ],
1112  ],
1113  'extra_conds' => [ 'baz' => null ],
1114  'options' => [
1115  'NOTALL',
1116  'ORDER BY' => [ 'foo_id' ],
1117  'LIMIT' => 25,
1118  ],
1119  ],
1120  "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " .
1121  "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " .
1122  "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) " .
1123  "ORDER BY foo_id LIMIT 25"
1124  ],
1125  [
1126  [
1127  'table' => 'foo',
1128  'vars' => [ 'foo_id' ],
1129  'permute_conds' => [
1130  'bar' => [ 1, 2, 3 ],
1131  ],
1132  'extra_conds' => [ 'baz' => null ],
1133  'options' => [
1134  'NOTALL' => true,
1135  'ORDER BY' => [ 'foo_id' ],
1136  'LIMIT' => 25,
1137  ],
1138  'unionSupportsOrderAndLimit' => false,
1139  ],
1140  "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ) UNION " .
1141  "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ) UNION " .
1142  "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ) " .
1143  "ORDER BY foo_id LIMIT 25"
1144  ],
1145  [
1146  [
1147  'table' => 'foo',
1148  'vars' => [ 'foo_id' ],
1149  'permute_conds' => [],
1150  'extra_conds' => [ 'baz' => null ],
1151  'options' => [
1152  'ORDER BY' => [ 'foo_id' ],
1153  'LIMIT' => 25,
1154  ],
1155  ],
1156  "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25"
1157  ],
1158  [
1159  [
1160  'table' => 'foo',
1161  'vars' => [ 'foo_id' ],
1162  'permute_conds' => [
1163  'bar' => [],
1164  ],
1165  'extra_conds' => [ 'baz' => null ],
1166  'options' => [
1167  'ORDER BY' => [ 'foo_id' ],
1168  'LIMIT' => 25,
1169  ],
1170  ],
1171  "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25"
1172  ],
1173  [
1174  [
1175  'table' => 'foo',
1176  'vars' => [ 'foo_id' ],
1177  'permute_conds' => [
1178  'bar' => [ 1 ],
1179  ],
1180  'options' => [
1181  'ORDER BY' => [ 'foo_id' ],
1182  'LIMIT' => 25,
1183  'OFFSET' => 150,
1184  ],
1185  ],
1186  "SELECT foo_id FROM foo WHERE bar = '1' ORDER BY foo_id LIMIT 150,25"
1187  ],
1188  [
1189  [
1190  'table' => 'foo',
1191  'vars' => [ 'foo_id' ],
1192  'permute_conds' => [],
1193  'extra_conds' => [ 'baz' => null ],
1194  'options' => [
1195  'ORDER BY' => [ 'foo_id' ],
1196  'LIMIT' => 25,
1197  'OFFSET' => 150,
1198  'INNER ORDER BY' => [ 'bar_id' ],
1199  ],
1200  ],
1201  "(SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY bar_id LIMIT 175 ) ORDER BY foo_id LIMIT 150,25"
1202  ],
1203  [
1204  [
1205  'table' => 'foo',
1206  'vars' => [ 'foo_id' ],
1207  'permute_conds' => [],
1208  'extra_conds' => [ 'baz' => null ],
1209  'options' => [
1210  'ORDER BY' => [ 'foo_id' ],
1211  'LIMIT' => 25,
1212  'OFFSET' => 150,
1213  'INNER ORDER BY' => [ 'bar_id' ],
1214  ],
1215  'unionSupportsOrderAndLimit' => false,
1216  ],
1217  "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 150,25"
1218  ],
1219  ];
1220  // phpcs:enable
1221  }
1222 
1227  public function testTransactionCommit() {
1228  $this->database->begin( __METHOD__ );
1229  $this->database->commit( __METHOD__ );
1230  $this->assertLastSql( 'BEGIN; COMMIT' );
1231  }
1232 
1237  public function testTransactionRollback() {
1238  $this->database->begin( __METHOD__ );
1239  $this->database->rollback( __METHOD__ );
1240  $this->assertLastSql( 'BEGIN; ROLLBACK' );
1241  }
1242 
1246  public function testDropTable() {
1247  $this->database->setExistingTables( [ 'table' ] );
1248  $this->database->dropTable( 'table', __METHOD__ );
1249  $this->assertLastSql( 'DROP TABLE table CASCADE' );
1250  }
1251 
1255  public function testDropNonExistingTable() {
1256  $this->assertFalse(
1257  $this->database->dropTable( 'non_existing', __METHOD__ )
1258  );
1259  }
1260 
1265  public function testMakeList( $list, $mode, $sqlText ) {
1266  $this->assertEquals( trim( $this->database->makeList(
1267  $list, $mode
1268  ) ), $sqlText );
1269  }
1270 
1271  public static function provideMakeList() {
1272  return [
1273  [
1274  [ 'value', 'value2' ],
1275  LIST_COMMA,
1276  "'value','value2'"
1277  ],
1278  [
1279  [ 'field', 'field2' ],
1280  LIST_NAMES,
1281  "field,field2"
1282  ],
1283  [
1284  [ 'field' => 'value', 'field2' => 'value2' ],
1285  LIST_AND,
1286  "field = 'value' AND field2 = 'value2'"
1287  ],
1288  [
1289  [ 'field' => null, "field2 != 'value2'" ],
1290  LIST_AND,
1291  "field IS NULL AND (field2 != 'value2')"
1292  ],
1293  [
1294  [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ],
1295  LIST_AND,
1296  "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'"
1297  ],
1298  [
1299  [ 'field' => [ null ], 'field2' => null ],
1300  LIST_AND,
1301  "field IS NULL AND field2 IS NULL"
1302  ],
1303  [
1304  [ 'field' => 'value', 'field2' => 'value2' ],
1305  LIST_OR,
1306  "field = 'value' OR field2 = 'value2'"
1307  ],
1308  [
1309  [ 'field' => 'value', 'field2' => null ],
1310  LIST_OR,
1311  "field = 'value' OR field2 IS NULL"
1312  ],
1313  [
1314  [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ],
1315  LIST_OR,
1316  "field IN ('value','value2') OR field2 = 'value'"
1317  ],
1318  [
1319  [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ],
1320  LIST_OR,
1321  "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')"
1322  ],
1323  [
1324  [ 'field' => 'value', 'field2' => 'value2' ],
1325  LIST_SET,
1326  "field = 'value',field2 = 'value2'"
1327  ],
1328  [
1329  [ 'field' => 'value', 'field2' => null ],
1330  LIST_SET,
1331  "field = 'value',field2 = NULL"
1332  ],
1333  [
1334  [ 'field' => 'value', "field2 != 'value2'" ],
1335  LIST_SET,
1336  "field = 'value',field2 != 'value2'"
1337  ],
1338  ];
1339  }
1340 
1344  public function testSessionTempTables() {
1345  $temp1 = $this->database->tableName( 'tmp_table_1' );
1346  $temp2 = $this->database->tableName( 'tmp_table_2' );
1347  $temp3 = $this->database->tableName( 'tmp_table_3' );
1348 
1349  $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ );
1350  $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ );
1351  $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ );
1352 
1353  $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
1354  $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
1355  $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
1356 
1357  $this->database->dropTable( 'tmp_table_1', __METHOD__ );
1358  $this->database->dropTable( 'tmp_table_2', __METHOD__ );
1359  $this->database->dropTable( 'tmp_table_3', __METHOD__ );
1360 
1361  $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
1362  $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
1363  $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
1364 
1365  $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
1366  $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
1367  $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
1368 
1369  $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
1370  $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
1371  $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
1372 
1373  $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
1374  $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
1375  $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
1376 
1377  $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
1378  $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
1379  $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
1380  }
1381 
1382  public function provideBuildSubstring() {
1383  yield [ 'someField', 1, 2, 'SUBSTRING(someField FROM 1 FOR 2)' ];
1384  yield [ 'someField', 1, null, 'SUBSTRING(someField FROM 1)' ];
1385  }
1386 
1391  public function testBuildSubstring( $input, $start, $length, $expected ) {
1392  $output = $this->database->buildSubstring( $input, $start, $length );
1393  $this->assertSame( $expected, $output );
1394  }
1395 
1397  yield [ -1, 1 ];
1398  yield [ 1, -1 ];
1399  yield [ 1, 'foo' ];
1400  yield [ 'foo', 1 ];
1401  yield [ null, 1 ];
1402  yield [ 0, 1 ];
1403  }
1404 
1410  public function testBuildSubstring_invalidParams( $start, $length ) {
1411  $this->setExpectedException( InvalidArgumentException::class );
1412  $this->database->buildSubstring( 'foo', $start, $length );
1413  }
1414 
1418  public function testBuildIntegerCast() {
1419  $output = $this->database->buildIntegerCast( 'fieldName' );
1420  $this->assertSame( 'CAST( fieldName AS INTEGER )', $output );
1421  }
1422 
1432  public function testAtomicSections() {
1433  $this->database->startAtomic( __METHOD__ );
1434  $this->database->endAtomic( __METHOD__ );
1435  $this->assertLastSql( 'BEGIN; COMMIT' );
1436 
1437  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1438  $this->database->cancelAtomic( __METHOD__ );
1439  $this->assertLastSql( 'BEGIN; ROLLBACK' );
1440 
1441  $this->database->begin( __METHOD__ );
1442  $this->database->startAtomic( __METHOD__ );
1443  $this->database->endAtomic( __METHOD__ );
1444  $this->database->commit( __METHOD__ );
1445  $this->assertLastSql( 'BEGIN; COMMIT' );
1446 
1447  $this->database->begin( __METHOD__ );
1448  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1449  $this->database->endAtomic( __METHOD__ );
1450  $this->database->commit( __METHOD__ );
1451  // phpcs:ignore Generic.Files.LineLength
1452  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
1453 
1454  $this->database->begin( __METHOD__ );
1455  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1456  $this->database->cancelAtomic( __METHOD__ );
1457  $this->database->commit( __METHOD__ );
1458  // phpcs:ignore Generic.Files.LineLength
1459  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
1460 
1461  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1462  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1463  $this->database->cancelAtomic( __METHOD__ );
1464  $this->database->endAtomic( __METHOD__ );
1465  // phpcs:ignore Generic.Files.LineLength
1466  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
1467 
1468  $noOpCallack = function () {
1469  };
1470 
1471  $this->database->doAtomicSection( __METHOD__, $noOpCallack, IDatabase::ATOMIC_CANCELABLE );
1472  $this->assertLastSql( 'BEGIN; COMMIT' );
1473 
1474  $this->database->doAtomicSection( __METHOD__, $noOpCallack );
1475  $this->assertLastSql( 'BEGIN; COMMIT' );
1476 
1477  $this->database->begin( __METHOD__ );
1478  $this->database->doAtomicSection( __METHOD__, $noOpCallack, IDatabase::ATOMIC_CANCELABLE );
1479  $this->database->rollback( __METHOD__ );
1480  // phpcs:ignore Generic.Files.LineLength
1481  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK' );
1482 
1483  $fname = __METHOD__;
1484  $triggerMap = [
1485  '-' => '-',
1486  IDatabase::TRIGGER_COMMIT => 'tCommit',
1487  IDatabase::TRIGGER_ROLLBACK => 'tRollback'
1488  ];
1489  $pcCallback = function ( IDatabase $db ) use ( $fname ) {
1490  $this->database->query( "SELECT 0", $fname );
1491  };
1492  $callback1 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
1493  $this->database->query( "SELECT 1, {$triggerMap[$trigger]} AS t", $fname );
1494  };
1495  $callback2 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
1496  $this->database->query( "SELECT 2, {$triggerMap[$trigger]} AS t", $fname );
1497  };
1498  $callback3 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
1499  $this->database->query( "SELECT 3, {$triggerMap[$trigger]} AS t", $fname );
1500  };
1501 
1502  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1503  $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
1504  $this->database->cancelAtomic( __METHOD__ );
1505  $this->assertLastSql( 'BEGIN; ROLLBACK' );
1506 
1507  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1508  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1509  $this->database->cancelAtomic( __METHOD__ );
1510  $this->assertLastSql( 'BEGIN; ROLLBACK' );
1511 
1512  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1513  $this->database->onTransactionResolution( $callback1, __METHOD__ );
1514  $this->database->cancelAtomic( __METHOD__ );
1515  $this->assertLastSql( 'BEGIN; ROLLBACK; SELECT 1, tRollback AS t' );
1516 
1517  $this->database->startAtomic( __METHOD__ . '_outer' );
1518  $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
1519  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1520  $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
1521  $this->database->cancelAtomic( __METHOD__ );
1522  $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
1523  $this->database->endAtomic( __METHOD__ . '_outer' );
1524  $this->assertLastSql( implode( "; ", [
1525  'BEGIN',
1526  'SAVEPOINT wikimedia_rdbms_atomic1',
1527  'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
1528  'SELECT 0',
1529  'SELECT 0',
1530  'COMMIT'
1531  ] ) );
1532 
1533  $this->database->startAtomic( __METHOD__ . '_outer' );
1534  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1535  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1536  $this->database->onTransactionCommitOrIdle( $callback2, __METHOD__ );
1537  $this->database->cancelAtomic( __METHOD__ );
1538  $this->database->onTransactionCommitOrIdle( $callback3, __METHOD__ );
1539  $this->database->endAtomic( __METHOD__ . '_outer' );
1540  $this->assertLastSql( implode( "; ", [
1541  'BEGIN',
1542  'SAVEPOINT wikimedia_rdbms_atomic1',
1543  'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
1544  'COMMIT',
1545  'SELECT 1, tCommit AS t',
1546  'SELECT 3, tCommit AS t'
1547  ] ) );
1548 
1549  $this->database->startAtomic( __METHOD__ . '_outer' );
1550  $this->database->onTransactionResolution( $callback1, __METHOD__ );
1551  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1552  $this->database->onTransactionResolution( $callback2, __METHOD__ );
1553  $this->database->cancelAtomic( __METHOD__ );
1554  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1555  $this->database->endAtomic( __METHOD__ . '_outer' );
1556  $this->assertLastSql( implode( "; ", [
1557  'BEGIN',
1558  'SAVEPOINT wikimedia_rdbms_atomic1',
1559  'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
1560  'COMMIT',
1561  'SELECT 1, tCommit AS t',
1562  'SELECT 2, tRollback AS t',
1563  'SELECT 3, tCommit AS t'
1564  ] ) );
1565 
1566  $makeCallback = function ( $id ) use ( $fname, $triggerMap ) {
1567  return function ( $trigger = '-' ) use ( $id, $fname, $triggerMap ) {
1568  $this->database->query( "SELECT $id, {$triggerMap[$trigger]} AS t", $fname );
1569  };
1570  };
1571 
1572  $this->database->startAtomic( __METHOD__ . '_outer' );
1573  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1574  $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ );
1575  $this->database->cancelAtomic( __METHOD__ );
1576  $this->database->endAtomic( __METHOD__ . '_outer' );
1577  $this->assertLastSql( implode( "; ", [
1578  'BEGIN',
1579  'SAVEPOINT wikimedia_rdbms_atomic1',
1580  'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
1581  'COMMIT',
1582  'SELECT 1, tRollback AS t'
1583  ] ) );
1584 
1585  $this->database->startAtomic( __METHOD__ . '_level1', IDatabase::ATOMIC_CANCELABLE );
1586  $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ );
1587  $this->database->startAtomic( __METHOD__ . '_level2' );
1588  $this->database->startAtomic( __METHOD__ . '_level3', IDatabase::ATOMIC_CANCELABLE );
1589  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1590  $this->database->onTransactionResolution( $makeCallback( 2 ), __METHOD__ );
1591  $this->database->endAtomic( __METHOD__ );
1592  $this->database->onTransactionResolution( $makeCallback( 3 ), __METHOD__ );
1593  $this->database->cancelAtomic( __METHOD__ . '_level3' );
1594  $this->database->endAtomic( __METHOD__ . '_level2' );
1595  $this->database->onTransactionResolution( $makeCallback( 4 ), __METHOD__ );
1596  $this->database->endAtomic( __METHOD__ . '_level1' );
1597  $this->assertLastSql( implode( "; ", [
1598  'BEGIN',
1599  'SAVEPOINT wikimedia_rdbms_atomic1',
1600  'SAVEPOINT wikimedia_rdbms_atomic2',
1601  'RELEASE SAVEPOINT wikimedia_rdbms_atomic2',
1602  'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
1603  'COMMIT; SELECT 1, tCommit AS t',
1604  'SELECT 2, tRollback AS t',
1605  'SELECT 3, tRollback AS t',
1606  'SELECT 4, tCommit AS t'
1607  ] ) );
1608  }
1609 
1619  public function testAtomicSectionsRecovery() {
1620  $this->database->begin( __METHOD__ );
1621  try {
1622  $this->database->doAtomicSection(
1623  __METHOD__,
1624  function () {
1625  $this->database->startAtomic( 'inner_func1' );
1626  $this->database->startAtomic( 'inner_func2' );
1627 
1628  throw new RuntimeException( 'Test exception' );
1629  },
1630  IDatabase::ATOMIC_CANCELABLE
1631  );
1632  $this->fail( 'Expected exception not thrown' );
1633  } catch ( RuntimeException $ex ) {
1634  $this->assertSame( 'Test exception', $ex->getMessage() );
1635  }
1636  $this->database->commit( __METHOD__ );
1637  // phpcs:ignore Generic.Files.LineLength
1638  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
1639 
1640  $this->database->begin( __METHOD__ );
1641  try {
1642  $this->database->doAtomicSection(
1643  __METHOD__,
1644  function () {
1645  throw new RuntimeException( 'Test exception' );
1646  }
1647  );
1648  $this->fail( 'Test exception not thrown' );
1649  } catch ( RuntimeException $ex ) {
1650  $this->assertSame( 'Test exception', $ex->getMessage() );
1651  }
1652  try {
1653  $this->database->commit( __METHOD__ );
1654  $this->fail( 'Test exception not thrown' );
1655  } catch ( DBTransactionError $ex ) {
1656  $this->assertSame(
1657  'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
1658  $ex->getMessage()
1659  );
1660  }
1661  $this->database->rollback( __METHOD__ );
1662  $this->assertLastSql( 'BEGIN; ROLLBACK' );
1663  }
1664 
1675  $fname = __METHOD__;
1676  $callback1Called = null;
1677  $callback1 = function ( $trigger = '-' ) use ( $fname, &$callback1Called ) {
1678  $callback1Called = $trigger;
1679  $this->database->query( "SELECT 1", $fname );
1680  };
1681  $callback2Called = null;
1682  $callback2 = function ( $trigger = '-' ) use ( $fname, &$callback2Called ) {
1683  $callback2Called = $trigger;
1684  $this->database->query( "SELECT 2", $fname );
1685  };
1686  $callback3Called = null;
1687  $callback3 = function ( $trigger = '-' ) use ( $fname, &$callback3Called ) {
1688  $callback3Called = $trigger;
1689  $this->database->query( "SELECT 3", $fname );
1690  };
1691 
1692  $this->database->startAtomic( __METHOD__ . '_outer' );
1693  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1694  $this->database->startAtomic( __METHOD__ . '_inner' );
1695  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1696  $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
1697  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1698  $this->database->endAtomic( __METHOD__ . '_inner' );
1699  $this->database->cancelAtomic( __METHOD__ );
1700  $this->database->endAtomic( __METHOD__ . '_outer' );
1701  $this->assertNull( $callback1Called );
1702  $this->assertNull( $callback2Called );
1703  $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
1704  // phpcs:ignore Generic.Files.LineLength
1705  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
1706 
1707  $callback1Called = null;
1708  $callback2Called = null;
1709  $callback3Called = null;
1710  $this->database->startAtomic( __METHOD__ . '_outer' );
1711  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1712  $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
1713  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1714  $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
1715  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1716  $this->database->endAtomic( __METHOD__ . '_inner' );
1717  $this->database->cancelAtomic( __METHOD__ );
1718  $this->database->endAtomic( __METHOD__ . '_outer' );
1719  $this->assertNull( $callback1Called );
1720  $this->assertNull( $callback2Called );
1721  $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
1722  // phpcs:ignore Generic.Files.LineLength
1723  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
1724 
1725  $callback1Called = null;
1726  $callback2Called = null;
1727  $callback3Called = null;
1728  $this->database->startAtomic( __METHOD__ . '_outer' );
1729  $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1730  $this->database->startAtomic( __METHOD__ . '_inner' );
1731  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1732  $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
1733  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1734  $this->database->cancelAtomic( __METHOD__, $atomicId );
1735  $this->database->endAtomic( __METHOD__ . '_outer' );
1736  $this->assertNull( $callback1Called );
1737  $this->assertNull( $callback2Called );
1738  $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
1739 
1740  $callback1Called = null;
1741  $callback2Called = null;
1742  $callback3Called = null;
1743  $this->database->startAtomic( __METHOD__ . '_outer' );
1744  $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1745  $this->database->startAtomic( __METHOD__ . '_inner' );
1746  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1747  $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
1748  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1749  try {
1750  $this->database->cancelAtomic( __METHOD__ . '_X', $atomicId );
1751  } catch ( DBUnexpectedError $e ) {
1752  $m = __METHOD__;
1753  $this->assertSame(
1754  "Invalid atomic section ended (got {$m}_X but expected {$m}).",
1755  $e->getMessage()
1756  );
1757  }
1758  $this->database->cancelAtomic( __METHOD__ );
1759  $this->database->endAtomic( __METHOD__ . '_outer' );
1760  $this->assertNull( $callback1Called );
1761  $this->assertNull( $callback2Called );
1762  $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
1763 
1764  $this->database->startAtomic( __METHOD__ . '_outer' );
1765  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1766  $this->database->startAtomic( __METHOD__ . '_inner' );
1767  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1768  $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
1769  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1770  $this->database->cancelAtomic( __METHOD__ . '_inner' );
1771  $this->database->cancelAtomic( __METHOD__ );
1772  $this->database->endAtomic( __METHOD__ . '_outer' );
1773  $this->assertNull( $callback1Called );
1774  $this->assertNull( $callback2Called );
1775  $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
1776 
1777  $wrapper = TestingAccessWrapper::newFromObject( $this->database );
1778  $callback1Called = null;
1779  $callback2Called = null;
1780  $callback3Called = null;
1781  $this->database->startAtomic( __METHOD__ . '_outer' );
1782  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1783  $this->database->startAtomic( __METHOD__ . '_inner' );
1784  $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
1785  $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
1786  $this->database->onTransactionResolution( $callback3, __METHOD__ );
1787  $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
1788  $this->database->cancelAtomic( __METHOD__ . '_inner' );
1789  $this->database->cancelAtomic( __METHOD__ );
1790  $this->database->endAtomic( __METHOD__ . '_outer' );
1791  $this->assertNull( $callback1Called );
1792  $this->assertNull( $callback2Called );
1793  $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
1794  }
1795 
1805  public function testAtomicSectionsTrxRound() {
1806  $this->database->setFlag( IDatabase::DBO_TRX );
1807  $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
1808  $this->database->query( 'SELECT 1', __METHOD__ );
1809  $this->database->endAtomic( __METHOD__ );
1810  $this->database->commit( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
1811  // phpcs:ignore Generic.Files.LineLength
1812  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SELECT 1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
1813  }
1814 
1815  public static function provideAtomicSectionMethodsForErrors() {
1816  return [
1817  [ 'endAtomic' ],
1818  [ 'cancelAtomic' ],
1819  ];
1820  }
1821 
1827  public function testNoAtomicSection( $method ) {
1828  try {
1829  $this->database->$method( __METHOD__ );
1830  $this->fail( 'Expected exception not thrown' );
1831  } catch ( DBUnexpectedError $ex ) {
1832  $this->assertSame(
1833  'No atomic section is open (got ' . __METHOD__ . ').',
1834  $ex->getMessage()
1835  );
1836  }
1837  }
1838 
1844  public function testInvalidAtomicSectionEnded( $method ) {
1845  $this->database->startAtomic( __METHOD__ . 'X' );
1846  try {
1847  $this->database->$method( __METHOD__ );
1848  $this->fail( 'Expected exception not thrown' );
1849  } catch ( DBUnexpectedError $ex ) {
1850  $this->assertSame(
1851  'Invalid atomic section ended (got ' . __METHOD__ . ' but expected ' .
1852  __METHOD__ . 'X' . ').',
1853  $ex->getMessage()
1854  );
1855  }
1856  }
1857 
1862  $this->database->startAtomic( __METHOD__ );
1863  try {
1864  $this->database->cancelAtomic( __METHOD__ );
1865  $this->database->select( 'test', '1', [], __METHOD__ );
1866  $this->fail( 'Expected exception not thrown' );
1867  } catch ( DBTransactionError $ex ) {
1868  $this->assertSame(
1869  'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
1870  $ex->getMessage()
1871  );
1872  }
1873  }
1874 
1878  public function testTransactionErrorState1() {
1879  $wrapper = TestingAccessWrapper::newFromObject( $this->database );
1880 
1881  $this->database->begin( __METHOD__ );
1882  $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
1883  $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
1884  $this->database->commit( __METHOD__ );
1885  }
1886 
1890  public function testTransactionErrorState2() {
1891  $wrapper = TestingAccessWrapper::newFromObject( $this->database );
1892 
1893  $this->database->startAtomic( __METHOD__ );
1894  $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
1895  $this->database->rollback( __METHOD__ );
1896  $this->assertEquals( 0, $this->database->trxLevel() );
1897  $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() );
1898  $this->assertLastSql( 'BEGIN; ROLLBACK' );
1899 
1900  $this->database->startAtomic( __METHOD__ );
1901  $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() );
1902  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
1903  $this->database->endAtomic( __METHOD__ );
1904  $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() );
1905  $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' );
1906  $this->assertEquals( 0, $this->database->trxLevel(), 'Use after rollback()' );
1907 
1908  $this->database->begin( __METHOD__ );
1909  $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE );
1910  $this->database->update( 'y', [ 'a' => 1 ], [ 'field' => 1 ], __METHOD__ );
1911  $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
1912  $this->database->cancelAtomic( __METHOD__ );
1913  $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() );
1914  $this->database->startAtomic( __METHOD__ );
1915  $this->database->delete( 'y', [ 'field' => 1 ], __METHOD__ );
1916  $this->database->endAtomic( __METHOD__ );
1917  $this->database->commit( __METHOD__ );
1918  // phpcs:ignore Generic.Files.LineLength
1919  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; UPDATE y SET a = \'1\' WHERE field = \'1\'; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM y WHERE field = \'1\'; COMMIT' );
1920  $this->assertEquals( 0, $this->database->trxLevel(), 'Use after rollback()' );
1921 
1922  // Next transaction
1923  $this->database->startAtomic( __METHOD__ );
1924  $this->assertEquals( Database::STATUS_TRX_OK, $wrapper->trxStatus() );
1925  $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
1926  $this->database->endAtomic( __METHOD__ );
1927  $this->assertEquals( Database::STATUS_TRX_NONE, $wrapper->trxStatus() );
1928  $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; COMMIT' );
1929  $this->assertEquals( 0, $this->database->trxLevel() );
1930  }
1931 
1936  $doError = function () {
1937  $this->database->forceNextQueryError( 666, 'Evilness' );
1938  try {
1939  $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
1940  $this->fail( 'Expected exception not thrown' );
1941  } catch ( DBError $e ) {
1942  $this->assertSame( 666, $e->errno );
1943  }
1944  };
1945 
1946  $this->database->setFlag( Database::DBO_TRX );
1947 
1948  // Implicit transaction does not get silently rolled back
1949  $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
1950  call_user_func( $doError );
1951  try {
1952  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
1953  $this->fail( 'Expected exception not thrown' );
1954  } catch ( DBTransactionError $e ) {
1955  $this->assertEquals(
1956  'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
1957  $e->getMessage()
1958  );
1959  }
1960  try {
1961  $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
1962  $this->fail( 'Expected exception not thrown' );
1963  } catch ( DBTransactionError $e ) {
1964  $this->assertEquals(
1965  'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
1966  $e->getMessage()
1967  );
1968  }
1969  $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL );
1970  $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK' );
1971 
1972  // Likewise if there were prior writes
1973  $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
1974  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
1975  call_user_func( $doError );
1976  try {
1977  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
1978  $this->fail( 'Expected exception not thrown' );
1979  } catch ( DBTransactionStateError $e ) {
1980  }
1981  $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL );
1982  // phpcs:ignore
1983  $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; DELETE FROM error WHERE 1; ROLLBACK' );
1984  }
1985 
1990  $wrapper = TestingAccessWrapper::newFromObject( $this->database );
1991  $warning = [];
1992  $wrapper->deprecationLogger = function ( $msg ) use ( &$warning ) {
1993  $warning[] = $msg;
1994  };
1995 
1996  $doError = function () {
1997  $this->database->forceNextQueryError( 666, 'Evilness', [
1998  'wasKnownStatementRollbackError' => true,
1999  ] );
2000  try {
2001  $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
2002  $this->fail( 'Expected exception not thrown' );
2003  } catch ( DBError $e ) {
2004  $this->assertSame( 666, $e->errno );
2005  }
2006  };
2007  $expectWarning = 'Caller from ' . __METHOD__ .
2008  ' ignored an error originally raised from ' . __CLASS__ . '::SomeCaller: [666] Evilness';
2009 
2010  // Rollback doesn't raise a warning
2011  $warning = [];
2012  $this->database->startAtomic( __METHOD__ );
2013  call_user_func( $doError );
2014  $this->database->rollback( __METHOD__ );
2015  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
2016  $this->assertSame( [], $warning );
2017  // phpcs:ignore
2018  $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; DELETE FROM x WHERE field = \'1\'' );
2019 
2020  // cancelAtomic() doesn't raise a warning
2021  $warning = [];
2022  $this->database->begin( __METHOD__ );
2023  $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE );
2024  call_user_func( $doError );
2025  $this->database->cancelAtomic( __METHOD__ );
2026  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
2027  $this->database->commit( __METHOD__ );
2028  $this->assertSame( [], $warning );
2029  // phpcs:ignore
2030  $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM error WHERE 1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM x WHERE field = \'1\'; COMMIT' );
2031 
2032  // Commit does raise a warning
2033  $warning = [];
2034  $this->database->begin( __METHOD__ );
2035  call_user_func( $doError );
2036  $this->database->commit( __METHOD__ );
2037  $this->assertSame( [ $expectWarning ], $warning );
2038  $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; COMMIT' );
2039 
2040  // Deprecation only gets raised once
2041  $warning = [];
2042  $this->database->begin( __METHOD__ );
2043  call_user_func( $doError );
2044  $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
2045  $this->database->commit( __METHOD__ );
2046  $this->assertSame( [ $expectWarning ], $warning );
2047  // phpcs:ignore
2048  $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; DELETE FROM x WHERE field = \'1\'; COMMIT' );
2049  }
2050 
2054  public function testPrematureClose1() {
2055  $fname = __METHOD__;
2056  $this->database->begin( __METHOD__ );
2057  $this->database->onTransactionCommitOrIdle( function () use ( $fname ) {
2058  $this->database->query( 'SELECT 1', $fname );
2059  } );
2060  $this->database->onTransactionResolution( function () use ( $fname ) {
2061  $this->database->query( 'SELECT 2', $fname );
2062  } );
2063  $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
2064  try {
2065  $this->database->close();
2066  $this->fail( 'Expected exception not thrown' );
2067  } catch ( DBUnexpectedError $ex ) {
2068  $this->assertSame(
2069  "Wikimedia\Rdbms\Database::close: transaction is still open (from $fname).",
2070  $ex->getMessage()
2071  );
2072  }
2073 
2074  $this->assertFalse( $this->database->isOpen() );
2075  $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK; SELECT 2' );
2076  $this->assertEquals( 0, $this->database->trxLevel() );
2077  }
2078 
2082  public function testPrematureClose2() {
2083  try {
2084  $fname = __METHOD__;
2085  $this->database->startAtomic( __METHOD__ );
2086  $this->database->onTransactionCommitOrIdle( function () use ( $fname ) {
2087  $this->database->query( 'SELECT 1', $fname );
2088  } );
2089  $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
2090  $this->database->close();
2091  $this->fail( 'Expected exception not thrown' );
2092  } catch ( DBUnexpectedError $ex ) {
2093  $this->assertSame(
2094  'Wikimedia\Rdbms\Database::close: atomic sections ' .
2095  'DatabaseSQLTest::testPrematureClose2 are still open.',
2096  $ex->getMessage()
2097  );
2098  }
2099 
2100  $this->assertFalse( $this->database->isOpen() );
2101  $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
2102  $this->assertEquals( 0, $this->database->trxLevel() );
2103  }
2104 
2108  public function testPrematureClose3() {
2109  try {
2110  $this->database->setFlag( IDatabase::DBO_TRX );
2111  $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
2112  $this->assertEquals( 1, $this->database->trxLevel() );
2113  $this->database->close();
2114  $this->fail( 'Expected exception not thrown' );
2115  } catch ( DBUnexpectedError $ex ) {
2116  $this->assertSame(
2117  'Wikimedia\Rdbms\Database::close: ' .
2118  'mass commit/rollback of peer transaction required (DBO_TRX set).',
2119  $ex->getMessage()
2120  );
2121  }
2122 
2123  $this->assertFalse( $this->database->isOpen() );
2124  $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
2125  $this->assertEquals( 0, $this->database->trxLevel() );
2126  }
2127 
2131  public function testPrematureClose4() {
2132  $this->database->setFlag( IDatabase::DBO_TRX );
2133  $this->database->query( 'SELECT 1', __METHOD__ );
2134  $this->assertEquals( 1, $this->database->trxLevel() );
2135  $this->database->close();
2136  $this->database->clearFlag( IDatabase::DBO_TRX );
2137 
2138  $this->assertFalse( $this->database->isOpen() );
2139  $this->assertLastSql( 'BEGIN; SELECT 1; ROLLBACK' );
2140  $this->assertEquals( 0, $this->database->trxLevel() );
2141  }
2142 
2146  public function testSelectFieldValues() {
2147  $this->database->forceNextResult( [
2148  (object)[ 'value' => 'row1' ],
2149  (object)[ 'value' => 'row2' ],
2150  (object)[ 'value' => 'row3' ],
2151  ] );
2152 
2153  $this->assertSame(
2154  [ 'row1', 'row2', 'row3' ],
2155  $this->database->selectFieldValues( 'table', 'table.field', 'conds', __METHOD__ )
2156  );
2157  $this->assertLastSql( 'SELECT table.field AS value FROM table WHERE conds' );
2158  }
2159 }
DatabaseSQLTest\provideUnionQueries
static provideUnionQueries()
Definition: DatabaseSQLTest.php:1028
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
DatabaseSQLTest\assertLastSqlDb
assertLastSqlDb( $sqlText, DatabaseTestHelper $db)
Definition: DatabaseSQLTest.php:35
DatabaseSQLTest\provideNativeReplace
static provideNativeReplace()
Definition: DatabaseSQLTest.php:895
DatabaseSQLTest\testBuildConcat
testBuildConcat( $stringList, $sqlText)
provideBuildConcat Wikimedia\Rdbms\Database::buildConcat
Definition: DatabaseSQLTest.php:954
DatabaseSQLTest\provideDeleteJoin
static provideDeleteJoin()
Definition: DatabaseSQLTest.php:545
DatabaseSQLTest\testUnionConditionPermutations
testUnionConditionPermutations( $params, $expect)
provideUnionConditionPermutations Wikimedia\Rdbms\Database::unionConditionPermutations
Definition: DatabaseSQLTest.php:1058
DatabaseSQLTest\testTransactionErrorState1
testTransactionErrorState1()
\Wikimedia\Rdbms\DBTransactionStateError
Definition: DatabaseSQLTest.php:1878
DatabaseSQLTest\testDropTable
testDropTable()
Wikimedia\Rdbms\Database::dropTable.
Definition: DatabaseSQLTest.php:1246
DatabaseTestHelper\getLastSqls
getLastSqls()
Returns SQL queries grouped by '; ' Clear the list of queries that have been done so far.
Definition: DatabaseTestHelper.php:68
DatabaseSQLTest\$database
DatabaseTestHelper Database $database
Definition: DatabaseSQLTest.php:21
DatabaseSQLTest\testMakeList
testMakeList( $list, $mode, $sqlText)
provideMakeList Wikimedia\Rdbms\Database::makeList
Definition: DatabaseSQLTest.php:1265
DatabaseSQLTest\testSelectFieldValues
testSelectFieldValues()
Wikimedia\Rdbms\Database::selectFieldValues()
Definition: DatabaseSQLTest.php:2146
DatabaseSQLTest\assertLastSql
assertLastSql( $sqlText)
Definition: DatabaseSQLTest.php:28
Wikimedia\Rdbms\DBTransactionStateError
Definition: DBTransactionStateError.php:27
DatabaseSQLTest\testTransactionStatementRollbackIgnoring
testTransactionStatementRollbackIgnoring()
\Wikimedia\Rdbms\Database::query
Definition: DatabaseSQLTest.php:1989
DatabaseSQLTest\testAtomicSections
testAtomicSections()
\Wikimedia\Rdbms\Database::doSavepoint \Wikimedia\Rdbms\Database::doReleaseSavepoint \Wikimedia\Rdbms...
Definition: DatabaseSQLTest.php:1432
$params
$params
Definition: styleTest.css.php:44
DatabaseSQLTest\testPrematureClose2
testPrematureClose2()
\Wikimedia\Rdbms\Database::close
Definition: DatabaseSQLTest.php:2082
DBO_TRX
const DBO_TRX
Definition: defines.php:12
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
LIST_AND
const LIST_AND
Definition: Defines.php:43
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
DatabaseSQLTest\testSessionTempTables
testSessionTempTables()
Wikimedia\Rdbms\Database::registerTempTableOperation.
Definition: DatabaseSQLTest.php:1344
DatabaseSQLTest\provideLockForUpdate
static provideLockForUpdate()
Definition: DatabaseSQLTest.php:277
DatabaseSQLTest\provideInsert
static provideInsert()
Definition: DatabaseSQLTest.php:591
DatabaseSQLTest\provideBuildConcat
static provideBuildConcat()
Definition: DatabaseSQLTest.php:960
LIST_OR
const LIST_OR
Definition: Defines.php:46
DatabaseSQLTest\provideSelect
static provideSelect()
Definition: DatabaseSQLTest.php:64
DatabaseSQLTest\testUnionQueries
testUnionQueries( $sql, $sqlText)
provideUnionQueries Wikimedia\Rdbms\Database::unionQueries
Definition: DatabaseSQLTest.php:1021
DatabaseSQLTest\provideMakeList
static provideMakeList()
Definition: DatabaseSQLTest.php:1271
DatabaseSQLTest\testTransactionCommit
testTransactionCommit()
Wikimedia\Rdbms\Database::commit Wikimedia\Rdbms\Database::doCommit.
Definition: DatabaseSQLTest.php:1227
DatabaseSQLTest\testUncancellableAtomicSection
testUncancellableAtomicSection()
\Wikimedia\Rdbms\Database::cancelAtomic
Definition: DatabaseSQLTest.php:1861
DatabaseSQLTest\testAtomicSectionsTrxRound
testAtomicSectionsTrxRound()
\Wikimedia\Rdbms\Database::doSavepoint \Wikimedia\Rdbms\Database::doReleaseSavepoint \Wikimedia\Rdbms...
Definition: DatabaseSQLTest.php:1805
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
DatabaseSQLTest\testBuildIntegerCast
testBuildIntegerCast()
\Wikimedia\Rdbms\Database::buildIntegerCast
Definition: DatabaseSQLTest.php:1418
DatabaseSQLTest\provideBuildSubstring
provideBuildSubstring()
Definition: DatabaseSQLTest.php:1382
DatabaseSQLTest\testInvalidAtomicSectionEnded
testInvalidAtomicSectionEnded( $method)
provideAtomicSectionMethodsForErrors \Wikimedia\Rdbms\Database::endAtomic \Wikimedia\Rdbms\Database::...
Definition: DatabaseSQLTest.php:1844
DatabaseSQLTest\testPrematureClose3
testPrematureClose3()
\Wikimedia\Rdbms\Database::close
Definition: DatabaseSQLTest.php:2108
LIST_SET
const LIST_SET
Definition: Defines.php:44
DatabaseSQLTest\testAtomicSectionsRecovery
testAtomicSectionsRecovery()
\Wikimedia\Rdbms\Database::doSavepoint \Wikimedia\Rdbms\Database::doReleaseSavepoint \Wikimedia\Rdbms...
Definition: DatabaseSQLTest.php:1619
DatabaseSQLTest\testDeleteJoin
testDeleteJoin( $sql, $sqlText)
provideDeleteJoin Wikimedia\Rdbms\Database::deleteJoin
Definition: DatabaseSQLTest.php:533
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
DatabaseSQLTest\testUpsert
testUpsert( $sql, $sqlText)
provideUpsert Wikimedia\Rdbms\Database::upsert
Definition: DatabaseSQLTest.php:497
DatabaseSQLTest\provideUpdate
static provideUpdate()
Definition: DatabaseSQLTest.php:423
$output
$output
Definition: SyntaxHighlight.php:334
DatabaseSQLTest\testInsertSelectBatching
testInsertSelectBatching()
Definition: DatabaseSQLTest.php:743
DatabaseSQLTest\testConditional
testConditional( $sql, $sqlText)
provideConditional Wikimedia\Rdbms\Database::conditional
Definition: DatabaseSQLTest.php:913
DatabaseSQLTest\provideUnionConditionPermutations
static provideUnionConditionPermutations()
Definition: DatabaseSQLTest.php:1075
DatabaseSQLTest\testDropNonExistingTable
testDropNonExistingTable()
Wikimedia\Rdbms\Database::dropTable.
Definition: DatabaseSQLTest.php:1255
DatabaseSQLTest\testPrematureClose1
testPrematureClose1()
\Wikimedia\Rdbms\Database::close
Definition: DatabaseSQLTest.php:2054
DatabaseSQLTest\testSelectRowCount
testSelectRowCount( $sql, $sqlText)
Wikimedia\Rdbms\Subquery provideSelectRowCount.
Definition: DatabaseSQLTest.php:317
LIST_COMMA
const LIST_COMMA
Definition: Defines.php:42
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:121
DatabaseSQLTest\testInsert
testInsert( $sql, $sqlText)
provideInsert Wikimedia\Rdbms\Database::insert Wikimedia\Rdbms\Database::makeInsertOptions
Definition: DatabaseSQLTest.php:581
DatabaseSQLTest\testInsertSelect
testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert)
provideInsertSelect Wikimedia\Rdbms\Database::insertSelect Wikimedia\Rdbms\Database::nativeInsertSele...
Definition: DatabaseSQLTest.php:635
DatabaseSQLTest\testBuildSubstring
testBuildSubstring( $input, $start, $length, $expected)
Wikimedia\Rdbms\Database::buildSubstring provideBuildSubstring.
Definition: DatabaseSQLTest.php:1391
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2221
DatabaseSQLTest\testTransactionErrorState2
testTransactionErrorState2()
\Wikimedia\Rdbms\Database::query
Definition: DatabaseSQLTest.php:1890
DatabaseSQLTest\provideReplace
static provideReplace()
Definition: DatabaseSQLTest.php:781
DatabaseSQLTest\testUpdate
testUpdate( $sql, $sqlText)
provideUpdate Wikimedia\Rdbms\Database::update Wikimedia\Rdbms\Database::makeUpdateOptions Wikimedia\...
Definition: DatabaseSQLTest.php:412
DatabaseSQLTest\testImplicitTransactionRollback
testImplicitTransactionRollback()
\Wikimedia\Rdbms\Database::query
Definition: DatabaseSQLTest.php:1935
DatabaseSQLTest\testPrematureClose4
testPrematureClose4()
\Wikimedia\Rdbms\Database::close
Definition: DatabaseSQLTest.php:2131
DatabaseSQLTest\provideUpsert
static provideUpsert()
Definition: DatabaseSQLTest.php:508
DatabaseSQLTest\provideSelectRowCount
static provideSelectRowCount()
Definition: DatabaseSQLTest.php:329
DatabaseSQLTest\testDelete
testDelete( $sql, $sqlText)
provideDelete Wikimedia\Rdbms\Database::delete
Definition: DatabaseSQLTest.php:464
DatabaseSQLTest\testTransactionRollback
testTransactionRollback()
Wikimedia\Rdbms\Database::rollback Wikimedia\Rdbms\Database::doRollback.
Definition: DatabaseSQLTest.php:1237
Wikimedia\Rdbms\LikeMatch
Used by Database::buildLike() to represent characters that have special meaning in SQL LIKE clauses a...
Definition: LikeMatch.php:10
DatabaseSQLTest\provideDelete
static provideDelete()
Definition: DatabaseSQLTest.php:473
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:27
DatabaseSQLTest\setUp
setUp()
Definition: DatabaseSQLTest.php:23
DatabaseTestHelper
Helper for testing the methods from the Database class.
Definition: DatabaseTestHelper.php:11
DatabaseSQLTest\testLockForUpdate
testLockForUpdate( $sql, $sqlText)
provideLockForUpdate Wikimedia\Rdbms\Database::lockForUpdate
Definition: DatabaseSQLTest.php:263
DatabaseSQLTest\testReplace
testReplace( $sql, $sqlText)
provideReplace Wikimedia\Rdbms\Database::replace
Definition: DatabaseSQLTest.php:771
Wikimedia\Rdbms\DBTransactionError
Definition: DBTransactionError.php:27
DatabaseSQLTest\testNativeReplace
testNativeReplace( $sql, $sqlText)
provideNativeReplace Wikimedia\Rdbms\Database::nativeReplace
Definition: DatabaseSQLTest.php:886
DatabaseSQLTest\testBuildSubstring_invalidParams
testBuildSubstring_invalidParams( $start, $length)
Wikimedia\Rdbms\Database::buildSubstring Wikimedia\Rdbms\Database::assertBuildSubstringParams provide...
Definition: DatabaseSQLTest.php:1410
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2683
DatabaseSQLTest
Test the parts of the Database abstract class that deal with creating SQL text.
Definition: DatabaseSQLTest.php:15
DatabaseSQLTest\provideAtomicSectionMethodsForErrors
static provideAtomicSectionMethodsForErrors()
Definition: DatabaseSQLTest.php:1815
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:2044
DatabaseSQLTest\provideConditional
static provideConditional()
Definition: DatabaseSQLTest.php:921
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
LIST_NAMES
const LIST_NAMES
Definition: Defines.php:45
DatabaseSQLTest\testBuildLike
testBuildLike( $array, $sqlText)
provideBuildLike Wikimedia\Rdbms\Database::buildLike Wikimedia\Rdbms\Database::escapeLikeInternal
Definition: DatabaseSQLTest.php:978
DatabaseSQLTest\testAtomicSectionsCallbackCancellation
testAtomicSectionsCallbackCancellation()
\Wikimedia\Rdbms\Database::doSavepoint \Wikimedia\Rdbms\Database::doReleaseSavepoint \Wikimedia\Rdbms...
Definition: DatabaseSQLTest.php:1674
DatabaseSQLTest\testSelect
testSelect( $sql, $sqlText)
provideSelect Wikimedia\Rdbms\Database::select Wikimedia\Rdbms\Database::selectSQLText Wikimedia\Rdbm...
Definition: DatabaseSQLTest.php:52
DatabaseSQLTest\provideInsertSelect
static provideInsertSelect()
Definition: DatabaseSQLTest.php:665
DatabaseSQLTest\testNoAtomicSection
testNoAtomicSection( $method)
provideAtomicSectionMethodsForErrors \Wikimedia\Rdbms\Database::endAtomic \Wikimedia\Rdbms\Database::...
Definition: DatabaseSQLTest.php:1827
DatabaseSQLTest\provideBuildLike
static provideBuildLike()
Definition: DatabaseSQLTest.php:984
DatabaseSQLTest\provideBuildSubstring_invalidParams
provideBuildSubstring_invalidParams()
Definition: DatabaseSQLTest.php:1396