versioned_csv_file.cc 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <util/versioned_csv_file.h>
  7. namespace isc {
  8. namespace util {
  9. VersionedCSVFile::VersionedCSVFile(const std::string& filename)
  10. : CSVFile(filename), columns_(0), valid_column_count_(0),
  11. minimum_valid_columns_(0), input_header_count_(0),
  12. input_schema_state_(CURRENT) {
  13. }
  14. VersionedCSVFile::~VersionedCSVFile() {
  15. }
  16. void
  17. VersionedCSVFile::addColumn(const std::string& name,
  18. const std::string& version,
  19. const std::string& default_value) {
  20. CSVFile::addColumn(name);
  21. columns_.push_back(VersionedColumnPtr(new VersionedColumn(name, version,
  22. default_value)));
  23. }
  24. void
  25. VersionedCSVFile::setMinimumValidColumns(const std::string& column_name) {
  26. try {
  27. int index = getColumnIndex(column_name);
  28. minimum_valid_columns_ = index + 1;
  29. } catch (...) {
  30. isc_throw(VersionedCSVFileError,
  31. "setMinimumValidColumns: " << column_name << " is not "
  32. "defined");
  33. }
  34. }
  35. size_t
  36. VersionedCSVFile::getMinimumValidColumns() const {
  37. return (minimum_valid_columns_);
  38. }
  39. size_t
  40. VersionedCSVFile::getValidColumnCount() const {
  41. return (valid_column_count_);
  42. }
  43. size_t
  44. VersionedCSVFile::getInputHeaderCount() const {
  45. return (input_header_count_);
  46. }
  47. void
  48. VersionedCSVFile::open(const bool seek_to_end) {
  49. if (getColumnCount() == 0) {
  50. isc_throw(VersionedCSVFileError,
  51. "no schema has been defined, cannot open CSV file :"
  52. << getFilename());
  53. }
  54. CSVFile::open(seek_to_end);
  55. }
  56. void
  57. VersionedCSVFile::recreate() {
  58. if (getColumnCount() == 0) {
  59. isc_throw(VersionedCSVFileError,
  60. "no schema has been defined, cannot create CSV file :"
  61. << getFilename());
  62. }
  63. CSVFile::recreate();
  64. // For new files they always match.
  65. input_header_count_ = valid_column_count_ = getColumnCount();
  66. }
  67. VersionedCSVFile::InputSchemaState
  68. VersionedCSVFile::getInputSchemaState() const {
  69. return (input_schema_state_);
  70. }
  71. bool
  72. VersionedCSVFile::needsConversion() const {
  73. return (input_schema_state_ != CURRENT);
  74. }
  75. std::string
  76. VersionedCSVFile::getInputSchemaVersion() const {
  77. if (getValidColumnCount() > 0) {
  78. return (getVersionedColumn(getValidColumnCount() - 1)->version_);
  79. }
  80. return ("undefined");
  81. }
  82. std::string
  83. VersionedCSVFile::getSchemaVersion() const {
  84. if (getColumnCount() > 0) {
  85. return (getVersionedColumn(getColumnCount() - 1)->version_);
  86. }
  87. return ("undefined");
  88. }
  89. const VersionedColumnPtr&
  90. VersionedCSVFile::getVersionedColumn(const size_t index) const {
  91. if (index >= getColumnCount()) {
  92. isc_throw(isc::OutOfRange, "versioned column index " << index
  93. << " out of range; CSV file : " << getFilename()
  94. << " only has " << getColumnCount() << " columns ");
  95. }
  96. return (columns_[index]);
  97. }
  98. bool
  99. VersionedCSVFile::next(CSVRow& row) {
  100. setReadMsg("success");
  101. // Use base class to physical read the row, but skip its row
  102. // validation
  103. CSVFile::next(row, true);
  104. if (row == CSVFile::EMPTY_ROW()) {
  105. return(true);
  106. }
  107. bool row_valid = true;
  108. switch(getInputSchemaState()) {
  109. case CURRENT:
  110. // All rows must match than the current schema
  111. if (row.getValuesCount() != getColumnCount()) {
  112. columnCountError(row, "must match current schema");
  113. row_valid = false;
  114. }
  115. break;
  116. case NEEDS_UPGRADE:
  117. // The input header met the minimum column count but
  118. // is less than the current schema so:
  119. // Rows must not be shorter than the valid column count
  120. // and not longer than the current schema
  121. if (row.getValuesCount() < getValidColumnCount()) {
  122. columnCountError(row, "too few columns to upgrade");
  123. row_valid = false;
  124. } else if (row.getValuesCount() > getColumnCount()) {
  125. columnCountError(row, "too many columns to upgrade");
  126. row_valid = false;
  127. } else {
  128. // Add any missing values
  129. for (size_t index = row.getValuesCount();
  130. index < getColumnCount(); ++index) {
  131. row.append(columns_[index]->default_value_);
  132. }
  133. }
  134. break;
  135. case NEEDS_DOWNGRADE:
  136. // The input header exceeded current schema so:
  137. // Rows may be as long as input header but not shorter than
  138. // the the current schema
  139. if (row.getValuesCount() < getColumnCount()) {
  140. columnCountError(row, "too few columns to downgrade");
  141. } else if (row.getValuesCount() > getInputHeaderCount()) {
  142. columnCountError(row, "too many columns to downgrade");
  143. } else {
  144. // Toss any the extra columns
  145. row.trim(row.getValuesCount() - getColumnCount());
  146. }
  147. break;
  148. }
  149. return (row_valid);
  150. }
  151. void
  152. VersionedCSVFile::columnCountError(const CSVRow& row,
  153. const std::string& reason) {
  154. std::ostringstream s;
  155. s << "Invalid number of columns: "
  156. << row.getValuesCount() << " in row: '" << row
  157. << "', file: '" << getFilename() << "' : " << reason;
  158. setReadMsg(s.str());
  159. }
  160. bool
  161. VersionedCSVFile::validateHeader(const CSVRow& header) {
  162. if (getColumnCount() == 0) {
  163. isc_throw(VersionedCSVFileError,
  164. "cannot validate header, no schema has been defined");
  165. }
  166. input_header_count_ = header.getValuesCount();
  167. // Iterate over the number of columns in the header, testing
  168. // each against the defined column in the same position.
  169. // If there is a mismatch, bail.
  170. size_t i = 0;
  171. for ( ; i < getInputHeaderCount() && i < getColumnCount(); ++i) {
  172. if (getColumnName(i) != header.readAt(i)) {
  173. std::ostringstream s;
  174. s << " - header contains an invalid column: '"
  175. << header.readAt(i) << "'";
  176. setReadMsg(s.str());
  177. return (false);
  178. }
  179. }
  180. // If we found too few valid columns, then we cannot convert this
  181. // file. It's too old, too corrupt, or not a Kea file.
  182. if (i < getMinimumValidColumns()) {
  183. std::ostringstream s;
  184. s << " - header has only " << i << " valid column(s), "
  185. << "it must have at least " << getMinimumValidColumns();
  186. setReadMsg(s.str());
  187. return (false);
  188. }
  189. // Remember the number of valid columns we found. When this number
  190. // is less than the number of defined columns, then we have an older
  191. // version of the lease file. We'll need this value to validate
  192. // and upgrade data rows.
  193. valid_column_count_ = i;
  194. if (getValidColumnCount() < getColumnCount()) {
  195. input_schema_state_ = NEEDS_UPGRADE;
  196. } else if (getInputHeaderCount() > getColumnCount()) {
  197. // If there are more values in the header than defined columns
  198. // then, we'll drop the extra. This allows someone to attempt to
  199. // downgrade if need be.
  200. input_schema_state_ = NEEDS_DOWNGRADE;
  201. std::ostringstream s;
  202. s << " - header has " << getInputHeaderCount() - getColumnCount()
  203. << " extra column(s), these will be ignored";
  204. setReadMsg(s.str());
  205. }
  206. return (true);
  207. }
  208. } // end of isc::util namespace
  209. } // end of isc namespace