A term query with CIDR notation on an ip field matches docs in the memtable but returns zero after they flush to a segment — a silent flush-divergence (same query, different results before/after flush), the #413/#423 pattern. range on the CIDR bounds works on both paths, so the data is fine; only the CIDR term breaks on the segment.
Reproduced (current main / rc.70, own node, non-9200)
Index ip-mapped addr with 192.168.1.5, .200, .0, .255, 10.0.0.1, 192.168.2.5, then a fresh 192.168.1.77.
# memtable (fresh doc, not force-merged):
term addr=192.168.1.0/24 -> matches 192.168.1.77 ✅
# after _flush + _forcemerge:
term addr=192.168.1.0/24 -> [] ❌ (should be all 192.168.1.x)
# range still works on both:
range addr gte 192.168.1.0 lte 192.168.1.255 -> all 192.168.1.x ✅
Also 0 after flush for /16, /25, /32. Exact term addr=192.168.1.5 works (in dict).
Cause
The memtable fast path try_doc_values_query (index.rs:35183) and the mem-bool path (35116) explicitly BAIL on a /-containing value (return None) → fall back to the source-scan matcher doc_matches_query, whose Term arm handles CIDR via ip_matches_cidr (index.rs:36041/40495). The segment term path does an exact term-dictionary lookup for the literal "192.168.1.0/24", finds nothing (the dict holds exact IPs), and returns 0 without the same CIDR fallback. ES answers CIDR term on an ip field as the subnet range.
Fix direction (two options, both decision-free)
- Rewrite a
Term{ip_field, "a.b.c.d/n"} into Range{gte: network, lte: broadcast} at query build/normalization where the schema is available — range already works on both memtable and segment, so this fixes it uniformly.
- Make the segment term path route a CIDR value on an ip field to the source-scan
doc_matches_query/ip_matches_cidr fallback, mirroring what the memtable fast paths already do by bailing.
Found via a proactive probe. Will take (1) or (2) with a fail-before segment test (memtable matches, segment 0) once the exact segment term-resolution site is pinned.
A
termquery with CIDR notation on anipfield matches docs in the memtable but returns zero after they flush to a segment — a silent flush-divergence (same query, different results before/after flush), the #413/#423 pattern.rangeon the CIDR bounds works on both paths, so the data is fine; only the CIDRtermbreaks on the segment.Reproduced (current main / rc.70, own node, non-9200)
Index
ip-mappedaddrwith 192.168.1.5, .200, .0, .255, 10.0.0.1, 192.168.2.5, then a fresh 192.168.1.77.Also 0 after flush for
/16,/25,/32. Exactterm addr=192.168.1.5works (in dict).Cause
The memtable fast path
try_doc_values_query(index.rs:35183) and the mem-bool path (35116) explicitly BAIL on a/-containing value (return None) → fall back to the source-scan matcherdoc_matches_query, whoseTermarm handles CIDR viaip_matches_cidr(index.rs:36041/40495). The segment term path does an exact term-dictionary lookup for the literal"192.168.1.0/24", finds nothing (the dict holds exact IPs), and returns 0 without the same CIDR fallback. ES answers CIDRtermon an ip field as the subnet range.Fix direction (two options, both decision-free)
Term{ip_field, "a.b.c.d/n"}intoRange{gte: network, lte: broadcast}at query build/normalization where the schema is available —rangealready works on both memtable and segment, so this fixes it uniformly.doc_matches_query/ip_matches_cidrfallback, mirroring what the memtable fast paths already do by bailing.Found via a proactive probe. Will take (1) or (2) with a fail-before segment test (memtable matches, segment 0) once the exact segment term-resolution site is pinned.