--- /dev/null
+// Copyright (C) 2018-2021 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/.
+
+#include <config.h>
+#include "pgsql_cb_impl.h"
+#include <asiolink/io_address.h>
+#include <config_backend/constants.h>
+#include <pgsql/pgsql_exchange.h>
+#include <dhcp/option_space.h>
+#include <util/buffer.h>
+#include <cstdint>
+#include <utility>
+
+using namespace isc::asiolink;
+using namespace isc::cb;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr();
+
+PgSqlConfigBackendImpl::
+ScopedAuditRevision::ScopedAuditRevision(PgSqlConfigBackendImpl* impl,
+ const int index,
+ const ServerSelector& server_selector,
+ const std::string& log_message,
+ bool cascade_transaction)
+ : impl_(impl) {
+ impl_->createAuditRevision(index, server_selector,
+ boost::posix_time::microsec_clock::local_time(),
+ log_message,
+ cascade_transaction);
+}
+
+PgSqlConfigBackendImpl::
+ScopedAuditRevision::~ScopedAuditRevision() {
+ impl_->clearAuditRevision();
+}
+
+PgSqlConfigBackendImpl::
+PgSqlConfigBackendImpl(const DatabaseConnection::ParameterMap& parameters,
+ const DbCallback db_reconnect_callback)
+ : conn_(parameters,
+ IOServiceAccessorPtr(new IOServiceAccessor(PgSqlConfigBackendImpl::getIOService)),
+ db_reconnect_callback), timer_name_(""),
+ audit_revision_created_(false), parameters_(parameters) {
+ // Test schema version first.
+ std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
+ PG_SCHEMA_VERSION_MINOR);
+ std::pair<uint32_t, uint32_t> db_version =
+ PgSqlConnection::getVersion(parameters);
+ if (code_version != db_version) {
+ isc_throw(DbOpenError, "Postgres schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
+ }
+
+ // Open the database.
+ conn_.openDatabase();
+}
+
+PgSqlConfigBackendImpl::~PgSqlConfigBackendImpl() {
+ /// nothing to do there. The conn_ connection will be deleted and its dtor
+ /// will take care of releasing the compiled statements and similar.
+}
+
+#if 0
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createBinding(const Triplet<uint32_t>& triplet) {
+ PsqlBindArrayPtr bind(new PsqlBindArray());
+
+ if (triplet.unspecified()) {
+ bind->addNull();
+ } else {
+ bind->add<uint32_t>(triplet.get());
+ }
+ return (bind);
+}
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createMinBinding(const Triplet<uint32_t>& triplet) {
+ PsqlBindArrayPtr bind(new PsqlBindArray());
+ if (triplet.unspecified() || (triplet.getMin() == triplet.get())) {
+ bind->addNull();
+ } else {
+ bind->add<uint32_t>(triplet.getMin());
+ }
+ return (bind);
+}
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createMaxBinding(const Triplet<uint32_t>& triplet) {
+ PsqlBindArrayPtr bind(new PsqlBindArray());
+ if (triplet.unspecified() || (triplet.getMax() == triplet.get())) {
+ bind->addNull();
+ } else {
+ bind->add<uint32_t>(triplet.getMax());
+ }
+ return (bind);
+}
+#endif
+
+/* Triplet<uint32_t>
+PgSqlConfigBackendImpl::createTriplet(const PsqlBindArrayPtr& binding) {
+ if (!binding) {
+ isc_throw(Unexpected, "Postgres configuration backend internal error: "
+ "binding pointer is NULL when creating a triplet value");
+ }
+
+ if (binding->empty()) {
+ return (Triplet<uint32_t>());
+ }
+
+ return (Triplet<uint32_t>(binding->getInteger<uint32_t>()));
+} */
+
+/* Triplet<uint32_t>
+PgSqlConfigBackendImpl::createTriplet(const PsqlBindArrayPtr& def_binding,
+ const PsqlBindArrayPtr& min_binding,
+ const PsqlBindArrayPtr& max_binding) {
+ if (!def_binding || !min_binding || !max_binding) {
+ isc_throw(Unexpected, "Postgres configuration backend internal error: "
+ "binding pointer is NULL when creating a triplet value");
+ }
+
+ // This code assumes the database was filled using the API, e.g. it
+ // is not possible (so not handled) to have only the min_binding not NULL.
+ if (def_binding->empty()) {
+ return (Triplet<uint32_t>());
+ }
+
+ uint32_t value = def_binding->getInteger<uint32_t>();
+ uint32_t min_value = value;
+ if (!min_binding->empty()) {
+ min_value = min_binding->getInteger<uint32_t>();
+ }
+ uint32_t max_value = value;
+ if (!max_binding->empty()) {
+ max_value = max_binding->getInteger<uint32_t>();
+ }
+
+ return (Triplet<uint32_t>(min_value, value, max_value));
+} */
+
+void
+PgSqlConfigBackendImpl::createAuditRevision(const int index,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& audit_ts,
+ const std::string& log_message,
+ const bool cascade_transaction) {
+ // Do not touch existing audit revision in case of the cascade update.
+ if (audit_revision_created_) {
+ return;
+ }
+
+ /// @todo The audit trail is not really well prepared to handle multiple server
+ /// tags or no server tags. Therefore, if the server selector appears to be
+ /// pointing to multiple servers, no servers or any server we simply associate the
+ /// audit revision with all servers. The only case when we create a dedicated
+ /// audit entry is when there is a single server tag, i.e. "all" or explicit
+ /// server name. In fact, these are the most common two cases.
+ std::string tag = ServerTag::ALL;
+ auto tags = server_selector.getTags();
+ if (tags.size() == 1) {
+ tag = tags.begin()->get();
+ }
+
+#if 0
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createTimestamp(audit_ts),
+ PsqlBindArray::createString(tag),
+ PsqlBindArray::createString(log_message),
+ PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(cascade_transaction))
+ };
+ conn_.insertQuery(index, in_bindings);
+ audit_revision_created_ = true;
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::clearAuditRevision() {
+ audit_revision_created_ = false;
+}
+
+void
+PgSqlConfigBackendImpl::getRecentAuditEntries(const int index,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id,
+ AuditEntryCollection& audit_entries) {
+ isc_throw(NotImplemented, "");
+}
+
+uint64_t
+PgSqlConfigBackendImpl::deleteFromTable(const int index,
+ const ServerSelector& server_selector,
+ const std::string& operation) {
+ // When deleting multiple objects we must not use ANY server.
+ if (server_selector.amAny()) {
+ isc_throw(InvalidOperation, "deleting multiple objects for ANY server is not"
+ " supported");
+ }
+
+ PsqlBindArray in_bindings;
+ return (deleteFromTable(index, server_selector, operation, in_bindings));
+}
+
+void
+PgSqlConfigBackendImpl::getGlobalParameters(const int index,
+ const PsqlBindArray& in_bindings,
+ StampedValueCollection& parameters) {
+#if 0
+ // The following parameters from the dhcp[46]_global_parameter table are
+ // returned:
+ // - id
+ // - name - parameter name
+ // - value - parameter value
+ // - modification_ts - modification timestamp.
+ PsqlBindArray out_bindings = {
+ PsqlBindArray::createInteger<uint64_t>(), // id
+ PsqlBindArray::createString(GLOBAL_PARAMETER_NAME_BUF_LENGTH), // name
+ PsqlBindArray::createString(GLOBAL_PARAMETER_VALUE_BUF_LENGTH), // value
+ PsqlBindArray::createInteger<uint8_t>(), // parameter_type
+ PsqlBindArray::createTimestamp(), // modification_ts
+ PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH) // server_tag
+ };
+
+ StampedValuePtr last_param;
+
+ StampedValueCollection local_parameters;
+
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&last_param, &local_parameters]
+ (PsqlBindArray& out_bindings) {
+
+ uint64_t id = out_bindings[0]->getInteger<uint64_t>();
+
+ // If we're starting or if this is new parameter being processed...
+ if (!last_param || (last_param->getId() != id)) {
+
+ // parameter name
+ std::string name = out_bindings[1]->getString();
+
+ if (!name.empty()) {
+ last_param = StampedValue::create(name,
+ out_bindings[2]->getString(),
+ static_cast<Element::types>
+ (out_bindings[3]->getInteger<uint8_t>()));
+
+ // id
+ last_param->setId(id);
+
+ // modification_ts
+ last_param->setModificationTime(out_bindings[4]->getTimestamp());
+
+ // server_tag
+ ServerTag last_param_server_tag(out_bindings[5]->getString());
+ last_param->setServerTag(last_param_server_tag.get());
+ // If we're fetching parameters for a given server (explicit server
+ // tag is provided), it takes precedence over the same parameter
+ // specified for all servers. Therefore, we check if the given
+ // parameter already exists and belongs to 'all'.
+ auto& index = local_parameters.get<StampedValueNameIndexTag>();
+ auto existing = index.find(name);
+ if (existing != index.end()) {
+ // This parameter was already fetched. Let's check if we should
+ // replace it or not.
+ if (!last_param_server_tag.amAll() && (*existing)->hasAllServerTag()) {
+ // Replace parameter specified for 'all' with the one associated
+ // with the particular server tag.
+ local_parameters.replace(existing, last_param);
+ return;
+ }
+
+ }
+
+ // If there is no such parameter yet or the existing parameter
+ // belongs to a different server and the inserted parameter is
+ // not for all servers.
+ if ((existing == index.end()) ||
+ (!(*existing)->hasServerTag(last_param_server_tag) &&
+ !last_param_server_tag.amAll())) {
+ local_parameters.insert(last_param);
+ }
+ }
+ }
+ });
+
+ parameters.insert(local_parameters.begin(), local_parameters.end());
+#endif
+}
+
+OptionDefinitionPtr
+PgSqlConfigBackendImpl::getOptionDef(const int index,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ auto tag = getServerTag(server_selector, "fetching option definition");
+
+ OptionDefContainer option_defs;
+#if 0
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createString(tag),
+ PsqlBindArray::createInteger<uint16_t>(code),
+ PsqlBindArray::createString(space)
+ };
+ getOptionDefs(index, in_bindings, option_defs);
+#endif
+ return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin());
+}
+
+void
+PgSqlConfigBackendImpl::getAllOptionDefs(const int index,
+ const ServerSelector& server_selector,
+ OptionDefContainer& option_defs) {
+ auto tags = server_selector.getTags();
+#if 0
+ for (auto tag : tags) {
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createString(tag.get())
+ };
+ getOptionDefs(index, in_bindings, option_defs);
+ }
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::getModifiedOptionDefs(const int index,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ OptionDefContainer& option_defs) {
+ auto tags = server_selector.getTags();
+#if 0
+ for (auto tag : tags) {
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createString(tag.get()),
+ PsqlBindArray::createTimestamp(modification_time)
+ };
+ getOptionDefs(index, in_bindings, option_defs);
+ }
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::getOptionDefs(const int index,
+ const PsqlBindArray& in_bindings,
+ OptionDefContainer& option_defs) {
+ // Create output bindings. The order must match that in the prepared
+ // statement.
+#if 0
+ PsqlBindArray out_bindings = {
+ PsqlBindArray::createInteger<uint64_t>(), // id
+ PsqlBindArray::createInteger<uint16_t>(), // code
+ PsqlBindArray::createString(OPTION_NAME_BUF_LENGTH), // name
+ PsqlBindArray::createString(OPTION_SPACE_BUF_LENGTH), // space
+ PsqlBindArray::createInteger<uint8_t>(), // type
+ PsqlBindArray::createTimestamp(), // modification_ts
+ PsqlBindArray::createInteger<uint8_t>(), // array
+ PsqlBindArray::createString(OPTION_ENCAPSULATE_BUF_LENGTH), // encapsulate
+ PsqlBindArray::createString(OPTION_RECORD_TYPES_BUF_LENGTH), // record_types
+ PsqlBindArray::createString(USER_CONTEXT_BUF_LENGTH), // user_context
+ PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH) // server_tag
+ };
+#endif
+
+ uint64_t last_def_id = 0;
+
+ OptionDefContainer local_option_defs;
+
+#if 0
+ // Run select query.
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&local_option_defs, &last_def_id]
+ (PsqlBindArray& out_bindings) {
+ // Get pointer to last fetched option definition.
+ OptionDefinitionPtr last_def;
+ if (!local_option_defs.empty()) {
+ last_def = *local_option_defs.rbegin();
+ }
+
+ // See if the last fetched definition is the one for which we now got
+ // the row of data. If not, it means that we need to create new option
+ // definition.
+ if ((last_def_id == 0) ||
+ (last_def_id != out_bindings[0]->getInteger<uint64_t>())) {
+
+ last_def_id = out_bindings[0]->getInteger<uint64_t>();
+
+ // Check array type, because depending on this value we have to use
+ // different constructor.
+ bool array_type = static_cast<bool>(out_bindings[6]->getInteger<uint8_t>());
+ if (array_type) {
+ // Create array option.
+ last_def = OptionDefinition::create(out_bindings[2]->getString(),
+ out_bindings[1]->getInteger<uint16_t>(),
+ out_bindings[3]->getString(),
+ static_cast<OptionDataType>
+ (out_bindings[4]->getInteger<uint8_t>()),
+ array_type);
+ } else {
+ // Create non-array option.
+ last_def = OptionDefinition::create(out_bindings[2]->getString(),
+ out_bindings[1]->getInteger<uint16_t>(),
+ out_bindings[3]->getString(),
+ static_cast<OptionDataType>
+ (out_bindings[4]->getInteger<uint8_t>()),
+ out_bindings[7]->getStringOrDefault("").c_str());
+ }
+
+ // id
+ last_def->setId(last_def_id);
+
+ // record_types
+ ElementPtr record_types_element = out_bindings[8]->getJSON();
+ if (record_types_element) {
+ if (record_types_element->getType() != Element::list) {
+ isc_throw(BadValue, "invalid record_types value "
+ << out_bindings[8]->getString());
+ }
+ // This element must contain a list of integers specifying
+ // types of the record fields.
+ for (auto i = 0; i < record_types_element->size(); ++i) {
+ auto type_element = record_types_element->get(i);
+ if (type_element->getType() != Element::integer) {
+ isc_throw(BadValue, "record type values must be integers");
+ }
+ last_def->addRecordField(static_cast<OptionDataType>
+ (type_element->intValue()));
+ }
+ }
+
+ // Update modification time.
+ last_def->setModificationTime(out_bindings[5]->getTimestamp());
+
+ // server_tag
+ ServerTag last_def_server_tag(out_bindings[10]->getString());
+ last_def->setServerTag(last_def_server_tag.get());
+
+ // If we're fetching option definitions for a given server
+ // (explicit server tag is provided), it takes precedence over
+ // the same option definition specified for all servers.
+ // Therefore, we check if the given option already exists and
+ // belongs to 'all'.
+ auto& index = local_option_defs.get<1>();
+ auto existing_it_pair = index.equal_range(last_def->getCode());
+ auto existing_it = existing_it_pair.first;
+ bool found = false;
+ for ( ; existing_it != existing_it_pair.second; ++existing_it) {
+ if ((*existing_it)->getOptionSpaceName() == last_def->getOptionSpaceName()) {
+ found = true;
+ // This option definition was already fetched. Let's check
+ // if we should replace it or not.
+ if (!last_def_server_tag.amAll() && (*existing_it)->hasAllServerTag()) {
+ index.replace(existing_it, last_def);
+ return;
+ }
+ break;
+ }
+ }
+
+ // If there is no such option definition yet or the existing option
+ // definition belongs to a different server and the inserted option
+ // definition is not for all servers.
+ if (!found ||
+ (!(*existing_it)->hasServerTag(last_def_server_tag) &&
+ !last_def_server_tag.amAll())) {
+ static_cast<void>(local_option_defs.push_back(last_def));
+ }
+ }
+ });
+
+ // Append the option definition fetched by this function into the container
+ // supplied by the caller. The container supplied by the caller may already
+ // hold some option definitions fetched for other server tags.
+ option_defs.insert(option_defs.end(), local_option_defs.begin(),
+ local_option_defs.end());
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def,
+ const std::string& /*space*/,
+ const int& /*get_option_def_code_space*/,
+ const int& insert_option_def,
+ const int& update_option_def,
+ const int& create_audit_revision,
+ const int& insert_option_def_server) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ auto tag = getServerTag(server_selector, "creating or updating option definition");
+
+ ElementPtr record_types = Element::createList();
+ for (auto field : option_def->getRecordFields()) {
+ record_types->add(Element::create(static_cast<int>(field)));
+ }
+#if 0
+ PsqlBindArrayPtr record_types_binding = record_types->empty() ?
+ PsqlBindArray::createNull() : PsqlBindArray::createString(record_types->str());
+
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createInteger<uint16_t>(option_def->getCode()),
+ PsqlBindArray::createString(option_def->getName()),
+ PsqlBindArray::createString(option_def->getOptionSpaceName()),
+ PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(option_def->getType())),
+ PsqlBindArray::createTimestamp(option_def->getModificationTime()),
+ PsqlBindArray::createBool(option_def->getArrayType()),
+ PsqlBindArray::createString(option_def->getEncapsulatedSpace()),
+ record_types_binding,
+ createInputContextBinding(option_def),
+ PsqlBindArray::createString(tag),
+ PsqlBindArray::createInteger<uint16_t>(option_def->getCode()),
+ PsqlBindArray::createString(option_def->getOptionSpaceName())
+ };
+
+ PgSqlTransaction transaction(conn_);
+
+ // Create scoped audit revision. As long as this instance exists
+ // no new audit revisions are created in any subsequent calls.
+ ScopedAuditRevision audit_revision(this,
+ create_audit_revision,
+ server_selector,
+ "option definition set",
+ true);
+
+ if (conn_.updateDeleteQuery(update_option_def, in_bindings) == 0) {
+ // Remove the bindings used only during the update.
+ in_bindings.resize(in_bindings.size() - 3);
+ conn_.insertQuery(insert_option_def, in_bindings);
+
+ // Fetch unique identifier of the inserted option definition and use it
+ // as input to the next query.
+ uint64_t id = pgsql_insert_id(conn_.pgsql_);
+
+ // Insert associations of the option definition with servers.
+ attachElementToServers(insert_option_def_server,
+ server_selector,
+ PsqlBindArray::createInteger<uint64_t>(id),
+ PsqlBindArray::createTimestamp(option_def->getModificationTime()));
+ }
+
+ transaction.commit();
+#endif
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+ const Option::Universe& universe,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ auto tag = getServerTag(server_selector, "fetching global option");
+
+#if 0
+ OptionContainer options;
+ PsqlBindArray in_bindings;
+ in_bindings.push_back(PsqlBindArray::createString(tag));
+ if (universe == Option::V4) {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+ } else {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+ }
+ in_bindings.push_back(PsqlBindArray::createString(space));
+ getOptions(index, in_bindings, universe, options);
+ return (options.empty() ? OptionDescriptorPtr() :
+ OptionDescriptor::create(*options.begin()));
+#endif
+}
+
+OptionContainer
+PgSqlConfigBackendImpl::getAllOptions(const int index,
+ const Option::Universe& universe,
+ const ServerSelector& server_selector) {
+ OptionContainer options;
+#if 0
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createString(tag.get())
+ };
+ getOptions(index, in_bindings, universe, options);
+ }
+#endif
+
+ return (options);
+}
+
+OptionContainer
+PgSqlConfigBackendImpl::getModifiedOptions(const int index,
+ const Option::Universe& universe,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) {
+ OptionContainer options;
+
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ PsqlBindArray in_bindings;
+#if 0
+ PsqlBindArray in_bindings = {
+ PsqlBindArray::createString(tag.get()),
+ PsqlBindArray::createTimestamp(modification_time)
+ };
+#endif
+ getOptions(index, in_bindings, universe, options);
+ }
+
+ return (options);
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+ const Option::Universe& universe,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ auto tag = getServerTag(server_selector, "fetching subnet level option");
+
+ OptionContainer options;
+ PsqlBindArray in_bindings;
+#if 0
+ in_bindings.push_back(PsqlBindArray::createString(tag));
+ uint32_t id = static_cast<uint32_t>(subnet_id);
+ in_bindings.push_back(PsqlBindArray::createInteger<uint32_t>(id));
+ if (universe == Option::V4) {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+ } else {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+ }
+ in_bindings.push_back(PsqlBindArray::createString(space));
+#endif
+ getOptions(index, in_bindings, universe, options);
+ return (options.empty() ? OptionDescriptorPtr() :
+ OptionDescriptor::create(*options.begin()));
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+ const ServerSelector& server_selector,
+ const Lease::Type& pool_type,
+ const uint64_t pool_id,
+ const uint16_t code,
+ const std::string& space) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ std::string msg = "fetching ";
+ if (pool_type == Lease::TYPE_PD) {
+ msg += "prefix delegation";
+ } else {
+ msg += "address";
+ }
+ msg += " pool level option";
+ auto tag = getServerTag(server_selector, msg);
+
+ Option::Universe universe = Option::V4;
+ OptionContainer options;
+ PsqlBindArray in_bindings;
+#if 0
+ in_bindings.push_back(PsqlBindArray::createString(tag));
+ in_bindings.push_back(PsqlBindArray::createInteger<uint64_t>(pool_id));
+ if (pool_type == Lease::TYPE_V4) {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+ } else {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+ universe = Option::V6;
+ }
+ in_bindings.push_back(PsqlBindArray::createString(space));
+#endif
+ getOptions(index, in_bindings, universe, options);
+ return (options.empty() ? OptionDescriptorPtr() :
+ OptionDescriptor::create(*options.begin()));
+}
+
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::getOption(const int index,
+ const Option::Universe& universe,
+ const ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ auto tag = getServerTag(server_selector, "fetching shared network level option");
+
+ OptionContainer options;
+ PsqlBindArray in_bindings;
+#if 0
+ in_bindings.push_back(PsqlBindArray::createString(tag));
+ in_bindings.push_back(PsqlBindArray::createString(shared_network_name));
+ if (universe == Option::V4) {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint8_t>(static_cast<uint8_t>(code)));
+ } else {
+ in_bindings.push_back(PsqlBindArray::createInteger<uint16_t>(code));
+ }
+ in_bindings.push_back(PsqlBindArray::createString(space));
+#endif
+ getOptions(index, in_bindings, universe, options);
+ return (options.empty() ? OptionDescriptorPtr() :
+ OptionDescriptor::create(*options.begin()));
+}
+
+void
+PgSqlConfigBackendImpl::getOptions(const int index,
+ const db::PsqlBindArray& in_bindings,
+ const Option::Universe& universe,
+ OptionContainer& options) {
+#if 0
+ // Create output bindings. The order must match that in the prepared
+ // statement.
+ PsqlBindArray out_bindings;
+ // option_id
+ out_bindings.push_back(PsqlBindArray::createInteger<uint64_t>());
+ // code
+ if (universe == Option::V4) {
+ out_bindings.push_back(PsqlBindArray::createInteger<uint8_t>());
+ } else {
+ out_bindings.push_back(PsqlBindArray::createInteger<uint16_t>());
+ }
+ // value
+ out_bindings.push_back(PsqlBindArray::createBlob(OPTION_VALUE_BUF_LENGTH));
+ // forma\tted_value
+ out_bindings.push_back(PsqlBindArray::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH));
+ // space
+ out_bindings.push_back(PsqlBindArray::createString(OPTION_SPACE_BUF_LENGTH));
+ // persistent
+ out_bindings.push_back(PsqlBindArray::createInteger<uint8_t>());
+ // dhcp[46]_subnet_id
+ out_bindings.push_back(PsqlBindArray::createInteger<uint32_t>());
+ // scope_id
+ out_bindings.push_back(PsqlBindArray::createInteger<uint8_t>());
+ // user_context
+ out_bindings.push_back(PsqlBindArray::createString(USER_CONTEXT_BUF_LENGTH));
+ // shared_network_name
+ out_bindings.push_back(PsqlBindArray::createString(SHARED_NETWORK_NAME_BUF_LENGTH));
+ // pool_id
+ out_bindings.push_back(PsqlBindArray::createInteger<uint64_t>());
+ // modification_ts
+ out_bindings.push_back(PsqlBindArray::createTimestamp());
+ // server_tag
+ out_bindings.push_back(PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH));
+ // pd_pool_id
+ if (universe == Option::V6) {
+ out_bindings.push_back(PsqlBindArray::createInteger<uint64_t>());
+ }
+
+ uint64_t last_option_id = 0;
+
+ OptionContainer local_options;
+
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [this, universe, &local_options, &last_option_id]
+ (PsqlBindArray& out_bindings) {
+ // Parse option.
+ if (!out_bindings[0]->amNull() &&
+ ((last_option_id == 0) ||
+ (last_option_id < out_bindings[0]->getInteger<uint64_t>()))) {
+ last_option_id = out_bindings[0]->getInteger<uint64_t>();
+
+ OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin());
+ if (desc) {
+ // server_tag for the global option
+ ServerTag last_option_server_tag(out_bindings[12]->getString());
+ desc->setServerTag(last_option_server_tag.get());
+
+ // If we're fetching options for a given server (explicit server
+ // tag is provided), it takes precedence over the same option
+ // specified for all servers. Therefore, we check if the given
+ // option already exists and belongs to 'all'.
+ auto& index = local_options.get<1>();
+ auto existing_it_pair = index.equal_range(desc->option_->getType());
+ auto existing_it = existing_it_pair.first;
+ bool found = false;
+ for ( ; existing_it != existing_it_pair.second; ++existing_it) {
+ if (existing_it->space_name_ == desc->space_name_) {
+ found = true;
+ // This option was already fetched. Let's check if we should
+ // replace it or not.
+ if (!last_option_server_tag.amAll() && existing_it->hasAllServerTag()) {
+ index.replace(existing_it, *desc);
+ return;
+ }
+ break;
+ }
+ }
+
+ // If there is no such global option yet or the existing option
+ // belongs to a different server and the inserted option is not
+ // for all servers.
+ if (!found ||
+ (!existing_it->hasServerTag(last_option_server_tag) &&
+ !last_option_server_tag.amAll())) {
+ static_cast<void>(local_options.push_back(*desc));
+ }
+ }
+ }
+ });
+
+ // Append the options fetched by this function into the container supplied
+ // by the caller. The container supplied by the caller may already hold
+ // some options fetched for other server tags.
+ options.insert(options.end(), local_options.begin(), local_options.end());
+#endif
+}
+
+#if 0
+OptionDescriptorPtr
+PgSqlConfigBackendImpl::processOptionRow(const Option::Universe& universe,
+ PsqlBindArray::iterator first_binding) {
+ // Some of the options have standard or custom definitions.
+ // Depending whether the option has a definition or not a different
+ // C++ class may be used to represent the option. Therefore, the
+ // first thing to do is to see if there is a definition for our
+ // parsed option. The option code and space is needed for it.
+ std::string space = (*(first_binding + 4))->getString();
+ uint16_t code;
+ if (universe == Option::V4) {
+ code = (*(first_binding + 1))->getInteger<uint8_t>();
+ } else {
+ code = (*(first_binding + 1))->getInteger<uint16_t>();
+ }
+
+
+ // Get formatted value if available.
+ std::string formatted_value = (*(first_binding + 3))->getStringOrDefault("");
+
+ OptionPtr option = Option::create(universe, code);
+
+ // If we don't have a formatted value, check for a blob. Add it to the
+ // option if it exists.
+ if (formatted_value.empty()) {
+ std::vector<uint8_t> blob;
+ if (!(*(first_binding + 2))->amNull()) {
+ blob = (*(first_binding + 2))->getBlob();
+ }
+ option->setData(blob.begin(), blob.end());
+ }
+
+ // Check if the option is persistent.
+ bool persistent = static_cast<bool>((*(first_binding + 5))->getIntegerOrDefault<uint8_t>(0));
+
+ // Create option descriptor which encapsulates our option and adds
+ // additional information, i.e. whether the option is persistent,
+ // its option space and timestamp.
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, persistent, formatted_value);
+ desc->space_name_ = space;
+ desc->setModificationTime((*(first_binding + 11))->getTimestamp());
+
+ // Set database id for the option.
+ if (!(*first_binding)->amNull()) {
+ desc->setId((*first_binding)->getInteger<uint64_t>());
+ }
+
+ return (desc);
+}
+
+void
+PgSqlConfigBackendImpl::attachElementToServers(const int index,
+ const ServerSelector& server_selector,
+ const PsqlBindArrayPtr& first_binding,
+ const PsqlBindArrayPtr& in_bindings...) {
+ // Create the vector from the parameter pack.
+ PsqlBindArray in_server_bindings = { first_binding, in_bindings };
+ for (auto tag : server_selector.getTags()) {
+ in_server_bindings.push_back(PsqlBindArray::createString(tag.get()));
+ // Handles the case where the server does not exists.
+ try {
+ conn_.insertQuery(index, in_server_bindings);
+ } catch (const NullKeyError&) {
+ // The message should give the tag value.
+ isc_throw(NullKeyError,
+ "server '" << tag.get() << "' does not exist");
+ }
+ in_server_bindings.pop_back();
+ }
+}
+#endif
+
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createInputRelayBinding(const NetworkPtr& network) {
+ ElementPtr relay_element = Element::createList();
+ const auto& addresses = network->getRelayAddresses();
+ if (!addresses.empty()) {
+ for (const auto& address : addresses) {
+ relay_element->add(Element::create(address.toText()));
+ }
+ }
+
+#if 0
+ return (relay_element->empty() ? PsqlBindArray::createNull() :
+ PsqlBindArray::condCreateString(relay_element->str()));
+#endif
+}
+
+PsqlBindArrayPtr
+PgSqlConfigBackendImpl::createOptionValueBinding(const OptionDescriptorPtr& option) {
+
+ PsqlBindArrayPtr p(new PsqlBindArray());
+ OptionPtr opt = option->option_;
+ if (option->formatted_value_.empty() && (opt->len() > opt->getHeaderLen())) {
+ OutputBuffer buf(opt->len());
+ opt->pack(buf);
+ const char* buf_ptr = static_cast<const char*>(buf.getData());
+ std::vector<uint8_t> blob(buf_ptr + opt->getHeaderLen(),
+ buf_ptr + buf.getLength());
+
+ // return (PsqlBindArray::createBlob(blob.begin(), blob.end()));
+ }
+
+ // return (PsqlBindArray::createNull());
+ return (p);
+}
+
+ServerPtr
+PgSqlConfigBackendImpl::getServer(const int index, const ServerTag& server_tag) {
+ ServerCollection servers;
+ PsqlBindArray in_bindings; /* = {
+ PsqlBindArray::createString(server_tag.get())
+ }; */
+ getServers(index, in_bindings, servers);
+
+ return (servers.empty() ? ServerPtr() : *servers.begin());
+}
+
+void
+PgSqlConfigBackendImpl::getAllServers(const int index, db::ServerCollection& servers) {
+ PsqlBindArray in_bindings;
+ getServers(index, in_bindings, servers);
+}
+
+void
+PgSqlConfigBackendImpl::getServers(const int index,
+ const PsqlBindArray& in_bindings,
+ ServerCollection& servers) {
+ PsqlBindArray out_bindings; /*= {
+ PsqlBindArray::createInteger<uint64_t>(),
+ PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH),
+ PsqlBindArray::createString(SERVER_DESCRIPTION_BUF_LENGTH),
+ PsqlBindArray::createTimestamp()
+ }; */
+#if 0
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&servers](PsqlBindArray& out_bindings) {
+
+ ServerPtr last_server;
+ uint64_t id = out_bindings[0]->getInteger<uint64_t>();
+ if (!last_server || (last_server->getId() != id)) {
+
+ // Set description if it is non-null.
+ auto desc = (out_bindings[2]->amNull() ? "" : out_bindings[2]->getString());
+ last_server = Server::create(ServerTag(out_bindings[1]->getString()),
+ desc);
+
+ // id
+ last_server->setId(id);
+
+ // modification_ts
+ last_server->setModificationTime(out_bindings[3]->getTimestamp());
+
+ // New server fetched. Let's store it.
+ servers.insert(last_server);
+ }
+ });
+#endif
+}
+
+void
+PgSqlConfigBackendImpl::createUpdateServer(const int& create_audit_revision,
+ const int& create_index,
+ const int& update_index,
+ const ServerPtr& server) {
+ // The server tag 'all' is reserved.
+ if (server->getServerTag().amAll()) {
+ isc_throw(InvalidOperation, "'all' is a name reserved for the server tag which"
+ " associates the configuration elements with all servers connecting"
+ " to the database and a server with this name may not be created");
+ }
+
+ // Create scoped audit revision. As long as this instance exists
+ // no new audit revisions are created in any subsequent calls.
+ ScopedAuditRevision audit_revision(this,
+ create_audit_revision,
+ ServerSelector::ALL(),
+ "server set",
+ true);
+
+ PgSqlTransaction transaction(conn_);
+
+ PsqlBindArray in_bindings; /* = {
+ PsqlBindArray::createString(server->getServerTagAsText()),
+ PsqlBindArray::createString(server->getDescription()),
+ PsqlBindArray::createTimestamp(server->getModificationTime())
+ };
+
+ try {
+ conn_.insertQuery(create_index, in_bindings);
+
+ } catch (const DuplicateEntry&) {
+ in_bindings.push_back(PsqlBindArray::createString(server->getServerTagAsText()));
+ conn_.updateDeleteQuery(update_index, in_bindings);
+ }*/
+
+ transaction.commit();
+}
+
+std::string
+PgSqlConfigBackendImpl::getType() const {
+ return ("mysql");
+}
+
+std::string
+PgSqlConfigBackendImpl::getHost() const {
+ std::string host = "localhost";
+ try {
+ host = conn_.getParameter("host");
+ } catch (...) {
+ // No host parameter. Return localhost as a default.
+ }
+ return (host);
+}
+
+uint16_t
+PgSqlConfigBackendImpl::getPort() const {
+ try {
+ std::string sport = conn_.getParameter("port");
+ return (boost::lexical_cast<uint16_t>(sport));
+
+ } catch (...) {
+ // No port parameter or parameter invalid.
+ }
+ return (0);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2021 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/.
+
+#ifndef PGSQL_CONFIG_BACKEND_IMPL_H
+#define PGSQL_CONFIG_BACKEND_IMPL_H
+
+#include <cc/stamped_value.h>
+#include <database/audit_entry.h>
+#include <database/database_connection.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/subnet_id.h>
+#include <exceptions/exceptions.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for PostgreSQL Config Backend implementations.
+///
+/// This class contains common methods for manipulating data in the
+/// Postgres database, used by all servers.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+class PgSqlConfigBackendImpl {
+protected:
+
+ /// @brief RAII object used to protect against creating multiple
+ /// audit revisions during cascade configuration updates.
+ ///
+ /// Audit revision is created per a single database transaction.
+ /// It includes log message associated with the configuration
+ /// change. Single command sent over the control API should
+ /// result in a single audit revision entry in the database.
+ /// A single configuration update often consists of multiple
+ /// insertions, updates and/or deletes in the database. For
+ /// example, a subnet contains pools and DHCP options which are
+ /// inserted to their respective tables. We refer to such update
+ /// as a cascade update. Cascade update should result in a single
+ /// audit revision and an audit entry for a subnet, rather than
+ /// multiple audit revisions and audit entries for the subnet,
+ /// pools and child DHCP options.
+ ///
+ /// Creating an instance of the @c ScopedAuditRevision guards
+ /// against creation of multiple audit revisions when child
+ /// objects are inserted or updated in the database. When the
+ /// instance of this object goes out of scope the new audit
+ /// revisions can be created. The caller must ensure that
+ /// the instance of this object exists throughout the whole
+ /// transaction with the database.
+ class ScopedAuditRevision {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates new audit revision and sets the flag in the
+ /// Postgres CB implementation object which prevents new audit
+ /// revisions to be created while this instance exists.
+ ///
+ /// @param impl pointer to the Postgres CB implementation.
+ /// @param index index of the query to set session variables
+ /// used for creation of the audit revision and the audit
+ /// entries.
+ /// @param server_selector Server selector.
+ /// @param log_message log message associated with the audit
+ /// revision to be inserted into the database.
+ /// @param cascade_transaction boolean flag indicating if
+ /// we're performing cascade transaction. If set to true,
+ /// the audit entries for the child objects (e.g. DHCP
+ /// options) won't be created.
+ ScopedAuditRevision(PgSqlConfigBackendImpl* impl,
+ const int index,
+ const db::ServerSelector& server_selector,
+ const std::string& log_message,
+ bool cascade_transaction);
+
+ /// @brief Destructor.
+ ///
+ /// Clears the flag which is blocking creation of the new
+ /// audit revisions.
+ ~ScopedAuditRevision();
+
+ private:
+
+ /// @brief Pointer to the Postgres CB implementation.
+ PgSqlConfigBackendImpl* impl_;
+ };
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ /// @param db_reconnect_callback The connection recovery callback.
+ explicit PgSqlConfigBackendImpl(const db::DatabaseConnection::ParameterMap& parameters,
+ const db::DbCallback db_reconnect_callback);
+
+ /// @brief Destructor.
+ virtual ~PgSqlConfigBackendImpl();
+
+ /// @todo: implement condCreateInteger(const util::Optional<T>& value)
+#if 0
+ static db::PsqlBindArrayPtr createBinding(const Triplet<uint32_t>& triplet);
+ static db::PsqlBindArrayPtr createMaxBinding(const Triplet<uint32_t>& triplet);
+ static db::PsqlBindArrayPtr createMinBinding(const Triplet<uint32_t>& triplet);
+ static Triplet<uint32_t> createTriplet(const db::PsqlBindArrayPtr& binding);
+ static Triplet<uint32_t> createTriplet(const db::PsqlBindArrayPtr& def_binding,
+ const db::PsqlBindArrayPtr& min_binding, const db::PsqlBindArrayPtr& max_binding);
+#endif
+ std::string getServerTag(const db::ServerSelector& server_selector,
+ const std::string& operation );
+
+ /// @brief Returns server tags associated with the particular selector
+ /// as text.
+ ///
+ /// This method is useful for logging purposes.
+ std::string getServerTagsAsText(const db::ServerSelector& server_selector) const {
+ std::ostringstream s;
+ auto server_tags = server_selector.getTags();
+ for (auto tag : server_tags) {
+ if (s.tellp() != 0) {
+ s << ", ";
+ }
+ s << tag.get();
+ }
+
+ return (s.str());
+ }
+
+
+
+ /// @brief Invokes the corresponding stored procedure in MySQL.
+ ///
+ /// The @c createAuditRevision stored procedure creates new audit
+ /// revision and initializes several session variables to be used when
+ /// the audit entries will be created for the inserted, updated or
+ /// deleted configuration elements.
+ ///
+ /// @param index query index.
+ /// @param server_selector Server selector.
+ /// @param audit_ts Timestamp to be associated with the audit
+ /// revision.
+ /// @param log_message log message to be used for the audit revision.
+ /// @param cascade_transaction Boolean value indicating whether the
+ /// configuration modification is performed as part of the owning
+ /// element modification, e.g. subnet is modified resulting in
+ /// modification of the DHCP options it owns. In that case only the
+ /// audit entry for the owning element should be created.
+ void createAuditRevision(const int index,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& audit_ts,
+ const std::string& log_message,
+ const bool cascade_transaction);
+
+ /// @brief Clears the flag blocking creation of the new audit revisions.
+ ///
+ /// This is used by the @c ScopedAuditRevision object.
+ void clearAuditRevision();
+
+ /// @brief Sends query to the database to retrieve most recent audit entries.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) revisions have the same
+ /// modification_time.
+ /// @param [out] audit_entries Reference to the container where fetched audit
+ /// entries will be inserted.
+ void getRecentAuditEntries(const int index,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id,
+ db::AuditEntryCollection& audit_entries);
+
+ /// @todo: implement uint64_t deleteFromTable(const int index, ...)
+
+ /// @brief Sends query to delete rows from a table.
+ ///
+ /// @param index Index of the statement to be executed.
+ /// @param server_selector Server selector.
+ /// @param operation Operation which results in calling this function. This is
+ /// used for error reporting purposes.
+ /// @return Number of deleted rows.
+ uint64_t deleteFromTable(const int index,
+ const db::ServerSelector& server_selector,
+ const std::string& operation);
+
+ uint64_t deleteFromTable(const int index,
+ const db::ServerSelector& server_selector,
+ const std::string& operation,
+ db::PsqlBindArray& bindings);
+
+ /// @brief Sends query to retrieve multiple global parameters.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param in_bindings Input bindings specifying selection criteria. The
+ /// size of the bindings collection must match the number of placeholders
+ /// in the prepared statement. The input bindings collection must be empty
+ /// if the query contains no WHERE clause.
+ /// @param [out] parameters Reference to the container where fetched parameters
+ /// will be inserted.
+ void getGlobalParameters(const int index,
+ const db::PsqlBindArray& in_bindings,
+ data::StampedValueCollection& parameters);
+
+ /// @brief Sends query to retrieve single option definition by code and
+ /// option space.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the returned option definition or NULL if such
+ /// option definition doesn't exist.
+ OptionDefinitionPtr getOptionDef(const int index,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Sends query to retrieve all option definitions.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_selector Server selector.
+ /// @param [out] option_defs Reference to the container where option
+ /// definitions are to be stored.
+ void getAllOptionDefs(const int index,
+ const db::ServerSelector& server_selector,
+ OptionDefContainer& option_defs);
+
+ /// @brief Sends query to retrieve option definitions with modification
+ /// time later than specified timestamp.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @param [out] option_defs Reference to the container where option
+ /// definitions are to be stored.
+ void getModifiedOptionDefs(const int index,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ OptionDefContainer& option_defs);
+
+ /// @brief Sends query to the database to retrieve multiple option
+ /// definitions.
+ ///
+ /// Query should order option definitions by id.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param in_bindings Input bindings specifying selection criteria. The
+ /// size of the bindings collection must match the number of placeholders
+ /// in the prepared statement. The input bindings collection must be empty
+ /// if the query contains no WHERE clause.
+ /// @param [out] option_defs Reference to the container where fetched
+ /// option definitions will be inserted.
+ void
+ getOptionDefs(const int index,
+ const db::PsqlBindArray& in_bindings,
+ OptionDefContainer& option_defs);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ /// @param space Default option space
+ /// @param get_option_def_code_space Statement getting option
+ /// definition by code and space.
+ /// @param insert_option_def Statement inserting option definition.
+ /// @param update_option_def Statement updating option definition.
+ /// @param create_audit_revision Statement creating audit revision.
+ /// @param insert_option_def_server Statement associating option
+ /// definition with a server.
+ /// @throw NotImplemented if server selector is "unassigned".
+ void createUpdateOptionDef(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def,
+ const std::string& space,
+ const int& get_option_def_code_space,
+ const int& insert_option_def,
+ const int& update_option_def,
+ const int& create_audit_revision,
+ const int& insert_option_def_server);
+
+ /// @brief Sends query to retrieve single global option by code and
+ /// option space.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param universe Option universe, i.e. V4 or V6.
+ /// @param server_selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the returned option or NULL if such option
+ /// doesn't exist.
+ OptionDescriptorPtr
+ getOption(const int index, const Option::Universe& universe,
+ const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Sends query to retrieve all global options.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param universe Option universe, i.e. V4 or V6.
+ /// @param server_selector Server selector.
+ /// @return Container holding returned options.
+ OptionContainer
+ getAllOptions(const int index, const Option::Universe& universe,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Sends query to retrieve global options with modification
+ /// time later than specified timestamp.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param universe Option universe, i.e. V4 or V6.
+ /// @param server_selector Server selector.
+ /// @return Container holding returned options.
+ OptionContainer
+ getModifiedOptions(const int index, const Option::Universe& universe,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time);
+
+ /// @brief Sends query to retrieve single option by code and option space
+ /// for a given subnet id.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param universe Option universe, i.e. V4 or V6.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Subnet identifier.
+ /// @param code Option code.
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the returned option descriptor or NULL if such
+ /// option doesn't exist.
+ OptionDescriptorPtr getOption(const int index,
+ const Option::Universe& universe,
+ const db::ServerSelector& server_selector,
+ const dhcp::SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Sends query to retrieve single option by code and option space
+ /// for a given address or prefix delegation (v6) pool id.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_selector Server selector.
+ /// @param pool_type Pool type (Lease::TYPE_V4, TYPE_NA or TYPE_PD).
+ /// @param pool_id Pool identifier in the database.
+ /// @param code Option code.
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the returned option descriptor or NULL if such
+ /// option doesn't exist.
+ OptionDescriptorPtr getOption(const int index,
+ const db::ServerSelector& server_selector,
+ const dhcp::Lease::Type& pool_type,
+ const uint64_t pool_id,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Sends query to retrieve single option by code and option space
+ /// for a given shared network.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param universe Option universe, i.e. V4 or V6.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Shared network name.
+ /// @param code Option code.
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the returned option descriptor or NULL if such
+ /// option doesn't exist.
+ OptionDescriptorPtr getOption(const int index,
+ const Option::Universe& universe,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Sends query to the database to retrieve multiple options.
+ ///
+ /// Query should order by option_id.
+ ///
+ /// @note The universe is reused to switch between DHCPv4 and DHCPv6
+ /// option layouts.
+ /// @param family Address family (either AF_INET or AF_INET6).
+ /// @param index Index of the query to be used.
+ /// @param in_bindings Input bindings specifying selection criteria. The
+ /// size of the bindings collection must match the number of placeholders
+ /// in the prepared statement. The input bindings collection must be empty
+ /// if the query contains no WHERE clause.
+ /// @param universe Option universe, i.e. V4 or V6.
+ /// @param [out] options Reference to the container where fetched options
+ /// will be inserted.
+ void getOptions(const int index,
+ const db::PsqlBindArray& in_bindings,
+ const Option::Universe& universe,
+ OptionContainer& options);
+
+ /// @todo implement OptionDescriptorPtr processOptionRow(const Option::Universe& universe, ...)
+
+ /// @todo implement void attachElementToServers(const int index, ...)
+
+ /// @todo implement
+ db::PsqlBindArrayPtr createInputRelayBinding(const NetworkPtr& network);
+
+ /// @todo implement template<typename T> db::MySqlBindingPtr createInputRequiredClassesBinding(const T& object)
+
+ /// @todo implement db::MySqlBindingPtr createInputContextBinding(const T& config_element) {
+
+ /// @todo implement
+ db::PsqlBindArrayPtr createOptionValueBinding(const OptionDescriptorPtr& option);
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_tag Server tag of the server to be retrieved.
+ /// @return Pointer to the @c Server object representing the server or
+ /// null if such server doesn't exist.
+ db::ServerPtr getServer(const int index, const data::ServerTag& server_tag);
+
+ /// @brief Retrieves all servers.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param [out] servers Reference to the container where fetched servers
+ /// will be inserted.
+ void getAllServers(const int index, db::ServerCollection& servers);
+
+ /// @todo implement
+ /// @brief Sends query to retrieve servers.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param bindings Reference to the MySQL input bindings.
+ /// @param [out] servers Reference to the container where fetched servers
+ /// will be inserted.
+ void getServers(const int index,
+ const db::PsqlBindArray& bindings,
+ db::ServerCollection& servers);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// This method attempts to insert a new server into the database using
+ /// the query identified by @c create_index. If the insertion fails because
+ /// the server with the given tag already exists in the database, the
+ /// existing server is updated using the query identified by the
+ /// @c update_index.
+ ///
+ /// @param create_audit_revision Index of the query inserting audit
+ /// revision.
+ /// @param create_index Index of the INSERT query to be used.
+ /// @param update_index Index of the UPDATE query to be used.
+ /// @param server Pointer to the server to be inserted or updated.
+ /// @throw InvalidOperation when trying to create a duplicate or
+ /// update the logical server 'all'.
+ void createUpdateServer(const int& create_audit_revision,
+ const int& create_index,
+ const int& update_index,
+ const db::ServerPtr& server);
+
+ /// @todo implement template<typename T, typename... R> void multipleUpdateDeleteQueries(T first_index, R... other_indexes)
+
+ /// @brief Removes configuration elements from the index which don't match
+ /// the specified server selector.
+ ///
+ /// This is a generic function which removes configuration elements which
+ /// don't match the specified selector. In order to fetch all server tags
+ /// for the returned configuration element, the query must not limit the
+ /// results to the given server tag. Instead, it must post process the
+ /// result to eliminate those configuration elements for which the desired
+ /// server tag wasn't found.
+ ///
+ /// If the server selector is set to ANY, this method is no-op.
+ ///
+ /// @tparam CollectionIndex Type of the collection to be processed.
+ /// @param server_selector Server selector.
+ /// @param index Reference to the index holding the returned configuration
+ /// elements to be processed.
+ template<typename CollectionIndex>
+ void tossNonMatchingElements(const db::ServerSelector& server_selector,
+ CollectionIndex& index) {
+ // Don't filter the matching server tags if the server selector is
+ // set to ANY.
+ if (server_selector.amAny()) {
+ return;
+ }
+
+ // Go over the collection of elements.
+ for (auto elem = index.begin(); elem != index.end(); ) {
+
+ // If we're asking for shared networks matching all servers,
+ // we have to make sure that the fetched element has "all"
+ // server tag.
+ if (server_selector.amAll()) {
+ if (!(*elem)->hasAllServerTag()) {
+ // It doesn't so let's remove it.
+ elem = index.erase(elem);
+ continue;
+ }
+
+ } else if (server_selector.amUnassigned()) {
+ // Returned element has server tags but we expect that the
+ // elements are unassigned.
+ if (!(*elem)->getServerTags().empty()) {
+ elem = index.erase(elem);
+ continue;
+ }
+
+ } else {
+ // Server selector contains explicit server tags, so
+ // let's see if the returned elements includes any of
+ // them.
+ auto tags = server_selector.getTags();
+ bool tag_found = false;
+ for (auto tag : tags) {
+ if ((*elem)->hasServerTag(tag) ||
+ (*elem)->hasAllServerTag()) {
+ tag_found = true;
+ break;
+ }
+ }
+ if (!tag_found) {
+ // Tag not matching, so toss the element.
+ elem = index.erase(elem);
+ continue;
+ }
+ }
+
+ // Go to the next element if we didn't toss the current one.
+ // Otherwise, the erase() function should have already taken
+ // us to the next one.
+ ++elem;
+ }
+ }
+
+ /// @brief Returns backend type in the textual format.
+ ///
+ /// @return "pgsql".
+ std::string getType() const;
+
+ /// @brief Returns backend host.
+ ///
+ /// This is used by the @c BaseConfigBackendPool to select backend
+ /// when @c BackendSelector is specified.
+ ///
+ /// @return host on which the database is located.
+ std::string getHost() const;
+
+ /// @brief Returns backend port number.
+ ///
+ /// This is used by the @c BaseConfigBackendPool to select backend
+ /// when @c BackendSelector is specified.
+ ///
+ /// @return Port number on which database service is available.
+ uint16_t getPort() const;
+
+ /// @brief Return backend parameters
+ ///
+ /// Returns the backend parameters
+ ///
+ /// @return Parameters of the backend.
+ const isc::db::DatabaseConnection::ParameterMap& getParameters() {
+ return (parameters_);
+ }
+
+ /// @brief Sets IO service to be used by the Postgres config backend.
+ ///
+ /// @param IOService object, used for all ASIO operations.
+ static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
+ io_service_ = io_service;
+ }
+
+ /// @brief Returns pointer to the IO service.
+ static isc::asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Represents connection to the Postgres database.
+ db::PgSqlConnection conn_;
+
+protected:
+
+ /// @brief Timer name used to register database reconnect timer.
+ std::string timer_name_;
+
+private:
+
+ /// @brief Boolean flag indicating if audit revision has been created
+ /// using @c ScopedAuditRevision object.
+ bool audit_revision_created_;
+
+ /// @brief Connection parameters
+ isc::db::DatabaseConnection::ParameterMap parameters_;
+
+ /// The IOService object, used for all ASIO operations.
+ static isc::asiolink::IOServicePtr io_service_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif