1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822 |
- <?php
- declare(strict_types=1);
- namespace PhpMyAdmin;
- use PhpMyAdmin\ConfigStorage\Features\BookmarkFeature;
- use PhpMyAdmin\ConfigStorage\Relation;
- use PhpMyAdmin\ConfigStorage\RelationCleanup;
- use PhpMyAdmin\Dbal\ResultInterface;
- use PhpMyAdmin\Display\Results as DisplayResults;
- use PhpMyAdmin\Html\Generator;
- use PhpMyAdmin\Html\MySQLDocumentation;
- use PhpMyAdmin\Query\Generator as QueryGenerator;
- use PhpMyAdmin\Query\Utilities;
- use PhpMyAdmin\SqlParser\Components\Expression;
- use PhpMyAdmin\SqlParser\Statements\AlterStatement;
- use PhpMyAdmin\SqlParser\Statements\DropStatement;
- use PhpMyAdmin\SqlParser\Statements\SelectStatement;
- use PhpMyAdmin\SqlParser\Utils\Query;
- use PhpMyAdmin\Utils\ForeignKey;
- use function __;
- use function array_key_exists;
- use function array_keys;
- use function array_map;
- use function bin2hex;
- use function ceil;
- use function count;
- use function defined;
- use function explode;
- use function htmlspecialchars;
- use function in_array;
- use function is_array;
- use function is_bool;
- use function is_object;
- use function session_start;
- use function session_write_close;
- use function sprintf;
- use function str_contains;
- use function str_replace;
- use function ucwords;
- /**
- * Set of functions for the SQL executor
- */
- class Sql
- {
- /** @var DatabaseInterface */
- private $dbi;
- /** @var Relation */
- private $relation;
- /** @var RelationCleanup */
- private $relationCleanup;
- /** @var Transformations */
- private $transformations;
- /** @var Operations */
- private $operations;
- /** @var Template */
- private $template;
- public function __construct(
- DatabaseInterface $dbi,
- Relation $relation,
- RelationCleanup $relationCleanup,
- Operations $operations,
- Transformations $transformations,
- Template $template
- ) {
- $this->dbi = $dbi;
- $this->relation = $relation;
- $this->relationCleanup = $relationCleanup;
- $this->operations = $operations;
- $this->transformations = $transformations;
- $this->template = $template;
- }
- /**
- * Handle remembered sorting order, only for single table query
- *
- * @param string $db database name
- * @param string $table table name
- * @param array $analyzedSqlResults the analyzed query results
- * @param string $fullSqlQuery SQL query
- */
- private function handleSortOrder(
- $db,
- $table,
- array &$analyzedSqlResults,
- &$fullSqlQuery
- ): void {
- $tableObject = new Table($table, $db);
- if (empty($analyzedSqlResults['order'])) {
- // Retrieving the name of the column we should sort after.
- $sortCol = $tableObject->getUiProp(Table::PROP_SORTED_COLUMN);
- if (empty($sortCol)) {
- return;
- }
- // Remove the name of the table from the retrieved field name.
- $sortCol = str_replace(
- Util::backquote($table) . '.',
- '',
- $sortCol
- );
- // Create the new query.
- $fullSqlQuery = Query::replaceClause(
- $analyzedSqlResults['statement'],
- $analyzedSqlResults['parser']->list,
- 'ORDER BY ' . $sortCol
- );
- // TODO: Avoid reparsing the query.
- $analyzedSqlResults = Query::getAll($fullSqlQuery);
- } else {
- // Store the remembered table into session.
- $tableObject->setUiProp(
- Table::PROP_SORTED_COLUMN,
- Query::getClause(
- $analyzedSqlResults['statement'],
- $analyzedSqlResults['parser']->list,
- 'ORDER BY'
- )
- );
- }
- }
- /**
- * Append limit clause to SQL query
- *
- * @param array $analyzedSqlResults the analyzed query results
- *
- * @return string limit clause appended SQL query
- */
- private function getSqlWithLimitClause(array $analyzedSqlResults)
- {
- return Query::replaceClause(
- $analyzedSqlResults['statement'],
- $analyzedSqlResults['parser']->list,
- 'LIMIT ' . $_SESSION['tmpval']['pos'] . ', '
- . $_SESSION['tmpval']['max_rows']
- );
- }
- /**
- * Verify whether the result set has columns from just one table
- *
- * @param array $fieldsMeta meta fields
- */
- private function resultSetHasJustOneTable(array $fieldsMeta): bool
- {
- $justOneTable = true;
- $prevTable = '';
- foreach ($fieldsMeta as $oneFieldMeta) {
- if ($oneFieldMeta->table != '' && $prevTable != '' && $oneFieldMeta->table != $prevTable) {
- $justOneTable = false;
- }
- if ($oneFieldMeta->table == '') {
- continue;
- }
- $prevTable = $oneFieldMeta->table;
- }
- return $justOneTable && $prevTable != '';
- }
- /**
- * Verify whether the result set contains all the columns
- * of at least one unique key
- *
- * @param string $db database name
- * @param string $table table name
- * @param array $fieldsMeta meta fields
- */
- private function resultSetContainsUniqueKey(string $db, string $table, array $fieldsMeta): bool
- {
- if ($table === '') {
- return false;
- }
- $columns = $this->dbi->getColumns($db, $table);
- $resultSetColumnNames = [];
- foreach ($fieldsMeta as $oneMeta) {
- $resultSetColumnNames[] = $oneMeta->name;
- }
- foreach (Index::getFromTable($table, $db) as $index) {
- if (! $index->isUnique()) {
- continue;
- }
- $indexColumns = $index->getColumns();
- $numberFound = 0;
- foreach (array_keys($indexColumns) as $indexColumnName) {
- if (
- ! in_array($indexColumnName, $resultSetColumnNames)
- && array_key_exists($indexColumnName, $columns)
- && ! str_contains($columns[$indexColumnName]['Extra'], 'INVISIBLE')
- ) {
- continue;
- }
- $numberFound++;
- }
- if ($numberFound == count($indexColumns)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Get the HTML for relational column dropdown
- * During grid edit, if we have a relational field, returns the html for the
- * dropdown
- *
- * @param string $db current database
- * @param string $table current table
- * @param string $column current column
- * @param string $currentValue current selected value
- *
- * @return string html for the dropdown
- */
- public function getHtmlForRelationalColumnDropdown($db, $table, $column, $currentValue)
- {
- $foreigners = $this->relation->getForeigners($db, $table, $column);
- $foreignData = $this->relation->getForeignData($foreigners, $column, false, '', '');
- if ($foreignData['disp_row'] == null) {
- //Handle the case when number of values
- //is more than $cfg['ForeignKeyMaxLimit']
- $urlParams = [
- 'db' => $db,
- 'table' => $table,
- 'field' => $column,
- ];
- $dropdown = $this->template->render('sql/relational_column_dropdown', [
- 'current_value' => $_POST['curr_value'],
- 'params' => $urlParams,
- ]);
- } else {
- $dropdown = $this->relation->foreignDropdown(
- $foreignData['disp_row'],
- $foreignData['foreign_field'],
- $foreignData['foreign_display'],
- $currentValue,
- $GLOBALS['cfg']['ForeignKeyMaxLimit']
- );
- $dropdown = '<select>' . $dropdown . '</select>';
- }
- return $dropdown;
- }
- /** @return array<string, int|array> */
- private function getDetailedProfilingStats(array $profilingResults): array
- {
- $profiling = [
- 'total_time' => 0,
- 'states' => [],
- 'chart' => [],
- 'profile' => [],
- ];
- foreach ($profilingResults as $oneResult) {
- $status = ucwords($oneResult['Status']);
- $profiling['total_time'] += $oneResult['Duration'];
- $profiling['profile'][] = [
- 'status' => $status,
- 'duration' => Util::formatNumber($oneResult['Duration'], 3, 1),
- 'duration_raw' => $oneResult['Duration'],
- ];
- if (! isset($profiling['states'][$status])) {
- $profiling['states'][$status] = [
- 'total_time' => $oneResult['Duration'],
- 'calls' => 1,
- ];
- $profiling['chart'][$status] = $oneResult['Duration'];
- } else {
- $profiling['states'][$status]['calls']++;
- $profiling['states'][$status]['total_time'] += $oneResult['Duration'];
- $profiling['chart'][$status] += $oneResult['Duration'];
- }
- }
- return $profiling;
- }
- /**
- * Get value of a column for a specific row (marked by $whereClause)
- */
- public function getFullValuesForSetColumn(
- string $db,
- string $table,
- string $column,
- string $whereClause
- ): string {
- $row = $this->dbi->fetchSingleRow(sprintf(
- 'SELECT `%s` FROM `%s`.`%s` WHERE %s',
- $column,
- $db,
- $table,
- $whereClause
- ));
- if ($row === null) {
- return '';
- }
- return $row[$column];
- }
- /**
- * Get all the values for a enum column or set column in a table
- *
- * @param string $db current database
- * @param string $table current table
- * @param string $column current column
- *
- * @return array|null array containing the value list for the column, null on failure
- */
- public function getValuesForColumn(string $db, string $table, string $column): ?array
- {
- $fieldInfoQuery = QueryGenerator::getColumnsSql($db, $table, $this->dbi->escapeString($column));
- $fieldInfoResult = $this->dbi->fetchResult($fieldInfoQuery);
- if (! isset($fieldInfoResult[0])) {
- return null;
- }
- return Util::parseEnumSetValues($fieldInfoResult[0]['Type'], false);
- }
- /**
- * Function to check whether to remember the sorting order or not
- *
- * @param array $analyzedSqlResults the analyzed query and other variables set
- * after analyzing the query
- */
- private function isRememberSortingOrder(array $analyzedSqlResults): bool
- {
- return isset($analyzedSqlResults['select_expr'], $analyzedSqlResults['select_tables'])
- && $GLOBALS['cfg']['RememberSorting']
- && ! ($analyzedSqlResults['is_count']
- || $analyzedSqlResults['is_export']
- || $analyzedSqlResults['is_func']
- || $analyzedSqlResults['is_analyse'])
- && $analyzedSqlResults['select_from']
- && (empty($analyzedSqlResults['select_expr'])
- || ((count($analyzedSqlResults['select_expr']) === 1)
- && ($analyzedSqlResults['select_expr'][0] === '*')))
- && count($analyzedSqlResults['select_tables']) === 1;
- }
- /**
- * Function to check whether the LIMIT clause should be appended or not
- *
- * @param array $analyzedSqlResults the analyzed query and other variables set
- * after analyzing the query
- */
- private function isAppendLimitClause(array $analyzedSqlResults): bool
- {
- // Assigning LIMIT clause to an syntactically-wrong query
- // is not needed. Also we would want to show the true query
- // and the true error message to the query executor
- return (isset($analyzedSqlResults['parser'])
- && count($analyzedSqlResults['parser']->errors) === 0)
- && ($_SESSION['tmpval']['max_rows'] !== 'all')
- && ! ($analyzedSqlResults['is_export']
- || $analyzedSqlResults['is_analyse'])
- && ($analyzedSqlResults['select_from']
- || $analyzedSqlResults['is_subquery'])
- && empty($analyzedSqlResults['limit']);
- }
- /**
- * Function to check whether this query is for just browsing
- *
- * @param array<string, mixed> $analyzedSqlResults the analyzed query and other variables set
- * after analyzing the query
- * @param bool|null $findRealEnd whether the real end should be found
- */
- public static function isJustBrowsing(array $analyzedSqlResults, ?bool $findRealEnd): bool
- {
- return ! $analyzedSqlResults['is_group']
- && ! $analyzedSqlResults['is_func']
- && empty($analyzedSqlResults['union'])
- && empty($analyzedSqlResults['distinct'])
- && $analyzedSqlResults['select_from']
- && (count($analyzedSqlResults['select_tables']) === 1)
- && (empty($analyzedSqlResults['statement']->where)
- || (count($analyzedSqlResults['statement']->where) === 1
- && $analyzedSqlResults['statement']->where[0]->expr === '1'))
- && empty($analyzedSqlResults['group'])
- && ! isset($findRealEnd)
- && ! $analyzedSqlResults['is_subquery']
- && ! $analyzedSqlResults['join']
- && empty($analyzedSqlResults['having']);
- }
- /**
- * Function to check whether the related transformation information should be deleted
- *
- * @param array $analyzedSqlResults the analyzed query and other variables set
- * after analyzing the query
- */
- private function isDeleteTransformationInfo(array $analyzedSqlResults): bool
- {
- return ! empty($analyzedSqlResults['querytype'])
- && (($analyzedSqlResults['querytype'] === 'ALTER')
- || ($analyzedSqlResults['querytype'] === 'DROP'));
- }
- /**
- * Function to check whether the user has rights to drop the database
- *
- * @param array $analyzedSqlResults the analyzed query and other variables set
- * after analyzing the query
- * @param bool $allowUserDropDatabase whether the user is allowed to drop db
- * @param bool $isSuperUser whether this user is a superuser
- */
- public function hasNoRightsToDropDatabase(
- array $analyzedSqlResults,
- $allowUserDropDatabase,
- $isSuperUser
- ): bool {
- return ! $allowUserDropDatabase
- && isset($analyzedSqlResults['drop_database'])
- && $analyzedSqlResults['drop_database']
- && ! $isSuperUser;
- }
- /**
- * Function to set a column property
- *
- * @param Table $table Table instance
- * @param string $requestIndex col_order|col_visib
- *
- * @return bool|Message
- */
- public function setColumnProperty(Table $table, string $requestIndex)
- {
- $propertyValue = array_map('intval', explode(',', $_POST[$requestIndex]));
- switch ($requestIndex) {
- case 'col_order':
- $propertyToSet = Table::PROP_COLUMN_ORDER;
- break;
- case 'col_visib':
- $propertyToSet = Table::PROP_COLUMN_VISIB;
- break;
- default:
- $propertyToSet = '';
- }
- return $table->setUiProp($propertyToSet, $propertyValue, $_POST['table_create_time'] ?? null);
- }
- /**
- * Function to find the real end of rows
- *
- * @param string $db the current database
- * @param string $table the current table
- *
- * @return mixed the number of rows if "retain" param is true, otherwise true
- */
- public function findRealEndOfRows($db, $table)
- {
- $unlimNumRows = $this->dbi->getTable($db, $table)->countRecords(true);
- $_SESSION['tmpval']['pos'] = $this->getStartPosToDisplayRow($unlimNumRows);
- return $unlimNumRows;
- }
- /**
- * Function to get the default sql query for browsing page
- *
- * @param string $db the current database
- * @param string $table the current table
- *
- * @return string the default $sql_query for browse page
- */
- public function getDefaultSqlQueryForBrowse($db, $table): string
- {
- $bookmark = Bookmark::get($this->dbi, $GLOBALS['cfg']['Server']['user'], $db, $table, 'label', false, true);
- if ($bookmark !== null && $bookmark->getQuery() !== '') {
- $GLOBALS['using_bookmark_message'] = Message::notice(
- __('Using bookmark "%s" as default browse query.')
- );
- $GLOBALS['using_bookmark_message']->addParam($table);
- $GLOBALS['using_bookmark_message']->addHtml(
- MySQLDocumentation::showDocumentation('faq', 'faq6-22')
- );
- return $bookmark->getQuery();
- }
- $defaultOrderByClause = '';
- if (
- isset($GLOBALS['cfg']['TablePrimaryKeyOrder'])
- && ($GLOBALS['cfg']['TablePrimaryKeyOrder'] !== 'NONE')
- ) {
- $primaryKey = null;
- $primary = Index::getPrimary($table, $db);
- if ($primary !== false) {
- $primarycols = $primary->getColumns();
- foreach ($primarycols as $col) {
- $primaryKey = $col->getName();
- break;
- }
- if ($primaryKey !== null) {
- $defaultOrderByClause = ' ORDER BY '
- . Util::backquote($table) . '.'
- . Util::backquote($primaryKey) . ' '
- . $GLOBALS['cfg']['TablePrimaryKeyOrder'];
- }
- }
- }
- return 'SELECT * FROM ' . Util::backquote($table) . $defaultOrderByClause;
- }
- /**
- * Responds an error when an error happens when executing the query
- *
- * @param bool $isGotoFile whether goto file or not
- * @param string $error error after executing the query
- * @param string $fullSqlQuery full sql query
- */
- private function handleQueryExecuteError($isGotoFile, $error, $fullSqlQuery): void
- {
- if ($isGotoFile) {
- $message = Message::rawError($error);
- $response = ResponseRenderer::getInstance();
- $response->setRequestStatus(false);
- $response->addJSON('message', $message);
- } else {
- Generator::mysqlDie($error, $fullSqlQuery, false);
- }
- exit;
- }
- /**
- * Function to store the query as a bookmark
- *
- * @param string $db the current database
- * @param string $bookmarkUser the bookmarking user
- * @param string $sqlQueryForBookmark the query to be stored in bookmark
- * @param string $bookmarkLabel bookmark label
- * @param bool $bookmarkReplace whether to replace existing bookmarks
- */
- public function storeTheQueryAsBookmark(
- ?BookmarkFeature $bookmarkFeature,
- $db,
- $bookmarkUser,
- $sqlQueryForBookmark,
- $bookmarkLabel,
- bool $bookmarkReplace
- ): void {
- $bfields = [
- 'bkm_database' => $db,
- 'bkm_user' => $bookmarkUser,
- 'bkm_sql_query' => $sqlQueryForBookmark,
- 'bkm_label' => $bookmarkLabel,
- ];
- // Should we replace bookmark?
- if ($bookmarkReplace && $bookmarkFeature !== null) {
- $bookmarks = Bookmark::getList($bookmarkFeature, $this->dbi, $GLOBALS['cfg']['Server']['user'], $db);
- foreach ($bookmarks as $bookmark) {
- if ($bookmark->getLabel() != $bookmarkLabel) {
- continue;
- }
- $bookmark->delete();
- }
- }
- $bookmark = Bookmark::createBookmark(
- $this->dbi,
- $bfields,
- isset($_POST['bkm_all_users'])
- );
- if ($bookmark === false) {
- return;
- }
- $bookmark->save();
- }
- /**
- * Function to get the affected or changed number of rows after executing a query
- *
- * @param bool $isAffected whether the query affected a table
- * @param ResultInterface|false $result results of executing the query
- *
- * @return int|string number of rows affected or changed
- * @psalm-return int|numeric-string
- */
- private function getNumberOfRowsAffectedOrChanged($isAffected, $result)
- {
- if ($isAffected) {
- return $this->dbi->affectedRows();
- }
- if ($result) {
- return $result->numRows();
- }
- return 0;
- }
- /**
- * Checks if the current database has changed
- * This could happen if the user sends a query like "USE `database`;"
- *
- * @param string $db the database in the query
- *
- * @return bool whether to reload the navigation(1) or not(0)
- */
- private function hasCurrentDbChanged(string $db): bool
- {
- if ($db === '') {
- return false;
- }
- $currentDb = $this->dbi->fetchValue('SELECT DATABASE()');
- // $current_db is false, except when a USE statement was sent
- return ($currentDb != false) && ($db !== $currentDb);
- }
- /**
- * If a table, database or column gets dropped, clean comments.
- *
- * @param string $db current database
- * @param string $table current table
- * @param string|null $column current column
- * @param bool $purge whether purge set or not
- */
- private function cleanupRelations(string $db, string $table, ?string $column, bool $purge): void
- {
- if (! $purge || $db === '') {
- return;
- }
- if ($table !== '') {
- if ($column !== null && $column !== '') {
- $this->relationCleanup->column($db, $table, $column);
- } else {
- $this->relationCleanup->table($db, $table);
- }
- } else {
- $this->relationCleanup->database($db);
- }
- }
- /**
- * Function to count the total number of rows for the same 'SELECT' query without
- * the 'LIMIT' clause that may have been programmatically added
- *
- * @param int|string $numRows number of rows affected/changed by the query
- * @param bool $justBrowsing whether just browsing or not
- * @param string $db the current database
- * @param string $table the current table
- * @param array $analyzedSqlResults the analyzed query and other variables set after analyzing the query
- * @psalm-param int|numeric-string $numRows
- *
- * @return int|string unlimited number of rows
- * @psalm-return int|numeric-string
- */
- private function countQueryResults(
- $numRows,
- bool $justBrowsing,
- string $db,
- string $table,
- array $analyzedSqlResults
- ) {
- /* Shortcut for not analyzed/empty query */
- if ($analyzedSqlResults === []) {
- return 0;
- }
- if (! $this->isAppendLimitClause($analyzedSqlResults)) {
- // if we did not append a limit, set this to get a correct
- // "Showing rows..." message
- // $_SESSION['tmpval']['max_rows'] = 'all';
- $unlimNumRows = $numRows;
- } elseif ($_SESSION['tmpval']['max_rows'] > $numRows) {
- // When user has not defined a limit in query and total rows in
- // result are less than max_rows to display, there is no need
- // to count total rows for that query again
- $unlimNumRows = $_SESSION['tmpval']['pos'] + $numRows;
- } elseif ($analyzedSqlResults['querytype'] === 'SELECT' || $analyzedSqlResults['is_subquery']) {
- // c o u n t q u e r y
- // If we are "just browsing", there is only one table (and no join),
- // and no WHERE clause (or just 'WHERE 1 '),
- // we do a quick count (which uses MaxExactCount) because
- // SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables
- // However, do not count again if we did it previously
- // due to $find_real_end == true
- if ($justBrowsing) {
- // Get row count (is approximate for InnoDB)
- $unlimNumRows = $this->dbi->getTable($db, $table)->countRecords();
- /**
- * @todo Can we know at this point that this is InnoDB,
- * (in this case there would be no need for getting
- * an exact count)?
- */
- if ($unlimNumRows < $GLOBALS['cfg']['MaxExactCount']) {
- // Get the exact count if approximate count
- // is less than MaxExactCount
- /**
- * @todo In countRecords(), MaxExactCount is also verified,
- * so can we avoid checking it twice?
- */
- $unlimNumRows = $this->dbi->getTable($db, $table)
- ->countRecords(true);
- }
- } else {
- /** @var SelectStatement $statement */
- $statement = $analyzedSqlResults['statement'];
- $changeOrder = $analyzedSqlResults['order'] !== false;
- $changeLimit = $analyzedSqlResults['limit'] !== false;
- $changeExpression = $analyzedSqlResults['is_group'] === false
- && $analyzedSqlResults['distinct'] === false
- && $analyzedSqlResults['union'] === false
- && count($statement->expr) === 1;
- if ($changeOrder || $changeLimit || $changeExpression) {
- $statement = clone $statement;
- }
- // Remove ORDER BY to decrease unnecessary sorting time
- $statement->order = null;
- // Removes LIMIT clause that might have been added
- $statement->limit = null;
- if ($changeExpression) {
- $statement->expr[0] = new Expression();
- $statement->expr[0]->expr = '1';
- }
- $countQuery = 'SELECT COUNT(*) FROM (' . $statement->build() . ' ) as cnt';
- $unlimNumRows = $this->dbi->fetchValue($countQuery);
- if ($unlimNumRows === false) {
- $unlimNumRows = 0;
- }
- }
- } else {// not $is_select
- $unlimNumRows = 0;
- }
- return $unlimNumRows;
- }
- /**
- * Function to handle all aspects relating to executing the query
- *
- * @param array $analyzedSqlResults analyzed sql results
- * @param string $fullSqlQuery full sql query
- * @param bool $isGotoFile whether to go to a file
- * @param string $db current database
- * @param string|null $table current table
- * @param bool|null $findRealEnd whether to find the real end
- * @param string|null $sqlQueryForBookmark sql query to be stored as bookmark
- * @param array|null $extraData extra data
- *
- * @psalm-return array{
- * ResultInterface|false|null,
- * int|numeric-string,
- * int|numeric-string,
- * array<string, string>|null,
- * array|null
- * }
- */
- private function executeTheQuery(
- array $analyzedSqlResults,
- $fullSqlQuery,
- $isGotoFile,
- string $db,
- ?string $table,
- ?bool $findRealEnd,
- ?string $sqlQueryForBookmark,
- $extraData
- ): array {
- $response = ResponseRenderer::getInstance();
- $response->getHeader()->getMenu()->setTable($table ?? '');
- // Only if we ask to see the php code
- if (isset($GLOBALS['show_as_php'])) {
- $result = null;
- $numRows = 0;
- $unlimNumRows = 0;
- $profilingResults = null;
- } else { // If we don't ask to see the php code
- Profiling::enable($this->dbi);
- if (! defined('TESTSUITE')) {
- // close session in case the query takes too long
- session_write_close();
- }
- $result = $this->dbi->tryQuery($fullSqlQuery);
- $GLOBALS['querytime'] = $this->dbi->lastQueryExecutionTime;
- if (! defined('TESTSUITE')) {
- // reopen session
- session_start();
- }
- // Displays an error message if required and stop parsing the script
- $error = $this->dbi->getError();
- if ($error && $GLOBALS['cfg']['IgnoreMultiSubmitErrors']) {
- $extraData['error'] = $error;
- } elseif ($error) {
- $this->handleQueryExecuteError($isGotoFile, $error, $fullSqlQuery);
- }
- // If there are no errors and bookmarklabel was given,
- // store the query as a bookmark
- if (! empty($_POST['bkm_label']) && $sqlQueryForBookmark) {
- $bookmarkFeature = $this->relation->getRelationParameters()->bookmarkFeature;
- $this->storeTheQueryAsBookmark(
- $bookmarkFeature,
- $db,
- $bookmarkFeature !== null ? $GLOBALS['cfg']['Server']['user'] : '',
- $sqlQueryForBookmark,
- $_POST['bkm_label'],
- isset($_POST['bkm_replace'])
- );
- }
- // Gets the number of rows affected/returned
- // (This must be done immediately after the query because
- // mysql_affected_rows() reports about the last query done)
- $numRows = $this->getNumberOfRowsAffectedOrChanged($analyzedSqlResults['is_affected'], $result);
- $profilingResults = Profiling::getInformation($this->dbi);
- $justBrowsing = self::isJustBrowsing($analyzedSqlResults, $findRealEnd ?? null);
- $unlimNumRows = $this->countQueryResults($numRows, $justBrowsing, $db, $table ?? '', $analyzedSqlResults);
- $this->cleanupRelations($db, $table ?? '', $_POST['dropped_column'] ?? null, ! empty($_POST['purge']));
- if (
- isset($_POST['dropped_column'])
- && $db !== '' && $table !== null && $table !== ''
- ) {
- // to refresh the list of indexes (Ajax mode)
- $indexes = Index::getFromTable($table, $db);
- $indexesDuplicates = Index::findDuplicates($table, $db);
- $template = new Template();
- $extraData['indexes_list'] = $template->render('indexes', [
- 'url_params' => $GLOBALS['urlParams'],
- 'indexes' => $indexes,
- 'indexes_duplicates' => $indexesDuplicates,
- ]);
- }
- }
- return [
- $result,
- $numRows,
- $unlimNumRows,
- $profilingResults,
- $extraData,
- ];
- }
- /**
- * Delete related transformation information
- *
- * @param string $db current database
- * @param string $table current table
- * @param array $analyzedSqlResults analyzed sql results
- */
- private function deleteTransformationInfo(string $db, string $table, array $analyzedSqlResults): void
- {
- if (! isset($analyzedSqlResults['statement'])) {
- return;
- }
- $statement = $analyzedSqlResults['statement'];
- if ($statement instanceof AlterStatement) {
- if (
- ! empty($statement->altered[0])
- && $statement->altered[0]->options->has('DROP')
- && ! empty($statement->altered[0]->field->column)
- ) {
- $this->transformations->clear($db, $table, $statement->altered[0]->field->column);
- }
- } elseif ($statement instanceof DropStatement) {
- $this->transformations->clear($db, $table);
- }
- }
- /**
- * Function to get the message for the no rows returned case
- *
- * @param string|null $messageToShow message to show
- * @param array $analyzedSqlResults analyzed sql results
- * @param int|string $numRows number of rows
- */
- private function getMessageForNoRowsReturned(
- ?string $messageToShow,
- array $analyzedSqlResults,
- $numRows
- ): Message {
- if ($analyzedSqlResults['querytype'] === 'DELETE') {
- $message = Message::getMessageForDeletedRows($numRows);
- } elseif ($analyzedSqlResults['is_insert']) {
- if ($analyzedSqlResults['querytype'] === 'REPLACE') {
- // For REPLACE we get DELETED + INSERTED row count,
- // so we have to call it affected
- $message = Message::getMessageForAffectedRows($numRows);
- } else {
- $message = Message::getMessageForInsertedRows($numRows);
- }
- $insertId = $this->dbi->insertId();
- if ($insertId !== 0) {
- // insert_id is id of FIRST record inserted in one insert,
- // so if we inserted multiple rows, we had to increment this
- $message->addText('[br]');
- // need to use a temporary because the Message class
- // currently supports adding parameters only to the first
- // message
- $inserted = Message::notice(__('Inserted row id: %1$d'));
- $inserted->addParam($insertId + $numRows - 1);
- $message->addMessage($inserted);
- }
- } elseif ($analyzedSqlResults['is_affected']) {
- $message = Message::getMessageForAffectedRows($numRows);
- // Ok, here is an explanation for the !$is_select.
- // The form generated by PhpMyAdmin\SqlQueryForm
- // and /database/sql has many submit buttons
- // on the same form, and some confusion arises from the
- // fact that $message_to_show is sent for every case.
- // The $message_to_show containing a success message and sent with
- // the form should not have priority over errors
- } elseif ($messageToShow && $analyzedSqlResults['querytype'] !== 'SELECT') {
- $message = Message::rawSuccess(htmlspecialchars($messageToShow));
- } elseif (! empty($GLOBALS['show_as_php'])) {
- $message = Message::success(__('Showing as PHP code'));
- } elseif (isset($GLOBALS['show_as_php'])) {
- /* User disable showing as PHP, query is only displayed */
- $message = Message::notice(__('Showing SQL query'));
- } else {
- $message = Message::success(
- __('MySQL returned an empty result set (i.e. zero rows).')
- );
- }
- if (isset($GLOBALS['querytime'])) {
- $queryTime = Message::notice(
- '(' . __('Query took %01.4f seconds.') . ')'
- );
- $queryTime->addParam($GLOBALS['querytime']);
- $message->addMessage($queryTime);
- }
- // In case of ROLLBACK, notify the user.
- if (isset($_POST['rollback_query'])) {
- $message->addText(__('[ROLLBACK occurred.]'));
- }
- return $message;
- }
- /**
- * Function to respond back when the query returns zero rows
- * This method is called
- * 1-> When browsing an empty table
- * 2-> When executing a query on a non empty table which returns zero results
- * 3-> When executing a query on an empty table
- * 4-> When executing an INSERT, UPDATE, DELETE query from the SQL tab
- * 5-> When deleting a row from BROWSE tab
- * 6-> When searching using the SEARCH tab which returns zero results
- * 7-> When changing the structure of the table except change operation
- *
- * @param array $analyzedSqlResults analyzed sql results
- * @param string $db current database
- * @param string|null $table current table
- * @param string|null $messageToShow message to show
- * @param int|string $numRows number of rows
- * @param DisplayResults $displayResultsObject DisplayResult instance
- * @param array|null $extraData extra data
- * @param array|null $profilingResults profiling results
- * @param ResultInterface|false|null $result executed query results
- * @param string $sqlQuery sql query
- * @param string|null $completeQuery complete sql query
- * @psalm-param int|numeric-string $numRows
- *
- * @return string html
- */
- private function getQueryResponseForNoResultsReturned(
- array $analyzedSqlResults,
- string $db,
- ?string $table,
- ?string $messageToShow,
- $numRows,
- $displayResultsObject,
- ?array $extraData,
- ?array $profilingResults,
- $result,
- $sqlQuery,
- ?string $completeQuery
- ): string {
- if ($this->isDeleteTransformationInfo($analyzedSqlResults)) {
- $this->deleteTransformationInfo($db, $table ?? '', $analyzedSqlResults);
- }
- if (isset($extraData['error'])) {
- $message = Message::rawError($extraData['error']);
- } else {
- $message = $this->getMessageForNoRowsReturned($messageToShow, $analyzedSqlResults, $numRows);
- }
- $queryMessage = Generator::getMessage($message, $sqlQuery, 'success');
- if (isset($GLOBALS['show_as_php'])) {
- return $queryMessage;
- }
- if (! empty($GLOBALS['reload'])) {
- $extraData['reload'] = 1;
- $extraData['db'] = $GLOBALS['db'];
- }
- // For ajax requests add message and sql_query as JSON
- if (empty($_REQUEST['ajax_page_request'])) {
- $extraData['message'] = $message;
- if ($GLOBALS['cfg']['ShowSQL']) {
- $extraData['sql_query'] = $queryMessage;
- }
- }
- $response = ResponseRenderer::getInstance();
- $response->addJSON($extraData ?? []);
- if (($result instanceof ResultInterface && $result->numFields() === 0) || isset($extraData['error'])) {
- return $queryMessage;
- }
- $displayParts = [
- 'edit_lnk' => null,
- 'del_lnk' => null,
- 'sort_lnk' => '1',
- 'nav_bar' => '0',
- 'bkm_form' => '1',
- 'text_btn' => '1',
- 'pview_lnk' => '1',
- ];
- $sqlQueryResultsTable = $this->getHtmlForSqlQueryResultsTable(
- $displayResultsObject,
- $displayParts,
- false,
- 0,
- $numRows,
- null,
- $result,
- $analyzedSqlResults,
- true
- );
- $profilingChart = '';
- if ($profilingResults !== null) {
- $header = $response->getHeader();
- $scripts = $header->getScripts();
- $scripts->addFile('sql.js');
- $profiling = $this->getDetailedProfilingStats($profilingResults);
- $profilingChart = $this->template->render('sql/profiling_chart', ['profiling' => $profiling]);
- }
- $bookmark = '';
- $bookmarkFeature = $this->relation->getRelationParameters()->bookmarkFeature;
- if (
- $bookmarkFeature !== null
- && empty($_GET['id_bookmark'])
- && $sqlQuery
- ) {
- $bookmark = $this->template->render('sql/bookmark', [
- 'db' => $db,
- 'goto' => Url::getFromRoute('/sql', [
- 'db' => $db,
- 'table' => $table,
- 'sql_query' => $sqlQuery,
- 'id_bookmark' => 1,
- ]),
- 'user' => $GLOBALS['cfg']['Server']['user'],
- 'sql_query' => $completeQuery ?? $sqlQuery,
- ]);
- }
- return $this->template->render('sql/no_results_returned', [
- 'message' => $queryMessage,
- 'sql_query_results_table' => $sqlQueryResultsTable,
- 'profiling_chart' => $profilingChart,
- 'bookmark' => $bookmark,
- 'db' => $db,
- 'table' => $table,
- 'sql_query' => $sqlQuery,
- 'is_procedure' => ! empty($analyzedSqlResults['is_procedure']),
- ]);
- }
- /**
- * Function to send response for ajax grid edit
- *
- * @param ResultInterface $result result of the executed query
- */
- private function getResponseForGridEdit(ResultInterface $result): void
- {
- $row = $result->fetchRow();
- $fieldsMeta = $this->dbi->getFieldsMeta($result);
- if (isset($fieldsMeta[0]) && $fieldsMeta[0]->isBinary()) {
- $row[0] = bin2hex($row[0]);
- }
- $response = ResponseRenderer::getInstance();
- $response->addJSON('value', $row[0]);
- }
- /**
- * Returns a message for successful creation of a bookmark or null if a bookmark
- * was not created
- */
- private function getBookmarkCreatedMessage(): string
- {
- $output = '';
- if (isset($_GET['label'])) {
- $message = Message::success(
- __('Bookmark %s has been created.')
- );
- $message->addParam($_GET['label']);
- $output = $message->getDisplay();
- }
- return $output;
- }
- /**
- * Function to get html for the sql query results table
- *
- * @param DisplayResults $displayResultsObject instance of DisplayResult
- * @param array $displayParts the parts to display
- * @param bool $editable whether the result table is
- * editable or not
- * @param int|string $unlimNumRows unlimited number of rows
- * @param int|string $numRows number of rows
- * @param array|null $showTable table definitions
- * @param ResultInterface|false|null $result result of the executed query
- * @param array $analyzedSqlResults analyzed sql results
- * @param bool $isLimitedDisplay Show only limited operations or not
- * @psalm-param int|numeric-string $unlimNumRows
- * @psalm-param int|numeric-string $numRows
- */
- private function getHtmlForSqlQueryResultsTable(
- $displayResultsObject,
- array $displayParts,
- $editable,
- $unlimNumRows,
- $numRows,
- ?array $showTable,
- $result,
- array $analyzedSqlResults,
- $isLimitedDisplay = false
- ): string {
- $printView = isset($_POST['printview']) && $_POST['printview'] == '1' ? '1' : null;
- $tableHtml = '';
- $isBrowseDistinct = ! empty($_POST['is_browse_distinct']);
- if ($analyzedSqlResults['is_procedure']) {
- do {
- if ($result === null) {
- $result = $this->dbi->storeResult();
- }
- if ($result === false) {
- $result = null;
- continue;
- }
- $numRows = $result->numRows();
- if ($numRows > 0) {
- $fieldsMeta = $this->dbi->getFieldsMeta($result);
- $fieldsCount = count($fieldsMeta);
- $displayResultsObject->setProperties(
- $numRows,
- $fieldsMeta,
- $analyzedSqlResults['is_count'],
- $analyzedSqlResults['is_export'],
- $analyzedSqlResults['is_func'],
- $analyzedSqlResults['is_analyse'],
- $numRows,
- $fieldsCount,
- $GLOBALS['querytime'],
- $GLOBALS['text_dir'],
- $analyzedSqlResults['is_maint'],
- $analyzedSqlResults['is_explain'],
- $analyzedSqlResults['is_show'],
- $showTable,
- $printView,
- $editable,
- $isBrowseDistinct
- );
- $displayParts = [
- 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE,
- 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE,
- 'sort_lnk' => '1',
- 'nav_bar' => '1',
- 'bkm_form' => '1',
- 'text_btn' => '1',
- 'pview_lnk' => '1',
- ];
- $tableHtml .= $displayResultsObject->getTable(
- $result,
- $displayParts,
- $analyzedSqlResults,
- $isLimitedDisplay
- );
- }
- $result = null;
- } while ($this->dbi->moreResults() && $this->dbi->nextResult());
- } else {
- $fieldsMeta = [];
- if (isset($result) && ! is_bool($result)) {
- $fieldsMeta = $this->dbi->getFieldsMeta($result);
- }
- $fieldsCount = count($fieldsMeta);
- $_SESSION['is_multi_query'] = false;
- $displayResultsObject->setProperties(
- $unlimNumRows,
- $fieldsMeta,
- $analyzedSqlResults['is_count'],
- $analyzedSqlResults['is_export'],
- $analyzedSqlResults['is_func'],
- $analyzedSqlResults['is_analyse'],
- $numRows,
- $fieldsCount,
- $GLOBALS['querytime'],
- $GLOBALS['text_dir'],
- $analyzedSqlResults['is_maint'],
- $analyzedSqlResults['is_explain'],
- $analyzedSqlResults['is_show'],
- $showTable,
- $printView,
- $editable,
- $isBrowseDistinct
- );
- if (! is_bool($result)) {
- $tableHtml .= $displayResultsObject->getTable(
- $result,
- $displayParts,
- $analyzedSqlResults,
- $isLimitedDisplay
- );
- }
- }
- return $tableHtml;
- }
- /**
- * Function to get html for the previous query if there is such.
- *
- * @param string|null $displayQuery display query
- * @param bool $showSql whether to show sql
- * @param array $sqlData sql data
- * @param Message|string $displayMessage display message
- */
- private function getHtmlForPreviousUpdateQuery(
- ?string $displayQuery,
- bool $showSql,
- array $sqlData,
- $displayMessage
- ): string {
- $output = '';
- if ($displayQuery !== null && $showSql && $sqlData === []) {
- $output = Generator::getMessage($displayMessage, $displayQuery, 'success');
- }
- return $output;
- }
- /**
- * To get the message if a column index is missing. If not will return null
- *
- * @param string|null $table current table
- * @param string $database current database
- * @param bool $editable whether the results table can be editable or not
- * @param bool $hasUniqueKey whether there is a unique key
- */
- private function getMessageIfMissingColumnIndex(
- ?string $table,
- string $database,
- bool $editable,
- bool $hasUniqueKey
- ): string {
- if ($table === null) {
- return '';
- }
- $output = '';
- if (Utilities::isSystemSchema($database) || ! $editable) {
- $output = Message::notice(
- sprintf(
- __(
- 'Current selection does not contain a unique column.'
- . ' Grid edit, checkbox, Edit, Copy and Delete features'
- . ' are not available. %s'
- ),
- MySQLDocumentation::showDocumentation(
- 'config',
- 'cfg_RowActionLinksWithoutUnique'
- )
- )
- )->getDisplay();
- } elseif (! $hasUniqueKey) {
- $output = Message::notice(
- sprintf(
- __(
- 'Current selection does not contain a unique column.'
- . ' Grid edit, Edit, Copy and Delete features may result in'
- . ' undesired behavior. %s'
- ),
- MySQLDocumentation::showDocumentation(
- 'config',
- 'cfg_RowActionLinksWithoutUnique'
- )
- )
- )->getDisplay();
- }
- return $output;
- }
- /**
- * Function to display results when the executed query returns non empty results
- *
- * @param ResultInterface|false|null $result executed query results
- * @param array $analyzedSqlResults analysed sql results
- * @param string $db current database
- * @param string|null $table current table
- * @param array|null $sqlData sql data
- * @param DisplayResults $displayResultsObject Instance of DisplayResults
- * @param int|string $unlimNumRows unlimited number of rows
- * @param int|string $numRows number of rows
- * @param string|null $dispQuery display query
- * @param Message|string|null $dispMessage display message
- * @param array|null $profilingResults profiling results
- * @param string $sqlQuery sql query
- * @param string|null $completeQuery complete sql query
- * @psalm-param int|numeric-string $unlimNumRows
- * @psalm-param int|numeric-string $numRows
- *
- * @return string html
- */
- private function getQueryResponseForResultsReturned(
- $result,
- array $analyzedSqlResults,
- string $db,
- ?string $table,
- ?array $sqlData,
- $displayResultsObject,
- $unlimNumRows,
- $numRows,
- ?string $dispQuery,
- $dispMessage,
- ?array $profilingResults,
- $sqlQuery,
- ?string $completeQuery
- ): string {
- global $showtable;
- // If we are retrieving the full value of a truncated field or the original
- // value of a transformed field, show it here
- if (isset($_POST['grid_edit']) && $_POST['grid_edit'] == true && is_object($result)) {
- $this->getResponseForGridEdit($result);
- exit;
- }
- // Gets the list of fields properties
- $fieldsMeta = [];
- if ($result !== null && ! is_bool($result)) {
- $fieldsMeta = $this->dbi->getFieldsMeta($result);
- }
- // Should be initialized these parameters before parsing
- if (! is_array($showtable)) {
- $showtable = null;
- }
- $response = ResponseRenderer::getInstance();
- $header = $response->getHeader();
- $scripts = $header->getScripts();
- $justOneTable = $this->resultSetHasJustOneTable($fieldsMeta);
- // hide edit and delete links:
- // - for information_schema
- // - if the result set does not contain all the columns of a unique key
- // (unless this is an updatable view)
- // - if the SELECT query contains a join or a subquery
- $updatableView = false;
- $statement = $analyzedSqlResults['statement'] ?? null;
- if ($statement instanceof SelectStatement) {
- if ($statement->expr && $statement->expr[0]->expr === '*' && $table) {
- $_table = new Table($table, $db);
- $updatableView = $_table->isUpdatableView();
- }
- if (
- $analyzedSqlResults['join']
- || $analyzedSqlResults['is_subquery']
- || count($analyzedSqlResults['select_tables']) !== 1
- ) {
- $justOneTable = false;
- }
- }
- $hasUnique = $table !== null && $this->resultSetContainsUniqueKey($db, $table, $fieldsMeta);
- $editable = ($hasUnique
- || $GLOBALS['cfg']['RowActionLinksWithoutUnique']
- || $updatableView)
- && $justOneTable
- && ! Utilities::isSystemSchema($db);
- $_SESSION['tmpval']['possible_as_geometry'] = $editable;
- $displayParts = [
- 'edit_lnk' => $displayResultsObject::UPDATE_ROW,
- 'del_lnk' => $displayResultsObject::DELETE_ROW,
- 'sort_lnk' => '1',
- 'nav_bar' => '1',
- 'bkm_form' => '1',
- 'text_btn' => '0',
- 'pview_lnk' => '1',
- ];
- if (! $editable) {
- $displayParts = [
- 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE,
- 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE,
- 'sort_lnk' => '1',
- 'nav_bar' => '1',
- 'bkm_form' => '1',
- 'text_btn' => '1',
- 'pview_lnk' => '1',
- ];
- }
- if (isset($_POST['printview']) && $_POST['printview'] == '1') {
- $displayParts = [
- 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE,
- 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE,
- 'sort_lnk' => '0',
- 'nav_bar' => '0',
- 'bkm_form' => '0',
- 'text_btn' => '0',
- 'pview_lnk' => '0',
- ];
- }
- if (! isset($_POST['printview']) || $_POST['printview'] != '1') {
- $scripts->addFile('makegrid.js');
- $scripts->addFile('sql.js');
- unset($GLOBALS['message']);
- //we don't need to buffer the output in getMessage here.
- //set a global variable and check against it in the function
- $GLOBALS['buffer_message'] = false;
- }
- $previousUpdateQueryHtml = $this->getHtmlForPreviousUpdateQuery(
- $dispQuery,
- (bool) $GLOBALS['cfg']['ShowSQL'],
- $sqlData ?? [],
- $dispMessage ?? ''
- );
- $profilingChartHtml = '';
- if ($profilingResults) {
- $profiling = $this->getDetailedProfilingStats($profilingResults);
- $profilingChartHtml = $this->template->render('sql/profiling_chart', ['profiling' => $profiling]);
- }
- $missingUniqueColumnMessage = $this->getMessageIfMissingColumnIndex($table, $db, $editable, $hasUnique);
- $bookmarkCreatedMessage = $this->getBookmarkCreatedMessage();
- $tableHtml = $this->getHtmlForSqlQueryResultsTable(
- $displayResultsObject,
- $displayParts,
- $editable,
- $unlimNumRows,
- $numRows,
- $showtable,
- $result,
- $analyzedSqlResults
- );
- $bookmarkSupportHtml = '';
- $bookmarkFeature = $this->relation->getRelationParameters()->bookmarkFeature;
- if (
- $bookmarkFeature !== null
- && $displayParts['bkm_form'] == '1'
- && empty($_GET['id_bookmark'])
- && $sqlQuery
- ) {
- $bookmarkSupportHtml = $this->template->render('sql/bookmark', [
- 'db' => $db,
- 'goto' => Url::getFromRoute('/sql', [
- 'db' => $db,
- 'table' => $table,
- 'sql_query' => $sqlQuery,
- 'id_bookmark' => 1,
- ]),
- 'user' => $GLOBALS['cfg']['Server']['user'],
- 'sql_query' => $completeQuery ?? $sqlQuery,
- ]);
- }
- return $this->template->render('sql/sql_query_results', [
- 'previous_update_query' => $previousUpdateQueryHtml,
- 'profiling_chart' => $profilingChartHtml,
- 'missing_unique_column_message' => $missingUniqueColumnMessage,
- 'bookmark_created_message' => $bookmarkCreatedMessage,
- 'table' => $tableHtml,
- 'bookmark_support' => $bookmarkSupportHtml,
- ]);
- }
- /**
- * Function to execute the query and send the response
- *
- * @param array|null $analyzedSqlResults analysed sql results
- * @param bool $isGotoFile whether goto file or not
- * @param string $db current database
- * @param string|null $table current table
- * @param bool|null $findRealEnd whether to find real end or not
- * @param string|null $sqlQueryForBookmark the sql query to be stored as bookmark
- * @param array|null $extraData extra data
- * @param string|null $messageToShow message to show
- * @param array|null $sqlData sql data
- * @param string $goto goto page url
- * @param string|null $dispQuery display query
- * @param Message|string|null $dispMessage display message
- * @param string $sqlQuery sql query
- * @param string|null $completeQuery complete query
- */
- public function executeQueryAndSendQueryResponse(
- $analyzedSqlResults,
- $isGotoFile,
- string $db,
- ?string $table,
- $findRealEnd,
- $sqlQueryForBookmark,
- $extraData,
- $messageToShow,
- $sqlData,
- $goto,
- $dispQuery,
- $dispMessage,
- $sqlQuery,
- $completeQuery
- ): string {
- if ($analyzedSqlResults == null) {
- // Parse and analyze the query
- [
- $analyzedSqlResults,
- $db,
- $tableFromSql,
- ] = ParseAnalyze::sqlQuery($sqlQuery, $db);
- $table = $tableFromSql ?: $table;
- }
- return $this->executeQueryAndGetQueryResponse(
- $analyzedSqlResults, // analyzed_sql_results
- $isGotoFile, // is_gotofile
- $db, // db
- $table, // table
- $findRealEnd, // find_real_end
- $sqlQueryForBookmark, // sql_query_for_bookmark
- $extraData, // extra_data
- $messageToShow, // message_to_show
- $sqlData, // sql_data
- $goto, // goto
- $dispQuery, // disp_query
- $dispMessage, // disp_message
- $sqlQuery, // sql_query
- $completeQuery // complete_query
- );
- }
- /**
- * Function to execute the query and send the response
- *
- * @param array $analyzedSqlResults analysed sql results
- * @param bool $isGotoFile whether goto file or not
- * @param string $db current database
- * @param string|null $table current table
- * @param bool|null $findRealEnd whether to find real end or not
- * @param string|null $sqlQueryForBookmark the sql query to be stored as bookmark
- * @param array|null $extraData extra data
- * @param string|null $messageToShow message to show
- * @param array|null $sqlData sql data
- * @param string $goto goto page url
- * @param string|null $dispQuery display query
- * @param Message|string|null $dispMessage display message
- * @param string $sqlQuery sql query
- * @param string|null $completeQuery complete query
- *
- * @return string html
- */
- public function executeQueryAndGetQueryResponse(
- array $analyzedSqlResults,
- $isGotoFile,
- string $db,
- ?string $table,
- $findRealEnd,
- ?string $sqlQueryForBookmark,
- $extraData,
- ?string $messageToShow,
- $sqlData,
- $goto,
- ?string $dispQuery,
- $dispMessage,
- $sqlQuery,
- ?string $completeQuery
- ): string {
- // Handle disable/enable foreign key checks
- $defaultFkCheck = ForeignKey::handleDisableCheckInit();
- // Handle remembered sorting order, only for single table query.
- // Handling is not required when it's a union query
- // (the parser never sets the 'union' key to 0).
- // Handling is also not required if we came from the "Sort by key"
- // drop-down.
- if (
- $analyzedSqlResults !== []
- && $this->isRememberSortingOrder($analyzedSqlResults)
- && empty($analyzedSqlResults['union'])
- && ! isset($_POST['sort_by_key'])
- ) {
- if (! isset($_SESSION['sql_from_query_box'])) {
- $this->handleSortOrder($db, $table, $analyzedSqlResults, $sqlQuery);
- } else {
- unset($_SESSION['sql_from_query_box']);
- }
- }
- $displayResultsObject = new DisplayResults(
- $GLOBALS['dbi'],
- $GLOBALS['db'],
- $GLOBALS['table'],
- $GLOBALS['server'],
- $goto,
- $sqlQuery
- );
- $displayResultsObject->setConfigParamsForDisplayTable($analyzedSqlResults);
- // assign default full_sql_query
- $fullSqlQuery = $sqlQuery;
- // Do append a "LIMIT" clause?
- if ($this->isAppendLimitClause($analyzedSqlResults)) {
- $fullSqlQuery = $this->getSqlWithLimitClause($analyzedSqlResults);
- }
- $GLOBALS['reload'] = $this->hasCurrentDbChanged($db);
- $this->dbi->selectDb($db);
- [
- $result,
- $numRows,
- $unlimNumRows,
- $profilingResults,
- $extraData,
- ] = $this->executeTheQuery(
- $analyzedSqlResults,
- $fullSqlQuery,
- $isGotoFile,
- $db,
- $table,
- $findRealEnd,
- $sqlQueryForBookmark,
- $extraData
- );
- $warningMessages = $this->operations->getWarningMessagesArray();
- // No rows returned -> move back to the calling page
- if (($numRows == 0 && $unlimNumRows == 0) || $analyzedSqlResults['is_affected']) {
- $htmlOutput = $this->getQueryResponseForNoResultsReturned(
- $analyzedSqlResults,
- $db,
- $table,
- $messageToShow,
- $numRows,
- $displayResultsObject,
- $extraData,
- $profilingResults,
- $result,
- $sqlQuery,
- $completeQuery
- );
- } else {
- // At least one row is returned -> displays a table with results
- $htmlOutput = $this->getQueryResponseForResultsReturned(
- $result,
- $analyzedSqlResults,
- $db,
- $table,
- $sqlData,
- $displayResultsObject,
- $unlimNumRows,
- $numRows,
- $dispQuery,
- $dispMessage,
- $profilingResults,
- $sqlQuery,
- $completeQuery
- );
- }
- // Handle disable/enable foreign key checks
- ForeignKey::handleDisableCheckCleanup($defaultFkCheck);
- foreach ($warningMessages as $warning) {
- $message = Message::notice(Message::sanitize($warning));
- $htmlOutput .= $message->getDisplay();
- }
- return $htmlOutput;
- }
- /**
- * Function to define pos to display a row
- *
- * @param int $numberOfLine Number of the line to display
- *
- * @return int Start position to display the line
- */
- private function getStartPosToDisplayRow($numberOfLine)
- {
- $maxRows = $_SESSION['tmpval']['max_rows'];
- return @((int) ceil($numberOfLine / $maxRows) - 1) * $maxRows;
- }
- /**
- * Function to calculate new pos if pos is higher than number of rows
- * of displayed table
- *
- * @param string $db Database name
- * @param string $table Table name
- * @param int|null $pos Initial position
- *
- * @return int Number of pos to display last page
- */
- public function calculatePosForLastPage($db, $table, $pos)
- {
- if ($pos === null) {
- $pos = $_SESSION['tmpval']['pos'];
- }
- $tableObject = new Table($table, $db);
- $unlimNumRows = $tableObject->countRecords(true);
- //If position is higher than number of rows
- if ($unlimNumRows <= $pos && $pos != 0) {
- $pos = $this->getStartPosToDisplayRow($unlimNumRows);
- }
- return $pos;
- }
- }
|