DatabaseTestHelper.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <?php
  2. use Wikimedia\Rdbms\TransactionProfiler;
  3. use Wikimedia\Rdbms\DatabaseDomain;
  4. use Wikimedia\Rdbms\Database;
  5. /**
  6. * Helper for testing the methods from the Database class
  7. * @since 1.22
  8. */
  9. class DatabaseTestHelper extends Database {
  10. /**
  11. * __CLASS__ of the test suite,
  12. * used to determine, if the function name is passed every time to query()
  13. */
  14. protected $testName = [];
  15. /**
  16. * Array of lastSqls passed to query(),
  17. * This is an array since some methods in Database can do more than one
  18. * query. Cleared when calling getLastSqls().
  19. */
  20. protected $lastSqls = [];
  21. /** @var array List of row arrays */
  22. protected $nextResult = [];
  23. /** @var array|null */
  24. protected $nextError = null;
  25. /** @var array|null */
  26. protected $lastError = null;
  27. /**
  28. * Array of tables to be considered as existing by tableExist()
  29. * Use setExistingTables() to alter.
  30. */
  31. protected $tablesExists;
  32. /**
  33. * Value to return from unionSupportsOrderAndLimit()
  34. */
  35. protected $unionSupportsOrderAndLimit = true;
  36. public function __construct( $testName, array $opts = [] ) {
  37. $this->testName = $testName;
  38. $this->profiler = new ProfilerStub( [] );
  39. $this->trxProfiler = new TransactionProfiler();
  40. $this->cliMode = $opts['cliMode'] ?? true;
  41. $this->connLogger = new \Psr\Log\NullLogger();
  42. $this->queryLogger = new \Psr\Log\NullLogger();
  43. $this->errorLogger = function ( Exception $e ) {
  44. wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
  45. };
  46. $this->deprecationLogger = function ( $msg ) {
  47. wfWarn( $msg );
  48. };
  49. $this->currentDomain = DatabaseDomain::newUnspecified();
  50. $this->open( 'localhost', 'testuser', 'password', 'testdb' );
  51. }
  52. /**
  53. * Returns SQL queries grouped by '; '
  54. * Clear the list of queries that have been done so far.
  55. * @return string
  56. */
  57. public function getLastSqls() {
  58. $lastSqls = implode( '; ', $this->lastSqls );
  59. $this->lastSqls = [];
  60. return $lastSqls;
  61. }
  62. public function setExistingTables( $tablesExists ) {
  63. $this->tablesExists = (array)$tablesExists;
  64. }
  65. /**
  66. * @param mixed $res Use an array of row arrays to set row result
  67. */
  68. public function forceNextResult( $res ) {
  69. $this->nextResult = $res;
  70. }
  71. /**
  72. * @param int $errno Error number
  73. * @param string $error Error text
  74. * @param array $options
  75. * - wasKnownStatementRollbackError: Return value for wasKnownStatementRollbackError()
  76. */
  77. public function forceNextQueryError( $errno, $error, $options = [] ) {
  78. $this->nextError = [ 'errno' => $errno, 'error' => $error ] + $options;
  79. }
  80. protected function addSql( $sql ) {
  81. // clean up spaces before and after some words and the whole string
  82. $this->lastSqls[] = trim( preg_replace(
  83. '/\s{2,}(?=FROM|WHERE|GROUP BY|ORDER BY|LIMIT)|(?<=SELECT|INSERT|UPDATE)\s{2,}/',
  84. ' ', $sql
  85. ) );
  86. }
  87. protected function checkFunctionName( $fname ) {
  88. if ( $fname === 'Wikimedia\\Rdbms\\Database::close' ) {
  89. return; // no $fname parameter
  90. }
  91. // Handle some internal calls from the Database class
  92. $check = $fname;
  93. if ( preg_match( '/^Wikimedia\\\\Rdbms\\\\Database::query \((.+)\)$/', $fname, $m ) ) {
  94. $check = $m[1];
  95. }
  96. if ( substr( $check, 0, strlen( $this->testName ) ) !== $this->testName ) {
  97. throw new MWException( 'function name does not start with test class. ' .
  98. $fname . ' vs. ' . $this->testName . '. ' .
  99. 'Please provide __METHOD__ to database methods.' );
  100. }
  101. }
  102. function strencode( $s ) {
  103. // Choose apos to avoid handling of escaping double quotes in quoted text
  104. return str_replace( "'", "\'", $s );
  105. }
  106. public function addIdentifierQuotes( $s ) {
  107. // no escaping to avoid handling of double quotes in quoted text
  108. return $s;
  109. }
  110. public function query( $sql, $fname = '', $tempIgnore = false ) {
  111. $this->checkFunctionName( $fname );
  112. return parent::query( $sql, $fname, $tempIgnore );
  113. }
  114. public function tableExists( $table, $fname = __METHOD__ ) {
  115. $tableRaw = $this->tableName( $table, 'raw' );
  116. if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
  117. return true; // already known to exist
  118. }
  119. $this->checkFunctionName( $fname );
  120. return in_array( $table, (array)$this->tablesExists );
  121. }
  122. // Redeclare parent method to make it public
  123. public function nativeReplace( $table, $rows, $fname ) {
  124. return parent::nativeReplace( $table, $rows, $fname );
  125. }
  126. function getType() {
  127. return 'test';
  128. }
  129. function open( $server, $user, $password, $dbName ) {
  130. $this->conn = (object)[ 'test' ];
  131. return true;
  132. }
  133. function fetchObject( $res ) {
  134. return false;
  135. }
  136. function fetchRow( $res ) {
  137. return false;
  138. }
  139. function numRows( $res ) {
  140. return -1;
  141. }
  142. function numFields( $res ) {
  143. return -1;
  144. }
  145. function fieldName( $res, $n ) {
  146. return 'test';
  147. }
  148. function insertId() {
  149. return -1;
  150. }
  151. function dataSeek( $res, $row ) {
  152. /* nop */
  153. }
  154. function lastErrno() {
  155. return $this->lastError ? $this->lastError['errno'] : -1;
  156. }
  157. function lastError() {
  158. return $this->lastError ? $this->lastError['error'] : 'test';
  159. }
  160. protected function wasKnownStatementRollbackError() {
  161. return $this->lastError['wasKnownStatementRollbackError'] ?? false;
  162. }
  163. function fieldInfo( $table, $field ) {
  164. return false;
  165. }
  166. function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
  167. return false;
  168. }
  169. function fetchAffectedRowCount() {
  170. return -1;
  171. }
  172. function getSoftwareLink() {
  173. return 'test';
  174. }
  175. function getServerVersion() {
  176. return 'test';
  177. }
  178. function getServerInfo() {
  179. return 'test';
  180. }
  181. function isOpen() {
  182. return $this->conn ? true : false;
  183. }
  184. function ping( &$rtt = null ) {
  185. $rtt = 0.0;
  186. return true;
  187. }
  188. protected function closeConnection() {
  189. return true;
  190. }
  191. protected function doQuery( $sql ) {
  192. $sql = preg_replace( '< /\* .+? \*/>', '', $sql );
  193. $this->addSql( $sql );
  194. if ( $this->nextError ) {
  195. $this->lastError = $this->nextError;
  196. $this->nextError = null;
  197. return false;
  198. }
  199. $res = $this->nextResult;
  200. $this->nextResult = [];
  201. $this->lastError = null;
  202. return new FakeResultWrapper( $res );
  203. }
  204. public function unionSupportsOrderAndLimit() {
  205. return $this->unionSupportsOrderAndLimit;
  206. }
  207. public function setUnionSupportsOrderAndLimit( $v ) {
  208. $this->unionSupportsOrderAndLimit = (bool)$v;
  209. }
  210. }