Passed
Pull Request — development (#3445)
by Emanuele
06:25
created

Query::replace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2.5623

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 12
nc 2
nop 5
dl 0
loc 32
ccs 12
cts 25
cp 0.48
crap 2.5623
rs 9.8666
c 2
b 0
f 0
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the Postgre 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
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\Database\Postgresql;
18
19
use ElkArte\Database\AbstractQuery;
20
use ElkArte\Errors\Errors;
21
use ElkArte\ValuesContainer;
22
23
/**
24
 * PostgreSQL database class, implements database class to control mysql functions
25
 */
26
class Query extends AbstractQuery
27
{
28
	/**
29
	 * {@inheritDoc}
30
	 */
31
	const ESCAPE_CHAR = '\'\'';
32
33
	/**
34
	 * Since PostgreSQL doesn't support INSERT REPLACE we are using this to remember
35
	 * the rows affected by the delete
36
	 *
37
	 * @var int
38
	 */
39
	private $_in_transaction = false;
40
41
	/**
42
	 * {@inheritDoc}
43
	 */
44
	protected $ilike = ' ILIKE ';
45
46
	/**
47
	 * {@inheritDoc}
48
	 */
49
	protected $not_ilike = ' NOT ILIKE ';
50
51
	/**
52
	 * {@inheritDoc}
53
	 */
54
	protected $rlike = ' ~* ';
55
56
	/**
57
	 * {@inheritDoc}
58
	 */
59
	protected $not_rlike = ' !~* ';
60
61
	/**
62
	 * Used by insert to deal with conflicts (mostly for replace)
63
	 */
64
	private $on_conflict = '';
65
66
	/**
67
	 * {@inheritDoc}
68
	 */
69
	public function fix_prefix($db_prefix, $db_name)
70
	{
71
		return $db_prefix;
72
	}
73
74
	/**
75
	 * {@inheritDoc}
76 37
	 */
77
	public function last_error()
78
	{
79 37
		if (is_resource($this->connection))
0 ignored issues
show
introduced by
The condition is_resource($this->connection) is always false.
Loading history...
80
		{
81
			return pg_last_error($this->connection);
82
		}
83
84
		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...
85 37
	}
86
87 27
	/**
88
	 * {@inheritDoc}
89
	 */
90
	public function insert($method, $table, $columns, $data, $keys, $disable_trans = false)
91 37
	{
92
		// Compatibility check meant to support the old way of doing REPLACE's
93 37
		if ($method === 'replace')
94 37
		{
95
			return $this->replace($table, $columns, $data, $keys, $disable_trans);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->replace($t... $keys, $disable_trans) returns the type ElkArte\Database\Postgresql\Result which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::insert() of boolean|resource.

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...
96 33
		}
97 33
98
		if ($method === 'ignore')
99
		{
100
			$this->on_conflict = 'ON CONFLICT (' . implode(', ', $keys) . ') DO NOTHING';
101 37
		}
102
103 28
		list($table, $indexed_columns, $insertRows) = $this->prepareInsert($table, $columns, $data);
104 28
105 28
		// Do the insert.
106 28
		$this->result = $this->query('', '
107
			INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '")
108
			VALUES
109 28
			' . implode(',
110
			', $insertRows) . $this->on_conflict,
111 20
			array(
112
				'security_override' => true,
113
			)
114
		);
115 17
		if ($method === 'ignore')
116
		{
117
			$this->on_conflict = '';
118
		}
119 28
120
		$inserted_results = !is_resource($this->_db_last_result) ? 0 : pg_affected_rows($this->_db_last_result);
121 28
122
		$last_inserted_id = $this->insert_id($table);
123 28
124
		$this->result->updateDetails([
125
			'insert_id' => $last_inserted_id,
126
			'insertedResults' => $inserted_results,
127 28
			'lastResult' => $this->_db_last_result,
128
		]);
129 28
130
		return $this->result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->result returns the type ElkArte\Database\Postgresql\Result which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::insert() of boolean|resource.

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...
131 28
	}
132 28
133 28
	/**
134
	 * {@inheritDoc}
135
	 */
136 28
	public function replace($table, $columns, $data, $keys, $disable_trans = false)
137
	{
138
// 		$local_transaction = false;
139
// 		if (!$this->_in_transaction && !$disable_trans)
140
// 		{
141 37
// 			$this->transaction('begin');
142
// 			$local_transaction = true;
143
// 		}
144 37
145 37
		$sets = [];
146
		foreach ($columns as $columnName => $type)
147
		{
148 37
			$sets[] = $columnName . ' = EXCLUDED.' . $columnName;
149
		}
150 25
		$this->on_conflict = '
151
			ON CONFLICT (' . implode(', ', $keys) . ') DO
152
			UPDATE SET ' . implode(',
153
				', $sets);
154 34
155
		$this->insert('', $table, $columns, $data, $keys, $disable_trans);
156
		$this->on_conflict = '';
157 37
158
		$this->result->updateDetails([
159
			'replaceResults' => $this->result->getDetail('insertedResults')
160 37
		]);
161
162
// 		if ($local_transaction)
163 37
// 		{
164 37
// 			$this->transaction('commit');
165
// 		}
166 37
167
		return $this->result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->result returns the type ElkArte\Database\Postgresql\Result which is incompatible with the return type mandated by ElkArte\Database\QueryInterface::replace() of boolean|resource.

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...
168
	}
169 37
170 37
	/**
171 37
	 * {@inheritDoc}
172
	 */
173
	public function transaction($type = 'commit')
174 37
	{
175 37
		if ($type === 'begin')
176
		{
177 37
			$this->_in_transaction = true;
0 ignored issues
show
Documentation Bug introduced by
The property $_in_transaction was declared of type integer, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
178
179
			return @pg_query($this->connection, 'BEGIN');
0 ignored issues
show
Bug introduced by
$this->connection of type ElkArte\Database\ConnectionInterface is incompatible with the type resource expected by parameter $connection of pg_query(). ( Ignorable by Annotation )

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

179
			return @pg_query(/** @scrutinizer ignore-type */ $this->connection, 'BEGIN');
Loading history...
180 37
		}
181
182
		if ($type === 'rollback')
183 37
		{
184
			return @pg_query($this->connection, 'ROLLBACK');
185 37
		}
186
187 28
		if ($type === 'commit')
188
		{
189
			$this->_in_transaction = false;
190
191 37
			return @pg_query($this->connection, 'COMMIT');
192
		}
193 33
194
		return false;
195
	}
196 37
197
	/**
198 37
	 * {@inheritDoc}
199
	 */
200
	protected function initialChecks($db_string, $db_values, $identifier = '')
201 37
	{
202 37
		// Special queries that need processing.
203 37
		$replacements = array(
204 37
			'pm_conversation_list' => array(
205 37
				'~ORDER\\s+BY\\s+\\{raw:sort\\}~' => 'ORDER BY ' . (isset($db_values['sort']) ? ($db_values['sort'] === 'pm.id_pm' ? 'MAX(pm.id_pm)' : $db_values['sort']) : ''),
206 37
			),
207
		);
208
209
		if (isset($replacements[$identifier]))
210 37
		{
211
			$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string);
212
		}
213
214
		// Limits need to be a little different, left in place for non conformance addons
215
		$db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})(.*)~i', ' LIMIT $2 OFFSET $1 $3', $db_string);
216 33
217
		return $db_string;
218 33
	}
219
220 33
	/**
221
	 * {@inheritDoc}
222 33
	 */
223
	protected function executeQuery($db_string)
224
	{
225 33
		$this->_db_last_result = @pg_query($this->connection, $db_string);
0 ignored issues
show
Bug introduced by
$this->connection of type ElkArte\Database\ConnectionInterface is incompatible with the type resource expected by parameter $connection of pg_query(). ( Ignorable by Annotation )

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

225
		$this->_db_last_result = @pg_query(/** @scrutinizer ignore-type */ $this->connection, $db_string);
Loading history...
Documentation Bug introduced by
It seems like @pg_query($this->connection, $db_string) can also be of type false. However, the property $_db_last_result is declared as type 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...
226
227
		$this->result = new Result($this->_db_last_result);
228
	}
229
230 33
	/**
231
	 * {@inheritDoc}
232 33
	 */
233
	public function error($db_string)
234 33
	{
235
		global $txt, $modSettings;
236
237
		// We'll try recovering the file and line number the original db query was called from.
238
		list ($file, $line) = $this->backtrace_message();
239
240
		// Just in case nothing can be found from debug_backtrace
241
		$file = $file ?? __FILE__;
242
		$line = $line ?? __LINE__;
243 148
244
		// Decide which connection to use
245
		// This is the error message...
246
		$query_error = @pg_last_error($this->connection);
0 ignored issues
show
Bug introduced by
$this->connection of type ElkArte\Database\ConnectionInterface is incompatible with the type resource expected by parameter $connection of pg_last_error(). ( Ignorable by Annotation )

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

246
		$query_error = @pg_last_error(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
247
248 148
		// Log the error.
249
		Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" . $db_string : ''), 'database', $file, $line);
250
251
		$this->throwError($db_string, $query_error, $file, $line);
252
	}
253
254
	/**
255
	 * Last inserted id.
256
	 *
257
	 * @param string $table
258
	 *
259
	 * @return bool|int
260
	 * @throws \ElkArte\Exceptions\Exception
261
	 */
262
	public function insert_id($table)
263
	{
264
		$table = str_replace('{db_prefix}', $this->_db_prefix, $table);
265
266
		$this->skip_next_error();
267
268
		// Try get the last ID for the auto increment field.
269
		$request = $this->query('', '
270
			SELECT CURRVAL(\'' . $table . '_seq\') AS insertID',
271 148
			array('security_override' => true)
272
		);
273
274
		if (!$request)
0 ignored issues
show
introduced by
$request is of type ElkArte\Database\Postgresql\Result, thus it always evaluated to true.
Loading history...
275
		{
276
			return false;
277
		}
278 148
279
		list ($lastID) = $request->fetch_row();
280
		$request->free_result();
281
282
		return $lastID;
283
	}
284 148
285
	/**
286 148
	 * Unescape an escaped string!
287
	 *
288
	 * @param string $string
289
	 *
290
	 * @return string
291
	 */
292 148
	public function unescape_string($string)
293 148
	{
294
		return strtr($string, array('\'\'' => '\''));
295 148
	}
296
297
	/**
298 148
	 * {@inheritDoc}
299
	 */
300 148
	public function support_ignore()
301
	{
302 148
		return false;
303
	}
304 148
305
	/**
306
	 * {@inheritDoc}
307
	 */
308
	public function server_version()
309
	{
310 148
		$version = pg_version();
311
312 37
		return $version['server'];
313
	}
314
315
	/**
316 148
	 * {@inheritDoc}
317
	 */
318 148
	public function title()
319
	{
320
		return 'PostgreSQL';
321
	}
322 148
323
	/**
324 34
	 * {@inheritDoc}
325
	 */
326
	public function case_sensitive()
327 148
	{
328
		return true;
329
	}
330
331
	/**
332
	 * {@inheritDoc}
333
	 */
334
	public function escape_string($string)
335
	{
336
		return pg_escape_string($string);
0 ignored issues
show
Bug introduced by
The call to pg_escape_string() has too few arguments starting with data. ( Ignorable by Annotation )

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

336
		return /** @scrutinizer ignore-call */ pg_escape_string($string);

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...
337
	}
338
339
	/**
340
	 * {@inheritDoc}
341
	 */
342
	public function server_info()
343
	{
344
		// give info on client! we use it in install and upgrade and such things.
345
		$version = pg_version();
346
347
		return $version['client'];
348
	}
349
350
	/**
351
	 * {@inheritDoc}
352
	 */
353
	public function client_version()
354
	{
355
		$version = pg_version();
356
357
		return $version['client'];
358
	}
359
360
	/**
361
	 * Dummy function really. Doesn't do anything in PostgreSQL.
362
	 *
363
	 * {@inheritDoc}
364
	 */
365
	public function select_db($db_name = null)
366
	{
367
		return true;
368
	}
369
370
	/**
371
	 * Returns the result resouce of the last query executed
372
	 *
373
	 * @return resource
374
	 */
375
	public function lastResult()
376
	{
377
		return $this->_db_last_result;
378
	}
379
380
	/**
381
	 * {@inheritDoc}
382
	 */
383
	public function validConnection()
384 37
	{
385
		return is_resource($this->connection);
386 37
	}
387
388 37
	/**
389
	 * {@inheritDoc}
390
	 */
391 37
	public function list_tables($db_name_str = false, $filter = false)
392 37
	{
393 37
		$dump = new Dump($this);
394
395
		return $dump->list_tables($db_name_str, $filter);
396 37
	}
397
398 34
	/**
399
	 * {@inheritDoc}
400
	 */
401 19
	protected function _replaceIdentifier($replacement)
402 19
	{
403
		if (preg_match('~[a-z_][0-9a-zA-Z$,_]{0,60}~', $replacement) !== 1)
404 19
		{
405
			$this->error_backtrace('Wrong value type sent to the database. Invalid identifier used. (' . $replacement . ')', '', E_USER_ERROR, __FILE__, __LINE__);
406
		}
407
408
		return '"' . $replacement . '"';
409
	}
410
}
411