Completed
Push — release-2.1 ( 6f6d35...abeae7 )
by Mathias
08:46
created

Sources/Subs-Db-postgresql.php (14 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to the database.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Maps the implementations in this file (smf_db_function_name)
21
 * to the $smcFunc['db_function_name'] variable.
22
 * @see Subs-Db-mysql.php#smf_db_initiate
23
 *
24
 * @param string $db_server The database server
25
 * @param string $db_name The name of the database
26
 * @param string $db_user The database username
27
 * @param string $db_passwd The database password
28
 * @param string $db_prefix The table prefix
29
 * @param array $db_options An array of database options
30
 * @return null|resource Returns null on failure if $db_options['non_fatal'] is true or a PostgreSQL connection resource handle if the connection was successful.
31
 */
32
function smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, &$db_prefix, $db_options = array())
33
{
34
	global $smcFunc;
35
36
	// Map some database specific functions, only do this once.
37 View Code Duplication
	if (!isset($smcFunc['db_fetch_assoc']))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
38
		$smcFunc += array(
39
			'db_query' => 'smf_db_query',
40
			'db_quote' => 'smf_db_quote',
41
			'db_insert' => 'smf_db_insert',
42
			'db_insert_id' => 'smf_db_insert_id',
43
			'db_fetch_assoc' => 'smf_db_fetch_assoc',
44
			'db_fetch_row' => 'smf_db_fetch_row',
45
			'db_free_result' => 'pg_free_result',
46
			'db_num_rows' => 'pg_num_rows',
47
			'db_data_seek' => 'smf_db_data_seek',
48
			'db_num_fields' => 'pg_num_fields',
49
			'db_escape_string' => 'pg_escape_string',
50
			'db_unescape_string' => 'smf_db_unescape_string',
51
			'db_server_info' => 'smf_db_version',
52
			'db_affected_rows' => 'smf_db_affected_rows',
53
			'db_transaction' => 'smf_db_transaction',
54
			'db_error' => 'pg_last_error',
55
			'db_select_db' => 'smf_db_select_db',
56
			'db_title' => 'PostgreSQL',
57
			'db_sybase' => true,
58
			'db_case_sensitive' => true,
59
			'db_escape_wildcard_string' => 'smf_db_escape_wildcard_string',
60
			'db_is_resource' => 'is_resource',
61
			'db_mb4' => true,
62
			'db_ping' => 'pg_ping',
63
			'db_fetch_all' => 'smf_db_fetch_all',
64
		);
65
66
	if (!empty($db_options['persist']))
67
		$connection = @pg_pconnect('host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'' . (empty($db_options['port']) ? '' : ' port=\'' . $db_options['port'] . '\''));
68
	else
69
		$connection = @pg_connect('host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'' . (empty($db_options['port']) ? '' : ' port=\'' . $db_options['port'] . '\''));
70
71
	// Something's wrong, show an error if its fatal (which we assume it is)
72
	if (!$connection)
73
	{
74
		if (!empty($db_options['non_fatal']))
75
		{
76
			return null;
77
		}
78
		else
79
		{
80
			display_db_error();
81
		}
82
	}
83
84
	return $connection;
85
}
86
87
/**
88
 * Extend the database functionality. It calls the respective file's init
89
 * to add the implementations in that file to $smcFunc array.
90
 *
91
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
92
 */
93
function db_extend($type = 'extra')
94
{
95
	global $sourcedir, $db_type;
96
97
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php');
98
	$initFunc = 'db_' . $type . '_init';
99
	$initFunc();
100
}
101
102
/**
103
 * Fix the database prefix if necessary.
104
 * Does nothing on PostgreSQL
105
 *
106
 * @param string $db_prefix The database prefix
107
 * @param string $db_name The database name
108
 */
109
function db_fix_prefix(&$db_prefix, $db_name)
110
{
111
	return;
112
}
113
114
/**
115
 * Callback for preg_replace_callback on the query.
116
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board', etc), with
117
 * their current values from $user_info.
118
 * In addition, it performs checks and sanitization on the values sent to the database.
119
 *
120
 * @param array $matches The matches from preg_replace_callback
121
 * @return string The appropriate string depending on $matches[1]
122
 */
123
function smf_db_replacement__callback($matches)
124
{
125
	global $db_callback, $user_info, $db_prefix, $smcFunc;
126
127
	list ($values, $connection) = $db_callback;
128
129
	if ($matches[1] === 'db_prefix')
130
		return $db_prefix;
131
132
	if (isset($user_info[$matches[1]]) && strpos($matches[1], 'query_') !== false)
133
		return $user_info[$matches[1]];
134
135
	if ($matches[1] === 'empty')
136
		return '\'\'';
137
138
	if (!isset($matches[2]))
139
		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
140
141
	if ($matches[1] === 'literal')
142
		return '\'' . pg_escape_string($matches[2]) . '\'';
143
144
	if (!isset($values[$matches[2]]))
145
		smf_db_error_backtrace('The database value you\'re trying to insert does not exist: ' . (isset($smcFunc['htmlspecialchars']) ? $smcFunc['htmlspecialchars']($matches[2]) : htmlspecialchars($matches[2])), '', E_USER_ERROR, __FILE__, __LINE__);
146
147
	$replacement = $values[$matches[2]];
148
149
	switch ($matches[1])
150
	{
151
		case 'int':
152
			if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
153
				smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
154
			return (string) (int) $replacement;
155
		break;
156
157
		case 'string':
158
		case 'text':
159
			return sprintf('\'%1$s\'', pg_escape_string($replacement));
160
		break;
161
162
		case 'array_int':
163
			if (is_array($replacement))
164
			{
165
				if (empty($replacement))
166
					smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
167
168
				foreach ($replacement as $key => $value)
169
				{
170
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
171
						smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
172
173
					$replacement[$key] = (string) (int) $value;
174
				}
175
176
				return implode(', ', $replacement);
177
			}
178
			else
179
				smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
180
181
		break;
182
183
		case 'array_string':
184
			if (is_array($replacement))
185
			{
186
				if (empty($replacement))
187
					smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
188
189
				foreach ($replacement as $key => $value)
190
					$replacement[$key] = sprintf('\'%1$s\'', pg_escape_string($value));
191
192
				return implode(', ', $replacement);
193
			}
194
			else
195
				smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
196
		break;
197
198 View Code Duplication
		case 'date':
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
200
				return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]).'::date';
201
			else
202
				smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
203
		break;
204
205 View Code Duplication
		case 'time':
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
			if (preg_match('~^([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $time_matches) === 1)
207
				return sprintf('\'%02d:%02d:%02d\'', $time_matches[1], $time_matches[2], $time_matches[3]).'::time';
208
			else
209
				smf_db_error_backtrace('Wrong value type sent to the database. Time expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
210
		break;
211
212 View Code Duplication
		case 'datetime':
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d) ([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $datetime_matches) === 1)
214
				return 'to_timestamp('.
215
					sprintf('\'%04d-%02d-%02d %02d:%02d:%02d\'', $datetime_matches[1], $datetime_matches[2], $datetime_matches[3], $datetime_matches[4], $datetime_matches[5] ,$datetime_matches[6]).
216
					',\'YYYY-MM-DD HH24:MI:SS\')';
217
			else
218
				smf_db_error_backtrace('Wrong value type sent to the database. Datetime expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
219
		break;
220
221
		case 'float':
222
			if (!is_numeric($replacement))
223
				smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
224
			return (string) (float) $replacement;
225
		break;
226
227
		case 'identifier':
228
			return '"' . strtr($replacement, array('`' => '', '.' => '"."')) . '"';
229
		break;
230
231
		case 'raw':
232
			return $replacement;
233
		break;
234
235
		case 'inet':
236
			if ($replacement == 'null' || $replacement == '')
237
				return 'null';
238
			if (inet_pton($replacement) === false)
239
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
240
			return sprintf('\'%1$s\'::inet', pg_escape_string($replacement));
241
242
		case 'array_inet':
243
			if (is_array($replacement))
244
			{
245
				if (empty($replacement))
246
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
247
248
				foreach ($replacement as $key => $value)
249
				{
250
					if ($replacement == 'null' || $replacement == '')
251
						$replacement[$key] = 'null';
252
					if (!isValidIP($value))
253
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
254
					$replacement[$key] = sprintf('\'%1$s\'::inet', pg_escape_string($value));
255
				}
256
257
				return implode(', ', $replacement);
258
			}
259
			else
260
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
261
		break;
262
263
		default:
264
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
265
		break;
266
	}
267
}
268
269
/**
270
 * Just like the db_query, escape and quote a string, but not executing the query.
271
 *
272
 * @param string $db_string The database string
273
 * @param array $db_values An array of values to be injected into the string
274
 * @param resource $connection = null The connection to use (null to use $db_connection)
275
 * @return string The string with the values inserted
276
 */
277 View Code Duplication
function smf_db_quote($db_string, $db_values, $connection = null)
278
{
279
	global $db_callback, $db_connection;
280
281
	// Only bother if there's something to replace.
282
	if (strpos($db_string, '{') !== false)
283
	{
284
		// This is needed by the callback function.
285
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
286
287
		// Do the quoting and escaping
288
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
289
290
		// Clear this global variable.
291
		$db_callback = array();
292
	}
293
294
	return $db_string;
295
}
296
297
/**
298
 * Do a query.  Takes care of errors too.
299
 * Special queries may need additional replacements to be appropriate
300
 * for PostgreSQL.
301
 *
302
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
303
 * @param string $db_string The database string
304
 * @param array $db_values = array() The values to be inserted into the string
305
 * @param resource $connection = null The connection to use (null to use $db_connection)
306
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
307
 */
308
function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null)
309
{
310
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
311
	global $db_callback, $db_last_result, $db_replace_result, $modSettings;
312
313
	// Decide which connection to use.
314
	$connection = $connection === null ? $db_connection : $connection;
315
316
	// Special queries that need processing.
317
	$replacements = array(
318
		'consolidate_spider_stats' => array(
319
			'~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))',
320
		),
321
		'attach_download_increase' => array(
322
			'~LOW_PRIORITY~' => '',
323
		),
324
		'get_random_number' => array(
325
			'~RAND~' => 'RANDOM',
326
		),
327
		'insert_log_search_topics' => array(
328
			'~NOT RLIKE~' => '!~',
329
		),
330
		'insert_log_search_results_no_index' => array(
331
			'~NOT RLIKE~' => '!~',
332
		),
333
		'insert_log_search_results_subject' => array(
334
			'~NOT RLIKE~' => '!~',
335
		),
336
		'pm_conversation_list' => array(
337
			'~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']) : ''),
338
		),
339
		'profile_board_stats' => array(
340
			'~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)',
341
		),
342
	);
343
344
	// Special optimizer Hints
345
	$query_opt = array(
346
		'load_board_info' => array(
347
			'join_collapse_limit' => 1,
348
		),
349
		'calendar_get_events' => array(
350
			'enable_seqscan' => 'off',
351
		),
352
	);
353
354 View Code Duplication
	if (isset($replacements[$identifier]))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
		$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string);
356
357
	// Limits need to be a little different.
358
	$db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string);
359
360
	if (trim($db_string) == '')
361
		return false;
362
363
	// Comments that are allowed in a query are preg_removed.
364
	static $allowed_comments_from = array(
365
		'~\s+~s',
366
		'~/\*!40001 SQL_NO_CACHE \*/~',
367
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
368
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
369
	);
370
	static $allowed_comments_to = array(
371
		' ',
372
		'',
373
		'',
374
		'',
375
	);
376
377
	// One more query....
378
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
379
	$db_replace_result = 0;
380
381 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
382
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
383
384 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
	{
386
		// Pass some values to the global space for use in the callback function.
387
		$db_callback = array($db_values, $connection);
388
389
		// Inject the values passed to this function.
390
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
391
392
		// This shouldn't be residing in global space any longer.
393
		$db_callback = array();
394
	}
395
396
	// Debugging.
397 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
	{
399
		// Get the file and line number this function was called.
400
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
401
402
		// Initialize $db_cache if not already initialized.
403
		if (!isset($db_cache))
404
			$db_cache = array();
405
406
		if (!empty($_SESSION['debug_redirect']))
407
		{
408
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
409
			$db_count = count($db_cache) + 1;
410
			$_SESSION['debug_redirect'] = array();
411
		}
412
413
		$st = microtime();
414
		// Don't overload it.
415
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
416
		$db_cache[$db_count]['f'] = $file;
417
		$db_cache[$db_count]['l'] = $line;
418
		$db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start));
419
	}
420
421
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
422 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
423
	{
424
		$clean = '';
425
		$old_pos = 0;
426
		$pos = -1;
427
		while (true)
428
		{
429
			$pos = strpos($db_string, '\'', $pos + 1);
430
			if ($pos === false)
431
				break;
432
			$clean .= substr($db_string, $old_pos, $pos - $old_pos);
433
434
			while (true)
435
			{
436
				$pos1 = strpos($db_string, '\'', $pos + 1);
437
				$pos2 = strpos($db_string, '\\', $pos + 1);
438
				if ($pos1 === false)
439
					break;
440
				elseif ($pos2 === false || $pos2 > $pos1)
441
				{
442
					$pos = $pos1;
443
					break;
444
				}
445
446
				$pos = $pos2 + 1;
447
			}
448
			$clean .= ' %s ';
449
450
			$old_pos = $pos + 1;
451
		}
452
		$clean .= substr($db_string, $old_pos);
453
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
454
455
		// Comments?  We don't use comments in our queries, we leave 'em outside!
456
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
457
			$fail = true;
458
		// Trying to change passwords, slow us down, or something?
459
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
460
			$fail = true;
461
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
462
			$fail = true;
463
464
		if (!empty($fail) && function_exists('log_error'))
465
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
466
	}
467
468
	// Set optimize stuff
469
	if (isset($query_opt[$identifier]))
470
	{
471
		$query_hints = $query_opt[$identifier];
472
		$query_hints_set = '';
473
		if (isset($query_hints['join_collapse_limit']))
474
		{
475
			$query_hints_set .= 'SET LOCAL join_collapse_limit = ' . $query_hints['join_collapse_limit'] . ';';
476
		}
477
		if (isset($query_hints['enable_seqscan']))
478
		{
479
			$query_hints_set .= 'SET LOCAL enable_seqscan = ' . $query_hints['enable_seqscan'] . ';';
480
		}
481
482
		$db_string = $query_hints_set . $db_string;
483
		
484
		if (isset($db_show_debug) && $db_show_debug === true && $db_cache[$db_count]['q'] != '...')
485
			$db_cache[$db_count]['q'] = "\t\t" . $db_string;
486
	}
487
488
	$db_last_result = @pg_query($connection, $db_string);
489
490 View Code Duplication
	if ($db_last_result === false && empty($db_values['db_error_skip']))
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
491
		$db_last_result = smf_db_error($db_string, $connection);
492
493
	// Debugging.
494 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
495
		$db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
496
497
	return $db_last_result;
498
}
499
500
/**
501
 * Returns the amount of affected rows for a query.
502
 *
503
 * @param mixed $result
504
 *
505
 * @return int
506
 *
507
 */
508
function smf_db_affected_rows($result = null)
509
{
510
	global $db_last_result, $db_replace_result;
511
512
	if ($db_replace_result)
513
		return $db_replace_result;
514
	elseif ($result === null && !$db_last_result)
515
		return 0;
516
517
	return pg_affected_rows($result === null ? $db_last_result : $result);
518
}
519
520
/**
521
 * Gets the ID of the most recently inserted row.
522
 *
523
 * @param string $table The table (only used for Postgres)
524
 * @param string $field = null The specific field (not used here)
525
 * @param resource $connection = null The connection (if null, $db_connection is used) (not used here)
526
 * @return int The ID of the most recently inserted row
527
 */
528
function smf_db_insert_id($table, $field = null, $connection = null)
529
{
530
	global $smcFunc, $db_prefix;
531
532
	$table = str_replace('{db_prefix}', $db_prefix, $table);
533
534
	// Try get the last ID for the auto increment field.
535
	$request = $smcFunc['db_query']('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID',
536
		array(
537
		)
538
	);
539
	if (!$request)
540
		return false;
541
	list ($lastID) = $smcFunc['db_fetch_row']($request);
542
	$smcFunc['db_free_result']($request);
543
544
	return $lastID;
545
}
546
547
/**
548
 * Do a transaction.
549
 *
550
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
551
 * @param resource $connection The connection to use (if null, $db_connection is used)
552
 * @return bool True if successful, false otherwise
553
 */
554 View Code Duplication
function smf_db_transaction($type = 'commit', $connection = null)
555
{
556
	global $db_connection;
557
558
	// Decide which connection to use
559
	$connection = $connection === null ? $db_connection : $connection;
560
561
	if ($type == 'begin')
562
		return @pg_query($connection, 'BEGIN');
563
	elseif ($type == 'rollback')
564
		return @pg_query($connection, 'ROLLBACK');
565
	elseif ($type == 'commit')
566
		return @pg_query($connection, 'COMMIT');
567
568
	return false;
569
}
570
571
/**
572
 * Database error!
573
 * Backtrace, log, try to fix.
574
 *
575
 * @param string $db_string The DB string
576
 * @param resource $connection The connection to use (if null, $db_connection is used)
577
 */
578
function smf_db_error($db_string, $connection = null)
579
{
580
	global $txt, $context, $modSettings;
581
	global $db_connection;
582
	global $db_show_debug;
583
584
	// We'll try recovering the file and line number the original db query was called from.
585
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
586
587
	// Decide which connection to use
588
	$connection = $connection === null ? $db_connection : $connection;
589
590
	// This is the error message...
591
	$query_error = @pg_last_error($connection);
592
593
	// Log the error.
594
	if (function_exists('log_error'))
595
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" . $db_string : ''), 'database', $file, $line);
596
597
	// Nothing's defined yet... just die with it.
598
	if (empty($context) || empty($txt))
599
		die($query_error);
600
601
	// Show an error message, if possible.
602
	$context['error_title'] = $txt['database_error'];
603
	if (allowedTo('admin_forum'))
604
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
605
	else
606
		$context['error_message'] = $txt['try_again'];
607
608
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
609
	{
610
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
611
	}
612
613
	// It's already been logged... don't log it again.
614
	fatal_error($context['error_message'], false);
615
}
616
617
/**
618
 * A PostgreSQL specific function for tracking the current row...
619
 *
620
 * @param resource $request A PostgreSQL result resource
621
 * @param bool|int $counter The row number in the result to fetch (false to fetch the next one)
622
 * @return array The contents of the row that was fetched
623
 */
624 View Code Duplication
function smf_db_fetch_row($request, $counter = false)
625
{
626
	global $db_row_count;
627
628
	if ($counter !== false)
629
		return pg_fetch_row($request, $counter);
630
631
	// Reset the row counter...
632
	if (!isset($db_row_count[(int) $request]))
633
		$db_row_count[(int) $request] = 0;
634
635
	// Return the right row.
636
	return @pg_fetch_row($request, $db_row_count[(int) $request]++);
637
}
638
639
/**
640
 * Get an associative array
641
 *
642
 * @param resource $request A PostgreSQL result resource
643
 * @param int|bool $counter The row to get. If false, returns the next row.
644
 * @return array An associative array of row contents
645
 */
646 View Code Duplication
function smf_db_fetch_assoc($request, $counter = false)
647
{
648
	global $db_row_count;
649
650
	if ($counter !== false)
651
		return pg_fetch_assoc($request, $counter);
652
653
	// Reset the row counter...
654
	if (!isset($db_row_count[(int) $request]))
655
		$db_row_count[(int) $request] = 0;
656
657
	// Return the right row.
658
	return @pg_fetch_assoc($request, $db_row_count[(int) $request]++);
659
}
660
661
/**
662
 * Reset the pointer...
663
 *
664
 * @param resource $request A PostgreSQL result resource
665
 * @param int $counter The counter
666
 * @return bool Always returns true
667
 */
668
function smf_db_data_seek($request, $counter)
669
{
670
	global $db_row_count;
671
672
	$db_row_count[(int) $request] = $counter;
673
674
	return true;
675
}
676
677
/**
678
 * Unescape an escaped string!
679
 *
680
 * @param string $string The string to unescape
681
 * @return string The unescaped string
682
 */
683
function smf_db_unescape_string($string)
684
{
685
	return strtr($string, array('\'\'' => '\''));
686
}
687
688
/**
689
 * Inserts data into a table
690
 *
691
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
692
 * @param string $table The table we're inserting the data into
693
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
694
 * @param array $data The data to insert
695
 * @param array $keys The keys for the table
696
 * @param int returnmode 0 = nothing(default), 1 = last row id, 2 = all rows id as array; every mode runs only with method != 'ignore'
697
 * @param resource $connection The connection to use (if null, $db_connection is used)
698
 * @return mixed value of the first key, behavior based on returnmode. null if no data.
699
 */
700
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $returnmode = 0, $connection = null)
701
{
702
	global $smcFunc, $db_connection, $db_prefix;
703
704
	$connection = $connection === null ? $db_connection : $connection;
705
706
	$replace = '';
707
708
	if (empty($data))
709
		return;
710
711
	if (!is_array($data[array_rand($data)]))
712
		$data = array($data);
713
714
	// Replace the prefix holder with the actual prefix.
715
	$table = str_replace('{db_prefix}', $db_prefix, $table);
716
717
	// PostgreSQL doesn't support replace: we implement a MySQL-compatible behavior instead
718
	if ($method == 'replace')
719
	{
720
		$key_str = '';
721
		$col_str = '';
722
		static $pg_version;
723
		static $replace_support;
724
725
		if (empty($pg_version))
726
		{
727
			db_extend();
728
			//pg 9.5 got replace support
729
			$pg_version = $smcFunc['db_get_version']();
730
			// if we got a Beta Version
731 View Code Duplication
			if (stripos($pg_version, 'beta') !== false)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
732
				$pg_version = substr($pg_version, 0, stripos($pg_version, 'beta')) . '.0';
733
			// or RC
734 View Code Duplication
			if (stripos($pg_version, 'rc') !== false)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
735
				$pg_version = substr($pg_version, 0, stripos($pg_version, 'rc')) . '.0';
736
737
			$replace_support = (version_compare($pg_version, '9.5.0', '>=') ? true : false);
738
		}
739
740
		$count = 0;
741
		$where = '';
742
		$count_pk = 0;
743
744
		If ($replace_support)
745
		{
746
			foreach ($columns as $columnName => $type)
747
			{
748
				//check pk fiel
749
				IF (in_array($columnName, $keys))
750
				{
751
					$key_str .= ($count_pk > 0 ? ',' : '');
752
					$key_str .= $columnName;
753
					$count_pk++;
754
				}
755
				else //normal field
756
				{
757
					$col_str .= ($count > 0 ? ',' : '');
758
					$col_str .= $columnName . ' = EXCLUDED.' . $columnName;
759
					$count++;
760
				}
761
			}
762
			$replace = ' ON CONFLICT (' . $key_str . ') DO UPDATE SET ' . $col_str;
763
		}
764
		else
765
		{
766
			foreach ($columns as $columnName => $type)
767
			{
768
				// Are we restricting the length?
769
				if (strpos($type, 'string-') !== false)
770
					$actualType = sprintf($columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $count);
771
				else
772
					$actualType = sprintf($columnName . ' = {%1$s:%2$s}, ', $type, $count);
773
774
				// A key? That's what we were looking for.
775
				if (in_array($columnName, $keys))
776
					$where .= (empty($where) ? '' : ' AND ') . substr($actualType, 0, -2);
777
				$count++;
778
			}
779
780
			// Make it so.
781
			if (!empty($where) && !empty($data))
782
			{
783
				foreach ($data as $k => $entry)
784
				{
785
					$smcFunc['db_query']('', '
786
						DELETE FROM ' . $table .
787
						' WHERE ' . $where,
788
						$entry, $connection
789
					);
790
				}
791
			}
792
		}
793
	}
794
795
	$returning = '';
796
	$with_returning = false;
797
	// lets build the returning string, mysql allow only in normal mode
798 View Code Duplication
	if (!empty($keys) && (count($keys) > 0) && $returnmode > 0)
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
799
	{
800
		// we only take the first key
801
		$returning = ' RETURNING '.$keys[0];
802
		$with_returning = true;
803
	}
804
805
	if (!empty($data))
806
	{
807
		// Create the mold for a single row insert.
808
		$insertData = '(';
809
		foreach ($columns as $columnName => $type)
810
		{
811
			// Are we restricting the length?
812
			if (strpos($type, 'string-') !== false)
813
				$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
814
			else
815
				$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
816
		}
817
		$insertData = substr($insertData, 0, -2) . ')';
818
819
		// Create an array consisting of only the columns.
820
		$indexed_columns = array_keys($columns);
821
822
		// Here's where the variables are injected to the query.
823
		$insertRows = array();
824
		foreach ($data as $dataRow)
825
			$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
826
827
		// Do the insert.
828
		$request = $smcFunc['db_query']('', '
829
			INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '")
830
			VALUES
831
				' . implode(',
832
				', $insertRows).$replace.$returning,
833
			array(
834
				'security_override' => true,
835
				'db_error_skip' => $method == 'ignore' || $table === $db_prefix . 'log_errors',
836
			),
837
			$connection
838
		);
839
840
		if ($with_returning && $request !== false)
841
		{
842
			if ($returnmode === 2)
843
				$return_var = array();
844
845
			while(($row = $smcFunc['db_fetch_row']($request)) && $with_returning)
846
			{
847
				if (is_numeric($row[0])) // try to emulate mysql limitation
848
				{
849
					if ($returnmode === 1)
850
						$return_var = $row[0];
851
					elseif ($returnmode === 2)
852
						$return_var[] = $row[0];
853
				}
854
				else
855
				{
856
					$with_returning = false;
857
					trigger_error('trying to returning ID Field which is not a Int field', E_USER_ERROR);
858
				}
859
			}
860
		}
861
	}
862
863
	if ($with_returning && !empty($return_var))
864
		return $return_var;
865
}
866
867
/**
868
 * Dummy function really. Doesn't do anything on PostgreSQL.
869
 *
870
 * @param string $db_name The database name
871
 * @param resource $db_connection The database connection
872
 * @return true Always returns true
873
 */
874
function smf_db_select_db($db_name, $db_connection)
875
{
876
	return true;
877
}
878
879
/**
880
 * Get the current version.
881
 * @return string The client version
882
 */
883
function smf_db_version()
884
{
885
	$version = pg_version();
886
887
	return $version['client'];
888
}
889
890
/**
891
 * This function tries to work out additional error information from a back trace.
892
 *
893
 * @param string $error_message The error message
894
 * @param string $log_message The message to log
895
 * @param string|bool $error_type What type of error this is
896
 * @param string $file The file the error occurred in
897
 * @param int $line What line of $file the code which generated the error is on
898
 * @return void|array Returns an array with the file and line if $error_type is 'return'
899
 */
900 View Code Duplication
function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
901
{
902
	if (empty($log_message))
903
		$log_message = $error_message;
904
905
	foreach (debug_backtrace() as $step)
906
	{
907
		// Found it?
908
		if (strpos($step['function'], 'query') === false && !in_array(substr($step['function'], 0, 7), array('smf_db_', 'preg_re', 'db_erro', 'call_us')) && strpos($step['function'], '__') !== 0)
909
		{
910
			$log_message .= '<br>Function: ' . $step['function'];
911
			break;
912
		}
913
914
		if (isset($step['line']))
915
		{
916
			$file = $step['file'];
917
			$line = $step['line'];
918
		}
919
	}
920
921
	// A special case - we want the file and line numbers for debugging.
922
	if ($error_type == 'return')
923
		return array($file, $line);
924
925
	// Is always a critical error.
926
	if (function_exists('log_error'))
927
		log_error($log_message, 'critical', $file, $line);
928
929
	if (function_exists('fatal_error'))
930
	{
931
		fatal_error($error_message, $error_type);
932
933
		// Cannot continue...
934
		exit;
935
	}
936
	elseif ($error_type)
937
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
938
	else
939
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
940
}
941
942
/**
943
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
944
 *
945
 * @param string $string The string to escape
946
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
947
 * @return string The escaped string
948
 */
949 View Code Duplication
function smf_db_escape_wildcard_string($string, $translate_human_wildcards = false)
950
{
951
	$replacements = array(
952
		'%' => '\%',
953
		'_' => '\_',
954
		'\\' => '\\\\',
955
	);
956
957
	if ($translate_human_wildcards)
958
		$replacements += array(
959
			'*' => '%',
960
		);
961
962
	return strtr($string, $replacements);
963
}
964
965
/**
966
 * Fetches all rows from a result as an array 
967
 *
968
 * @param resource $request A PostgreSQL result resource
969
 * @return array An array that contains all rows (records) in the result resource
970
 */
971
function smf_db_fetch_all($request)
972
{
973
	// Return the right row.
974
	return @pg_fetch_all($request);
975
}
976
977
?>