[chmod +x src/share/database/scripts/pgsql/upgrade_007_to_008.sh])
AC_CONFIG_FILES([src/share/database/scripts/pgsql/upgrade_008_to_009.sh],
[chmod +x src/share/database/scripts/pgsql/upgrade_008_to_009.sh])
+AC_CONFIG_FILES([src/share/database/scripts/pgsql/upgrade_009_to_010.sh],
+ [chmod +x src/share/database/scripts/pgsql/upgrade_009_to_010.sh])
AC_CONFIG_FILES([src/share/database/scripts/pgsql/wipe_data.sh],
[chmod +x src/share/database/scripts/pgsql/wipe_data.sh])
AC_CONFIG_FILES([src/share/yang/Makefile])
run_command \
"${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}"
version="${OUTPUT}"
- assert_str_eq "9.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+ assert_str_eq "10.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
# Let's wipe the whole database
pgsql_wipe
run_command \
pgsql_execute "$session_sql"
- # The changes are not readily testable without querying the information schema,
- # not sure the effort is worthwhile. For now we'll just check the version.
+ # Most changes are not readily testable without querying the information schema,
+ # not sure the effort is worthwhile. Verify that function gmt_epoch() was created.
+ run_command \
+ pgsql_execute "select gmt_epoch(now());"
- # Verify that kea-admin db-version returns the correct version
+ assert_eq 0 "${EXIT_CODE}" "function gmt_epoch() broken or missing. (expected status code %d, returned %d)"
+
+}
+
+pgsql_upgrade_9_0_to_10_0() {
run_command \
- "${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}"
- version="${OUTPUT}"
- assert_str_eq "9.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+ pgsql_execute "$session_sql"
+
+ # Get function source code so we can check that it returns NEW.
+ # Function name must be lower case for WHERE clause.
+ run_command \
+ pgsql_execute "select proname,prosrc from pg_proc where proname='func_dhcp6_client_class_check_dependency_bins'"
+
+ assert_eq 0 "${EXIT_CODE}" '$cmd, expected exit code %d, actual %d'
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'RETURN NEW') || true
+ assert_eq 1 "${count}" "func_dhcp6_client_class_check_dependency_BINS is missing RETURN NEW. (expected count %d, returned %d)"
}
pgsql_upgrade_test() {
"${kea_admin}" db-upgrade pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
assert_eq 0 "${EXIT_CODE}" "db-upgrade failed, expected exit code: %d, actual: %d"
- # Verify upgraded schema reports version 9.0.
+ # Verify upgraded schema reports version 10.0.
version=$("${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
- assert_str_eq "9.0" "${version}" 'Expected kea-admin to return %s, returned value was %s'
+ assert_str_eq "10.0" "${version}" 'Expected kea-admin to return %s, returned value was %s'
# Check 1.0 to 2.0 upgrade
pgsql_upgrade_1_0_to_2_0
# Check 8.0 to 9.0 upgrade
pgsql_upgrade_8_0_to_9_0
+ # Check 9.0 to 10.0 upgrade
+ pgsql_upgrade_9_0_to_10_0
+
# Let's wipe the whole database
pgsql_wipe
/// @param selector Server selector.
/// @param client_class Pointer to the client_class the option belongs to.
/// @param option Pointer to the option descriptor encapsulating the option..
- void createUpdateOption4(const ServerSelector& /* server_selector */,
- const ClientClassDefPtr& /* client_class */,
- const OptionDescriptorPtr& /* option */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ void createUpdateOption4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const OptionDescriptorPtr& option) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ PsqlBindArray in_bindings;
+ std::string class_name = client_class->getName();
+ in_bindings.add(option->option_->getType());
+ addOptionValueBinding(in_bindings, option);
+ in_bindings.addOptional(option->formatted_value_);
+ in_bindings.addOptional(option->space_name_);
+ in_bindings.add(option->persistent_);
+ in_bindings.add(class_name);
+ in_bindings.addNull();
+ in_bindings.add(2);
+ in_bindings.add(option->getContext());
+ in_bindings.addNull();
+ in_bindings.addNull();
+ in_bindings.addTimestamp(option->getModificationTime());
+
+ // Remember the size before we added where clause arguments.
+ size_t pre_where_size = in_bindings.size();
+ in_bindings.add(class_name);
+ in_bindings.add(option->option_->getType());
+ in_bindings.addOptional(option->space_name_);
+
+ // Create scoped audit revision. As long as this instance exists
+ // no new audit revisions are created in any subsequent calls.
+ ScopedAuditRevision
+ audit_revision(this,
+ PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ server_selector, "client class specific option set",
+ true);
+
+ if (updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_CLIENT_CLASS,
+ in_bindings) == 0) {
+ // The option doesn't exist, so we'll try to insert it.
+ // Remove the update where clause bindings.
+ while (in_bindings.size() > pre_where_size) {
+ in_bindings.popBack();
+ }
+
+ insertOption4(server_selector, in_bindings, option->getModificationTime());
+ }
}
/// @brief Sends query to insert or update option definition.
/// @param server_selector Server selector.
/// @param option_def Pointer to the option definition to be inserted or updated.
/// @param client_class Client class name.
- void createUpdateOptionDef4(const ServerSelector& /* server_selector */,
- const OptionDefinitionPtr& /* option_def */,
- const std::string& /* client_class_name */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
- }
+ void createUpdateOptionDef4(const ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def,
+ const std::string& client_class_name) {
+ createUpdateOptionDef(server_selector, option_def, DHCP4_OPTION_SPACE,
+ PgSqlConfigBackendDHCPv4Impl::GET_OPTION_DEF4_CODE_SPACE,
+ PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_CLIENT_CLASS,
+ PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4_CLIENT_CLASS,
+ PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ PgSqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_SERVER,
+ client_class_name);
+ }
/// @brief Sends query to delete option definition by code and
/// option space name.
/// @param client_class Pointer to the client class for which options
/// should be deleted.
/// @return Number of deleted options.
- uint64_t deleteOptions4(const ServerSelector& /* server_selector */,
- const ClientClassDefPtr& /* client_class */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ uint64_t deleteOptions4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class) {
+ PsqlBindArray in_bindings;
+ in_bindings.addTempString(client_class->getName());
+
+ // Run DELETE.
+ return (deleteTransactional(PgSqlConfigBackendDHCPv4Impl::
+ DELETE_OPTIONS4_CLIENT_CLASS, server_selector,
+ "deleting options for a client class",
+ "client class specific options deleted",
+ true, in_bindings));
}
/// @brief Common function to retrieve client classes.
/// if the query contains no WHERE clause.
/// @param [out] client_classes Reference to a container where fetched client
/// classes will be inserted.
- void getClientClasses4(const StatementIndex& /* index */,
- const ServerSelector& /* server_selector */,
- const PsqlBindArray& /* in_bindings */,
- ClientClassDictionary& /* client_classes */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ void getClientClasses4(const StatementIndex& index,
+ const ServerSelector& server_selector,
+ const PsqlBindArray& in_bindings,
+ ClientClassDictionary& client_classes) {
+ std::list<ClientClassDefPtr> class_list;
+ uint64_t last_option_id = 0;
+ uint64_t last_option_def_id = 0;
+ std::string last_tag;
+
+ selectQuery(index, in_bindings,
+ [this, &class_list, &last_option_id, &last_option_def_id, &last_tag]
+ (PgSqlResult& r, int row) {
+ // Create a convenience worker for the row.
+ PgSqlResultRowWorker worker(r, row);
+
+ ClientClassDefPtr last_client_class;
+ if (!class_list.empty()) {
+ last_client_class = *class_list.rbegin();
+ }
+
+ // Class ID is column 0.
+ uint64_t id = worker.getBigInt(0) ;
+
+ if (!last_client_class || (last_client_class->getId() != id)) {
+ last_option_id = 0;
+ last_option_def_id = 0;
+ last_tag.clear();
+
+ auto options = boost::make_shared<CfgOption>();
+ auto option_defs = boost::make_shared<CfgOptionDef>();
+ auto expression = boost::make_shared<Expression>();
+
+ last_client_class = boost::make_shared<ClientClassDef>(worker.getString(1), expression, options);
+ last_client_class->setCfgOptionDef(option_defs);
+
+ // id
+ last_client_class->setId(id);
+
+ // name
+ last_client_class->setName(worker.getString(1));
+
+ // test
+ if (!worker.isColumnNull(2)) {
+ last_client_class->setTest(worker.getString(2));
+ }
+
+ // next server
+ if (!worker.isColumnNull(3)) {
+ last_client_class->setNextServer(worker.getInet4(3));
+ }
+
+ // sname
+ if (!worker.isColumnNull(4)) {
+ last_client_class->setSname(worker.getString(4));
+ }
+
+ // filename
+ if (!worker.isColumnNull(5)) {
+ last_client_class->setFilename(worker.getString(5));
+ }
+
+ // required
+ if (!worker.isColumnNull(6)) {
+ last_client_class->setRequired(worker.getBool(6));
+ }
+
+ // valid lifetime: default, min, max
+ last_client_class->setValid(worker.getTriplet(7, 8, 9));
+
+ // depend on known directly or indirectly
+ last_client_class->setDependOnKnown(worker.getBool(10) || worker.getBool(11));
+
+ // modification_ts
+ last_client_class->setModificationTime(worker.getTimestamp(12));
+
+ class_list.push_back(last_client_class);
+ }
+
+ // Check for new server tags at 35.
+ if (!worker.isColumnNull(35)) {
+ std::string new_tag = worker.getString(35);
+ if (last_tag != new_tag) {
+ if (!new_tag.empty() && !last_client_class->hasServerTag(ServerTag(new_tag))) {
+ last_client_class->setServerTag(new_tag);
+ }
+
+ last_tag = new_tag;
+ }
+ }
+
+ // Parse client class specific option definition from 13 to 22.
+ if (!worker.isColumnNull(13) &&
+ (last_option_def_id < worker.getBigInt(13))) {
+ last_option_def_id = worker.getBigInt(13);
+
+ auto def = processOptionDefRow(worker, 13);
+ if (def) {
+ last_client_class->getCfgOptionDef()->add(def);
+ }
+ }
+
+ // Parse client class specific option from 23 to 34.
+ if (!worker.isColumnNull(23) &&
+ (last_option_id < worker.getBigInt(23))) {
+ last_option_id = worker.getBigInt(23);
+
+ OptionDescriptorPtr desc = processOptionRow(Option::V4, worker, 23);
+ if (desc) {
+ last_client_class->getCfgOption()->add(*desc, desc->space_name_);
+ }
+ }
+ });
+
+ tossNonMatchingElements(server_selector, class_list);
+
+ for (auto c : class_list) {
+ client_classes.addClass(c);
+ }
}
/// @brief Sends query to retrieve a client class by name.
/// @param server_selector Server selector.
/// @param name Name of the class to be retrieved.
/// @return Pointer to the client class or null if the class is not found.
- ClientClassDefPtr getClientClass4(const ServerSelector& /* server_selector */,
- const std::string& /* name */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ ClientClassDefPtr getClientClass4(const ServerSelector& server_selector,
+ const std::string& name) {
+ PsqlBindArray in_bindings;
+ in_bindings.add(name);
+
+ ClientClassDictionary client_classes;
+ getClientClasses4(PgSqlConfigBackendDHCPv4Impl::GET_CLIENT_CLASS4_NAME,
+ server_selector, in_bindings, client_classes);
+ return (client_classes.getClasses()->empty() ? ClientClassDefPtr() :
+ (*client_classes.getClasses()->begin()));
}
/// @brief Sends query to retrieve all client classes.
/// @param server_selector Server selector.
/// @param [out] client_classes Reference to the client classes collection
/// where retrieved classes will be stored.
- void getAllClientClasses4(const ServerSelector& /* server_selector */,
- ClientClassDictionary& /* client_classes */) {
- /// @todo Rather than throw, we do nothing. This allows CB to be used for
- /// everything except classes.
- /// isc_throw(NotImplemented, NOT_IMPL_STR);
+ void getAllClientClasses4(const ServerSelector& server_selector,
+ ClientClassDictionary& client_classes) {
+ PsqlBindArray in_bindings;
+ getClientClasses4(server_selector.amUnassigned() ?
+ PgSqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4_UNASSIGNED :
+ PgSqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4,
+ server_selector, in_bindings, client_classes);
}
/// @brief Sends query to retrieve modified client classes.
/// @param modification_ts Lower bound modification timestamp.
/// @param [out] client_classes Reference to the client classes collection
/// where retrieved classes will be stored.
- void getModifiedClientClasses4(const ServerSelector& /* server_selector */,
- const boost::posix_time::ptime& /* modification_ts */,
- ClientClassDictionary& /* client_classes */) {
- /// @todo Rather than throw, we do nothing. This allows CB to be used for
- /// everything except classes.
- /// isc_throw(NotImplemented, NOT_IMPL_STR);
- }
+ void getModifiedClientClasses4(const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_ts,
+ ClientClassDictionary& client_classes) {
+ if (server_selector.amAny()) {
+ isc_throw(InvalidOperation, "fetching modified client classes for ANY "
+ "server is not supported");
+ }
+ PsqlBindArray in_bindings;
+ in_bindings.addTimestamp(modification_ts);
+ getClientClasses4(server_selector.amUnassigned() ?
+ PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED :
+ PgSqlConfigBackendDHCPv4Impl::GET_MODIFIED_CLIENT_CLASSES4,
+ server_selector, in_bindings, client_classes);
+ }
/// @brief Upserts client class.
///
/// new or updated class should be positioned. An empty value
/// causes the class to be appended at the end of the class
/// hierarchy.
- void createUpdateClientClass4(const ServerSelector& /* server_selector */,
- const ClientClassDefPtr& /* client_class */,
- const std::string& /* follow_class_name */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ void createUpdateClientClass4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ // We need to evaluate class expression to see if it references any
+ // other classes (dependencies). As part of this evaluation we will
+ // also check if the client class depends on KNOWN/UNKNOWN built-in
+ // classes.
+ std::list<std::string> dependencies;
+ bool depend_on_known = false;
+ if (!client_class->getTest().empty()) {
+ ExpressionPtr expression;
+ ExpressionParser parser;
+ // Parse the test expression. The callback function is normally used to
+ // interrupt config file parsing when one of the classes refers to a
+ // non-existing client class. It returns false in this case. Here,
+ // we use the callback to capture client classes referenced by the
+ // upserted client class and record whether this class depends on
+ // KNOWN/UNKNOWN built-ins. The callback always returns true to avoid
+ // reporting the parsing error. The dependency check is performed later
+ // at the database level.
+ parser.parse(expression, Element::create(client_class->getTest()), AF_INET,
+ [&dependencies, &depend_on_known](const ClientClass& client_class) -> bool {
+ if (isClientClassBuiltIn(client_class)) {
+ if ((client_class == "KNOWN") || (client_class == "UNKNOWN")) {
+ depend_on_known = true;
+ }
+ } else {
+ dependencies.push_back(client_class);
+ }
+ return (true);
+ });
+ }
+
+ PsqlBindArray in_bindings;
+ std::string class_name = client_class->getName();
+ in_bindings.add(class_name);
+ in_bindings.addTempString(client_class->getTest());
+ in_bindings.addInet4(client_class->getNextServer());
+ in_bindings.addTempString(client_class->getSname());
+ in_bindings.addTempString(client_class->getFilename());
+ in_bindings.add(client_class->getRequired());
+ in_bindings.add(client_class->getValid());
+ in_bindings.add(client_class->getValid().getMin());
+ in_bindings.add(client_class->getValid().getMax());
+ in_bindings.add(depend_on_known);
+
+ // 11
+ if (follow_class_name.empty()) {
+ in_bindings.addNull();
+ } else {
+ in_bindings.add(follow_class_name);
+ }
+
+ in_bindings.addTimestamp(client_class->getModificationTime());
+
+ PgSqlTransaction transaction(conn_);
+
+ ScopedAuditRevision audit_revision(this, PgSqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ server_selector, "client class set", true);
+
+ // Create a savepoint in case we are called as part of larger
+ // transaction.
+ conn_.createSavepoint("createUpdateClass4");
+
+ // Keeps track of whether the client class is inserted or updated.
+ auto update = false;
+ try {
+ insertQuery(PgSqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4, in_bindings);
+ } catch (const DuplicateEntry&) {
+ // It already exists, rollback to the savepoint to preserve
+ // any prior work.
+ conn_.rollbackToSavepoint("createUpdateClass4");
+
+ // Delete options and option definitions. They will be re-created from the new class
+ // instance.
+ deleteOptions4(ServerSelector::ANY(), client_class);
+ deleteOptionDefs4(ServerSelector::ANY(), client_class);
+
+ if (follow_class_name.empty()) {
+ // leave follow name on there, SQL ignores it
+ // in_bindings.popBack();
+
+ // Add the class name for the where clause.
+ in_bindings.add(class_name);
+ updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4_SAME_POSITION,
+ in_bindings);
+ } else {
+ // Update with follow_class_name specifying the position.
+ // Add the class name for the where clause.
+ in_bindings.add(class_name);
+ updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4,
+ in_bindings);
+ }
+
+ // Delete class associations with the servers and dependencies. We will re-create
+ // them according to the new class specification.
+ PsqlBindArray in_assoc_bindings;
+ in_assoc_bindings.add(class_name);
+ updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_DEPENDENCY,
+ in_assoc_bindings);
+ updateDeleteQuery(PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_SERVER,
+ in_assoc_bindings);
+ update = true;
+ }
+
+ // Associate client class with the servers.
+ PsqlBindArray attach_bindings;
+ attach_bindings.add(class_name);
+ attach_bindings.addTimestamp(client_class->getModificationTime());
+
+ attachElementToServers(PgSqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_SERVER,
+ server_selector, attach_bindings);
+
+ // Iterate over the captured dependencies and try to insert them into the database.
+ for (auto dependency : dependencies) {
+ try {
+ PsqlBindArray in_dependency_bindings;
+ in_dependency_bindings.add(class_name);
+ in_dependency_bindings.add(dependency);
+
+ // We deleted earlier dependencies, so we can simply insert new ones.
+ insertQuery(PgSqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_DEPENDENCY,
+ in_dependency_bindings);
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidOperation, "unmet dependency on client class: " << dependency);
+ }
+ }
+
+ // If we performed client class update we also have to verify that its dependency
+ // on KNOWN/UNKNOWN client classes hasn't changed.
+ if (update) {
+ PsqlBindArray in_check_bindings;
+ insertQuery(PgSqlConfigBackendDHCPv4Impl::CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE,
+ in_check_bindings);
+ }
+
+ // (Re)create option definitions.
+ if (client_class->getCfgOptionDef()) {
+ auto option_defs = client_class->getCfgOptionDef()->getContainer();
+ auto option_spaces = option_defs.getOptionSpaceNames();
+ for (auto option_space : option_spaces) {
+ OptionDefContainerPtr defs = option_defs.getItems(option_space);
+ for (auto def = defs->begin(); def != defs->end(); ++def) {
+ createUpdateOptionDef4(server_selector, *def, client_class->getName());
+ }
+ }
+ }
+
+ // (Re)create options.
+ auto option_spaces = client_class->getCfgOption()->getOptionSpaceNames();
+ for (auto option_space : option_spaces) {
+ OptionContainerPtr options = client_class->getCfgOption()->getAll(option_space);
+ for (auto desc = options->begin(); desc != options->end(); ++desc) {
+ OptionDescriptorPtr desc_copy = OptionDescriptor::create(*desc);
+ desc_copy->space_name_ = option_space;
+ createUpdateOption4(server_selector, client_class, desc_copy);
+ }
+ }
+
+ // All ok. Commit the transaction.
+ transaction.commit();
}
/// @brief Removes client class by name.
/// @param server_selector Server selector.
/// @param name Removed client class name.
/// @return Number of deleted client classes.
- uint64_t deleteClientClass4(const ServerSelector& /* server_selector */,
- const std::string& /* name */) {
- isc_throw(NotImplemented, NOT_IMPL_STR);
+ uint64_t deleteClientClass4(const ServerSelector& server_selector,
+ const std::string& name) {
+ int index = server_selector.amAny() ?
+ PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_ANY :
+ PgSqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4;
+
+ uint64_t result = deleteTransactional(index, server_selector,
+ "deleting client class",
+ "client class deleted",
+ true,
+ name);
+ return (result);
}
/// @brief Removes unassigned global parameters, global options and
OID_INT8, // 8 min_valid_lifetime
OID_INT8, // 9 max_valid_lifetime
OID_BOOL, // 10 depend_on_known_directly
- OID_TIMESTAMP, // 11 modification_ts
- OID_VARCHAR, // 12 name (of class to update)
- OID_VARCHAR // 13 follow_class_name
+ OID_VARCHAR, // 11 follow_class_name
+ OID_TIMESTAMP, // 12 modification_ts
+ OID_VARCHAR // 13 name (of class to update)
},
"UPDATE_CLIENT_CLASS4",
- PGSQL_UPDATE_CLIENT_CLASS4("follow_class_name = $13,")
+ PGSQL_UPDATE_CLIENT_CLASS4("follow_class_name = $11,")
},
// Update existing client class without specifying its position.
{
// PgSqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4_SAME_POSITION,
- 12,
+ 13,
{
OID_VARCHAR, // 1 name
OID_TEXT, // 2 test
OID_INT8, // 8 min_valid_lifetime
OID_INT8, // 9 max_valid_lifetime
OID_BOOL, // 10 depend_on_known_directly
- OID_TIMESTAMP, // 11 modification_ts
- OID_VARCHAR // 12 name (of class to update)
+ OID_VARCHAR, // 11 filler for follow_class_name
+ OID_TIMESTAMP, // 12 modification_ts
+ OID_VARCHAR // 13 name (of class to update)
},
"UPDATE_CLIENT_CLASS4_SAME_POSITION",
PGSQL_UPDATE_CLIENT_CLASS4("")
" is_array = $6," \
" encapsulate = $7," \
" record_types = $8," \
- " user_context = cast($9 as json)" \
+ " user_context = cast($9 as json) " \
"FROM " #table_prefix "_option_def_server as a, " \
" " #table_prefix "_server as s " \
"WHERE d.id = a.option_def_id AND " \
- " a.server_id = s.id AND " \
- " d.class_id = (SELECT id FROM dhcp4_client_class WHERE name = $10)"
+ " a.server_id = s.id AND " \
+ " d.class_id = (SELECT id FROM dhcp4_client_class WHERE name = $10) " \
+ " AND s.tag = $11 AND d.code = $12 AND d.space = $13"
+
#endif
" max_valid_lifetime = $9," \
" depend_on_known_directly = $10," \
follow_class_name_set \
- " modification_ts = $11 " \
- "WHERE name = $12"
+ " modification_ts = $12 " \
+ "WHERE name = $13"
#endif
#ifndef PGSQL_UPDATE_CLIENT_CLASS6
sharedNetworkOptionIdOrderTest();
}
+TEST_F(PgSqlConfigBackendDHCPv4Test, setAndGetAllClientClasses4Test) {
+ setAndGetAllClientClasses4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, getClientClass4Test) {
+ getClientClass4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateClientClass4OptionsTest) {
+ createUpdateClientClass4OptionsTest();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, getModifiedClientClasses4Test) {
+ getModifiedClientClasses4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, deleteClientClass4Test) {
+ deleteClientClass4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, deleteAllClientClasses4Test) {
+ deleteAllClientClasses4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, clientClassDependencies4Test) {
+ clientClassDependencies4Test();
+}
+
+TEST_F(PgSqlConfigBackendDHCPv4Test, multipleAuditEntriesTest) {
+ multipleAuditEntriesTest();
+}
+
/// @brief Test fixture for verifying database connection loss-recovery
/// behavior.
class PgSqlConfigBackendDHCPv4DbLostCallbackTest : public GenericConfigBackendDbLostCallbackTest {
namespace isc {
namespace db {
-/// @brief Define PostgreSQL backend version: 9.0
-const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 9;
+/// @brief Define PostgreSQL backend version: 10.0
+const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 10;
const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
// Maximum number of parameters that can be used a statement
pgsql_SCRIPTS += upgrade_006.2_to_007.0.sh
pgsql_SCRIPTS += upgrade_007_to_008.sh
pgsql_SCRIPTS += upgrade_008_to_009.sh
+pgsql_SCRIPTS += upgrade_009_to_010.sh
pgsql_SCRIPTS += wipe_data.sh
DISTCLEANFILES = ${pgsql_SCRIPTS}
END;$$
LANGUAGE plpgsql;
+-- Schema 9.0 specification ends here.
+
+-- This starts schema update to 10.0.
+-- It adds corrections for client classes for CB
+
+-- Replace setClientClass4Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+-- 2. follow_class_index needs to be BIGINT
+
+-- -----------------------------------------------------------------------
+-- 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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass4Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp4_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_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.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ 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;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp4_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ 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
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ 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.
+ -- ON CONFLICT required 9.5 or later
+ UPDATE dhcp4_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;$$;
+
+-- Replace setClientClass4Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass6Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp6_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_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.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ 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;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp6_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ 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
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ 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.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp6_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;$$;
+
+-- Change primary key to composite, dependency table can have multiple rows
+-- per class id.
+ALTER TABLE dhcp4_client_class_dependency DROP CONSTRAINT dhcp4_client_class_dependency_pkey;
+ALTER TABLE dhcp4_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+ALTER TABLE dhcp6_client_class_dependency DROP CONSTRAINT dhcp6_client_class_dependency_pkey;
+ALTER TABLE dhcp6_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+-- Replace triggers that verify class dependency.
+-- Because they are BEFORE INSERT triggers they need to return NEW not NULL.
+-- -----------------------------------------------------------------------
+-- 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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_check_dependency_BINS()
+ RETURNS trigger AS $dhcp4_client_class_check_dependency_BINS$
+BEGIN
+ PERFORM checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+$dhcp4_client_class_check_dependency_BINS$
+LANGUAGE plpgsql;
+
+-- -----------------------------------------------------------------------
+-- 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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_check_dependency_BINS()
+ RETURNS trigger AS $dhcp6_client_class_check_dependency_BINS$
+BEGIN
+ PERFORM checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+$dhcp6_client_class_check_dependency_BINS$
+LANGUAGE plpgsql;
+
-- Update the schema version number.
UPDATE schema_version
- SET version = '9', minor = '0';
+ SET version = '10', minor = '0';
-- Commit the script transaction.
COMMIT;
--- /dev/null
+#!/bin/sh
+
+# Copyright (C) 2022 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# 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="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "9.0" ]; then
+ printf 'This script upgrades 9.0 to 10.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This starts schema update to 10.0.
+-- It adds corrections for client classes for CB
+
+-- Replace setClientClass4Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+-- 2. follow_class_index needs to be BIGINT
+
+-- -----------------------------------------------------------------------
+-- 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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass4Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp4_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_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.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ 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;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp4_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ 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
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ 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.
+ -- ON CONFLICT required 9.5 or later
+ UPDATE dhcp4_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;\$\$;
+
+-- Replace setClientClass4Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass6Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp6_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_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.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ 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;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp6_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ 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
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ 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.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp6_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;\$\$;
+
+-- Change primary key to composite, dependency table can have multiple rows
+-- per class id.
+ALTER TABLE dhcp4_client_class_dependency DROP CONSTRAINT dhcp4_client_class_dependency_pkey;
+ALTER TABLE dhcp4_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+ALTER TABLE dhcp6_client_class_dependency DROP CONSTRAINT dhcp6_client_class_dependency_pkey;
+ALTER TABLE dhcp6_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+-- Replace triggers that verify class dependency.
+-- Because they are BEFORE INSERT triggers they need to return NEW not NULL.
+-- -----------------------------------------------------------------------
+-- 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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_check_dependency_BINS()
+ RETURNS trigger AS \$dhcp4_client_class_check_dependency_BINS\$
+BEGIN
+ PERFORM checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+\$dhcp4_client_class_check_dependency_BINS\$
+LANGUAGE plpgsql;
+
+-- -----------------------------------------------------------------------
+-- 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.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_check_dependency_BINS()
+ RETURNS trigger AS \$dhcp6_client_class_check_dependency_BINS\$
+BEGIN
+ PERFORM checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+\$dhcp6_client_class_check_dependency_BINS\$
+LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF