Validate database constraints (#9670)

* Validate database constraints

* Fix some differences with migrations

* Update functions.php
This commit is contained in:
Tony Murray 2019-01-17 08:59:42 -06:00 committed by GitHub
parent 9da12ee8af
commit 10f6eac677
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 36 deletions

View File

@ -185,14 +185,15 @@ class Database extends BaseValidation
$schema_update[] = $this->dropColumnSql($table, $column);
}
$index_changes = [];
if (isset($data['Indexes'])) {
foreach ($data['Indexes'] as $name => $index) {
if (empty($current_schema[$table]['Indexes'][$name])) {
$validator->fail("Database: missing index ($table/$name)");
$schema_update[] = $this->addIndexSql($table, $index);
$index_changes[] = $this->addIndexSql($table, $index);
} elseif ($index != $current_schema[$table]['Indexes'][$name]) {
$validator->fail("Database: incorrect index ($table/$name)");
$schema_update[] = $this->updateIndexSql($table, $name, $index);
$index_changes[] = $this->updateIndexSql($table, $name, $index);
}
unset($current_schema[$table]['Indexes'][$name]);
@ -205,6 +206,32 @@ class Database extends BaseValidation
$schema_update[] = $this->dropIndexSql($table, $name);
}
}
$schema_update = array_merge($schema_update, $index_changes); // drop before create/update
$constraint_changes = [];
if (isset($data['Constraints'])) {
foreach ($data['Constraints'] as $name => $constraint) {
if (empty($current_schema[$table]['Constraints'][$name])) {
$validator->fail("Database: missing constraint ($table/$name)");
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
} elseif ($constraint != $current_schema[$table]['Constraints'][$name]) {
$validator->fail("Database: incorrect constraint ($table/$name)");
$constraint_changes[] = $this->dropConstraintSql($table, $name);
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
}
unset($current_schema[$table]['Constraints'][$name]);
}
}
if (isset($current_schema[$table]['Constraints'])) {
foreach ($current_schema[$table]['Constraints'] as $name => $_unused) {
$validator->fail("Database: extra constraint ($table/$name)");
$schema_update[] = $this->dropConstraintSql($table, $name);
}
}
$schema_update = array_merge($schema_update, $constraint_changes); // drop before create/update
}
unset($current_schema[$table]); // remove checked tables
@ -332,4 +359,21 @@ class Database extends BaseValidation
return sprintf($index, $columns);
}
private function addConstraintSql($table, $constraint)
{
$sql = "ALTER TABLE `$table` ADD CONSTRAINT `{$constraint['name']}` FOREIGN KEY (`{$constraint['foreign_key']}`) ";
$sql .= " REFERENCES `{$constraint['table']}` (`{$constraint['key']}`)";
if (!empty($constraint['extra'])) {
$sql .= ' ' . $constraint['extra'];
}
$sql .= ';';
return $sql;
}
private function dropConstraintSql($table, $name)
{
return "ALTER TABLE `$table` DROP FOREIGN KEY `$name`;";
}
}

View File

@ -14,7 +14,7 @@ class AddForeignKeysToSensorsToStateIndexesTable extends Migration
public function up()
{
Schema::table('sensors_to_state_indexes', function (Blueprint $table) {
$table->foreign('state_index_id', 'sensors_to_state_indexes_ibfk_2')->references('state_index_id')->on('state_indexes')->onUpdate('RESTRICT')->onDelete('RESTRICT');
$table->foreign('state_index_id', 'sensors_to_state_indexes_ibfk_1')->references('state_index_id')->on('state_indexes')->onUpdate('RESTRICT')->onDelete('RESTRICT');
$table->foreign('sensor_id')->references('sensor_id')->on('sensors')->onUpdate('RESTRICT')->onDelete('CASCADE');
});
}

View File

@ -2393,20 +2393,18 @@ function cache_peeringdb()
*/
function dump_db_schema()
{
global $config;
$output = array();
$output = [];
$db_name = dbFetchCell('SELECT DATABASE()');
foreach (dbFetchRows("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '$db_name' ORDER BY TABLE_NAME;") as $table) {
$table = $table['TABLE_NAME'];
foreach (dbFetchRows("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$db_name' AND TABLE_NAME='$table'") as $data) {
$def = array(
$def = [
'Field' => $data['COLUMN_NAME'],
'Type' => $data['COLUMN_TYPE'],
'Null' => $data['IS_NULLABLE'] === 'YES',
'Extra' => str_replace('current_timestamp()', 'CURRENT_TIMESTAMP', $data['EXTRA']),
);
];
if (isset($data['COLUMN_DEFAULT']) && $data['COLUMN_DEFAULT'] != 'NULL') {
$default = trim($data['COLUMN_DEFAULT'], "'");
@ -2421,15 +2419,30 @@ function dump_db_schema()
if (isset($output[$table]['Indexes'][$key_name])) {
$output[$table]['Indexes'][$key_name]['Columns'][] = $key['Column_name'];
} else {
$output[$table]['Indexes'][$key_name] = array(
$output[$table]['Indexes'][$key_name] = [
'Name' => $key['Key_name'],
'Columns' => array($key['Column_name']),
'Columns' => [$key['Column_name']],
'Unique' => !$key['Non_unique'],
'Type' => $key['Index_type'],
);
];
}
}
$create = dbFetchRow("SHOW CREATE TABLE `$table`")['Create Table'];
$constraint_regex = '/CONSTRAINT `(?<name>[A-Za-z_0-9]+)` FOREIGN KEY \(`(?<foreign_key>[A-Za-z_0-9]+)`\) REFERENCES `(?<table>[A-Za-z_0-9]+)` \(`(?<key>[A-Za-z_0-9]+)`\) ?(?<extra>[ A-Z]+)?/';
$constraint_count = preg_match_all($constraint_regex, $create, $constraints);
for ($i = 0; $i < $constraint_count; $i++) {
$constraint_name = $constraints['name'][$i];
$output[$table]['Constraints'][$constraint_name] = [
'name' => $constraint_name,
'foreign_key' => $constraints['foreign_key'][$i],
'table' => $constraints['table'][$i],
'key' => $constraints['key'][$i],
'extra' => $constraints['extra'][$i],
];
}
}
return $output;
}

View File

@ -25,9 +25,7 @@
* @author Tony Murray <murraytony@gmail.com>
*/
use Artisan;
use LibreNMS\Config;
use LibreNMS\Exceptions\DatabaseConnectException;
use LibreNMS\Exceptions\LockException;
use LibreNMS\Util\FileLock;
use LibreNMS\Util\MemcacheLock;

View File

@ -17,8 +17,8 @@ access_points:
- { Field: interference, Type: 'tinyint(3) unsigned', 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [accesspoint_id], Unique: true, Type: BTREE }
deleted: { Name: deleted, Columns: [deleted], Unique: false, Type: BTREE }
name: { Name: name, Columns: [name, radio_number], Unique: false, Type: BTREE }
deleted: { Name: deleted, Columns: [deleted], Unique: false, Type: BTREE }
alerts:
Columns:
- { Field: id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -33,8 +33,8 @@ alerts:
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
unique_alert: { Name: unique_alert, Columns: [device_id, rule_id], Unique: true, Type: BTREE }
rule_id: { Name: rule_id, Columns: [rule_id], Unique: false, Type: BTREE }
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
rule_id: { Name: rule_id, Columns: [rule_id], Unique: false, Type: BTREE }
alert_device_map:
Columns:
- { Field: id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -86,8 +86,8 @@ alert_schedulables:
- { Field: alert_schedulable_type, Type: varchar(255), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [item_id], Unique: true, Type: BTREE }
schedule_id: { Name: schedule_id, Columns: [schedule_id], Unique: false, Type: BTREE }
schedulable_morph_index: { Name: schedulable_morph_index, Columns: [alert_schedulable_type, alert_schedulable_id], Unique: false, Type: BTREE }
schedule_id: { Name: schedule_id, Columns: [schedule_id], Unique: false, Type: BTREE }
alert_schedule:
Columns:
- { Field: schedule_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -378,6 +378,8 @@ component_prefs:
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
component: { Name: component, Columns: [component], Unique: false, Type: BTREE }
Constraints:
component_prefs_ibfk_1: { name: component_prefs_ibfk_1, foreign_key: component, table: component, key: id, extra: 'ON DELETE CASCADE ON UPDATE CASCADE' }
component_statuslog:
Columns:
- { Field: id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -388,6 +390,8 @@ component_statuslog:
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
device: { Name: device, Columns: [component_id], Unique: false, Type: BTREE }
Constraints:
component_statuslog_ibfk_1: { name: component_statuslog_ibfk_1, foreign_key: component_id, table: component, key: id, extra: 'ON DELETE CASCADE ON UPDATE CASCADE' }
config:
Columns:
- { Field: config_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -479,10 +483,10 @@ devices:
- { Field: max_depth, Type: int(11), 'Null': false, Extra: '', Default: '0' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [device_id], Unique: true, Type: BTREE }
status: { Name: status, Columns: [status], Unique: false, Type: BTREE }
hostname: { Name: hostname, Columns: [hostname], Unique: false, Type: BTREE }
sysName: { Name: sysName, Columns: [sysName], Unique: false, Type: BTREE }
os: { Name: os, Columns: [os], Unique: false, Type: BTREE }
status: { Name: status, Columns: [status], Unique: false, Type: BTREE }
last_polled: { Name: last_polled, Columns: [last_polled], Unique: false, Type: BTREE }
last_poll_attempted: { Name: last_poll_attempted, Columns: [last_poll_attempted], Unique: false, Type: BTREE }
devices_attribs:
@ -525,6 +529,9 @@ device_group_device:
PRIMARY: { Name: PRIMARY, Columns: [device_group_id, device_id], Unique: true, Type: BTREE }
device_group_device_device_group_id_index: { Name: device_group_device_device_group_id_index, Columns: [device_group_id], Unique: false, Type: BTREE }
device_group_device_device_id_index: { Name: device_group_device_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
Constraints:
device_group_device_device_group_id_foreign: { name: device_group_device_device_group_id_foreign, foreign_key: device_group_id, table: device_groups, key: id, extra: 'ON DELETE CASCADE' }
device_group_device_device_id_foreign: { name: device_group_device_device_id_foreign, foreign_key: device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }
device_mibs:
Columns:
- { Field: device_id, Type: 'int(10) unsigned', 'Null': false, Extra: '' }
@ -568,6 +575,9 @@ device_relationships:
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [parent_device_id, child_device_id], Unique: true, Type: BTREE }
device_relationship_child_device_id_fk: { Name: device_relationship_child_device_id_fk, Columns: [child_device_id], Unique: false, Type: BTREE }
Constraints:
device_relationship_child_device_id_fk: { name: device_relationship_child_device_id_fk, foreign_key: child_device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }
device_relationship_parent_device_id_fk: { name: device_relationship_parent_device_id_fk, foreign_key: parent_device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }
entityState:
Columns:
- { Field: entity_state_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -628,8 +638,8 @@ eventlog:
- { Field: severity, Type: tinyint(4), 'Null': false, Extra: '', Default: '2' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [event_id], Unique: true, Type: BTREE }
datetime: { Name: datetime, Columns: [datetime], Unique: false, Type: BTREE }
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
datetime: { Name: datetime, Columns: [datetime], Unique: false, Type: BTREE }
graph_types:
Columns:
- { Field: graph_type, Type: varchar(32), 'Null': false, Extra: '' }
@ -739,9 +749,9 @@ links:
- { Field: remote_version, Type: varchar(256), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
local_device_id: { Name: local_device_id, Columns: [local_device_id, remote_device_id], Unique: false, Type: BTREE }
src_if: { Name: src_if, Columns: [local_port_id], Unique: false, Type: BTREE }
dst_if: { Name: dst_if, Columns: [remote_port_id], Unique: false, Type: BTREE }
local_device_id: { Name: local_device_id, Columns: [local_device_id, remote_device_id], Unique: false, Type: BTREE }
loadbalancer_rservers:
Columns:
- { Field: rserver_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -835,13 +845,6 @@ mempools:
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [mempool_id], Unique: true, Type: BTREE }
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
migrations:
Columns:
- { Field: id, Type: int(10) unsigned, 'Null': false, Extra: auto_increment }
- { Field: migration, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: batch, Type: int(11), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
mibdefs:
Columns:
- { Field: module, Type: varchar(255), 'Null': false, Extra: '' }
@ -856,6 +859,13 @@ mibdefs:
- { Field: last_modified, Type: timestamp, 'Null': false, Extra: 'on update CURRENT_TIMESTAMP', Default: CURRENT_TIMESTAMP }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [module, mib, object_type], Unique: true, Type: BTREE }
migrations:
Columns:
- { Field: id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
- { Field: migration, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: batch, Type: int(11), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
munin_plugins:
Columns:
- { Field: mplug_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -1232,10 +1242,10 @@ ports_fdb:
- { Field: device_id, Type: 'int(10) unsigned', 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [ports_fdb_id], Unique: true, Type: BTREE }
mac_address: { Name: mac_address, Columns: [mac_address], Unique: false, Type: BTREE }
ports_fdb_port_id_index: { Name: ports_fdb_port_id_index, Columns: [port_id], Unique: false, Type: BTREE }
ports_fdb_device_id_index: { Name: ports_fdb_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
mac_address: { Name: mac_address, Columns: [mac_address], Unique: false, Type: BTREE }
ports_fdb_vlan_id_index: { Name: ports_fdb_vlan_id_index, Columns: [vlan_id], Unique: false, Type: BTREE }
ports_fdb_device_id_index: { Name: ports_fdb_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
ports_nac:
Columns:
- { Field: ports_nac_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -1255,8 +1265,8 @@ ports_nac:
- { Field: time_left, Type: varchar(50), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [ports_nac_id], Unique: true, Type: BTREE }
ports_nac_device_id_index: { Name: ports_nac_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
ports_nac_port_id_mac_address_index: { Name: ports_nac_port_id_mac_address_index, Columns: [port_id, mac_address], Unique: false, Type: BTREE }
ports_nac_device_id_index: { Name: ports_nac_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
ports_perms:
Columns:
- { Field: user_id, Type: 'int(10) unsigned', 'Null': false, Extra: '' }
@ -1446,9 +1456,11 @@ sensors:
- { Field: user_func, Type: varchar(100), 'Null': true, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [sensor_id], Unique: true, Type: BTREE }
sensor_host: { Name: sensor_host, Columns: [device_id], Unique: false, Type: BTREE }
sensor_class: { Name: sensor_class, Columns: [sensor_class], Unique: false, Type: BTREE }
sensor_host: { Name: sensor_host, Columns: [device_id], Unique: false, Type: BTREE }
sensor_type: { Name: sensor_type, Columns: [sensor_type], Unique: false, Type: BTREE }
Constraints:
sensors_device_id_foreign: { name: sensors_device_id_foreign, foreign_key: device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }
sensors_to_state_indexes:
Columns:
- { Field: sensors_to_state_translations_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -1458,6 +1470,9 @@ sensors_to_state_indexes:
PRIMARY: { Name: PRIMARY, Columns: [sensors_to_state_translations_id], Unique: true, Type: BTREE }
sensor_id_state_index_id: { Name: sensor_id_state_index_id, Columns: [sensor_id, state_index_id], Unique: true, Type: BTREE }
state_index_id: { Name: state_index_id, Columns: [state_index_id], Unique: false, Type: BTREE }
Constraints:
sensors_to_state_indexes_ibfk_1: { name: sensors_to_state_indexes_ibfk_1, foreign_key: state_index_id, table: state_indexes, key: state_index_id, extra: '' }
sensors_to_state_indexes_sensor_id_foreign: { name: sensors_to_state_indexes_sensor_id_foreign, foreign_key: sensor_id, table: sensors, key: sensor_id, extra: 'ON DELETE CASCADE' }
services:
Columns:
- { Field: service_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -1575,11 +1590,11 @@ syslog:
- { Field: seq, Type: 'bigint(20) unsigned', 'Null': false, Extra: auto_increment }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [seq], Unique: true, Type: BTREE }
datetime: { Name: datetime, Columns: [timestamp], Unique: false, Type: BTREE }
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
program: { Name: program, Columns: [program], Unique: false, Type: BTREE }
priority_level: { Name: priority_level, Columns: [priority, level], Unique: false, Type: BTREE }
device_id-timestamp: { Name: device_id-timestamp, Columns: [device_id, timestamp], Unique: false, Type: BTREE }
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
datetime: { Name: datetime, Columns: [timestamp], Unique: false, Type: BTREE }
program: { Name: program, Columns: [program], Unique: false, Type: BTREE }
tnmsneinfo:
Columns:
- { Field: id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -1710,10 +1725,10 @@ vrf_lite_cisco:
- { Field: vrf_name, Type: varchar(128), 'Null': true, Extra: '', Default: Default }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [vrf_lite_cisco_id], Unique: true, Type: BTREE }
vrf: { Name: vrf, Columns: [vrf_name], Unique: false, Type: BTREE }
context: { Name: context, Columns: [context_name], Unique: false, Type: BTREE }
device: { Name: device, Columns: [device_id], Unique: false, Type: BTREE }
mix: { Name: mix, Columns: [device_id, context_name, vrf_name], Unique: false, Type: BTREE }
device: { Name: device, Columns: [device_id], Unique: false, Type: BTREE }
context: { Name: context, Columns: [context_name], Unique: false, Type: BTREE }
vrf: { Name: vrf, Columns: [vrf_name], Unique: false, Type: BTREE }
widgets:
Columns:
- { Field: widget_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
@ -1753,3 +1768,5 @@ wireless_sensors:
sensor_class: { Name: sensor_class, Columns: [sensor_class], Unique: false, Type: BTREE }
sensor_host: { Name: sensor_host, Columns: [device_id], Unique: false, Type: BTREE }
sensor_type: { Name: sensor_type, Columns: [sensor_type], Unique: false, Type: BTREE }
Constraints:
wireless_sensors_device_id_foreign: { name: wireless_sensors_device_id_foreign, foreign_key: device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }