Skip to content

Commit 681c18a

Browse files
committed
add ICEBERG_BUILD_REST_CATALOG library
1 parent 579db22 commit 681c18a

File tree

8 files changed

+280
-4
lines changed

8 files changed

+280
-4
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ option(ICEBERG_BUILD_STATIC "Build static library" ON)
3838
option(ICEBERG_BUILD_SHARED "Build shared library" OFF)
3939
option(ICEBERG_BUILD_TESTS "Build tests" ON)
4040
option(ICEBERG_BUILD_BUNDLE "Build the battery included library" ON)
41+
option(ICEBERG_BUILD_REST_CATALOG "Build REST catalog library" ON)
4142
option(ICEBERG_ENABLE_ASAN "Enable Address Sanitizer" OFF)
4243
option(ICEBERG_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)
4344

cmake_modules/IcebergThirdpartyToolchain.cmake

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -640,11 +640,13 @@ resolve_croaring_dependency()
640640
resolve_nlohmann_json_dependency()
641641
resolve_spdlog_dependency()
642642

643-
resolve_curl_dependency()
644-
resolve_cpr_dependency()
645-
646643
if(ICEBERG_BUILD_BUNDLE)
647644
resolve_arrow_dependency()
648645
resolve_avro_dependency()
649646
resolve_zstd_dependency()
650647
endif()
648+
649+
if(ICEBERG_BUILD_REST_CATALOG)
650+
resolve_curl_dependency()
651+
resolve_cpr_dependency()
652+
endif()

src/iceberg/catalog/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@
1616
# under the License.
1717

1818
iceberg_install_all_headers(iceberg/catalog)
19+
20+
if(ICEBERG_BUILD_REST_CATALOG)
21+
add_subdirectory(rest)
22+
endif()
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
if(NOT ICEBERG_BUILD_REST_CATALOG)
19+
return()
20+
endif()
21+
22+
set(ICEBERG_REST_CATALOG_SOURCES rest_catalog.cc)
23+
24+
set(ICEBERG_REST_CATALOG_STATIC_BUILD_INTERFACE_LIBS)
25+
set(ICEBERG_REST_CATALOG_SHARED_BUILD_INTERFACE_LIBS)
26+
set(ICEBERG_REST_CATALOG_STATIC_INSTALL_INTERFACE_LIBS)
27+
set(ICEBERG_REST_CATALOG_SHARED_INSTALL_INTERFACE_LIBS)
28+
29+
list(APPEND ICEBERG_REST_CATALOG_STATIC_BUILD_INTERFACE_LIBS
30+
"$<IF:$<TARGET_EXISTS:iceberg_static>,iceberg_static,iceberg_shared>" cpr::cpr)
31+
list(APPEND ICEBERG_REST_CATALOG_SHARED_BUILD_INTERFACE_LIBS
32+
"$<IF:$<TARGET_EXISTS:iceberg_shared>,iceberg_shared,iceberg_static>" cpr::cpr)
33+
list(APPEND
34+
ICEBERG_REST_CATALOG_STATIC_INSTALL_INTERFACE_LIBS
35+
"$<IF:$<TARGET_EXISTS:Iceberg::iceberg_static>,Iceberg::iceberg_static,Iceberg::iceberg_shared>"
36+
"$<IF:$<BOOL:${CPR_VENDORED}>,Iceberg::cpr,cpr::cpr>")
37+
list(APPEND
38+
ICEBERG_REST_CATALOG_SHARED_INSTALL_INTERFACE_LIBS
39+
"$<IF:$<TARGET_EXISTS:Iceberg::iceberg_shared>,Iceberg::iceberg_shared,Iceberg::iceberg_static>"
40+
"$<IF:$<BOOL:${CPR_VENDORED}>,Iceberg::cpr,cpr::cpr>")
41+
42+
add_iceberg_lib(iceberg_rest_catalog
43+
SOURCES
44+
${ICEBERG_REST_CATALOG_SOURCES}
45+
SHARED_LINK_LIBS
46+
${ICEBERG_REST_CATALOG_SHARED_BUILD_INTERFACE_LIBS}
47+
STATIC_LINK_LIBS
48+
${ICEBERG_REST_CATALOG_STATIC_BUILD_INTERFACE_LIBS}
49+
STATIC_INSTALL_INTERFACE_LIBS
50+
${ICEBERG_REST_CATALOG_STATIC_INSTALL_INTERFACE_LIBS}
51+
SHARED_INSTALL_INTERFACE_LIBS
52+
${ICEBERG_REST_CATALOG_SHARED_INSTALL_INTERFACE_LIBS})
53+
54+
iceberg_install_all_headers(iceberg/catalog/rest)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/catalog/rest/rest_catalog.h"
21+
22+
#include <utility>
23+
24+
#include <cpr/cpr.h>
25+
26+
namespace iceberg::catalog::rest {
27+
28+
RestCatalog::RestCatalog(const std::string& base_url) : base_url_(std::move(base_url)) {}
29+
30+
cpr::Response RestCatalog::GetConfig() {
31+
cpr::Url url = cpr::Url{base_url_ + "/v1/config"};
32+
cpr::Response r = cpr::Get(url);
33+
return r;
34+
}
35+
36+
cpr::Response RestCatalog::ListNamespaces() {
37+
cpr::Url url = cpr::Url{base_url_ + "/v1/namespaces"};
38+
cpr::Response r = cpr::Get(url);
39+
return r;
40+
}
41+
42+
} // namespace iceberg::catalog::rest
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#pragma once
19+
20+
#include <string>
21+
22+
#include <cpr/cpr.h>
23+
24+
namespace iceberg::catalog::rest {
25+
26+
class RestCatalog {
27+
public:
28+
explicit RestCatalog(const std::string& base_url);
29+
~RestCatalog() = default;
30+
31+
cpr::Response GetConfig();
32+
33+
cpr::Response ListNamespaces();
34+
35+
private:
36+
std::string base_url_;
37+
};
38+
39+
} // namespace iceberg::catalog::rest

test/CMakeLists.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,23 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
fetchcontent_declare(cpp-httplib
19+
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
20+
GIT_TAG 89c932f313c6437c38f2982869beacc89c2f2246 #release-0.26.0
21+
)
22+
1823
fetchcontent_declare(googletest
1924
GIT_REPOSITORY https://github.com/google/googletest.git
2025
GIT_TAG b514bdc898e2951020cbdca1304b75f5950d1f59 # release-1.15.2
2126
FIND_PACKAGE_ARGS
2227
NAMES
2328
GTest)
24-
fetchcontent_makeavailable(googletest)
29+
30+
if(ICEBERG_BUILD_REST_CATALOG)
31+
fetchcontent_makeavailable(cpp-httplib googletest)
32+
else()
33+
fetchcontent_makeavailable(googletest)
34+
endif()
2535

2636
set(ICEBERG_TEST_RESOURCES "${CMAKE_SOURCE_DIR}/test/resources")
2737

@@ -131,3 +141,16 @@ if(ICEBERG_BUILD_BUNDLE)
131141
add_iceberg_test(scan_test USE_BUNDLE SOURCES file_scan_task_test.cc)
132142

133143
endif()
144+
145+
if(ICEBERG_BUILD_REST_CATALOG)
146+
add_iceberg_test(rest_catalog_test SOURCES rest_catalog_test.cc)
147+
target_link_libraries(rest_catalog_test PRIVATE iceberg_rest_catalog_static)
148+
target_include_directories(rest_catalog_test PRIVATE ${cpp-httplib_SOURCE_DIR})
149+
150+
if(WIN32)
151+
target_link_libraries(rest_catalog_test PRIVATE ws2_32 crypt32)
152+
else()
153+
target_link_libraries(rest_catalog_test PRIVATE Threads::Threads)
154+
endif()
155+
156+
endif()

test/rest_catalog_test.cc

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/catalog/rest/rest_catalog.h"
21+
22+
#include <httplib.h>
23+
24+
#include <memory>
25+
#include <thread>
26+
27+
#include <gmock/gmock.h>
28+
#include <gtest/gtest.h>
29+
#include <nlohmann/json.hpp>
30+
31+
namespace iceberg::catalog::rest {
32+
33+
class RestCatalogIntegrationTest : public ::testing::Test {
34+
protected:
35+
void SetUp() override {
36+
server_ = std::make_unique<httplib::Server>();
37+
port_ = server_->bind_to_any_port("127.0.0.1");
38+
39+
server_thread_ = std::thread([this]() { server_->listen_after_bind(); });
40+
}
41+
42+
void TearDown() override {
43+
server_->stop();
44+
if (server_thread_.joinable()) {
45+
server_thread_.join();
46+
}
47+
}
48+
49+
std::unique_ptr<httplib::Server> server_;
50+
int port_ = -1;
51+
std::thread server_thread_;
52+
};
53+
54+
TEST_F(RestCatalogIntegrationTest, GetConfigSuccessfully) {
55+
server_->Get("/v1/config", [](const httplib::Request&, httplib::Response& res) {
56+
res.status = 200;
57+
res.set_content(R"({"warehouse": "s3://test-bucket"})", "application/json");
58+
});
59+
60+
std::string base_uri = "http://127.0.0.1:" + std::to_string(port_);
61+
RestCatalog catalog(base_uri);
62+
cpr::Response response = catalog.GetConfig();
63+
64+
ASSERT_EQ(response.error.code, cpr::ErrorCode::OK);
65+
ASSERT_EQ(response.status_code, 200);
66+
67+
auto json_body = nlohmann::json::parse(response.text);
68+
EXPECT_EQ(json_body["warehouse"], "s3://test-bucket");
69+
}
70+
71+
TEST_F(RestCatalogIntegrationTest, ListNamespacesReturnsMultipleResults) {
72+
server_->Get("/v1/namespaces", [](const httplib::Request&, httplib::Response& res) {
73+
res.status = 200;
74+
res.set_content(R"({
75+
"namespaces": [
76+
["accounting", "db"],
77+
["production", "db"]
78+
]
79+
})",
80+
"application/json");
81+
});
82+
83+
std::string base_uri = "http://127.0.0.1:" + std::to_string(port_);
84+
RestCatalog catalog(base_uri);
85+
cpr::Response response = catalog.ListNamespaces();
86+
87+
ASSERT_EQ(response.error.code, cpr::ErrorCode::OK);
88+
ASSERT_EQ(response.status_code, 200);
89+
90+
auto json_body = nlohmann::json::parse(response.text);
91+
ASSERT_TRUE(json_body.contains("namespaces"));
92+
EXPECT_EQ(json_body["namespaces"].size(), 2);
93+
EXPECT_THAT(json_body["namespaces"][0][0], "accounting");
94+
}
95+
96+
TEST_F(RestCatalogIntegrationTest, HandlesServerError) {
97+
server_->Get("/v1/config", [](const httplib::Request&, httplib::Response& res) {
98+
res.status = 500;
99+
res.set_content("Internal Server Error", "text/plain");
100+
});
101+
102+
std::string base_uri = "http://127.0.0.1:" + std::to_string(port_);
103+
RestCatalog catalog(base_uri);
104+
cpr::Response response = catalog.GetConfig();
105+
106+
ASSERT_EQ(response.error.code, cpr::ErrorCode::OK);
107+
ASSERT_EQ(response.status_code, 500);
108+
ASSERT_EQ(response.text, "Internal Server Error");
109+
}
110+
111+
} // namespace iceberg::catalog::rest

0 commit comments

Comments
 (0)