1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Main interface for database interactions |
4
|
|
|
* |
5
|
|
|
* @package PhpMyAdmin-DBI |
6
|
|
|
*/ |
7
|
|
|
declare(strict_types=1); |
8
|
|
|
|
9
|
|
|
namespace PhpMyAdmin; |
10
|
|
|
|
11
|
|
|
use mysqli_result; |
12
|
|
|
use PhpMyAdmin\Database\DatabaseList; |
13
|
|
|
use PhpMyAdmin\Dbi\DbiExtension; |
14
|
|
|
use PhpMyAdmin\Dbi\DbiMysqli; |
15
|
|
|
use PhpMyAdmin\SqlParser\Context; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Main interface for database interactions |
19
|
|
|
* |
20
|
|
|
* @package PhpMyAdmin-DBI |
21
|
|
|
*/ |
22
|
|
|
class DatabaseInterface |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Force STORE_RESULT method, ignored by classic MySQL. |
26
|
|
|
*/ |
27
|
|
|
public const QUERY_STORE = 1; |
28
|
|
|
/** |
29
|
|
|
* Do not read whole query. |
30
|
|
|
*/ |
31
|
|
|
public const QUERY_UNBUFFERED = 2; |
32
|
|
|
/** |
33
|
|
|
* Get session variable. |
34
|
|
|
*/ |
35
|
|
|
public const GETVAR_SESSION = 1; |
36
|
|
|
/** |
37
|
|
|
* Get global variable. |
38
|
|
|
*/ |
39
|
|
|
public const GETVAR_GLOBAL = 2; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* User connection. |
43
|
|
|
*/ |
44
|
|
|
public const CONNECT_USER = 0x100; |
45
|
|
|
/** |
46
|
|
|
* Control user connection. |
47
|
|
|
*/ |
48
|
|
|
public const CONNECT_CONTROL = 0x101; |
49
|
|
|
/** |
50
|
|
|
* Auxiliary connection. |
51
|
|
|
* |
52
|
|
|
* Used for example for replication setup. |
53
|
|
|
*/ |
54
|
|
|
public const CONNECT_AUXILIARY = 0x102; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var DbiExtension |
58
|
|
|
*/ |
59
|
|
|
private $_extension; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Opened database links |
63
|
|
|
* |
64
|
|
|
* @var array |
65
|
|
|
*/ |
66
|
|
|
private $_links; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @var array Table data cache |
70
|
|
|
*/ |
71
|
|
|
private $_table_cache; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @var array Current user and host cache |
75
|
|
|
*/ |
76
|
|
|
private $_current_user; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @var null|string lower_case_table_names value cache |
80
|
|
|
*/ |
81
|
|
|
private $_lower_case_table_names = null; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @var boolean Whether connection is MariaDB |
85
|
|
|
*/ |
86
|
|
|
private $_is_mariadb = false; |
87
|
|
|
/** |
88
|
|
|
* @var boolean Whether connection is Percona |
89
|
|
|
*/ |
90
|
|
|
private $_is_percona = false; |
91
|
|
|
/** |
92
|
|
|
* @var integer Server version as number |
93
|
|
|
*/ |
94
|
|
|
private $_version_int = 55000; |
95
|
|
|
/** |
96
|
|
|
* @var string Server version |
97
|
|
|
*/ |
98
|
|
|
private $_version_str = '5.50.0'; |
99
|
|
|
/** |
100
|
|
|
* @var string Server version comment |
101
|
|
|
*/ |
102
|
|
|
private $_version_comment = ''; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @var Types MySQL types data |
106
|
|
|
*/ |
107
|
|
|
public $types; |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @var Relation |
111
|
|
|
*/ |
112
|
|
|
private $relation; |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Constructor |
116
|
|
|
* |
117
|
|
|
* @param DbiExtension $ext Object to be used for database queries |
118
|
|
|
*/ |
119
|
|
|
public function __construct(DbiExtension $ext) |
120
|
|
|
{ |
121
|
|
|
$this->_extension = $ext; |
122
|
|
|
$this->_links = []; |
123
|
|
|
if (defined('TESTSUITE')) { |
124
|
|
|
$this->_links[DatabaseInterface::CONNECT_USER] = 1; |
125
|
|
|
$this->_links[DatabaseInterface::CONNECT_CONTROL] = 2; |
126
|
|
|
} |
127
|
|
|
$this->_table_cache = []; |
128
|
|
|
$this->_current_user = []; |
129
|
|
|
$this->types = new Types($this); |
130
|
|
|
$this->relation = new Relation($this); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Checks whether database extension is loaded |
135
|
|
|
* |
136
|
|
|
* @param string $extension mysql extension to check |
137
|
|
|
* |
138
|
|
|
* @return bool |
139
|
|
|
*/ |
140
|
|
|
public static function checkDbExtension(string $extension = 'mysqli'): bool |
141
|
|
|
{ |
142
|
|
|
return function_exists($extension . '_connect'); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* runs a query |
147
|
|
|
* |
148
|
|
|
* @param string $query SQL query to execute |
149
|
|
|
* @param mixed $link optional database link to use |
150
|
|
|
* @param int $options optional query options |
151
|
|
|
* @param bool $cache_affected_rows whether to cache affected rows |
152
|
|
|
* |
153
|
|
|
* @return mixed |
154
|
|
|
*/ |
155
|
|
|
public function query( |
156
|
|
|
string $query, |
157
|
|
|
$link = DatabaseInterface::CONNECT_USER, |
158
|
|
|
int $options = 0, |
159
|
|
|
bool $cache_affected_rows = true |
160
|
|
|
) { |
161
|
|
|
$res = $this->tryQuery($query, $link, $options, $cache_affected_rows) |
162
|
|
|
or Util::mysqlDie($this->getError($link), $query); |
163
|
|
|
|
164
|
|
|
return $res; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Get a cached value from table cache. |
169
|
|
|
* |
170
|
|
|
* @param array $contentPath Array of the name of the target value |
171
|
|
|
* @param mixed $default Return value on cache miss |
172
|
|
|
* |
173
|
|
|
* @return mixed cached value or default |
174
|
|
|
*/ |
175
|
|
|
public function getCachedTableContent(array $contentPath, $default = null) |
176
|
|
|
{ |
177
|
|
|
return Util::getValueByKey($this->_table_cache, $contentPath, $default); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Set an item in table cache using dot notation. |
182
|
|
|
* |
183
|
|
|
* @param array|null $contentPath Array with the target path |
184
|
|
|
* @param mixed $value Target value |
185
|
|
|
* |
186
|
|
|
* @return void |
187
|
|
|
*/ |
188
|
|
|
public function cacheTableContent(?array $contentPath, $value): void |
189
|
|
|
{ |
190
|
|
|
$loc = &$this->_table_cache; |
191
|
|
|
|
192
|
|
|
if (! isset($contentPath)) { |
193
|
|
|
$loc = $value; |
194
|
|
|
return; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
while (count($contentPath) > 1) { |
198
|
|
|
$key = array_shift($contentPath); |
199
|
|
|
|
200
|
|
|
// If the key doesn't exist at this depth, we will just create an empty |
201
|
|
|
// array to hold the next value, allowing us to create the arrays to hold |
202
|
|
|
// final values at the correct depth. Then we'll keep digging into the |
203
|
|
|
// array. |
204
|
|
|
if (! isset($loc[$key]) || ! is_array($loc[$key])) { |
205
|
|
|
$loc[$key] = []; |
206
|
|
|
} |
207
|
|
|
$loc = &$loc[$key]; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
$loc[array_shift($contentPath)] = $value; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Clear the table cache. |
215
|
|
|
* |
216
|
|
|
* @return void |
217
|
|
|
*/ |
218
|
|
|
public function clearTableCache(): void |
219
|
|
|
{ |
220
|
|
|
$this->_table_cache = []; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Caches table data so Table does not require to issue |
225
|
|
|
* SHOW TABLE STATUS again |
226
|
|
|
* |
227
|
|
|
* @param array $tables information for tables of some databases |
228
|
|
|
* @param string|bool $table table name |
229
|
|
|
* |
230
|
|
|
* @return void |
231
|
|
|
*/ |
232
|
|
|
private function _cacheTableData(array $tables, $table): void |
233
|
|
|
{ |
234
|
|
|
// Note: I don't see why we would need array_merge_recursive() here, |
235
|
|
|
// as it creates double entries for the same table (for example a double |
236
|
|
|
// entry for Comment when changing the storage engine in Operations) |
237
|
|
|
// Note 2: Instead of array_merge(), simply use the + operator because |
238
|
|
|
// array_merge() renumbers numeric keys starting with 0, therefore |
239
|
|
|
// we would lose a db name that consists only of numbers |
240
|
|
|
|
241
|
|
|
foreach ($tables as $one_database => $its_tables) { |
242
|
|
|
if (isset($this->_table_cache[$one_database])) { |
243
|
|
|
// the + operator does not do the intended effect |
244
|
|
|
// when the cache for one table already exists |
245
|
|
|
if ($table |
246
|
|
|
&& isset($this->_table_cache[$one_database][$table]) |
247
|
|
|
) { |
248
|
|
|
unset($this->_table_cache[$one_database][$table]); |
249
|
|
|
} |
250
|
|
|
$this->_table_cache[$one_database] |
251
|
|
|
+= $tables[$one_database]; |
252
|
|
|
} else { |
253
|
|
|
$this->_table_cache[$one_database] = $tables[$one_database]; |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Stores query data into session data for debugging purposes |
260
|
|
|
* |
261
|
|
|
* @param string $query Query text |
262
|
|
|
* @param mixed $link link type |
263
|
|
|
* @param object|boolean $result Query result |
264
|
|
|
* @param integer|float $time Time to execute query |
265
|
|
|
* |
266
|
|
|
* @return void |
267
|
|
|
*/ |
268
|
|
|
private function _dbgQuery(string $query, $link, $result, $time): void |
269
|
|
|
{ |
270
|
|
|
$dbgInfo = []; |
271
|
|
|
$error_message = $this->getError($link); |
272
|
|
|
if ($result == false && is_string($error_message)) { |
273
|
|
|
$dbgInfo['error'] |
274
|
|
|
= '<span class="color_red">' |
275
|
|
|
. htmlspecialchars($error_message) . '</span>'; |
276
|
|
|
} |
277
|
|
|
$dbgInfo['query'] = htmlspecialchars($query); |
278
|
|
|
$dbgInfo['time'] = $time; |
279
|
|
|
// Get and slightly format backtrace, this is used |
280
|
|
|
// in the javascript console. |
281
|
|
|
// Strip call to _dbgQuery |
282
|
|
|
$dbgInfo['trace'] = Error::processBacktrace( |
283
|
|
|
array_slice(debug_backtrace(), 1) |
284
|
|
|
); |
285
|
|
|
$dbgInfo['hash'] = md5($query); |
286
|
|
|
|
287
|
|
|
$_SESSION['debug']['queries'][] = $dbgInfo; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* runs a query and returns the result |
292
|
|
|
* |
293
|
|
|
* @param string $query query to run |
294
|
|
|
* @param mixed $link link type |
295
|
|
|
* @param integer $options query options |
296
|
|
|
* @param bool $cache_affected_rows whether to cache affected row |
297
|
|
|
* |
298
|
|
|
* @return mixed |
299
|
|
|
*/ |
300
|
|
|
public function tryQuery( |
301
|
|
|
string $query, |
302
|
|
|
$link = DatabaseInterface::CONNECT_USER, |
303
|
|
|
int $options = 0, |
304
|
|
|
bool $cache_affected_rows = true |
305
|
|
|
) { |
306
|
|
|
$debug = $GLOBALS['cfg']['DBG']['sql']; |
307
|
|
|
if (! isset($this->_links[$link])) { |
308
|
|
|
return false; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$time = 0; |
312
|
|
|
if ($debug) { |
313
|
|
|
$time = microtime(true); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
$result = $this->_extension->realQuery($query, $this->_links[$link], $options); |
317
|
|
|
|
318
|
|
|
if ($cache_affected_rows) { |
319
|
|
|
$GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
if ($debug) { |
323
|
|
|
$time = microtime(true) - $time; |
324
|
|
|
$this->_dbgQuery($query, $link, $result, $time); |
325
|
|
|
if ($GLOBALS['cfg']['DBG']['sqllog']) { |
326
|
|
|
$warningsCount = ''; |
327
|
|
|
if ($options & DatabaseInterface::QUERY_STORE == DatabaseInterface::QUERY_STORE) { |
328
|
|
|
if (isset($this->_links[$link]->warning_count)) { |
329
|
|
|
$warningsCount = $this->_links[$link]->warning_count; |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
openlog('phpMyAdmin', LOG_NDELAY | LOG_PID, LOG_USER); |
334
|
|
|
|
335
|
|
|
syslog( |
336
|
|
|
LOG_INFO, |
337
|
|
|
'SQL[' . basename($_SERVER['SCRIPT_NAME']) . ']: ' |
338
|
|
|
. sprintf('%0.3f', $time) . '(W:' . $warningsCount . ') > ' . $query |
339
|
|
|
); |
340
|
|
|
closelog(); |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
if ($result !== false && Tracker::isActive()) { |
345
|
|
|
Tracker::handleQuery($query); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
return $result; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Run multi query statement and return results |
353
|
|
|
* |
354
|
|
|
* @param string $multiQuery multi query statement to execute |
355
|
|
|
* @param int $linkIndex index of the opened database link |
356
|
|
|
* |
357
|
|
|
* @return mysqli_result[]|boolean (false) |
358
|
|
|
*/ |
359
|
|
|
public function tryMultiQuery( |
360
|
|
|
string $multiQuery = '', |
361
|
|
|
$linkIndex = DatabaseInterface::CONNECT_USER |
362
|
|
|
) { |
363
|
|
|
if (! isset($this->_links[$linkIndex])) { |
364
|
|
|
return false; |
365
|
|
|
} |
366
|
|
|
return $this->_extension->realMultiQuery($this->_links[$linkIndex], $multiQuery); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* returns array with table names for given db |
371
|
|
|
* |
372
|
|
|
* @param string $database name of database |
373
|
|
|
* @param mixed $link mysql link resource|object |
374
|
|
|
* |
375
|
|
|
* @return array tables names |
376
|
|
|
*/ |
377
|
|
|
public function getTables(string $database, $link = DatabaseInterface::CONNECT_USER): array |
378
|
|
|
{ |
379
|
|
|
$tables = $this->fetchResult( |
380
|
|
|
'SHOW TABLES FROM ' . Util::backquote($database) . ';', |
381
|
|
|
null, |
382
|
|
|
0, |
383
|
|
|
$link, |
384
|
|
|
self::QUERY_STORE |
385
|
|
|
); |
386
|
|
|
if ($GLOBALS['cfg']['NaturalOrder']) { |
387
|
|
|
usort($tables, 'strnatcasecmp'); |
388
|
|
|
} |
389
|
|
|
return $tables; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* returns |
395
|
|
|
* |
396
|
|
|
* @param string $database name of database |
397
|
|
|
* @param array $tables list of tables to search for for relations |
398
|
|
|
* @param int $link mysql link resource|object |
399
|
|
|
* |
400
|
|
|
* @return array array of found foreign keys |
401
|
|
|
*/ |
402
|
|
|
public function getForeignKeyConstrains(string $database, array $tables, $link = DatabaseInterface::CONNECT_USER): array |
403
|
|
|
{ |
404
|
|
|
$tablesListForQuery = ''; |
405
|
|
|
foreach ($tables as $table) { |
406
|
|
|
$tablesListForQuery .= "'" . $this->escapeString($table) . "',"; |
407
|
|
|
} |
408
|
|
|
$tablesListForQuery = rtrim($tablesListForQuery, ','); |
409
|
|
|
|
410
|
|
|
$foreignKeyConstrains = $this->fetchResult( |
411
|
|
|
"SELECT" |
412
|
|
|
. " TABLE_NAME," |
413
|
|
|
. " COLUMN_NAME," |
414
|
|
|
. " REFERENCED_TABLE_NAME," |
415
|
|
|
. " REFERENCED_COLUMN_NAME" |
416
|
|
|
. " FROM information_schema.key_column_usage" |
417
|
|
|
. " WHERE referenced_table_name IS NOT NULL" |
418
|
|
|
. " AND TABLE_SCHEMA = '" . $this->escapeString($database) . "'" |
419
|
|
|
. " AND TABLE_NAME IN (" . $tablesListForQuery . ")" |
420
|
|
|
. " AND REFERENCED_TABLE_NAME IN (" . $tablesListForQuery . ");", |
421
|
|
|
null, |
422
|
|
|
null, |
423
|
|
|
$link, |
424
|
|
|
self::QUERY_STORE |
425
|
|
|
); |
426
|
|
|
return $foreignKeyConstrains; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* returns a segment of the SQL WHERE clause regarding table name and type |
431
|
|
|
* |
432
|
|
|
* @param array|string $table table(s) |
433
|
|
|
* @param boolean $tbl_is_group $table is a table group |
434
|
|
|
* @param string $table_type whether table or view |
435
|
|
|
* |
436
|
|
|
* @return string a segment of the WHERE clause |
437
|
|
|
*/ |
438
|
|
|
private function _getTableCondition( |
439
|
|
|
$table, |
440
|
|
|
bool $tbl_is_group, |
441
|
|
|
?string $table_type |
442
|
|
|
): string { |
443
|
|
|
// get table information from information_schema |
444
|
|
|
if ($table) { |
445
|
|
|
if (is_array($table)) { |
446
|
|
|
$sql_where_table = 'AND t.`TABLE_NAME` ' |
447
|
|
|
. Util::getCollateForIS() . ' IN (\'' |
448
|
|
|
. implode( |
449
|
|
|
'\', \'', |
450
|
|
|
array_map( |
451
|
|
|
[ |
452
|
|
|
$this, |
453
|
|
|
'escapeString', |
454
|
|
|
], |
455
|
|
|
$table |
456
|
|
|
) |
457
|
|
|
) |
458
|
|
|
. '\')'; |
459
|
|
|
} elseif (true === $tbl_is_group) { |
460
|
|
|
$sql_where_table = 'AND t.`TABLE_NAME` LIKE \'' |
461
|
|
|
. Util::escapeMysqlWildcards( |
462
|
|
|
$this->escapeString($table) |
463
|
|
|
) |
464
|
|
|
. '%\''; |
465
|
|
|
} else { |
466
|
|
|
$sql_where_table = 'AND t.`TABLE_NAME` ' |
467
|
|
|
. Util::getCollateForIS() . ' = \'' |
468
|
|
|
. $this->escapeString($table) . '\''; |
469
|
|
|
} |
470
|
|
|
} else { |
471
|
|
|
$sql_where_table = ''; |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
if ($table_type) { |
475
|
|
|
if ($table_type == 'view') { |
476
|
|
|
$sql_where_table .= " AND t.`TABLE_TYPE` NOT IN ('BASE TABLE', 'SYSTEM VERSIONED')"; |
477
|
|
|
} elseif ($table_type == 'table') { |
478
|
|
|
$sql_where_table .= " AND t.`TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')"; |
479
|
|
|
} |
480
|
|
|
} |
481
|
|
|
return $sql_where_table; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* returns the beginning of the SQL statement to fetch the list of tables |
486
|
|
|
* |
487
|
|
|
* @param string[] $this_databases databases to list |
488
|
|
|
* @param string $sql_where_table additional condition |
489
|
|
|
* |
490
|
|
|
* @return string the SQL statement |
491
|
|
|
*/ |
492
|
|
|
private function _getSqlForTablesFull($this_databases, string $sql_where_table): string |
493
|
|
|
{ |
494
|
|
|
return ' |
495
|
|
|
SELECT *, |
496
|
|
|
`TABLE_SCHEMA` AS `Db`, |
497
|
|
|
`TABLE_NAME` AS `Name`, |
498
|
|
|
`TABLE_TYPE` AS `TABLE_TYPE`, |
499
|
|
|
`ENGINE` AS `Engine`, |
500
|
|
|
`ENGINE` AS `Type`, |
501
|
|
|
`VERSION` AS `Version`, |
502
|
|
|
`ROW_FORMAT` AS `Row_format`, |
503
|
|
|
`TABLE_ROWS` AS `Rows`, |
504
|
|
|
`AVG_ROW_LENGTH` AS `Avg_row_length`, |
505
|
|
|
`DATA_LENGTH` AS `Data_length`, |
506
|
|
|
`MAX_DATA_LENGTH` AS `Max_data_length`, |
507
|
|
|
`INDEX_LENGTH` AS `Index_length`, |
508
|
|
|
`DATA_FREE` AS `Data_free`, |
509
|
|
|
`AUTO_INCREMENT` AS `Auto_increment`, |
510
|
|
|
`CREATE_TIME` AS `Create_time`, |
511
|
|
|
`UPDATE_TIME` AS `Update_time`, |
512
|
|
|
`CHECK_TIME` AS `Check_time`, |
513
|
|
|
`TABLE_COLLATION` AS `Collation`, |
514
|
|
|
`CHECKSUM` AS `Checksum`, |
515
|
|
|
`CREATE_OPTIONS` AS `Create_options`, |
516
|
|
|
`TABLE_COMMENT` AS `Comment` |
517
|
|
|
FROM `information_schema`.`TABLES` t |
518
|
|
|
WHERE `TABLE_SCHEMA` ' . Util::getCollateForIS() . ' |
519
|
|
|
IN (\'' . implode("', '", $this_databases) . '\') |
520
|
|
|
' . $sql_where_table; |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
/** |
524
|
|
|
* returns array of all tables in given db or dbs |
525
|
|
|
* this function expects unquoted names: |
526
|
|
|
* RIGHT: my_database |
527
|
|
|
* WRONG: `my_database` |
528
|
|
|
* WRONG: my\_database |
529
|
|
|
* if $tbl_is_group is true, $table is used as filter for table names |
530
|
|
|
* |
531
|
|
|
* <code> |
532
|
|
|
* $dbi->getTablesFull('my_database'); |
533
|
|
|
* $dbi->getTablesFull('my_database', 'my_table')); |
534
|
|
|
* $dbi->getTablesFull('my_database', 'my_tables_', true)); |
535
|
|
|
* </code> |
536
|
|
|
* |
537
|
|
|
* @param string $database database |
538
|
|
|
* @param string|array $table table name(s) |
539
|
|
|
* @param boolean $tbl_is_group $table is a table group |
540
|
|
|
* @param integer $limit_offset zero-based offset for the count |
541
|
|
|
* @param boolean|integer $limit_count number of tables to return |
542
|
|
|
* @param string $sort_by table attribute to sort by |
543
|
|
|
* @param string $sort_order direction to sort (ASC or DESC) |
544
|
|
|
* @param string $table_type whether table or view |
545
|
|
|
* @param mixed $link link type |
546
|
|
|
* |
547
|
|
|
* @todo move into Table |
548
|
|
|
* |
549
|
|
|
* @return array list of tables in given db(s) |
550
|
|
|
*/ |
551
|
|
|
public function getTablesFull( |
552
|
|
|
string $database, |
553
|
|
|
$table = '', |
554
|
|
|
bool $tbl_is_group = false, |
555
|
|
|
int $limit_offset = 0, |
556
|
|
|
$limit_count = false, |
557
|
|
|
string $sort_by = 'Name', |
558
|
|
|
string $sort_order = 'ASC', |
559
|
|
|
?string $table_type = null, |
560
|
|
|
$link = DatabaseInterface::CONNECT_USER |
561
|
|
|
): array { |
562
|
|
|
if (true === $limit_count) { |
563
|
|
|
$limit_count = $GLOBALS['cfg']['MaxTableList']; |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
$databases = [$database]; |
567
|
|
|
|
568
|
|
|
$tables = []; |
569
|
|
|
|
570
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
571
|
|
|
$sql_where_table = $this->_getTableCondition( |
572
|
|
|
$table, |
573
|
|
|
$tbl_is_group, |
574
|
|
|
$table_type |
575
|
|
|
); |
576
|
|
|
|
577
|
|
|
// for PMA bc: |
578
|
|
|
// `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME` |
579
|
|
|
// |
580
|
|
|
// on non-Windows servers, |
581
|
|
|
// added BINARY in the WHERE clause to force a case sensitive |
582
|
|
|
// comparison (if we are looking for the db Aa we don't want |
583
|
|
|
// to find the db aa) |
584
|
|
|
$this_databases = array_map( |
585
|
|
|
[ |
586
|
|
|
$this, |
587
|
|
|
'escapeString', |
588
|
|
|
], |
589
|
|
|
$databases |
590
|
|
|
); |
591
|
|
|
|
592
|
|
|
$sql = $this->_getSqlForTablesFull($this_databases, $sql_where_table); |
593
|
|
|
|
594
|
|
|
// Sort the tables |
595
|
|
|
$sql .= " ORDER BY $sort_by $sort_order"; |
596
|
|
|
|
597
|
|
|
if ($limit_count) { |
598
|
|
|
$sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
$tables = $this->fetchResult( |
602
|
|
|
$sql, |
603
|
|
|
[ |
604
|
|
|
'TABLE_SCHEMA', |
605
|
|
|
'TABLE_NAME', |
606
|
|
|
], |
607
|
|
|
null, |
608
|
|
|
$link |
609
|
|
|
); |
610
|
|
|
|
611
|
|
|
if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { |
612
|
|
|
// here, the array's first key is by schema name |
613
|
|
|
foreach ($tables as $one_database_name => $one_database_tables) { |
614
|
|
|
uksort($one_database_tables, 'strnatcasecmp'); |
615
|
|
|
|
616
|
|
|
if ($sort_order == 'DESC') { |
617
|
|
|
$one_database_tables = array_reverse($one_database_tables); |
618
|
|
|
} |
619
|
|
|
$tables[$one_database_name] = $one_database_tables; |
620
|
|
|
} |
621
|
|
|
} elseif ($sort_by == 'Data_length') { |
622
|
|
|
// Size = Data_length + Index_length |
623
|
|
|
foreach ($tables as $one_database_name => $one_database_tables) { |
624
|
|
|
uasort( |
625
|
|
|
$one_database_tables, |
626
|
|
|
function ($a, $b) { |
627
|
|
|
$aLength = $a['Data_length'] + $a['Index_length']; |
628
|
|
|
$bLength = $b['Data_length'] + $b['Index_length']; |
629
|
|
|
return $aLength <=> $bLength; |
630
|
|
|
} |
631
|
|
|
); |
632
|
|
|
|
633
|
|
|
if ($sort_order == 'DESC') { |
634
|
|
|
$one_database_tables = array_reverse($one_database_tables); |
635
|
|
|
} |
636
|
|
|
$tables[$one_database_name] = $one_database_tables; |
637
|
|
|
} |
638
|
|
|
} |
639
|
|
|
} // end (get information from table schema) |
640
|
|
|
|
641
|
|
|
// If permissions are wrong on even one database directory, |
642
|
|
|
// information_schema does not return any table info for any database |
643
|
|
|
// this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002 |
644
|
|
|
if (empty($tables)) { |
645
|
|
|
foreach ($databases as $each_database) { |
646
|
|
|
if ($table || (true === $tbl_is_group) || ! empty($table_type)) { |
647
|
|
|
$sql = 'SHOW TABLE STATUS FROM ' |
648
|
|
|
. Util::backquote($each_database) |
649
|
|
|
. ' WHERE'; |
650
|
|
|
$needAnd = false; |
651
|
|
|
if ($table || (true === $tbl_is_group)) { |
652
|
|
|
if (is_array($table)) { |
653
|
|
|
$sql .= ' `Name` IN (\'' |
654
|
|
|
. implode( |
655
|
|
|
'\', \'', |
656
|
|
|
array_map( |
657
|
|
|
[ |
658
|
|
|
$this, |
659
|
|
|
'escapeString', |
660
|
|
|
], |
661
|
|
|
$table, |
662
|
|
|
$link |
|
|
|
|
663
|
|
|
) |
664
|
|
|
) . '\')'; |
665
|
|
|
} else { |
666
|
|
|
$sql .= " `Name` LIKE '" |
667
|
|
|
. Util::escapeMysqlWildcards( |
668
|
|
|
$this->escapeString($table, $link) |
669
|
|
|
) |
670
|
|
|
. "%'"; |
671
|
|
|
} |
672
|
|
|
$needAnd = true; |
673
|
|
|
} |
674
|
|
|
if (! empty($table_type)) { |
675
|
|
|
if ($needAnd) { |
676
|
|
|
$sql .= " AND"; |
677
|
|
|
} |
678
|
|
|
if ($table_type == 'view') { |
679
|
|
|
$sql .= " `Comment` = 'VIEW'"; |
680
|
|
|
} elseif ($table_type == 'table') { |
681
|
|
|
$sql .= " `Comment` != 'VIEW'"; |
682
|
|
|
} |
683
|
|
|
} |
684
|
|
|
} else { |
685
|
|
|
$sql = 'SHOW TABLE STATUS FROM ' |
686
|
|
|
. Util::backquote($each_database); |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
$each_tables = $this->fetchResult($sql, 'Name', null, $link); |
690
|
|
|
|
691
|
|
|
// Sort naturally if the config allows it and we're sorting |
692
|
|
|
// the Name column. |
693
|
|
|
if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) { |
694
|
|
|
uksort($each_tables, 'strnatcasecmp'); |
695
|
|
|
|
696
|
|
|
if ($sort_order == 'DESC') { |
697
|
|
|
$each_tables = array_reverse($each_tables); |
698
|
|
|
} |
699
|
|
|
} else { |
700
|
|
|
// Prepare to sort by creating array of the selected sort |
701
|
|
|
// value to pass to array_multisort |
702
|
|
|
|
703
|
|
|
// Size = Data_length + Index_length |
704
|
|
|
if ($sort_by == 'Data_length') { |
705
|
|
|
foreach ($each_tables as $table_name => $table_data) { |
706
|
|
|
${$sort_by}[$table_name] = strtolower( |
707
|
|
|
$table_data['Data_length'] |
708
|
|
|
+ $table_data['Index_length'] |
709
|
|
|
); |
710
|
|
|
} |
711
|
|
|
} else { |
712
|
|
|
foreach ($each_tables as $table_name => $table_data) { |
713
|
|
|
${$sort_by}[$table_name] |
714
|
|
|
= strtolower($table_data[$sort_by]); |
715
|
|
|
} |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
if (! empty($$sort_by)) { |
719
|
|
|
if ($sort_order == 'DESC') { |
720
|
|
|
array_multisort($$sort_by, SORT_DESC, $each_tables); |
721
|
|
|
} else { |
722
|
|
|
array_multisort($$sort_by, SORT_ASC, $each_tables); |
723
|
|
|
} |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
// cleanup the temporary sort array |
727
|
|
|
unset($$sort_by); |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
if ($limit_count) { |
731
|
|
|
$each_tables = array_slice( |
732
|
|
|
$each_tables, |
733
|
|
|
$limit_offset, |
734
|
|
|
$limit_count |
735
|
|
|
); |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
foreach ($each_tables as $table_name => $each_table) { |
739
|
|
|
if (! isset($each_tables[$table_name]['Type']) |
740
|
|
|
&& isset($each_tables[$table_name]['Engine']) |
741
|
|
|
) { |
742
|
|
|
// pma BC, same parts of PMA still uses 'Type' |
743
|
|
|
$each_tables[$table_name]['Type'] |
744
|
|
|
=& $each_tables[$table_name]['Engine']; |
745
|
|
|
} elseif (! isset($each_tables[$table_name]['Engine']) |
746
|
|
|
&& isset($each_tables[$table_name]['Type']) |
747
|
|
|
) { |
748
|
|
|
// old MySQL reports Type, newer MySQL reports Engine |
749
|
|
|
$each_tables[$table_name]['Engine'] |
750
|
|
|
=& $each_tables[$table_name]['Type']; |
751
|
|
|
} |
752
|
|
|
|
753
|
|
|
// Compatibility with INFORMATION_SCHEMA output |
754
|
|
|
$each_tables[$table_name]['TABLE_SCHEMA'] |
755
|
|
|
= $each_database; |
756
|
|
|
$each_tables[$table_name]['TABLE_NAME'] |
757
|
|
|
=& $each_tables[$table_name]['Name']; |
758
|
|
|
$each_tables[$table_name]['ENGINE'] |
759
|
|
|
=& $each_tables[$table_name]['Engine']; |
760
|
|
|
$each_tables[$table_name]['VERSION'] |
761
|
|
|
=& $each_tables[$table_name]['Version']; |
762
|
|
|
$each_tables[$table_name]['ROW_FORMAT'] |
763
|
|
|
=& $each_tables[$table_name]['Row_format']; |
764
|
|
|
$each_tables[$table_name]['TABLE_ROWS'] |
765
|
|
|
=& $each_tables[$table_name]['Rows']; |
766
|
|
|
$each_tables[$table_name]['AVG_ROW_LENGTH'] |
767
|
|
|
=& $each_tables[$table_name]['Avg_row_length']; |
768
|
|
|
$each_tables[$table_name]['DATA_LENGTH'] |
769
|
|
|
=& $each_tables[$table_name]['Data_length']; |
770
|
|
|
$each_tables[$table_name]['MAX_DATA_LENGTH'] |
771
|
|
|
=& $each_tables[$table_name]['Max_data_length']; |
772
|
|
|
$each_tables[$table_name]['INDEX_LENGTH'] |
773
|
|
|
=& $each_tables[$table_name]['Index_length']; |
774
|
|
|
$each_tables[$table_name]['DATA_FREE'] |
775
|
|
|
=& $each_tables[$table_name]['Data_free']; |
776
|
|
|
$each_tables[$table_name]['AUTO_INCREMENT'] |
777
|
|
|
=& $each_tables[$table_name]['Auto_increment']; |
778
|
|
|
$each_tables[$table_name]['CREATE_TIME'] |
779
|
|
|
=& $each_tables[$table_name]['Create_time']; |
780
|
|
|
$each_tables[$table_name]['UPDATE_TIME'] |
781
|
|
|
=& $each_tables[$table_name]['Update_time']; |
782
|
|
|
$each_tables[$table_name]['CHECK_TIME'] |
783
|
|
|
=& $each_tables[$table_name]['Check_time']; |
784
|
|
|
$each_tables[$table_name]['TABLE_COLLATION'] |
785
|
|
|
=& $each_tables[$table_name]['Collation']; |
786
|
|
|
$each_tables[$table_name]['CHECKSUM'] |
787
|
|
|
=& $each_tables[$table_name]['Checksum']; |
788
|
|
|
$each_tables[$table_name]['CREATE_OPTIONS'] |
789
|
|
|
=& $each_tables[$table_name]['Create_options']; |
790
|
|
|
$each_tables[$table_name]['TABLE_COMMENT'] |
791
|
|
|
=& $each_tables[$table_name]['Comment']; |
792
|
|
|
|
793
|
|
|
if (strtoupper($each_tables[$table_name]['Comment']) === 'VIEW' |
794
|
|
|
&& $each_tables[$table_name]['Engine'] == null |
795
|
|
|
) { |
796
|
|
|
$each_tables[$table_name]['TABLE_TYPE'] = 'VIEW'; |
797
|
|
|
} elseif ($each_database == 'information_schema') { |
798
|
|
|
$each_tables[$table_name]['TABLE_TYPE'] = 'SYSTEM VIEW'; |
799
|
|
|
} else { |
800
|
|
|
/** |
801
|
|
|
* @todo difference between 'TEMPORARY' and 'BASE TABLE' |
802
|
|
|
* but how to detect? |
803
|
|
|
*/ |
804
|
|
|
$each_tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE'; |
805
|
|
|
} |
806
|
|
|
} |
807
|
|
|
|
808
|
|
|
$tables[$each_database] = $each_tables; |
809
|
|
|
} |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
// cache table data |
813
|
|
|
// so Table does not require to issue SHOW TABLE STATUS again |
814
|
|
|
$this->_cacheTableData($tables, $table); |
|
|
|
|
815
|
|
|
|
816
|
|
|
if (isset($tables[$database])) { |
817
|
|
|
return $tables[$database]; |
818
|
|
|
} |
819
|
|
|
|
820
|
|
|
if (isset($tables[mb_strtolower($database)])) { |
821
|
|
|
// on windows with lower_case_table_names = 1 |
822
|
|
|
// MySQL returns |
823
|
|
|
// with SHOW DATABASES or information_schema.SCHEMATA: `Test` |
824
|
|
|
// but information_schema.TABLES gives `test` |
825
|
|
|
// see https://github.com/phpmyadmin/phpmyadmin/issues/8402 |
826
|
|
|
return $tables[mb_strtolower($database)]; |
827
|
|
|
} |
828
|
|
|
|
829
|
|
|
return $tables; |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
/** |
833
|
|
|
* Get VIEWs in a particular database |
834
|
|
|
* |
835
|
|
|
* @param string $db Database name to look in |
836
|
|
|
* |
837
|
|
|
* @return array Set of VIEWs inside the database |
838
|
|
|
*/ |
839
|
|
|
public function getVirtualTables(string $db): array |
840
|
|
|
{ |
841
|
|
|
$tables_full = $this->getTablesFull($db); |
842
|
|
|
$views = []; |
843
|
|
|
|
844
|
|
|
foreach ($tables_full as $table => $tmp) { |
845
|
|
|
$_table = $this->getTable($db, (string) $table); |
846
|
|
|
if ($_table->isView()) { |
847
|
|
|
$views[] = $table; |
848
|
|
|
} |
849
|
|
|
} |
850
|
|
|
|
851
|
|
|
return $views; |
852
|
|
|
} |
853
|
|
|
|
854
|
|
|
|
855
|
|
|
/** |
856
|
|
|
* returns array with databases containing extended infos about them |
857
|
|
|
* |
858
|
|
|
* @param string $database database |
859
|
|
|
* @param boolean $force_stats retrieve stats also for MySQL < 5 |
860
|
|
|
* @param integer $link link type |
861
|
|
|
* @param string $sort_by column to order by |
862
|
|
|
* @param string $sort_order ASC or DESC |
863
|
|
|
* @param integer $limit_offset starting offset for LIMIT |
864
|
|
|
* @param bool|int $limit_count row count for LIMIT or true |
865
|
|
|
* for $GLOBALS['cfg']['MaxDbList'] |
866
|
|
|
* |
867
|
|
|
* @todo move into ListDatabase? |
868
|
|
|
* |
869
|
|
|
* @return array |
870
|
|
|
*/ |
871
|
|
|
public function getDatabasesFull( |
872
|
|
|
?string $database = null, |
873
|
|
|
bool $force_stats = false, |
874
|
|
|
$link = DatabaseInterface::CONNECT_USER, |
875
|
|
|
string $sort_by = 'SCHEMA_NAME', |
876
|
|
|
string $sort_order = 'ASC', |
877
|
|
|
int $limit_offset = 0, |
878
|
|
|
$limit_count = false |
879
|
|
|
): array { |
880
|
|
|
$sort_order = strtoupper($sort_order); |
881
|
|
|
|
882
|
|
|
if (true === $limit_count) { |
883
|
|
|
$limit_count = $GLOBALS['cfg']['MaxDbList']; |
884
|
|
|
} |
885
|
|
|
|
886
|
|
|
$apply_limit_and_order_manual = true; |
887
|
|
|
|
888
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
889
|
|
|
/** |
890
|
|
|
* if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT |
891
|
|
|
* cause MySQL does not support natural ordering, |
892
|
|
|
* we have to do it afterward |
893
|
|
|
*/ |
894
|
|
|
$limit = ''; |
895
|
|
|
if (! $GLOBALS['cfg']['NaturalOrder']) { |
896
|
|
|
if ($limit_count) { |
897
|
|
|
$limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset; |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
$apply_limit_and_order_manual = false; |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
// get table information from information_schema |
904
|
|
|
if (! empty($database)) { |
905
|
|
|
$sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \'' |
906
|
|
|
. $this->escapeString($database, $link) . '\''; |
907
|
|
|
} else { |
908
|
|
|
$sql_where_schema = ''; |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
$sql = 'SELECT *, |
912
|
|
|
CAST(BIN_NAME AS CHAR CHARACTER SET utf8) AS SCHEMA_NAME |
913
|
|
|
FROM ('; |
914
|
|
|
$sql .= 'SELECT |
915
|
|
|
BINARY s.SCHEMA_NAME AS BIN_NAME, |
916
|
|
|
s.DEFAULT_COLLATION_NAME'; |
917
|
|
|
if ($force_stats) { |
918
|
|
|
$sql .= ', |
919
|
|
|
COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES, |
920
|
|
|
SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS, |
921
|
|
|
SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH, |
922
|
|
|
SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH, |
923
|
|
|
SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH, |
924
|
|
|
SUM(t.DATA_LENGTH + t.INDEX_LENGTH) |
925
|
|
|
AS SCHEMA_LENGTH, |
926
|
|
|
SUM(IF(t.ENGINE <> \'InnoDB\', t.DATA_FREE, 0)) |
927
|
|
|
AS SCHEMA_DATA_FREE'; |
928
|
|
|
} |
929
|
|
|
$sql .= ' |
930
|
|
|
FROM `information_schema`.SCHEMATA s '; |
931
|
|
|
if ($force_stats) { |
932
|
|
|
$sql .= ' |
933
|
|
|
LEFT JOIN `information_schema`.TABLES t |
934
|
|
|
ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME'; |
935
|
|
|
} |
936
|
|
|
$sql .= $sql_where_schema . ' |
937
|
|
|
GROUP BY BINARY s.SCHEMA_NAME, s.DEFAULT_COLLATION_NAME |
938
|
|
|
ORDER BY '; |
939
|
|
|
if ($sort_by == 'SCHEMA_NAME' |
940
|
|
|
|| $sort_by == 'DEFAULT_COLLATION_NAME' |
941
|
|
|
) { |
942
|
|
|
$sql .= 'BINARY '; |
943
|
|
|
} |
944
|
|
|
$sql .= Util::backquote($sort_by) |
945
|
|
|
. ' ' . $sort_order |
946
|
|
|
. $limit; |
947
|
|
|
$sql .= ') a'; |
948
|
|
|
|
949
|
|
|
$databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link); |
950
|
|
|
|
951
|
|
|
$mysql_error = $this->getError($link); |
952
|
|
|
if (! count($databases) && $GLOBALS['errno']) { |
953
|
|
|
Util::mysqlDie($mysql_error, $sql); |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
// display only databases also in official database list |
957
|
|
|
// f.e. to apply hide_db and only_db |
958
|
|
|
$drops = array_diff( |
959
|
|
|
array_keys($databases), |
960
|
|
|
(array) $GLOBALS['dblist']->databases |
961
|
|
|
); |
962
|
|
|
foreach ($drops as $drop) { |
963
|
|
|
unset($databases[$drop]); |
964
|
|
|
} |
965
|
|
|
} else { |
966
|
|
|
$databases = []; |
967
|
|
|
foreach ($GLOBALS['dblist']->databases as $database_name) { |
968
|
|
|
// Compatibility with INFORMATION_SCHEMA output |
969
|
|
|
$databases[$database_name]['SCHEMA_NAME'] = $database_name; |
970
|
|
|
|
971
|
|
|
$databases[$database_name]['DEFAULT_COLLATION_NAME'] |
972
|
|
|
= $this->getDbCollation($database_name); |
973
|
|
|
|
974
|
|
|
if (! $force_stats) { |
975
|
|
|
continue; |
976
|
|
|
} |
977
|
|
|
|
978
|
|
|
// get additional info about tables |
979
|
|
|
$databases[$database_name]['SCHEMA_TABLES'] = 0; |
980
|
|
|
$databases[$database_name]['SCHEMA_TABLE_ROWS'] = 0; |
981
|
|
|
$databases[$database_name]['SCHEMA_DATA_LENGTH'] = 0; |
982
|
|
|
$databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] = 0; |
983
|
|
|
$databases[$database_name]['SCHEMA_INDEX_LENGTH'] = 0; |
984
|
|
|
$databases[$database_name]['SCHEMA_LENGTH'] = 0; |
985
|
|
|
$databases[$database_name]['SCHEMA_DATA_FREE'] = 0; |
986
|
|
|
|
987
|
|
|
$res = $this->query( |
988
|
|
|
'SHOW TABLE STATUS FROM ' |
989
|
|
|
. Util::backquote($database_name) . ';' |
|
|
|
|
990
|
|
|
); |
991
|
|
|
|
992
|
|
|
if ($res === false) { |
993
|
|
|
unset($res); |
994
|
|
|
continue; |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
while ($row = $this->fetchAssoc($res)) { |
998
|
|
|
$databases[$database_name]['SCHEMA_TABLES']++; |
999
|
|
|
$databases[$database_name]['SCHEMA_TABLE_ROWS'] |
1000
|
|
|
+= $row['Rows']; |
1001
|
|
|
$databases[$database_name]['SCHEMA_DATA_LENGTH'] |
1002
|
|
|
+= $row['Data_length']; |
1003
|
|
|
$databases[$database_name]['SCHEMA_MAX_DATA_LENGTH'] |
1004
|
|
|
+= $row['Max_data_length']; |
1005
|
|
|
$databases[$database_name]['SCHEMA_INDEX_LENGTH'] |
1006
|
|
|
+= $row['Index_length']; |
1007
|
|
|
|
1008
|
|
|
// for InnoDB, this does not contain the number of |
1009
|
|
|
// overhead bytes but the total free space |
1010
|
|
|
if ('InnoDB' != $row['Engine']) { |
1011
|
|
|
$databases[$database_name]['SCHEMA_DATA_FREE'] |
1012
|
|
|
+= $row['Data_free']; |
1013
|
|
|
} |
1014
|
|
|
$databases[$database_name]['SCHEMA_LENGTH'] |
1015
|
|
|
+= $row['Data_length'] + $row['Index_length']; |
1016
|
|
|
} |
1017
|
|
|
$this->freeResult($res); |
1018
|
|
|
unset($res); |
1019
|
|
|
} |
1020
|
|
|
} |
1021
|
|
|
|
1022
|
|
|
/** |
1023
|
|
|
* apply limit and order manually now |
1024
|
|
|
* (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder']) |
1025
|
|
|
*/ |
1026
|
|
|
if ($apply_limit_and_order_manual) { |
1027
|
|
|
$GLOBALS['callback_sort_order'] = $sort_order; |
1028
|
|
|
$GLOBALS['callback_sort_by'] = $sort_by; |
1029
|
|
|
usort( |
1030
|
|
|
$databases, |
1031
|
|
|
[ |
1032
|
|
|
self::class, |
1033
|
|
|
'_usortComparisonCallback', |
1034
|
|
|
] |
1035
|
|
|
); |
1036
|
|
|
unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']); |
1037
|
|
|
|
1038
|
|
|
/** |
1039
|
|
|
* now apply limit |
1040
|
|
|
*/ |
1041
|
|
|
if ($limit_count) { |
1042
|
|
|
$databases = array_slice($databases, $limit_offset, $limit_count); |
1043
|
|
|
} |
1044
|
|
|
} |
1045
|
|
|
|
1046
|
|
|
return $databases; |
1047
|
|
|
} |
1048
|
|
|
|
1049
|
|
|
/** |
1050
|
|
|
* usort comparison callback |
1051
|
|
|
* |
1052
|
|
|
* @param array $a first argument to sort |
1053
|
|
|
* @param array $b second argument to sort |
1054
|
|
|
* |
1055
|
|
|
* @return int a value representing whether $a should be before $b in the |
1056
|
|
|
* sorted array or not |
1057
|
|
|
* |
1058
|
|
|
* @access private |
1059
|
|
|
*/ |
1060
|
|
|
private static function _usortComparisonCallback($a, $b): int |
1061
|
|
|
{ |
1062
|
|
|
if ($GLOBALS['cfg']['NaturalOrder']) { |
1063
|
|
|
$sorter = 'strnatcasecmp'; |
1064
|
|
|
} else { |
1065
|
|
|
$sorter = 'strcasecmp'; |
1066
|
|
|
} |
1067
|
|
|
/* No sorting when key is not present */ |
1068
|
|
|
if (! isset($a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']]) |
1069
|
|
|
) { |
1070
|
|
|
return 0; |
1071
|
|
|
} |
1072
|
|
|
// produces f.e.: |
1073
|
|
|
// return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"]) |
1074
|
|
|
return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter( |
1075
|
|
|
$a[$GLOBALS['callback_sort_by']], |
1076
|
|
|
$b[$GLOBALS['callback_sort_by']] |
1077
|
|
|
); |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
/** |
1081
|
|
|
* returns detailed array with all columns for sql |
1082
|
|
|
* |
1083
|
|
|
* @param string $sql_query target SQL query to get columns |
1084
|
|
|
* @param array $view_columns alias for columns |
1085
|
|
|
* |
1086
|
|
|
* @return array |
1087
|
|
|
*/ |
1088
|
|
|
public function getColumnMapFromSql(string $sql_query, array $view_columns = []): array |
1089
|
|
|
{ |
1090
|
|
|
$result = $this->tryQuery($sql_query); |
1091
|
|
|
|
1092
|
|
|
if ($result === false) { |
1093
|
|
|
return []; |
1094
|
|
|
} |
1095
|
|
|
|
1096
|
|
|
$meta = $this->getFieldsMeta( |
1097
|
|
|
$result |
1098
|
|
|
); |
1099
|
|
|
|
1100
|
|
|
$nbFields = count($meta); |
1101
|
|
|
if ($nbFields <= 0) { |
1102
|
|
|
return []; |
1103
|
|
|
} |
1104
|
|
|
|
1105
|
|
|
$column_map = []; |
1106
|
|
|
$nbColumns = count($view_columns); |
1107
|
|
|
|
1108
|
|
|
for ($i = 0; $i < $nbFields; $i++) { |
1109
|
|
|
$map = []; |
1110
|
|
|
$map['table_name'] = $meta[$i]->table; |
1111
|
|
|
$map['refering_column'] = $meta[$i]->name; |
1112
|
|
|
|
1113
|
|
|
if ($nbColumns > 1) { |
1114
|
|
|
$map['real_column'] = $view_columns[$i]; |
1115
|
|
|
} |
1116
|
|
|
|
1117
|
|
|
$column_map[] = $map; |
1118
|
|
|
} |
1119
|
|
|
|
1120
|
|
|
return $column_map; |
1121
|
|
|
} |
1122
|
|
|
|
1123
|
|
|
/** |
1124
|
|
|
* returns detailed array with all columns for given table in database, |
1125
|
|
|
* or all tables/databases |
1126
|
|
|
* |
1127
|
|
|
* @param string $database name of database |
1128
|
|
|
* @param string $table name of table to retrieve columns from |
1129
|
|
|
* @param string $column name of specific column |
1130
|
|
|
* @param mixed $link mysql link resource |
1131
|
|
|
* |
1132
|
|
|
* @return array |
1133
|
|
|
*/ |
1134
|
|
|
public function getColumnsFull( |
1135
|
|
|
?string $database = null, |
1136
|
|
|
?string $table = null, |
1137
|
|
|
?string $column = null, |
1138
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1139
|
|
|
): array { |
1140
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
1141
|
|
|
$sql_wheres = []; |
1142
|
|
|
$array_keys = []; |
1143
|
|
|
|
1144
|
|
|
// get columns information from information_schema |
1145
|
|
|
if (null !== $database) { |
1146
|
|
|
$sql_wheres[] = '`TABLE_SCHEMA` = \'' |
1147
|
|
|
. $this->escapeString($database, $link) . '\' '; |
1148
|
|
|
} else { |
1149
|
|
|
$array_keys[] = 'TABLE_SCHEMA'; |
1150
|
|
|
} |
1151
|
|
|
if (null !== $table) { |
1152
|
|
|
$sql_wheres[] = '`TABLE_NAME` = \'' |
1153
|
|
|
. $this->escapeString($table, $link) . '\' '; |
1154
|
|
|
} else { |
1155
|
|
|
$array_keys[] = 'TABLE_NAME'; |
1156
|
|
|
} |
1157
|
|
|
if (null !== $column) { |
1158
|
|
|
$sql_wheres[] = '`COLUMN_NAME` = \'' |
1159
|
|
|
. $this->escapeString($column, $link) . '\' '; |
1160
|
|
|
} else { |
1161
|
|
|
$array_keys[] = 'COLUMN_NAME'; |
1162
|
|
|
} |
1163
|
|
|
|
1164
|
|
|
// for PMA bc: |
1165
|
|
|
// `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]` |
1166
|
|
|
$sql = ' |
1167
|
|
|
SELECT *, |
1168
|
|
|
`COLUMN_NAME` AS `Field`, |
1169
|
|
|
`COLUMN_TYPE` AS `Type`, |
1170
|
|
|
`COLLATION_NAME` AS `Collation`, |
1171
|
|
|
`IS_NULLABLE` AS `Null`, |
1172
|
|
|
`COLUMN_KEY` AS `Key`, |
1173
|
|
|
`COLUMN_DEFAULT` AS `Default`, |
1174
|
|
|
`EXTRA` AS `Extra`, |
1175
|
|
|
`PRIVILEGES` AS `Privileges`, |
1176
|
|
|
`COLUMN_COMMENT` AS `Comment` |
1177
|
|
|
FROM `information_schema`.`COLUMNS`'; |
1178
|
|
|
|
1179
|
|
|
if (count($sql_wheres)) { |
1180
|
|
|
$sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres); |
1181
|
|
|
} |
1182
|
|
|
return $this->fetchResult($sql, $array_keys, null, $link); |
1183
|
|
|
} |
1184
|
|
|
|
1185
|
|
|
$columns = []; |
1186
|
|
|
if (null === $database) { |
1187
|
|
|
foreach ($GLOBALS['dblist']->databases as $database) { |
1188
|
|
|
$columns[$database] = $this->getColumnsFull( |
1189
|
|
|
$database, |
1190
|
|
|
null, |
1191
|
|
|
null, |
1192
|
|
|
$link |
1193
|
|
|
); |
1194
|
|
|
} |
1195
|
|
|
return $columns; |
1196
|
|
|
} elseif (null === $table) { |
1197
|
|
|
$tables = $this->getTables($database); |
1198
|
|
|
foreach ($tables as $table) { |
1199
|
|
|
$columns[$table] = $this->getColumnsFull( |
1200
|
|
|
$database, |
1201
|
|
|
$table, |
1202
|
|
|
null, |
1203
|
|
|
$link |
1204
|
|
|
); |
1205
|
|
|
} |
1206
|
|
|
return $columns; |
1207
|
|
|
} |
1208
|
|
|
$sql = 'SHOW FULL COLUMNS FROM ' |
1209
|
|
|
. Util::backquote($database) . '.' . Util::backquote($table); |
1210
|
|
|
if (null !== $column) { |
1211
|
|
|
$sql .= " LIKE '" . $this->escapeString($column, $link) . "'"; |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
$columns = $this->fetchResult($sql, 'Field', null, $link); |
1215
|
|
|
$ordinal_position = 1; |
1216
|
|
|
foreach ($columns as $column_name => $each_column) { |
1217
|
|
|
// Compatibility with INFORMATION_SCHEMA output |
1218
|
|
|
$columns[$column_name]['COLUMN_NAME'] |
1219
|
|
|
=& $columns[$column_name]['Field']; |
1220
|
|
|
$columns[$column_name]['COLUMN_TYPE'] |
1221
|
|
|
=& $columns[$column_name]['Type']; |
1222
|
|
|
$columns[$column_name]['COLLATION_NAME'] |
1223
|
|
|
=& $columns[$column_name]['Collation']; |
1224
|
|
|
$columns[$column_name]['IS_NULLABLE'] |
1225
|
|
|
=& $columns[$column_name]['Null']; |
1226
|
|
|
$columns[$column_name]['COLUMN_KEY'] |
1227
|
|
|
=& $columns[$column_name]['Key']; |
1228
|
|
|
$columns[$column_name]['COLUMN_DEFAULT'] |
1229
|
|
|
=& $columns[$column_name]['Default']; |
1230
|
|
|
$columns[$column_name]['EXTRA'] |
1231
|
|
|
=& $columns[$column_name]['Extra']; |
1232
|
|
|
$columns[$column_name]['PRIVILEGES'] |
1233
|
|
|
=& $columns[$column_name]['Privileges']; |
1234
|
|
|
$columns[$column_name]['COLUMN_COMMENT'] |
1235
|
|
|
=& $columns[$column_name]['Comment']; |
1236
|
|
|
|
1237
|
|
|
$columns[$column_name]['TABLE_CATALOG'] = null; |
1238
|
|
|
$columns[$column_name]['TABLE_SCHEMA'] = $database; |
1239
|
|
|
$columns[$column_name]['TABLE_NAME'] = $table; |
1240
|
|
|
$columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position; |
1241
|
|
|
$columns[$column_name]['DATA_TYPE'] |
1242
|
|
|
= substr( |
1243
|
|
|
$columns[$column_name]['COLUMN_TYPE'], |
1244
|
|
|
0, |
1245
|
|
|
strpos($columns[$column_name]['COLUMN_TYPE'], '(') |
1246
|
|
|
); |
1247
|
|
|
/** |
1248
|
|
|
* @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE |
1249
|
|
|
*/ |
1250
|
|
|
$columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null; |
1251
|
|
|
/** |
1252
|
|
|
* @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH |
1253
|
|
|
*/ |
1254
|
|
|
$columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null; |
1255
|
|
|
$columns[$column_name]['NUMERIC_PRECISION'] = null; |
1256
|
|
|
$columns[$column_name]['NUMERIC_SCALE'] = null; |
1257
|
|
|
$columns[$column_name]['CHARACTER_SET_NAME'] |
1258
|
|
|
= substr( |
1259
|
|
|
$columns[$column_name]['COLLATION_NAME'], |
1260
|
|
|
0, |
1261
|
|
|
strpos($columns[$column_name]['COLLATION_NAME'], '_') |
1262
|
|
|
); |
1263
|
|
|
|
1264
|
|
|
$ordinal_position++; |
1265
|
|
|
} |
1266
|
|
|
|
1267
|
|
|
if (null !== $column) { |
1268
|
|
|
return reset($columns); |
1269
|
|
|
} |
1270
|
|
|
|
1271
|
|
|
return $columns; |
1272
|
|
|
} |
1273
|
|
|
|
1274
|
|
|
/** |
1275
|
|
|
* Returns SQL query for fetching columns for a table |
1276
|
|
|
* |
1277
|
|
|
* The 'Key' column is not calculated properly, use $dbi->getColumns() |
1278
|
|
|
* to get correct values. |
1279
|
|
|
* |
1280
|
|
|
* @param string $database name of database |
1281
|
|
|
* @param string $table name of table to retrieve columns from |
1282
|
|
|
* @param string $column name of column, null to show all columns |
1283
|
|
|
* @param boolean $full whether to return full info or only column names |
1284
|
|
|
* |
1285
|
|
|
* @see getColumns() |
1286
|
|
|
* |
1287
|
|
|
* @return string |
1288
|
|
|
*/ |
1289
|
|
|
public function getColumnsSql( |
1290
|
|
|
string $database, |
1291
|
|
|
string $table, |
1292
|
|
|
?string $column = null, |
1293
|
|
|
bool $full = false |
1294
|
|
|
): string { |
1295
|
|
|
$sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM ' |
1296
|
|
|
. Util::backquote($database) . '.' . Util::backquote($table) |
1297
|
|
|
. ($column !== null ? "LIKE '" |
1298
|
|
|
. $this->escapeString($column) . "'" : ''); |
1299
|
|
|
|
1300
|
|
|
return $sql; |
1301
|
|
|
} |
1302
|
|
|
|
1303
|
|
|
/** |
1304
|
|
|
* Returns descriptions of columns in given table (all or given by $column) |
1305
|
|
|
* |
1306
|
|
|
* @param string $database name of database |
1307
|
|
|
* @param string $table name of table to retrieve columns from |
1308
|
|
|
* @param string $column name of column, null to show all columns |
1309
|
|
|
* @param boolean $full whether to return full info or only column names |
1310
|
|
|
* @param integer $link link type |
1311
|
|
|
* |
1312
|
|
|
* @return array array indexed by column names or, |
1313
|
|
|
* if $column is given, flat array description |
1314
|
|
|
*/ |
1315
|
|
|
public function getColumns( |
1316
|
|
|
string $database, |
1317
|
|
|
string $table, |
1318
|
|
|
?string $column = null, |
1319
|
|
|
bool $full = false, |
1320
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1321
|
|
|
): array { |
1322
|
|
|
$sql = $this->getColumnsSql($database, $table, $column, $full); |
1323
|
|
|
$fields = $this->fetchResult($sql, 'Field', null, $link); |
1324
|
|
|
if (! is_array($fields) || count($fields) === 0) { |
|
|
|
|
1325
|
|
|
return []; |
1326
|
|
|
} |
1327
|
|
|
// Check if column is a part of multiple-column index and set its 'Key'. |
1328
|
|
|
$indexes = Index::getFromTable($table, $database); |
1329
|
|
|
foreach ($fields as $field => $field_data) { |
1330
|
|
|
if (! empty($field_data['Key'])) { |
1331
|
|
|
continue; |
1332
|
|
|
} |
1333
|
|
|
|
1334
|
|
|
foreach ($indexes as $index) { |
1335
|
|
|
/** @var Index $index */ |
1336
|
|
|
if (! $index->hasColumn($field)) { |
1337
|
|
|
continue; |
1338
|
|
|
} |
1339
|
|
|
|
1340
|
|
|
$index_columns = $index->getColumns(); |
1341
|
|
|
if ($index_columns[$field]->getSeqInIndex() > 1) { |
1342
|
|
|
if ($index->isUnique()) { |
1343
|
|
|
$fields[$field]['Key'] = 'UNI'; |
1344
|
|
|
} else { |
1345
|
|
|
$fields[$field]['Key'] = 'MUL'; |
1346
|
|
|
} |
1347
|
|
|
} |
1348
|
|
|
} |
1349
|
|
|
} |
1350
|
|
|
|
1351
|
|
|
return $column != null ? array_shift($fields) : $fields; |
|
|
|
|
1352
|
|
|
} |
1353
|
|
|
|
1354
|
|
|
/** |
1355
|
|
|
* Returns all column names in given table |
1356
|
|
|
* |
1357
|
|
|
* @param string $database name of database |
1358
|
|
|
* @param string $table name of table to retrieve columns from |
1359
|
|
|
* @param mixed $link mysql link resource |
1360
|
|
|
* |
1361
|
|
|
* @return null|array |
1362
|
|
|
*/ |
1363
|
|
|
public function getColumnNames( |
1364
|
|
|
string $database, |
1365
|
|
|
string $table, |
1366
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1367
|
|
|
): ?array { |
1368
|
|
|
$sql = $this->getColumnsSql($database, $table); |
1369
|
|
|
// We only need the 'Field' column which contains the table's column names |
1370
|
|
|
$fields = array_keys($this->fetchResult($sql, 'Field', null, $link)); |
1371
|
|
|
|
1372
|
|
|
if (! is_array($fields) || count($fields) === 0) { |
|
|
|
|
1373
|
|
|
return null; |
1374
|
|
|
} |
1375
|
|
|
return $fields; |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
/** |
1379
|
|
|
* Returns SQL for fetching information on table indexes (SHOW INDEXES) |
1380
|
|
|
* |
1381
|
|
|
* @param string $database name of database |
1382
|
|
|
* @param string $table name of the table whose indexes are to be retrieved |
1383
|
|
|
* @param string $where additional conditions for WHERE |
1384
|
|
|
* |
1385
|
|
|
* @return string SQL for getting indexes |
1386
|
|
|
*/ |
1387
|
|
|
public function getTableIndexesSql( |
1388
|
|
|
string $database, |
1389
|
|
|
string $table, |
1390
|
|
|
?string $where = null |
1391
|
|
|
): string { |
1392
|
|
|
$sql = 'SHOW INDEXES FROM ' . Util::backquote($database) . '.' |
1393
|
|
|
. Util::backquote($table); |
1394
|
|
|
if ($where) { |
1395
|
|
|
$sql .= ' WHERE (' . $where . ')'; |
1396
|
|
|
} |
1397
|
|
|
return $sql; |
1398
|
|
|
} |
1399
|
|
|
|
1400
|
|
|
/** |
1401
|
|
|
* Returns indexes of a table |
1402
|
|
|
* |
1403
|
|
|
* @param string $database name of database |
1404
|
|
|
* @param string $table name of the table whose indexes are to be retrieved |
1405
|
|
|
* @param mixed $link mysql link resource |
1406
|
|
|
* |
1407
|
|
|
* @return array |
1408
|
|
|
*/ |
1409
|
|
|
public function getTableIndexes( |
1410
|
|
|
string $database, |
1411
|
|
|
string $table, |
1412
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1413
|
|
|
): array { |
1414
|
|
|
$sql = $this->getTableIndexesSql($database, $table); |
1415
|
|
|
$indexes = $this->fetchResult($sql, null, null, $link); |
1416
|
|
|
|
1417
|
|
|
if (! is_array($indexes) || count($indexes) < 1) { |
|
|
|
|
1418
|
|
|
return []; |
1419
|
|
|
} |
1420
|
|
|
return $indexes; |
1421
|
|
|
} |
1422
|
|
|
|
1423
|
|
|
/** |
1424
|
|
|
* returns value of given mysql server variable |
1425
|
|
|
* |
1426
|
|
|
* @param string $var mysql server variable name |
1427
|
|
|
* @param int $type DatabaseInterface::GETVAR_SESSION | |
1428
|
|
|
* DatabaseInterface::GETVAR_GLOBAL |
1429
|
|
|
* @param mixed $link mysql link resource|object |
1430
|
|
|
* |
1431
|
|
|
* @return mixed value for mysql server variable |
1432
|
|
|
*/ |
1433
|
|
|
public function getVariable( |
1434
|
|
|
string $var, |
1435
|
|
|
int $type = self::GETVAR_SESSION, |
1436
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1437
|
|
|
) { |
1438
|
|
|
switch ($type) { |
1439
|
|
|
case self::GETVAR_SESSION: |
1440
|
|
|
$modifier = ' SESSION'; |
1441
|
|
|
break; |
1442
|
|
|
case self::GETVAR_GLOBAL: |
1443
|
|
|
$modifier = ' GLOBAL'; |
1444
|
|
|
break; |
1445
|
|
|
default: |
1446
|
|
|
$modifier = ''; |
1447
|
|
|
} |
1448
|
|
|
return $this->fetchValue( |
1449
|
|
|
'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', |
1450
|
|
|
0, |
1451
|
|
|
1, |
1452
|
|
|
$link |
1453
|
|
|
); |
1454
|
|
|
} |
1455
|
|
|
|
1456
|
|
|
/** |
1457
|
|
|
* Sets new value for a variable if it is different from the current value |
1458
|
|
|
* |
1459
|
|
|
* @param string $var variable name |
1460
|
|
|
* @param string $value value to set |
1461
|
|
|
* @param mixed $link mysql link resource|object |
1462
|
|
|
* |
1463
|
|
|
* @return bool whether query was a successful |
1464
|
|
|
*/ |
1465
|
|
|
public function setVariable( |
1466
|
|
|
string $var, |
1467
|
|
|
string $value, |
1468
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1469
|
|
|
): bool { |
1470
|
|
|
$current_value = $this->getVariable( |
1471
|
|
|
$var, |
1472
|
|
|
self::GETVAR_SESSION, |
1473
|
|
|
$link |
1474
|
|
|
); |
1475
|
|
|
if ($current_value == $value) { |
1476
|
|
|
return true; |
1477
|
|
|
} |
1478
|
|
|
|
1479
|
|
|
return $this->query("SET " . $var . " = " . $value . ';', $link); |
1480
|
|
|
} |
1481
|
|
|
|
1482
|
|
|
/** |
1483
|
|
|
* Convert version string to integer. |
1484
|
|
|
* |
1485
|
|
|
* @param string $version MySQL server version |
1486
|
|
|
* |
1487
|
|
|
* @return int |
1488
|
|
|
*/ |
1489
|
|
|
public static function versionToInt(string $version): int |
1490
|
|
|
{ |
1491
|
|
|
$match = explode('.', $version); |
1492
|
|
|
return (int) sprintf('%d%02d%02d', $match[0], $match[1], intval($match[2])); |
1493
|
|
|
} |
1494
|
|
|
|
1495
|
|
|
/** |
1496
|
|
|
* Function called just after a connection to the MySQL database server has |
1497
|
|
|
* been established. It sets the connection collation, and determines the |
1498
|
|
|
* version of MySQL which is running. |
1499
|
|
|
* |
1500
|
|
|
* @return void |
1501
|
|
|
*/ |
1502
|
|
|
public function postConnect(): void |
1503
|
|
|
{ |
1504
|
|
|
$version = $this->fetchSingleRow( |
1505
|
|
|
'SELECT @@version, @@version_comment', |
1506
|
|
|
'ASSOC', |
1507
|
|
|
DatabaseInterface::CONNECT_USER |
1508
|
|
|
); |
1509
|
|
|
|
1510
|
|
|
if ($version) { |
1511
|
|
|
$this->_version_int = self::versionToInt($version['@@version']); |
1512
|
|
|
$this->_version_str = $version['@@version']; |
1513
|
|
|
$this->_version_comment = $version['@@version_comment']; |
1514
|
|
|
if (stripos($version['@@version'], 'mariadb') !== false) { |
1515
|
|
|
$this->_is_mariadb = true; |
1516
|
|
|
} |
1517
|
|
|
if (stripos($version['@@version_comment'], 'percona') !== false) { |
1518
|
|
|
$this->_is_percona = true; |
1519
|
|
|
} |
1520
|
|
|
} |
1521
|
|
|
|
1522
|
|
|
if ($this->_version_int > 50503) { |
1523
|
|
|
$default_charset = 'utf8mb4'; |
1524
|
|
|
$default_collation = 'utf8mb4_general_ci'; |
1525
|
|
|
} else { |
1526
|
|
|
$default_charset = 'utf8'; |
1527
|
|
|
$default_collation = 'utf8_general_ci'; |
1528
|
|
|
} |
1529
|
|
|
$GLOBALS['collation_connection'] = $default_collation; |
1530
|
|
|
$GLOBALS['charset_connection'] = $default_charset; |
1531
|
|
|
$this->query( |
1532
|
|
|
"SET NAMES '$default_charset' COLLATE '$default_collation';", |
1533
|
|
|
DatabaseInterface::CONNECT_USER, |
1534
|
|
|
self::QUERY_STORE |
1535
|
|
|
); |
1536
|
|
|
|
1537
|
|
|
/* Locale for messages */ |
1538
|
|
|
$locale = LanguageManager::getInstance()->getCurrentLanguage()->getMySQLLocale(); |
1539
|
|
|
if (! empty($locale)) { |
1540
|
|
|
$this->query( |
1541
|
|
|
"SET lc_messages = '" . $locale . "';", |
1542
|
|
|
DatabaseInterface::CONNECT_USER, |
1543
|
|
|
self::QUERY_STORE |
1544
|
|
|
); |
1545
|
|
|
} |
1546
|
|
|
|
1547
|
|
|
// Set timezone for the session, if required. |
1548
|
|
|
if ($GLOBALS['cfg']['Server']['SessionTimeZone'] != '') { |
1549
|
|
|
$sql_query_tz = 'SET ' . Util::backquote('time_zone') . ' = ' |
1550
|
|
|
. '\'' |
1551
|
|
|
. $this->escapeString($GLOBALS['cfg']['Server']['SessionTimeZone']) |
1552
|
|
|
. '\''; |
1553
|
|
|
|
1554
|
|
|
if (! $this->tryQuery($sql_query_tz)) { |
1555
|
|
|
$error_message_tz = sprintf( |
1556
|
|
|
__( |
1557
|
|
|
'Unable to use timezone "%1$s" for server %2$d. ' |
1558
|
|
|
. 'Please check your configuration setting for ' |
1559
|
|
|
. '[em]$cfg[\'Servers\'][%3$d][\'SessionTimeZone\'][/em]. ' |
1560
|
|
|
. 'phpMyAdmin is currently using the default time zone ' |
1561
|
|
|
. 'of the database server.' |
1562
|
|
|
), |
1563
|
|
|
$GLOBALS['cfg']['Server']['SessionTimeZone'], |
1564
|
|
|
$GLOBALS['server'], |
1565
|
|
|
$GLOBALS['server'] |
1566
|
|
|
); |
1567
|
|
|
|
1568
|
|
|
trigger_error($error_message_tz, E_USER_WARNING); |
1569
|
|
|
} |
1570
|
|
|
} |
1571
|
|
|
|
1572
|
|
|
/* Loads closest context to this version. */ |
1573
|
|
|
Context::loadClosest( |
1574
|
|
|
($this->_is_mariadb ? 'MariaDb' : 'MySql') . $this->_version_int |
1575
|
|
|
); |
1576
|
|
|
|
1577
|
|
|
/** |
1578
|
|
|
* the DatabaseList class as a stub for the ListDatabase class |
1579
|
|
|
*/ |
1580
|
|
|
$GLOBALS['dblist'] = new DatabaseList(); |
1581
|
|
|
} |
1582
|
|
|
|
1583
|
|
|
/** |
1584
|
|
|
* Sets collation connection for user link |
1585
|
|
|
* |
1586
|
|
|
* @param string $collation collation to set |
1587
|
|
|
* |
1588
|
|
|
* @return void |
1589
|
|
|
*/ |
1590
|
|
|
public function setCollation(string $collation): void |
1591
|
|
|
{ |
1592
|
|
|
$charset = $GLOBALS['charset_connection']; |
1593
|
|
|
/* Automatically adjust collation if not supported by server */ |
1594
|
|
|
if ($charset == 'utf8' && strncmp('utf8mb4_', $collation, 8) == 0) { |
1595
|
|
|
$collation = 'utf8_' . substr($collation, 8); |
1596
|
|
|
} |
1597
|
|
|
$result = $this->tryQuery( |
1598
|
|
|
"SET collation_connection = '" |
1599
|
|
|
. $this->escapeString($collation, DatabaseInterface::CONNECT_USER) |
1600
|
|
|
. "';", |
1601
|
|
|
DatabaseInterface::CONNECT_USER, |
1602
|
|
|
self::QUERY_STORE |
1603
|
|
|
); |
1604
|
|
|
if ($result === false) { |
1605
|
|
|
trigger_error( |
1606
|
|
|
__('Failed to set configured collation connection!'), |
1607
|
|
|
E_USER_WARNING |
1608
|
|
|
); |
1609
|
|
|
} else { |
1610
|
|
|
$GLOBALS['collation_connection'] = $collation; |
1611
|
|
|
} |
1612
|
|
|
} |
1613
|
|
|
|
1614
|
|
|
/** |
1615
|
|
|
* Function called just after a connection to the MySQL database server has |
1616
|
|
|
* been established. It sets the connection collation, and determines the |
1617
|
|
|
* version of MySQL which is running. |
1618
|
|
|
* |
1619
|
|
|
* @return void |
1620
|
|
|
*/ |
1621
|
|
|
public function postConnectControl(): void |
1622
|
|
|
{ |
1623
|
|
|
// If Zero configuration mode enabled, check PMA tables in current db. |
1624
|
|
|
if ($GLOBALS['cfg']['ZeroConf'] == true) { |
1625
|
|
|
/** |
1626
|
|
|
* the DatabaseList class as a stub for the ListDatabase class |
1627
|
|
|
*/ |
1628
|
|
|
$GLOBALS['dblist'] = new DatabaseList(); |
1629
|
|
|
|
1630
|
|
|
if (strlen($GLOBALS['db'])) { |
1631
|
|
|
$cfgRelation = $this->relation->getRelationsParam(); |
1632
|
|
|
if (empty($cfgRelation['db'])) { |
1633
|
|
|
$this->relation->fixPmaTables($GLOBALS['db'], false); |
1634
|
|
|
} |
1635
|
|
|
} |
1636
|
|
|
$cfgRelation = $this->relation->getRelationsParam(); |
1637
|
|
|
if (empty($cfgRelation['db']) && $GLOBALS['dblist']->databases->exists('phpmyadmin')) { |
1638
|
|
|
$this->relation->fixPmaTables('phpmyadmin', false); |
1639
|
|
|
} |
1640
|
|
|
} |
1641
|
|
|
} |
1642
|
|
|
|
1643
|
|
|
/** |
1644
|
|
|
* returns a single value from the given result or query, |
1645
|
|
|
* if the query or the result has more than one row or field |
1646
|
|
|
* the first field of the first row is returned |
1647
|
|
|
* |
1648
|
|
|
* <code> |
1649
|
|
|
* $sql = 'SELECT `name` FROM `user` WHERE `id` = 123'; |
1650
|
|
|
* $user_name = $dbi->fetchValue($sql); |
1651
|
|
|
* // produces |
1652
|
|
|
* // $user_name = 'John Doe' |
1653
|
|
|
* </code> |
1654
|
|
|
* |
1655
|
|
|
* @param string $query The query to execute |
1656
|
|
|
* @param integer $row_number row to fetch the value from, |
1657
|
|
|
* starting at 0, with 0 being default |
1658
|
|
|
* @param integer|string $field field to fetch the value from, |
1659
|
|
|
* starting at 0, with 0 being default |
1660
|
|
|
* @param integer $link link type |
1661
|
|
|
* |
1662
|
|
|
* @return mixed value of first field in first row from result |
1663
|
|
|
* or false if not found |
1664
|
|
|
*/ |
1665
|
|
|
public function fetchValue( |
1666
|
|
|
string $query, |
1667
|
|
|
int $row_number = 0, |
1668
|
|
|
$field = 0, |
1669
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1670
|
|
|
) { |
1671
|
|
|
$value = false; |
1672
|
|
|
|
1673
|
|
|
$result = $this->tryQuery( |
1674
|
|
|
$query, |
1675
|
|
|
$link, |
1676
|
|
|
self::QUERY_STORE, |
1677
|
|
|
false |
1678
|
|
|
); |
1679
|
|
|
if ($result === false) { |
1680
|
|
|
return false; |
1681
|
|
|
} |
1682
|
|
|
|
1683
|
|
|
// return false if result is empty or false |
1684
|
|
|
// or requested row is larger than rows in result |
1685
|
|
|
if ($this->numRows($result) < ($row_number + 1)) { |
1686
|
|
|
return $value; |
1687
|
|
|
} |
1688
|
|
|
|
1689
|
|
|
// if $field is an integer use non associative mysql fetch function |
1690
|
|
|
if (is_int($field)) { |
1691
|
|
|
$fetch_function = 'fetchRow'; |
1692
|
|
|
} else { |
1693
|
|
|
$fetch_function = 'fetchAssoc'; |
1694
|
|
|
} |
1695
|
|
|
|
1696
|
|
|
// get requested row |
1697
|
|
|
for ($i = 0; $i <= $row_number; $i++) { |
1698
|
|
|
$row = $this->$fetch_function($result); |
1699
|
|
|
} |
1700
|
|
|
$this->freeResult($result); |
1701
|
|
|
|
1702
|
|
|
// return requested field |
1703
|
|
|
if (isset($row[$field])) { |
1704
|
|
|
$value = $row[$field]; |
1705
|
|
|
} |
1706
|
|
|
|
1707
|
|
|
return $value; |
1708
|
|
|
} |
1709
|
|
|
|
1710
|
|
|
/** |
1711
|
|
|
* returns only the first row from the result |
1712
|
|
|
* |
1713
|
|
|
* <code> |
1714
|
|
|
* $sql = 'SELECT * FROM `user` WHERE `id` = 123'; |
1715
|
|
|
* $user = $dbi->fetchSingleRow($sql); |
1716
|
|
|
* // produces |
1717
|
|
|
* // $user = array('id' => 123, 'name' => 'John Doe') |
1718
|
|
|
* </code> |
1719
|
|
|
* |
1720
|
|
|
* @param string $query The query to execute |
1721
|
|
|
* @param string $type NUM|ASSOC|BOTH returned array should either numeric |
1722
|
|
|
* associative or both |
1723
|
|
|
* @param integer $link link type |
1724
|
|
|
* |
1725
|
|
|
* @return array|boolean first row from result |
1726
|
|
|
* or false if result is empty |
1727
|
|
|
*/ |
1728
|
|
|
public function fetchSingleRow( |
1729
|
|
|
string $query, |
1730
|
|
|
string $type = 'ASSOC', |
1731
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1732
|
|
|
) { |
1733
|
|
|
$result = $this->tryQuery( |
1734
|
|
|
$query, |
1735
|
|
|
$link, |
1736
|
|
|
self::QUERY_STORE, |
1737
|
|
|
false |
1738
|
|
|
); |
1739
|
|
|
if ($result === false) { |
1740
|
|
|
return false; |
1741
|
|
|
} |
1742
|
|
|
|
1743
|
|
|
// return false if result is empty or false |
1744
|
|
|
if (! $this->numRows($result)) { |
1745
|
|
|
return false; |
1746
|
|
|
} |
1747
|
|
|
|
1748
|
|
|
switch ($type) { |
1749
|
|
|
case 'NUM': |
1750
|
|
|
$fetch_function = 'fetchRow'; |
1751
|
|
|
break; |
1752
|
|
|
case 'ASSOC': |
1753
|
|
|
$fetch_function = 'fetchAssoc'; |
1754
|
|
|
break; |
1755
|
|
|
case 'BOTH': |
1756
|
|
|
default: |
1757
|
|
|
$fetch_function = 'fetchArray'; |
1758
|
|
|
break; |
1759
|
|
|
} |
1760
|
|
|
|
1761
|
|
|
$row = $this->$fetch_function($result); |
1762
|
|
|
$this->freeResult($result); |
1763
|
|
|
return $row; |
1764
|
|
|
} |
1765
|
|
|
|
1766
|
|
|
/** |
1767
|
|
|
* Returns row or element of a row |
1768
|
|
|
* |
1769
|
|
|
* @param array $row Row to process |
1770
|
|
|
* @param string|null|int $value Which column to return |
1771
|
|
|
* |
1772
|
|
|
* @return mixed |
1773
|
|
|
*/ |
1774
|
|
|
private function _fetchValue(array $row, $value) |
1775
|
|
|
{ |
1776
|
|
|
if ($value === null) { |
1777
|
|
|
return $row; |
1778
|
|
|
} |
1779
|
|
|
|
1780
|
|
|
return $row[$value]; |
1781
|
|
|
} |
1782
|
|
|
|
1783
|
|
|
/** |
1784
|
|
|
* returns all rows in the resultset in one array |
1785
|
|
|
* |
1786
|
|
|
* <code> |
1787
|
|
|
* $sql = 'SELECT * FROM `user`'; |
1788
|
|
|
* $users = $dbi->fetchResult($sql); |
1789
|
|
|
* // produces |
1790
|
|
|
* // $users[] = array('id' => 123, 'name' => 'John Doe') |
1791
|
|
|
* |
1792
|
|
|
* $sql = 'SELECT `id`, `name` FROM `user`'; |
1793
|
|
|
* $users = $dbi->fetchResult($sql, 'id'); |
1794
|
|
|
* // produces |
1795
|
|
|
* // $users['123'] = array('id' => 123, 'name' => 'John Doe') |
1796
|
|
|
* |
1797
|
|
|
* $sql = 'SELECT `id`, `name` FROM `user`'; |
1798
|
|
|
* $users = $dbi->fetchResult($sql, 0); |
1799
|
|
|
* // produces |
1800
|
|
|
* // $users['123'] = array(0 => 123, 1 => 'John Doe') |
1801
|
|
|
* |
1802
|
|
|
* $sql = 'SELECT `id`, `name` FROM `user`'; |
1803
|
|
|
* $users = $dbi->fetchResult($sql, 'id', 'name'); |
1804
|
|
|
* // or |
1805
|
|
|
* $users = $dbi->fetchResult($sql, 0, 1); |
1806
|
|
|
* // produces |
1807
|
|
|
* // $users['123'] = 'John Doe' |
1808
|
|
|
* |
1809
|
|
|
* $sql = 'SELECT `name` FROM `user`'; |
1810
|
|
|
* $users = $dbi->fetchResult($sql); |
1811
|
|
|
* // produces |
1812
|
|
|
* // $users[] = 'John Doe' |
1813
|
|
|
* |
1814
|
|
|
* $sql = 'SELECT `group`, `name` FROM `user`' |
1815
|
|
|
* $users = $dbi->fetchResult($sql, array('group', null), 'name'); |
1816
|
|
|
* // produces |
1817
|
|
|
* // $users['admin'][] = 'John Doe' |
1818
|
|
|
* |
1819
|
|
|
* $sql = 'SELECT `group`, `name` FROM `user`' |
1820
|
|
|
* $users = $dbi->fetchResult($sql, array('group', 'name'), 'id'); |
1821
|
|
|
* // produces |
1822
|
|
|
* // $users['admin']['John Doe'] = '123' |
1823
|
|
|
* </code> |
1824
|
|
|
* |
1825
|
|
|
* @param string $query query to execute |
1826
|
|
|
* @param string|integer|array $key field-name or offset |
1827
|
|
|
* used as key for array |
1828
|
|
|
* or array of those |
1829
|
|
|
* @param string|integer $value value-name or offset |
1830
|
|
|
* used as value for array |
1831
|
|
|
* @param integer $link link type |
1832
|
|
|
* @param integer $options query options |
1833
|
|
|
* |
1834
|
|
|
* @return array resultrows or values indexed by $key |
1835
|
|
|
*/ |
1836
|
|
|
public function fetchResult( |
1837
|
|
|
string $query, |
1838
|
|
|
$key = null, |
1839
|
|
|
$value = null, |
1840
|
|
|
$link = DatabaseInterface::CONNECT_USER, |
1841
|
|
|
int $options = 0 |
1842
|
|
|
) { |
1843
|
|
|
$resultrows = []; |
1844
|
|
|
|
1845
|
|
|
$result = $this->tryQuery($query, $link, $options, false); |
1846
|
|
|
|
1847
|
|
|
// return empty array if result is empty or false |
1848
|
|
|
if ($result === false) { |
1849
|
|
|
return $resultrows; |
1850
|
|
|
} |
1851
|
|
|
|
1852
|
|
|
$fetch_function = 'fetchAssoc'; |
1853
|
|
|
|
1854
|
|
|
// no nested array if only one field is in result |
1855
|
|
|
if (null === $key && 1 === $this->numFields($result)) { |
1856
|
|
|
$value = 0; |
1857
|
|
|
$fetch_function = 'fetchRow'; |
1858
|
|
|
} |
1859
|
|
|
|
1860
|
|
|
// if $key is an integer use non associative mysql fetch function |
1861
|
|
|
if (is_int($key)) { |
1862
|
|
|
$fetch_function = 'fetchRow'; |
1863
|
|
|
} |
1864
|
|
|
|
1865
|
|
|
if (null === $key) { |
1866
|
|
|
while ($row = $this->$fetch_function($result)) { |
1867
|
|
|
$resultrows[] = $this->_fetchValue($row, $value); |
1868
|
|
|
} |
1869
|
|
|
} else { |
1870
|
|
|
if (is_array($key)) { |
1871
|
|
|
while ($row = $this->$fetch_function($result)) { |
1872
|
|
|
$result_target =& $resultrows; |
1873
|
|
|
foreach ($key as $key_index) { |
1874
|
|
|
if (null === $key_index) { |
1875
|
|
|
$result_target =& $result_target[]; |
1876
|
|
|
continue; |
1877
|
|
|
} |
1878
|
|
|
|
1879
|
|
|
if (! isset($result_target[$row[$key_index]])) { |
1880
|
|
|
$result_target[$row[$key_index]] = []; |
1881
|
|
|
} |
1882
|
|
|
$result_target =& $result_target[$row[$key_index]]; |
1883
|
|
|
} |
1884
|
|
|
$result_target = $this->_fetchValue($row, $value); |
1885
|
|
|
} |
1886
|
|
|
} else { |
1887
|
|
|
while ($row = $this->$fetch_function($result)) { |
1888
|
|
|
$resultrows[$row[$key]] = $this->_fetchValue($row, $value); |
1889
|
|
|
} |
1890
|
|
|
} |
1891
|
|
|
} |
1892
|
|
|
|
1893
|
|
|
$this->freeResult($result); |
1894
|
|
|
return $resultrows; |
1895
|
|
|
} |
1896
|
|
|
|
1897
|
|
|
/** |
1898
|
|
|
* Get supported SQL compatibility modes |
1899
|
|
|
* |
1900
|
|
|
* @return array supported SQL compatibility modes |
1901
|
|
|
*/ |
1902
|
|
|
public function getCompatibilities(): array |
1903
|
|
|
{ |
1904
|
|
|
$compats = ['NONE']; |
1905
|
|
|
$compats[] = 'ANSI'; |
1906
|
|
|
$compats[] = 'DB2'; |
1907
|
|
|
$compats[] = 'MAXDB'; |
1908
|
|
|
$compats[] = 'MYSQL323'; |
1909
|
|
|
$compats[] = 'MYSQL40'; |
1910
|
|
|
$compats[] = 'MSSQL'; |
1911
|
|
|
$compats[] = 'ORACLE'; |
1912
|
|
|
// removed; in MySQL 5.0.33, this produces exports that |
1913
|
|
|
// can't be read by POSTGRESQL (see our bug #1596328) |
1914
|
|
|
//$compats[] = 'POSTGRESQL'; |
1915
|
|
|
$compats[] = 'TRADITIONAL'; |
1916
|
|
|
|
1917
|
|
|
return $compats; |
1918
|
|
|
} |
1919
|
|
|
|
1920
|
|
|
/** |
1921
|
|
|
* returns warnings for last query |
1922
|
|
|
* |
1923
|
|
|
* @param integer $link link type |
1924
|
|
|
* |
1925
|
|
|
* @return array warnings |
1926
|
|
|
*/ |
1927
|
|
|
public function getWarnings($link = DatabaseInterface::CONNECT_USER): array |
1928
|
|
|
{ |
1929
|
|
|
return $this->fetchResult('SHOW WARNINGS', null, null, $link); |
1930
|
|
|
} |
1931
|
|
|
|
1932
|
|
|
/** |
1933
|
|
|
* returns an array of PROCEDURE or FUNCTION names for a db |
1934
|
|
|
* |
1935
|
|
|
* @param string $db db name |
1936
|
|
|
* @param string $which PROCEDURE | FUNCTION |
1937
|
|
|
* @param integer $link link type |
1938
|
|
|
* |
1939
|
|
|
* @return array the procedure names or function names |
1940
|
|
|
*/ |
1941
|
|
|
public function getProceduresOrFunctions( |
1942
|
|
|
string $db, |
1943
|
|
|
string $which, |
1944
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1945
|
|
|
): array { |
1946
|
|
|
$shows = $this->fetchResult( |
1947
|
|
|
'SHOW ' . $which . ' STATUS;', |
1948
|
|
|
null, |
1949
|
|
|
null, |
1950
|
|
|
$link |
1951
|
|
|
); |
1952
|
|
|
$result = []; |
1953
|
|
|
foreach ($shows as $one_show) { |
1954
|
|
|
if ($one_show['Db'] == $db && $one_show['Type'] == $which) { |
1955
|
|
|
$result[] = $one_show['Name']; |
1956
|
|
|
} |
1957
|
|
|
} |
1958
|
|
|
return $result; |
1959
|
|
|
} |
1960
|
|
|
|
1961
|
|
|
/** |
1962
|
|
|
* returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW |
1963
|
|
|
* |
1964
|
|
|
* @param string $db db name |
1965
|
|
|
* @param string $which PROCEDURE | FUNCTION | EVENT | VIEW |
1966
|
|
|
* @param string $name the procedure|function|event|view name |
1967
|
|
|
* @param integer $link link type |
1968
|
|
|
* |
1969
|
|
|
* @return string|null the definition |
1970
|
|
|
*/ |
1971
|
|
|
public function getDefinition( |
1972
|
|
|
string $db, |
1973
|
|
|
string $which, |
1974
|
|
|
string $name, |
1975
|
|
|
$link = DatabaseInterface::CONNECT_USER |
1976
|
|
|
): ?string { |
1977
|
|
|
$returned_field = [ |
1978
|
|
|
'PROCEDURE' => 'Create Procedure', |
1979
|
|
|
'FUNCTION' => 'Create Function', |
1980
|
|
|
'EVENT' => 'Create Event', |
1981
|
|
|
'VIEW' => 'Create View', |
1982
|
|
|
]; |
1983
|
|
|
$query = 'SHOW CREATE ' . $which . ' ' |
1984
|
|
|
. Util::backquote($db) . '.' |
1985
|
|
|
. Util::backquote($name); |
1986
|
|
|
$result = $this->fetchValue($query, 0, $returned_field[$which], $link); |
1987
|
|
|
return is_string($result) ? $result : null; |
1988
|
|
|
} |
1989
|
|
|
|
1990
|
|
|
/** |
1991
|
|
|
* returns details about the PROCEDUREs or FUNCTIONs for a specific database |
1992
|
|
|
* or details about a specific routine |
1993
|
|
|
* |
1994
|
|
|
* @param string $db db name |
1995
|
|
|
* @param string $which PROCEDURE | FUNCTION or null for both |
1996
|
|
|
* @param string $name name of the routine (to fetch a specific routine) |
1997
|
|
|
* |
1998
|
|
|
* @return array information about ROCEDUREs or FUNCTIONs |
1999
|
|
|
*/ |
2000
|
|
|
public function getRoutines( |
2001
|
|
|
string $db, |
2002
|
|
|
?string $which = null, |
2003
|
|
|
string $name = '' |
2004
|
|
|
): array { |
2005
|
|
|
$routines = []; |
2006
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
2007
|
|
|
$query = "SELECT" |
2008
|
|
|
. " `ROUTINE_SCHEMA` AS `Db`," |
2009
|
|
|
. " `SPECIFIC_NAME` AS `Name`," |
2010
|
|
|
. " `ROUTINE_TYPE` AS `Type`," |
2011
|
|
|
. " `DEFINER` AS `Definer`," |
2012
|
|
|
. " `LAST_ALTERED` AS `Modified`," |
2013
|
|
|
. " `CREATED` AS `Created`," |
2014
|
|
|
. " `SECURITY_TYPE` AS `Security_type`," |
2015
|
|
|
. " `ROUTINE_COMMENT` AS `Comment`," |
2016
|
|
|
. " `CHARACTER_SET_CLIENT` AS `character_set_client`," |
2017
|
|
|
. " `COLLATION_CONNECTION` AS `collation_connection`," |
2018
|
|
|
. " `DATABASE_COLLATION` AS `Database Collation`," |
2019
|
|
|
. " `DTD_IDENTIFIER`" |
2020
|
|
|
. " FROM `information_schema`.`ROUTINES`" |
2021
|
|
|
. " WHERE `ROUTINE_SCHEMA` " . Util::getCollateForIS() |
2022
|
|
|
. " = '" . $this->escapeString($db) . "'"; |
2023
|
|
|
if (Core::isValid($which, ['FUNCTION', 'PROCEDURE'])) { |
2024
|
|
|
$query .= " AND `ROUTINE_TYPE` = '" . $which . "'"; |
2025
|
|
|
} |
2026
|
|
|
if (! empty($name)) { |
2027
|
|
|
$query .= " AND `SPECIFIC_NAME`" |
2028
|
|
|
. " = '" . $this->escapeString($name) . "'"; |
2029
|
|
|
} |
2030
|
|
|
$result = $this->fetchResult($query); |
2031
|
|
|
if (! empty($result)) { |
2032
|
|
|
$routines = $result; |
2033
|
|
|
} |
2034
|
|
|
} else { |
2035
|
|
|
if ($which == 'FUNCTION' || $which == null) { |
|
|
|
|
2036
|
|
|
$query = "SHOW FUNCTION STATUS" |
2037
|
|
|
. " WHERE `Db` = '" . $this->escapeString($db) . "'"; |
2038
|
|
|
if (! empty($name)) { |
2039
|
|
|
$query .= " AND `Name` = '" |
2040
|
|
|
. $this->escapeString($name) . "'"; |
2041
|
|
|
} |
2042
|
|
|
$result = $this->fetchResult($query); |
2043
|
|
|
if (! empty($result)) { |
2044
|
|
|
$routines = array_merge($routines, $result); |
2045
|
|
|
} |
2046
|
|
|
} |
2047
|
|
|
if ($which == 'PROCEDURE' || $which == null) { |
|
|
|
|
2048
|
|
|
$query = "SHOW PROCEDURE STATUS" |
2049
|
|
|
. " WHERE `Db` = '" . $this->escapeString($db) . "'"; |
2050
|
|
|
if (! empty($name)) { |
2051
|
|
|
$query .= " AND `Name` = '" |
2052
|
|
|
. $this->escapeString($name) . "'"; |
2053
|
|
|
} |
2054
|
|
|
$result = $this->fetchResult($query); |
2055
|
|
|
if (! empty($result)) { |
2056
|
|
|
$routines = array_merge($routines, $result); |
2057
|
|
|
} |
2058
|
|
|
} |
2059
|
|
|
} |
2060
|
|
|
|
2061
|
|
|
$ret = []; |
2062
|
|
|
foreach ($routines as $routine) { |
2063
|
|
|
$one_result = []; |
2064
|
|
|
$one_result['db'] = $routine['Db']; |
2065
|
|
|
$one_result['name'] = $routine['Name']; |
2066
|
|
|
$one_result['type'] = $routine['Type']; |
2067
|
|
|
$one_result['definer'] = $routine['Definer']; |
2068
|
|
|
$one_result['returns'] = isset($routine['DTD_IDENTIFIER']) |
2069
|
|
|
? $routine['DTD_IDENTIFIER'] : ""; |
2070
|
|
|
$ret[] = $one_result; |
2071
|
|
|
} |
2072
|
|
|
|
2073
|
|
|
// Sort results by name |
2074
|
|
|
$name = []; |
2075
|
|
|
foreach ($ret as $value) { |
2076
|
|
|
$name[] = $value['name']; |
2077
|
|
|
} |
2078
|
|
|
array_multisort($name, SORT_ASC, $ret); |
2079
|
|
|
|
2080
|
|
|
return $ret; |
2081
|
|
|
} |
2082
|
|
|
|
2083
|
|
|
/** |
2084
|
|
|
* returns details about the EVENTs for a specific database |
2085
|
|
|
* |
2086
|
|
|
* @param string $db db name |
2087
|
|
|
* @param string $name event name |
2088
|
|
|
* |
2089
|
|
|
* @return array information about EVENTs |
2090
|
|
|
*/ |
2091
|
|
|
public function getEvents(string $db, string $name = ''): array |
2092
|
|
|
{ |
2093
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
2094
|
|
|
$query = "SELECT" |
2095
|
|
|
. " `EVENT_SCHEMA` AS `Db`," |
2096
|
|
|
. " `EVENT_NAME` AS `Name`," |
2097
|
|
|
. " `DEFINER` AS `Definer`," |
2098
|
|
|
. " `TIME_ZONE` AS `Time zone`," |
2099
|
|
|
. " `EVENT_TYPE` AS `Type`," |
2100
|
|
|
. " `EXECUTE_AT` AS `Execute at`," |
2101
|
|
|
. " `INTERVAL_VALUE` AS `Interval value`," |
2102
|
|
|
. " `INTERVAL_FIELD` AS `Interval field`," |
2103
|
|
|
. " `STARTS` AS `Starts`," |
2104
|
|
|
. " `ENDS` AS `Ends`," |
2105
|
|
|
. " `STATUS` AS `Status`," |
2106
|
|
|
. " `ORIGINATOR` AS `Originator`," |
2107
|
|
|
. " `CHARACTER_SET_CLIENT` AS `character_set_client`," |
2108
|
|
|
. " `COLLATION_CONNECTION` AS `collation_connection`, " |
2109
|
|
|
. "`DATABASE_COLLATION` AS `Database Collation`" |
2110
|
|
|
. " FROM `information_schema`.`EVENTS`" |
2111
|
|
|
. " WHERE `EVENT_SCHEMA` " . Util::getCollateForIS() |
2112
|
|
|
. " = '" . $this->escapeString($db) . "'"; |
2113
|
|
|
if (! empty($name)) { |
2114
|
|
|
$query .= " AND `EVENT_NAME`" |
2115
|
|
|
. " = '" . $this->escapeString($name) . "'"; |
2116
|
|
|
} |
2117
|
|
|
} else { |
2118
|
|
|
$query = "SHOW EVENTS FROM " . Util::backquote($db); |
2119
|
|
|
if (! empty($name)) { |
2120
|
|
|
$query .= " AND `Name` = '" |
2121
|
|
|
. $this->escapeString($name) . "'"; |
2122
|
|
|
} |
2123
|
|
|
} |
2124
|
|
|
|
2125
|
|
|
$result = []; |
2126
|
|
|
if ($events = $this->fetchResult($query)) { |
2127
|
|
|
foreach ($events as $event) { |
2128
|
|
|
$one_result = []; |
2129
|
|
|
$one_result['name'] = $event['Name']; |
2130
|
|
|
$one_result['type'] = $event['Type']; |
2131
|
|
|
$one_result['status'] = $event['Status']; |
2132
|
|
|
$result[] = $one_result; |
2133
|
|
|
} |
2134
|
|
|
} |
2135
|
|
|
|
2136
|
|
|
// Sort results by name |
2137
|
|
|
$name = []; |
2138
|
|
|
foreach ($result as $value) { |
2139
|
|
|
$name[] = $value['name']; |
2140
|
|
|
} |
2141
|
|
|
array_multisort($name, SORT_ASC, $result); |
2142
|
|
|
|
2143
|
|
|
return $result; |
2144
|
|
|
} |
2145
|
|
|
|
2146
|
|
|
/** |
2147
|
|
|
* returns details about the TRIGGERs for a specific table or database |
2148
|
|
|
* |
2149
|
|
|
* @param string $db db name |
2150
|
|
|
* @param string $table table name |
2151
|
|
|
* @param string $delimiter the delimiter to use (may be empty) |
2152
|
|
|
* |
2153
|
|
|
* @return array information about triggers (may be empty) |
2154
|
|
|
*/ |
2155
|
|
|
public function getTriggers(string $db, string $table = '', $delimiter = '//') |
2156
|
|
|
{ |
2157
|
|
|
$result = []; |
2158
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
2159
|
|
|
$query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION' |
2160
|
|
|
. ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT' |
2161
|
|
|
. ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER' |
2162
|
|
|
. ' FROM information_schema.TRIGGERS' |
2163
|
|
|
. ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '=' |
2164
|
|
|
. ' \'' . $this->escapeString($db) . '\''; |
2165
|
|
|
|
2166
|
|
|
if (! empty($table)) { |
2167
|
|
|
$query .= " AND EVENT_OBJECT_TABLE " . Util::getCollateForIS() |
2168
|
|
|
. " = '" . $this->escapeString($table) . "';"; |
2169
|
|
|
} |
2170
|
|
|
} else { |
2171
|
|
|
$query = "SHOW TRIGGERS FROM " . Util::backquote($db); |
2172
|
|
|
if (! empty($table)) { |
2173
|
|
|
$query .= " LIKE '" . $this->escapeString($table) . "';"; |
2174
|
|
|
} |
2175
|
|
|
} |
2176
|
|
|
|
2177
|
|
|
if ($triggers = $this->fetchResult($query)) { |
2178
|
|
|
foreach ($triggers as $trigger) { |
2179
|
|
|
if ($GLOBALS['cfg']['Server']['DisableIS']) { |
2180
|
|
|
$trigger['TRIGGER_NAME'] = $trigger['Trigger']; |
2181
|
|
|
$trigger['ACTION_TIMING'] = $trigger['Timing']; |
2182
|
|
|
$trigger['EVENT_MANIPULATION'] = $trigger['Event']; |
2183
|
|
|
$trigger['EVENT_OBJECT_TABLE'] = $trigger['Table']; |
2184
|
|
|
$trigger['ACTION_STATEMENT'] = $trigger['Statement']; |
2185
|
|
|
$trigger['DEFINER'] = $trigger['Definer']; |
2186
|
|
|
} |
2187
|
|
|
$one_result = []; |
2188
|
|
|
$one_result['name'] = $trigger['TRIGGER_NAME']; |
2189
|
|
|
$one_result['table'] = $trigger['EVENT_OBJECT_TABLE']; |
2190
|
|
|
$one_result['action_timing'] = $trigger['ACTION_TIMING']; |
2191
|
|
|
$one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION']; |
2192
|
|
|
$one_result['definition'] = $trigger['ACTION_STATEMENT']; |
2193
|
|
|
$one_result['definer'] = $trigger['DEFINER']; |
2194
|
|
|
|
2195
|
|
|
// do not prepend the schema name; this way, importing the |
2196
|
|
|
// definition into another schema will work |
2197
|
|
|
$one_result['full_trigger_name'] = Util::backquote( |
2198
|
|
|
$trigger['TRIGGER_NAME'] |
2199
|
|
|
); |
2200
|
|
|
$one_result['drop'] = 'DROP TRIGGER IF EXISTS ' |
2201
|
|
|
. $one_result['full_trigger_name']; |
|
|
|
|
2202
|
|
|
$one_result['create'] = 'CREATE TRIGGER ' |
2203
|
|
|
. $one_result['full_trigger_name'] . ' ' |
2204
|
|
|
. $trigger['ACTION_TIMING'] . ' ' |
2205
|
|
|
. $trigger['EVENT_MANIPULATION'] |
2206
|
|
|
. ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE']) |
|
|
|
|
2207
|
|
|
. "\n" . ' FOR EACH ROW ' |
2208
|
|
|
. $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n"; |
2209
|
|
|
|
2210
|
|
|
$result[] = $one_result; |
2211
|
|
|
} |
2212
|
|
|
} |
2213
|
|
|
|
2214
|
|
|
// Sort results by name |
2215
|
|
|
$name = []; |
2216
|
|
|
foreach ($result as $value) { |
2217
|
|
|
$name[] = $value['name']; |
2218
|
|
|
} |
2219
|
|
|
array_multisort($name, SORT_ASC, $result); |
2220
|
|
|
|
2221
|
|
|
return $result; |
2222
|
|
|
} |
2223
|
|
|
|
2224
|
|
|
/** |
2225
|
|
|
* Formats database error message in a friendly way. |
2226
|
|
|
* This is needed because some errors messages cannot |
2227
|
|
|
* be obtained by mysql_error(). |
2228
|
|
|
* |
2229
|
|
|
* @param int $error_number Error code |
2230
|
|
|
* @param string $error_message Error message as returned by server |
2231
|
|
|
* |
2232
|
|
|
* @return string HML text with error details |
2233
|
|
|
*/ |
2234
|
|
|
public static function formatError(int $error_number, string $error_message): string |
2235
|
|
|
{ |
2236
|
|
|
$error_message = htmlspecialchars($error_message); |
2237
|
|
|
|
2238
|
|
|
$error = '#' . ((string) $error_number); |
2239
|
|
|
$separator = ' — '; |
2240
|
|
|
|
2241
|
|
|
if ($error_number == 2002) { |
2242
|
|
|
$error .= ' - ' . $error_message; |
2243
|
|
|
$error .= $separator; |
2244
|
|
|
$error .= __( |
2245
|
|
|
'The server is not responding (or the local server\'s socket' |
2246
|
|
|
. ' is not correctly configured).' |
2247
|
|
|
); |
2248
|
|
|
} elseif ($error_number == 2003) { |
2249
|
|
|
$error .= ' - ' . $error_message; |
2250
|
|
|
$error .= $separator . __('The server is not responding.'); |
2251
|
|
|
} elseif ($error_number == 1698) { |
2252
|
|
|
$error .= ' - ' . $error_message; |
2253
|
|
|
$error .= $separator . '<a href="' . Url::getFromRoute('/logout') . '" class="disableAjax">'; |
2254
|
|
|
$error .= __('Logout and try as another user.') . '</a>'; |
2255
|
|
|
} elseif ($error_number == 1005) { |
2256
|
|
|
if (strpos($error_message, 'errno: 13') !== false) { |
2257
|
|
|
$error .= ' - ' . $error_message; |
2258
|
|
|
$error .= $separator |
2259
|
|
|
. __( |
2260
|
|
|
'Please check privileges of directory containing database.' |
2261
|
|
|
); |
2262
|
|
|
} else { |
2263
|
|
|
/** |
2264
|
|
|
* InnoDB constraints, see |
2265
|
|
|
* https://dev.mysql.com/doc/refman/5.0/en/ |
2266
|
|
|
* innodb-foreign-key-constraints.html |
2267
|
|
|
*/ |
2268
|
|
|
$error .= ' - ' . $error_message . |
2269
|
|
|
' (<a href="' . |
2270
|
|
|
Url::getFromRoute('/server/engines', [ |
2271
|
|
|
'engine' => 'InnoDB', |
2272
|
|
|
'page' => 'Status', |
2273
|
|
|
]) . |
2274
|
|
|
'">' . __('Details…') . '</a>)'; |
2275
|
|
|
} |
2276
|
|
|
} else { |
2277
|
|
|
$error .= ' - ' . $error_message; |
2278
|
|
|
} |
2279
|
|
|
|
2280
|
|
|
return $error; |
2281
|
|
|
} |
2282
|
|
|
|
2283
|
|
|
/** |
2284
|
|
|
* gets the current user with host |
2285
|
|
|
* |
2286
|
|
|
* @return string the current user i.e. user@host |
2287
|
|
|
*/ |
2288
|
|
|
public function getCurrentUser(): string |
2289
|
|
|
{ |
2290
|
|
|
if (Util::cacheExists('mysql_cur_user')) { |
2291
|
|
|
return Util::cacheGet('mysql_cur_user'); |
|
|
|
|
2292
|
|
|
} |
2293
|
|
|
$user = $this->fetchValue('SELECT CURRENT_USER();'); |
2294
|
|
|
if ($user !== false) { |
2295
|
|
|
Util::cacheSet('mysql_cur_user', $user); |
2296
|
|
|
return $user; |
2297
|
|
|
} |
2298
|
|
|
return '@'; |
2299
|
|
|
} |
2300
|
|
|
|
2301
|
|
|
/** |
2302
|
|
|
* Checks if current user is superuser |
2303
|
|
|
* |
2304
|
|
|
* @return bool Whether user is a superuser |
2305
|
|
|
*/ |
2306
|
|
|
public function isSuperuser(): bool |
2307
|
|
|
{ |
2308
|
|
|
return $this->isUserType('super'); |
2309
|
|
|
} |
2310
|
|
|
|
2311
|
|
|
/** |
2312
|
|
|
* Checks if current user has global create user/grant privilege |
2313
|
|
|
* or is a superuser (i.e. SELECT on mysql.users) |
2314
|
|
|
* while caching the result in session. |
2315
|
|
|
* |
2316
|
|
|
* @param string $type type of user to check for |
2317
|
|
|
* i.e. 'create', 'grant', 'super' |
2318
|
|
|
* |
2319
|
|
|
* @return bool Whether user is a given type of user |
2320
|
|
|
*/ |
2321
|
|
|
public function isUserType(string $type): bool |
2322
|
|
|
{ |
2323
|
|
|
if (Util::cacheExists('is_' . $type . 'user')) { |
2324
|
|
|
return Util::cacheGet('is_' . $type . 'user'); |
|
|
|
|
2325
|
|
|
} |
2326
|
|
|
|
2327
|
|
|
// when connection failed we don't have a $userlink |
2328
|
|
|
if (! isset($this->_links[DatabaseInterface::CONNECT_USER])) { |
2329
|
|
|
return false; |
2330
|
|
|
} |
2331
|
|
|
|
2332
|
|
|
// checking if user is logged in |
2333
|
|
|
if ($type === 'logged') { |
2334
|
|
|
return true; |
2335
|
|
|
} |
2336
|
|
|
|
2337
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS'] || $type === 'super') { |
2338
|
|
|
// Prepare query for each user type check |
2339
|
|
|
$query = ''; |
2340
|
|
|
if ($type === 'super') { |
2341
|
|
|
$query = 'SELECT 1 FROM mysql.user LIMIT 1'; |
2342
|
|
|
} elseif ($type === 'create') { |
2343
|
|
|
list($user, $host) = $this->getCurrentUserAndHost(); |
2344
|
|
|
$query = "SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` " |
2345
|
|
|
. "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND " |
2346
|
|
|
. "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; |
2347
|
|
|
} elseif ($type === 'grant') { |
2348
|
|
|
list($user, $host) = $this->getCurrentUserAndHost(); |
2349
|
|
|
$query = "SELECT 1 FROM (" |
2350
|
|
|
. "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " |
2351
|
|
|
. "`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION " |
2352
|
|
|
. "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " |
2353
|
|
|
. "`INFORMATION_SCHEMA`.`TABLE_PRIVILEGES` UNION " |
2354
|
|
|
. "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " |
2355
|
|
|
. "`INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES` UNION " |
2356
|
|
|
. "SELECT `GRANTEE`, `IS_GRANTABLE` FROM " |
2357
|
|
|
. "`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t " |
2358
|
|
|
. "WHERE `IS_GRANTABLE` = 'YES' AND " |
2359
|
|
|
. "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; |
2360
|
|
|
} |
2361
|
|
|
|
2362
|
|
|
$is = false; |
2363
|
|
|
$result = $this->tryQuery( |
2364
|
|
|
$query, |
2365
|
|
|
self::CONNECT_USER, |
2366
|
|
|
self::QUERY_STORE |
2367
|
|
|
); |
2368
|
|
|
if ($result) { |
2369
|
|
|
$is = (bool) $this->numRows($result); |
2370
|
|
|
} |
2371
|
|
|
$this->freeResult($result); |
|
|
|
|
2372
|
|
|
} else { |
2373
|
|
|
$is = false; |
2374
|
|
|
$grants = $this->fetchResult( |
2375
|
|
|
"SHOW GRANTS FOR CURRENT_USER();", |
2376
|
|
|
null, |
2377
|
|
|
null, |
2378
|
|
|
self::CONNECT_USER, |
2379
|
|
|
self::QUERY_STORE |
2380
|
|
|
); |
2381
|
|
|
if ($grants) { |
2382
|
|
|
foreach ($grants as $grant) { |
2383
|
|
|
if ($type === 'create') { |
2384
|
|
|
if (strpos($grant, "ALL PRIVILEGES ON *.*") !== false |
|
|
|
|
2385
|
|
|
|| strpos($grant, "CREATE USER") !== false |
2386
|
|
|
) { |
2387
|
|
|
$is = true; |
2388
|
|
|
break; |
2389
|
|
|
} |
2390
|
|
|
} elseif ($type === 'grant') { |
2391
|
|
|
if (strpos($grant, "WITH GRANT OPTION") !== false) { |
2392
|
|
|
$is = true; |
2393
|
|
|
break; |
2394
|
|
|
} |
2395
|
|
|
} |
2396
|
|
|
} |
2397
|
|
|
} |
2398
|
|
|
} |
2399
|
|
|
|
2400
|
|
|
Util::cacheSet('is_' . $type . 'user', $is); |
2401
|
|
|
return $is; |
2402
|
|
|
} |
2403
|
|
|
|
2404
|
|
|
/** |
2405
|
|
|
* Get the current user and host |
2406
|
|
|
* |
2407
|
|
|
* @return array array of username and hostname |
2408
|
|
|
*/ |
2409
|
|
|
public function getCurrentUserAndHost(): array |
2410
|
|
|
{ |
2411
|
|
|
if (count($this->_current_user) === 0) { |
2412
|
|
|
$user = $this->getCurrentUser(); |
2413
|
|
|
$this->_current_user = explode("@", $user); |
2414
|
|
|
} |
2415
|
|
|
return $this->_current_user; |
2416
|
|
|
} |
2417
|
|
|
|
2418
|
|
|
/** |
2419
|
|
|
* Returns value for lower_case_table_names variable |
2420
|
|
|
* |
2421
|
|
|
* @return string|bool |
2422
|
|
|
*/ |
2423
|
|
|
public function getLowerCaseNames() |
2424
|
|
|
{ |
2425
|
|
|
if ($this->_lower_case_table_names === null) { |
2426
|
|
|
$this->_lower_case_table_names = $this->fetchValue( |
|
|
|
|
2427
|
|
|
"SELECT @@lower_case_table_names" |
2428
|
|
|
); |
2429
|
|
|
} |
2430
|
|
|
return $this->_lower_case_table_names; |
2431
|
|
|
} |
2432
|
|
|
|
2433
|
|
|
/** |
2434
|
|
|
* Get the list of system schemas |
2435
|
|
|
* |
2436
|
|
|
* @return array list of system schemas |
2437
|
|
|
*/ |
2438
|
|
|
public function getSystemSchemas(): array |
2439
|
|
|
{ |
2440
|
|
|
$schemas = [ |
2441
|
|
|
'information_schema', |
2442
|
|
|
'performance_schema', |
2443
|
|
|
'mysql', |
2444
|
|
|
'sys', |
2445
|
|
|
]; |
2446
|
|
|
$systemSchemas = []; |
2447
|
|
|
foreach ($schemas as $schema) { |
2448
|
|
|
if ($this->isSystemSchema($schema, true)) { |
2449
|
|
|
$systemSchemas[] = $schema; |
2450
|
|
|
} |
2451
|
|
|
} |
2452
|
|
|
return $systemSchemas; |
2453
|
|
|
} |
2454
|
|
|
|
2455
|
|
|
/** |
2456
|
|
|
* Checks whether given schema is a system schema |
2457
|
|
|
* |
2458
|
|
|
* @param string $schema_name Name of schema (database) to test |
2459
|
|
|
* @param bool $testForMysqlSchema Whether 'mysql' schema should |
2460
|
|
|
* be treated the same as IS and DD |
2461
|
|
|
* |
2462
|
|
|
* @return bool |
2463
|
|
|
*/ |
2464
|
|
|
public function isSystemSchema( |
2465
|
|
|
string $schema_name, |
2466
|
|
|
bool $testForMysqlSchema = false |
2467
|
|
|
): bool { |
2468
|
|
|
$schema_name = strtolower($schema_name); |
2469
|
|
|
return $schema_name == 'information_schema' |
2470
|
|
|
|| $schema_name == 'performance_schema' |
2471
|
|
|
|| ($schema_name == 'mysql' && $testForMysqlSchema) |
2472
|
|
|
|| $schema_name == 'sys'; |
2473
|
|
|
} |
2474
|
|
|
|
2475
|
|
|
/** |
2476
|
|
|
* Return connection parameters for the database server |
2477
|
|
|
* |
2478
|
|
|
* @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL |
2479
|
|
|
* or CONNECT_AUXILIARY. |
2480
|
|
|
* @param array|null $server Server information like host/port/socket/persistent |
2481
|
|
|
* |
2482
|
|
|
* @return array user, host and server settings array |
2483
|
|
|
*/ |
2484
|
|
|
public function getConnectionParams(int $mode, ?array $server = null): array |
2485
|
|
|
{ |
2486
|
|
|
global $cfg; |
2487
|
|
|
|
2488
|
|
|
$user = null; |
2489
|
|
|
$password = null; |
2490
|
|
|
|
2491
|
|
|
if ($mode == DatabaseInterface::CONNECT_USER) { |
2492
|
|
|
$user = $cfg['Server']['user']; |
2493
|
|
|
$password = $cfg['Server']['password']; |
2494
|
|
|
$server = $cfg['Server']; |
2495
|
|
|
} elseif ($mode == DatabaseInterface::CONNECT_CONTROL) { |
2496
|
|
|
$user = $cfg['Server']['controluser']; |
2497
|
|
|
$password = $cfg['Server']['controlpass']; |
2498
|
|
|
|
2499
|
|
|
$server = []; |
2500
|
|
|
|
2501
|
|
|
if (! empty($cfg['Server']['controlhost'])) { |
2502
|
|
|
$server['host'] = $cfg['Server']['controlhost']; |
2503
|
|
|
} else { |
2504
|
|
|
$server['host'] = $cfg['Server']['host']; |
2505
|
|
|
} |
2506
|
|
|
// Share the settings if the host is same |
2507
|
|
|
if ($server['host'] == $cfg['Server']['host']) { |
2508
|
|
|
$shared = [ |
2509
|
|
|
'port', |
2510
|
|
|
'socket', |
2511
|
|
|
'compress', |
2512
|
|
|
'ssl', |
2513
|
|
|
'ssl_key', |
2514
|
|
|
'ssl_cert', |
2515
|
|
|
'ssl_ca', |
2516
|
|
|
'ssl_ca_path', |
2517
|
|
|
'ssl_ciphers', |
2518
|
|
|
'ssl_verify', |
2519
|
|
|
]; |
2520
|
|
|
foreach ($shared as $item) { |
2521
|
|
|
if (isset($cfg['Server'][$item])) { |
2522
|
|
|
$server[$item] = $cfg['Server'][$item]; |
2523
|
|
|
} |
2524
|
|
|
} |
2525
|
|
|
} |
2526
|
|
|
// Set configured port |
2527
|
|
|
if (! empty($cfg['Server']['controlport'])) { |
2528
|
|
|
$server['port'] = $cfg['Server']['controlport']; |
2529
|
|
|
} |
2530
|
|
|
// Set any configuration with control_ prefix |
2531
|
|
|
foreach ($cfg['Server'] as $key => $val) { |
2532
|
|
|
if (substr($key, 0, 8) === 'control_') { |
2533
|
|
|
$server[substr($key, 8)] = $val; |
2534
|
|
|
} |
2535
|
|
|
} |
2536
|
|
|
} else { |
2537
|
|
|
if ($server === null) { |
2538
|
|
|
return [ |
2539
|
|
|
null, |
2540
|
|
|
null, |
2541
|
|
|
null, |
2542
|
|
|
]; |
2543
|
|
|
} |
2544
|
|
|
if (isset($server['user'])) { |
2545
|
|
|
$user = $server['user']; |
2546
|
|
|
} |
2547
|
|
|
if (isset($server['password'])) { |
2548
|
|
|
$password = $server['password']; |
2549
|
|
|
} |
2550
|
|
|
} |
2551
|
|
|
|
2552
|
|
|
// Perform sanity checks on some variables |
2553
|
|
|
if (empty($server['port'])) { |
2554
|
|
|
$server['port'] = 0; |
2555
|
|
|
} else { |
2556
|
|
|
$server['port'] = intval($server['port']); |
2557
|
|
|
} |
2558
|
|
|
if (empty($server['socket'])) { |
2559
|
|
|
$server['socket'] = null; |
2560
|
|
|
} |
2561
|
|
|
if (empty($server['host'])) { |
2562
|
|
|
$server['host'] = 'localhost'; |
2563
|
|
|
} |
2564
|
|
|
if (! isset($server['ssl'])) { |
2565
|
|
|
$server['ssl'] = false; |
2566
|
|
|
} |
2567
|
|
|
if (! isset($server['compress'])) { |
2568
|
|
|
$server['compress'] = false; |
2569
|
|
|
} |
2570
|
|
|
|
2571
|
|
|
return [ |
2572
|
|
|
$user, |
2573
|
|
|
$password, |
2574
|
|
|
$server, |
2575
|
|
|
]; |
2576
|
|
|
} |
2577
|
|
|
|
2578
|
|
|
/** |
2579
|
|
|
* connects to the database server |
2580
|
|
|
* |
2581
|
|
|
* @param integer $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL |
2582
|
|
|
* or CONNECT_AUXILIARY. |
2583
|
|
|
* @param array|null $server Server information like host/port/socket/persistent |
2584
|
|
|
* @param integer $target How to store connection link, defaults to $mode |
2585
|
|
|
* |
2586
|
|
|
* @return mixed false on error or a connection object on success |
2587
|
|
|
*/ |
2588
|
|
|
public function connect(int $mode, ?array $server = null, ?int $target = null) |
2589
|
|
|
{ |
2590
|
|
|
list($user, $password, $server) = $this->getConnectionParams($mode, $server); |
2591
|
|
|
|
2592
|
|
|
if ($target === null) { |
2593
|
|
|
$target = $mode; |
2594
|
|
|
} |
2595
|
|
|
|
2596
|
|
|
if ($user === null || $password === null) { |
2597
|
|
|
trigger_error( |
2598
|
|
|
__('Missing connection parameters!'), |
2599
|
|
|
E_USER_WARNING |
2600
|
|
|
); |
2601
|
|
|
return false; |
2602
|
|
|
} |
2603
|
|
|
|
2604
|
|
|
// Do not show location and backtrace for connection errors |
2605
|
|
|
$GLOBALS['error_handler']->setHideLocation(true); |
2606
|
|
|
$result = $this->_extension->connect( |
2607
|
|
|
$user, |
2608
|
|
|
$password, |
2609
|
|
|
$server |
2610
|
|
|
); |
2611
|
|
|
$GLOBALS['error_handler']->setHideLocation(false); |
2612
|
|
|
|
2613
|
|
|
if ($result) { |
2614
|
|
|
$this->_links[$target] = $result; |
2615
|
|
|
/* Run post connect for user connections */ |
2616
|
|
|
if ($target == DatabaseInterface::CONNECT_USER) { |
2617
|
|
|
$this->postConnect(); |
2618
|
|
|
} elseif ($target == DatabaseInterface::CONNECT_CONTROL) { |
2619
|
|
|
$this->postConnectControl(); |
2620
|
|
|
} |
2621
|
|
|
return $result; |
2622
|
|
|
} |
2623
|
|
|
|
2624
|
|
|
if ($mode == DatabaseInterface::CONNECT_CONTROL) { |
2625
|
|
|
trigger_error( |
2626
|
|
|
__( |
2627
|
|
|
'Connection for controluser as defined in your ' |
2628
|
|
|
. 'configuration failed.' |
2629
|
|
|
), |
2630
|
|
|
E_USER_WARNING |
2631
|
|
|
); |
2632
|
|
|
return false; |
2633
|
|
|
} elseif ($mode == DatabaseInterface::CONNECT_AUXILIARY) { |
2634
|
|
|
// Do not go back to main login if connection failed |
2635
|
|
|
// (currently used only in unit testing) |
2636
|
|
|
return false; |
2637
|
|
|
} |
2638
|
|
|
|
2639
|
|
|
return $result; |
2640
|
|
|
} |
2641
|
|
|
|
2642
|
|
|
/** |
2643
|
|
|
* selects given database |
2644
|
|
|
* |
2645
|
|
|
* @param string $dbname database name to select |
2646
|
|
|
* @param integer $link link type |
2647
|
|
|
* |
2648
|
|
|
* @return boolean |
2649
|
|
|
*/ |
2650
|
|
|
public function selectDb(string $dbname, $link = DatabaseInterface::CONNECT_USER): bool |
2651
|
|
|
{ |
2652
|
|
|
if (! isset($this->_links[$link])) { |
2653
|
|
|
return false; |
2654
|
|
|
} |
2655
|
|
|
return $this->_extension->selectDb($dbname, $this->_links[$link]); |
2656
|
|
|
} |
2657
|
|
|
|
2658
|
|
|
/** |
2659
|
|
|
* returns array of rows with associative and numeric keys from $result |
2660
|
|
|
* |
2661
|
|
|
* @param object $result result set identifier |
2662
|
|
|
* |
2663
|
|
|
* @return array |
2664
|
|
|
*/ |
2665
|
|
|
public function fetchArray($result) |
2666
|
|
|
{ |
2667
|
|
|
return $this->_extension->fetchArray($result); |
2668
|
|
|
} |
2669
|
|
|
|
2670
|
|
|
/** |
2671
|
|
|
* returns array of rows with associative keys from $result |
2672
|
|
|
* |
2673
|
|
|
* @param object $result result set identifier |
2674
|
|
|
* |
2675
|
|
|
* @return array|bool |
2676
|
|
|
*/ |
2677
|
|
|
public function fetchAssoc($result) |
2678
|
|
|
{ |
2679
|
|
|
return $this->_extension->fetchAssoc($result); |
2680
|
|
|
} |
2681
|
|
|
|
2682
|
|
|
/** |
2683
|
|
|
* returns array of rows with numeric keys from $result |
2684
|
|
|
* |
2685
|
|
|
* @param object $result result set identifier |
2686
|
|
|
* |
2687
|
|
|
* @return array|bool |
2688
|
|
|
*/ |
2689
|
|
|
public function fetchRow($result) |
2690
|
|
|
{ |
2691
|
|
|
return $this->_extension->fetchRow($result); |
2692
|
|
|
} |
2693
|
|
|
|
2694
|
|
|
/** |
2695
|
|
|
* Adjusts the result pointer to an arbitrary row in the result |
2696
|
|
|
* |
2697
|
|
|
* @param object $result database result |
2698
|
|
|
* @param integer $offset offset to seek |
2699
|
|
|
* |
2700
|
|
|
* @return bool true on success, false on failure |
2701
|
|
|
*/ |
2702
|
|
|
public function dataSeek($result, int $offset): bool |
2703
|
|
|
{ |
2704
|
|
|
return $this->_extension->dataSeek($result, $offset); |
2705
|
|
|
} |
2706
|
|
|
|
2707
|
|
|
/** |
2708
|
|
|
* Frees memory associated with the result |
2709
|
|
|
* |
2710
|
|
|
* @param object $result database result |
2711
|
|
|
* |
2712
|
|
|
* @return void |
2713
|
|
|
*/ |
2714
|
|
|
public function freeResult($result): void |
2715
|
|
|
{ |
2716
|
|
|
$this->_extension->freeResult($result); |
2717
|
|
|
} |
2718
|
|
|
|
2719
|
|
|
/** |
2720
|
|
|
* Check if there are any more query results from a multi query |
2721
|
|
|
* |
2722
|
|
|
* @param integer $link link type |
2723
|
|
|
* |
2724
|
|
|
* @return bool true or false |
2725
|
|
|
*/ |
2726
|
|
|
public function moreResults($link = DatabaseInterface::CONNECT_USER): bool |
2727
|
|
|
{ |
2728
|
|
|
if (! isset($this->_links[$link])) { |
2729
|
|
|
return false; |
2730
|
|
|
} |
2731
|
|
|
return $this->_extension->moreResults($this->_links[$link]); |
2732
|
|
|
} |
2733
|
|
|
|
2734
|
|
|
/** |
2735
|
|
|
* Prepare next result from multi_query |
2736
|
|
|
* |
2737
|
|
|
* @param integer $link link type |
2738
|
|
|
* |
2739
|
|
|
* @return bool true or false |
2740
|
|
|
*/ |
2741
|
|
|
public function nextResult($link = DatabaseInterface::CONNECT_USER): bool |
2742
|
|
|
{ |
2743
|
|
|
if (! isset($this->_links[$link])) { |
2744
|
|
|
return false; |
2745
|
|
|
} |
2746
|
|
|
return $this->_extension->nextResult($this->_links[$link]); |
2747
|
|
|
} |
2748
|
|
|
|
2749
|
|
|
/** |
2750
|
|
|
* Store the result returned from multi query |
2751
|
|
|
* |
2752
|
|
|
* @param integer $link link type |
2753
|
|
|
* |
2754
|
|
|
* @return mixed false when empty results / result set when not empty |
2755
|
|
|
*/ |
2756
|
|
|
public function storeResult($link = DatabaseInterface::CONNECT_USER) |
2757
|
|
|
{ |
2758
|
|
|
if (! isset($this->_links[$link])) { |
2759
|
|
|
return false; |
2760
|
|
|
} |
2761
|
|
|
return $this->_extension->storeResult($this->_links[$link]); |
2762
|
|
|
} |
2763
|
|
|
|
2764
|
|
|
/** |
2765
|
|
|
* Returns a string representing the type of connection used |
2766
|
|
|
* |
2767
|
|
|
* @param integer $link link type |
2768
|
|
|
* |
2769
|
|
|
* @return string|bool type of connection used |
2770
|
|
|
*/ |
2771
|
|
|
public function getHostInfo($link = DatabaseInterface::CONNECT_USER) |
2772
|
|
|
{ |
2773
|
|
|
if (! isset($this->_links[$link])) { |
2774
|
|
|
return false; |
2775
|
|
|
} |
2776
|
|
|
return $this->_extension->getHostInfo($this->_links[$link]); |
2777
|
|
|
} |
2778
|
|
|
|
2779
|
|
|
/** |
2780
|
|
|
* Returns the version of the MySQL protocol used |
2781
|
|
|
* |
2782
|
|
|
* @param integer $link link type |
2783
|
|
|
* |
2784
|
|
|
* @return int|bool version of the MySQL protocol used |
2785
|
|
|
*/ |
2786
|
|
|
public function getProtoInfo($link = DatabaseInterface::CONNECT_USER) |
2787
|
|
|
{ |
2788
|
|
|
if (! isset($this->_links[$link])) { |
2789
|
|
|
return false; |
2790
|
|
|
} |
2791
|
|
|
return $this->_extension->getProtoInfo($this->_links[$link]); |
2792
|
|
|
} |
2793
|
|
|
|
2794
|
|
|
/** |
2795
|
|
|
* returns a string that represents the client library version |
2796
|
|
|
* |
2797
|
|
|
* @param integer $link link type |
2798
|
|
|
* |
2799
|
|
|
* @return string MySQL client library version |
2800
|
|
|
*/ |
2801
|
|
|
public function getClientInfo($link = DatabaseInterface::CONNECT_USER): string |
2802
|
|
|
{ |
2803
|
|
|
if (! isset($this->_links[$link])) { |
2804
|
|
|
return ''; |
2805
|
|
|
} |
2806
|
|
|
return $this->_extension->getClientInfo($this->_links[$link]); |
2807
|
|
|
} |
2808
|
|
|
|
2809
|
|
|
/** |
2810
|
|
|
* returns last error message or false if no errors occurred |
2811
|
|
|
* |
2812
|
|
|
* @param integer $link link type |
2813
|
|
|
* |
2814
|
|
|
* @return string|bool error or false |
2815
|
|
|
*/ |
2816
|
|
|
public function getError($link = DatabaseInterface::CONNECT_USER) |
2817
|
|
|
{ |
2818
|
|
|
if (! isset($this->_links[$link])) { |
2819
|
|
|
return false; |
2820
|
|
|
} |
2821
|
|
|
return $this->_extension->getError($this->_links[$link]); |
2822
|
|
|
} |
2823
|
|
|
|
2824
|
|
|
/** |
2825
|
|
|
* returns the number of rows returned by last query |
2826
|
|
|
* |
2827
|
|
|
* @param object $result result set identifier |
2828
|
|
|
* |
2829
|
|
|
* @return string|int |
2830
|
|
|
*/ |
2831
|
|
|
public function numRows($result) |
2832
|
|
|
{ |
2833
|
|
|
return $this->_extension->numRows($result); |
2834
|
|
|
} |
2835
|
|
|
|
2836
|
|
|
/** |
2837
|
|
|
* returns last inserted auto_increment id for given $link |
2838
|
|
|
* or $GLOBALS['userlink'] |
2839
|
|
|
* |
2840
|
|
|
* @param integer $link link type |
2841
|
|
|
* |
2842
|
|
|
* @return int|boolean |
2843
|
|
|
*/ |
2844
|
|
|
public function insertId($link = DatabaseInterface::CONNECT_USER) |
2845
|
|
|
{ |
2846
|
|
|
// If the primary key is BIGINT we get an incorrect result |
2847
|
|
|
// (sometimes negative, sometimes positive) |
2848
|
|
|
// and in the present function we don't know if the PK is BIGINT |
2849
|
|
|
// so better play safe and use LAST_INSERT_ID() |
2850
|
|
|
// |
2851
|
|
|
// When no controluser is defined, using mysqli_insert_id($link) |
2852
|
|
|
// does not always return the last insert id due to a mixup with |
2853
|
|
|
// the tracking mechanism, but this works: |
2854
|
|
|
return $this->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link); |
2855
|
|
|
} |
2856
|
|
|
|
2857
|
|
|
/** |
2858
|
|
|
* returns the number of rows affected by last query |
2859
|
|
|
* |
2860
|
|
|
* @param integer $link link type |
2861
|
|
|
* @param bool $get_from_cache whether to retrieve from cache |
2862
|
|
|
* |
2863
|
|
|
* @return int|boolean |
2864
|
|
|
*/ |
2865
|
|
|
public function affectedRows( |
2866
|
|
|
$link = DatabaseInterface::CONNECT_USER, |
2867
|
|
|
bool $get_from_cache = true |
2868
|
|
|
) { |
2869
|
|
|
if (! isset($this->_links[$link])) { |
2870
|
|
|
return false; |
2871
|
|
|
} |
2872
|
|
|
|
2873
|
|
|
if ($get_from_cache) { |
2874
|
|
|
return $GLOBALS['cached_affected_rows']; |
2875
|
|
|
} |
2876
|
|
|
|
2877
|
|
|
return $this->_extension->affectedRows($this->_links[$link]); |
2878
|
|
|
} |
2879
|
|
|
|
2880
|
|
|
/** |
2881
|
|
|
* returns metainfo for fields in $result |
2882
|
|
|
* |
2883
|
|
|
* @param object $result result set identifier |
2884
|
|
|
* |
2885
|
|
|
* @return mixed meta info for fields in $result |
2886
|
|
|
*/ |
2887
|
|
|
public function getFieldsMeta($result) |
2888
|
|
|
{ |
2889
|
|
|
$result = $this->_extension->getFieldsMeta($result); |
2890
|
|
|
|
2891
|
|
|
if ($this->getLowerCaseNames() === '2') { |
2892
|
|
|
/** |
2893
|
|
|
* Fixup orgtable for lower_case_table_names = 2 |
2894
|
|
|
* |
2895
|
|
|
* In this setup MySQL server reports table name lower case |
2896
|
|
|
* but we still need to operate on original case to properly |
2897
|
|
|
* match existing strings |
2898
|
|
|
*/ |
2899
|
|
|
foreach ($result as $value) { |
2900
|
|
|
if (strlen($value->orgtable) !== 0 && |
2901
|
|
|
mb_strtolower($value->orgtable) === mb_strtolower($value->table)) { |
2902
|
|
|
$value->orgtable = $value->table; |
2903
|
|
|
} |
2904
|
|
|
} |
2905
|
|
|
} |
2906
|
|
|
|
2907
|
|
|
return $result; |
2908
|
|
|
} |
2909
|
|
|
|
2910
|
|
|
/** |
2911
|
|
|
* return number of fields in given $result |
2912
|
|
|
* |
2913
|
|
|
* @param object $result result set identifier |
2914
|
|
|
* |
2915
|
|
|
* @return int field count |
2916
|
|
|
*/ |
2917
|
|
|
public function numFields($result): int |
2918
|
|
|
{ |
2919
|
|
|
return $this->_extension->numFields($result); |
2920
|
|
|
} |
2921
|
|
|
|
2922
|
|
|
/** |
2923
|
|
|
* returns the length of the given field $i in $result |
2924
|
|
|
* |
2925
|
|
|
* @param object $result result set identifier |
2926
|
|
|
* @param int $i field |
2927
|
|
|
* |
2928
|
|
|
* @return int|bool length of field |
2929
|
|
|
*/ |
2930
|
|
|
public function fieldLen($result, int $i) |
2931
|
|
|
{ |
2932
|
|
|
return $this->_extension->fieldLen($result, $i); |
2933
|
|
|
} |
2934
|
|
|
|
2935
|
|
|
/** |
2936
|
|
|
* returns name of $i. field in $result |
2937
|
|
|
* |
2938
|
|
|
* @param object $result result set identifier |
2939
|
|
|
* @param int $i field |
2940
|
|
|
* |
2941
|
|
|
* @return string name of $i. field in $result |
2942
|
|
|
*/ |
2943
|
|
|
public function fieldName($result, int $i): string |
2944
|
|
|
{ |
2945
|
|
|
return $this->_extension->fieldName($result, $i); |
2946
|
|
|
} |
2947
|
|
|
|
2948
|
|
|
/** |
2949
|
|
|
* returns concatenated string of human readable field flags |
2950
|
|
|
* |
2951
|
|
|
* @param object $result result set identifier |
2952
|
|
|
* @param int $i field |
2953
|
|
|
* |
2954
|
|
|
* @return string field flags |
2955
|
|
|
*/ |
2956
|
|
|
public function fieldFlags($result, $i): string |
2957
|
|
|
{ |
2958
|
|
|
return $this->_extension->fieldFlags($result, $i); |
2959
|
|
|
} |
2960
|
|
|
|
2961
|
|
|
/** |
2962
|
|
|
* returns properly escaped string for use in MySQL queries |
2963
|
|
|
* |
2964
|
|
|
* @param string $str string to be escaped |
2965
|
|
|
* @param mixed $link optional database link to use |
2966
|
|
|
* |
2967
|
|
|
* @return string a MySQL escaped string |
2968
|
|
|
*/ |
2969
|
|
|
public function escapeString(string $str, $link = DatabaseInterface::CONNECT_USER) |
2970
|
|
|
{ |
2971
|
|
|
if ($this->_extension === null || ! isset($this->_links[$link])) { |
2972
|
|
|
return $str; |
2973
|
|
|
} |
2974
|
|
|
|
2975
|
|
|
return $this->_extension->escapeString($this->_links[$link], $str); |
2976
|
|
|
} |
2977
|
|
|
|
2978
|
|
|
/** |
2979
|
|
|
* Checks if this database server is running on Amazon RDS. |
2980
|
|
|
* |
2981
|
|
|
* @return boolean |
2982
|
|
|
*/ |
2983
|
|
|
public function isAmazonRds(): bool |
2984
|
|
|
{ |
2985
|
|
|
if (Util::cacheExists('is_amazon_rds')) { |
2986
|
|
|
return Util::cacheGet('is_amazon_rds'); |
|
|
|
|
2987
|
|
|
} |
2988
|
|
|
$sql = 'SELECT @@basedir'; |
2989
|
|
|
$result = $this->fetchValue($sql); |
2990
|
|
|
$rds = (substr($result, 0, 10) == '/rdsdbbin/'); |
|
|
|
|
2991
|
|
|
Util::cacheSet('is_amazon_rds', $rds); |
2992
|
|
|
|
2993
|
|
|
return $rds; |
2994
|
|
|
} |
2995
|
|
|
|
2996
|
|
|
/** |
2997
|
|
|
* Gets SQL for killing a process. |
2998
|
|
|
* |
2999
|
|
|
* @param int $process Process ID |
3000
|
|
|
* |
3001
|
|
|
* @return string |
3002
|
|
|
*/ |
3003
|
|
|
public function getKillQuery(int $process): string |
3004
|
|
|
{ |
3005
|
|
|
if ($this->isAmazonRds()) { |
3006
|
|
|
return 'CALL mysql.rds_kill(' . $process . ');'; |
3007
|
|
|
} |
3008
|
|
|
|
3009
|
|
|
return 'KILL ' . $process . ';'; |
3010
|
|
|
} |
3011
|
|
|
|
3012
|
|
|
/** |
3013
|
|
|
* Get the phpmyadmin database manager |
3014
|
|
|
* |
3015
|
|
|
* @return SystemDatabase |
3016
|
|
|
*/ |
3017
|
|
|
public function getSystemDatabase(): SystemDatabase |
3018
|
|
|
{ |
3019
|
|
|
return new SystemDatabase($this); |
3020
|
|
|
} |
3021
|
|
|
|
3022
|
|
|
/** |
3023
|
|
|
* Get a table with database name and table name |
3024
|
|
|
* |
3025
|
|
|
* @param string $db_name DB name |
3026
|
|
|
* @param string $table_name Table name |
3027
|
|
|
* |
3028
|
|
|
* @return Table |
3029
|
|
|
*/ |
3030
|
|
|
public function getTable(string $db_name, string $table_name): Table |
3031
|
|
|
{ |
3032
|
|
|
return new Table($table_name, $db_name, $this); |
3033
|
|
|
} |
3034
|
|
|
|
3035
|
|
|
/** |
3036
|
|
|
* returns collation of given db |
3037
|
|
|
* |
3038
|
|
|
* @param string $db name of db |
3039
|
|
|
* |
3040
|
|
|
* @return string collation of $db |
3041
|
|
|
*/ |
3042
|
|
|
public function getDbCollation(string $db): string |
3043
|
|
|
{ |
3044
|
|
|
if ($this->isSystemSchema($db)) { |
3045
|
|
|
// We don't have to check the collation of the virtual |
3046
|
|
|
// information_schema database: We know it! |
3047
|
|
|
return 'utf8_general_ci'; |
3048
|
|
|
} |
3049
|
|
|
|
3050
|
|
|
if (! $GLOBALS['cfg']['Server']['DisableIS']) { |
3051
|
|
|
// this is slow with thousands of databases |
3052
|
|
|
$sql = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA' |
3053
|
|
|
. ' WHERE SCHEMA_NAME = \'' . $this->escapeString($db) |
3054
|
|
|
. '\' LIMIT 1'; |
3055
|
|
|
return $this->fetchValue($sql); |
|
|
|
|
3056
|
|
|
} |
3057
|
|
|
|
3058
|
|
|
$this->selectDb($db); |
3059
|
|
|
$return = $this->fetchValue('SELECT @@collation_database'); |
3060
|
|
|
if ($db !== $GLOBALS['db']) { |
3061
|
|
|
$this->selectDb($GLOBALS['db']); |
3062
|
|
|
} |
3063
|
|
|
return $return; |
|
|
|
|
3064
|
|
|
} |
3065
|
|
|
|
3066
|
|
|
/** |
3067
|
|
|
* returns default server collation from show variables |
3068
|
|
|
* |
3069
|
|
|
* @return string |
3070
|
|
|
*/ |
3071
|
|
|
public function getServerCollation(): string |
3072
|
|
|
{ |
3073
|
|
|
return $this->fetchValue('SELECT @@collation_server'); |
|
|
|
|
3074
|
|
|
} |
3075
|
|
|
|
3076
|
|
|
/** |
3077
|
|
|
* Server version as number |
3078
|
|
|
* |
3079
|
|
|
* @return integer |
3080
|
|
|
*/ |
3081
|
|
|
public function getVersion(): int |
3082
|
|
|
{ |
3083
|
|
|
return $this->_version_int; |
3084
|
|
|
} |
3085
|
|
|
|
3086
|
|
|
/** |
3087
|
|
|
* Server version |
3088
|
|
|
* |
3089
|
|
|
* @return string |
3090
|
|
|
*/ |
3091
|
|
|
public function getVersionString(): string |
3092
|
|
|
{ |
3093
|
|
|
return $this->_version_str; |
3094
|
|
|
} |
3095
|
|
|
|
3096
|
|
|
/** |
3097
|
|
|
* Server version comment |
3098
|
|
|
* |
3099
|
|
|
* @return string |
3100
|
|
|
*/ |
3101
|
|
|
public function getVersionComment(): string |
3102
|
|
|
{ |
3103
|
|
|
return $this->_version_comment; |
3104
|
|
|
} |
3105
|
|
|
|
3106
|
|
|
/** |
3107
|
|
|
* Whether connection is MariaDB |
3108
|
|
|
* |
3109
|
|
|
* @return boolean |
3110
|
|
|
*/ |
3111
|
|
|
public function isMariaDB(): bool |
3112
|
|
|
{ |
3113
|
|
|
return $this->_is_mariadb; |
3114
|
|
|
} |
3115
|
|
|
|
3116
|
|
|
/** |
3117
|
|
|
* Whether connection is Percona |
3118
|
|
|
* |
3119
|
|
|
* @return boolean |
3120
|
|
|
*/ |
3121
|
|
|
public function isPercona(): bool |
3122
|
|
|
{ |
3123
|
|
|
return $this->_is_percona; |
3124
|
|
|
} |
3125
|
|
|
|
3126
|
|
|
/** |
3127
|
|
|
* Load correct database driver |
3128
|
|
|
* |
3129
|
|
|
* @param DbiExtension|null $extension Force the use of an alternative extension |
3130
|
|
|
* |
3131
|
|
|
* @return self |
3132
|
|
|
*/ |
3133
|
|
|
public static function load(?DbiExtension $extension = null): self |
3134
|
|
|
{ |
3135
|
|
|
global $dbi; |
3136
|
|
|
|
3137
|
|
|
if ($extension !== null) { |
3138
|
|
|
$dbi = new self($extension); |
3139
|
|
|
return $dbi; |
3140
|
|
|
} |
3141
|
|
|
|
3142
|
|
|
if (! self::checkDbExtension('mysqli')) { |
3143
|
|
|
$docUrl = Util::getDocuLink('faq', 'faqmysql'); |
3144
|
|
|
$docLink = sprintf( |
3145
|
|
|
__('See %sour documentation%s for more information.'), |
3146
|
|
|
'[a@' . $docUrl . '@documentation]', |
3147
|
|
|
'[/a]' |
3148
|
|
|
); |
3149
|
|
|
Core::warnMissingExtension( |
3150
|
|
|
'mysqli', |
3151
|
|
|
true, |
3152
|
|
|
$docLink |
3153
|
|
|
); |
3154
|
|
|
} |
3155
|
|
|
|
3156
|
|
|
$dbi = new self(new DbiMysqli()); |
3157
|
|
|
return $dbi; |
3158
|
|
|
} |
3159
|
|
|
|
3160
|
|
|
/** |
3161
|
|
|
* Prepare an SQL statement for execution. |
3162
|
|
|
* |
3163
|
|
|
* @param string $query The query, as a string. |
3164
|
|
|
* @param int $link Link type. |
3165
|
|
|
* |
3166
|
|
|
* @return object|false A statement object or false. |
3167
|
|
|
*/ |
3168
|
|
|
public function prepare(string $query, $link = DatabaseInterface::CONNECT_USER) |
3169
|
|
|
{ |
3170
|
|
|
return $this->_extension->prepare($this->_links[$link], $query); |
3171
|
|
|
} |
3172
|
|
|
} |
3173
|
|
|
|