blob: d92799ed5605cdbb6788783650545d6f9635f171 [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.
*/
#pragma once
#include <algorithm>
#include <any>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "graphar/arrow/chunk_writer.h"
#include "graphar/fwd.h"
#include "graphar/graph_info.h"
#include "graphar/types.h"
namespace arrow {
class Array;
}
namespace graphar::builder {
/**
* @brief Hash functor for std::pair<IdType, IdType>.
*
* This is needed because the C++ standard does not provide a default
* std::hash specialization for std::pair.
*/
struct PairIdHash {
std::size_t operator()(const std::pair<IdType, IdType>& p) const noexcept {
// Combine two 64-bit hashes: a common technique is to XOR the
// first hash with a shifted version of the second.
std::hash<IdType> h;
return h(p.first) ^ (h(p.second) << 1);
}
};
/**
* @brief Edge is designed for constructing edges builder.
*
*/
class Edge {
public:
/**
* @brief Initialize the edge with its source and destination.
*
* @param src_id The id of the source vertex.
* @param dst_id The id of the destination vertex.
*/
explicit Edge(IdType src_id, IdType dst_id)
: src_id_(src_id), dst_id_(dst_id), empty_(true) {}
/**
* @brief Check if the edge is empty.
*
* @return true/false.
*/
bool Empty() const noexcept { return empty_; }
/**
* @brief Get source id of the edge.
*
* @return The id of the source vertex.
*/
IdType GetSource() const noexcept { return src_id_; }
/**
* @brief Get destination id of the edge.
*
* @return The id of the destination vertex.
*/
IdType GetDestination() const noexcept { return dst_id_; }
/**
* @brief Add a property to the edge.
*
* @param name The name of the property.
* @param val The value of the property.
*/
// TODO(@acezen): Enable the property to be a vector(list).
void AddProperty(const std::string& name, const std::any& val) {
empty_ = false;
properties_[name] = val;
}
/**
* @brief Get a property of the edge.
*
* @param property The name of the property.
* @return The value of the property.
*/
const std::any& GetProperty(const std::string& property) const {
return properties_.at(property);
}
/**
* @brief Get all properties of the edge.
*
* @return The map containing all properties of the edge.
*/
const std::unordered_map<std::string, std::any>& GetProperties() const {
return properties_;
}
/**
* @brief Check if the edge contains a property.
*
* @param property The name of the property.
* @return true/false.
*/
bool ContainProperty(const std::string& property) const {
return (properties_.find(property) != properties_.end());
}
private:
IdType src_id_, dst_id_;
bool empty_;
std::unordered_map<std::string, std::any> properties_;
};
/**
* @brief The compare function for sorting edges by source id.
*
* @param a The first edge to compare.
* @param b The second edge to compare.
* @return If a is less than b: true/false.
*/
inline bool cmp_src(const Edge& a, const Edge& b) {
return a.GetSource() < b.GetSource();
}
/**
* @brief The compare function for sorting edges by destination id.
*
* @param a The first edge to compare.
* @param b The second edge to compare.
* @return If a is less than b: true/false.
*/
inline bool cmp_dst(const Edge& a, const Edge& b) {
return a.GetDestination() < b.GetDestination();
}
/**
* @brief EdgeBuilder is designed for building and writing a collection of
* edges.
*
*/
class EdgesBuilder {
public:
/**
* @brief Initialize the EdgesBuilder.
*
* @param edge_info The edge info that describes the vertex type.
* @param prefix The absolute prefix.
* @param adj_list_type The adj list type of the edges.
* @param num_vertices The total number of vertices for source or destination.
* @param writerOptions The writerOptions provides configuration options for
* different file format writers.
* @param validate_level The global validate level for the writer, with no
* validate by default. It could be ValidateLevel::no_validate,
* ValidateLevel::weak_validate or ValidateLevel::strong_validate, but could
* not be ValidateLevel::default_validate.
*/
explicit EdgesBuilder(
const std::shared_ptr<EdgeInfo>& edge_info, const std::string& prefix,
AdjListType adj_list_type, IdType num_vertices,
std::shared_ptr<WriterOptions> writerOptions = nullptr,
const ValidateLevel& validate_level = ValidateLevel::no_validate)
: edge_info_(std::move(edge_info)),
prefix_(prefix),
adj_list_type_(adj_list_type),
num_vertices_(num_vertices),
writer_options_(writerOptions),
validate_level_(validate_level) {
if (validate_level_ == ValidateLevel::default_validate) {
throw std::runtime_error(
"default_validate is not allowed to be set as the global validate "
"level for EdgesBuilder");
}
edges_.clear();
num_edges_ = 0;
is_saved_ = false;
switch (adj_list_type) {
case AdjListType::unordered_by_source:
vertex_chunk_size_ = edge_info_->GetSrcChunkSize();
break;
case AdjListType::ordered_by_source:
vertex_chunk_size_ = edge_info_->GetSrcChunkSize();
break;
case AdjListType::unordered_by_dest:
vertex_chunk_size_ = edge_info_->GetDstChunkSize();
break;
case AdjListType::ordered_by_dest:
vertex_chunk_size_ = edge_info_->GetDstChunkSize();
break;
default:
vertex_chunk_size_ = edge_info_->GetSrcChunkSize();
}
}
/**
* @brief Set the validate level.
*
* @param validate_level The validate level to set.
*/
void SetValidateLevel(const ValidateLevel& validate_level) {
if (validate_level == ValidateLevel::default_validate) {
return;
}
validate_level_ = validate_level;
}
/**
* @brief Set the writerOptions.
*
* @return The writerOptions provides configuration options for different file
* format writers.
*/
void SetWriterOptions(std::shared_ptr<WriterOptions> writer_options) {
this->writer_options_ = writer_options;
}
/**
* @brief Set the writerOptions.
*
* @param writerOptions The writerOptions provides configuration options for
* different file format writers.
*/
std::shared_ptr<WriterOptions> GetWriterOptions() {
return this->writer_options_;
}
/**
* @brief Get the validate level.
*
* @return The validate level of this writer.
*/
ValidateLevel GetValidateLevel() const { return validate_level_; }
/**
* @brief Clear the edges in this EdgesBuilder.
*/
void Clear() {
edges_.clear();
num_edges_ = 0;
is_saved_ = false;
}
/**
* @brief Add an edge to the collection.
*
* The validate_level for this operation could be:
*
* ValidateLevel::default_validate: to use the validate_level of the builder,
* which set through the constructor or the SetValidateLevel method;
*
* ValidateLevel::no_validate: without validation;
*
* ValidateLevel::weak_validate: to validate if the adj_list type is valid,
* and the data in builder is not saved;
*
* ValidateLevel::strong_validate: besides weak_validate, also validate the
* schema of the edge is consistent with the info defined.
*
* @param e The edge to add.
* @param validate_level The validate level for this operation,
* which is the builder's validate level by default.
* @return Status: ok or Status::Invalid error.
*/
Status AddEdge(const Edge& e, const ValidateLevel& validate_level =
ValidateLevel::default_validate) {
// validate
GAR_RETURN_NOT_OK(validate(e, validate_level));
// add an edge
IdType vertex_chunk_index = getVertexChunkIndex(e);
edges_[vertex_chunk_index].push_back(e);
num_edges_++;
return Status::OK();
}
/**
* @brief Add a property to all edges in the collection.
*
* @param property name of the property
* @param values vector of values where values[i] is mapped to the i-th edge
* in chunk-major order with size equal to the edges collection
*
* @return Status: ok or Status::Invalid error.
*/
[[nodiscard]] Status AddPropertyColumn(const std::string& property,
const std::vector<std::any>& values) {
if (static_cast<IdType>(values.size()) != num_edges_) {
return Status::Invalid(
"The size of values vector is not equal to the number of edges.");
}
IdType value = 0;
for (auto& [id, edges] : edges_) {
for (Edge& edge : edges) {
edge.AddProperty(property, values[value++]);
}
}
return Status::OK();
}
/**
* @brief Add a property to edges in the collection by (src, dst) mapping.
*
* Edges whose (src_id, dst_id) is not present in the map will not have this
* property set (written as null later).
*
* @param property name of the property
* @param values map from (src_id, dst_id) to the property value
* @return Status: ok.
*/
[[nodiscard]] Status AddPropertyColumn(
const std::string& property,
const std::unordered_map<std::pair<IdType, IdType>, std::any, PairIdHash>&
values) {
for (auto& [chunk_index, edges] : edges_) {
for (Edge& edge : edges) {
auto it = values.find({edge.GetSource(), edge.GetDestination()});
if (it != values.end()) {
edge.AddProperty(property, it->second);
}
}
}
return Status::OK();
}
/**
* @brief Get the current number of edges in the collection.
*
* @return The current number of edges in the collection.
*/
IdType GetNum() const { return num_edges_; }
/**
* @brief Dump the collection into files.
*
* @return Status: ok or error.
*/
Status Dump();
/**
* @brief Construct an EdgesBuilder from edge info.
*
* @param edge_info The edge info that describes the edge type.
* @param prefix The absolute prefix.
* @param adj_list_type The adj list type of the edges.
* @param num_vertices The total number of vertices for source or destination.
* @param writerOptions The writerOptions provides configuration options for
* different file format writers.
* @param validate_level The global validate level for the builder, default is
* no_validate.
*/
static Result<std::shared_ptr<EdgesBuilder>> Make(
const std::shared_ptr<EdgeInfo>& edge_info, const std::string& prefix,
AdjListType adj_list_type, IdType num_vertices,
std::shared_ptr<WriterOptions> writer_options,
const ValidateLevel& validate_level = ValidateLevel::no_validate) {
if (!edge_info->HasAdjacentListType(adj_list_type)) {
return Status::KeyError(
"The adjacent list type ", AdjListTypeToString(adj_list_type),
" doesn't exist in edge ", edge_info->GetEdgeType(), ".");
}
return std::make_shared<EdgesBuilder>(edge_info, prefix, adj_list_type,
num_vertices, writer_options,
validate_level);
}
static Result<std::shared_ptr<EdgesBuilder>> Make(
const std::shared_ptr<EdgeInfo>& edge_info, const std::string& prefix,
AdjListType adj_list_type, IdType num_vertices,
const ValidateLevel& validate_level = ValidateLevel::no_validate) {
if (!edge_info->HasAdjacentListType(adj_list_type)) {
return Status::KeyError(
"The adjacent list type ", AdjListTypeToString(adj_list_type),
" doesn't exist in edge ", edge_info->GetEdgeType(), ".");
}
return std::make_shared<EdgesBuilder>(edge_info, prefix, adj_list_type,
num_vertices, nullptr,
validate_level);
}
/**
* @brief Construct an EdgesBuilder from graph info.
*
* @param graph_info The graph info that describes the graph.
* @param src_type The type of the source vertex type.
* @param edge_type The type of the edge type.
* @param dst_type The type of the destination vertex type.
* @param adj_list_type The adj list type of the edges.
* @param num_vertices The total number of vertices for source or destination.
* @param validate_level The global validate level for the builder, default is
* no_validate.
*/
static Result<std::shared_ptr<EdgesBuilder>> Make(
const std::shared_ptr<GraphInfo>& graph_info, const std::string& src_type,
const std::string& edge_type, const std::string& dst_type,
const AdjListType& adj_list_type, IdType num_vertices,
std::shared_ptr<WriterOptions> writer_options,
const ValidateLevel& validate_level = ValidateLevel::no_validate) {
auto edge_info = graph_info->GetEdgeInfo(src_type, edge_type, dst_type);
if (!edge_info) {
return Status::KeyError("The edge ", src_type, " ", edge_type, " ",
dst_type, " doesn't exist.");
}
return Make(edge_info, graph_info->GetPrefix(), adj_list_type, num_vertices,
writer_options, validate_level);
}
static Result<std::shared_ptr<EdgesBuilder>> Make(
const std::shared_ptr<GraphInfo>& graph_info, const std::string& src_type,
const std::string& edge_type, const std::string& dst_type,
const AdjListType& adj_list_type, IdType num_vertices,
const ValidateLevel& validate_level = ValidateLevel::no_validate) {
auto edge_info = graph_info->GetEdgeInfo(src_type, edge_type, dst_type);
if (!edge_info) {
return Status::KeyError("The edge ", src_type, " ", edge_type, " ",
dst_type, " doesn't exist.");
}
return Make(edge_info, graph_info->GetPrefix(), adj_list_type, num_vertices,
nullptr, validate_level);
}
private:
/**
* @brief Get the vertex chunk index of a given edge.
*
* @param e The edge to add.
* @return The vertex chunk index of the edge.
*/
IdType getVertexChunkIndex(const Edge& e) {
switch (adj_list_type_) {
case AdjListType::unordered_by_source:
return e.GetSource() / vertex_chunk_size_;
case AdjListType::ordered_by_source:
return e.GetSource() / vertex_chunk_size_;
case AdjListType::unordered_by_dest:
return e.GetDestination() / vertex_chunk_size_;
case AdjListType::ordered_by_dest:
return e.GetDestination() / vertex_chunk_size_;
default:
return e.GetSource() / vertex_chunk_size_;
}
}
/**
* @brief Check if adding an edge is allowed.
*
* @param e The edge to add.
* @param validate_level The validate level for this operation.
* @return Status: ok or status::InvalidOperation error.
*/
Status validate(const Edge& e, ValidateLevel validate_level) const;
/**
* @brief Construct an array for a given property.
*
* @param type The type of the property.
* @param property_name The name of the property.
* @param array The constructed array.
* @param edges The edges of a specific vertex chunk.
* @return Status: ok or Status::TypeError error.
*/
Status appendToArray(const std::shared_ptr<DataType>& type,
const std::string& property_name,
std::shared_ptr<arrow::Array>& array, // NOLINT
const std::vector<Edge>& edges);
/**
* @brief Append the values for a property for edges in a specific vertex
* chunk into the given array.
*
* @tparam type The data type.
* @param property_name The name of the property.
* @param array The array to append.
* @param edges The edges of a specific vertex chunk.
* @return Status: ok or Status::ArrowError error.
*/
template <Type type>
Status tryToAppend(const std::string& property_name,
std::shared_ptr<arrow::Array>& array, // NOLINT
const std::vector<Edge>& edges);
/**
* @brief Append the adj list for edges in a specific vertex chunk
* into the given array.
*
* @param src_or_dest Choose to append sources or destinations.
* @param array The array to append.
* @param edges The edges of a specific vertex chunk.
* @return Status: ok or Status::ArrowError error.
*/
Status tryToAppend(int src_or_dest,
std::shared_ptr<arrow::Array>& array, // NOLINT
const std::vector<Edge>& edges);
/**
* @brief Convert the edges in a specific vertex chunk into
* an Arrow Table.
*
* @param edges The edges of a specific vertex chunk.
*/
Result<std::shared_ptr<arrow::Table>> convertToTable(
const std::vector<Edge>& edges);
/**
* @brief Construct the offset table if the adj list type is ordered.
*
* @param vertex_chunk_index The corresponding vertex chunk index.
* @param edges The edges of a specific vertex chunk.
*/
Result<std::shared_ptr<arrow::Table>> getOffsetTable(
IdType vertex_chunk_index, const std::vector<Edge>& edges);
private:
std::shared_ptr<EdgeInfo> edge_info_;
std::string prefix_;
AdjListType adj_list_type_;
std::map<IdType, std::vector<Edge>> edges_;
IdType vertex_chunk_size_;
IdType num_vertices_;
IdType num_edges_;
bool is_saved_;
std::shared_ptr<WriterOptions> writer_options_;
ValidateLevel validate_level_;
};
} // namespace graphar::builder