smf_db_get_vendor()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
c 0
b 0
f 0
nop 0
dl 0
loc 24
rs 9.4555
nc 5
1
<?php
2
3
/**
4
 * This file contains rarely used extended database functionality.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2022 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.0
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Add the functions implemented in this file to the $smcFunc array.
21
 */
22
function db_extra_init()
23
{
24
	global $smcFunc;
25
26
	if (!isset($smcFunc['db_backup_table']) || $smcFunc['db_backup_table'] != 'smf_db_backup_table')
27
		$smcFunc += array(
28
			'db_backup_table' => 'smf_db_backup_table',
29
			'db_optimize_table' => 'smf_db_optimize_table',
30
			'db_table_sql' => 'smf_db_table_sql',
31
			'db_list_tables' => 'smf_db_list_tables',
32
			'db_get_version' => 'smf_db_get_version',
33
			'db_get_vendor' => 'smf_db_get_vendor',
34
			'db_allow_persistent' => 'smf_db_allow_persistent',
35
		);
36
}
37
38
/**
39
 * Backup $table to $backup_table.
40
 *
41
 * @param string $table The name of the table to backup
42
 * @param string $backup_table The name of the backup table for this table
43
 * @return resource -the request handle to the table creation query
44
 */
45
function smf_db_backup_table($table, $backup_table)
46
{
47
	global $smcFunc, $db_prefix;
48
49
	$table = str_replace('{db_prefix}', $db_prefix, $table);
50
51
	// First, get rid of the old table.
52
	$smcFunc['db_query']('', '
53
		DROP TABLE IF EXISTS {raw:backup_table}',
54
		array(
55
			'backup_table' => $backup_table,
56
		)
57
	);
58
59
	// Can we do this the quick way?
60
	$result = $smcFunc['db_query']('', '
61
		CREATE TABLE {raw:backup_table} LIKE {raw:table}',
62
		array(
63
			'backup_table' => $backup_table,
64
			'table' => $table
65
		)
66
	);
67
	// If this failed, we go old school.
68
	if ($result)
69
	{
70
		$request = $smcFunc['db_query']('', '
71
			INSERT INTO {raw:backup_table}
72
			SELECT *
73
			FROM {raw:table}',
74
			array(
75
				'backup_table' => $backup_table,
76
				'table' => $table
77
			)
78
		);
79
80
		// Old school or no school?
81
		if ($request)
82
			return $request;
83
	}
84
85
	// At this point, the quick method failed.
86
	$result = $smcFunc['db_query']('', '
87
		SHOW CREATE TABLE {raw:table}',
88
		array(
89
			'table' => $table,
90
		)
91
	);
92
	list (, $create) = $smcFunc['db_fetch_row']($result);
93
	$smcFunc['db_free_result']($result);
94
95
	$create = preg_split('/[\n\r]/', $create);
96
97
	$auto_inc = '';
98
	// Default engine type.
99
	$engine = 'MyISAM';
100
	$charset = '';
101
	$collate = '';
102
103
	foreach ($create as $k => $l)
104
	{
105
		// Get the name of the auto_increment column.
106
		if (strpos($l, 'auto_increment'))
107
			$auto_inc = trim($l);
108
109
		// For the engine type, see if we can work out what it is.
110
		if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false)
111
		{
112
			// Extract the engine type.
113
			preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match);
114
115
			if (!empty($match[1]))
116
				$engine = $match[1];
117
118
			if (!empty($match[2]))
119
				$engine = $match[2];
120
121
			if (!empty($match[5]))
122
				$charset = $match[5];
123
124
			if (!empty($match[7]))
125
				$collate = $match[7];
126
		}
127
128
		// Skip everything but keys...
129
		if (strpos($l, 'KEY') === false)
130
			unset($create[$k]);
131
	}
132
133
	if (!empty($create))
134
		$create = '(
135
			' . implode('
136
			', $create) . ')';
137
	else
138
		$create = '';
139
140
	$request = $smcFunc['db_query']('', '
141
		CREATE TABLE {raw:backup_table} {raw:create}
142
		ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . '
143
		SELECT *
144
		FROM {raw:table}',
145
		array(
146
			'backup_table' => $backup_table,
147
			'table' => $table,
148
			'create' => $create,
149
			'engine' => $engine,
150
			'charset' => empty($charset) ? '' : $charset,
151
			'collate' => empty($collate) ? '' : $collate,
152
		)
153
	);
154
155
	if ($auto_inc != '')
156
	{
157
		if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',')
158
			$auto_inc = substr($auto_inc, 0, -1);
159
160
		$smcFunc['db_query']('', '
161
			ALTER TABLE {raw:backup_table}
162
			CHANGE COLUMN {raw:column_detail} {raw:auto_inc}',
163
			array(
164
				'backup_table' => $backup_table,
165
				'column_detail' => $match[1],
166
				'auto_inc' => $auto_inc,
167
			)
168
		);
169
	}
170
171
	return $request;
172
}
173
174
/**
175
 * This function optimizes a table.
176
 *
177
 * @param string $table The table to be optimized
178
 * @return int How much space was gained
179
 */
180
function smf_db_optimize_table($table)
181
{
182
	global $smcFunc, $db_prefix;
183
184
	$table = str_replace('{db_prefix}', $db_prefix, $table);
185
186
	// Get how much overhead there is.
187
	$request = $smcFunc['db_query']('', '
188
		SHOW TABLE STATUS LIKE {string:table_name}',
189
		array(
190
			'table_name' => str_replace('_', '\_', $table),
191
		)
192
	);
193
	$row = $smcFunc['db_fetch_assoc']($request);
194
	$smcFunc['db_free_result']($request);
195
196
	$data_before = isset($row['Data_free']) ? $row['Data_free'] : 0;
197
	$request = $smcFunc['db_query']('', '
198
		OPTIMIZE TABLE `{raw:table}`',
199
		array(
200
			'table' => $table,
201
		)
202
	);
203
	if (!$request)
204
		return -1;
205
206
	// How much left?
207
	$request = $smcFunc['db_query']('', '
208
		SHOW TABLE STATUS LIKE {string:table}',
209
		array(
210
			'table' => str_replace('_', '\_', $table),
211
		)
212
	);
213
	$row = $smcFunc['db_fetch_assoc']($request);
214
	$smcFunc['db_free_result']($request);
215
216
	$total_change = isset($row['Data_free']) && $data_before > $row['Data_free'] ? $data_before / 1024 : 0;
217
218
	return $total_change;
219
}
220
221
/**
222
 * This function lists all tables in the database.
223
 * The listing could be filtered according to $filter.
224
 *
225
 * @param string|boolean $db string The database name or false to use the current DB
226
 * @param string|boolean $filter String to filter by or false to list all tables
227
 * @return array An array of table names
228
 */
229
function smf_db_list_tables($db = false, $filter = false)
230
{
231
	global $db_name, $smcFunc;
232
233
	$db = $db == false ? $db_name : $db;
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $db of type boolean|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
234
	$db = trim($db);
0 ignored issues
show
Bug introduced by
It seems like $db can also be of type true; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

234
	$db = trim(/** @scrutinizer ignore-type */ $db);
Loading history...
235
	$filter = $filter == false ? '' : ' LIKE \'' . $filter . '\'';
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $filter of type boolean|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
Bug introduced by
Are you sure $filter of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

235
	$filter = $filter == false ? '' : ' LIKE \'' . /** @scrutinizer ignore-type */ $filter . '\'';
Loading history...
236
237
	$request = $smcFunc['db_query']('', '
238
		SHOW TABLES
239
		FROM `{raw:db}`
240
		{raw:filter}',
241
		array(
242
			'db' => $db[0] == '`' ? strtr($db, array('`' => '')) : $db,
243
			'filter' => $filter,
244
		)
245
	);
246
	$tables = array();
247
	while ($row = $smcFunc['db_fetch_row']($request))
248
		$tables[] = $row[0];
249
	$smcFunc['db_free_result']($request);
250
251
	return $tables;
252
}
253
254
/**
255
 * Dumps the schema (CREATE) for a table.
256
 *
257
 * @todo why is this needed for?
258
 * @param string $tableName The name of the table
259
 * @return string The "CREATE TABLE" SQL string for this table
260
 */
261
function smf_db_table_sql($tableName)
262
{
263
	global $smcFunc, $db_prefix;
264
265
	$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
266
267
	// This will be needed...
268
	$crlf = "\r\n";
269
270
	// Drop it if it exists.
271
	$schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf;
272
273
	// Start the create table...
274
	$schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf;
275
276
	// Find all the fields.
277
	$result = $smcFunc['db_query']('', '
278
		SHOW FIELDS
279
		FROM `{raw:table}`',
280
		array(
281
			'table' => $tableName,
282
		)
283
	);
284
	while ($row = $smcFunc['db_fetch_assoc']($result))
285
	{
286
		// Make the CREATE for this column.
287
		$schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : '');
288
289
		// Add a default...?
290
		if (!empty($row['Default']) || $row['Null'] !== 'YES')
291
		{
292
			// Make a special case of auto-timestamp.
293
			if ($row['Default'] == 'CURRENT_TIMESTAMP')
294
				$schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */';
295
			// Text shouldn't have a default.
296
			elseif ($row['Default'] !== null)
297
			{
298
				// If this field is numeric the default needs no escaping.
299
				$type = strtolower($row['Type']);
300
				$isNumericColumn = strpos($type, 'int') !== false || strpos($type, 'bool') !== false || strpos($type, 'bit') !== false || strpos($type, 'float') !== false || strpos($type, 'double') !== false || strpos($type, 'decimal') !== false;
301
302
				$schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $smcFunc['db_escape_string']($row['Default']) . '\'');
303
			}
304
		}
305
306
		// And now any extra information. (such as auto_increment.)
307
		$schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf;
308
	}
309
	$smcFunc['db_free_result']($result);
310
311
	// Take off the last comma.
312
	$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
313
314
	// Find the keys.
315
	$result = $smcFunc['db_query']('', '
316
		SHOW KEYS
317
		FROM `{raw:table}`',
318
		array(
319
			'table' => $tableName,
320
		)
321
	);
322
	$indexes = array();
323
	while ($row = $smcFunc['db_fetch_assoc']($result))
324
	{
325
		// IS this a primary key, unique index, or regular index?
326
		$row['Key_name'] = $row['Key_name'] == 'PRIMARY' ? 'PRIMARY KEY' : (empty($row['Non_unique']) ? 'UNIQUE ' : ($row['Comment'] == 'FULLTEXT' || (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT') ? 'FULLTEXT ' : 'KEY ')) . '`' . $row['Key_name'] . '`';
327
328
		// Is this the first column in the index?
329
		if (empty($indexes[$row['Key_name']]))
330
			$indexes[$row['Key_name']] = array();
331
332
		// A sub part, like only indexing 15 characters of a varchar.
333
		if (!empty($row['Sub_part']))
334
			$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')';
335
		else
336
			$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`';
337
	}
338
	$smcFunc['db_free_result']($result);
339
340
	// Build the CREATEs for the keys.
341
	foreach ($indexes as $keyname => $columns)
342
	{
343
		// Ensure the columns are in proper order.
344
		ksort($columns);
345
346
		$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')';
347
	}
348
349
	// Now just get the comment and engine... (MyISAM, etc.)
350
	$result = $smcFunc['db_query']('', '
351
		SHOW TABLE STATUS
352
		LIKE {string:table}',
353
		array(
354
			'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')),
355
		)
356
	);
357
	$row = $smcFunc['db_fetch_assoc']($result);
358
	$smcFunc['db_free_result']($result);
359
360
	// Probably MyISAM.... and it might have a comment.
361
	$schema_create .= $crlf . ') ENGINE=' . $row['Engine'] . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
362
363
	return $schema_create;
364
}
365
366
/**
367
 *  Get the version number.
368
 *
369
 * @return string The version
370
 */
371
function smf_db_get_version()
372
{
373
	static $ver;
374
375
	if (!empty($ver))
376
		return $ver;
377
378
	global $smcFunc;
379
380
	$request = $smcFunc['db_query']('', '
381
		SELECT VERSION()',
382
		array(
383
		)
384
	);
385
	list ($ver) = $smcFunc['db_fetch_row']($request);
386
	$smcFunc['db_free_result']($request);
387
388
	return $ver;
389
}
390
391
/**
392
 * Figures out if we are using MySQL, Percona or MariaDB
393
 *
394
 * @return string The database engine we are using
395
 */
396
function smf_db_get_vendor()
397
{
398
	global $smcFunc;
399
	static $db_type;
400
401
	if (!empty($db_type))
402
		return $db_type;
403
404
	$request = $smcFunc['db_query']('', 'SELECT @@version_comment');
405
	list ($comment) = $smcFunc['db_fetch_row']($request);
406
	$smcFunc['db_free_result']($request);
407
408
	// Skip these if we don't have a comment.
409
	if (!empty($comment))
410
	{
411
		if (stripos($comment, 'percona') !== false)
412
			return 'Percona';
413
		if (stripos($comment, 'mariadb') !== false)
414
			return 'MariaDB';
415
	}
416
	else
417
		return 'fail';
418
419
	return 'MySQL';
420
}
421
422
/**
423
 * Figures out if persistent connection is allowed
424
 *
425
 * @return boolean
426
 */
427
function smf_db_allow_persistent()
428
{
429
	$value = ini_get('mysqli.allow_persistent');
430
	if (strtolower($value) == 'on' || strtolower($value) == 'true' || $value == '1')
431
		return true;
432
	else
433
		return false;
434
}
435
436
?>