summaryrefslogtreecommitdiffstats
path: root/src/lib/util/versioned_csv_file.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/versioned_csv_file.cc')
-rw-r--r--src/lib/util/versioned_csv_file.cc247
1 files changed, 247 insertions, 0 deletions
diff --git a/src/lib/util/versioned_csv_file.cc b/src/lib/util/versioned_csv_file.cc
new file mode 100644
index 0000000..8c48f66
--- /dev/null
+++ b/src/lib/util/versioned_csv_file.cc
@@ -0,0 +1,247 @@
+// Copyright (C) 2015-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 <util/versioned_csv_file.h>
+
+namespace isc {
+namespace util {
+
+VersionedCSVFile::VersionedCSVFile(const std::string& filename)
+ : CSVFile(filename), columns_(0), valid_column_count_(0),
+ minimum_valid_columns_(0), input_header_count_(0),
+ input_schema_state_(CURRENT) {
+}
+
+VersionedCSVFile::~VersionedCSVFile() {
+}
+
+void
+VersionedCSVFile::addColumn(const std::string& name,
+ const std::string& version,
+ const std::string& default_value) {
+ CSVFile::addColumn(name);
+ columns_.push_back(VersionedColumnPtr(new VersionedColumn(name, version,
+ default_value)));
+}
+
+void
+VersionedCSVFile::setMinimumValidColumns(const std::string& column_name) {
+ try {
+ int index = getColumnIndex(column_name);
+ minimum_valid_columns_ = index + 1;
+
+ } catch (...) {
+ isc_throw(VersionedCSVFileError,
+ "setMinimumValidColumns: " << column_name << " is not "
+ "defined");
+ }
+}
+
+size_t
+VersionedCSVFile::getMinimumValidColumns() const {
+ return (minimum_valid_columns_);
+}
+
+size_t
+VersionedCSVFile::getValidColumnCount() const {
+ return (valid_column_count_);
+}
+
+size_t
+VersionedCSVFile::getInputHeaderCount() const {
+ return (input_header_count_);
+}
+
+void
+VersionedCSVFile::open(const bool seek_to_end) {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "no schema has been defined, cannot open CSV file :"
+ << getFilename());
+ }
+
+ CSVFile::open(seek_to_end);
+}
+
+void
+VersionedCSVFile::recreate() {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "no schema has been defined, cannot create CSV file :"
+ << getFilename());
+ }
+
+ CSVFile::recreate();
+ // For new files they always match.
+ input_header_count_ = valid_column_count_ = getColumnCount();
+}
+
+VersionedCSVFile::InputSchemaState
+VersionedCSVFile::getInputSchemaState() const {
+ return (input_schema_state_);
+}
+
+bool
+VersionedCSVFile::needsConversion() const {
+ return (input_schema_state_ != CURRENT);
+}
+
+std::string
+VersionedCSVFile::getInputSchemaVersion() const {
+ if (getValidColumnCount() > 0) {
+ return (getVersionedColumn(getValidColumnCount() - 1)->version_);
+ }
+
+ return ("undefined");
+}
+
+std::string
+VersionedCSVFile::getSchemaVersion() const {
+ if (getColumnCount() > 0) {
+ return (getVersionedColumn(getColumnCount() - 1)->version_);
+ }
+
+ return ("undefined");
+}
+
+const VersionedColumnPtr&
+VersionedCSVFile::getVersionedColumn(const size_t index) const {
+ if (index >= getColumnCount()) {
+ isc_throw(isc::OutOfRange, "versioned column index " << index
+ << " out of range; CSV file : " << getFilename()
+ << " only has " << getColumnCount() << " columns ");
+ }
+
+ return (columns_[index]);
+}
+
+bool
+VersionedCSVFile::next(CSVRow& row) {
+ setReadMsg("success");
+ // Use base class to physical read the row, but skip its row
+ // validation
+ CSVFile::next(row, true);
+ if (row == CSVFile::EMPTY_ROW()) {
+ return(true);
+ }
+
+ bool row_valid = true;
+ switch(getInputSchemaState()) {
+ case CURRENT:
+ // All rows must match than the current schema
+ if (row.getValuesCount() != getColumnCount()) {
+ columnCountError(row, "must match current schema");
+ row_valid = false;
+ }
+ break;
+
+ case NEEDS_UPGRADE:
+ // The input header met the minimum column count but
+ // is less than the current schema so:
+ // Rows must not be shorter than the valid column count
+ // and not longer than the current schema
+ if (row.getValuesCount() < getValidColumnCount()) {
+ columnCountError(row, "too few columns to upgrade");
+ row_valid = false;
+ } else if (row.getValuesCount() > getColumnCount()) {
+ columnCountError(row, "too many columns to upgrade");
+ row_valid = false;
+ } else {
+ // Add any missing values
+ for (size_t index = row.getValuesCount();
+ index < getColumnCount(); ++index) {
+ row.append(columns_[index]->default_value_);
+ }
+ }
+ break;
+
+ case NEEDS_DOWNGRADE:
+ // The input header exceeded current schema so:
+ // Rows may be as long as input header but not shorter than
+ // the current schema
+ if (row.getValuesCount() < getColumnCount()) {
+ columnCountError(row, "too few columns to downgrade");
+ } else if (row.getValuesCount() > getInputHeaderCount()) {
+ columnCountError(row, "too many columns to downgrade");
+ } else {
+ // Toss any the extra columns
+ row.trim(row.getValuesCount() - getColumnCount());
+ }
+ break;
+ }
+
+ return (row_valid);
+}
+
+void
+VersionedCSVFile::columnCountError(const CSVRow& row,
+ const std::string& reason) {
+ std::ostringstream s;
+ s << "Invalid number of columns: "
+ << row.getValuesCount() << " in row: '" << row
+ << "', file: '" << getFilename() << "' : " << reason;
+ setReadMsg(s.str());
+}
+
+bool
+VersionedCSVFile::validateHeader(const CSVRow& header) {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "cannot validate header, no schema has been defined");
+ }
+
+ input_header_count_ = header.getValuesCount();
+
+ // Iterate over the number of columns in the header, testing
+ // each against the defined column in the same position.
+ // If there is a mismatch, bail.
+ size_t i = 0;
+ for ( ; i < getInputHeaderCount() && i < getColumnCount(); ++i) {
+ if (getColumnName(i) != header.readAt(i)) {
+ std::ostringstream s;
+ s << " - header contains an invalid column: '"
+ << header.readAt(i) << "'";
+ setReadMsg(s.str());
+ return (false);
+ }
+ }
+
+ // If we found too few valid columns, then we cannot convert this
+ // file. It's too old, too corrupt, or not a Kea file.
+ if (i < getMinimumValidColumns()) {
+ std::ostringstream s;
+ s << " - header has only " << i << " valid column(s), "
+ << "it must have at least " << getMinimumValidColumns();
+ setReadMsg(s.str());
+ return (false);
+ }
+
+ // Remember the number of valid columns we found. When this number
+ // is less than the number of defined columns, then we have an older
+ // version of the lease file. We'll need this value to validate
+ // and upgrade data rows.
+ valid_column_count_ = i;
+
+ if (getValidColumnCount() < getColumnCount()) {
+ input_schema_state_ = NEEDS_UPGRADE;
+ } else if (getInputHeaderCount() > getColumnCount()) {
+ // If there are more values in the header than defined columns
+ // then, we'll drop the extra. This allows someone to attempt to
+ // downgrade if need be.
+ input_schema_state_ = NEEDS_DOWNGRADE;
+ std::ostringstream s;
+ s << " - header has " << getInputHeaderCount() - getColumnCount()
+ << " extra column(s), these will be ignored";
+ setReadMsg(s.str());
+ }
+
+ return (true);
+}
+
+} // end of isc::util namespace
+} // end of isc namespace