From 73547187beac3e431e1613e13176cc82b88393aa Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 22 Nov 2025 02:58:51 +0300 Subject: [PATCH 1/3] fix: search titles rewritten --- sql/queries.sql.go | 101 +++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 27 deletions(-) diff --git a/sql/queries.sql.go b/sql/queries.sql.go index 7d970cb..c043f8a 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -287,8 +287,23 @@ func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams 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 + t.id, t.title_names, t.studio_id, t.poster_id, t.title_status, t.rating, t.rating_count, t.release_year, t.release_season, t.season, t.episodes_aired, t.episodes_all, t.episodes_len, + i.storage_type as title_storage_type, + i.image_path as title_image_path, + g.tag_names as tag_names, + s.studio_name as studio_name, + s.illust_id as studio_illust_id, + s.studio_desc as studio_desc, + si.storage_type as studio_storage_type, + si.image_path as studio_image_path + +FROM titles as t +LEFT JOIN images as i ON (t.image_id = i.id) +LEFT JOIN title_tags as tt ON (t.id = tt.title_id) +LEFT JOIN tags as g ON (tt.tag_id = g.id) +LEFT JOIN studios as s ON (t.studio_id = s.id) +LEFT JOIN images as si ON (s.illust_id = si.id) + WHERE CASE WHEN $1::boolean THEN @@ -296,17 +311,17 @@ WHERE CASE $2::text WHEN 'year' THEN ($3::int IS NULL) OR - (release_year > $3::int) OR - (release_year = $3::int AND id > $4::bigint) + (t.release_year > $3::int) OR + (t.release_year = $3::int AND t.id > $4::bigint) WHEN 'rating' THEN ($5::float IS NULL) OR - (rating > $5::float) OR - (rating = $5::float AND id > $4::bigint) + (t.rating > $5::float) OR + (t.rating = $5::float AND t.id > $4::bigint) WHEN 'id' THEN ($4::bigint IS NULL) OR - (id > $4::bigint) + (t.id > $4::bigint) ELSE true -- fallback END @@ -316,17 +331,17 @@ WHERE CASE $2::text WHEN 'year' THEN ($3::int IS NULL) OR - (release_year < $3::int) OR - (release_year = $3::int AND id < $4::bigint) + (t.release_year < $3::int) OR + (t.release_year = $3::int AND t.id < $4::bigint) WHEN 'rating' THEN ($5::float IS NULL) OR - (rating < $5::float) OR - (rating = $5::float AND id < $4::bigint) + (t.rating < $5::float) OR + (t.rating = $5::float AND t.id < $4::bigint) WHEN 'id' THEN ($4::bigint IS NULL) OR - (id < $4::bigint) + (t.id < $4::bigint) ELSE true END @@ -339,7 +354,7 @@ WHERE SELECT bool_and( EXISTS ( SELECT 1 - FROM jsonb_each_text(title_names) AS t(key, val) + FROM jsonb_each_text(t.title_names) AS t(key, val) WHERE val ILIKE pattern ) ) @@ -355,28 +370,28 @@ WHERE END ) - 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) + AND ($7::title_status_t IS NULL OR t.title_status = $7::title_status_t) + AND ($8::float IS NULL OR t.rating >= $8::float) + AND ($9::int IS NULL OR t.release_year = $9::int) + AND ($10::release_season_t IS NULL OR t.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 + WHEN $2::text = 'id' THEN t.id + WHEN $2::text = 'year' THEN t.release_year + WHEN $2::text = 'rating' THEN t.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 + WHEN $2::text = 'id' THEN t.id + WHEN $2::text = 'year' THEN t.release_year + WHEN $2::text = 'rating' THEN t.rating END END DESC, - CASE WHEN $2::text <> 'id' THEN id END ASC + CASE WHEN $2::text <> 'id' THEN t.id END ASC LIMIT COALESCE($11::int, 100) ` @@ -395,7 +410,31 @@ type SearchTitlesParams struct { Limit *int32 `json:"limit"` } -func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]Title, error) { +type SearchTitlesRow struct { + ID int64 `json:"id"` + TitleNames []byte `json:"title_names"` + StudioID int64 `json:"studio_id"` + PosterID *int64 `json:"poster_id"` + TitleStatus TitleStatusT `json:"title_status"` + Rating *float64 `json:"rating"` + RatingCount *int32 `json:"rating_count"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + Season *int32 `json:"season"` + EpisodesAired *int32 `json:"episodes_aired"` + EpisodesAll *int32 `json:"episodes_all"` + EpisodesLen []byte `json:"episodes_len"` + TitleStorageType NullStorageTypeT `json:"title_storage_type"` + TitleImagePath *string `json:"title_image_path"` + TagNames []byte `json:"tag_names"` + StudioName *string `json:"studio_name"` + StudioIllustID *int64 `json:"studio_illust_id"` + StudioDesc *string `json:"studio_desc"` + StudioStorageType NullStorageTypeT `json:"studio_storage_type"` + StudioImagePath *string `json:"studio_image_path"` +} + +func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]SearchTitlesRow, error) { rows, err := q.db.Query(ctx, searchTitles, arg.Forward, arg.SortBy, @@ -413,9 +452,9 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]T return nil, err } defer rows.Close() - items := []Title{} + items := []SearchTitlesRow{} for rows.Next() { - var i Title + var i SearchTitlesRow if err := rows.Scan( &i.ID, &i.TitleNames, @@ -430,6 +469,14 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]T &i.EpisodesAired, &i.EpisodesAll, &i.EpisodesLen, + &i.TitleStorageType, + &i.TitleImagePath, + &i.TagNames, + &i.StudioName, + &i.StudioIllustID, + &i.StudioDesc, + &i.StudioStorageType, + &i.StudioImagePath, ); err != nil { return nil, err } From 870bbe2395ae08481f391b4097d8dd0c1b08793b Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 22 Nov 2025 05:14:42 +0300 Subject: [PATCH 2/3] feat: now status can be array --- api/paths/titles.yaml | 6 ++++++ api/schemas/Image.yaml | 3 ++- api/schemas/Studio.yaml | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/api/paths/titles.yaml b/api/paths/titles.yaml index e868ed6..3c5cf76 100644 --- a/api/paths/titles.yaml +++ b/api/paths/titles.yaml @@ -15,7 +15,13 @@ get: - in: query name: status schema: + type: array + items: $ref: '../schemas/enums/TitleStatus.yaml' + description: List of title statuses to filter + style: form + explode: true + - in: query name: rating schema: diff --git a/api/schemas/Image.yaml b/api/schemas/Image.yaml index 7226b29..4ae3cb7 100644 --- a/api/schemas/Image.yaml +++ b/api/schemas/Image.yaml @@ -1,6 +1,7 @@ type: object properties: - id: +# id выпиливаем + id: type: integer format: int64 storage_type: diff --git a/api/schemas/Studio.yaml b/api/schemas/Studio.yaml index 35b40a8..26a2adf 100644 --- a/api/schemas/Studio.yaml +++ b/api/schemas/Studio.yaml @@ -3,6 +3,7 @@ required: - id - name properties: +# id не нужен id: type: integer format: int64 From 89a05492c350433ee7d83a6256f5cca9e0fe2b06 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 22 Nov 2025 06:34:07 +0300 Subject: [PATCH 3/3] refact: optimizied queries --- api/_build/openapi.yaml | 7 +- api/api.gen.go | 14 ++- api/paths/titles.yaml | 8 +- modules/backend/handlers/titles.go | 177 ++++++++++++++++++----------- modules/backend/queries.sql | 91 ++++++++++----- sql/queries.sql.go | 140 ++++++++++++++++------- 6 files changed, 294 insertions(+), 143 deletions(-) diff --git a/api/_build/openapi.yaml b/api/_build/openapi.yaml index 5ff77e0..c059166 100644 --- a/api/_build/openapi.yaml +++ b/api/_build/openapi.yaml @@ -23,7 +23,12 @@ paths: - in: query name: status schema: - $ref: '#/components/schemas/TitleStatus' + type: array + items: + $ref: '#/components/schemas/TitleStatus' + description: List of title statuses to filter + style: form + explode: false - in: query name: rating schema: diff --git a/api/api.gen.go b/api/api.gen.go index f252a5a..e56f6b8 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -156,11 +156,13 @@ type Cursor = string // GetTitlesParams defines parameters for GetTitles. type GetTitlesParams struct { - Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` - Sort *TitleSort `form:"sort,omitempty" json:"sort,omitempty"` - SortForward *bool `form:"sort_forward,omitempty" json:"sort_forward,omitempty"` - Word *string `form:"word,omitempty" json:"word,omitempty"` - Status *TitleStatus `form:"status,omitempty" json:"status,omitempty"` + Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` + Sort *TitleSort `form:"sort,omitempty" json:"sort,omitempty"` + SortForward *bool `form:"sort_forward,omitempty" json:"sort_forward,omitempty"` + Word *string `form:"word,omitempty" json:"word,omitempty"` + + // Status List of title statuses to filter + Status *[]TitleStatus `form:"status,omitempty" json:"status,omitempty"` Rating *float64 `form:"rating,omitempty" json:"rating,omitempty"` ReleaseYear *int32 `form:"release_year,omitempty" json:"release_year,omitempty"` ReleaseSeason *ReleaseSeason `form:"release_season,omitempty" json:"release_season,omitempty"` @@ -638,7 +640,7 @@ func (siw *ServerInterfaceWrapper) GetTitles(c *gin.Context) { // ------------- Optional query parameter "status" ------------- - err = runtime.BindQueryParameter("form", true, false, "status", c.Request.URL.Query(), ¶ms.Status) + err = runtime.BindQueryParameter("form", false, false, "status", c.Request.URL.Query(), ¶ms.Status) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter status: %w", err), http.StatusBadRequest) return diff --git a/api/paths/titles.yaml b/api/paths/titles.yaml index 3c5cf76..af2d17b 100644 --- a/api/paths/titles.yaml +++ b/api/paths/titles.yaml @@ -15,12 +15,12 @@ get: - in: query name: status schema: - type: array - items: - $ref: '../schemas/enums/TitleStatus.yaml' + type: array + items: + $ref: '../schemas/enums/TitleStatus.yaml' description: List of title statuses to filter style: form - explode: true + explode: false - in: query name: rating diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index aea6037..71547c2 100644 --- a/modules/backend/handlers/titles.go +++ b/modules/backend/handlers/titles.go @@ -20,18 +20,44 @@ func Word2Sqlc(s *string) *string { return s } -func TitleStatus2Sqlc(s *oapi.TitleStatus) (*sqlc.TitleStatusT, error) { +type SqlcStatus struct { + ongoing string + finished string + planned string +} + +func TitleStatus2Sqlc(s []oapi.TitleStatus) (*SqlcStatus, error) { if s == nil { return nil, nil } - var t sqlc.TitleStatusT + var sqlc_status SqlcStatus + for _, t := range s { + switch t { + case oapi.TitleStatusFinished: + sqlc_status.finished = "finished" + case oapi.TitleStatusOngoing: + sqlc_status.ongoing = "ongoing" + case oapi.TitleStatusPlanned: + sqlc_status.planned = "planned" + default: + return nil, fmt.Errorf("unexpected tittle status: %s", t) + } + } + return &sqlc_status, nil +} + +func TitleStatus2oapi(s *sqlc.TitleStatusT) (*oapi.TitleStatus, error) { + if s == nil { + return nil, nil + } + var t oapi.TitleStatus switch *s { - case oapi.TitleStatusFinished: - t = sqlc.TitleStatusTFinished - case oapi.TitleStatusOngoing: - t = sqlc.TitleStatusTOngoing - case oapi.TitleStatusPlanned: - t = sqlc.TitleStatusTPlanned + case sqlc.TitleStatusTFinished: + t = oapi.TitleStatusFinished + case sqlc.TitleStatusTOngoing: + t = oapi.TitleStatusOngoing + case sqlc.TitleStatusTPlanned: + t = oapi.TitleStatusPlanned default: return nil, fmt.Errorf("unexpected tittle status: %s", *s) } @@ -132,66 +158,74 @@ func (s Server) GetStudio(ctx context.Context, id int64) (*oapi.Studio, error) { return &oapi_studio, nil } -func (s Server) mapTitle(ctx context.Context, title sqlc.Title) (oapi.Title, error) { +func (s Server) mapTitle(ctx context.Context, title sqlc.SearchTitlesRow) (oapi.Title, error) { - var oapi_title oapi.Title + // var oapi_title oapi.Title - title_names := make(map[string][]string, 1) + title_names := make(map[string][]string, 0) err := json.Unmarshal(title.TitleNames, &title_names) if err != nil { - return oapi_title, fmt.Errorf("unmarshal TitleNames: %v", err) + return oapi.Title{}, fmt.Errorf("unmarshal TitleNames: %v", err) } - episodes_lens := make(map[string]float64, 1) + episodes_lens := make(map[string]float64, 0) err = json.Unmarshal(title.EpisodesLen, &episodes_lens) if err != nil { - return oapi_title, fmt.Errorf("unmarshal EpisodesLen: %v", err) + return oapi.Title{}, fmt.Errorf("unmarshal EpisodesLen: %v", err) } - oapi_tag_names, err := s.GetTagsByTitleId(ctx, title.ID) + oapi_tag_names := make(oapi.Tags, 0) + err = json.Unmarshal(title.TagNames, &oapi_tag_names) if err != nil { - return oapi_title, fmt.Errorf("GetTagsByTitleId: %v", err) + return oapi.Title{}, fmt.Errorf("unmarshalling title_tag: %v", err) } - if oapi_tag_names != nil { - oapi_title.Tags = oapi_tag_names + + var oapi_studio oapi.Studio + + oapi_studio.Id = title.StudioID + if title.StudioName != nil { + oapi_studio.Name = *title.StudioName } + oapi_studio.Description = title.StudioDesc + if title.StudioIllustID != nil { + oapi_studio.Poster.Id = title.StudioIllustID + oapi_studio.Poster.ImagePath = title.StudioImagePath + oapi_studio.Poster.StorageType = &title.StudioStorageType + } + + var oapi_image oapi.Image if title.PosterID != nil { - oapi_image, err := s.GetImage(ctx, *title.PosterID) - if err != nil { - return oapi_title, fmt.Errorf("GetImage: %v", err) - } - if oapi_image != nil { - oapi_title.Poster = oapi_image - } - } - - oapi_studio, err := s.GetStudio(ctx, title.StudioID) - if err != nil { - return oapi_title, fmt.Errorf("GetStudio: %v", err) - } - if oapi_studio != nil { - oapi_title.Studio = oapi_studio + oapi_image.Id = title.PosterID + oapi_image.ImagePath = title.TitleImagePath + oapi_image.StorageType = &title.TitleStorageType } + var release_season oapi.ReleaseSeason if title.ReleaseSeason != nil { - rs := oapi.ReleaseSeason(*title.ReleaseSeason) - oapi_title.ReleaseSeason = &rs - } else { - oapi_title.ReleaseSeason = nil + release_season = oapi.ReleaseSeason(*title.ReleaseSeason) } - ts := oapi.TitleStatus(title.TitleStatus) - oapi_title.TitleStatus = &ts - - oapi_title.Id = title.ID - oapi_title.Rating = title.Rating - oapi_title.RatingCount = title.RatingCount - oapi_title.ReleaseYear = title.ReleaseYear - oapi_title.TitleNames = title_names - oapi_title.EpisodesAired = title.EpisodesAired - oapi_title.EpisodesAll = title.EpisodesAll - oapi_title.EpisodesLen = &episodes_lens + oapi_status, err := TitleStatus2oapi(&title.TitleStatus) + if err != nil { + return oapi.Title{}, fmt.Errorf("TitleStatus2oapi: %v", err) + } + oapi_title := oapi.Title{ + EpisodesAired: title.EpisodesAired, + EpisodesAll: title.EpisodesAired, + EpisodesLen: &episodes_lens, + Id: title.ID, + Poster: &oapi_image, + Rating: title.Rating, + RatingCount: title.RatingCount, + ReleaseSeason: &release_season, + ReleaseYear: title.ReleaseYear, + Studio: &oapi_studio, + Tags: oapi_tag_names, + TitleNames: title_names, + TitleStatus: oapi_status, + // AdditionalProperties: + } return oapi_title, nil } @@ -207,8 +241,29 @@ func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitl log.Errorf("%v", err) return oapi.GetTitlesTitleId500Response{}, nil } - - oapi_title, err = s.mapTitle(ctx, sqlc_title) + _sqlc_title := sqlc.SearchTitlesRow{ + ID: sqlc_title.ID, + StudioID: sqlc_title.StudioID, + PosterID: sqlc_title.PosterID, + TitleStatus: sqlc_title.TitleStatus, + Rating: sqlc_title.Rating, + RatingCount: sqlc_title.RatingCount, + ReleaseYear: sqlc_title.ReleaseYear, + ReleaseSeason: sqlc_title.ReleaseSeason, + Season: sqlc_title.Season, + EpisodesAired: sqlc_title.EpisodesAired, + EpisodesAll: sqlc_title.EpisodesAll, + EpisodesLen: sqlc_title.EpisodesLen, + TitleStorageType: sqlc_title.TitleStorageType, + TitleImagePath: sqlc_title.TitleImagePath, + TagNames: sqlc_title.TitleNames, + StudioName: sqlc_title.StudioName, + StudioIllustID: sqlc_title.StudioIllustID, + StudioDesc: sqlc_title.StudioDesc, + StudioStorageType: sqlc_title.StudioStorageType, + StudioImagePath: sqlc_title.StudioImagePath, + } + oapi_title, err = s.mapTitle(ctx, _sqlc_title) if err != nil { log.Errorf("%v", err) return oapi.GetTitlesTitleId500Response{}, nil @@ -220,19 +275,8 @@ func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitl func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObject) (oapi.GetTitlesResponseObject, error) { opai_titles := make([]oapi.Title, 0) - // old_cursor := oapi.CursorObj{ - // Id: 1, - // } - - // if request.Params.Cursor != nil { - // if old_cursor, err := parseCursor(*request.Params.Cursor); err != nil { - // log.Errorf("%v", err) - // return oapi.GetTitles400Response{}, err - // } - // } - word := Word2Sqlc(request.Params.Word) - status, err := TitleStatus2Sqlc(request.Params.Status) + status, err := TitleStatus2Sqlc(*request.Params.Status) if err != nil { log.Errorf("%v", err) return oapi.GetTitles400Response{}, err @@ -245,7 +289,9 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje params := sqlc.SearchTitlesParams{ Word: word, - Status: status, + Ongoing: status.ongoing, + Finished: status.finished, + Planned: status.planned, Rating: request.Params.Rating, ReleaseYear: request.Params.ReleaseYear, ReleaseSeason: season, @@ -254,6 +300,9 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje Limit: request.Params.Limit, } + if request.Params.SortForward != nil { + params.Forward = *request.Params.SortForward + } if request.Params.Sort != nil { params.SortBy = string(*request.Params.Sort) if request.Params.Cursor != nil { @@ -289,7 +338,7 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje if request.Params.Sort != nil { switch string(*request.Params.Sort) { case "year": - tmp := string(*t.ReleaseYear) + tmp := fmt.Sprint("%d", *t.ReleaseYear) new_cursor.Param = &tmp case "rating": tmp := strconv.FormatFloat(*t.Rating, 'f', -1, 64) diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index 7118781..16f8120 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -73,14 +73,48 @@ RETURNING id, tag_names; -- WHERE user_id = $1; -- name: GetTitleByID :one -SELECT * -FROM titles -WHERE id = sqlc.arg('title_id')::bigint; +-- sqlc.struct: TitlesFull +SELECT + t.*, + i.storage_type::text as title_storage_type, + i.image_path as title_image_path, + jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + s.studio_name as studio_name, + s.illust_id as studio_illust_id, + s.studio_desc as studio_desc, + si.storage_type::text as studio_storage_type, + si.image_path as studio_image_path + +FROM titles as t +LEFT JOIN images as i ON (t.image_id = i.id) +LEFT JOIN title_tags as tt ON (t.id = tt.title_id) +LEFT JOIN tags as g ON (tt.tag_id = g.id) +LEFT JOIN studios as s ON (t.studio_id = s.id) +LEFT JOIN images as si ON (s.illust_id = si.id) + +WHERE id = sqlc.arg('title_id')::bigint +GROUP BY + t.id, i.id, s.id, si.id; -- name: SearchTitles :many SELECT - * -FROM titles + t.*, + i.storage_type::text as title_storage_type, + i.image_path as title_image_path, + jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + s.studio_name as studio_name, + s.illust_id as studio_illust_id, + s.studio_desc as studio_desc, + si.storage_type::text as studio_storage_type, + si.image_path as studio_image_path + +FROM titles as t +LEFT JOIN images as i ON (t.image_id = i.id) +LEFT JOIN title_tags as tt ON (t.id = tt.title_id) +LEFT JOIN tags as g ON (tt.tag_id = g.id) +LEFT JOIN studios as s ON (t.studio_id = s.id) +LEFT JOIN images as si ON (s.illust_id = si.id) + WHERE CASE WHEN sqlc.arg('forward')::boolean THEN @@ -88,17 +122,17 @@ WHERE CASE sqlc.arg('sort_by')::text WHEN 'year' THEN (sqlc.narg('cursor_year')::int IS NULL) OR - (release_year > sqlc.narg('cursor_year')::int) OR - (release_year = sqlc.narg('cursor_year')::int AND id > sqlc.narg('cursor_id')::bigint) + (t.release_year > sqlc.narg('cursor_year')::int) OR + (t.release_year = sqlc.narg('cursor_year')::int AND t.id > sqlc.narg('cursor_id')::bigint) WHEN 'rating' THEN (sqlc.narg('cursor_rating')::float IS NULL) OR - (rating > sqlc.narg('cursor_rating')::float) OR - (rating = sqlc.narg('cursor_rating')::float AND id > sqlc.narg('cursor_id')::bigint) + (t.rating > sqlc.narg('cursor_rating')::float) OR + (t.rating = sqlc.narg('cursor_rating')::float AND t.id > sqlc.narg('cursor_id')::bigint) WHEN 'id' THEN (sqlc.narg('cursor_id')::bigint IS NULL) OR - (id > sqlc.narg('cursor_id')::bigint) + (t.id > sqlc.narg('cursor_id')::bigint) ELSE true -- fallback END @@ -108,17 +142,17 @@ WHERE CASE sqlc.arg('sort_by')::text WHEN 'year' THEN (sqlc.narg('cursor_year')::int IS NULL) OR - (release_year < sqlc.narg('cursor_year')::int) OR - (release_year = sqlc.narg('cursor_year')::int AND id < sqlc.narg('cursor_id')::bigint) + (t.release_year < sqlc.narg('cursor_year')::int) OR + (t.release_year = sqlc.narg('cursor_year')::int AND t.id < sqlc.narg('cursor_id')::bigint) WHEN 'rating' THEN (sqlc.narg('cursor_rating')::float IS NULL) OR - (rating < sqlc.narg('cursor_rating')::float) OR - (rating = sqlc.narg('cursor_rating')::float AND id < sqlc.narg('cursor_id')::bigint) + (t.rating < sqlc.narg('cursor_rating')::float) OR + (t.rating = sqlc.narg('cursor_rating')::float AND t.id < sqlc.narg('cursor_id')::bigint) WHEN 'id' THEN (sqlc.narg('cursor_id')::bigint IS NULL) OR - (id < sqlc.narg('cursor_id')::bigint) + (t.id < sqlc.narg('cursor_id')::bigint) ELSE true END @@ -131,7 +165,7 @@ WHERE SELECT bool_and( EXISTS ( SELECT 1 - FROM jsonb_each_text(title_names) AS t(key, val) + FROM jsonb_each_text(t.title_names) AS t(key, val) WHERE val ILIKE pattern ) ) @@ -147,28 +181,31 @@ WHERE END ) - AND (sqlc.narg('status')::title_status_t IS NULL OR title_status = sqlc.narg('status')::title_status_t) - AND (sqlc.narg('rating')::float IS NULL OR rating >= sqlc.narg('rating')::float) - AND (sqlc.narg('release_year')::int IS NULL OR release_year = sqlc.narg('release_year')::int) - AND (sqlc.narg('release_season')::release_season_t IS NULL OR release_season = sqlc.narg('release_season')::release_season_t) + AND (t.title_status::text IN (sqlc.arg('ongoing')::text, sqlc.arg('finished')::text, sqlc.arg('planned')::text)) + AND (sqlc.narg('rating')::float IS NULL OR t.rating >= sqlc.narg('rating')::float) + AND (sqlc.narg('release_year')::int IS NULL OR t.release_year = sqlc.narg('release_year')::int) + AND (sqlc.narg('release_season')::release_season_t IS NULL OR t.release_season = sqlc.narg('release_season')::release_season_t) + +GROUP BY + t.id, i.id, s.id, si.id ORDER BY CASE WHEN sqlc.arg('forward')::boolean THEN CASE - WHEN sqlc.arg('sort_by')::text = 'id' THEN id - WHEN sqlc.arg('sort_by')::text = 'year' THEN release_year - WHEN sqlc.arg('sort_by')::text = 'rating' THEN rating + WHEN sqlc.arg('sort_by')::text = 'id' THEN t.id + WHEN sqlc.arg('sort_by')::text = 'year' THEN t.release_year + WHEN sqlc.arg('sort_by')::text = 'rating' THEN t.rating END END ASC, CASE WHEN NOT sqlc.arg('forward')::boolean THEN CASE - WHEN sqlc.arg('sort_by')::text = 'id' THEN id - WHEN sqlc.arg('sort_by')::text = 'year' THEN release_year - WHEN sqlc.arg('sort_by')::text = 'rating' THEN rating + WHEN sqlc.arg('sort_by')::text = 'id' THEN t.id + WHEN sqlc.arg('sort_by')::text = 'year' THEN t.release_year + WHEN sqlc.arg('sort_by')::text = 'rating' THEN t.rating END END DESC, - CASE WHEN sqlc.arg('sort_by')::text <> 'id' THEN id END ASC + CASE WHEN sqlc.arg('sort_by')::text <> 'id' THEN t.id END ASC LIMIT COALESCE(sqlc.narg('limit')::int, 100); -- 100 is default limit diff --git a/sql/queries.sql.go b/sql/queries.sql.go index c043f8a..4342a12 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -116,11 +116,53 @@ const getTitleByID = `-- name: GetTitleByID :one -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 +SELECT + t.id, t.title_names, t.studio_id, t.poster_id, t.title_status, t.rating, t.rating_count, t.release_year, t.release_season, t.season, t.episodes_aired, t.episodes_all, t.episodes_len, + i.storage_type::text as title_storage_type, + i.image_path as title_image_path, + jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + s.studio_name as studio_name, + s.illust_id as studio_illust_id, + s.studio_desc as studio_desc, + si.storage_type::text as studio_storage_type, + si.image_path as studio_image_path + +FROM titles as t +LEFT JOIN images as i ON (t.image_id = i.id) +LEFT JOIN title_tags as tt ON (t.id = tt.title_id) +LEFT JOIN tags as g ON (tt.tag_id = g.id) +LEFT JOIN studios as s ON (t.studio_id = s.id) +LEFT JOIN images as si ON (s.illust_id = si.id) + WHERE id = $1::bigint +GROUP BY + t.id, i.id, s.id, si.id ` +type GetTitleByIDRow struct { + ID int64 `json:"id"` + TitleNames []byte `json:"title_names"` + StudioID int64 `json:"studio_id"` + PosterID *int64 `json:"poster_id"` + TitleStatus TitleStatusT `json:"title_status"` + Rating *float64 `json:"rating"` + RatingCount *int32 `json:"rating_count"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + Season *int32 `json:"season"` + EpisodesAired *int32 `json:"episodes_aired"` + EpisodesAll *int32 `json:"episodes_all"` + EpisodesLen []byte `json:"episodes_len"` + TitleStorageType string `json:"title_storage_type"` + TitleImagePath *string `json:"title_image_path"` + TagNames []byte `json:"tag_names"` + StudioName *string `json:"studio_name"` + StudioIllustID *int64 `json:"studio_illust_id"` + StudioDesc *string `json:"studio_desc"` + StudioStorageType string `json:"studio_storage_type"` + StudioImagePath *string `json:"studio_image_path"` +} + // -- name: ListUsers :many // SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date // FROM users @@ -144,9 +186,10 @@ WHERE id = $1::bigint // -- name: DeleteUser :exec // DELETE FROM users // WHERE user_id = $1; -func (q *Queries) GetTitleByID(ctx context.Context, titleID int64) (Title, error) { +// sqlc.struct: TitlesFull +func (q *Queries) GetTitleByID(ctx context.Context, titleID int64) (GetTitleByIDRow, error) { row := q.db.QueryRow(ctx, getTitleByID, titleID) - var i Title + var i GetTitleByIDRow err := row.Scan( &i.ID, &i.TitleNames, @@ -161,6 +204,14 @@ func (q *Queries) GetTitleByID(ctx context.Context, titleID int64) (Title, error &i.EpisodesAired, &i.EpisodesAll, &i.EpisodesLen, + &i.TitleStorageType, + &i.TitleImagePath, + &i.TagNames, + &i.StudioName, + &i.StudioIllustID, + &i.StudioDesc, + &i.StudioStorageType, + &i.StudioImagePath, ) return i, err } @@ -288,15 +339,15 @@ func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams const searchTitles = `-- name: SearchTitles :many SELECT t.id, t.title_names, t.studio_id, t.poster_id, t.title_status, t.rating, t.rating_count, t.release_year, t.release_season, t.season, t.episodes_aired, t.episodes_all, t.episodes_len, - i.storage_type as title_storage_type, - i.image_path as title_image_path, - g.tag_names as tag_names, - s.studio_name as studio_name, - s.illust_id as studio_illust_id, - s.studio_desc as studio_desc, - si.storage_type as studio_storage_type, - si.image_path as studio_image_path - + i.storage_type::text as title_storage_type, + i.image_path as title_image_path, + jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + s.studio_name as studio_name, + s.illust_id as studio_illust_id, + s.studio_desc as studio_desc, + si.storage_type::text as studio_storage_type, + si.image_path as studio_image_path + FROM titles as t LEFT JOIN images as i ON (t.image_id = i.id) LEFT JOIN title_tags as tt ON (t.id = tt.title_id) @@ -370,10 +421,13 @@ WHERE END ) - AND ($7::title_status_t IS NULL OR t.title_status = $7::title_status_t) - AND ($8::float IS NULL OR t.rating >= $8::float) - AND ($9::int IS NULL OR t.release_year = $9::int) - AND ($10::release_season_t IS NULL OR t.release_season = $10::release_season_t) + AND (t.title_status::text IN ($7::text, $8::text, $9::text)) + AND ($10::float IS NULL OR t.rating >= $10::float) + AND ($11::int IS NULL OR t.release_year = $11::int) + AND ($12::release_season_t IS NULL OR t.release_season = $12::release_season_t) + +GROUP BY + t.id, i.id, s.id, si.id ORDER BY CASE WHEN $1::boolean THEN @@ -393,7 +447,7 @@ ORDER BY CASE WHEN $2::text <> 'id' THEN t.id END ASC -LIMIT COALESCE($11::int, 100) +LIMIT COALESCE($13::int, 100) ` type SearchTitlesParams struct { @@ -403,7 +457,9 @@ type SearchTitlesParams struct { CursorID *int64 `json:"cursor_id"` CursorRating *float64 `json:"cursor_rating"` Word *string `json:"word"` - Status *TitleStatusT `json:"status"` + Ongoing string `json:"ongoing"` + Finished string `json:"finished"` + Planned string `json:"planned"` Rating *float64 `json:"rating"` ReleaseYear *int32 `json:"release_year"` ReleaseSeason *ReleaseSeasonT `json:"release_season"` @@ -411,27 +467,27 @@ type SearchTitlesParams struct { } type SearchTitlesRow struct { - ID int64 `json:"id"` - TitleNames []byte `json:"title_names"` - StudioID int64 `json:"studio_id"` - PosterID *int64 `json:"poster_id"` - TitleStatus TitleStatusT `json:"title_status"` - Rating *float64 `json:"rating"` - RatingCount *int32 `json:"rating_count"` - ReleaseYear *int32 `json:"release_year"` - ReleaseSeason *ReleaseSeasonT `json:"release_season"` - Season *int32 `json:"season"` - EpisodesAired *int32 `json:"episodes_aired"` - EpisodesAll *int32 `json:"episodes_all"` - EpisodesLen []byte `json:"episodes_len"` - TitleStorageType NullStorageTypeT `json:"title_storage_type"` - TitleImagePath *string `json:"title_image_path"` - TagNames []byte `json:"tag_names"` - StudioName *string `json:"studio_name"` - StudioIllustID *int64 `json:"studio_illust_id"` - StudioDesc *string `json:"studio_desc"` - StudioStorageType NullStorageTypeT `json:"studio_storage_type"` - StudioImagePath *string `json:"studio_image_path"` + ID int64 `json:"id"` + TitleNames []byte `json:"title_names"` + StudioID int64 `json:"studio_id"` + PosterID *int64 `json:"poster_id"` + TitleStatus TitleStatusT `json:"title_status"` + Rating *float64 `json:"rating"` + RatingCount *int32 `json:"rating_count"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + Season *int32 `json:"season"` + EpisodesAired *int32 `json:"episodes_aired"` + EpisodesAll *int32 `json:"episodes_all"` + EpisodesLen []byte `json:"episodes_len"` + TitleStorageType string `json:"title_storage_type"` + TitleImagePath *string `json:"title_image_path"` + TagNames []byte `json:"tag_names"` + StudioName *string `json:"studio_name"` + StudioIllustID *int64 `json:"studio_illust_id"` + StudioDesc *string `json:"studio_desc"` + StudioStorageType string `json:"studio_storage_type"` + StudioImagePath *string `json:"studio_image_path"` } func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]SearchTitlesRow, error) { @@ -442,7 +498,9 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S arg.CursorID, arg.CursorRating, arg.Word, - arg.Status, + arg.Ongoing, + arg.Finished, + arg.Planned, arg.Rating, arg.ReleaseYear, arg.ReleaseSeason,