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
![]() |
|||||
234 | $db = trim($db); |
||||
0 ignored issues
–
show
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
![]() |
|||||
235 | $filter = $filter == false ? '' : ' LIKE \'' . $filter . '\''; |
||||
0 ignored issues
–
show
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
![]() |
|||||
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 | ?> |