blob: cf60970db2040cf70969b0aa689aa6be4a3e57cd [file]
/*
* 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/format/lance/lance_utils.h"
#include <cstring>
#include <map>
#include <optional>
#include "paimon/common/utils/path_util.h"
#include "paimon/common/utils/string_utils.h"
#include "paimon/format/lance/lance_ffi.h"
namespace paimon::lance {
namespace {
void CopyOption(const std::map<std::string, std::string>& options, const std::string& source,
const std::string& target, std::map<std::string, std::string>* storage_options) {
auto iter = options.find(source);
if (iter != options.end() && !iter->second.empty()) {
storage_options->insert_or_assign(target, iter->second);
}
}
const char* CanonicalS3OptionName(const std::string& option) {
if (option == "access-key" || option == "access.key" || option == "accessKeyId") {
return "s3.access-key";
}
if (option == "secret-key" || option == "secret.key" || option == "accessKeySecret") {
return "s3.secret-key";
}
if (option == "session.token" || option == "session-token" || option == "security.token" ||
option == "security-token" || option == "securityToken") {
return "s3.session.token";
}
if (option == "endpoint") {
return "s3.endpoint";
}
if (option == "region") {
return "s3.region";
}
if (option == "path-style-access" || option == "path.style.access") {
return "s3.path-style-access";
}
return nullptr;
}
std::map<std::string, std::string> NormalizeS3Options(
const std::map<std::string, std::string>& options) {
std::map<std::string, std::string> normalized = options;
for (const auto& [key, value] : options) {
for (const char* prefix : {"s3a.", "fs.s3.", "fs.s3a.", "s3."}) {
if (!StringUtils::StartsWith(key, prefix)) {
continue;
}
const char* canonical = CanonicalS3OptionName(key.substr(std::strlen(prefix)));
if (canonical != nullptr && normalized.find(canonical) == normalized.end()) {
normalized.emplace(canonical, value);
}
break;
}
}
return normalized;
}
void AddS3Options(const std::map<std::string, std::string>& options,
std::map<std::string, std::string>* storage_options) {
const std::map<std::string, std::string> normalized = NormalizeS3Options(options);
CopyOption(normalized, "s3.region", "region", storage_options);
CopyOption(normalized, "s3.endpoint", "endpoint", storage_options);
CopyOption(normalized, "s3.access-key", "access_key_id", storage_options);
CopyOption(normalized, "s3.secret-key", "secret_access_key", storage_options);
CopyOption(normalized, "s3.session.token", "session_token", storage_options);
auto endpoint = storage_options->find("endpoint");
if (endpoint != storage_options->end() && endpoint->second.find("://") == std::string::npos) {
endpoint->second = "https://" + endpoint->second;
}
auto path_style = normalized.find("s3.path-style-access");
if (path_style != normalized.end()) {
std::optional<bool> enabled = StringUtils::StringToValue<bool>(path_style->second);
if (enabled) {
storage_options->insert_or_assign("virtual_hosted_style_request",
*enabled ? "false" : "true");
}
}
}
void AddOssOptions(const std::map<std::string, std::string>& options, const std::string& bucket,
std::map<std::string, std::string>* storage_options) {
auto copy_oss_option = [&](const std::string& suffix, const std::string& target) {
CopyOption(options, "fs.oss." + suffix, target, storage_options);
if (!bucket.empty()) {
CopyOption(options, "fs.oss.bucket." + bucket + "." + suffix, target, storage_options);
}
};
// lance-io 0.39 uses OpenDAL for oss://. It consumes oss_endpoint and adds
// the bucket itself; Java's S3 endpoint/virtual-host options are unused here.
copy_oss_option("endpoint", "oss_endpoint");
copy_oss_option("region", "oss_region");
copy_oss_option("accessKeyId", "oss_access_key_id");
copy_oss_option("accessKeySecret", "oss_secret_access_key");
copy_oss_option("sessionToken", "oss_session_token");
copy_oss_option("securityToken", "oss_session_token");
copy_oss_option("accessKeyId", "access_key_id");
copy_oss_option("accessKeySecret", "secret_access_key");
copy_oss_option("sessionToken", "session_token");
copy_oss_option("securityToken", "session_token");
auto endpoint = storage_options->find("oss_endpoint");
if (endpoint != storage_options->end() && endpoint->second.find("://") == std::string::npos) {
endpoint->second = "https://" + endpoint->second;
}
}
} // namespace
LanceStorageOptions GetLanceStorageOptions(const std::map<std::string, std::string>& options,
const std::string& uri) {
std::map<std::string, std::string> storage_options;
Result<Path> parsed_path = PathUtil::ToPath(uri);
if (parsed_path.ok() && parsed_path.value().scheme == "s3") {
AddS3Options(options, &storage_options);
} else if (parsed_path.ok() && parsed_path.value().scheme == "oss") {
AddOssOptions(options, parsed_path.value().authority, &storage_options);
}
for (const auto& [key, value] : options) {
if (key.rfind(kLanceStorageOptionPrefix, 0) == 0) {
storage_options.insert_or_assign(key.substr(sizeof(kLanceStorageOptionPrefix) - 1),
value);
}
}
auto endpoint = storage_options.find(
parsed_path.ok() && parsed_path.value().scheme == "oss" ? "oss_endpoint" : "endpoint");
if (storage_options.find("allow_http") == storage_options.end() &&
endpoint != storage_options.end() && StringUtils::StartsWith(endpoint->second, "http://")) {
storage_options.emplace("allow_http", "true");
}
return LanceStorageOptions(storage_options.begin(), storage_options.end());
}
Status LanceFfiError(const std::string& operation) {
const char* error = paimon_lance_last_error();
return Status::Invalid(operation, ": ", error == nullptr ? "unknown Lance error" : error);
}
} // namespace paimon::lance