Skip to content

Full-text search (D1 FTS5)#93

Open
aloewright wants to merge 3 commits into
mainfrom
conductor/alo-150-full-text-search-d1-fts5
Open

Full-text search (D1 FTS5)#93
aloewright wants to merge 3 commits into
mainfrom
conductor/alo-150-full-text-search-d1-fts5

Conversation

@aloewright
Copy link
Copy Markdown
Owner

@aloewright aloewright commented May 8, 2026

Closes ALO-150.

Summary

  • Extends the existing videos_fts FTS5 virtual table to include a tags column (tag labels + slugs concatenated), so search now spans title / description / channel / tags as the ticket spec requires.
  • FTS5 doesn't support ALTER, so the migration drops and recreates the virtual table plus the keep-in-sync triggers, then adds new triggers on video_tags (insert/delete) and tags (label updates) so the tag column stays current.
  • Backend route /api/videos/search?q= and the FE header search box were already in place from earlier work; the MATCH spans all indexed columns so no query change was needed.

Test plan

  • npm test — 491/491 pass
  • npm run lint — clean (incl. AI Gateway guard)
  • After deploy, run npm run db:migrate to apply migration 0019 in prod

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced search functionality to include video tags in search results
    • Search index automatically updates when tags are added, modified, or removed

Extend the videos_fts virtual table with a tags column (label + slug)
and recreate the sync triggers, plus add triggers on video_tags and
tags so the index stays current. The /api/videos/search MATCH already
spans all FTS columns, so tag-keyed queries now hit too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 8, 2026 14:34
@aloewright aloewright added the conductor Conductor-managed PR label May 8, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack

Warning

Rate limit exceeded

@aloewright has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 72694048-c40a-4c94-b63b-9f128cce15d7

📥 Commits

Reviewing files that changed from the base of the PR and between 583e154 and 623a0cb.

📒 Files selected for processing (1)
  • src/db/migrations.test.ts

Walkthrough

This PR adds tags to the videos_fts full-text search index. The migration rebuilds the FTS5 table with a new tags column, backfills existing tags from the video_tags and tags tables, and creates seven triggers to keep the index synchronized with changes to videos, users, video-tag associations, and tag labels.

Changes

Full-Text Search Index for Tags

Layer / File(s) Summary
FTS Virtual Table Schema
src/db/migrations/0019_videos_fts_tags.sql
New videos_fts FTS5 table adds a tags column alongside title, description, and channel_name using unicode61 tokenizer.
Table Cleanup and Backfill
src/db/migrations/0019_videos_fts_tags.sql
Drops previous FTS table and triggers, then backfills by aggregating each video's tag label and slug values into space-separated text.
Synchronization Triggers
src/db/migrations/0019_videos_fts_tags.sql
Seven triggers maintain FTS consistency: on videos insert/update/delete (syncing title, description, channel_name), on user.name update (propagating channel_name), on video_tags insert/delete (recomputing aggregated tags), and on tags.label update (refreshing tags for affected videos).
Migration Test Coverage
src/db/migrations.test.ts
New test validates that the migration creates the FTS table with tags, includes tag aggregation logic, and defines all required synchronization triggers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • aloewright/spooool#23: Directly related as it modifies the same videos_fts FTS5 virtual table and extends the index to include tags with additional trigger coverage.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Full-text search (D1 FTS5)' directly and specifically describes the main change: extending full-text search capabilities using D1's FTS5 feature by adding a tags column to the videos_fts virtual table.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch conductor/alo-150-full-text-search-d1-fts5

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Sentry


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a database migration to update the videos_fts virtual table to include tags, allowing for more comprehensive search functionality. The reviewer identified a critical issue where the migration references a non-existent user table and name column, which conflicts with the actual schema. Additionally, the reviewer noted that using UNINDEXED for video_id in the FTS5 table will cause performance issues due to full table scans during trigger operations as the dataset grows.

Comment on lines +28 to +100
COALESCE(u.name, ''),
COALESCE(
(SELECT GROUP_CONCAT(t.label || ' ' || t.slug, ' ')
FROM video_tags vt
JOIN tags t ON t.slug = vt.tag_slug
WHERE vt.video_id = v.id),
''
)
FROM videos v
LEFT JOIN user u ON u.id = v.user_id
WHERE v.deleted_at IS NULL;

CREATE TRIGGER videos_fts_ai AFTER INSERT ON videos
WHEN new.deleted_at IS NULL
BEGIN
INSERT INTO videos_fts (video_id, title, description, channel_name, tags)
VALUES (
new.id,
new.title,
new.description,
COALESCE((SELECT name FROM user WHERE id = new.user_id), ''),
COALESCE(
(SELECT GROUP_CONCAT(t.label || ' ' || t.slug, ' ')
FROM video_tags vt
JOIN tags t ON t.slug = vt.tag_slug
WHERE vt.video_id = new.id),
''
)
);
END;

CREATE TRIGGER videos_fts_au
AFTER UPDATE OF title, description, user_id, deleted_at ON videos
BEGIN
DELETE FROM videos_fts WHERE video_id = old.id;
INSERT INTO videos_fts (video_id, title, description, channel_name, tags)
SELECT new.id,
new.title,
new.description,
COALESCE(u.name, ''),
COALESCE(
(SELECT GROUP_CONCAT(t.label || ' ' || t.slug, ' ')
FROM video_tags vt
JOIN tags t ON t.slug = vt.tag_slug
WHERE vt.video_id = new.id),
''
)
FROM (SELECT 1) AS s LEFT JOIN user u ON u.id = new.user_id
WHERE new.deleted_at IS NULL;
END;

CREATE TRIGGER videos_fts_ad AFTER DELETE ON videos BEGIN
DELETE FROM videos_fts WHERE video_id = old.id;
END;

CREATE TRIGGER user_name_videos_fts
AFTER UPDATE OF name ON user
BEGIN
DELETE FROM videos_fts WHERE video_id IN (SELECT id FROM videos WHERE user_id = new.id);
INSERT INTO videos_fts (video_id, title, description, channel_name, tags)
SELECT v.id,
v.title,
v.description,
new.name,
COALESCE(
(SELECT GROUP_CONCAT(t.label || ' ' || t.slug, ' ')
FROM video_tags vt
JOIN tags t ON t.slug = vt.tag_slug
WHERE vt.video_id = v.id),
''
)
FROM videos v
WHERE v.user_id = new.id AND v.deleted_at IS NULL;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The migration references a table named user and a column named name (e.g., lines 28, 48, 75, 84, 91). However, src/db/schema.sql defines the table as users and includes columns username and display_name, but no name column. This discrepancy will cause the migration to fail if the database follows the provided schema. Please verify the correct table and column names and ensure consistency with src/db/schema.sql.

DROP TABLE IF EXISTS videos_fts;

CREATE VIRTUAL TABLE videos_fts USING fts5(
video_id UNINDEXED,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The video_id column is marked as UNINDEXED. In SQLite FTS5, performing DELETE or UPDATE operations using a non-rowid column in the WHERE clause (as seen in the triggers on lines 62, 80, 86, 114, 127, and 142) results in a full table scan of the virtual table. While acceptable for small datasets, this will lead to performance degradation as the number of videos increases. If performance becomes an issue, consider mapping the TEXT video ID to an integer rowid or using an external content table if the primary key can be adapted.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 872589c5d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

END;

-- Keep tags column in sync when video_tags rows are inserted/deleted.
CREATE TRIGGER video_tags_ai_fts AFTER INSERT ON video_tags
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make new FTS tag triggers idempotent for migration retries

This migration creates video_tags_ai_fts (and later video_tags_ad_fts / tags_au_label_fts) without IF NOT EXISTS, but the preamble only drops the legacy videos_fts_* and user_name_videos_fts triggers. If an initial apply fails after one of these new triggers is created (for example, a later statement errors), rerunning 0019 will stop with "trigger already exists", which can block deploys until manual DB cleanup.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…150)

Pin the FTS5-includes-tags contract so a future drop/recreate or trigger
removal would fail the migration suite — search relevance silently
regressing was the failure mode that hid the original gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 9, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/db/migrations.test.ts (1)

74-85: ⚡ Quick win

Consider asserting the recreated video/user triggers to close the regression-prevention gap.

The test verifies the three new tag-related triggers but does not assert that the four recreated triggers (videos_fts_ai, videos_fts_au, videos_fts_ad, user_name_videos_fts) are present. Since the migration drops and recreates all of them, accidentally omitting a CREATE TRIGGER statement for any of them would go undetected.

♻️ Proposed additions to close the gap
     expect(sql).toMatch(/AFTER INSERT ON video_tags/);
     expect(sql).toMatch(/AFTER DELETE ON video_tags/);
     expect(sql).toMatch(/AFTER UPDATE OF label ON tags/);
+    // Recreated video + user triggers must also be present.
+    expect(sql).toMatch(/CREATE TRIGGER videos_fts_ai/);
+    expect(sql).toMatch(/CREATE TRIGGER videos_fts_au/);
+    expect(sql).toMatch(/CREATE TRIGGER videos_fts_ad/);
+    expect(sql).toMatch(/CREATE TRIGGER user_name_videos_fts/);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/db/migrations.test.ts` around lines 74 - 85, The test for
0019_videos_fts_tags currently checks only tag-related triggers but must also
assert the recreated FTS triggers are present; update the test in
migrations.test.ts to add expect assertions that the SQL contains CREATE TRIGGER
statements for videos_fts_ai, videos_fts_au, videos_fts_ad and
user_name_videos_fts (e.g. expect(sql).toMatch(/CREATE
TRIGGER\s+videos_fts_ai/), etc.), so any missing recreated trigger will fail the
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/db/migrations.test.ts`:
- Around line 74-85: The test for 0019_videos_fts_tags currently checks only
tag-related triggers but must also assert the recreated FTS triggers are
present; update the test in migrations.test.ts to add expect assertions that the
SQL contains CREATE TRIGGER statements for videos_fts_ai, videos_fts_au,
videos_fts_ad and user_name_videos_fts (e.g. expect(sql).toMatch(/CREATE
TRIGGER\s+videos_fts_ai/), etc.), so any missing recreated trigger will fail the
test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10b19483-a592-4a0c-90b3-fa5145a3dcf9

📥 Commits

Reviewing files that changed from the base of the PR and between 4d3c13f and 583e154.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • src/db/migrations.test.ts
  • src/db/migrations/0019_videos_fts_tags.sql

The 0019 migration drops and recreates videos_fts_ai/au/ad and
user_name_videos_fts. Without explicit assertions, accidentally omitting
any of those CREATE TRIGGER statements would slip through. Per
CodeRabbit feedback on PR #93.
@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 9, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@aloewright
Copy link
Copy Markdown
Owner Author

ecc-tools comment is an informational no-op — its body says ECC bundle files are already tracked in this repository and it's skipping generation of another bundle PR. No change requested, no code change needed. Latest commit (623a0cb) already addressed the CodeRabbit nitpick about asserting the recreated FTS triggers (videos_fts_ai/au/ad, user_name_videos_fts), and CI is green on that commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conductor Conductor-managed PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants