Search: Improve filter value parsing and update tests #1994

This commit is contained in:
Michael Mayer 2022-04-02 22:23:38 +02:00
parent bf4005d9ef
commit a5f948d68a
7 changed files with 154 additions and 69 deletions

View file

@ -151,9 +151,9 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
// Filter for specific face clusters? Example: PLJ7A3G4MBGZJRMVDIUCBLC46IAP4N7O
if len(f.Face) >= 32 {
for _, f := range strings.Split(strings.ToUpper(f.Face), txt.And) {
for _, f := range SplitAnd(strings.ToUpper(f.Face)) {
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 face_id IN (?))",
entity.Marker{}.TableName()), strings.Split(f, txt.Or))
entity.Marker{}.TableName()), SplitOr(f))
}
} else if txt.New(f.Face) {
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 AND m.marker_type = ? WHERE subj_uid IS NULL OR subj_uid = '')",
@ -168,8 +168,8 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
// Filter for one or more subjects?
if f.Subject != "" {
for _, subj := range strings.Split(strings.ToLower(f.Subject), txt.And) {
if subjects := strings.Split(subj, txt.Or); rnd.ContainsUIDs(subjects, 'j') {
for _, subj := range SplitAnd(strings.ToLower(f.Subject)) {
if subjects := SplitOr(subj); rnd.ContainsUIDs(subjects, 'j') {
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 {
@ -230,7 +230,7 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
// Filter by main color?
if f.Color != "" {
s = s.Where("files.file_main_color IN (?)", strings.Split(strings.ToLower(f.Color), txt.Or))
s = s.Where("files.file_main_color IN (?)", SplitOr(strings.ToLower(f.Color)))
}
// Find favorites only?
@ -250,12 +250,12 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
// Filter by location country?
if f.Country != "" {
s = s.Where("photos.photo_country IN (?)", strings.Split(strings.ToLower(f.Country), txt.Or))
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(f.Country)))
}
// Filter by media type?
if f.Type != "" {
s = s.Where("photos.photo_type IN (?)", strings.Split(strings.ToLower(f.Type), txt.Or))
s = s.Where("photos.photo_type IN (?)", SplitOr(strings.ToLower(f.Type)))
} else if f.Video {
s = s.Where("photos.photo_type = 'video'")
} else if f.Photo {

View file

@ -261,3 +261,38 @@ func OrLike(col, s string) (where string, values []interface{}) {
return where, values
}
// Split splits a search string into separate values and trims whitespace.
func Split(s string, sep string) (result []string) {
if s == "" {
return []string{}
}
// Trim separator and split.
s = strings.Trim(s, sep)
v := strings.Split(s, sep)
if len(v) <= 1 {
return v
}
result = make([]string, 0, len(v))
for i := range v {
if t := strings.TrimSpace(v[i]); t != "" {
result = append(result, t)
}
}
return result
}
// SplitOr splits a search string into separate OR values for an IN condition.
func SplitOr(s string) (values []string) {
return Split(s, txt.Or)
}
// SplitAnd splits a search string into separate AND values.
func SplitAnd(s string) (values []string) {
return Split(s, txt.And)
}

View file

@ -358,3 +358,79 @@ func TestOrLike(t *testing.T) {
assert.Equal(t, []interface{}{"1990%", "2790/07/27900704_070228_D6D51B6C.jpg"}, values)
})
}
func TestSplit(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
values := Split("", "")
assert.Equal(t, []string{}, values)
})
t.Run("FooBar", func(t *testing.T) {
values := Split(" foo | Bar ", "&")
assert.Equal(t, []string{" foo | Bar "}, values)
})
t.Run("FooAndBar", func(t *testing.T) {
values := Split(" foo & Bar ", "o")
assert.Equal(t, []string{"f", "& Bar"}, values)
})
t.Run("FooAndBarAndBaz", func(t *testing.T) {
values := Split(" foo & Bar&BAZ ", "&")
assert.Equal(t, []string{"foo", "Bar", "BAZ"}, values)
})
}
func TestSplitOr(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
values := SplitOr("")
assert.Equal(t, []string{}, values)
})
t.Run("FooBar", func(t *testing.T) {
values := SplitOr(" foo | Bar ")
assert.Equal(t, []string{"foo", "Bar"}, values)
})
t.Run("FooBarTrim", func(t *testing.T) {
values := SplitOr(" foo | Bar |")
assert.Equal(t, []string{"foo", "Bar"}, values)
})
t.Run("FooAndBar", func(t *testing.T) {
values := SplitOr(" foo & Bar ")
assert.Equal(t, []string{" foo & Bar "}, values)
})
t.Run("FooAndBarAndBaz", func(t *testing.T) {
values := SplitOr(" foo & Bar&BAZ ")
assert.Equal(t, []string{" foo & Bar&BAZ "}, values)
})
}
func TestSplitAnd(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
values := SplitAnd("")
assert.Equal(t, []string{}, values)
})
t.Run("FooOrBar", func(t *testing.T) {
values := SplitAnd(" foo | Bar ")
assert.Equal(t, []string{" foo | Bar "}, values)
})
t.Run("FooAndBar", func(t *testing.T) {
values := SplitAnd(" foo & Bar ")
assert.Equal(t, []string{"foo", "Bar"}, values)
})
t.Run("FooAndBarAndBaz", func(t *testing.T) {
values := SplitAnd(" foo & Bar&BAZ ")
assert.Equal(t, []string{"foo", "Bar", "BAZ"}, values)
})
}

View file

@ -104,7 +104,7 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
}
if txt.NotEmpty(f.UID) {
s = s.Where("photos.photo_uid IN (?)", strings.Split(strings.ToLower(f.UID), txt.Or))
s = s.Where("photos.photo_uid IN (?)", SplitOr(strings.ToLower(f.UID)))
// Take shortcut?
if f.Album == "" && f.Query == "" {
@ -256,9 +256,9 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
// Filter for specific face clusters? Example: PLJ7A3G4MBGZJRMVDIUCBLC46IAP4N7O
if len(f.Face) >= 32 {
for _, f := range strings.Split(strings.ToUpper(f.Face), txt.And) {
for _, f := range SplitAnd(strings.ToUpper(f.Face)) {
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 face_id IN (?))",
entity.Marker{}.TableName()), strings.Split(f, txt.Or))
entity.Marker{}.TableName()), SplitOr(f))
}
} else if txt.New(f.Face) {
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 AND m.marker_type = ? WHERE subj_uid IS NULL OR subj_uid = '')",
@ -273,8 +273,8 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
// Filter for one or more subjects?
if txt.NotEmpty(f.Subject) {
for _, subj := range strings.Split(strings.ToLower(f.Subject), txt.And) {
if subjects := strings.Split(subj, txt.Or); rnd.ContainsUIDs(subjects, 'j') {
for _, subj := range SplitAnd(strings.ToLower(f.Subject)) {
if subjects := SplitOr(subj); rnd.ContainsUIDs(subjects, 'j') {
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 {
@ -345,7 +345,7 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
// Filter by main color?
if f.Color != "" {
s = s.Where("files.file_main_color IN (?)", strings.Split(strings.ToLower(f.Color), txt.Or))
s = s.Where("files.file_main_color IN (?)", SplitOr(strings.ToLower(f.Color)))
}
// Find favorites only?
@ -376,23 +376,23 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
// Filter by location country?
if txt.NotEmpty(f.Country) {
s = s.Where("photos.photo_country IN (?)", strings.Split(strings.ToLower(f.Country), txt.Or))
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(f.Country)))
}
// Filter by location state?
if txt.NotEmpty(f.State) {
s = s.Where("places.place_state IN (?)", strings.Split(f.State, txt.Or))
s = s.Where("places.place_state IN (?)", SplitOr(f.State))
}
// Filter by location category?
if txt.NotEmpty(f.Category) {
s = s.Joins("JOIN cells ON photos.cell_id = cells.id").
Where("cells.cell_category IN (?)", strings.Split(strings.ToLower(f.Category), txt.Or))
Where("cells.cell_category IN (?)", SplitOr(strings.ToLower(f.Category)))
}
// Filter by media type?
if txt.NotEmpty(f.Type) {
s = s.Where("photos.photo_type IN (?)", strings.Split(strings.ToLower(f.Type), txt.Or))
s = s.Where("photos.photo_type IN (?)", SplitOr(strings.ToLower(f.Type)))
} else if f.Video {
s = s.Where("photos.photo_type = 'video'")
} else if f.Photo {
@ -451,7 +451,7 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
// Filter by file hash?
if txt.NotEmpty(f.Hash) {
s = s.Where("files.file_hash IN (?)", strings.Split(strings.ToLower(f.Hash), txt.Or))
s = s.Where("files.file_hash IN (?)", SplitOr(strings.ToLower(f.Hash)))
}
if f.Mono {

View file

@ -143,7 +143,7 @@ func TestPhotosFilterCategory(t *testing.T) {
t.Run("CenterAsterisk", func(t *testing.T) {
var f form.SearchPhotos
f.Category = "My*Kids"
f.Category = "My*Camping"
f.Merged = true
photos, _, err := Photos(f)
@ -156,7 +156,7 @@ func TestPhotosFilterCategory(t *testing.T) {
t.Run("EndsWithAsterisk", func(t *testing.T) {
var f form.SearchPhotos
f.Category = "Yoga***"
f.Category = "Camping***"
f.Merged = true
photos, _, err := Photos(f)
@ -169,7 +169,7 @@ func TestPhotosFilterCategory(t *testing.T) {
t.Run("StartsWithPipe", func(t *testing.T) {
var f form.SearchPhotos
f.Category = "|Banana"
f.Category = "|Camping"
f.Merged = true
photos, _, err := Photos(f)
@ -183,7 +183,7 @@ func TestPhotosFilterCategory(t *testing.T) {
t.Run("CenterPipe", func(t *testing.T) {
var f form.SearchPhotos
f.Category = "Red|Green"
f.Category = "Camping|botanical garden"
f.Merged = true
photos, _, err := Photos(f)
@ -192,12 +192,14 @@ func TestPhotosFilterCategory(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, len(photos), 0)
assert.GreaterOrEqual(t, len(photos), 30)
})
t.Run("EndsWithPipe", func(t *testing.T) {
var f form.SearchPhotos
f.Category = "Blue|"
// Db().LogMode(true)
f.Category = "Camping|"
f.Merged = true
photos, _, err := Photos(f)

View file

@ -48,8 +48,7 @@ func TestPhotosFilterCountry(t *testing.T) {
}
assert.GreaterOrEqual(t, len(photos), 13)
})
//TODO
/*t.Run("mx whitespace pipe whitespace de", func(t *testing.T) {
t.Run("mx whitespace pipe whitespace de", func(t *testing.T) {
var f form.SearchPhotos
f.Country = "mx | de"
@ -62,32 +61,6 @@ func TestPhotosFilterCountry(t *testing.T) {
}
assert.GreaterOrEqual(t, len(photos), 13)
})
t.Run("mx or de", func(t *testing.T) {
var f form.SearchPhotos
f.Country = "mx or de"
f.Merged = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.GreaterOrEqual(t, len(photos), 13)
})
t.Run("mx OR de", func(t *testing.T) {
var f form.SearchPhotos
f.Country = "mx OR de"
f.Merged = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.GreaterOrEqual(t, len(photos), 13)
})*/
t.Run("StartsWithPercent", func(t *testing.T) {
var f form.SearchPhotos
@ -284,7 +257,7 @@ func TestPhotosFilterCountry(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, len(photos), 0)
assert.Equal(t, 0, len(photos))
})
t.Run("StartsWithNumber", func(t *testing.T) {
var f form.SearchPhotos
@ -367,8 +340,7 @@ func TestPhotosQueryCountry(t *testing.T) {
}
assert.GreaterOrEqual(t, len(photos), 13)
})
//TODO
/*t.Run("mx whitespace pipe whitespace de", func(t *testing.T) {
t.Run("mx whitespace pipe whitespace de", func(t *testing.T) {
var f form.SearchPhotos
f.Query = "country:\"mx | de\""
@ -393,7 +365,7 @@ func TestPhotosQueryCountry(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.GreaterOrEqual(t, len(photos), 13)
assert.Equal(t, 0, len(photos))
})
t.Run("mx OR de", func(t *testing.T) {
var f form.SearchPhotos
@ -406,8 +378,8 @@ func TestPhotosQueryCountry(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.GreaterOrEqual(t, len(photos), 13)
})*/
assert.Equal(t, 0, len(photos))
})
t.Run("StartsWithPercent", func(t *testing.T) {
var f form.SearchPhotos

View file

@ -138,7 +138,7 @@ func TestPhotosFilterState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, len(photos), 0)
assert.Equal(t, 0, len(photos))
})
t.Run("CenterAmpersand", func(t *testing.T) {
var f form.SearchPhotos
@ -248,7 +248,7 @@ func TestPhotosFilterState(t *testing.T) {
t.Run("StartsWithPipe", func(t *testing.T) {
var f form.SearchPhotos
f.State = "|Banana"
f.State = "|New york"
f.Merged = true
photos, _, err := Photos(f)
@ -256,11 +256,11 @@ func TestPhotosFilterState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Log(photos[0].PlaceLabel)
t.Log(photos[1].PlaceLabel)
// t.Log(photos[0].PlaceLabel)
// t.Log(photos[1].PlaceLabel)
//TODO shows photos where places set but state empty
assert.Equal(t, len(photos), 2)
assert.Equal(t, 1, len(photos))
})
t.Run("CenterPipe", func(t *testing.T) {
var f form.SearchPhotos
@ -274,12 +274,12 @@ func TestPhotosFilterState(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, len(photos), 0)
assert.Equal(t, 0, len(photos))
})
t.Run("EndsWithPipe", func(t *testing.T) {
var f form.SearchPhotos
f.State = "Blue|"
f.State = "Rheinland-Pfalz|"
f.Merged = true
photos, _, err := Photos(f)
@ -287,10 +287,10 @@ func TestPhotosFilterState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Log(photos[0].PlaceLabel)
t.Log(photos[1].PlaceLabel)
// t.Log(photos[0].PlaceLabel)
// t.Log(photos[1].PlaceLabel)
//TODO shows photos where places set but state empty
assert.Equal(t, len(photos), 2)
assert.Equal(t, 4, len(photos))
})
t.Run("StartsWithNumber", func(t *testing.T) {
var f form.SearchPhotos
@ -303,7 +303,7 @@ func TestPhotosFilterState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, len(photos), 0)
assert.Equal(t, 0, len(photos))
})
t.Run("CenterNumber", func(t *testing.T) {
var f form.SearchPhotos