diff --git a/.clang-tidy b/.clang-tidy index fb0fa6f..43f6d61 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,6 +3,7 @@ Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, + -bugprone-invalid-enum-default-initialization, clang-analyzer-*, -clang-analyzer-optin.cplusplus.VirtualCall, clang-diagnostic-*, diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5a575..72462f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.55.0 - 2026-04-28 + +### Enhancements +- Improved `DbnDecoder` throughput on current-version data and `AsIs` workloads by + caching whether the upgrade policy-version combination requires upgrading, skipping + the per-record `DecodeRecordCompat` dispatch on the fast path +- Made `detail::Buffer` shifts explicit to avoid redundant moves during record decoding +- Added new publisher values for Cboe Titanium Cboe Global Indices Feed +- Added `Year` to `SplitDuration` enum for yearly historical batch job submissions +- Upgraded default cpp-httplib version to 0.43.1 +- Upgraded default nlohmann/json version to 3.12.0 + ## 0.54.0 - 2026-04-21 ### Enhancements diff --git a/CMakeLists.txt b/CMakeLists.txt index 3260c5f..f6e34de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.24..4.2) project( databento - VERSION 0.54.0 + VERSION 0.55.0 LANGUAGES CXX DESCRIPTION "Official Databento client library" ) @@ -152,7 +152,7 @@ if(${PROJECT_NAME_UPPERCASE}_USE_EXTERNAL_JSON) find_package(nlohmann_json REQUIRED) endif() else() - set(json_version 3.11.3) + set(json_version 3.12.0) # Required to correctly install nlohmann_json set(JSON_Install ON) FetchContent_Declare( @@ -178,7 +178,7 @@ if(${PROJECT_NAME_UPPERCASE}_USE_EXTERNAL_HTTPLIB) find_package(httplib REQUIRED) endif() else() - set(httplib_version 0.37.2) + set(httplib_version 0.43.1) FetchContent_Declare( httplib URL https://github.com/yhirose/cpp-httplib/archive/refs/tags/v${httplib_version}.tar.gz diff --git a/include/databento/dbn_decoder.hpp b/include/databento/dbn_decoder.hpp index 2456e01..ba94404 100644 --- a/include/databento/dbn_decoder.hpp +++ b/include/databento/dbn_decoder.hpp @@ -35,6 +35,9 @@ class DbnDecoder { VersionUpgradePolicy upgrade_policy, bool ts_out, std::array* compat_buffer, Record rec); + // Returns whether a record from `version`-formatted data requires runtime + // upgrade dispatch under `upgrade_policy`. + static bool NeedsUpgrade(VersionUpgradePolicy upgrade_policy, std::uint8_t version); // Should be called exactly once. Metadata DecodeMetadata(); @@ -61,6 +64,7 @@ class DbnDecoder { ILogReceiver* log_receiver_; std::uint8_t version_{}; VersionUpgradePolicy upgrade_policy_; + bool needs_upgrade_{true}; bool ts_out_{}; std::unique_ptr input_; detail::Buffer buffer_{}; diff --git a/include/databento/detail/buffer.hpp b/include/databento/detail/buffer.hpp index f54b1ca..09bd5ca 100644 --- a/include/databento/detail/buffer.hpp +++ b/include/databento/detail/buffer.hpp @@ -46,14 +46,7 @@ class Buffer : public IReadable, public IWritable { std::byte* ReadEnd() { return write_pos_; } const std::byte* ReadBegin() const { return read_pos_; } const std::byte* ReadEnd() const { return write_pos_; } - // Indicate how many bytes were read - void Consume(std::size_t length) { - read_pos_ += length; - if (static_cast(read_pos_ - buf_.get()) > (Capacity() / 2)) { - Shift(); - } - } - void ConsumeNoShift(std::size_t length) { read_pos_ += length; } + void Consume(std::size_t length) { read_pos_ += length; } std::size_t ReadCapacity() const { return static_cast(write_pos_ - read_pos_); } @@ -65,6 +58,13 @@ class Buffer : public IReadable, public IWritable { } void Reserve(std::size_t capacity); void Shift(); + // Shifts unread data to offset 0 if writable space is less than `needed`, + // reclaiming the consumed prefix. Does not grow the buffer. + void ShiftForSpace(std::size_t needed) { + if (WriteCapacity() < needed && read_pos_ != buf_.get()) { + Shift(); + } + } friend std::ostream& operator<<(std::ostream& stream, const Buffer& buffer); diff --git a/include/databento/detail/dbn_buffer_decoder.hpp b/include/databento/detail/dbn_buffer_decoder.hpp index 1a813a1..b379493 100644 --- a/include/databento/detail/dbn_buffer_decoder.hpp +++ b/include/databento/detail/dbn_buffer_decoder.hpp @@ -61,6 +61,7 @@ class DbnBufferDecoder { alignas(RecordHeader) std::array compat_buffer_{}; std::uint8_t input_version_{}; bool ts_out_{}; + bool needs_upgrade_{true}; DecoderState state_{DecoderState::Init}; }; } // namespace databento::detail diff --git a/include/databento/detail/http_client.hpp b/include/databento/detail/http_client.hpp index 0b740dc..bb3f25a 100644 --- a/include/databento/detail/http_client.hpp +++ b/include/databento/detail/http_client.hpp @@ -43,7 +43,7 @@ class HttpClient { httplib::Result&& res) const; void CheckWarnings(const httplib::Response& response) const; - static const httplib::Headers kHeaders; + static const httplib::Headers& BaseHeaders(); ILogReceiver* log_receiver_; httplib::Client client_; diff --git a/include/databento/enums.hpp b/include/databento/enums.hpp index 5283dc4..f5cd9bd 100644 --- a/include/databento/enums.hpp +++ b/include/databento/enums.hpp @@ -24,6 +24,7 @@ enum class SplitDuration : std::uint8_t { Day = 0, Week, Month, + Year, None, }; diff --git a/include/databento/publishers.hpp b/include/databento/publishers.hpp index a77a5d2..2ced8ec 100644 --- a/include/databento/publishers.hpp +++ b/include/databento/publishers.hpp @@ -117,6 +117,8 @@ enum class Venue : std::uint16_t { Mxto = 54, // IEX Options LLC Iexo = 55, + // Cboe Global Indices Feed + Cgif = 56, }; // A source of data. @@ -203,6 +205,8 @@ enum class Dataset : std::uint16_t { XcbfPitch = 40, // Blue Ocean ATS MEMOIR Depth OceaMemoir = 41, + // Cboe Titanium Cboe Global Indices Feed + CgifTitanium = 42, }; // A specific Venue from a specific data source. @@ -425,6 +429,8 @@ enum class Publisher : std::uint16_t { OpraPillarMxto = 108, // OPRA - IEX Options LLC OpraPillarIexo = 109, + // Cboe Global Indices Feed + CgifTitaniumCgif = 110, }; // Get a Publisher's Venue. diff --git a/include/databento/symbology.hpp b/include/databento/symbology.hpp index 5d5f00f..071a70a 100644 --- a/include/databento/symbology.hpp +++ b/include/databento/symbology.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -28,10 +29,10 @@ struct SymbologyResolution { // // Throws InvalidArgumentError if symbols is empty or the iterator range is // empty. -std::string JoinSymbolStrings(const std::string& method_name, +std::string JoinSymbolStrings(std::string_view method_name, std::vector::const_iterator symbols_begin, std::vector::const_iterator symbols_end); -std::string JoinSymbolStrings(const std::string& method_name, +std::string JoinSymbolStrings(std::string_view method_name, const std::vector& symbols); std::string ToString(const SymbologyResolution& sym_res); std::ostream& operator<<(std::ostream& stream, const SymbologyResolution& sym_res); diff --git a/pkg/PKGBUILD b/pkg/PKGBUILD index 660bb37..1f649b1 100644 --- a/pkg/PKGBUILD +++ b/pkg/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Databento _pkgname=databento-cpp pkgname=databento-cpp-git -pkgver=0.54.0 +pkgver=0.55.0 pkgrel=1 pkgdesc="Official C++ client for Databento" arch=('any') diff --git a/src/dbn_decoder.cpp b/src/dbn_decoder.cpp index ee91f99..91abc6a 100644 --- a/src/dbn_decoder.cpp +++ b/src/dbn_decoder.cpp @@ -173,6 +173,7 @@ databento::Metadata DbnDecoder::DecodeMetadata() { buffer_.ReadBegin(), kMetadataPreludeSize); buffer_.Consume(kMetadataPreludeSize); version_ = version; + needs_upgrade_ = NeedsUpgrade(upgrade_policy_, version_); buffer_.Reserve(size); input_->ReadExact(buffer_.WriteBegin(), size); buffer_.Fill(size); @@ -273,6 +274,19 @@ databento::Record DbnDecoder::DecodeRecordCompat( return rec; } +bool DbnDecoder::NeedsUpgrade(VersionUpgradePolicy upgrade_policy, + std::uint8_t version) { + switch (upgrade_policy) { + case VersionUpgradePolicy::UpgradeToV2: + return version < 2; + case VersionUpgradePolicy::UpgradeToV3: + return version < 3; + case VersionUpgradePolicy::AsIs: + default: + return false; + } +} + // assumes DecodeMetadata has been called const databento::Record* DbnDecoder::DecodeRecord() { // need some unread unread_bytes @@ -293,16 +307,16 @@ const databento::Record* DbnDecoder::DecodeRecord() { } } current_record_ = Record{BufferRecordHeader()}; - buffer_.ConsumeNoShift(current_record_.Size()); - current_record_ = DbnDecoder::DecodeRecordCompat(version_, upgrade_policy_, ts_out_, - &compat_buffer_, current_record_); + buffer_.Consume(current_record_.Size()); + if (needs_upgrade_) { + current_record_ = DbnDecoder::DecodeRecordCompat(version_, upgrade_policy_, ts_out_, + &compat_buffer_, current_record_); + } return ¤t_record_; } size_t DbnDecoder::FillBuffer() { - if (buffer_.WriteCapacity() < kMaxRecordLen) { - buffer_.Shift(); - } + buffer_.ShiftForSpace(kMaxRecordLen); const auto fill_size = input_->ReadSome(buffer_.WriteBegin(), buffer_.WriteCapacity()); buffer_.Fill(fill_size); diff --git a/src/detail/buffer.cpp b/src/detail/buffer.cpp index 88198d4..ef4bcc7 100644 --- a/src/detail/buffer.cpp +++ b/src/detail/buffer.cpp @@ -13,9 +13,7 @@ size_t Buffer::Write(const char* data, std::size_t length) { return Write(reinterpret_cast(data), length); } size_t Buffer::Write(const std::byte* data, std::size_t length) { - if (length > WriteCapacity()) { - Shift(); - } + ShiftForSpace(length); const auto write_size = std::min(WriteCapacity(), length); std::copy(data, data + write_size, WriteBegin()); Fill(write_size); @@ -28,8 +26,8 @@ void Buffer::WriteAll(const char* data, std::size_t length) { void Buffer::WriteAll(const std::byte* data, std::size_t length) { if (length > Capacity() - ReadCapacity()) { Reserve(ReadCapacity() + length); - } else if (length >= WriteCapacity()) { - Shift(); + } else { + ShiftForSpace(length); } std::copy(data, data + length, WriteBegin()); write_pos_ += length; diff --git a/src/detail/dbn_buffer_decoder.cpp b/src/detail/dbn_buffer_decoder.cpp index 15f0633..d0b0ceb 100644 --- a/src/detail/dbn_buffer_decoder.cpp +++ b/src/detail/dbn_buffer_decoder.cpp @@ -10,6 +10,7 @@ using databento::detail::DbnBufferDecoder; databento::KeepGoing DbnBufferDecoder::Process(const char* data, std::size_t length) { zstd_buffer_->WriteAll(data, length); while (true) { + dbn_buffer_.ShiftForSpace(kMaxRecordLen); const auto read_size = zstd_stream_.ReadSome(dbn_buffer_.WriteBegin(), dbn_buffer_.WriteCapacity()); dbn_buffer_.Fill(read_size); @@ -24,6 +25,7 @@ databento::KeepGoing DbnBufferDecoder::Process(const char* data, std::size_t len std::tie(input_version_, bytes_needed_) = DbnDecoder::DecodeMetadataVersionAndSize(dbn_buffer_.ReadBegin(), dbn_buffer_.ReadCapacity()); + needs_upgrade_ = DbnDecoder::NeedsUpgrade(upgrade_policy_, input_version_); dbn_buffer_.Consume(kMetadataPreludeSize); dbn_buffer_.Reserve(bytes_needed_); state_ = DecoderState::Metadata; @@ -55,8 +57,10 @@ databento::KeepGoing DbnBufferDecoder::Process(const char* data, std::size_t len if (dbn_buffer_.ReadCapacity() < bytes_needed_) { break; } - record = DbnDecoder::DecodeRecordCompat(input_version_, upgrade_policy_, - ts_out_, &compat_buffer_, record); + if (needs_upgrade_) { + record = DbnDecoder::DecodeRecordCompat(input_version_, upgrade_policy_, + ts_out_, &compat_buffer_, record); + } if (record_callback_(record) == KeepGoing::Stop) { return KeepGoing::Stop; } @@ -77,6 +81,7 @@ std::ostream& operator<<(std::ostream& stream, const DbnBufferDecoder& buffer) { .AddField("bytes_needed_", buffer.bytes_needed_) .AddField("input_version_", buffer.input_version_) .AddField("ts_out_", buffer.ts_out_) + .AddField("needs_upgrade_", buffer.needs_upgrade_) .AddField("state_", buffer.state_) .Finish(); } diff --git a/src/detail/http_client.cpp b/src/detail/http_client.cpp index 77a148c..630026c 100644 --- a/src/detail/http_client.cpp +++ b/src/detail/http_client.cpp @@ -15,15 +15,19 @@ using databento::detail::HttpClient; constexpr std::chrono::seconds kTimeout{100}; -const httplib::Headers HttpClient::kHeaders{ - {"accept", "application/json"}, - {"user-agent", kUserAgent}, -}; + +const httplib::Headers& HttpClient::BaseHeaders() { + static const httplib::Headers kHeaders{ + {"accept", "application/json"}, + {"user-agent", kUserAgent}, + }; + return kHeaders; +} HttpClient::HttpClient(databento::ILogReceiver* log_receiver, const std::string& key, const std::string& gateway) : log_receiver_{log_receiver}, client_{gateway} { - auto headers = HttpClient::kHeaders; + auto headers = HttpClient::BaseHeaders(); headers.insert(httplib::make_basic_authentication_header(key, "")); client_.set_default_headers(headers); client_.set_basic_auth(key, ""); @@ -34,7 +38,7 @@ HttpClient::HttpClient(databento::ILogReceiver* log_receiver, const std::string& HttpClient::HttpClient(databento::ILogReceiver* log_receiver, const std::string& key, const std::string& gateway, std::uint16_t port) : log_receiver_{log_receiver}, client_{gateway, port} { - auto headers = HttpClient::kHeaders; + auto headers = HttpClient::BaseHeaders(); headers.insert(httplib::make_basic_authentication_header(key, "")); client_.set_default_headers(headers); client_.set_basic_auth(key, ""); diff --git a/src/enums.cpp b/src/enums.cpp index d3ab534..d30b1f8 100644 --- a/src/enums.cpp +++ b/src/enums.cpp @@ -48,6 +48,9 @@ const char* ToString(SplitDuration duration_interval) { case SplitDuration::Month: { return "month"; } + case SplitDuration::Year: { + return "year"; + } case SplitDuration::None: { return "none"; } @@ -979,6 +982,9 @@ SplitDuration FromString(const std::string& str) { if (str == "month") { return SplitDuration::Month; } + if (str == "year") { + return SplitDuration::Year; + } if (str == "none") { return SplitDuration::None; } diff --git a/src/historical.cpp b/src/historical.cpp index 8b53d89..3b27b9d 100644 --- a/src/historical.cpp +++ b/src/historical.cpp @@ -13,6 +13,7 @@ #include // back_inserter #include #include +#include #include #include // move #include @@ -226,7 +227,7 @@ Historical::Historical(ILogReceiver* log_receiver, std::string key, std::string upgrade_policy_{upgrade_policy}, client_{log_receiver, key_, gateway_, port} {} -static const std::string kBatchSubmitJobEndpoint = "Historical::BatchSubmitJob"; +constexpr std::string_view kBatchSubmitJobEndpoint = "Historical::BatchSubmitJob"; databento::BatchJob Historical::BatchSubmitJob( const std::string& dataset, const std::vector& symbols, Schema schema, @@ -471,7 +472,7 @@ void Historical::DownloadFile(const std::string& url, } catch (const databento::Exception& exc) { retry += 1; if (retry == kMaxRetries) { - throw exc; + throw; } ss.str(""); ss << '[' << kMethod << "] Retrying download attempt " << retry + 1 << " after " @@ -685,7 +686,7 @@ databento::DatasetRange Historical::MetadataGetDatasetRange( std::move(range_by_schema)}; } -static const std::string kMetadataGetRecordCountEndpoint = +constexpr std::string_view kMetadataGetRecordCountEndpoint = "Historical::MetadataGetRecordCount"; std::uint64_t Historical::MetadataGetRecordCount( @@ -738,7 +739,7 @@ std::uint64_t Historical::MetadataGetRecordCount(const httplib::Params& params) return json; } -static const std::string kMetadataGetBillableSizeEndpoint = +constexpr std::string_view kMetadataGetBillableSizeEndpoint = "Historical::MetadataGetBillableSize"; std::uint64_t Historical::MetadataGetBillableSize( @@ -792,7 +793,7 @@ std::uint64_t Historical::MetadataGetBillableSize(const httplib::Params& params) return json; } -static const std::string kMetadataGetCostEndpoint = "Historical::MetadataGetCost"; +constexpr std::string_view kMetadataGetCostEndpoint = "Historical::MetadataGetCost"; double Historical::MetadataGetCost(const std::string& dataset, const DateTimeRange& datetime_range, @@ -912,8 +913,13 @@ databento::SymbologyResolution Historical::SymbologyResolve( return res; } -static const std::string kTimeseriesGetRangeEndpoint = "Historical::TimeseriesGetRange"; -static const std::string kTimeseriesGetRangePath = ::BuildTimeseriesPath(".get_range"); +constexpr std::string_view kTimeseriesGetRangeEndpoint = + "Historical::TimeseriesGetRange"; + +static const std::string& TimeseriesGetRangePath() { + static const std::string kPath = ::BuildTimeseriesPath(".get_range"); + return kPath; +} void Historical::TimeseriesGetRange(const std::string& dataset, const DateTimeRange& datetime_range, @@ -986,7 +992,7 @@ void Historical::TimeseriesGetRange(const HttplibParams& params, bool early_exit = false; this->client_.PostRawStream( - kTimeseriesGetRangePath, params, + TimeseriesGetRangePath(), params, [&decoder, &early_exit](const char* data, std::size_t length) mutable { if (decoder.Process(data, length) == KeepGoing::Continue) { return true; @@ -1050,11 +1056,11 @@ databento::DbnStore Historical::TimeseriesGetRange( return this->TimeseriesGetRange(params); } databento::DbnStore Historical::TimeseriesGetRange(const HttplibParams& params) { - auto stream = client_.OpenPostStream(kTimeseriesGetRangePath, params); + auto stream = client_.OpenPostStream(TimeseriesGetRangePath(), params); return DbnStore{log_receiver_, std::move(stream), upgrade_policy_}; } -static const std::string kTimeseriesGetRangeToFileEndpoint = +constexpr std::string_view kTimeseriesGetRangeToFileEndpoint = "Historical::TimeseriesGetRangeToFile"; databento::DbnStore Historical::TimeseriesGetRangeToFile( @@ -1111,7 +1117,7 @@ databento::DbnStore Historical::TimeseriesGetRangeToFile( const HttplibParams& params, const std::filesystem::path& file_path) { { OutFileStream out_file{file_path}; - this->client_.PostRawStream(kTimeseriesGetRangePath, params, + this->client_.PostRawStream(TimeseriesGetRangePath(), params, [&out_file](const char* data, std::size_t length) { out_file.WriteAll( reinterpret_cast(data), length); diff --git a/src/live_blocking.cpp b/src/live_blocking.cpp index 9602d10..75fd1e8 100644 --- a/src/live_blocking.cpp +++ b/src/live_blocking.cpp @@ -482,7 +482,7 @@ databento::IReadable::Result LiveBlocking::FillBuffer() { databento::IReadable::Result LiveBlocking::FillBuffer( std::chrono::milliseconds timeout) { - buffer_.Shift(); + buffer_.ShiftForSpace(kMaxRecordLen); const auto read_res = connection_.ReadSome(buffer_.WriteBegin(), buffer_.WriteCapacity(), timeout); buffer_.Fill(read_res.read_size); @@ -494,7 +494,7 @@ databento::IReadable::Result LiveBlocking::FillBuffer( const databento::Record* LiveBlocking::ConsumeBufferedRecord() { current_record_ = Record{BufferRecordHeader()}; - buffer_.ConsumeNoShift(current_record_.Size()); + buffer_.Consume(current_record_.Size()); current_record_ = DbnDecoder::DecodeRecordCompat( version_, upgrade_policy_, send_ts_out_, &compat_buffer_, current_record_); return ¤t_record_; diff --git a/src/publishers.cpp b/src/publishers.cpp index cd94554..6e888f1 100644 --- a/src/publishers.cpp +++ b/src/publishers.cpp @@ -176,6 +176,9 @@ const char* ToString(Venue venue) { case Venue::Iexo: { return "IEXO"; } + case Venue::Cgif: { + return "CGIF"; + } default: { return "Unknown"; } @@ -354,6 +357,9 @@ Venue FromString(const std::string& str) { if (str == "IEXO") { return Venue::Iexo; } + if (str == "CGIF") { + return Venue::Cgif; + } throw InvalidArgumentError{"FromString", "str", "unknown value '" + str + '\''}; } @@ -483,6 +489,9 @@ const char* ToString(Dataset dataset) { case Dataset::OceaMemoir: { return "OCEA.MEMOIR"; } + case Dataset::CgifTitanium: { + return "CGIF.TITANIUM"; + } default: { return "Unknown"; } @@ -619,6 +628,9 @@ Dataset FromString(const std::string& str) { if (str == "OCEA.MEMOIR") { return Dataset::OceaMemoir; } + if (str == "CGIF.TITANIUM") { + return Dataset::CgifTitanium; + } throw InvalidArgumentError{"FromString", "str", "unknown value '" + str + '\''}; } @@ -952,6 +964,9 @@ Venue PublisherVenue(Publisher publisher) { case Publisher::OpraPillarIexo: { return Venue::Iexo; } + case Publisher::CgifTitaniumCgif: { + return Venue::Cgif; + } default: { throw InvalidArgumentError{ "PublisherVenue", "publisher", @@ -1289,6 +1304,9 @@ Dataset PublisherDataset(Publisher publisher) { case Publisher::OpraPillarIexo: { return Dataset::OpraPillar; } + case Publisher::CgifTitaniumCgif: { + return Dataset::CgifTitanium; + } default: { throw InvalidArgumentError{ "PublisherDataset", "publisher", @@ -1627,6 +1645,9 @@ const char* ToString(Publisher publisher) { case Publisher::OpraPillarIexo: { return "OPRA.PILLAR.IEXO"; } + case Publisher::CgifTitaniumCgif: { + return "CGIF.TITANIUM.CGIF"; + } default: { return "Unknown"; } @@ -1967,6 +1988,9 @@ Publisher FromString(const std::string& str) { if (str == "OPRA.PILLAR.IEXO") { return Publisher::OpraPillarIexo; } + if (str == "CGIF.TITANIUM.CGIF") { + return Publisher::CgifTitaniumCgif; + } throw InvalidArgumentError{"FromString", "str", "unknown value '" + str + '\''}; } diff --git a/src/symbology.cpp b/src/symbology.cpp index 4877b56..e918797 100644 --- a/src/symbology.cpp +++ b/src/symbology.cpp @@ -33,11 +33,11 @@ TsSymbolMap SymbologyResolution::CreateSymbolMap() const { return res; } -std::string JoinSymbolStrings(const std::string& method_name, +std::string JoinSymbolStrings(std::string_view method_name, std::vector::const_iterator symbols_begin, std::vector::const_iterator symbols_end) { if (symbols_begin == symbols_end) { - throw InvalidArgumentError{method_name, "symbols", "Cannot be empty"}; + throw InvalidArgumentError{std::string{method_name}, "symbols", "Cannot be empty"}; } return std::accumulate(symbols_begin, symbols_end, std::string{}, [](std::string acc, const std::string& sym) { @@ -45,10 +45,10 @@ std::string JoinSymbolStrings(const std::string& method_name, }); } -std::string JoinSymbolStrings(const std::string& method_name, +std::string JoinSymbolStrings(std::string_view method_name, const std::vector& symbols) { if (symbols.empty()) { - throw InvalidArgumentError{method_name, "symbols", "Cannot be empty"}; + throw InvalidArgumentError{std::string{method_name}, "symbols", "Cannot be empty"}; } return JoinSymbolStrings(method_name, symbols.begin(), symbols.end()); } diff --git a/tests/src/buffer_tests.cpp b/tests/src/buffer_tests.cpp index 45f1b1d..1e349f3 100644 --- a/tests/src/buffer_tests.cpp +++ b/tests/src/buffer_tests.cpp @@ -11,7 +11,7 @@ namespace databento::detail::tests { TEST(BufferTests, TestWriteAllPastCapacity) { Buffer target{10}; target.Fill(4); - target.ConsumeNoShift(2); + target.Consume(2); ASSERT_EQ(target.WriteCapacity(), 6); ASSERT_EQ(target.ReadCapacity(), 2); ASSERT_EQ(target.Capacity(), 10); @@ -25,7 +25,7 @@ TEST(BufferTests, TestWriteAllPastCapacity) { TEST(BufferTests, TestWriteAllShift) { Buffer target{20}; target.WriteAll("TestWriteAllShift", 17); - target.ConsumeNoShift(4); + target.Consume(4); ASSERT_EQ(target.WriteCapacity(), 3); ASSERT_EQ(target.ReadCapacity(), 13); ASSERT_EQ(target.Capacity(), 20); @@ -39,7 +39,7 @@ TEST(BufferTests, TestWriteAllShift) { TEST(BufferTests, TestWriteRead) { Buffer target{10}; target.Fill(5); - target.ConsumeNoShift(5); + target.Consume(5); const auto write_len = target.Write("BufferTests", 11); ASSERT_EQ(write_len, 10); std::array read_buf{}; @@ -54,16 +54,42 @@ TEST(BufferTests, TestReserve) { ASSERT_EQ(target.ReadCapacity(), 0); ASSERT_EQ(target.Capacity(), 120); target.WriteAll("TestReserve", 11); - target.ConsumeNoShift(4); + target.Consume(4); } -TEST(BufferTests, TestConsumeShift) { +TEST(BufferTests, TestConsumeDoesNotShift) { + Buffer target{16}; + target.Fill(12); + target.Consume(10); + ASSERT_EQ(target.ReadCapacity(), 2); + ASSERT_EQ(target.WriteCapacity(), 4); +} + +TEST(BufferTests, TestShiftForSpace) { Buffer target{120}; - target.Fill(120); - ASSERT_EQ(target.WriteCapacity(), 0); - target.ConsumeNoShift(100); - ASSERT_EQ(target.WriteCapacity(), 0); - target.Consume(1); - ASSERT_EQ(target.WriteCapacity(), 101); + target.Fill(40); + target.Consume(20); + ASSERT_EQ(target.WriteCapacity(), 80); + ASSERT_EQ(target.ReadCapacity(), 20); + // Writable space is sufficient: no shift + target.ShiftForSpace(50); + ASSERT_EQ(target.WriteCapacity(), 80); + ASSERT_EQ(target.ReadCapacity(), 20); + // Writable space is insufficient: reclaim the consumed prefix + target.ShiftForSpace(100); + ASSERT_EQ(target.WriteCapacity(), 100); + ASSERT_EQ(target.ReadCapacity(), 20); + // Nothing left to reclaim; shift is a no-op + target.ShiftForSpace(1000); + ASSERT_EQ(target.WriteCapacity(), 100); + ASSERT_EQ(target.ReadCapacity(), 20); +} + +TEST(BufferTests, TestShiftForSpaceNoopWhenUnconsumed) { + Buffer target{16}; + target.Fill(4); + target.ShiftForSpace(1000); + ASSERT_EQ(target.WriteCapacity(), 12); + ASSERT_EQ(target.ReadCapacity(), 4); } } // namespace databento::detail::tests diff --git a/tests/src/dbn_decoder_tests.cpp b/tests/src/dbn_decoder_tests.cpp index 15a4207..819045a 100644 --- a/tests/src/dbn_decoder_tests.cpp +++ b/tests/src/dbn_decoder_tests.cpp @@ -217,6 +217,29 @@ TEST_F(DbnDecoderTests, TestUpgradeMbp1WithTsOut) { ASSERT_EQ(&orig, &upgraded); } +TEST_F(DbnDecoderTests, TestNeedsUpgrade) { + struct Case { + VersionUpgradePolicy policy; + std::uint8_t version; + bool expected; + }; + constexpr Case kCases[] = { + {VersionUpgradePolicy::AsIs, 1, false}, + {VersionUpgradePolicy::AsIs, 2, false}, + {VersionUpgradePolicy::AsIs, 3, false}, + {VersionUpgradePolicy::UpgradeToV2, 1, true}, + {VersionUpgradePolicy::UpgradeToV2, 2, false}, + {VersionUpgradePolicy::UpgradeToV3, 1, true}, + {VersionUpgradePolicy::UpgradeToV3, 2, true}, + {VersionUpgradePolicy::UpgradeToV3, 3, false}, + }; + for (const auto& c : kCases) { + EXPECT_EQ(DbnDecoder::NeedsUpgrade(c.policy, c.version), c.expected) + << "policy=" << static_cast(c.policy) + << " version=" << static_cast(c.version); + } +} + class DbnDecoderSchemaTests : public DbnDecoderTests, public testing::WithParamInterface> {};