%PDF- %PDF-
Direktori : /backups/router/usr/local/share/kea/scripts/mysql/ |
Current File : //backups/router/usr/local/share/kea/scripts/mysql/upgrade_009.6_to_010.0.sh |
#!/bin/sh # Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC") # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # Exit with error if commands exit with non-zero and if undefined variables are # used. set -eu # shellcheck disable=SC2034 # SC2034: ... appears unused. Verify use (or export if used externally). prefix="/usr/local" # Include utilities based on location of this script. Check for sources first, # so that the unexpected situations with weird paths fall on the default # case of installed. script_path=$(cd "$(dirname "${0}")" && pwd) if test "${script_path}" = "/usr/obj/usr/ports/net/kea/work/kea-2.6.1/src/share/database/scripts/mysql"; then # shellcheck source=./src/bin/admin/admin-utils.sh.in . "/usr/obj/usr/ports/net/kea/work/kea-2.6.1/src/bin/admin/admin-utils.sh" else # shellcheck source=./src/bin/admin/admin-utils.sh.in . "${prefix}/share/kea/scripts/admin-utils.sh" fi # Check version. version=$(mysql_version "${@}") if test "${version}" != "9.6"; then printf 'This script upgrades 9.6 to 10.0. ' printf 'Reported version is %s. Skipping upgrade.\n' "${version}" exit 0 fi # Get the schema name from database argument. We need this to # query information_schema for the right database. for arg in "${@}" do if ! printf '%s' "${arg}" | grep -Eq -- '^--' then schema="$arg" break fi done # Make sure we have the schema. if [ -z "$schema" ] then printf "Could not find database schema name in cmd line args: %s\n" "${*}" exit 255 fi shrink_tag_column() { local schema="${1-}"; shift local table="${1-}"; shift # Check if the table already has a correct server tag length. sql="SELECT CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='${schema}' AND TABLE_NAME='${table}' AND COLUMN_NAME='tag'" if ! taglen=$(mysql -N -B "${@}" -e "${sql}") then printf 'shrink_tag_column: schema query failed [%s]\n' "${sql}" exit 255 fi # We aim for the length of 64 characters. if [ "$taglen" -ne 64 ] then # Check if any of the tags are longer than 64 characters. sql="SELECT COUNT(*) FROM $table WHERE CHAR_LENGTH(tag) > 64 LIMIT 1" if ! longtag=$(mysql -N -B "${@}" -e "${sql}") then printf 'shrink_tag_column: select query failed [%s]\n' "${sql}" exit 255 fi # Report an error if there are any server tags exceeding 64 characters. # A user should fix the tags and rerun this migration. if [ "$longtag" -eq 1 ] then printf 'shrink_tag_column: failed to resize server tag column for table %s.\n' "${table}" printf 'Ensure that no server tags are longer than 64 characters and rerun this migration.\n' printf 'The remote-server4-set and remote-server6-set commands from the cb_cmds hooks\n' printf 'library can be used to modify the tags.\n' exit 255 fi # If there are no long server tags we can safely alter the column. sql="ALTER TABLE $table MODIFY COLUMN tag VARCHAR(64) NOT NULL" if ! mysql -N -B "${@}" -e "${sql}" then printf 'shrink_tag_column: alter query failed [%s]\n' "${sql}" exit 255 fi fi } shrink_tag_column "${schema}" dhcp4_server "${@}" shrink_tag_column "${schema}" dhcp6_server "${@}" mysql "$@" <<EOF -- This line starts the schema upgrade to version 10.0. -- ----------------------------------------------------------------------- -- Create a table holding the DHCPv4 client classes. Most table -- columns map directly to respective client class properties in -- Kea configuration. The depend_on_known_directly column is -- explicitly set in an insert or update statement to indicate -- if the client class directly depends on KNOWN or UNKNOWN -- built-in classes. A caller should determine it by evaluating -- a test expression before inserting or updating the client -- class in the database. The nullable follow_class_name column -- can be used for positioning the inserted or updated client -- class within the class hierarchy. Set this column value to -- an existing class name, after which this class should be -- placed in the class hierarchy. See dhcp4_client_class_order -- description for the details of how classes are ordered. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp4_client_class ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(128) NOT NULL, test TEXT, next_server INT UNSIGNED DEFAULT NULL, server_hostname VARCHAR(128) DEFAULT NULL, boot_file_name VARCHAR(512) DEFAULT NULL, only_if_required TINYINT NOT NULL DEFAULT '0', valid_lifetime INT DEFAULT NULL, min_valid_lifetime INT DEFAULT NULL, max_valid_lifetime INT DEFAULT NULL, depend_on_known_directly TINYINT NOT NULL DEFAULT '0', follow_class_name VARCHAR(128) DEFAULT NULL, modification_ts TIMESTAMP NOT NULL, PRIMARY KEY (id), UNIQUE KEY id_UNIQUE (id), UNIQUE KEY name_UNIQUE (name), KEY key_dhcp4_client_class_modification_ts (modification_ts) ) ENGINE=InnoDB; -- ----------------------------------------------------------------------- -- Create a table for ordering client classes and holding information -- about indirect dependencies on KNOWN/UKNOWN built-in client classes. -- Each class in the dhcp4_client_class table has a corresponding row -- in the dhcp4_client_class_order table. A caller should not modify -- the contents of this table. Its entries are automatically created -- upon inserting or updating client classes in the dhcp4_client_classes -- using triggers. The order_index designates the position of the client -- class within the class hierarchy. If the follow_class_name value of -- the dhcp4_client_class table is set to NULL, the client class is -- appended at the end of the hierarchy. The assigned order_index -- value for that class is set to a maximum current value + 1. -- If the follow_client_class specifies a name of an existing class, -- the generated order_index is set to an id of that class + 1, and -- the order_index values of the later classes are incremented by 1. -- The depend_on_known_indirectly column holds a boolean value indicating -- whether the given class depends on KNOWN/UKNOWN built-in classes -- via other classes, i.e. it depends on classes that directly or -- indirectly depend on these built-ins. This value is auto-generated -- by a trigger on the dhcp4_client_class_dependency table. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp4_client_class_order ( class_id BIGINT UNSIGNED NOT NULL, order_index BIGINT UNSIGNED NOT NULL, depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0', PRIMARY KEY (class_id), KEY key_dhcp4_client_class_order_index (order_index), CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id) REFERENCES dhcp4_client_class (id) ON DELETE CASCADE ) ENGINE=InnoDB; DROP TRIGGER IF EXISTS dhcp4_client_class_AINS; DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD; DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL; DROP PROCEDURE IF EXISTS setClientClass4Order; -- ----------------------------------------------------------------------- -- Stored procedure positioning an inserted or updated client class -- within the class hierarchy, depending on the value of the -- follow_class_name parameter. -- -- Parameters: -- - id id of the positioned class, -- - follow_class_name name of the class after which this class should be -- positioned within the class hierarchy. -- - old_follow_class_name previous name of the class after which this -- class was positioned within the class hierarchy. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE setClientClass4Order(IN id BIGINT UNSIGNED, IN follow_class_name VARCHAR(128), IN old_follow_class_name VARCHAR(128)) proc_label:BEGIN -- This variable will be optionally set if the follow_class_name -- column value is specified. DECLARE follow_class_index BIGINT UNSIGNED; DECLARE msg TEXT; -- Remember currently used value of depend_on_known_indirectly. SET @depend_on_known_indirectly = ( SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE id = class_id ); -- Bail if the class is updated without re-positioning. IF( @depend_on_known_indirectly IS NOT NULL AND ((follow_class_name IS NULL AND old_follow_class_name IS NULL) OR (follow_class_name = old_follow_class_name)) ) THEN -- The depend_on_known_indirectly is set to 0 because this procedure is invoked -- whenever the dhcp4_client_class record is updated. Such update may include -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes. -- This value will be later adjusted when dependencies are inserted. UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = 0 WHERE class_id = id; LEAVE proc_label; END IF; IF follow_class_name IS NOT NULL THEN -- Get the position of the class after which the new class should be added. SET follow_class_index = ( SELECT o.order_index FROM dhcp4_client_class AS c INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id WHERE c.name = follow_class_name ); IF follow_class_index IS NULL THEN -- The class with a name specified with follow_class_name does -- not exist. SET msg = CONCAT('Class ', follow_class_name, ' does not exist.'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg; END IF; -- We need to place the new class at the position of follow_class_index + 1. -- There may be a class at this position already. IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN -- There is a class at this position already. Let's move all classes -- starting from this position by one to create a spot for the new -- class. UPDATE dhcp4_client_class_order SET order_index = order_index + 1 WHERE order_index >= follow_class_index + 1 ORDER BY order_index DESC; END IF; ELSE -- A caller did not specify the follow_class_name value. Let's append the -- new class at the end of the hierarchy. SET follow_class_index = (SELECT MAX(order_index) FROM dhcp4_client_class_order); IF follow_class_index IS NULL THEN -- Apparently, there are no classes. Let's start from 0. SET follow_class_index = 0; END IF; END IF; -- Check if moving the class doesn't break dependent classes. IF EXISTS( SELECT 1 FROM dhcp4_client_class_dependency AS d INNER JOIN dhcp4_client_class_order AS o ON d.class_id = o.class_id WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1 LIMIT 1 ) THEN SET msg = CONCAT('Unable to move class with id ', id, ' because it would break its dependencies'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg; END IF; -- The depend_on_known_indirectly is set to 0 because this procedure is invoked -- whenever the dhcp4_client_class record is updated. Such update may include -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes. -- This value will be later adjusted when dependencies are inserted. REPLACE INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly) VALUES (id, follow_class_index + 1, 0); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger to position an inserted class within the class hierarchy -- and create audit. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp4_client_class_AINS AFTER INSERT ON dhcp4_client_class FOR EACH ROW BEGIN CALL setClientClass4Order(NEW.id, NEW.follow_class_name, NULL); CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "create"); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger to position an updated class within the class hierarchy, -- create audit and remember the direct dependency on the -- KNOWN/UNKNOWN built-in classes before the class update. -- When updating a client class, it is very important to ensure that -- its dependency on KNOWN or UNKNOWN built-in client classes is not -- changed. It is because there may be other classes that depend on -- these built-ins via this class. Changing the dependency would break -- the chain of dependencies for other classes. Here, we store the -- information about the dependency in the session variables. Their -- values will be compared with the new dependencies after an update. -- If they change, an error will be signaled. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp4_client_class_AUPD AFTER UPDATE ON dhcp4_client_class FOR EACH ROW BEGIN SET @depend_on_known_directly = OLD.depend_on_known_directly; SET @client_class_id = NEW.id; CALL setClientClass4Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name); CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "update"); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger to create dhcp4_client_class audit. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp4_client_class_ADEL AFTER DELETE ON dhcp4_client_class FOR EACH ROW BEGIN CALL createAuditEntryDHCP4('dhcp4_client_class', OLD.id, "delete"); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Create a table associating client classes stored in the -- dhcp4_client_class table with their dependencies. There is -- an M:N relationship between these tables. Each class may have -- many dependencies (created using member operator in test expression), -- and each class may be a dependency for many other classes. A caller -- is responsible for inserting dependencies for a class after inserting -- or updating it in the dhcp4_client_class table. A caller should -- delete all existing dependencies for an updated client class, evaluate -- test expression to discover new dependencies (in case test expression -- has changed), and insert new dependencies to this table. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency ( class_id BIGINT UNSIGNED NOT NULL, dependency_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (class_id,dependency_id), KEY dhcp4_client_class_dependency_id_idx (dependency_id), CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id) REFERENCES dhcp4_client_class (id) ON DELETE CASCADE, CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id) REFERENCES dhcp4_client_class (id) ) ENGINE=InnoDB; DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS; DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency; -- ----------------------------------------------------------------------- -- Stored procedure verifying if class dependency is met. It includes -- checking if referenced classes exist, are associated with the same -- server or all servers, and are defined before the class specified with -- class_id. -- -- Parameters: -- - class_id id client class, -- - dependency_id id of the dependency. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE checkDHCPv4ClientClassDependency(IN class_id BIGINT UNSIGNED, IN dependency_id BIGINT UNSIGNED) BEGIN DECLARE class_index BIGINT UNSIGNED; DECLARE dependency_index BIGINT UNSIGNED; DECLARE err_msg TEXT; -- We could check the same with a constraint but later in this -- trigger we use this value to verify if the dependencies are -- met. IF class_id IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Client class id must not be NULL.'; END IF; IF dependency_id IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Class dependency id must not be NULL.'; END IF; -- Dependencies on self make no sense. IF class_id = dependency_id THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Client class must not have dependency on self.'; END IF; -- Check position of our class in the hierarchy. SET class_index = ( SELECT o.order_index FROM dhcp4_client_class AS c INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id WHERE c.id = class_id); IF class_index IS NULL THEN SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; -- Check position of the dependency. SET dependency_index = ( SELECT o.order_index FROM dhcp4_client_class AS c INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id WHERE c.id = dependency_id ); IF dependency_index IS NULL THEN SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; -- The dependency must not be later than our class. IF dependency_index > class_index THEN SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; -- Check if all servers associated with the new class have dependent -- classes configured. This catches the cases that class A belongs to -- server1 and depends on class B which belongs only to server 2. -- It is fine if the class B belongs to all servers in this case. -- Make a SELECT on the dhcp4_client_class_server table to gather -- all servers to which the class belongs. LEFT JOIN it with the -- same table, selecting all records matching the dependency class -- and the servers to which the new class belongs. If there are -- any NULL records joined it implies that some dependencies are -- not met (didn't find a dependency for at least one server). IF EXISTS( SELECT 1 FROM dhcp4_client_class_server AS t1 LEFT JOIN dhcp4_client_class_server AS t2 ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id) WHERE t1.class_id = class_id AND t2.server_id IS NULL LIMIT 1 ) THEN SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger verifying if class dependency is met. It includes checking -- if referenced classes exist, are associated with the same server -- or all servers, and are defined before the class specified with -- class_id. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp4_client_class_check_dependency_BINS BEFORE INSERT ON dhcp4_client_class_dependency FOR EACH ROW BEGIN CALL checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id); END $$ DELIMITER ; DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS; DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency; -- ----------------------------------------------------------------------- -- Stored procedure setting client class indirect dependency on KNOWN or -- UNKNOWN built-in classes by checking this flag for the client classes -- on which it depends. -- -- Parameters: -- - client_class_id id of the client class which dependency is set, -- - dependency_id id of the client class on which the given class depends. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE updateDHCPv4ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED, IN dependency_id BIGINT UNSIGNED) BEGIN DECLARE dependency TINYINT; -- Check if the dependency class references KNOWN/UNKNOWN. SET dependency = ( SELECT depend_on_known_directly FROM dhcp4_client_class WHERE id = dependency_id ); -- If it doesn't, check if the dependency references KNOWN/UNKNOWN -- indirectly (via other classes). IF dependency = 0 THEN SET dependency = ( SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE class_id = dependency_id ); END IF; IF dependency <> 0 THEN UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = 1 WHERE class_id = client_class_id; END IF; END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger setting client class indirect dependency on KNOWN or UNKNOWN -- built-in classes by checking this flag for the client classes on which -- it depends. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp4_client_class_dependency_AINS AFTER INSERT ON dhcp4_client_class_dependency FOR EACH ROW BEGIN CALL updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id); END $$ DELIMITER ; DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange; -- ----------------------------------------------------------------------- -- Stored procedure to be executed before committing a transaction -- updating a DHCPv4 client class. It verifies if the class dependency on -- KNOWN or UNKNOWN built-in classes has changed as a result of the -- update. It signals an error if it has changed and there is at least -- one class depending on this class. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE checkDHCPv4ClientClassKnownDependencyChange() BEGIN DECLARE depended TINYINT DEFAULT 0; DECLARE depends TINYINT DEFAULT 0; -- Session variables are set upon a client class update. IF @client_class_id IS NOT NULL THEN -- Check if any of the classes depend on this class. If not, -- it is ok to change the dependency on KNOWN/UNKNOWN. IF EXISTS( SELECT 1 FROM dhcp4_client_class_dependency WHERE dependency_id = @client_class_id LIMIT 1 ) THEN -- Using the session variables, determine whether the client class -- depended on KNOWN/UNKNOWN before the update. IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN SET depended = 1; END IF; -- Check if the client class depends on KNOWN/UNKNOWN after the update. SET depends = ( SELECT depend_on_known_directly FROM dhcp4_client_class WHERE id = @client_class_id ); -- If it doesn't depend directly, check indirect dependencies. IF depends = 0 THEN SET depends = ( SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE class_id = @client_class_id ); END IF; -- The resulting dependency on KNOWN/UNKNOWN must not change. IF depended <> depends THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.'; END IF; END IF; END IF; END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Create table matching DHCPv4 classes with the servers. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp4_client_class_server ( class_id bigint unsigned NOT NULL, server_id bigint unsigned NOT NULL, modification_ts timestamp NULL DEFAULT NULL, PRIMARY KEY (class_id,server_id), KEY fk_dhcp4_client_class_server_id (server_id), CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id) REFERENCES dhcp4_client_class (id) ON DELETE CASCADE, CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id) REFERENCES dhcp4_server (id) ) ENGINE=InnoDB; -- ----------------------------------------------------------------------- -- Extend the table holding DHCPv4 option definitions with a nullable -- column matching option defintions with client classes. -- ----------------------------------------------------------------------- ALTER TABLE dhcp4_option_def ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL; ALTER TABLE dhcp4_option_def ADD CONSTRAINT fk_dhcp4_option_def_client_class_id FOREIGN KEY (class_id) REFERENCES dhcp4_client_class (id) ON DELETE CASCADE ON UPDATE CASCADE; -- ----------------------------------------------------------------------- -- Create a table holding the DHCPv6 client classes. Most table -- columns map directly to respective client class properties in -- Kea configuration. The depend_on_known_directly column is -- explicitly set in an insert or update statement to indicate -- if the client class directly depends on KNOWN or UNKNOWN -- built-in classes. A caller should determine it by evaluating -- a test expression before inserting or updating the client -- class in the database. The nullable follow_class_name column -- can be used for positioning the inserted or updated client -- class within the class hierarchy. Set this column value to -- an existing class name, after which this class should be -- placed in the class hierarchy. See dhcp6_client_class_order -- description for the details of how classes are ordered. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp6_client_class ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(128) NOT NULL, test TEXT, only_if_required TINYINT NOT NULL DEFAULT '0', valid_lifetime INT DEFAULT NULL, min_valid_lifetime INT DEFAULT NULL, max_valid_lifetime INT DEFAULT NULL, depend_on_known_directly TINYINT NOT NULL DEFAULT '0', follow_class_name VARCHAR(128) DEFAULT NULL, modification_ts TIMESTAMP NOT NULL, PRIMARY KEY (id), UNIQUE KEY id_UNIQUE (id), UNIQUE KEY name_UNIQUE (name), KEY key_dhcp6_client_class_modification_ts (modification_ts) ) ENGINE=InnoDB; -- ----------------------------------------------------------------------- -- Create a table for ordering client classes and holding information -- about indirect dependencies on KNOWN/UKNOWN built-in client classes. -- Each class in the dhcp6_client_class table has a corresponding row -- in the dhcp6_client_class_order table. A caller should not modify -- the contents of this table. Its entries are automatically created -- upon inserting or updating client classes in the dhcp6_client_classes -- using triggers. The order_index designates the position of the client -- class within the class hierarchy. If the follow_class_name value of -- the dhcp6_client_class table is set to NULL, the client class is -- appended at the end of the hierarchy. The assigned order_index -- value for that class is set to a maximum current value + 1. -- If the follow_client_class specifies a name of an existing class, -- the generated order_index is set to an id of that class + 1, and -- the order_index values of the later classes are incremented by 1. -- The depend_on_known_indirectly column holds a boolean value indicating -- whether the given class depends on KNOWN/UKNOWN built-in classes -- via other classes, i.e. it depends on classes that directly or -- indirectly depend on these built-ins. This value is auto-generated -- by a trigger on the dhcp6_client_class_dependency table. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp6_client_class_order ( class_id BIGINT UNSIGNED NOT NULL, order_index BIGINT UNSIGNED NOT NULL, depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0', PRIMARY KEY (class_id), KEY key_dhcp6_client_class_order_index (order_index), CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id) REFERENCES dhcp6_client_class (id) ON DELETE CASCADE ) ENGINE=InnoDB; DROP TRIGGER IF EXISTS dhcp6_client_class_AINS; DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD; DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL; DROP PROCEDURE IF EXISTS setClientClass6Order; -- ----------------------------------------------------------------------- -- Stored procedure positioning an inserted or updated client class -- within the class hierarchy, depending on the value of the -- follow_class_name parameter. -- -- Parameters: -- - id id of the positioned class, -- - follow_class_name name of the class after which this class should be -- positioned within the class hierarchy. -- - old_follow_class_name previous name of the class after which this -- class was positioned within the class hierarchy. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE setClientClass6Order(IN id BIGINT UNSIGNED, IN follow_class_name VARCHAR(128), IN old_follow_class_name VARCHAR(128)) proc_label:BEGIN -- This variable will be optionally set if the follow_class_name -- column value is specified. DECLARE follow_class_index BIGINT UNSIGNED; DECLARE msg TEXT; -- Remember currently used value of depend_on_known_indirectly. SET @depend_on_known_indirectly = ( SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE id = class_id ); -- Bail if the class is updated without re-positioning. IF( @depend_on_known_indirectly IS NOT NULL AND ((follow_class_name IS NULL AND old_follow_class_name IS NULL) OR (follow_class_name = old_follow_class_name)) ) THEN -- The depend_on_known_indirectly is set to 0 because this procedure is invoked -- whenever the dhcp6_client_class record is updated. Such update may include -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes. -- This value will be later adjusted when dependencies are inserted. UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = 0 WHERE class_id = id; LEAVE proc_label; END IF; IF follow_class_name IS NOT NULL THEN -- Get the position of the class after which the new class should be added. SET follow_class_index = ( SELECT o.order_index FROM dhcp6_client_class AS c INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id WHERE c.name = follow_class_name ); IF follow_class_index IS NULL THEN -- The class with a name specified with follow_class_name does -- not exist. SET msg = CONCAT('Class ', follow_class_name, ' does not exist.'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg; END IF; -- We need to place the new class at the position of follow_class_index + 1. -- There may be a class at this position already. IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN -- There is a class at this position already. Let's move all classes -- starting from this position by one to create a spot for the new -- class. UPDATE dhcp6_client_class_order SET order_index = order_index + 1 WHERE order_index >= follow_class_index + 1 ORDER BY order_index DESC; END IF; ELSE -- A caller did not specify the follow_class_name value. Let's append the -- new class at the end of the hierarchy. SET follow_class_index = (SELECT MAX(order_index) FROM dhcp6_client_class_order); IF follow_class_index IS NULL THEN -- Apparently, there are no classes. Let's start from 0. SET follow_class_index = 0; END IF; END IF; -- Check if moving the class doesn't break dependent classes. IF EXISTS( SELECT 1 FROM dhcp6_client_class_dependency AS d INNER JOIN dhcp6_client_class_order AS o ON d.class_id = o.class_id WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1 LIMIT 1 ) THEN SET msg = CONCAT('Unable to move class with id ', id, ' because it would break its dependencies'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg; END IF; -- The depend_on_known_indirectly is set to 0 because this procedure is invoked -- whenever the dhcp6_client_class record is updated. Such update may include -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes. -- This value will be later adjusted when dependencies are inserted. REPLACE INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly) VALUES (id, follow_class_index + 1, 0); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger to position an inserted class within the class hierarchy -- and create audit. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp6_client_class_AINS AFTER INSERT ON dhcp6_client_class FOR EACH ROW BEGIN CALL setClientClass6Order(NEW.id, NEW.follow_class_name, NULL); CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "create"); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger to position an updated class within the class hierarchy, -- create audit and remember the direct dependency on the -- KNOWN/UNKNOWN built-in classes before the class update. -- When updating a client class, it is very important to ensure that -- its dependency on KNOWN or UNKNOWN built-in client classes is not -- changed. It is because there may be other classes that depend on -- these built-ins via this class. Changing the dependency would break -- the chain of dependencies for other classes. Here, we store the -- information about the dependency in the session variables. Their -- values will be compared with the new dependencies after an update. -- If they change, an error will be signaled. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp6_client_class_AUPD AFTER UPDATE ON dhcp6_client_class FOR EACH ROW BEGIN SET @depend_on_known_directly = OLD.depend_on_known_directly; SET @client_class_id = NEW.id; CALL setClientClass6Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name); CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "update"); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger to create dhcp6_client_class audit. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp6_client_class_ADEL AFTER DELETE ON dhcp6_client_class FOR EACH ROW BEGIN CALL createAuditEntryDHCP6('dhcp6_client_class', OLD.id, "delete"); END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Create a table associating client classes stored in the -- dhcp6_client_class table with their dependencies. There is -- an M:N relationship between these tables. Each class may have -- many dependencies (created using member operator in test expression), -- and each class may be a dependency for many other classes. A caller -- is responsible for inserting dependencies for a class after inserting -- or updating it in the dhcp6_client_class table. A caller should -- delete all existing dependencies for an updated client class, evaluate -- test expression to discover new dependencies (in case test expression -- has changed), and insert new dependencies to this table. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency ( class_id BIGINT UNSIGNED NOT NULL, dependency_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (class_id,dependency_id), KEY dhcp6_client_class_dependency_id_idx (dependency_id), CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id) REFERENCES dhcp6_client_class (id) ON DELETE CASCADE, CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id) REFERENCES dhcp6_client_class (id) ) ENGINE=InnoDB; DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS; DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency; -- ----------------------------------------------------------------------- -- Stored procedure verifying if class dependency is met. It includes -- checking if referenced classes exist, are associated with the same -- server or all servers, and are defined before the class specified with -- class_id. -- -- Parameters: -- - class_id id client class, -- - dependency_id id of the dependency. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE checkDHCPv6ClientClassDependency(IN class_id BIGINT UNSIGNED, IN dependency_id BIGINT UNSIGNED) BEGIN DECLARE class_index BIGINT UNSIGNED; DECLARE dependency_index BIGINT UNSIGNED; DECLARE err_msg TEXT; -- We could check the same with a constraint but later in this -- trigger we use this value to verify if the dependencies are -- met. IF class_id IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Client class id must not be NULL.'; END IF; IF dependency_id IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Class dependency id must not be NULL.'; END IF; -- Dependencies on self make no sense. IF class_id = dependency_id THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Client class must not have dependency on self.'; END IF; -- Check position of our class in the hierarchy. SET class_index = ( SELECT o.order_index FROM dhcp6_client_class AS c INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id WHERE c.id = class_id); IF class_index IS NULL THEN SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; -- Check position of the dependency. SET dependency_index = ( SELECT o.order_index FROM dhcp6_client_class AS c INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id WHERE c.id = dependency_id ); IF dependency_index IS NULL THEN SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; -- The dependency must not be later than our class. IF dependency_index > class_index THEN SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; -- Check if all servers associated with the new class have dependent -- classes configured. This catches the cases that class A belongs to -- server1 and depends on class B which belongs only to server 2. -- It is fine if the class B belongs to all servers in this case. -- Make a SELECT on the dhcp6_client_class_server table to gather -- all servers to which the class belongs. LEFT JOIN it with the -- same table, selecting all records matching the dependency class -- and the servers to which the new class belongs. If there are -- any NULL records joined it implies that some dependencies are -- not met (didn't find a dependency for at least one server). IF EXISTS( SELECT 1 FROM dhcp6_client_class_server AS t1 LEFT JOIN dhcp6_client_class_server AS t2 ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id) WHERE t1.class_id = class_id AND t2.server_id IS NULL LIMIT 1 ) THEN SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg; END IF; END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger verifying if class dependency is met. It includes checking -- if referenced classes exist, are associated with the same server -- or all servers, and are defined before the class specified with -- class_id. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp6_client_class_check_dependency_BINS BEFORE INSERT ON dhcp6_client_class_dependency FOR EACH ROW BEGIN CALL checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id); END $$ DELIMITER ; DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS; DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency; -- ----------------------------------------------------------------------- -- Stored procedure setting client class indirect dependency on KNOWN or -- UNKNOWN built-in classes by checking this flag for the client classes -- on which it depends. -- -- Parameters: -- - client_class_id id of the client class which dependency is set, -- - dependency_id id of the client class on which the given class depends. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE updateDHCPv6ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED, IN dependency_id BIGINT UNSIGNED) BEGIN DECLARE dependency TINYINT; -- Check if the dependency class references KNOWN/UNKNOWN. SET dependency = ( SELECT depend_on_known_directly FROM dhcp6_client_class WHERE id = dependency_id ); -- If it doesn't, check if the dependency references KNOWN/UNKNOWN -- indirectly (via other classes). IF dependency = 0 THEN SET dependency = ( SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE class_id = dependency_id ); END IF; IF dependency <> 0 THEN UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = 1 WHERE class_id = client_class_id; END IF; END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Trigger setting client class indirect dependency on KNOWN or UNKNOWN -- built-in classes by checking this flag for the client classes on which -- it depends. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE TRIGGER dhcp6_client_class_dependency_AINS AFTER INSERT ON dhcp6_client_class_dependency FOR EACH ROW BEGIN CALL updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id); END $$ DELIMITER ; DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange; -- ----------------------------------------------------------------------- -- Stored procedure to be executed before committing a transaction -- updating a DHCPv6 client class. It verifies if the class dependency on -- KNOWN or UNKNOWN built-in classes has changed as a result of the -- update. It signals an error if it has changed and there is at least -- one class depending on this class. -- ----------------------------------------------------------------------- DELIMITER $$ CREATE PROCEDURE checkDHCPv6ClientClassKnownDependencyChange() BEGIN DECLARE depended TINYINT DEFAULT 0; DECLARE depends TINYINT DEFAULT 0; -- Session variables are set upon a client class update. IF @client_class_id IS NOT NULL THEN -- Check if any of the classes depend on this class. If not, -- it is ok to change the dependency on KNOWN/UNKNOWN. IF EXISTS( SELECT 1 FROM dhcp6_client_class_dependency WHERE dependency_id = @client_class_id LIMIT 1 ) THEN -- Using the session variables, determine whether the client class -- depended on KNOWN/UNKNOWN before the update. IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN SET depended = 1; END IF; -- Check if the client class depends on KNOWN/UNKNOWN after the update. SET depends = ( SELECT depend_on_known_directly FROM dhcp6_client_class WHERE id = @client_class_id ); -- If it doesn't depend directly, check indirect dependencies. IF depends = 0 THEN SET depends = ( SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE class_id = @client_class_id ); END IF; -- The resulting dependency on KNOWN/UNKNOWN must not change. IF depended <> depends THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.'; END IF; END IF; END IF; END $$ DELIMITER ; -- ----------------------------------------------------------------------- -- Create table matching DHCPv6 classes with the servers. -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS dhcp6_client_class_server ( class_id bigint unsigned NOT NULL, server_id bigint unsigned NOT NULL, modification_ts timestamp NULL DEFAULT NULL, PRIMARY KEY (class_id,server_id), KEY fk_dhcp6_client_class_server_id (server_id), CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id) REFERENCES dhcp6_client_class (id) ON DELETE CASCADE, CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ) ENGINE=InnoDB; -- ----------------------------------------------------------------------- -- Extend the table holding DHCPv6 option definitions with a nullable -- column matching option defintions with client classes. -- ----------------------------------------------------------------------- ALTER TABLE dhcp6_option_def ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL; ALTER TABLE dhcp6_option_def ADD CONSTRAINT fk_dhcp6_option_def_client_class_id FOREIGN KEY (class_id) REFERENCES dhcp6_client_class (id) ON DELETE CASCADE ON UPDATE CASCADE; # Update the schema version number. UPDATE schema_version SET version = '10', minor = '0'; # This line concludes the schema upgrade to version 10.0. EOF