Vince's CSV Parser
Loading...
Searching...
No Matches
csv_reader.cpp
Go to the documentation of this file.
1
5#include "csv_reader.hpp"
6
7namespace csv {
8 void CSVReader::open_file(csv::string_view filename, const CSVFormat& format) {
9#if defined(__EMSCRIPTEN__)
10 owned_stream.reset(new std::ifstream(std::string(filename), std::ios::binary));
11 if (!*owned_stream) {
12 internals::fail(error_, CSVErrorCode::OpenFailed,
13 internals::make_prefixed_message(internals::ERROR_CANNOT_OPEN_FILE, filename));
14 return;
15 }
16 init_from_stream(*owned_stream, format);
17#else
18 init_parser(std::unique_ptr<internals::parser::CSVParserDriverBase>(
19 new internals::parser::MmapParser(filename, format, col_names)));
20#endif
21 }
22
23 std::unique_ptr<CSVReader> CSVReader::try_open(
24 csv::string_view filename, CSVError& error, const CSVFormat& format) {
25 error.clear();
26 std::unique_ptr<CSVReader> reader(new CSVReader(format, initialize_tag{}));
27 reader->open_file(filename, format);
28 error = reader->error_;
29 return error ? nullptr : std::move(reader);
30 }
31
32 std::unique_ptr<CSVReader> CSVReader::try_open(
33 std::unique_ptr<std::istream> source, CSVError& error, const CSVFormat& format) {
34 error.clear();
35 if (!source) {
36 internals::fail_static(error, CSVErrorCode::InvalidArgument, internals::ERROR_READER_NULL_STREAM);
37 return nullptr;
38 }
39 std::unique_ptr<CSVReader> reader(new CSVReader(format, initialize_tag{}));
40 reader->owned_stream = std::move(source);
41 reader->init_from_stream(*reader->owned_stream, format);
42 error = reader->error_;
43 return error ? nullptr : std::move(reader);
44 }
45
46#ifdef _MSC_VER
47#pragma region Reading helpers
48#endif
49 bool CSVReader::check_for_rows() {
50 if (error_) return false;
51 if (!this->records->empty()) return true;
52
53 if (this->read_scheduler_.wait_if_active(
54 [this] { return this->records->is_waitable(); },
55 [this] { this->records->wait(); }
56 )) {
57 return true;
58 }
59
60 this->read_scheduler_.join();
61 this->read_scheduler_.rethrow_exception_if_any();
62 if (parser->error()) {
63 error_ = parser->error();
64 return false;
65 }
66
67 if (this->parser->eof()) return false;
68
69 if (this->_read_requested && this->records->empty()) {
70 return internals::fail(error_, CSVErrorCode::RowTooLarge,
71 internals::make_row_larger_than_chunk_message(this->_chunk_size));
72 }
73
74 this->read_scheduler_.run(
75 [this] { this->read_csv(this->_chunk_size); },
76 [this] { this->records->notify_all(); }
77 );
78 this->read_scheduler_.rethrow_exception_if_any();
79 // The parser may still be running. Inspect its error only after join().
80 this->_read_requested = true;
81 return true;
82 }
83
84#ifdef _MSC_VER
85#pragma endregion Reading helpers
86#endif
87
88#ifdef _MSC_VER
89#pragma region Format and header helpers
90#endif
91 void CSVReader::init_parser(
92 std::unique_ptr<internals::parser::CSVParserDriverBase> parser_impl
93 ) {
94 if (parser_impl->error()) {
95 error_ = parser_impl->error();
96 return;
97 }
98 auto resolved = parser_impl->get_resolved_format();
99 this->_format = resolved.format;
100 this->_chunk_size = this->_format.get_chunk_size();
101 this->n_cols = resolved.n_cols;
102
103 if (!this->_format.col_names.empty()) {
104 this->set_col_names(this->_format.col_names);
105 }
106
107 this->parser = std::move(parser_impl);
108 this->initial_read();
109 }
110
112 CSVFormat new_format = this->_format;
113
114 // Since users are normally not allowed to set
115 // column names and header row simulatenously,
116 // we will set the backing variables directly here
117 new_format.col_names = this->col_names->get_col_names();
118 new_format.header = this->_format.header;
119
120 return new_format;
121 }
122
123 void CSVReader::initial_read() {
124 // Issue #337: A single read window may end before the header row when
125 // earlier rows are long, so keep reading until the header is consumed.
126 do {
127 const int trimmed_before = this->header_rows_trimmed_;
128 this->read_scheduler_.run([this] { this->read_csv(this->_chunk_size); });
129 this->read_scheduler_.join();
130 this->read_scheduler_.rethrow_exception_if_any();
131 if (parser->error()) {
132 error_ = parser->error();
133 return;
134 }
135
136 // trim_header() drains the queue until the header is consumed, so an
137 // untrimmed header with no progress means the read produced no rows.
138 if (!this->header_trimmed && !this->parser->eof()
139 && this->header_rows_trimmed_ == trimmed_before) {
140 internals::fail(error_, CSVErrorCode::RowTooLarge,
141 internals::make_row_larger_than_chunk_message(this->_chunk_size));
142 return;
143 }
144 } while (!this->header_trimmed && !this->parser->eof());
145 }
146
147 void CSVReader::trim_header() {
148 if (!this->header_trimmed) {
149 // The rows up to and including the header may arrive over several
150 // reads, so track progress instead of assuming one read holds them all.
151 while (this->header_rows_trimmed_ <= this->_format.header && !this->records->empty()) {
152 if (this->header_rows_trimmed_ == this->_format.header && this->col_names->empty()) {
153 this->set_col_names(this->records->pop_front());
154 }
155 else {
156 this->records->pop_front();
157 }
158
159 this->header_rows_trimmed_++;
160 }
161
162 this->header_trimmed = this->header_rows_trimmed_ > this->_format.header;
163 }
164 }
165
167 void CSVReader::set_col_names(const std::vector<std::string>& names)
168 {
169 this->col_names->set_policy(this->_format.get_column_name_policy());
170 this->col_names->set_col_names(names);
171 this->n_cols = names.size();
172 }
173#ifdef _MSC_VER
174#pragma endregion Format and header helpers
175#endif
176
177#ifdef _MSC_VER
178#pragma region Reading helpers
179#endif
180 bool CSVReader::accept_row(CSVRow&& candidate, CSVRow* single_row, std::vector<CSVRow>* batch_rows) {
181 const auto policy = this->_format.variable_column_policy;
182 const size_t next_row_size = candidate.size();
183
184 if (policy == VariableColumnPolicy::KEEP_NON_EMPTY && next_row_size == 0) {
185 return false;
186 }
187
188 if (next_row_size != this->n_cols &&
189 (policy == VariableColumnPolicy::THROW || policy == VariableColumnPolicy::IGNORE_ROW)) {
190 if (policy == VariableColumnPolicy::THROW) {
191 if (candidate.size() < this->n_cols) {
192 return internals::fail(error_, CSVErrorCode::RowLengthMismatch,
193 internals::make_prefixed_message(internals::ERROR_ROW_TOO_SHORT, candidate.raw_str()));
194 }
195
196 return internals::fail(error_, CSVErrorCode::RowLengthMismatch,
197 internals::make_prefixed_message(internals::ERROR_ROW_TOO_LONG, candidate.raw_str()));
198 }
199
200 return false;
201 }
202
203 if (single_row != nullptr) {
204 *single_row = std::move(candidate);
205 } else if (batch_rows != nullptr) {
206 batch_rows->push_back(std::move(candidate));
207 } else {
208 return false;
209 }
210
211 this->_n_rows++;
212 this->_read_requested = false;
213 return true;
214 }
215
216 void CSVReader::drain_rows_into_chunk(std::vector<CSVRow>& out, size_t max_rows) {
217 std::vector<CSVRow> drained;
218 drained.reserve(max_rows - out.size());
219 this->records->drain_front(drained, max_rows - out.size());
220
221 for (size_t i = 0; i < drained.size(); ++i) {
222 this->accept_row(std::move(drained[i]), nullptr, &out);
223 if (error_) break;
224 }
225 }
226#ifdef _MSC_VER
227#pragma endregion Reading helpers
228#endif
229
230#ifdef _MSC_VER
231#pragma region Worker reading methods
232#endif
233
244 bool CSVReader::read_csv(size_t bytes) {
245 // SCHEDULED READ FUNCTION: Runs asynchronously when runtime threading
246 // is enabled, or synchronously when CSVFormat::threading(false) is active.
247 //
248 // Threading model:
249 // 1. notify_all() - signals read_row() that worker is active
250 // 2. parser->next() - reads and parses bytes (10MB chunks)
251 // 3. kill_all() - signals read_row() that worker is done
252 //
253 // Operational errors stay on the source adapter until the consumer joins.
254 // In exception-enabled builds, CSVReadScheduler catches exceptions and rethrows
255 // them on the consumer thread. Bug #282 fixed cases where worker
256 // exceptions were swallowed, causing std::terminate().
257
258 // Tell read_row() to listen for CSV rows
259 this->records->notify_all();
260
261 CSV_TRY {
262 this->parser->set_output(*this->records);
263 this->parser->next(bytes);
264
265 if (!parser->error() && !this->header_trimmed) {
266 this->trim_header();
267 }
268 }
269 CSV_CATCH((...), {
270 this->records->kill_all();
271 throw;
272 })
273
274 // Tell read_row() to stop waiting
275 this->records->kill_all();
276
277 return true;
278 }
279
281 error.clear();
282 while (this->check_for_rows()) {
283 if (this->records->empty())
284 continue;
285
286 if (this->accept_row(this->records->pop_front(), &row, nullptr))
287 return true;
288 }
289
290 error = error_;
291 return false;
292 }
293
294 bool CSVReader::try_read_chunk(std::vector<CSVRow>& out, size_t max_rows, CSVError& error) {
295 error.clear();
296 out.clear();
297
298 if (max_rows == 0) {
299 return internals::fail_static(error, CSVErrorCode::InvalidArgument, "read_chunk() requires a non-zero row count.");
300 }
301
302 while (out.size() < max_rows) {
303 if (check_for_rows()) {
304 if (this->records->empty()) {
305 continue;
306 }
307
308 const size_t before_size = out.size();
309 this->drain_rows_into_chunk(out, max_rows);
310 if (error_) { error = error_; return false; }
311
312 if (out.size() == before_size) {
313 continue;
314 }
315 }
316 else { error = error_; return !error && !out.empty(); }
317 }
318
319 return true;
320 }
321#if CSV_EXCEPTIONS_ENABLED
323 CSVError error;
324 const bool read = try_read_row(row, error);
325 internals::throw_error(error);
326 return read;
327 }
328
329 bool CSVReader::read_chunk(std::vector<CSVRow>& out, size_t max_rows) {
330 if (max_rows == 0) { out.clear(); return false; }
331 CSVError error;
332 const bool read = try_read_chunk(out, max_rows, error);
333 internals::throw_error(error);
334 return read;
335 }
336#endif
337#ifdef _MSC_VER
338#pragma endregion Worker reading methods
339#endif
340}
Stores information about how to parse a CSV file.
Main class for parsing CSVs from files and in-memory sources.
bool read_chunk(std::vector< CSVRow > &out, size_t max_rows)
Read up to max_rows rows into a caller-owned batch buffer.
bool try_read_chunk(std::vector< CSVRow > &out, size_t max_rows, CSVError &error)
Clear out and read a batch.
CSVFormat get_format() const
Return the resolved parsing format for this CSV source.
static std::unique_ptr< CSVReader > try_open(csv::string_view filename, CSVError &error, const CSVFormat &format=CSVFormat::guess_csv())
Construct CSVReader from filename.
bool read_row(CSVRow &row)
Retrieve the next CSV row, returning true while more rows are available.
bool try_read_row(CSVRow &row, CSVError &error)
Retrieve a row, distinguishing EOF (false, no error) from failure.
Data structure for representing CSV rows.
Definition csv_row.hpp:533
Defines functionality needed for basic CSV parsing.
std::unique_ptr< std::istream > owned_stream
Optional owned stream used by two paths: 1) Emscripten filename-constructor fallback to stream parsin...
std::unique_ptr< RowCollection > records
Queue of parsed CSV rows.
size_t _n_rows
How many rows (minus header) have been read so far.
bool read_csv(size_t bytes=internals::CSV_CHUNK_SIZE_DEFAULT)
Read a chunk of CSV data.
internals::ColNamesPtr col_names
Pointer to a object containing column information.
void set_col_names(const std::vector< std::string > &)
Sets this reader's column names and associated data.
std::unique_ptr< internals::parser::CSVParserDriverBase > parser
Helper class which actually does the parsing.
size_t n_cols
The number of columns in this CSV.
The all encompassing namespace.
@ InvalidArgument
Invalid operation input, such as a null stream or zero batch size.
@ RowTooLarge
A complete record could not fit within the configured read window.
@ RowLengthMismatch
A record's field count violates the configured variable-column policy.
@ OpenFailed
A native file could not be opened, including initial input mapping.
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