31#include <system_error>
32#include <unordered_set>
38# if !defined(NOMINMAX)
55constexpr std::int32_t kInt64RowCount = INT32_MIN;
57[[noreturn]]
void throwIo(
const std::string &message,
const std::filesystem::path &path = {}) {
58 throw IoError(ErrorKind::Io, message, path);
61[[noreturn]]
void throwFormat(
const std::string &message,
const std::filesystem::path &path = {},
62 std::int64_t page = 0,
63 std::optional<std::string> field = std::nullopt,
64 std::optional<std::uint64_t> offset = std::nullopt,
65 std::optional<std::int64_t> row = std::nullopt) {
66 throw FormatError(ErrorKind::Format, message, path, page, std::move(field), offset, row);
69[[noreturn]]
void throwType(
const std::string &message,
70 std::optional<std::string> field = std::nullopt) {
71 throw TypeError(ErrorKind::Type, message, {}, 0, std::move(field));
74[[noreturn]]
void throwState(
const std::string &message) {
75 throw StateError(ErrorKind::State, message);
78[[noreturn]]
void throwLimit(
const std::string &message,
const std::filesystem::path &path = {},
79 std::int64_t page = 0,
80 std::optional<std::string> field = std::nullopt,
81 std::optional<std::int64_t> row = std::nullopt) {
82 throw LimitError(ErrorKind::Limit, message, path, page, std::move(field), std::nullopt, row);
85[[noreturn]]
void throwWithContext(
const Error &error,
86 const std::filesystem::path &path,
88 std::optional<std::uint64_t> offset = std::nullopt,
89 std::optional<std::int64_t> row = std::nullopt,
90 std::optional<std::string> field = std::nullopt) {
91 const std::filesystem::path effectivePath = error.path().empty() ? path : error.path();
92 const std::int64_t effectivePage = error.page() ? error.page() : page;
93 const auto effectiveOffset = error.offset() ? error.offset() : offset;
94 const auto effectiveRow = error.row() ? error.row() : row;
95 const auto effectiveField = error.field() ? error.field() : field;
96 switch (error.kind()) {
98 throw IoError(ErrorKind::Io, error.what(), effectivePath, effectivePage, effectiveField,
99 effectiveOffset, effectiveRow);
100 case ErrorKind::Format:
101 throw FormatError(ErrorKind::Format, error.what(), effectivePath, effectivePage,
102 effectiveField, effectiveOffset, effectiveRow);
103 case ErrorKind::Type:
104 throw TypeError(ErrorKind::Type, error.what(), effectivePath, effectivePage, effectiveField,
105 effectiveOffset, effectiveRow);
106 case ErrorKind::State:
107 throw StateError(ErrorKind::State, error.what(), effectivePath, effectivePage, effectiveField,
108 effectiveOffset, effectiveRow);
109 case ErrorKind::Limit:
110 throw LimitError(ErrorKind::Limit, error.what(), effectivePath, effectivePage, effectiveField,
111 effectiveOffset, effectiveRow);
116std::string trim(std::string value) {
117 const auto first = value.find_first_not_of(
" \t\r\n");
118 if (first == std::string::npos)
120 const auto last = value.find_last_not_of(
" \t\r\n");
121 return value.substr(first, last - first + 1);
124std::string lower(std::string value) {
125 std::transform(value.begin(), value.end(), value.begin(), [](
unsigned char c) {
126 return static_cast<char>(std::tolower(c));
131bool parseBool(
const std::string &value) {
132 const std::string text = lower(trim(value));
133 if (text ==
"1" || text ==
"true" || text ==
"yes")
135 if (text ==
"0" || text ==
"false" || text ==
"no")
137 throwFormat(
"invalid boolean value: " + value);
141T parseInteger(
const std::string &text,
const char *what) {
143 const std::string value = trim(text);
144 const char *begin = value.data();
145 const char *end = begin + value.size();
146 const auto converted = std::from_chars(begin, end, result, 10);
147 if (converted.ec != std::errc() || converted.ptr != end)
148 throwFormat(std::string(
"invalid ") + what +
": " + text);
152std::string unescape(std::string_view input) {
154 result.reserve(input.size());
155 for (std::size_t i = 0; i < input.size(); ++i) {
156 if (input[i] !=
'\\' || i + 1 >= input.size()) {
157 result.push_back(input[i]);
160 const char next = input[++i];
161 if (next >=
'0' && next <=
'7') {
162 unsigned value =
static_cast<unsigned>(next -
'0');
164 while (digits < 3 && i + 1 < input.size() && input[i + 1] >=
'0' &&
165 input[i + 1] <=
'7') {
166 value = value * 8U +
static_cast<unsigned>(input[++i] -
'0');
169 result.push_back(
static_cast<char>(value));
170 }
else if (next ==
'n') {
171 result.push_back(
'\n');
172 }
else if (next ==
'r') {
173 result.push_back(
'\r');
174 }
else if (next ==
't') {
175 result.push_back(
'\t');
177 result.push_back(next);
183std::string quote(
const std::optional<std::string> &value) {
186 std::string result =
"\"";
187 for (
unsigned char c : *value) {
188 if (c ==
'\\' || c ==
'"' || c ==
'!') {
189 result.push_back(
'\\');
190 result.push_back(
static_cast<char>(c));
191 }
else if (!std::isprint(c)) {
193 std::snprintf(buffer,
sizeof(buffer),
"\\%03o", c);
196 result.push_back(
static_cast<char>(c));
199 result.push_back(
'"');
203std::string quoteRequired(
const std::string &value) {
204 return quote(std::optional<std::string>(value));
207Type parseType(
const std::string &name) {
208 const std::string value = lower(trim(name));
209 if (value ==
"longdouble")
return Type::LongDouble;
210 if (value ==
"double")
return Type::Double;
211 if (value ==
"float")
return Type::Float;
212 if (value ==
"long64")
return Type::Int64;
213 if (value ==
"ulong64")
return Type::UInt64;
214 if (value ==
"long")
return Type::Int32;
215 if (value ==
"ulong")
return Type::UInt32;
216 if (value ==
"short")
return Type::Int16;
217 if (value ==
"ushort")
return Type::UInt16;
218 if (value ==
"string")
return Type::String;
219 if (value ==
"character")
return Type::Character;
220 throwFormat(
"unknown SDDS type: " + name);
223Values emptyValues(Type type) {
225 case Type::LongDouble:
return std::vector<long double>{};
226 case Type::Double:
return std::vector<double>{};
227 case Type::Float:
return std::vector<float>{};
228 case Type::Int64:
return std::vector<std::int64_t>{};
229 case Type::UInt64:
return std::vector<std::uint64_t>{};
230 case Type::Int32:
return std::vector<std::int32_t>{};
231 case Type::UInt32:
return std::vector<std::uint32_t>{};
232 case Type::Int16:
return std::vector<std::int16_t>{};
233 case Type::UInt16:
return std::vector<std::uint16_t>{};
234 case Type::String:
return std::vector<std::string>{};
235 case Type::Character:
return std::vector<char>{};
237 throwType(
"unknown SDDS type");
240Scalar defaultScalar(Type type) {
242 case Type::LongDouble:
return static_cast<long double>(0);
243 case Type::Double:
return 0.0;
244 case Type::Float:
return 0.0F;
245 case Type::Int64:
return static_cast<std::int64_t
>(0);
246 case Type::UInt64:
return static_cast<std::uint64_t
>(0);
247 case Type::Int32:
return static_cast<std::int32_t
>(0);
248 case Type::UInt32:
return static_cast<std::uint32_t
>(0);
249 case Type::Int16:
return static_cast<std::int16_t
>(0);
250 case Type::UInt16:
return static_cast<std::uint16_t
>(0);
251 case Type::String:
return std::string{};
252 case Type::Character:
return static_cast<char>(0);
254 throwType(
"unknown SDDS type");
257std::size_t valuesSize(
const Values &values) {
258 return std::visit([](
const auto &v) {
return v.size(); }, values);
263 virtual ~Stream() =
default;
264 virtual std::size_t
read(
void *data, std::size_t size) = 0;
265 virtual void write(
const void *data, std::size_t size) = 0;
266 virtual bool eof()
const = 0;
267 virtual bool seekable() const noexcept {
return false; }
268 virtual std::optional<std::uint64_t> size()
const {
return std::nullopt; }
269 virtual std::uint64_t tell()
const { throwState(
"stream is not seekable"); }
270 virtual void seek(std::uint64_t) { throwState(
"stream is not seekable"); }
271 virtual void truncate(std::uint64_t) { throwState(
"stream is not seekable"); }
272 virtual void flush() = 0;
273 virtual void sync() { throwState(
"stream does not support sync"); }
274 virtual void close() = 0;
277class InputSourceStream final :
public Stream {
279 explicit InputSourceStream(std::unique_ptr<InputSource> source) : source_(std::move(source)) {
280 if (!source_) throwState(
"input source cannot be null");
282 std::size_t
read(
void *data, std::size_t size)
override {
return source_->read(data, size); }
283 void write(
const void *, std::size_t)
override { throwState(
"stream is not writable"); }
284 bool eof()
const override {
return source_->eof(); }
285 bool seekable() const noexcept
override {
return source_->capabilities().seek; }
286 std::uint64_t tell()
const override {
return source_->tell(); }
287 void seek(std::uint64_t offset)
override { source_->seek(offset); }
288 void flush()
override {}
289 void close()
override { source_->close(); }
292 std::unique_ptr<InputSource> source_;
295class OutputSinkStream final :
public Stream {
297 explicit OutputSinkStream(std::unique_ptr<OutputSink> sink) : sink_(std::move(sink)) {
298 if (!sink_) throwState(
"output sink cannot be null");
300 std::size_t
read(
void *, std::size_t)
override { throwState(
"stream is not readable"); }
301 void write(
const void *data, std::size_t size)
override { sink_->write(data, size); }
302 bool eof()
const override {
return false; }
303 bool seekable() const noexcept
override {
return sink_->capabilities().seek; }
304 std::uint64_t tell()
const override {
return sink_->tell(); }
305 void seek(std::uint64_t offset)
override { sink_->seek(offset); }
306 void truncate(std::uint64_t length)
override { sink_->truncate(length); }
307 void flush()
override { sink_->flush(); }
308 void sync()
override { sink_->sync(); }
309 void close()
override { sink_->close(); }
312 std::unique_ptr<OutputSink> sink_;
315class FileStream final :
public Stream {
317 FileStream(FILE *file, std::filesystem::path path,
bool owned,
bool writable,
318 std::size_t bufferBytes = 256U * 1024U)
319 : file_(file), path_(std::move(path)), owned_(owned), writable_(writable) {
321 throwIo(
"null file stream", path_);
323 buffer_.resize(bufferBytes);
324 if (setvbuf(file_,
reinterpret_cast<char *
>(buffer_.data()), _IOFBF, buffer_.size()) != 0) {
325 if (owned_) std::fclose(file_);
327 throwIo(
"unable to configure file buffer", path_);
332 ~FileStream()
override {
333 try { close(); }
catch (...) {}
336 std::size_t
read(
void *data, std::size_t size)
override {
337 const std::size_t count = std::fread(data, 1, size, file_);
338 if (count < size && std::ferror(file_))
339 throwIo(
"read failure: " + std::string(std::strerror(errno)), path_);
343 void write(
const void *data, std::size_t size)
override {
345 throwState(
"stream is not writable");
346 if (size && std::fwrite(data, 1, size, file_) != size)
347 throwIo(
"write failure: " + std::string(std::strerror(errno)), path_);
350 bool eof()
const override {
return std::feof(file_) != 0; }
351 bool seekable() const noexcept
override {
return owned_; }
353 std::optional<std::uint64_t> size()
const override {
355 struct _stat64 status{};
356 if (_fstat64(_fileno(file_), &status) != 0 || status.st_size < 0)
359 struct stat status{};
360 if (fstat(fileno(file_), &status) != 0 || status.st_size < 0)
363 return static_cast<std::uint64_t
>(status.st_size);
366 std::uint64_t tell()
const override {
368 const auto offset = _ftelli64(file_);
370 const auto offset = ftello(file_);
373 throwIo(
"unable to query file position", path_);
374 return static_cast<std::uint64_t
>(offset);
377 void seek(std::uint64_t offset)
override {
379 const int status = _fseeki64(file_,
static_cast<__int64
>(offset), SEEK_SET);
381 if (offset >
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max()))
382 throwLimit(
"file offset exceeds platform limit", path_);
383 const int status = fseeko(file_,
static_cast<off_t
>(offset), SEEK_SET);
386 throwIo(
"seek failure", path_);
387 std::clearerr(file_);
390 void truncate(std::uint64_t length)
override {
393 if (_chsize_s(_fileno(file_), length) != 0)
395 if (length >
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max()) ||
396 ftruncate(fileno(file_),
static_cast<off_t
>(length)) != 0)
398 throwIo(
"unable to truncate output file", path_);
401 void flush()
override {
402 if (writable_ && std::fflush(file_) != 0)
403 throwIo(
"flush failure: " + std::string(std::strerror(errno)), path_);
406 void sync()
override {
409 if (_commit(_fileno(file_)) != 0)
411 if (::fsync(fileno(file_)) != 0)
413 throwIo(
"fsync failure", path_);
416 FILE *file() const noexcept {
return file_; }
418 void close()
override {
422 FILE *closing = file_;
424 if (std::fclose(closing) != 0)
425 throwIo(
"close failure", path_);
426 }
else if (writable_) {
435 FILE *file_ =
nullptr;
436 std::filesystem::path path_;
438 bool writable_ =
false;
439 std::vector<unsigned char> buffer_;
442class GzipStream final :
public Stream {
444 GzipStream(gzFile file, std::filesystem::path path,
bool writable,
445 std::size_t bufferBytes = 256U * 1024U)
446 : file_(file), path_(std::move(path)), writable_(writable) {
448 throwIo(
"unable to open gzip stream", path_);
449 if (bufferBytes && gzbuffer(file_,
static_cast<unsigned>(
450 std::min<std::size_t>(bufferBytes, UINT_MAX))) != 0) {
453 throwIo(
"unable to configure gzip buffer", path_);
457 ~GzipStream()
override {
458 try { close(); }
catch (...) {}
461 std::size_t
read(
void *data, std::size_t size)
override {
462 std::size_t total = 0;
463 while (total < size) {
464 const unsigned amount =
static_cast<unsigned>(std::min<std::size_t>(size - total, UINT_MAX));
465 const int result = gzread(file_,
static_cast<char *
>(data) + total, amount);
468 const char *message = gzerror(file_, &code);
469 throwIo(std::string(
"gzip read failure: ") + (message ? message :
"unknown"), path_);
473 total +=
static_cast<std::size_t
>(result);
478 void write(
const void *data, std::size_t size)
override {
480 throwState(
"gzip stream is not writable");
481 std::size_t total = 0;
482 while (total < size) {
483 const unsigned amount =
static_cast<unsigned>(std::min<std::size_t>(size - total, UINT_MAX));
484 const int result = gzwrite(file_,
static_cast<const char *
>(data) + total, amount);
486 throwIo(
"gzip write failure", path_);
487 total +=
static_cast<std::size_t
>(result);
491 bool eof()
const override {
return gzeof(file_) != 0; }
492 void flush()
override {
493 if (writable_ && gzflush(file_, Z_SYNC_FLUSH) != Z_OK)
494 throwIo(
"gzip flush failure", path_);
496 void close()
override {
499 gzFile closing = file_;
501 if (gzclose(closing) != Z_OK)
502 throwIo(
"gzip close failure", path_);
506 gzFile file_ =
nullptr;
507 std::filesystem::path path_;
508 bool writable_ =
false;
511class LzmaStream final :
public Stream {
513 LzmaStream(FILE *file, std::filesystem::path path,
bool writable,
514 std::size_t bufferBytes = 256U * 1024U, std::uint32_t preset = 6,
515 LzmaCheck check = LzmaCheck::Crc64,
bool alone =
false)
516 : file_(file), path_(std::move(path)), writable_(writable),
518 input_(std::max<std::size_t>(bufferBytes, 4096U)),
519 output_(std::max<std::size_t>(bufferBytes, 4096U)) {
520 stream_ = LZMA_STREAM_INIT;
521 lzma_ret result = LZMA_OK;
523 result = lzma_auto_decoder(&stream_, UINT64_MAX, 0);
525 lzma_options_lzma filters{};
526 if (lzma_lzma_preset(&filters, preset)) result = LZMA_OPTIONS_ERROR;
527 else result = lzma_alone_encoder(&stream_, &filters);
529 lzma_check selected = LZMA_CHECK_CRC64;
531 case LzmaCheck::None: selected = LZMA_CHECK_NONE;
break;
532 case LzmaCheck::Crc32: selected = LZMA_CHECK_CRC32;
break;
533 case LzmaCheck::Crc64: selected = LZMA_CHECK_CRC64;
break;
534 case LzmaCheck::Sha256: selected = LZMA_CHECK_SHA256;
break;
536 result = lzma_easy_encoder(&stream_, preset, selected);
538 if (result != LZMA_OK) {
541 throwIo(
"unable to initialize LZMA codec", path_);
545 ~LzmaStream()
override {
546 try { close(); }
catch (...) {}
549 std::size_t
read(
void *data, std::size_t size)
override {
551 throwState(
"LZMA stream is not readable");
552 stream_.next_out =
static_cast<std::uint8_t *
>(data);
553 stream_.avail_out = size;
554 while (stream_.avail_out && !finished_) {
555 if (!stream_.avail_in) {
556 const std::size_t count = std::fread(input_.data(), 1, input_.size(), file_);
557 if (!count && std::ferror(file_))
558 throwIo(
"LZMA backing-file read failure", path_);
559 stream_.next_in = input_.data();
560 stream_.avail_in = count;
561 inputEof_ = count == 0;
563 const lzma_ret result = lzma_code(&stream_, inputEof_ ? LZMA_FINISH : LZMA_RUN);
564 if (result == LZMA_STREAM_END) {
566 }
else if (result != LZMA_OK) {
567 throwFormat(
"invalid LZMA/XZ stream", path_);
568 }
else if (inputEof_ && !stream_.avail_in && stream_.avail_out == size) {
569 throwFormat(
"truncated LZMA/XZ stream", path_);
572 return size - stream_.avail_out;
575 void write(
const void *data, std::size_t size)
override {
577 throwState(
"LZMA stream is not writable");
578 stream_.next_in =
static_cast<const std::uint8_t *
>(data);
579 stream_.avail_in = size;
583 bool eof()
const override {
return finished_; }
584 void flush()
override {
585 if (writable_ && !alone_)
586 encode(LZMA_SYNC_FLUSH);
587 if (std::fflush(file_) != 0)
588 throwIo(
"LZMA flush failure", path_);
590 void close()
override {
593 if (writable_ && !finished_) {
598 FILE *closing = file_;
600 if (std::fclose(closing) != 0)
601 throwIo(
"LZMA close failure", path_);
605 void encode(lzma_action action) {
607 stream_.next_out = output_.data();
608 stream_.avail_out = output_.size();
609 const lzma_ret result = lzma_code(&stream_, action);
610 const std::size_t produced = output_.size() - stream_.avail_out;
611 if (produced && std::fwrite(output_.data(), 1, produced, file_) != produced)
612 throwIo(
"LZMA backing-file write failure", path_);
613 if (result == LZMA_STREAM_END) {
614 if (action == LZMA_FINISH)
618 if (result != LZMA_OK)
619 throwIo(
"LZMA encoding failure", path_);
620 if (action == LZMA_RUN && stream_.avail_in == 0)
622 if (action == LZMA_SYNC_FLUSH && stream_.avail_out != 0)
627 FILE *file_ =
nullptr;
628 std::filesystem::path path_;
629 bool writable_ =
false;
631 bool finished_ =
false;
632 bool inputEof_ =
false;
633 lzma_stream stream_ = LZMA_STREAM_INIT;
634 std::vector<std::uint8_t> input_;
635 std::vector<std::uint8_t> output_;
638class GzipCodecStream final :
public Stream {
640 GzipCodecStream(std::unique_ptr<Stream> backing,
bool writable, std::size_t bufferBytes,
642 : backing_(std::move(backing)), writable_(writable),
643 input_(std::max<std::size_t>(bufferBytes, 4096U)),
644 output_(std::max<std::size_t>(bufferBytes, 4096U)) {
645 if (!backing_) throwState(
"gzip codec requires a backing stream");
646 std::memset(&codec_, 0,
sizeof(codec_));
647 const int result = writable_
648 ? deflateInit2(&codec_, level < 0 ? Z_DEFAULT_COMPRESSION : level,
649 Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY)
650 : inflateInit2(&codec_, 15 + 32);
651 if (result != Z_OK) throwIo(
"unable to initialize gzip codec");
653 ~GzipCodecStream()
override {
try { close(); }
catch (...) {} }
655 std::size_t
read(
void *data, std::size_t size)
override {
656 if (writable_) throwState(
"gzip stream is not readable");
657 std::size_t total = 0;
658 while (total < size && !finished_) {
659 const std::size_t chunk = std::min<std::size_t>(size - total, UINT_MAX);
660 codec_.next_out =
reinterpret_cast<Bytef *
>(
static_cast<char *
>(data) + total);
661 codec_.avail_out =
static_cast<uInt
>(chunk);
662 while (codec_.avail_out && !finished_) {
663 if (!codec_.avail_in && !inputEof_) {
664 const std::size_t count = backing_->read(input_.data(), input_.size());
665 codec_.next_in = input_.data();
666 codec_.avail_in =
static_cast<uInt
>(count);
667 inputEof_ = count == 0;
669 const uInt before = codec_.avail_out;
670 const int result = inflate(&codec_, Z_NO_FLUSH);
671 if (result == Z_STREAM_END) {
673 }
else if (result != Z_OK && result != Z_BUF_ERROR) {
674 throwFormat(
"invalid gzip stream");
675 }
else if (inputEof_ && !codec_.avail_in && codec_.avail_out == before) {
676 throwFormat(
"truncated gzip stream");
679 total += chunk - codec_.avail_out;
680 if (codec_.avail_out)
break;
684 void write(
const void *data, std::size_t size)
override {
685 if (!writable_) throwState(
"gzip stream is not writable");
686 const auto *bytes =
static_cast<const unsigned char *
>(data);
688 const std::size_t chunk = std::min<std::size_t>(size, UINT_MAX);
689 codec_.next_in =
const_cast<Bytef *
>(
reinterpret_cast<const Bytef *
>(bytes));
690 codec_.avail_in =
static_cast<uInt
>(chunk);
696 bool eof()
const override {
return finished_; }
697 void flush()
override {
698 if (writable_) encode(Z_SYNC_FLUSH);
701 void close()
override {
702 if (!backing_)
return;
703 if (writable_ && !finished_) {
707 writable_ ? deflateEnd(&codec_) : inflateEnd(&codec_);
713 void encode(
int flushMode) {
715 codec_.next_out = output_.data();
716 codec_.avail_out =
static_cast<uInt
>(output_.size());
717 const int result = deflate(&codec_, flushMode);
718 if (result != Z_OK && result != Z_STREAM_END &&
719 !(flushMode == Z_FINISH && result == Z_BUF_ERROR))
720 throwIo(
"gzip encoding failure");
721 const std::size_t produced = output_.size() - codec_.avail_out;
722 if (produced) backing_->write(output_.data(), produced);
723 if (result == Z_STREAM_END) { finished_ =
true;
break; }
724 if (flushMode == Z_NO_FLUSH && codec_.avail_in == 0)
break;
725 if (flushMode == Z_SYNC_FLUSH && codec_.avail_out != 0)
break;
728 std::unique_ptr<Stream> backing_;
729 bool writable_ =
false;
730 bool finished_ =
false;
731 bool inputEof_ =
false;
733 std::vector<unsigned char> input_;
734 std::vector<unsigned char> output_;
737class LzmaCodecStream final :
public Stream {
739 LzmaCodecStream(std::unique_ptr<Stream> backing,
bool writable, std::size_t bufferBytes,
740 std::uint32_t preset = 6, LzmaCheck check = LzmaCheck::Crc64,
742 : backing_(std::move(backing)), writable_(writable),
744 input_(std::max<std::size_t>(bufferBytes, 4096U)),
745 output_(std::max<std::size_t>(bufferBytes, 4096U)) {
746 if (!backing_) throwState(
"LZMA codec requires a backing stream");
747 codec_ = LZMA_STREAM_INIT;
748 lzma_ret result = LZMA_OK;
750 result = lzma_auto_decoder(&codec_, UINT64_MAX, 0);
752 lzma_options_lzma options{};
753 if (lzma_lzma_preset(&options, preset)) result = LZMA_OPTIONS_ERROR;
754 else result = lzma_alone_encoder(&codec_, &options);
756 lzma_check selected = LZMA_CHECK_CRC64;
758 case LzmaCheck::None: selected = LZMA_CHECK_NONE;
break;
759 case LzmaCheck::Crc32: selected = LZMA_CHECK_CRC32;
break;
760 case LzmaCheck::Crc64: selected = LZMA_CHECK_CRC64;
break;
761 case LzmaCheck::Sha256: selected = LZMA_CHECK_SHA256;
break;
763 result = lzma_easy_encoder(&codec_, preset, selected);
765 if (result != LZMA_OK) throwIo(
"unable to initialize LZMA codec");
767 ~LzmaCodecStream()
override {
try { close(); }
catch (...) {} }
769 std::size_t
read(
void *data, std::size_t size)
override {
770 if (writable_) throwState(
"LZMA stream is not readable");
771 codec_.next_out =
static_cast<std::uint8_t *
>(data);
772 codec_.avail_out = size;
773 while (codec_.avail_out && !finished_) {
774 if (!codec_.avail_in) {
775 const std::size_t count = backing_->read(input_.data(), input_.size());
776 codec_.next_in = input_.data();
777 codec_.avail_in = count;
778 inputEof_ = count == 0;
780 const std::size_t before = codec_.avail_out;
781 const lzma_ret result = lzma_code(&codec_, inputEof_ ? LZMA_FINISH : LZMA_RUN);
782 if (result == LZMA_STREAM_END) finished_ =
true;
783 else if (result != LZMA_OK) throwFormat(
"invalid LZMA/XZ stream");
784 else if (inputEof_ && !codec_.avail_in && codec_.avail_out == before)
785 throwFormat(
"truncated LZMA/XZ stream");
787 return size - codec_.avail_out;
789 void write(
const void *data, std::size_t size)
override {
790 if (!writable_) throwState(
"LZMA stream is not writable");
791 codec_.next_in =
static_cast<const std::uint8_t *
>(data);
792 codec_.avail_in = size;
795 bool eof()
const override {
return finished_; }
796 void flush()
override {
797 if (writable_ && !alone_) encode(LZMA_SYNC_FLUSH);
800 void close()
override {
801 if (!backing_)
return;
802 if (writable_ && !finished_) { encode(LZMA_FINISH); finished_ =
true; }
809 void encode(lzma_action action) {
811 codec_.next_out = output_.data();
812 codec_.avail_out = output_.size();
813 const lzma_ret result = lzma_code(&codec_, action);
814 const std::size_t produced = output_.size() - codec_.avail_out;
815 if (produced) backing_->write(output_.data(), produced);
816 if (result == LZMA_STREAM_END) {
if (action == LZMA_FINISH) finished_ =
true;
break; }
817 if (result != LZMA_OK) throwIo(
"LZMA encoding failure");
818 if (action == LZMA_RUN && codec_.avail_in == 0)
break;
819 if (action == LZMA_SYNC_FLUSH && codec_.avail_out != 0)
break;
822 std::unique_ptr<Stream> backing_;
823 bool writable_ =
false;
825 bool finished_ =
false;
826 bool inputEof_ =
false;
827 lzma_stream codec_ = LZMA_STREAM_INIT;
828 std::vector<std::uint8_t> input_;
829 std::vector<std::uint8_t> output_;
832std::unique_ptr<Stream> wrapInputCodec(std::unique_ptr<Stream> stream,
833 const ReaderOptions &options) {
834 switch (options.compression) {
835 case Compression::Auto:
836 case Compression::None:
return stream;
837 case Compression::Gzip:
838 return std::make_unique<GzipCodecStream>(std::move(stream),
false, options.bufferBytes);
839 case Compression::Xz:
840 case Compression::Lzma:
841 return std::make_unique<LzmaCodecStream>(std::move(stream),
false, options.bufferBytes);
843 throwState(
"unknown compression mode");
846std::unique_ptr<Stream> wrapOutputCodec(std::unique_ptr<Stream> stream,
847 const WriterOptions &options) {
848 switch (options.compression) {
849 case Compression::Auto:
850 case Compression::None:
return stream;
851 case Compression::Gzip:
852 return std::make_unique<GzipCodecStream>(std::move(stream),
true, options.bufferBytes,
854 case Compression::Xz:
855 return std::make_unique<LzmaCodecStream>(std::move(stream),
true, options.bufferBytes,
856 options.lzmaPreset, options.lzmaCheck,
false);
857 case Compression::Lzma:
858 return std::make_unique<LzmaCodecStream>(std::move(stream),
true, options.bufferBytes,
859 options.lzmaPreset, options.lzmaCheck,
true);
861 throwState(
"unknown compression mode");
864Compression compressionFor(
const std::filesystem::path &path, Compression requested) {
865 if (requested != Compression::Auto)
867 const std::string extension = lower(path.extension().string());
868 if (extension ==
".gz")
return Compression::Gzip;
869 if (extension ==
".xz")
return Compression::Xz;
870 if (extension ==
".lzma")
return Compression::Lzma;
871 return Compression::None;
875 std::uint64_t device = 0;
876 std::uint64_t inode = 0;
880FileIdentity fileIdentity(
const std::filesystem::path &path) {
881 if (path.empty())
return {};
883 const HANDLE handle = CreateFileW(path.wstring().c_str(), FILE_READ_ATTRIBUTES,
884 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
885 nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
nullptr);
886 if (handle == INVALID_HANDLE_VALUE)
return {};
887 BY_HANDLE_FILE_INFORMATION information{};
888 const bool found = GetFileInformationByHandle(handle, &information) != 0;
890 if (!found)
return {};
891 return {
static_cast<std::uint64_t
>(information.dwVolumeSerialNumber),
892 (
static_cast<std::uint64_t
>(information.nFileIndexHigh) << 32U) |
893 information.nFileIndexLow,
896 struct stat status{};
897 if (stat(path.c_str(), &status) != 0)
return {};
898 return {
static_cast<std::uint64_t
>(status.st_dev),
899 static_cast<std::uint64_t
>(status.st_ino),
true};
903bool sameFile(
const FileIdentity &left,
const FileIdentity &right) {
904 return left.valid && right.valid && left.device == right.device && left.inode == right.inode;
907void replaceFile(
const std::filesystem::path &source,
908 const std::filesystem::path &destination) {
910 if (!MoveFileExW(source.wstring().c_str(), destination.wstring().c_str(),
911 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
912 throwIo(
"unable to replace output after last-page update", destination);
914 std::error_code error;
915 std::filesystem::rename(source, destination, error);
917 throwIo(
"unable to replace output after last-page update: " + error.message(),
924 PathLock(
const std::filesystem::path &path, LockMode mode,
bool create) : path_(path) {
925 if (mode == LockMode::None || path.empty())
return;
927 const DWORD access = mode == LockMode::Exclusive ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ;
928 const DWORD disposition = create ? OPEN_ALWAYS : OPEN_EXISTING;
929 handle_ = CreateFileW(path.wstring().c_str(), access,
930 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
931 nullptr, disposition, FILE_ATTRIBUTE_NORMAL,
nullptr);
932 if (handle_ == INVALID_HANDLE_VALUE)
933 throwIo(
"unable to open file for advisory locking", path);
934 OVERLAPPED overlapped{};
935 overlapped.Offset = MAXDWORD;
936 overlapped.OffsetHigh = 0x7FFFFFFFU;
937 const DWORD flags = LOCKFILE_FAIL_IMMEDIATELY |
938 (mode == LockMode::Exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0U);
939 if (!LockFileEx(handle_, flags, 0, 1, 0, &overlapped)) {
940 CloseHandle(handle_);
941 handle_ = INVALID_HANDLE_VALUE;
942 throwIo(
"unable to acquire advisory file lock", path);
945 const int flags = mode == LockMode::Exclusive ? O_RDWR | (create ? O_CREAT : 0) : O_RDONLY;
946 descriptor_ = ::open(path.c_str(), flags, 0666);
947 if (descriptor_ < 0) throwIo(
"unable to open file for advisory locking", path);
948 const int operation = (mode == LockMode::Exclusive ? LOCK_EX : LOCK_SH) | LOCK_NB;
949 if (flock(descriptor_, operation) != 0) {
950 ::close(descriptor_);
952 throwIo(
"unable to acquire advisory file lock", path);
957 PathLock(
const PathLock &) =
delete;
958 PathLock &operator=(
const PathLock &) =
delete;
961 if (handle_ != INVALID_HANDLE_VALUE) {
962 OVERLAPPED overlapped{};
963 overlapped.Offset = MAXDWORD;
964 overlapped.OffsetHigh = 0x7FFFFFFFU;
965 UnlockFileEx(handle_, 0, 1, 0, &overlapped);
966 CloseHandle(handle_);
969 if (descriptor_ >= 0) {
970 flock(descriptor_, LOCK_UN);
971 ::close(descriptor_);
977 std::filesystem::path path_;
979 HANDLE handle_ = INVALID_HANDLE_VALUE;
981 int descriptor_ = -1;
985std::unique_ptr<PathLock> acquirePathLock(
const std::filesystem::path &path, LockMode mode,
987 return mode == LockMode::None ? nullptr : std::make_unique<PathLock>(path, mode, create);
990void setBinaryMode(FILE *file) {
992 if (_setmode(_fileno(file), _O_BINARY) == -1)
993 throwIo(
"unable to set standard stream to binary mode");
999std::unique_ptr<Stream> openInput(
const std::filesystem::path &path, Compression requested,
1000 std::size_t bufferBytes = 256U * 1024U) {
1001 const Compression compression = compressionFor(path, requested);
1002 if (compression == Compression::Gzip) {
1003 return std::make_unique<GzipStream>(gzopen(path.string().c_str(),
"rb"), path,
false,
1006 FILE *file = std::fopen(path.string().c_str(),
"rb");
1008 throwIo(
"unable to open input: " + std::string(std::strerror(errno)), path);
1009 if (compression == Compression::Xz || compression == Compression::Lzma)
1010 return std::make_unique<LzmaStream>(file, path,
false, bufferBytes);
1011 return std::make_unique<FileStream>(file, path,
true,
false, bufferBytes);
1014std::unique_ptr<Stream> openOutput(
const std::filesystem::path &path, Compression requested,
1015 const char *mode =
"wb",
1016 std::size_t bufferBytes = 256U * 1024U,
1017 int gzipLevel = -1, std::uint32_t lzmaPreset = 6,
1018 LzmaCheck lzmaCheck = LzmaCheck::Crc64) {
1019 const Compression compression = compressionFor(path, requested);
1020 if (gzipLevel < -1 || gzipLevel > 9)
1021 throwState(
"gzip compression level must be between 0 and 9, or -1 for default");
1023 throwState(
"LZMA preset must be between 0 and 9");
1024 if (compression == Compression::Gzip) {
1025 std::string gzipMode =
"wb";
1026 if (gzipLevel >= 0) gzipMode.push_back(
static_cast<char>(
'0' + gzipLevel));
1027 return std::make_unique<GzipStream>(gzopen(path.string().c_str(), gzipMode.c_str()),
1028 path,
true, bufferBytes);
1030 FILE *file = std::fopen(path.string().c_str(), mode);
1032 throwIo(
"unable to open output: " + std::string(std::strerror(errno)), path);
1033 if (compression == Compression::Xz || compression == Compression::Lzma) {
1034 if (std::strcmp(mode,
"wb") != 0) {
1036 throwState(
"compressed append/update is not supported");
1038 return std::make_unique<LzmaStream>(file, path,
true, bufferBytes, lzmaPreset,
1039 lzmaCheck, compression == Compression::Lzma);
1041 return std::make_unique<FileStream>(file, path,
true,
true, bufferBytes);
1044class BufferedStream {
1046 explicit BufferedStream(std::unique_ptr<Stream> stream,
1047 std::uint64_t maxReadBytes = UINT64_MAX,
1048 std::filesystem::path path = {})
1049 : stream_(std::move(stream)), maxReadBytes_(maxReadBytes), path_(std::move(path)) {}
1051 std::size_t
read(
void *data, std::size_t size) {
1052 std::size_t total = 0;
1053 if (pushback_ && size) {
1054 *
static_cast<unsigned char *
>(data) = *pushback_;
1059 while (total < size) {
1060 if (offset_ >= maxReadBytes_)
1061 throwLimit(
"decompressed input exceeds configured limit", path_);
1062 if (readPosition_ < readSize_) {
1063 const std::size_t count = std::min(size - total, readSize_ - readPosition_);
1064 std::memcpy(
static_cast<unsigned char *
>(data) + total,
1065 readBuffer_.data() + readPosition_, count);
1066 readPosition_ += count;
1071 const std::size_t allowed =
static_cast<std::size_t
>(std::min<std::uint64_t>(
1072 size - total, maxReadBytes_ - offset_));
1073 if (allowed < readBuffer_.size()) {
1075 readSize_ = stream_->read(readBuffer_.data(),
static_cast<std::size_t
>(
1076 std::min<std::uint64_t>(readBuffer_.size(), maxReadBytes_ - offset_)));
1081 const std::size_t count = stream_->read(
static_cast<unsigned char *
>(data) + total,
1091 void readExact(
void *data, std::size_t size,
const std::filesystem::path &path,
1092 std::int64_t page = 0) {
1093 if (
read(data, size) != size)
1094 throwFormat(
"unexpected end of file", path, page, std::nullopt, offset_);
1097 std::optional<unsigned char> get() {
1098 unsigned char value = 0;
1099 if (
read(&value, 1) != 1)
1100 return std::nullopt;
1104 void unget(
unsigned char value) {
1106 throwState(
"only one byte of pushback is supported");
1112 void skip(std::uint64_t size,
const std::filesystem::path &path,
1113 std::int64_t page = 0) {
1115 constexpr std::uint64_t seekThreshold = 64U * 1024U;
1116 if (size >= seekThreshold && !pushback_ && stream_->seekable()) {
1117 if (size > UINT64_MAX - tell()) throwLimit(
"file offset overflow", path, page);
1118 const std::uint64_t destination = tell() + size;
1119 if (destination > maxReadBytes_)
1120 throwLimit(
"decompressed input exceeds configured limit", path, page);
1121 const auto streamSize = stream_->size();
1122 if (streamSize && destination > *streamSize) {
1124 throwFormat(
"unexpected end of file", path, page, std::nullopt, offset_);
1131 std::array<unsigned char, 65536> discard;
1133 const std::size_t amount =
static_cast<std::size_t
>(
1134 std::min<std::uint64_t>(size, discard.size()));
1135 if (
read(discard.data(), amount) != amount)
1136 throwFormat(
"unexpected end of file", path, page, std::nullopt, offset_);
1141 std::optional<std::string> line(std::uint64_t maxBytes,
1142 const std::filesystem::path &path) {
1145 const auto byte = get();
1147 return result.empty() ? std::nullopt : std::optional<std::string>(std::move(result));
1151 result.push_back(
static_cast<char>(*
byte));
1152 if (result.size() > maxBytes)
1153 throwLimit(
"line exceeds configured limit", path);
1158 void write(
const void *data, std::size_t size) {
1159 if (readPosition_ < readSize_ || pushback_) {
1160 if (!stream_->seekable())
1161 throwState(
"cannot write after buffered input on a sequential stream");
1162 stream_->seek(offset_);
1163 readPosition_ = readSize_ = 0;
1166 stream_->write(data, size);
1169 void write(std::string_view text) {
write(text.data(), text.size()); }
1170 bool eof()
const {
return !pushback_ && readPosition_ == readSize_ && stream_->eof(); }
1171 bool seekable() const noexcept {
return stream_->seekable(); }
1172 std::uint64_t tell() const noexcept {
return offset_; }
1173 void seek(std::uint64_t offset) {
1174 if (offset > maxReadBytes_)
1175 throwLimit(
"decompressed input exceeds configured limit", path_);
1177 readPosition_ = readSize_ = 0;
1178 stream_->seek(offset);
1181 void flush() { stream_->flush(); }
1182 void truncate(std::uint64_t length) { stream_->truncate(length); }
1183 void close() { stream_->close(); }
1184 void replace(std::unique_ptr<Stream> stream, std::uint64_t offset = 0) {
1185 stream_ = std::move(stream);
1187 readPosition_ = readSize_ = 0;
1189 if (offset) seek(offset);
1191 Stream &raw() {
return *stream_; }
1192 std::uint64_t offset() const noexcept {
return offset_; }
1195 std::unique_ptr<Stream> stream_;
1196 std::optional<unsigned char> pushback_;
1197 std::array<unsigned char, 64U * 1024U> readBuffer_;
1198 std::size_t readPosition_ = 0;
1199 std::size_t readSize_ = 0;
1200 std::uint64_t offset_ = 0;
1201 std::uint64_t maxReadBytes_ = UINT64_MAX;
1202 std::filesystem::path path_;
1205using Tags = std::vector<std::pair<std::string, std::string>>;
1207Tags parseTags(std::string_view text) {
1209 std::size_t position = 0;
1210 while (position < text.size()) {
1211 while (position < text.size() &&
1212 (std::isspace(
static_cast<unsigned char>(text[position])) || text[position] ==
','))
1214 if (position >= text.size())
1216 const std::size_t keyStart = position;
1217 while (position < text.size() && text[position] !=
'=' && text[position] !=
',' &&
1218 !std::isspace(
static_cast<unsigned char>(text[position])))
1220 std::string key(text.substr(keyStart, position - keyStart));
1221 while (position < text.size() && std::isspace(
static_cast<unsigned char>(text[position])))
1223 if (position >= text.size() || text[position] !=
'=')
1224 throwFormat(
"missing '=' after namelist tag " + key);
1226 while (position < text.size() && std::isspace(
static_cast<unsigned char>(text[position])))
1229 if (position < text.size() && text[position] ==
'"') {
1231 bool escaped =
false;
1232 while (position < text.size()) {
1233 const char c = text[position++];
1234 if (c ==
'"' && !escaped)
1237 if (c ==
'\\' && !escaped)
1242 value = unescape(value);
1244 const std::size_t valueStart = position;
1245 while (position < text.size() && text[position] !=
',' &&
1246 !std::isspace(
static_cast<unsigned char>(text[position])))
1248 value = unescape(text.substr(valueStart, position - valueStart));
1250 tags.emplace_back(lower(std::move(key)), std::move(value));
1255std::optional<std::string> tag(
const Tags &tags, std::string_view name) {
1256 for (
const auto &entry : tags)
1257 if (entry.first == name)
1258 return entry.second;
1259 return std::nullopt;
1262std::string requiredTag(
const Tags &tags, std::string_view name) {
1263 auto value = tag(tags, name);
1265 throwFormat(
"missing required namelist tag " + std::string(name));
1274std::optional<Namelist> readNamelist(BufferedStream &stream,
const ReaderOptions &options,
1275 const std::filesystem::path &path,
1276 std::optional<ByteOrder> &commentOrder,
1277 bool &fixedRowComment) {
1278 std::string command;
1279 bool started =
false;
1281 auto line = stream.line(options.limits.maxLayoutCommandBytes, path);
1283 return std::nullopt;
1284 const std::string stripped = trim(*line);
1285 if (!started && !stripped.empty() && stripped.front() ==
'!') {
1286 const std::string special = lower(stripped);
1287 if (special.find(
"big-endian") != std::string::npos)
1288 commentOrder = ByteOrder::Big;
1289 if (special.find(
"little-endian") != std::string::npos)
1290 commentOrder = ByteOrder::Little;
1291 if (special.find(
"fixed-rowcount") != std::string::npos ||
1292 special.find(
"fixed-row-count") != std::string::npos)
1293 fixedRowComment =
true;
1296 std::size_t start = 0;
1298 start = line->find(
'&');
1299 if (start == std::string::npos)
1303 if (!command.empty())
1304 command.push_back(
' ');
1305 command.append(line->substr(start));
1306 if (command.size() > options.limits.maxLayoutCommandBytes)
1307 throwLimit(
"layout command exceeds configured limit", path);
1308 bool quoted =
false;
1309 bool escaped =
false;
1310 for (std::size_t i = 0; i + 3 < command.size(); ++i) {
1311 const char c = command[i];
1312 if (c ==
'"' && !escaped)
1314 if (!quoted && c ==
'&' && lower(command.substr(i, 4)) ==
"&end") {
1315 const std::string body = command.substr(1, i - 1);
1316 const std::size_t separator = body.find_first_of(
" \t\r\n");
1318 result.name = lower(separator == std::string::npos ? body : body.substr(0, separator));
1319 result.tags = parseTags(separator == std::string::npos ? std::string_view{} :
1320 std::string_view(body).substr(separator + 1));
1323 if (c ==
'\\' && !escaped)
1331void validateLayout(Layout &layout) {
1332 std::unordered_set<std::string> names;
1333 auto validateFields = [&](
auto &definitions,
const char *kind) {
1335 for (
auto &definition : definitions) {
1336 if (definition.name.empty())
1337 throwFormat(std::string(kind) +
" name is empty");
1338 if (!names.insert(definition.name).second)
1339 throwFormat(std::string(
"duplicate ") + kind +
" name: " + definition.name);
1342 validateFields(layout.parameters,
"parameter");
1343 validateFields(layout.arrays,
"array");
1344 validateFields(layout.columns,
"column");
1346 for (
const auto &associate : layout.associates) {
1347 if (associate.name.empty())
1348 throwFormat(
"associate name is empty");
1349 if (!names.insert(associate.name).second)
1350 throwFormat(
"duplicate associate name: " + associate.name);
1352 for (
const auto &array : layout.arrays)
1353 if (array.dimensions < 1)
1354 throwFormat(
"array dimensions must be positive", {}, 0, array.name);
1355 if (layout.data.linesPerRow < 0 || layout.data.additionalHeaderLines < 0)
1356 throwFormat(
"negative data-mode count");
1357 if (layout.data.rowCountMode == RowCountMode::Fixed && layout.data.fixedRowIncrement < 1)
1358 throwFormat(
"fixed row increment must be positive");
1360 std::int32_t version = 1;
1361 auto consider = [&](Type type) {
1362 if (type == Type::UInt16 || type == Type::UInt32) version = std::max(version, 2);
1363 if (type == Type::LongDouble) version = std::max(version, 4);
1364 if (type == Type::Int64 || type == Type::UInt64) version = std::max(version, 5);
1366 for (
const auto &definition : layout.parameters) consider(definition.type);
1367 for (
const auto &definition : layout.arrays) consider(definition.type);
1368 for (
const auto &definition : layout.columns) consider(definition.type);
1369 if (layout.data.mode == DataMode::Binary && layout.data.majorOrder == MajorOrder::Column)
1370 version = std::max(version, 3);
1371 layout.version = version;
1374FieldMetadata fieldFrom(
const Tags &tags) {
1375 FieldMetadata field;
1376 field.name = requiredTag(tags,
"name");
1377 field.type = parseType(requiredTag(tags,
"type"));
1378 field.symbol = tag(tags,
"symbol");
1379 field.units = tag(tags,
"units");
1380 field.description = tag(tags,
"description");
1381 field.format = tag(tags,
"format_string");
1385void parseLayoutStream(BufferedStream &stream, Layout &layout,
const ReaderOptions &options,
1386 const std::filesystem::path &path, std::uint32_t depth,
1387 std::unordered_set<std::string> &includeStack,
1388 std::optional<ByteOrder> &commentOrder,
bool &fixedRowComment,
1390 if (depth > options.limits.maxIncludeDepth)
1391 throwLimit(
"maximum include depth exceeded", path);
1393 auto header = stream.line(options.limits.maxLayoutCommandBytes, path);
1394 if (!header || header->rfind(
"SDDS", 0) != 0)
1395 throwFormat(
"missing SDDS version header", path);
1396 const std::int32_t version = parseInteger<std::int32_t>(header->substr(4),
"SDDS version");
1397 if (version < 1 || version > 5)
1398 throwFormat(
"unsupported SDDS version " + std::to_string(version), path);
1399 layout.version = version;
1402 while (
auto command = readNamelist(stream, options, path, commentOrder, fixedRowComment)) {
1403 if (command->name ==
"description") {
1404 layout.description = tag(command->tags,
"text");
1405 layout.contents = tag(command->tags,
"contents");
1406 }
else if (command->name ==
"parameter") {
1407 ParameterDefinition definition;
1408 static_cast<FieldMetadata &
>(definition) = fieldFrom(command->tags);
1409 definition.fixedValue = tag(command->tags,
"fixed_value");
1410 layout.parameters.push_back(std::move(definition));
1411 }
else if (command->name ==
"column") {
1412 ColumnDefinition definition;
1413 static_cast<FieldMetadata &
>(definition) = fieldFrom(command->tags);
1414 if (
auto value = tag(command->tags,
"field_length"))
1415 definition.fieldLength = parseInteger<std::int32_t>(*value,
"field_length");
1416 layout.columns.push_back(std::move(definition));
1417 }
else if (command->name ==
"array") {
1418 ArrayDefinition definition;
1419 static_cast<FieldMetadata &
>(definition) = fieldFrom(command->tags);
1420 if (
auto value = tag(command->tags,
"field_length"))
1421 definition.fieldLength = parseInteger<std::int32_t>(*value,
"field_length");
1422 if (
auto value = tag(command->tags,
"dimensions"))
1423 definition.dimensions = parseInteger<std::int32_t>(*value,
"array dimensions");
1425 definition.dimensions = 1;
1426 definition.groupName = tag(command->tags,
"group_name");
1427 layout.arrays.push_back(std::move(definition));
1428 }
else if (command->name ==
"associate") {
1429 AssociateDefinition definition;
1430 definition.name = requiredTag(command->tags,
"name");
1431 definition.filename = tag(command->tags,
"filename");
1432 definition.path = tag(command->tags,
"path");
1433 definition.description = tag(command->tags,
"description");
1434 definition.contents = tag(command->tags,
"contents");
1435 if (
auto value = tag(command->tags,
"sdds"))
1436 definition.isSdds = parseBool(*value);
1437 layout.associates.push_back(std::move(definition));
1438 }
else if (command->name ==
"include") {
1439 const std::filesystem::path includePath(requiredTag(command->tags,
"filename"));
1440 const std::string key = std::filesystem::absolute(includePath).lexically_normal().string();
1441 if (!includeStack.insert(key).second)
1442 throwFormat(
"cyclic SDDS include: " + includePath.string(), path);
1443 auto includeInput = openInput(includePath, Compression::Auto, options.bufferBytes);
1444 BufferedStream included(std::move(includeInput), options.limits.maxDecompressedBytes,
1446 parseLayoutStream(included, layout, options, includePath, depth + 1, includeStack,
1447 commentOrder, fixedRowComment,
false);
1448 includeStack.erase(key);
1449 }
else if (command->name ==
"data") {
1450 const std::string mode = lower(requiredTag(command->tags,
"mode"));
1451 if (mode ==
"binary") layout.data.mode = DataMode::Binary;
1452 else if (mode ==
"ascii") layout.data.mode = DataMode::Ascii;
1453 else throwFormat(
"invalid SDDS data mode: " + mode, path);
1454 if (
auto value = tag(command->tags,
"lines_per_row"))
1455 layout.data.linesPerRow = parseInteger<std::int32_t>(*value,
"lines_per_row");
1456 if (
auto value = tag(command->tags,
"additional_header_lines"))
1457 layout.data.additionalHeaderLines = parseInteger<std::int32_t>(*value,
"additional_header_lines");
1458 if (
auto value = tag(command->tags,
"column_major_order"))
1459 layout.data.majorOrder = parseBool(*value) ? MajorOrder::Column : MajorOrder::Row;
1460 if (
auto value = tag(command->tags,
"endian")) {
1461 const std::string order = lower(*value);
1462 if (order ==
"big") layout.data.byteOrder = ByteOrder::Big;
1463 else if (order ==
"little") layout.data.byteOrder = ByteOrder::Little;
1464 else throwFormat(
"invalid endian value: " + *value, path);
1466 if (
auto value = tag(command->tags,
"no_row_counts"))
1467 layout.data.rowCountMode = parseBool(*value) ? RowCountMode::None : RowCountMode::Variable;
1468 if (
auto value = tag(command->tags,
"fixed_row_count"))
1469 if (parseBool(*value)) layout.data.rowCountMode = RowCountMode::Fixed;
1470 if (fixedRowComment)
1471 layout.data.rowCountMode = RowCountMode::Fixed;
1473 layout.data.byteOrder = *commentOrder;
1474 for (std::int32_t i = 0; i < layout.data.additionalHeaderLines; ++i)
1475 if (!stream.line(options.limits.maxLayoutCommandBytes, path))
1476 throwFormat(
"unexpected EOF in additional header lines", path);
1479 throwFormat(
"unknown SDDS layout command: " + command->name, path);
1483 throwFormat(
"missing SDDS data command", path);
1488LayoutBuilder::LayoutBuilder(Layout layout) : layout_(std::move(layout)) {}
1490LayoutBuilder &LayoutBuilder::setDescription(std::optional<std::string> text,
1491 std::optional<std::string> contents) {
1492 layout_.description = std::move(text);
1493 layout_.contents = std::move(contents);
1497LayoutBuilder &LayoutBuilder::setDataOptions(DataOptions options) {
1498 layout_.data = options;
1502LayoutBuilder &LayoutBuilder::addParameter(ParameterDefinition definition) {
1503 layout_.parameters.push_back(std::move(definition));
1507LayoutBuilder &LayoutBuilder::addArray(ArrayDefinition definition) {
1508 layout_.arrays.push_back(std::move(definition));
1512LayoutBuilder &LayoutBuilder::addColumn(ColumnDefinition definition) {
1513 layout_.columns.push_back(std::move(definition));
1517LayoutBuilder &LayoutBuilder::addAssociate(AssociateDefinition definition) {
1518 layout_.associates.push_back(std::move(definition));
1522Layout LayoutBuilder::build()
const {
1523 Layout result = layout_;
1524 validateLayout(result);
1528Page::Page(std::shared_ptr<const Layout> layout, LoadMode load) : layout_(std::move(layout)) {
1530 throwState(
"page requires a layout");
1531 parameters_.reserve(layout_->parameters.size());
1532 for (
const auto &definition : layout_->parameters)
1533 parameters_.push_back(defaultScalar(definition.type));
1534 arrays_.reserve(layout_->arrays.size());
1535 for (
const auto &definition : layout_->arrays)
1536 arrays_.push_back({std::vector<std::int32_t>(static_cast<std::size_t>(definition.dimensions), 0),
1537 emptyValues(definition.type)});
1538 columns_.reserve(layout_->columns.size());
1539 for (
const auto &definition : layout_->columns)
1540 columns_.push_back(emptyValues(definition.type));
1541 const bool loaded = load == LoadMode::All;
1542 parametersLoaded_.assign(parameters_.size(), loaded);
1543 arraysLoaded_.assign(arrays_.size(), loaded);
1544 columnsLoaded_.assign(columns_.size(), loaded);
1547const Layout &Page::layout()
const {
1548 if (!layout_) throwState(
"page has no layout");
1552bool Page::parameterLoaded(std::size_t index)
const {
return parametersLoaded_.at(index); }
1553bool Page::parameterLoaded(std::string_view name)
const {
1554 return parameterLoaded(layout().parameterIndex(name));
1556bool Page::arrayLoaded(std::size_t index)
const {
return arraysLoaded_.at(index); }
1557bool Page::arrayLoaded(std::string_view name)
const {
return arrayLoaded(layout().arrayIndex(name)); }
1558bool Page::columnLoaded(std::size_t index)
const {
return columnsLoaded_.at(index); }
1559bool Page::columnLoaded(std::string_view name)
const {
1560 return columnLoaded(layout().columnIndex(name));
1562bool Page::allFieldsLoaded() const noexcept {
1563 const auto all = [](
const std::vector<bool> &loaded) {
1564 return std::all_of(loaded.begin(), loaded.end(), [](
bool value) { return value; });
1566 return all(parametersLoaded_) && all(arraysLoaded_) && all(columnsLoaded_);
1569const Scalar &Page::parameter(std::size_t index)
const {
1570 if (!parameterLoaded(index))
1571 throwState(
"parameter was not requested: " + layout().parameters.at(index).name);
1572 return parameters_.at(index);
1574const Scalar &Page::parameter(std::string_view name)
const {
return parameter(layout().parameterIndex(name)); }
1575const ArrayData &Page::array(std::size_t index)
const {
1576 if (!arrayLoaded(index))
1577 throwState(
"array was not requested: " + layout().arrays.at(index).name);
1578 return arrays_.at(index);
1580const ArrayData &Page::array(std::string_view name)
const {
return array(layout().arrayIndex(name)); }
1581const Values &Page::column(std::size_t index)
const {
1582 if (!columnLoaded(index))
1583 throwState(
"column was not requested: " + layout().columns.at(index).name);
1584 return columns_.at(index);
1586const Values &Page::column(std::string_view name)
const {
return column(layout().columnIndex(name)); }
1588void Page::setParameter(std::size_t index, Scalar value) {
1589 if (typeOf(value) != layout().parameters.at(index).type)
1590 throwType(
"parameter type mismatch", layout().parameters.at(index).name);
1591 parameters_.at(index) = std::move(value);
1592 parametersLoaded_.at(index) =
true;
1594void Page::setParameter(std::string_view name, Scalar value) {
1595 setParameter(layout().parameterIndex(name), std::move(value));
1597void Page::setArray(std::size_t index, ArrayData value) {
1598 const auto &definition = layout().arrays.at(index);
1599 if (typeOf(value.values) != definition.type)
1600 throwType(
"array type mismatch", definition.name);
1601 if (value.dimensions.size() !=
static_cast<std::size_t
>(definition.dimensions))
1602 throwType(
"array dimension count mismatch", definition.name);
1603 std::uint64_t elements = 1;
1604 for (
const auto dimension : value.dimensions) {
1606 throwType(
"negative array dimension", definition.name);
1607 if (dimension && elements > UINT64_MAX /
static_cast<std::uint64_t
>(dimension))
1608 throwLimit(
"array dimension product overflow", {}, 0, definition.name);
1609 elements *=
static_cast<std::uint64_t
>(dimension);
1611 if (elements != valuesSize(value.values))
1612 throwType(
"array element count does not match dimensions", definition.name);
1613 arrays_.at(index) = std::move(value);
1614 arraysLoaded_.at(index) =
true;
1616void Page::setArray(std::string_view name, ArrayData value) {
1617 setArray(layout().arrayIndex(name), std::move(value));
1619void Page::setColumn(std::size_t index, Values value) {
1620 const auto &definition = layout().columns.at(index);
1621 if (typeOf(value) != definition.type)
1622 throwType(
"column type mismatch", definition.name);
1623 columns_.at(index) = std::move(value);
1624 columnsLoaded_.at(index) =
true;
1626 for (std::size_t column = 0; column < columns_.size(); ++column)
1627 if (columnsLoaded_[column])
1628 rowCount_ = std::max(rowCount_,
static_cast<std::int64_t
>(valuesSize(columns_[column])));
1630void Page::setColumn(std::string_view name, Values value) {
1631 setColumn(layout().columnIndex(name), std::move(value));
1636bool nativeBigEndian() noexcept {
1637 const std::uint16_t value = 0x0102;
1638 return *
reinterpret_cast<const unsigned char *
>(&value) == 0x01;
1641ByteOrder resolvedOrder(ByteOrder order)
noexcept {
1642 if (order != ByteOrder::Native)
1644 return nativeBigEndian() ? ByteOrder::Big : ByteOrder::Little;
1647bool mustSwap(ByteOrder order)
noexcept {
1648 return (resolvedOrder(order) == ByteOrder::Big) != nativeBigEndian();
1652T readPod(BufferedStream &stream, ByteOrder order,
const std::filesystem::path &path,
1653 std::int64_t page) {
1655 stream.readExact(&value,
sizeof(value), path, page);
1656 if (mustSwap(order)) {
1657 auto *bytes =
reinterpret_cast<unsigned char *
>(&value);
1658 std::reverse(bytes, bytes +
sizeof(value));
1664void writePod(BufferedStream &stream, T value, ByteOrder order) {
1665 if (mustSwap(order)) {
1666 auto *bytes =
reinterpret_cast<unsigned char *
>(&value);
1667 std::reverse(bytes, bytes +
sizeof(value));
1669 stream.write(&value,
sizeof(value));
1672long double decodeExtended80(std::array<unsigned char, 16> bytes, ByteOrder order) {
1673 if (resolvedOrder(order) == ByteOrder::Big)
1674 std::reverse(bytes.begin(), bytes.begin() + 12);
1675 std::uint64_t significand = 0;
1676 for (
int i = 7; i >= 0; --i)
1677 significand = (significand << 8U) | bytes[
static_cast<std::size_t
>(i)];
1678 const std::uint16_t signExponent =
1679 static_cast<std::uint16_t
>(bytes[8]) |
1680 static_cast<std::uint16_t
>(
static_cast<std::uint16_t
>(bytes[9]) << 8U);
1681 const bool negative = (signExponent & 0x8000U) != 0;
1682 const std::uint16_t exponent = signExponent & 0x7fffU;
1683 if (exponent == 0 && significand == 0)
1684 return negative ? -0.0L : 0.0L;
1685 if (exponent == 0x7fffU) {
1686 if ((significand & UINT64_C(0x7fffffffffffffff)) == 0)
1687 return negative ? -std::numeric_limits<long double>::infinity()
1688 : std::numeric_limits<long double>::infinity();
1689 return std::numeric_limits<long double>::quiet_NaN();
1691 const long double fraction = std::ldexp(
static_cast<long double>(significand), -63);
1692 const long double value = std::ldexp(fraction,
static_cast<int>(exponent) - 16383);
1693 return negative ? -value : value;
1696std::array<unsigned char, 16> encodeExtended80(
long double value, ByteOrder order) {
1697 std::array<unsigned char, 16> bytes{};
1698 const bool negative = std::signbit(value);
1699 const long double magnitude = std::fabs(value);
1700 std::uint16_t exponent = 0;
1701 std::uint64_t significand = 0;
1702 if (std::isnan(magnitude)) {
1704 significand = UINT64_C(0xc000000000000000);
1705 }
else if (std::isinf(magnitude)) {
1707 significand = UINT64_C(0x8000000000000000);
1708 }
else if (magnitude != 0) {
1709 int binaryExponent = 0;
1710 long double fraction = std::frexp(magnitude, &binaryExponent);
1713 const int biased = binaryExponent + 16383;
1716 significand =
static_cast<std::uint64_t
>(
1717 std::ldexp(magnitude, 63 + 16382));
1718 }
else if (biased >= 0x7fff) {
1720 significand = UINT64_C(0x8000000000000000);
1722 exponent =
static_cast<std::uint16_t
>(biased);
1723 const long double scaled = std::ldexp(fraction, 63);
1724 significand = scaled >= std::ldexp(1.0L, 64)
1726 :
static_cast<std::uint64_t
>(scaled);
1729 for (std::size_t i = 0; i < 8; ++i) {
1730 bytes[i] =
static_cast<unsigned char>(significand & 0xffU);
1733 const std::uint16_t signExponent = exponent | (negative ? 0x8000U : 0U);
1734 bytes[8] =
static_cast<unsigned char>(signExponent & 0xffU);
1735 bytes[9] =
static_cast<unsigned char>(signExponent >> 8U);
1736 if (resolvedOrder(order) == ByteOrder::Big)
1737 std::reverse(bytes.begin(), bytes.begin() + 12);
1741Scalar parseAsciiScalar(Type type,
const std::string &text) {
1742 if (type == Type::String)
1744 if (type == Type::Character) {
1745 if (text.empty()) throwFormat(
"empty character value");
1746 return text.front();
1748 const std::string value = trim(text);
1749 char *end =
nullptr;
1752 case Type::LongDouble: {
1753 const long double result = std::strtold(value.c_str(), &end);
1754 if (errno == ERANGE || end != value.c_str() + value.size())
1755 throwFormat(
"invalid longdouble value: " + text);
1758 case Type::Double: {
1759 const double result = std::strtod(value.c_str(), &end);
1760 if (errno == ERANGE || end != value.c_str() + value.size())
1761 throwFormat(
"invalid double value: " + text);
1765 const float result = std::strtof(value.c_str(), &end);
1766 if (errno == ERANGE || end != value.c_str() + value.size())
1767 throwFormat(
"invalid float value: " + text);
1770 case Type::Int64:
return parseInteger<std::int64_t>(value,
"long64 value");
1771 case Type::UInt64:
return parseInteger<std::uint64_t>(value,
"ulong64 value");
1772 case Type::Int32:
return parseInteger<std::int32_t>(value,
"long value");
1773 case Type::UInt32:
return parseInteger<std::uint32_t>(value,
"ulong value");
1774 case Type::Int16:
return parseInteger<std::int16_t>(value,
"short value");
1775 case Type::UInt16:
return parseInteger<std::uint16_t>(value,
"ushort value");
1777 case Type::Character:
break;
1779 throwType(
"unknown ASCII scalar type");
1782std::string formatAsciiScalar(
const Scalar &value) {
1783 return std::visit([](
const auto &item) -> std::string {
1784 using T = std::decay_t<
decltype(item)>;
1785 if constexpr (std::is_same_v<T, std::string>) {
1786 return quoteRequired(item);
1787 }
else if constexpr (std::is_same_v<T, char>) {
1788 return quoteRequired(std::string(1, item));
1789 }
else if constexpr (std::is_floating_point_v<T>) {
1790 std::ostringstream output;
1791 output.imbue(std::locale::classic());
1792 output << std::setprecision(std::numeric_limits<T>::max_digits10) << item;
1793 return output.str();
1794 }
else if constexpr (std::is_signed_v<T>) {
1795 return std::to_string(
static_cast<long long>(item));
1797 return std::to_string(
static_cast<unsigned long long>(item));
1802Scalar readBinaryScalar(BufferedStream &stream, Type type, ByteOrder order,
1803 LongDoubleEncoding longDoubleEncoding,
1804 const ReaderLimits &limits,
const std::filesystem::path &path,
1805 std::int64_t page) {
1807 case Type::LongDouble:
1808 if (longDoubleEncoding == LongDoubleEncoding::LegacyFloat64)
1809 return static_cast<long double>(readPod<double>(stream, order, path, page));
1811 std::array<unsigned char, 16> bytes{};
1812 stream.readExact(bytes.data(), bytes.size(), path, page);
1813 return decodeExtended80(bytes, order);
1815 case Type::Double:
return readPod<double>(stream, order, path, page);
1816 case Type::Float:
return readPod<float>(stream, order, path, page);
1817 case Type::Int64:
return readPod<std::int64_t>(stream, order, path, page);
1818 case Type::UInt64:
return readPod<std::uint64_t>(stream, order, path, page);
1819 case Type::Int32:
return readPod<std::int32_t>(stream, order, path, page);
1820 case Type::UInt32:
return readPod<std::uint32_t>(stream, order, path, page);
1821 case Type::Int16:
return readPod<std::int16_t>(stream, order, path, page);
1822 case Type::UInt16:
return readPod<std::uint16_t>(stream, order, path, page);
1823 case Type::Character: {
1825 stream.readExact(&value, 1, path, page);
1828 case Type::String: {
1829 const std::int32_t length = readPod<std::int32_t>(stream, order, path, page);
1831 throwFormat(
"negative binary string length", path, page, std::nullopt, stream.offset());
1832 if (
static_cast<std::uint64_t
>(length) > limits.maxStringBytes)
1833 throwLimit(
"binary string exceeds configured limit", path, page);
1834 std::string value(
static_cast<std::size_t
>(length),
'\0');
1836 stream.readExact(value.data(), value.size(), path, page);
1840 throwType(
"unknown binary scalar type");
1843std::uint64_t binaryScalarBytes(Type type, LongDoubleEncoding longDoubleEncoding) {
1845 case Type::LongDouble:
1846 return longDoubleEncoding == LongDoubleEncoding::LegacyFloat64 ?
sizeof(double) : 16U;
1847 case Type::Double:
return sizeof(double);
1848 case Type::Float:
return sizeof(float);
1849 case Type::Int64:
return sizeof(std::int64_t);
1850 case Type::UInt64:
return sizeof(std::uint64_t);
1851 case Type::Int32:
return sizeof(std::int32_t);
1852 case Type::UInt32:
return sizeof(std::uint32_t);
1853 case Type::Int16:
return sizeof(std::int16_t);
1854 case Type::UInt16:
return sizeof(std::uint16_t);
1855 case Type::Character:
return 1U;
1856 case Type::String:
return 0U;
1858 throwType(
"unknown binary scalar type");
1861void skipBinaryScalar(BufferedStream &stream, Type type, ByteOrder order,
1862 LongDoubleEncoding longDoubleEncoding,
const ReaderLimits &limits,
1863 const std::filesystem::path &path, std::int64_t page) {
1864 if (type != Type::String) {
1865 stream.skip(binaryScalarBytes(type, longDoubleEncoding), path, page);
1868 const std::int32_t length = readPod<std::int32_t>(stream, order, path, page);
1870 throwFormat(
"negative binary string length", path, page, std::nullopt, stream.offset());
1871 if (
static_cast<std::uint64_t
>(length) > limits.maxStringBytes)
1872 throwLimit(
"binary string exceeds configured limit", path, page);
1873 stream.skip(
static_cast<std::uint64_t
>(length), path, page);
1876void reserveValues(Values &values, std::size_t count) {
1877 std::visit([&](
auto &items) { items.reserve(count); }, values);
1881T fixedValue(
const unsigned char *data, ByteOrder order) {
1883 std::memcpy(&value, data,
sizeof(value));
1884 if (mustSwap(order)) {
1885 auto *bytes =
reinterpret_cast<unsigned char *
>(&value);
1886 std::reverse(bytes, bytes +
sizeof(value));
1891void appendFixedBinaryValue(Values &values, Type type,
const unsigned char *data,
1892 ByteOrder order, LongDoubleEncoding longDoubleEncoding) {
1894 case Type::LongDouble:
1895 if (longDoubleEncoding == LongDoubleEncoding::LegacyFloat64)
1896 std::get<std::vector<long double>>(values).push_back(fixedValue<double>(data, order));
1898 std::array<unsigned char, 16> bytes{};
1899 std::memcpy(bytes.data(), data, bytes.size());
1900 std::get<std::vector<long double>>(values).push_back(decodeExtended80(bytes, order));
1904 std::get<std::vector<double>>(values).push_back(fixedValue<double>(data, order));
return;
1906 std::get<std::vector<float>>(values).push_back(fixedValue<float>(data, order));
return;
1908 std::get<std::vector<std::int64_t>>(values).push_back(
1909 fixedValue<std::int64_t>(data, order));
return;
1911 std::get<std::vector<std::uint64_t>>(values).push_back(
1912 fixedValue<std::uint64_t>(data, order));
return;
1914 std::get<std::vector<std::int32_t>>(values).push_back(
1915 fixedValue<std::int32_t>(data, order));
return;
1917 std::get<std::vector<std::uint32_t>>(values).push_back(
1918 fixedValue<std::uint32_t>(data, order));
return;
1920 std::get<std::vector<std::int16_t>>(values).push_back(
1921 fixedValue<std::int16_t>(data, order));
return;
1923 std::get<std::vector<std::uint16_t>>(values).push_back(
1924 fixedValue<std::uint16_t>(data, order));
return;
1925 case Type::Character:
1926 std::get<std::vector<char>>(values).push_back(
static_cast<char>(*data));
return;
1927 case Type::String: throwState(
"string is not a fixed-width binary value");
1929 throwType(
"unknown binary scalar type");
1932void writeBinaryScalar(BufferedStream &stream,
const Scalar &value, ByteOrder order,
1933 LongDoubleEncoding longDoubleEncoding) {
1934 std::visit([&](
const auto &item) {
1935 using T = std::decay_t<
decltype(item)>;
1936 if constexpr (std::is_same_v<T, long double>) {
1937 if (longDoubleEncoding == LongDoubleEncoding::LegacyFloat64)
1938 writePod(stream,
static_cast<double>(item), order);
1940 const auto bytes = encodeExtended80(item, order);
1941 stream.write(bytes.data(), bytes.size());
1943 }
else if constexpr (std::is_same_v<T, std::string>) {
1944 if (item.size() >
static_cast<std::size_t
>(INT32_MAX))
1945 throwLimit(
"string is too large for SDDS binary encoding");
1946 writePod(stream,
static_cast<std::int32_t
>(item.size()), order);
1947 stream.write(item.data(), item.size());
1948 }
else if constexpr (std::is_same_v<T, char>) {
1949 stream.write(&item, 1);
1951 writePod(stream, item, order);
1957void storeFixedValue(
unsigned char *destination, T value, ByteOrder order) {
1958 if (mustSwap(order)) {
1959 auto *bytes =
reinterpret_cast<unsigned char *
>(&value);
1960 std::reverse(bytes, bytes +
sizeof(value));
1962 std::memcpy(destination, &value,
sizeof(value));
1965void storeFixedBinaryValue(
unsigned char *destination,
const Values &values,
1966 std::size_t index, Type type, ByteOrder order,
1967 LongDoubleEncoding longDoubleEncoding) {
1969 case Type::LongDouble: {
1970 const long double value = std::get<std::vector<long double>>(values).at(index);
1971 if (longDoubleEncoding == LongDoubleEncoding::LegacyFloat64)
1972 storeFixedValue(destination,
static_cast<double>(value), order);
1974 const auto bytes = encodeExtended80(value, order);
1975 std::memcpy(destination, bytes.data(), bytes.size());
1980 storeFixedValue(destination, std::get<std::vector<double>>(values).at(index), order);
return;
1982 storeFixedValue(destination, std::get<std::vector<float>>(values).at(index), order);
return;
1984 storeFixedValue(destination, std::get<std::vector<std::int64_t>>(values).at(index), order);
1987 storeFixedValue(destination, std::get<std::vector<std::uint64_t>>(values).at(index), order);
1990 storeFixedValue(destination, std::get<std::vector<std::int32_t>>(values).at(index), order);
1993 storeFixedValue(destination, std::get<std::vector<std::uint32_t>>(values).at(index), order);
1996 storeFixedValue(destination, std::get<std::vector<std::int16_t>>(values).at(index), order);
1999 storeFixedValue(destination, std::get<std::vector<std::uint16_t>>(values).at(index), order);
2001 case Type::Character:
2002 *destination =
static_cast<unsigned char>(std::get<std::vector<char>>(values).at(index));
2004 case Type::String: throwState(
"string is not a fixed-width binary value");
2006 throwType(
"unknown binary scalar type");
2009void appendScalar(Values &values, Scalar value) {
2010 if (typeOf(values) != typeOf(value))
2011 throwType(
"internal scalar/vector type mismatch");
2012 std::visit([&](
auto &vector) {
2013 using VectorType = std::decay_t<
decltype(vector)>;
2014 using T =
typename VectorType::value_type;
2015 vector.push_back(std::get<T>(std::move(value)));
2019Scalar scalarAt(
const Values &values, std::size_t index) {
2020 return std::visit([&](
const auto &vector) -> Scalar {
return vector.at(index); }, values);
2023std::vector<std::string> splitAsciiTokens(std::string_view line) {
2024 std::vector<std::string> tokens;
2025 std::size_t position = 0;
2026 while (position < line.size()) {
2027 while (position < line.size() && std::isspace(
static_cast<unsigned char>(line[position])))
2029 if (position >= line.size())
break;
2030 if (line[position] ==
'!')
break;
2032 if (line[position] ==
'"') {
2034 bool escaped =
false;
2035 while (position < line.size()) {
2036 const char c = line[position++];
2037 if (c ==
'"' && !escaped)
break;
2039 if (c ==
'\\' && !escaped) escaped =
true;
2040 else escaped =
false;
2042 token = unescape(token);
2044 const std::size_t start = position;
2045 while (position < line.size() && !std::isspace(
static_cast<unsigned char>(line[position])))
2047 token = unescape(line.substr(start, position - start));
2049 tokens.push_back(std::move(token));
2056 AsciiCursor(BufferedStream &stream,
const ReaderOptions &options,
2057 std::filesystem::path path, std::optional<std::string> pending = std::nullopt)
2058 : stream_(stream), options_(options), path_(std::move(path)), pending_(std::move(pending)) {}
2060 std::optional<std::string> nextLine(
bool keepBlank =
true) {
2062 std::optional<std::string> line;
2064 line = std::move(pending_);
2067 line = stream_.line(options_.limits.maxLayoutCommandBytes, path_);
2069 if (!line)
return std::nullopt;
2070 const std::string stripped = trim(*line);
2071 if (!stripped.empty() && stripped.front() ==
'!')
continue;
2072 if (!keepBlank && stripped.empty())
continue;
2077 std::vector<std::string> requiredTokens(
const std::string &what) {
2078 auto line = nextLine(
false);
2079 if (!line) throwFormat(
"unexpected EOF reading " + what, path_);
2080 auto tokens = splitAsciiTokens(*line);
2081 if (tokens.empty()) throwFormat(
"missing " + what, path_);
2086 BufferedStream &stream_;
2087 const ReaderOptions &options_;
2088 std::filesystem::path path_;
2089 std::optional<std::string> pending_;
2092std::uint64_t checkedArrayElements(
const std::vector<std::int32_t> &dimensions,
2093 const ReaderLimits &limits,
2094 const std::filesystem::path &path,
2095 std::int64_t page,
const std::string &name) {
2096 std::uint64_t elements = 1;
2097 for (
const auto dimension : dimensions) {
2099 throwFormat(
"negative array dimension", path, page, name);
2100 if (dimension && elements > UINT64_MAX /
static_cast<std::uint64_t
>(dimension))
2101 throwLimit(
"array dimension product overflow", path, page, name);
2102 elements *=
static_cast<std::uint64_t
>(dimension);
2104 if (elements > limits.maxElements || elements >
static_cast<std::uint64_t
>(SIZE_MAX))
2105 throwLimit(
"array exceeds configured element limit", path, page, name);
2109std::string fieldTags(
const FieldMetadata &field) {
2110 std::string result =
"name=" + quoteRequired(field.name) +
", ";
2111 if (field.symbol) result +=
"symbol=" + quote(field.symbol) +
", ";
2112 if (field.units) result +=
"units=" + quote(field.units) +
", ";
2113 if (field.description) result +=
"description=" + quote(field.description) +
", ";
2114 if (field.format) result +=
"format_string=" + quote(field.format) +
", ";
2115 result +=
"type=" + std::string(typeName(field.type)) +
", ";
2119void writeLayout(BufferedStream &stream, Layout &layout,
const WriterOptions &options) {
2120 validateLayout(layout);
2121 layout.version = std::max(layout.version, options.minimumVersion);
2122 if (layout.version < 1 || layout.version > 5)
2123 throwState(
"minimum SDDS version must be in the range 1 through 5");
2124 stream.write(
"SDDS" + std::to_string(layout.version) +
"\n");
2125 if (layout.data.mode == DataMode::Binary) {
2126 stream.write(resolvedOrder(layout.data.byteOrder) == ByteOrder::Big
2127 ?
"!# big-endian\n" :
"!# little-endian\n");
2129 if (layout.data.rowCountMode == RowCountMode::Fixed)
2130 stream.write(
"!# fixed-rowcount\n");
2131 if (layout.description || layout.contents) {
2132 std::string command =
"&description ";
2133 if (layout.description) command +=
"text=" + quote(layout.description) +
", ";
2134 if (layout.contents) command +=
"contents=" + quote(layout.contents) +
", ";
2135 stream.write(command +
"&end\n");
2137 for (
const auto &definition : layout.parameters) {
2138 std::string command =
"¶meter " + fieldTags(definition);
2139 if (definition.fixedValue) command +=
"fixed_value=" + quote(definition.fixedValue) +
", ";
2140 stream.write(command +
"&end\n");
2142 for (
const auto &definition : layout.arrays) {
2143 std::string command =
"&array " + fieldTags(definition);
2144 if (definition.groupName) command +=
"group_name=" + quote(definition.groupName) +
", ";
2145 if (definition.fieldLength) command +=
"field_length=" + std::to_string(definition.fieldLength) +
", ";
2146 command +=
"dimensions=" + std::to_string(definition.dimensions) +
", &end\n";
2147 stream.write(command);
2149 for (
const auto &definition : layout.columns) {
2150 std::string command =
"&column " + fieldTags(definition);
2151 if (definition.fieldLength) command +=
"field_length=" + std::to_string(definition.fieldLength) +
", ";
2152 stream.write(command +
"&end\n");
2154 for (
const auto &definition : layout.associates) {
2155 std::string command =
"&associate name=" + quoteRequired(definition.name) +
", ";
2156 if (definition.filename) command +=
"filename=" + quote(definition.filename) +
", ";
2157 if (definition.path) command +=
"path=" + quote(definition.path) +
", ";
2158 if (definition.description) command +=
"description=" + quote(definition.description) +
", ";
2159 if (definition.contents) command +=
"contents=" + quote(definition.contents) +
", ";
2160 command += std::string(
"sdds=") + (definition.isSdds ?
"1" :
"0") +
", &end\n";
2161 stream.write(command);
2163 std::string data =
"&data mode=";
2164 data += layout.data.mode == DataMode::Binary ?
"binary, " :
"ascii, ";
2165 if (layout.data.mode == DataMode::Ascii && layout.data.linesPerRow != 1)
2166 data +=
"lines_per_row=" + std::to_string(layout.data.linesPerRow) +
", ";
2167 if (layout.data.rowCountMode == RowCountMode::None) data +=
"no_row_counts=1, ";
2168 if (layout.data.rowCountMode == RowCountMode::Fixed) data +=
"fixed_row_count=1, ";
2169 if (layout.data.mode == DataMode::Binary) {
2170 data += resolvedOrder(layout.data.byteOrder) == ByteOrder::Big ?
"endian=big, " :
"endian=little, ";
2171 if (layout.data.majorOrder == MajorOrder::Column) data +=
"column_major_order=1, ";
2173 if (layout.data.additionalHeaderLines)
2174 data +=
"additional_header_lines=" + std::to_string(layout.data.additionalHeaderLines) +
", ";
2175 stream.write(data +
"&end\n");
2176 for (std::int32_t i = 0; i < layout.data.additionalHeaderLines; ++i)
2177 stream.write(
"! additional SDDS header line\n");
2180void validatePage(
const Page &page,
const Layout &layout) {
2181 if (page.parameters().size() != layout.parameters.size() ||
2182 page.arrays().size() != layout.arrays.size() || page.columns().size() != layout.columns.size())
2183 throwType(
"page does not match writer layout");
2184 if (!page.allFieldsLoaded())
2185 throwState(
"a projected page must be completed before it can be written");
2186 std::optional<std::size_t> rows;
2187 for (std::size_t i = 0; i < layout.parameters.size(); ++i)
2188 if (typeOf(page.parameters()[i]) != layout.parameters[i].type)
2189 throwType(
"parameter type mismatch", layout.parameters[i].name);
2190 for (std::size_t i = 0; i < layout.arrays.size(); ++i) {
2191 if (typeOf(page.arrays()[i].values) != layout.arrays[i].type)
2192 throwType(
"array type mismatch", layout.arrays[i].name);
2193 if (page.arrays()[i].dimensions.size() !=
static_cast<std::size_t
>(layout.arrays[i].dimensions))
2194 throwType(
"array dimension count mismatch", layout.arrays[i].name);
2195 ReaderLimits unlimited;
2196 const auto count = checkedArrayElements(page.arrays()[i].dimensions, unlimited, {}, 0,
2197 layout.arrays[i].name);
2198 if (count != valuesSize(page.arrays()[i].values))
2199 throwType(
"array element count mismatch", layout.arrays[i].name);
2201 for (std::size_t i = 0; i < layout.columns.size(); ++i) {
2202 if (typeOf(page.columns()[i]) != layout.columns[i].type)
2203 throwType(
"column type mismatch", layout.columns[i].name);
2204 if (!rows) rows = valuesSize(page.columns()[i]);
2205 else if (*rows != valuesSize(page.columns()[i]))
2206 throwType(
"columns have inconsistent row counts", layout.columns[i].name);
2212 std::int64_t rows = 0;
2213 std::int64_t rawRows = 0;
2214 bool recovered =
false;
2217bool recoveryEnabled(
const Layout &layout,
const ReaderOptions &options) {
2218 return options.recovery == RecoveryMode::Recover ||
2219 (options.recovery == RecoveryMode::Automatic &&
2220 layout.data.rowCountMode == RowCountMode::Fixed);
2223bool endOfStreamError(
const FormatError &error) {
2224 const std::string message(error.what());
2225 return message.find(
"end of file") != std::string::npos ||
2226 message.find(
"truncated LZMA/XZ stream") != std::string::npos;
2229struct ResolvedReadRequest {
2230 std::vector<bool> parameters;
2231 std::vector<bool> arrays;
2232 std::vector<bool> columns;
2236template <
class Definition>
2237std::vector<bool> resolveFields(
const FieldSelection &selection,
2238 const std::vector<Definition> &definitions,
2239 std::uint32_t &projected,
const ReaderOptions &options) {
2240 if (selection.all && !selection.names.empty())
2241 throwState(
"all-fields projection cannot also contain names");
2242 std::vector<bool> result(definitions.size(), selection.all);
2243 if (!selection.all) {
2244 for (
const auto &name : selection.names) {
2245 const auto found = std::find_if(definitions.begin(), definitions.end(),
2246 [&](
const auto &definition) {
2247 return definition.name == name;
2249 if (found == definitions.end())
2250 throwType(
"unknown projected field: " + name, name);
2251 result[
static_cast<std::size_t
>(found - definitions.begin())] =
true;
2254 for (
const bool selected : result) {
2255 if (!selected)
continue;
2256 if (projected == options.limits.maxProjectedFields)
2257 throwLimit(
"projection exceeds configured field limit");
2263ResolvedReadRequest resolveRequest(
const Layout &layout,
const ReadRequest &request,
2264 const ReaderOptions &options) {
2265 if (request.rows.first < 0 || request.rows.stride < 1 ||
2266 (request.rows.count && *request.rows.count < 0) ||
2267 (request.rows.last && *request.rows.last < 0) ||
2268 (request.rows.last && (request.rows.first || request.rows.count)))
2269 throwState(
"invalid row slice");
2270 std::uint32_t projected = 0;
2271 ResolvedReadRequest result;
2272 result.parameters = resolveFields(request.parameters, layout.parameters, projected, options);
2273 result.arrays = resolveFields(request.arrays, layout.arrays, projected, options);
2274 result.columns = resolveFields(request.columns, layout.columns, projected, options);
2275 result.rows = request.rows;
2279bool selectKnownRow(
const RowSlice &slice, std::int64_t row, std::int64_t total) {
2280 const std::int64_t begin = slice.last ? std::max<std::int64_t>(0, total - *slice.last)
2281 : std::min(slice.first, total);
2282 if (row < begin || (row - begin) % slice.stride)
2284 return !slice.count || (row - begin) / slice.stride < *slice.count;
2287void appendRingScalar(Values &values, Scalar value, std::size_t limit,
2288 std::size_t position) {
2290 std::visit([&](
auto &items) {
2291 using Vector = std::decay_t<
decltype(items)>;
2292 using Value =
typename Vector::value_type;
2293 if (items.size() < limit)
2294 items.push_back(std::get<Value>(std::move(value)));
2296 items.at(position) = std::get<Value>(std::move(value));
2300void rotateRing(Values &values, std::size_t first) {
2302 std::visit([&](
auto &items) {
2303 if (first < items.size()) std::rotate(items.begin(), items.begin() + first, items.end());
2307std::vector<std::string> readAsciiTokens(AsciiCursor &cursor, std::size_t count,
2308 const std::string &what) {
2309 std::vector<std::string> result;
2310 result.reserve(count);
2311 while (result.size() < count) {
2312 auto line = cursor.nextLine(
false);
2314 throwFormat(
"unexpected EOF reading " + what);
2315 auto tokens = splitAsciiTokens(*line);
2316 result.insert(result.end(), std::make_move_iterator(tokens.begin()),
2317 std::make_move_iterator(tokens.end()));
2319 if (result.size() != count)
2320 throwFormat(
"too many values while reading " + what);
2324void discardAsciiTokens(AsciiCursor &cursor, std::size_t count,
const std::string &what,
2325 Type type,
const ReaderOptions &options,
2326 const std::filesystem::path &path, std::int64_t page,
2327 const std::string &field) {
2328 std::size_t consumed = 0;
2329 while (consumed < count) {
2330 auto line = cursor.nextLine(
false);
2331 if (!line) throwFormat(
"unexpected EOF reading " + what, path, page, field);
2332 auto tokens = splitAsciiTokens(*line);
2333 if (tokens.size() > count - consumed)
2334 throwFormat(
"too many values while reading " + what, path, page, field);
2335 if (type == Type::String)
2336 for (
const auto &token : tokens)
2337 if (token.size() > options.limits.maxStringBytes)
2338 throwLimit(
"ASCII string exceeds configured limit", path, page, field);
2339 consumed += tokens.size();
2343void retainStride(Values &values, std::int64_t stride) {
2344 if (stride == 1)
return;
2345 std::visit([&](
auto &items) {
2346 using Vector = std::decay_t<
decltype(items)>;
2348 retained.reserve((items.size() +
static_cast<std::size_t
>(stride) - 1) /
2349 static_cast<std::size_t
>(stride));
2350 for (std::size_t index = 0; index < items.size(); index +=
static_cast<std::size_t
>(stride))
2351 retained.push_back(std::move(items[index]));
2352 items = std::move(retained);
2356std::optional<std::string> findAsciiPage(BufferedStream &stream,
const ReaderOptions &options,
2357 const std::filesystem::path &path,
bool &foundMarker) {
2358 foundMarker =
false;
2359 while (
auto line = stream.line(options.limits.maxLayoutCommandBytes, path)) {
2360 const std::string stripped = trim(*line);
2361 if (stripped.empty())
2363 if (stripped.front() ==
'!') {
2364 if (lower(stripped).find(
"page number") != std::string::npos)
2370 return std::nullopt;
2373DecodedPage readAsciiPage(BufferedStream &stream,
const std::shared_ptr<const Layout> &layout,
2374 const ReaderOptions &options,
const std::filesystem::path &path,
2375 std::int64_t pageNumber, std::optional<std::string> firstLine,
2376 const ResolvedReadRequest &request) {
2377 AsciiCursor cursor(stream, options, path, std::move(firstLine));
2378 Page page(layout, LoadMode::None);
2379 for (std::size_t i = 0; i < layout->parameters.size(); ++i) {
2380 const auto &definition = layout->parameters[i];
2382 if (definition.fixedValue) {
2383 value = *definition.fixedValue;
2385 auto line = cursor.nextLine(
false);
2387 throwFormat(
"unexpected EOF reading parameter", path, pageNumber, definition.name);
2388 auto tokens = splitAsciiTokens(*line);
2390 throwFormat(
"missing parameter value", path, pageNumber, definition.name);
2391 value = definition.type == Type::String ? tokens.front() : tokens.front();
2393 if (definition.type == Type::String && value.size() > options.limits.maxStringBytes)
2394 throwLimit(
"ASCII string exceeds configured limit", path, pageNumber, definition.name);
2395 if (request.parameters[i]) {
2397 page.setParameter(i, parseAsciiScalar(definition.type, value));
2398 }
catch (
const Error &error) {
2399 throwWithContext(error, path, pageNumber, stream.offset(), std::nullopt,
2405 for (std::size_t i = 0; i < layout->arrays.size(); ++i) {
2406 const auto &definition = layout->arrays[i];
2407 const auto dimensionTokens = readAsciiTokens(
2408 cursor,
static_cast<std::size_t
>(definition.dimensions),
"array dimensions");
2410 array.dimensions.reserve(
static_cast<std::size_t
>(definition.dimensions));
2411 for (
const auto &token : dimensionTokens) {
2413 array.dimensions.push_back(parseInteger<std::int32_t>(token,
"array dimension"));
2414 }
catch (
const Error &error) {
2415 throwWithContext(error, path, pageNumber, stream.offset(), std::nullopt,
2419 const std::uint64_t elements = checkedArrayElements(array.dimensions, options.limits, path,
2420 pageNumber, definition.name);
2421 array.values = emptyValues(definition.type);
2422 if (elements && request.arrays[i]) {
2423 const auto values = readAsciiTokens(cursor,
static_cast<std::size_t
>(elements),
2424 "array " + definition.name);
2425 for (
const auto &value : values) {
2426 if (definition.type == Type::String && value.size() > options.limits.maxStringBytes)
2427 throwLimit(
"ASCII string exceeds configured limit", path, pageNumber, definition.name);
2429 appendScalar(array.values, parseAsciiScalar(definition.type, value));
2430 }
catch (
const Error &error) {
2431 throwWithContext(error, path, pageNumber, stream.offset(), std::nullopt,
2435 }
else if (elements) {
2436 discardAsciiTokens(cursor,
static_cast<std::size_t
>(elements),
2437 "array " + definition.name, definition.type, options,
2438 path, pageNumber, definition.name);
2440 if (request.arrays[i]) page.setArray(i, std::move(array));
2443 std::int64_t expectedRows = 0;
2444 if (!layout->columns.empty() && layout->data.rowCountMode != RowCountMode::None) {
2445 auto line = cursor.nextLine(
false);
2447 throwFormat(
"unexpected EOF reading row count", path, pageNumber);
2448 const auto tokens = splitAsciiTokens(*line);
2450 throwFormat(
"missing row count", path, pageNumber);
2451 expectedRows = parseInteger<std::int64_t>(tokens.front(),
"row count");
2452 if (expectedRows < 0)
2453 throwFormat(
"negative row count", path, pageNumber);
2454 if (expectedRows > options.limits.maxRows)
2455 throwLimit(
"page exceeds configured row limit", path, pageNumber);
2458 std::vector<Values> columns;
2459 columns.reserve(layout->columns.size());
2460 for (
const auto &definition : layout->columns)
2461 columns.push_back(emptyValues(definition.type));
2463 bool recovered =
false;
2464 std::int64_t rows = 0;
2465 if (!layout->columns.empty()) {
2466 std::vector<std::string> rowTokens;
2467 const bool counted = layout->data.rowCountMode != RowCountMode::None;
2468 while (!counted || rows < expectedRows) {
2469 while (rowTokens.size() < layout->columns.size()) {
2470 auto line = cursor.nextLine(
true);
2471 if (!line || trim(*line).empty()) {
2472 if (counted && rows < expectedRows) {
2473 if (!recoveryEnabled(*layout, options))
2474 throwFormat(
"unexpected EOF in ASCII row data", path, pageNumber,
2475 std::nullopt, stream.offset(), rows);
2477 }
else if (!rowTokens.empty() && !recoveryEnabled(*layout, options)) {
2478 throwFormat(
"incomplete ASCII row", path, pageNumber, std::nullopt,
2479 stream.offset(), rows);
2480 }
else if (!rowTokens.empty()) {
2484 goto ascii_rows_done;
2486 auto tokens = splitAsciiTokens(*line);
2487 rowTokens.insert(rowTokens.end(), std::make_move_iterator(tokens.begin()),
2488 std::make_move_iterator(tokens.end()));
2490 if (rowTokens.size() != layout->columns.size())
2491 throwFormat(
"ASCII row has the wrong number of fields", path, pageNumber,
2492 std::nullopt, stream.offset(), rows);
2494 for (std::size_t i = 0; i < layout->columns.size(); ++i) {
2495 if (layout->columns[i].type == Type::String &&
2496 rowTokens[i].size() > options.limits.maxStringBytes)
2497 throwLimit(
"ASCII string exceeds configured limit", path, pageNumber,
2498 layout->columns[i].name);
2499 bool selected =
false;
2500 if (layout->data.rowCountMode == RowCountMode::None && request.rows.last) {
2501 selected = *request.rows.last > 0;
2502 }
else if (layout->data.rowCountMode == RowCountMode::None) {
2503 const auto &slice = request.rows;
2504 selected = rows >= slice.first && (rows - slice.first) % slice.stride == 0 &&
2505 (!slice.count || (rows - slice.first) / slice.stride < *slice.count);
2507 selected = selectKnownRow(request.rows, rows, expectedRows);
2509 if (request.columns[i] && selected) {
2512 value = parseAsciiScalar(layout->columns[i].type, rowTokens[i]);
2513 }
catch (
const Error &error) {
2514 throwWithContext(error, path, pageNumber, stream.offset(), rows,
2515 layout->columns[i].name);
2517 if (layout->data.rowCountMode == RowCountMode::None && request.rows.last) {
2518 const auto limit =
static_cast<std::size_t
>(*request.rows.last);
2519 appendRingScalar(columns[i], std::move(value), limit,
2520 limit ?
static_cast<std::size_t
>(rows) % limit : 0);
2522 appendScalar(columns[i], std::move(value));
2526 }
catch (
const Error &error) {
2527 throwWithContext(error, path, pageNumber, stream.offset(), rows);
2531 if (rows > options.limits.maxRows)
2532 throwLimit(
"page exceeds configured row limit", path, pageNumber,
2533 std::nullopt, rows);
2537 if (layout->data.rowCountMode == RowCountMode::None && request.rows.last) {
2538 const auto limit =
static_cast<std::size_t
>(*request.rows.last);
2539 const std::size_t first = limit && rows >
static_cast<std::int64_t
>(limit)
2540 ?
static_cast<std::size_t
>(rows) % limit : 0;
2541 for (std::size_t i = 0; i < columns.size(); ++i)
2542 if (request.columns[i]) {
2543 rotateRing(columns[i], first);
2544 retainStride(columns[i], request.rows.stride);
2547 std::int64_t selectedRows = 0;
2548 for (std::size_t i = 0; i < columns.size(); ++i) {
2549 if (!request.columns[i])
continue;
2550 selectedRows =
static_cast<std::int64_t
>(valuesSize(columns[i]));
2551 page.setColumn(i, std::move(columns[i]));
2553 if (layout->columns.empty() ||
2554 std::none_of(request.columns.begin(), request.columns.end(), [](
bool value) { return value; })) {
2555 if (layout->data.rowCountMode == RowCountMode::None && request.rows.last) {
2556 const std::int64_t retained = std::min(rows, *request.rows.last);
2557 selectedRows = (retained + request.rows.stride - 1) / request.rows.stride;
2559 for (std::int64_t row = 0; row < rows; ++row)
2560 if (layout->data.rowCountMode == RowCountMode::None
2561 ? (row >= request.rows.first && (row - request.rows.first) % request.rows.stride == 0 &&
2562 (!request.rows.count ||
2563 (row - request.rows.first) / request.rows.stride < *request.rows.count))
2564 : selectKnownRow(request.rows, row, expectedRows))
2568 return {std::move(page), selectedRows, rows, recovered};
2571DecodedPage readBinaryPage(BufferedStream &stream,
const std::shared_ptr<const Layout> &layout,
2572 const ReaderOptions &options,
const std::filesystem::path &path,
2573 std::int64_t pageNumber,
const ResolvedReadRequest &request) {
2574 const ByteOrder order = layout->data.byteOrder;
2575 const std::int32_t count32 = readPod<std::int32_t>(stream, order, path, pageNumber);
2576 const std::int64_t expectedRows = count32 == kInt64RowCount
2577 ? readPod<std::int64_t>(stream, order, path, pageNumber) : count32;
2578 if (expectedRows < 0)
2579 throwFormat(
"negative row count", path, pageNumber);
2580 if (expectedRows > options.limits.maxRows)
2581 throwLimit(
"page exceeds configured row limit", path, pageNumber);
2583 Page page(layout, LoadMode::None);
2584 for (std::size_t i = 0; i < layout->parameters.size(); ++i) {
2585 const auto &definition = layout->parameters[i];
2587 if (definition.fixedValue) {
2588 if (request.parameters[i])
2589 page.setParameter(i, parseAsciiScalar(definition.type, *definition.fixedValue));
2590 }
else if (request.parameters[i]) {
2591 page.setParameter(i, readBinaryScalar(stream, definition.type, order,
2592 options.longDoubleEncoding, options.limits,
2595 skipBinaryScalar(stream, definition.type, order, options.longDoubleEncoding,
2596 options.limits, path, pageNumber);
2598 }
catch (
const Error &error) {
2599 throwWithContext(error, path, pageNumber, stream.offset(), std::nullopt,
2603 for (std::size_t i = 0; i < layout->arrays.size(); ++i) {
2604 const auto &definition = layout->arrays[i];
2607 for (std::int32_t dimension = 0; dimension < definition.dimensions; ++dimension)
2608 array.dimensions.push_back(readPod<std::int32_t>(stream, order, path, pageNumber));
2609 const std::uint64_t elements = checkedArrayElements(array.dimensions, options.limits, path,
2610 pageNumber, definition.name);
2611 array.values = emptyValues(definition.type);
2612 for (std::uint64_t element = 0; element < elements; ++element) {
2613 if (request.arrays[i])
2614 appendScalar(array.values,
2615 readBinaryScalar(stream, definition.type, order,
2616 options.longDoubleEncoding, options.limits,
2619 skipBinaryScalar(stream, definition.type, order, options.longDoubleEncoding,
2620 options.limits, path, pageNumber);
2622 if (request.arrays[i]) page.setArray(i, std::move(array));
2623 }
catch (
const Error &error) {
2624 throwWithContext(error, path, pageNumber, stream.offset(), std::nullopt,
2629 std::vector<Values> columns;
2630 columns.reserve(layout->columns.size());
2631 for (
const auto &definition : layout->columns)
2632 columns.push_back(emptyValues(definition.type));
2633 bool recovered =
false;
2634 std::int64_t rows = 0;
2635 const bool fixedWidthColumns = std::none_of(
2636 layout->columns.begin(), layout->columns.end(),
2637 [](
const auto &definition) { return definition.type == Type::String; });
2638 if (fixedWidthColumns && layout->data.majorOrder == MajorOrder::Row) {
2639 std::vector<std::uint64_t> offsets(layout->columns.size());
2640 std::uint64_t rowBytes = 0;
2641 bool anySelected =
false;
2642 std::size_t selectedCapacity = 0;
2643 for (std::int64_t row = 0; row < expectedRows; ++row)
2644 if (selectKnownRow(request.rows, row, expectedRows)) ++selectedCapacity;
2645 for (std::size_t i = 0; i < layout->columns.size(); ++i) {
2646 offsets[i] = rowBytes;
2647 const std::uint64_t width = binaryScalarBytes(layout->columns[i].type,
2648 options.longDoubleEncoding);
2649 if (width > UINT64_MAX - rowBytes)
2650 throwLimit(
"binary row width overflow", path, pageNumber);
2652 if (request.columns[i]) {
2654 reserveValues(columns[i], selectedCapacity);
2657 if (rowBytes &&
static_cast<std::uint64_t
>(expectedRows) > UINT64_MAX / rowBytes)
2658 throwLimit(
"binary page size overflow", path, pageNumber);
2659 if (!anySelected && !recoveryEnabled(*layout, options)) {
2660 const std::uint64_t startOffset = stream.offset();
2662 stream.skip(rowBytes *
static_cast<std::uint64_t
>(expectedRows), path, pageNumber);
2663 }
catch (
const Error &error) {
2664 const std::uint64_t consumed = stream.offset() - startOffset;
2665 const std::uint64_t withinRow = consumed % rowBytes;
2666 std::optional<std::string> field;
2667 for (std::size_t i = 0; i < layout->columns.size(); ++i)
2668 if (withinRow >= offsets[i]) field = layout->columns[i].name;
2669 throwWithContext(error, path, pageNumber, stream.offset(),
2670 static_cast<std::int64_t
>(consumed / rowBytes), field);
2672 rows = expectedRows;
2673 }
else if (!rowBytes) {
2674 rows = expectedRows;
2676 const std::uint64_t chunkRows = std::max<std::uint64_t>(
2677 1, (1024U * 1024U) / rowBytes);
2678 std::vector<unsigned char> buffer(
static_cast<std::size_t
>(
2679 std::min<std::uint64_t>(
static_cast<std::uint64_t
>(expectedRows), chunkRows) *
2681 while (rows < expectedRows) {
2682 const std::uint64_t requestedRows = std::min<std::uint64_t>(
2683 static_cast<std::uint64_t
>(expectedRows - rows), chunkRows);
2684 const std::size_t requestedBytes =
static_cast<std::size_t
>(requestedRows * rowBytes);
2685 const std::size_t bytesRead = stream.read(buffer.data(), requestedBytes);
2686 const std::int64_t completeRows =
static_cast<std::int64_t
>(bytesRead / rowBytes);
2687 for (std::int64_t localRow = 0; localRow < completeRows; ++localRow) {
2688 const std::int64_t sourceRow = rows + localRow;
2689 if (!selectKnownRow(request.rows, sourceRow, expectedRows))
continue;
2690 const auto *rowData = buffer.data() +
static_cast<std::size_t
>(localRow * rowBytes);
2691 for (std::size_t i = 0; i < layout->columns.size(); ++i)
2692 if (request.columns[i])
2693 appendFixedBinaryValue(columns[i], layout->columns[i].type,
2694 rowData + offsets[i], order,
2695 options.longDoubleEncoding);
2697 rows += completeRows;
2698 if (bytesRead != requestedBytes) {
2699 if (!recoveryEnabled(*layout, options)) {
2700 const std::uint64_t withinRow = bytesRead % rowBytes;
2701 std::optional<std::string> field;
2702 for (std::size_t i = 0; i < layout->columns.size(); ++i)
2703 if (withinRow >= offsets[i]) field = layout->columns[i].name;
2704 throwFormat(
"unexpected end of file", path, pageNumber, field,
2705 stream.offset(), rows);
2712 }
else if (fixedWidthColumns) {
2713 std::vector<std::int64_t> columnRows(layout->columns.size(), 0);
2714 std::size_t selectedCapacity = 0;
2715 for (std::int64_t row = 0; row < expectedRows; ++row)
2716 if (selectKnownRow(request.rows, row, expectedRows)) ++selectedCapacity;
2717 for (std::size_t i = 0; i < layout->columns.size(); ++i) {
2718 const std::uint64_t width = binaryScalarBytes(layout->columns[i].type,
2719 options.longDoubleEncoding);
2720 if (width &&
static_cast<std::uint64_t
>(expectedRows) > UINT64_MAX / width)
2721 throwLimit(
"binary column size overflow", path, pageNumber);
2722 if (!request.columns[i] && !recoveryEnabled(*layout, options)) {
2723 const std::uint64_t startOffset = stream.offset();
2725 stream.skip(width *
static_cast<std::uint64_t
>(expectedRows), path, pageNumber);
2726 }
catch (
const Error &error) {
2727 const std::uint64_t consumed = stream.offset() - startOffset;
2728 throwWithContext(error, path, pageNumber, stream.offset(),
2729 static_cast<std::int64_t
>(consumed / width),
2730 layout->columns[i].name);
2732 columnRows[i] = expectedRows;
2735 if (request.columns[i]) reserveValues(columns[i], selectedCapacity);
2736 const std::uint64_t chunkRows = std::max<std::uint64_t>(1, (1024U * 1024U) / width);
2737 std::vector<unsigned char> buffer(
static_cast<std::size_t
>(
2738 std::min<std::uint64_t>(
static_cast<std::uint64_t
>(expectedRows), chunkRows) * width));
2739 while (columnRows[i] < expectedRows) {
2740 const std::uint64_t requestedRows = std::min<std::uint64_t>(
2741 static_cast<std::uint64_t
>(expectedRows - columnRows[i]), chunkRows);
2742 const std::size_t requestedBytes =
static_cast<std::size_t
>(requestedRows * width);
2743 const std::size_t bytesRead = stream.read(buffer.data(), requestedBytes);
2744 const std::int64_t completeRows =
static_cast<std::int64_t
>(bytesRead / width);
2745 if (request.columns[i])
2746 for (std::int64_t localRow = 0; localRow < completeRows; ++localRow) {
2747 const std::int64_t sourceRow = columnRows[i] + localRow;
2748 if (selectKnownRow(request.rows, sourceRow, expectedRows))
2749 appendFixedBinaryValue(columns[i], layout->columns[i].type,
2750 buffer.data() +
static_cast<std::size_t
>(localRow * width),
2751 order, options.longDoubleEncoding);
2753 columnRows[i] += completeRows;
2754 if (bytesRead != requestedBytes) {
2755 if (!recoveryEnabled(*layout, options))
2756 throwFormat(
"unexpected end of file", path, pageNumber, layout->columns[i].name,
2757 stream.offset(), columnRows[i]);
2762 if (recovered)
break;
2764 rows = columnRows.empty() ? expectedRows
2765 : *std::min_element(columnRows.begin(), columnRows.end());
2767 for (std::size_t i = 0; i < columns.size(); ++i) {
2768 if (!request.columns[i])
continue;
2769 std::size_t selected = 0;
2770 for (std::int64_t row = 0; row < rows; ++row)
2771 if (selectKnownRow(request.rows, row, expectedRows)) ++selected;
2772 std::visit([&](
auto &values) { values.resize(std::min(values.size(), selected)); },
2775 }
else if (layout->data.majorOrder == MajorOrder::Row) {
2776 for (; rows < expectedRows; ++rows) {
2777 std::size_t currentColumn = 0;
2779 const bool selected = selectKnownRow(request.rows, rows, expectedRows);
2780 for (; currentColumn < layout->columns.size(); ++currentColumn) {
2781 const auto &definition = layout->columns[currentColumn];
2782 if (request.columns[currentColumn] && selected)
2783 appendScalar(columns[currentColumn], readBinaryScalar(
2784 stream, definition.type, order, options.longDoubleEncoding,
2785 options.limits, path, pageNumber));
2787 skipBinaryScalar(stream, definition.type, order, options.longDoubleEncoding,
2788 options.limits, path, pageNumber);
2790 }
catch (
const FormatError &error) {
2791 if (!recoveryEnabled(*layout, options) || !endOfStreamError(error))
2792 throwWithContext(error, path, pageNumber, stream.offset(), rows,
2793 currentColumn < layout->columns.size()
2794 ? std::optional<std::string>(layout->columns[currentColumn].name)
2801 std::vector<std::int64_t> columnRows(layout->columns.size(), 0);
2802 for (std::size_t i = 0; i < layout->columns.size(); ++i) {
2804 for (; columnRows[i] < expectedRows; ++columnRows[i]) {
2805 if (request.columns[i] && selectKnownRow(request.rows, columnRows[i], expectedRows))
2806 appendScalar(columns[i], readBinaryScalar(stream, layout->columns[i].type, order,
2807 options.longDoubleEncoding, options.limits,
2810 skipBinaryScalar(stream, layout->columns[i].type, order,
2811 options.longDoubleEncoding, options.limits, path, pageNumber);
2813 }
catch (
const FormatError &error) {
2814 if (!recoveryEnabled(*layout, options) || !endOfStreamError(error))
2815 throwWithContext(error, path, pageNumber, stream.offset(), columnRows[i],
2816 layout->columns[i].name);
2821 rows = columnRows.empty() ? expectedRows
2822 : *std::min_element(columnRows.begin(), columnRows.end());
2824 for (std::size_t i = 0; i < columns.size(); ++i) {
2825 if (!request.columns[i])
continue;
2826 std::size_t selected = 0;
2827 for (std::int64_t row = 0; row < rows; ++row)
2828 if (selectKnownRow(request.rows, row, expectedRows)) ++selected;
2829 std::visit([&](
auto &values) { values.resize(std::min(values.size(), selected)); },
2833 std::int64_t selectedRows = 0;
2834 for (std::int64_t row = 0; row < rows; ++row)
2835 if (selectKnownRow(request.rows, row, expectedRows)) ++selectedRows;
2836 for (std::size_t i = 0; i < columns.size(); ++i)
2837 if (request.columns[i]) page.setColumn(i, std::move(columns[i]));
2838 return {std::move(page), selectedRows, rows, recovered};
2841void writeRowCount(BufferedStream &stream, std::int64_t rows, ByteOrder order) {
2842 if (rows > INT32_MAX) {
2843 writePod(stream, kInt64RowCount, order);
2844 writePod(stream, rows, order);
2846 writePod(stream,
static_cast<std::int32_t
>(rows), order);
2850void writeAsciiPage(BufferedStream &stream,
const Page &page,
const Layout &layout,
2851 std::int64_t pageNumber) {
2852 if (layout.data.rowCountMode == RowCountMode::None && pageNumber > 1)
2854 stream.write(
"! page number " + std::to_string(pageNumber) +
"\n");
2855 for (std::size_t i = 0; i < layout.parameters.size(); ++i) {
2856 if (layout.parameters[i].fixedValue)
2858 stream.write(formatAsciiScalar(page.parameter(i)) +
"\n");
2860 for (std::size_t i = 0; i < layout.arrays.size(); ++i) {
2861 const auto &definition = layout.arrays[i];
2862 const auto &array = page.array(i);
2863 for (
const auto dimension : array.dimensions)
2864 stream.write(std::to_string(dimension) +
" ");
2865 stream.write(
" ! " + std::to_string(definition.dimensions) +
2866 "-dimensional array " + definition.name +
":\n");
2867 for (std::size_t element = 0; element < valuesSize(array.values); ++element) {
2868 stream.write(formatAsciiScalar(scalarAt(array.values, element)));
2869 if ((element + 1) % 6 == 0 || element + 1 == valuesSize(array.values))
2875 if (!layout.columns.empty() && layout.data.rowCountMode != RowCountMode::None)
2876 stream.write(std::to_string(page.rowCount()) +
"\n");
2877 for (std::int64_t row = 0; row < page.rowCount(); ++row) {
2878 for (std::size_t column = 0; column < layout.columns.size(); ++column) {
2881 stream.write(formatAsciiScalar(scalarAt(page.column(column),
2882 static_cast<std::size_t
>(row))));
2888void writeBinaryPage(BufferedStream &stream,
const Page &page,
const Layout &layout,
2889 const WriterOptions &options) {
2890 const ByteOrder order = layout.data.byteOrder;
2891 writeRowCount(stream, page.rowCount(), order);
2892 for (std::size_t i = 0; i < layout.parameters.size(); ++i)
2893 if (!layout.parameters[i].fixedValue)
2894 writeBinaryScalar(stream, page.parameter(i), order, options.longDoubleEncoding);
2895 for (std::size_t i = 0; i < layout.arrays.size(); ++i) {
2896 const auto &array = page.array(i);
2897 for (
const auto dimension : array.dimensions)
2898 writePod(stream, dimension, order);
2899 for (std::size_t element = 0; element < valuesSize(array.values); ++element)
2900 writeBinaryScalar(stream, scalarAt(array.values, element), order,
2901 options.longDoubleEncoding);
2903 const bool fixedWidthColumns = std::none_of(
2904 layout.columns.begin(), layout.columns.end(),
2905 [](
const auto &definition) { return definition.type == Type::String; });
2906 if (fixedWidthColumns && layout.data.majorOrder == MajorOrder::Row &&
2907 !layout.columns.empty()) {
2908 std::vector<std::uint64_t> offsets(layout.columns.size());
2909 std::vector<const Values *> columnValues;
2910 columnValues.reserve(layout.columns.size());
2911 bool allDouble =
true;
2912 std::uint64_t rowBytes = 0;
2913 for (std::size_t column = 0; column < layout.columns.size(); ++column) {
2914 columnValues.push_back(&page.column(column));
2915 allDouble = allDouble && layout.columns[column].type == Type::Double;
2916 offsets[column] = rowBytes;
2917 const std::uint64_t width = binaryScalarBytes(layout.columns[column].type,
2918 options.longDoubleEncoding);
2919 if (width > UINT64_MAX - rowBytes) throwLimit(
"binary row width overflow");
2922 const bool directDouble = allDouble && !mustSwap(order);
2923 const std::uint64_t chunkRows = std::max<std::uint64_t>(1, (1024U * 1024U) / rowBytes);
2924 std::vector<unsigned char> buffer(
static_cast<std::size_t
>(
2925 std::min<std::uint64_t>(
static_cast<std::uint64_t
>(page.rowCount()), chunkRows) *
2927 std::int64_t firstRow = 0;
2928 while (firstRow < page.rowCount()) {
2929 const std::uint64_t rows = std::min<std::uint64_t>(
2930 static_cast<std::uint64_t
>(page.rowCount() - firstRow), chunkRows);
2931 for (std::uint64_t row = 0; row < rows; ++row) {
2932 auto *rowData = buffer.data() +
static_cast<std::size_t
>(row * rowBytes);
2934 for (std::size_t column = 0; column < layout.columns.size(); ++column) {
2935 const double value = std::get<std::vector<double>>(*columnValues[column])[
2936 static_cast<std::size_t
>(firstRow + row)];
2938 std::memcpy(rowData + offsets[column], &value,
sizeof(value));
2940 storeFixedValue(rowData + offsets[column], value, order);
2943 for (std::size_t column = 0; column < layout.columns.size(); ++column)
2944 storeFixedBinaryValue(rowData + offsets[column], *columnValues[column],
2945 static_cast<std::size_t
>(firstRow + row),
2946 layout.columns[column].type, order,
2947 options.longDoubleEncoding);
2950 stream.write(buffer.data(),
static_cast<std::size_t
>(rows * rowBytes));
2951 firstRow +=
static_cast<std::int64_t
>(rows);
2953 }
else if (fixedWidthColumns && layout.data.majorOrder == MajorOrder::Column) {
2954 for (std::size_t column = 0; column < layout.columns.size(); ++column) {
2955 const std::uint64_t width = binaryScalarBytes(layout.columns[column].type,
2956 options.longDoubleEncoding);
2957 const std::uint64_t chunkRows = std::max<std::uint64_t>(1, (1024U * 1024U) / width);
2958 std::vector<unsigned char> buffer(
static_cast<std::size_t
>(
2959 std::min<std::uint64_t>(
static_cast<std::uint64_t
>(page.rowCount()), chunkRows) *
2961 std::int64_t firstRow = 0;
2962 while (firstRow < page.rowCount()) {
2963 const std::uint64_t rows = std::min<std::uint64_t>(
2964 static_cast<std::uint64_t
>(page.rowCount() - firstRow), chunkRows);
2965 for (std::uint64_t row = 0; row < rows; ++row)
2966 storeFixedBinaryValue(buffer.data() +
static_cast<std::size_t
>(row * width),
2967 page.column(column),
2968 static_cast<std::size_t
>(firstRow + row),
2969 layout.columns[column].type, order,
2970 options.longDoubleEncoding);
2971 stream.write(buffer.data(),
static_cast<std::size_t
>(rows * width));
2972 firstRow +=
static_cast<std::int64_t
>(rows);
2975 }
else if (layout.data.majorOrder == MajorOrder::Column) {
2976 for (std::size_t column = 0; column < layout.columns.size(); ++column)
2977 for (std::int64_t row = 0; row < page.rowCount(); ++row)
2978 writeBinaryScalar(stream, scalarAt(page.column(column),
2979 static_cast<std::size_t
>(row)), order,
2980 options.longDoubleEncoding);
2982 for (std::int64_t row = 0; row < page.rowCount(); ++row)
2983 for (std::size_t column = 0; column < layout.columns.size(); ++column)
2984 writeBinaryScalar(stream, scalarAt(page.column(column),
2985 static_cast<std::size_t
>(row)), order,
2986 options.longDoubleEncoding);
2990void writePageData(BufferedStream &stream,
const Page &page,
const Layout &layout,
2991 const WriterOptions &options, std::int64_t pageNumber) {
2992 validatePage(page, layout);
2993 if (layout.data.mode == DataMode::Ascii)
2994 writeAsciiPage(stream, page, layout, pageNumber);
2996 writeBinaryPage(stream, page, layout, options);
2999std::unique_ptr<Stream> standardInput(
const ReaderOptions &options) {
3000 const Compression compression = options.compression;
3001 setBinaryMode(stdin);
3002 if (compression == Compression::Auto || compression == Compression::None)
3003 return std::make_unique<FileStream>(stdin, std::filesystem::path{},
false,
false,
3004 options.bufferBytes);
3006 const int descriptor = _dup(_fileno(stdin));
3008 const int descriptor = dup(fileno(stdin));
3011 throwIo(
"unable to duplicate standard input");
3012 if (compression == Compression::Gzip)
3013 return std::make_unique<GzipStream>(gzdopen(descriptor,
"rb"),
3014 std::filesystem::path{},
false, options.bufferBytes);
3016 FILE *file = _fdopen(descriptor,
"rb");
3018 FILE *file = fdopen(descriptor,
"rb");
3021 throwIo(
"unable to open duplicate standard input");
3022 return std::make_unique<LzmaStream>(file, std::filesystem::path{},
false,
3023 options.bufferBytes);
3026std::unique_ptr<Stream> standardOutput(
const WriterOptions &options) {
3027 const Compression compression = options.compression;
3028 if (options.gzipLevel < -1 || options.gzipLevel > 9)
3029 throwState(
"gzip compression level must be between 0 and 9, or -1 for default");
3030 if (options.lzmaPreset > 9)
3031 throwState(
"LZMA preset must be between 0 and 9");
3032 setBinaryMode(stdout);
3033 if (compression == Compression::Auto || compression == Compression::None)
3034 return std::make_unique<FileStream>(stdout, std::filesystem::path{},
false,
true,
3035 options.bufferBytes);
3037 const int descriptor = _dup(_fileno(stdout));
3039 const int descriptor = dup(fileno(stdout));
3042 throwIo(
"unable to duplicate standard output");
3043 if (compression == Compression::Gzip) {
3044 std::string mode =
"wb";
3045 if (options.gzipLevel >= 0) mode.push_back(
static_cast<char>(
'0' + options.gzipLevel));
3046 return std::make_unique<GzipStream>(gzdopen(descriptor, mode.c_str()),
3047 std::filesystem::path{},
true, options.bufferBytes);
3050 FILE *file = _fdopen(descriptor,
"wb");
3052 FILE *file = fdopen(descriptor,
"wb");
3055 throwIo(
"unable to open duplicate standard output");
3056 return std::make_unique<LzmaStream>(file, std::filesystem::path{},
true,
3057 options.bufferBytes, options.lzmaPreset,
3058 options.lzmaCheck, compression == Compression::Lzma);
3062void mergeValues(std::vector<T> &destination, std::vector<T> &&source,
3063 std::size_t startRow) {
3064 if (destination.size() < startRow)
3065 destination.resize(startRow);
3066 if (destination.size() < startRow + source.size())
3067 destination.resize(startRow + source.size());
3068 std::move(source.begin(), source.end(), destination.begin() + startRow);
3071void mergeValuesInPlace(Values &existing, Values incoming, std::int64_t startRow) {
3073 throwState(
"column start row cannot be negative");
3074 if (existing.index() != incoming.index())
3075 throwType(
"column type mismatch");
3076 std::visit([&](
auto &destination) {
3077 using Vector = std::decay_t<
decltype(destination)>;
3078 mergeValues(destination, std::get<Vector>(std::move(incoming)),
3079 static_cast<std::size_t
>(startRow));
3086 Impl(std::filesystem::path sourcePath, ReaderOptions readerOptions,
3087 std::unique_ptr<Stream> input, std::optional<Layout> suppliedLayout = std::nullopt,
3088 bool isPathBacked =
false)
3089 : path(std::move(sourcePath)), options(std::move(readerOptions)),
3090 stream(std::move(input), options.limits.maxDecompressedBytes, path),
3091 pathBacked(isPathBacked) {
3092 if (pathBacked) identity = fileIdentity(path);
3093 if (suppliedLayout) {
3094 validateLayout(*suppliedLayout);
3095 layout = std::make_shared<Layout>(std::move(*suppliedLayout));
3098 std::unordered_set<std::string> includeStack;
3099 std::optional<ByteOrder> commentOrder;
3100 bool fixedRowComment =
false;
3101 parseLayoutStream(stream, parsed, options, path, 0, includeStack, commentOrder,
3102 fixedRowComment,
true);
3103 const std::int32_t declaredVersion = parsed.version;
3104 validateLayout(parsed);
3105 if (declaredVersion < parsed.version)
3106 throwFormat(
"SDDS version is too old for its layout", path);
3107 parsed.version = declaredVersion;
3108 layout = std::make_shared<Layout>(std::move(parsed));
3110 if (stream.seekable()) {
3111 dataStart = stream.tell();
3112 rememberOffset(0, *dataStart);
3116 void rememberOffset(std::size_t index, std::uint64_t offset) {
3117 if (index >= options.limits.maxPageIndexEntries)
3118 throwLimit(
"page index exceeds configured entry limit", path);
3119 if (pageOffsets.size() <= index) pageOffsets.resize(index + 1);
3120 pageOffsets[index] = offset;
3123 std::optional<DecodedPage> next(
const ReadRequest &readRequest) {
3125 throwState(
"reader is closed");
3127 throwState(
"reader is disconnected");
3128 const ResolvedReadRequest request = resolveRequest(*layout, readRequest, options);
3129 if (stream.seekable()) rememberOffset(
static_cast<std::size_t
>(pageNumber), stream.tell());
3130 std::optional<DecodedPage> result;
3131 if (layout->data.mode == DataMode::Ascii) {
3132 bool marker =
false;
3133 auto first = findAsciiPage(stream, options, path, marker);
3134 if (first || marker)
3135 result = readAsciiPage(stream, layout, options, path, pageNumber + 1,
3136 std::move(first), request);
3138 const auto first = stream.get();
3140 stream.unget(*first);
3141 result = readBinaryPage(stream, layout, options, path, pageNumber + 1, request);
3145 indexComplete =
true;
3146 indexedPages = pageNumber;
3147 if (!tailInitialized) {
3148 tailPageNumber = pageNumber;
3149 tailRows = lastRawRows;
3150 tailInitialized =
true;
3152 return std::nullopt;
3154 if (stream.seekable()) rememberOffset(
static_cast<std::size_t
>(pageNumber + 1), stream.tell());
3158 std::filesystem::path path;
3159 ReaderOptions options;
3160 BufferedStream stream;
3161 std::shared_ptr<Layout> layout;
3162 std::optional<std::uint64_t> dataStart;
3163 std::vector<std::uint64_t> pageOffsets;
3164 std::optional<std::int64_t> indexedPages;
3165 std::int64_t pageNumber = 0;
3166 std::optional<std::uint64_t> disconnectedOffset;
3167 std::unique_ptr<PathLock> lock;
3168 FileIdentity identity;
3169 std::int64_t lastRawRows = 0;
3170 std::int64_t tailPageNumber = 0;
3171 std::int64_t tailRows = 0;
3172 bool tailInitialized =
false;
3173 bool headerless =
false;
3174 bool pathBacked =
false;
3175 bool indexComplete =
false;
3176 bool disconnected =
false;
3177 bool closed =
false;
3180Reader::Reader(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {}
3181Reader::Reader(Reader &&) noexcept = default;
3182Reader &Reader::operator=(Reader &&) noexcept = default;
3183Reader::~Reader() = default;
3185Reader Reader::open(const std::filesystem::path &path, ReaderOptions options) {
3187 auto lock = acquirePathLock(path, options.lockMode,
false);
3188 auto impl = std::make_unique<Impl>(path, options,
3189 openInput(path, options.compression, options.bufferBytes),
3190 std::nullopt,
true);
3191 impl->lock = std::move(lock);
3192 return Reader(std::move(impl));
3193 }
catch (
const Error &error) {
3194 throwWithContext(error, path, 0);
3198Reader Reader::openHeaderless(
const std::filesystem::path &path, Layout layout,
3199 ReaderOptions options) {
3201 auto lock = acquirePathLock(path, options.lockMode,
false);
3202 auto impl = std::make_unique<Impl>(path, options,
3203 openInput(path, options.compression, options.bufferBytes),
3204 std::move(layout),
true);
3205 impl->lock = std::move(lock);
3206 impl->headerless =
true;
3207 return Reader(std::move(impl));
3208 }
catch (
const Error &error) {
3209 throwWithContext(error, path, 0);
3213Reader Reader::fromStdin(ReaderOptions options) {
3214 return Reader(std::make_unique<Impl>(std::filesystem::path{}, options,
3215 standardInput(options)));
3218Reader Reader::fromSource(std::unique_ptr<InputSource> source, std::string sourceName,
3219 ReaderOptions options) {
3220 const std::filesystem::path path(sourceName);
3222 auto stream = wrapInputCodec(std::make_unique<InputSourceStream>(std::move(source)), options);
3223 return Reader(std::make_unique<Impl>(path, options, std::move(stream)));
3224 }
catch (
const Error &error) {
3225 throwWithContext(error, path, 0);
3229Reader Reader::fromHeaderlessSource(std::unique_ptr<InputSource> source, Layout layout,
3230 std::string sourceName, ReaderOptions options) {
3231 const std::filesystem::path path(sourceName);
3233 auto stream = wrapInputCodec(std::make_unique<InputSourceStream>(std::move(source)), options);
3234 auto impl = std::make_unique<Impl>(path, options, std::move(stream), std::move(layout));
3235 impl->headerless =
true;
3236 return Reader(std::move(impl));
3237 }
catch (
const Error &error) {
3238 throwWithContext(error, path, 0);
3242const Layout &Reader::layout()
const {
3243 if (!impl_ || impl_->closed)
3244 throwState(
"reader is closed");
3245 return *impl_->layout;
3248std::optional<Page> Reader::next(ReadRequest request) {
3250 throwState(
"reader has no implementation");
3251 std::optional<DecodedPage> decoded;
3253 decoded = impl_->next(request);
3254 }
catch (
const Error &error) {
3255 throwWithContext(error, impl_->path, impl_->pageNumber + 1, impl_->stream.offset());
3258 return std::nullopt;
3259 ++impl_->pageNumber;
3260 impl_->lastRawRows = decoded->rawRows;
3261 decoded->page.number_ = impl_->pageNumber;
3262 decoded->page.rowCount_ = decoded->rows;
3263 decoded->page.recovered_ = decoded->recovered;
3264 decoded->page.maxTransformationElements_ = impl_->options.limits.maxTransformationElements;
3265 return std::move(decoded->page);
3268std::optional<Page> Reader::next(ReadSelection selection) {
3269 ReadRequest request;
3270 request.rows.first = selection.sparseOffset;
3271 request.rows.stride = selection.sparseInterval;
3272 request.rows.last = selection.lastRows;
3273 return next(std::move(request));
3276void Reader::gotoPage(std::int64_t pageNumber) {
3277 if (!impl_ || impl_->closed)
3278 throwState(
"reader is closed");
3280 throwState(
"page numbers start at one");
3281 if (!impl_->dataStart)
3282 throwState(
"gotoPage requires a seekable, uncompressed input");
3283 std::size_t startIndex = 0;
3284 if (
static_cast<std::size_t
>(pageNumber - 1) < impl_->pageOffsets.size()) {
3285 startIndex =
static_cast<std::size_t
>(pageNumber - 1);
3286 }
else if (!impl_->pageOffsets.empty()) {
3287 startIndex = impl_->pageOffsets.size() - 1;
3289 impl_->stream.seek(impl_->pageOffsets.at(startIndex));
3290 impl_->pageNumber =
static_cast<std::int64_t
>(startIndex);
3292 skip.parameters = FieldSelection::noFields();
3293 skip.arrays = FieldSelection::noFields();
3294 skip.columns = FieldSelection::noFields();
3295 while (impl_->pageNumber + 1 < pageNumber)
3297 throwState(
"requested page is beyond end of file");
3300void Reader::buildPageIndex() {
3301 if (!impl_ || impl_->closed) throwState(
"reader is closed");
3302 if (!impl_->dataStart) throwState(
"page indexing requires a seekable, uncompressed input");
3303 const std::uint64_t savedOffset = impl_->stream.tell();
3304 const std::int64_t savedPage = impl_->pageNumber;
3305 impl_->stream.seek(*impl_->dataStart);
3306 impl_->pageNumber = 0;
3308 skip.parameters = FieldSelection::noFields();
3309 skip.arrays = FieldSelection::noFields();
3310 skip.columns = FieldSelection::noFields();
3311 while (next(skip)) {}
3312 impl_->indexedPages = impl_->pageNumber;
3313 impl_->indexComplete =
true;
3314 impl_->stream.seek(savedOffset);
3315 impl_->pageNumber = savedPage;
3318std::optional<std::int64_t> Reader::indexedPageCount() const noexcept {
3319 return impl_ && impl_->indexComplete ? impl_->indexedPages : std::nullopt;
3322void Reader::disconnect() {
3323 if (!impl_ || impl_->closed) throwState(
"reader is closed");
3324 if (impl_->disconnected)
return;
3325 if (!impl_->pathBacked || compressionFor(impl_->path, impl_->options.compression) != Compression::None ||
3326 !impl_->stream.seekable())
3327 throwState(
"disconnect requires a path-backed, uncompressed, seekable input");
3328 impl_->disconnectedOffset = impl_->stream.tell();
3329 impl_->stream.close();
3330 impl_->disconnected =
true;
3333void Reader::reconnect() {
3334 if (!impl_ || impl_->closed) throwState(
"reader is closed");
3335 if (!impl_->disconnected)
return;
3336 const FileIdentity current = fileIdentity(impl_->path);
3337 if (!sameFile(impl_->identity, current))
3338 throwState(
"input file was replaced while disconnected");
3339 auto input = openInput(impl_->path, Compression::None, impl_->options.bufferBytes);
3340 impl_->stream.replace(std::move(input), *impl_->disconnectedOffset);
3341 impl_->disconnected =
false;
3344std::optional<PageDelta> Reader::readNewRows(ReadRequest request) {
3345 if (!impl_ || impl_->closed) throwState(
"reader is closed");
3346 if (!impl_->pathBacked || compressionFor(impl_->path, impl_->options.compression) != Compression::None ||
3347 impl_->layout->data.mode != DataMode::Binary ||
3348 impl_->layout->data.majorOrder != MajorOrder::Row)
3349 throwState(
"reading newly appended rows requires a path-backed, uncompressed "
3350 "row-major binary input");
3351 (void)resolveRequest(*impl_->layout, request, impl_->options);
3352 if (!sameFile(impl_->identity, fileIdentity(impl_->path)))
3353 throwState(
"input file was replaced while following it");
3354 ReaderOptions pollOptions = impl_->options;
3355 pollOptions.lockMode = LockMode::None;
3356 Reader poll = Reader::open(impl_->path, pollOptions);
3357 poll.buildPageIndex();
3358 const std::int64_t pageNumber = poll.indexedPageCount().value_or(0);
3361 return std::nullopt;
3364 probe.parameters = FieldSelection::noFields();
3365 probe.arrays = FieldSelection::noFields();
3366 probe.columns = FieldSelection::noFields();
3367 poll.gotoPage(pageNumber);
3368 if (!poll.next(probe)) throwState(
"indexed final page could not be read");
3369 const std::int64_t observedRows = poll.impl_->lastRawRows;
3370 if (!impl_->tailInitialized) {
3371 impl_->tailPageNumber = pageNumber;
3372 impl_->tailRows = observedRows;
3373 impl_->tailInitialized =
true;
3375 return std::nullopt;
3377 if (pageNumber != impl_->tailPageNumber)
3378 throwState(
"readNewRows only follows growth of the existing final page");
3379 if (observedRows < impl_->tailRows)
3380 throwState(
"the followed page was truncated");
3381 if (observedRows == impl_->tailRows) {
3383 return std::nullopt;
3385 const std::int64_t firstRow = impl_->tailRows;
3386 const std::int64_t newRows = observedRows - firstRow;
3387 if (request.rows.last) {
3388 request.rows.first = firstRow + std::max<std::int64_t>(0, newRows - *request.rows.last);
3389 request.rows.last.reset();
3391 request.rows.first = firstRow + std::min(request.rows.first, newRows);
3393 poll.gotoPage(pageNumber);
3394 auto delta = poll.next(std::move(request));
3396 if (!delta) throwState(
"final page disappeared while reading newly appended rows");
3397 impl_->tailRows = observedRows;
3398 return PageDelta{pageNumber, firstRow, std::move(*delta)};
3401MaterializedDataset Reader::readAll(ReadRequest request) {
3402 MaterializedDataset result{layout(), {}};
3403 while (
auto page = next(request))
3404 result.pages.push_back(std::move(*page));
3408void Reader::close() {
3409 if (!impl_ || impl_->closed)
3411 if (!impl_->disconnected) impl_->stream.close();
3412 impl_->lock.reset();
3413 impl_->closed =
true;
3417 std::filesystem::path path;
3418 WriterOptions options;
3419 std::shared_ptr<Layout> layout;
3420 std::unique_ptr<BufferedStream> stream;
3421 std::optional<Page> current;
3422 std::int64_t pageNumber = 0;
3423 std::int64_t expectedRows = 0;
3424 std::int64_t updateInterval = 0;
3425 std::optional<std::uint64_t> pageStart;
3426 std::optional<std::uint64_t> disconnectedOffset;
3427 std::unique_ptr<PathLock> lock;
3428 FileIdentity identity;
3429 bool snapshotWritten =
false;
3430 bool rewriteLastPage =
false;
3431 bool pathBacked =
false;
3432 bool disconnected =
false;
3433 bool closed =
false;
3435 void writeCurrent(
bool keepOpen) {
3437 throwState(
"no page has been started");
3439 throwState(
"writer is disconnected");
3440 validatePage(*current, *layout);
3441 if (rewriteLastPage) {
3442 const std::filesystem::path temporary(path.string() +
".sddspp.tmp");
3443 if (std::filesystem::exists(temporary))
3444 throwState(
"temporary rewrite file already exists: " + temporary.string());
3446 Layout outputLayout = *layout;
3447 BufferedStream output(openOutput(temporary,
3448 compressionFor(path, options.compression),
"wb",
3449 options.bufferBytes, options.gzipLevel,
3450 options.lzmaPreset, options.lzmaCheck));
3451 writeLayout(output, outputLayout, options);
3452 std::int64_t number = 0;
3453 Reader source = Reader::open(path);
3454 while (number < pageNumber) {
3455 auto page = source.next();
3457 throwState(
"source file lost a page during atomic last-page update");
3458 writePageData(output, *page, outputLayout, options, ++number);
3461 writePageData(output, *current, outputLayout, options, ++number);
3466 replaceFile(temporary, path);
3467 identity = fileIdentity(path);
3468 if (options.lockMode != LockMode::None)
3469 lock = acquirePathLock(path, options.lockMode,
false);
3471 std::error_code ignored;
3472 std::filesystem::remove(temporary, ignored);
3475 snapshotWritten =
true;
3478 throwState(
"writer has no output stream");
3479 if (snapshotWritten) {
3480 if (!pageStart || !stream->seekable())
3481 throwState(
"page update requires a seekable, uncompressed output");
3482 stream->seek(*pageStart);
3484 pageStart = stream->seekable() ? std::optional<std::uint64_t>(stream->tell())
3487 writePageData(*stream, *current, *layout, options, pageNumber + 1);
3488 if (snapshotWritten)
3489 stream->truncate(stream->tell());
3490 snapshotWritten =
true;
3497 snapshotWritten =
false;
3502Writer::Writer(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {}
3503Writer::Writer(Writer &&) noexcept = default;
3504Writer &Writer::operator=(Writer &&) noexcept = default;
3505Writer::~Writer() = default;
3507Writer Writer::create(const std::filesystem::path &path, Layout layout,
3508 WriterOptions options) {
3509 const Compression compression = compressionFor(path, options.compression);
3510 if (compression == Compression::Xz || compression == Compression::Lzma)
3511 layout.data.mode = DataMode::Binary;
3512 validateLayout(layout);
3513 auto lock = acquirePathLock(path, options.lockMode,
true);
3514 auto impl = std::make_unique<Impl>();
3516 impl->pathBacked =
true;
3517 impl->options = options;
3518 impl->layout = std::make_shared<Layout>(std::move(layout));
3519 impl->stream = std::make_unique<BufferedStream>(
3520 openOutput(path, options.compression,
"wb", options.bufferBytes,
3521 options.gzipLevel, options.lzmaPreset, options.lzmaCheck));
3522 impl->identity = fileIdentity(path);
3523 impl->lock = std::move(lock);
3524 writeLayout(*impl->stream, *impl->layout, options);
3525 return Writer(std::move(impl));
3528Writer Writer::toStdout(Layout layout, WriterOptions options) {
3529 if (options.compression == Compression::Xz || options.compression == Compression::Lzma)
3530 layout.data.mode = DataMode::Binary;
3531 validateLayout(layout);
3532 auto impl = std::make_unique<Impl>();
3533 impl->options = options;
3534 impl->layout = std::make_shared<Layout>(std::move(layout));
3535 impl->stream = std::make_unique<BufferedStream>(standardOutput(options));
3536 writeLayout(*impl->stream, *impl->layout, options);
3537 return Writer(std::move(impl));
3540Writer Writer::toSink(std::unique_ptr<OutputSink> sink, Layout layout,
3541 std::string sinkName, WriterOptions options) {
3542 if (options.compression == Compression::Xz || options.compression == Compression::Lzma)
3543 layout.data.mode = DataMode::Binary;
3544 validateLayout(layout);
3545 auto impl = std::make_unique<Impl>();
3546 impl->path = std::filesystem::path(std::move(sinkName));
3547 impl->options = options;
3548 impl->layout = std::make_shared<Layout>(std::move(layout));
3549 auto stream = wrapOutputCodec(std::make_unique<OutputSinkStream>(std::move(sink)), options);
3550 impl->stream = std::make_unique<BufferedStream>(std::move(stream));
3551 writeLayout(*impl->stream, *impl->layout, options);
3552 return Writer(std::move(impl));
3555Writer Writer::append(
const std::filesystem::path &path, WriterOptions options) {
3556 if (compressionFor(path, options.compression) != Compression::None)
3557 throwState(
"appending new pages to compressed SDDS files is not supported");
3558 auto lock = acquirePathLock(path, options.lockMode,
false);
3559 Reader reader = Reader::open(path);
3560 Layout layout = reader.layout();
3561 std::int64_t pages = 0;
3562 if (layout.data.mode == DataMode::Ascii) {
3563 reader.buildPageIndex();
3564 pages = reader.indexedPageCount().value_or(0);
3567 auto impl = std::make_unique<Impl>();
3569 impl->pathBacked =
true;
3570 impl->options = options;
3571 impl->layout = std::make_shared<Layout>(std::move(layout));
3572 impl->pageNumber = pages;
3573 impl->stream = std::make_unique<BufferedStream>(
3574 openOutput(path, Compression::None,
"r+b", options.bufferBytes));
3575 impl->identity = fileIdentity(path);
3576 impl->lock = std::move(lock);
3577 impl->stream->seek(std::filesystem::file_size(path));
3578 return Writer(std::move(impl));
3581Writer Writer::appendToLastPage(
const std::filesystem::path &path,
3582 std::int64_t updateInterval, WriterOptions options) {
3583 if (updateInterval < 1)
3584 throwState(
"update interval must be positive");
3585 auto lock = acquirePathLock(path, options.lockMode,
false);
3586 Reader reader = Reader::open(path);
3587 Layout layout = reader.layout();
3588 std::optional<std::uint64_t> lastPageOffset;
3589 std::optional<Page> lastPage;
3590 std::int64_t totalPages = 0;
3591 if (reader.impl_->dataStart) {
3592 reader.buildPageIndex();
3593 totalPages = reader.indexedPageCount().value_or(0);
3595 lastPageOffset = reader.impl_->pageOffsets.at(
static_cast<std::size_t
>(totalPages - 1));
3596 reader.gotoPage(totalPages);
3597 lastPage = reader.next();
3600 while (
auto page = reader.next()) {
3601 lastPage = std::move(*page);
3606 throwState(
"cannot append to the last page of an empty SDDS file");
3608 auto impl = std::make_unique<Impl>();
3610 impl->pathBacked =
true;
3611 impl->options = options;
3612 impl->layout = std::make_shared<Layout>(std::move(layout));
3613 impl->current = std::move(*lastPage);
3614 impl->pageNumber = totalPages - 1;
3615 impl->updateInterval = updateInterval;
3616 const bool canUpdateInPlace = compressionFor(path, options.compression) == Compression::None &&
3617 impl->layout->data.mode == DataMode::Binary &&
3618 impl->layout->data.majorOrder == MajorOrder::Row;
3619 if (options.updateStrategy == UpdateStrategy::InPlaceOnly && !canUpdateInPlace)
3620 throwState(
"in-place last-page updates require uncompressed row-major binary data");
3621 impl->rewriteLastPage = options.updateStrategy == UpdateStrategy::AtomicRewrite ||
3623 if (!impl->rewriteLastPage) {
3624 if (!lastPageOffset)
3625 throwState(
"in-place last-page update requires a seekable input");
3626 impl->stream = std::make_unique<BufferedStream>(
3627 openOutput(path, Compression::None,
"r+b", options.bufferBytes));
3628 impl->pageStart = *lastPageOffset;
3629 impl->snapshotWritten =
true;
3631 impl->identity = fileIdentity(path);
3632 impl->lock = std::move(lock);
3633 return Writer(std::move(impl));
3636const Layout &Writer::layout()
const {
3637 if (!impl_ || impl_->closed)
3638 throwState(
"writer is closed");
3639 return *impl_->layout;
3642std::int64_t Writer::rowsPresent() const noexcept {
3643 return impl_ && impl_->current ? impl_->current->rowCount() : 0;
3646void Writer::write(
const Page &page) {
3647 if (!impl_ || impl_->closed)
3648 throwState(
"writer is closed");
3650 throwState(
"commit the current page before writing another page");
3651 impl_->current = page;
3653 impl_->writeCurrent(
false);
3654 }
catch (
const Error &error) {
3655 throwWithContext(error, impl_->path, impl_->pageNumber + 1,
3656 impl_->stream ? std::optional<std::uint64_t>(impl_->stream->offset())
3661void Writer::write(Page &&page) {
3662 if (!impl_ || impl_->closed) throwState(
"writer is closed");
3663 if (impl_->disconnected) throwState(
"writer is disconnected");
3664 if (impl_->current) throwState(
"commit the current page before writing another page");
3665 impl_->current.emplace(std::move(page));
3667 impl_->writeCurrent(
false);
3668 }
catch (
const Error &error) {
3669 throwWithContext(error, impl_->path, impl_->pageNumber + 1,
3670 impl_->stream ? std::optional<std::uint64_t>(impl_->stream->offset())
3675void Writer::beginPage(std::int64_t expectedRows) {
3676 if (!impl_ || impl_->closed)
3677 throwState(
"writer is closed");
3679 throwState(
"a page is already active");
3680 if (expectedRows < 0)
3681 throwState(
"expected row count cannot be negative");
3682 impl_->expectedRows = expectedRows;
3683 impl_->current.emplace(impl_->layout);
3686void Writer::setParameter(std::string_view name, Scalar value) {
3687 if (!impl_ || !impl_->current)
3688 throwState(
"no page has been started");
3689 impl_->current->setParameter(name, std::move(value));
3692void Writer::setArray(std::string_view name, ArrayData value) {
3693 if (!impl_ || !impl_->current)
3694 throwState(
"no page has been started");
3695 impl_->current->setArray(name, std::move(value));
3698void Writer::setColumn(std::string_view name, Values value, std::int64_t startRow) {
3699 if (!impl_ || !impl_->current)
3700 throwState(
"no page has been started");
3701 const std::size_t index = impl_->layout->columnIndex(name);
3703 impl_->current->setColumn(index, std::move(value));
3705 mergeValuesInPlace(impl_->current->columns_.at(index), std::move(value), startRow);
3706 impl_->current->columnsLoaded_.at(index) =
true;
3707 impl_->current->rowCount_ = 0;
3708 for (std::size_t column = 0; column < impl_->current->columns_.size(); ++column)
3709 if (impl_->current->columnsLoaded_[column])
3710 impl_->current->rowCount_ = std::max(
3711 impl_->current->rowCount_,
3712 static_cast<std::int64_t
>(valuesSize(impl_->current->columns_[column])));
3716void Writer::commitPage() {
3717 if (!impl_ || impl_->closed)
3718 throwState(
"writer is closed");
3720 impl_->writeCurrent(
false);
3721 }
catch (
const Error &error) {
3722 throwWithContext(error, impl_->path, impl_->pageNumber + 1,
3723 impl_->stream ? std::optional<std::uint64_t>(impl_->stream->offset())
3728void Writer::updatePage(
bool flushRows) {
3729 if (!impl_ || impl_->closed)
3730 throwState(
"writer is closed");
3731 if (!impl_->rewriteLastPage && impl_->stream && !impl_->stream->seekable() &&
3732 impl_->snapshotWritten)
3733 throwState(
"page updates require a seekable output or atomic-rewrite mode");
3735 impl_->writeCurrent(
true);
3736 }
catch (
const Error &error) {
3737 throwWithContext(error, impl_->path, impl_->pageNumber + 1,
3738 impl_->stream ? std::optional<std::uint64_t>(impl_->stream->offset())
3745void Writer::sync() {
3746 if (!impl_ || impl_->closed)
3747 throwState(
"writer is closed");
3748 if (impl_->stream) {
3749 impl_->stream->flush();
3750 if (impl_->layout->data.fsync) {
3751 impl_->stream->raw().sync();
3756void Writer::disconnect() {
3757 if (!impl_ || impl_->closed) throwState(
"writer is closed");
3758 if (impl_->disconnected)
return;
3759 if (!impl_->pathBacked || compressionFor(impl_->path, impl_->options.compression) != Compression::None ||
3760 !impl_->stream || !impl_->stream->seekable())
3761 throwState(
"disconnect requires a path-backed, uncompressed, seekable output");
3762 impl_->disconnectedOffset = impl_->stream->tell();
3763 impl_->stream->close();
3764 impl_->disconnected =
true;
3767void Writer::reconnect() {
3768 if (!impl_ || impl_->closed) throwState(
"writer is closed");
3769 if (!impl_->disconnected)
return;
3770 if (!sameFile(impl_->identity, fileIdentity(impl_->path)))
3771 throwState(
"output file was replaced while disconnected");
3772 impl_->stream->replace(openOutput(impl_->path, Compression::None,
"r+b",
3773 impl_->options.bufferBytes),
3774 *impl_->disconnectedOffset);
3775 impl_->disconnected =
false;
3778void Writer::close() {
3779 if (!impl_ || impl_->closed)
3783 impl_->writeCurrent(
false);
3784 if (impl_->stream && !impl_->disconnected)
3785 impl_->stream->close();
3786 }
catch (
const Error &error) {
3787 throwWithContext(error, impl_->path, impl_->pageNumber + 1,
3788 impl_->stream ? std::optional<std::uint64_t>(impl_->stream->offset())
3791 impl_->lock.reset();
3792 impl_->closed =
true;
write(SddsFile sdds_file, output_file)
Mostly backward compatible with the PyLHC sdds module write() function.
SddsFile read(input_file)
Mostly backward compatible with the PyLHC sdds module read() function.