Repository navigation
driver-memory's analytics generateSql() renders the LIKE family with NO wildcards, so the echoed statement is an EQUALITY the pipeline never ran #7117
Description
Activity
Triage:
pm:queue+domain:drivers.- Classification: concrete defect with named landing and a clear check strategy (pin
generateSql()WHERE againstquery()rows for the LIKE family). Auto-queue class. - Routing: fix lands in
packages/drivers/driver-memory/src/memory-analytics.ts⇒domain:drivers. - Premise check on
origin/main@9136327: all anchors verified —operatorToSqlmaps'contains': 'LIKE'/'notContains': 'NOT LIKE'(memory-analytics.ts:930-931), the WHERE builder emits the bare comparand viatoSqlLiteralwith no wildcards (:587), while the mingo exit builds a real substring regex (:226). The divergence is present as described. - Dedup: nearest neighbours are
$regexon driver-sql is not a regex — it compiles to a substring LIKE, so it both over-matches and silently matches nothing #4706 ($regex→ LIKE on driver-sql, execution-side, tracking) and the/analytics/sql回显的 SQL 丢掉$startsWith/$endsWith谓词:回显比实际执行的查询更宽,无法复现结果 #5333 echo family onservice-analytics— different faces, no shadow; clean. - Target: wrong display SQL on driver-memory's analytics echo, not a wrong-rows or permission defect — not release-blocking by the four-class test.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Classification: concrete defect with named landing and a clear check strategy (pin
Drivers seat: deferring dispatch — same unresolved conflict as #6915, and this is the second instance.
The fix lands in
packages/drivers/driver-memory/src/memory-analytics.ts. Seat post #6020's body carries the maintainer's standing instruction of 2026-08-05, anchored on #5499:driver-memory / driver-mongodb 族投入冻结 —— 新单照常打
domain:drivers,但挂pm:on-hold引用 #5499,不入队;driver-sql 不受影响;影响 CI 判绿正确性的按 restore-invariant 例外升级So a
driver-memorycard should takepm:on-holdreferencing #5499 rather thanpm:queue, unless it qualifies for the CI-green-correctness exception. This one does not, on triage's own reading — its note says the defect is wrong display SQL, not a wrong-rows or permission defect, not release-blocking by the four-class test. That is a sound classification and it is exactly what puts the card outside the exception.⛔ Not disputing the triage classification, and ⛔ not changing
domain:*orpm:*here — single-producer rule. Recording the conflict so this card is not picked up as clear-to-dispatch while it stands.Why this is worth raising rather than absorbing: the same conflict was raised on #6915 at 09:0xZ (seat comment
5230719256on #6020) and is still unresolved. Twodriver-memorycards are now queued against a standing instruction that says they should not be, and #7052, #6814 and #6850 are further memory/mongodb findings that would follow the same path once graded. Either the standing #5499 rule still governs — in which case both cards revert topm:on-holdand the freeze holds — or the freeze has been relaxed for cards of this shape, in which case this seat will dispatch them in queue order. One line settles it; this seat has no basis to pick either reading on its own.Card quality is not the issue: the anchors are verified, the divergence is real, and the suggested check (pin
generateSql()'s WHERE againstquery()'s row set for the LIKE family, rather than asserting the SQL string) is the right shape — an assertion on the string alone would not have caught the missing%either. It is ready to run the moment the freeze question is answered.
Generated by Claude Code
Maintainer ruling (2026-08-10, chat): #6915 and #7117 take
pm:on-holdunder #5499. The standing 2026-08-05 instruction in seat post #6020 governs — it was not relaxed for this shape.Applied:
pm:queue→pm:on-hold,domain:driverskept. Unassigned; not dispatched. This settles the deferral recorded at 19:0xZ (comment5233324830).The finding is not retracted and its quality was never the question. The divergence is real as filed: the mingo exit builds
$regexfromfilterSubstringPattern(a substring match) whilegenerateSql()emits the bare comparand throughtoSqlLiteralwith no%, so{ name: { $contains: 'acme' } }echoesWHERE name LIKE 'acme'— an equality — next to aquery()that returns every row containingacme.notContainsmirrors it, andstartsWith/endsWithfall tooperatorToSql's|| '='fallback, which is the same silent-wrong-answer shape #5374 and #5345 removed from this file's sibling tables.Two things worth keeping attached to the card for whoever picks it up after the freeze:
- The suggested check is the right one and should not be weakened into a string assertion: pin
generateSql()'s WHERE againstquery()'s row set for each member of the LIKE family, the wayobjectql-echo-operator-coverage.test.tspins echo against execution. An assertion on the SQL text alone would not have caught the missing%either. - The
icontains: 'LIKE'row JS 求值面全体拒收$icontains(driver-memory 两面 / driver-mongodb / objectqlhaving/ formula)—— SQL 族已实现,同一 filter 在内存 double 上抛错 #6520 added to that table inherits the same missing-wildcard defect by construction; it was added so the operator would not fall to the=fallback, which would have been strictly worse. Fixing this issue fixes that row with the others — it does not need its own card.
Generated by Claude Code
- The suggested check is the right one and should not be weakened into a string assertion: pin
huangyiirene commented
on Aug 11, 2026 CollaboratorMore actionsReturned to
pm:queue— the hold's restart condition is met: the maintainer fully lifted the #5499 freeze on 2026-08-11 (verbatim on #5499, comment5252526378). Anchors were last verified at hold time; re-verify on dispatch.Serial constraint recorded for whoever dispatches: the in-flight #6814+#6682 memory DEBT-clearing card (branch
claude/issue-6814-6682-memory-debt-rows) touchesmemory-analytics.ts— this card's surface. Dispatch this card only after that PR lands, on the merged ref (the fold-site sweep there may move the lines this card quotes; the wildcard defect itself is explicitly out of that card's scope).
Generated by Claude Code
huangyiirene commented
on Aug 11, 2026 CollaboratorMore actionsClaimed by the drivers seat — PM session
session_013ZU38vNzLXCscgdFZjKd53, dev branchclaude/issue-7117-analytics-like-wildcards.Serial constraint from
5252552149is satisfied: PR #7723 landed (69fde55, 19:49Z), so this card dispatches on the merged ref.Anchors re-verified on
69fde55(the fold-site sweep in #7723 moved the line numbers the card quotes; the defect itself is intact):-
operatorToSql— now at:1006. Table still'contains': 'LIKE'/'notContains': 'NOT LIKE'/'icontains': 'LIKE', still nostartsWith/endsWithrows, stillreturn opMap[operator] || '='. - The
generateSql()WHERE builder — now at:645-668. StillwhereClauses.push(\${fieldPath} ${sqlOp} ${this.toSqlLiteral(comparand)}`), no wildcards. (#5373's null-comparandIS NULL` branch sits immediately above and is unrelated.) - The mingo exit —
CUBE_OPERATOR_TO_MONGO_PREDICATEat:247,contains: ({ raw, substring }) => ({ $regex: substring(raw[0]) }).
One thing changed under this card, and it enlarges it. #7723 made the
$containsfamily case-exact on this package's execution faces — includingfilterSubstringPattern, which the analytics mingo exit borrows. So the two exits now disagree on two axes, not one:axis query()(executed)generateSql()(echoed)containment substring equality — no %case case-exact (post-#7723) LIKEfolds ASCII on a SQLite-shaped consumerThat second row is the adjacency PR #7723 deliberately serialized behind this card, in its own words: making the echo case-exact means choosing
GLOBor a binary cast per dialect, and the pattern that construct takes is exactly the wildcard rendering this card owns — which is why a half-change would emit incoherent SQL and why both halves belong here.
Generated by Claude Code
-
huangyiirene commented
on Aug 11, 2026 CollaboratorMore actionsImplemented — draft PR #7852, branch
claude/issue-7117-analytics-like-wildcards, based on69fde55.What changed, by face
face verdict action query()(mingo$match)correct — it is the reference untouched; the echo is now pinned against its rows generateSql()the defect fixed; the whole diff generateSqlFromPipeline()(AnalyticsResult.sql)not this defect; a different, lesser one it prints the executed pipeline as JSON, so it cannot render a predicate execution did not run — but JSON.stringifydrops aRegExpto{}(measured:{"name":{"$regex":{}}}). Filed as #7853measureToSql()n/a measures only; no filter operator reaches it Before → after, measured (not read)
Both axes, since #7723 opened the second one:
wherequery()echoed, before echoed, now {name: {$contains: 'Industries'}}1,3name LIKE 'Industries'→[]name GLOB '*Industries*'→1,3{name: {$contains: 'ACME'}}2LIKEfolds ASCII →1,2GLOBis case-exact →2{name: {$icontains: 'industries'}}1,2,3LIKE 'industries'→[]lower(name) GLOB lower('*industries*')→1,2,3{name: {$notContains: 'Industries'}}2,4,5,6NOT LIKE→ wrong set, NULL row dropped(name IS NULL OR name NOT GLOB …)→2,4,5,6{name: {$contains: '%'}}4LIKE '%'→ every non-null rowescaped → 4{name: {$in: [a,b]}}1,3name = a→1name IN (a, b)→1,3{name: {$nin: [a]}}2,3,4,5,6name = a→ the complement(name IS NULL OR name NOT IN (a)){name: {$exists: true}}1..6name = 1→ nothingname IS NOT NULL→1..5{name: {$in: []}}[]no WHERE → whole table 1 = 0→[]{at: {$lte: '2026-01-02'}}1,2at <= '2026-01-02'→1at < '2026-01-03'→1,2GLOBrather thanLIKEbecause this exit emits SQLite-shaped SQL (its owntoSqlLiteraland #6520'sicontainsrow already assume it) and SQLite'sLIKEfolds ASCII unconditionally — so aLIKEecho would have contradicted execution on the case axis the moment the wildcard axis was fixed. Translation via the spec's sharedlikePatternToGlobPatternover a LIKE-escaped comparand.PM assumptions: what measurement said
- A — partly FALSIFIED. The two defect sites are right, but "缺
startsWith/endsWith行,落到|| '='" is not reachable. Neither operator is inMONGO_TO_CUBE_OPERATOR, so driver-memory 的 analytics 面静默丢弃大半个 filter:$or/$not整条丢,$between/$startsWith/$null/$regex因无 cube 映射而丢 —— 聚合结果被放大 #5345's gate refuses both on both exits withINVALID_FILTER/ 400 before any lowering — measured, and now asserted. The operators actually falling to|| '='werein,notIn,set, and$ninwas the worst cell on the card: the echo returned the exact complement of the query's rows. - B — CONFIRMED, and yes, one PR. With
filterSubstringPatterncase-exact post-fix(driver-memory): make the$containsfamily case-exact and answercount_distinct#7723, the echo diverged on containment and case. They are inseparable because the case-exact SQLite construct isGLOB, andGLOB's pattern language is notLIKE's — choosing the construct is choosing the wildcard rendering. - C — CONFIRMED, form checked not assumed.
service-analytics'likePatternescapes[\\%_]and pairs a boundESCAPE ?. Not copyable here: this exit inlines literals (params: []always) andGLOBtakes noESCAPEclause at all. So the comparand is LIKE-escaped and handed to the spec'slikePatternToGlobPattern, which is the shared definitiondriver-sqlanddriver-tursoalready use — no third hand-copy. - D — CONFIRMED and wider than stated. The NULL trap is not about a null comparand; it is SQL's three-valued
WHEREdropping NULL-column rows from every negation. It hitnotEquals,notInandnotContains, andnotEqualshad it before this card. Fixed with(col IS NULL OR …)—$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146's repo-wide ruling,read-scope-sql.ts's spelling. driver-memory analytics 面的 cube 值往返是有损的:布尔比较数变成数字({is_active: true}取到 0 行),null比较数被整条丢掉(取到全表) #5373'sIS NULLbranch is preserved as a row of the new table.
One cell SQL cannot translate exactly and I did not pretend otherwise: mingo's
$existstests KEY PRESENCE, so an explicitly-nullrow satisfies$exists: trueonquery()and failsIS NOT NULL.IS NOT NULLis what this repo's other two SQL lowerings emit, and it is pinned as an explicit inequality so it cannot be closed in silence.The check
memory-analytics-echo-operator-coverage.test.ts, 44 cases. The echoed statement is executed on a real SQLite engine (sql.js) over the same fixture the pipeline runs on, and its row ids compared toquery()'s — not a string assertion, per comment5234868334. The closed vocabulary is enumerated in both directions (every accepted operator round-trips; every refused one refuses identically on both exits). Eight reversions, direction predicted before each run, all eight held — recorded with the exact assertion text in the suite docblock.Also corrected: three docblocks in this package that #7723 left false (
MongoPredicateInput.substring/.asciiSubstring,memory-like-pattern.test.ts's$containscontrol,memory-icontains.test.ts's coverage note). The same stale prose inpackages/specis #7854 rather than a second package in this diff.Gates
driver-memory 708 passed / 23 files (from 664 / 22) ·
@objectstack/spec9970 / 378 · all 17 measured dependents green (runtime 2057, cli 1182, driver-turso 946, service-datasource 326, client 282, cloud-connection 112, hono 73, http-conformance 72, plugin-dev 45, client-react 34, verify 23, embed-objectql 2) ·tsc --noEmitclean ·pnpm lintclean ·check:query-options-erasureholds (67/17, none new, baseline verified against69fde55) ·check:wildcard-fallthrough7/0/5 ·check:driver-conformance40 covered / 0 DEBT / 0 exempt — unchanged.Changeset present (
@objectstack/driver-memorypatch — the displayed SQL is user-visible).content/docs/releases/untouched.Adjacencies filed: #7853 (pipeline dump loses the RegExp), #7854 (spec prose stale after #7723).
Generated by Claude Code
- A — partly FALSIFIED. The two defect sites are right, but "缺
huangyiirene commented
on Aug 11, 2026 CollaboratorMore actionsPM review: ACCEPT — PR #7852.
Multi-face declaration complete and load-bearing. All four faces named with a verdict each:
query()(reference, untouched),generateSql()(the defect, the whole diff),generateSqlFromPipeline()(a different defect found in passing —JSON.stringifydrops aRegExpto{}— filed as #7853 rather than smuggled in),measureToSql()(n/a, no filter operator reaches it). ThegenerateSqlFromPipeline()row is the kind that usually gets skipped; measuring it and splitting it out is the right disposition.The check is a row-set pin, not a string assertion — the standing instruction from comment
5234868334held. The echoed statement is executed on a real SQLite engine (sql.js) over the same fixture the pipeline runs on and its row ids compared toquery()'s. 44 cases, closed vocabulary enumerated in both directions, eight reversions with direction predicted before each run. That is what caught the cells a string assertion could not.Both axes handled, and the ruling on why they are one PR is sound.
GLOBrather thanLIKEbecause this exit emits SQLite-shaped SQL and SQLite'sLIKEfolds ASCII unconditionally — after #7723 put this package's execution faces on #4706 Q2 = A, aLIKEecho would have contradicted execution on the case axis the moment the wildcard axis was fixed. Choosing the construct is choosing the pattern language; a half-change would have emitted incoherent SQL. This is exactly the adjacency #7723 serialized behind this card, resolved as predicted.Assumption A was falsified by measurement, correctly. The PM card said
startsWith/endsWithfall tooperatorToSql's|| '='. Measured: neither is inMONGO_TO_CUBE_OPERATOR, so #5345's gate refuses both on both exits withINVALID_FILTER/400 before any lowering — unreachable, now asserted. The operators actually falling through werein,notIn,set, and$ninwas the worst cell on the card: the echo returned the exact complement of the query's rows. Finding a worse defect than the one the card described, in the place the card pointed, is the outcome this seat wants from a dev — the card's premise was corrected on the record rather than worked around.Escape form checked, not assumed (assumption C).
service-analytics'likePatternescapes[\\%_]with a boundESCAPE ?— measured as not copyable here, since this exit inlines literals (params: []always) andGLOBtakes noESCAPEclause at all. The comparand is LIKE-escaped and handed to the spec's sharedlikePatternToGlobPattern, the same definitiondriver-sqlanddriver-tursouse — no third hand-copy, which is whatlike-pattern.ts's own header asks for.NULL handling widened correctly (assumption D). Not a null-comparand question but SQL's three-valued
WHEREdropping NULL-column rows from every negation: it hitnotEquals(pre-existing),notInandnotContains. Fixed as(col IS NULL OR …)per #5146 / #5297. Empty$inrenders1 = 0rather than emitting no WHERE at all — previously the whole table.One inequality declared rather than hidden: mingo's
$existstests key presence, so an explicitly-nullrow satisfies$exists: trueonquery()and failsIS NOT NULLon the echo.IS NOT NULLis what this repo's other two SQL lowerings emit; it is pinned as an explicit inequality so it cannot be closed in silence. Correct call — the alternative would have been an untrue pin.File surface / gates. 7 files, +813/−71, entirely inside
driver-memoryplus its changeset and the lockfile — no other seat's files, nopackages/spec(that stale prose is #7854, correctly split out), nocontent/docs/releases/. New devDepssql.js@^1.14.1+@types/sql.js@^1.4.11are version-identical to the repo's existing pins inservice-analyticsanddriver-sqlite-wasm— not a new dependency, andValidate Package Dependenciesis green.check:driver-conformance40 covered / 0 DEBT / 0 exempt — unchanged;check:query-options-erasureholds (67/17, baseline verified against69fde55, none new). CI: 26/26 conclusive, all success — ESLint, TypeScript Type Check, every Test Core shard, Dogfood, Temporal Conformance.Flipping ready and queueing.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026
Found while implementing #6520 (
$icontainson every JS evaluation face). Out of that card's scope — it is the$containsfamily, not$icontains, and it predates that work — so it is filed rather than fixed there.Measured on
origin/main@f5a9bc2f3, by reading the sourceMemoryAnalyticsServicehas two exits for one normalized filter tree, and they disagree about what the LIKE family MEANS.The mingo exit (
query()) builds a real containment pattern —memory-analytics.ts,CUBE_OPERATOR_TO_MONGO_PREDICATE:where
substringis the driver's ownfilterSubstringPattern(value)=new RegExp(escapeRegex(value), 'i')— a substring match.The SQL exit (
generateSql()) emits the comparand as a bare literal, with no%anywhere (memory-analytics.ts, the WHERE builder):toSqlLiteralonly quotes and escapes quotes; it adds no wildcards. So{ name: { $contains: 'acme' } }echoeswhich is an equality (case-folded on SQLite, exact on Postgres), while
query()returns every row containingacme.notContainshas the mirror of the same bug viaNOT LIKE.Why this matters
This is the #5333 / #3650 class — "a rendering that contradicts execution is worse than no rendering" — reached through driver-memory's analytics face instead of
service-analytics' echo. An author who runs the echoed statement to reproduce a chart gets a narrower row set than the chart, so the filter reads as broken in the opposite direction from #5333's widening.service-analyticshas an enumerated regression test for exactly this property on its own three compilers (objectql-echo-operator-coverage.test.ts); driver-memory's analyticsgenerateSql()has none, which is why this survived.Note the direction is wrong-rendering, not a permission bypass: this exit produces display SQL, not the executed query.
Scope
contains-> should renderLIKE '%v%'notContains->NOT LIKE '%v%'startsWith/endsWithare not inoperatorToSql's table at all, so they fall to its|| '='fallback and render as equality too — same defect, one step further along. That|| '='fallback is itself the silent-wrong-answer shape driver-memory analytics 面的$notContains编译成裸 mingo{$not: 'x'},该谓词不约束任何行 —— 结果被放大到全表 #5374 and driver-memory 的 analytics 面静默丢弃大半个 filter:$or/$not整条丢,$between/$startsWith/$null/$regex因无 cube 映射而丢 —— 聚合结果被放大 #5345 removed from this file's sibling tables.$icontains(driver-memory 两面 / driver-mongodb / objectqlhaving/ formula)—— SQL 族已实现,同一 filter 在内存 double 上抛错 #6520 added anicontains: 'LIKE'row to that same table. It is consistent with itscontainsneighbour and therefore inherits this same missing-wildcard defect; it was added explicitly so the operator would not fall to the=fallback, which would have been strictly worse. Fixing this issue fixes that row with the others.A fix should also consider whether the comparand needs LIKE-escaping here (
%/_in an author's comparand), whichservice-analytics'likePatternalready does for the compilers on that side.Suggested check
Pin
generateSql()'s WHERE againstquery()'s row set for each member of the LIKE family, the wayobjectql-echo-operator-coverage.test.tspins the echo against execution — an assertion on the SQL string alone would not have caught the missing%either.