blob: 70274edc6aaedc55cd2a8d448fd8b70316d2b467 [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 "iceberg/location_provider.h"
#include "iceberg/partition_spec.h"
#include "iceberg/table_properties.h"
#include "iceberg/util/location_util.h"
#include "iceberg/util/murmurhash3_internal.h"
namespace iceberg {
namespace {
constexpr uint8_t kEntropyDirMask = 0x0F;
constexpr uint8_t kRestDirMask = 0xFF;
constexpr int32_t kHashBits = 20;
constexpr int32_t kEntropyDirLength = 4;
constexpr int32_t kEntropyDirDepth = 3;
std::string DataLocation(const TableProperties& properties, std::string_view location) {
auto data_location = properties.Get(TableProperties::kWriteDataLocation);
if (data_location.empty()) {
data_location = std::format("{}/data", location);
}
return data_location;
}
std::string PathContext(std::string_view location) {
std::string_view path = LocationUtil::StripTrailingSlash(location);
size_t last_slash = path.find_last_of('/');
if (last_slash != std::string_view::npos && last_slash < path.length() - 1) {
std::string_view data_path = path.substr(last_slash + 1);
std::string_view parent_path(path.data(), last_slash);
size_t parent_last_slash = parent_path.find_last_of('/');
if (parent_last_slash != std::string::npos) {
std::string_view parent_name = parent_path.substr(parent_last_slash + 1);
return std::format("{}/{}", parent_name, data_path);
} else {
return std::format("{}/{}", parent_path, data_path);
}
}
return std::string(location);
}
/// \brief Divides hash into directories for optimized orphan removal operation using
/// kEntropyDirDepth and kEntropyDirLength.
///
/// If the low `kHashBits = 20` of `hash` is '10011001100110011001', then return
/// '1001/1001/1001/10011001' with depth 3 and length 4.
///
/// \param hash The hash value to be divided.
/// \return The path according to the `hash` value.
std::string DirsFromHash(int32_t hash) {
std::string hash_with_dirs;
for (int32_t i = 0; i < kEntropyDirDepth * kEntropyDirLength; i += kEntropyDirLength) {
if (i > 0) {
hash_with_dirs += "/";
}
uint8_t dir_bits = kEntropyDirMask & (hash >> (kHashBits - i - kEntropyDirLength));
hash_with_dirs += std::format("{:04b}", dir_bits);
}
hash_with_dirs += "/";
uint8_t rest_bits = kRestDirMask & hash;
hash_with_dirs += std::format("{:08b}", rest_bits);
return hash_with_dirs;
}
std::string ComputeHash(std::string_view file_name) {
return DirsFromHash(
static_cast<int32_t>(MurmurHash3_x86_32(file_name.data(), file_name.size(), 0)));
}
} // namespace
// Default location provider for local file system.
class DefaultLocationProvider : public LocationProvider {
public:
DefaultLocationProvider(std::string_view location, const TableProperties& properties);
std::string NewDataLocation(std::string_view filename) override;
Result<std::string> NewDataLocation(const PartitionSpec& spec,
const PartitionValues& partition,
std::string_view filename) override;
private:
std::string data_location_;
};
// Implementation of DefaultLocationProvider
DefaultLocationProvider::DefaultLocationProvider(std::string_view location,
const TableProperties& properties)
: data_location_(
LocationUtil::StripTrailingSlash(DataLocation(properties, location))) {}
std::string DefaultLocationProvider::NewDataLocation(std::string_view filename) {
return std::format("{}/{}", data_location_, filename);
}
Result<std::string> DefaultLocationProvider::NewDataLocation(
const PartitionSpec& spec, const PartitionValues& partition,
std::string_view filename) {
ICEBERG_ASSIGN_OR_RAISE(auto partition_path, spec.PartitionPath(partition));
return std::format("{}/{}/{}", data_location_, partition_path, filename);
}
// Location provider for object stores.
class ObjectStoreLocationProvider : public LocationProvider {
public:
ObjectStoreLocationProvider(std::string_view location,
const TableProperties& properties);
std::string NewDataLocation(std::string_view filename) override;
Result<std::string> NewDataLocation(const PartitionSpec& spec,
const PartitionValues& partition,
std::string_view filename) override;
private:
std::string storage_location_;
std::string context_;
bool include_partition_paths_;
};
// Implementation of ObjectStoreLocationProvider
ObjectStoreLocationProvider::ObjectStoreLocationProvider(
std::string_view location, const TableProperties& properties)
: include_partition_paths_(
properties.Get(TableProperties::kWriteObjectStorePartitionedPaths)) {
storage_location_ =
LocationUtil::StripTrailingSlash(DataLocation(properties, location));
// If the storage location is within the table prefix, don't add table and database name
// context
if (!storage_location_.starts_with(location)) {
context_ = PathContext(location);
}
}
std::string ObjectStoreLocationProvider::NewDataLocation(std::string_view filename) {
std::string hash = ComputeHash(filename);
if (!context_.empty()) {
return std::format("{}/{}/{}/{}", storage_location_, hash, context_, filename);
} else {
// If partition paths are included, add last part of entropy as dir before partition
// names
if (include_partition_paths_) {
return std::format("{}/{}/{}", storage_location_, hash, filename);
} else {
// If partition paths are not included, append last part of entropy with `-` to file
// name
return std::format("{}/{}-{}", storage_location_, hash, filename);
}
}
}
Result<std::string> ObjectStoreLocationProvider::NewDataLocation(
const PartitionSpec& spec, const PartitionValues& partition,
std::string_view filename) {
if (include_partition_paths_) {
ICEBERG_ASSIGN_OR_RAISE(auto partition_path, spec.PartitionPath(partition));
return NewDataLocation(std::format("{}/{}", partition_path, filename));
} else {
return NewDataLocation(filename);
}
}
Result<std::unique_ptr<LocationProvider>> LocationProvider::Make(
std::string_view location, const TableProperties& properties) {
location = LocationUtil::StripTrailingSlash(location);
// TODO(xxx): create location provider specified by "write.location-provider.impl"
if (properties.Get(TableProperties::kObjectStoreEnabled)) {
return std::make_unique<ObjectStoreLocationProvider>(location, properties);
} else {
return std::make_unique<DefaultLocationProvider>(location, properties);
}
}
} // namespace iceberg