Motivation
A user scanning ~250k records needed to find ones where a subfield did NOT match a regex. Currently this requires Python-side filtering (not re.match(...)) which means:
- Extra string copies across the Rust/Python FFI boundary for every record
- GIL overhead on each field value that crosses into Python
- Inability to short-circuit inside Rust iteration
Adding a negate flag pushes the inversion into Rust so non-matching fields never cross the FFI boundary.
Design
Follow the existing partial flag pattern on SubfieldValueQuery — add a negate: bool field defaulting to false.
Rust core (src/field_query.rs)
- Add
pub negate: bool to SubfieldPatternQuery
- Update
new() to set negate: false
- Add
pub fn negated(tag, subfield_code, pattern) -> Result<Self> constructor (sets negate: true)
- Update
matches(): use != self.negate so that when negated, a match means "subfield exists but does NOT match the pattern"
PyO3 bindings (src-python/src/query.rs)
- Add
negate as an optional keyword argument to PySubfieldPatternQuery::new() (default False)
- Add
#[getter] fn negate(&self) -> bool
- Update
__repr__ to include negate=true when set
Python stubs (mrrc/_mrrc.pyi)
- Update
SubfieldPatternQuery.__init__ signature: add negate: bool = False
- Add
negate property
Tests
- Rust unit tests in
src/field_query.rs: negated match, negated non-match, negated with missing subfield (should return false — subfield must exist)
- Python tests in
tests/python/test_query_dsl.py: negated pattern query via kwarg, verify negate property, integration with fields_matching_pattern()
Documentation
- Update
docs/guides/query-dsl.md with negation examples
- Update
docs/tutorials/python/querying-fields.md if SubfieldPatternQuery is covered there
- Update API reference docs if they document SubfieldPatternQuery parameters
Edge cases
- Missing subfield: When
negate=true and the subfield doesn't exist on a field, matches() returns false. Negation means "the subfield exists but its value doesn't match" — not "the subfield is absent."
- Repeating subfields:
get_subfield() returns only the first subfield with the given code. With negate=true, only the first value is checked. This is consistent with existing non-negated behavior but should be documented.
matches() implementation
field.get_subfield(self.subfield_code)
.is_some_and(|value| self.pattern.is_match(value) != self.negate)
Scope
This issue covers the negate flag on SubfieldPatternQuery only. SubfieldValueQuery negation is tracked separately. A broader query composition framework (NotQuery, AndQuery, OrQuery) is a separate concern.
Bead: bd-w9cm
Motivation
A user scanning ~250k records needed to find ones where a subfield did NOT match a regex. Currently this requires Python-side filtering (
not re.match(...)) which means:Adding a
negateflag pushes the inversion into Rust so non-matching fields never cross the FFI boundary.Design
Follow the existing
partialflag pattern onSubfieldValueQuery— add anegate: boolfield defaulting tofalse.Rust core (
src/field_query.rs)pub negate: booltoSubfieldPatternQuerynew()to setnegate: falsepub fn negated(tag, subfield_code, pattern) -> Result<Self>constructor (setsnegate: true)matches(): use!= self.negateso that when negated, a match means "subfield exists but does NOT match the pattern"PyO3 bindings (
src-python/src/query.rs)negateas an optional keyword argument toPySubfieldPatternQuery::new()(defaultFalse)#[getter] fn negate(&self) -> bool__repr__to includenegate=truewhen setPython stubs (
mrrc/_mrrc.pyi)SubfieldPatternQuery.__init__signature: addnegate: bool = FalsenegatepropertyTests
src/field_query.rs: negated match, negated non-match, negated with missing subfield (should return false — subfield must exist)tests/python/test_query_dsl.py: negated pattern query via kwarg, verifynegateproperty, integration withfields_matching_pattern()Documentation
docs/guides/query-dsl.mdwith negation examplesdocs/tutorials/python/querying-fields.mdif SubfieldPatternQuery is covered thereEdge cases
negate=trueand the subfield doesn't exist on a field,matches()returnsfalse. Negation means "the subfield exists but its value doesn't match" — not "the subfield is absent."get_subfield()returns only the first subfield with the given code. Withnegate=true, only the first value is checked. This is consistent with existing non-negated behavior but should be documented.matches() implementation
Scope
This issue covers the
negateflag onSubfieldPatternQueryonly.SubfieldValueQuerynegation is tracked separately. A broader query composition framework (NotQuery, AndQuery, OrQuery) is a separate concern.Bead: bd-w9cm