fix(driver-memory): render the pipeline dump's RegExp operand instead of dropping it to {} (#7853) - #7869
Conversation
… of dropping it to `{}` (#7853)
`AnalyticsResult.sql` from `generateSqlFromPipeline()` dumped each mingo stage
with a bare `JSON.stringify`. A `RegExp` has no own enumerable properties, so
every pattern operand rendered as `{}` — `{"name":{"$regex":{}}}` — and the one
field an author debugging an in-memory chart is looking for was the one the
dump dropped.
Measured across the twelve operators this face declares: exactly three carry a
pattern (`$contains`, `$icontains`, `$notContains` inside `$not`) and all three
were affected. They now render the pattern's own literal syntax, `/source/flags`
— chosen over the mongo-shaped `{$regex, $options}` because the `RegExp` sits AT
the `$regex` key, so a value replacer producing the pair renders the doubled
`{"$regex":{"$regex":"et","$options":""}}`, a shape no mongo query has, and
flattening it would make the dump disagree with the pipeline it dumps.
No executed behaviour changes: the dump is explicitly not SQL, and `query()`'s
rows, `generateSql()`'s SQL, and the other nine operators' dumps are unchanged.
The existing dump check asserted structure (`$not` wraps a `$regex`) and passed
throughout — it is strengthened here to assert the pattern TEXT for each
affected operator, since `{"$regex":{}}` is non-empty and satisfies any
presence-only assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzMdsWW2aZwGCEhfDLo1sT
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7853
MemoryAnalyticsService.query()returnsAnalyticsResult.sqlfromgenerateSqlFromPipeline()— a stage-by-stage dump of the mingo pipeline it actually executed, and the only thing an author debugging an in-memory chart is given. It dumped each stage with a bareJSON.stringify, and aRegExphas no own enumerable properties, so every pattern operand rendered as{}. The$matchstage was reported as constrainingnameby an empty object: the one field the reader came for is the one the dump dropped.Measured on
origin/main@55635fc, executedAll twelve operators this face declares, before → after. Nine are byte-identical; the three that carry a pattern are the whole change:
$contains{"name":{"$regex":{}}}{"name":{"$regex":"/et/"}}$icontains{"name":{"$regex":{}}}{"name":{"$regex":"/[Bb][Ee][Tt]/"}}$notContains{"name":{"$not":{"$regex":{}}}}{"name":{"$not":{"$regex":"/et/"}}}$eq$ne$gt$gte$lt$lte$in$nin$existsA metacharacter comparand now shows its escape too:
{$contains: 'a.p'}dumps{"name":{"$regex":"/a\\.p/"}}, so an author can see thata.pran as a literal and not as "any character between a and p" (#5567's direction). The dump is the only place that distinction is visible.Nothing executed changes. This surface is explicitly not SQL — its own header says
-- MongoDB Aggregation Pipeline on table: …— so it cannot render a predicate execution did not run.query()'s rows andgenerateSql()'s SQL are untouched; the diff is one replacer argument plus the function it names.The rendering-form decision, and why it went against the lean
The two candidates were the pattern's own literal syntax (
/et/) and the mongo-shaped{"$regex":"et","$options":""}that the rest of the dump speaks. The mongo form was the first choice for internal consistency, and measurement ruled it out on a structural ground rather than a cosmetic one: theRegExpsits at the$regexkey, so a value replacer substituting the pair renders the doubled— a shape no mongo query has. Flattening it into the real mongo spelling means rewriting the parent object, which would make the dump disagree with the pipeline it claims to be dumping: what mingo executes at that key is a JS
RegExpobject, not a source/options pair. That trades a visibly degenerate rendering for a plausible-but-wrong one, which is the #5333 direction this card is explicitly graded away from.The literal form is also the only one-token rendering that keeps the flags, and flags are not decoration on this axis:
$icontains' fold lives in the pattern source (#6520) while$containsis case-exact (#7723 / #4706 Q2 = A), so a form that dropped them would recreate a smaller copy of this same information loss. No declared operator carries a flag today — measured, every builder folds into the source — so the segment renders empty; it is in the form because a fold that ever moved into a flag would otherwise vanish exactly the way the source did.The check is strengthened, not duplicated
There was already a check on this dump —
memory-driver-filter-logic-conformance.test.ts, "the emitted$matchwraps the negation around a pattern instead of a bare scalar" — and it passed for the whole life of the defect. It asserted$notand$regexare present and even carried a comment conceding the loss ("JSON.stringifyrenders a RegExp as{}, so assert on the STRUCTURE"). That is precisely the blind spot the card names:{"$regex":{}}is non-empty and satisfies every presence-only assertion.So it is strengthened in place rather than shadowed by a parallel file: four cases now assert the pattern text for each affected operator plus the metacharacter escape, and the structural assertion stays as the guard it was built to be.
Reverse-verified: with the replacer argument removed, all four new cases fail (
expected '{"name":{"$regex":{}}}' not to contain '{}') and the pre-existing structural case stays green — measured confirmation that it was blind to this defect.Assumptions the dispatch carried, measured
contains/icontains/notContainsreach the defectJSON.stringifyDatecomparand is canonicalized to an ISO string bycomparandsForbefore it arrives (andtoJSONruns before a replacer anyway). ABigIntcomparand does throw, but out of mingo's ownQuery.compileduring execution, before this dump is ever built, so no replacer here reaches it — stack capturedGates
@objectstack/driver-memorytests@objectstack/service-analyticstests (the only consumer of this service outside the package)tsc --noEmitpnpm lintcheck:driver-conformancecheck:query-options-erasure55635fcChangeset:
@objectstack/driver-memorypatch — a user-visible debug string.content/docs/releases/untouched. No adjacent card was folded in; nothing outsidepackages/drivers/driver-memoryplus the changeset is touched.Generated by Claude Code