Filed unassigned by the #13553 dev while sweeping all 18 declared operators across a NUMERIC fixture. Recording only — no severity asserted, routing is triage's.
Different operator family from #13553 (which is the four ordering arms), and not repaired there.
Measured
Executed on 8b04c75d7d and again on 43b799bc66, on both faces of packages/drivers/driver-memory, identical in both runs and unmoved by #13553's repair. Fixture:
{id:'1', n:5} {id:'2', n:0} {id:'3', n:null} {id:'4'} (key ABSENT)
| filter |
live mingo path |
reference matcher |
|
{n: {$notContains: '0'}} |
['1','2','3','4'] |
['3','4'] |
DISAGREE |
{n: {$notContains: '5'}} |
['1','2','3','4'] |
['3','4'] |
DISAGREE |
{n: {$contains: '5'}} |
[] |
[] |
agree |
The two VALUED rows are the disagreement. Rows 3 and 4 (no value) already match on both faces since #13166.
Cause
packages/drivers/driver-memory/src/memory-matcher.ts, the $notContains arm, after its no-value branch:
if (typeof value !== 'string' || value.includes(target)) return false;
The typeof test is a TYPE test standing in for the predicate. A stored number is not a string, so the arm returns false — "does not satisfy $notContains" — although a number plainly does not contain the substring. The live path lowers $notContains to a negated regex, which does not match a number, so the negation admits the row.
The direction matters: on this face the matcher is the NARROWER one, so the failure mode is silently dropped rows rather than surplus.
Why the positive twins agree and this one does not
$contains / $startsWith / $endsWith / $icontains / $like / $ilike are all guarded by the same typeof value !== 'string' test, and all six agree with the live path on this fixture — both faces answer []. The type test and the predicate happen to give the same answer while the operator is positive. Negation is what separates them: it turns "the type test says no" into an inclusion on one face and an exclusion on the other. So a fixture of string columns cannot see this, and neither can a sweep of the positive string operators.
Note for triage
The docblock over the arm already records that "a present, non-string value keeps the answer it had" — deliberately, when #13166 moved the no-value cells. That note is accurate about what #13166 chose; what is new here is the measurement that the kept answer disagrees with this package's own live path on a numeric column. Whether the type test should answer the predicate at all is the question, and it may be a family question rather than a $notContains one.
Related
#13553 (the ordering arms on the same fixture, repaired in PR #14077) · #13166 (the no-value cells of this same arm) · #5374 ($contains, the last time this face and the live one answered one filter two ways)
Filed unassigned by the #13553 dev while sweeping all 18 declared operators across a NUMERIC fixture. Recording only — no severity asserted, routing is triage's.
Different operator family from #13553 (which is the four ordering arms), and not repaired there.
Measured
Executed on
8b04c75d7dand again on43b799bc66, on both faces ofpackages/drivers/driver-memory, identical in both runs and unmoved by #13553's repair. Fixture:{n: {$notContains: '0'}}['1','2','3','4']['3','4']{n: {$notContains: '5'}}['1','2','3','4']['3','4']{n: {$contains: '5'}}[][]The two VALUED rows are the disagreement. Rows 3 and 4 (no value) already match on both faces since #13166.
Cause
packages/drivers/driver-memory/src/memory-matcher.ts, the$notContainsarm, after its no-value branch:The
typeoftest is a TYPE test standing in for the predicate. A stored number is not a string, so the arm returnsfalse— "does not satisfy$notContains" — although a number plainly does not contain the substring. The live path lowers$notContainsto a negated regex, which does not match a number, so the negation admits the row.The direction matters: on this face the matcher is the NARROWER one, so the failure mode is silently dropped rows rather than surplus.
Why the positive twins agree and this one does not
$contains/$startsWith/$endsWith/$icontains/$like/$ilikeare all guarded by the sametypeof value !== 'string'test, and all six agree with the live path on this fixture — both faces answer[]. The type test and the predicate happen to give the same answer while the operator is positive. Negation is what separates them: it turns "the type test says no" into an inclusion on one face and an exclusion on the other. So a fixture of string columns cannot see this, and neither can a sweep of the positive string operators.Note for triage
The docblock over the arm already records that "a present, non-string value keeps the answer it had" — deliberately, when #13166 moved the no-value cells. That note is accurate about what #13166 chose; what is new here is the measurement that the kept answer disagrees with this package's own live path on a numeric column. Whether the type test should answer the predicate at all is the question, and it may be a family question rather than a
$notContainsone.Related
#13553 (the ordering arms on the same fixture, repaired in PR #14077) · #13166 (the no-value cells of this same arm) · #5374 (
$contains, the last time this face and the live one answered one filter two ways)