Sql.php 82 KB

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