mirror of https://github.com/stashapp/stash.git
608 lines
18 KiB
Go
608 lines
18 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
)
|
|
|
|
const performerTable = "performers"
|
|
const performerIDColumn = "performer_id"
|
|
const performersTagsTable = "performers_tags"
|
|
const performersImageTable = "performers_image" // performer cover image
|
|
|
|
var countPerformersForTagQuery = `
|
|
SELECT tag_id AS id FROM performers_tags
|
|
WHERE performers_tags.tag_id = ?
|
|
GROUP BY performers_tags.performer_id
|
|
`
|
|
|
|
type performerQueryBuilder struct {
|
|
repository
|
|
}
|
|
|
|
func NewPerformerReaderWriter(tx dbi) *performerQueryBuilder {
|
|
return &performerQueryBuilder{
|
|
repository{
|
|
tx: tx,
|
|
tableName: performerTable,
|
|
idColumn: idColumn,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) Create(newObject models.Performer) (*models.Performer, error) {
|
|
var ret models.Performer
|
|
if err := qb.insertObject(newObject, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) Update(updatedObject models.PerformerPartial) (*models.Performer, error) {
|
|
const partial = true
|
|
if err := qb.update(updatedObject.ID, updatedObject, partial); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ret models.Performer
|
|
if err := qb.get(updatedObject.ID, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) UpdateFull(updatedObject models.Performer) (*models.Performer, error) {
|
|
const partial = false
|
|
if err := qb.update(updatedObject.ID, updatedObject, partial); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ret models.Performer
|
|
if err := qb.get(updatedObject.ID, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) Destroy(id int) error {
|
|
// TODO - add on delete cascade to performers_scenes
|
|
_, err := qb.tx.Exec("DELETE FROM performers_scenes WHERE performer_id = ?", id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return qb.destroyExisting([]int{id})
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) Find(id int) (*models.Performer, error) {
|
|
var ret models.Performer
|
|
if err := qb.get(id, &ret); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindMany(ids []int) ([]*models.Performer, error) {
|
|
var performers []*models.Performer
|
|
for _, id := range ids {
|
|
performer, err := qb.Find(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if performer == nil {
|
|
return nil, fmt.Errorf("performer with id %d not found", id)
|
|
}
|
|
|
|
performers = append(performers, performer)
|
|
}
|
|
|
|
return performers, nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindBySceneID(sceneID int) ([]*models.Performer, error) {
|
|
query := selectAll("performers") + `
|
|
LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id
|
|
WHERE scenes_join.scene_id = ?
|
|
`
|
|
args := []interface{}{sceneID}
|
|
return qb.queryPerformers(query, args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindByImageID(imageID int) ([]*models.Performer, error) {
|
|
query := selectAll("performers") + `
|
|
LEFT JOIN performers_images as images_join on images_join.performer_id = performers.id
|
|
WHERE images_join.image_id = ?
|
|
`
|
|
args := []interface{}{imageID}
|
|
return qb.queryPerformers(query, args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindByGalleryID(galleryID int) ([]*models.Performer, error) {
|
|
query := selectAll("performers") + `
|
|
LEFT JOIN performers_galleries as galleries_join on galleries_join.performer_id = performers.id
|
|
WHERE galleries_join.gallery_id = ?
|
|
`
|
|
args := []interface{}{galleryID}
|
|
return qb.queryPerformers(query, args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindNamesBySceneID(sceneID int) ([]*models.Performer, error) {
|
|
query := `
|
|
SELECT performers.name FROM performers
|
|
LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id
|
|
WHERE scenes_join.scene_id = ?
|
|
`
|
|
args := []interface{}{sceneID}
|
|
return qb.queryPerformers(query, args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindByNames(names []string, nocase bool) ([]*models.Performer, error) {
|
|
query := "SELECT * FROM performers WHERE name"
|
|
if nocase {
|
|
query += " COLLATE NOCASE"
|
|
}
|
|
query += " IN " + getInBinding(len(names))
|
|
|
|
var args []interface{}
|
|
for _, name := range names {
|
|
args = append(args, name)
|
|
}
|
|
return qb.queryPerformers(query, args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) CountByTagID(tagID int) (int, error) {
|
|
args := []interface{}{tagID}
|
|
return qb.runCountQuery(qb.buildCountQuery(countPerformersForTagQuery), args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) Count() (int, error) {
|
|
return qb.runCountQuery(qb.buildCountQuery("SELECT performers.id FROM performers"), nil)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) All() ([]*models.Performer, error) {
|
|
return qb.queryPerformers(selectAll("performers")+qb.getPerformerSort(nil), nil)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) QueryForAutoTag(words []string) ([]*models.Performer, error) {
|
|
// TODO - Query needs to be changed to support queries of this type, and
|
|
// this method should be removed
|
|
query := selectAll(performerTable)
|
|
|
|
var whereClauses []string
|
|
var args []interface{}
|
|
|
|
for _, w := range words {
|
|
whereClauses = append(whereClauses, "name like ?")
|
|
args = append(args, "%"+w+"%")
|
|
whereClauses = append(whereClauses, "aliases like ?")
|
|
args = append(args, "%"+w+"%")
|
|
}
|
|
|
|
where := strings.Join(whereClauses, " OR ")
|
|
return qb.queryPerformers(query+" WHERE "+where, args)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) validateFilter(filter *models.PerformerFilterType) error {
|
|
const and = "AND"
|
|
const or = "OR"
|
|
const not = "NOT"
|
|
|
|
if filter.And != nil {
|
|
if filter.Or != nil {
|
|
return illegalFilterCombination(and, or)
|
|
}
|
|
if filter.Not != nil {
|
|
return illegalFilterCombination(and, not)
|
|
}
|
|
|
|
return qb.validateFilter(filter.And)
|
|
}
|
|
|
|
if filter.Or != nil {
|
|
if filter.Not != nil {
|
|
return illegalFilterCombination(or, not)
|
|
}
|
|
|
|
return qb.validateFilter(filter.Or)
|
|
}
|
|
|
|
if filter.Not != nil {
|
|
return qb.validateFilter(filter.Not)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) makeFilter(filter *models.PerformerFilterType) *filterBuilder {
|
|
query := &filterBuilder{}
|
|
|
|
if filter.And != nil {
|
|
query.and(qb.makeFilter(filter.And))
|
|
}
|
|
if filter.Or != nil {
|
|
query.or(qb.makeFilter(filter.Or))
|
|
}
|
|
if filter.Not != nil {
|
|
query.not(qb.makeFilter(filter.Not))
|
|
}
|
|
|
|
const tableName = performerTable
|
|
query.handleCriterionFunc(boolCriterionHandler(filter.FilterFavorites, tableName+".favorite"))
|
|
|
|
query.handleCriterionFunc(yearFilterCriterionHandler(filter.BirthYear, tableName+".birthdate"))
|
|
query.handleCriterionFunc(yearFilterCriterionHandler(filter.DeathYear, tableName+".death_date"))
|
|
|
|
query.handleCriterionFunc(performerAgeFilterCriterionHandler(filter.Age))
|
|
|
|
query.handleCriterionFunc(func(f *filterBuilder) {
|
|
if gender := filter.Gender; gender != nil {
|
|
f.addWhere(tableName+".gender = ?", gender.Value.String())
|
|
}
|
|
})
|
|
|
|
query.handleCriterionFunc(performerIsMissingCriterionHandler(qb, filter.IsMissing))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Ethnicity, tableName+".ethnicity"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Country, tableName+".country"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.EyeColor, tableName+".eye_color"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Height, tableName+".height"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Measurements, tableName+".measurements"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.FakeTits, tableName+".fake_tits"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Piercings, tableName+".piercings"))
|
|
query.handleCriterionFunc(intCriterionHandler(filter.Rating, tableName+".rating"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.HairColor, tableName+".hair_color"))
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.URL, tableName+".url"))
|
|
query.handleCriterionFunc(intCriterionHandler(filter.Weight, tableName+".weight"))
|
|
query.handleCriterionFunc(func(f *filterBuilder) {
|
|
if filter.StashID != nil {
|
|
qb.stashIDRepository().join(f, "performer_stash_ids", "performers.id")
|
|
stringCriterionHandler(filter.StashID, "performer_stash_ids.stash_id")(f)
|
|
}
|
|
})
|
|
|
|
// TODO - need better handling of aliases
|
|
query.handleCriterionFunc(stringCriterionHandler(filter.Aliases, tableName+".aliases"))
|
|
|
|
query.handleCriterionFunc(performerTagsCriterionHandler(qb, filter.Tags))
|
|
|
|
query.handleCriterionFunc(performerStudiosCriterionHandler(filter.Studios))
|
|
|
|
query.handleCriterionFunc(performerTagCountCriterionHandler(qb, filter.TagCount))
|
|
query.handleCriterionFunc(performerSceneCountCriterionHandler(qb, filter.SceneCount))
|
|
query.handleCriterionFunc(performerImageCountCriterionHandler(qb, filter.ImageCount))
|
|
query.handleCriterionFunc(performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
|
|
|
|
return query
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) Query(performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error) {
|
|
if performerFilter == nil {
|
|
performerFilter = &models.PerformerFilterType{}
|
|
}
|
|
if findFilter == nil {
|
|
findFilter = &models.FindFilterType{}
|
|
}
|
|
|
|
tableName := "performers"
|
|
query := qb.newQuery()
|
|
|
|
query.body = selectDistinctIDs(tableName)
|
|
|
|
if q := findFilter.Q; q != nil && *q != "" {
|
|
searchColumns := []string{"performers.name", "performers.aliases"}
|
|
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
|
query.addWhere(clause)
|
|
query.addArg(thisArgs...)
|
|
}
|
|
|
|
if err := qb.validateFilter(performerFilter); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
filter := qb.makeFilter(performerFilter)
|
|
|
|
query.addFilter(filter)
|
|
|
|
query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter)
|
|
idsResult, countResult, err := query.executeFind()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var performers []*models.Performer
|
|
for _, id := range idsResult {
|
|
performer, err := qb.Find(id)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
performers = append(performers, performer)
|
|
}
|
|
|
|
return performers, countResult, nil
|
|
}
|
|
|
|
func performerIsMissingCriterionHandler(qb *performerQueryBuilder, isMissing *string) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if isMissing != nil && *isMissing != "" {
|
|
switch *isMissing {
|
|
case "scenes": // Deprecated: use `scene_count == 0` filter instead
|
|
f.addJoin(performersScenesTable, "scenes_join", "scenes_join.performer_id = performers.id")
|
|
f.addWhere("scenes_join.scene_id IS NULL")
|
|
case "image":
|
|
f.addJoin(performersImageTable, "image_join", "image_join.performer_id = performers.id")
|
|
f.addWhere("image_join.performer_id IS NULL")
|
|
default:
|
|
f.addWhere("(performers." + *isMissing + " IS NULL OR TRIM(performers." + *isMissing + ") = '')")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func yearFilterCriterionHandler(year *models.IntCriterionInput, col string) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if year != nil && year.Modifier.IsValid() {
|
|
yearStr := strconv.Itoa(year.Value)
|
|
startOfYear := yearStr + "-01-01"
|
|
endOfYear := yearStr + "-12-31"
|
|
|
|
switch year.Modifier {
|
|
case models.CriterionModifierEquals:
|
|
// between yyyy-01-01 and yyyy-12-31
|
|
f.addWhere(col+" >= ?", startOfYear)
|
|
f.addWhere(col+" <= ?", endOfYear)
|
|
case models.CriterionModifierNotEquals:
|
|
// outside of yyyy-01-01 to yyyy-12-31
|
|
f.addWhere(col+" < ? OR "+col+" > ?", startOfYear, endOfYear)
|
|
case models.CriterionModifierGreaterThan:
|
|
// > yyyy-12-31
|
|
f.addWhere(col+" > ?", endOfYear)
|
|
case models.CriterionModifierLessThan:
|
|
// < yyyy-01-01
|
|
f.addWhere(col+" < ?", startOfYear)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func performerAgeFilterCriterionHandler(age *models.IntCriterionInput) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if age != nil && age.Modifier.IsValid() {
|
|
var op string
|
|
|
|
switch age.Modifier {
|
|
case models.CriterionModifierEquals:
|
|
op = "=="
|
|
case models.CriterionModifierNotEquals:
|
|
op = "!="
|
|
case models.CriterionModifierGreaterThan:
|
|
op = ">"
|
|
case models.CriterionModifierLessThan:
|
|
op = "<"
|
|
}
|
|
|
|
if op != "" {
|
|
f.addWhere("cast(IFNULL(strftime('%Y.%m%d', performers.death_date), strftime('%Y.%m%d', 'now')) - strftime('%Y.%m%d', performers.birthdate) as int) "+op+" ?", age.Value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func performerTagsCriterionHandler(qb *performerQueryBuilder, tags *models.MultiCriterionInput) criterionHandlerFunc {
|
|
h := joinedMultiCriterionHandlerBuilder{
|
|
primaryTable: performerTable,
|
|
joinTable: performersTagsTable,
|
|
joinAs: "tags_join",
|
|
primaryFK: performerIDColumn,
|
|
foreignFK: tagIDColumn,
|
|
|
|
addJoinTable: func(f *filterBuilder) {
|
|
qb.tagsRepository().join(f, "tags_join", "performers.id")
|
|
},
|
|
}
|
|
|
|
return h.handler(tags)
|
|
}
|
|
|
|
func performerTagCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc {
|
|
h := countCriterionHandlerBuilder{
|
|
primaryTable: performerTable,
|
|
joinTable: performersTagsTable,
|
|
primaryFK: performerIDColumn,
|
|
}
|
|
|
|
return h.handler(count)
|
|
}
|
|
|
|
func performerSceneCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc {
|
|
h := countCriterionHandlerBuilder{
|
|
primaryTable: performerTable,
|
|
joinTable: performersScenesTable,
|
|
primaryFK: performerIDColumn,
|
|
}
|
|
|
|
return h.handler(count)
|
|
}
|
|
|
|
func performerImageCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc {
|
|
h := countCriterionHandlerBuilder{
|
|
primaryTable: performerTable,
|
|
joinTable: performersImagesTable,
|
|
primaryFK: performerIDColumn,
|
|
}
|
|
|
|
return h.handler(count)
|
|
}
|
|
|
|
func performerGalleryCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc {
|
|
h := countCriterionHandlerBuilder{
|
|
primaryTable: performerTable,
|
|
joinTable: performersGalleriesTable,
|
|
primaryFK: performerIDColumn,
|
|
}
|
|
|
|
return h.handler(count)
|
|
}
|
|
|
|
func performerStudiosCriterionHandler(studios *models.MultiCriterionInput) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if studios != nil {
|
|
var countCondition string
|
|
var clauseJoin string
|
|
|
|
if studios.Modifier == models.CriterionModifierIncludes {
|
|
// return performers who appear in scenes/images/galleries with any of the given studios
|
|
countCondition = " > 0"
|
|
clauseJoin = " OR "
|
|
} else if studios.Modifier == models.CriterionModifierExcludes {
|
|
// exclude performers who appear in scenes/images/galleries with any of the given studios
|
|
countCondition = " = 0"
|
|
clauseJoin = " AND "
|
|
} else {
|
|
return
|
|
}
|
|
|
|
templStr := "(SELECT COUNT(DISTINCT %[1]s.id) FROM %[1]s LEFT JOIN %[2]s ON %[1]s.id = %[2]s.%[3]s WHERE %[2]s.performer_id = performers.id AND %[1]s.studio_id IN %[4]s)" + countCondition
|
|
|
|
inBinding := getInBinding(len(studios.Value))
|
|
|
|
clauses := []string{
|
|
fmt.Sprintf(templStr, sceneTable, performersScenesTable, sceneIDColumn, inBinding),
|
|
fmt.Sprintf(templStr, imageTable, performersImagesTable, imageIDColumn, inBinding),
|
|
fmt.Sprintf(templStr, galleryTable, performersGalleriesTable, galleryIDColumn, inBinding),
|
|
}
|
|
|
|
var args []interface{}
|
|
for _, tagID := range studios.Value {
|
|
args = append(args, tagID)
|
|
}
|
|
|
|
// this is a bit gross. We need the args three times
|
|
combinedArgs := append(args, append(args, args...)...)
|
|
|
|
f.addWhere(fmt.Sprintf("(%s)", strings.Join(clauses, clauseJoin)), combinedArgs...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) getPerformerSort(findFilter *models.FindFilterType) string {
|
|
var sort string
|
|
var direction string
|
|
if findFilter == nil {
|
|
sort = "name"
|
|
direction = "ASC"
|
|
} else {
|
|
sort = findFilter.GetSort("name")
|
|
direction = findFilter.GetDirection()
|
|
}
|
|
|
|
if sort == "tag_count" {
|
|
return getCountSort(performerTable, performersTagsTable, performerIDColumn, direction)
|
|
}
|
|
if sort == "scenes_count" {
|
|
return getCountSort(performerTable, performersScenesTable, performerIDColumn, direction)
|
|
}
|
|
|
|
return getSort(sort, direction, "performers")
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) queryPerformers(query string, args []interface{}) ([]*models.Performer, error) {
|
|
var ret models.Performers
|
|
if err := qb.query(query, args, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []*models.Performer(ret), nil
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) tagsRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: performersTagsTable,
|
|
idColumn: performerIDColumn,
|
|
},
|
|
fkColumn: tagIDColumn,
|
|
}
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) GetTagIDs(id int) ([]int, error) {
|
|
return qb.tagsRepository().getIDs(id)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) UpdateTags(id int, tagIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.tagsRepository().replace(id, tagIDs)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) imageRepository() *imageRepository {
|
|
return &imageRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: "performers_image",
|
|
idColumn: performerIDColumn,
|
|
},
|
|
imageColumn: "image",
|
|
}
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) GetImage(performerID int) ([]byte, error) {
|
|
return qb.imageRepository().get(performerID)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) UpdateImage(performerID int, image []byte) error {
|
|
return qb.imageRepository().replace(performerID, image)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) DestroyImage(performerID int) error {
|
|
return qb.imageRepository().destroy([]int{performerID})
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) stashIDRepository() *stashIDRepository {
|
|
return &stashIDRepository{
|
|
repository{
|
|
tx: qb.tx,
|
|
tableName: "performer_stash_ids",
|
|
idColumn: performerIDColumn,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) GetStashIDs(performerID int) ([]*models.StashID, error) {
|
|
return qb.stashIDRepository().get(performerID)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) UpdateStashIDs(performerID int, stashIDs []models.StashID) error {
|
|
return qb.stashIDRepository().replace(performerID, stashIDs)
|
|
}
|
|
|
|
func (qb *performerQueryBuilder) FindByStashIDStatus(hasStashID bool, stashboxEndpoint string) ([]*models.Performer, error) {
|
|
query := selectAll("performers") + `
|
|
LEFT JOIN performer_stash_ids on performer_stash_ids.performer_id = performers.id
|
|
`
|
|
|
|
if hasStashID {
|
|
query += `
|
|
WHERE performer_stash_ids.stash_id IS NOT NULL
|
|
AND performer_stash_ids.endpoint = ?
|
|
`
|
|
} else {
|
|
query += `
|
|
WHERE performer_stash_ids.stash_id IS NULL
|
|
`
|
|
}
|
|
|
|
args := []interface{}{stashboxEndpoint}
|
|
return qb.queryPerformers(query, args)
|
|
}
|