diff --git a/CHANGELOG.md b/CHANGELOG.md index c3cadfd5..8e4ffacd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - MySQL, PostgreSQL: Support index algorithms (bug #1030) - PostgreSQL, CockroachDB: Creating partitioned tables (bug #1031) - PostgreSQL: Move partitioned tables from table list to parent table +- PostgreSQL: Support partial indices (bug #1048) - PostgreSQL: Support calling functions returning table (bug #1040) - Designs: adminer.css with 'prefers-color-scheme: dark' doesn'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 bdc42317..cd82724f 100644 --- a/adminer/drivers/mysql.inc.php +++ b/adminer/drivers/mysql.inc.php @@ -754,7 +754,7 @@ if (!defined('Adminer\DRIVER')) { /** Run commands to alter indexes * @param string $table escaped table name - * @param list, 3?: string}> $alter of ["index type", "name", ["column definition", ...], "algorithm"] or ["index type", "name", "DROP"] + * @param list, 3?: string, 4?: string}> $alter of ["index type", "name", ["column definition", ...], "algorithm", "condition"] 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 0eb3c5c9..da6dfe2a 100644 --- a/adminer/drivers/pgsql.inc.php +++ b/adminer/drivers/pgsql.inc.php @@ -529,7 +529,7 @@ 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, pg_am.amname as algorithm + get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, (indpred IS NOT NULL)::int as indispartial, pg_am.amname as algorithm, pg_get_expr(pg_index.indpred, pg_index.indrelid, true) AS partial FROM pg_index JOIN pg_class ON indexrelid = oid JOIN pg_am ON pg_am.oid = pg_class.relam @@ -541,6 +541,7 @@ ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row $return[$relname]["columns"] = array(); $return[$relname]["descs"] = array(); $return[$relname]["algorithm"] = $row["algorithm"]; + $return[$relname]["partial"] = $row["partial"]; if ($row["indkey"]) { foreach (explode(" ", $row["indkey"]) as $indkey) { $return[$relname]["columns"][] = $columns[$indkey]; @@ -721,7 +722,12 @@ 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) . ($val[3] ? " USING $val[3]" : "") . " (" . 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]) . ")" + . ($val[4] ? " WHERE $val[4]" : "") + ; } } if ($create) { @@ -1028,9 +1034,11 @@ AND typelem = 0" } function support($feature) { - return preg_match('~^(check|database|table|columns|sql|indexes|descidx|comment|view|' . (min_version(9.3) ? 'materializedview|' : '') . 'scheme|' . (min_version(11) ? 'procedure|' : '') . 'routine|sequence|trigger|type|variables|drop_col' + return preg_match('~^(check|columns|comment|database|drop_col|dump|descidx|indexes|kill|partial_indexes|routine|scheme|sequence|sql|table|trigger|type|variables|view' + . (min_version(9.3) ? '|materializedview' : '') + . (min_version(11) ? '|procedure' : '') . (connection()->flavor == 'cockroach' ? '' : '|processlist') // https://github.com/cockroachdb/cockroach/issues/24745 - . '|kill|dump)$~', $feature) + . ')$~', $feature) ; } diff --git a/adminer/include/adminer.inc.php b/adminer/include/adminer.inc.php index 9bd7277c..3d5a86e6 100644 --- a/adminer/include/adminer.inc.php +++ b/adminer/include/adminer.inc.php @@ -355,6 +355,10 @@ class Adminer { * @param TableStatus $tableStatus */ function tableIndexesPrint(array $indexes, array $tableStatus): void { + $partial = false; + foreach ($indexes as $name => $index) { + $partial |= !!$index["partial"]; + } echo "\n"; $default_algorithm = first(driver()->indexAlgorithms($tableStatus)); foreach ($indexes as $name => $index) { @@ -370,6 +374,9 @@ class Adminer { echo ""; echo "
$index[type]" . ($default_algorithm && $index['algorithm'] != $default_algorithm ? " ($index[algorithm])" : ""); echo "" . implode(", ", $print); + if ($partial) { + echo "" . ($index['partial'] ? "WHERE " . h($index['partial']) : ""); + } echo "\n"; } echo "
\n"; diff --git a/adminer/indexes.inc.php b/adminer/indexes.inc.php index 1676e603..746c0433 100644 --- a/adminer/indexes.inc.php +++ b/adminer/indexes.inc.php @@ -30,6 +30,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) { $columns = array(); $lengths = array(); $descs = array(); + $index_condition = (support("partial_indexes") ? $index["partial"] : ""); $index_algorithm = (in_array($index["algorithm"], $index_algorithms) ? $index["algorithm"] : ""); $set = array(); ksort($index["columns"]); @@ -54,6 +55,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["partial"] == $index_condition && (!$index_algorithms || $existing["algorithm"] == $index_algorithm) ) { // skip existing index @@ -62,7 +64,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) { } } if ($columns) { - $alter[] = array($index["type"], $name, $set, $index_algorithm); + $alter[] = array($index["type"], $name, $set, $index_algorithm, $index_condition); } } } @@ -121,6 +123,11 @@ if ($lengths || support("descidx")) { } ?> +" . lang('Condition'); +} +?> \n"; + if (support("partial_indexes")) { + echo "\n"; + } echo "" . icon("cross", "drop_col[$j]", "x", lang('Remove')) . script("qsl('button').onclick = partial(editingRemoveRow, 'indexes\$1[type]');"); } $j++; diff --git a/adminer/lang/cs.inc.php b/adminer/lang/cs.inc.php index 8252c2d2..0d98870e 100644 --- a/adminer/lang/cs.inc.php +++ b/adminer/lang/cs.inc.php @@ -211,6 +211,7 @@ Lang::$translations = array( 'Index Type' => 'Typ indexu', 'length' => 'délka', 'Algorithm' => 'Algoritmus', + 'Condition' => 'Podmínka', 'Foreign keys' => 'Cizí klíče', 'Foreign key' => 'Cizí klíč', diff --git a/adminer/lang/xx.inc.php b/adminer/lang/xx.inc.php index d647696d..d5b9ce76 100644 --- a/adminer/lang/xx.inc.php +++ b/adminer/lang/xx.inc.php @@ -213,6 +213,7 @@ Lang::$translations = array( 'Index Type' => 'Xx', 'length' => 'xx', 'Algorithm' => 'Xx', + 'Condition' => 'Xx', 'Foreign keys' => 'Xx', 'Foreign key' => 'Xx', diff --git a/phpstan.neon b/phpstan.neon index a2d7f054..7fac67fa 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, algorithm?:string}" + Index: "array{type:string, columns:list, lengths:list, descs:list, algorithm?:string, partial?: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}" diff --git a/tests/pgsql.html b/tests/pgsql.html index 36b28d1f..edcd5451 100644 --- a/tests/pgsql.html +++ b/tests/pgsql.html @@ -31,6 +31,9 @@ typefields[1.1][field]name selectfields[1.1][type]label=character varying typefields[1.1][length]50 +typefields[1.11][field]surname +selectfields[1.11][type]label=character varying +typefields[1.11][length]50 uncheckname=comments clickAndWaitname=comments typefields[1.1][comment]Interpret @@ -50,8 +53,16 @@ clickAndWait//input[@value='Save'] verifyTextPresentmultiple primary keys for table "interprets" are not allowed selectindexes[2][type]label=INDEX +click//input[@name='options'] +selectindexes[3][type]label=INDEX +selectindexes[3][columns][1]label=surname +selectindexes[3][algorithm]label=hash +typeindexes[3][partial]surname IS NOT NULL clickAndWait//input[@value='Save'] verifyTextPresentIndexes have been altered. +verifyTextPresent//tr[@title='interprets_surname']INDEX (hash) +verifyTextPresent//tr[@title='interprets_surname']surname +verifyTextPresent//tr[@title='interprets_surname']WHERE surname IS NOT NULL