Blooms: case-insensitive regex literals prune matching structured metadata
Describe the bug
When Bloom Gateway filtering is enabled, a pipeline label filter with a case-insensitive regex literal can return no lines even though the structured-metadata value matches.
{app="a"} | device_id=~"(?i)a66347031de50be5"The same filter works without blooms, and these variants also work with blooms:
{app="a"} | device_id=~"a66347031de50be5" # case-sensitive literal
{app="a"} | device_id=~"(?i)a66347031de50be5.*" # extra wildcard
{app="a"} | device_id=~"(?i)a66347031de50be5$" # explicit anchor
{app="a"} | device_id=~"^(?i)a66347031de50be5" # explicit anchorTo Reproduce
- Loki with bloom creation and Bloom Gateway filtering enabled for the tenant.
- Ingest a log with structured metadata
device_id="a66347031de50be5"(lowercase). Wait until a bloom is built for that chunk. - Query:
{app="a"} | device_id=~"(?i)a66347031de50be5"- Observed: 0 lines.
- Repeat with
| device_id=~"a66347031de50be5"or| device_id=~"(?i)a66347031de50be5.*". Those return the line.
Without blooms, pkg/logql / logqltest already treats (?i)a66347031de50be5 as an equality match (with case folding) and returns both a66347031de50be5 and A66347031DE50BE5.
Expected behavior
| device_id=~"(?i)a66347031de50be5" should return every line whose device_id value equals that hex string, ignoring case. Bloom filtering should not drop a chunk that contains a case-folded match.
Cause
ExtractTestableLabelMatchers in pkg/storage/bloom/v1/ast_extractor.go simplifies (?i)a66347031de50be5 to a KeyValueMatcher whose Value is the regex engine's canonical literal:
case regexsyn.OpLiteral:
return KeyValueMatcher{
Key: key,
Value: string(reg.Rune),
}regexp/syntax with FoldCase stores that literal in uppercase (A66347031DE50BE5). Blooms are built from the stored metadata as-is (a66347031de50be5), so the bloom test misses and the gateway prunes the chunk.
Patterns that do not simplify to a bare OpLiteral (.*, ^, $, character classes) become UnsupportedLabelMatcher, for which bloom tests always pass. That is why adding a wildcard or anchor "fixes" the query.
Confirmed with:
{app="a"} | device_id=~"a66347031de50be5" → KeyValueMatcher{Key:"device_id", Value:"a66347031de50be5"}
{app="a"} | device_id=~"(?i)a66347031de50be5" → KeyValueMatcher{Key:"device_id", Value:"A66347031DE50BE5"}
{app="a"} | device_id=~"(?i)a66347031de50be5.*" → UnsupportedLabelMatcher{}
{app="a"} | device_id=~"(?i)a66347031de50be5$" → UnsupportedLabelMatcher{}
{app="a"} | device_id=~"^(?i)a66347031de50be5" → UnsupportedLabelMatcher{}Workaround
Disable bloom_gateway_enable_filtering for the tenant, or rewrite the filter so the extractor cannot turn it into a case-folded equality ((?i)….*, ^, $).
Source: grafana/loki