mrdrivingduck commented on code in PR #205: URL: https://github.com/apache/paimon-cpp/pull/205#discussion_r3804007948
########## src/paimon/fs/oss/oss_file_system_test.cpp: ########## @@ -0,0 +1,64 @@ +/* + * Copyright 2026-present Alibaba Inc. + * + * Licensed 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/fs/oss/oss_file_system.h" + +#include <map> +#include <string> + +#include "gtest/gtest.h" +#include "paimon/fs/oss/oss_file_system_factory.h" + +namespace paimon::oss { + +TEST(OssFileSystemFactoryTest, TestOptionValidation) { + OssFileSystemFactory factory; + std::map<std::string, std::string> options; + ASSERT_FALSE(factory.Create("s3://bucket/key", options).ok()); Review Comment: Done. ########## src/paimon/fs/oss/oss_file_system_factory.cpp: ########## @@ -0,0 +1,156 @@ +/* + * Copyright 2026-present Alibaba Inc. + * + * Licensed 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/fs/oss/oss_file_system_factory.h" + +#include <algorithm> +#include <cctype> +#include <memory> +#include <string> + +#include "alibabacloud/oss2/ClientConfiguration.h" +#include "alibabacloud/oss2/OSSClient.h" +#include "alibabacloud/oss2/credentials/CredentialsProvider.h" +#include "fmt/format.h" +#include "paimon/common/utils/path_util.h" +#include "paimon/factories/factory.h" +#include "paimon/fs/oss/oss_file_system.h" + +namespace paimon::oss { +namespace { + +namespace oss2 = alibabacloud::oss2; + +std::string GetOption(const std::map<std::string, std::string>& options, const std::string& bucket, + const std::string& suffix) { + auto bucket_option = options.find("fs.oss.bucket." + bucket + "." + suffix); + if (bucket_option != options.end()) { + return bucket_option->second; + } + auto global_option = options.find("fs.oss." + suffix); + return global_option == options.end() ? "" : global_option->second; +} + +Result<std::string> GetRequiredOption(const std::map<std::string, std::string>& options, + const std::string& bucket, const std::string& suffix) { + std::string value = GetOption(options, bucket, suffix); + if (value.empty()) { + return Status::Invalid(fmt::format("OSS option 'fs.oss.{}' must not be empty", suffix)); + } + return value; +} + +Result<bool> ParseBool(std::string value, const std::string& option) { Review Comment: Done. ########## src/paimon/fs/oss/oss_file_system.cpp: ########## @@ -0,0 +1,188 @@ +/* + * Copyright 2026-present Alibaba Inc. + * + * Licensed 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/fs/oss/oss_file_system.h" + +#include <curl/curl.h> + +#include <algorithm> +#include <cstdint> +#include <ctime> +#include <memory> +#include <utility> + +#include "alibabacloud/oss2/OSSClient.h" +#include "alibabacloud/oss2/Operation.h" +#include "alibabacloud/oss2/Types.h" +#include "alibabacloud/oss2/io/ByteWriter.h" +#include "alibabacloud/oss2/models/BucketBasic.h" +#include "alibabacloud/oss2/models/ObjectBasic.h" +#include "fmt/format.h" +#include "paimon/executor.h" + +namespace paimon::oss { +namespace { + +namespace oss2 = alibabacloud::oss2; + +bool IsNotFoundError(const oss2::OperationError& error) { + return error.getStatusCode() == 404 || error.getCode() == "NoSuchKey" || + error.getCode() == "NoSuchBucket" || error.getCode() == "NotFound"; +} + +Status ToPaimonStatus(const oss2::OperationError& error, const std::string& operation, + const ObjectStorePath& path) { + std::string message = fmt::format("OSS {} 'oss://{}/{}' failed: code={}, status={}, message={}", + operation, path.bucket, path.key, error.getCode(), + error.getStatusCode(), error.getMessage()); + if (!error.getRequestId().empty()) { + message += fmt::format(", request_id={}", error.getRequestId()); + } + if (IsNotFoundError(error)) { + return Status::NotExist(message); + } + if (error.getCode() == "RequestCanceled") { + return Status::Cancelled(message); + } + return Status::IOError(message); +} + +int64_t ParseTimeMillis(const std::string& value) { + time_t seconds = curl_getdate(value.c_str(), nullptr); + return seconds < 0 ? 0 : static_cast<int64_t>(seconds) * 1000; +} + +class OssObjectStoreClient : public ObjectStoreClient, + public std::enable_shared_from_this<OssObjectStoreClient> { + public: + OssObjectStoreClient(std::string bucket, std::shared_ptr<oss2::OSSClient> client) + : bucket_(std::move(bucket)), client_(std::move(client)) {} + + Result<ObjectMetadata> HeadObject(const ObjectStorePath& path) const override { + PAIMON_RETURN_NOT_OK(ValidateBucket(path)); + oss2::models::HeadObjectRequest request; + request.setBucket(path.bucket).setKey(path.key); + oss2::HeadObjectOutcome outcome = client_->headObject(request); + if (!outcome.has_value()) { + return ToPaimonStatus(outcome.error(), "HeadObject", path); + } + const oss2::models::HeadObjectResult& result = outcome.value(); + if (result.getContentLength() < 0) { + return Status::IOError("OSS HeadObject response is missing Content-Length"); + } + return ObjectMetadata{path.key, result.getContentLength(), + ParseTimeMillis(result.getLastModified())}; + } + + Result<ListObjectsResult> ListObjects(const ObjectStorePath& path, + const std::string& continuation_token, + int max_keys) const override { Review Comment: Done. ########## src/paimon/fs/oss/oss_file_system.cpp: ########## @@ -0,0 +1,188 @@ +/* + * Copyright 2026-present Alibaba Inc. + * + * Licensed 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/fs/oss/oss_file_system.h" + +#include <curl/curl.h> + +#include <algorithm> +#include <cstdint> +#include <ctime> +#include <memory> +#include <utility> + +#include "alibabacloud/oss2/OSSClient.h" +#include "alibabacloud/oss2/Operation.h" +#include "alibabacloud/oss2/Types.h" +#include "alibabacloud/oss2/io/ByteWriter.h" +#include "alibabacloud/oss2/models/BucketBasic.h" +#include "alibabacloud/oss2/models/ObjectBasic.h" +#include "fmt/format.h" +#include "paimon/executor.h" + +namespace paimon::oss { +namespace { + +namespace oss2 = alibabacloud::oss2; + +bool IsNotFoundError(const oss2::OperationError& error) { + return error.getStatusCode() == 404 || error.getCode() == "NoSuchKey" || + error.getCode() == "NoSuchBucket" || error.getCode() == "NotFound"; +} + +Status ToPaimonStatus(const oss2::OperationError& error, const std::string& operation, + const ObjectStorePath& path) { + std::string message = fmt::format("OSS {} 'oss://{}/{}' failed: code={}, status={}, message={}", + operation, path.bucket, path.key, error.getCode(), + error.getStatusCode(), error.getMessage()); + if (!error.getRequestId().empty()) { + message += fmt::format(", request_id={}", error.getRequestId()); + } + if (IsNotFoundError(error)) { + return Status::NotExist(message); + } + if (error.getCode() == "RequestCanceled") { + return Status::Cancelled(message); + } + return Status::IOError(message); +} + +int64_t ParseTimeMillis(const std::string& value) { + time_t seconds = curl_getdate(value.c_str(), nullptr); + return seconds < 0 ? 0 : static_cast<int64_t>(seconds) * 1000; +} + +class OssObjectStoreClient : public ObjectStoreClient, + public std::enable_shared_from_this<OssObjectStoreClient> { + public: + OssObjectStoreClient(std::string bucket, std::shared_ptr<oss2::OSSClient> client) + : bucket_(std::move(bucket)), client_(std::move(client)) {} + + Result<ObjectMetadata> HeadObject(const ObjectStorePath& path) const override { + PAIMON_RETURN_NOT_OK(ValidateBucket(path)); + oss2::models::HeadObjectRequest request; + request.setBucket(path.bucket).setKey(path.key); + oss2::HeadObjectOutcome outcome = client_->headObject(request); + if (!outcome.has_value()) { + return ToPaimonStatus(outcome.error(), "HeadObject", path); + } + const oss2::models::HeadObjectResult& result = outcome.value(); + if (result.getContentLength() < 0) { + return Status::IOError("OSS HeadObject response is missing Content-Length"); + } + return ObjectMetadata{path.key, result.getContentLength(), + ParseTimeMillis(result.getLastModified())}; + } + + Result<ListObjectsResult> ListObjects(const ObjectStorePath& path, + const std::string& continuation_token, + int max_keys) const override { + PAIMON_RETURN_NOT_OK(ValidateBucket(path)); + oss2::models::ListObjectsV2Request request; + request.setBucket(path.bucket).setPrefix(path.key).setDelimiter("/"); + if (!continuation_token.empty()) { + request.setContinuationToken(continuation_token); + } + if (max_keys > 0) { + request.setMaxKeys(std::min(max_keys, 999)); Review Comment: Done. ########## cmake_modules/ThirdpartyToolchain.cmake: ########## @@ -1359,6 +1373,106 @@ macro(build_jindosdk_nextarch) add_dependencies(jindosdk::nextarch jindosdk-nextarch_ep) endmacro() +macro(build_oss_sdk_v2) + message(STATUS "Building Alibaba Cloud OSS C++ SDK v2 from source") + find_package(CURL REQUIRED) + find_package(Threads REQUIRED) + + set(OSS_SDK_V2_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/oss_sdk_v2_ep-install") + set(OSS_SDK_V2_INCLUDE_DIR "${OSS_SDK_V2_PREFIX}/include") + set(OSS_SDK_V2_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}") + set(OSS_SDK_V2_LIB_DIR "${OSS_SDK_V2_PREFIX}/${OSS_SDK_V2_INSTALL_LIBDIR}") + set(OSS_SDK_V2_STATIC_LIB + "${OSS_SDK_V2_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}alibabacloud-oss-cpp-sdk-v2${CMAKE_STATIC_LIBRARY_SUFFIX}" + ) + + set(OSS_SDK_V2_CMAKE_CXX_FLAGS "${EP_CXX_FLAGS} -Wno-error") + set(OSS_SDK_V2_CMAKE_C_FLAGS "${EP_C_FLAGS} -Wno-error") + string(REPLACE "-Werror" "" OSS_SDK_V2_CMAKE_CXX_FLAGS ${OSS_SDK_V2_CMAKE_CXX_FLAGS}) Review Comment: Done. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
