feat: cursor implemented

This commit is contained in:
Iron_Felix 2025-11-22 00:01:48 +03:00
parent 9c0fada00e
commit af0492cdf1
8 changed files with 435 additions and 106 deletions

View file

@ -179,7 +179,7 @@ func (q *Queries) GetTitleTags(ctx context.Context, titleID int64) ([][]byte, er
return nil, err
}
defer rows.Close()
var items [][]byte
items := [][]byte{}
for rows.Next() {
var tag_names []byte
if err := rows.Scan(&tag_names); err != nil {
@ -289,84 +289,131 @@ const searchTitles = `-- name: SearchTitles :many
SELECT
id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len
FROM titles
WHERE
CASE
WHEN $1::text IS NOT NULL THEN
(
SELECT bool_and(
EXISTS (
SELECT 1
FROM jsonb_each_text(title_names) AS t(key, val)
WHERE val ILIKE pattern
)
)
FROM unnest(
ARRAY(
SELECT '%' || trim(w) || '%'
FROM unnest(string_to_array($1::text, ' ')) AS w
WHERE trim(w) <> ''
)
) AS pattern
)
ELSE true
WHERE
CASE
WHEN $1::boolean THEN
-- forward: greater than cursor (next page)
CASE $2::text
WHEN 'year' THEN
($3::int IS NULL) OR
(release_year > $3::int) OR
(release_year = $3::int AND id > $4::bigint)
WHEN 'rating' THEN
($5::float IS NULL) OR
(rating > $5::float) OR
(rating = $5::float AND id > $4::bigint)
WHEN 'id' THEN
($4::bigint IS NULL) OR
(id > $4::bigint)
ELSE true -- fallback
END
ELSE
-- backward: less than cursor (prev page)
CASE $2::text
WHEN 'year' THEN
($3::int IS NULL) OR
(release_year < $3::int) OR
(release_year = $3::int AND id < $4::bigint)
WHEN 'rating' THEN
($5::float IS NULL) OR
(rating < $5::float) OR
(rating = $5::float AND id < $4::bigint)
WHEN 'id' THEN
($4::bigint IS NULL) OR
(id < $4::bigint)
ELSE true
END
END
AND ($2::title_status_t IS NULL OR title_status = $2::title_status_t)
AND ($3::float IS NULL OR rating >= $3::float)
AND ($4::int IS NULL OR release_year = $4::int)
AND ($5::release_season_t IS NULL OR release_season = $5::release_season_t)
ORDER BY
-- Основной ключ: выбранное поле
CASE
WHEN $6::boolean AND $7::text = 'id' THEN id
WHEN $6::boolean AND $7::text = 'year' THEN release_year
WHEN $6::boolean AND $7::text = 'rating' THEN rating
-- WHEN sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'views' THEN views
END ASC,
CASE
WHEN NOT $6::boolean AND $7::text = 'id' THEN id
WHEN NOT $6::boolean AND $7::text = 'year' THEN release_year
WHEN NOT $6::boolean AND $7::text = 'rating' THEN rating
-- WHEN NOT sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'views' THEN views
END DESC,
AND (
CASE
WHEN $6::text IS NOT NULL THEN
(
SELECT bool_and(
EXISTS (
SELECT 1
FROM jsonb_each_text(title_names) AS t(key, val)
WHERE val ILIKE pattern
)
)
FROM unnest(
ARRAY(
SELECT '%' || trim(w) || '%'
FROM unnest(string_to_array($6::text, ' ')) AS w
WHERE trim(w) <> ''
)
) AS pattern
)
ELSE true
END
)
-- Вторичный ключ: id только если НЕ сортируем по id
CASE
WHEN $7::text != 'id' AND $6::boolean THEN id
END ASC,
CASE
WHEN $7::text != 'id' AND NOT $6::boolean THEN id
END DESC
LIMIT COALESCE($8::int, 100)
AND ($7::title_status_t IS NULL OR title_status = $7::title_status_t)
AND ($8::float IS NULL OR rating >= $8::float)
AND ($9::int IS NULL OR release_year = $9::int)
AND ($10::release_season_t IS NULL OR release_season = $10::release_season_t)
ORDER BY
CASE WHEN $1::boolean THEN
CASE
WHEN $2::text = 'id' THEN id
WHEN $2::text = 'year' THEN release_year
WHEN $2::text = 'rating' THEN rating
END
END ASC,
CASE WHEN NOT $1::boolean THEN
CASE
WHEN $2::text = 'id' THEN id
WHEN $2::text = 'year' THEN release_year
WHEN $2::text = 'rating' THEN rating
END
END DESC,
CASE WHEN $2::text <> 'id' THEN id END ASC
LIMIT COALESCE($11::int, 100)
`
type SearchTitlesParams struct {
Forward bool `json:"forward"`
SortBy string `json:"sort_by"`
CursorYear *int32 `json:"cursor_year"`
CursorID *int64 `json:"cursor_id"`
CursorRating *float64 `json:"cursor_rating"`
Word *string `json:"word"`
Status *TitleStatusT `json:"status"`
Rating *float64 `json:"rating"`
ReleaseYear *int32 `json:"release_year"`
ReleaseSeason *ReleaseSeasonT `json:"release_season"`
Forward bool `json:"forward"`
SortBy string `json:"sort_by"`
Limit *int32 `json:"limit"`
}
func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]Title, error) {
rows, err := q.db.Query(ctx, searchTitles,
arg.Forward,
arg.SortBy,
arg.CursorYear,
arg.CursorID,
arg.CursorRating,
arg.Word,
arg.Status,
arg.Rating,
arg.ReleaseYear,
arg.ReleaseSeason,
arg.Forward,
arg.SortBy,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Title
items := []Title{}
for rows.Next() {
var i Title
if err := rows.Scan(
@ -464,7 +511,6 @@ type SearchUserTitlesRow struct {
}
// 100 is default limit
// OFFSET sqlc.narg('offset')::int;
func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesParams) ([]SearchUserTitlesRow, error) {
rows, err := q.db.Query(ctx, searchUserTitles,
arg.Word,
@ -479,7 +525,7 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara
return nil, err
}
defer rows.Close()
var items []SearchUserTitlesRow
items := []SearchUserTitlesRow{}
for rows.Next() {
var i SearchUserTitlesRow
if err := rows.Scan(

View file

@ -12,6 +12,7 @@ sql:
sql_driver: "github.com/jackc/pgx/v5"
emit_json_tags: true
emit_pointers_for_null_types: true
emit_empty_slices: true #slices returned by :many queries will be empty instead of nil
overrides:
- db_type: "uuid"
nullable: false