#pragma once #include #include #include #include #include namespace DB { /** Простой абстрактный класс для буферизованного чтения данных (последовательности char) откуда-нибудь. * В отличие от std::istream, предоставляет доступ к внутреннему буферу, * а также позволяет вручную управлять позицией внутри буфера. * * Замечание! Используется char *, а не const char * * (для того, чтобы можно было вынести общий код в BufferBase, а также для того, чтобы можно было заполнять буфер новыми данными). * Это вызывает неудобства - например, при использовании ReadBuffer для чтения из куска памяти const char *, * приходится использовать const_cast. * * Наследники должны реализовать метод nextImpl(). */ class ReadBuffer : public BufferBase { public: /** Создаёт буфер и устанавливает курсор на его конец, * чтобы при первой попытке чтения вызвалась функция next() для загрузки в буфер новой порции данных. */ ReadBuffer(Position ptr, size_t size) : BufferBase(ptr, size, size) {} /** Используется, если буфер уже заполнен данными, которые можно читать. * (в этом случае, передайте 0 в качестве offset) */ ReadBuffer(Position ptr, size_t size, size_t offset) : BufferBase(ptr, size, offset) {} void set(Position ptr, size_t size) { BufferBase::set(ptr, size, size); } /** прочитать следующие данные и заполнить ими буфер; переместить позицию в начало; * вернуть false в случае конца, true иначе; кинуть исключение, если что-то не так */ inline bool next() { bytes += offset(); bool res = nextImpl(); if (!res) working_buffer.resize(0); pos = working_buffer.begin(); return res; } virtual ~ReadBuffer() {} /** В отличие от std::istream, возвращает true, если все данные были прочитаны * (а не в случае, если была попытка чтения после конца). * Если на данный момент позиция находится на конце буфера, то вызывает метод next(). * То есть, имеет побочный эффект - если буфер закончился, то обновляет его и переносит позицию в начало. * * При попытке чтения после конца, следует кидать исключение. */ inline bool eof() { return pos == working_buffer.end() && !next(); } void ignore() { if (!eof()) ++pos; else throw Exception("Attempt to read after eof", ErrorCodes::ATTEMPT_TO_READ_AFTER_EOF); } void ignore(size_t n) { while (!eof() && n != 0) { size_t bytes_to_ignore = std::min(static_cast(working_buffer.end() - pos), n); pos += bytes_to_ignore; n -= bytes_to_ignore; } if (n) throw Exception("Attempt to read after eof", ErrorCodes::ATTEMPT_TO_READ_AFTER_EOF); } /** Читает столько, сколько есть, не больше n байт. */ size_t read(char * to, size_t n) { size_t bytes_copied = 0; while (!eof() && bytes_copied < n) { size_t bytes_to_copy = std::min(static_cast(working_buffer.end() - pos), n - bytes_copied); std::memcpy(to + bytes_copied, pos, bytes_to_copy); pos += bytes_to_copy; bytes_copied += bytes_to_copy; } return bytes_copied; } /** Читает n байт, если есть меньше - кидает исключение. */ void readStrict(char * to, size_t n) { if (n != read(to, n)) throw Exception("Cannot read all data", ErrorCodes::CANNOT_READ_ALL_DATA); } private: /** Прочитать следующие данные и заполнить ими буфер. * Вернуть false в случае конца, true иначе. * Кинуть исключение, если что-то не так. */ virtual bool nextImpl() { return false; }; }; }