diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7c33f9..096be7bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - MySQL: Avoid warning on selecting tables with fulltext indexes (bug #1036) - PostgreSQL, CockroachDB: Creating partitioned tables (bug #1031) - PostgreSQL: Move partitioned tables from table list to parent table +- PostgreSQL: Support index algorithms (bug #1030) - PostgreSQL: Support calling functions returning table (bug #1040) - Designs: adminer.css with 'prefers-color-scheme: dark' don't disable dark mode - Plugins: Method bodyClass() to add <body class> diff --git a/adminer/drivers/mysql.inc.php b/adminer/drivers/mysql.inc.php index f0247de7..51c7db74 100644 --- a/adminer/drivers/mysql.inc.php +++ b/adminer/drivers/mysql.inc.php @@ -749,7 +749,7 @@ if (!defined('Adminer\DRIVER')) { /** Run commands to alter indexes * @param string $table escaped table name - * @param list}> $alter of ["index type", "name", ["column definition", ...]] or ["index type", "name", "DROP"] + * @param list, 3?: string}> $alter of ["index type", "name", ["column definition", ...], "algorithm"] or ["index type", "name", "DROP"] * @return Result|bool */ function alter_indexes(string $table, $alter) { diff --git a/adminer/drivers/pgsql.inc.php b/adminer/drivers/pgsql.inc.php index c2b5c78c..a2c24611 100644 --- a/adminer/drivers/pgsql.inc.php +++ b/adminer/drivers/pgsql.inc.php @@ -355,6 +355,14 @@ if (isset($_GET["pgsql"])) { return "(SELECT oid FROM pg_class WHERE relnamespace = $this->nsOid AND relname = " . q($table) . " AND relkind IN ('r', 'm', 'v', 'f', 'p'))"; } + function indexMethods(): array { + static $methods = array(); + if (!$methods) { + $methods = get_vals("SELECT amname FROM pg_am" . (min_version(9.6) ? " WHERE amtype = 'i'" : "") . " ORDER BY amname = 'btree' DESC, amname"); + } + return $methods; + } + function supportsIndex(array $table_status): bool { // returns true for "materialized view" return $table_status["Engine"] != "view"; @@ -521,9 +529,10 @@ ORDER BY a.attnum") as $row $table_oid = driver()->tableOid($table); $columns = get_key_vals("SELECT attnum, attname FROM pg_attribute WHERE attrelid = $table_oid AND attnum > 0", $connection2); foreach ( - get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, (indpred IS NOT NULL)::int as indispartial + get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, (indpred IS NOT NULL)::int as indispartial, pg_am.amname as algorithm FROM pg_index JOIN pg_class ON indexrelid = oid +JOIN pg_am ON pg_am.oid = pg_class.relam WHERE indrelid = $table_oid ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row ) { @@ -531,6 +540,7 @@ ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row $return[$relname]["type"] = ($row["indispartial"] ? "INDEX" : ($row["indisprimary"] ? "PRIMARY" : ($row["indisunique"] ? "UNIQUE" : "INDEX"))); $return[$relname]["columns"] = array(); $return[$relname]["descs"] = array(); + $return[$relname]["algorithm"] = $row["algorithm"]; if ($row["indkey"]) { foreach (explode(" ", $row["indkey"]) as $indkey) { $return[$relname]["columns"][] = $columns[$indkey]; @@ -711,7 +721,7 @@ ORDER BY conkey, conname") as $row } elseif ($val[2] == "DROP") { $drop[] = idf_escape($val[1]); } else { - $queries[] = "CREATE INDEX " . idf_escape($val[1] != "" ? $val[1] : uniqid($table . "_")) . " ON " . table($table) . " (" . implode(", ", $val[2]) . ")"; + $queries[] = "CREATE INDEX " . idf_escape($val[1] != "" ? $val[1] : uniqid($table . "_")) . " ON " . table($table) . ($val[3] ? " USING $val[3]" : "") . " (" . implode(", ", $val[2]) . ")"; } } if ($create) { diff --git a/adminer/include/adminer.inc.php b/adminer/include/adminer.inc.php index a8f89080..1f44018a 100644 --- a/adminer/include/adminer.inc.php +++ b/adminer/include/adminer.inc.php @@ -360,7 +360,11 @@ class Adminer { . ($index["descs"][$key] ? " DESC" : "") ; } - echo "$index[type]" . implode(", ", $print) . "\n"; + + echo ""; + echo "$index[type]" . ($index['algorithm'] != first(driver()->indexMethods()) ? " ($index[algorithm])" : ""); + echo "" . implode(", ", $print); + echo "\n"; } echo "\n"; } diff --git a/adminer/include/driver.inc.php b/adminer/include/driver.inc.php index 5437d479..c7577a5b 100644 --- a/adminer/include/driver.inc.php +++ b/adminer/include/driver.inc.php @@ -258,6 +258,14 @@ abstract class SqlDriver { return !is_view($table_status); } + /** + * Return list of supported index methods, first one is default + * @return list + */ + function indexMethods(): array { + return array(); + } + /** Get defined check constraints * @return string[] [$name => $clause] */ diff --git a/adminer/indexes.inc.php b/adminer/indexes.inc.php index d1b15a9a..00fd7f7f 100644 --- a/adminer/indexes.inc.php +++ b/adminer/indexes.inc.php @@ -29,6 +29,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) { $columns = array(); $lengths = array(); $descs = array(); + $index_algorithm = (in_array($index["algorithm"], driver()->indexMethods()) ? $index["algorithm"] : ""); $set = array(); ksort($index["columns"]); foreach ($index["columns"] as $key => $column) { @@ -52,6 +53,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) { && array_values($existing["columns"]) === $columns && (!$existing["lengths"] || array_values($existing["lengths"]) === $lengths) && array_values($existing["descs"]) === $descs + && $existing["algorithm"] === $index_algorithm ) { // skip existing index unset($indexes[$name]); @@ -59,7 +61,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) { } } if ($columns) { - $alter[] = array($index["type"], $name, $set); + $alter[] = array($index["type"], $name, $set, $index_algorithm); } } } @@ -105,6 +107,11 @@ $show_options = ($_POST ? $_POST["options"] : get_setting("index_options"));
+indexMethods()) { + echo "" . lang('Algorithm'); +} +?> (" . lang('length') . ")" : ""); if ($lengths || support("descidx")) { @@ -128,6 +135,10 @@ foreach ($row["indexes"] as $index) { if (!$_POST["drop_col"] || $j != key($_POST["drop_col"])) { echo "
" . html_select("indexes[$j][type]", array(-1 => "") + $index_types, $index["type"], ($j == count($row["indexes"]) ? "indexesAddRow.call(this);" : ""), "label-type"); + if (driver()->indexMethods()) { + echo "" . html_select("indexes[$j][algorithm]", array_merge(array(""), driver()->indexMethods()), $index['algorithm'], "label-method"); + } + echo ""; ksort($index["columns"]); $i = 1; diff --git a/adminer/lang/cs.inc.php b/adminer/lang/cs.inc.php index 1a96c67d..b69997f5 100644 --- a/adminer/lang/cs.inc.php +++ b/adminer/lang/cs.inc.php @@ -355,6 +355,7 @@ Lang::$translations = array( '%s must return an array.' => '%s musí vracet pole.', 'Configure %s in %s.' => 'Nakonfigurujte %s v %s.', 'screenshot' => 'obrázek', + 'Algorithm' => 'Algoritmus', ); // run `php ../../lang.php cs` to update this file diff --git a/adminer/lang/xx.inc.php b/adminer/lang/xx.inc.php index 283be7e5..ccafdbe9 100644 --- a/adminer/lang/xx.inc.php +++ b/adminer/lang/xx.inc.php @@ -357,6 +357,7 @@ Lang::$translations = array( '%s must return an array.' => '%s xx xx.', 'Configure %s in %s.' => 'Xx %s xx %s.', 'screenshot' => 'xx', + 'Algorithm' => 'Xx', ); // run `php ../../lang.php xx` to update this file diff --git a/phpstan.neon b/phpstan.neon index 121201ae..a2d7f054 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -65,7 +65,7 @@ parameters: Field: "array{field?:string, full_type:string, type:string, length:numeric-string, unsigned:string, default?:string, null:bool, auto_increment:bool, collation:string, privileges:int[], comment:string, primary:bool, generated:string, orig?:string, on_update?:string, on_delete?:string, default_constraint?: string}" FieldType: "array{type:string, length:numeric-string, unsigned:string, collation:string}" # subset of RoutineField and Field RoutineField: "array{field:string, type:string, length:numeric-string, unsigned:string, null:bool, full_type:string, collation:string, inout?:string}" - Index: "array{type:string, columns:list, lengths:list, descs:list}" + Index: "array{type:string, columns:list, lengths:list, descs:list, algorithm?:string}" ForeignKey: "array{db?:string, ns?:string, table:string, source:list, target:list, on_delete:string, on_update?:string, definition?:string, deferrable?:string}" Trigger: "array{Trigger?:string, Timing?:string, Event?:string, Of?:string, Type?:string, Statement?:string}" Routine: "array{name?:string, fields:list, comment:string, returns?:FieldType, definition:string, language?:string}"