]> git.joonet.de Git - adminer.git/commitdiff
PostgreSQL: Partial Indexes
authorsalacr <radek@salac.org>
Thu, 24 Apr 2025 09:14:15 +0000 (11:14 +0200)
committerJakub Vrana <jakub@vrana.cz>
Thu, 24 Apr 2025 09:39:59 +0000 (11:39 +0200)
CHANGELOG.md
adminer/drivers/mysql.inc.php
adminer/drivers/pgsql.inc.php
adminer/include/adminer.inc.php
adminer/indexes.inc.php
adminer/lang/cs.inc.php
adminer/lang/xx.inc.php
phpstan.neon
tests/pgsql.html

index c3cadfd53f5cc9187494d56da8999f6ca02ded96..8e4ffacdfa73f2064162cda468fb4bde19c75c35 100644 (file)
@@ -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 &lt;body class>
index bdc423170c6ddbe86d4252e03568349a03e9b764..cd82724fd8c5769506bf4286820a51cb5456eb7b 100644 (file)
@@ -754,7 +754,7 @@ if (!defined('Adminer\DRIVER')) {
 
        /** Run commands to alter indexes
        * @param string $table escaped table name
-       * @param list<array{string, string, 'DROP'|list<string>, 3?: string}> $alter of ["index type", "name", ["column definition", ...], "algorithm"] or ["index type", "name", "DROP"]
+       * @param list<array{string, string, 'DROP'|list<string>, 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) {
index 0eb3c5c9010a07e0f2a40d0bc5de260bf4b2dd7d..da6dfe2a587459edb0480aec3513af2b51761eaf 100644 (file)
@@ -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)
                ;
        }
 
index 9bd7277c8a794c294bd10a1341c8acdd736f902f..3d5a86e63c54fda03a29609166637aaafeaf73fc 100644 (file)
@@ -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 "<table>\n";
                $default_algorithm = first(driver()->indexAlgorithms($tableStatus));
                foreach ($indexes as $name => $index) {
@@ -370,6 +374,9 @@ class Adminer {
                        echo "<tr title='" . h($name) . "'>";
                        echo "<th>$index[type]" . ($default_algorithm && $index['algorithm'] != $default_algorithm ? " ($index[algorithm])" : "");
                        echo "<td>" . implode(", ", $print);
+                       if ($partial) {
+                               echo "<td>" . ($index['partial'] ? "<code class='jush-" . JUSH . "'>WHERE " . h($index['partial']) : "");
+                       }
                        echo "\n";
                }
                echo "</table>\n";
index 1676e6032d1ecc625fc496b3f6e74dd0d0049310..746c0433e9a13e4cd7ce56a09b932863a5ffc78e 100644 (file)
@@ -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")) {
 }
 ?>
 <th id="label-name"><?php echo lang('Name'); ?>
+<?php
+if (support("partial_indexes")) {
+       echo "<th id='label-condition' class='idxopts" .  ($show_options ? "" : " hidden") . "'>" . lang('Condition');
+}
+?>
 <th><noscript><?php echo icon("plus", "add[0]", "+", lang('Add next')); ?></noscript>
 </thead>
 <?php
@@ -159,6 +166,9 @@ foreach ($row["indexes"] as $index) {
                }
 
                echo "<td><input name='indexes[$j][name]' value='" . h($index["name"]) . "' autocapitalize='off' aria-labelledby='label-name'>\n";
+               if (support("partial_indexes")) {
+                       echo "<td class='idxopts" .  ($show_options ? "" : " hidden") . "'><input name='indexes[$j][partial]' value='" . h($index["partial"]) . "' autocapitalize='off' aria-labelledby='label-condition'>\n";
+               }
                echo "<td>" . icon("cross", "drop_col[$j]", "x", lang('Remove')) . script("qsl('button').onclick = partial(editingRemoveRow, 'indexes\$1[type]');");
        }
        $j++;
index 8252c2d22150717ca2a78cc68c0aaca824ab7d3f..0d98870e6a750f606e5105e7cb8bc36791f83016 100644 (file)
@@ -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íč',
index d647696dc47634de8e784cbfafa1b7b2902d046b..d5b9ce7601a57cfd04a37df3239e9841d4ed0c5a 100644 (file)
@@ -213,6 +213,7 @@ Lang::$translations = array(
        'Index Type' => 'Xx',
        'length' => 'xx',
        'Algorithm' => 'Xx',
+       'Condition' => 'Xx',
 
        'Foreign keys' => 'Xx',
        'Foreign key' => 'Xx',
index a2d7f054225a39cd3e79152c3b4397f779c87751..7fac67faf15bea73785fad64556936c672ab87d2 100644 (file)
@@ -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<string>, lengths:list<numeric-string>, descs:list<?bool>, algorithm?:string}"
+               Index: "array{type:string, columns:list<string>, lengths:list<numeric-string>, descs:list<?bool>, algorithm?:string, partial?:string}"
                ForeignKey: "array{db?:string, ns?:string, table:string, source:list<string>, target:list<?string>, 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<RoutineField>, comment:string, returns?:FieldType, definition:string, language?:string}"
index 36b28d1fc219f42cb1c9b401c060f3dd64df3db6..edcd5451b42e1eb84f25a58fc566447737593fe1 100644 (file)
@@ -31,6 +31,9 @@
 <tr><td>type</td><td>fields[1.1][field]</td><td>name</td></tr>
 <tr><td>select</td><td>fields[1.1][type]</td><td>label=character varying</td></tr>
 <tr><td>type</td><td>fields[1.1][length]</td><td>50</td></tr>
+<tr><td>type</td><td>fields[1.11][field]</td><td>surname</td></tr>
+<tr><td>select</td><td>fields[1.11][type]</td><td>label=character varying</td></tr>
+<tr><td>type</td><td>fields[1.11][length]</td><td>50</td></tr>
 <tr><td>uncheck</td><td>name=comments</td><td></td></tr>
 <tr><td>clickAndWait</td><td>name=comments</td><td></td></tr>
 <tr><td>type</td><td>fields[1.1][comment]</td><td>Interpret</td></tr>
 <tr><td>clickAndWait</td><td>//input[@value='Save']</td><td></td></tr>
 <tr><td>verifyTextPresent</td><td>multiple primary keys for table "interprets" are not allowed</td><td></td></tr>
 <tr><td>select</td><td>indexes[2][type]</td><td>label=INDEX</td></tr>
+<tr><td>click</td><td>//input[@name='options']</td><td></td></tr>
+<tr><td>select</td><td>indexes[3][type]</td><td>label=INDEX</td></tr>
+<tr><td>select</td><td>indexes[3][columns][1]</td><td>label=surname</td></tr>
+<tr><td>select</td><td>indexes[3][algorithm]</td><td>label=hash</td></tr>
+<tr><td>type</td><td>indexes[3][partial]</td><td>surname IS NOT NULL</td></tr>
 <tr><td>clickAndWait</td><td>//input[@value='Save']</td><td></td></tr>
 <tr><td>verifyTextPresent</td><td>Indexes have been altered.</td><td></td></tr>
+<tr><td>verifyTextPresent</td><td>//tr[@title='interprets_surname']</td><td>INDEX (hash)</td></tr>
+<tr><td>verifyTextPresent</td><td>//tr[@title='interprets_surname']</td><td>surname</td></tr>
+<tr><td>verifyTextPresent</td><td>//tr[@title='interprets_surname']</td><td>WHERE surname IS NOT NULL</td></tr>
 </tbody></table>
 
 <table cellpadding="1" cellspacing="1" border="1">