| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| #include "paimon/io/buffered_input_stream.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cstring> | |
| #include <utility> | |
| #include "fmt/format.h" | |
| #include "paimon/common/utils/math.h" | |
| #include "paimon/memory/bytes.h" | |
| namespace paimon { | |
| class MemoryPool; | |
| BufferedInputStream::BufferedInputStream(const std::shared_ptr<InputStream>& in, | |
| int64_t buffer_size, MemoryPool* pool) | |
| : buffer_size_(buffer_size), in_(in) { | |
| assert(buffer_size > 0); | |
| buffer_ = std::make_unique<Bytes>(buffer_size, pool); | |
| } | |
| BufferedInputStream::~BufferedInputStream() noexcept = default; | |
| Status BufferedInputStream::Seek(int64_t offset, SeekOrigin origin) { | |
| // Convert all seek origins to an absolute offset so the buffer-hit fast | |
| // path below can work uniformly on absolute positions. | |
| int64_t target_abs_offset = offset; | |
| if (origin == SeekOrigin::FS_SEEK_CUR) { | |
| PAIMON_ASSIGN_OR_RAISE(int64_t cur_pos, GetPos()); | |
| target_abs_offset = cur_pos + offset; | |
| } else if (origin == SeekOrigin::FS_SEEK_END) { | |
| PAIMON_ASSIGN_OR_RAISE(int64_t length, in_->Length()); | |
| target_abs_offset = length + offset; | |
| } | |
| // else: FS_SEEK_SET — target_abs_offset is already absolute. | |
| // Fast path: if the new absolute offset still falls into the bytes already | |
| // cached in buffer_ (i.e. the window from buf_start_abs to buf_end_abs), just | |
| // adjust pos_ without touching the underlying stream. | |
| if (count_ > 0) { | |
| PAIMON_ASSIGN_OR_RAISE(int64_t in_pos, in_->GetPos()); | |
| const int64_t buf_start_abs = in_pos - count_; | |
| const int64_t buf_end_abs = in_pos; | |
| if (target_abs_offset >= buf_start_abs && target_abs_offset <= buf_end_abs) { | |
| pos_ = target_abs_offset - buf_start_abs; | |
| return Status::OK(); | |
| } | |
| } | |
| // Slow path: the target is outside the current buffer window, fall back to | |
| // a real seek on the underlying stream and invalidate the buffer. | |
| PAIMON_RETURN_NOT_OK(in_->Seek(target_abs_offset, FS_SEEK_SET)); | |
| pos_ = 0; | |
| count_ = 0; | |
| return Status::OK(); | |
| } | |
| Result<int64_t> BufferedInputStream::GetPos() const { | |
| PAIMON_ASSIGN_OR_RAISE(int64_t in_pos, in_->GetPos()); | |
| return in_pos - count_ + pos_; | |
| } | |
| Result<int64_t> BufferedInputStream::Read(char* buffer, int64_t size) { | |
| PAIMON_RETURN_NOT_OK(ValidateValueNonNegative(size, "read length")); | |
| int64_t actual_read_len = 0; | |
| while (actual_read_len < size) { | |
| PAIMON_ASSIGN_OR_RAISE(int64_t nread, | |
| InnerRead(buffer + actual_read_len, size - actual_read_len)); | |
| assert(nread > 0); | |
| actual_read_len += nread; | |
| } | |
| PAIMON_RETURN_NOT_OK(AssertReadLength(size, actual_read_len)); | |
| return actual_read_len; | |
| } | |
| Result<int64_t> BufferedInputStream::Read(char* buffer, int64_t size, int64_t offset) { | |
| return Status::Invalid("BufferedInputStream does not support Read from offset"); | |
| } | |
| void BufferedInputStream::ReadAsync(char* buffer, int64_t size, int64_t offset, | |
| std::function<void(Status)>&& callback) { | |
| callback(Status::NotImplemented("BufferedInputStream do not support ReadAsync")); | |
| } | |
| Result<int64_t> BufferedInputStream::Length() const { | |
| return in_->Length(); | |
| } | |
| Status BufferedInputStream::Close() { | |
| pos_ = 0; | |
| count_ = 0; | |
| buffer_.reset(); | |
| return Status::OK(); | |
| } | |
| Result<std::string> BufferedInputStream::GetUri() const { | |
| return in_->GetUri(); | |
| } | |
| Status BufferedInputStream::Fill() { | |
| pos_ = 0; | |
| count_ = 0; | |
| PAIMON_ASSIGN_OR_RAISE(int64_t in_pos, in_->GetPos()); | |
| PAIMON_ASSIGN_OR_RAISE(int64_t length, in_->Length()); | |
| int64_t left_to_read = std::min(length - in_pos, buffer_size_); | |
| PAIMON_ASSIGN_OR_RAISE(int64_t actual_read_len, in_->Read(buffer_->data(), left_to_read)); | |
| PAIMON_RETURN_NOT_OK(AssertReadLength(left_to_read, actual_read_len)); | |
| count_ = actual_read_len; | |
| return Status::OK(); | |
| } | |
| Result<int64_t> BufferedInputStream::InnerRead(char* buffer, int64_t size) { | |
| assert(size > 0); | |
| if (PAIMON_UNLIKELY(pos_ > count_)) { | |
| return Status::Invalid(fmt::format( | |
| "BufferedInputStream internal error: pos_ {} exceeds count_ {}", pos_, count_)); | |
| } | |
| int64_t avail = count_ - pos_; | |
| if (avail == 0) { | |
| /* If the requested length is at least as large as the buffer, and | |
| if there is no mark/reset activity, do not bother to copy the | |
| bytes into the local buffer. In this way buffered streams will | |
| cascade harmlessly. */ | |
| if (size >= buffer_size_) { | |
| return in_->Read(buffer, size); | |
| } | |
| PAIMON_RETURN_NOT_OK(Fill()); | |
| avail = count_ - pos_; | |
| if (avail <= 0) { | |
| return Status::Invalid(fmt::format( | |
| "InnerRead failed, after Fill(), still no bytes available (may read eof), but " | |
| "expect read {} bytes", | |
| size)); | |
| } | |
| } | |
| int64_t copy_length = std::min(avail, size); | |
| memcpy(buffer, buffer_->data() + pos_, static_cast<size_t>(copy_length)); | |
| pos_ += copy_length; | |
| return copy_length; | |
| } | |
| Status BufferedInputStream::AssertReadLength(int64_t read_length, | |
| int64_t actual_read_length) const { | |
| if (read_length != actual_read_length) { | |
| return Status::Invalid( | |
| fmt::format("assert read length failed: read length not match, read length {}, actual " | |
| "read length {}", | |
| read_length, actual_read_length)); | |
| } | |
| return Status::OK(); | |
| } | |
| } // namespace paimon |