Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion olp-cpp-sdk-core/include/olp/core/client/ErrorCode.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2025 HERE Europe B.V.
* Copyright (C) 2019-2026 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -101,6 +101,11 @@ enum class ErrorCode {
* Absence of network connectivity.
*/
Offline,

/**
* The requested content does not exist in the requested resource.
*/
NoContent,
};

} // namespace client
Expand Down
8 changes: 4 additions & 4 deletions olp-cpp-sdk-dataservice-read/src/VersionedLayerClientImpl.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2025 HERE Europe B.V.
* Copyright (C) 2019-2026 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -341,8 +341,8 @@ client::CancellationToken VersionedLayerClientImpl::PrefetchPartitions(
auto download = [=](std::string data_handle,
client::CancellationContext inner_context) mutable {
if (data_handle.empty()) {
return BlobApi::DataResponse(
client::ApiError(client::ErrorCode::NotFound, "Not found"));
return BlobApi::DataResponse(client::ApiError(
client::ErrorCode::NoContent, "Partition is empty"));
}
repository::DataCacheRepository data_cache_repository(catalog_,
settings_.cache);
Expand Down Expand Up @@ -533,7 +533,7 @@ client::CancellationToken VersionedLayerClientImpl::PrefetchTiles(
client::CancellationContext inner_context) mutable {
if (data_handle.empty()) {
return BlobApi::DataResponse(
ApiError(ErrorCode::NotFound, "Not found"));
ApiError(ErrorCode::NoContent, "Partition is empty"));
}

repository::DataCacheRepository cache(catalog_,
Expand Down
6 changes: 3 additions & 3 deletions olp-cpp-sdk-dataservice-read/src/VolatileLayerClientImpl.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2024 HERE Europe B.V.
* Copyright (C) 2019-2026 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -251,8 +251,8 @@ client::CancellationToken VolatileLayerClientImpl::PrefetchTiles(
auto download = [=](std::string data_handle,
client::CancellationContext inner_context) mutable {
if (data_handle.empty()) {
return BlobApi::DataResponse(
client::ApiError(client::ErrorCode::NotFound, "Not found"));
return BlobApi::DataResponse(client::ApiError(
client::ErrorCode::NoContent, "Partition is empty"));
}
repository::DataCacheRepository data_cache_repository(
catalog_, settings_.cache);
Expand Down
10 changes: 6 additions & 4 deletions olp-cpp-sdk-dataservice-read/src/repositories/DataRepository.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2024 HERE Europe B.V.
* Copyright (C) 2019-2026 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -123,8 +123,9 @@ BlobApi::DataResponse DataRepository::GetVersionedData(
catalog_.ToCatalogHRNString().c_str(),
request.CreateKey(layer_id, version).c_str());

return DataResponse(client::ApiError::NotFound("Partition not found"),
network_statistics);
return DataResponse(
client::ApiError(client::ErrorCode::NoContent, "Partition is empty"),
network_statistics);
}

partition = std::move(partitions.front());
Expand Down Expand Up @@ -281,7 +282,8 @@ BlobApi::DataResponse DataRepository::GetVolatileData(
catalog_.ToCatalogHRNString().c_str(),
request.CreateKey(layer_id, olp::porting::none).c_str());

return client::ApiError::NotFound("Partition not found");
return client::ApiError(client::ErrorCode::NoContent,
"Partition is empty");
}

partition = std::move(partitions.front());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2024 HERE Europe B.V.
* Copyright (C) 2019-2026 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@
#include "PartitionsRepository.h"

#include <algorithm>
#include <memory>
#include <utility>

#include <olp/core/cache/KeyGenerator.h>
Expand Down Expand Up @@ -169,6 +170,36 @@ bool CheckAdditionalFields(

return true;
}

model::Partitions CreateNoContentPartitions(
const std::vector<std::string>& partition_ids) {
model::Partitions result;
auto& partitions = result.GetMutablePartitions();
partitions.reserve(partition_ids.size());

std::transform(partition_ids.cbegin(), partition_ids.cend(),
std::back_inserter(partitions),
[&](const std::string& partition_id) {
// Partition can't be empty, see `Partition::GetPartition`.
// Data handle is the only not optional field and actual data
// is requested by the data handle so not setting it is a
// sign that `Partition` object has no corresponding data.
model::Partition partition;
partition.SetPartition(partition_id);
return partition;
});

return result;
}

bool HasNoContent(const model::Partition& partition) {
// There's client code that caches mocked partitions with zero size for not
// existing data so it's better to check all fields.
return partition.GetDataHandle().empty() && !partition.GetDataSize() &&
!partition.GetCompressedDataSize() && !partition.GetChecksum() &&
!partition.GetCrc() && !partition.GetVersion();
}

} // namespace

namespace olp {
Expand Down Expand Up @@ -252,6 +283,16 @@ PartitionsRepository::GetPartitionsExtendedResponse(
OLP_SDK_LOG_TRACE_F(kLogTag,
"GetPartitions found in cache, hrn='%s', key='%s'",
catalog_str.c_str(), key.c_str());

// Clear from cached NoContent partitions
auto& mutable_partitions = cached_partitions->GetMutablePartitions();
mutable_partitions.erase(
std::remove_if(mutable_partitions.begin(), mutable_partitions.end(),
[](const model::Partition& partition) {
return HasNoContent(partition);
}),
mutable_partitions.end());

return *cached_partitions;
} else if (fetch_option == CacheOnly) {
OLP_SDK_LOG_TRACE_F(
Expand Down Expand Up @@ -302,8 +343,13 @@ PartitionsRepository::GetPartitionsExtendedResponse(
OLP_SDK_LOG_TRACE_F(kLogTag,
"GetPartitions put to cache, hrn='%s', key='%s'",
catalog_str.c_str(), key.c_str());

const auto put_result =
cache_.Put(response.GetResult(), version, expiry, is_layer_metadata);
cache_.Put(!response.GetResult().GetPartitions().empty()
? response.GetResult()
: CreateNoContentPartitions(partition_ids),
version, expiry, is_layer_metadata);

if (!put_result.IsSuccessful() && fail_on_cache_error) {
OLP_SDK_LOG_ERROR_F(kLogTag,
"Failed to write data to cache, hrn='%s', key='%s'",
Expand Down Expand Up @@ -356,6 +402,16 @@ PartitionsResponse PartitionsRepository::GetPartitionById(
OLP_SDK_LOG_TRACE_F(kLogTag,
"GetPartitionById found in cache, hrn='%s', key='%s'",
catalog_.ToCatalogHRNString().c_str(), key.c_str());

// Clear from cached NoContent partitions
auto& mutable_partitions = cached_partitions.GetMutablePartitions();
mutable_partitions.erase(
std::remove_if(mutable_partitions.begin(), mutable_partitions.end(),
[](const model::Partition& partition) {
return HasNoContent(partition);
}),
mutable_partitions.end());

return cached_partitions;
} else if (fetch_option == CacheOnly) {
OLP_SDK_LOG_TRACE_F(
Expand Down Expand Up @@ -383,8 +439,13 @@ PartitionsResponse PartitionsRepository::GetPartitionById(
OLP_SDK_LOG_TRACE_F(kLogTag,
"GetPartitionById put to cache, hrn='%s', key='%s'",
catalog_.ToCatalogHRNString().c_str(), key.c_str());

const auto put_result =
cache_.Put(query_response.GetResult(), version, olp::porting::none);
cache_.Put(!query_response.GetResult().GetPartitions().empty()
? query_response.GetResult()
: CreateNoContentPartitions(partitions),
version, olp::porting::none);

if (!put_result.IsSuccessful()) {
OLP_SDK_LOG_ERROR_F(kLogTag,
"GetPartitionById failed to write data to cache, "
Expand Down
150 changes: 146 additions & 4 deletions olp-cpp-sdk-dataservice-read/tests/PartitionsRepositoryTest.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2025 HERE Europe B.V.
* Copyright (C) 2019-2026 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -144,6 +144,10 @@ const std::string kHttpResponseLookupQuery =
const std::string kUrlQueryApi =
R"(https://sab.query.data.api.platform.here.com/query/v1/catalogs/hrn:here:data::olp-here-test:hereos-internal-test-v2)";

const std::string kUrlQueryVersionedPartition =
kUrlQueryApi +
R"(/layers/testlayer/partitions?partition=1111&version=100)";

const std::string kQueryTreeIndexWithAdditionalFields =
R"(https://sab.query.data.api.platform.here.com/query/v1/catalogs/hrn:here:data::olp-here-test:hereos-internal-test-v2/layers/testlayer/versions/100/quadkeys/23064/depths/4?additionalFields=)" +
olp::utils::Url::Encode(R"(checksum,crc,dataSize,compressedDataSize)");
Expand Down Expand Up @@ -592,9 +596,6 @@ TEST_F(PartitionsRepositoryTest, GetPartitionById) {
TEST_F(PartitionsRepositoryTest, GetVersionedPartitions) {
using testing::Return;

std::shared_ptr<cache::KeyValueCache> default_cache =
client::OlpClientSettingsFactory::CreateDefaultCache({});

auto mock_network = std::make_shared<NetworkMock>();
auto cache = std::make_shared<testing::StrictMock<CacheMock>>();
const auto catalog = HRN::FromString(kCatalog);
Expand Down Expand Up @@ -641,6 +642,147 @@ TEST_F(PartitionsRepositoryTest, GetVersionedPartitions) {
ASSERT_FALSE(response.IsSuccessful());
EXPECT_TRUE(response.GetResult().GetPartitions().empty());
}
{
SCOPED_TRACE(
"Succeeds the cache look up when one of the partitions has no content");
OlpClientSettings settings;
settings.cache = cache;
settings.network_request_handler = mock_network;
settings.retry_settings.timeout = 1;

const std::string cache_key_1 =
kCatalog + "::" + kVersionedLayerId + "::" + kPartitionId +
"::" + std::to_string(kVersion) + "::partition";

const std::string cache_key_2 =
kCatalog + "::" + kVersionedLayerId + "::" + kInvalidPartitionId +
"::" + std::to_string(kVersion) + "::partition";

const std::string query_cache_response =
R"jsonString({"version":100,"partition":"1111","layer":"testlayer","dataHandle":"qwerty"})jsonString";

const std::string no_content_cache_response =
R"jsonString({"partition":"2222"})jsonString";

auto response_data = std::make_shared<cache::KeyValueCache::ValueType>(
query_cache_response.begin(), query_cache_response.end());

EXPECT_CALL(*cache, Read(cache_key_1)).WillOnce(Return(response_data));

EXPECT_CALL(*cache, Read(cache_key_2))
.WillOnce(Return(std::make_shared<cache::KeyValueCache::ValueType>(
no_content_cache_response.cbegin(),
no_content_cache_response.cend())));

client::CancellationContext context;
ApiLookupClient lookup_client(catalog, settings);
repository::PartitionsRepository repository(catalog, kVersionedLayerId,
settings, lookup_client);

read::PartitionsRequest request;
request.WithPartitionIds({kPartitionId, kInvalidPartitionId});
request.WithFetchOption(read::CacheOnly);

auto response = repository.GetVersionedPartitionsExtendedResponse(
request, kVersion, context);

ASSERT_TRUE(response.IsSuccessful());
EXPECT_EQ(response.GetResult().GetPartitions().size(), 1);
}
{
SCOPED_TRACE("Cache utilised for not existing partitions");

OlpClientSettings settings;
settings.cache = cache;
settings.network_request_handler = mock_network;
settings.retry_settings.timeout = 1;

const std::string cache_key_1 =
kCatalog + "::" + kVersionedLayerId + "::" + kPartitionId +
"::" + std::to_string(kVersion) + "::partition";

EXPECT_CALL(*cache, Read(cache_key_1))
.WillOnce(Return(client::ApiError::NotFound()));

EXPECT_CALL(*cache, Write(_, _, _)).WillOnce(Return(client::ApiNoResult()));

EXPECT_CALL(*cache, Get(kCacheKeyMetadata, _))
.WillOnce(Return(kUrlQueryApi));

EXPECT_CALL(*mock_network,
Send(IsGetRequest(kUrlQueryVersionedPartition), _, _, _, _))
.WillOnce(ReturnHttpResponse(olp::http::NetworkResponse().WithStatus(
olp::http::HttpStatusCode::OK),
kOlpSdkHttpResponseEmptyPartitionList));

client::CancellationContext context;
ApiLookupClient lookup_client(catalog, settings);
repository::PartitionsRepository repository(catalog, kVersionedLayerId,
settings, lookup_client);

read::PartitionsRequest request;
request.WithPartitionIds({kPartitionId});
request.WithFetchOption(read::OnlineIfNotFound);

auto response = repository.GetVersionedPartitionsExtendedResponse(
request, kVersion, context);

ASSERT_TRUE(response.IsSuccessful());
EXPECT_TRUE(response.GetResult().GetPartitions().empty());
}
{
SCOPED_TRACE("Cache utilised for valid partition");

OlpClientSettings settings;
settings.cache = cache;
settings.network_request_handler = mock_network;
settings.retry_settings.timeout = 1;

const std::string cache_key_1 =
kCatalog + "::" + kVersionedLayerId + "::" + kPartitionId +
"::" + std::to_string(kVersion) + "::partition";

EXPECT_CALL(*cache, Read(cache_key_1))
.WillOnce(Return(client::ApiError::NotFound()));

EXPECT_CALL(*cache, Write(_, _, _))
.Times(4)
.WillRepeatedly(Return(client::ApiNoResult()));

EXPECT_CALL(*cache, Get(kCacheKeyMetadata, _))
.WillOnce(Return(kUrlQueryApi));

EXPECT_CALL(*mock_network,
Send(IsGetRequest(kUrlQueryVersionedPartition), _, _, _, _))
.WillOnce(ReturnHttpResponse(olp::http::NetworkResponse().WithStatus(
olp::http::HttpStatusCode::OK),
kOlpSdkHttpResponsePartitions));

client::CancellationContext context;
ApiLookupClient lookup_client(catalog, settings);
repository::PartitionsRepository repository(catalog, kVersionedLayerId,
settings, lookup_client);

read::PartitionsRequest request;
request.WithPartitionIds({kPartitionId});
request.WithFetchOption(read::OnlineIfNotFound);

auto response = repository.GetVersionedPartitionsExtendedResponse(
request, kVersion, context);

ASSERT_TRUE(response.IsSuccessful());
EXPECT_EQ(response.GetResult().GetPartitions().size(), 4);
}
}

TEST_F(PartitionsRepositoryTest, GetVersionedPartitions_WithCache) {
using testing::Return;

auto mock_network = std::make_shared<NetworkMock>();
const auto catalog = HRN::FromString(kCatalog);
std::shared_ptr<cache::KeyValueCache> default_cache =
client::OlpClientSettingsFactory::CreateDefaultCache({});

{
SCOPED_TRACE("Successful fetch from network with a list of partitions");

Expand Down
Loading
Loading