fix(content): search searchable custom fields - #2191
Conversation
🦋 Changeset detectedLatest commit: 1cf9d73 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
97a4de1 to
be1120f
Compare
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
be1120f to
d5706ce
Compare
d5706ce to
4d8e02c
Compare
There was a problem hiding this comment.
This is the right fix for the described problem. The bug — that content-list search only matched slug/title/name in the non-FTS path while the FTS index already covered all searchable fields — is real, and the change resolves it by reading searchable flags from _emdash_fields and including those columns in both the FTS-coverage decision and the LIKE fallback. CASTing column values to TEXT makes the fallback work consistently across SQLite/Postgres for scalar custom fields (including the integer field tested), and validateIdentifier keeps the dynamic identifiers safe.
What I checked:
- Diff and changed files:
resolveSearchColumnsnow pullssearchableflags;applySearchFiltercasts toTEXTfor the LIKE fallback; docstrings and tests are updated. - AGENTS.md conventions: SQL identifiers are validated via
validateIdentifier; no user-facing strings are added; a proper changeset is present; no new logged-out queries are introduced; comments are descriptive, not reviewer-facing or issue-referencing. - FTS consistency:
canUseFtsForListFilterchecks that every non-slugsearch column is present in the FTS index's searchable-field set, so the fallback still kicks in when a display field liketitleisn't searchable. The schema registry already rebuilds the FTS index when a field'ssearchableflag changes, so the index stays in sync with the new column set. - Tests: Both the FTS path and the LIKE fallback path now have regressions for searching a custom field, and the integer-field case is covered in the dialect-agnostic test. Tests are behavioral, not tautological.
Headline conclusion: clean, well-scoped bug fix. No blocking issues found.
4d8e02c to
8990c44
Compare
8990c44 to
1cf9d73
Compare
There was a problem hiding this comment.
This is the right fix for the reported gap. The content-list search previously matched only slug/title/name in the non-FTS path, while the FTS index already honored the searchable flag. The PR resolves the full set of searchable columns from _emdash_fields, includes them in both the FTS coverage decision and the LIKE fallback, and CASTs values to TEXT so scalar custom fields (including integers) are matched consistently across SQLite and Postgres.
What I checked:
- Approach and correctness:
resolveSearchColumnsnow adds every field withsearchable === 1;canUseFtsForListFiltercorrectly falls back to LIKE when any non-slugdisplay column is not covered by the FTS index. TheCAST(... AS TEXT)change inapplySearchFilteris standard SQL and works for both integer and text columns. - SQL safety: Dynamic identifiers are passed through
validateIdentifierbeforesql.ref(), so user-defined field slugs cannot be injected. - Locale filtering: Content-table queries still apply
localebefore the search filter, and FTS rows are scoped to the same table. - Index sync: The schema registry rebuilds the FTS index whenever a field's
searchableflag changes (createField,updateField,deleteField), so the index stays consistent with the new column set. - Logged-out query counts:
handleContentListis gated oncontent:read; no anonymous visitor can trigger the new_emdash_fieldsquery. - Tests: The new regressions cover both the FTS path and the LIKE fallback, including an integer field in the dialect-agnostic test. They are behavioral, not tautological.
- Changeset: Concise, present-tense, and describes the user-visible effect.
I found one non-blocking stale docstring in FindManyOptions; the headline conclusion is still clean and ready to merge.
Findings
-
[suggestion]
packages/core/src/database/repositories/types.ts:157The
searchColumnsdocstring still says the columns come from the collection's "display fields", but after this changeresolveSearchColumnsalso addsslugand every field markedsearchable. The stale description can mislead callers about what theqfilter will actually match.* Resolved by the handler from the collection's display fields, slug, * and any field marked searchable. Each name is validated as a SQL identifier.
What does this PR do?
Content-list search now includes every schema field marked
searchable, in addition to the collection display fields and slug.The FTS path already indexed searchable custom fields, but the fallback path only matched
slug,title, andname. That made custom metadata search backend-dependent and prevented values such as internal ticket numbers from being found when FTS could not serve the query.This change keeps the existing FTS path, resolves searchable fields from the collection schema, and casts fallback values to text so scalar custom fields can be matched consistently across supported SQL dialects.
Addresses the search portion of #2179.
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runAI-generated code disclosure
Screenshots / test output
pnpm --filter emdash test --run tests/integration/content/content-list-search.test.ts tests/integration/content/content-list-search-fts.test.ts(17 tests passed)pnpm typecheckpnpm lintpnpm format:check