Passed
Pull Request — development (#3829)
by Spuds
07:44
created

Query::error()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.3906

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 3
nop 1
dl 0
loc 35
ccs 9
cts 12
cp 0.75
crap 5.3906
rs 9.3888
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the mysql database.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 * copyright:    2004-2011, GreyWyvern - All rights reserved.
13
 *
14
 * @version 2.0 Beta 1
15
 *
16
 */
17
18
namespace ElkArte\Database\Mysqli;
19
20
use ElkArte\Cache\Cache;
21
use ElkArte\Database\AbstractQuery;
22
use ElkArte\Database\AbstractResult;
23
use ElkArte\Errors\Errors;
0 ignored issues
show
Bug introduced by
The type ElkArte\Errors\Errors was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use ElkArte\Helper\Util;
25
use ElkArte\Helper\ValuesContainer;
26
use ElkArte\Languages\Txt;
27
28
/**
29
 * SQL database class, implements database class to control mysql functions
30
 */
31
class Query extends AbstractQuery
32
{
33
	/** {@inheritDoc} */
34
	protected $ilike = ' LIKE ';
35
36
	/** {@inheritDoc} */
37
	protected $not_ilike = ' NOT LIKE ';
38
39
	/** {@inheritDoc} */
40
	protected $rlike = ' RLIKE ';
41
42 1
	/** {@inheritDoc} */
43
	protected $not_rlike = ' NOT RLIKE ';
44 1
45
	// Error number constants
46 1
	private const ERR_COMMAND_DENIED = 1142;
47
	private const ERR_TABLE_HANDLER = 1030;
48
	private const ERR_KEY_FILE = 1016;
49 1
	private const ERR_INCORRECT_KEY_FILE = 1034;
50
	private const ERR_OLD_KEY_FILE = 1035;
51
	private const ERR_LOCK_WAIT_TIMEOUT_EXCEEDED = 1205;
52
	private const ERR_DEADLOCK_FOUND = 1213;
53
	private const ERR_SERVER_HAS_GONE_AWAY = 2006;
54 1
	private const ERR_LOST_CONNECTION_TO_SERVER = 2013;
55
56 1
	/**
57
	 * {@inheritDoc}
58
	 */
59
	public function fix_prefix($db_prefix, $db_name)
60
	{
61
		return is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix;
62
	}
63
64
	/**
65
	 * {@inheritDoc}
66
	 */
67
	public function transaction($type = 'commit')
68
	{
69
		if ($type === 'begin')
70
		{
71
			return @mysqli_query($this->connection, 'BEGIN');
0 ignored issues
show
Bug Best Practice introduced by
The expression return @mysqli_query($this->connection, 'BEGIN') also could return the type mysqli_result which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::transaction() of boolean|resource.
Loading history...
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

71
			return @mysqli_query(/** @scrutinizer ignore-type */ $this->connection, 'BEGIN');
Loading history...
72
		}
73
74
		if ($type === 'rollback')
75
		{
76
			return @mysqli_query($this->connection, 'ROLLBACK');
0 ignored issues
show
Bug Best Practice introduced by
The expression return @mysqli_query($th...connection, 'ROLLBACK') also could return the type mysqli_result which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::transaction() of boolean|resource.
Loading history...
77
		}
78
79
		if ($type === 'commit')
80 42
		{
81
			return @mysqli_query($this->connection, 'COMMIT');
0 ignored issues
show
Bug Best Practice introduced by
The expression return @mysqli_query($this->connection, 'COMMIT') also could return the type mysqli_result which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::transaction() of boolean|resource.
Loading history...
82
		}
83 42
84
		return false;
85
	}
86
87
	/**
88
	 * {@inheritDoc}
89 42
	 */
90
	public function last_error()
91 30
	{
92
		if (is_object($this->connection))
93
		{
94
			return mysqli_error($this->connection);
95 42
		}
96
97
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::last_error() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
98 42
	}
99 42
100
	/**
101
	 * {@inheritDoc}
102 42
	 */
103
	public function insert($method, $table, $columns, $data, $keys, $disable_trans = false)
104 30
	{
105
		[$table, $indexed_columns, $insertRows] = $this->prepareInsert($table, $columns, $data);
106
107
		// Determine the method of insertion.
108 40
		$queryTitle = $method === 'replace' ? 'REPLACE' : ($method === 'ignore' ? 'INSERT IGNORE' : 'INSERT');
109
110
		// Do the insert.
111 42
		$this->result = $this->query('', '
112
			' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`)
113
			VALUES
114 42
				' . implode(',
115
				', $insertRows),
116
			[
117 42
				'security_override' => true,
118 42
			]
119
		);
120 42
121
		$this->result->updateDetails([
122
			'connection' => $this->connection
123
		]);
124 42
125
		return $this->result;
126 42
	}
127 42
128
	/**
129 42
	 * {@inheritDoc}
130 42
	 */
131
	public function replace($table, $columns, $data, $keys, $disable_trans = false)
132 42
	{
133 37
		return $this->insert('replace', $table, $columns, $data, $keys, $disable_trans);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->insert('re... $keys, $disable_trans) returns the type ElkArte\Database\AbstractResult which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::replace() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
134
	}
135 42
136
	/**
137
	 * {@inheritDoc}
138
	 */
139 42
	protected function initialChecks($db_string, $db_values, $identifier = '')
140 42
	{
141 42
		// Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By
142 42
		if (str_contains($db_string, 'GROUP BY')
143
			&& !str_contains($db_string, 'ORDER BY')
144
			&& preg_match('~^\s+SELECT~i', $db_string))
145
		{
146 42
			if (($pos = strpos($db_string, 'LIMIT ')) !== false)
147
			{
148
				// Add before LIMIT
149
				$db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string));
150
			}
151
			else
152 153
			{
153
				// Append it.
154
				$db_string .= "\n\t\t\tORDER BY null";
155 153
			}
156
		}
157
158 153
		return $db_string;
159 153
	}
160 153
161
	protected function executeQuery($db_string)
162 14
	{
163
		if (!$this->_unbuffered)
164
		{
165 2
			$this->_db_last_result = @mysqli_query($this->connection, $db_string);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_query() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
			$this->_db_last_result = @mysqli_query(/** @scrutinizer ignore-type */ $this->connection, $db_string);
Loading history...
Documentation Bug introduced by
It seems like @mysqli_query($this->connection, $db_string) can also be of type mysqli_result. However, the property $_db_last_result is declared as type boolean|resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
166
		}
167
		else
168
		{
169
			$this->_db_last_result = @mysqli_query($this->connection, $db_string, MYSQLI_USE_RESULT);
170 12
		}
171
172
		$this->result = new Result($this->_db_last_result,
173
			new ValuesContainer([
174 153
				'connection' => $this->connection
175
			])
176
		);
177 153
	}
178
179 153
	/**
180
	 * {@inheritDoc}
181 153
	 */
182
	public function error($db_string)
183 153
	{
184
		global $txt, $modSettings;
185
186
		[$file, $line] = $this->backtrace_message();
187
		$file = $file ?? __FILE__;
188
		$line = $line ?? __LINE__;
189
190 153
		$query_error = mysqli_error($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_error() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

190
		$query_error = mysqli_error(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
191
		$query_errno = mysqli_errno($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_errno() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

191
		$query_errno = mysqli_errno(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
192
193
		// See if there is a recovery we can attempt
194
		$check = match ($query_errno)
195
		{
196 153
			self::ERR_COMMAND_DENIED => $this->handleCommandDeniedError($db_string, $query_error, $file, $line),
197
			self::ERR_TABLE_HANDLER, self::ERR_KEY_FILE, self::ERR_INCORRECT_KEY_FILE, self::ERR_OLD_KEY_FILE => $this->handleTableOrKeyFileError($db_string, $query_errno, $query_error),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->handleTableOrKeyF...ry_errno, $query_error) targeting ElkArte\Database\Mysqli\...leTableOrKeyFileError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
198 4
			self::ERR_SERVER_HAS_GONE_AWAY, self::ERR_LOST_CONNECTION_TO_SERVER => $this->handleConnectionError($db_string),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->handleConnectionError($db_string) targeting ElkArte\Database\Mysqli\...handleConnectionError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
199
			default => null,
200
		};
201
202 153
		// Did we attempt to do a repair, return those results
203
		if ($check !== null)
204 153
		{
205 153
			return $check;
206 153
		}
207
208
		// Log the error.
209
		$query_error = $this->handleSpaceError($query_errno, $query_error);
210 153
		if ($query_errno !== self::ERR_DEADLOCK_FOUND && $query_errno !== self::ERR_LOCK_WAIT_TIMEOUT_EXCEEDED)
211
		{
212
			Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (empty($modSettings['enableErrorQueryLogging']) ? '' : "\n\n$db_string"), 'database', $file, $line);
213
		}
214
215
		// Argh, enough said.
216
		$this->throwError($db_string, $query_error, $file, $line);
217
	}
218
219
	/**
220
	 * Handles the command denied error and takes appropriate action.
221
	 *
222
	 * @param string $db_string The database query string.
223
	 * @param string $query_error The error message related to the query.
224
	 * @param string $file The file where the error occurred.
225
	 * @param int $line The line number where the error occurred.
226
	 * @return bool|null Returns false if the command is DELETE, UPDATE, or INSERT. Returns null otherwise.
227
	 */
228
	private function handleCommandDeniedError($db_string, $query_error, $file, $line): ?bool
229
	{
230
		global $txt, $modSettings;
231
232
		// We cannot do something, try to find out what and act accordingly
233
		$command = substr(trim($db_string), 0, 6);
234
		if ($command === 'DELETE' || $command === 'UPDATE' || $command === 'INSERT')
235
		{
236
			// We can try to ignore it (warning the admin, though it's a thing to do) \
237
			// and serve the page just SELECTing
238
			$_SESSION['query_command_denied'][$command] = $query_error;
239
240
			// Let the admin know there is a command-denied issue
241
			Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (empty($modSettings['enableErrorQueryLogging']) ? '' : "\n\n$db_string"), 'database', $file, $line);
242
243
			return false;
244
		}
245
246
		return null;
247
	}
248
249
	/**
250
	 * Handles table or key file errors in the database.
251
	 *
252
	 * @param string $db_string The database query string.
253
	 * @param int $query_errno The error code of the query.
254
	 * @param string $query_error The error message of the query.
255
	 *
256
	 * @return null|AbstractResult Returns the result of the query if successful, otherwise null.
257
	 */
258
	private function handleTableOrKeyFileError($db_string, $query_errno, $query_error): ?AbstractResult
259
	{
260
		global $modSettings;
261
262
		if (function_exists('\\ElkArte\\Cache\\Cache::instance()->get')
263
			&& (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] === '1'))
264
		{
265
			$db_last_error = db_last_error();
266
			$cache = Cache::instance();
267
268
			// Force caching on, just for the error checking.
269
			$old_cache = $cache->getLevel();
270
			if ($cache->isEnabled() === false)
271
			{
272
				$cache->setLevel(1);
273
			}
274
275
			$temp = null;
276
			if ($cache->getVar($temp, 'db_last_error', 600))
277
			{
278
				$db_last_error = max($db_last_error, $temp);
279
			}
280
281
			// Check for errors like 145... only fix it once every three days and send an email. (can't use empty because it might not be set yet...)
282
			if ($db_last_error < time() - 3600 * 24 * 3)
283
			{
284
				// We know there's a problem... but what?  Try to auto-detect.
285
				$fix_tables = $this->getTablesToRepair($db_string, $query_errno, $query_error);
286
				if ($fix_tables !== false)
0 ignored issues
show
introduced by
The condition $fix_tables !== false is always true.
Loading history...
287
				{
288
					return $this->attemptRepair($fix_tables, $db_string);
289
				}
290
			}
291
292
			$modSettings['cache_enable'] = $old_cache;
293
		}
294
295
		return null;
296
	}
297
298
	/**
299
	 * Handles connection errors and tries to reconnect.
300
	 *
301
	 * @param string $db_string The database query string.
302
	 * @return null|AbstractResult Returns the result of the query if successful, otherwise null.
303
	 */
304
	private function handleConnectionError($db_string): ?AbstractResult
305
	{
306
		global $db_persist, $db_server, $db_user, $db_passwd, $db_name, $ssi_db_user, $ssi_db_passwd, $db_port;
307
308
		// Check for the "lost connection" or "deadlock found" errors - and try it just one more time.
309
		$new_connection = false;
310
311
		// Are we in SSI mode?  If so, try that username and password first
312
		if (ELK === 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
313
		{
314
			$new_connection = @mysqli_connect((empty($db_persist) ? '' : 'p:') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name, $db_port ?? null);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with socket. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

314
			$new_connection = @/** @scrutinizer ignore-call */ mysqli_connect((empty($db_persist) ? '' : 'p:') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name, $db_port ?? null);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
315
		}
316
317
		// Fall back to the regular username and password if need be
318
		if (!$new_connection)
319
		{
320
			$new_connection = @mysqli_connect((empty($db_persist) ? '' : 'p:') . $db_server, $db_user, $db_passwd, $db_name, $db_port ?? null);
321
		}
322
323
		if ($new_connection)
324
		{
325
			$this->connection = $new_connection;
326
327
			// Try a deadlock more than once more.
328
			for ($n = 0; $n < 4; $n++)
329
			{
330
				$ret = $this->query('', $db_string, false);
331
332
				$new_errno = mysqli_errno($new_connection);
333
				if ($ret->hasResults() || in_array($new_errno, [self::ERR_LOCK_WAIT_TIMEOUT_EXCEEDED, self::ERR_DEADLOCK_FOUND]))
334
				{
335
					break;
336
				}
337
			}
338
339
			// If it failed again, shucks to be you... we're not trying it over and over.
340
			if ($ret->hasResults())
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.
Loading history...
341
			{
342
				return $ret;
343
			}
344
		}
345
346
		return null;
347
	}
348
349
	/**
350
	 * Handle space error in a database query.
351
	 *
352
	 * @param int $query_errno The error number of the query.
353
	 * @param string $query_error The error message of the query.
354
	 * @return string The updated error message with space error handling.
355
	 */
356
	private function handleSpaceError($query_errno, $query_error): string
357
	{
358
		global $txt;
359
360
		if ($query_errno === self::ERR_TABLE_HANDLER &&
361
			(str_contains($query_error, ' -1 ')
362
				|| str_contains($query_error, ' 28 ')
363
				|| str_contains($query_error, ' 12 ')))
364
		{
365
			if (!isset($txt))
366
			{
367
				$query_error .= ' - check database storage space.';
368
			}
369
			else
370
			{
371
				if (!isset($txt['mysql_error_space']))
372
				{
373
					Txt::load('Errors');
374
				}
375
376
				$query_error .= $txt['mysql_error_space'] ?? ' - check database storage space.';
377
			}
378
		}
379
380
		return $query_error;
381
	}
382
383
	/**
384
	 * Returns an array of tables to repair based on the provided parameters.
385
	 *
386
	 * @param string $db_string The database query string.
387
	 * @param int $query_errno The error number associated with the query.
388
	 * @param string $query_error The error message associated with the query.
389
	 * @return array|bool An array of tables to repair, or false if no tables to repair.
390
	 */
391
	private function getTablesToRepair($db_string, $query_errno, $query_error)
392
	{
393
		if ($query_errno === self::ERR_TABLE_HANDLER && str_contains($query_error, ' 127 '))
394
		{
395
			preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~', $db_string, $matches);
396
397
			$fix_tables = [];
398
			foreach ($matches[1] as $tables)
399
			{
400
				$tables = array_unique(explode(',', $tables));
401
				foreach ($tables as $table)
402
				{
403
					// Now, it's still theoretically possible this could be an injection.  So backtick it!
404
					if (trim($table) !== '')
405
					{
406
						$fix_tables[] = '`' . strtr(trim($table), ['`' => '']) . '`';
407
					}
408
				}
409
			}
410
411
			return array_unique($fix_tables);
412
		}
413
414
		// Table crashed.  Let's try to fix it.
415
		if (($query_errno === self::ERR_KEY_FILE)
416
			&& preg_match('~\'([^.\']+)~', $query_error, $match) === 1)
417
		{
418
			return ['`' . $match[1] . '`'];
419
		}
420
421
		// Indexes crashed.  Should be easy to fix!
422
		if (($query_errno === self::ERR_INCORRECT_KEY_FILE || $query_errno === self::ERR_OLD_KEY_FILE)
423
			&& preg_match("~'([^']+?)'~", $query_error, $match) === 1)
424
		{
425
			return ['`' . $match[1] . '`'];
426
		}
427
428
		return false;
429
	}
430
431
	/**
432
	 * Attempt to repair the specified tables in the database.
433
	 *
434
	 * @param array $fix_tables An array containing the names of the tables to be repaired.
435
	 * @param string $db_string The database query string to be executed after attempting the repair.
436
	 *
437
	 * @return null|AbstractResult Returns the query results if the repair was successful, null otherwise.
438
	 */
439
	private function attemptRepair($fix_tables, $db_string): ?AbstractResult
440
	{
441
		global $webmaster_email, $txt;
442
443
		// sources/Logging.php for logLastDatabaseError(), subs/Mail.subs.php for sendmail().
444
		// @todo this should go somewhere else, not into the db-mysql layer I think
445
		require_once(SOURCEDIR . '/Logging.php');
446
		require_once(SUBSDIR . '/Mail.subs.php');
447
448
		// Make a note of the REPAIR...
449
		$cache = Cache::instance();
450
		$cache->put('db_last_error', time(), 600);
451
		if (!$cache->getVar($temp, 'db_last_error', 600))
452
		{
453
			logLastDatabaseError();
454
		}
455
456
		// Attempt to find and repair the broken table.
457
		foreach ($fix_tables as $table)
458
		{
459
			$this->query('', '
460
				REPAIR TABLE ' . $table, false);
461
		}
462
463
		// And send off an email!
464
		sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair']);
465
466 1
		// Try the query again...?
467
		$ret = $this->query('', $db_string, false);
468 1
		if ($ret->hasResults())
469
		{
470
			return $ret;
471
		}
472
473
		return null;
474
	}
475
476
	/**
477
	 * Unescape an escaped string!
478
	 *
479
	 * @param string $string
480
	 *
481
	 * @return string
482
	 */
483
	public function unescape_string($string)
484
	{
485
		return stripslashes($string);
486
	}
487
488
	/**
489 16
	 * {@inheritDoc}
490
	 */
491 16
	public function support_ignore()
492
	{
493
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by ElkArte\Database\AbstractQuery::support_ignore() of false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
494
	}
495
496
	/**
497 1
	 * {@inheritDoc}
498
	 */
499 1
	public function server_version()
500
	{
501
		$request = $this->query('', '
502
			SELECT VERSION()'
503
		);
504
		[$ver] = $request->fetch_row();
505 62
		$request->free_result();
506
507 62
		return $ver;
508
	}
509 62
510
	/**
511
	 * {@inheritDoc}
512
	 */
513
	public function title()
514
	{
515
		return 'MySQL';
516
	}
517
518
	/**
519
	 * {@inheritDoc}
520
	 */
521
	public function case_sensitive()
522
	{
523 62
		return false;
524
	}
525 62
526
	/**
527 62
	 * {@inheritDoc}
528
	 */
529
	public function escape_string($string)
530
	{
531
		$string = Util::clean_4byte_chars($string);
532 62
533 62
		return mysqli_real_escape_string($this->connection, $string);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_real_escape_string() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

533
		return mysqli_real_escape_string(/** @scrutinizer ignore-type */ $this->connection, $string);
Loading history...
534
	}
535
536 62
	/**
537
	 * {@inheritDoc}
538
	 */
539
	public function server_info()
540
	{
541
		return mysqli_get_server_info($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_get_server_info() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

541
		return mysqli_get_server_info(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
542
	}
543
544
	/**
545
	 * {@inheritDoc}
546
	 */
547
	public function client_version()
548
	{
549
		$request = $this->query('', '
550
			SELECT VERSION()'
551
		);
552
		[$ver] = $request->fetch_row();
553
		$request->free_result();
554
555
		return $ver;
556
	}
557
558
	/**
559
	 * {@inheritDoc}
560
	 */
561
	public function select_db($dbName = null)
562
	{
563
		return mysqli_select_db($this->connection, $dbName);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type resource; however, parameter $mysql of mysqli_select_db() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

563
		return mysqli_select_db(/** @scrutinizer ignore-type */ $this->connection, $dbName);
Loading history...
564
	}
565
566
	/**
567
	 * {@inheritDoc}
568
	 */
569 62
	public function validConnection()
570
	{
571
		return is_object($this->connection);
572
	}
573
574
	/**
575
	 * {@inheritDoc}
576
	 */
577
	public function list_tables($db_name_str = false, $filter = false)
578
	{
579
		return (new Dump($this))->list_tables($db_name_str, $filter);
580
	}
581
582
	/**
583
	 * {@inheritDoc}
584
	 */
585
	public function supportMediumtext()
586
	{
587
		return true;
588
	}
589
590
	/**
591
	 * {@inheritDoc}
592
	 */
593
	protected function _replaceStringCaseSensitive($replacement)
594
	{
595
		return 'BINARY ' . $this->_replaceString($replacement);
596
	}
597
598
	/**
599
	 * {@inheritDoc}
600
	 */
601
	protected function _replaceStringCaseInsensitive($replacement)
602
	{
603
		return $this->_replaceString($replacement);
604
	}
605
606
	/**
607
	 * Casts the column to LOWER(column_name) for replacement__callback.
608
	 *
609
	 * @param mixed $replacement
610
	 * @return string
611
	 */
612
	protected function _replaceColumnCaseInsensitive($replacement)
613
	{
614
		return $replacement;
615
	}
616
}
617