mirror of
https://github.com/stashapp/stash.git
synced 2025-12-15 12:52:38 +01:00
* Change queryStruct to use tx.Get instead of queryFunc Using queryFunc meant that the performance logging was inaccurate due to the query actually being executed during the call to Scan. * Only add join args if join was added * Omit joins that are only used for sorting when skipping sorting Should provide some marginal improvement on systems with a lot of items. * Make all calls to the database pass context. This means that long queries can be cancelled by navigating to another page. Previously the query would continue to run, impacting on future queries.
229 lines
4.9 KiB
Go
229 lines
4.9 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
)
|
|
|
|
type queryBuilder struct {
|
|
repository *repository
|
|
|
|
columns []string
|
|
from string
|
|
|
|
joins joins
|
|
whereClauses []string
|
|
havingClauses []string
|
|
args []interface{}
|
|
withClauses []string
|
|
recursiveWith bool
|
|
|
|
sortAndPagination string
|
|
}
|
|
|
|
func (qb queryBuilder) body(includeSortPagination bool) string {
|
|
return fmt.Sprintf("SELECT %s FROM %s%s", strings.Join(qb.columns, ", "), qb.from, qb.joins.toSQL(includeSortPagination))
|
|
}
|
|
|
|
func (qb *queryBuilder) addColumn(column string) {
|
|
qb.columns = append(qb.columns, column)
|
|
}
|
|
|
|
func (qb queryBuilder) toSQL(includeSortPagination bool) string {
|
|
body := qb.body(includeSortPagination)
|
|
|
|
withClause := ""
|
|
if len(qb.withClauses) > 0 {
|
|
var recursive string
|
|
if qb.recursiveWith {
|
|
recursive = " RECURSIVE "
|
|
}
|
|
withClause = "WITH " + recursive + strings.Join(qb.withClauses, ", ") + " "
|
|
}
|
|
|
|
body = withClause + qb.repository.buildQueryBody(body, qb.whereClauses, qb.havingClauses)
|
|
if includeSortPagination {
|
|
body += qb.sortAndPagination
|
|
}
|
|
|
|
return body
|
|
}
|
|
|
|
func (qb queryBuilder) findIDs(ctx context.Context) ([]int, error) {
|
|
const includeSortPagination = true
|
|
sql := qb.toSQL(includeSortPagination)
|
|
return qb.repository.runIdsQuery(ctx, sql, qb.args)
|
|
}
|
|
|
|
func (qb queryBuilder) executeFind(ctx context.Context) ([]int, int, error) {
|
|
const includeSortPagination = true
|
|
body := qb.body(includeSortPagination)
|
|
return qb.repository.executeFindQuery(ctx, body, qb.args, qb.sortAndPagination, qb.whereClauses, qb.havingClauses, qb.withClauses, qb.recursiveWith)
|
|
}
|
|
|
|
func (qb queryBuilder) executeCount(ctx context.Context) (int, error) {
|
|
const includeSortPagination = false
|
|
body := qb.body(includeSortPagination)
|
|
|
|
withClause := ""
|
|
if len(qb.withClauses) > 0 {
|
|
var recursive string
|
|
if qb.recursiveWith {
|
|
recursive = " RECURSIVE "
|
|
}
|
|
withClause = "WITH " + recursive + strings.Join(qb.withClauses, ", ") + " "
|
|
}
|
|
|
|
body = qb.repository.buildQueryBody(body, qb.whereClauses, qb.havingClauses)
|
|
countQuery := withClause + qb.repository.buildCountQuery(body)
|
|
return qb.repository.runCountQuery(ctx, countQuery, qb.args)
|
|
}
|
|
|
|
func (qb *queryBuilder) addWhere(clauses ...string) {
|
|
for _, clause := range clauses {
|
|
if len(clause) > 0 {
|
|
qb.whereClauses = append(qb.whereClauses, clause)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *queryBuilder) addHaving(clauses ...string) {
|
|
for _, clause := range clauses {
|
|
if len(clause) > 0 {
|
|
qb.havingClauses = append(qb.havingClauses, clause)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *queryBuilder) addWith(recursive bool, clauses ...string) {
|
|
for _, clause := range clauses {
|
|
if len(clause) > 0 {
|
|
qb.withClauses = append(qb.withClauses, clause)
|
|
}
|
|
}
|
|
|
|
qb.recursiveWith = qb.recursiveWith || recursive
|
|
}
|
|
|
|
func (qb *queryBuilder) addArg(args ...interface{}) {
|
|
qb.args = append(qb.args, args...)
|
|
}
|
|
|
|
func (qb *queryBuilder) hasJoin(alias string) bool {
|
|
for _, j := range qb.joins {
|
|
if j.alias() == alias {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (qb *queryBuilder) join(table, as, onClause string) {
|
|
newJoin := join{
|
|
table: table,
|
|
as: as,
|
|
onClause: onClause,
|
|
joinType: "LEFT",
|
|
}
|
|
|
|
qb.joins.add(newJoin)
|
|
}
|
|
|
|
func (qb *queryBuilder) joinSort(table, as, onClause string) {
|
|
newJoin := join{
|
|
sort: true,
|
|
table: table,
|
|
as: as,
|
|
onClause: onClause,
|
|
joinType: "LEFT",
|
|
}
|
|
|
|
qb.joins.add(newJoin)
|
|
}
|
|
|
|
func (qb *queryBuilder) addJoins(joins ...join) {
|
|
for _, j := range joins {
|
|
if qb.joins.addUnique(j) {
|
|
qb.args = append(qb.args, j.args...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *queryBuilder) addFilter(f *filterBuilder) error {
|
|
err := f.getError()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
clause, args := f.generateWithClauses()
|
|
if len(clause) > 0 {
|
|
qb.addWith(f.recursiveWith, clause)
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
// WITH clause always comes first and thus precedes alk args
|
|
qb.args = append(args, qb.args...)
|
|
}
|
|
|
|
// add joins here to insert args
|
|
qb.addJoins(f.getAllJoins()...)
|
|
|
|
clause, args = f.generateWhereClauses()
|
|
if len(clause) > 0 {
|
|
qb.addWhere(clause)
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
qb.addArg(args...)
|
|
}
|
|
|
|
clause, args = f.generateHavingClauses()
|
|
if len(clause) > 0 {
|
|
qb.addHaving(clause)
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
qb.addArg(args...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (qb *queryBuilder) parseQueryString(columns []string, q string) {
|
|
specs := models.ParseSearchString(q)
|
|
|
|
for _, t := range specs.MustHave {
|
|
var clauses []string
|
|
|
|
for _, column := range columns {
|
|
clauses = append(clauses, column+" LIKE ?")
|
|
qb.addArg(like(t))
|
|
}
|
|
|
|
qb.addWhere("(" + strings.Join(clauses, " OR ") + ")")
|
|
}
|
|
|
|
for _, t := range specs.MustNot {
|
|
for _, column := range columns {
|
|
qb.addWhere(coalesce(column) + " NOT LIKE ?")
|
|
qb.addArg(like(t))
|
|
}
|
|
}
|
|
|
|
for _, set := range specs.AnySets {
|
|
var clauses []string
|
|
|
|
for _, column := range columns {
|
|
for _, v := range set {
|
|
clauses = append(clauses, column+" LIKE ?")
|
|
qb.addArg(like(v))
|
|
}
|
|
}
|
|
|
|
qb.addWhere("(" + strings.Join(clauses, " OR ") + ")")
|
|
}
|
|
}
|