feat(objectql,spec): execute $search via metadata-driven cross-field filter (ADR-0061 P1+P2)#2134
Merged
Merged
Conversation
…filter (ADR-0061 P1+P2)
`$search` was a declared-but-unenforced no-op: defined in spec, sent by every
client surface, executed by no driver. This wires it end-to-end as the ADR-0061
decision specifies — server-resolved, driver-agnostic.
P1 — contract:
- spec: add `object.searchableFields` (canonical searchable-field source) to
ObjectSchemaBase; classify it live in the liveness ledger.
P2 — executor:
- objectql: `search-filter.ts` (`expandSearchToFilter`) turns a `$search` term
into a `{ $or: [{ field: { $contains } }] }` filter across server-resolved
fields — declared `searchableFields` → auto-default (name/title + short-text).
Multiple terms AND, fields OR; `select`/`status` map the query to option
values via labels ($in) with a raw-value fallback; an optional `$searchFields`
override is intersected with the allowed set (never client-trusted).
- Wired into `engine.find()` after schema resolution, before the driver call, so
every driver (all already execute $or/$contains) honours it with no driver
change. Merges with any existing filter via $and.
showcase: `showcase_account.searchableFields = [name, industry, status,
billing_email, tax_id]` — searching "retail" now matches by industry, not name.
Tests: `search-filter.test.ts` (10 — field resolution, label→value, multi-term,
override validation) + engine.test ($search expansion reaches the driver where).
Builds on ADR-0061. Next: P3 (unify list/lookup/⌘K + objectui `$searchFields`
contract) and P4 (trigram/relevance/external) are deferred per the ADR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 93 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
xuyushun441-sys
added a commit
that referenced
this pull request
Jun 21, 2026
…ecutor runs (ADR-0061) (#2142) Follow-up to the $search executor (#2134): over the REST/OData path the term never reached engine.find(). protocol.findData() normalizes $-prefixed OData params ($top/$skip/$orderby/$select/$count) to bare names, but NOT $search / $searchFields — so `$search=retail` fell through to the implicit-field-filter pass and became `where.$search = 'retail'` (a non-existent column), which the driver silently returns [] for. The engine's expandSearchToFilter (which reads ast.search) therefore never fired. - Add ['$search','search'] and ['$searchFields','searchFields'] to the dollar→bare normalization, and add 'searchFields' to knownParams so the bare form isn't treated as an implicit filter. - Test: findData normalizes $search/$searchFields to bare and never leaks them into where. Verified end-to-end over the real HTTP API against a dev backend: searching "retail" returns the 3 retail-industry accounts (matched by the industry label→value), "technology" → 4, "Contoso" → 1 by name, "zzqq" → 0. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements ADR-0061 phases P1 (contract) and P2 (executor).
$searchwas a textbook declared-but-unenforced no-op (defined in spec, sent by every client, executed by no driver — seedocs/audits/2026-06-record-search-liveness.md). This wires it end-to-end, server-resolved and driver-agnostic.P1 — contract
spec: addobject.searchableFieldstoObjectSchemaBase— the canonical, server-side source for which fields$searchmatches. Classified live inliveness/object.json.P2 — executor
objectql/search-filter.ts(expandSearchToFilter): turns a$searchterm into{ $or: [{ field: { $contains } }] }across server-resolved fields — declaredsearchableFields→ auto-default (name/title + short-text types), excluding system/heavy/enumerated-as-text. Multiple terms AND, fields OR.select/statusmap the query to stored option values via labels ($in) with a raw-value fallback. An optional$searchFieldsoverride is intersected with the allowed set (never client-trusted).engine.find()after schema resolution and before the driver call, merging with any existing filter via$and. No driver changes — every driver already executes$or/$contains.Showcase
showcase_account.searchableFields = [name, industry, status, billing_email, tax_id]— searching "retail" now matches by industry, not just name.Verification
search-filter.test.ts— 10 cases (field resolution, auto-default exclusions, declared override,$searchFieldssubset-validation, label→value$in, multi-term AND/OR, case-insensitive).engine.test.ts—$searchexpands into thewherethat reaches the driver; ANDs with an existing filter; honours declaredsearchableFields.Scope
Per ADR-0061, P3 (unify list/lookup/⌘K onto this resolver + objectui
$searchFieldscontract) and P4 (trigram/tsvectorindexes, relevance/boost/fuzzy, external engines, serversearchAll) are deferred to follow-ups. The renderer$orhack from the "rejected alternatives" is not used.