diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab6b25b19..6cc293725e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Automatic generation on delivery no longer creates a consumable model per received consumable - CI: fix Psalm cache directory, drop the stray `glpi-project/tools` composer dependency, declare a unique composer autoloader suffix +- Plugin upgrade no longer rebuilds tables whose columns already have the expected type, nor replays legacy data migrations, which made it time out on large databases (#616) ## [2.12.11] - 2026-09-18 diff --git a/inc/bill.class.php b/inc/bill.class.php index 773f982e62..ad9fbc5d84 100644 --- a/inc/bill.class.php +++ b/inc/bill.class.php @@ -609,7 +609,10 @@ public static function install(Migration $migration) } } - $migration->changeField($table, "value", "value", "decimal(20,6) NOT NULL DEFAULT '0.000000'"); + if (!PluginOrderConfig::fieldHasType($table, "value", 'decimal(20,6)')) { + $migration->changeField($table, "value", "value", "decimal(20,6) NOT NULL DEFAULT '0.000000'"); + } + $migration->migrationOneTable($table); } diff --git a/inc/config.class.php b/inc/config.class.php index 8e170183e9..668613d299 100644 --- a/inc/config.class.php +++ b/inc/config.class.php @@ -28,7 +28,7 @@ * ------------------------------------------------------------------------- */ - +use function Safe\preg_replace; class PluginOrderConfig extends CommonDBTM { @@ -669,6 +669,33 @@ public function canRenameDocuments() //----------------- Install & uninstall -------------------// + + /** + * Tell whether a column already has the expected SQL type, so that an upgrade does + * not replay a type change: every CHANGE rebuilds the whole table. + * + * @param string $table Table name + * @param string $field Column name + * @param string $type Expected type, as reported by SHOW COLUMNS (ie. "decimal(20,6)", "int unsigned") + * + * @return bool true when the column is missing or already has this type + */ + public static function fieldHasType(string $table, string $field, string $type): bool + { + /** @var DBmysql $DB */ + global $DB; + + $column = $DB->getField($table, $field, false); + if ($column === null) { + return true; + } + + // MySQL 5.7 and MariaDB report an integer display width ("int(10) unsigned"), MySQL 8 does not + $current = preg_replace('/^(tinyint|smallint|mediumint|int|bigint)\(\d+\)/', '$1', strtolower((string) $column['Type'])); + + return $current === strtolower($type); + } + public static function install(Migration $migration) { /** @var DBmysql $DB */ diff --git a/inc/deliverystate.class.php b/inc/deliverystate.class.php index 86203effa6..ed62703047 100644 --- a/inc/deliverystate.class.php +++ b/inc/deliverystate.class.php @@ -69,7 +69,10 @@ public static function install(Migration $migration) //Upgrade 1.2.0 $migration->renameTable("glpi_dropdown_plugin_order_deliverystate", $table); $migration->changeField($table, "ID", "id", sprintf('int %s NOT NULL auto_increment', $default_key_sign)); - $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + if (!PluginOrderConfig::fieldHasType($table, "name", 'varchar(255)')) { + $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + } + $migration->changeField($table, "comments", "comment", "text"); $migration->migrationOneTable($table); } diff --git a/inc/order.class.php b/inc/order.class.php index b655b1d0be..e2d35b54fa 100644 --- a/inc/order.class.php +++ b/inc/order.class.php @@ -2735,12 +2735,10 @@ public static function install(Migration $migration) "is_recursive", "tinyint NOT NULL default 0", ); - $migration->changeField( - $table, - "name", - "name", - "varchar(255) default NULL", - ); + if (!PluginOrderConfig::fieldHasType($table, "name", 'varchar(255)')) { + $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + } + $migration->changeField( $table, "budget", @@ -2795,12 +2793,10 @@ public static function install(Migration $migration) "states_id", sprintf("int %s NOT NULL default '0'", $default_key_sign), ); - $migration->changeField( - $table, - "comment", - "comment", - "text", - ); + if (!PluginOrderConfig::fieldHasType($table, "comment", 'text')) { + $migration->changeField($table, "comment", "comment", "text"); + } + $migration->changeField( $table, "notes", @@ -2863,12 +2859,10 @@ public static function install(Migration $migration) "end_date", "date default NULL", ); - $migration->changeField( - "glpi_plugin_order_budgets", - "value", - "value", - "float NOT NULL DEFAULT '0'", - ); + if (!PluginOrderConfig::fieldHasType("glpi_plugin_order_budgets", "value", 'float')) { + $migration->changeField("glpi_plugin_order_budgets", "value", "value", "float NOT NULL DEFAULT '0'"); + } + $migration->addKey("glpi_plugin_order_budgets", "entities_id"); $migration->addKey("glpi_plugin_order_budgets", "is_deleted"); $migration->migrationOneTable("glpi_plugin_order_budgets"); @@ -3023,7 +3017,9 @@ public static function install(Migration $migration) $migration->addField($table, "global_discount", "FLOAT NOT NULL default '0'"); //2.7.3 - $migration->changeField($table, "plugin_order_billstates_id", "plugin_order_billstates_id", sprintf('int %s NOT NULL DEFAULT 0', $default_key_sign)); + if (!PluginOrderConfig::fieldHasType($table, "plugin_order_billstates_id", trim('int ' . $default_key_sign))) { + $migration->changeField($table, "plugin_order_billstates_id", "plugin_order_billstates_id", sprintf('int %s NOT NULL DEFAULT 0', $default_key_sign)); + } // Add ecotax fields if they don't exist if (!$DB->fieldExists($table, 'ecotax_price')) { diff --git a/inc/order_item.class.php b/inc/order_item.class.php index 7d5bd9bc3e..a1b82a7be4 100644 --- a/inc/order_item.class.php +++ b/inc/order_item.class.php @@ -2107,26 +2107,29 @@ public static function install(Migration $migration) $migration->migrationOneTable($table); //1.4.0 - $migration->addField( + $vat_added = $migration->addField( $table, "plugin_order_ordertaxes_id", sprintf("INT %s NOT NULL default '0' COMMENT 'RELATION to glpi_plugin_order_ordertaxes (id)'", $default_key_sign), ); $migration->migrationOneTable($table); - /* Migrate VAT */ - foreach ($DB->request(['FROM' => 'glpi_plugin_order_orders']) as $data) { - $migration->addPostQuery( - $DB->buildUpdate( - 'glpi_plugin_order_orders_items', - ['plugin_order_ordertaxes_id' => $data['plugin_order_ordertaxes_id']], - ['plugin_order_orders_id' => $data['id']], - ), - ); + /* Migrate VAT, only when the column has just been created: one query per order + on every upgrade makes the migration time out on large databases */ + if ($vat_added) { + foreach ($DB->request(['FROM' => 'glpi_plugin_order_orders']) as $data) { + $migration->addPostQuery( + $DB->buildUpdate( + 'glpi_plugin_order_orders_items', + ['plugin_order_ordertaxes_id' => $data['plugin_order_ordertaxes_id']], + ['plugin_order_orders_id' => $data['id']], + ), + ); + } } //1.5.0 - $migration->addField($table, "entities_id", sprintf("INT %s NOT NULL DEFAULT '0'", $default_key_sign)); + $entities_added = $migration->addField($table, "entities_id", sprintf("INT %s NOT NULL DEFAULT '0'", $default_key_sign)); $migration->addField($table, "is_recursive", "TINYINT NOT NULL DEFAULT '0'"); $migration->addField($table, "plugin_order_bills_id", sprintf("INT %s NOT NULL DEFAULT '0'", $default_key_sign)); $migration->addField($table, "plugin_order_billstates_id", sprintf("INT %s NOT NULL DEFAULT '0'", $default_key_sign)); @@ -2137,62 +2140,51 @@ public static function install(Migration $migration) $migration->migrationOneTable($table); //Change format for prices : from float to decimal - $migration->changeField( - $table, - "price_taxfree", - "price_taxfree", - "decimal(20,6) NOT NULL DEFAULT '0.000000'", - ); - $migration->changeField( - $table, - "price_discounted", - "price_discounted", - "decimal(20,6) NOT NULL DEFAULT '0.000000'", - ); - $migration->changeField( - $table, - "price_ati", - "price_ati", - "decimal(20,6) NOT NULL DEFAULT '0.000000'", - ); - $migration->changeField( - $table, - "discount", - "discount", - "decimal(20,6) NOT NULL DEFAULT '0.000000'", - ); + foreach (['price_taxfree', 'price_discounted', 'price_ati', 'discount'] as $field) { + if (!PluginOrderConfig::fieldHasType($table, $field, 'decimal(20,6)')) { + $migration->changeField( + $table, + $field, + $field, + "decimal(20,6) NOT NULL DEFAULT '0.000000'", + ); + } + } //Drop unused fields from previous migration $migration->dropField($table, "price_taxfree2"); $migration->dropField($table, "price_discounted2"); $migration->migrationOneTable($table); - //Forward entities_id and is_recursive into table glpi_plugin_order_orders_items - $query = [ - 'SELECT' => [ - 'go.entities_id as entities_id', - 'go.is_recursive as is_recursive', - 'goi.id as items_id', - ], - 'FROM' => [ - 'glpi_plugin_order_orders as go', - $table . ' as goi', - ], - 'WHERE' => [ - 'goi.plugin_order_orders_id' => new QueryExpression(DBmysql::quoteName('go.id')), - ], - ]; - foreach ($DB->request($query) as $data) { - $migration->addPostQuery( - $DB->buildUpdate( - $table, - [ - 'entities_id' => $data['entities_id'], - 'is_recursive' => $data['is_recursive'], - ], - ['id' => $data['items_id']], - ), - ); + //Forward entities_id and is_recursive into table glpi_plugin_order_orders_items, + //only when the column has just been created (one query per order line otherwise) + if ($entities_added) { + $query = [ + 'SELECT' => [ + 'go.entities_id as entities_id', + 'go.is_recursive as is_recursive', + 'goi.id as items_id', + ], + 'FROM' => [ + 'glpi_plugin_order_orders as go', + $table . ' as goi', + ], + 'WHERE' => [ + 'goi.plugin_order_orders_id' => new QueryExpression(DBmysql::quoteName('go.id')), + ], + ]; + foreach ($DB->request($query) as $data) { + $migration->addPostQuery( + $DB->buildUpdate( + $table, + [ + 'entities_id' => $data['entities_id'], + 'is_recursive' => $data['is_recursive'], + ], + ['id' => $data['items_id']], + ), + ); + } } if (!$DB->fieldExists($table, 'plugin_order_analyticnatures_id')) { diff --git a/inc/orderpayment.class.php b/inc/orderpayment.class.php index de87bbdaa9..dfc23f542f 100644 --- a/inc/orderpayment.class.php +++ b/inc/orderpayment.class.php @@ -68,7 +68,10 @@ public static function install(Migration $migration) //1.2.0 $migration->renameTable("glpi_dropdown_plugin_order_payment", $table); $migration->changeField($table, "ID", "id", sprintf('int %s NOT NULL auto_increment', $default_key_sign)); - $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + if (!PluginOrderConfig::fieldHasType($table, "name", 'varchar(255)')) { + $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + } + $migration->changeField($table, "comments", "comment", "text"); $migration->migrationOneTable($table); } diff --git a/inc/ordertax.class.php b/inc/ordertax.class.php index ca8a167e91..90f24dc3c4 100644 --- a/inc/ordertax.class.php +++ b/inc/ordertax.class.php @@ -78,7 +78,10 @@ public static function install(Migration $migration) //1.2.0 $migration->renameTable("glpi_dropdown_plugin_order_taxes", $table); $migration->changeField($table, "ID", "id", sprintf('int %s NOT NULL auto_increment', $default_key_sign)); - $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + if (!PluginOrderConfig::fieldHasType($table, "name", 'varchar(255)')) { + $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + } + $migration->changeField($table, "comments", "comment", "text"); $migration->migrationOneTable($table); diff --git a/inc/reference.class.php b/inc/reference.class.php index 029953e544..c81b1fa68a 100644 --- a/inc/reference.class.php +++ b/inc/reference.class.php @@ -1146,12 +1146,10 @@ public static function install(Migration $migration) "is_recursive", "tinyint NOT NULL default '0'", ); - $migration->changeField( - $table, - "name", - "name", - "varchar(255) default NULL", - ); + if (!PluginOrderConfig::fieldHasType($table, "name", 'varchar(255)')) { + $migration->changeField($table, "name", "name", "varchar(255) default NULL"); + } + $migration->changeField( $table, "FK_glpi_enterprise", diff --git a/inc/reference_supplier.class.php b/inc/reference_supplier.class.php index 7cecf6dced..29d0b0da9f 100644 --- a/inc/reference_supplier.class.php +++ b/inc/reference_supplier.class.php @@ -436,6 +436,9 @@ public static function install(Migration $migration) $migration->migrationOneTable("glpi_plugin_order_references_manufacturers"); } + //Legacy data forwarding below only applies to a pre-1.2.0 table + $is_legacy = $DB->tableExists("glpi_plugin_order_references_manufacturers"); + //1.2.0 $migration->renameTable("glpi_plugin_order_references_manufacturers", $table); $migration->addField($table, "is_recursive", sprintf("int %s NOT NULL default '0'", $default_key_sign)); @@ -465,40 +468,38 @@ public static function install(Migration $migration) "suppliers_id", sprintf("int %s NOT NULL default '0' COMMENT 'RELATION to glpi_suppliers (id)'", $default_key_sign), ); - $migration->changeField( - $table, - "reference_code", - "reference_code", - "varchar(255) default NULL", - ); - $migration->changeField( - $table, - "price_taxfree", - "price_taxfree", - "decimal(20,6) NOT NULL DEFAULT '0.000000'", - ); + if (!PluginOrderConfig::fieldHasType($table, "reference_code", 'varchar(255)')) { + $migration->changeField($table, "reference_code", "reference_code", "varchar(255) default NULL"); + } + + if (!PluginOrderConfig::fieldHasType($table, "price_taxfree", 'decimal(20,6)')) { + $migration->changeField($table, "price_taxfree", "price_taxfree", "decimal(20,6) NOT NULL DEFAULT '0.000000'"); + } + $migration->migrationOneTable($table); - //1.5.0 - $query = [ - 'SELECT' => [ - 'entities_id', - 'is_recursive', - 'id', - ], - 'FROM' => 'glpi_plugin_order_references', - ]; - foreach ($DB->request($query) as $data) { - $migration->addPostQuery( - $DB->buildUpdate( - 'glpi_plugin_order_references_suppliers', - [ - 'entities_id' => $data['entities_id'], - 'is_recursive' => $data['is_recursive'], - ], - ['plugin_order_references_id' => $data['id']], - ), - ); + //1.5.0, replayed on every upgrade otherwise (one query per reference) + if ($is_legacy) { + $query = [ + 'SELECT' => [ + 'entities_id', + 'is_recursive', + 'id', + ], + 'FROM' => 'glpi_plugin_order_references', + ]; + foreach ($DB->request($query) as $data) { + $migration->addPostQuery( + $DB->buildUpdate( + 'glpi_plugin_order_references_suppliers', + [ + 'entities_id' => $data['entities_id'], + 'is_recursive' => $data['is_recursive'], + ], + ['plugin_order_references_id' => $data['id']], + ), + ); + } } } } diff --git a/inc/surveysupplier.class.php b/inc/surveysupplier.class.php index c92d9d366a..ea9d4ac42a 100644 --- a/inc/surveysupplier.class.php +++ b/inc/surveysupplier.class.php @@ -556,38 +556,40 @@ public static function install(Migration $migration) "suppliers_id", sprintf("int %s NOT NULL default '0' COMMENT 'RELATION to glpi_suppliers (id)'", $default_key_sign), ); - $migration->changeField( - $table, - "comment", - "comment", - "text", - ); - $migration->addField($table, "entities_id", sprintf("int %s NOT NULL default '0'", $default_key_sign)); + if (!PluginOrderConfig::fieldHasType($table, "comment", 'text')) { + $migration->changeField($table, "comment", "comment", "text"); + } + + $entities_added = $migration->addField($table, "entities_id", sprintf("int %s NOT NULL default '0'", $default_key_sign)); $migration->addField($table, "is_recursive", "tinyint NOT NULL default '0'"); $migration->addKey($table, "plugin_order_orders_id"); $migration->addKey($table, "suppliers_id"); $migration->migrationOneTable($table); - $query = [ - 'SELECT' => [ - 'suppliers_id', - 'entities_id', - 'is_recursive', - 'id', - ], - 'FROM' => 'glpi_plugin_order_orders', - ]; - foreach ($DB->request($query) as $data) { - $migration->addPostQuery( - $DB->buildUpdate( - 'glpi_plugin_order_surveysuppliers', - [ - 'entities_id' => $data['entities_id'], - 'is_recursive' => $data['is_recursive'], - ], - ['plugin_order_orders_id' => $data['id']], - ), - ); + //Forward the order entity only when the column has just been created + //(one query per order on every upgrade otherwise) + if ($entities_added) { + $query = [ + 'SELECT' => [ + 'suppliers_id', + 'entities_id', + 'is_recursive', + 'id', + ], + 'FROM' => 'glpi_plugin_order_orders', + ]; + foreach ($DB->request($query) as $data) { + $migration->addPostQuery( + $DB->buildUpdate( + 'glpi_plugin_order_surveysuppliers', + [ + 'entities_id' => $data['entities_id'], + 'is_recursive' => $data['is_recursive'], + ], + ['plugin_order_orders_id' => $data['id']], + ), + ); + } } } }