1 | <?php |
||
2 | /* vim: set expandtab sw=4 ts=4 sts=4: */ |
||
3 | /** |
||
4 | * Set of functions for the SQL executor |
||
5 | * |
||
6 | * @package PhpMyAdmin |
||
7 | */ |
||
8 | declare(strict_types=1); |
||
9 | |||
10 | namespace PhpMyAdmin; |
||
11 | |||
12 | use PhpMyAdmin\Bookmark; |
||
13 | use PhpMyAdmin\Core; |
||
14 | use PhpMyAdmin\DatabaseInterface; |
||
15 | use PhpMyAdmin\Display\Results as DisplayResults; |
||
16 | use PhpMyAdmin\Index; |
||
17 | use PhpMyAdmin\Message; |
||
18 | use PhpMyAdmin\Operations; |
||
19 | use PhpMyAdmin\ParseAnalyze; |
||
20 | use PhpMyAdmin\Relation; |
||
21 | use PhpMyAdmin\RelationCleanup; |
||
22 | use PhpMyAdmin\Response; |
||
23 | use PhpMyAdmin\SqlParser\Statements\AlterStatement; |
||
24 | use PhpMyAdmin\SqlParser\Statements\DropStatement; |
||
25 | use PhpMyAdmin\SqlParser\Statements\SelectStatement; |
||
26 | use PhpMyAdmin\SqlParser\Utils\Query; |
||
27 | use PhpMyAdmin\Table; |
||
28 | use PhpMyAdmin\Transformations; |
||
29 | use PhpMyAdmin\Url; |
||
30 | use PhpMyAdmin\Util; |
||
31 | |||
32 | /** |
||
33 | * Set of functions for the SQL executor |
||
34 | * |
||
35 | * @package PhpMyAdmin |
||
36 | */ |
||
37 | class Sql |
||
38 | { |
||
39 | /** |
||
40 | * @var Relation |
||
41 | */ |
||
42 | private $relation; |
||
43 | |||
44 | /** |
||
45 | * @var RelationCleanup |
||
46 | */ |
||
47 | private $relationCleanup; |
||
48 | |||
49 | /** |
||
50 | * @var Transformations |
||
51 | */ |
||
52 | private $transformations; |
||
53 | |||
54 | /** |
||
55 | * @var Operations |
||
56 | */ |
||
57 | private $operations; |
||
58 | |||
59 | /** |
||
60 | * @var Template |
||
61 | */ |
||
62 | private $template; |
||
63 | |||
64 | /** |
||
65 | * Constructor |
||
66 | */ |
||
67 | public function __construct() |
||
68 | { |
||
69 | $this->relation = new Relation($GLOBALS['dbi']); |
||
70 | $this->relationCleanup = new RelationCleanup($GLOBALS['dbi'], $this->relation); |
||
71 | $this->operations = new Operations($GLOBALS['dbi'], $this->relation); |
||
72 | $this->transformations = new Transformations(); |
||
73 | $this->template = new Template(); |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * Parses and analyzes the given SQL query. |
||
78 | * |
||
79 | * @param string $sql_query SQL query |
||
80 | * @param string $db DB name |
||
81 | * |
||
82 | * @return mixed |
||
83 | */ |
||
84 | public function parseAndAnalyze($sql_query, $db = null) |
||
85 | { |
||
86 | if (is_null($db) && isset($GLOBALS['db']) && strlen($GLOBALS['db'])) { |
||
87 | $db = $GLOBALS['db']; |
||
88 | } |
||
89 | list($analyzed_sql_results,,) = ParseAnalyze::sqlQuery($sql_query, $db); |
||
90 | return $analyzed_sql_results; |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * Handle remembered sorting order, only for single table query |
||
95 | * |
||
96 | * @param string $db database name |
||
97 | * @param string $table table name |
||
98 | * @param array $analyzed_sql_results the analyzed query results |
||
99 | * @param string $full_sql_query SQL query |
||
100 | * |
||
101 | * @return void |
||
102 | */ |
||
103 | private function handleSortOrder( |
||
104 | $db, |
||
105 | $table, |
||
106 | array &$analyzed_sql_results, |
||
107 | &$full_sql_query |
||
108 | ) { |
||
109 | $pmatable = new Table($table, $db); |
||
110 | |||
111 | if (empty($analyzed_sql_results['order'])) { |
||
112 | // Retrieving the name of the column we should sort after. |
||
113 | $sortCol = $pmatable->getUiProp(Table::PROP_SORTED_COLUMN); |
||
114 | if (empty($sortCol)) { |
||
115 | return; |
||
116 | } |
||
117 | |||
118 | // Remove the name of the table from the retrieved field name. |
||
119 | $sortCol = str_replace( |
||
120 | Util::backquote($table) . '.', |
||
121 | '', |
||
122 | $sortCol |
||
123 | ); |
||
124 | |||
125 | // Create the new query. |
||
126 | $full_sql_query = Query::replaceClause( |
||
127 | $analyzed_sql_results['statement'], |
||
128 | $analyzed_sql_results['parser']->list, |
||
129 | 'ORDER BY ' . $sortCol |
||
130 | ); |
||
131 | |||
132 | // TODO: Avoid reparsing the query. |
||
133 | $analyzed_sql_results = Query::getAll($full_sql_query); |
||
134 | } else { |
||
135 | // Store the remembered table into session. |
||
136 | $pmatable->setUiProp( |
||
137 | Table::PROP_SORTED_COLUMN, |
||
138 | Query::getClause( |
||
139 | $analyzed_sql_results['statement'], |
||
140 | $analyzed_sql_results['parser']->list, |
||
141 | 'ORDER BY' |
||
142 | ) |
||
143 | ); |
||
144 | } |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Append limit clause to SQL query |
||
149 | * |
||
150 | * @param array $analyzed_sql_results the analyzed query results |
||
151 | * |
||
152 | * @return string limit clause appended SQL query |
||
153 | */ |
||
154 | private function getSqlWithLimitClause(array &$analyzed_sql_results) |
||
155 | { |
||
156 | return Query::replaceClause( |
||
157 | $analyzed_sql_results['statement'], |
||
158 | $analyzed_sql_results['parser']->list, |
||
159 | 'LIMIT ' . $_SESSION['tmpval']['pos'] . ', ' |
||
160 | . $_SESSION['tmpval']['max_rows'] |
||
161 | ); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Verify whether the result set has columns from just one table |
||
166 | * |
||
167 | * @param array $fields_meta meta fields |
||
168 | * |
||
169 | * @return boolean whether the result set has columns from just one table |
||
170 | */ |
||
171 | private function resultSetHasJustOneTable(array $fields_meta) |
||
172 | { |
||
173 | $just_one_table = true; |
||
174 | $prev_table = ''; |
||
175 | foreach ($fields_meta as $one_field_meta) { |
||
176 | if ($one_field_meta->table != '' |
||
177 | && $prev_table != '' |
||
178 | && $one_field_meta->table != $prev_table |
||
179 | ) { |
||
180 | $just_one_table = false; |
||
181 | } |
||
182 | if ($one_field_meta->table != '') { |
||
183 | $prev_table = $one_field_meta->table; |
||
184 | } |
||
185 | } |
||
186 | return $just_one_table && $prev_table != ''; |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Verify whether the result set contains all the columns |
||
191 | * of at least one unique key |
||
192 | * |
||
193 | * @param string $db database name |
||
194 | * @param string $table table name |
||
195 | * @param array $fields_meta meta fields |
||
196 | * |
||
197 | * @return boolean whether the result set contains a unique key |
||
198 | */ |
||
199 | private function resultSetContainsUniqueKey($db, $table, array $fields_meta) |
||
200 | { |
||
201 | $columns = $GLOBALS['dbi']->getColumns($db, $table); |
||
202 | $resultSetColumnNames = []; |
||
203 | foreach ($fields_meta as $oneMeta) { |
||
204 | $resultSetColumnNames[] = $oneMeta->name; |
||
205 | } |
||
206 | foreach (Index::getFromTable($table, $db) as $index) { |
||
207 | if ($index->isUnique()) { |
||
208 | $indexColumns = $index->getColumns(); |
||
209 | $numberFound = 0; |
||
210 | foreach ($indexColumns as $indexColumnName => $dummy) { |
||
211 | if (in_array($indexColumnName, $resultSetColumnNames)) { |
||
212 | $numberFound++; |
||
213 | } else if (!in_array($indexColumnName, $columns)) { |
||
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
|
|||
214 | $numberFound++; |
||
215 | } else if (strpos($columns[$indexColumnName]['Extra'], 'INVISIBLE') !== false) { |
||
216 | $numberFound++; |
||
217 | } |
||
218 | } |
||
219 | if ($numberFound == count($indexColumns)) { |
||
220 | return true; |
||
221 | } |
||
222 | } |
||
223 | } |
||
224 | return false; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Get the HTML for relational column dropdown |
||
229 | * During grid edit, if we have a relational field, returns the html for the |
||
230 | * dropdown |
||
231 | * |
||
232 | * @param string $db current database |
||
233 | * @param string $table current table |
||
234 | * @param string $column current column |
||
235 | * @param string $curr_value current selected value |
||
236 | * |
||
237 | * @return string html for the dropdown |
||
238 | */ |
||
239 | private function getHtmlForRelationalColumnDropdown($db, $table, $column, $curr_value) |
||
240 | { |
||
241 | $foreigners = $this->relation->getForeigners($db, $table, $column); |
||
242 | |||
243 | $foreignData = $this->relation->getForeignData( |
||
244 | $foreigners, |
||
245 | $column, |
||
246 | false, |
||
247 | '', |
||
248 | '' |
||
249 | ); |
||
250 | |||
251 | if ($foreignData['disp_row'] == null) { |
||
252 | //Handle the case when number of values |
||
253 | //is more than $cfg['ForeignKeyMaxLimit'] |
||
254 | $_url_params = [ |
||
255 | 'db' => $db, |
||
256 | 'table' => $table, |
||
257 | 'field' => $column, |
||
258 | ]; |
||
259 | |||
260 | $dropdown = $this->template->render('sql/relational_column_dropdown', [ |
||
261 | 'current_value' => $_POST['curr_value'], |
||
262 | 'params' => $_url_params, |
||
263 | ]); |
||
264 | } else { |
||
265 | $dropdown = $this->relation->foreignDropdown( |
||
266 | $foreignData['disp_row'], |
||
267 | $foreignData['foreign_field'], |
||
268 | $foreignData['foreign_display'], |
||
269 | $curr_value, |
||
270 | $GLOBALS['cfg']['ForeignKeyMaxLimit'] |
||
271 | ); |
||
272 | $dropdown = '<select>' . $dropdown . '</select>'; |
||
273 | } |
||
274 | |||
275 | return $dropdown; |
||
276 | } |
||
277 | |||
278 | /** |
||
279 | * Get the HTML for the profiling table and accompanying chart if profiling is set. |
||
280 | * Otherwise returns null |
||
281 | * |
||
282 | * @param string|null $urlQuery url query |
||
283 | * @param string $database current database |
||
284 | * @param array $profilingResults array containing the profiling info |
||
285 | * |
||
286 | * @return string html for the profiling table and chart |
||
287 | */ |
||
288 | private function getHtmlForProfilingChart($urlQuery, $database, $profilingResults): string |
||
289 | { |
||
290 | if (! empty($profilingResults)) { |
||
291 | $urlQuery = isset($urlQuery) ? $urlQuery : Url::getCommon(['db' => $database]); |
||
292 | |||
293 | list( |
||
294 | $detailedTable, |
||
295 | $chartJson, |
||
296 | $profilingStats |
||
297 | ) = $this->analyzeAndGetTableHtmlForProfilingResults($profilingResults); |
||
298 | |||
299 | return $this->template->render('sql/profiling_chart', [ |
||
300 | 'url_query' => $urlQuery, |
||
301 | 'detailed_table' => $detailedTable, |
||
302 | 'states' => $profilingStats['states'], |
||
303 | 'total_time' => $profilingStats['total_time'], |
||
304 | 'chart_json' => $chartJson, |
||
305 | ]); |
||
306 | } |
||
307 | return ''; |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Function to get HTML for detailed profiling results table, profiling stats, and |
||
312 | * $chart_json for displaying the chart. |
||
313 | * |
||
314 | * @param array $profiling_results profiling results |
||
315 | * |
||
316 | * @return mixed |
||
317 | */ |
||
318 | private function analyzeAndGetTableHtmlForProfilingResults( |
||
319 | $profiling_results |
||
320 | ) { |
||
321 | $profiling_stats = [ |
||
322 | 'total_time' => 0, |
||
323 | 'states' => [], |
||
324 | ]; |
||
325 | $chart_json = []; |
||
326 | $i = 1; |
||
327 | $table = ''; |
||
328 | foreach ($profiling_results as $one_result) { |
||
329 | if (isset($profiling_stats['states'][ucwords($one_result['Status'])])) { |
||
330 | $states = $profiling_stats['states']; |
||
331 | $states[ucwords($one_result['Status'])]['total_time'] |
||
332 | += $one_result['Duration']; |
||
333 | $states[ucwords($one_result['Status'])]['calls']++; |
||
334 | } else { |
||
335 | $profiling_stats['states'][ucwords($one_result['Status'])] = [ |
||
336 | 'total_time' => $one_result['Duration'], |
||
337 | 'calls' => 1, |
||
338 | ]; |
||
339 | } |
||
340 | $profiling_stats['total_time'] += $one_result['Duration']; |
||
341 | |||
342 | $table .= $this->template->render('sql/detailed_table', [ |
||
343 | 'index' => $i++, |
||
344 | 'status' => $one_result['Status'], |
||
345 | 'duration' => $one_result['Duration'], |
||
346 | ]); |
||
347 | |||
348 | if (isset($chart_json[ucwords($one_result['Status'])])) { |
||
349 | $chart_json[ucwords($one_result['Status'])] |
||
350 | += $one_result['Duration']; |
||
351 | } else { |
||
352 | $chart_json[ucwords($one_result['Status'])] |
||
353 | = $one_result['Duration']; |
||
354 | } |
||
355 | } |
||
356 | return [ |
||
357 | $table, |
||
358 | $chart_json, |
||
359 | $profiling_stats, |
||
360 | ]; |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Get the HTML for the enum column dropdown |
||
365 | * During grid edit, if we have a enum field, returns the html for the |
||
366 | * dropdown |
||
367 | * |
||
368 | * @param string $db current database |
||
369 | * @param string $table current table |
||
370 | * @param string $column current column |
||
371 | * @param string $curr_value currently selected value |
||
372 | * |
||
373 | * @return string html for the dropdown |
||
374 | */ |
||
375 | private function getHtmlForEnumColumnDropdown($db, $table, $column, $curr_value) |
||
376 | { |
||
377 | $values = $this->getValuesForColumn($db, $table, $column); |
||
378 | return $this->template->render('sql/enum_column_dropdown', [ |
||
379 | 'values' => $values, |
||
380 | 'selected_values' => [$curr_value], |
||
381 | ]); |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Get value of a column for a specific row (marked by $where_clause) |
||
386 | * |
||
387 | * @param string $db current database |
||
388 | * @param string $table current table |
||
389 | * @param string $column current column |
||
390 | * @param string $where_clause where clause to select a particular row |
||
391 | * |
||
392 | * @return string with value |
||
393 | */ |
||
394 | private function getFullValuesForSetColumn($db, $table, $column, $where_clause) |
||
395 | { |
||
396 | $result = $GLOBALS['dbi']->fetchSingleRow( |
||
397 | "SELECT `$column` FROM `$db`.`$table` WHERE $where_clause" |
||
398 | ); |
||
399 | |||
400 | return $result[$column]; |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Get the HTML for the set column dropdown |
||
405 | * During grid edit, if we have a set field, returns the html for the |
||
406 | * dropdown |
||
407 | * |
||
408 | * @param string $db current database |
||
409 | * @param string $table current table |
||
410 | * @param string $column current column |
||
411 | * @param string $curr_value currently selected value |
||
412 | * |
||
413 | * @return string html for the set column |
||
414 | */ |
||
415 | private function getHtmlForSetColumn($db, $table, $column, $curr_value): string |
||
416 | { |
||
417 | $values = $this->getValuesForColumn($db, $table, $column); |
||
418 | |||
419 | $full_values = isset($_POST['get_full_values']) ? $_POST['get_full_values'] : false; |
||
420 | $where_clause = isset($_POST['where_clause']) ? $_POST['where_clause'] : null; |
||
421 | |||
422 | // If the $curr_value was truncated, we should |
||
423 | // fetch the correct full values from the table |
||
424 | if ($full_values && ! empty($where_clause)) { |
||
425 | $curr_value = $this->getFullValuesForSetColumn( |
||
426 | $db, |
||
427 | $table, |
||
428 | $column, |
||
429 | $where_clause |
||
430 | ); |
||
431 | } |
||
432 | |||
433 | //converts characters of $curr_value to HTML entities |
||
434 | $converted_curr_value = htmlentities( |
||
435 | $curr_value, |
||
436 | ENT_COMPAT, |
||
437 | "UTF-8" |
||
438 | ); |
||
439 | |||
440 | $selected_values = explode(',', $converted_curr_value); |
||
441 | $select_size = (count($values) > 10) ? 10 : count($values); |
||
442 | |||
443 | return $this->template->render('sql/set_column', [ |
||
444 | 'size' => $select_size, |
||
445 | 'values' => $values, |
||
446 | 'selected_values' => $selected_values, |
||
447 | ]); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Get all the values for a enum column or set column in a table |
||
452 | * |
||
453 | * @param string $db current database |
||
454 | * @param string $table current table |
||
455 | * @param string $column current column |
||
456 | * |
||
457 | * @return array array containing the value list for the column |
||
458 | */ |
||
459 | private function getValuesForColumn($db, $table, $column) |
||
460 | { |
||
461 | $field_info_query = $GLOBALS['dbi']->getColumnsSql($db, $table, $column); |
||
462 | |||
463 | $field_info_result = $GLOBALS['dbi']->fetchResult( |
||
464 | $field_info_query, |
||
465 | null, |
||
466 | null, |
||
467 | DatabaseInterface::CONNECT_USER, |
||
468 | DatabaseInterface::QUERY_STORE |
||
469 | ); |
||
470 | |||
471 | return Util::parseEnumSetValues($field_info_result[0]['Type']); |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Function to get html for bookmark support if bookmarks are enabled. Else will |
||
476 | * return null |
||
477 | * |
||
478 | * @param array $displayParts the parts to display |
||
479 | * @param array $cfgBookmark configuration setting for bookmarking |
||
480 | * @param string $sql_query sql query |
||
481 | * @param string $db current database |
||
482 | * @param string $table current table |
||
483 | * @param string|null $complete_query complete query |
||
484 | * @param string $bkm_user bookmarking user |
||
485 | * |
||
486 | * @return string |
||
487 | */ |
||
488 | public function getHtmlForBookmark( |
||
489 | array $displayParts, |
||
490 | array $cfgBookmark, |
||
491 | $sql_query, |
||
492 | $db, |
||
493 | $table, |
||
494 | ?string $complete_query, |
||
495 | $bkm_user |
||
496 | ): string { |
||
497 | if ($displayParts['bkm_form'] == '1' |
||
498 | && (! empty($cfgBookmark) && empty($_GET['id_bookmark'])) |
||
499 | && ! empty($sql_query) |
||
500 | ) { |
||
501 | return $this->template->render('sql/bookmark', [ |
||
502 | 'db' => $db, |
||
503 | 'goto' => 'sql.php' . Url::getCommon([ |
||
504 | 'db' => $db, |
||
505 | 'table' => $table, |
||
506 | 'sql_query' => $sql_query, |
||
507 | 'id_bookmark' => 1, |
||
508 | ]), |
||
509 | 'user' => $bkm_user, |
||
510 | 'sql_query' => isset($complete_query) ? $complete_query : $sql_query, |
||
511 | ]); |
||
512 | } |
||
513 | return ''; |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Function to check whether to remember the sorting order or not |
||
518 | * |
||
519 | * @param array $analyzed_sql_results the analyzed query and other variables set |
||
520 | * after analyzing the query |
||
521 | * |
||
522 | * @return boolean |
||
523 | */ |
||
524 | private function isRememberSortingOrder(array $analyzed_sql_results) |
||
525 | { |
||
526 | return $GLOBALS['cfg']['RememberSorting'] |
||
527 | && ! ($analyzed_sql_results['is_count'] |
||
528 | || $analyzed_sql_results['is_export'] |
||
529 | || $analyzed_sql_results['is_func'] |
||
530 | || $analyzed_sql_results['is_analyse']) |
||
531 | && $analyzed_sql_results['select_from'] |
||
532 | && isset($analyzed_sql_results['select_expr']) |
||
533 | && isset($analyzed_sql_results['select_tables']) |
||
534 | && (empty($analyzed_sql_results['select_expr']) |
||
535 | || ((count($analyzed_sql_results['select_expr']) == 1) |
||
536 | && ($analyzed_sql_results['select_expr'][0] == '*'))) |
||
537 | && count($analyzed_sql_results['select_tables']) == 1; |
||
538 | } |
||
539 | |||
540 | /** |
||
541 | * Function to check whether the LIMIT clause should be appended or not |
||
542 | * |
||
543 | * @param array $analyzed_sql_results the analyzed query and other variables set |
||
544 | * after analyzing the query |
||
545 | * |
||
546 | * @return boolean |
||
547 | */ |
||
548 | private function isAppendLimitClause(array $analyzed_sql_results) |
||
549 | { |
||
550 | // Assigning LIMIT clause to an syntactically-wrong query |
||
551 | // is not needed. Also we would want to show the true query |
||
552 | // and the true error message to the query executor |
||
553 | |||
554 | return (isset($analyzed_sql_results['parser']) |
||
555 | && count($analyzed_sql_results['parser']->errors) === 0) |
||
556 | && ($_SESSION['tmpval']['max_rows'] != 'all') |
||
557 | && ! ($analyzed_sql_results['is_export'] |
||
558 | || $analyzed_sql_results['is_analyse']) |
||
559 | && ($analyzed_sql_results['select_from'] |
||
560 | || $analyzed_sql_results['is_subquery']) |
||
561 | && empty($analyzed_sql_results['limit']); |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * Function to check whether this query is for just browsing |
||
566 | * |
||
567 | * @param array $analyzed_sql_results the analyzed query and other variables set |
||
568 | * after analyzing the query |
||
569 | * @param boolean|null $find_real_end whether the real end should be found |
||
570 | * |
||
571 | * @return boolean |
||
572 | */ |
||
573 | public function isJustBrowsing(array $analyzed_sql_results, ?bool $find_real_end): bool |
||
574 | { |
||
575 | return ! $analyzed_sql_results['is_group'] |
||
576 | && ! $analyzed_sql_results['is_func'] |
||
577 | && empty($analyzed_sql_results['union']) |
||
578 | && empty($analyzed_sql_results['distinct']) |
||
579 | && $analyzed_sql_results['select_from'] |
||
580 | && (count($analyzed_sql_results['select_tables']) === 1) |
||
581 | && (empty($analyzed_sql_results['statement']->where) |
||
582 | || (count($analyzed_sql_results['statement']->where) == 1 |
||
583 | && $analyzed_sql_results['statement']->where[0]->expr === '1')) |
||
584 | && empty($analyzed_sql_results['group']) |
||
585 | && ! isset($find_real_end) |
||
586 | && ! $analyzed_sql_results['is_subquery'] |
||
587 | && ! $analyzed_sql_results['join'] |
||
588 | && empty($analyzed_sql_results['having']); |
||
589 | } |
||
590 | |||
591 | /** |
||
592 | * Function to check whether the related transformation information should be deleted |
||
593 | * |
||
594 | * @param array $analyzed_sql_results the analyzed query and other variables set |
||
595 | * after analyzing the query |
||
596 | * |
||
597 | * @return boolean |
||
598 | */ |
||
599 | private function isDeleteTransformationInfo(array $analyzed_sql_results) |
||
600 | { |
||
601 | return ! empty($analyzed_sql_results['querytype']) |
||
602 | && (($analyzed_sql_results['querytype'] == 'ALTER') |
||
603 | || ($analyzed_sql_results['querytype'] == 'DROP')); |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Function to check whether the user has rights to drop the database |
||
608 | * |
||
609 | * @param array $analyzed_sql_results the analyzed query and other variables set |
||
610 | * after analyzing the query |
||
611 | * @param boolean $allowUserDropDatabase whether the user is allowed to drop db |
||
612 | * @param boolean $is_superuser whether this user is a superuser |
||
613 | * |
||
614 | * @return boolean |
||
615 | */ |
||
616 | public function hasNoRightsToDropDatabase( |
||
617 | array $analyzed_sql_results, |
||
618 | $allowUserDropDatabase, |
||
619 | $is_superuser |
||
620 | ) { |
||
621 | return ! $allowUserDropDatabase |
||
622 | && isset($analyzed_sql_results['drop_database']) |
||
623 | && $analyzed_sql_results['drop_database'] |
||
624 | && ! $is_superuser; |
||
625 | } |
||
626 | |||
627 | /** |
||
628 | * Function to set a column property |
||
629 | * |
||
630 | * @param Table $pmatable Table instance |
||
631 | * @param string $request_index col_order|col_visib |
||
632 | * |
||
633 | * @return boolean |
||
634 | */ |
||
635 | private function setColumnProperty($pmatable, $request_index) |
||
636 | { |
||
637 | $property_value = array_map('intval', explode(',', $_POST[$request_index])); |
||
638 | switch ($request_index) { |
||
639 | case 'col_order': |
||
640 | $property_to_set = Table::PROP_COLUMN_ORDER; |
||
641 | break; |
||
642 | case 'col_visib': |
||
643 | $property_to_set = Table::PROP_COLUMN_VISIB; |
||
644 | break; |
||
645 | default: |
||
646 | $property_to_set = ''; |
||
647 | } |
||
648 | $retval = $pmatable->setUiProp( |
||
649 | $property_to_set, |
||
650 | $property_value, |
||
651 | $_POST['table_create_time'] |
||
652 | ); |
||
653 | if (gettype($retval) != 'boolean') { |
||
654 | $response = Response::getInstance(); |
||
655 | $response->setRequestStatus(false); |
||
656 | $response->addJSON('message', $retval->getString()); |
||
657 | exit; |
||
658 | } |
||
659 | |||
660 | return $retval; |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Function to check the request for setting the column order or visibility |
||
665 | * |
||
666 | * @param string $table the current table |
||
667 | * @param string $db the current database |
||
668 | * |
||
669 | * @return void |
||
670 | */ |
||
671 | public function setColumnOrderOrVisibility($table, $db) |
||
672 | { |
||
673 | $pmatable = new Table($table, $db); |
||
674 | $retval = false; |
||
675 | |||
676 | // set column order |
||
677 | if (isset($_POST['col_order'])) { |
||
678 | $retval = $this->setColumnProperty($pmatable, 'col_order'); |
||
679 | } |
||
680 | |||
681 | // set column visibility |
||
682 | if ($retval === true && isset($_POST['col_visib'])) { |
||
683 | $retval = $this->setColumnProperty($pmatable, 'col_visib'); |
||
684 | } |
||
685 | |||
686 | $response = Response::getInstance(); |
||
687 | $response->setRequestStatus($retval == true); |
||
688 | exit; |
||
689 | } |
||
690 | |||
691 | /** |
||
692 | * Function to add a bookmark |
||
693 | * |
||
694 | * @param string $goto goto page URL |
||
695 | * |
||
696 | * @return void |
||
697 | */ |
||
698 | public function addBookmark($goto) |
||
699 | { |
||
700 | $bookmark = Bookmark::createBookmark( |
||
701 | $GLOBALS['dbi'], |
||
702 | $GLOBALS['cfg']['Server']['user'], |
||
703 | $_POST['bkm_fields'], |
||
704 | (isset($_POST['bkm_all_users']) |
||
705 | && $_POST['bkm_all_users'] == 'true' ? true : false |
||
706 | ) |
||
707 | ); |
||
708 | $result = $bookmark->save(); |
||
709 | $response = Response::getInstance(); |
||
710 | if ($response->isAjax()) { |
||
711 | if ($result) { |
||
712 | $msg = Message::success(__('Bookmark %s has been created.')); |
||
713 | $msg->addParam($_POST['bkm_fields']['bkm_label']); |
||
714 | $response->addJSON('message', $msg); |
||
715 | } else { |
||
716 | $msg = Message::error(__('Bookmark not created!')); |
||
717 | $response->setRequestStatus(false); |
||
718 | $response->addJSON('message', $msg); |
||
719 | } |
||
720 | exit; |
||
721 | } else { |
||
722 | // go back to sql.php to redisplay query; do not use & in this case: |
||
723 | /** |
||
724 | * @todo In which scenario does this happen? |
||
725 | */ |
||
726 | Core::sendHeaderLocation( |
||
727 | './' . $goto |
||
728 | . '&label=' . $_POST['bkm_fields']['bkm_label'] |
||
729 | ); |
||
730 | } |
||
731 | } |
||
732 | |||
733 | /** |
||
734 | * Function to find the real end of rows |
||
735 | * |
||
736 | * @param string $db the current database |
||
737 | * @param string $table the current table |
||
738 | * |
||
739 | * @return mixed the number of rows if "retain" param is true, otherwise true |
||
740 | */ |
||
741 | public function findRealEndOfRows($db, $table) |
||
742 | { |
||
743 | $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(true); |
||
744 | $_SESSION['tmpval']['pos'] = $this->getStartPosToDisplayRow($unlim_num_rows); |
||
745 | |||
746 | return $unlim_num_rows; |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * Function to get values for the relational columns |
||
751 | * |
||
752 | * @param string $db the current database |
||
753 | * @param string $table the current table |
||
754 | * |
||
755 | * @return void |
||
756 | */ |
||
757 | public function getRelationalValues($db, $table) |
||
758 | { |
||
759 | $column = $_POST['column']; |
||
760 | if ($_SESSION['tmpval']['relational_display'] == 'D' |
||
761 | && isset($_POST['relation_key_or_display_column']) |
||
762 | && $_POST['relation_key_or_display_column'] |
||
763 | ) { |
||
764 | $curr_value = $_POST['relation_key_or_display_column']; |
||
765 | } else { |
||
766 | $curr_value = $_POST['curr_value']; |
||
767 | } |
||
768 | $dropdown = $this->getHtmlForRelationalColumnDropdown( |
||
769 | $db, |
||
770 | $table, |
||
771 | $column, |
||
772 | $curr_value |
||
773 | ); |
||
774 | $response = Response::getInstance(); |
||
775 | $response->addJSON('dropdown', $dropdown); |
||
776 | exit; |
||
777 | } |
||
778 | |||
779 | /** |
||
780 | * Function to get values for Enum or Set Columns |
||
781 | * |
||
782 | * @param string $db the current database |
||
783 | * @param string $table the current table |
||
784 | * @param string $columnType whether enum or set |
||
785 | * |
||
786 | * @return void |
||
787 | */ |
||
788 | public function getEnumOrSetValues($db, $table, $columnType) |
||
789 | { |
||
790 | $column = $_POST['column']; |
||
791 | $curr_value = $_POST['curr_value']; |
||
792 | $response = Response::getInstance(); |
||
793 | if ($columnType == "enum") { |
||
794 | $dropdown = $this->getHtmlForEnumColumnDropdown( |
||
795 | $db, |
||
796 | $table, |
||
797 | $column, |
||
798 | $curr_value |
||
799 | ); |
||
800 | $response->addJSON('dropdown', $dropdown); |
||
801 | } else { |
||
802 | $select = $this->getHtmlForSetColumn( |
||
803 | $db, |
||
804 | $table, |
||
805 | $column, |
||
806 | $curr_value |
||
807 | ); |
||
808 | $response->addJSON('select', $select); |
||
809 | } |
||
810 | exit; |
||
811 | } |
||
812 | |||
813 | /** |
||
814 | * Function to get the default sql query for browsing page |
||
815 | * |
||
816 | * @param string $db the current database |
||
817 | * @param string $table the current table |
||
818 | * |
||
819 | * @return string the default $sql_query for browse page |
||
820 | */ |
||
821 | public function getDefaultSqlQueryForBrowse($db, $table) |
||
822 | { |
||
823 | $bookmark = Bookmark::get( |
||
824 | $GLOBALS['dbi'], |
||
825 | $GLOBALS['cfg']['Server']['user'], |
||
826 | $db, |
||
827 | $table, |
||
828 | 'label', |
||
829 | false, |
||
830 | true |
||
831 | ); |
||
832 | |||
833 | if (! empty($bookmark) && ! empty($bookmark->getQuery())) { |
||
834 | $GLOBALS['using_bookmark_message'] = Message::notice( |
||
835 | __('Using bookmark "%s" as default browse query.') |
||
836 | ); |
||
837 | $GLOBALS['using_bookmark_message']->addParam($table); |
||
838 | $GLOBALS['using_bookmark_message']->addHtml( |
||
839 | Util::showDocu('faq', 'faq6-22') |
||
840 | ); |
||
841 | $sql_query = $bookmark->getQuery(); |
||
842 | } else { |
||
843 | $defaultOrderByClause = ''; |
||
844 | |||
845 | if (isset($GLOBALS['cfg']['TablePrimaryKeyOrder']) |
||
846 | && ($GLOBALS['cfg']['TablePrimaryKeyOrder'] !== 'NONE') |
||
847 | ) { |
||
848 | $primaryKey = null; |
||
849 | $primary = Index::getPrimary($table, $db); |
||
850 | |||
851 | if ($primary !== false) { |
||
852 | $primarycols = $primary->getColumns(); |
||
853 | |||
854 | foreach ($primarycols as $col) { |
||
855 | $primaryKey = $col->getName(); |
||
856 | break; |
||
857 | } |
||
858 | |||
859 | if ($primaryKey != null) { |
||
860 | $defaultOrderByClause = ' ORDER BY ' |
||
861 | . Util::backquote($table) . '.' |
||
862 | . Util::backquote($primaryKey) . ' ' |
||
863 | . $GLOBALS['cfg']['TablePrimaryKeyOrder']; |
||
864 | } |
||
865 | } |
||
866 | } |
||
867 | |||
868 | $sql_query = 'SELECT * FROM ' . Util::backquote($table) |
||
869 | . $defaultOrderByClause; |
||
870 | } |
||
871 | |||
872 | return $sql_query; |
||
873 | } |
||
874 | |||
875 | /** |
||
876 | * Responds an error when an error happens when executing the query |
||
877 | * |
||
878 | * @param boolean $is_gotofile whether goto file or not |
||
879 | * @param string $error error after executing the query |
||
880 | * @param string $full_sql_query full sql query |
||
881 | * |
||
882 | * @return void |
||
883 | */ |
||
884 | private function handleQueryExecuteError($is_gotofile, $error, $full_sql_query) |
||
885 | { |
||
886 | if ($is_gotofile) { |
||
887 | $message = Message::rawError($error); |
||
888 | $response = Response::getInstance(); |
||
889 | $response->setRequestStatus(false); |
||
890 | $response->addJSON('message', $message); |
||
891 | } else { |
||
892 | Util::mysqlDie($error, $full_sql_query, '', ''); |
||
893 | } |
||
894 | exit; |
||
895 | } |
||
896 | |||
897 | /** |
||
898 | * Function to store the query as a bookmark |
||
899 | * |
||
900 | * @param string $db the current database |
||
901 | * @param string $bkm_user the bookmarking user |
||
902 | * @param string $sql_query_for_bookmark the query to be stored in bookmark |
||
903 | * @param string $bkm_label bookmark label |
||
904 | * @param boolean|null $bkm_replace whether to replace existing bookmarks |
||
905 | * |
||
906 | * @return void |
||
907 | */ |
||
908 | public function storeTheQueryAsBookmark( |
||
909 | $db, |
||
910 | $bkm_user, |
||
911 | $sql_query_for_bookmark, |
||
912 | $bkm_label, |
||
913 | ?bool $bkm_replace |
||
914 | ) { |
||
915 | $bfields = [ |
||
916 | 'bkm_database' => $db, |
||
917 | 'bkm_user' => $bkm_user, |
||
918 | 'bkm_sql_query' => $sql_query_for_bookmark, |
||
919 | 'bkm_label' => $bkm_label, |
||
920 | ]; |
||
921 | |||
922 | // Should we replace bookmark? |
||
923 | if (isset($bkm_replace)) { |
||
924 | $bookmarks = Bookmark::getList( |
||
925 | $GLOBALS['dbi'], |
||
926 | $GLOBALS['cfg']['Server']['user'], |
||
927 | $db |
||
928 | ); |
||
929 | foreach ($bookmarks as $bookmark) { |
||
930 | if ($bookmark->getLabel() == $bkm_label) { |
||
931 | $bookmark->delete(); |
||
932 | } |
||
933 | } |
||
934 | } |
||
935 | |||
936 | $bookmark = Bookmark::createBookmark( |
||
937 | $GLOBALS['dbi'], |
||
938 | $GLOBALS['cfg']['Server']['user'], |
||
939 | $bfields, |
||
940 | isset($_POST['bkm_all_users']) |
||
941 | ); |
||
942 | $bookmark->save(); |
||
943 | } |
||
944 | |||
945 | /** |
||
946 | * Executes the SQL query and measures its execution time |
||
947 | * |
||
948 | * @param string $full_sql_query the full sql query |
||
949 | * |
||
950 | * @return array ($result, $querytime) |
||
951 | */ |
||
952 | private function executeQueryAndMeasureTime($full_sql_query) |
||
953 | { |
||
954 | // close session in case the query takes too long |
||
955 | session_write_close(); |
||
956 | |||
957 | // Measure query time. |
||
958 | $querytime_before = array_sum(explode(' ', microtime())); |
||
959 | |||
960 | $result = @$GLOBALS['dbi']->tryQuery( |
||
961 | $full_sql_query, |
||
962 | DatabaseInterface::CONNECT_USER, |
||
963 | DatabaseInterface::QUERY_STORE |
||
964 | ); |
||
965 | $querytime_after = array_sum(explode(' ', microtime())); |
||
966 | |||
967 | // reopen session |
||
968 | session_start(); |
||
969 | |||
970 | return [ |
||
971 | $result, |
||
972 | $querytime_after - $querytime_before, |
||
973 | ]; |
||
974 | } |
||
975 | |||
976 | /** |
||
977 | * Function to get the affected or changed number of rows after executing a query |
||
978 | * |
||
979 | * @param boolean $is_affected whether the query affected a table |
||
980 | * @param mixed $result results of executing the query |
||
981 | * |
||
982 | * @return int number of rows affected or changed |
||
983 | */ |
||
984 | private function getNumberOfRowsAffectedOrChanged($is_affected, $result) |
||
985 | { |
||
986 | if (! $is_affected) { |
||
987 | $num_rows = $result ? @$GLOBALS['dbi']->numRows($result) : 0; |
||
988 | } else { |
||
989 | $num_rows = @$GLOBALS['dbi']->affectedRows(); |
||
990 | } |
||
991 | |||
992 | return $num_rows; |
||
993 | } |
||
994 | |||
995 | /** |
||
996 | * Checks if the current database has changed |
||
997 | * This could happen if the user sends a query like "USE `database`;" |
||
998 | * |
||
999 | * @param string $db the database in the query |
||
1000 | * |
||
1001 | * @return int whether to reload the navigation(1) or not(0) |
||
1002 | */ |
||
1003 | private function hasCurrentDbChanged($db) |
||
1004 | { |
||
1005 | if (strlen($db) > 0) { |
||
1006 | $current_db = $GLOBALS['dbi']->fetchValue('SELECT DATABASE()'); |
||
1007 | // $current_db is false, except when a USE statement was sent |
||
1008 | return ($current_db != false) && ($db !== $current_db); |
||
1009 | } |
||
1010 | |||
1011 | return false; |
||
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * If a table, database or column gets dropped, clean comments. |
||
1016 | * |
||
1017 | * @param string $db current database |
||
1018 | * @param string $table current table |
||
1019 | * @param string|null $column current column |
||
1020 | * @param bool $purge whether purge set or not |
||
1021 | * |
||
1022 | * @return void |
||
1023 | */ |
||
1024 | private function cleanupRelations($db, $table, ?string $column, $purge) |
||
1025 | { |
||
1026 | if (! empty($purge) && strlen($db) > 0) { |
||
1027 | if (strlen($table) > 0) { |
||
1028 | if (isset($column) && strlen($column) > 0) { |
||
1029 | $this->relationCleanup->column($db, $table, $column); |
||
1030 | } else { |
||
1031 | $this->relationCleanup->table($db, $table); |
||
1032 | } |
||
1033 | } else { |
||
1034 | $this->relationCleanup->database($db); |
||
1035 | } |
||
1036 | } |
||
1037 | } |
||
1038 | |||
1039 | /** |
||
1040 | * Function to count the total number of rows for the same 'SELECT' query without |
||
1041 | * the 'LIMIT' clause that may have been programatically added |
||
1042 | * |
||
1043 | * @param int $num_rows number of rows affected/changed by the query |
||
1044 | * @param bool $justBrowsing whether just browsing or not |
||
1045 | * @param string $db the current database |
||
1046 | * @param string $table the current table |
||
1047 | * @param array $analyzed_sql_results the analyzed query and other variables set |
||
1048 | * after analyzing the query |
||
1049 | * |
||
1050 | * @return int unlimited number of rows |
||
1051 | */ |
||
1052 | private function countQueryResults( |
||
1053 | $num_rows, |
||
1054 | $justBrowsing, |
||
1055 | $db, |
||
1056 | $table, |
||
1057 | array $analyzed_sql_results |
||
1058 | ) { |
||
1059 | |||
1060 | /* Shortcut for not analyzed/empty query */ |
||
1061 | if (empty($analyzed_sql_results)) { |
||
1062 | return 0; |
||
1063 | } |
||
1064 | |||
1065 | if (! $this->isAppendLimitClause($analyzed_sql_results)) { |
||
1066 | // if we did not append a limit, set this to get a correct |
||
1067 | // "Showing rows..." message |
||
1068 | // $_SESSION['tmpval']['max_rows'] = 'all'; |
||
1069 | $unlim_num_rows = $num_rows; |
||
1070 | } elseif ($analyzed_sql_results['querytype'] == 'SELECT' |
||
1071 | || $analyzed_sql_results['is_subquery'] |
||
1072 | ) { |
||
1073 | // c o u n t q u e r y |
||
1074 | |||
1075 | // If we are "just browsing", there is only one table (and no join), |
||
1076 | // and no WHERE clause (or just 'WHERE 1 '), |
||
1077 | // we do a quick count (which uses MaxExactCount) because |
||
1078 | // SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables |
||
1079 | |||
1080 | // However, do not count again if we did it previously |
||
1081 | // due to $find_real_end == true |
||
1082 | if ($justBrowsing) { |
||
1083 | // Get row count (is approximate for InnoDB) |
||
1084 | $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); |
||
1085 | /** |
||
1086 | * @todo Can we know at this point that this is InnoDB, |
||
1087 | * (in this case there would be no need for getting |
||
1088 | * an exact count)? |
||
1089 | */ |
||
1090 | if ($unlim_num_rows < $GLOBALS['cfg']['MaxExactCount']) { |
||
1091 | // Get the exact count if approximate count |
||
1092 | // is less than MaxExactCount |
||
1093 | /** |
||
1094 | * @todo In countRecords(), MaxExactCount is also verified, |
||
1095 | * so can we avoid checking it twice? |
||
1096 | */ |
||
1097 | $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table) |
||
1098 | ->countRecords(true); |
||
1099 | } |
||
1100 | } else { |
||
1101 | // The SQL_CALC_FOUND_ROWS option of the SELECT statement is used. |
||
1102 | |||
1103 | // For UNION statements, only a SQL_CALC_FOUND_ROWS is required |
||
1104 | // after the first SELECT. |
||
1105 | |||
1106 | $count_query = Query::replaceClause( |
||
1107 | $analyzed_sql_results['statement'], |
||
1108 | $analyzed_sql_results['parser']->list, |
||
1109 | 'SELECT SQL_CALC_FOUND_ROWS', |
||
1110 | null, |
||
1111 | true |
||
1112 | ); |
||
1113 | |||
1114 | // Another LIMIT clause is added to avoid long delays. |
||
1115 | // A complete result will be returned anyway, but the LIMIT would |
||
1116 | // stop the query as soon as the result that is required has been |
||
1117 | // computed. |
||
1118 | |||
1119 | if (empty($analyzed_sql_results['union'])) { |
||
1120 | $count_query .= ' LIMIT 1'; |
||
1121 | } |
||
1122 | |||
1123 | // Running the count query. |
||
1124 | $GLOBALS['dbi']->tryQuery($count_query); |
||
1125 | |||
1126 | $unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()'); |
||
1127 | } // end else "just browsing" |
||
1128 | } else {// not $is_select |
||
1129 | $unlim_num_rows = 0; |
||
1130 | } |
||
1131 | |||
1132 | return $unlim_num_rows; |
||
1133 | } |
||
1134 | |||
1135 | /** |
||
1136 | * Function to handle all aspects relating to executing the query |
||
1137 | * |
||
1138 | * @param array $analyzed_sql_results analyzed sql results |
||
1139 | * @param string $full_sql_query full sql query |
||
1140 | * @param boolean $is_gotofile whether to go to a file |
||
1141 | * @param string $db current database |
||
1142 | * @param string $table current table |
||
1143 | * @param boolean|null $find_real_end whether to find the real end |
||
1144 | * @param string $sql_query_for_bookmark sql query to be stored as bookmark |
||
1145 | * @param array $extra_data extra data |
||
1146 | * |
||
1147 | * @return mixed |
||
1148 | */ |
||
1149 | private function executeTheQuery( |
||
1150 | array $analyzed_sql_results, |
||
1151 | $full_sql_query, |
||
1152 | $is_gotofile, |
||
1153 | $db, |
||
1154 | $table, |
||
1155 | ?bool $find_real_end, |
||
1156 | $sql_query_for_bookmark, |
||
1157 | $extra_data |
||
1158 | ) { |
||
1159 | $response = Response::getInstance(); |
||
1160 | $response->getHeader()->getMenu()->setTable($table); |
||
1161 | |||
1162 | // Only if we ask to see the php code |
||
1163 | if (isset($GLOBALS['show_as_php'])) { |
||
1164 | $result = null; |
||
1165 | $num_rows = 0; |
||
1166 | $unlim_num_rows = 0; |
||
1167 | } else { // If we don't ask to see the php code |
||
1168 | if (isset($_SESSION['profiling']) |
||
1169 | && Util::profilingSupported() |
||
1170 | ) { |
||
1171 | $GLOBALS['dbi']->query('SET PROFILING=1;'); |
||
1172 | } |
||
1173 | |||
1174 | list( |
||
1175 | $result, |
||
1176 | $GLOBALS['querytime'] |
||
1177 | ) = $this->executeQueryAndMeasureTime($full_sql_query); |
||
1178 | |||
1179 | // Displays an error message if required and stop parsing the script |
||
1180 | $error = $GLOBALS['dbi']->getError(); |
||
1181 | if ($error && $GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { |
||
1182 | $extra_data['error'] = $error; |
||
1183 | } elseif ($error) { |
||
1184 | $this->handleQueryExecuteError($is_gotofile, $error, $full_sql_query); |
||
1185 | } |
||
1186 | |||
1187 | // If there are no errors and bookmarklabel was given, |
||
1188 | // store the query as a bookmark |
||
1189 | if (! empty($_POST['bkm_label']) && ! empty($sql_query_for_bookmark)) { |
||
1190 | $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); |
||
1191 | $this->storeTheQueryAsBookmark( |
||
1192 | $db, |
||
1193 | $cfgBookmark['user'], |
||
1194 | $sql_query_for_bookmark, |
||
1195 | $_POST['bkm_label'], |
||
1196 | isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null |
||
1197 | ); |
||
1198 | } // end store bookmarks |
||
1199 | |||
1200 | // Gets the number of rows affected/returned |
||
1201 | // (This must be done immediately after the query because |
||
1202 | // mysql_affected_rows() reports about the last query done) |
||
1203 | $num_rows = $this->getNumberOfRowsAffectedOrChanged( |
||
1204 | $analyzed_sql_results['is_affected'], |
||
1205 | $result |
||
1206 | ); |
||
1207 | |||
1208 | // Grabs the profiling results |
||
1209 | if (isset($_SESSION['profiling']) |
||
1210 | && Util::profilingSupported() |
||
1211 | ) { |
||
1212 | $profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;'); |
||
1213 | } |
||
1214 | |||
1215 | $justBrowsing = $this->isJustBrowsing( |
||
1216 | $analyzed_sql_results, |
||
1217 | isset($find_real_end) ? $find_real_end : null |
||
1218 | ); |
||
1219 | |||
1220 | $unlim_num_rows = $this->countQueryResults( |
||
1221 | $num_rows, |
||
1222 | $justBrowsing, |
||
1223 | $db, |
||
1224 | $table, |
||
1225 | $analyzed_sql_results |
||
1226 | ); |
||
1227 | |||
1228 | $this->cleanupRelations( |
||
1229 | isset($db) ? $db : '', |
||
1230 | isset($table) ? $table : '', |
||
1231 | isset($_POST['dropped_column']) ? $_POST['dropped_column'] : null, |
||
1232 | isset($_POST['purge']) ? $_POST['purge'] : null |
||
1233 | ); |
||
1234 | |||
1235 | if (isset($_POST['dropped_column']) |
||
1236 | && strlen($db) > 0 |
||
1237 | && strlen($table) > 0 |
||
1238 | ) { |
||
1239 | // to refresh the list of indexes (Ajax mode) |
||
1240 | $extra_data['indexes_list'] = Index::getHtmlForIndexes( |
||
1241 | $table, |
||
1242 | $db |
||
1243 | ); |
||
1244 | } |
||
1245 | } |
||
1246 | |||
1247 | return [ |
||
1248 | $result, |
||
1249 | $num_rows, |
||
1250 | $unlim_num_rows, |
||
1251 | isset($profiling_results) ? $profiling_results : null, |
||
1252 | $extra_data, |
||
1253 | ]; |
||
1254 | } |
||
1255 | /** |
||
1256 | * Delete related transformation information |
||
1257 | * |
||
1258 | * @param string $db current database |
||
1259 | * @param string $table current table |
||
1260 | * @param array $analyzed_sql_results analyzed sql results |
||
1261 | * |
||
1262 | * @return void |
||
1263 | */ |
||
1264 | private function deleteTransformationInfo($db, $table, array $analyzed_sql_results) |
||
1265 | { |
||
1266 | if (! isset($analyzed_sql_results['statement'])) { |
||
1267 | return; |
||
1268 | } |
||
1269 | $statement = $analyzed_sql_results['statement']; |
||
1270 | if ($statement instanceof AlterStatement) { |
||
1271 | if (! empty($statement->altered[0]) |
||
1272 | && $statement->altered[0]->options->has('DROP') |
||
1273 | ) { |
||
1274 | if (! empty($statement->altered[0]->field->column)) { |
||
1275 | $this->transformations->clear( |
||
1276 | $db, |
||
1277 | $table, |
||
1278 | $statement->altered[0]->field->column |
||
1279 | ); |
||
1280 | } |
||
1281 | } |
||
1282 | } elseif ($statement instanceof DropStatement) { |
||
1283 | $this->transformations->clear($db, $table); |
||
1284 | } |
||
1285 | } |
||
1286 | |||
1287 | /** |
||
1288 | * Function to get the message for the no rows returned case |
||
1289 | * |
||
1290 | * @param string $message_to_show message to show |
||
1291 | * @param array $analyzed_sql_results analyzed sql results |
||
1292 | * @param int $num_rows number of rows |
||
1293 | * |
||
1294 | * @return string |
||
1295 | */ |
||
1296 | private function getMessageForNoRowsReturned( |
||
1297 | $message_to_show, |
||
1298 | array $analyzed_sql_results, |
||
1299 | $num_rows |
||
1300 | ) { |
||
1301 | if ($analyzed_sql_results['querytype'] == 'DELETE"') { |
||
1302 | $message = Message::getMessageForDeletedRows($num_rows); |
||
1303 | } elseif ($analyzed_sql_results['is_insert']) { |
||
1304 | if ($analyzed_sql_results['querytype'] == 'REPLACE') { |
||
1305 | // For REPLACE we get DELETED + INSERTED row count, |
||
1306 | // so we have to call it affected |
||
1307 | $message = Message::getMessageForAffectedRows($num_rows); |
||
1308 | } else { |
||
1309 | $message = Message::getMessageForInsertedRows($num_rows); |
||
1310 | } |
||
1311 | $insert_id = $GLOBALS['dbi']->insertId(); |
||
1312 | if ($insert_id != 0) { |
||
1313 | // insert_id is id of FIRST record inserted in one insert, |
||
1314 | // so if we inserted multiple rows, we had to increment this |
||
1315 | $message->addText('[br]'); |
||
1316 | // need to use a temporary because the Message class |
||
1317 | // currently supports adding parameters only to the first |
||
1318 | // message |
||
1319 | $_inserted = Message::notice(__('Inserted row id: %1$d')); |
||
1320 | $_inserted->addParam($insert_id + $num_rows - 1); |
||
1321 | $message->addMessage($_inserted); |
||
1322 | } |
||
1323 | } elseif ($analyzed_sql_results['is_affected']) { |
||
1324 | $message = Message::getMessageForAffectedRows($num_rows); |
||
1325 | |||
1326 | // Ok, here is an explanation for the !$is_select. |
||
1327 | // The form generated by PhpMyAdmin\SqlQueryForm |
||
1328 | // and db_sql.php has many submit buttons |
||
1329 | // on the same form, and some confusion arises from the |
||
1330 | // fact that $message_to_show is sent for every case. |
||
1331 | // The $message_to_show containing a success message and sent with |
||
1332 | // the form should not have priority over errors |
||
1333 | } elseif (! empty($message_to_show) |
||
1334 | && $analyzed_sql_results['querytype'] != 'SELECT' |
||
1335 | ) { |
||
1336 | $message = Message::rawSuccess(htmlspecialchars($message_to_show)); |
||
1337 | } elseif (! empty($GLOBALS['show_as_php'])) { |
||
1338 | $message = Message::success(__('Showing as PHP code')); |
||
1339 | } elseif (isset($GLOBALS['show_as_php'])) { |
||
1340 | /* User disable showing as PHP, query is only displayed */ |
||
1341 | $message = Message::notice(__('Showing SQL query')); |
||
1342 | } else { |
||
1343 | $message = Message::success( |
||
1344 | __('MySQL returned an empty result set (i.e. zero rows).') |
||
1345 | ); |
||
1346 | } |
||
1347 | |||
1348 | if (isset($GLOBALS['querytime'])) { |
||
1349 | $_querytime = Message::notice( |
||
1350 | '(' . __('Query took %01.4f seconds.') . ')' |
||
1351 | ); |
||
1352 | $_querytime->addParam($GLOBALS['querytime']); |
||
1353 | $message->addMessage($_querytime); |
||
1354 | } |
||
1355 | |||
1356 | // In case of ROLLBACK, notify the user. |
||
1357 | if (isset($_POST['rollback_query'])) { |
||
1358 | $message->addText(__('[ROLLBACK occurred.]')); |
||
1359 | } |
||
1360 | |||
1361 | return $message; |
||
1362 | } |
||
1363 | |||
1364 | /** |
||
1365 | * Function to respond back when the query returns zero rows |
||
1366 | * This method is called |
||
1367 | * 1-> When browsing an empty table |
||
1368 | * 2-> When executing a query on a non empty table which returns zero results |
||
1369 | * 3-> When executing a query on an empty table |
||
1370 | * 4-> When executing an INSERT, UPDATE, DELETE query from the SQL tab |
||
1371 | * 5-> When deleting a row from BROWSE tab |
||
1372 | * 6-> When searching using the SEARCH tab which returns zero results |
||
1373 | * 7-> When changing the structure of the table except change operation |
||
1374 | * |
||
1375 | * @param array $analyzed_sql_results analyzed sql results |
||
1376 | * @param string $db current database |
||
1377 | * @param string $table current table |
||
1378 | * @param string|null $message_to_show message to show |
||
1379 | * @param int $num_rows number of rows |
||
1380 | * @param DisplayResults $displayResultsObject DisplayResult instance |
||
1381 | * @param array|null $extra_data extra data |
||
1382 | * @param string $pmaThemeImage uri of the theme image |
||
1383 | * @param object $result executed query results |
||
1384 | * @param string $sql_query sql query |
||
1385 | * @param string|null $complete_query complete sql query |
||
1386 | * |
||
1387 | * @return string html |
||
1388 | */ |
||
1389 | private function getQueryResponseForNoResultsReturned( |
||
1390 | array $analyzed_sql_results, |
||
1391 | $db, |
||
1392 | $table, |
||
1393 | ?string $message_to_show, |
||
1394 | $num_rows, |
||
1395 | $displayResultsObject, |
||
1396 | ?array $extra_data, |
||
1397 | $pmaThemeImage, |
||
1398 | $result, |
||
1399 | $sql_query, |
||
1400 | ?string $complete_query |
||
1401 | ) { |
||
1402 | global $url_query; |
||
1403 | if ($this->isDeleteTransformationInfo($analyzed_sql_results)) { |
||
1404 | $this->deleteTransformationInfo($db, $table, $analyzed_sql_results); |
||
1405 | } |
||
1406 | |||
1407 | if (isset($extra_data['error'])) { |
||
1408 | $message = Message::rawError($extra_data['error']); |
||
1409 | } else { |
||
1410 | $message = $this->getMessageForNoRowsReturned( |
||
1411 | isset($message_to_show) ? $message_to_show : null, |
||
1412 | $analyzed_sql_results, |
||
1413 | $num_rows |
||
1414 | ); |
||
1415 | } |
||
1416 | |||
1417 | $html_output = ''; |
||
1418 | $html_message = Util::getMessage( |
||
1419 | $message, |
||
1420 | $GLOBALS['sql_query'], |
||
1421 | 'success' |
||
1422 | ); |
||
1423 | $html_output .= $html_message; |
||
1424 | if (! isset($GLOBALS['show_as_php'])) { |
||
1425 | if (! empty($GLOBALS['reload'])) { |
||
1426 | $extra_data['reload'] = 1; |
||
1427 | $extra_data['db'] = $GLOBALS['db']; |
||
1428 | } |
||
1429 | |||
1430 | // For ajax requests add message and sql_query as JSON |
||
1431 | if (empty($_REQUEST['ajax_page_request'])) { |
||
1432 | $extra_data['message'] = $message; |
||
1433 | if ($GLOBALS['cfg']['ShowSQL']) { |
||
1434 | $extra_data['sql_query'] = $html_message; |
||
1435 | } |
||
1436 | } |
||
1437 | |||
1438 | $response = Response::getInstance(); |
||
1439 | $response->addJSON(isset($extra_data) ? $extra_data : []); |
||
1440 | |||
1441 | if (! empty($analyzed_sql_results['is_select']) && |
||
1442 | ! isset($extra_data['error'])) { |
||
1443 | $url_query = isset($url_query) ? $url_query : null; |
||
1444 | |||
1445 | $displayParts = [ |
||
1446 | 'edit_lnk' => null, |
||
1447 | 'del_lnk' => null, |
||
1448 | 'sort_lnk' => '1', |
||
1449 | 'nav_bar' => '0', |
||
1450 | 'bkm_form' => '1', |
||
1451 | 'text_btn' => '1', |
||
1452 | 'pview_lnk' => '1', |
||
1453 | ]; |
||
1454 | |||
1455 | $html_output .= $this->getHtmlForSqlQueryResultsTable( |
||
1456 | $displayResultsObject, |
||
1457 | $pmaThemeImage, |
||
1458 | $url_query, |
||
1459 | $displayParts, |
||
1460 | false, |
||
1461 | 0, |
||
1462 | $num_rows, |
||
1463 | true, |
||
1464 | $result, |
||
1465 | $analyzed_sql_results, |
||
1466 | true |
||
1467 | ); |
||
1468 | |||
1469 | $html_output .= $displayResultsObject->getCreateViewQueryResultOp( |
||
1470 | $analyzed_sql_results |
||
1471 | ); |
||
1472 | |||
1473 | $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); |
||
1474 | if ($cfgBookmark) { |
||
1475 | $html_output .= $this->getHtmlForBookmark( |
||
1476 | $displayParts, |
||
1477 | $cfgBookmark, |
||
1478 | $sql_query, |
||
1479 | $db, |
||
1480 | $table, |
||
1481 | isset($complete_query) ? $complete_query : $sql_query, |
||
1482 | $cfgBookmark['user'] |
||
1483 | ); |
||
1484 | } |
||
1485 | } |
||
1486 | } |
||
1487 | |||
1488 | return $html_output; |
||
1489 | } |
||
1490 | |||
1491 | /** |
||
1492 | * Function to send response for ajax grid edit |
||
1493 | * |
||
1494 | * @param object $result result of the executed query |
||
1495 | * |
||
1496 | * @return void |
||
1497 | */ |
||
1498 | private function sendResponseForGridEdit($result) |
||
1499 | { |
||
1500 | $row = $GLOBALS['dbi']->fetchRow($result); |
||
1501 | $field_flags = $GLOBALS['dbi']->fieldFlags($result, 0); |
||
1502 | if (stristr($field_flags, DisplayResults::BINARY_FIELD)) { |
||
1503 | $row[0] = bin2hex($row[0]); |
||
1504 | } |
||
1505 | $response = Response::getInstance(); |
||
1506 | $response->addJSON('value', $row[0]); |
||
1507 | exit; |
||
1508 | } |
||
1509 | |||
1510 | /** |
||
1511 | * Returns a message for successful creation of a bookmark or null if a bookmark |
||
1512 | * was not created |
||
1513 | * |
||
1514 | * @return string |
||
1515 | */ |
||
1516 | private function getBookmarkCreatedMessage(): string |
||
1517 | { |
||
1518 | $output = ''; |
||
1519 | if (isset($_GET['label'])) { |
||
1520 | $message = Message::success( |
||
1521 | __('Bookmark %s has been created.') |
||
1522 | ); |
||
1523 | $message->addParam($_GET['label']); |
||
1524 | $output = $message->getDisplay(); |
||
1525 | } |
||
1526 | |||
1527 | return $output; |
||
1528 | } |
||
1529 | |||
1530 | /** |
||
1531 | * Function to get html for the sql query results table |
||
1532 | * |
||
1533 | * @param DisplayResults $displayResultsObject instance of DisplayResult |
||
1534 | * @param string $pmaThemeImage theme image uri |
||
1535 | * @param string $url_query url query |
||
1536 | * @param array $displayParts the parts to display |
||
1537 | * @param bool $editable whether the result table is |
||
1538 | * editable or not |
||
1539 | * @param int $unlim_num_rows unlimited number of rows |
||
1540 | * @param int $num_rows number of rows |
||
1541 | * @param bool $showtable whether to show table or not |
||
1542 | * @param object|null $result result of the executed query |
||
1543 | * @param array $analyzed_sql_results analyzed sql results |
||
1544 | * @param bool $is_limited_display Show only limited operations or not |
||
1545 | * |
||
1546 | * @return string |
||
1547 | */ |
||
1548 | private function getHtmlForSqlQueryResultsTable( |
||
1549 | $displayResultsObject, |
||
1550 | $pmaThemeImage, |
||
1551 | $url_query, |
||
1552 | array $displayParts, |
||
1553 | $editable, |
||
1554 | $unlim_num_rows, |
||
1555 | $num_rows, |
||
1556 | $showtable, |
||
1557 | $result, |
||
1558 | array $analyzed_sql_results, |
||
1559 | $is_limited_display = false |
||
1560 | ) { |
||
1561 | $printview = isset($_POST['printview']) && $_POST['printview'] == '1' ? '1' : null; |
||
1562 | $table_html = ''; |
||
1563 | $browse_dist = ! empty($_POST['is_browse_distinct']); |
||
1564 | |||
1565 | if ($analyzed_sql_results['is_procedure']) { |
||
1566 | do { |
||
1567 | if (! isset($result)) { |
||
1568 | $result = $GLOBALS['dbi']->storeResult(); |
||
1569 | } |
||
1570 | $num_rows = $GLOBALS['dbi']->numRows($result); |
||
1571 | |||
1572 | if ($result !== false && $num_rows > 0) { |
||
1573 | $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); |
||
1574 | if (! is_array($fields_meta)) { |
||
1575 | $fields_cnt = 0; |
||
1576 | } else { |
||
1577 | $fields_cnt = count($fields_meta); |
||
1578 | } |
||
1579 | |||
1580 | $displayResultsObject->setProperties( |
||
1581 | $num_rows, |
||
1582 | $fields_meta, |
||
1583 | $analyzed_sql_results['is_count'], |
||
1584 | $analyzed_sql_results['is_export'], |
||
1585 | $analyzed_sql_results['is_func'], |
||
1586 | $analyzed_sql_results['is_analyse'], |
||
1587 | $num_rows, |
||
1588 | $fields_cnt, |
||
1589 | $GLOBALS['querytime'], |
||
1590 | $pmaThemeImage, |
||
1591 | $GLOBALS['text_dir'], |
||
1592 | $analyzed_sql_results['is_maint'], |
||
1593 | $analyzed_sql_results['is_explain'], |
||
1594 | $analyzed_sql_results['is_show'], |
||
1595 | $showtable, |
||
1596 | $printview, |
||
1597 | $url_query, |
||
1598 | $editable, |
||
1599 | $browse_dist |
||
1600 | ); |
||
1601 | |||
1602 | $displayParts = [ |
||
1603 | 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, |
||
1604 | 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, |
||
1605 | 'sort_lnk' => '1', |
||
1606 | 'nav_bar' => '1', |
||
1607 | 'bkm_form' => '1', |
||
1608 | 'text_btn' => '1', |
||
1609 | 'pview_lnk' => '1', |
||
1610 | ]; |
||
1611 | |||
1612 | $table_html .= $displayResultsObject->getTable( |
||
1613 | $result, |
||
1614 | $displayParts, |
||
1615 | $analyzed_sql_results, |
||
1616 | $is_limited_display |
||
1617 | ); |
||
1618 | } |
||
1619 | |||
1620 | $GLOBALS['dbi']->freeResult($result); |
||
1621 | unset($result); |
||
1622 | } while ($GLOBALS['dbi']->moreResults() && $GLOBALS['dbi']->nextResult()); |
||
1623 | } else { |
||
1624 | $fields_meta = []; |
||
1625 | $fields_cnt = 0; |
||
1626 | if (isset($result) && $result !== false) { |
||
1627 | $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); |
||
1628 | $fields_cnt = count($fields_meta); |
||
1629 | } |
||
1630 | $_SESSION['is_multi_query'] = false; |
||
1631 | $displayResultsObject->setProperties( |
||
1632 | $unlim_num_rows, |
||
1633 | $fields_meta, |
||
1634 | $analyzed_sql_results['is_count'], |
||
1635 | $analyzed_sql_results['is_export'], |
||
1636 | $analyzed_sql_results['is_func'], |
||
1637 | $analyzed_sql_results['is_analyse'], |
||
1638 | $num_rows, |
||
1639 | $fields_cnt, |
||
1640 | $GLOBALS['querytime'], |
||
1641 | $pmaThemeImage, |
||
1642 | $GLOBALS['text_dir'], |
||
1643 | $analyzed_sql_results['is_maint'], |
||
1644 | $analyzed_sql_results['is_explain'], |
||
1645 | $analyzed_sql_results['is_show'], |
||
1646 | $showtable, |
||
1647 | $printview, |
||
1648 | $url_query, |
||
1649 | $editable, |
||
1650 | $browse_dist |
||
1651 | ); |
||
1652 | |||
1653 | $table_html .= $displayResultsObject->getTable( |
||
1654 | $result, |
||
1655 | $displayParts, |
||
1656 | $analyzed_sql_results, |
||
1657 | $is_limited_display |
||
1658 | ); |
||
1659 | $GLOBALS['dbi']->freeResult($result); |
||
1660 | } |
||
1661 | |||
1662 | return $table_html; |
||
1663 | } |
||
1664 | |||
1665 | /** |
||
1666 | * Function to get html for the previous query if there is such. If not will return |
||
1667 | * null |
||
1668 | * |
||
1669 | * @param string|null $displayQuery display query |
||
1670 | * @param bool $showSql whether to show sql |
||
1671 | * @param array $sqlData sql data |
||
1672 | * @param Message|string $displayMessage display message |
||
1673 | * |
||
1674 | * @return string |
||
1675 | */ |
||
1676 | private function getHtmlForPreviousUpdateQuery( |
||
1677 | ?string $displayQuery, |
||
1678 | $showSql, |
||
1679 | $sqlData, |
||
1680 | $displayMessage |
||
1681 | ): string { |
||
1682 | $output = ''; |
||
1683 | if (isset($displayQuery) && ($showSql == true) && empty($sqlData)) { |
||
1684 | $output = Util::getMessage( |
||
1685 | $displayMessage, |
||
1686 | $displayQuery, |
||
1687 | 'success' |
||
1688 | ); |
||
1689 | } |
||
1690 | |||
1691 | return $output; |
||
1692 | } |
||
1693 | |||
1694 | /** |
||
1695 | * To get the message if a column index is missing. If not will return null |
||
1696 | * |
||
1697 | * @param string $table current table |
||
1698 | * @param string $database current database |
||
1699 | * @param boolean $editable whether the results table can be editable or not |
||
1700 | * @param boolean $hasUniqueKey whether there is a unique key |
||
1701 | * |
||
1702 | * @return string |
||
1703 | */ |
||
1704 | private function getMessageIfMissingColumnIndex($table, $database, $editable, $hasUniqueKey): string |
||
1705 | { |
||
1706 | $output = ''; |
||
1707 | if (! empty($table) && ($GLOBALS['dbi']->isSystemSchema($database) || ! $editable)) { |
||
1708 | $output = Message::notice( |
||
1709 | sprintf( |
||
1710 | __( |
||
1711 | 'Current selection does not contain a unique column.' |
||
1712 | . ' Grid edit, checkbox, Edit, Copy and Delete features' |
||
1713 | . ' are not available. %s' |
||
1714 | ), |
||
1715 | Util::showDocu( |
||
1716 | 'config', |
||
1717 | 'cfg_RowActionLinksWithoutUnique' |
||
1718 | ) |
||
1719 | ) |
||
1720 | )->getDisplay(); |
||
1721 | } elseif (! empty($table) && ! $hasUniqueKey) { |
||
1722 | $output = Message::notice( |
||
1723 | sprintf( |
||
1724 | __( |
||
1725 | 'Current selection does not contain a unique column.' |
||
1726 | . ' Grid edit, Edit, Copy and Delete features may result in' |
||
1727 | . ' undesired behavior. %s' |
||
1728 | ), |
||
1729 | Util::showDocu( |
||
1730 | 'config', |
||
1731 | 'cfg_RowActionLinksWithoutUnique' |
||
1732 | ) |
||
1733 | ) |
||
1734 | )->getDisplay(); |
||
1735 | } |
||
1736 | |||
1737 | return $output; |
||
1738 | } |
||
1739 | |||
1740 | /** |
||
1741 | * Function to get html to display problems in indexes |
||
1742 | * |
||
1743 | * @param string|null $queryType query type |
||
1744 | * @param array|null $selectedTables array of table names selected from the |
||
1745 | * database structure page, for an action |
||
1746 | * like check table, optimize table, |
||
1747 | * analyze table or repair table |
||
1748 | * @param string $database current database |
||
1749 | * |
||
1750 | * @return string |
||
1751 | */ |
||
1752 | private function getHtmlForIndexesProblems(?string $queryType, ?array $selectedTables, string $database): string |
||
1753 | { |
||
1754 | // BEGIN INDEX CHECK See if indexes should be checked. |
||
1755 | $output = ''; |
||
1756 | if (isset($queryType) |
||
1757 | && $queryType == 'check_tbl' |
||
1758 | && isset($selectedTables) |
||
1759 | && is_array($selectedTables) |
||
1760 | ) { |
||
1761 | foreach ($selectedTables as $table) { |
||
1762 | $check = Index::findDuplicates($table, $database); |
||
1763 | if (! empty($check)) { |
||
1764 | $output .= sprintf( |
||
1765 | __('Problems with indexes of table `%s`'), |
||
1766 | $table |
||
1767 | ); |
||
1768 | $output .= $check; |
||
1769 | } |
||
1770 | } |
||
1771 | } |
||
1772 | |||
1773 | return $output; |
||
1774 | } |
||
1775 | |||
1776 | /** |
||
1777 | * Function to display results when the executed query returns non empty results |
||
1778 | * |
||
1779 | * @param object|null $result executed query results |
||
1780 | * @param array $analyzed_sql_results analysed sql results |
||
1781 | * @param string $db current database |
||
1782 | * @param string $table current table |
||
1783 | * @param string|null $message message to show |
||
1784 | * @param array|null $sql_data sql data |
||
1785 | * @param DisplayResults $displayResultsObject Instance of DisplayResults |
||
1786 | * @param string $pmaThemeImage uri of the theme image |
||
1787 | * @param int $unlim_num_rows unlimited number of rows |
||
1788 | * @param int $num_rows number of rows |
||
1789 | * @param string|null $disp_query display query |
||
1790 | * @param Message|string|null $disp_message display message |
||
1791 | * @param array|null $profiling_results profiling results |
||
1792 | * @param string|null $query_type query type |
||
1793 | * @param array|null $selectedTables array of table names selected |
||
1794 | * from the database structure page, for |
||
1795 | * an action like check table, |
||
1796 | * optimize table, analyze table or |
||
1797 | * repair table |
||
1798 | * @param string $sql_query sql query |
||
1799 | * @param string|null $complete_query complete sql query |
||
1800 | * |
||
1801 | * @return string html |
||
1802 | */ |
||
1803 | private function getQueryResponseForResultsReturned( |
||
1804 | $result, |
||
1805 | array $analyzed_sql_results, |
||
1806 | $db, |
||
1807 | $table, |
||
1808 | ?string $message, |
||
1809 | ?array $sql_data, |
||
1810 | $displayResultsObject, |
||
1811 | $pmaThemeImage, |
||
1812 | $unlim_num_rows, |
||
1813 | $num_rows, |
||
1814 | ?string $disp_query, |
||
1815 | $disp_message, |
||
1816 | ?array $profiling_results, |
||
1817 | ?string $query_type, |
||
1818 | $selectedTables, |
||
1819 | $sql_query, |
||
1820 | ?string $complete_query |
||
1821 | ) { |
||
1822 | global $showtable, $url_query; |
||
1823 | // If we are retrieving the full value of a truncated field or the original |
||
1824 | // value of a transformed field, show it here |
||
1825 | if (isset($_POST['grid_edit']) && $_POST['grid_edit'] == true) { |
||
1826 | $this->sendResponseForGridEdit($result); |
||
1827 | // script has exited at this point |
||
1828 | } |
||
1829 | |||
1830 | // Gets the list of fields properties |
||
1831 | if (isset($result) && $result) { |
||
1832 | $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); |
||
1833 | } else { |
||
1834 | $fields_meta = []; |
||
1835 | } |
||
1836 | |||
1837 | // Should be initialized these parameters before parsing |
||
1838 | $showtable = isset($showtable) ? $showtable : null; |
||
1839 | $url_query = isset($url_query) ? $url_query : null; |
||
1840 | |||
1841 | $response = Response::getInstance(); |
||
1842 | $header = $response->getHeader(); |
||
1843 | $scripts = $header->getScripts(); |
||
1844 | |||
1845 | $just_one_table = $this->resultSetHasJustOneTable($fields_meta); |
||
1846 | |||
1847 | // hide edit and delete links: |
||
1848 | // - for information_schema |
||
1849 | // - if the result set does not contain all the columns of a unique key |
||
1850 | // (unless this is an updatable view) |
||
1851 | // - if the SELECT query contains a join or a subquery |
||
1852 | |||
1853 | $updatableView = false; |
||
1854 | |||
1855 | $statement = isset($analyzed_sql_results['statement']) ? $analyzed_sql_results['statement'] : null; |
||
1856 | if ($statement instanceof SelectStatement) { |
||
1857 | if (! empty($statement->expr)) { |
||
1858 | if ($statement->expr[0]->expr === '*') { |
||
1859 | $_table = new Table($table, $db); |
||
1860 | $updatableView = $_table->isUpdatableView(); |
||
1861 | } |
||
1862 | } |
||
1863 | |||
1864 | if ($analyzed_sql_results['join'] |
||
1865 | || $analyzed_sql_results['is_subquery'] |
||
1866 | || count($analyzed_sql_results['select_tables']) !== 1 |
||
1867 | ) { |
||
1868 | $just_one_table = false; |
||
1869 | } |
||
1870 | } |
||
1871 | |||
1872 | $has_unique = $this->resultSetContainsUniqueKey( |
||
1873 | $db, |
||
1874 | $table, |
||
1875 | $fields_meta |
||
1876 | ); |
||
1877 | |||
1878 | $editable = ($has_unique |
||
1879 | || $GLOBALS['cfg']['RowActionLinksWithoutUnique'] |
||
1880 | || $updatableView) |
||
1881 | && $just_one_table; |
||
1882 | |||
1883 | $_SESSION['tmpval']['possible_as_geometry'] = $editable; |
||
1884 | |||
1885 | $displayParts = [ |
||
1886 | 'edit_lnk' => $displayResultsObject::UPDATE_ROW, |
||
1887 | 'del_lnk' => $displayResultsObject::DELETE_ROW, |
||
1888 | 'sort_lnk' => '1', |
||
1889 | 'nav_bar' => '1', |
||
1890 | 'bkm_form' => '1', |
||
1891 | 'text_btn' => '0', |
||
1892 | 'pview_lnk' => '1', |
||
1893 | ]; |
||
1894 | |||
1895 | if ($GLOBALS['dbi']->isSystemSchema($db) || ! $editable) { |
||
1896 | $displayParts = [ |
||
1897 | 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, |
||
1898 | 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, |
||
1899 | 'sort_lnk' => '1', |
||
1900 | 'nav_bar' => '1', |
||
1901 | 'bkm_form' => '1', |
||
1902 | 'text_btn' => '1', |
||
1903 | 'pview_lnk' => '1', |
||
1904 | ]; |
||
1905 | } |
||
1906 | if (isset($_POST['printview']) && $_POST['printview'] == '1') { |
||
1907 | $displayParts = [ |
||
1908 | 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, |
||
1909 | 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, |
||
1910 | 'sort_lnk' => '0', |
||
1911 | 'nav_bar' => '0', |
||
1912 | 'bkm_form' => '0', |
||
1913 | 'text_btn' => '0', |
||
1914 | 'pview_lnk' => '0', |
||
1915 | ]; |
||
1916 | } |
||
1917 | |||
1918 | $tableMaintenanceHtml = ''; |
||
1919 | if (isset($_POST['table_maintenance'])) { |
||
1920 | $scripts->addFile('makegrid.js'); |
||
1921 | $scripts->addFile('sql.js'); |
||
1922 | if (isset($message)) { |
||
1923 | $message = Message::success($message); |
||
1924 | $tableMaintenanceHtml = Util::getMessage( |
||
1925 | $message, |
||
1926 | $GLOBALS['sql_query'], |
||
1927 | 'success' |
||
1928 | ); |
||
1929 | } |
||
1930 | $tableMaintenanceHtml .= $this->getHtmlForSqlQueryResultsTable( |
||
1931 | $displayResultsObject, |
||
1932 | $pmaThemeImage, |
||
1933 | $url_query, |
||
1934 | $displayParts, |
||
1935 | false, |
||
1936 | $unlim_num_rows, |
||
1937 | $num_rows, |
||
1938 | $showtable, |
||
1939 | $result, |
||
1940 | $analyzed_sql_results |
||
1941 | ); |
||
1942 | if (empty($sql_data) || ($sql_data['valid_queries'] = 1)) { |
||
1943 | $response->addHTML($tableMaintenanceHtml); |
||
1944 | exit(); |
||
1945 | } |
||
1946 | } |
||
1947 | |||
1948 | if (! isset($_POST['printview']) || $_POST['printview'] != '1') { |
||
1949 | $scripts->addFile('makegrid.js'); |
||
1950 | $scripts->addFile('sql.js'); |
||
1951 | unset($GLOBALS['message']); |
||
1952 | //we don't need to buffer the output in getMessage here. |
||
1953 | //set a global variable and check against it in the function |
||
1954 | $GLOBALS['buffer_message'] = false; |
||
1955 | } |
||
1956 | |||
1957 | $previousUpdateQueryHtml = $this->getHtmlForPreviousUpdateQuery( |
||
1958 | isset($disp_query) ? $disp_query : null, |
||
1959 | $GLOBALS['cfg']['ShowSQL'], |
||
1960 | isset($sql_data) ? $sql_data : null, |
||
1961 | isset($disp_message) ? $disp_message : null |
||
1962 | ); |
||
1963 | |||
1964 | $profilingChartHtml = $this->getHtmlForProfilingChart( |
||
1965 | $url_query, |
||
1966 | $db, |
||
1967 | isset($profiling_results) ? $profiling_results : [] |
||
1968 | ); |
||
1969 | |||
1970 | $missingUniqueColumnMessage = $this->getMessageIfMissingColumnIndex( |
||
1971 | $table, |
||
1972 | $db, |
||
1973 | $editable, |
||
1974 | $has_unique |
||
1975 | ); |
||
1976 | |||
1977 | $bookmarkCreatedMessage = $this->getBookmarkCreatedMessage(); |
||
1978 | |||
1979 | $tableHtml = $this->getHtmlForSqlQueryResultsTable( |
||
1980 | $displayResultsObject, |
||
1981 | $pmaThemeImage, |
||
1982 | $url_query, |
||
1983 | $displayParts, |
||
1984 | $editable, |
||
1985 | $unlim_num_rows, |
||
1986 | $num_rows, |
||
1987 | $showtable, |
||
1988 | $result, |
||
1989 | $analyzed_sql_results |
||
1990 | ); |
||
1991 | |||
1992 | $indexesProblemsHtml = $this->getHtmlForIndexesProblems( |
||
1993 | isset($query_type) ? $query_type : null, |
||
1994 | isset($selectedTables) ? $selectedTables : null, |
||
1995 | $db |
||
1996 | ); |
||
1997 | |||
1998 | $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); |
||
1999 | $bookmarkSupportHtml = ''; |
||
2000 | if ($cfgBookmark) { |
||
2001 | $bookmarkSupportHtml = $this->getHtmlForBookmark( |
||
2002 | $displayParts, |
||
2003 | $cfgBookmark, |
||
2004 | $sql_query, |
||
2005 | $db, |
||
2006 | $table, |
||
2007 | isset($complete_query) ? $complete_query : $sql_query, |
||
2008 | $cfgBookmark['user'] |
||
2009 | ); |
||
2010 | } |
||
2011 | |||
2012 | return $this->template->render('sql/sql_query_results', [ |
||
2013 | 'table_maintenance' => $tableMaintenanceHtml, |
||
2014 | 'previous_update_query' => $previousUpdateQueryHtml, |
||
2015 | 'profiling_chart' => $profilingChartHtml, |
||
2016 | 'missing_unique_column_message' => $missingUniqueColumnMessage, |
||
2017 | 'bookmark_created_message' => $bookmarkCreatedMessage, |
||
2018 | 'table' => $tableHtml, |
||
2019 | 'indexes_problems' => $indexesProblemsHtml, |
||
2020 | 'bookmark_support' => $bookmarkSupportHtml, |
||
2021 | ]); |
||
2022 | } |
||
2023 | |||
2024 | /** |
||
2025 | * Function to execute the query and send the response |
||
2026 | * |
||
2027 | * @param array $analyzed_sql_results analysed sql results |
||
2028 | * @param bool $is_gotofile whether goto file or not |
||
2029 | * @param string $db current database |
||
2030 | * @param string $table current table |
||
2031 | * @param bool|null $find_real_end whether to find real end or not |
||
2032 | * @param string $sql_query_for_bookmark the sql query to be stored as bookmark |
||
2033 | * @param array|null $extra_data extra data |
||
2034 | * @param string $message_to_show message to show |
||
2035 | * @param string $message message |
||
2036 | * @param array|null $sql_data sql data |
||
2037 | * @param string $goto goto page url |
||
2038 | * @param string $pmaThemeImage uri of the PMA theme image |
||
2039 | * @param string $disp_query display query |
||
2040 | * @param Message|string $disp_message display message |
||
2041 | * @param string $query_type query type |
||
2042 | * @param string $sql_query sql query |
||
2043 | * @param array|null $selectedTables array of table names selected from the |
||
2044 | * database structure page, for an action |
||
2045 | * like check table, optimize table, |
||
2046 | * analyze table or repair table |
||
2047 | * @param string $complete_query complete query |
||
2048 | * |
||
2049 | * @return void |
||
2050 | */ |
||
2051 | public function executeQueryAndSendQueryResponse( |
||
2052 | $analyzed_sql_results, |
||
2053 | $is_gotofile, |
||
2054 | $db, |
||
2055 | $table, |
||
2056 | $find_real_end, |
||
2057 | $sql_query_for_bookmark, |
||
2058 | $extra_data, |
||
2059 | $message_to_show, |
||
2060 | $message, |
||
2061 | $sql_data, |
||
2062 | $goto, |
||
2063 | $pmaThemeImage, |
||
2064 | $disp_query, |
||
2065 | $disp_message, |
||
2066 | $query_type, |
||
2067 | $sql_query, |
||
2068 | $selectedTables, |
||
2069 | $complete_query |
||
2070 | ) { |
||
2071 | if ($analyzed_sql_results == null) { |
||
2072 | // Parse and analyze the query |
||
2073 | list( |
||
2074 | $analyzed_sql_results, |
||
2075 | $db, |
||
2076 | $table_from_sql |
||
2077 | ) = ParseAnalyze::sqlQuery($sql_query, $db); |
||
2078 | // @todo: possibly refactor |
||
2079 | extract($analyzed_sql_results); |
||
2080 | |||
2081 | if ($table != $table_from_sql && ! empty($table_from_sql)) { |
||
2082 | $table = $table_from_sql; |
||
2083 | } |
||
2084 | } |
||
2085 | |||
2086 | $html_output = $this->executeQueryAndGetQueryResponse( |
||
2087 | $analyzed_sql_results, // analyzed_sql_results |
||
2088 | $is_gotofile, // is_gotofile |
||
2089 | $db, // db |
||
2090 | $table, // table |
||
2091 | $find_real_end, // find_real_end |
||
2092 | $sql_query_for_bookmark, // sql_query_for_bookmark |
||
2093 | $extra_data, // extra_data |
||
2094 | $message_to_show, // message_to_show |
||
2095 | $message, // message |
||
2096 | $sql_data, // sql_data |
||
2097 | $goto, // goto |
||
2098 | $pmaThemeImage, // pmaThemeImage |
||
2099 | $disp_query, // disp_query |
||
2100 | $disp_message, // disp_message |
||
2101 | $query_type, // query_type |
||
2102 | $sql_query, // sql_query |
||
2103 | $selectedTables, // selectedTables |
||
2104 | $complete_query // complete_query |
||
2105 | ); |
||
2106 | |||
2107 | $response = Response::getInstance(); |
||
2108 | $response->addHTML($html_output); |
||
2109 | } |
||
2110 | |||
2111 | /** |
||
2112 | * Function to execute the query and send the response |
||
2113 | * |
||
2114 | * @param array $analyzed_sql_results analysed sql results |
||
2115 | * @param bool $is_gotofile whether goto file or not |
||
2116 | * @param string $db current database |
||
2117 | * @param string $table current table |
||
2118 | * @param bool|null $find_real_end whether to find real end or not |
||
2119 | * @param string|null $sql_query_for_bookmark the sql query to be stored as bookmark |
||
2120 | * @param array|null $extra_data extra data |
||
2121 | * @param string|null $message_to_show message to show |
||
2122 | * @param Message|string|null $message message |
||
2123 | * @param array|null $sql_data sql data |
||
2124 | * @param string $goto goto page url |
||
2125 | * @param string $pmaThemeImage uri of the PMA theme image |
||
2126 | * @param string|null $disp_query display query |
||
2127 | * @param Message|string|null $disp_message display message |
||
2128 | * @param string|null $query_type query type |
||
2129 | * @param string $sql_query sql query |
||
2130 | * @param array|null $selectedTables array of table names selected from the |
||
2131 | * database structure page, for an action |
||
2132 | * like check table, optimize table, |
||
2133 | * analyze table or repair table |
||
2134 | * @param string|null $complete_query complete query |
||
2135 | * |
||
2136 | * @return string html |
||
2137 | */ |
||
2138 | public function executeQueryAndGetQueryResponse( |
||
2139 | array $analyzed_sql_results, |
||
2140 | $is_gotofile, |
||
2141 | $db, |
||
2142 | $table, |
||
2143 | $find_real_end, |
||
2144 | ?string $sql_query_for_bookmark, |
||
2145 | $extra_data, |
||
2146 | ?string $message_to_show, |
||
2147 | $message, |
||
2148 | $sql_data, |
||
2149 | $goto, |
||
2150 | $pmaThemeImage, |
||
2151 | ?string $disp_query, |
||
2152 | $disp_message, |
||
2153 | ?string $query_type, |
||
2154 | $sql_query, |
||
2155 | $selectedTables, |
||
2156 | ?string $complete_query |
||
2157 | ) { |
||
2158 | // Handle disable/enable foreign key checks |
||
2159 | $default_fk_check = Util::handleDisableFKCheckInit(); |
||
2160 | |||
2161 | // Handle remembered sorting order, only for single table query. |
||
2162 | // Handling is not required when it's a union query |
||
2163 | // (the parser never sets the 'union' key to 0). |
||
2164 | // Handling is also not required if we came from the "Sort by key" |
||
2165 | // drop-down. |
||
2166 | if (! empty($analyzed_sql_results) |
||
2167 | && $this->isRememberSortingOrder($analyzed_sql_results) |
||
2168 | && empty($analyzed_sql_results['union']) |
||
2169 | && ! isset($_POST['sort_by_key']) |
||
2170 | ) { |
||
2171 | if (! isset($_SESSION['sql_from_query_box'])) { |
||
2172 | $this->handleSortOrder($db, $table, $analyzed_sql_results, $sql_query); |
||
2173 | } else { |
||
2174 | unset($_SESSION['sql_from_query_box']); |
||
2175 | } |
||
2176 | } |
||
2177 | |||
2178 | $displayResultsObject = new DisplayResults( |
||
2179 | $GLOBALS['db'], |
||
2180 | $GLOBALS['table'], |
||
2181 | $goto, |
||
2182 | $sql_query |
||
2183 | ); |
||
2184 | $displayResultsObject->setConfigParamsForDisplayTable(); |
||
2185 | |||
2186 | // assign default full_sql_query |
||
2187 | $full_sql_query = $sql_query; |
||
2188 | |||
2189 | // Do append a "LIMIT" clause? |
||
2190 | if ($this->isAppendLimitClause($analyzed_sql_results)) { |
||
2191 | $full_sql_query = $this->getSqlWithLimitClause($analyzed_sql_results); |
||
2192 | } |
||
2193 | |||
2194 | $GLOBALS['reload'] = $this->hasCurrentDbChanged($db); |
||
2195 | $GLOBALS['dbi']->selectDb($db); |
||
2196 | |||
2197 | list( |
||
2198 | $result, |
||
2199 | $num_rows, |
||
2200 | $unlim_num_rows, |
||
2201 | $profiling_results, |
||
2202 | $extra_data |
||
2203 | ) = $this->executeTheQuery( |
||
2204 | $analyzed_sql_results, |
||
2205 | $full_sql_query, |
||
2206 | $is_gotofile, |
||
2207 | $db, |
||
2208 | $table, |
||
2209 | isset($find_real_end) ? $find_real_end : null, |
||
2210 | isset($sql_query_for_bookmark) ? $sql_query_for_bookmark : null, |
||
2211 | isset($extra_data) ? $extra_data : null |
||
2212 | ); |
||
2213 | |||
2214 | $warning_messages = $this->operations->getWarningMessagesArray(); |
||
2215 | |||
2216 | // No rows returned -> move back to the calling page |
||
2217 | if ((0 == $num_rows && 0 == $unlim_num_rows) |
||
2218 | || $analyzed_sql_results['is_affected'] |
||
2219 | ) { |
||
2220 | $html_output = $this->getQueryResponseForNoResultsReturned( |
||
2221 | $analyzed_sql_results, |
||
2222 | $db, |
||
2223 | $table, |
||
2224 | isset($message_to_show) ? $message_to_show : null, |
||
2225 | $num_rows, |
||
2226 | $displayResultsObject, |
||
2227 | $extra_data, |
||
2228 | $pmaThemeImage, |
||
2229 | isset($result) ? $result : null, |
||
2230 | $sql_query, |
||
2231 | isset($complete_query) ? $complete_query : null |
||
2232 | ); |
||
2233 | } else { |
||
2234 | // At least one row is returned -> displays a table with results |
||
2235 | $html_output = $this->getQueryResponseForResultsReturned( |
||
2236 | isset($result) ? $result : null, |
||
2237 | $analyzed_sql_results, |
||
2238 | $db, |
||
2239 | $table, |
||
2240 | isset($message) ? $message : null, |
||
2241 | isset($sql_data) ? $sql_data : null, |
||
2242 | $displayResultsObject, |
||
2243 | $pmaThemeImage, |
||
2244 | $unlim_num_rows, |
||
2245 | $num_rows, |
||
2246 | isset($disp_query) ? $disp_query : null, |
||
2247 | isset($disp_message) ? $disp_message : null, |
||
2248 | $profiling_results, |
||
2249 | isset($query_type) ? $query_type : null, |
||
2250 | isset($selectedTables) ? $selectedTables : null, |
||
2251 | $sql_query, |
||
2252 | isset($complete_query) ? $complete_query : null |
||
2253 | ); |
||
2254 | } |
||
2255 | |||
2256 | // Handle disable/enable foreign key checks |
||
2257 | Util::handleDisableFKCheckCleanup($default_fk_check); |
||
2258 | |||
2259 | foreach ($warning_messages as $warning) { |
||
2260 | $message = Message::notice(Message::sanitize($warning)); |
||
2261 | $html_output .= $message->getDisplay(); |
||
2262 | } |
||
2263 | |||
2264 | return $html_output; |
||
2265 | } |
||
2266 | |||
2267 | /** |
||
2268 | * Function to define pos to display a row |
||
2269 | * |
||
2270 | * @param int $number_of_line Number of the line to display |
||
2271 | * @param int $max_rows Number of rows by page |
||
2272 | * |
||
2273 | * @return int Start position to display the line |
||
2274 | */ |
||
2275 | private function getStartPosToDisplayRow($number_of_line, $max_rows = null) |
||
2276 | { |
||
2277 | if (null === $max_rows) { |
||
2278 | $max_rows = $_SESSION['tmpval']['max_rows']; |
||
2279 | } |
||
2280 | |||
2281 | return @((ceil($number_of_line / $max_rows) - 1) * $max_rows); |
||
2282 | } |
||
2283 | |||
2284 | /** |
||
2285 | * Function to calculate new pos if pos is higher than number of rows |
||
2286 | * of displayed table |
||
2287 | * |
||
2288 | * @param string $db Database name |
||
2289 | * @param string $table Table name |
||
2290 | * @param int|null $pos Initial position |
||
2291 | * |
||
2292 | * @return int Number of pos to display last page |
||
2293 | */ |
||
2294 | public function calculatePosForLastPage($db, $table, $pos) |
||
2295 | { |
||
2296 | if (null === $pos) { |
||
2297 | $pos = $_SESSION['tmpval']['pos']; |
||
2298 | } |
||
2299 | |||
2300 | $_table = new Table($table, $db); |
||
2301 | $unlim_num_rows = $_table->countRecords(true); |
||
2302 | //If position is higher than number of rows |
||
2303 | if ($unlim_num_rows <= $pos && 0 != $pos) { |
||
2304 | $pos = $this->getStartPosToDisplayRow($unlim_num_rows); |
||
2305 | } |
||
2306 | |||
2307 | return $pos; |
||
2308 | } |
||
2309 | } |
||
2310 |