1
0
mirror of https://github.com/vrana/adminer.git synced 2025-08-04 21:58:28 +02:00

PostgreSQL: Partial Indexes

This commit is contained in:
salacr
2025-04-24 11:14:15 +02:00
committed by Jakub Vrana
parent 1466051402
commit c734deca84
9 changed files with 46 additions and 7 deletions

View 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 <body class>

View 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) {

View 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)
;
}

View 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";

View 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++;

View 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íč',

View File

@@ -213,6 +213,7 @@ Lang::$translations = array(
'Index Type' => 'Xx',
'length' => 'xx',
'Algorithm' => 'Xx',
'Condition' => 'Xx',
'Foreign keys' => 'Xx',
'Foreign key' => 'Xx',

View 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}"

View 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>
@@ -50,8 +53,16 @@
<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">