diff --git a/adminer/call.inc.php b/adminer/call.inc.php index f8988675..dc4d2677 100644 --- a/adminer/call.inc.php +++ b/adminer/call.inc.php @@ -1,5 +1,6 @@ multi_query($query); $affected = $connection->affected_rows; // getting warnigns overwrites this echo $adminer->selectQuery($query, $start, !$result); - + if (!$result) { echo "
" . error() . "\n"; } else { @@ -42,7 +43,7 @@ if (!$error && $_POST) { if (is_object($connection2)) { $connection2->select_db(DB); } - + do { $result = $connection->store_result(); if (is_object($result)) { @@ -53,16 +54,15 @@ if (!$error && $_POST) { ; } } while ($connection->next_result()); - + if ($out) { select($connection->query("SELECT " . implode(", ", $out))); } } } -?> -
+ +echo "", + "", + "", + "
\n", + "\n"; + +$comment = $routine["comment"]; +if ($comment !== null && $comment !== "") { + $comment = h(trim($routine["comment"], "\n")); + + // Remove indenting of all lines (used in MySQL routines in 'sys' database). + if (preg_match('~^ +~', $comment, $matches)) { + preg_match_all("~^($matches[0]|$)~m", $comment, $linesWithIndent); + + if (count($linesWithIndent[0]) == substr_count($comment, "\n")) { + $comment = preg_replace("~^($matches[0])~m", "", $comment); + } + } + + // Format common headlines (used in MySQL routines in 'sys' database). + $comment = preg_replace('~(^|[^\n]\n)(Description|Parameters|Example)\n~', "$1\n$2\n", $comment); + + echo "$comment\n"; +} diff --git a/adminer/db.inc.php b/adminer/db.inc.php index 03633a98..2203a3e9 100644 --- a/adminer/db.inc.php +++ b/adminer/db.inc.php @@ -182,25 +182,43 @@ if ($adminer->homepage()) { if (support("routine")) { echo "
' . lang('Name') . ' | ' . lang('Type') . ' | ' . lang('Return type') . " | ||
---|---|---|---|---|
', lang('Name'), ' | ', lang('Type'), ' | ', lang('Return type'), " | "; + if ($commentsSupported) { + echo "", lang('Comment'), " | "; + } + echo "", + " |
' . h($row["ROUTINE_NAME"]) . ''; - echo ' | ' . h($row["ROUTINE_TYPE"]); - echo ' | ' . h($row["DTD_IDENTIFIER"]); - echo ' | ' . lang('Alter') . ""; + // not computed on the pages to be able to print the header first + $name = ($row["SPECIFIC_NAME"] == $row["ROUTINE_NAME"] ? "" : "&name=" . urlencode($row["ROUTINE_NAME"])); + + echo ' | |
', h($row["ROUTINE_NAME"]), ' | ', + '', h($row["ROUTINE_TYPE"]), ' | ', + '', h($row["DTD_IDENTIFIER"]), ' | '; + + if ($commentsSupported) { + echo '', shorten_utf8(preg_replace('~\s{2,}~', " ", trim($row["ROUTINE_COMMENT"])), 50), ' | '; + } + + echo '' . lang('Alter') . " | "; } + echo "
' - . (support("procedure") ? '' . lang('Create procedure') . '' : '') - . '' . lang('Create function') . "\n" - ; + + echo '
', + (support("procedure") ? '' . lang('Create procedure') . '' : ''), + '' . lang('Create function') . "\n"; } if (support("sequence")) { diff --git a/adminer/drivers/mysql.inc.php b/adminer/drivers/mysql.inc.php index 7822004a..fca9c5f3 100644 --- a/adminer/drivers/mysql.inc.php +++ b/adminer/drivers/mysql.inc.php @@ -901,23 +901,30 @@ if (!defined("DRIVER")) { ); } - /** Get information about stored routine - * @param string - * @param string "FUNCTION" or "PROCEDURE" - * @return array ("fields" => array("field" => , "type" => , "length" => , "unsigned" => , "inout" => , "collation" => ), "returns" => , "definition" => , "language" => ) - */ + /** + * Gets information about stored routine. + * + * @param string $name + * @param string $type "FUNCTION" or "PROCEDURE" + * + * @return array ("fields" => array("field" => , "type" => , "length" => , "unsigned" => , "inout" => , "collation" => ), "returns" => , "definition" => , "language" => ) + */ function routine($name, $type) { global $connection, $enum_length, $inout, $types; - $aliases = array("bool", "boolean", "integer", "double precision", "real", "dec", "numeric", "fixed", "national char", "national varchar"); + + $info = get_rows("SELECT ROUTINE_BODY, ROUTINE_COMMENT FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = " . q(DB) . " AND ROUTINE_NAME = " . q($name))[0]; + + $aliases = ["bool", "boolean", "integer", "double precision", "real", "dec", "numeric", "fixed", "national char", "national varchar"]; $space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)"; $type_pattern = "((" . implode("|", array_merge(array_keys($types), $aliases)) . ")\\b(?:\\s*\\(((?:[^'\")]|$enum_length)++)\\))?\\s*(zerofill\\s*)?(unsigned(?:\\s+zerofill)?)?)(?:\\s*(?:CHARSET|CHARACTER\\s+SET)\\s*['\"]?([^'\"\\s,]+)['\"]?)?"; $pattern = "$space*(" . ($type == "FUNCTION" ? "" : $inout) . ")?\\s*(?:`((?:[^`]|``)*)`\\s*|\\b(\\S+)\\s+)$type_pattern"; $create = $connection->result("SHOW CREATE $type " . idf_escape($name), 2); preg_match("~\\(((?:$pattern\\s*,?)*)\\)\\s*" . ($type == "FUNCTION" ? "RETURNS\\s+$type_pattern\\s+" : "") . "(.*)~is", $create, $match); - $fields = array(); + $fields = []; preg_match_all("~$pattern\\s*,?~is", $match[1], $matches, PREG_SET_ORDER); + foreach ($matches as $param) { - $fields[] = array( + $fields[] = [ "field" => str_replace("``", "`", $param[2]) . $param[3], "type" => strtolower($param[5]), "length" => preg_replace_callback("~$enum_length~s", 'normalize_enum', $param[6]), @@ -926,24 +933,29 @@ if (!defined("DRIVER")) { "full_type" => $param[4], "inout" => strtoupper($param[1]), "collation" => strtolower($param[9]), - ); + ]; } - if ($type != "FUNCTION") { - return array("fields" => $fields, "definition" => $match[11]); - } - return array( + + return $type == "FUNCTION" ? [ "fields" => $fields, - "returns" => array("type" => $match[12], "length" => $match[13], "unsigned" => $match[15], "collation" => $match[16]), + "returns" => ["type" => $match[12], "length" => $match[13], "unsigned" => $match[15], "collation" => $match[16]], "definition" => $match[17], - "language" => "SQL", // available in information_schema.ROUTINES.PARAMETER_STYLE - ); + "language" => $info["ROUTINE_BODY"], + "comment" => $info["ROUTINE_COMMENT"], + ] : [ + "fields" => $fields, + "returns" => null, + "definition" => $match[11], + "language" => $info["ROUTINE_BODY"], + "comment" => $info["ROUTINE_COMMENT"], + ]; } /** Get list of routines * @return array ("SPECIFIC_NAME" => , "ROUTINE_NAME" => , "ROUTINE_TYPE" => , "DTD_IDENTIFIER" => ) */ function routines() { - return get_rows("SELECT ROUTINE_NAME AS SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DTD_IDENTIFIER FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = " . q(DB)); + return get_rows("SELECT ROUTINE_NAME AS SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DTD_IDENTIFIER, ROUTINE_COMMENT FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = " . q(DB)); } /** Get list of available routine languages diff --git a/adminer/drivers/pgsql.inc.php b/adminer/drivers/pgsql.inc.php index df1f6e6f..829d8e81 100644 --- a/adminer/drivers/pgsql.inc.php +++ b/adminer/drivers/pgsql.inc.php @@ -668,23 +668,29 @@ ORDER BY connamespace, conname") as $row) { } function routine($name, $type) { - $rows = get_rows('SELECT routine_definition AS definition, LOWER(external_language) AS language, * -FROM information_schema.routines -WHERE routine_schema = current_schema() AND specific_name = ' . q($name)); - $return = $rows[0]; - $return["returns"] = array("type" => $return["type_udt_name"]); - $return["fields"] = get_rows('SELECT parameter_name AS field, data_type AS type, character_maximum_length AS length, parameter_mode AS inout -FROM information_schema.parameters -WHERE specific_schema = current_schema() AND specific_name = ' . q($name) . ' -ORDER BY ordinal_position'); - return $return; + $info = get_rows('SELECT routine_definition, external_language, type_udt_name + FROM information_schema.routines + WHERE routine_schema = current_schema() AND specific_name = ' . q($name))[0]; + + $fields = get_rows('SELECT parameter_name AS field, data_type AS type, character_maximum_length AS length, parameter_mode AS inout + FROM information_schema.parameters + WHERE specific_schema = current_schema() AND specific_name = ' . q($name) . ' + ORDER BY ordinal_position'); + + return [ + "fields" => $fields, + "returns" => ["type" => $info["type_udt_name"]], + "definition" => $info["routine_definition"], + "language" => strtolower($info["external_language"]), + "comment" => null, // Comments are not supported. + ]; } function routines() { - return get_rows('SELECT specific_name AS "SPECIFIC_NAME", routine_type AS "ROUTINE_TYPE", routine_name AS "ROUTINE_NAME", type_udt_name AS "DTD_IDENTIFIER" -FROM information_schema.routines -WHERE routine_schema = current_schema() -ORDER BY SPECIFIC_NAME'); + return get_rows('SELECT specific_name AS "SPECIFIC_NAME", routine_name AS "ROUTINE_NAME", routine_type AS "ROUTINE_TYPE", type_udt_name AS "DTD_IDENTIFIER", null AS ROUTINE_COMMENT + FROM information_schema.routines + WHERE routine_schema = current_schema() + ORDER BY SPECIFIC_NAME'); } function routine_languages() { diff --git a/adminer/include/functions.inc.php b/adminer/include/functions.inc.php index 8ede5064..9ce3bf9f 100644 --- a/adminer/include/functions.inc.php +++ b/adminer/include/functions.inc.php @@ -830,6 +830,8 @@ function is_utf8($val) { * @return string escaped string with appended ... */ function shorten_utf8($string, $length = 80, $suffix = "") { + if ($string == "") return $suffix; + if (!preg_match("(^(" . repeat_pattern("[\t\r\n -\x{10FFFF}]", $length) . ")($)?)u", $string, $match)) { // ~s causes trash in $match[2] under some PHP versions, (.|\n) is slow preg_match("(^(" . repeat_pattern("[\t\r\n -~]", $length) . ")($)?)", $string, $match); } diff --git a/adminer/static/default.css b/adminer/static/default.css index aadf4326..a16d9108 100644 --- a/adminer/static/default.css +++ b/adminer/static/default.css @@ -24,6 +24,8 @@ pre { margin: 1em 0 0; } pre code { display: block; font-size: 100%; } pre, textarea { font: 110%/1.25 monospace; } pre.jush { background: #fff; } +pre.comment { white-space: pre-wrap; } +form + pre.comment { margin-top: 2em; } input, textarea, select { box-sizing: border-box; } input[type="image"] { vertical-align: middle; margin-top: -3px; } input[type="number"] { -moz-appearance: textfield; }