Challenges
Broken Full-Text Search
The search endpoint uses ILIKE — replace it with a tsvector full-text search.
The Problem
The GET /posts/search?q=term endpoint searches posts using ILIKE. This works on a 10-row table in development. On a real dataset it's a disaster:
ILIKE '%keyword%'does a sequential scan — it reads every row, every character- It can't use a B-tree index because the leading
%prevents index lookups - Relevance ranking is impossible — all matches are equal
The posts table has a search_vector column that Postgres maintains automatically. It's a GENERATED ALWAYS AS column storing a pre-computed tsvector of title and body. There's already a GIN index on it. Your job is to use it.
What You Need to Fix
Load this challenge:
php artisan challenge:start db-broken-ftsTwo files are copied into your project:
Http/controllers/posts/search.php— usesILIKEand%string concatenationroutes/routes.php— registersGET /posts/search
Open Http/controllers/posts/search.php. The query looks like this:
$posts = $db->query(
'SELECT id, title, created_at FROM posts WHERE title ILIKE :q ORDER BY created_at DESC',
['q' => '%' . $q . '%']
)->get();There are three problems:
ILIKE— case-insensitive substring match, no index support%concatenation — necessary for ILIKE but means no index can help- No relevance ordering —
ORDER BY created_atignores match quality
What You Must Do
Replace the broken query with a full-text search using the existing search_vector column:
$posts = $db->query(
"SELECT id, title, created_at,
ts_rank(search_vector, plainto_tsquery('english', :q)) AS relevance
FROM posts
WHERE search_vector @@ plainto_tsquery('english', :q)
ORDER BY relevance DESC
LIMIT 20",
['q' => $q]
)->get();Changes to make:
- Replace
WHERE title ILIKE :qwithWHERE search_vector @@ plainto_tsquery('english', :q) - Change
['q' => '%' . $q . '%']to['q' => $q]— no%needed with full-text search - Replace
ORDER BY created_at DESCwithORDER BY relevance DESC - Remove
ILIKEentirely — it must not appear anywhere in the controller
Hints
@@is the full-text search match operator in Postgresplainto_tsquery('english', :q)converts plain search text into a normalized query — it handles multiple words and stemming automaticallysearch_vectoris a generated column — Postgres already maintains it fromtitle || ' ' || body; you don't need to callto_tsvector()in your queryts_rank(search_vector, query)scores matches; higher is more relevant
Verify
php artisan challenge:verify