blob: c2394cedcee7de26992cbccb1ea0af657d571c43 [file]
/*
* 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.
*/
#pragma once
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "paimon/catalog/identifier.h"
#include "paimon/core/snapshot.h"
#include "paimon/rest/resource_paths.h"
#include "paimon/rest/rest_auth.h"
#include "paimon/rest/rest_http_client.h"
#include "paimon/rest/rest_messages.h"
#include "paimon/result.h"
#include "paimon/status.h"
namespace paimon {
/// Carries the code of a failed rest request (the parsed error body's code, falling back
/// to the http status) so callers can tell e.g. an authentication failure (401/403) from
/// another IO error.
class RestErrorDetail : public StatusDetail {
public:
static constexpr const char* kTypeId = "rest-error-detail";
explicit RestErrorDetail(int64_t code) : code_(code) {}
const char* type_id() const override {
return kTypeId;
}
std::string ToString() const override {
return "rest error code " + std::to_string(code_);
}
int64_t GetCode() const {
return code_;
}
private:
int64_t code_;
};
/// The client of the REST catalog server. This layer only talks HTTP + JSON and never
/// touches the file system.
class RestApi {
public:
static constexpr const char* kQueryParamPageToken = "pageToken";
static constexpr const char* kQueryParamWarehouse = "warehouse";
/// Option key of the url path prefix inserted after "/v1", usually pushed down by
/// "/v1/config".
static constexpr const char* kOptionUrlPrefix = "prefix";
/// Options with this prefix are sent as http headers (with the prefix stripped).
static constexpr const char* kHeaderOptionPrefix = "header.";
/// Creates the api client.
///
/// @param options Client side options; `CatalogOptions::URI` and
/// `CatalogOptions::TOKEN_PROVIDER` are required.
/// @param warehouse Warehouse sent as query parameter of "/v1/config"; may be empty.
/// @param config_required When true, fetch "/v1/config" and merge the server
/// defaults/overrides into `options` with the precedence
/// overrides > client options > defaults.
/// @param http_config Transport level settings, mainly overridable for tests.
static Result<std::unique_ptr<RestApi>> Create(
const std::map<std::string, std::string>& options, const std::string& warehouse,
bool config_required, const RestHttpClient::Config& http_config = RestHttpClient::Config());
/// Options merged with the server side config.
const std::map<std::string, std::string>& GetMergedOptions() const {
return options_;
}
Result<std::vector<std::string>> ListDatabases() const;
Status CreateDatabase(const std::string& name,
const std::map<std::string, std::string>& options) const;
Result<GetDatabaseResponse> GetDatabase(const std::string& name) const;
Status DropDatabase(const std::string& name) const;
Result<std::vector<std::string>> ListTables(const std::string& database_name) const;
Result<GetTableResponse> GetTable(const Identifier& identifier) const;
/// `schema_json` uses the schema JSON layout of the protocol
/// (fields/partitionKeys/primaryKeys/options/comment).
Status CreateTable(const Identifier& identifier, const std::string& schema_json) const;
Status DropTable(const Identifier& identifier) const;
Status RenameTable(const Identifier& from_table, const Identifier& to_table) const;
Result<std::vector<Snapshot>> ListSnapshots(const Identifier& identifier) const;
/// Maps a non-successful http response to a status: 404 becomes `NotExist`, 409
/// becomes `Exist`, 400 becomes `Invalid`, 501 becomes `NotImplemented` and the
/// other codes become `IOError`. The status carries a `RestErrorDetail` with the
/// mapped code.
static Status ErrorToStatus(const RestHttpClient::Response& response);
private:
RestApi(std::unique_ptr<RestHttpClient> client, std::unique_ptr<AuthProvider> auth_provider,
const std::map<std::string, std::string>& base_headers,
const std::map<std::string, std::string>& options, const ResourcePaths& paths);
/// Executes one request with the authentication headers merged in, mapping a
/// non-successful response to an error status.
Result<RestHttpClient::Response> Execute(const std::string& method, const std::string& path,
const std::map<std::string, std::string>& query_params,
const std::string& body) const;
template <typename ResponseT>
Result<ResponseT> GetEntity(const std::string& path,
const std::map<std::string, std::string>& query_params) const;
/// Fetches all pages, stopping at an empty page or a missing/empty next page token.
template <typename ResponseT>
Result<std::vector<typename ResponseT::ItemType>> ListAllPages(const std::string& path) const;
std::unique_ptr<RestHttpClient> client_;
std::unique_ptr<AuthProvider> auth_provider_;
std::map<std::string, std::string> base_headers_;
std::map<std::string, std::string> options_;
ResourcePaths resource_paths_;
};
} // namespace paimon