Skip to content

search: don't assume a title column exists in searchable collections#1246

Open
mvanhorn wants to merge 3 commits into
emdash-cms:mainfrom
mvanhorn:fix/1178-mcp-search-d1-column
Open

search: don't assume a title column exists in searchable collections#1246
mvanhorn wants to merge 3 commits into
emdash-cms:mainfrom
mvanhorn:fix/1178-mcp-search-d1-column

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

@mvanhorn mvanhorn commented May 31, 2026

What does this PR do?

Search and suggestions hardcoded c.title in the generated SQL, so searching a collection whose schema has no title field failed with D1_ERROR: no such column: title and broke multi-collection search entirely.

The query now detects whether the collection actually defines a title field (via the fields registry, with identifier validation) and only selects it when present: NULL in the main search projection and COALESCE(slug, id) for suggestions, dropping the title IS NOT NULL filter when there is no title field.

Closes #1178

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes (changed files)
  • pnpm test passes (targeted tests for my change: search + mcp/search, 13 passing)
  • pnpm format has been run
  • I have added/updated tests for my changes
  • User-visible strings: N/A (no admin UI strings)
  • I have added a changeset (emdash: patch)
  • New features link to an approved Discussion: N/A (bug fix)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5.5 (Codex CLI)

Screenshots / test output

tests/integration/mcp/search.test.ts + tests/integration/search/suggest.test.ts
Test Files  2 passed (2)
     Tests  13 passed (13)

The search query and suggestion SQL hardcoded c.title, so searching a
collection whose schema has no title field failed with
D1_ERROR: no such column: title and broke the whole multi-collection search.

Detect whether the collection actually has a title field (via the fields
registry, with identifier validation) and select it only when present:
NULL in the main search projection and COALESCE(slug, id) for suggestions,
dropping the title IS NOT NULL filter when there is no title field. Adds
integration tests with a title-less searchable collection.

Closes emdash-cms#1178
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 31, 2026

🦋 Changeset detected

Latest commit: c04d512

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 31, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1246

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1246

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1246

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1246

emdash

npm i https://pkg.pr.new/emdash@1246

create-emdash

npm i https://pkg.pr.new/create-emdash@1246

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1246

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1246

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1246

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1246

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1246

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1246

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1246

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1246

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1246

commit: c04d512

@github-actions github-actions Bot added the query-count changed PR diff modifies query-count snapshot files label May 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 31, 2026

Query-count snapshot changes

4 routes changed, total Δ +4 queries.

SQLite

Route Before After Δ
GET /search (cold) 6 7 +1
GET /search (warm) 6 7 +1

D1

Route Before After Δ
GET /search (cold) 14 15 +1
GET /search (warm) 5 6 +1

Comparing snapshot files between base and head. Updated automatically on each push.

Copy link
Copy Markdown
Contributor

@emdashbot emdashbot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix/1178-mcp-search-d1-column

Approach judgment: This is the right fix for the right problem. Search and suggestions assumed every collection had a title column, which broke multi-collection search when any searchable collection lacked one. Detecting the field via _emdash_fields and conditionally selecting c.title, NULL, or COALESCE(c.slug, c.id) is a sound, minimally invasive approach.

What I checked:

  • Full diff of packages/core/src/search/query.ts and the two new integration tests.
  • SQL safety: no raw interpolation of variables; identifiers are validated with validateIdentifier(). The new collectionHasField helper uses the Kysely query builder correctly.
  • Content-table conventions: locale filters are preserved, deleted_at checks are preserved, no locale filtering issues.
  • Cross-cutting concerns: verified there are no other hardcoded c.title references in search/MCP code.
  • Edge cases: title may be null even when the field exists, so row.title ?? undefined remains correct. The COALESCE(c.slug, c.id) fallback in suggestions guarantees a non-null string since id is a required primary key.
  • Tests: both the MCP search and suggestions integration tests cover titleless collections in multi-collection scenarios, which directly reproduces the reported bug.
  • Changeset: present (emdash: patch).

Headline conclusion: The implementation is clean, safe, and well-tested. I found no logic bugs, regressions, security issues, or AGENTS.md convention violations.

One non-blocking observation: collectionHasField adds an extra query per collection in loops that already query _emdash_collections for search config. In the existing architecture this is acceptable, but future work could batch or cache field metadata if search latency becomes a concern.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels May 31, 2026
@ascorbic
Copy link
Copy Markdown
Collaborator

ascorbic commented Jun 1, 2026

You could avoid the extra query by updating getSearchableFields to get the info you need in the same query.

If you want to improve things further in a follow-up, you could put all of the metadata queries (getSearchConfig, getSearchableFields, ftsTableExists at least) into a new getCollectionFieldInfo function that's wrapped with requestCached("collectionFields:" + slug, ...) or something.

Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid the extra hot-path query

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/approved Approved; no new commits since labels Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core cla: signed query-count changed PR diff modifies query-count snapshot files review/awaiting-author Reviewed; waiting on the author to respond size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: search MCP tool returns D1_ERROR: no such column: c.title when collections argument is omitted

2 participants