mirror of https://github.com/stashapp/stash.git
617 lines
18 KiB
Go
617 lines
18 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
)
|
|
|
|
const imageTable = "images"
|
|
const imageIDColumn = "image_id"
|
|
const performersImagesTable = "performers_images"
|
|
const imagesTagsTable = "images_tags"
|
|
|
|
var imagesForGalleryQuery = selectAll(imageTable) + `
|
|
INNER JOIN galleries_images as galleries_join on galleries_join.image_id = images.id
|
|
WHERE galleries_join.gallery_id = ?
|
|
GROUP BY images.id
|
|
`
|
|
|
|
var countImagesForGalleryQuery = `
|
|
SELECT gallery_id FROM galleries_images
|
|
WHERE gallery_id = ?
|
|
GROUP BY image_id
|
|
`
|
|
|
|
type imageQueryBuilder struct {
|
|
repository
|
|
}
|
|
|
|
func NewImageReaderWriter(tx dbi) *imageQueryBuilder {
|
|
return &imageQueryBuilder{
|
|
repository{
|
|
tx: tx,
|
|
tableName: imageTable,
|
|
idColumn: idColumn,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Create(newObject models.Image) (*models.Image, error) {
|
|
var ret models.Image
|
|
if err := qb.insertObject(newObject, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Update(updatedObject models.ImagePartial) (*models.Image, error) {
|
|
const partial = true
|
|
if err := qb.update(updatedObject.ID, updatedObject, partial); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return qb.find(updatedObject.ID)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) UpdateFull(updatedObject models.Image) (*models.Image, error) {
|
|
const partial = false
|
|
if err := qb.update(updatedObject.ID, updatedObject, partial); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return qb.find(updatedObject.ID)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) IncrementOCounter(id int) (int, error) {
|
|
_, err := qb.tx.Exec(
|
|
`UPDATE `+imageTable+` SET o_counter = o_counter + 1 WHERE `+imageTable+`.id = ?`,
|
|
id,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
image, err := qb.find(id)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return image.OCounter, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) DecrementOCounter(id int) (int, error) {
|
|
_, err := qb.tx.Exec(
|
|
`UPDATE `+imageTable+` SET o_counter = o_counter - 1 WHERE `+imageTable+`.id = ? and `+imageTable+`.o_counter > 0`,
|
|
id,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
image, err := qb.find(id)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return image.OCounter, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) ResetOCounter(id int) (int, error) {
|
|
_, err := qb.tx.Exec(
|
|
`UPDATE `+imageTable+` SET o_counter = 0 WHERE `+imageTable+`.id = ?`,
|
|
id,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
image, err := qb.find(id)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return image.OCounter, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Destroy(id int) error {
|
|
return qb.destroyExisting([]int{id})
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Find(id int) (*models.Image, error) {
|
|
return qb.find(id)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) FindMany(ids []int) ([]*models.Image, error) {
|
|
var images []*models.Image
|
|
for _, id := range ids {
|
|
image, err := qb.Find(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if image == nil {
|
|
return nil, fmt.Errorf("image with id %d not found", id)
|
|
}
|
|
|
|
images = append(images, image)
|
|
}
|
|
|
|
return images, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) find(id int) (*models.Image, error) {
|
|
var ret models.Image
|
|
if err := qb.get(id, &ret); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) FindByChecksum(checksum string) (*models.Image, error) {
|
|
query := "SELECT * FROM images WHERE checksum = ? LIMIT 1"
|
|
args := []interface{}{checksum}
|
|
return qb.queryImage(query, args)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) FindByPath(path string) (*models.Image, error) {
|
|
query := selectAll(imageTable) + "WHERE path = ? LIMIT 1"
|
|
args := []interface{}{path}
|
|
return qb.queryImage(query, args)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) FindByGalleryID(galleryID int) ([]*models.Image, error) {
|
|
args := []interface{}{galleryID}
|
|
sort := "path"
|
|
sortDir := models.SortDirectionEnumAsc
|
|
return qb.queryImages(imagesForGalleryQuery+qb.getImageSort(&models.FindFilterType{
|
|
Sort: &sort,
|
|
Direction: &sortDir,
|
|
}), args)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) CountByGalleryID(galleryID int) (int, error) {
|
|
args := []interface{}{galleryID}
|
|
return qb.runCountQuery(qb.buildCountQuery(countImagesForGalleryQuery), args)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Count() (int, error) {
|
|
return qb.runCountQuery(qb.buildCountQuery("SELECT images.id FROM images"), nil)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Size() (float64, error) {
|
|
return qb.runSumQuery("SELECT SUM(cast(size as double)) as sum FROM images", nil)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) All() ([]*models.Image, error) {
|
|
return qb.queryImages(selectAll(imageTable)+qb.getImageSort(nil), nil)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) validateFilter(imageFilter *models.ImageFilterType) error {
|
|
const and = "AND"
|
|
const or = "OR"
|
|
const not = "NOT"
|
|
|
|
if imageFilter.And != nil {
|
|
if imageFilter.Or != nil {
|
|
return illegalFilterCombination(and, or)
|
|
}
|
|
if imageFilter.Not != nil {
|
|
return illegalFilterCombination(and, not)
|
|
}
|
|
|
|
return qb.validateFilter(imageFilter.And)
|
|
}
|
|
|
|
if imageFilter.Or != nil {
|
|
if imageFilter.Not != nil {
|
|
return illegalFilterCombination(or, not)
|
|
}
|
|
|
|
return qb.validateFilter(imageFilter.Or)
|
|
}
|
|
|
|
if imageFilter.Not != nil {
|
|
return qb.validateFilter(imageFilter.Not)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) makeFilter(imageFilter *models.ImageFilterType) *filterBuilder {
|
|
query := &filterBuilder{}
|
|
|
|
if imageFilter.And != nil {
|
|
query.and(qb.makeFilter(imageFilter.And))
|
|
}
|
|
if imageFilter.Or != nil {
|
|
query.or(qb.makeFilter(imageFilter.Or))
|
|
}
|
|
if imageFilter.Not != nil {
|
|
query.not(qb.makeFilter(imageFilter.Not))
|
|
}
|
|
|
|
query.handleCriterion(stringCriterionHandler(imageFilter.Checksum, "images.checksum"))
|
|
query.handleCriterion(stringCriterionHandler(imageFilter.Title, "images.title"))
|
|
query.handleCriterion(stringCriterionHandler(imageFilter.Path, "images.path"))
|
|
query.handleCriterion(intCriterionHandler(imageFilter.Rating, "images.rating"))
|
|
query.handleCriterion(intCriterionHandler(imageFilter.OCounter, "images.o_counter"))
|
|
query.handleCriterion(boolCriterionHandler(imageFilter.Organized, "images.organized"))
|
|
query.handleCriterion(resolutionCriterionHandler(imageFilter.Resolution, "images.height", "images.width"))
|
|
query.handleCriterion(imageIsMissingCriterionHandler(qb, imageFilter.IsMissing))
|
|
|
|
query.handleCriterion(imageTagsCriterionHandler(qb, imageFilter.Tags))
|
|
query.handleCriterion(imageTagCountCriterionHandler(qb, imageFilter.TagCount))
|
|
query.handleCriterion(imageGalleriesCriterionHandler(qb, imageFilter.Galleries))
|
|
query.handleCriterion(imagePerformersCriterionHandler(qb, imageFilter.Performers))
|
|
query.handleCriterion(imagePerformerCountCriterionHandler(qb, imageFilter.PerformerCount))
|
|
query.handleCriterion(imageStudioCriterionHandler(qb, imageFilter.Studios))
|
|
query.handleCriterion(imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
|
|
query.handleCriterion(imagePerformerFavoriteCriterionHandler(imageFilter.PerformerFavorite))
|
|
|
|
return query
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) makeQuery(imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) {
|
|
if imageFilter == nil {
|
|
imageFilter = &models.ImageFilterType{}
|
|
}
|
|
if findFilter == nil {
|
|
findFilter = &models.FindFilterType{}
|
|
}
|
|
|
|
query := qb.newQuery()
|
|
distinctIDs(&query, imageTable)
|
|
|
|
if q := findFilter.Q; q != nil && *q != "" {
|
|
searchColumns := []string{"images.title", "images.path", "images.checksum"}
|
|
query.parseQueryString(searchColumns, *q)
|
|
}
|
|
|
|
if err := qb.validateFilter(imageFilter); err != nil {
|
|
return nil, err
|
|
}
|
|
filter := qb.makeFilter(imageFilter)
|
|
|
|
query.addFilter(filter)
|
|
|
|
query.sortAndPagination = qb.getImageSort(findFilter) + getPagination(findFilter)
|
|
|
|
return &query, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) Query(options models.ImageQueryOptions) (*models.ImageQueryResult, error) {
|
|
query, err := qb.makeQuery(options.ImageFilter, options.FindFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := qb.queryGroupedFields(options, *query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error querying aggregate fields: %w", err)
|
|
}
|
|
|
|
idsResult, err := query.findIDs()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error finding IDs: %w", err)
|
|
}
|
|
|
|
result.IDs = idsResult
|
|
return result, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) queryGroupedFields(options models.ImageQueryOptions, query queryBuilder) (*models.ImageQueryResult, error) {
|
|
if !options.Count && !options.Megapixels && !options.TotalSize {
|
|
// nothing to do - return empty result
|
|
return models.NewImageQueryResult(qb), nil
|
|
}
|
|
|
|
aggregateQuery := qb.newQuery()
|
|
|
|
if options.Count {
|
|
aggregateQuery.addColumn("COUNT(temp.id) as total")
|
|
}
|
|
|
|
if options.Megapixels {
|
|
query.addColumn("COALESCE(images.width, 0) * COALESCE(images.height, 0) / 1000000 as megapixels")
|
|
aggregateQuery.addColumn("COALESCE(SUM(temp.megapixels), 0) as megapixels")
|
|
}
|
|
|
|
if options.TotalSize {
|
|
query.addColumn("COALESCE(images.size, 0) as size")
|
|
aggregateQuery.addColumn("COALESCE(SUM(temp.size), 0) as size")
|
|
}
|
|
|
|
const includeSortPagination = false
|
|
aggregateQuery.from = fmt.Sprintf("(%s) as temp", query.toSQL(includeSortPagination))
|
|
|
|
out := struct {
|
|
Total int
|
|
Megapixels float64
|
|
Size float64
|
|
}{}
|
|
if err := qb.repository.queryStruct(aggregateQuery.toSQL(includeSortPagination), query.args, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ret := models.NewImageQueryResult(qb)
|
|
ret.Count = out.Total
|
|
ret.Megapixels = out.Megapixels
|
|
ret.TotalSize = out.Size
|
|
return ret, nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) QueryCount(imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) (int, error) {
|
|
query, err := qb.makeQuery(imageFilter, findFilter)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return query.executeCount()
|
|
}
|
|
|
|
func imageIsMissingCriterionHandler(qb *imageQueryBuilder, isMissing *string) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if isMissing != nil && *isMissing != "" {
|
|
switch *isMissing {
|
|
case "studio":
|
|
f.addWhere("images.studio_id IS NULL")
|
|
case "performers":
|
|
qb.performersRepository().join(f, "performers_join", "images.id")
|
|
f.addWhere("performers_join.image_id IS NULL")
|
|
case "galleries":
|
|
qb.galleriesRepository().join(f, "galleries_join", "images.id")
|
|
f.addWhere("galleries_join.image_id IS NULL")
|
|
case "tags":
|
|
qb.tagsRepository().join(f, "tags_join", "images.id")
|
|
f.addWhere("tags_join.image_id IS NULL")
|
|
default:
|
|
f.addWhere("(images." + *isMissing + " IS NULL OR TRIM(images." + *isMissing + ") = '')")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
|
return multiCriterionHandlerBuilder{
|
|
primaryTable: imageTable,
|
|
foreignTable: foreignTable,
|
|
joinTable: joinTable,
|
|
primaryFK: imageIDColumn,
|
|
foreignFK: foreignFK,
|
|
addJoinsFunc: addJoinsFunc,
|
|
}
|
|
}
|
|
|
|
func imageTagsCriterionHandler(qb *imageQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
|
h := joinedHierarchicalMultiCriterionHandlerBuilder{
|
|
tx: qb.tx,
|
|
|
|
primaryTable: imageTable,
|
|
foreignTable: tagTable,
|
|
foreignFK: "tag_id",
|
|
|
|
relationsTable: "tags_relations",
|
|
joinAs: "image_tag",
|
|
joinTable: imagesTagsTable,
|
|
primaryFK: imageIDColumn,
|
|
}
|
|
|
|
return h.handler(tags)
|
|
}
|
|
|
|
func imageTagCountCriterionHandler(qb *imageQueryBuilder, tagCount *models.IntCriterionInput) criterionHandlerFunc {
|
|
h := countCriterionHandlerBuilder{
|
|
primaryTable: imageTable,
|
|
joinTable: imagesTagsTable,
|
|
primaryFK: imageIDColumn,
|
|
}
|
|
|
|
return h.handler(tagCount)
|
|
}
|
|
|
|
func imageGalleriesCriterionHandler(qb *imageQueryBuilder, galleries *models.MultiCriterionInput) criterionHandlerFunc {
|
|
addJoinsFunc := func(f *filterBuilder) {
|
|
qb.galleriesRepository().join(f, "", "images.id")
|
|
f.addLeftJoin(galleryTable, "", "galleries_images.gallery_id = galleries.id")
|
|
}
|
|
h := qb.getMultiCriterionHandlerBuilder(galleryTable, galleriesImagesTable, galleryIDColumn, addJoinsFunc)
|
|
|
|
return h.handler(galleries)
|
|
}
|
|
|
|
func imagePerformersCriterionHandler(qb *imageQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc {
|
|
h := joinedMultiCriterionHandlerBuilder{
|
|
primaryTable: imageTable,
|
|
joinTable: performersImagesTable,
|
|
joinAs: "performers_join",
|
|
primaryFK: imageIDColumn,
|
|
foreignFK: performerIDColumn,
|
|
|
|
addJoinTable: func(f *filterBuilder) {
|
|
qb.performersRepository().join(f, "performers_join", "images.id")
|
|
},
|
|
}
|
|
|
|
return h.handler(performers)
|
|
}
|
|
|
|
func imagePerformerCountCriterionHandler(qb *imageQueryBuilder, performerCount *models.IntCriterionInput) criterionHandlerFunc {
|
|
h := countCriterionHandlerBuilder{
|
|
primaryTable: imageTable,
|
|
joinTable: performersImagesTable,
|
|
primaryFK: imageIDColumn,
|
|
}
|
|
|
|
return h.handler(performerCount)
|
|
}
|
|
|
|
func imagePerformerFavoriteCriterionHandler(performerfavorite *bool) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if performerfavorite != nil {
|
|
f.addLeftJoin("performers_images", "", "images.id = performers_images.image_id")
|
|
|
|
if *performerfavorite {
|
|
// contains at least one favorite
|
|
f.addLeftJoin("performers", "", "performers.id = performers_images.performer_id")
|
|
f.addWhere("performers.favorite = 1")
|
|
} else {
|
|
// contains zero favorites
|
|
f.addLeftJoin(`(SELECT performers_images.image_id as id FROM performers_images
|
|
JOIN performers ON performers.id = performers_images.performer_id
|
|
GROUP BY performers_images.image_id HAVING SUM(performers.favorite) = 0)`, "nofaves", "images.id = nofaves.id")
|
|
f.addWhere("performers_images.image_id IS NULL OR nofaves.id IS NOT NULL")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func imageStudioCriterionHandler(qb *imageQueryBuilder, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
|
h := hierarchicalMultiCriterionHandlerBuilder{
|
|
tx: qb.tx,
|
|
|
|
primaryTable: imageTable,
|
|
foreignTable: studioTable,
|
|
foreignFK: studioIDColumn,
|
|
derivedTable: "studio",
|
|
parentFK: "parent_id",
|
|
}
|
|
|
|
return h.handler(studios)
|
|
}
|
|
|
|
func imagePerformerTagsCriterionHandler(qb *imageQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
|
return func(f *filterBuilder) {
|
|
if tags != nil {
|
|
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
|
var notClause string
|
|
if tags.Modifier == models.CriterionModifierNotNull {
|
|
notClause = "NOT"
|
|
}
|
|
|
|
f.addLeftJoin("performers_images", "", "images.id = performers_images.image_id")
|
|
f.addLeftJoin("performers_tags", "", "performers_images.performer_id = performers_tags.performer_id")
|
|
|
|
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
|
|
return
|
|
}
|
|
|
|
if len(tags.Value) == 0 {
|
|
return
|
|
}
|
|
|
|
valuesClause := getHierarchicalValues(qb.tx, tags.Value, tagTable, "tags_relations", "", tags.Depth)
|
|
|
|
f.addWith(`performer_tags AS (
|
|
SELECT pi.image_id, t.column1 AS root_tag_id FROM performers_images pi
|
|
INNER JOIN performers_tags pt ON pt.performer_id = pi.performer_id
|
|
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
|
|
)`)
|
|
|
|
f.addLeftJoin("performer_tags", "", "performer_tags.image_id = images.id")
|
|
|
|
addHierarchicalConditionClauses(f, tags, "performer_tags", "root_tag_id")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) getImageSort(findFilter *models.FindFilterType) string {
|
|
if findFilter == nil || findFilter.Sort == nil || *findFilter.Sort == "" {
|
|
return ""
|
|
}
|
|
sort := findFilter.GetSort("title")
|
|
direction := findFilter.GetDirection()
|
|
|
|
switch sort {
|
|
case "tag_count":
|
|
return getCountSort(imageTable, imagesTagsTable, imageIDColumn, direction)
|
|
case "performer_count":
|
|
return getCountSort(imageTable, performersImagesTable, imageIDColumn, direction)
|
|
default:
|
|
return getSort(sort, direction, "images")
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) queryImage(query string, args []interface{}) (*models.Image, error) {
|
|
results, err := qb.queryImages(query, args)
|
|
if err != nil || len(results) < 1 {
|
|
return nil, err
|
|
}
|
|
return results[0], nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) queryImages(query string, args []interface{}) ([]*models.Image, error) {
|
|
var ret models.Images
|
|
if err := qb.query(query, args, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []*models.Image(ret), nil
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) galleriesRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: galleriesImagesTable,
|
|
idColumn: imageIDColumn,
|
|
},
|
|
fkColumn: galleryIDColumn,
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) GetGalleryIDs(imageID int) ([]int, error) {
|
|
return qb.galleriesRepository().getIDs(imageID)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) UpdateGalleries(imageID int, galleryIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.galleriesRepository().replace(imageID, galleryIDs)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) performersRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: performersImagesTable,
|
|
idColumn: imageIDColumn,
|
|
},
|
|
fkColumn: performerIDColumn,
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) GetPerformerIDs(imageID int) ([]int, error) {
|
|
return qb.performersRepository().getIDs(imageID)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) UpdatePerformers(imageID int, performerIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.performersRepository().replace(imageID, performerIDs)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) tagsRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: imagesTagsTable,
|
|
idColumn: imageIDColumn,
|
|
},
|
|
fkColumn: tagIDColumn,
|
|
}
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) GetTagIDs(imageID int) ([]int, error) {
|
|
return qb.tagsRepository().getIDs(imageID)
|
|
}
|
|
|
|
func (qb *imageQueryBuilder) UpdateTags(imageID int, tagIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.tagsRepository().replace(imageID, tagIDs)
|
|
}
|