This is an automated email from the ASF dual-hosted git repository.

lxy-9602 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 19cba8d1 feat(fs): add OSS file system (#205)
19cba8d1 is described below

commit 19cba8d1212bcb6f5b5d9cd605fe893c42e8a6a6
Author: Mr Dk. <[email protected]>
AuthorDate: Sat Aug 29 15:41:15 2026 +0800

    feat(fs): add OSS file system (#205)
---
 CMakeLists.txt                                     |  18 +-
 README.md                                          |   2 +-
 ci/scripts/build_paimon.sh                         |   1 +
 cmake_modules/DefineOptions.cmake                  |   5 +
 cmake_modules/ThirdpartyToolchain.cmake            | 119 ++++++++
 docs/source/build_system.rst                       |   2 +
 docs/source/building.rst                           |   2 +
 src/paimon/CMakeLists.txt                          |   4 +-
 src/paimon/common/fs/object_store_file_system.cpp  |   1 +
 src/paimon/common/fs/object_store_file_system.h    |  72 ++++-
 .../common/fs/object_store_file_system_test.cpp    |  11 +
 src/paimon/fs/oss/CMakeLists.txt                   |  50 ++++
 src/paimon/fs/oss/oss_file_system.cpp              | 192 ++++++++++++
 src/paimon/fs/oss/oss_file_system.h                |  51 ++++
 src/paimon/fs/oss/oss_file_system_factory.cpp      | 234 +++++++++++++++
 src/paimon/fs/oss/oss_file_system_factory.h        |  38 +++
 src/paimon/fs/oss/oss_file_system_test.cpp         | 327 +++++++++++++++++++++
 src/paimon/fs/s3/s3_file_system.cpp                |  13 +-
 src/paimon/fs/s3/s3_file_system_test.cpp           |   6 +-
 third_party/versions.txt                           |   5 +
 20 files changed, 1136 insertions(+), 17 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 46a55426..2fe2df8e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -62,6 +62,7 @@ option(PAIMON_ENABLE_AVRO "Whether to enable avro file 
format" ON)
 option(PAIMON_ENABLE_ORC "Whether to enable orc file format" ON)
 option(PAIMON_ENABLE_MOSAIC "Whether to enable mosaic file format (Rust FFI)" 
OFF)
 option(PAIMON_ENABLE_JINDO "Whether to enable jindo file system" OFF)
+option(PAIMON_ENABLE_OSS "Whether to enable OSS SDK v2 file system" OFF)
 option(PAIMON_ENABLE_S3 "Whether to enable S3 file system" OFF)
 option(PAIMON_ENABLE_NETWORK_TESTS
        "Whether to enable tests that access real remote services over the 
network" OFF)
@@ -77,8 +78,10 @@ endif()
 if(PAIMON_ENABLE_REST)
     add_definitions(-DPAIMON_ENABLE_REST)
 endif()
-# libcurl backs the HTTP client shared by the S3 file system and the rest 
catalog.
-if(PAIMON_ENABLE_S3 OR PAIMON_ENABLE_REST)
+# libcurl supports the REST and S3 HTTP clients, the OSS SDK transport, and 
object store timestamps.
+if(PAIMON_ENABLE_OSS
+   OR PAIMON_ENABLE_S3
+   OR PAIMON_ENABLE_REST)
     find_package(CURL REQUIRED)
 endif()
 if(PAIMON_ENABLE_REST)
@@ -93,6 +96,9 @@ endif()
 if(PAIMON_ENABLE_JINDO)
     add_definitions(-DPAIMON_ENABLE_JINDO)
 endif()
+if(PAIMON_ENABLE_OSS)
+    add_definitions(-DPAIMON_ENABLE_OSS)
+endif()
 if(PAIMON_ENABLE_S3)
     add_definitions(-DPAIMON_ENABLE_S3)
 endif()
@@ -473,6 +479,13 @@ if(PAIMON_BUILD_TESTS)
                                            paimon_jindo_file_system_shared)
         list(APPEND TEST_STATIC_LINK_LIBS ${TEST_PLUGIN_LINK_LIBS})
     endif()
+    if(PAIMON_ENABLE_OSS)
+        
paimon_link_libraries_whole_archive(PAIMON_OSS_FILE_SYSTEM_STATIC_LINK_LIBS
+                                            paimon_oss_file_system_static)
+        paimon_link_libraries_no_as_needed(TEST_PLUGIN_LINK_LIBS
+                                           paimon_oss_file_system_shared)
+        list(APPEND TEST_STATIC_LINK_LIBS ${TEST_PLUGIN_LINK_LIBS})
+    endif()
     if(PAIMON_ENABLE_S3)
         
paimon_link_libraries_whole_archive(PAIMON_S3_FILE_SYSTEM_STATIC_LINK_LIBS
                                             paimon_s3_file_system_static)
@@ -532,6 +545,7 @@ add_subdirectory(src/paimon/fs/local)
 if(PAIMON_ENABLE_JINDO)
     add_subdirectory(src/paimon/fs/jindo)
 endif()
+add_subdirectory(src/paimon/fs/oss)
 add_subdirectory(src/paimon/fs/s3)
 add_subdirectory(src/paimon/format/blob)
 add_subdirectory(src/paimon/format/orc)
diff --git a/README.md b/README.md
index 00a391c0..da928e71 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ Paimon C++ currently provides:
 - **Scan**: batch and stream scan for append tables and primary key tables 
without changelog.
 - **Read**: append table read, primary key table read with deletion vector, 
and primary key table merge-on-read.
 - **Arrow integration**: batch read and write interfaces based on the [Arrow 
Columnar In-Memory Format](https://arrow.apache.org).
-- **File systems**: file system abstraction with built-in local and Jindo file 
system support.
+- **File systems**: file system abstraction with built-in local, Jindo, OSS, 
and S3 file system support.
 - **File formats**: file format abstraction with built-in ORC, Parquet, and 
Avro support.
 - **Runtime utilities**: memory pool and thread pool abstractions with default 
implementations.
 - **AI-Oriented Features**: supports RowTracking and DataEvolution mode and 
provides Global Index
diff --git a/ci/scripts/build_paimon.sh b/ci/scripts/build_paimon.sh
index 58ed9b0a..cea59da8 100755
--- a/ci/scripts/build_paimon.sh
+++ b/ci/scripts/build_paimon.sh
@@ -149,6 +149,7 @@ CMAKE_ARGS=(
     "-DPAIMON_BUILD_TESTS=ON"
     "-DPAIMON_ENABLE_MOSAIC=ON"
     "-DPAIMON_ENABLE_JINDO=ON"
+    "-DPAIMON_ENABLE_OSS=ON"
     "-DPAIMON_ENABLE_S3=ON"
     "-DPAIMON_ENABLE_LUMINA=${ENABLE_LUMINA}"
     "-DPAIMON_ENABLE_LUCENE=ON"
diff --git a/cmake_modules/DefineOptions.cmake 
b/cmake_modules/DefineOptions.cmake
index 21f65315..9f099208 100644
--- a/cmake_modules/DefineOptions.cmake
+++ b/cmake_modules/DefineOptions.cmake
@@ -219,6 +219,11 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL 
"${CMAKE_CURRENT_SOURCE_DIR}")
                          ""
                          AUTO
                          BUNDLED)
+    define_option_string(OSS_SDK_V2_SOURCE
+                         "Dependency source for OSS SDK v2; SYSTEM is 
unsupported"
+                         ""
+                         AUTO
+                         BUNDLED)
     define_option_string(fmt_SOURCE
                          "Dependency source for fmt"
                          ""
diff --git a/cmake_modules/ThirdpartyToolchain.cmake 
b/cmake_modules/ThirdpartyToolchain.cmake
index a35ecd49..0c64819c 100644
--- a/cmake_modules/ThirdpartyToolchain.cmake
+++ b/cmake_modules/ThirdpartyToolchain.cmake
@@ -320,6 +320,16 @@ else()
     endif()
 endif()
 
+if(DEFINED ENV{PAIMON_OSS_SDK_V2_URL})
+    set(OSS_SDK_V2_SOURCE_URL "$ENV{PAIMON_OSS_SDK_V2_URL}")
+elseif(EXISTS "${THIRDPARTY_DIR}/${PAIMON_OSS_SDK_V2_PKG_NAME}")
+    set_urls(OSS_SDK_V2_SOURCE_URL 
"${THIRDPARTY_DIR}/${PAIMON_OSS_SDK_V2_PKG_NAME}")
+else()
+    set_urls(OSS_SDK_V2_SOURCE_URL
+             
"${THIRDPARTY_MIRROR_URL}https://github.com/aliyun/alibabacloud-oss-cpp-sdk-v2/archive/refs/tags/${PAIMON_OSS_SDK_V2_BUILD_VERSION}.tar.gz";
+    )
+endif()
+
 if(DEFINED ENV{PAIMON_LUMINA_URL})
     set(LUMINA_SOURCE_URL "$ENV{PAIMON_LUMINA_URL}")
 elseif(EXISTS "${THIRDPARTY_DIR}/${PAIMON_LUMINA_PKG_NAME}")
@@ -504,6 +514,25 @@ function(paimon_enforce_patched_dependency_policy)
                 PARENT_SCOPE)
         endif()
     endif()
+
+    if(PAIMON_ENABLE_OSS)
+        paimon_set_dependency_source_default(
+            OSS_SDK_V2 BUNDLED "OSS SDK v2 is only supported as a bundled 
dependency")
+        paimon_get_dependency_source(OSS_SDK_V2 _oss_sdk_v2_source)
+        if(_oss_sdk_v2_source STREQUAL "SYSTEM")
+            message(FATAL_ERROR "OSS_SDK_V2_SOURCE=SYSTEM is not supported. "
+                                "Use OSS_SDK_V2_SOURCE=BUNDLED.")
+        elseif(_oss_sdk_v2_source STREQUAL "AUTO")
+            message(STATUS "Forcing OSS_SDK_V2_SOURCE to BUNDLED because 
paimon-cpp "
+                           "only supports the bundled OSS SDK v2")
+            set(OSS_SDK_V2_SOURCE
+                "BUNDLED"
+                CACHE STRING "Dependency source for OSS SDK v2" FORCE)
+            set(OSS_SDK_V2_SOURCE
+                "BUNDLED"
+                PARENT_SCOPE)
+        endif()
+    endif()
 endfunction()
 
 function(paimon_apply_dependency_source_defaults)
@@ -609,6 +638,8 @@ function(paimon_get_dependency_compat_target 
DEPENDENCY_NAME OUT_VAR)
         set(_target tbb)
     elseif("${DEPENDENCY_NAME}" STREQUAL "Avro")
         set(_target avro)
+    elseif("${DEPENDENCY_NAME}" STREQUAL "OSS_SDK_V2")
+        set(_target alibabacloud_oss_v2::oss)
     else()
         set(_target "${DEPENDENCY_NAME}")
     endif()
@@ -683,6 +714,8 @@ macro(paimon_build_dependency DEPENDENCY_NAME)
         build_glog()
     elseif("${DEPENDENCY_NAME}" STREQUAL "Avro")
         build_avro()
+    elseif("${DEPENDENCY_NAME}" STREQUAL "OSS_SDK_V2")
+        build_oss_sdk_v2()
     elseif("${DEPENDENCY_NAME}" STREQUAL "GTest")
         build_gtest()
     elseif("${DEPENDENCY_NAME}" STREQUAL "benchmark")
@@ -1415,6 +1448,89 @@ 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_ARGS
+        ${EP_COMMON_CMAKE_ARGS}
+        "-DCMAKE_INSTALL_PREFIX=${OSS_SDK_V2_PREFIX}"
+        "-DCMAKE_INSTALL_LIBDIR=${OSS_SDK_V2_INSTALL_LIBDIR}"
+        -DCMAKE_PARENT_CXX_STANDARD=17
+        -DBUILD_SHARED_LIBS=OFF
+        -DBUILD_TESTS=OFF
+        -DBUILD_SAMPLES=OFF
+        -DENABLE_RTTI=OFF
+        -DUSE_CURL_TRANSPORT=ON
+        -DUSE_SYSTEM_CURL=ON
+        -DUSE_SYSTEM_OPENSSL=OFF
+        -DUSE_SYSTEM_MBEDTLS=OFF
+        -DUSE_SYSTEM_TINYXML2=OFF
+        -DUSE_STD_EXPECTED=OFF
+        -DENABLE_ENCRYPTION=OFF)
+    set(OSS_SDK_V2_CURL_INCLUDE_DIR "${CURL_INCLUDE_DIR}")
+    if(NOT OSS_SDK_V2_CURL_INCLUDE_DIR AND CURL_INCLUDE_DIRS)
+        list(GET CURL_INCLUDE_DIRS 0 OSS_SDK_V2_CURL_INCLUDE_DIR)
+    endif()
+    set(OSS_SDK_V2_CURL_LIBRARY "${CURL_LIBRARY_RELEASE}")
+    if(NOT OSS_SDK_V2_CURL_LIBRARY)
+        set(OSS_SDK_V2_CURL_LIBRARY "${CURL_LIBRARY}")
+    endif()
+    if(TARGET CURL::libcurl)
+        if(NOT OSS_SDK_V2_CURL_INCLUDE_DIR)
+            get_target_property(OSS_SDK_V2_CURL_INCLUDE_DIR CURL::libcurl
+                                INTERFACE_INCLUDE_DIRECTORIES)
+        endif()
+        if(NOT OSS_SDK_V2_CURL_LIBRARY)
+            foreach(CURL_CONFIG RELEASE RELWITHDEBINFO DEBUG NOCONFIG)
+                get_target_property(OSS_SDK_V2_CURL_LIBRARY CURL::libcurl
+                                    "IMPORTED_LOCATION_${CURL_CONFIG}")
+                if(OSS_SDK_V2_CURL_LIBRARY)
+                    break()
+                endif()
+            endforeach()
+        endif()
+        if(NOT OSS_SDK_V2_CURL_LIBRARY)
+            get_target_property(OSS_SDK_V2_CURL_LIBRARY CURL::libcurl 
IMPORTED_LOCATION)
+        endif()
+    endif()
+    if(OSS_SDK_V2_CURL_INCLUDE_DIR AND OSS_SDK_V2_CURL_LIBRARY)
+        list(APPEND
+             OSS_SDK_V2_CMAKE_ARGS
+             "-DCURL_INCLUDE_DIR=${OSS_SDK_V2_CURL_INCLUDE_DIR}"
+             "-DCURL_LIBRARY=${OSS_SDK_V2_CURL_LIBRARY}"
+             "-DCURL_LIBRARY_RELEASE=${OSS_SDK_V2_CURL_LIBRARY}")
+    endif()
+
+    externalproject_add(oss_sdk_v2_ep
+                        ${EP_COMMON_OPTIONS}
+                        URL ${OSS_SDK_V2_SOURCE_URL}
+                        URL_HASH 
"SHA256=${PAIMON_OSS_SDK_V2_BUILD_SHA256_CHECKSUM}"
+                        CMAKE_ARGS ${OSS_SDK_V2_CMAKE_ARGS} 
${THIRDPARTY_LOG_OPTIONS}
+                        BUILD_BYPRODUCTS "${OSS_SDK_V2_STATIC_LIB}")
+
+    file(MAKE_DIRECTORY "${OSS_SDK_V2_INCLUDE_DIR}")
+    file(MAKE_DIRECTORY "${OSS_SDK_V2_LIB_DIR}")
+
+    add_library(alibabacloud_oss_v2::oss STATIC IMPORTED)
+    set_target_properties(alibabacloud_oss_v2::oss
+                          PROPERTIES IMPORTED_LOCATION 
"${OSS_SDK_V2_STATIC_LIB}"
+                                     INTERFACE_INCLUDE_DIRECTORIES
+                                     "${OSS_SDK_V2_INCLUDE_DIR}")
+    target_link_libraries(alibabacloud_oss_v2::oss INTERFACE CURL::libcurl
+                                                             Threads::Threads)
+    add_dependencies(alibabacloud_oss_v2::oss oss_sdk_v2_ep)
+endmacro()
+
 macro(build_protobuf)
     message(STATUS "Building protobuf from source")
     set(PROTOBUF_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/protobuf_ep-install")
@@ -2026,6 +2142,9 @@ if(PAIMON_ENABLE_JINDO)
     build_jindosdk_c()
     build_jindosdk_nextarch()
 endif()
+if(PAIMON_ENABLE_OSS)
+    resolve_dependency(OSS_SDK_V2)
+endif()
 if(PAIMON_ENABLE_S3)
     include(BuildAwsAuth)
     build_aws_auth()
diff --git a/docs/source/build_system.rst b/docs/source/build_system.rst
index a2a1a9e7..40964f6f 100644
--- a/docs/source/build_system.rst
+++ b/docs/source/build_system.rst
@@ -106,6 +106,8 @@ Paimon provides a set of built-in optional plugins that you 
can link to as neede
 
   - ``Paimon::paimon_local_file_system_shared``
   - ``Paimon::paimon_jindo_file_system_shared``
+  - ``Paimon::paimon_oss_file_system_shared``
+  - ``Paimon::paimon_s3_file_system_shared``
 
 - Index plugins:
 
diff --git a/docs/source/building.rst b/docs/source/building.rst
index 32c7e67c..a057ff53 100644
--- a/docs/source/building.rst
+++ b/docs/source/building.rst
@@ -178,6 +178,8 @@ boolean flags to ``cmake``.
 * ``-DPAIMON_ENABLE_ORC=ON``: Paimon integration with Apache ORC
 * ``-DPAIMON_ENABLE_AVRO=ON``: Apache Avro libraries and Paimon integration
 * ``-DPAIMON_ENABLE_JINDO=ON``: Support for Alibaba Jindo filesystems
+* ``-DPAIMON_ENABLE_OSS=ON``: Support for Alibaba Cloud OSS through OSS SDK V2
+* ``-DPAIMON_ENABLE_S3=ON``: Support for Amazon S3-compatible filesystems
 * ``-DPAIMON_ENABLE_LUMINA=ON``: Support for the Lumina vector index. Requires
   Linux ``x86_64``; see :ref:`cpp-building-platforms`.
 * ``-DPAIMON_ENABLE_LUCENE=ON``: Support for Lucene full-text search indexes
diff --git a/src/paimon/CMakeLists.txt b/src/paimon/CMakeLists.txt
index 3de2b667..0ec23a0a 100644
--- a/src/paimon/CMakeLists.txt
+++ b/src/paimon/CMakeLists.txt
@@ -201,7 +201,7 @@ set(PAIMON_REST_LINK_LIBS)
 if(PAIMON_ENABLE_REST)
     set(PAIMON_REST_LINK_LIBS OpenSSL::Crypto)
 endif()
-if(PAIMON_ENABLE_S3)
+if(PAIMON_ENABLE_OSS OR PAIMON_ENABLE_S3)
     list(APPEND PAIMON_COMMON_SRCS common/fs/object_store_file_system.cpp)
 endif()
 
@@ -948,7 +948,7 @@ if(PAIMON_BUILD_TESTS)
     endif()
 
     set(PAIMON_OBJECT_STORE_FS_TEST_SOURCES)
-    if(PAIMON_ENABLE_S3)
+    if(PAIMON_ENABLE_OSS OR PAIMON_ENABLE_S3)
         list(APPEND PAIMON_OBJECT_STORE_FS_TEST_SOURCES
              common/fs/object_store_file_system_test.cpp)
     endif()
diff --git a/src/paimon/common/fs/object_store_file_system.cpp 
b/src/paimon/common/fs/object_store_file_system.cpp
index ae44835e..26cbe2aa 100644
--- a/src/paimon/common/fs/object_store_file_system.cpp
+++ b/src/paimon/common/fs/object_store_file_system.cpp
@@ -30,6 +30,7 @@
 #include "paimon/common/utils/path_util.h"
 
 namespace paimon {
+
 namespace {
 
 constexpr int64_t kMaxReadAheadMemory = 64LL * 1024LL * 1024LL;
diff --git a/src/paimon/common/fs/object_store_file_system.h 
b/src/paimon/common/fs/object_store_file_system.h
index a1313b85..a40ef15c 100644
--- a/src/paimon/common/fs/object_store_file_system.h
+++ b/src/paimon/common/fs/object_store_file_system.h
@@ -19,7 +19,12 @@
 
 #pragma once
 
+#include <curl/curl.h>
+
+#include <cctype>
+#include <cerrno>
 #include <cstdint>
+#include <ctime>
 #include <functional>
 #include <memory>
 #include <mutex>
@@ -31,6 +36,71 @@
 
 namespace paimon {
 
+class ObjectStoreFileSystemUtils {
+ public:
+    static inline int64_t ParseModificationTime(const std::string& value) {
+        std::tm parsed_time{};
+        const char* current = strptime(value.c_str(), "%Y-%m-%dT%H:%M:%S", 
&parsed_time);
+        if (current != nullptr) {
+            int32_t milliseconds = 0;
+            int32_t fraction_digits = 0;
+            if (*current == '.') {
+                ++current;
+                const char* fraction_begin = current;
+                while (std::isdigit(static_cast<unsigned char>(*current))) {
+                    if (fraction_digits < 3) {
+                        milliseconds = milliseconds * 10 + (*current - '0');
+                    }
+                    ++fraction_digits;
+                    ++current;
+                }
+                if (current == fraction_begin) {
+                    current = nullptr;
+                }
+                while (fraction_digits < 3) {
+                    milliseconds *= 10;
+                    ++fraction_digits;
+                }
+            }
+
+            int32_t timezone_offset_seconds = 0;
+            bool valid_timezone = false;
+            if (current != nullptr && *current == 'Z' && current[1] == '\0') {
+                valid_timezone = true;
+            } else if (current != nullptr && (*current == '+' || *current == 
'-') &&
+                       std::isdigit(static_cast<unsigned char>(current[1])) &&
+                       std::isdigit(static_cast<unsigned char>(current[2])) && 
current[3] == ':' &&
+                       std::isdigit(static_cast<unsigned char>(current[4])) &&
+                       std::isdigit(static_cast<unsigned char>(current[5])) && 
current[6] == '\0') {
+                int32_t hours = (current[1] - '0') * 10 + current[2] - '0';
+                int32_t minutes = (current[4] - '0') * 10 + current[5] - '0';
+                if (hours <= 23 && minutes <= 59) {
+                    timezone_offset_seconds = (hours * 60 + minutes) * 60;
+                    if (*current == '-') {
+                        timezone_offset_seconds = -timezone_offset_seconds;
+                    }
+                    valid_timezone = true;
+                }
+            }
+
+            if (valid_timezone) {
+                errno = 0;
+                time_t seconds = timegm(&parsed_time);
+                if (seconds != static_cast<time_t>(-1) || errno != EOVERFLOW) {
+                    int64_t utc_seconds = static_cast<int64_t>(seconds) - 
timezone_offset_seconds;
+                    return utc_seconds * 1000 + milliseconds;
+                }
+            }
+        }
+
+        time_t seconds = curl_getdate(value.c_str(), nullptr);
+        if (seconds == static_cast<time_t>(-1)) {
+            return FileStatus::kUnknownModificationTime;
+        }
+        return static_cast<int64_t>(seconds) * 1000;
+    }
+};
+
 struct ObjectStorePath {
     std::string bucket;
     std::string key;
@@ -39,7 +109,7 @@ struct ObjectStorePath {
 struct ObjectMetadata {
     std::string key;
     int64_t size = 0;
-    int64_t modification_time = 0;
+    int64_t modification_time = FileStatus::kUnknownModificationTime;
 };
 
 struct ListObjectsResult {
diff --git a/src/paimon/common/fs/object_store_file_system_test.cpp 
b/src/paimon/common/fs/object_store_file_system_test.cpp
index 824060f8..6b8b5650 100644
--- a/src/paimon/common/fs/object_store_file_system_test.cpp
+++ b/src/paimon/common/fs/object_store_file_system_test.cpp
@@ -33,6 +33,17 @@
 namespace paimon::test {
 namespace {
 
+TEST(ObjectStoreFileSystemTest, TestParseModificationTime) {
+    ASSERT_EQ(1704067200000,
+              
ObjectStoreFileSystemUtils::ParseModificationTime("2024-01-01T00:00:00.000Z"));
+    ASSERT_EQ(1704067200123,
+              
ObjectStoreFileSystemUtils::ParseModificationTime("2024-01-01T08:00:00.123+08:00"));
+    ASSERT_EQ(1704067200000,
+              ObjectStoreFileSystemUtils::ParseModificationTime("Mon, 01 Jan 
2024 00:00:00 GMT"));
+    ASSERT_EQ(FileStatus::kUnknownModificationTime,
+              
ObjectStoreFileSystemUtils::ParseModificationTime("not-a-timestamp"));
+}
+
 using Range = std::pair<int64_t, int64_t>;
 
 class MockObjectStoreClient : public ObjectStoreClient {
diff --git a/src/paimon/fs/oss/CMakeLists.txt b/src/paimon/fs/oss/CMakeLists.txt
new file mode 100644
index 00000000..fb0ea674
--- /dev/null
+++ b/src/paimon/fs/oss/CMakeLists.txt
@@ -0,0 +1,50 @@
+# 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.
+
+if(PAIMON_ENABLE_OSS)
+    add_paimon_lib(paimon_oss_file_system
+                   SOURCES
+                   oss_file_system.cpp
+                   oss_file_system_factory.cpp
+                   EXTRA_INCLUDES
+                   ${OSS_SDK_V2_INCLUDE_DIR}
+                   DEPENDENCIES
+                   paimon_shared
+                   CURL::libcurl
+                   STATIC_LINK_LIBS
+                   alibabacloud_oss_v2::oss
+                   fmt
+                   SHARED_LINK_LIBS
+                   paimon_shared
+                   SHARED_LINK_FLAGS
+                   ${PAIMON_VERSION_SCRIPT_FLAGS})
+
+    add_dependencies(paimon_oss_file_system_objlib oss_sdk_v2_ep)
+
+    if(PAIMON_BUILD_TESTS)
+        add_paimon_test(oss_file_system_test
+                        SOURCES
+                        oss_file_system_test.cpp
+                        EXTRA_INCLUDES
+                        ${OSS_SDK_V2_INCLUDE_DIR}
+                        STATIC_LINK_LIBS
+                        paimon_shared
+                        test_utils_static
+                        ${PAIMON_OSS_FILE_SYSTEM_STATIC_LINK_LIBS}
+                        ${GTEST_LINK_TOOLCHAIN})
+    endif()
+endif()
diff --git a/src/paimon/fs/oss/oss_file_system.cpp 
b/src/paimon/fs/oss/oss_file_system.cpp
new file mode 100644
index 00000000..bc283cc8
--- /dev/null
+++ b/src/paimon/fs/oss/oss_file_system.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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/fs/oss/oss_file_system.h"
+
+#include <algorithm>
+#include <cstdint>
+#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;
+
+constexpr int32_t kMaxKeysPerRequest = 1000;
+
+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);
+}
+
+class OssObjectStoreClient : public ObjectStoreClient,
+                             public 
std::enable_shared_from_this<OssObjectStoreClient> {
+ public:
+    OssObjectStoreClient(std::string bucket, std::shared_ptr<oss2::OSSClient> 
client,
+                         std::unique_ptr<Executor> executor)
+        : bucket_(std::move(bucket)), client_(std::move(client)), 
executor_(std::move(executor)) {}
+
+    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(),
+            
ObjectStoreFileSystemUtils::ParseModificationTime(result.getLastModified())};
+    }
+
+    Result<ListObjectsResult> ListObjects(const ObjectStorePath& path,
+                                          const std::string& 
continuation_token,
+                                          int32_t 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) {
+            // OSS limits ListObjectsV2 requests to 1000 keys.
+            request.setMaxKeys(std::min(max_keys, kMaxKeysPerRequest));
+        }
+        oss2::ListObjectsV2Outcome outcome = client_->listObjectsV2(request);
+        if (!outcome.has_value()) {
+            return ToPaimonStatus(outcome.error(), "ListObjectsV2", path);
+        }
+        const oss2::models::ListObjectsV2Result& value = outcome.value();
+        ListObjectsResult result;
+        result.objects.reserve(value.getContents().size());
+        for (const oss2::models::ObjectSummary& object : value.getContents()) {
+            result.objects.push_back(ObjectMetadata{
+                object.key, object.size,
+                
ObjectStoreFileSystemUtils::ParseModificationTime(object.lastModified)});
+        }
+        result.common_prefixes.reserve(value.getCommonPrefixes().size());
+        for (const oss2::models::CommonPrefix& prefix : 
value.getCommonPrefixes()) {
+            result.common_prefixes.push_back(prefix.prefix);
+        }
+        result.is_truncated = value.getIsTruncated();
+        result.continuation_token = value.getNextContinuationToken();
+        return result;
+    }
+
+    Result<int64_t> GetObjectRange(const ObjectStorePath& path, int64_t 
offset, int64_t size,
+                                   char* buffer) const override {
+        PAIMON_RETURN_NOT_OK(ValidateBucket(path));
+        if (size == 0) {
+            return 0;
+        }
+        auto writer = std::make_shared<std::shared_ptr<oss2::MemoryWriter>>();
+        oss2::SinkFactory sink;
+        sink.isOneShot = false;
+        sink.supplier = [buffer, size, writer](int64_t, const 
oss2::HeaderCollection&) {
+            auto memory_writer = std::make_shared<oss2::MemoryWriter>(
+                reinterpret_cast<uint8_t*>(buffer), static_cast<size_t>(size));
+            *writer = memory_writer;
+            return memory_writer;
+        };
+        oss2::models::GetObjectRequest request;
+        request.setBucket(path.bucket)
+            .setKey(path.key)
+            .setRange(fmt::format("bytes={}-{}", offset, offset + size - 1))
+            .setRangeBehavior("standard")
+            .setSinkFactory(std::move(sink));
+        oss2::GetObjectOutcome outcome = client_->getObject(request);
+        if (!outcome.has_value()) {
+            return ToPaimonStatus(outcome.error(), "GetObject", path);
+        }
+        int64_t written =
+            *writer ? static_cast<int64_t>((*writer)->written()) : 
static_cast<int64_t>(0);
+        if (written != size) {
+            return Status::IOError(
+                fmt::format("OSS GetObject read {} bytes for oss://{}/{}, 
expected {}", written,
+                            path.bucket, path.key, size));
+        }
+        return written;
+    }
+
+    void GetObjectRangeAsync(const ObjectStorePath& path, int64_t offset, 
int64_t size,
+                             char* buffer, std::function<void(Status)>&& 
callback) const override {
+        std::shared_ptr<const OssObjectStoreClient> self = shared_from_this();
+        executor_->Add([self = std::move(self), path, offset, size, buffer,
+                        callback = std::move(callback)]() mutable {
+            Result<int64_t> result = self->GetObjectRange(path, offset, size, 
buffer);
+            callback(result.ok() ? Status::OK() : result.status());
+        });
+    }
+
+ private:
+    Status ValidateBucket(const ObjectStorePath& path) const {
+        if (path.bucket != bucket_) {
+            return Status::Invalid(
+                fmt::format("OSS file system for bucket '{}' cannot access "
+                            "'oss://{}/{}'",
+                            bucket_, path.bucket, path.key));
+        }
+        return Status::OK();
+    }
+
+    std::string bucket_;
+    std::shared_ptr<oss2::OSSClient> client_;
+    std::unique_ptr<Executor> executor_;
+};
+
+}  // namespace
+
+OssFileSystem::OssFileSystem(std::string bucket, 
std::shared_ptr<oss2::OSSClient> client,
+                             std::unique_ptr<Executor> executor)
+    : ObjectStoreFileSystem("oss", std::make_shared<OssObjectStoreClient>(
+                                       std::move(bucket), std::move(client), 
std::move(executor))) {
+}
+
+}  // namespace paimon::oss
diff --git a/src/paimon/fs/oss/oss_file_system.h 
b/src/paimon/fs/oss/oss_file_system.h
new file mode 100644
index 00000000..0749718e
--- /dev/null
+++ b/src/paimon/fs/oss/oss_file_system.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "paimon/common/fs/object_store_file_system.h"
+#include "paimon/executor.h"
+
+namespace alibabacloud::oss2 {
+class OSSClient;
+}
+
+namespace paimon::oss {
+
+inline constexpr char kOssAccessKeyIdOption[] = "fs.oss.accessKeyId";
+inline constexpr char kOssAccessKeySecretOption[] = "fs.oss.accessKeySecret";
+inline constexpr char kOssEndpointOption[] = "fs.oss.endpoint";
+inline constexpr char kOssRegionOption[] = "fs.oss.region";
+inline constexpr char kOssSignatureVersionOption[] = "fs.oss.signatureVersion";
+inline constexpr char kOssSecurityTokenOption[] = "fs.oss.securityToken";
+inline constexpr char kOssSessionTokenOption[] = "fs.oss.sessionToken";
+inline constexpr char kOssUsePathStyleOption[] = "fs.oss.usePathStyle";
+inline constexpr char kOssExecutorThreadCountOption[] = 
"fs.oss.executor.thread-count";
+
+class OssFileSystem : public ObjectStoreFileSystem {
+ public:
+    OssFileSystem(std::string bucket, 
std::shared_ptr<alibabacloud::oss2::OSSClient> client,
+                  std::unique_ptr<Executor> executor);
+    ~OssFileSystem() override = default;
+};
+
+}  // namespace paimon::oss
diff --git a/src/paimon/fs/oss/oss_file_system_factory.cpp 
b/src/paimon/fs/oss/oss_file_system_factory.cpp
new file mode 100644
index 00000000..327586d0
--- /dev/null
+++ b/src/paimon/fs/oss/oss_file_system_factory.cpp
@@ -0,0 +1,234 @@
+/*
+ * 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/fs/oss/oss_file_system_factory.h"
+
+#include <cctype>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#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/common/utils/string_utils.h"
+#include "paimon/factories/factory.h"
+#include "paimon/fs/oss/oss_file_system.h"
+
+namespace paimon::oss {
+namespace {
+
+namespace oss2 = alibabacloud::oss2;
+
+constexpr std::string_view kOssOptionPrefix = "fs.oss.";
+constexpr char kEndpointPrefix[] = "oss-";
+constexpr char kEndpointSuffix[] = ".aliyuncs.com";
+constexpr char kDualStackEndpointSuffix[] = ".oss.aliyuncs.com";
+constexpr char kInternalEndpointSuffix[] = "-internal";
+
+bool IsValidRegion(const std::string& region) {
+    size_t first_separator = region.find('-');
+    if (first_separator < 2 || first_separator > 3 || first_separator + 1 == 
region.size()) {
+        return false;
+    }
+    for (size_t i = 0; i < first_separator; ++i) {
+        if (!std::islower(static_cast<unsigned char>(region[i]))) {
+            return false;
+        }
+    }
+    bool previous_was_separator = true;
+    for (size_t i = first_separator + 1; i < region.size(); ++i) {
+        char value = region[i];
+        if (value == '-') {
+            if (previous_was_separator || i + 1 == region.size()) {
+                return false;
+            }
+            previous_was_separator = true;
+        } else if (std::islower(static_cast<unsigned char>(value)) ||
+                   std::isdigit(static_cast<unsigned char>(value))) {
+            previous_was_separator = false;
+        } else {
+            return false;
+        }
+    }
+    return !StringUtils::EndsWith(region, "-dualstack") && 
!StringUtils::EndsWith(region, "-pub");
+}
+
+std::string GetBucketOptionKey(const std::string& bucket, std::string_view 
option) {
+    return fmt::format("fs.oss.bucket.{}.{}", bucket, 
option.substr(kOssOptionPrefix.size()));
+}
+
+const std::string* FindOption(const std::map<std::string, std::string>& 
options,
+                              const std::string& bucket, std::string_view 
option,
+                              std::string* option_key) {
+    std::string bucket_option_key = GetBucketOptionKey(bucket, option);
+    auto bucket_option = options.find(bucket_option_key);
+    if (bucket_option != options.end()) {
+        *option_key = std::move(bucket_option_key);
+        return &bucket_option->second;
+    }
+    option_key->assign(option);
+    auto global_option = options.find(*option_key);
+    return global_option == options.end() ? nullptr : &global_option->second;
+}
+
+std::string GetOption(const std::map<std::string, std::string>& options, const 
std::string& bucket,
+                      std::string_view option) {
+    std::string option_key;
+    const std::string* value = FindOption(options, bucket, option, 
&option_key);
+    return value == nullptr ? "" : *value;
+}
+
+Result<std::string> GetRequiredOption(const std::map<std::string, 
std::string>& options,
+                                      const std::string& bucket, 
std::string_view option) {
+    std::string option_key;
+    const std::string* value = FindOption(options, bucket, option, 
&option_key);
+    if (value == nullptr || value->empty()) {
+        return Status::Invalid(fmt::format("OSS option '{}' must not be 
empty", option_key));
+    }
+    return *value;
+}
+
+Result<std::unique_ptr<Executor>> CreateExecutor(const std::map<std::string, 
std::string>& options,
+                                                 const std::string& bucket) {
+    std::string option_key;
+    const std::string* value =
+        FindOption(options, bucket, kOssExecutorThreadCountOption, 
&option_key);
+    if (value == nullptr) {
+        return CreateDefaultExecutor();
+    }
+    std::optional<uint32_t> thread_count = 
StringUtils::StringToValue<uint32_t>(*value);
+    if (!thread_count.has_value() || *thread_count == 0) {
+        return Status::Invalid(fmt::format(
+            "OSS executor thread count for option '{}' must be greater than 
0", option_key));
+    }
+    return CreateDefaultExecutor(*thread_count);
+}
+
+std::string NormalizeEndpoint(std::string endpoint) {
+    if (!endpoint.empty() && endpoint.find("://") == std::string::npos) {
+        endpoint = "https://"; + endpoint;
+    }
+    return endpoint;
+}
+
+std::string InferRegion(std::string endpoint) {
+    size_t scheme = endpoint.find("://");
+    if (scheme != std::string::npos) {
+        endpoint.erase(0, scheme + 3);
+    }
+    size_t slash = endpoint.find('/');
+    if (slash != std::string::npos) {
+        endpoint.erase(slash);
+    }
+    size_t port_separator = endpoint.rfind(':');
+    if (port_separator != std::string::npos && endpoint.find(':') == 
port_separator) {
+        std::optional<uint16_t> port =
+            
StringUtils::StringToValue<uint16_t>(endpoint.substr(port_separator + 1));
+        if (port.has_value()) {
+            endpoint.erase(port_separator);
+        }
+    }
+
+    std::string region;
+    if (StringUtils::StartsWith(endpoint, kEndpointPrefix) &&
+        StringUtils::EndsWith(endpoint, kEndpointSuffix)) {
+        region = endpoint.substr(
+            sizeof(kEndpointPrefix) - 1,
+            endpoint.size() - (sizeof(kEndpointPrefix) - 1) - 
(sizeof(kEndpointSuffix) - 1));
+        if (StringUtils::EndsWith(region, kInternalEndpointSuffix)) {
+            region.erase(region.size() - (sizeof(kInternalEndpointSuffix) - 
1));
+        }
+    } else if (StringUtils::EndsWith(endpoint, kDualStackEndpointSuffix)) {
+        region = endpoint.substr(0, endpoint.size() - 
(sizeof(kDualStackEndpointSuffix) - 1));
+    }
+    return IsValidRegion(region) ? region : "";
+}
+
+}  // namespace
+
+const char OssFileSystemFactory::IDENTIFIER[] = "oss";
+
+Result<std::unique_ptr<FileSystem>> OssFileSystemFactory::Create(
+    const std::string& path, const std::map<std::string, std::string>& 
options) const {
+    PAIMON_ASSIGN_OR_RAISE(Path parsed_path, PathUtil::ToPath(path));
+    if (parsed_path.scheme != "oss" || parsed_path.authority.empty()) {
+        return Status::Invalid(fmt::format("invalid OSS path '{}'", path));
+    }
+    const std::string& bucket = parsed_path.authority;
+    PAIMON_ASSIGN_OR_RAISE(std::string access_key_id,
+                           GetRequiredOption(options, bucket, 
kOssAccessKeyIdOption));
+    PAIMON_ASSIGN_OR_RAISE(std::string access_key_secret,
+                           GetRequiredOption(options, bucket, 
kOssAccessKeySecretOption));
+    std::string endpoint = GetOption(options, bucket, kOssEndpointOption);
+    std::string region = GetOption(options, bucket, kOssRegionOption);
+    if (region.empty()) {
+        region = InferRegion(endpoint);
+    }
+    if (endpoint.empty() && region.empty()) {
+        return Status::Invalid("OSS endpoint or region must be configured");
+    }
+    std::string signature_version = GetOption(options, bucket, 
kOssSignatureVersionOption);
+    if (!signature_version.empty() && signature_version != "v1" && 
signature_version != "v4") {
+        return Status::Invalid(
+            fmt::format("invalid OSS signature version '{}'", 
signature_version));
+    }
+    if (region.empty() && signature_version != "v1") {
+        return Status::Invalid(
+            "OSS region must be configured when the endpoint does not identify 
a region");
+    }
+    std::string security_token = GetOption(options, bucket, 
kOssSecurityTokenOption);
+    if (security_token.empty()) {
+        security_token = GetOption(options, bucket, kOssSessionTokenOption);
+    }
+
+    oss2::ClientConfiguration config = 
oss2::ClientConfiguration::loadDefault();
+    if (!endpoint.empty()) {
+        config.endpoint = NormalizeEndpoint(endpoint);
+    }
+    if (!region.empty()) {
+        config.region = region;
+    }
+    if (!signature_version.empty()) {
+        config.signatureVersion = signature_version;
+    }
+    config.userAgent = "paimon-cpp";
+    config.credentialsProvider = 
std::make_shared<oss2::StaticCredentialsProvider>(
+        std::move(access_key_id), std::move(access_key_secret), 
std::move(security_token));
+    std::string path_style = GetOption(options, bucket, 
kOssUsePathStyleOption);
+    if (!path_style.empty()) {
+        std::optional<bool> value = 
StringUtils::StringToValue<bool>(path_style);
+        if (!value.has_value()) {
+            return Status::Invalid(fmt::format("invalid boolean value '{}' for 
OSS option '{}'",
+                                               path_style, 
kOssUsePathStyleOption));
+        }
+        config.usePathStyle = *value;
+    }
+    PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<Executor> executor, 
CreateExecutor(options, bucket));
+    return std::make_unique<OssFileSystem>(bucket, 
std::make_shared<oss2::OSSClient>(config),
+                                           std::move(executor));
+}
+
+REGISTER_PAIMON_FACTORY(OssFileSystemFactory);
+
+}  // namespace paimon::oss
diff --git a/src/paimon/fs/oss/oss_file_system_factory.h 
b/src/paimon/fs/oss/oss_file_system_factory.h
new file mode 100644
index 00000000..fe92715e
--- /dev/null
+++ b/src/paimon/fs/oss/oss_file_system_factory.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "paimon/fs/file_system_factory.h"
+
+namespace paimon::oss {
+
+class OssFileSystemFactory : public FileSystemFactory {
+ public:
+    static const char IDENTIFIER[];
+
+    const char* Identifier() const override {
+        return IDENTIFIER;
+    }
+
+    Result<std::unique_ptr<FileSystem>> Create(
+        const std::string& path, const std::map<std::string, std::string>& 
options) const override;
+};
+
+}  // namespace paimon::oss
diff --git a/src/paimon/fs/oss/oss_file_system_test.cpp 
b/src/paimon/fs/oss/oss_file_system_test.cpp
new file mode 100644
index 00000000..fbd847f8
--- /dev/null
+++ b/src/paimon/fs/oss/oss_file_system_test.cpp
@@ -0,0 +1,327 @@
+/*
+ * 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/fs/oss/oss_file_system.h"
+
+#include <chrono>
+#include <cstdint>
+#include <future>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+#include "alibabacloud/oss2/ClientConfiguration.h"
+#include "alibabacloud/oss2/OSSClient.h"
+#include "alibabacloud/oss2/credentials/CredentialsProvider.h"
+#include "alibabacloud/oss2/io/ByteWriter.h"
+#include "alibabacloud/oss2/transport/HttpTransport.h"
+#include "gtest/gtest.h"
+#include "paimon/fs/oss/oss_file_system_factory.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::oss {
+namespace {
+
+namespace oss2 = alibabacloud::oss2;
+
+class MockHttpTransport : public oss2::HttpTransport {
+ public:
+    oss2::ResponseResult send(std::unique_ptr<oss2::RequestMessage>& request,
+                              const oss2::RequestOptions& options) override {
+        
requests_.emplace_back(std::make_unique<oss2::RequestMessage>(*request));
+        if (responses_.empty()) {
+            return 
oss2::TransportError{std::make_error_code(std::errc::no_message_available), "",
+                                        ""};
+        }
+        std::unique_ptr<oss2::ResponseMessage> response = 
std::move(responses_.front());
+        responses_.erase(responses_.begin());
+        if (response->statusCode / 100 == 2 && options.sinkFactory.has_value() 
&&
+            response->body != nullptr) {
+            int64_t content_length = -1;
+            auto content_length_header = 
response->headers.find("Content-Length");
+            if (content_length_header != response->headers.end()) {
+                content_length = std::stoll(content_length_header->second);
+            }
+            std::shared_ptr<oss2::ByteWriter> sink =
+                options.sinkFactory.value()(content_length, response->headers);
+            std::ostringstream body;
+            body << response->body->rdbuf();
+            const std::string data = body.str();
+            sink->write(reinterpret_cast<const uint8_t*>(data.data()), 
data.size());
+            response->body.reset();
+        }
+        return response;
+    }
+
+    std::string getName() const override {
+        return "MockHttpTransport";
+    }
+
+    void AddResponse(int status_code, oss2::HeaderCollection headers, 
std::string body = "") {
+        std::shared_ptr<std::iostream> response_body;
+        if (!body.empty()) {
+            response_body = 
std::make_shared<std::stringstream>(std::move(body));
+        }
+        
responses_.emplace_back(std::make_unique<oss2::ResponseMessage>(oss2::ResponseMessage{
+            status_code, "", std::move(headers), std::move(response_body), 
nullptr}));
+    }
+
+    std::vector<std::unique_ptr<oss2::ResponseMessage>> responses_;
+    std::vector<std::unique_ptr<oss2::RequestMessage>> requests_;
+};
+
+std::unique_ptr<OssFileSystem> CreateFileSystem(
+    const std::shared_ptr<MockHttpTransport>& transport) {
+    oss2::ClientConfiguration config = 
oss2::ClientConfiguration::loadDefault();
+    config.region = "cn-hangzhou";
+    config.credentialsProvider =
+        std::make_shared<oss2::StaticCredentialsProvider>("access-key", 
"secret-key");
+    config.httpTransport = transport;
+    return std::make_unique<OssFileSystem>("bucket", 
std::make_shared<oss2::OSSClient>(config),
+                                           CreateDefaultExecutor());
+}
+
+}  // namespace
+
+TEST(OssFileSystemFactoryTest, TestOptionValidation) {
+    OssFileSystemFactory factory;
+    std::map<std::string, std::string> options;
+    ASSERT_NOK(factory.Create("s3://bucket/key", options));
+    ASSERT_NOK(factory.Create("oss://bucket/key", options));
+
+    options[kOssAccessKeyIdOption] = "access-key";
+    options[kOssAccessKeySecretOption] = "secret-key";
+    options[kOssEndpointOption] = "oss-cn-hangzhou.aliyuncs.com";
+    options[kOssUsePathStyleOption] = "treu";
+    ASSERT_NOK(factory.Create("oss://bucket/key", options));
+
+    options[kOssUsePathStyleOption] = "false";
+    options[kOssSignatureVersionOption] = "v2";
+    ASSERT_NOK(factory.Create("oss://bucket/key", options));
+
+    options[kOssSignatureVersionOption] = "v4";
+    options[kOssExecutorThreadCountOption] = "0";
+    ASSERT_NOK(factory.Create("oss://bucket/key", options));
+
+    options[kOssExecutorThreadCountOption] = "4";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+
+    options[kOssEndpointOption] = "";
+    options[kOssRegionOption] = "cn-hangzhou";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+}
+
+TEST(OssFileSystemFactoryTest, TestBucketOptionsOverrideGlobalOptions) {
+    OssFileSystemFactory factory;
+    std::map<std::string, std::string> options = {
+        {kOssAccessKeyIdOption, ""},
+        {kOssAccessKeySecretOption, ""},
+        {kOssEndpointOption, ""},
+        {"fs.oss.bucket.bucket.accessKeyId", "access-key"},
+        {"fs.oss.bucket.bucket.accessKeySecret", "secret-key"},
+        {"fs.oss.bucket.bucket.endpoint", "oss-cn-hangzhou.aliyuncs.com"},
+    };
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+}
+
+TEST(OssFileSystemFactoryTest, TestBucketOptionErrorReportsBucketKey) {
+    OssFileSystemFactory factory;
+    std::map<std::string, std::string> options = {
+        {kOssAccessKeyIdOption, "access-key"},
+        {kOssAccessKeySecretOption, "secret-key"},
+        {kOssEndpointOption, "oss-cn-hangzhou.aliyuncs.com"},
+        {"fs.oss.bucket.bucket.accessKeyId", ""},
+    };
+    ASSERT_NOK_WITH_MSG(factory.Create("oss://bucket/key", options),
+                        "fs.oss.bucket.bucket.accessKeyId");
+}
+
+TEST(OssFileSystemFactoryTest, TestEndpointRegionValidation) {
+    OssFileSystemFactory factory;
+    std::map<std::string, std::string> options = {
+        {kOssAccessKeyIdOption, "access-key"},
+        {kOssAccessKeySecretOption, "secret-key"},
+    };
+
+    options[kOssEndpointOption] = "oss-cn-hangzhou.aliyuncs.com";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+
+    options[kOssEndpointOption] = "oss-cn-hangzhou-internal.aliyuncs.com";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+
+    options[kOssEndpointOption] = "oss-ap-southeast-1.aliyuncs.com:443";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+
+    options[kOssEndpointOption] = "cn-hangzhou.oss.aliyuncs.com";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+
+    options[kOssEndpointOption] = "oss-accelerate.aliyuncs.com";
+    ASSERT_NOK_WITH_MSG(factory.Create("oss://bucket/key", options), "OSS 
region must be");
+
+    options[kOssEndpointOption] = "oss-accelerate-overseas.aliyuncs.com";
+    ASSERT_NOK_WITH_MSG(factory.Create("oss://bucket/key", options), "OSS 
region must be");
+
+    options[kOssEndpointOption] = "oss.example.com";
+    ASSERT_NOK_WITH_MSG(factory.Create("oss://bucket/key", options), "OSS 
region must be");
+
+    options[kOssRegionOption] = "cn-hangzhou";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+
+    options.erase(kOssRegionOption);
+    options[kOssSignatureVersionOption] = "v1";
+    ASSERT_OK(factory.Create("oss://bucket/key", options));
+}
+
+TEST(OssFileSystemTest, TestHeadObjectParsesMetadata) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(200, {{"Content-Length", "3"},
+                                 {"Last-Modified", "not-a-timestamp"},
+                                 {"x-oss-request-id", "request-id"}});
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+
+    ASSERT_OK_AND_ASSIGN(FileStatus status, 
file_system->GetFileStatus("oss://bucket/key"));
+    ASSERT_EQ(3, status.GetLen());
+    ASSERT_EQ(FileStatus::kUnknownModificationTime, 
status.GetModificationTime());
+    ASSERT_EQ(1U, transport->requests_.size());
+    ASSERT_EQ("HEAD", transport->requests_[0]->method);
+}
+
+TEST(OssFileSystemTest, TestHeadObjectNotFound) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(404, {{"x-oss-request-id", "request-id"}},
+                           
"<Error><Code>NoSuchKey</Code><Message>missing</Message></Error>");
+    transport->AddResponse(200, {},
+                           
"<ListBucketResult><IsTruncated>false</IsTruncated></ListBucketResult>");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+
+    Result<FileStatus> status = 
file_system->GetFileStatus("oss://bucket/missing");
+    ASSERT_TRUE(status.status().IsNotExist()) << status.status().ToString();
+    ASSERT_NOK_WITH_MSG(status, "does not exist");
+}
+
+TEST(OssFileSystemTest, TestHeadObjectErrorMapping) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(403, {{"x-oss-request-id", "request-id"}},
+                           
"<Error><Code>AccessDenied</Code><Message>denied</Message></Error>");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+
+    Result<FileStatus> status = file_system->GetFileStatus("oss://bucket/key");
+    ASSERT_TRUE(status.status().IsIOError()) << status.status().ToString();
+    ASSERT_NOK_WITH_MSG(status, "code=AccessDenied, status=403");
+}
+
+TEST(OssFileSystemTest, TestListObjects) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(404, {{"x-oss-request-id", "request-id"}},
+                           
"<Error><Code>NoSuchKey</Code><Message>missing</Message></Error>");
+    transport->AddResponse(200, {{"x-oss-request-id", "request-id"}}, R"(
+<ListBucketResult>
+  <IsTruncated>false</IsTruncated>
+  <Contents>
+    <Key>prefix/file</Key>
+    <Size>3</Size>
+    <LastModified>2024-01-01T00:00:00.000Z</LastModified>
+  </Contents>
+  <CommonPrefixes><Prefix>prefix/sub/</Prefix></CommonPrefixes>
+</ListBucketResult>)");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+    std::vector<FileStatus> statuses;
+
+    ASSERT_OK(file_system->ListFileStatus("oss://bucket/prefix", &statuses));
+    ASSERT_EQ(2U, statuses.size());
+    ASSERT_EQ(1704067200000, statuses[0].GetModificationTime());
+    ASSERT_EQ(2U, transport->requests_.size());
+    ASSERT_EQ("HEAD", transport->requests_[0]->method);
+    ASSERT_EQ("GET", transport->requests_[1]->method);
+    ASSERT_NE(std::string::npos, 
transport->requests_[1]->uri.find("list-type=2"));
+}
+
+TEST(OssFileSystemTest, TestListObjectsErrorMapping) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(403, {{"x-oss-request-id", "request-id"}},
+                           
"<Error><Code>AccessDenied</Code><Message>denied</Message></Error>");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+    std::vector<BasicFileStatus> statuses;
+
+    Status status = file_system->ListDir("oss://bucket/prefix/", &statuses);
+    ASSERT_TRUE(status.IsIOError()) << status.ToString();
+    ASSERT_NOK_WITH_MSG(status, "code=AccessDenied, status=403");
+}
+
+TEST(OssFileSystemTest, TestGetObjectRangeAndShortRead) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(206, {{"Content-Length", "3"}}, "abc");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+    FileStatus file_status("oss://bucket/key", 3);
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> stream, 
file_system->Open(file_status));
+    char buffer[3];
+
+    ASSERT_OK_AND_ASSIGN(int64_t bytes_read, stream->Read(buffer, 3, 0));
+    ASSERT_EQ(3, bytes_read);
+    ASSERT_EQ("abc", std::string(buffer, sizeof(buffer)));
+    ASSERT_EQ(1U, transport->requests_.size());
+    ASSERT_EQ("bytes=0-2", transport->requests_[0]->headers.at("range"));
+
+    std::shared_ptr<MockHttpTransport> short_transport = 
std::make_shared<MockHttpTransport>();
+    short_transport->AddResponse(206, {{"Content-Length", "2"}}, "ab");
+    std::unique_ptr<OssFileSystem> short_file_system = 
CreateFileSystem(short_transport);
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> short_stream,
+                         short_file_system->Open(file_status));
+    ASSERT_NOK_WITH_MSG(short_stream->Read(buffer, 3, 0), "expected 3");
+}
+
+TEST(OssFileSystemTest, TestGetObjectRangeErrorMapping) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(403, {{"x-oss-request-id", "request-id"}},
+                           
"<Error><Code>AccessDenied</Code><Message>denied</Message></Error>");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+    FileStatus file_status("oss://bucket/key", 3);
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> stream, 
file_system->Open(file_status));
+    char buffer[3];
+
+    Result<int64_t> result = stream->Read(buffer, 3, 0);
+    ASSERT_TRUE(result.status().IsIOError()) << result.status().ToString();
+    ASSERT_NOK_WITH_MSG(result, "code=AccessDenied, status=403");
+}
+
+TEST(OssFileSystemTest, TestGetObjectRangeAsync) {
+    std::shared_ptr<MockHttpTransport> transport = 
std::make_shared<MockHttpTransport>();
+    transport->AddResponse(206, {{"Content-Length", "3"}}, "abc");
+    std::unique_ptr<OssFileSystem> file_system = CreateFileSystem(transport);
+    FileStatus file_status("oss://bucket/key", 3);
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> stream, 
file_system->Open(file_status));
+    char buffer[3];
+    std::promise<Status> promise;
+    std::future<Status> future = promise.get_future();
+
+    stream->ReadAsync(buffer, 3, 0,
+                      [&promise](Status status) { 
promise.set_value(std::move(status)); });
+
+    ASSERT_EQ(std::future_status::ready, 
future.wait_for(std::chrono::seconds(5)));
+    ASSERT_OK(future.get());
+    ASSERT_EQ("abc", std::string(buffer, sizeof(buffer)));
+    ASSERT_EQ(1U, transport->requests_.size());
+    ASSERT_EQ("bytes=0-2", transport->requests_[0]->headers.at("range"));
+}
+
+}  // namespace paimon::oss
diff --git a/src/paimon/fs/s3/s3_file_system.cpp 
b/src/paimon/fs/s3/s3_file_system.cpp
index e31cc9ca..b9b98f1e 100644
--- a/src/paimon/fs/s3/s3_file_system.cpp
+++ b/src/paimon/fs/s3/s3_file_system.cpp
@@ -71,11 +71,6 @@ Result<int64_t> ParseNonNegativeInt64(const std::string& 
value, const std::strin
     return *result;
 }
 
-int64_t ParseModificationTime(const std::string& value) {
-    time_t seconds = curl_getdate(value.c_str(), nullptr);
-    return seconds == static_cast<time_t>(-1) ? 0 : 
static_cast<int64_t>(seconds) * 1000;
-}
-
 std::string XmlUnescape(const std::string& value) {
     const std::pair<const char*, const char*> entities[] = {
         {"&amp;", "&"}, {"&lt;", "<"}, {"&gt;", ">"}, {"&quot;", "\""}, 
{"&apos;", "'"}};
@@ -662,10 +657,10 @@ class S3ObjectStoreClient : public ObjectStoreClient,
         if (length == response.headers.end()) {
             return Status::IOError("HeadObject response is missing 
Content-Length");
         }
-        int64_t modification_time = 0;
+        int64_t modification_time = FileStatus::kUnknownModificationTime;
         auto modified = response.headers.find("last-modified");
         if (modified != response.headers.end()) {
-            modification_time = ParseModificationTime(modified->second);
+            modification_time = 
ObjectStoreFileSystemUtils::ParseModificationTime(modified->second);
         }
         PAIMON_ASSIGN_OR_RAISE(int64_t object_size,
                                ParseNonNegativeInt64(length->second, 
"Content-Length"));
@@ -712,10 +707,10 @@ class S3ObjectStoreClient : public ObjectStoreClient,
             PAIMON_ASSIGN_OR_RAISE(std::string decoded_key, 
PercentDecode(*key, "Key"));
             PAIMON_ASSIGN_OR_RAISE(int64_t object_size,
                                    ParseNonNegativeInt64(*size, "ListObjectsV2 
Size"));
-            int64_t modified = 0;
+            int64_t modified = FileStatus::kUnknownModificationTime;
             auto last_modified = TagValue(block, "LastModified");
             if (last_modified) {
-                modified = ParseModificationTime(*last_modified);
+                modified = 
ObjectStoreFileSystemUtils::ParseModificationTime(*last_modified);
             }
             result.objects.push_back(ObjectMetadata{decoded_key, object_size, 
modified});
         }
diff --git a/src/paimon/fs/s3/s3_file_system_test.cpp 
b/src/paimon/fs/s3/s3_file_system_test.cpp
index 063a7dcb..822e1aed 100644
--- a/src/paimon/fs/s3/s3_file_system_test.cpp
+++ b/src/paimon/fs/s3/s3_file_system_test.cpp
@@ -302,13 +302,13 @@ TEST(S3ObjectStoreClientTest, 
TestInvalidModificationTime) {
     ASSERT_OK_AND_ASSIGN(std::shared_ptr<ObjectStoreClient> client,
                          MakeS3ObjectStoreClient(StaticOptions(), http));
     ASSERT_OK_AND_ASSIGN(auto metadata, client->HeadObject({"bucket", 
"file"}));
-    ASSERT_EQ(metadata.modification_time, 0);
+    ASSERT_EQ(metadata.modification_time, 
FileStatus::kUnknownModificationTime);
 
     http->body_ =
         
"<ListBucketResult><IsTruncated>false</IsTruncated><Contents><Key>file</Key>"
         
"<LastModified>invalid</LastModified><Size>12</Size></Contents></ListBucketResult>";
     ASSERT_OK_AND_ASSIGN(auto result, client->ListObjects({"bucket", ""}, "", 
0));
-    ASSERT_EQ(result.objects[0].modification_time, 0);
+    ASSERT_EQ(result.objects[0].modification_time, 
FileStatus::kUnknownModificationTime);
 }
 
 TEST(S3ObjectStoreClientTest, TestRegionFromEnvironment) {
@@ -438,7 +438,9 @@ TEST(S3ObjectStoreClientTest, TestRangeAndListObjects) {
     ASSERT_TRUE(result.is_truncated);
     ASSERT_EQ(result.continuation_token, "next token");
     ASSERT_EQ(result.objects[0].key, "dir/a&b");
+    ASSERT_EQ(result.objects[0].modification_time, 1767225600000);
     ASSERT_EQ(result.objects[1].key, "dir/a&lt;b");
+    ASSERT_EQ(result.objects[1].modification_time, 
FileStatus::kUnknownModificationTime);
     ASSERT_EQ(result.common_prefixes[0], "dir/sub/");
     ASSERT_NE(http->request_.url.find("amazonaws.com/?list-type=2"), 
std::string::npos);
     ASSERT_NE(http->request_.url.find("encoding-type=url"), std::string::npos);
diff --git a/third_party/versions.txt b/third_party/versions.txt
index ffb1bbf3..1afdf7a1 100644
--- a/third_party/versions.txt
+++ b/third_party/versions.txt
@@ -101,6 +101,10 @@ PAIMON_AWS_S2N_BUILD_VERSION=v1.7.4
 
PAIMON_AWS_S2N_BUILD_SHA256_CHECKSUM=af5ce0783fd9e05ed1899fda0c76e02fa5dd92128018e5bfe71634312d2ce8e7
 PAIMON_AWS_S2N_PKG_NAME=s2n-${PAIMON_AWS_S2N_BUILD_VERSION}.zip
 
+PAIMON_OSS_SDK_V2_BUILD_VERSION=0.1.2
+PAIMON_OSS_SDK_V2_BUILD_SHA256_CHECKSUM=6a6c00692c8fe8461594a359de8ba97f4468d90f92ffb6bcb92b82a9d1b9311b
+PAIMON_OSS_SDK_V2_PKG_NAME=alibabacloud-oss-cpp-sdk-v2-${PAIMON_OSS_SDK_V2_BUILD_VERSION}.tar.gz
+
 PAIMON_FMT_BUILD_VERSION=11.2.0
 
PAIMON_FMT_BUILD_SHA256_CHECKSUM=bc23066d87ab3168f27cef3e97d545fa63314f5c79df5ea444d41d56f962c6af
 PAIMON_FMT_PKG_NAME=fmt-${PAIMON_FMT_BUILD_VERSION}.tar.gz
@@ -162,6 +166,7 @@ DEPENDENCIES=(
   "PAIMON_LZ4_URL ${PAIMON_LZ4_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/lz4/lz4/archive/${PAIMON_LZ4_BUILD_VERSION}.tar.gz";
   "PAIMON_PROTOBUF_URL ${PAIMON_PROTOBUF_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/protocolbuffers/protobuf/releases/download/v${PAIMON_PROTOBUF_BUILD_VERSION}/protobuf-all-${PAIMON_PROTOBUF_BUILD_VERSION}.tar.gz";
   "PAIMON_TBB_URL ${PAIMON_TBB_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/uxlfoundation/oneTBB/archive/refs/tags/${PAIMON_TBB_BUILD_VERSION}.tar.gz";
+  "PAIMON_OSS_SDK_V2_URL ${PAIMON_OSS_SDK_V2_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/aliyun/alibabacloud-oss-cpp-sdk-v2/archive/refs/tags/${PAIMON_OSS_SDK_V2_BUILD_VERSION}.tar.gz";
   "PAIMON_ORC_URL ${PAIMON_ORC_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/apache/orc/archive/refs/tags/${PAIMON_ORC_BUILD_VERSION}.tar.gz";
   "PAIMON_GTEST_URL ${PAIMON_GTEST_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/google/googletest/archive/release-${PAIMON_GTEST_BUILD_VERSION}.tar.gz";
   "PAIMON_BENCHMARK_URL ${PAIMON_BENCHMARK_PKG_NAME} 
${THIRDPARTY_MIRROR_URL}https://github.com/google/benchmark/archive/refs/tags/v${PAIMON_BENCHMARK_BUILD_VERSION}.tar.gz";

Reply via email to