feat: index on movie genres, metadata, filters, pagination

This commit is contained in:
2026-03-19 14:03:01 +01:00
parent 6b170a2705
commit 0cd4386c44
6 changed files with 104 additions and 24 deletions
+24 -9
View File
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/lib/pq"
@@ -18,23 +19,31 @@ type MovieModel struct {
// Create a new GetAll() method which returns a slice of movies. Although we're not
// using them right now, we've set this up to accept the various filter parameters as
// arguments.
func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*Movie, error) {
func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*Movie, Metadata, error) {
// Use full-text search for the title filter.
query := `
SELECT id, created_at, title, year, runtime, genres, version
query := fmt.Sprintf(`
SELECT count(*) OVER(), id, created_at, title, year, runtime, genres, version
FROM movies
WHERE (to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '')
AND (genres @> $2 OR $2 = '{}')
ORDER BY id`
ORDER BY %s %s, id ASC
LIMIT $3 OFFSET $4`, filters.sortColumn(), filters.sortDirection())
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
args := []any{
title,
pq.Array(genres),
filters.limit(),
filters.offset(),
}
// Use QueryContext() to execute the query. This returns a sql.Rows resultset
// containing the result.
rows, err := m.DB.QueryContext(ctx, query, title, pq.Array(genres))
rows, err := m.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
return nil, Metadata{}, err
}
// Importantly, defer a call to rows.Close() to ensure that the resultset is closed
@@ -42,6 +51,7 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
defer rows.Close()
// Initialize an empty slice to hold the movie data.
totalRecords := 0
movies := []*Movie{}
// Use rows.Next to iterate through the rows in the resultset.
@@ -52,6 +62,7 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
// Scan the values from the row into the Movie struct. Again, note that we're
// using the pq.Array() adapter on the genres field here.
err := rows.Scan(
&totalRecords,
&movie.ID,
&movie.CreatedAt,
&movie.Title,
@@ -61,7 +72,7 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
&movie.Version,
)
if err != nil {
return nil, err
return nil, Metadata{}, err
}
// Add the Movie struct to the slice.
@@ -71,11 +82,15 @@ func (m MovieModel) GetAll(title string, genres []string, filters Filters) ([]*M
// When the rows.Next() loop has finished, call rows.Err() to retrieve any error
// that was encountered during the iteration.
if err = rows.Err(); err != nil {
return nil, err
return nil, Metadata{}, err
}
// Generate a Metadata struct, passing in the total record count and pagination
// parameters from the client.
metadata := calculateMetadata(totalRecords, filters.Page, filters.PageSize)
// If everything went OK, then return the slice of movies.
return movies, nil
return movies, metadata, nil
}
// Add a placeholder method for inserting a new record in the movies table.