-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ffi: Add class for key-value pair log events. #507
Changes from 28 commits
0da3f44
141f954
f3525aa
bbd3a56
b1fa61c
e148af7
f41e5d6
d29a17a
12679f7
534abf0
b7bed71
88aa36a
92c3388
698b406
bc565f5
2393038
285aab3
7623d76
ef0dd80
54ec36a
941fcbc
6623b5a
3ae389a
a066c67
353dccd
09dd01a
83ae4e0
7cd1ea7
6dd95a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
#include "KeyValuePairLogEvent.hpp" | ||
|
||
#include <memory> | ||
#include <string> | ||
#include <string_view> | ||
#include <system_error> | ||
#include <unordered_map> | ||
#include <unordered_set> | ||
#include <utility> | ||
#include <vector> | ||
|
||
#include <outcome/single-header/outcome.hpp> | ||
|
||
#include "../ir/EncodedTextAst.hpp" | ||
#include "../time_types.hpp" | ||
#include "SchemaTree.hpp" | ||
#include "SchemaTreeNode.hpp" | ||
#include "Value.hpp" | ||
|
||
using clp::ir::EightByteEncodedTextAst; | ||
using clp::ir::FourByteEncodedTextAst; | ||
using std::string; | ||
|
||
namespace clp::ffi { | ||
namespace { | ||
/** | ||
* @param type | ||
* @param value | ||
* @return Whether the given schema tree node type matches the given value's type. | ||
*/ | ||
[[nodiscard]] auto | ||
node_type_matches_value_type(SchemaTreeNode::Type type, Value const& value) -> bool; | ||
|
||
/** | ||
* Validates whether the given node-ID value pairs are leaf nodes in the `SchemaTree` forming a | ||
* sub-tree of their own. | ||
* @param schema_tree | ||
* @param node_id_value_pairs | ||
* @return success if the inputs are valid, or an error code indicating the failure: | ||
* - std::errc::operation_not_permitted if a node ID doesn't represent a valid node in the | ||
* schema tree, or a non-leaf node ID is paired with a value. | ||
* - std::errc::protocol_error if the schema tree node type doesn't match the value's type. | ||
* - std::errc::protocol_not_supported if the same key appears more than once under a parent | ||
* node. | ||
*/ | ||
[[nodiscard]] auto validate_node_id_value_pairs( | ||
SchemaTree const& schema_tree, | ||
KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs | ||
) -> std::errc; | ||
|
||
/** | ||
* @param schema_tree | ||
* @param node_id | ||
* @param node_id_value_pairs | ||
* @return Whether the given node is a leaf node in the sub-tree of the `SchemaTree` defined by | ||
* `node_id_value_pairs`. A node is considered a leaf if none of its descendants appear in | ||
* `node_id_value_pairs`. | ||
*/ | ||
[[nodiscard]] auto is_leaf_node( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disclaimer: I only have a rough understanding on how schema tree works. In addition, "the sub schema tree defined by And lastly a highlevel comment is that I feel this function should belong to the Schema tree class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a validation method: if an object type node appears in the key value pairs, it must be a leaf node. The way we store key value pairs is to store the schema (all as leaf nodes), so the path from the root to the leaves are implicitly indicated. In the merged schema tree, usually an object type node is a non leaf node. But in a key value pairs' schema, this node can be a leaf node as long as its value is |
||
SchemaTree const& schema_tree, | ||
SchemaTreeNode::id_t node_id, | ||
KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs | ||
) -> bool; | ||
|
||
auto node_type_matches_value_type(SchemaTreeNode::Type type, Value const& value) -> bool { | ||
switch (type) { | ||
case SchemaTreeNode::Type::Obj: | ||
return value.is_null(); | ||
case SchemaTreeNode::Type::Int: | ||
return value.is<value_int_t>(); | ||
case SchemaTreeNode::Type::Float: | ||
return value.is<value_float_t>(); | ||
case SchemaTreeNode::Type::Bool: | ||
return value.is<value_bool_t>(); | ||
case SchemaTreeNode::Type::UnstructuredArray: | ||
return value.is<FourByteEncodedTextAst>() || value.is<EightByteEncodedTextAst>(); | ||
case SchemaTreeNode::Type::Str: | ||
return value.is<string>() || value.is<FourByteEncodedTextAst>() | ||
|| value.is<EightByteEncodedTextAst>(); | ||
default: | ||
return false; | ||
} | ||
} | ||
|
||
auto validate_node_id_value_pairs( | ||
SchemaTree const& schema_tree, | ||
KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs | ||
) -> std::errc { | ||
try { | ||
std::unordered_map<SchemaTreeNode::id_t, std::unordered_set<std::string_view>> | ||
parent_node_id_to_key_names; | ||
for (auto const& [node_id, value] : node_id_value_pairs) { | ||
if (SchemaTree::cRootId == node_id) { | ||
return std::errc::operation_not_permitted; | ||
} | ||
|
||
auto const& node{schema_tree.get_node(node_id)}; | ||
auto const node_type{node.get_type()}; | ||
if (false == value.has_value()) { | ||
// Value is an empty object (`{}`, which is not the same as `null`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this comment tries to explain why the line doesn't use And we can combine the two if statements? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still don't get when there will be {} and when there will be null (and why we only check for {} but not null in this function) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (SchemaTreeNode::Type::Obj != node_type) { | ||
return std::errc::protocol_error; | ||
} | ||
} else if (false == node_type_matches_value_type(node_type, value.value())) { | ||
return std::errc::protocol_error; | ||
} | ||
|
||
if (SchemaTreeNode::Type::Obj == node_type | ||
&& false == is_leaf_node(schema_tree, node_id, node_id_value_pairs)) | ||
{ | ||
// The node's value is `null` or `{}` but its descendants appear in | ||
// `node_id_value_pairs`. | ||
return std::errc::operation_not_permitted; | ||
} | ||
|
||
auto const parent_node_id{node.get_parent_id()}; | ||
auto const key_name{node.get_key_name()}; | ||
if (parent_node_id_to_key_names.contains(parent_node_id)) { | ||
if (parent_node_id_to_key_names.at(parent_node_id).contains(key_name)) { | ||
// The key is duplicated under the same parent | ||
return std::errc::protocol_not_supported; | ||
} | ||
} else { | ||
parent_node_id_to_key_names.emplace(parent_node_id, std::unordered_set{key_name}); | ||
} | ||
} | ||
} catch (SchemaTree::OperationFailed const& ex) { | ||
return std::errc::operation_not_permitted; | ||
} | ||
return std::errc{}; | ||
} | ||
|
||
auto is_leaf_node( | ||
SchemaTree const& schema_tree, | ||
SchemaTreeNode::id_t node_id, | ||
KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs | ||
) -> bool { | ||
std::vector<SchemaTreeNode::id_t> dfs_stack; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the next PR, you will see that I'm actually using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, was just asking. |
||
dfs_stack.reserve(schema_tree.get_size()); | ||
dfs_stack.push_back(node_id); | ||
while (false == dfs_stack.empty()) { | ||
auto const curr_node_id{dfs_stack.back()}; | ||
dfs_stack.pop_back(); | ||
for (auto const child_node_id : schema_tree.get_node(curr_node_id).get_children_ids()) { | ||
if (node_id_value_pairs.contains(child_node_id)) { | ||
return false; | ||
} | ||
dfs_stack.push_back(child_node_id); | ||
} | ||
} | ||
return true; | ||
} | ||
} // namespace | ||
|
||
auto KeyValuePairLogEvent::create( | ||
std::shared_ptr<SchemaTree const> schema_tree, | ||
NodeIdValuePairs node_id_value_pairs, | ||
UtcOffset utc_offset | ||
) -> OUTCOME_V2_NAMESPACE::std_result<KeyValuePairLogEvent> { | ||
if (auto const ret_val{validate_node_id_value_pairs(*schema_tree, node_id_value_pairs)}; | ||
std::errc{} != ret_val) | ||
{ | ||
return ret_val; | ||
} | ||
return KeyValuePairLogEvent{std::move(schema_tree), std::move(node_id_value_pairs), utc_offset}; | ||
} | ||
} // namespace clp::ffi |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#ifndef CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP | ||
#define CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP | ||
|
||
#include <memory> | ||
#include <optional> | ||
#include <unordered_map> | ||
#include <utility> | ||
|
||
#include <outcome/single-header/outcome.hpp> | ||
|
||
#include "../time_types.hpp" | ||
#include "SchemaTree.hpp" | ||
#include "SchemaTreeNode.hpp" | ||
#include "Value.hpp" | ||
|
||
namespace clp::ffi { | ||
/** | ||
* A log event containing key-value pairs. Each event contains: | ||
* - A collection of node-ID & value pairs, where each pair represents a leaf `SchemaTreeNode` in | ||
* the `SchemaTree`. | ||
* - A reference to the `SchemaTree` | ||
* - The UTC offset of the current log event | ||
*/ | ||
class KeyValuePairLogEvent { | ||
public: | ||
// Types | ||
using NodeIdValuePairs = std::unordered_map<SchemaTreeNode::id_t, std::optional<Value>>; | ||
|
||
// Factory functions | ||
/** | ||
* @param schema_tree | ||
* @param node_id_value_pairs | ||
* @param utc_offset | ||
* @return A result containing the key-value pair log event or an error code indicating the | ||
* failure. See `valdiate_node_id_value_pairs` for the possible error codes. | ||
*/ | ||
[[nodiscard]] static auto create( | ||
std::shared_ptr<SchemaTree const> schema_tree, | ||
NodeIdValuePairs node_id_value_pairs, | ||
UtcOffset utc_offset | ||
) -> OUTCOME_V2_NAMESPACE::std_result<KeyValuePairLogEvent>; | ||
|
||
// Disable copy constructor and assignment operator | ||
KeyValuePairLogEvent(KeyValuePairLogEvent const&) = delete; | ||
auto operator=(KeyValuePairLogEvent const&) -> KeyValuePairLogEvent& = delete; | ||
|
||
// Default move constructor and assignment operator | ||
KeyValuePairLogEvent(KeyValuePairLogEvent&&) = default; | ||
auto operator=(KeyValuePairLogEvent&&) -> KeyValuePairLogEvent& = default; | ||
|
||
// Destructor | ||
~KeyValuePairLogEvent() = default; | ||
haiqi96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Methods | ||
[[nodiscard]] auto get_schema_tree() const -> SchemaTree const& { return *m_schema_tree; } | ||
|
||
[[nodiscard]] auto get_node_id_value_pairs() const -> NodeIdValuePairs const& { | ||
return m_node_id_value_pairs; | ||
} | ||
|
||
[[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } | ||
|
||
private: | ||
// Constructor | ||
KeyValuePairLogEvent( | ||
haiqi96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
std::shared_ptr<SchemaTree const> schema_tree, | ||
NodeIdValuePairs node_id_value_pairs, | ||
UtcOffset utc_offset | ||
) | ||
: m_schema_tree{std::move(schema_tree)}, | ||
m_node_id_value_pairs{std::move(node_id_value_pairs)}, | ||
m_utc_offset{utc_offset} {} | ||
|
||
// Variables | ||
std::shared_ptr<SchemaTree const> m_schema_tree; | ||
NodeIdValuePairs m_node_id_value_pairs; | ||
UtcOffset m_utc_offset{0}; | ||
}; | ||
} // namespace clp::ffi | ||
|
||
#endif // CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A non-leaf node paired with a value actually returns
protocol_not_supported
. That said, I think it should returnoperation_not_permitted
since if it was permitted,node_id_value_pairs
wouldn't be a tree.