Unverified Commit 8fc5d4f6 authored by Cédric Anne's avatar Cédric Anne Committed by GitHub
Browse files

Fix inconsistency on date fields and add index on name fields (#9200)

* Rename db check commands

* Check DB schema consistency for date fields

* Add indexes on "name" fields

* Rename some date fields to improve naming consistency

* Fix search option id migration

* Add test on name indexing

* Add tests on DatabaseSchemaConsistencyChecker

* Fix missing renaming
parent a66a7f4b
......@@ -15,8 +15,9 @@ if [[ -n $(grep "Warning" $LOG_FILE) ]];
fi
# Check DB
bin/console glpi:database:check_schema --config-dir=./tests/config --ansi --no-interaction --strict
bin/console glpi:database:check_keys --config-dir=./tests/config --ansi --no-interaction --detect-useless-keys
bin/console glpi:database:check_schema_integrity --config-dir=./tests/config --ansi --no-interaction --strict
bin/console glpi:tools:check_database_keys --config-dir=./tests/config --ansi --no-interaction --detect-useless-keys
bin/console glpi:tools:check_database_schema_consistency --config-dir=./tests/config --ansi --no-interaction
# Execute update
## Should do nothing.
......
......@@ -26,7 +26,7 @@ if [[ -z $(grep "No migration needed." $LOG_FILE) ]];
then echo "bin/console glpi:database:update command FAILED" && exit 1;
fi
## Check DB
bin/console glpi:database:check_schema --config-dir=./tests/config --ansi --no-interaction --ignore-utf8mb4-migration
bin/console glpi:database:check_schema_integrity --config-dir=./tests/config --ansi --no-interaction --ignore-utf8mb4-migration
# Execute myisam_to_innodb migration
## First run should do nothing.
......@@ -54,7 +54,7 @@ if [[ -z $(grep "No migration needed." $LOG_FILE) ]];
then echo "bin/console glpi:migration:utf8mb4 command FAILED" && exit 1;
fi
# Check DB
bin/console glpi:database:check_schema --config-dir=./tests/config --ansi --no-interaction
bin/console glpi:database:check_schema_integrity --config-dir=./tests/config --ansi --no-interaction
# Check updated data
bin/console glpi:database:configure \
......
......@@ -20,7 +20,7 @@ if [[ -z $(grep "No migration needed." $LOG_FILE) ]];
then echo "bin/console glpi:database:update command FAILED" && exit 1;
fi
## Check DB
bin/console glpi:database:check_schema \
bin/console glpi:database:check_schema_integrity \
--config-dir=./tests/config --ansi --no-interaction \
--ignore-innodb-migration --ignore-timestamps-migration --ignore-dynamic-row-format-migration --ignore-utf8mb4-migration
......@@ -36,7 +36,7 @@ if [[ -z $(grep "No migration needed." $LOG_FILE) ]];
then echo "bin/console glpi:migration:myisam_to_innodb command FAILED" && exit 1;
fi
## Check DB
bin/console glpi:database:check_schema \
bin/console glpi:database:check_schema_integrity \
--config-dir=./tests/config --ansi --no-interaction \
--ignore-timestamps-migration --ignore-dynamic-row-format-migration --ignore-utf8mb4-migration
......@@ -52,7 +52,7 @@ if [[ -z $(grep "No migration needed." $LOG_FILE) ]];
then echo "bin/console glpi:migration:timestamps command FAILED" && exit 1;
fi
## Check DB
bin/console glpi:database:check_schema \
bin/console glpi:database:check_schema_integrity \
--config-dir=./tests/config --ansi --no-interaction \
--ignore-dynamic-row-format-migration --ignore-utf8mb4-migration
......@@ -60,7 +60,7 @@ bin/console glpi:database:check_schema \
## Result will depend on DB server/version, we just expect that command will not fail.
bin/console glpi:migration:dynamic_row_format --config-dir=./tests/config --ansi --no-interaction
## Check DB
bin/console glpi:database:check_schema --config-dir=./tests/config --ansi --no-interaction --ignore-utf8mb4-migration
bin/console glpi:database:check_schema_integrity --config-dir=./tests/config --ansi --no-interaction --ignore-utf8mb4-migration
# Execute utf8mb4 migration
## First run should do the migration (with no warnings).
......@@ -74,7 +74,7 @@ if [[ -z $(grep "No migration needed." $LOG_FILE) ]];
then echo "bin/console glpi:migration:utf8mb4 command FAILED" && exit 1;
fi
## Check DB
bin/console glpi:database:check_schema --config-dir=./tests/config --ansi --no-interaction
bin/console glpi:database:check_schema_integrity --config-dir=./tests/config --ansi --no-interaction
# Check updated data
bin/console glpi:database:configure \
......
......@@ -25,6 +25,14 @@ The present file will list all changes made to the project; according to the
- Format of `Message-Id` header sent in Tickets notifications changed to match format used by other items.
- Added `DB::truncate()` to replace raw SQL queries
- Impact context `positions` field type changed from `TEXT` to `MEDIUMTEXT`
- Field `date` of KnowbaseItem has been renamed to `date_creation`.
- Field `date_creation` of KnowbaseItem_Revision has been renamed to `date`.
- Field `date_creation` of NetworkPortConnectionLog has been renamed to `date`.
- Field `date_creation` of NetworkPortMetrics has been renamed to `date`.
- Field `date` of Notepad has been renamed to `date_creation`.
- Field `date_mod` of ObjectLock has been renamed to `date`.
- Field `date_creation` of PrinterLog has been renamed to `date`.
- Field `date` of ProjectTask has been renamed to `date_creation`.
#### Deprecated
- Usage of `GLPI_FORCE_EMPTY_SQL_MODE` constant
......
......@@ -37,12 +37,12 @@ if (!defined('GLPI_ROOT')) {
}
use Glpi\Console\AbstractCommand;
use Glpi\System\Diagnostic\DatabaseSchemaChecker;
use Glpi\System\Diagnostic\DatabaseSchemaIntegrityChecker;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CheckSchemaCommand extends AbstractCommand {
class CheckSchemaIntegrityCommand extends AbstractCommand {
/**
* Error code returned when failed to read empty SQL file.
......@@ -61,10 +61,10 @@ class CheckSchemaCommand extends AbstractCommand {
protected function configure() {
parent::configure();
$this->setName('glpi:database:check_schema');
$this->setName('glpi:database:check_schema_integrity');
$this->setAliases(
[
'db:check_schema',
'db:check_schema_integrity',
'glpi:database:check', // old name
'db:check', // old alias
]
......@@ -109,7 +109,7 @@ class CheckSchemaCommand extends AbstractCommand {
protected function execute(InputInterface $input, OutputInterface $output) {
$checker = new DatabaseSchemaChecker(
$checker = new DatabaseSchemaIntegrityChecker(
$this->db,
$input->getOption('strict'),
$input->getOption('ignore-innodb-migration'),
......
......@@ -282,6 +282,7 @@ class Printer extends NetworkEquipment
$metrics = new PrinterLog();
$input = (array)$this->counters;
$input['printers_id'] = $this->item->fields['id'];
$input['date'] = $_SESSION['glpi_currenttime'];
$metrics->add($input, [], false);
}
}
......@@ -623,12 +623,6 @@ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {
function prepareInputForAdd($input) {
// set new date if not exists
if (!isset($input["date"]) || empty($input["date"])) {
$input["date"] = $_SESSION["glpi_currenttime"];
}
// set users_id
// set title for question if empty
if (isset($input["name"]) && empty($input["name"])) {
$input["name"] = __('New item');
......@@ -735,9 +729,9 @@ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {
KnowbaseItemCategory::dropdown(['value' => $this->fields["knowbaseitemcategories_id"]]);
echo "</td>";
echo "<td>";
if ($this->fields["date"]) {
if ($this->fields["date_creation"]) {
//TRANS: %s is the datetime of insertion
printf(__('Created on %s'), Html::convDateTime($this->fields["date"]));
printf(__('Created on %s'), Html::convDateTime($this->fields["date_creation"]));
}
echo "</td><td>";
if ($this->fields["date_mod"]) {
......@@ -1021,9 +1015,9 @@ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {
$out.= "<br>";
}
if ($this->fields["date"]) {
if ($this->fields["date_creation"]) {
//TRANS: %s is the datetime of update
$out.= sprintf(__('Created on %s'), Html::convDateTime($this->fields["date"]));
$out.= sprintf(__('Created on %s'), Html::convDateTime($this->fields["date_creation"]));
$out.= "<br>";
}
if ($this->fields["date_mod"]) {
......@@ -1737,7 +1731,7 @@ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {
];
if ($type == "recent") {
$criteria['ORDERBY'] = 'date DESC';
$criteria['ORDERBY'] = 'date_creation DESC';
$title = __('Recent entries');
} else if ($type == 'lastupdate') {
$criteria['ORDERBY'] = 'date_mod DESC';
......@@ -1859,15 +1853,6 @@ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {
'datatype' => 'dropdown'
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'date',
'name' => _n('Date', 'Dates', 1),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '6',
'table' => $this->getTable(),
......@@ -1927,6 +1912,15 @@ class KnowbaseItem extends CommonDBVisible implements ExtraVisibilityCriteria {
'massiveaction' => false
];
$tab[] = [
'id' => '121',
'table' => $this->getTable(),
'field' => 'date_creation',
'name' => __('Creation date'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '70',
'table' => 'glpi_users',
......
......@@ -172,7 +172,7 @@ class KnowbaseItem_Revision extends CommonDBTM {
echo "/> <input type='radio' name='diff' value='{$revision['id']}'/></td>";
echo "<td>" . ($hasRevUser ? $user->getLink() : __('Unknown user')) . "</td>".
"<td class='tab_date'>". $revision['date_creation'] . "</td>";
"<td class='tab_date'>". $revision['date'] . "</td>";
$form = null;
if ($item->getType() == KnowbaseItem::getType()) {
......@@ -299,7 +299,7 @@ class KnowbaseItem_Revision extends CommonDBTM {
$this->fields['knowbaseitems_id'] = $item->fields['id'];
$this->fields['name'] = Toolbox::addslashes_deep($item->fields['name']);
$this->fields['answer'] = Toolbox::addslashes_deep($item->fields['answer']);
$this->fields['date_creation'] = $item->fields['date_mod'];
$this->fields['date'] = $item->fields['date_mod'];
$this->fields['revision'] = $this->getNewRevision();
$this->fields['users_id'] = $item->fields['users_id'];
$this->addToDB();
......@@ -318,7 +318,7 @@ class KnowbaseItem_Revision extends CommonDBTM {
$this->fields['knowbaseitems_id'] = $item->fields['knowbaseitems_id'];
$this->fields['name'] = Toolbox::addslashes_deep($item->fields['name']);
$this->fields['answer'] = Toolbox::addslashes_deep($item->fields['answer']);
$this->fields['date_creation'] = $item->fields['date_mod'];
$this->fields['date'] = $item->fields['date_mod'];
$this->fields['language'] = $item->fields['language'];
$this->fields['revision'] = $this->getNewRevision();
$this->fields['users_id'] = $item->fields['users_id'];
......
......@@ -379,6 +379,7 @@ class NetworkPort extends CommonDBChild {
'ifoutbytes' => $this->fields['ifoutbytes'] ?? 0,
'ifinerrors' => $this->fields['ifinerrors'] ?? 0,
'ifouterrors' => $this->fields['ifouterrors'] ?? 0,
'date' => $_SESSION['glpi_currenttime'],
], [], false);
}
......
......@@ -312,7 +312,8 @@ class NetworkPort_NetworkPort extends CommonDBRelation {
$input = [
'networkports_id_source' => $netports_id,
'networkports_id_destination' => $opposite_port,
'connected' => ($action === 'add')
'connected' => ($action === 'add'),
'date' => $_SESSION['glpi_currenttime'],
];
$log->add($input);
......
......@@ -133,7 +133,7 @@ class NetworkPortConnectionLog extends CommonDBChild {
}
echo "<i class='fas $co_class' title='$title'></i> <span class='sr-only'>$title</span>";
echo "</td>";
echo "<td>" . $row['date_creation'] . "</td>";
echo "<td>" . $row['date'] . "</td>";
echo "<td>";
$is_source = $netport->fields['id'] == $row['networkports_id_source'];
......
......@@ -106,7 +106,7 @@ class NetworkPortMetrics extends CommonDBChild {
$bdate = new DateTime();
$bdate->sub(new DateInterval('P1Y'));
$filters = [
'date_creation' => ['>', $bdate->format('Y-m-d')]
'date' => ['>', $bdate->format('Y-m-d')]
];
$filters = array_merge($filters, $user_filters);
......@@ -140,9 +140,9 @@ class NetworkPortMetrics extends CommonDBChild {
$labels = [];
$i = 0;
foreach ($raw_metrics as $metrics) {
$date = new \DateTime($metrics['date_creation']);
$date = new \DateTime($metrics['date']);
$labels[] = $date->format(__('Y-m-d'));
unset($metrics['id'], $metrics['date_creation'], $metrics[static::$items_id]);
unset($metrics['id'], $metrics['date'], $metrics[static::$items_id]);
$bytes_metrics = $metrics;
unset($bytes_metrics['ifinerrors'], $bytes_metrics['ifouterrors']);
......
......@@ -84,7 +84,6 @@ class Notepad extends CommonDBChild {
$input['users_id'] = Session::getLoginUserID();
$input['users_id_lastupdater'] = Session::getLoginUserID();
$input['date'] = $_SESSION['glpi_currenttime'];
return $input;
}
......@@ -192,7 +191,7 @@ class Notepad extends CommonDBChild {
$tab[] = [
'id' => '201',
'table' => 'glpi_notepads',
'field' => 'date',
'field' => 'date_creation',
'name' => __('Creation date'),
'datatype' => 'datetime',
'joinparams' => [
......@@ -324,7 +323,7 @@ class Notepad extends CommonDBChild {
$username = getUserName($note['users_id'], $showuserlink);
}
$create = sprintf(__('Create by %1$s on %2$s'), $username,
Html::convDateTime($note['date']));
Html::convDateTime($note['date_creation']));
printf(__('%1$s / %2$s'), $update, $create);
echo "</div>"; // floatright
......
......@@ -59,7 +59,8 @@ class NotificationTargetObjectLock extends NotificationTarget {
'objectlock.name' => __('Item Name'),
'objectlock.id' => __('Item ID'),
'objectlock.type' => __('Item Type'),
'objectlock.date_mod' => __('Lock date'),
'objectlock.date' => __('Lock date'),
'objectlock.date_mod' => __('Lock date'), // old field name
'objectlock.lockedby.lastname' => __('Lastname of locking user'),
'objectlock.lockedby.firstname' => __('Firstname of locking user'),
'objectlock.requester.lastname' => __('Requester Lastname'),
......@@ -110,8 +111,9 @@ class NotificationTargetObjectLock extends NotificationTarget {
$this->data['##objectlock.name##'] = $object->fields['name'];
$this->data['##objectlock.id##'] = $options['item']->fields['items_id'];
$this->data['##objectlock.type##'] = $options['item']->fields['itemtype'];
$this->data['##objectlock.date_mod##'] = Html::convDateTime($options['item']->fields['date_mod'],
$this->data['##objectlock.date##'] = Html::convDateTime($options['item']->fields['date_mod'],
$user->fields['date_format']);
$this->data['##objectlock.date_mod##'] = $this->data['##objectlock.date##'];
$this->data['##objectlock.lockedby.lastname##']
= $user->fields['realname'];
$this->data['##objectlock.lockedby.firstname##']
......
......@@ -268,7 +268,7 @@ class ObjectLock extends CommonDBTM {
$msg = "<strong class='nowrap'>";
$msg .= sprintf(__('Locked by %s'), "<a href='" . $user->getLinkURL() . "'>" . $userdata['name'] . "</a>");
$msg .= "&nbsp;" . Html::showToolTip($userdata["comment"], ['link' => $userdata['link'], 'display' => false]);
$msg .= " -> " . Html::convDateTime($this->fields['date_mod']);
$msg .= " -> " . Html::convDateTime($this->fields['date']);
$msg .= "</strong>";
if ($showAskUnlock) {
$msg .= "<a class='vsubmit' onclick='javascript:askUnlock();'>".__('Ask for unlock')."</a>";
......@@ -556,7 +556,7 @@ class ObjectLock extends CommonDBTM {
$tab[] = [
'id' => '206',
'table' => getTableForItemType('ObjectLock'),
'field' => 'date_mod',
'field' => 'date',
'datatype' => 'datetime',
'name' => __('Locked date'),
'joinparams' => ['jointype' => 'itemtype_item'],
......@@ -626,7 +626,7 @@ class ObjectLock extends CommonDBTM {
$lockedItems = getAllDataFromTable(
getTableForItemType(__CLASS__), [
'date_mod' => ['<', date("Y-m-d H:i:s", time() - ($task->fields['param'] * HOUR_TIMESTAMP))]
'date' => ['<', date("Y-m-d H:i:s", time() - ($task->fields['param'] * HOUR_TIMESTAMP))]
]
);
......
......@@ -107,7 +107,7 @@ class PrinterLog extends CommonDBChild {
$bdate = new DateTime();
$bdate->sub(new DateInterval('P1Y'));
$filters = [
'date_creation' => ['>', $bdate->format('Y-m-d')]
'date' => ['>', $bdate->format('Y-m-d')]
];
$filters = array_merge($filters, $user_filters);
......@@ -140,9 +140,9 @@ class PrinterLog extends CommonDBChild {
$labels = [];
$i = 0;
foreach ($raw_metrics as $metrics) {
$date = new DateTime($metrics['date_creation']);
$date = new DateTime($metrics['date']);
$labels[] = $date->format(__('Y-m-d'));
unset($metrics['id'], $metrics['date_creation'], $metrics['printers_id']);
unset($metrics['id'], $metrics['date'], $metrics['printers_id']);
foreach ($metrics as $key => $value) {
if ($value > 0) {
......
......@@ -930,8 +930,8 @@ class Project extends CommonDBTM implements ExtraVisibilityCriteria {
$tab[] = [
'id' => '115',
'table' => ProjectTask::getTable(),
'field' => 'date',
'name' => __('Opening date'),
'field' => 'date_creation',
'name' => __('Creation date'),
'datatype' => 'datetime',
'massiveaction' => false,
'forcegroupby' => true,
......
......@@ -406,9 +406,6 @@ class ProjectTask extends CommonDBChild implements CalDAVCompatibleItemInterface
if (!isset($input['users_id'])) {
$input['users_id'] = Session::getLoginUserID();
}
if (!isset($input['date'])) {
$input['date'] = $_SESSION['glpi_currenttime'];
}
if (isset($input["plan"])) {
$input["plan_start_date"] = $input['plan']["begin"];
......@@ -640,7 +637,7 @@ class ProjectTask extends CommonDBChild implements CalDAVCompatibleItemInterface
echo "<tr class='tab_bg_1'>";
echo "<td>".__('Creation date')."</td>";
echo "<td>";
echo sprintf(__('%1$s by %2$s'), Html::convDateTime($this->fields["date"]),
echo sprintf(__('%1$s by %2$s'), Html::convDateTime($this->fields["date_creation"]),
getUserName($this->fields["users_id"], $showuserlink));
echo "</td>";
echo "<td>".__('Last update')."</td>";
......@@ -957,10 +954,10 @@ class ProjectTask extends CommonDBChild implements CalDAVCompatibleItemInterface
];
$tab[] = [
'id' => '15',
'id' => '121',
'table' => $this->getTable(),
'field' => 'date',
'name' => __('Opening date'),
'field' => 'date_creation',
'name' => __('Creation date'),
'datatype' => 'datetime',
'massiveaction' => false
];
......
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2021 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
namespace Glpi\System\Diagnostic;
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
use DBmysql;
/**
* @since 10.0.0
*/
abstract class AbstractDatabaseChecker {
/**
* DB instance.
*
* @var DBmysql
*/
protected $db;
/**
* Local cache for tables columns.
*
* @var array
*/
private $columns = [];
/**
* Local cache for tables indexes.
*
* @var array
*/
private $indexes = [];
/**
* @param DBmysql $db DB instance.
*/
public function __construct(DBmysql $db) {
$this->db = $db;
}
/**
* Return list of column names for given table.
*
* @param string $table_name
*
* @return array
*/
protected function getColumnsNames(string $table_name): array {
$this->fetchTableColumns($table_name);
return array_column($this->columns[$table_name], 'Field');
}
/**
* Return column type.
*
* @param string $table_name
* @param string $column_name
*
* @return null|string
*/
protected function getColumnType(string $table_name, string $column_name): ?string {
$this->fetchTableColumns($table_name);
foreach ($this->columns[$table_name] as $column_specs) {
if ($column_specs['Field'] === $column_name) {
return $column_specs['Type'];
}
}
return null;
}
/**
* Return column type.
*
* @param string $table_name
*
* @return void
*/
private function fetchTableColumns(string $table_name): void {
if (!array_key_exists($table_name, $this->columns)) {
if (($columns_res = $this->db->query('SHOW COLUMNS FROM ' . $this->db->quoteName($table_name))) === false) {
throw new \Exception(sprintf('Unable to get table "%s" columns', $table_name));
}
$this->columns[$table_name] = $columns_res->fetch_all(MYSQLI_ASSOC);
}
}
/**
* Return index for given table.
* Array keys are index key, and values are fields related to this key.
*
* @param string $table_name
*
* @return array
*/
protected function getIndex(string $table_name): array {
if (!array_key_exists($table_name, $this->indexes)) {
if (($keys_res = $this->db->query('SHOW INDEX FROM ' . $this->db->quoteName($table_name))) === false) {
throw new \Exception(sprintf('Unable to get table "%s" index', $table_name));
}
$index = [];
while ($key_specs = $keys_res->fetch_assoc()) {
if ($key_specs['Index_type'] === 'FULLTEXT') {
continue; // Ignore FULLTEXT keys
}
$key_name = $key_specs['Key_name'];
if (!array_key_exists($key_name, $index)) {
$index[$key_name] = [];
}
$index[$key_name][$key_specs['Seq_in_index'] - 1] = $key_specs['Column_name'];
}
$this->indexes[$table_name] = $index;
}
return $this->indexes[$table_name];
}
}
......@@ -36,40 +36,12 @@ if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
use DBmysql;
use CommonDBTM;
/**
* @since 10.0.0
*/
class DatabaseKeysChecker {
/**
* DB instance.
*