Completed
Push — release-2.1 ( e6c696...22bfba )
by Mathias
07:04
created

Sources/Subs-Db-postgresql.php (5 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 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
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']) || $smcFunc['db_fetch_assoc'] != 'postg_fetch_assoc')
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
		);
62
63
	if (!empty($db_options['persist']))
64
		$connection = @pg_pconnect('host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'' . (empty($db_options['port']) ? '' : ' port=\'' . $db_options['port'] . '\''));
65
	else
66
		$connection = @pg_connect( 'host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'' . (empty($db_options['port']) ? '' : ' port=\'' . $db_options['port'] . '\''));
67
68
	// Something's wrong, show an error if its fatal (which we assume it is)
69
	if (!$connection)
70
	{
71
		if (!empty($db_options['non_fatal']))
72
		{
73
			return null;
74
		}
75
		else
76
		{
77
			display_db_error();
78
		}
79
	}
80
81
	return $connection;
82
}
83
84
/**
85
 * Extend the database functionality. It calls the respective file's init
86
 * to add the implementations in that file to $smcFunc array.
87
 *
88
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
89
 */
90 View Code Duplication
function db_extend ($type = 'extra')
91
{
92
	global $sourcedir, $db_type;
93
94
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php');
95
	$initFunc = 'db_' . $type . '_init';
96
	$initFunc();
97
}
98
99
/**
100
 * Fix the database prefix if necessary.
101
 * Does nothing on PostgreSQL
102
 *
103
 * @param string $db_prefix The database prefix
104
 * @param string $db_name The database name
105
 */
106
function db_fix_prefix (&$db_prefix, $db_name)
107
{
108
	return;
109
}
110
111
/**
112
 * Callback for preg_replace_callback on the query.
113
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board'), with
114
 * their current values from $user_info.
115
 * In addition, it performs checks and sanitization on the values sent to the database.
116
 *
117
 * @param array $matches The matches from preg_replace_callback
118
 * @return string The appropriate string depending on $matches[1]
119
 */
120
function smf_db_replacement__callback($matches)
121
{
122
	global $db_callback, $user_info, $db_prefix, $smcFunc;
123
124
	list ($values, $connection) = $db_callback;
125
126
	if ($matches[1] === 'db_prefix')
127
		return $db_prefix;
128
129
	if ($matches[1] === 'query_see_board')
130
		return $user_info['query_see_board'];
131
132
	if ($matches[1] === 'query_wanna_see_board')
133
		return $user_info['query_wanna_see_board'];
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
		case 'date':
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]);
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
		case 'time':
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]);
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
		case 'float':
213
			if (!is_numeric($replacement))
214
				smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
215
			return (string) (float) $replacement;
216
		break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
217
218
		case 'identifier':
219
			return '"' . strtr($replacement, array('`' => '', '.' => '')) . '"';
220
		break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
221
222
		case 'raw':
223
			return $replacement;
224
		break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
225
226
		case 'inet':
227
			if ($replacement == 'null' || $replacement == '')
228
				return 'null';
229
			if (inet_pton($replacement) === false)
230
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
231
			return sprintf('\'%1$s\'::inet', pg_escape_string($replacement));
232
233
		case 'array_inet':
234
			if (is_array($replacement))
235
			{
236
				if (empty($replacement))
237
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
238
239
				foreach ($replacement as $key => $value)
240
				{
241
					if ($replacement == 'null' || $replacement == '')
242
						$replacement[$key] = 'null';
243
					if (!isValidIP($value))
244
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
245
					$replacement[$key] =  sprintf('\'%1$s\'::inet', pg_escape_string($value));
246
				}
247
248
				return implode(', ', $replacement);
249
			}
250
			else
251
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
252
		break;
253
254
		default:
255
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
256
		break;
257
	}
258
}
259
260
/**
261
 * Just like the db_query, escape and quote a string, but not executing the query.
262
 *
263
 * @param string $db_string The database string
264
 * @param array $db_values An array of values to be injected into the string
265
 * @param resource $connection = null The connection to use (null to use $db_connection)
266
 * @return string The string with the values inserted
267
 */
268 View Code Duplication
function smf_db_quote($db_string, $db_values, $connection = null)
269
{
270
	global $db_callback, $db_connection;
271
272
	// Only bother if there's something to replace.
273
	if (strpos($db_string, '{') !== false)
274
	{
275
		// This is needed by the callback function.
276
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
277
278
		// Do the quoting and escaping
279
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
280
281
		// Clear this global variable.
282
		$db_callback = array();
283
	}
284
285
	return $db_string;
286
}
287
288
/**
289
 * Do a query.  Takes care of errors too.
290
 * Special queries may need additional replacements to be appropriate
291
 * for PostgreSQL.
292
 *
293
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
294
 * @param string $db_string The database string
295
 * @param array $db_values = array() The values to be inserted into the string
296
 * @param resource $connection = null The connection to use (null to use $db_connection)
297
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
298
 */
299
function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null)
300
{
301
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
302
	global $db_callback, $db_last_result, $db_replace_result, $modSettings;
303
304
	// Decide which connection to use.
305
	$connection = $connection === null ? $db_connection : $connection;
306
307
	// Special queries that need processing.
308
	$replacements = array(
309
		'consolidate_spider_stats' => array(
310
			'~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))',
311
		),
312
		'attach_download_increase' => array(
313
			'~LOW_PRIORITY~' => '',
314
		),
315
		'boardindex_fetch_boards' => array(
316
			'~COALESCE\(lb.id_msg, 0\) >= b.id_msg_updated~' => 'CASE WHEN COALESCE(lb.id_msg, 0) >= b.id_msg_updated THEN 1 ELSE 0 END',
317
		),
318
		'get_random_number' => array(
319
			'~RAND~' => 'RANDOM',
320
		),
321
		'insert_log_search_topics' => array(
322
			'~NOT RLIKE~' => '!~',
323
		),
324
		'insert_log_search_results_no_index' => array(
325
			'~NOT RLIKE~' => '!~',
326
		),
327
		'insert_log_search_results_subject' => array(
328
			'~NOT RLIKE~' => '!~',
329
		),
330
		'pm_conversation_list' => array(
331
			'~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']) : ''),
332
		),
333
		'profile_board_stats' => array(
334
			'~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)',
335
		),
336
	);
337
338 View Code Duplication
	if (isset($replacements[$identifier]))
339
		$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string);
340
341
	// Limits need to be a little different.
342
	$db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string);
343
344
	if (trim($db_string) == '')
345
		return false;
346
347
	// Comments that are allowed in a query are preg_removed.
348
	static $allowed_comments_from = array(
349
		'~\s+~s',
350
		'~/\*!40001 SQL_NO_CACHE \*/~',
351
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
352
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
353
	);
354
	static $allowed_comments_to = array(
355
		' ',
356
		'',
357
		'',
358
		'',
359
	);
360
361
	// One more query....
362
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
363
	$db_replace_result = 0;
364
365 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
366
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
367
368 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
369
	{
370
		// Pass some values to the global space for use in the callback function.
371
		$db_callback = array($db_values, $connection);
372
373
		// Inject the values passed to this function.
374
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
375
376
		// This shouldn't be residing in global space any longer.
377
		$db_callback = array();
378
	}
379
380
	// Debugging.
381 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...
382
	{
383
		// Get the file and line number this function was called.
384
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
385
386
		// Initialize $db_cache if not already initialized.
387
		if (!isset($db_cache))
388
			$db_cache = array();
389
390
		if (!empty($_SESSION['debug_redirect']))
391
		{
392
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
393
			$db_count = count($db_cache) + 1;
394
			$_SESSION['debug_redirect'] = array();
395
		}
396
397
		$st = microtime();
398
		// Don't overload it.
399
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
400
		$db_cache[$db_count]['f'] = $file;
401
		$db_cache[$db_count]['l'] = $line;
402
		$db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start));
403
	}
404
405
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
406 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
407
	{
408
		$clean = '';
409
		$old_pos = 0;
410
		$pos = -1;
411
		while (true)
412
		{
413
			$pos = strpos($db_string, '\'', $pos + 1);
414
			if ($pos === false)
415
				break;
416
			$clean .= substr($db_string, $old_pos, $pos - $old_pos);
417
418
			while (true)
419
			{
420
				$pos1 = strpos($db_string, '\'', $pos + 1);
421
				$pos2 = strpos($db_string, '\\', $pos + 1);
422
				if ($pos1 === false)
423
					break;
424
				elseif ($pos2 === false || $pos2 > $pos1)
425
				{
426
					$pos = $pos1;
427
					break;
428
				}
429
430
				$pos = $pos2 + 1;
431
			}
432
			$clean .= ' %s ';
433
434
			$old_pos = $pos + 1;
435
		}
436
		$clean .= substr($db_string, $old_pos);
437
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
438
439
		// Comments?  We don't use comments in our queries, we leave 'em outside!
440
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
441
			$fail = true;
442
		// Trying to change passwords, slow us down, or something?
443
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
444
			$fail = true;
445
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
446
			$fail = true;
447
448
		if (!empty($fail) && function_exists('log_error'))
449
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
450
	}
451
452
	$db_last_result = @pg_query($connection, $db_string);
453
454 View Code Duplication
	if ($db_last_result === false && empty($db_values['db_error_skip']))
455
		$db_last_result = smf_db_error($db_string, $connection);
456
457
	// Debugging.
458 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...
459
		$db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
460
461
	return $db_last_result;
462
}
463
464
/**
465
 * affected_rows
466
 * @param resource $connection
467
 */
468
function smf_db_affected_rows($result = null)
469
{
470
	global $db_last_result, $db_replace_result;
471
472
	if ($db_replace_result)
473
		return $db_replace_result;
474
	elseif ($result === null && !$db_last_result)
475
		return 0;
476
477
	return pg_affected_rows($result === null ? $db_last_result : $result);
478
}
479
480
/**
481
 * Gets the ID of the most recently inserted row.
482
 *
483
 * @param string $table The table (only used for Postgres)
484
 * @param string $field = null The specific field (not used here)
485
 * @param resource $connection = null The connection (if null, $db_connection is used)
486
 * @return int The ID of the most recently inserted row
487
 */
488
function smf_db_insert_id($table, $field = null, $connection = null)
489
{
490
	global $db_connection, $smcFunc, $db_prefix;
491
492
	$table = str_replace('{db_prefix}', $db_prefix, $table);
493
494
	if ($connection === false)
495
		$connection = $db_connection;
496
497
	// Try get the last ID for the auto increment field.
498
	$request = $smcFunc['db_query']('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID',
499
		array(
500
		)
501
	);
502
	if (!$request)
503
		return false;
504
	list ($lastID) = $smcFunc['db_fetch_row']($request);
505
	$smcFunc['db_free_result']($request);
506
507
	return $lastID;
508
}
509
510
/**
511
 * Do a transaction.
512
 *
513
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
514
 * @param resource $connection The connection to use (if null, $db_connection is used)
515
 * @return bool True if successful, false otherwise
516
 */
517 View Code Duplication
function smf_db_transaction($type = 'commit', $connection = null)
518
{
519
	global $db_connection;
520
521
	// Decide which connection to use
522
	$connection = $connection === null ? $db_connection : $connection;
523
524
	if ($type == 'begin')
525
		return @pg_query($connection, 'BEGIN');
526
	elseif ($type == 'rollback')
527
		return @pg_query($connection, 'ROLLBACK');
528
	elseif ($type == 'commit')
529
		return @pg_query($connection, 'COMMIT');
530
531
	return false;
532
}
533
534
/**
535
 * Database error!
536
 * Backtrace, log, try to fix.
537
 *
538
 * @param string $db_string The DB string
539
 * @param resource $connection The connection to use (if null, $db_connection is used)
540
 */
541
function smf_db_error($db_string, $connection = null)
542
{
543
	global $txt, $context, $modSettings;
544
	global $db_connection;
545
	global $db_show_debug;
546
547
	// We'll try recovering the file and line number the original db query was called from.
548
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
549
550
	// Decide which connection to use
551
	$connection = $connection === null ? $db_connection : $connection;
552
553
	// This is the error message...
554
	$query_error = @pg_last_error($connection);
555
556
	// Log the error.
557
	if (function_exists('log_error'))
558
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" .$db_string : ''), 'database', $file, $line);
559
560
	// Nothing's defined yet... just die with it.
561
	if (empty($context) || empty($txt))
562
		die($query_error);
563
564
	// Show an error message, if possible.
565
	$context['error_title'] = $txt['database_error'];
566
	if (allowedTo('admin_forum'))
567
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
568
	else
569
		$context['error_message'] = $txt['try_again'];
570
571
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
572
	{
573
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
574
	}
575
576
	// It's already been logged... don't log it again.
577
	fatal_error($context['error_message'], false);
578
}
579
580
/**
581
 * A PostgreSQL specific function for tracking the current row...
582
 *
583
 * @param resource $request A PostgreSQL result resource
584
 * @param int $counter The row number in the result to fetch (false to fetch the next one)
585
 * @return array The contents of the row that was fetched
586
 */
587 View Code Duplication
function smf_db_fetch_row($request, $counter = false)
588
{
589
	global $db_row_count;
590
591
	if ($counter !== false)
592
		return pg_fetch_row($request, $counter);
593
594
	// Reset the row counter...
595
	if (!isset($db_row_count[(int) $request]))
596
		$db_row_count[(int) $request] = 0;
597
598
	// Return the right row.
599
	return @pg_fetch_row($request, $db_row_count[(int) $request]++);
600
}
601
602
/**
603
 * Get an associative array
604
 *
605
 * @param resource $request A PostgreSQL result resource
606
 * @param int $counter The row to get. If false, returns the next row.
607
 * @return array An associative array of row contents
608
 */
609 View Code Duplication
function smf_db_fetch_assoc($request, $counter = false)
610
{
611
	global $db_row_count;
612
613
	if ($counter !== false)
614
		return pg_fetch_assoc($request, $counter);
615
616
	// Reset the row counter...
617
	if (!isset($db_row_count[(int) $request]))
618
		$db_row_count[(int) $request] = 0;
619
620
	// Return the right row.
621
	return @pg_fetch_assoc($request, $db_row_count[(int) $request]++);
622
}
623
624
/**
625
 * Reset the pointer...
626
 *
627
 * @param resource $request A PostgreSQL result resource
628
 * @param int $counter The counter
629
 * @return bool Always returns true
630
 */
631
function smf_db_data_seek($request, $counter)
632
{
633
	global $db_row_count;
634
635
	$db_row_count[(int) $request] = $counter;
636
637
	return true;
638
}
639
640
/**
641
 * Unescape an escaped string!
642
 *
643
 * @param string $string The string to unescape
644
 * @return string The unescaped string
645
 */
646
function smf_db_unescape_string($string)
647
{
648
	return strtr($string, array('\'\'' => '\''));
649
}
650
651
/**
652
 * Inserts data into a table
653
 *
654
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
655
 * @param string $table The table we're inserting the data into
656
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
657
 * @param array $data The data to insert
658
 * @param array $keys The keys for the table
659
 * @param bool $disable_trans Whether to disable transactions
660
 * @param resource $connection The connection to use (if null, $db_connection is used)
661
 */
662
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null)
663
{
664
	global $db_in_transact, $smcFunc, $db_connection, $db_prefix;
665
666
	$connection = $connection === null ? $db_connection : $connection;
667
668
	$replace = '';
669
670
	if (empty($data))
671
		return;
672
673
	if (!is_array($data[array_rand($data)]))
674
		$data = array($data);
675
676
	// Replace the prefix holder with the actual prefix.
677
	$table = str_replace('{db_prefix}', $db_prefix, $table);
678
679
	$priv_trans = false;
680
	if ((count($data) > 1 || $method == 'replace') && !$db_in_transact && !$disable_trans)
681
	{
682
		$smcFunc['db_transaction']('begin', $connection);
683
		$priv_trans = true;
684
	}
685
686
	// PostgreSQL doesn't support replace: we implement a MySQL-compatible behavior instead
687
	if ($method == 'replace')
688
	{
689
		$key_str = '';
690
		$col_str = '';
691
		static $pg_version;
692
		static $replace_support;
693
694
		if(empty($pg_version))
695
		{
696
			db_extend();
697
			//pg 9.5 got replace support
698
			$pg_version = $smcFunc['db_get_version']();
699
			// if we got a Beta Version
700 View Code Duplication
			if (stripos($pg_version, 'beta') !== false)
701
				$pg_version = substr($pg_version, 0, stripos($pg_version, 'beta')).'.0';
702
			// or RC
703 View Code Duplication
			if (stripos($pg_version, 'rc') !== false)
704
				$pg_version = substr($pg_version, 0, stripos($pg_version, 'rc')).'.0';
705
706
			$replace_support = (version_compare($pg_version,'9.5.0','>=') ? true : false);
707
		}
708
709
		$count = 0;
710
		$where = '';
711
		$count_pk = 0;
712
713
		If($replace_support)
714
		{
715
			foreach ($columns as $columnName => $type)
716
			{
717
				//check pk fiel
718
				IF(in_array($columnName, $keys))
719
				{
720
					$key_str .= ($count_pk > 0 ? ',' : '');
721
					$key_str .= $columnName;
722
					$count_pk++;
723
				}
724
				else //normal field
725
				{
726
					$col_str .= ($count > 0 ? ',' : '');
727
					$col_str .= $columnName.' = EXCLUDED.'.$columnName;
728
					$count++;
729
				}
730
			}
731
			$replace = ' ON CONFLICT ('.$key_str.') DO UPDATE SET '.$col_str;
732
		}
733
		else
734
		{
735
			foreach ($columns as $columnName => $type)
736
			{
737
				// Are we restricting the length?
738
				if (strpos($type, 'string-') !== false)
739
					$actualType = sprintf($columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $count);
740
				else
741
					$actualType = sprintf($columnName . ' = {%1$s:%2$s}, ', $type, $count);
742
743
				// A key? That's what we were looking for.
744
				if (in_array($columnName, $keys))
745
					$where .= (empty($where) ? '' : ' AND ') . substr($actualType, 0, -2);
746
				$count++;
747
			}
748
749
			// Make it so.
750
			if (!empty($where) && !empty($data))
751
			{
752
				foreach ($data as $k => $entry)
753
				{
754
					$smcFunc['db_query']('', '
755
						DELETE FROM ' . $table .
756
						' WHERE ' . $where,
757
						$entry, $connection
758
					);
759
				}
760
			}
761
		}
762
	}
763
764
	if (!empty($data))
765
	{
766
		// Create the mold for a single row insert.
767
		$insertData = '(';
768
		foreach ($columns as $columnName => $type)
769
		{
770
			// Are we restricting the length?
771
			if (strpos($type, 'string-') !== false)
772
				$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
773
			else
774
				$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
775
		}
776
		$insertData = substr($insertData, 0, -2) . ')';
777
778
		// Create an array consisting of only the columns.
779
		$indexed_columns = array_keys($columns);
780
781
		// Here's where the variables are injected to the query.
782
		$insertRows = array();
783
		foreach ($data as $dataRow)
784
			$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
785
786
		foreach ($insertRows as $entry)
787
			// Do the insert.
788
			$smcFunc['db_query']('', '
789
				INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '")
790
				VALUES
791
					' . $entry.$replace,
792
				array(
793
					'security_override' => true,
794
					'db_error_skip' => $method == 'ignore' || $table === $db_prefix . 'log_errors',
795
				),
796
				$connection
797
			);
798
	}
799
800
	if ($priv_trans)
801
		$smcFunc['db_transaction']('commit', $connection);
802
}
803
804
/**
805
 * Dummy function really. Doesn't do anything on PostgreSQL.
806
 *
807
 * @param string $db_name The database name
808
 * @param resource $db_connection The database connection
809
 * @return true Always returns true
810
 */
811
function smf_db_select_db($db_name, $db_connection)
812
{
813
	return true;
814
}
815
816
/**
817
 * Get the current version.
818
 * @return string The client version
819
 */
820
function smf_db_version()
821
{
822
	$version = pg_version();
823
824
	return $version['client'];
825
}
826
827
/**
828
 * This function tries to work out additional error information from a back trace.
829
 *
830
 * @param string $error_message The error message
831
 * @param string $log_message The message to log
832
 * @param string|bool $error_type What type of error this is
833
 * @param string $file The file the error occurred in
834
 * @param int $line What line of $file the code which generated the error is on
835
 * @return void|array Returns an array with the file and line if $error_type is 'return'
836
 */
837 View Code Duplication
function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
838
{
839
	if (empty($log_message))
840
		$log_message = $error_message;
841
842
	foreach (debug_backtrace() as $step)
843
	{
844
		// Found it?
845
		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)
846
		{
847
			$log_message .= '<br>Function: ' . $step['function'];
848
			break;
849
		}
850
851
		if (isset($step['line']))
852
		{
853
			$file = $step['file'];
854
			$line = $step['line'];
855
		}
856
	}
857
858
	// A special case - we want the file and line numbers for debugging.
859
	if ($error_type == 'return')
860
		return array($file, $line);
861
862
	// Is always a critical error.
863
	if (function_exists('log_error'))
864
		log_error($log_message, 'critical', $file, $line);
865
866
	if (function_exists('fatal_error'))
867
	{
868
		fatal_error($error_message, $error_type);
869
870
		// Cannot continue...
871
		exit;
872
	}
873
	elseif ($error_type)
874
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
875
	else
876
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
877
}
878
879
/**
880
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
881
 *
882
 * @param string $string The string to escape
883
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
884
 * @return string The escaped string
885
 */
886 View Code Duplication
function smf_db_escape_wildcard_string($string, $translate_human_wildcards=false)
887
{
888
	$replacements = array(
889
		'%' => '\%',
890
		'_' => '\_',
891
		'\\' => '\\\\',
892
	);
893
894
	if ($translate_human_wildcards)
895
		$replacements += array(
896
			'*' => '%',
897
		);
898
899
	return strtr($string, $replacements);
900
}
901
902
?>