Vince's CSV Parser
Loading...
Searching...
No Matches
dirty_data_frame.hpp
1#pragma once
2
3#include <mutex>
4#include "clean_data_frame.hpp"
5
6namespace csv {
7 namespace internals {
8 namespace data_frame {
13 class DirtyDataFrame : public RowBackend {
14 public:
15 explicit inline DirtyDataFrame(CleanDataFrame& backing, bool allocate_edits = true)
16 : backing_(&backing), names_(backing.names()) {
17 if (allocate_edits) { edits_.resize(backing.rows().size()); }
18 }
19 DirtyDataFrame(const DirtyDataFrame&) = delete;
20 DirtyDataFrame& operator=(const DirtyDataFrame&) = delete;
21
22 inline void rebind(CleanDataFrame& backing) noexcept { backing_ = &backing; }
23 inline const ConstColNamesPtr& names() const noexcept { return names_; }
24 inline size_t n_cols() const noexcept { return names_->size(); }
25 inline const RowOverlay* find_edits(size_t row) const {
26 // Empty slots exist only in an unpublished, exclusive mutation
27 // backend. Published backends initialize every slot before release.
28 return edits_.empty() ? nullptr : edits_[row].get();
29 }
30 inline RowOverlay* ensure_edits(size_t row) {
31 if (auto* overlay = edits_[row].get()) { return overlay; }
32 std::lock_guard<std::mutex> lock(creation_lock_);
33 return edits_[row].ensure();
34 }
35 inline size_t physical_column(size_t column) const {
36 return column_indices_.empty() ? column : column_indices_[column];
37 }
38 inline size_t logical_column(size_t physical) const {
39 if (column_indices_.empty()) { return physical; }
40 const auto position = std::find(column_indices_.begin(), column_indices_.end(), physical);
41 CSV_DEBUG_ASSERT(position != column_indices_.end());
42 return static_cast<size_t>(std::distance(column_indices_.begin(), position));
43 }
44 inline CellBinding bind(size_t row, size_t column) const {
45 validate_column(column, n_cols());
46 return CellBinding{find_edits(row), physical_column(column)};
47 }
48 inline csv::string_view view(size_t row, size_t column) const {
49 const CellBinding cell = bind(row, column);
50 csv::string_view value;
51 if (cell.overlay && cell.overlay->try_get_view(cell.physical_column, value)) { return value; }
52 return RowViewAccessor::view(backing_->rows()[row], cell.physical_column);
53 }
54 inline CSVRow make_inserted_row(const std::vector<std::string>& row, CSVError& error) const {
55 if (!validate_row(row, n_cols(), error)) return {};
56 if (column_indices_.empty() && n_cols() == backing_->n_cols()) { return RowStorageBuilder::make_row(row, backing_->names()); }
57 std::vector<std::string> expanded(backing_->n_cols());
58 for (size_t i = 0; i < row.size(); ++i) { expanded[physical_column(i)] = row[i]; }
59 return RowStorageBuilder::make_row(expanded, backing_->names());
60 }
61 inline void reserve_insert() {
62 backing_->reserve_insert();
63 reserve_next(edits_);
64 }
65 inline void insert_row(size_t index, CSVRow&& row) {
66 // Call reserve_insert before mutating keyed metadata. Slot moves
67 // and CSVRow moves cannot allocate once capacity is available.
68 backing_->insert_row(index, std::move(row));
69 edits_.insert(edits_.begin() + index, RowOverlaySlot());
70 }
71 inline void erase_row(size_t index) {
72 edits_.erase(edits_.begin() + index);
73 backing_->erase_row(index);
74 }
75 inline bool erase_column(size_t index, int key_column, CSVError& error) {
76 error.clear();
77 if (index >= n_cols()) { return false; }
78 const size_t physical = physical_column(index);
79 if (key_column != CSV_NOT_FOUND && physical == static_cast<size_t>(key_column)) {
80 return fail_static(error, CSVErrorCode::KeyColumnProtected, "cannot erase key column from DataFrame");
81 }
82 std::vector<std::string> columns = names_->get_col_names();
83 columns.erase(columns.begin() + index);
84 ColNamesPtr names = std::make_shared<ColNames>();
85 names->set_policy(names_->get_policy());
86 names->set_col_names(columns);
87 std::vector<size_t> mapping = column_indices_;
88 if (mapping.empty()) {
89 mapping.reserve(backing_->n_cols());
90 for (size_t i = 0; i < backing_->n_cols(); ++i) { mapping.push_back(i); }
91 }
92 mapping.erase(mapping.begin() + index);
93 names_ = std::move(names);
94 column_indices_ = std::move(mapping);
95 return true;
96 }
97 inline std::unique_ptr<DirtyDataFrame> selected_rows(
98 CleanDataFrame& selected, const std::vector<std::uint8_t>& mask
99 ) const {
100 std::unique_ptr<DirtyDataFrame> result(new DirtyDataFrame(selected));
101 result->names_ = names_;
102 result->column_indices_ = column_indices_;
103 size_t target = 0;
104 for (size_t i = 0; i < mask.size(); ++i) {
105 if (mask[i]) {
106 if (const auto* overlay = find_edits(i)) {
107 *result->edits_[target].ensure() = overlay->snapshot();
108 }
109 ++target;
110 }
111 }
112 return result;
113 }
114
115 template<typename BeforeReplace>
116 inline bool insert_column(
117 size_t index, const std::string& name, const std::string& default_value,
118 int& key_column, const BeforeReplace& before_replace, CSVError& error
119 ) {
120 error.clear();
121 if (index > n_cols()) { return fail_static(error, CSVErrorCode::IndexOutOfBounds, "DataFrame insert_column index out of range"); }
122 if (name.empty()) { return fail_static(error, CSVErrorCode::InvalidArgument, "inserted column name must not be empty"); }
123 if (names_->index_of(name) != CSV_NOT_FOUND) {
124 return fail_static(error, CSVErrorCode::InvalidArgument, "inserted column name must not duplicate an existing column");
125 }
126 // A retained short CSV row cannot supply every visible source field.
127 // Check before materializing or preserving keys, so no unchecked field
128 // access can turn an input-shape error into termination without exceptions.
129 if (n_cols() != 0) {
130 size_t last_physical = 0;
131 for (size_t column = 0; column < n_cols(); ++column)
132 last_physical = std::max(last_physical, physical_column(column));
133 for (const auto& row : backing_->rows()) {
134 if (last_physical >= row.size())
135 return fail_static(error, CSVErrorCode::IndexOutOfBounds, CSV_ERROR_INDEX_OUT_OF_BOUNDS);
136 }
137 }
138 std::vector<std::string> columns = names_->get_col_names();
139 columns.insert(columns.begin() + index, name);
140 ColNamesPtr names = std::make_shared<ColNames>();
141 names->set_policy(names_->get_policy());
142 names->set_col_names(columns);
143 int new_key_column = key_column;
144 if (key_column != CSV_NOT_FOUND) {
145 const size_t logical = logical_column(static_cast<size_t>(key_column));
146 new_key_column = static_cast<int>(logical + (index <= logical ? 1 : 0));
147 }
148 // Select a concrete materializer once. An unpublished clean-input
149 // mutation backend has neither slot allocation nor per-field lookups.
150 std::vector<CSVRow> rebuilt = edits_.empty() && column_indices_.empty()
151 ? RowStorageBuilder::insert_column(backing_->rows().size(), names, index, default_value,
152 [this](size_t row, size_t column) { return RowViewAccessor::view(backing_->rows()[row], column); })
153 : RowStorageBuilder::insert_column(backing_->rows().size(), names, index, default_value,
154 [this](size_t row, size_t column) -> std::string {
155 const CellBinding cell = bind(row, column);
156 std::string value;
157 if (cell.overlay && cell.overlay->try_get_copy(cell.physical_column, value)) { return value; }
158 return std::string(RowViewAccessor::view(backing_->rows()[row], cell.physical_column));
159 });
160 // Prepare everything before releasing parsed backing or view-key bytes.
161 if (!before_replace(error)) return false;
162 backing_->replace_rows(std::move(rebuilt), std::move(names));
163 key_column = new_key_column;
164 return true;
165 }
166
167 private:
168 CleanDataFrame* backing_;
169 ConstColNamesPtr names_;
170 std::vector<RowOverlaySlot> edits_;
171 std::vector<size_t> column_indices_;
172 std::mutex creation_lock_;
173 };
174 }
175 }
176}
Data structure for representing CSV rows.
Definition csv_row.hpp:533
Borrow a row and error state for synchronous, single-pass field access.
Direct parsed-row backend and stable physical backing for editing.
The all encompassing namespace.
@ IndexOutOfBounds
A row/column position exceeds the visible schema or physical record width.
@ InvalidArgument
Invalid operation input, such as a null stream or zero batch size.
@ KeyColumnProtected
Removing the column that supplies the frame's stored keys is prohibited.
constexpr int CSV_NOT_FOUND
Integer indicating a requested column wasn't found.
Definition common.hpp:494
std::string_view string_view
The string_view class used by this library.
Definition common.hpp:182
A diagnostic whose text remains valid for the lifetime of the error.
Definition csv_error.hpp:94