Completed
Pull Request — release-2.1 (#4069)
by Jeremy
08:58
created

Sources/Subs-Db-postgresql.php (1 issue)

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 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']))
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
		);
64
65
	if (!empty($db_options['persist']))
66
		$connection = @pg_pconnect('host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'' . (empty($db_options['port']) ? '' : ' port=\'' . $db_options['port'] . '\''));
67
	else
68
		$connection = @pg_connect('host=' . $db_server . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\'' . (empty($db_options['port']) ? '' : ' port=\'' . $db_options['port'] . '\''));
69
70
	// Something's wrong, show an error if its fatal (which we assume it is)
71
	if (!$connection)
72
	{
73
		if (!empty($db_options['non_fatal']))
74
		{
75
			return null;
76
		}
77
		else
78
		{
79
			display_db_error();
80
		}
81
	}
82
83
	return $connection;
84
}
85
86
/**
87
 * Extend the database functionality. It calls the respective file's init
88
 * to add the implementations in that file to $smcFunc array.
89
 *
90
 * @param string $type Indicates which additional file to load. ('extra', 'packages')
91
 */
92
function db_extend($type = 'extra')
93
{
94
	global $sourcedir, $db_type;
95
96
	require_once($sourcedir . '/Db' . strtoupper($type[0]) . substr($type, 1) . '-' . $db_type . '.php');
97
	$initFunc = 'db_' . $type . '_init';
98
	$initFunc();
99
}
100
101
/**
102
 * Fix the database prefix if necessary.
103
 * Does nothing on PostgreSQL
104
 *
105
 * @param string $db_prefix The database prefix
106
 * @param string $db_name The database name
107
 */
108
function db_fix_prefix(&$db_prefix, $db_name)
109
{
110
	return;
111
}
112
113
/**
114
 * Callback for preg_replace_callback on the query.
115
 * It allows to replace on the fly a few pre-defined strings, for convenience ('query_see_board', 'query_wanna_see_board', etc), with
116
 * their current values from $user_info.
117
 * In addition, it performs checks and sanitization on the values sent to the database.
118
 *
119
 * @param array $matches The matches from preg_replace_callback
120
 * @return string The appropriate string depending on $matches[1]
121
 */
122
function smf_db_replacement__callback($matches)
123
{
124
	global $db_callback, $user_info, $db_prefix, $smcFunc;
125
126
	list ($values, $connection) = $db_callback;
127
128
	if ($matches[1] === 'db_prefix')
129
		return $db_prefix;
130
131 View Code Duplication
	if (!empty($user_info))
132
	{
133
		foreach (array_keys($user_info) as $key)
134
			if (strpos($key, 'query_') !== false && $key === $matches[1])
135
				return $user_info[$matches[1]];
136
	}
137
138
	if ($matches[1] === 'empty')
139
		return '\'\'';
140
141
	if (!isset($matches[2]))
142
		smf_db_error_backtrace('Invalid value inserted or no type specified.', '', E_USER_ERROR, __FILE__, __LINE__);
143
144
	if ($matches[1] === 'literal')
145
		return '\'' . pg_escape_string($matches[2]) . '\'';
146
147
	if (!isset($values[$matches[2]]))
148
		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__);
149
150
	$replacement = $values[$matches[2]];
151
152
	switch ($matches[1])
153
	{
154
		case 'int':
155
			if (!is_numeric($replacement) || (string) $replacement !== (string) (int) $replacement)
156
				smf_db_error_backtrace('Wrong value type sent to the database. Integer expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
157
			return (string) (int) $replacement;
158
		break;
159
160
		case 'string':
161
		case 'text':
162
			return sprintf('\'%1$s\'', pg_escape_string($replacement));
163
		break;
164
165
		case 'array_int':
166
			if (is_array($replacement))
167
			{
168
				if (empty($replacement))
169
					smf_db_error_backtrace('Database error, given array of integer values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
170
171
				foreach ($replacement as $key => $value)
172
				{
173
					if (!is_numeric($value) || (string) $value !== (string) (int) $value)
174
						smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
175
176
					$replacement[$key] = (string) (int) $value;
177
				}
178
179
				return implode(', ', $replacement);
180
			}
181
			else
182
				smf_db_error_backtrace('Wrong value type sent to the database. Array of integers expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
183
184
		break;
185
186
		case 'array_string':
187
			if (is_array($replacement))
188
			{
189
				if (empty($replacement))
190
					smf_db_error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
191
192
				foreach ($replacement as $key => $value)
193
					$replacement[$key] = sprintf('\'%1$s\'', pg_escape_string($value));
194
195
				return implode(', ', $replacement);
196
			}
197
			else
198
				smf_db_error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
199
		break;
200
201 View Code Duplication
		case 'date':
202
			if (preg_match('~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~', $replacement, $date_matches) === 1)
203
				return sprintf('\'%04d-%02d-%02d\'', $date_matches[1], $date_matches[2], $date_matches[3]).'::date';
204
			else
205
				smf_db_error_backtrace('Wrong value type sent to the database. Date expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
206
		break;
207
208 View Code Duplication
		case 'time':
209
			if (preg_match('~^([0-1]?\d|2[0-3]):([0-5]\d):([0-5]\d)$~', $replacement, $time_matches) === 1)
210
				return sprintf('\'%02d:%02d:%02d\'', $time_matches[1], $time_matches[2], $time_matches[3]).'::time';
211
			else
212
				smf_db_error_backtrace('Wrong value type sent to the database. Time expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
213
		break;
214
215 View Code Duplication
		case 'datetime':
216
			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)
217
				return 'to_timestamp('.
218
					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]).
219
					',\'YYYY-MM-DD HH24:MI:SS\')';
220
			else
221
				smf_db_error_backtrace('Wrong value type sent to the database. Datetime expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
222
		break;
223
224
		case 'float':
225
			if (!is_numeric($replacement))
226
				smf_db_error_backtrace('Wrong value type sent to the database. Floating point number expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
227
			return (string) (float) $replacement;
228
		break;
229
230
		case 'identifier':
231
			return '"' . strtr($replacement, array('`' => '', '.' => '"."')) . '"';
232
		break;
233
234
		case 'raw':
235
			return $replacement;
236
		break;
237
238
		case 'inet':
239
			if ($replacement == 'null' || $replacement == '')
240
				return 'null';
241
			if (inet_pton($replacement) === false)
242
				smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
243
			return sprintf('\'%1$s\'::inet', pg_escape_string($replacement));
244
245
		case 'array_inet':
246
			if (is_array($replacement))
247
			{
248
				if (empty($replacement))
249
					smf_db_error_backtrace('Database error, given array of IPv4 or IPv6 values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
250
251
				foreach ($replacement as $key => $value)
252
				{
253
					if ($replacement == 'null' || $replacement == '')
254
						$replacement[$key] = 'null';
255
					if (!isValidIP($value))
256
						smf_db_error_backtrace('Wrong value type sent to the database. IPv4 or IPv6 expected.(' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
257
					$replacement[$key] = sprintf('\'%1$s\'::inet', pg_escape_string($value));
258
				}
259
260
				return implode(', ', $replacement);
261
			}
262
			else
263
				smf_db_error_backtrace('Wrong value type sent to the database. Array of IPv4 or IPv6 expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__);
264
		break;
265
266
		default:
267
			smf_db_error_backtrace('Undefined type used in the database query. (' . $matches[1] . ':' . $matches[2] . ')', '', false, __FILE__, __LINE__);
268
		break;
269
	}
270
}
271
272
/**
273
 * Just like the db_query, escape and quote a string, but not executing the query.
274
 *
275
 * @param string $db_string The database string
276
 * @param array $db_values An array of values to be injected into the string
277
 * @param resource $connection = null The connection to use (null to use $db_connection)
278
 * @return string The string with the values inserted
279
 */
280 View Code Duplication
function smf_db_quote($db_string, $db_values, $connection = null)
281
{
282
	global $db_callback, $db_connection;
283
284
	// Only bother if there's something to replace.
285
	if (strpos($db_string, '{') !== false)
286
	{
287
		// This is needed by the callback function.
288
		$db_callback = array($db_values, $connection === null ? $db_connection : $connection);
289
290
		// Do the quoting and escaping
291
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
292
293
		// Clear this global variable.
294
		$db_callback = array();
295
	}
296
297
	return $db_string;
298
}
299
300
/**
301
 * Do a query.  Takes care of errors too.
302
 * Special queries may need additional replacements to be appropriate
303
 * for PostgreSQL.
304
 *
305
 * @param string $identifier An identifier. Only used in Postgres when we need to do things differently...
306
 * @param string $db_string The database string
307
 * @param array $db_values = array() The values to be inserted into the string
308
 * @param resource $connection = null The connection to use (null to use $db_connection)
309
 * @return resource|bool Returns a MySQL result resource (for SELECT queries), true (for UPDATE queries) or false if the query failed
310
 */
311
function smf_db_query($identifier, $db_string, $db_values = array(), $connection = null)
312
{
313
	global $db_cache, $db_count, $db_connection, $db_show_debug, $time_start;
314
	global $db_callback, $db_last_result, $db_replace_result, $modSettings;
315
316
	// Decide which connection to use.
317
	$connection = $connection === null ? $db_connection : $connection;
318
319
	// Special queries that need processing.
320
	$replacements = array(
321
		'consolidate_spider_stats' => array(
322
			'~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))',
323
		),
324
		'attach_download_increase' => array(
325
			'~LOW_PRIORITY~' => '',
326
		),
327
		'get_random_number' => array(
328
			'~RAND~' => 'RANDOM',
329
		),
330
		'insert_log_search_topics' => array(
331
			'~NOT RLIKE~' => '!~',
332
		),
333
		'insert_log_search_results_no_index' => array(
334
			'~NOT RLIKE~' => '!~',
335
		),
336
		'insert_log_search_results_subject' => array(
337
			'~NOT RLIKE~' => '!~',
338
		),
339
		'pm_conversation_list' => array(
340
			'~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']) : ''),
341
		),
342
		'profile_board_stats' => array(
343
			'~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)',
344
		),
345
	);
346
347 View Code Duplication
	if (isset($replacements[$identifier]))
348
		$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string);
349
350
	// Limits need to be a little different.
351
	$db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string);
352
353
	if (trim($db_string) == '')
354
		return false;
355
356
	// Comments that are allowed in a query are preg_removed.
357
	static $allowed_comments_from = array(
358
		'~\s+~s',
359
		'~/\*!40001 SQL_NO_CACHE \*/~',
360
		'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~',
361
		'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~',
362
	);
363
	static $allowed_comments_to = array(
364
		' ',
365
		'',
366
		'',
367
		'',
368
	);
369
370
	// One more query....
371
	$db_count = !isset($db_count) ? 1 : $db_count + 1;
372
	$db_replace_result = 0;
373
374 View Code Duplication
	if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override']))
375
		smf_db_error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__);
376
377 View Code Duplication
	if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false))
378
	{
379
		// Pass some values to the global space for use in the callback function.
380
		$db_callback = array($db_values, $connection);
381
382
		// Inject the values passed to this function.
383
		$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', 'smf_db_replacement__callback', $db_string);
384
385
		// This shouldn't be residing in global space any longer.
386
		$db_callback = array();
387
	}
388
389
	// Debugging.
390 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
391
	{
392
		// Get the file and line number this function was called.
393
		list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
394
395
		// Initialize $db_cache if not already initialized.
396
		if (!isset($db_cache))
397
			$db_cache = array();
398
399
		if (!empty($_SESSION['debug_redirect']))
400
		{
401
			$db_cache = array_merge($_SESSION['debug_redirect'], $db_cache);
402
			$db_count = count($db_cache) + 1;
403
			$_SESSION['debug_redirect'] = array();
404
		}
405
406
		$st = microtime();
407
		// Don't overload it.
408
		$db_cache[$db_count]['q'] = $db_count < 50 ? $db_string : '...';
409
		$db_cache[$db_count]['f'] = $file;
410
		$db_cache[$db_count]['l'] = $line;
411
		$db_cache[$db_count]['s'] = array_sum(explode(' ', $st)) - array_sum(explode(' ', $time_start));
412
	}
413
414
	// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over.
415 View Code Duplication
	if (empty($modSettings['disableQueryCheck']))
416
	{
417
		$clean = '';
418
		$old_pos = 0;
419
		$pos = -1;
420
		while (true)
421
		{
422
			$pos = strpos($db_string, '\'', $pos + 1);
423
			if ($pos === false)
424
				break;
425
			$clean .= substr($db_string, $old_pos, $pos - $old_pos);
426
427
			while (true)
428
			{
429
				$pos1 = strpos($db_string, '\'', $pos + 1);
430
				$pos2 = strpos($db_string, '\\', $pos + 1);
431
				if ($pos1 === false)
432
					break;
433
				elseif ($pos2 === false || $pos2 > $pos1)
434
				{
435
					$pos = $pos1;
436
					break;
437
				}
438
439
				$pos = $pos2 + 1;
440
			}
441
			$clean .= ' %s ';
442
443
			$old_pos = $pos + 1;
444
		}
445
		$clean .= substr($db_string, $old_pos);
446
		$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean)));
447
448
		// Comments?  We don't use comments in our queries, we leave 'em outside!
449
		if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false)
450
			$fail = true;
451
		// Trying to change passwords, slow us down, or something?
452
		elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0)
453
			$fail = true;
454
		elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
455
			$fail = true;
456
457
		if (!empty($fail) && function_exists('log_error'))
458
			smf_db_error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__);
459
	}
460
461
	$db_last_result = @pg_query($connection, $db_string);
462
463 View Code Duplication
	if ($db_last_result === false && empty($db_values['db_error_skip']))
464
		$db_last_result = smf_db_error($db_string, $connection);
465
466
	// Debugging.
467 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
468
		$db_cache[$db_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
469
470
	return $db_last_result;
471
}
472
473
/**
474
 * affected_rows
475
 * @param resource $connection
476
 */
477
function smf_db_affected_rows($result = null)
478
{
479
	global $db_last_result, $db_replace_result;
480
481
	if ($db_replace_result)
482
		return $db_replace_result;
483
	elseif ($result === null && !$db_last_result)
484
		return 0;
485
486
	return pg_affected_rows($result === null ? $db_last_result : $result);
487
}
488
489
/**
490
 * Gets the ID of the most recently inserted row.
491
 *
492
 * @param string $table The table (only used for Postgres)
493
 * @param string $field = null The specific field (not used here)
494
 * @param resource $connection = null The connection (if null, $db_connection is used)
495
 * @return int The ID of the most recently inserted row
496
 */
497
function smf_db_insert_id($table, $field = null, $connection = null)
498
{
499
	global $smcFunc, $db_prefix;
500
501
	$table = str_replace('{db_prefix}', $db_prefix, $table);
502
503
	// Try get the last ID for the auto increment field.
504
	$request = $smcFunc['db_query']('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID',
505
		array(
506
		)
507
	);
508
	if (!$request)
509
		return false;
510
	list ($lastID) = $smcFunc['db_fetch_row']($request);
511
	$smcFunc['db_free_result']($request);
512
513
	return $lastID;
514
}
515
516
/**
517
 * Do a transaction.
518
 *
519
 * @param string $type The step to perform (i.e. 'begin', 'commit', 'rollback')
520
 * @param resource $connection The connection to use (if null, $db_connection is used)
521
 * @return bool True if successful, false otherwise
522
 */
523 View Code Duplication
function smf_db_transaction($type = 'commit', $connection = null)
524
{
525
	global $db_connection;
526
527
	// Decide which connection to use
528
	$connection = $connection === null ? $db_connection : $connection;
529
530
	if ($type == 'begin')
531
		return @pg_query($connection, 'BEGIN');
532
	elseif ($type == 'rollback')
533
		return @pg_query($connection, 'ROLLBACK');
534
	elseif ($type == 'commit')
535
		return @pg_query($connection, 'COMMIT');
536
537
	return false;
538
}
539
540
/**
541
 * Database error!
542
 * Backtrace, log, try to fix.
543
 *
544
 * @param string $db_string The DB string
545
 * @param resource $connection The connection to use (if null, $db_connection is used)
546
 */
547
function smf_db_error($db_string, $connection = null)
548
{
549
	global $txt, $context, $modSettings;
550
	global $db_connection;
551
	global $db_show_debug;
552
553
	// We'll try recovering the file and line number the original db query was called from.
554
	list ($file, $line) = smf_db_error_backtrace('', '', 'return', __FILE__, __LINE__);
555
556
	// Decide which connection to use
557
	$connection = $connection === null ? $db_connection : $connection;
558
559
	// This is the error message...
560
	$query_error = @pg_last_error($connection);
561
562
	// Log the error.
563
	if (function_exists('log_error'))
564
		log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" . $db_string : ''), 'database', $file, $line);
565
566
	// Nothing's defined yet... just die with it.
567
	if (empty($context) || empty($txt))
568
		die($query_error);
569
570
	// Show an error message, if possible.
571
	$context['error_title'] = $txt['database_error'];
572
	if (allowedTo('admin_forum'))
573
		$context['error_message'] = nl2br($query_error) . '<br>' . $txt['file'] . ': ' . $file . '<br>' . $txt['line'] . ': ' . $line;
574
	else
575
		$context['error_message'] = $txt['try_again'];
576
577
	if (allowedTo('admin_forum') && isset($db_show_debug) && $db_show_debug === true)
578
	{
579
		$context['error_message'] .= '<br><br>' . nl2br($db_string);
580
	}
581
582
	// It's already been logged... don't log it again.
583
	fatal_error($context['error_message'], false);
584
}
585
586
/**
587
 * A PostgreSQL specific function for tracking the current row...
588
 *
589
 * @param resource $request A PostgreSQL result resource
590
 * @param int $counter The row number in the result to fetch (false to fetch the next one)
591
 * @return array The contents of the row that was fetched
592
 */
593 View Code Duplication
function smf_db_fetch_row($request, $counter = false)
594
{
595
	global $db_row_count;
596
597
	if ($counter !== false)
598
		return pg_fetch_row($request, $counter);
599
600
	// Reset the row counter...
601
	if (!isset($db_row_count[(int) $request]))
602
		$db_row_count[(int) $request] = 0;
603
604
	// Return the right row.
605
	return @pg_fetch_row($request, $db_row_count[(int) $request]++);
606
}
607
608
/**
609
 * Get an associative array
610
 *
611
 * @param resource $request A PostgreSQL result resource
612
 * @param int $counter The row to get. If false, returns the next row.
613
 * @return array An associative array of row contents
614
 */
615 View Code Duplication
function smf_db_fetch_assoc($request, $counter = false)
616
{
617
	global $db_row_count;
618
619
	if ($counter !== false)
620
		return pg_fetch_assoc($request, $counter);
621
622
	// Reset the row counter...
623
	if (!isset($db_row_count[(int) $request]))
624
		$db_row_count[(int) $request] = 0;
625
626
	// Return the right row.
627
	return @pg_fetch_assoc($request, $db_row_count[(int) $request]++);
628
}
629
630
/**
631
 * Reset the pointer...
632
 *
633
 * @param resource $request A PostgreSQL result resource
634
 * @param int $counter The counter
635
 * @return bool Always returns true
636
 */
637
function smf_db_data_seek($request, $counter)
638
{
639
	global $db_row_count;
640
641
	$db_row_count[(int) $request] = $counter;
642
643
	return true;
644
}
645
646
/**
647
 * Unescape an escaped string!
648
 *
649
 * @param string $string The string to unescape
650
 * @return string The unescaped string
651
 */
652
function smf_db_unescape_string($string)
653
{
654
	return strtr($string, array('\'\'' => '\''));
655
}
656
657
/**
658
 * Inserts data into a table
659
 *
660
 * @param string $method The insert method - can be 'replace', 'ignore' or 'insert'
661
 * @param string $table The table we're inserting the data into
662
 * @param array $columns An array of the columns we're inserting the data into. Should contain 'column' => 'datatype' pairs
663
 * @param array $data The data to insert
664
 * @param array $keys The keys for the table
665
 * @param int $returnmode 0 = nothing(default), 1 = last row id, 2 = all rows id as array; every mode runs only with method = ''
666
 * @param resource $connection The connection to use (if null, $db_connection is used)
667
 * @return mixed value of the first key, behavior based on returnmode. null if no data.
668
 */
669
function smf_db_insert($method = 'replace', $table, $columns, $data, $keys, $returnmode = 0, $connection = null)
0 ignored issues
show
The function smf_db_insert() has been defined more than once; this definition is ignored, only the first definition in Sources/Subs-Db-mysql.php (L776-843) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
670
{
671
	global $smcFunc, $db_connection, $db_prefix;
672
673
	$connection = $connection === null ? $db_connection : $connection;
674
675
	$replace = '';
676
677
	if (empty($data))
678
		return;
679
680
	if (!is_array($data[array_rand($data)]))
681
		$data = array($data);
682
683
	// Replace the prefix holder with the actual prefix.
684
	$table = str_replace('{db_prefix}', $db_prefix, $table);
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
	$returning = '';
765
	$with_returning = false;
766
	// lets build the returning string, mysql allow only in normal mode
767
	if(!empty($keys) && (count($keys) > 0) && $method == '' && $returnmode > 0)
768
	{
769
		// we only take the first key
770
		$returning = ' RETURNING '.$keys[0];
771
		$with_returning = true;
772
	}
773
774
	if (!empty($data))
775
	{
776
		// Create the mold for a single row insert.
777
		$insertData = '(';
778
		foreach ($columns as $columnName => $type)
779
		{
780
			// Are we restricting the length?
781
			if (strpos($type, 'string-') !== false)
782
				$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName);
783
			else
784
				$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName);
785
		}
786
		$insertData = substr($insertData, 0, -2) . ')';
787
788
		// Create an array consisting of only the columns.
789
		$indexed_columns = array_keys($columns);
790
791
		// Here's where the variables are injected to the query.
792
		$insertRows = array();
793
		foreach ($data as $dataRow)
794
			$insertRows[] = smf_db_quote($insertData, array_combine($indexed_columns, $dataRow), $connection);
795
796
		// Do the insert.
797
		$request = $smcFunc['db_query']('', '
798
			INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '")
799
			VALUES
800
				' . implode(',
801
				', $insertRows).$replace.$returning,
802
			array(
803
				'security_override' => true,
804
				'db_error_skip' => $method == 'ignore' || $table === $db_prefix . 'log_errors',
805
			),
806
			$connection
807
		);
808
809
		if ($with_returning && $request !== false)
810
		{
811
			if ($returnmode === 2)
812
				$return_var = array();
813
814
			while(($row = $smcFunc['db_fetch_row']($request)) && $with_returning)
815
			{
816
				if (is_numeric($row[0])) // try to emulate mysql limitation
817
				{
818
					if ($returnmode === 1)
819
						$return_var = $row[0];
820
					elseif ($returnmode === 2)
821
						$return_var[] = $row[0];
822
				}
823
				else
824
				{
825
					$with_returning = false;
826
					trigger_error('trying to returning ID Field which is not a Int field', E_USER_ERROR);
827
				}
828
			}
829
		}
830
	}
831
832
	if ($with_returning && !empty($return_var))
833
		return $return_var;
834
}
835
836
/**
837
 * Dummy function really. Doesn't do anything on PostgreSQL.
838
 *
839
 * @param string $db_name The database name
840
 * @param resource $db_connection The database connection
841
 * @return true Always returns true
842
 */
843
function smf_db_select_db($db_name, $db_connection)
844
{
845
	return true;
846
}
847
848
/**
849
 * Get the current version.
850
 * @return string The client version
851
 */
852
function smf_db_version()
853
{
854
	$version = pg_version();
855
856
	return $version['client'];
857
}
858
859
/**
860
 * This function tries to work out additional error information from a back trace.
861
 *
862
 * @param string $error_message The error message
863
 * @param string $log_message The message to log
864
 * @param string|bool $error_type What type of error this is
865
 * @param string $file The file the error occurred in
866
 * @param int $line What line of $file the code which generated the error is on
867
 * @return void|array Returns an array with the file and line if $error_type is 'return'
868
 */
869 View Code Duplication
function smf_db_error_backtrace($error_message, $log_message = '', $error_type = false, $file = null, $line = null)
870
{
871
	if (empty($log_message))
872
		$log_message = $error_message;
873
874
	foreach (debug_backtrace() as $step)
875
	{
876
		// Found it?
877
		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)
878
		{
879
			$log_message .= '<br>Function: ' . $step['function'];
880
			break;
881
		}
882
883
		if (isset($step['line']))
884
		{
885
			$file = $step['file'];
886
			$line = $step['line'];
887
		}
888
	}
889
890
	// A special case - we want the file and line numbers for debugging.
891
	if ($error_type == 'return')
892
		return array($file, $line);
893
894
	// Is always a critical error.
895
	if (function_exists('log_error'))
896
		log_error($log_message, 'critical', $file, $line);
897
898
	if (function_exists('fatal_error'))
899
	{
900
		fatal_error($error_message, $error_type);
901
902
		// Cannot continue...
903
		exit;
904
	}
905
	elseif ($error_type)
906
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''), $error_type);
907
	else
908
		trigger_error($error_message . ($line !== null ? '<em>(' . basename($file) . '-' . $line . ')</em>' : ''));
909
}
910
911
/**
912
 * Escape the LIKE wildcards so that they match the character and not the wildcard.
913
 *
914
 * @param string $string The string to escape
915
 * @param bool $translate_human_wildcards If true, turns human readable wildcards into SQL wildcards.
916
 * @return string The escaped string
917
 */
918 View Code Duplication
function smf_db_escape_wildcard_string($string, $translate_human_wildcards = false)
919
{
920
	$replacements = array(
921
		'%' => '\%',
922
		'_' => '\_',
923
		'\\' => '\\\\',
924
	);
925
926
	if ($translate_human_wildcards)
927
		$replacements += array(
928
			'*' => '%',
929
		);
930
931
	return strtr($string, $replacements);
932
}
933
934
?>