stash/pkg/sqlite/performer.go

849 lines
24 KiB
Go

package sqlite
import (
"context"
"database/sql"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
"gopkg.in/guregu/null.v4"
"gopkg.in/guregu/null.v4/zero"
)
const (
performerTable = "performers"
performerIDColumn = "performer_id"
performersAliasesTable = "performer_aliases"
performerAliasColumn = "alias"
performersTagsTable = "performers_tags"
performerURLsTable = "performer_urls"
performerURLColumn = "url"
performerImageBlobColumn = "image_blob"
)
type performerRow struct {
ID int `db:"id" goqu:"skipinsert"`
Name null.String `db:"name"` // TODO: make schema non-nullable
Disambigation zero.String `db:"disambiguation"`
Gender zero.String `db:"gender"`
Birthdate NullDate `db:"birthdate"`
Ethnicity zero.String `db:"ethnicity"`
Country zero.String `db:"country"`
EyeColor zero.String `db:"eye_color"`
Height null.Int `db:"height"`
Measurements zero.String `db:"measurements"`
FakeTits zero.String `db:"fake_tits"`
PenisLength null.Float `db:"penis_length"`
Circumcised zero.String `db:"circumcised"`
CareerLength zero.String `db:"career_length"`
Tattoos zero.String `db:"tattoos"`
Piercings zero.String `db:"piercings"`
Favorite bool `db:"favorite"`
CreatedAt Timestamp `db:"created_at"`
UpdatedAt Timestamp `db:"updated_at"`
// expressed as 1-100
Rating null.Int `db:"rating"`
Details zero.String `db:"details"`
DeathDate NullDate `db:"death_date"`
HairColor zero.String `db:"hair_color"`
Weight null.Int `db:"weight"`
IgnoreAutoTag bool `db:"ignore_auto_tag"`
// not used in resolution or updates
ImageBlob zero.String `db:"image_blob"`
}
func (r *performerRow) fromPerformer(o models.Performer) {
r.ID = o.ID
r.Name = null.StringFrom(o.Name)
r.Disambigation = zero.StringFrom(o.Disambiguation)
if o.Gender != nil && o.Gender.IsValid() {
r.Gender = zero.StringFrom(o.Gender.String())
}
r.Birthdate = NullDateFromDatePtr(o.Birthdate)
r.Ethnicity = zero.StringFrom(o.Ethnicity)
r.Country = zero.StringFrom(o.Country)
r.EyeColor = zero.StringFrom(o.EyeColor)
r.Height = intFromPtr(o.Height)
r.Measurements = zero.StringFrom(o.Measurements)
r.FakeTits = zero.StringFrom(o.FakeTits)
r.PenisLength = null.FloatFromPtr(o.PenisLength)
if o.Circumcised != nil && o.Circumcised.IsValid() {
r.Circumcised = zero.StringFrom(o.Circumcised.String())
}
r.CareerLength = zero.StringFrom(o.CareerLength)
r.Tattoos = zero.StringFrom(o.Tattoos)
r.Piercings = zero.StringFrom(o.Piercings)
r.Favorite = o.Favorite
r.CreatedAt = Timestamp{Timestamp: o.CreatedAt}
r.UpdatedAt = Timestamp{Timestamp: o.UpdatedAt}
r.Rating = intFromPtr(o.Rating)
r.Details = zero.StringFrom(o.Details)
r.DeathDate = NullDateFromDatePtr(o.DeathDate)
r.HairColor = zero.StringFrom(o.HairColor)
r.Weight = intFromPtr(o.Weight)
r.IgnoreAutoTag = o.IgnoreAutoTag
}
func (r *performerRow) resolve() *models.Performer {
ret := &models.Performer{
ID: r.ID,
Name: r.Name.String,
Disambiguation: r.Disambigation.String,
Birthdate: r.Birthdate.DatePtr(),
Ethnicity: r.Ethnicity.String,
Country: r.Country.String,
EyeColor: r.EyeColor.String,
Height: nullIntPtr(r.Height),
Measurements: r.Measurements.String,
FakeTits: r.FakeTits.String,
PenisLength: nullFloatPtr(r.PenisLength),
CareerLength: r.CareerLength.String,
Tattoos: r.Tattoos.String,
Piercings: r.Piercings.String,
Favorite: r.Favorite,
CreatedAt: r.CreatedAt.Timestamp,
UpdatedAt: r.UpdatedAt.Timestamp,
// expressed as 1-100
Rating: nullIntPtr(r.Rating),
Details: r.Details.String,
DeathDate: r.DeathDate.DatePtr(),
HairColor: r.HairColor.String,
Weight: nullIntPtr(r.Weight),
IgnoreAutoTag: r.IgnoreAutoTag,
}
if r.Gender.ValueOrZero() != "" {
v := models.GenderEnum(r.Gender.String)
ret.Gender = &v
}
if r.Circumcised.ValueOrZero() != "" {
v := models.CircumisedEnum(r.Circumcised.String)
ret.Circumcised = &v
}
return ret
}
type performerRowRecord struct {
updateRecord
}
func (r *performerRowRecord) fromPartial(o models.PerformerPartial) {
r.setString("name", o.Name)
r.setNullString("disambiguation", o.Disambiguation)
r.setNullString("gender", o.Gender)
r.setNullDate("birthdate", o.Birthdate)
r.setNullString("ethnicity", o.Ethnicity)
r.setNullString("country", o.Country)
r.setNullString("eye_color", o.EyeColor)
r.setNullInt("height", o.Height)
r.setNullString("measurements", o.Measurements)
r.setNullString("fake_tits", o.FakeTits)
r.setNullFloat64("penis_length", o.PenisLength)
r.setNullString("circumcised", o.Circumcised)
r.setNullString("career_length", o.CareerLength)
r.setNullString("tattoos", o.Tattoos)
r.setNullString("piercings", o.Piercings)
r.setBool("favorite", o.Favorite)
r.setTimestamp("created_at", o.CreatedAt)
r.setTimestamp("updated_at", o.UpdatedAt)
r.setNullInt("rating", o.Rating)
r.setNullString("details", o.Details)
r.setNullDate("death_date", o.DeathDate)
r.setNullString("hair_color", o.HairColor)
r.setNullInt("weight", o.Weight)
r.setBool("ignore_auto_tag", o.IgnoreAutoTag)
}
type performerRepositoryType struct {
repository
tags joinRepository
stashIDs stashIDRepository
scenes joinRepository
images joinRepository
galleries joinRepository
}
var (
performerRepository = performerRepositoryType{
repository: repository{
tableName: performerTable,
idColumn: idColumn,
},
tags: joinRepository{
repository: repository{
tableName: performersTagsTable,
idColumn: performerIDColumn,
},
fkColumn: tagIDColumn,
foreignTable: tagTable,
orderBy: "tags.name ASC",
},
stashIDs: stashIDRepository{
repository{
tableName: "performer_stash_ids",
idColumn: performerIDColumn,
},
},
scenes: joinRepository{
repository: repository{
tableName: performersScenesTable,
idColumn: performerIDColumn,
},
fkColumn: sceneIDColumn,
foreignTable: sceneTable,
},
images: joinRepository{
repository: repository{
tableName: performersImagesTable,
idColumn: performerIDColumn,
},
fkColumn: imageIDColumn,
foreignTable: imageTable,
},
galleries: joinRepository{
repository: repository{
tableName: performersGalleriesTable,
idColumn: performerIDColumn,
},
fkColumn: galleryIDColumn,
foreignTable: galleryTable,
},
}
)
type PerformerStore struct {
blobJoinQueryBuilder
tableMgr *table
}
func NewPerformerStore(blobStore *BlobStore) *PerformerStore {
return &PerformerStore{
blobJoinQueryBuilder: blobJoinQueryBuilder{
blobStore: blobStore,
joinTable: performerTable,
},
tableMgr: performerTableMgr,
}
}
func (qb *PerformerStore) table() exp.IdentifierExpression {
return qb.tableMgr.table
}
func (qb *PerformerStore) selectDataset() *goqu.SelectDataset {
return dialect.From(qb.table()).Select(qb.table().All())
}
func (qb *PerformerStore) Create(ctx context.Context, newObject *models.Performer) error {
var r performerRow
r.fromPerformer(*newObject)
id, err := qb.tableMgr.insertID(ctx, r)
if err != nil {
return err
}
if newObject.Aliases.Loaded() {
if err := performersAliasesTableMgr.insertJoins(ctx, id, newObject.Aliases.List()); err != nil {
return err
}
}
if newObject.URLs.Loaded() {
const startPos = 0
if err := performersURLsTableMgr.insertJoins(ctx, id, startPos, newObject.URLs.List()); err != nil {
return err
}
}
if newObject.TagIDs.Loaded() {
if err := performersTagsTableMgr.insertJoins(ctx, id, newObject.TagIDs.List()); err != nil {
return err
}
}
if newObject.StashIDs.Loaded() {
if err := performersStashIDsTableMgr.insertJoins(ctx, id, newObject.StashIDs.List()); err != nil {
return err
}
}
updated, err := qb.find(ctx, id)
if err != nil {
return fmt.Errorf("finding after create: %w", err)
}
*newObject = *updated
return nil
}
func (qb *PerformerStore) UpdatePartial(ctx context.Context, id int, partial models.PerformerPartial) (*models.Performer, error) {
r := performerRowRecord{
updateRecord{
Record: make(exp.Record),
},
}
r.fromPartial(partial)
if len(r.Record) > 0 {
if err := qb.tableMgr.updateByID(ctx, id, r.Record); err != nil {
return nil, err
}
}
if partial.Aliases != nil {
if err := performersAliasesTableMgr.modifyJoins(ctx, id, partial.Aliases.Values, partial.Aliases.Mode); err != nil {
return nil, err
}
}
if partial.URLs != nil {
if err := performersURLsTableMgr.modifyJoins(ctx, id, partial.URLs.Values, partial.URLs.Mode); err != nil {
return nil, err
}
}
if partial.TagIDs != nil {
if err := performersTagsTableMgr.modifyJoins(ctx, id, partial.TagIDs.IDs, partial.TagIDs.Mode); err != nil {
return nil, err
}
}
if partial.StashIDs != nil {
if err := performersStashIDsTableMgr.modifyJoins(ctx, id, partial.StashIDs.StashIDs, partial.StashIDs.Mode); err != nil {
return nil, err
}
}
return qb.find(ctx, id)
}
func (qb *PerformerStore) Update(ctx context.Context, updatedObject *models.Performer) error {
var r performerRow
r.fromPerformer(*updatedObject)
if err := qb.tableMgr.updateByID(ctx, updatedObject.ID, r); err != nil {
return err
}
if updatedObject.Aliases.Loaded() {
if err := performersAliasesTableMgr.replaceJoins(ctx, updatedObject.ID, updatedObject.Aliases.List()); err != nil {
return err
}
}
if updatedObject.URLs.Loaded() {
if err := performersURLsTableMgr.replaceJoins(ctx, updatedObject.ID, updatedObject.URLs.List()); err != nil {
return err
}
}
if updatedObject.TagIDs.Loaded() {
if err := performersTagsTableMgr.replaceJoins(ctx, updatedObject.ID, updatedObject.TagIDs.List()); err != nil {
return err
}
}
if updatedObject.StashIDs.Loaded() {
if err := performersStashIDsTableMgr.replaceJoins(ctx, updatedObject.ID, updatedObject.StashIDs.List()); err != nil {
return err
}
}
return nil
}
func (qb *PerformerStore) Destroy(ctx context.Context, id int) error {
// must handle image checksums manually
if err := qb.destroyImage(ctx, id); err != nil {
return err
}
return performerRepository.destroyExisting(ctx, []int{id})
}
// returns nil, nil if not found
func (qb *PerformerStore) Find(ctx context.Context, id int) (*models.Performer, error) {
ret, err := qb.find(ctx, id)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return ret, err
}
func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Performer, error) {
tableMgr := performerTableMgr
ret := make([]*models.Performer, len(ids))
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(batch...))
unsorted, err := qb.getMany(ctx, q)
if err != nil {
return err
}
for _, s := range unsorted {
i := slices.Index(ids, s.ID)
ret[i] = s
}
return nil
}); err != nil {
return nil, err
}
for i := range ret {
if ret[i] == nil {
return nil, fmt.Errorf("performer with id %d not found", ids[i])
}
}
return ret, nil
}
// returns nil, sql.ErrNoRows if not found
func (qb *PerformerStore) find(ctx context.Context, id int) (*models.Performer, error) {
q := qb.selectDataset().Where(qb.tableMgr.byID(id))
ret, err := qb.get(ctx, q)
if err != nil {
return nil, err
}
return ret, nil
}
func (qb *PerformerStore) findBySubquery(ctx context.Context, sq *goqu.SelectDataset) ([]*models.Performer, error) {
table := qb.table()
q := qb.selectDataset().Where(
table.Col(idColumn).Eq(
sq,
),
)
return qb.getMany(ctx, q)
}
// returns nil, sql.ErrNoRows if not found
func (qb *PerformerStore) get(ctx context.Context, q *goqu.SelectDataset) (*models.Performer, error) {
ret, err := qb.getMany(ctx, q)
if err != nil {
return nil, err
}
if len(ret) == 0 {
return nil, sql.ErrNoRows
}
return ret[0], nil
}
func (qb *PerformerStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.Performer, error) {
const single = false
var ret []*models.Performer
if err := queryFunc(ctx, q, single, func(r *sqlx.Rows) error {
var f performerRow
if err := r.StructScan(&f); err != nil {
return err
}
s := f.resolve()
ret = append(ret, s)
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func (qb *PerformerStore) FindBySceneID(ctx context.Context, sceneID int) ([]*models.Performer, error) {
sq := dialect.From(scenesPerformersJoinTable).Select(scenesPerformersJoinTable.Col(performerIDColumn)).Where(
scenesPerformersJoinTable.Col(sceneIDColumn).Eq(sceneID),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for scene %d: %w", sceneID, err)
}
return ret, nil
}
func (qb *PerformerStore) FindByImageID(ctx context.Context, imageID int) ([]*models.Performer, error) {
sq := dialect.From(performersImagesJoinTable).Select(performersImagesJoinTable.Col(performerIDColumn)).Where(
performersImagesJoinTable.Col(imageIDColumn).Eq(imageID),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for image %d: %w", imageID, err)
}
return ret, nil
}
func (qb *PerformerStore) FindByGalleryID(ctx context.Context, galleryID int) ([]*models.Performer, error) {
sq := dialect.From(performersGalleriesJoinTable).Select(performersGalleriesJoinTable.Col(performerIDColumn)).Where(
performersGalleriesJoinTable.Col(galleryIDColumn).Eq(galleryID),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for gallery %d: %w", galleryID, err)
}
return ret, nil
}
func (qb *PerformerStore) FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error) {
clause := "name "
if nocase {
clause += "COLLATE NOCASE "
}
clause += "IN " + getInBinding(len(names))
var args []interface{}
for _, name := range names {
args = append(args, name)
}
sq := qb.selectDataset().Prepared(true).Where(
goqu.L(clause, args...),
)
ret, err := qb.getMany(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers by names: %w", err)
}
return ret, nil
}
func (qb *PerformerStore) CountByTagID(ctx context.Context, tagID int) (int, error) {
joinTable := performersTagsJoinTable
q := dialect.Select(goqu.COUNT("*")).From(joinTable).Where(joinTable.Col(tagIDColumn).Eq(tagID))
return count(ctx, q)
}
func (qb *PerformerStore) Count(ctx context.Context) (int, error) {
q := dialect.Select(goqu.COUNT("*")).From(qb.table())
return count(ctx, q)
}
func (qb *PerformerStore) All(ctx context.Context) ([]*models.Performer, error) {
table := qb.table()
return qb.getMany(ctx, qb.selectDataset().Order(table.Col("name").Asc()))
}
func (qb *PerformerStore) QueryForAutoTag(ctx context.Context, words []string) ([]*models.Performer, error) {
// TODO - Query needs to be changed to support queries of this type, and
// this method should be removed
table := qb.table()
sq := dialect.From(table).Select(table.Col(idColumn))
// TODO - disabled alias matching until we get finer control over it
// .LeftJoin(
// performersAliasesJoinTable,
// goqu.On(performersAliasesJoinTable.Col(performerIDColumn).Eq(table.Col(idColumn))),
// )
var whereClauses []exp.Expression
for _, w := range words {
whereClauses = append(whereClauses, table.Col("name").Like(w+"%"))
// TODO - see above
// whereClauses = append(whereClauses, performersAliasesJoinTable.Col("alias").Like(w+"%"))
}
sq = sq.Where(
goqu.Or(whereClauses...),
table.Col("ignore_auto_tag").Eq(0),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for autotag: %w", err)
}
return ret, nil
}
func (qb *PerformerStore) makeQuery(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) {
if performerFilter == nil {
performerFilter = &models.PerformerFilterType{}
}
if findFilter == nil {
findFilter = &models.FindFilterType{}
}
query := performerRepository.newQuery()
distinctIDs(&query, performerTable)
if q := findFilter.Q; q != nil && *q != "" {
query.join(performersAliasesTable, "", "performer_aliases.performer_id = performers.id")
searchColumns := []string{"performers.name", "performer_aliases.alias"}
query.parseQueryString(searchColumns, *q)
}
filter := filterBuilderFromHandler(ctx, &performerFilterHandler{
performerFilter: performerFilter,
})
if err := query.addFilter(filter); err != nil {
return nil, err
}
var err error
query.sortAndPagination, err = qb.getPerformerSort(findFilter)
if err != nil {
return nil, err
}
query.sortAndPagination += getPagination(findFilter)
return &query, nil
}
func (qb *PerformerStore) Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error) {
query, err := qb.makeQuery(ctx, performerFilter, findFilter)
if err != nil {
return nil, 0, err
}
idsResult, countResult, err := query.executeFind(ctx)
if err != nil {
return nil, 0, err
}
performers, err := qb.FindMany(ctx, idsResult)
if err != nil {
return nil, 0, err
}
return performers, countResult, nil
}
func (qb *PerformerStore) QueryCount(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (int, error) {
query, err := qb.makeQuery(ctx, performerFilter, findFilter)
if err != nil {
return 0, err
}
return query.executeCount(ctx)
}
func (qb *PerformerStore) sortByOCounter(direction string) string {
// need to sum the o_counter from scenes and images
return " ORDER BY (" + selectPerformerOCountSQL + ") " + direction
}
func (qb *PerformerStore) sortByPlayCount(direction string) string {
// need to sum the o_counter from scenes and images
return " ORDER BY (" + selectPerformerPlayCountSQL + ") " + direction
}
// used for sorting on performer last o_date
var selectPerformerLastOAtSQL = utils.StrFormat(
"SELECT MAX(o_date) FROM ("+
"SELECT {o_date} FROM {performers_scenes} s "+
"LEFT JOIN {scenes} ON {scenes}.id = s.{scene_id} "+
"LEFT JOIN {scenes_o_dates} ON {scenes_o_dates}.{scene_id} = {scenes}.id "+
"WHERE s.{performer_id} = {performers}.id"+
")",
map[string]interface{}{
"performer_id": performerIDColumn,
"performers": performerTable,
"performers_scenes": performersScenesTable,
"scenes": sceneTable,
"scene_id": sceneIDColumn,
"scenes_o_dates": scenesODatesTable,
"o_date": sceneODateColumn,
},
)
func (qb *PerformerStore) sortByLastOAt(direction string) string {
// need to get the o_dates from scenes
return " ORDER BY (" + selectPerformerLastOAtSQL + ") " + direction
}
// used for sorting on performer last view_date
var selectPerformerLastPlayedAtSQL = utils.StrFormat(
"SELECT MAX(view_date) FROM ("+
"SELECT {view_date} FROM {performers_scenes} s "+
"LEFT JOIN {scenes} ON {scenes}.id = s.{scene_id} "+
"LEFT JOIN {scenes_view_dates} ON {scenes_view_dates}.{scene_id} = {scenes}.id "+
"WHERE s.{performer_id} = {performers}.id"+
")",
map[string]interface{}{
"performer_id": performerIDColumn,
"performers": performerTable,
"performers_scenes": performersScenesTable,
"scenes": sceneTable,
"scene_id": sceneIDColumn,
"scenes_view_dates": scenesViewDatesTable,
"view_date": sceneViewDateColumn,
},
)
func (qb *PerformerStore) sortByLastPlayedAt(direction string) string {
// need to get the view_dates from scenes
return " ORDER BY (" + selectPerformerLastPlayedAtSQL + ") " + direction
}
var performerSortOptions = sortOptions{
"birthdate",
"career_length",
"created_at",
"galleries_count",
"height",
"id",
"images_count",
"last_o_at",
"last_played_at",
"measurements",
"name",
"o_counter",
"penis_length",
"play_count",
"random",
"rating",
"scenes_count",
"tag_count",
"updated_at",
"weight",
}
func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) (string, error) {
var sort string
var direction string
if findFilter == nil {
sort = "name"
direction = "ASC"
} else {
sort = findFilter.GetSort("name")
direction = findFilter.GetDirection()
}
// CVE-2024-32231 - ensure sort is in the list of allowed sorts
if err := performerSortOptions.validateSort(sort); err != nil {
return "", err
}
sortQuery := ""
switch sort {
case "tag_count":
sortQuery += getCountSort(performerTable, performersTagsTable, performerIDColumn, direction)
case "scenes_count":
sortQuery += getCountSort(performerTable, performersScenesTable, performerIDColumn, direction)
case "images_count":
sortQuery += getCountSort(performerTable, performersImagesTable, performerIDColumn, direction)
case "galleries_count":
sortQuery += getCountSort(performerTable, performersGalleriesTable, performerIDColumn, direction)
case "play_count":
sortQuery += qb.sortByPlayCount(direction)
case "o_counter":
sortQuery += qb.sortByOCounter(direction)
case "last_played_at":
sortQuery += qb.sortByLastPlayedAt(direction)
case "last_o_at":
sortQuery += qb.sortByLastOAt(direction)
default:
sortQuery += getSort(sort, direction, "performers")
}
// Whatever the sorting, always use name/id as a final sort
sortQuery += ", COALESCE(performers.name, performers.id) COLLATE NATURAL_CI ASC"
return sortQuery, nil
}
func (qb *PerformerStore) GetTagIDs(ctx context.Context, id int) ([]int, error) {
return performerRepository.tags.getIDs(ctx, id)
}
func (qb *PerformerStore) GetImage(ctx context.Context, performerID int) ([]byte, error) {
return qb.blobJoinQueryBuilder.GetImage(ctx, performerID, performerImageBlobColumn)
}
func (qb *PerformerStore) HasImage(ctx context.Context, performerID int) (bool, error) {
return qb.blobJoinQueryBuilder.HasImage(ctx, performerID, performerImageBlobColumn)
}
func (qb *PerformerStore) UpdateImage(ctx context.Context, performerID int, image []byte) error {
return qb.blobJoinQueryBuilder.UpdateImage(ctx, performerID, performerImageBlobColumn, image)
}
func (qb *PerformerStore) destroyImage(ctx context.Context, performerID int) error {
return qb.blobJoinQueryBuilder.DestroyImage(ctx, performerID, performerImageBlobColumn)
}
func (qb *PerformerStore) GetAliases(ctx context.Context, performerID int) ([]string, error) {
return performersAliasesTableMgr.get(ctx, performerID)
}
func (qb *PerformerStore) GetURLs(ctx context.Context, performerID int) ([]string, error) {
return performersURLsTableMgr.get(ctx, performerID)
}
func (qb *PerformerStore) GetStashIDs(ctx context.Context, performerID int) ([]models.StashID, error) {
return performersStashIDsTableMgr.get(ctx, performerID)
}
func (qb *PerformerStore) FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Performer, error) {
sq := dialect.From(performersStashIDsJoinTable).Select(performersStashIDsJoinTable.Col(performerIDColumn)).Where(
performersStashIDsJoinTable.Col("stash_id").Eq(stashID.StashID),
performersStashIDsJoinTable.Col("endpoint").Eq(stashID.Endpoint),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for stash ID %s: %w", stashID.StashID, err)
}
return ret, nil
}
func (qb *PerformerStore) FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*models.Performer, error) {
table := qb.table()
sq := dialect.From(table).LeftJoin(
performersStashIDsJoinTable,
goqu.On(table.Col(idColumn).Eq(performersStashIDsJoinTable.Col(performerIDColumn))),
).Select(table.Col(idColumn))
if hasStashID {
sq = sq.Where(
performersStashIDsJoinTable.Col("stash_id").IsNotNull(),
performersStashIDsJoinTable.Col("endpoint").Eq(stashboxEndpoint),
)
} else {
sq = sq.Where(
performersStashIDsJoinTable.Col("stash_id").IsNull(),
)
}
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for stash-box endpoint %s: %w", stashboxEndpoint, err)
}
return ret, nil
}