mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Merge 56eb2ff4f9 into 26b5cbafcd
This commit is contained in:
commit
88f614e894
8 changed files with 326 additions and 243 deletions
|
|
@ -183,23 +183,26 @@ func UserAlbums(frm form.SearchAlbums, sess *entity.Session) (results AlbumResul
|
|||
if txt.NotEmpty(frm.Year) {
|
||||
// Filter by the pictures included if it is a manually managed album, as these do not have an explicit
|
||||
// year assigned to them, unlike calendar albums and moments for example.
|
||||
w, v := AnyInt("albums.album_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax)
|
||||
if frm.Type == entity.AlbumManual {
|
||||
s = s.Where("? OR albums.album_uid IN (SELECT DISTINCT pay.album_uid FROM photos_albums pay "+
|
||||
"JOIN photos py ON pay.photo_uid = py.photo_uid WHERE py.photo_year IN (?) AND pay.hidden = 0 AND pay.missing = 0)",
|
||||
gorm.Expr(AnyInt("albums.album_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax)), strings.Split(frm.Year, txt.Or))
|
||||
gorm.Expr(w, v...), strings.Split(frm.Year, txt.Or))
|
||||
} else {
|
||||
s = s.Where(AnyInt("albums.album_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by month?
|
||||
if txt.NotEmpty(frm.Month) {
|
||||
s = s.Where(AnyInt("albums.album_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
w, v := AnyInt("albums.album_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by day?
|
||||
if txt.NotEmpty(frm.Day) {
|
||||
s = s.Where(AnyInt("albums.album_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
w, v := AnyInt("albums.album_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Limit result count.
|
||||
|
|
|
|||
|
|
@ -17,12 +17,19 @@ func Like(s string) string {
|
|||
return strings.Trim(clean.SqlString(s), " |&*%")
|
||||
}
|
||||
|
||||
// SQLParam cleans and preps a string for use as a parameter in a query. Pre and Post are used to add a wild card character for like's.
|
||||
func SQLParam(s, pre, post string) string {
|
||||
return pre + strings.Trim(clean.SQLClean(s), " |&*%") + post
|
||||
}
|
||||
|
||||
// LikeAny builds OR-chained LIKE predicates for a text column. The input string
|
||||
// may contain AND / OR separators; keywords trigger stemming and plural
|
||||
// normalization while exact mode disables wildcard suffixes.
|
||||
func LikeAny(col, s string, keywords, exact bool) (wheres []string) {
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAny(col, s string, keywords, exact bool) (wheres []string, valuesSlice [][]interface{}) {
|
||||
if s == "" {
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
}
|
||||
|
||||
s = txt.StripOr(clean.SearchQuery(s))
|
||||
|
|
@ -39,6 +46,7 @@ func LikeAny(col, s string, keywords, exact bool) (wheres []string) {
|
|||
|
||||
for _, k := range txt.UnTrimmedSplitWithEscape(s, txt.AndRune, txt.EscapeRune) {
|
||||
var orWheres []string
|
||||
var orValues []interface{}
|
||||
var words []string
|
||||
|
||||
if keywords {
|
||||
|
|
@ -53,9 +61,11 @@ func LikeAny(col, s string, keywords, exact bool) (wheres []string) {
|
|||
|
||||
for _, w := range words {
|
||||
if wildcardThreshold > 0 && len(w) >= wildcardThreshold {
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE '%s%%'", col, Like(w)))
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE ?", col))
|
||||
orValues = append(orValues, SQLParam(w, "", "%"))
|
||||
} else {
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE '%s'", col, Like(w)))
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE ?", col))
|
||||
orValues = append(orValues, SQLParam(w, "", ""))
|
||||
}
|
||||
|
||||
if !keywords || !txt.ContainsASCIILetters(w) {
|
||||
|
|
@ -65,35 +75,45 @@ func LikeAny(col, s string, keywords, exact bool) (wheres []string) {
|
|||
singular := inflection.Singular(w)
|
||||
|
||||
if singular != w {
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE '%s'", col, Like(singular)))
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE ?", col))
|
||||
orValues = append(orValues, SQLParam(singular, "", ""))
|
||||
}
|
||||
}
|
||||
|
||||
if len(orWheres) > 0 {
|
||||
wheres = append(wheres, strings.Join(orWheres, " OR "))
|
||||
valuesSlice = append(valuesSlice, orValues)
|
||||
}
|
||||
}
|
||||
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
}
|
||||
|
||||
// LikeAnyKeyword is a keyword-optimized wrapper around LikeAny.
|
||||
func LikeAnyKeyword(col, s string) (wheres []string) {
|
||||
// It returns a slice of where statements, and slices of slice the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAnyKeyword(col, s string) (wheres []string, valuesSlice [][]interface{}) {
|
||||
return LikeAny(col, s, true, false)
|
||||
}
|
||||
|
||||
// LikeAnyWord matches whole words and keeps wildcard thresholds tuned for
|
||||
// free-form text search instead of keyword lists.
|
||||
func LikeAnyWord(col, s string) (wheres []string) {
|
||||
// It returns a slice of where statements, and slices of slice the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAnyWord(col, s string) (wheres []string, valuesSlice [][]interface{}) {
|
||||
return LikeAny(col, s, false, false)
|
||||
}
|
||||
|
||||
// LikeAll produces AND-chained LIKE predicates for every significant token in
|
||||
// the search string. When exact is false, longer words receive a suffix
|
||||
// wildcard to support prefix matches.
|
||||
func LikeAll(col, s string, keywords, exact bool) (wheres []string) {
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAll(col, s string, keywords, exact bool) (wheres []string, valuesSlice [][]interface{}) {
|
||||
if s == "" {
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
}
|
||||
|
||||
var words []string
|
||||
|
|
@ -108,42 +128,54 @@ func LikeAll(col, s string, keywords, exact bool) (wheres []string) {
|
|||
}
|
||||
|
||||
if len(words) == 0 {
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
} else if exact {
|
||||
wildcardThreshold = -1
|
||||
}
|
||||
|
||||
for _, w := range words {
|
||||
var value []interface{}
|
||||
if wildcardThreshold > 0 && len(w) >= wildcardThreshold {
|
||||
wheres = append(wheres, fmt.Sprintf("%s LIKE '%s%%'", col, Like(w)))
|
||||
wheres = append(wheres, fmt.Sprintf("%s LIKE ?", col))
|
||||
value = append(value, SQLParam(w, "", "%"))
|
||||
} else {
|
||||
wheres = append(wheres, fmt.Sprintf("%s LIKE '%s'", col, Like(w)))
|
||||
wheres = append(wheres, fmt.Sprintf("%s LIKE ?", col))
|
||||
value = append(value, SQLParam(w, "", ""))
|
||||
}
|
||||
valuesSlice = append(valuesSlice, value)
|
||||
}
|
||||
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
}
|
||||
|
||||
// LikeAllKeywords is LikeAll specialized for keyword search.
|
||||
func LikeAllKeywords(col, s string) (wheres []string) {
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAllKeywords(col, s string) (wheres []string, valuesSlice [][]interface{}) {
|
||||
return LikeAll(col, s, true, false)
|
||||
}
|
||||
|
||||
// LikeAllWords is LikeAll specialized for general word search.
|
||||
func LikeAllWords(col, s string) (wheres []string) {
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAllWords(col, s string) (wheres []string, valuesSlice [][]interface{}) {
|
||||
return LikeAll(col, s, false, false)
|
||||
}
|
||||
|
||||
// LikeAllNames splits a name query into AND-separated groups and generates
|
||||
// prefix or substring matches against each provided column, keeping multi-word
|
||||
// tokens intact so "John Doe" still matches full-name columns.
|
||||
func LikeAllNames(cols Cols, s string) (wheres []string) {
|
||||
// It returns a slice of where statements, and slices of slice the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(wheres[0], valuesSlice[0]...)
|
||||
func LikeAllNames(cols Cols, s string) (wheres []string, valuesSlice [][]interface{}) {
|
||||
if len(cols) == 0 || len(s) < 1 {
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
}
|
||||
|
||||
for _, k := range txt.UnTrimmedSplitWithEscape(s, txt.AndRune, txt.EscapeRune) {
|
||||
var orWheres []string
|
||||
var orValues []interface{}
|
||||
|
||||
for _, w := range txt.UnTrimmedSplitWithEscape(k, txt.OrRune, txt.EscapeRune) {
|
||||
w = strings.TrimSpace(w)
|
||||
|
|
@ -154,27 +186,33 @@ func LikeAllNames(cols Cols, s string) (wheres []string) {
|
|||
|
||||
for _, c := range cols {
|
||||
if strings.Contains(w, txt.Space) {
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE '%s%%'", c, Like(w)))
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE ?", c))
|
||||
orValues = append(orValues, SQLParam(w, "", "%"))
|
||||
} else {
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE '%%%s%%'", c, Like(w)))
|
||||
orWheres = append(orWheres, fmt.Sprintf("%s LIKE ?", c))
|
||||
orValues = append(orValues, SQLParam(w, "%", "%"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(orWheres) > 0 {
|
||||
wheres = append(wheres, strings.Join(orWheres, " OR "))
|
||||
valuesSlice = append(valuesSlice, orValues)
|
||||
}
|
||||
}
|
||||
|
||||
return wheres
|
||||
return wheres, valuesSlice
|
||||
}
|
||||
|
||||
// AnySlug converts human-friendly search terms into slugs and matches them
|
||||
// against the provided slug column, including the singularized variant for
|
||||
// plural words (e.g. "Cats" -> "cat").
|
||||
func AnySlug(col, search, sep string) (where string) {
|
||||
// It returns a where statement, and a slice of the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(where, values...)
|
||||
func AnySlug(col, search, sep string) (where string, values []interface{}) {
|
||||
if search == "" {
|
||||
return ""
|
||||
return "", values
|
||||
}
|
||||
|
||||
if sep == "" {
|
||||
|
|
@ -201,21 +239,25 @@ func AnySlug(col, search, sep string) (where string) {
|
|||
}
|
||||
|
||||
if len(words) == 0 {
|
||||
return ""
|
||||
return "", values
|
||||
}
|
||||
|
||||
for _, w := range words {
|
||||
wheres = append(wheres, fmt.Sprintf("%s = '%s'", col, Like(w)))
|
||||
wheres = append(wheres, fmt.Sprintf("%s = ?", col))
|
||||
values = append(values, SQLParam(w, "", ""))
|
||||
}
|
||||
|
||||
return strings.Join(wheres, " OR ")
|
||||
return strings.Join(wheres, " OR "), values
|
||||
}
|
||||
|
||||
// AnyInt filters user-specified integers through an allowed range and returns
|
||||
// an OR-chained equality predicate for the values that remain.
|
||||
func AnyInt(col, numbers, sep string, min, max int) (where string) {
|
||||
// It returns a where statement, and a slice of the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(where, values...)
|
||||
func AnyInt(col, numbers, sep string, low, high int) (where string, values []interface{}) {
|
||||
if numbers == "" {
|
||||
return ""
|
||||
return "", values
|
||||
}
|
||||
|
||||
if sep == "" {
|
||||
|
|
@ -228,7 +270,7 @@ func AnyInt(col, numbers, sep string, min, max int) (where string) {
|
|||
for _, n := range strings.Split(numbers, sep) {
|
||||
i := txt.Int(n)
|
||||
|
||||
if i == 0 || i < min || i > max {
|
||||
if i == 0 || i < low || i > high {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -236,19 +278,23 @@ func AnyInt(col, numbers, sep string, min, max int) (where string) {
|
|||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
return ""
|
||||
return "", values
|
||||
}
|
||||
|
||||
for _, n := range matches {
|
||||
wheres = append(wheres, fmt.Sprintf("%s = %d", col, n))
|
||||
wheres = append(wheres, fmt.Sprintf("%s = ?", col))
|
||||
values = append(values, n)
|
||||
}
|
||||
|
||||
return strings.Join(wheres, " OR ")
|
||||
return strings.Join(wheres, " OR "), values
|
||||
}
|
||||
|
||||
// OrLike prepares a parameterised OR/LIKE clause for a single column. Star (* )
|
||||
// wildcards are mapped to SQL percent wildcards before returning the query and
|
||||
// bind values.
|
||||
// It returns a where statement, and a slice of the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(where, values...)
|
||||
func OrLike(col, s string) (where string, values []interface{}) {
|
||||
if txt.Empty(col) || txt.Empty(s) {
|
||||
return "", []interface{}{}
|
||||
|
|
@ -279,6 +325,9 @@ func OrLike(col, s string) (where string, values []interface{}) {
|
|||
// OrLikeCols behaves like OrLike but fans out the same search terms across
|
||||
// multiple columns, preserving the order of values so callers can feed them to
|
||||
// database/sql.
|
||||
// It returns a where statement, and a slice of the parameter values.
|
||||
// Expectation is that each set of results will be fed into gorm.Expr
|
||||
// eg. gorm.Expr(where, values...)
|
||||
func OrLikeCols(cols []string, s string) (where string, values []interface{}) {
|
||||
if len(cols) == 0 || txt.Empty(s) {
|
||||
return "", []interface{}{}
|
||||
|
|
|
|||
|
|
@ -31,289 +31,248 @@ func TestLike(t *testing.T) {
|
|||
|
||||
func TestLikeAny(t *testing.T) {
|
||||
t.Run("AndOrSearch", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "table spoon & usa | img json", true, false); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'json%' OR k.keyword LIKE 'usa'", w[1])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "table spoon & usa | img json", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {"json%", "usa"}}, v)
|
||||
})
|
||||
t.Run("ExactAndOrSearch", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "table spoon & usa | img json", true, true); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon' OR k.keyword LIKE 'table'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'json' OR k.keyword LIKE 'usa'", w[1])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "table spoon & usa | img json", true, true)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon", "table"}, {"json", "usa"}}, v)
|
||||
})
|
||||
t.Run("AndOrSearchEn", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "table spoon and usa or img json", true, false); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'json%' OR k.keyword LIKE 'usa'", w[1])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "table spoon and usa or img json", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {"json%", "usa"}}, v)
|
||||
})
|
||||
t.Run("TableSpoonUsaImgJson", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "table spoon usa img json", true, false); len(w) != 1 {
|
||||
t.Fatal("one where condition expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'json%' OR k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%' OR k.keyword LIKE 'usa'", w[0])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "table spoon usa img json", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ? OR k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"json%", "spoon%", "table%", "usa"}}, v)
|
||||
})
|
||||
t.Run("CatDog", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "cat dog", true, false); len(w) != 1 {
|
||||
t.Fatal("one where condition expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'cat' OR k.keyword LIKE 'dog'", w[0])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "cat dog", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"cat", "dog"}}, v)
|
||||
})
|
||||
t.Run("CatsDogs", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "cats dogs", true, false); len(w) != 1 {
|
||||
t.Fatal("one where condition expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'cats%' OR k.keyword LIKE 'cat' OR k.keyword LIKE 'dogs%' OR k.keyword LIKE 'dog'", w[0])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "cats dogs", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ? OR k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"cats%", "cat", "dogs%", "dog"}}, v)
|
||||
})
|
||||
t.Run("Spoon", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "spoon", true, false); len(w) != 1 {
|
||||
t.Fatal("one where condition expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%'", w[0])
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "spoon", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%"}}, v)
|
||||
})
|
||||
t.Run("Img", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "img", true, false); len(w) > 0 {
|
||||
t.Fatal("no where condition expected")
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "img", true, false)
|
||||
assert.Empty(t, w, "where")
|
||||
assert.Empty(t, v, "value")
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
if w := LikeAny("k.keyword", "", true, false); len(w) > 0 {
|
||||
t.Fatal("no where condition expected")
|
||||
}
|
||||
w, v := LikeAny("k.keyword", "", true, false)
|
||||
assert.Empty(t, w, "where")
|
||||
assert.Empty(t, v, "value")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikeAnyKeyword(t *testing.T) {
|
||||
t.Run("AndOrSearch", func(t *testing.T) {
|
||||
if w := LikeAnyKeyword("k.keyword", "table spoon & usa | img json"); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'json%' OR k.keyword LIKE 'usa'", w[1])
|
||||
}
|
||||
w, v := LikeAnyKeyword("k.keyword", "table spoon & usa | img json")
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {"json%", "usa"}}, v)
|
||||
})
|
||||
t.Run("AndOrSearchEn", func(t *testing.T) {
|
||||
if w := LikeAnyKeyword("k.keyword", "table spoon and usa or img json"); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'json%' OR k.keyword LIKE 'usa'", w[1])
|
||||
}
|
||||
w, v := LikeAnyKeyword("k.keyword", "table spoon and usa or img json")
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {"json%", "usa"}}, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikeAnyWord(t *testing.T) {
|
||||
t.Run("SearchAndOr", func(t *testing.T) {
|
||||
if w := LikeAnyWord("k.keyword", "table spoon & usa | img json"); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'img%' OR k.keyword LIKE 'json%' OR k.keyword LIKE 'usa%'", w[1])
|
||||
}
|
||||
w, v := LikeAnyWord("k.keyword", "table spoon & usa | img json")
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {"img%", "json%", "usa%"}}, v)
|
||||
})
|
||||
t.Run("SearchAndOrEnglish", func(t *testing.T) {
|
||||
if w := LikeAnyWord("k.keyword", "table spoon and usa or img json"); len(w) != 2 {
|
||||
t.Fatal("two where conditions expected")
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE 'img%' OR k.keyword LIKE 'json%' OR k.keyword LIKE 'usa%'", w[1])
|
||||
}
|
||||
w, v := LikeAnyWord("k.keyword", "table spoon and usa or img json")
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ? OR k.keyword LIKE ? OR k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {"img%", "json%", "usa%"}}, v)
|
||||
})
|
||||
t.Run("EscapeSql", func(t *testing.T) {
|
||||
if w := LikeAnyWord("k.keyword", "table% | 'spoon' & \"us'a"); len(w) != 2 {
|
||||
t.Fatalf("two where conditions expected: %#v", w)
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE '\"\"us''a%'", w[1])
|
||||
}
|
||||
w, v := LikeAnyWord("k.keyword", "table% | 'spoon' & \"us'a")
|
||||
assert.Equal(t, []string{"k.keyword LIKE ? OR k.keyword LIKE ?", "k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"spoon%", "table%"}, {`"us'a%`}}, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikeAll(t *testing.T) {
|
||||
t.Run("Keywords", func(t *testing.T) {
|
||||
if w := LikeAll("k.keyword", "Jo Mander 李", true, false); len(w) == 2 {
|
||||
assert.Equal(t, "k.keyword LIKE 'mander%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE '李'", w[1])
|
||||
} else {
|
||||
t.Logf("wheres: %#v", w)
|
||||
t.Fatal("two where conditions expected")
|
||||
}
|
||||
w, v := LikeAll("k.keyword", "Jo Mander 李", true, false)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ?", "k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"mander%"}, {"李"}}, v)
|
||||
})
|
||||
t.Run("Exact", func(t *testing.T) {
|
||||
if w := LikeAll("k.keyword", "Jo Mander 李", true, true); len(w) == 2 {
|
||||
assert.Equal(t, "k.keyword LIKE 'mander'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE '李'", w[1])
|
||||
} else {
|
||||
t.Logf("wheres: %#v", w)
|
||||
t.Fatal("two where conditions expected")
|
||||
}
|
||||
w, v := LikeAll("k.keyword", "Jo Mander 李", true, true)
|
||||
assert.Equal(t, []string{"k.keyword LIKE ?", "k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"mander"}, {"李"}}, v)
|
||||
})
|
||||
t.Run("StringEmpty", func(t *testing.T) {
|
||||
w := LikeAll("k.keyword", "", true, true)
|
||||
w, v := LikeAll("k.keyword", "", true, true)
|
||||
assert.Empty(t, w)
|
||||
assert.Empty(t, v)
|
||||
})
|
||||
t.Run("ZeroWords", func(t *testing.T) {
|
||||
w := LikeAll("k.keyword", "ab", true, true)
|
||||
w, v := LikeAll("k.keyword", "ab", true, true)
|
||||
assert.Empty(t, w)
|
||||
assert.Empty(t, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikeAllKeywords(t *testing.T) {
|
||||
t.Run("Keywords", func(t *testing.T) {
|
||||
if w := LikeAllKeywords("k.keyword", "Jo Mander 李"); len(w) == 2 {
|
||||
assert.Equal(t, "k.keyword LIKE 'mander%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE '李'", w[1])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllKeywords("k.keyword", "Jo Mander 李")
|
||||
assert.Equal(t, []string{"k.keyword LIKE ?", "k.keyword LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"mander%"}, {"李"}}, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikeAllWords(t *testing.T) {
|
||||
t.Run("Keywords", func(t *testing.T) {
|
||||
if w := LikeAllWords("k.name", "Jo Mander 王"); len(w) == 3 {
|
||||
assert.Equal(t, "k.name LIKE 'jo%'", w[0])
|
||||
assert.Equal(t, "k.name LIKE 'mander%'", w[1])
|
||||
assert.Equal(t, "k.name LIKE '王%'", w[2])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllWords("k.name", "Jo Mander 王")
|
||||
assert.Equal(t, []string{"k.name LIKE ?", "k.name LIKE ?", "k.name LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"jo%"}, {"mander%"}, {"王%"}}, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLikeAllNames(t *testing.T) {
|
||||
t.Run("MultipleNames", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"k.name"}, "j Mander 王"); len(w) == 1 {
|
||||
assert.Equal(t, "k.name LIKE 'j Mander 王%'", w[0])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"k.name"}, "j Mander 王")
|
||||
assert.Equal(t, []string{"k.name LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"j Mander 王%"}}, v)
|
||||
})
|
||||
t.Run("MultipleColumns", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"a.col1", "b.col2"}, "Mo Mander"); len(w) == 1 {
|
||||
assert.Equal(t, "a.col1 LIKE 'Mo Mander%' OR b.col2 LIKE 'Mo Mander%'", w[0])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"a.col1", "b.col2"}, "Mo Mander")
|
||||
assert.Equal(t, []string{"a.col1 LIKE ? OR b.col2 LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"Mo Mander%", "Mo Mander%"}}, v)
|
||||
})
|
||||
t.Run("EmptyName", func(t *testing.T) {
|
||||
w := LikeAllNames(Cols{"k.name"}, "")
|
||||
w, v := LikeAllNames(Cols{"k.name"}, "")
|
||||
assert.Empty(t, w)
|
||||
assert.Empty(t, v)
|
||||
})
|
||||
t.Run("SingleCharacter", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"k.name"}, "a"); len(w) == 1 {
|
||||
assert.Equal(t, "k.name LIKE '%a%'", w[0])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"k.name"}, "a")
|
||||
assert.Equal(t, []string{"k.name LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"%a%"}}, v)
|
||||
})
|
||||
t.Run("FullNames", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"j.name", "j.alias"}, "Bill & Melinda Gates"); len(w) == 2 {
|
||||
assert.Equal(t, "j.name LIKE '%Bill%' OR j.alias LIKE '%Bill%'", w[0])
|
||||
assert.Equal(t, "j.name LIKE 'Melinda Gates%' OR j.alias LIKE 'Melinda Gates%'", w[1])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"j.name", "j.alias"}, "Bill & Melinda Gates")
|
||||
assert.Equal(t, []string{"j.name LIKE ? OR j.alias LIKE ?", "j.name LIKE ? OR j.alias LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"%Bill%", "%Bill%"}, {"Melinda Gates%", "Melinda Gates%"}}, v)
|
||||
})
|
||||
t.Run("Plus", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"name"}, clean.SearchQuery("Paul + Paula")); len(w) == 2 {
|
||||
assert.Equal(t, "name LIKE '%Paul%'", w[0])
|
||||
assert.Equal(t, "name LIKE '%Paula%'", w[1])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"name"}, clean.SearchQuery("Paul + Paula"))
|
||||
assert.Equal(t, []string{"name LIKE ?", "name LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"%Paul%"}, {"%Paula%"}}, v)
|
||||
})
|
||||
t.Run("And", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"name"}, clean.SearchQuery("P and Paula")); len(w) == 2 {
|
||||
assert.Equal(t, "name LIKE '%P%'", w[0])
|
||||
assert.Equal(t, "name LIKE '%Paula%'", w[1])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"name"}, clean.SearchQuery("P and Paula"))
|
||||
assert.Equal(t, []string{"name LIKE ?", "name LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"%P%"}, {"%Paula%"}}, v)
|
||||
})
|
||||
t.Run("Or", func(t *testing.T) {
|
||||
if w := LikeAllNames(Cols{"name"}, clean.SearchQuery("Paul or Paula")); len(w) == 1 {
|
||||
assert.Equal(t, "name LIKE '%Paul%' OR name LIKE '%Paula%'", w[0])
|
||||
} else {
|
||||
t.Fatalf("unexpected result: %#v", w)
|
||||
}
|
||||
w, v := LikeAllNames(Cols{"name"}, clean.SearchQuery("Paul or Paula"))
|
||||
assert.Equal(t, []string{"name LIKE ? OR name LIKE ?"}, w)
|
||||
assert.Equal(t, [][]interface{}{{"%Paul%", "%Paula%"}}, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAnySlug(t *testing.T) {
|
||||
t.Run("Multiple", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "table spoon usa img json", " ")
|
||||
assert.Equal(t, "custom_slug = 'table' OR custom_slug = 'spoon' OR custom_slug = 'usa' OR custom_slug = 'img' OR custom_slug = 'json'", where)
|
||||
w, v := AnySlug("custom_slug", "table spoon usa img json", " ")
|
||||
assert.Equal(t, "custom_slug = ? OR custom_slug = ? OR custom_slug = ? OR custom_slug = ? OR custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"table", "spoon", "usa", "img", "json"}, v)
|
||||
})
|
||||
t.Run("CatDog", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "cat dog", " ")
|
||||
assert.Equal(t, "custom_slug = 'cat' OR custom_slug = 'dog'", where)
|
||||
w, v := AnySlug("custom_slug", "cat dog", " ")
|
||||
assert.Equal(t, "custom_slug = ? OR custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"cat", "dog"}, v)
|
||||
})
|
||||
t.Run("CatsDogs", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "cats dogs", " ")
|
||||
assert.Equal(t, "custom_slug = 'cats' OR custom_slug = 'cat' OR custom_slug = 'dogs' OR custom_slug = 'dog'", where)
|
||||
w, v := AnySlug("custom_slug", "cats dogs", " ")
|
||||
assert.Equal(t, "custom_slug = ? OR custom_slug = ? OR custom_slug = ? OR custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"cats", "cat", "dogs", "dog"}, v)
|
||||
})
|
||||
t.Run("Spoon", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "spoon", " ")
|
||||
assert.Equal(t, "custom_slug = 'spoon'", where)
|
||||
w, v := AnySlug("custom_slug", "spoon", " ")
|
||||
assert.Equal(t, "custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"spoon"}, v)
|
||||
})
|
||||
t.Run("Img", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "img", " ")
|
||||
assert.Equal(t, "custom_slug = 'img'", where)
|
||||
w, v := AnySlug("custom_slug", "img", " ")
|
||||
assert.Equal(t, "custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"img"}, v)
|
||||
})
|
||||
t.Run("Space", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", " ", "")
|
||||
assert.Equal(t, "custom_slug = '' OR custom_slug = ''", where)
|
||||
w, v := AnySlug("custom_slug", " ", "")
|
||||
assert.Equal(t, "custom_slug = ? OR custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"", ""}, v)
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "", " ")
|
||||
where, values := AnySlug("custom_slug", "", " ")
|
||||
assert.Equal(t, "", where)
|
||||
assert.Empty(t, values)
|
||||
})
|
||||
t.Run("CommaSeparated", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "botanical-garden|landscape|bay", txt.Or)
|
||||
assert.Equal(t, "custom_slug = 'botanical-garden' OR custom_slug = 'landscape' OR custom_slug = 'bay'", where)
|
||||
w, v := AnySlug("custom_slug", "botanical-garden,landscape,bay", ",")
|
||||
assert.Equal(t, "custom_slug = ? OR custom_slug = ? OR custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"botanical-garden", "landscape", "bay"}, v)
|
||||
})
|
||||
t.Run("PipeSeparated", func(t *testing.T) {
|
||||
w, v := AnySlug("custom_slug", "botanical-garden|landscape|bay", txt.Or)
|
||||
assert.Equal(t, "custom_slug = ? OR custom_slug = ? OR custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"botanical-garden", "landscape", "bay"}, v)
|
||||
})
|
||||
t.Run("Emoji", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "💐", "|")
|
||||
assert.Equal(t, "custom_slug = '_5cpzfea'", where)
|
||||
w, v := AnySlug("custom_slug", "💐", "|")
|
||||
assert.Equal(t, "custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"_5cpzfea"}, v)
|
||||
})
|
||||
t.Run("EmojiSlug", func(t *testing.T) {
|
||||
where := AnySlug("custom_slug", "_5cpzfea", "|")
|
||||
assert.Equal(t, "custom_slug = '_5cpzfea'", where)
|
||||
w, v := AnySlug("custom_slug", "_5cpzfea", "|")
|
||||
assert.Equal(t, "custom_slug = ?", w)
|
||||
assert.Equal(t, []interface{}{"_5cpzfea"}, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAnyInt(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
where := AnyInt("photos.photo_month", "", txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
where, values := AnyInt("photos.photo_month", "", txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "", where)
|
||||
assert.Empty(t, values)
|
||||
})
|
||||
t.Run("Range", func(t *testing.T) {
|
||||
where := AnyInt("photos.photo_month", "-3|0|10|9|11|12|13", txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "photos.photo_month = 10 OR photos.photo_month = 9 OR photos.photo_month = 11 OR photos.photo_month = 12", where)
|
||||
w, v := AnyInt("photos.photo_month", "-3|0|10|9|11|12|13", txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "photos.photo_month = ? OR photos.photo_month = ? OR photos.photo_month = ? OR photos.photo_month = ?", w)
|
||||
assert.Equal(t, []interface{}{10, 9, 11, 12}, v)
|
||||
})
|
||||
t.Run("Chars", func(t *testing.T) {
|
||||
where := AnyInt("photos.photo_month", "a|b|c", txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
where, values := AnyInt("photos.photo_month", "a|b|c", txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "", where)
|
||||
assert.Empty(t, values)
|
||||
})
|
||||
t.Run("CommaSeparated", func(t *testing.T) {
|
||||
where := AnyInt("photos.photo_month", "-3,10,9,11,12,13", ",", entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "photos.photo_month = 10 OR photos.photo_month = 9 OR photos.photo_month = 11 OR photos.photo_month = 12", where)
|
||||
w, v := AnyInt("photos.photo_month", "-3,10,9,11,12,13", ",", entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "photos.photo_month = ? OR photos.photo_month = ? OR photos.photo_month = ? OR photos.photo_month = ?", w)
|
||||
assert.Equal(t, []interface{}{10, 9, 11, 12}, v)
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
where := AnyInt("photos.photo_month", " , | ", ",", entity.UnknownMonth, txt.MonthMax)
|
||||
where, values := AnyInt("photos.photo_month", " , | ", ",", entity.UnknownMonth, txt.MonthMax)
|
||||
assert.Equal(t, "", where)
|
||||
assert.Empty(t, values)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -290,7 +290,9 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
|
||||
if labelErr := Db().Where(AnySlug("label_slug", frm.Label, txt.Or)).Or(AnySlug("custom_slug", frm.Label, txt.Or)).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
wl, vl := AnySlug("label_slug", frm.Label, txt.Or)
|
||||
wc, vc := AnySlug("custom_slug", frm.Label, txt.Or)
|
||||
if labelErr := Db().Where(wl, vl...).Or(wc, vc...).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
log.Debugf("search: label %s not found", txt.LogParamLower(frm.Label))
|
||||
return PhotoResults{}, 0, nil
|
||||
} else {
|
||||
|
|
@ -400,11 +402,13 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
|
||||
if labelsErr := Db().Where(AnySlug("custom_slug", frm.Query, " ")).Find(&labels).Error; len(labels) == 0 || labelsErr != nil {
|
||||
w, v := AnySlug("custom_slug", frm.Query, " ")
|
||||
if labelsErr := Db().Where(w, v...).Find(&labels).Error; len(labels) == 0 || labelsErr != nil {
|
||||
log.Tracef("search: label %s not found, using fuzzy search", txt.LogParamLower(frm.Query))
|
||||
|
||||
for _, where := range LikeAnyKeyword("k.keyword", frm.Query) {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
wheres, values := LikeAnyKeyword("k.keyword", frm.Query)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
} else {
|
||||
for _, l := range labels {
|
||||
|
|
@ -419,10 +423,10 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
}
|
||||
}
|
||||
|
||||
if wheres := LikeAnyKeyword("k.keyword", frm.Query); len(wheres) > 0 {
|
||||
for _, where := range wheres {
|
||||
if wheres, values := LikeAnyKeyword("k.keyword", frm.Query); len(wheres) > 0 {
|
||||
for i, where := range wheres {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?)) OR "+
|
||||
"files.photo_id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", gorm.Expr(where), labelIds)
|
||||
"files.photo_id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", gorm.Expr(where, values[i]...), labelIds)
|
||||
}
|
||||
} else {
|
||||
s = s.Where("files.photo_id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", labelIds)
|
||||
|
|
@ -432,8 +436,9 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
|
||||
// Search for one or more keywords.
|
||||
if txt.NotEmpty(frm.Keywords) {
|
||||
for _, where := range LikeAnyWord("k.keyword", frm.Keywords) {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
wheres, values := LikeAnyWord("k.keyword", frm.Keywords)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -480,14 +485,16 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE subj_uid IN (?))",
|
||||
entity.Marker{}.TableName()), subjects)
|
||||
} else {
|
||||
w, v := AnySlug("s.subj_slug", subj, txt.Or)
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))",
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(AnySlug("s.subj_slug", subj, txt.Or)))
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(w, v...))
|
||||
}
|
||||
}
|
||||
} else if txt.NotEmpty(frm.Subjects) {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Subjects) {
|
||||
wheres, values := LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Subjects)
|
||||
for i, where := range wheres {
|
||||
s = s.Where(fmt.Sprintf("files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))",
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(where))
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -548,17 +555,20 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
|
||||
// Filter by year.
|
||||
if frm.Year != "" {
|
||||
s = s.Where(AnyInt("photos.photo_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
w, v := AnyInt("photos.photo_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by month.
|
||||
if frm.Month != "" {
|
||||
s = s.Where(AnyInt("photos.photo_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
w, v := AnyInt("photos.photo_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by day.
|
||||
if frm.Day != "" {
|
||||
s = s.Where(AnyInt("photos.photo_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
w, v := AnyInt("photos.photo_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by Resolution in Megapixels (MP).
|
||||
|
|
@ -826,8 +836,9 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
|
|||
v := strings.Trim(frm.Album, "*%") + "%"
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (a.album_title LIKE ? OR a.album_slug LIKE ?))", v, v)
|
||||
} else if txt.NotEmpty(frm.Albums) {
|
||||
for _, where := range LikeAnyWord("a.album_title", frm.Albums) {
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))", gorm.Expr(where))
|
||||
wheres, values := LikeAnyWord("a.album_title", frm.Albums)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,9 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
|
||||
if labelErr := Db().Where(AnySlug("label_slug", frm.Label, txt.Or)).Or(AnySlug("custom_slug", frm.Label, txt.Or)).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
wl, vl := AnySlug("label_slug", frm.Label, txt.Or)
|
||||
wc, vc := AnySlug("custom_slug", frm.Label, txt.Or)
|
||||
if labelErr := Db().Where(wl, vl...).Or(wc, vc...).Find(&labels).Error; len(labels) == 0 || labelErr != nil {
|
||||
log.Debugf("search: label %s not found", txt.LogParamLower(frm.Label))
|
||||
return GeoResults{}, nil
|
||||
} else {
|
||||
|
|
@ -312,11 +314,13 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
var labels []entity.Label
|
||||
var labelIds []uint
|
||||
|
||||
if labelsErr := Db().Where(AnySlug("custom_slug", frm.Query, " ")).Find(&labels).Error; len(labels) == 0 || labelsErr != nil {
|
||||
w, v := AnySlug("custom_slug", frm.Query, " ")
|
||||
if labelsErr := Db().Where(w, v...).Find(&labels).Error; len(labels) == 0 || labelsErr != nil {
|
||||
log.Tracef("search: label %s not found, using fuzzy search", txt.LogParamLower(frm.Query))
|
||||
|
||||
for _, where := range LikeAnyKeyword("k.keyword", frm.Query) {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
wheres, values := LikeAnyKeyword("k.keyword", frm.Query)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
} else {
|
||||
for _, l := range labels {
|
||||
|
|
@ -330,10 +334,10 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
}
|
||||
}
|
||||
|
||||
if wheres := LikeAnyKeyword("k.keyword", frm.Query); len(wheres) > 0 {
|
||||
for _, where := range wheres {
|
||||
if wheres, values := LikeAnyKeyword("k.keyword", frm.Query); len(wheres) > 0 {
|
||||
for i, where := range wheres {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?)) OR "+
|
||||
"photos.id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", gorm.Expr(where), labelIds)
|
||||
"photos.id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", gorm.Expr(where, values[i]...), labelIds)
|
||||
}
|
||||
} else {
|
||||
s = s.Where("photos.id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))", labelIds)
|
||||
|
|
@ -343,8 +347,9 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
|
||||
// Search for one or more keywords.
|
||||
if frm.Keywords != "" {
|
||||
for _, where := range LikeAnyWord("k.keyword", frm.Keywords) {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where))
|
||||
wheres, values := LikeAnyWord("k.keyword", frm.Keywords)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -391,14 +396,16 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE subj_uid IN (?))",
|
||||
entity.Marker{}.TableName()), subjects)
|
||||
} else {
|
||||
w, v := AnySlug("s.subj_slug", subj, txt.Or)
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))",
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(AnySlug("s.subj_slug", subj, txt.Or)))
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(w, v...))
|
||||
}
|
||||
}
|
||||
} else if frm.Subjects != "" {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Subjects) {
|
||||
wheres, values := LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Subjects)
|
||||
for i, where := range wheres {
|
||||
s = s.Where(fmt.Sprintf("photos.id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))",
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(where))
|
||||
entity.Marker{}.TableName(), entity.Subject{}.TableName()), gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -410,8 +417,9 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
v := strings.Trim(frm.Album, "*%") + "%"
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (a.album_title LIKE ? OR a.album_slug LIKE ?))", v, v)
|
||||
} else if txt.NotEmpty(frm.Albums) {
|
||||
for _, where := range LikeAnyWord("a.album_title", frm.Albums) {
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))", gorm.Expr(where))
|
||||
wheres, values := LikeAnyWord("a.album_title", frm.Albums)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -443,17 +451,20 @@ func UserPhotosGeo(frm form.SearchPhotosGeo, sess *entity.Session) (results GeoR
|
|||
|
||||
// Filter by year.
|
||||
if frm.Year != "" {
|
||||
s = s.Where(AnyInt("photos.photo_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax))
|
||||
w, v := AnyInt("photos.photo_year", frm.Year, txt.Or, entity.UnknownYear, txt.YearMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by month.
|
||||
if frm.Month != "" {
|
||||
s = s.Where(AnyInt("photos.photo_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
|
||||
w, v := AnyInt("photos.photo_month", frm.Month, txt.Or, entity.UnknownMonth, txt.MonthMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by day.
|
||||
if frm.Day != "" {
|
||||
s = s.Where(AnyInt("photos.photo_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax))
|
||||
w, v := AnyInt("photos.photo_day", frm.Day, txt.Or, entity.UnknownDay, txt.DayMax)
|
||||
s = s.Where(w, v...)
|
||||
}
|
||||
|
||||
// Filter by Resolution in Megapixels (MP).
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ func Subjects(frm form.SearchSubjects) (results SubjectResults, err error) {
|
|||
}
|
||||
|
||||
if frm.Query != "" {
|
||||
for _, where := range LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Query) {
|
||||
s = s.Where("?", gorm.Expr(where))
|
||||
wheres, values := LikeAllNames(Cols{"subj_name", "subj_alias"}, frm.Query)
|
||||
for i, where := range wheres {
|
||||
s = s.Where("?", gorm.Expr(where, values[i]...))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +122,7 @@ func SubjectUIDs(s string) (result []string, names []string, remaining string) {
|
|||
|
||||
var matches []Matches
|
||||
|
||||
wheres := LikeAllNames(Cols{"subj_name", "subj_alias"}, s)
|
||||
wheres, values := LikeAllNames(Cols{"subj_name", "subj_alias"}, s)
|
||||
|
||||
if len(wheres) == 0 {
|
||||
return result, names, s
|
||||
|
|
@ -129,11 +130,11 @@ func SubjectUIDs(s string) (result []string, names []string, remaining string) {
|
|||
|
||||
remaining = s
|
||||
|
||||
for _, where := range wheres {
|
||||
for i, where := range wheres {
|
||||
var subj []string
|
||||
|
||||
stmt := Db().Model(entity.Subject{})
|
||||
stmt = stmt.Where("?", gorm.Expr(where))
|
||||
stmt = stmt.Where("?", gorm.Expr(where, values[i]...))
|
||||
|
||||
if err := stmt.Scan(&matches).Error; err != nil {
|
||||
log.Errorf("search: %s while finding subjects", err)
|
||||
|
|
|
|||
|
|
@ -51,3 +51,34 @@ func SqlString(s string) string {
|
|||
|
||||
return string(b[:j])
|
||||
}
|
||||
|
||||
// SQLClean removes bytes from a string that are determined as requiring omitting
|
||||
func SQLClean(s string) string {
|
||||
var i int
|
||||
for i = range len(s) {
|
||||
if _, found := SqlSpecial(s[i]); found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Return if no omittable characters were found.
|
||||
if i >= len(s) {
|
||||
return s
|
||||
}
|
||||
|
||||
b := make([]byte, 2*len(s)-i)
|
||||
|
||||
copy(b, s[:i])
|
||||
|
||||
j := i
|
||||
|
||||
for ; i < len(s); i++ {
|
||||
if _, omit := SqlSpecial(s[i]); !omit {
|
||||
// Keep all bytes not omitted
|
||||
b[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return string(b[:j])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,3 +82,21 @@ func TestSqlString(t *testing.T) {
|
|||
assert.Equal(t, "123ABCabc", SqlString("123ABCabc"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSQLClean(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", SQLClean(""))
|
||||
})
|
||||
t.Run("Special", func(t *testing.T) {
|
||||
s := "' \" \t \n %_''" // Single Quote, Space, Double Qoute, Space, Tab, Space, New Line, Space, Percent, Underline, Single Quote, Single Quote
|
||||
exp := "' \" %_''" // Single Quote, Space, Double Qoute, Space, Space, Space, Percent, Underline, Single Quote, Single Quote
|
||||
result := SQLClean(s)
|
||||
assert.Equal(t, exp, result)
|
||||
})
|
||||
t.Run("Alnum", func(t *testing.T) {
|
||||
assert.Equal(t, "123ABCabc", SQLClean("123ABCabc"))
|
||||
})
|
||||
t.Run("Unicode", func(t *testing.T) {
|
||||
assert.Equal(t, "Clean《MeUp✀☒ちュس", SQLClean("Clean《MeUp✀☒ちュس"))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue