Skip to content

Clpp#2275

Draft
davidlion wants to merge 389 commits into
y-scope:mainfrom
davidlion:clpp
Draft

Clpp#2275
davidlion wants to merge 389 commits into
y-scope:mainfrom
davidlion:clpp

Conversation

@davidlion

Copy link
Copy Markdown
Member

Description

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

junhaoliao and others added 30 commits January 17, 2026 10:06
…ackage Docker Compose project (resolves y-scope#1747). (y-scope#1773)

Co-authored-by: sitaowang1998 <sitaowang1998@outlook.com>
…ngs to server configuration (fixes y-scope#1799). (y-scope#1803)

Co-authored-by: davemarco <83603688+davemarco@users.noreply.github.com>
…type inference (fixes y-scope#1864). (y-scope#1865)

Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>
…uling. (y-scope#1829)

Co-authored-by: Devin Gibson <gibber9809@users.noreply.github.com>
Co-authored-by: Kirk Rodrigues <2454684+kirkrodrigues@users.noreply.github.com>
m_string_type |= LiteralType::ClpStringT;
}
// if (m_v.find(' ') != std::string::npos) {
m_string_type = LiteralType::ClpStringT | LiteralType::VarStringT;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making a note that we'll need to resolve this properly before merging.

I think one thing we could do is just handle column resolution in a special way for clp+ that understands that the VarString/ClpString types are interchangeable for its purposes, and also just add a new as_string(...) method to Literal.hpp that gives you a string if the underlying is either VarString or ClpString.

The main complicating factors are:

  1. Will have to watch out for any filtering code that uses as_var_string/as_clp_string
  2. The NarrowTypes pass handles some special cases for not equals queries on predicates that can be interpreted as just ClpString or just VarString. E.g.:
    NOT msg<ClpString,VarString>: "varstring" -> msg<ClpString> exists OR (NOT msg<VarString>: "varstring")
    NOT msg<ClpString,VarString>: "clp string" -> (NOT msg<ClpString>: "clp string") OR msg<VarString> exists.

Need to think harder about whether that second point matters though.

@gibber9809 gibber9809 Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought about it a bit more -- I think it might be worth adding an option to NarrowTypes to disable the transformation into separate ClpString and VarString predicates, and use that option in the clp+ path. Since clp+ currently uses NodeType::VarString for captured variables we can produce VarString column entries that contain a space, which means that the transformation isn't even necessarily correct unless we also change column resolution to be aware of that, and even if we do make column resolution account for that we lose the perf benefit we get in clp-s from this transformation, so there's really no point in doing it for clp+.

@gibber9809 gibber9809 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read through the JsonParser ingestion path and left some comments. Going to make sure I at least also take a look at ArchiveWriter changes before I leave. Should be able to catch any format changes I'm worried about that way, which is the only kind of change I'm actually worried about for release-blocking.

} else {
node_id = m_archive_writer
->add_node(node_id_stack.top(), NodeType::VarString, cur_key);
m_current_parsed_message.add_unordered_value(value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applies to all of these blocks that check if a string contains a space before deciding how to encode it, but did we ever do the experiment where we encode all strings in clp+ using the log-mechanic path?

Only reason I'm bringing this up is that if we decide to do this later on (e.g., because handling everything with log-mechanic might make it simpler to do things like PII removal across all string fields), then we'll probably need to add a lot of special cases in the code for archive versions before and after that change.

return std::nullopt;
}

if (m_retain_float_format) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might make sense to force users to retain float formats for floats inside of strings, since we're supposed to compress string fields losslessly.

Comment on lines +1524 to +1558
auto parent_node_id{get_parent_schema_node(match.value(), log_msg_node_id)};

auto const rule_name{match->ffi_pointers.rule_name.as_cpp_view()};
auto const lexeme{match->ffi_pointers.lexeme.as_cpp_view()};

SchemaNode::id_t node_id{0};
if (0 < match->encoding_idx) {
auto const matched_encodings{m_log_surgeon_parser->get_encoding(match->encoding_idx)};
for (auto const& encoding : matched_encodings) {
if (clpp::cFloatEncodingName == encoding) {
if (auto const float_node_id{
try_add_float_value(lexeme, parent_node_id, rule_name)
};
float_node_id.has_value())
{
node_id = float_node_id.value();
break;
}
} else if (clpp::cIntEncodingName == encoding) {
if (encoded_variable_t var{0}; clp::EncodedVariableInterpreter::
convert_string_to_representable_integer_var(lexeme, var))
{
node_id = m_archive_writer
->add_node(parent_node_id, NodeType::Integer, rule_name);
m_current_parsed_message.add_unordered_value(var);
break;
}
}
}
}
if (0 == node_id) {
node_id = m_archive_writer->add_node(parent_node_id, NodeType::VarString, rule_name);
m_current_parsed_message.add_unordered_value(lexeme);
}
m_current_schema.insert_unordered(node_id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I kind of follow how the schema + MPT are being handled here, but I don't 100% get it.

MPT side I'm pretty sure I understand -- we create nodes in the MPT for parent rules with a special node type, which is more or less what I would expect.

Schema side it looks like we're adding nodes to the schema for each parent rule? If I'm interpreting the code correctly, it looks like for a rule a.b.c the schema tree becomes a -> b -> c and the schema becomes node id<a>, node id<b>, node id<c>? And if c has a sibling d (as in they're both subrules of the subrule a.b, and appear as part of the same parent match as opposed to two different instances of a.b being matched) the schema becomes node id<a>, node id<b>, node id<c>, node id<a>, node id<b>, node id<d>? Not sure if I'm completely following the code though.

The main thing I don't understand is how we figure out that c and d are siblings both for decompression, and for supporting search on parent rules to make sure we turn it into leaf queries on c and d that are part of the same parent match. If we don't currently have a good way of figuring this out, I think we'll need one once we implement a version of parent search that ensures that the resulting leaf queries target leafs from the same parent match.

I think what I would have expected is for us to use start_unordered_object/end_unordered_object for each parent rule to create schemas like:
<ParentRule, 3>, <ParentRule, 2>, node_id<c>, node_id<d>
or
<ParentRule, 5>, node_id<a>, <ParentRule, 3>, node_id<b>, node_id<c>, node_id<d>
if including the parent node IDs in the schema helps make parent search simpler.

Either way though, it lets the schema record whether some arbitrary group of leaf nodes is part of the same parent match.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're analysis was right that the code couldn't handle it.

The recent update should resolve this by switching parent rules to use start/end_unordered_object.

It was a good point that although the syntax can't expose this limitation yet, we will benefit by having the format ready so that "old" archives are usable when we support it.

@gibber9809 gibber9809 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did another round of review mostly looking through the ArchiveWriter. I think I've covered everything I'm worried about on the format side now.

Comment on lines +186 to +192
/**
* @return The root node ID of the LogType sub-tree.
* @return -1 if the sub-tree does not exist.
*/
auto get_logtype_node() -> SchemaNode::id_t {
return m_schema_tree.get_subtree_node_id(constants::cDefaultNamespace, NodeType::LogType);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: not used anymore? IIRC this is an artifact of an older version of the design.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. It should be removed now.
There might be a few of these sitting around. I still need to do a thorough pass of all changes in the branch.

);
}

if (false == m_parsing_spec_str.empty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential nit, is an empty string technically a legal schema? If so, might make sense to use optional for the parsing spec as well.

} else {
log_dict_size = m_log_dict->get_data_size();
}
return log_dict_size + m_var_dict->get_data_size() + m_array_dict->get_data_size()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that log shape stats array a meaningful size? May want to include that here as well if so.

if (compressed_size.has_error()) {
throw OperationFailed(ErrorCodeFailure, __FILENAME__, __LINE__);
}
files.emplace_back(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is pretty important -- the order that different components are packed into the single-file archive is determined by the order of entries of this files array. That means that by doing emplace_back for this and the other new sections we're putting them at the end of the archive after the ERTs. For local search that's fine, but for search from S3 that would e.g. force us to buffer the entire archive in memory so that we can read the parsing spec before we can go back and actually do search.

Generally, we want this array to be sorted in the exact order that we would access these sections during search, so that we can do early termination when streaming the archive from S3. I think we'd probably want to move the parsing spec either before or around the schema tree/schema map, and move the parent rule shapes/log shape stats before or around the dictionaries? You have a better understanding of the requirements for clpp search, so I'll leave the exact order up to you.

}

auto ArchiveReaderAdaptor::is_experimental_archive() const -> bool {
return has_section(constants::cArchiveParentRuleShapesFile);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works fine for distinguishing clp-s and clpp archives, but do you think it would also make sense to use some of the unused header bytes to indicate as much, or record it in one of the metadata packets?

log_shape.append(log_msg.substr(log_msg_pos));

auto [log_shape_id, new_log_shape]{
YSTDLIB_ERROR_HANDLING_TRYX(m_archive_writer->update_log_shape_dict(log_shape))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be missing something, but do we have to do escaping for these log shapes? I would have assumed so, but I don't see it happening in this code. If we do, it might be nice to have a class or something to represent an escaped log shape, since it's a bit hard to audit whether escaping rules are being respected when we're building up the log shape as an std::string directly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right I just hadn't gotten to it till recently and I hadn't pushed yet. This should be addressed now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants