Total Complexity | 229 |
Total Lines | 1243 |
Duplicated Lines | 0 % |
Changes | 2 | ||
Bugs | 1 | Features | 0 |
Complex classes like Database_MySQL often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Database_MySQL, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
28 | class Database_MySQL extends Database_Abstract |
||
29 | { |
||
30 | /** |
||
31 | * Holds current instance of the class |
||
32 | * |
||
33 | * @var Database_MySQL |
||
34 | */ |
||
35 | private static $_db = null; |
||
36 | |||
37 | /** |
||
38 | * Initializes a database connection. |
||
39 | * It returns the connection, if successful. |
||
40 | * |
||
41 | * @param string $db_server |
||
42 | * @param string $db_name |
||
43 | * @param string $db_user |
||
44 | * @param string $db_passwd |
||
45 | * @param string $db_prefix |
||
46 | * @param mixed[] $db_options |
||
47 | * |
||
48 | * @return mysqli|null |
||
49 | * @throws Elk_Exception |
||
50 | */ |
||
51 | public static function initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $db_options = array()) |
||
52 | { |
||
53 | global $mysql_set_mode; |
||
54 | |||
55 | // Initialize the instance... if not done already! |
||
56 | if (self::$_db === null) |
||
57 | self::$_db = new self(); |
||
58 | |||
59 | // Non-standard port |
||
60 | if (!empty($db_options['port'])) |
||
61 | $db_port = (int) $db_options['port']; |
||
62 | else |
||
63 | $db_port = 0; |
||
64 | |||
65 | // Select the database. Maybe. |
||
66 | if (empty($db_options['dont_select_db'])) |
||
67 | $connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name, $db_port); |
||
68 | else |
||
69 | $connection = @mysqli_connect((!empty($db_options['persist']) ? 'p:' : '') . $db_server, $db_user, $db_passwd, '', $db_port); |
||
70 | |||
71 | // Something's wrong, show an error if its fatal (which we assume it is) |
||
72 | if (!$connection) |
||
73 | { |
||
74 | if (!empty($db_options['non_fatal'])) |
||
75 | return null; |
||
76 | else |
||
77 | Errors::instance()->display_db_error(); |
||
78 | } |
||
79 | |||
80 | self::$_db->_connection = $connection; |
||
81 | |||
82 | // This makes it possible to automatically change the sql_mode and autocommit if needed. |
||
83 | if (isset($mysql_set_mode) && $mysql_set_mode === true) |
||
84 | { |
||
85 | self::$_db->query('', 'SET sql_mode = \'\', AUTOCOMMIT = 1', |
||
86 | array(), |
||
87 | false |
||
88 | ); |
||
89 | } |
||
90 | |||
91 | // Few databases still have not set UTF-8 as their default input charset |
||
92 | self::$_db->query('', ' |
||
93 | SET NAMES UTF8', |
||
94 | array( |
||
95 | ) |
||
96 | ); |
||
97 | |||
98 | // Unfortunately Elk still have some trouble dealing with the most recent settings of MySQL/Mariadb |
||
99 | // disable ONLY_FULL_GROUP_BY |
||
100 | self::$_db->query('', ' |
||
101 | SET sql_mode=(SELECT REPLACE(@@sql_mode, {string:disable}, {string:empty}))', |
||
102 | array( |
||
103 | 'disable' => 'ONLY_FULL_GROUP_BY', |
||
104 | 'empty' => '' |
||
105 | ) |
||
106 | ); |
||
107 | |||
108 | return $connection; |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Fix up the prefix so it doesn't require the database to be selected. |
||
113 | * |
||
114 | * @param string $db_prefix |
||
115 | * @param string $db_name |
||
116 | * |
||
117 | * @return string |
||
118 | */ |
||
119 | public function fix_prefix($db_prefix, $db_name) |
||
120 | { |
||
121 | $db_prefix = is_numeric(substr($db_prefix, 0, 1)) ? $db_name . '.' . $db_prefix : '`' . $db_name . '`.' . $db_prefix; |
||
122 | |||
123 | return $db_prefix; |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Do a query. Takes care of errors too. |
||
128 | * |
||
129 | * @param string $identifier |
||
130 | * @param string $db_string |
||
131 | * @param mixed[]|false $db_values = array() |
||
132 | * @param mysqli_result|false|null $connection = null |
||
133 | * @throws Elk_Exception |
||
134 | */ |
||
135 | public function query($identifier, $db_string, $db_values = array(), $connection = null) |
||
136 | { |
||
137 | global $db_show_debug, $time_start, $modSettings; |
||
138 | |||
139 | // Comments that are allowed in a query are preg_removed. |
||
140 | static $allowed_comments_from = array( |
||
141 | '~\s+~s', |
||
142 | '~/\*!40001 SQL_NO_CACHE \*/~', |
||
143 | '~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~', |
||
144 | '~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~', |
||
145 | ); |
||
146 | static $allowed_comments_to = array( |
||
147 | ' ', |
||
148 | '', |
||
149 | '', |
||
150 | '', |
||
151 | ); |
||
152 | |||
153 | // Decide which connection to use. |
||
154 | $connection = $connection === null ? $this->_connection : $connection; |
||
155 | |||
156 | // One more query.... |
||
157 | $this->_query_count++; |
||
158 | |||
159 | if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override'])) |
||
160 | $this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__); |
||
161 | |||
162 | // Use "ORDER BY null" to prevent Mysql doing filesorts for Group By clauses without an Order By |
||
163 | if (strpos($db_string, 'GROUP BY') !== false && strpos($db_string, 'ORDER BY') === false && strpos($db_string, 'INSERT INTO') === false) |
||
164 | { |
||
165 | // Add before LIMIT |
||
166 | if ($pos = strpos($db_string, 'LIMIT ')) |
||
167 | $db_string = substr($db_string, 0, $pos) . "\t\t\tORDER BY null\n" . substr($db_string, $pos, strlen($db_string)); |
||
168 | else |
||
169 | // Append it. |
||
170 | $db_string .= "\n\t\t\tORDER BY null"; |
||
171 | } |
||
172 | |||
173 | if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false)) |
||
174 | { |
||
175 | // Store these values for use in the callback function. |
||
176 | $this->_db_callback_values = $db_values; |
||
177 | $this->_db_callback_connection = $connection; |
||
|
|||
178 | |||
179 | // Inject the values passed to this function. |
||
180 | $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string); |
||
181 | |||
182 | // No need for them any longer. |
||
183 | $this->_db_callback_values = array(); |
||
184 | $this->_db_callback_connection = null; |
||
185 | } |
||
186 | |||
187 | // Debugging. |
||
188 | if ($db_show_debug === true) |
||
189 | { |
||
190 | $debug = Debug::instance(); |
||
191 | |||
192 | // Get the file and line number this function was called. |
||
193 | list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__); |
||
194 | |||
195 | if (!empty($_SESSION['debug_redirect'])) |
||
196 | { |
||
197 | $debug->merge_db($_SESSION['debug_redirect']); |
||
198 | // @todo this may be off by 1 |
||
199 | $this->_query_count += count($_SESSION['debug_redirect']); |
||
200 | $_SESSION['debug_redirect'] = array(); |
||
201 | } |
||
202 | |||
203 | // Don't overload it. |
||
204 | $st = microtime(true); |
||
205 | $db_cache = array(); |
||
206 | $db_cache['q'] = $this->_query_count < 50 ? $db_string : '...'; |
||
207 | $db_cache['f'] = $file; |
||
208 | $db_cache['l'] = $line; |
||
209 | $db_cache['s'] = $st - $time_start; |
||
210 | } |
||
211 | |||
212 | // First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over. |
||
213 | if (empty($modSettings['disableQueryCheck'])) |
||
214 | { |
||
215 | $clean = ''; |
||
216 | $old_pos = 0; |
||
217 | $pos = -1; |
||
218 | while (true) |
||
219 | { |
||
220 | $pos = strpos($db_string, '\'', $pos + 1); |
||
221 | if ($pos === false) |
||
222 | break; |
||
223 | $clean .= substr($db_string, $old_pos, $pos - $old_pos); |
||
224 | |||
225 | while (true) |
||
226 | { |
||
227 | $pos1 = strpos($db_string, '\'', $pos + 1); |
||
228 | $pos2 = strpos($db_string, '\\', $pos + 1); |
||
229 | if ($pos1 === false) |
||
230 | break; |
||
231 | elseif ($pos2 === false || $pos2 > $pos1) |
||
232 | { |
||
233 | $pos = $pos1; |
||
234 | break; |
||
235 | } |
||
236 | |||
237 | $pos = $pos2 + 1; |
||
238 | } |
||
239 | |||
240 | $clean .= ' %s '; |
||
241 | $old_pos = $pos + 1; |
||
242 | } |
||
243 | |||
244 | $clean .= substr($db_string, $old_pos); |
||
245 | $clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean))); |
||
246 | |||
247 | // Comments? We don't use comments in our queries, we leave 'em outside! |
||
248 | if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false) |
||
249 | $fail = true; |
||
250 | // Trying to change passwords, slow us down, or something? |
||
251 | elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0) |
||
252 | $fail = true; |
||
253 | elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) |
||
254 | $fail = true; |
||
255 | |||
256 | if (!empty($fail) && function_exists('log_error')) |
||
257 | $this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__); |
||
258 | } |
||
259 | |||
260 | if ($this->_unbuffered === false) |
||
261 | $ret = @mysqli_query($connection, $db_string); |
||
262 | else |
||
263 | $ret = @mysqli_query($connection, $db_string, MYSQLI_USE_RESULT); |
||
264 | |||
265 | // @deprecated since 1.1 - use skip_next_error method |
||
266 | if (!empty($db_values['db_error_skip'])) |
||
267 | { |
||
268 | $this->_skip_error = true; |
||
269 | } |
||
270 | |||
271 | if ($ret === false && $this->_skip_error === false) |
||
272 | { |
||
273 | $ret = $this->error($db_string, $connection); |
||
274 | } |
||
275 | |||
276 | // Revert not to skip errors |
||
277 | if ($this->_skip_error === true) |
||
278 | { |
||
279 | $this->_skip_error = false; |
||
280 | } |
||
281 | |||
282 | // Debugging. |
||
283 | if ($db_show_debug === true) |
||
284 | { |
||
285 | $db_cache['t'] = microtime(true) - $st; |
||
286 | $debug->db_query($db_cache); |
||
287 | } |
||
288 | |||
289 | return $ret; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Checks if the string contains any 4byte chars and if so, |
||
294 | * converts them into HTML entities. |
||
295 | * |
||
296 | * This is necessary because MySQL utf8 doesn't know how to store such |
||
297 | * characters and would generate an error any time one is used. |
||
298 | * The 4byte chars are used by emoji |
||
299 | * |
||
300 | * @param string $string |
||
301 | * @return string |
||
302 | */ |
||
303 | private function _clean_4byte_chars($string) |
||
304 | { |
||
305 | global $modSettings; |
||
306 | |||
307 | if (!empty($modSettings['using_utf8mb4'])) |
||
308 | return $string; |
||
309 | |||
310 | $result = $string; |
||
311 | $ord = array_map('ord', str_split($string)); |
||
312 | |||
313 | // If we are in the 4-byte range |
||
314 | if (max($ord) >= 240) |
||
315 | { |
||
316 | // Byte length |
||
317 | $length = strlen($string); |
||
318 | $result = ''; |
||
319 | |||
320 | // Look for a 4byte marker |
||
321 | for ($i = 0; $i < $length; $i++) |
||
322 | { |
||
323 | // The first byte of a 4-byte character encoding starts with the bytes 0xF0-0xF4 (240 <-> 244) |
||
324 | // but look all the way to 247 for safe measure |
||
325 | $ord1 = $ord[$i]; |
||
326 | if ($ord1 >= 240 && $ord1 <= 247) |
||
327 | { |
||
328 | // Replace it with the corresponding html entity |
||
329 | $entity = $this->_uniord(chr($ord[$i]) . chr($ord[$i + 1]) . chr($ord[$i + 2]) . chr($ord[$i + 3])); |
||
330 | if ($entity === false) |
||
331 | $result .= "\xEF\xBF\xBD"; |
||
332 | else |
||
333 | $result .= '&#x' . dechex($entity) . ';'; |
||
334 | $i += 3; |
||
335 | } |
||
336 | else |
||
337 | $result .= $string[$i]; |
||
338 | } |
||
339 | } |
||
340 | |||
341 | return $result; |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Converts a 4byte char into the corresponding HTML entity code. |
||
346 | * |
||
347 | * This function is derived from: |
||
348 | * http://www.greywyvern.com/code/php/utf8_html.phps |
||
349 | * |
||
350 | * @param string $c |
||
351 | * @return integer|false |
||
352 | */ |
||
353 | private function _uniord($c) |
||
354 | { |
||
355 | if (ord($c[0]) >= 0 && ord($c[0]) <= 127) |
||
356 | return ord($c[0]); |
||
357 | if (ord($c[0]) >= 192 && ord($c[0]) <= 223) |
||
358 | return (ord($c[0]) - 192) * 64 + (ord($c[1]) - 128); |
||
359 | if (ord($c[0]) >= 224 && ord($c[0]) <= 239) |
||
360 | return (ord($c[0]) - 224) * 4096 + (ord($c[1]) - 128) * 64 + (ord($c[2]) - 128); |
||
361 | if (ord($c[0]) >= 240 && ord($c[0]) <= 247) |
||
362 | return (ord($c[0]) - 240) * 262144 + (ord($c[1]) - 128) * 4096 + (ord($c[2]) - 128) * 64 + (ord($c[3]) - 128); |
||
363 | if (ord($c[0]) >= 248 && ord($c[0]) <= 251) |
||
364 | return (ord($c[0]) - 248) * 16777216 + (ord($c[1]) - 128) * 262144 + (ord($c[2]) - 128) * 4096 + (ord($c[3]) - 128) * 64 + (ord($c[4]) - 128); |
||
365 | if (ord($c[0]) >= 252 && ord($c[0]) <= 253) |
||
366 | return (ord($c[0]) - 252) * 1073741824 + (ord($c[1]) - 128) * 16777216 + (ord($c[2]) - 128) * 262144 + (ord($c[3]) - 128) * 4096 + (ord($c[4]) - 128) * 64 + (ord($c[5]) - 128); |
||
367 | if (ord($c[0]) >= 254 && ord($c[0]) <= 255) |
||
368 | return false; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Affected rows from previous operation. |
||
373 | * |
||
374 | * @param mysqli|null $connection |
||
375 | */ |
||
376 | public function affected_rows($connection = null) |
||
377 | { |
||
378 | return mysqli_affected_rows($connection === null ? $this->_connection : $connection); |
||
379 | } |
||
380 | |||
381 | /** |
||
382 | * Last inserted id. |
||
383 | * |
||
384 | * @param string $table |
||
385 | * @param string|null $field = null |
||
386 | * @param mysqli|null $connection = null |
||
387 | */ |
||
388 | public function insert_id($table, $field = null, $connection = null) |
||
389 | { |
||
390 | // MySQL doesn't need the table or field information. |
||
391 | return mysqli_insert_id($connection === null ? $this->_connection : $connection); |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Fetch a row from the result set given as parameter. |
||
396 | * MySQL implementation doesn't use $counter parameter. |
||
397 | * |
||
398 | * @param mysqli_result $result |
||
399 | * @param integer|bool $counter = false |
||
400 | */ |
||
401 | public function fetch_row($result, $counter = false) |
||
402 | { |
||
403 | // Just delegate to MySQL's function |
||
404 | return mysqli_fetch_row($result); |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Free the resultset. |
||
409 | * |
||
410 | * @param mysqli_result $result |
||
411 | */ |
||
412 | public function free_result($result) |
||
413 | { |
||
414 | // Just delegate to MySQL's function |
||
415 | mysqli_free_result($result); |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Get the number of rows in the result. |
||
420 | * |
||
421 | * @param mysqli_result $result |
||
422 | */ |
||
423 | public function num_rows($result) |
||
424 | { |
||
425 | // Simply delegate to the native function |
||
426 | return mysqli_num_rows($result); |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * Get the number of fields in the result set. |
||
431 | * |
||
432 | * @param mysqli_result $request |
||
433 | */ |
||
434 | public function num_fields($request) |
||
435 | { |
||
436 | return mysqli_num_fields($request); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Reset the internal result pointer. |
||
441 | * |
||
442 | * @param mysqli_result $request |
||
443 | * @param integer $counter |
||
444 | */ |
||
445 | public function data_seek($request, $counter) |
||
446 | { |
||
447 | // Delegate to native mysql function |
||
448 | return mysqli_data_seek($request, $counter); |
||
449 | } |
||
450 | |||
451 | /** |
||
452 | * Do a transaction. |
||
453 | * |
||
454 | * @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback') |
||
455 | * @param mysqli|null $connection = null |
||
456 | */ |
||
457 | public function db_transaction($type = 'commit', $connection = null) |
||
458 | { |
||
459 | // Decide which connection to use |
||
460 | $connection = $connection === null ? $this->_connection : $connection; |
||
461 | |||
462 | if ($type == 'begin') |
||
463 | return @mysqli_query($connection, 'BEGIN'); |
||
464 | elseif ($type == 'rollback') |
||
465 | return @mysqli_query($connection, 'ROLLBACK'); |
||
466 | elseif ($type == 'commit') |
||
467 | return @mysqli_query($connection, 'COMMIT'); |
||
468 | |||
469 | return false; |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * Return last error string from the database server |
||
474 | * |
||
475 | * @param mysqli|null $connection = null |
||
476 | */ |
||
477 | public function last_error($connection = null) |
||
478 | { |
||
479 | // Decide which connection to use |
||
480 | $connection = $connection === null ? $this->_connection : $connection; |
||
481 | |||
482 | if (is_object($connection)) |
||
483 | return mysqli_error($connection); |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * Database error. |
||
488 | * Backtrace, log, try to fix. |
||
489 | * |
||
490 | * @param string $db_string |
||
491 | * @param mysqli|null $connection = null |
||
492 | * |
||
493 | * @return bool |
||
494 | * @throws Elk_Exception |
||
495 | */ |
||
496 | public function error($db_string, $connection = null) |
||
497 | { |
||
498 | global $txt, $context, $webmaster_email, $modSettings, $db_persist; |
||
499 | global $db_server, $db_user, $db_passwd, $db_name, $db_show_debug, $ssi_db_user, $ssi_db_passwd; |
||
500 | |||
501 | // Get the file and line numbers. |
||
502 | list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__); |
||
503 | |||
504 | // Decide which connection to use |
||
505 | $connection = $connection === null ? $this->_connection : $connection; |
||
506 | |||
507 | // This is the error message... |
||
508 | $query_error = mysqli_error($connection); |
||
509 | $query_errno = mysqli_errno($connection); |
||
510 | |||
511 | // Error numbers: |
||
512 | // 1016: Can't open file '....MYI' |
||
513 | // 1030: Got error ??? from table handler. |
||
514 | // 1034: Incorrect key file for table. |
||
515 | // 1035: Old key file for table. |
||
516 | // 1142: Command denied |
||
517 | // 1205: Lock wait timeout exceeded. |
||
518 | // 1213: Deadlock found. |
||
519 | // 2006: Server has gone away. |
||
520 | // 2013: Lost connection to server during query. |
||
521 | |||
522 | // We cannot do something, try to find out what and act accordingly |
||
523 | if ($query_errno == 1142) |
||
524 | { |
||
525 | $command = substr(trim($db_string), 0, 6); |
||
526 | if ($command === 'DELETE' || $command === 'UPDATE' || $command === 'INSERT') |
||
527 | { |
||
528 | // We can try to ignore it (warning the admin though it's a thing to do) |
||
529 | // and serve the page just SELECTing |
||
530 | $_SESSION['query_command_denied'][$command] = $query_error; |
||
531 | |||
532 | // Let the admin know there is a command denied issue |
||
533 | if (class_exists('Errors')) |
||
534 | { |
||
535 | Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line); |
||
536 | } |
||
537 | |||
538 | return false; |
||
539 | } |
||
540 | } |
||
541 | |||
542 | // Log the error. |
||
543 | if ($query_errno != 1213 && $query_errno != 1205 && class_exists('Errors')) |
||
544 | { |
||
545 | Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n$db_string" : ''), 'database', $file, $line); |
||
546 | } |
||
547 | |||
548 | // Database error auto fixing ;). |
||
549 | if (function_exists('Cache::instance()->get') && (!isset($modSettings['autoFixDatabase']) || $modSettings['autoFixDatabase'] == '1')) |
||
550 | { |
||
551 | $db_last_error = db_last_error(); |
||
552 | |||
553 | // Force caching on, just for the error checking. |
||
554 | $old_cache = isset($modSettings['cache_enable']) ? $modSettings['cache_enable'] : null; |
||
555 | $modSettings['cache_enable'] = '1'; |
||
556 | |||
557 | if (Cache::instance()->getVar($temp, 'db_last_error', 600)) |
||
558 | $db_last_error = max($db_last_error, $temp); |
||
559 | |||
560 | if ($db_last_error < time() - 3600 * 24 * 3) |
||
561 | { |
||
562 | // We know there's a problem... but what? Try to auto detect. |
||
563 | if ($query_errno == 1030 && strpos($query_error, ' 127 ') !== false) |
||
564 | { |
||
565 | preg_match_all('~(?:[\n\r]|^)[^\']+?(?:FROM|JOIN|UPDATE|TABLE) ((?:[^\n\r(]+?(?:, )?)*)~s', $db_string, $matches); |
||
566 | |||
567 | $fix_tables = array(); |
||
568 | foreach ($matches[1] as $tables) |
||
569 | { |
||
570 | $tables = array_unique(explode(',', $tables)); |
||
571 | foreach ($tables as $table) |
||
572 | { |
||
573 | // Now, it's still theoretically possible this could be an injection. So backtick it! |
||
574 | if (trim($table) != '') |
||
575 | $fix_tables[] = '`' . strtr(trim($table), array('`' => '')) . '`'; |
||
576 | } |
||
577 | } |
||
578 | |||
579 | $fix_tables = array_unique($fix_tables); |
||
580 | } |
||
581 | // Table crashed. Let's try to fix it. |
||
582 | elseif ($query_errno == 1016) |
||
583 | { |
||
584 | if (preg_match('~\'([^\.\']+)~', $query_error, $match) != 0) |
||
585 | $fix_tables = array('`' . $match[1] . '`'); |
||
586 | } |
||
587 | // Indexes crashed. Should be easy to fix! |
||
588 | elseif ($query_errno == 1034 || $query_errno == 1035) |
||
589 | { |
||
590 | preg_match('~\'([^\']+?)\'~', $query_error, $match); |
||
591 | $fix_tables = array('`' . $match[1] . '`'); |
||
592 | } |
||
593 | } |
||
594 | |||
595 | // Check for errors like 145... only fix it once every three days, and send an email. (can't use empty because it might not be set yet...) |
||
596 | if (!empty($fix_tables)) |
||
597 | { |
||
598 | // subs/Admin.subs.php for updateDbLastError(), subs/Mail.subs.php for sendmail(). |
||
599 | // @todo this should go somewhere else, not into the db-mysql layer I think |
||
600 | require_once(SUBSDIR . '/Admin.subs.php'); |
||
601 | require_once(SUBSDIR . '/Mail.subs.php'); |
||
602 | |||
603 | // Make a note of the REPAIR... |
||
604 | Cache::instance()->put('db_last_error', time(), 600); |
||
605 | if (!Cache::instance()->getVar($temp, 'db_last_error', 600)) |
||
606 | updateDbLastError(time()); |
||
607 | |||
608 | // Attempt to find and repair the broken table. |
||
609 | foreach ($fix_tables as $table) |
||
610 | { |
||
611 | $this->query('', " |
||
612 | REPAIR TABLE $table", false, false); |
||
613 | } |
||
614 | |||
615 | // And send off an email! |
||
616 | sendmail($webmaster_email, $txt['database_error'], $txt['tried_to_repair']); |
||
617 | |||
618 | $modSettings['cache_enable'] = $old_cache; |
||
619 | |||
620 | // Try the query again...? |
||
621 | $ret = $this->query('', $db_string, false, false); |
||
622 | if ($ret !== false) |
||
623 | return $ret; |
||
624 | } |
||
625 | else |
||
626 | $modSettings['cache_enable'] = $old_cache; |
||
627 | |||
628 | // Check for the "lost connection" or "deadlock found" errors - and try it just one more time. |
||
629 | if (in_array($query_errno, array(1205, 1213, 2006, 2013))) |
||
630 | { |
||
631 | $new_connection = false; |
||
632 | if (in_array($query_errno, array(2006, 2013)) && $this->_connection == $connection) |
||
633 | { |
||
634 | // Are we in SSI mode? If so try that username and password first |
||
635 | if (ELK == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd)) |
||
636 | $new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $ssi_db_user, $ssi_db_passwd, $db_name); |
||
637 | |||
638 | // Fall back to the regular username and password if need be |
||
639 | if (!$new_connection) |
||
640 | $new_connection = @mysqli_connect((!empty($db_persist) ? 'p:' : '') . $db_server, $db_user, $db_passwd, $db_name); |
||
641 | } |
||
642 | |||
643 | if ($new_connection) |
||
644 | { |
||
645 | $this->_connection = $new_connection; |
||
646 | |||
647 | // Try a deadlock more than once more. |
||
648 | for ($n = 0; $n < 4; $n++) |
||
649 | { |
||
650 | $ret = $this->query('', $db_string, false, false); |
||
651 | |||
652 | $new_errno = mysqli_errno($new_connection); |
||
653 | if ($ret !== false || in_array($new_errno, array(1205, 1213))) |
||
654 | break; |
||
655 | } |
||
656 | |||
657 | // If it failed again, shucks to be you... we're not trying it over and over. |
||
658 | if ($ret !== false) |
||
659 | return $ret; |
||
660 | } |
||
661 | } |
||
662 | // Are they out of space, perhaps? |
||
663 | elseif ($query_errno == 1030 && (strpos($query_error, ' -1 ') !== false || strpos($query_error, ' 28 ') !== false || strpos($query_error, ' 12 ') !== false)) |
||
664 | { |
||
665 | if (!isset($txt)) |
||
666 | $query_error .= ' - check database storage space.'; |
||
667 | else |
||
668 | { |
||
669 | if (!isset($txt['mysql_error_space'])) |
||
670 | loadLanguage('Errors'); |
||
671 | |||
672 | $query_error .= !isset($txt['mysql_error_space']) ? ' - check database storage space.' : $txt['mysql_error_space']; |
||
673 | } |
||
674 | } |
||
675 | } |
||
676 | |||
677 | // Nothing's defined yet... just die with it. |
||
678 | if (empty($context) || empty($txt)) |
||
679 | die($query_error); |
||
680 | |||
681 | // Show an error message, if possible. |
||
682 | $context['error_title'] = $txt['database_error']; |
||
683 | if (allowedTo('admin_forum')) |
||
684 | $context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line; |
||
685 | else |
||
686 | $context['error_message'] = $txt['try_again']; |
||
687 | |||
688 | // Add database version that we know of, for the admin to know. (and ask for support) |
||
689 | if (allowedTo('admin_forum')) |
||
690 | $context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']); |
||
691 | |||
692 | if (allowedTo('admin_forum') && $db_show_debug === true) |
||
693 | $context['error_message'] .= '<br /><br />' . nl2br($db_string); |
||
694 | |||
695 | // It's already been logged... don't log it again. |
||
696 | throw new Elk_Exception($context['error_message'], false); |
||
697 | } |
||
698 | |||
699 | /** |
||
700 | * Insert data. |
||
701 | * |
||
702 | * @param string $method - options 'replace', 'ignore', 'insert' |
||
703 | * @param string $table |
||
704 | * @param mixed[] $columns |
||
705 | * @param mixed[] $data |
||
706 | * @param mixed[] $keys |
||
707 | * @param bool $disable_trans = false |
||
708 | * @param mysqli|null $connection = null |
||
709 | * @throws Elk_Exception |
||
710 | */ |
||
711 | public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null) |
||
712 | { |
||
713 | global $db_prefix; |
||
714 | |||
715 | $connection = $connection === null ? $this->_connection : $connection; |
||
716 | |||
717 | // With nothing to insert, simply return. |
||
718 | if (empty($data)) |
||
719 | return; |
||
720 | |||
721 | // Inserting data as a single row can be done as a single array. |
||
722 | if (!is_array($data[array_rand($data)])) |
||
723 | $data = array($data); |
||
724 | |||
725 | // Replace the prefix holder with the actual prefix. |
||
726 | $table = str_replace('{db_prefix}', $db_prefix, $table); |
||
727 | |||
728 | // Create the mold for a single row insert. |
||
729 | $insertData = '('; |
||
730 | foreach ($columns as $columnName => $type) |
||
731 | { |
||
732 | // Are we restricting the length? |
||
733 | if (strpos($type, 'string-') !== false) |
||
734 | $insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); |
||
735 | else |
||
736 | $insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); |
||
737 | } |
||
738 | $insertData = substr($insertData, 0, -2) . ')'; |
||
739 | |||
740 | // Create an array consisting of only the columns. |
||
741 | $indexed_columns = array_keys($columns); |
||
742 | |||
743 | // Here's where the variables are injected to the query. |
||
744 | $insertRows = array(); |
||
745 | foreach ($data as $dataRow) |
||
746 | { |
||
747 | $insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), $connection); |
||
748 | } |
||
749 | |||
750 | // Determine the method of insertion. |
||
751 | $queryTitle = $method === 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT'); |
||
752 | |||
753 | $skip_error = $table === $db_prefix . 'log_errors'; |
||
754 | $this->_skip_error = $skip_error; |
||
755 | // Do the insert. |
||
756 | $this->query('', ' |
||
757 | ' . $queryTitle . ' INTO ' . $table . '(`' . implode('`, `', $indexed_columns) . '`) |
||
758 | VALUES |
||
759 | ' . implode(', |
||
760 | ', $insertRows), |
||
761 | array( |
||
762 | 'security_override' => true, |
||
763 | ), |
||
764 | $connection |
||
765 | ); |
||
766 | } |
||
767 | |||
768 | /** |
||
769 | * Unescape an escaped string! |
||
770 | * |
||
771 | * @param string $string |
||
772 | */ |
||
773 | public function unescape_string($string) |
||
774 | { |
||
775 | return stripslashes($string); |
||
776 | } |
||
777 | |||
778 | /** |
||
779 | * Returns whether the database system supports ignore. |
||
780 | * |
||
781 | * @return boolean |
||
782 | */ |
||
783 | public function support_ignore() |
||
784 | { |
||
785 | return true; |
||
786 | } |
||
787 | |||
788 | /** |
||
789 | * Gets all the necessary INSERTs for the table named table_name. |
||
790 | * It goes in 250 row segments. |
||
791 | * |
||
792 | * @param string $tableName - the table to create the inserts for. |
||
793 | * @param bool $new_table |
||
794 | * |
||
795 | * @return string the query to insert the data back in, or an empty string if the table was empty. |
||
796 | * @throws Elk_Exception |
||
797 | */ |
||
798 | public function insert_sql($tableName, $new_table = false) |
||
799 | { |
||
800 | global $db_prefix; |
||
801 | |||
802 | static $start = 0, $num_rows, $fields, $limit; |
||
803 | |||
804 | if ($new_table) |
||
805 | { |
||
806 | $limit = strstr($tableName, 'log_') !== false ? 500 : 250; |
||
807 | $start = 0; |
||
808 | } |
||
809 | |||
810 | $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); |
||
811 | |||
812 | // This will be handy... |
||
813 | $crlf = "\r\n"; |
||
814 | |||
815 | $result = $this->query('', ' |
||
816 | SELECT /*!40001 SQL_NO_CACHE */ * |
||
817 | FROM `' . $tableName . '` |
||
818 | LIMIT ' . $start . ', ' . $limit, |
||
819 | array( |
||
820 | 'security_override' => true, |
||
821 | ) |
||
822 | ); |
||
823 | |||
824 | // The number of rows, just for record keeping and breaking INSERTs up. |
||
825 | $num_rows = $this->num_rows($result); |
||
826 | |||
827 | if ($num_rows == 0) |
||
828 | return ''; |
||
829 | |||
830 | if ($new_table) |
||
831 | { |
||
832 | $fields = array_keys($this->fetch_assoc($result)); |
||
833 | $this->data_seek($result, 0); |
||
834 | } |
||
835 | |||
836 | // Start it off with the basic INSERT INTO. |
||
837 | $data = 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES '; |
||
838 | |||
839 | // Loop through each row. |
||
840 | while ($row = $this->fetch_assoc($result)) |
||
841 | { |
||
842 | // Get the fields in this row... |
||
843 | $field_list = array(); |
||
844 | |||
845 | foreach ($row as $key => $item) |
||
846 | { |
||
847 | // Try to figure out the type of each field. (NULL, number, or 'string'.) |
||
848 | if (!isset($item)) |
||
849 | $field_list[] = 'NULL'; |
||
850 | elseif (is_numeric($item) && (int) $item == $item) |
||
851 | $field_list[] = $item; |
||
852 | else |
||
853 | $field_list[] = '\'' . $this->escape_string($item) . '\''; |
||
854 | } |
||
855 | |||
856 | $data .= '(' . implode(', ', $field_list) . '),' . $crlf . "\t"; |
||
857 | } |
||
858 | |||
859 | $this->free_result($result); |
||
860 | $data = substr(trim($data), 0, -1) . ';' . $crlf . $crlf; |
||
861 | |||
862 | $start += $limit; |
||
863 | |||
864 | return $data; |
||
865 | } |
||
866 | |||
867 | /** |
||
868 | * Dumps the schema (CREATE) for a table. |
||
869 | * |
||
870 | * @param string $tableName - the table |
||
871 | * |
||
872 | * @return string - the CREATE statement as string |
||
873 | * @throws Elk_Exception |
||
874 | */ |
||
875 | public function db_table_sql($tableName) |
||
876 | { |
||
877 | global $db_prefix; |
||
878 | |||
879 | $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); |
||
880 | |||
881 | // This will be needed... |
||
882 | $crlf = "\r\n"; |
||
883 | |||
884 | // Drop it if it exists. |
||
885 | $schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf; |
||
886 | |||
887 | // Start the create table... |
||
888 | $schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf; |
||
889 | |||
890 | // Find all the fields. |
||
891 | $result = $this->query('', ' |
||
892 | SHOW FIELDS |
||
893 | FROM `{raw:table}`', |
||
894 | array( |
||
895 | 'table' => $tableName, |
||
896 | ) |
||
897 | ); |
||
898 | while ($row = $this->fetch_assoc($result)) |
||
899 | { |
||
900 | // Make the CREATE for this column. |
||
901 | $schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : ''); |
||
902 | |||
903 | // Add a default...? |
||
904 | if (!empty($row['Default']) || $row['Null'] !== 'YES') |
||
905 | { |
||
906 | // Make a special case of auto-timestamp. |
||
907 | if ($row['Default'] == 'CURRENT_TIMESTAMP') |
||
908 | $schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */'; |
||
909 | // Text shouldn't have a default. |
||
910 | elseif ($row['Default'] !== null) |
||
911 | { |
||
912 | // If this field is numeric the default needs no escaping. |
||
913 | $type = strtolower($row['Type']); |
||
914 | $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; |
||
915 | |||
916 | $schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $this->escape_string($row['Default']) . '\''); |
||
917 | } |
||
918 | } |
||
919 | |||
920 | // And now any extra information. (such as auto_increment.) |
||
921 | $schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf; |
||
922 | } |
||
923 | $this->free_result($result); |
||
924 | |||
925 | // Take off the last comma. |
||
926 | $schema_create = substr($schema_create, 0, -strlen($crlf) - 1); |
||
927 | |||
928 | // Find the keys. |
||
929 | $result = $this->query('', ' |
||
930 | SHOW KEYS |
||
931 | FROM `{raw:table}`', |
||
932 | array( |
||
933 | 'table' => $tableName, |
||
934 | ) |
||
935 | ); |
||
936 | $indexes = array(); |
||
937 | while ($row = $this->fetch_assoc($result)) |
||
938 | { |
||
939 | // Is this a primary key, unique index, or regular index? |
||
940 | $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'] . '`'; |
||
941 | |||
942 | // Is this the first column in the index? |
||
943 | if (empty($indexes[$row['Key_name']])) |
||
944 | $indexes[$row['Key_name']] = array(); |
||
945 | |||
946 | // A sub part, like only indexing 15 characters of a varchar. |
||
947 | if (!empty($row['Sub_part'])) |
||
948 | $indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')'; |
||
949 | else |
||
950 | $indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`'; |
||
951 | } |
||
952 | $this->free_result($result); |
||
953 | |||
954 | // Build the CREATEs for the keys. |
||
955 | foreach ($indexes as $keyname => $columns) |
||
956 | { |
||
957 | // Ensure the columns are in proper order. |
||
958 | ksort($columns); |
||
959 | |||
960 | $schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')'; |
||
961 | } |
||
962 | |||
963 | // Now just get the comment and type... (MyISAM, etc.) |
||
964 | $result = $this->query('', ' |
||
965 | SHOW TABLE STATUS |
||
966 | LIKE {string:table}', |
||
967 | array( |
||
968 | 'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')), |
||
969 | ) |
||
970 | ); |
||
971 | $row = $this->fetch_assoc($result); |
||
972 | $this->free_result($result); |
||
973 | |||
974 | // Probably MyISAM.... and it might have a comment. |
||
975 | $schema_create .= $crlf . ') ENGINE=' . (isset($row['Type']) ? $row['Type'] : $row['Engine']) . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : ''); |
||
976 | |||
977 | return $schema_create; |
||
978 | } |
||
979 | |||
980 | /** |
||
981 | * {@inheritdoc} |
||
982 | */ |
||
983 | public function db_list_tables($db_name_str = false, $filter = false) |
||
984 | { |
||
985 | global $db_name; |
||
986 | |||
987 | $db_name_str = $db_name_str === false ? $db_name : $db_name_str; |
||
988 | $db_name_str = trim($db_name_str); |
||
989 | $filter = $filter === false ? '' : ' LIKE \'' . $filter . '\''; |
||
990 | |||
991 | $request = $this->query('', ' |
||
992 | SHOW TABLES |
||
993 | FROM `{raw:db_name_str}` |
||
994 | {raw:filter}', |
||
995 | array( |
||
996 | 'db_name_str' => $db_name_str[0] == '`' ? strtr($db_name_str, array('`' => '')) : $db_name_str, |
||
997 | 'filter' => $filter, |
||
998 | ) |
||
999 | ); |
||
1000 | $tables = array(); |
||
1001 | while ($row = $this->fetch_row($request)) |
||
1002 | { |
||
1003 | $tables[] = $row[0]; |
||
1004 | } |
||
1005 | $this->free_result($request); |
||
1006 | |||
1007 | return $tables; |
||
1008 | } |
||
1009 | |||
1010 | /** |
||
1011 | * Backup $table_name to $backup_table. |
||
1012 | * |
||
1013 | * @param string $table_name |
||
1014 | * @param string $backup_table |
||
1015 | * |
||
1016 | * @return resource - the request handle to the table creation query |
||
1017 | * @throws Elk_Exception |
||
1018 | */ |
||
1019 | public function db_backup_table($table_name, $backup_table) |
||
1020 | { |
||
1021 | global $db_prefix; |
||
1022 | |||
1023 | $table = str_replace('{db_prefix}', $db_prefix, $table_name); |
||
1024 | |||
1025 | // First, get rid of the old table. |
||
1026 | $db_table = db_table(); |
||
1027 | $db_table->db_drop_table($backup_table); |
||
1028 | |||
1029 | // Can we do this the quick way? |
||
1030 | $result = $this->query('', ' |
||
1031 | CREATE TABLE {raw:backup_table} LIKE {raw:table}', |
||
1032 | array( |
||
1033 | 'backup_table' => $backup_table, |
||
1034 | 'table' => $table |
||
1035 | )); |
||
1036 | // If this failed, we go old school. |
||
1037 | if ($result) |
||
1038 | { |
||
1039 | $request = $this->query('', ' |
||
1040 | INSERT INTO {raw:backup_table} |
||
1041 | SELECT * |
||
1042 | FROM {raw:table}', |
||
1043 | array( |
||
1044 | 'backup_table' => $backup_table, |
||
1045 | 'table' => $table |
||
1046 | )); |
||
1047 | |||
1048 | // Old school or no school? |
||
1049 | if ($request) |
||
1050 | return $request; |
||
1051 | } |
||
1052 | |||
1053 | // At this point, the quick method failed. |
||
1054 | $result = $this->query('', ' |
||
1055 | SHOW CREATE TABLE {raw:table}', |
||
1056 | array( |
||
1057 | 'table' => $table, |
||
1058 | ) |
||
1059 | ); |
||
1060 | list (, $create) = $this->fetch_row($result); |
||
1061 | $this->free_result($result); |
||
1062 | |||
1063 | $create = preg_split('/[\n\r]/', $create); |
||
1064 | |||
1065 | $auto_inc = ''; |
||
1066 | |||
1067 | // Default engine type. |
||
1068 | $engine = 'MyISAM'; |
||
1069 | $charset = ''; |
||
1070 | $collate = ''; |
||
1071 | |||
1072 | foreach ($create as $k => $l) |
||
1073 | { |
||
1074 | // Get the name of the auto_increment column. |
||
1075 | if (strpos($l, 'auto_increment')) |
||
1076 | $auto_inc = trim($l); |
||
1077 | |||
1078 | // For the engine type, see if we can work out what it is. |
||
1079 | if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false) |
||
1080 | { |
||
1081 | // Extract the engine type. |
||
1082 | preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match); |
||
1083 | |||
1084 | if (!empty($match[1])) |
||
1085 | $engine = $match[1]; |
||
1086 | |||
1087 | if (!empty($match[2])) |
||
1088 | $engine = $match[2]; |
||
1089 | |||
1090 | if (!empty($match[5])) |
||
1091 | $charset = $match[5]; |
||
1092 | |||
1093 | if (!empty($match[7])) |
||
1094 | $collate = $match[7]; |
||
1095 | } |
||
1096 | |||
1097 | // Skip everything but keys... |
||
1098 | if (strpos($l, 'KEY') === false) |
||
1099 | unset($create[$k]); |
||
1100 | } |
||
1101 | |||
1102 | if (!empty($create)) |
||
1103 | $create = '( |
||
1104 | ' . implode(' |
||
1105 | ', $create) . ')'; |
||
1106 | else |
||
1107 | $create = ''; |
||
1108 | |||
1109 | $request = $this->query('', ' |
||
1110 | CREATE TABLE {raw:backup_table} {raw:create} |
||
1111 | ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . ' |
||
1112 | SELECT * |
||
1113 | FROM {raw:table}', |
||
1114 | array( |
||
1115 | 'backup_table' => $backup_table, |
||
1116 | 'table' => $table, |
||
1117 | 'create' => $create, |
||
1118 | 'engine' => $engine, |
||
1119 | 'charset' => empty($charset) ? '' : $charset, |
||
1120 | 'collate' => empty($collate) ? '' : $collate, |
||
1121 | ) |
||
1122 | ); |
||
1123 | |||
1124 | if ($auto_inc != '') |
||
1125 | { |
||
1126 | if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',') |
||
1127 | $auto_inc = substr($auto_inc, 0, -1); |
||
1128 | |||
1129 | $this->query('', ' |
||
1130 | ALTER TABLE {raw:backup_table} |
||
1131 | CHANGE COLUMN {raw:column_detail} {raw:auto_inc}', |
||
1132 | array( |
||
1133 | 'backup_table' => $backup_table, |
||
1134 | 'column_detail' => $match[1], |
||
1135 | 'auto_inc' => $auto_inc, |
||
1136 | ) |
||
1137 | ); |
||
1138 | } |
||
1139 | |||
1140 | return $request; |
||
1141 | } |
||
1142 | |||
1143 | /** |
||
1144 | * Get the version number. |
||
1145 | * |
||
1146 | * @return string - the version |
||
1147 | * @throws Elk_Exception |
||
1148 | */ |
||
1149 | public function db_server_version() |
||
1150 | { |
||
1151 | $request = $this->query('', ' |
||
1152 | SELECT VERSION()', |
||
1153 | array( |
||
1154 | ) |
||
1155 | ); |
||
1156 | list ($ver) = $this->fetch_row($request); |
||
1157 | $this->free_result($request); |
||
1158 | |||
1159 | return $ver; |
||
1160 | } |
||
1161 | |||
1162 | /** |
||
1163 | * Get the name (title) of the database system. |
||
1164 | * |
||
1165 | * @return string |
||
1166 | */ |
||
1167 | public function db_title() |
||
1168 | { |
||
1169 | return 'MySQL'; |
||
1170 | } |
||
1171 | |||
1172 | /** |
||
1173 | * Whether the database system is case sensitive. |
||
1174 | * |
||
1175 | * @return false |
||
1176 | */ |
||
1177 | public function db_case_sensitive() |
||
1178 | { |
||
1179 | return false; |
||
1180 | } |
||
1181 | |||
1182 | /** |
||
1183 | * Escape string for the database input |
||
1184 | * |
||
1185 | * @param string $string |
||
1186 | */ |
||
1187 | public function escape_string($string) |
||
1188 | { |
||
1189 | $string = $this->_clean_4byte_chars($string); |
||
1190 | |||
1191 | return mysqli_real_escape_string($this->_connection, $string); |
||
1192 | } |
||
1193 | |||
1194 | /** |
||
1195 | * Fetch next result as association. |
||
1196 | * The mysql implementation simply delegates to mysqli_fetch_assoc(). |
||
1197 | * It ignores $counter parameter. |
||
1198 | * |
||
1199 | * @param mysqli_result $request |
||
1200 | * @param int|bool $counter = false |
||
1201 | */ |
||
1202 | public function fetch_assoc($request, $counter = false) |
||
1203 | { |
||
1204 | return mysqli_fetch_assoc($request); |
||
1205 | } |
||
1206 | |||
1207 | /** |
||
1208 | * Return server info. |
||
1209 | * |
||
1210 | * @param mysqli|null $connection |
||
1211 | * |
||
1212 | * @return string |
||
1213 | */ |
||
1214 | public function db_server_info($connection = null) |
||
1215 | { |
||
1216 | // Decide which connection to use |
||
1217 | $connection = $connection === null ? $this->_connection : $connection; |
||
1218 | |||
1219 | return mysqli_get_server_info($connection); |
||
1220 | } |
||
1221 | |||
1222 | /** |
||
1223 | * Get the version number. |
||
1224 | * |
||
1225 | * @return string - the version |
||
1226 | * @throws Elk_Exception |
||
1227 | */ |
||
1228 | public function db_client_version() |
||
1229 | { |
||
1230 | $request = $this->query('', ' |
||
1231 | SELECT VERSION()', |
||
1232 | array( |
||
1233 | ) |
||
1234 | ); |
||
1235 | list ($ver) = $this->fetch_row($request); |
||
1236 | $this->free_result($request); |
||
1237 | |||
1238 | return $ver; |
||
1239 | } |
||
1240 | |||
1241 | /** |
||
1242 | * Select database. |
||
1243 | * |
||
1244 | * @param string|null $dbName = null |
||
1245 | * @param mysqli|null $connection = null |
||
1246 | */ |
||
1247 | public function select_db($dbName = null, $connection = null) |
||
1248 | { |
||
1249 | // Decide which connection to use |
||
1250 | $connection = $connection === null ? $this->_connection : $connection; |
||
1251 | |||
1252 | return mysqli_select_db($connection, $dbName); |
||
1253 | } |
||
1254 | |||
1255 | /** |
||
1256 | * Returns a reference to the existing instance |
||
1257 | */ |
||
1258 | public static function db() |
||
1261 | } |
||
1262 | |||
1263 | /** |
||
1264 | * Finds out if the connection is still valid. |
||
1265 | * |
||
1266 | * @param mysqli|null $connection = null |
||
1267 | */ |
||
1268 | public function validConnection($connection = null) |
||
1271 | } |
||
1272 | } |
||
1273 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.