From c045b20a8ed4f8d2e61c44d2f2707990dfd511ce Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Fri, 7 Mar 2025 05:01:00 +0100 Subject: [PATCH] Allow creating generated columns (bug #857) --- adminer/create.inc.php | 4 ++-- adminer/drivers/mysql.inc.php | 26 ++++++++++++++++++-------- adminer/drivers/pgsql.inc.php | 9 ++++++--- adminer/include/driver.inc.php | 1 + adminer/include/editing.inc.php | 21 ++++++++++++++++----- adminer/static/editing.js | 9 ++++++--- changes.txt | 1 + 7 files changed, 50 insertions(+), 21 deletions(-) diff --git a/adminer/create.inc.php b/adminer/create.inc.php index 0015748b..4e4e933b 100644 --- a/adminer/create.inc.php +++ b/adminer/create.inc.php @@ -48,7 +48,7 @@ if ($_POST && !process_fields($row["fields"]) && !$error) { $foreign_key = $foreign_keys[$field["type"]]; $type_field = ($foreign_key !== null ? $referencable_primary[$foreign_key] : $field); //! can collide with user defined type if ($field["field"] != "") { - if (!$field["has_default"]) { + if (!$field["generated"]) { $field["default"] = null; } $process_field = process_field($field, $type_field); @@ -155,7 +155,7 @@ if (!$_POST) { $row["Auto_increment"] = ""; } foreach ($orig_fields as $field) { - $field["has_default"] = isset($field["default"]); + $field["generated"] = $field["generated"] ?: (isset($field["default"]) ? "DEFAULT" : ""); $row["fields"][] = $field; } diff --git a/adminer/drivers/mysql.inc.php b/adminer/drivers/mysql.inc.php index 051b2d50..94b4e4f1 100644 --- a/adminer/drivers/mysql.inc.php +++ b/adminer/drivers/mysql.inc.php @@ -322,6 +322,9 @@ if (!defined('Adminer\DRIVER')) { $this->types[lang('Numbers')]["vector"] = 16383; $this->editFunctions[0]['vector'] = 'string_to_vector'; } + if (min_version(5.7, 10.2, $connection)) { + $this->generated = array("STORED", "VIRTUAL"); + } } function insert($table, $set) { @@ -592,8 +595,9 @@ if (!defined('Adminer\DRIVER')) { $field = $row["COLUMN_NAME"]; $default = $row["COLUMN_DEFAULT"]; $type = $row["COLUMN_TYPE"]; + $extra = $row["EXTRA"]; // https://mariadb.com/kb/en/library/show-columns/, https://github.com/vrana/adminer/pull/359#pullrequestreview-276677186 - $generated = preg_match('~^(VIRTUAL|PERSISTENT|STORED)~', $row["EXTRA"]); + preg_match('~^(VIRTUAL|PERSISTENT|STORED)~', $extra, $generated); preg_match('~^([^( ]+)(?:\((.+)\))?( unsigned)?( zerofill)?$~', $type, $match); $return[$field] = array( "field" => $field, @@ -609,13 +613,13 @@ if (!defined('Adminer\DRIVER')) { ) ), "null" => ($row["IS_NULLABLE"] == "YES"), - "auto_increment" => ($row["EXTRA"] == "auto_increment"), - "on_update" => (preg_match('~\bon update (\w+)~i', $row["EXTRA"], $match) ? $match[1] : ""), //! available since MySQL 5.1.23 + "auto_increment" => ($extra == "auto_increment"), + "on_update" => (preg_match('~\bon update (\w+)~i', $extra, $match) ? $match[1] : ""), //! available since MySQL 5.1.23 "collation" => $row["COLLATION_NAME"], "privileges" => array_flip(explode(",", $row["PRIVILEGES"])), "comment" => $row["COLUMN_COMMENT"], "primary" => ($row["COLUMN_KEY"] == "PRI"), - "generated" => $generated, + "generated" => ($generated[1] == "PERSISTENT" ? "STORED" : $generated[1]), ); } return $return; @@ -788,10 +792,16 @@ if (!defined('Adminer\DRIVER')) { function alter_table($table, $name, $fields, $foreign, $comment, $engine, $collation, $auto_increment, $partitioning) { $alter = array(); foreach ($fields as $field) { - $alter[] = ($field[1] - ? ($table != "" ? ($field[0] != "" ? "CHANGE " . idf_escape($field[0]) : "ADD") : " ") . " " . implode($field[1]) . ($table != "" ? $field[2] : "") - : "DROP " . idf_escape($field[0]) - ); + if ($field[1]) { + $default = $field[1][3]; + if (preg_match('~ GENERATED~', $default)) { + $field[1][3] = $field[1][2]; + $field[1][2] = $default; + } + $alter[] = ($table != "" ? ($field[0] != "" ? "CHANGE " . idf_escape($field[0]) : "ADD") : " ") . " " . implode($field[1]) . ($table != "" ? $field[2] : ""); + } else { + $alter[] = "DROP " . idf_escape($field[0]); + } } $alter = array_merge($alter, $foreign); $status = ($comment !== null ? " COMMENT=" . q($comment) : "") diff --git a/adminer/drivers/pgsql.inc.php b/adminer/drivers/pgsql.inc.php index e25dbd58..cf9f63c6 100644 --- a/adminer/drivers/pgsql.inc.php +++ b/adminer/drivers/pgsql.inc.php @@ -229,6 +229,9 @@ if (isset($_GET["pgsql"])) { "char|text" => "||", ) ); + if (min_version(12, 0, $connection)) { + $this->generated = array("STORED"); + } } function setUserTypes($types) { @@ -441,7 +444,7 @@ ORDER BY a.attnum") as $row if (in_array($row['attidentity'], array('a', 'd'))) { $row['default'] = 'GENERATED ' . ($row['attidentity'] == 'd' ? 'BY DEFAULT' : 'ALWAYS') . ' AS IDENTITY'; } - $row["generated"] = ($row["attgenerated"] == "s"); + $row["generated"] = ($row["attgenerated"] == "s" ? "STORED" : ""); $row["null"] = !$row["attnotnull"]; $row["auto_increment"] = $row['attidentity'] || preg_match('~^nextval\(~i', $row["default"]); $row["privileges"] = array("insert" => 1, "select" => 1, "update" => 1); @@ -576,9 +579,9 @@ ORDER BY conkey, conname") as $row } $alter[] = "ALTER $column TYPE$val[1]"; $sequence_name = $table . "_" . idf_unescape($val[0]) . "_seq"; - $alter[] = "ALTER $column " . ($val[3] ? "SET$val[3]" + $alter[] = "ALTER $column " . ($val[3] ? "SET" . preg_replace('~GENERATED ALWAYS(.*) STORED~', 'EXPRESSION\1', $val[3]) : (isset($val[6]) ? "SET DEFAULT nextval(" . q($sequence_name) . ")" - : "DROP DEFAULT" + : "DROP DEFAULT" //! change to DROP EXPRESSION with generated columns )); if (isset($val[6])) { $sequence = "CREATE SEQUENCE IF NOT EXISTS " . idf_escape($sequence_name) . " OWNED BY " . idf_escape($table) . ".$val[0]"; diff --git a/adminer/include/driver.inc.php b/adminer/include/driver.inc.php index f066ebcb..2687a9d2 100644 --- a/adminer/include/driver.inc.php +++ b/adminer/include/driver.inc.php @@ -36,6 +36,7 @@ abstract class SqlDriver { var $onActions = "RESTRICT|NO ACTION|CASCADE|SET NULL|SET DEFAULT"; ///< @var string used in foreign_keys() var $inout = "IN|OUT|INOUT"; var $enumLength = "'(?:''|[^'\\\\]|\\\\.)*'"; + var $generated = array(); /** Create object for performing database operations * @param Db diff --git a/adminer/include/editing.inc.php b/adminer/include/editing.inc.php index 3015496e..40fac53d 100644 --- a/adminer/include/editing.inc.php +++ b/adminer/include/editing.inc.php @@ -302,11 +302,17 @@ function process_field($field, $type_field) { * @return string */ function default_value($field) { + global $driver; $default = $field["default"]; - return ($default === null ? "" : " DEFAULT " . - (!preg_match('~^GENERATED ~i', $default) && (preg_match('~char|binary|text|enum|set~', $field["type"]) || preg_match('~^(?![a-z])~i', $default)) - ? q($default) : str_ireplace("current_timestamp()", "CURRENT_TIMESTAMP", (JUSH == "sqlite" ? "($default)" : $default))) - ); + $generated = $field["generated"]; + return ( + $default === null ? "" + : (in_array($generated, $driver->generated) ? " GENERATED ALWAYS AS ($default) $generated" + : " DEFAULT " . (!preg_match('~^GENERATED ~i', $default) && (preg_match('~char|binary|text|enum|set~', $field["type"]) || preg_match('~^(?![a-z])~i', $default)) + ? q($default) + : str_ireplace("current_timestamp()", "CURRENT_TIMESTAMP", (JUSH == "sqlite" ? "($default)" : $default)) + ) + )); } /** Get type class to use in CSS @@ -380,7 +386,12 @@ function edit_fields($fields, $collations, $type = "TABLE", $foreign_keys = arra ?> >" aria-labelledby="label-default">generated + ? " " + : checkbox("fields[$i][generated]", 1, $field["generated"], "", "", "", "label-default") + ); + ?> +" aria-labelledby="label-default">" : ""); } echo ""; diff --git a/adminer/static/editing.js b/adminer/static/editing.js index 692c7bc2..5c461966 100644 --- a/adminer/static/editing.js +++ b/adminer/static/editing.js @@ -290,7 +290,8 @@ function editingClick(event) { function editingInput(event) { var el = getTarget(event); if (/\[default]$/.test(el.name)) { - el.previousSibling.checked = true; + el.previousElementSibling.checked = true; + el.previousElementSibling.selectedIndex = Math.max(el.previousElementSibling.selectedIndex, 1); } } @@ -359,8 +360,9 @@ function editingAddRow(focus) { if (/\[(orig|field|comment|default)/.test(tags[i].name)) { tags2[i].value = ''; } - if (/\[(has_default)/.test(tags[i].name)) { + if (/\[(generated)/.test(tags[i].name)) { tags2[i].checked = false; + tags2[i].selectedIndex = 0; } } tags[0].oninput = editingNameChange; @@ -422,8 +424,9 @@ function editingTypeChange() { } el.oninput.apply(el); } - if (lastType == 'timestamp' && el.name == name + '[has_default]' && /timestamp/i.test(formField(type.form, name + '[default]').value)) { + if (lastType == 'timestamp' && el.name == name + '[generated]' && /timestamp/i.test(formField(type.form, name + '[default]').value)) { el.checked = false; + el.selectedIndex = 0; } if (el.name == name + '[collation]') { alterClass(el, 'hidden', !/(char|text|enum|set)$/.test(text)); diff --git a/changes.txt b/changes.txt index 75c24d0f..68971e68 100644 --- a/changes.txt +++ b/changes.txt @@ -1,5 +1,6 @@ Adminer dev: Speed up with disabled output buffering +Allow creating generated columns (bug #857) Don't autofocus computed fields in insert form Skip generated columns in multi-edit (bug #882) MySQL: Display generated value in table structure