mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Fix path filters (#3041)
* Fix path filters * Replace getPathSearchClause * Remove incorrect tests
This commit is contained in:
@@ -796,7 +796,8 @@ func (qb *FileStore) Query(ctx context.Context, options models.FileQueryOptions)
|
|||||||
distinctIDs(&query, fileTable)
|
distinctIDs(&query, fileTable)
|
||||||
|
|
||||||
if q := findFilter.Q; q != nil && *q != "" {
|
if q := findFilter.Q; q != nil && *q != "" {
|
||||||
searchColumns := []string{"folders.path", "files.basename"}
|
filepathColumn := "folders.path || '" + string(filepath.Separator) + "' || files.basename"
|
||||||
|
searchColumns := []string{filepathColumn}
|
||||||
query.parseQueryString(searchColumns, *q)
|
query.parseQueryString(searchColumns, *q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -454,17 +454,19 @@ func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, bas
|
|||||||
f.setError(err)
|
f.setError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.addWhere(fmt.Sprintf("%s IS NOT NULL AND %s IS NOT NULL AND %[1]s || '%[3]s' || %[2]s regexp ?", pathColumn, basenameColumn, string(filepath.Separator)), c.Value)
|
filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn)
|
||||||
|
f.addWhere(fmt.Sprintf("%s IS NOT NULL AND %s IS NOT NULL AND %s regexp ?", pathColumn, basenameColumn, filepathColumn), c.Value)
|
||||||
case models.CriterionModifierNotMatchesRegex:
|
case models.CriterionModifierNotMatchesRegex:
|
||||||
if _, err := regexp.Compile(c.Value); err != nil {
|
if _, err := regexp.Compile(c.Value); err != nil {
|
||||||
f.setError(err)
|
f.setError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.addWhere(fmt.Sprintf("%s IS NULL OR %s IS NULL OR %[1]s || '%[3]s' || %[2]s NOT regexp ?", pathColumn, basenameColumn, string(filepath.Separator)), c.Value)
|
filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn)
|
||||||
|
f.addWhere(fmt.Sprintf("%s IS NULL OR %s IS NULL OR %s NOT regexp ?", pathColumn, basenameColumn, filepathColumn), c.Value)
|
||||||
case models.CriterionModifierIsNull:
|
case models.CriterionModifierIsNull:
|
||||||
f.addWhere(fmt.Sprintf("(%s IS NULL OR TRIM(%[1]s) = '' OR %s IS NULL OR TRIM(%[2]s) = '')", pathColumn, basenameColumn))
|
f.addWhere(fmt.Sprintf("%s IS NULL OR TRIM(%[1]s) = '' OR %s IS NULL OR TRIM(%[2]s) = ''", pathColumn, basenameColumn))
|
||||||
case models.CriterionModifierNotNull:
|
case models.CriterionModifierNotNull:
|
||||||
f.addWhere(fmt.Sprintf("(%s IS NOT NULL AND TRIM(%[1]s) != '' AND %s IS NOT NULL AND TRIM(%[2]s) != '')", pathColumn, basenameColumn))
|
f.addWhere(fmt.Sprintf("%s IS NOT NULL AND TRIM(%[1]s) != '' AND %s IS NOT NULL AND TRIM(%[2]s) != ''", pathColumn, basenameColumn))
|
||||||
default:
|
default:
|
||||||
panic("unsupported string filter modifier")
|
panic("unsupported string filter modifier")
|
||||||
}
|
}
|
||||||
@@ -474,46 +476,12 @@ func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, bas
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPathSearchClause(pathColumn, basenameColumn, p string, addWildcards, not bool) sqlClause {
|
func getPathSearchClause(pathColumn, basenameColumn, p string, addWildcards, not bool) sqlClause {
|
||||||
// if path value has slashes, then we're potentially searching directory only or
|
|
||||||
// directory plus basename
|
|
||||||
hasSlashes := strings.Contains(p, string(filepath.Separator))
|
|
||||||
trailingSlash := hasSlashes && p[len(p)-1] == filepath.Separator
|
|
||||||
const emptyDir = string(filepath.Separator)
|
|
||||||
|
|
||||||
// possible values:
|
|
||||||
// dir/basename
|
|
||||||
// dir1/subdir
|
|
||||||
// dir/
|
|
||||||
// /basename
|
|
||||||
// dirOrBasename
|
|
||||||
|
|
||||||
basename := filepath.Base(p)
|
|
||||||
dir := filepath.Dir(p)
|
|
||||||
|
|
||||||
if addWildcards {
|
if addWildcards {
|
||||||
p = "%" + p + "%"
|
p = "%" + p + "%"
|
||||||
basename += "%"
|
|
||||||
dir = "%" + dir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret sqlClause
|
filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn)
|
||||||
|
ret := makeClause(fmt.Sprintf("%s LIKE ?", filepathColumn), p)
|
||||||
switch {
|
|
||||||
case !hasSlashes:
|
|
||||||
// dir or basename
|
|
||||||
ret = makeClause(fmt.Sprintf("%s LIKE ? OR %s LIKE ?", pathColumn, basenameColumn), p, p)
|
|
||||||
case dir != emptyDir && !trailingSlash:
|
|
||||||
// (path like %dir AND basename like basename%) OR path like %p%
|
|
||||||
c1 := makeClause(fmt.Sprintf("%s LIKE ? AND %s LIKE ?", pathColumn, basenameColumn), dir, basename)
|
|
||||||
c2 := makeClause(fmt.Sprintf("%s LIKE ?", pathColumn), p)
|
|
||||||
ret = orClauses(c1, c2)
|
|
||||||
case dir == emptyDir && !trailingSlash:
|
|
||||||
// path like %p% OR basename like basename%
|
|
||||||
ret = makeClause(fmt.Sprintf("%s LIKE ? OR %s LIKE ?", pathColumn, basenameColumn), p, basename)
|
|
||||||
case dir != emptyDir && trailingSlash:
|
|
||||||
// path like %p% OR path like %dir
|
|
||||||
ret = makeClause(fmt.Sprintf("%s LIKE ? OR %[1]s LIKE ?", pathColumn), p, dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if not {
|
if not {
|
||||||
ret = ret.not()
|
ret = ret.not()
|
||||||
|
|||||||
@@ -719,7 +719,8 @@ func (qb *GalleryStore) makeQuery(ctx context.Context, galleryFilter *models.Gal
|
|||||||
)
|
)
|
||||||
|
|
||||||
// add joins for files and checksum
|
// add joins for files and checksum
|
||||||
searchColumns := []string{"galleries.title", "gallery_folder.path", "folders.path", "files.basename", "files_fingerprints.fingerprint"}
|
filepathColumn := "folders.path || '" + string(filepath.Separator) + "' || files.basename"
|
||||||
|
searchColumns := []string{"galleries.title", "gallery_folder.path", filepathColumn, "files_fingerprints.fingerprint"}
|
||||||
query.parseQueryString(searchColumns, *q)
|
query.parseQueryString(searchColumns, *q)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,12 +786,12 @@ func (qb *GalleryStore) galleryPathCriterionHandler(c *models.StringCriterionInp
|
|||||||
if modifier := c.Modifier; c.Modifier.IsValid() {
|
if modifier := c.Modifier; c.Modifier.IsValid() {
|
||||||
switch modifier {
|
switch modifier {
|
||||||
case models.CriterionModifierIncludes:
|
case models.CriterionModifierIncludes:
|
||||||
clause := getPathSearchClause(pathColumn, basenameColumn, c.Value, addWildcards, not)
|
clause := getPathSearchClauseMany(pathColumn, basenameColumn, c.Value, addWildcards, not)
|
||||||
clause2 := getStringSearchClause([]string{folderPathColumn}, c.Value, false)
|
clause2 := getStringSearchClause([]string{folderPathColumn}, c.Value, false)
|
||||||
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
||||||
case models.CriterionModifierExcludes:
|
case models.CriterionModifierExcludes:
|
||||||
not = true
|
not = true
|
||||||
clause := getPathSearchClause(pathColumn, basenameColumn, c.Value, addWildcards, not)
|
clause := getPathSearchClauseMany(pathColumn, basenameColumn, c.Value, addWildcards, not)
|
||||||
clause2 := getStringSearchClause([]string{folderPathColumn}, c.Value, true)
|
clause2 := getStringSearchClause([]string{folderPathColumn}, c.Value, true)
|
||||||
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
||||||
case models.CriterionModifierEquals:
|
case models.CriterionModifierEquals:
|
||||||
@@ -809,22 +810,24 @@ func (qb *GalleryStore) galleryPathCriterionHandler(c *models.StringCriterionInp
|
|||||||
f.setError(err)
|
f.setError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clause := makeClause(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?) OR (%s IS NOT NULL AND %[2]s regexp ?)", pathColumn, basenameColumn), c.Value, c.Value)
|
filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn)
|
||||||
clause2 := makeClause(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", folderPathColumn), c.Value)
|
clause := makeClause(fmt.Sprintf("%s IS NOT NULL AND %s IS NOT NULL AND %s regexp ?", pathColumn, basenameColumn, filepathColumn), c.Value)
|
||||||
|
clause2 := makeClause(fmt.Sprintf("%s IS NOT NULL AND %[1]s regexp ?", folderPathColumn), c.Value)
|
||||||
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
||||||
case models.CriterionModifierNotMatchesRegex:
|
case models.CriterionModifierNotMatchesRegex:
|
||||||
if _, err := regexp.Compile(c.Value); err != nil {
|
if _, err := regexp.Compile(c.Value); err != nil {
|
||||||
f.setError(err)
|
f.setError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?) AND (%s IS NULL OR %[2]s NOT regexp ?)", pathColumn, basenameColumn), c.Value, c.Value)
|
filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn)
|
||||||
f.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", folderPathColumn), c.Value)
|
f.addWhere(fmt.Sprintf("%s IS NULL OR %s IS NULL OR %s NOT regexp ?", pathColumn, basenameColumn, filepathColumn), c.Value)
|
||||||
|
f.addWhere(fmt.Sprintf("%s IS NULL OR %[1]s NOT regexp ?", folderPathColumn), c.Value)
|
||||||
case models.CriterionModifierIsNull:
|
case models.CriterionModifierIsNull:
|
||||||
f.whereClauses = append(f.whereClauses, makeClause(fmt.Sprintf("(%s IS NULL OR TRIM(%[1]s) = '' OR %s IS NULL OR TRIM(%[2]s) = '')", pathColumn, basenameColumn)))
|
f.addWhere(fmt.Sprintf("%s IS NULL OR TRIM(%[1]s) = '' OR %s IS NULL OR TRIM(%[2]s) = ''", pathColumn, basenameColumn))
|
||||||
f.whereClauses = append(f.whereClauses, makeClause(fmt.Sprintf("(%s IS NULL OR TRIM(%[1]s) = '')", folderPathColumn)))
|
f.addWhere(fmt.Sprintf("%s IS NULL OR TRIM(%[1]s) = ''", folderPathColumn))
|
||||||
case models.CriterionModifierNotNull:
|
case models.CriterionModifierNotNull:
|
||||||
clause := makeClause(fmt.Sprintf("(%s IS NOT NULL AND TRIM(%[1]s) != '' AND %s IS NOT NULL AND TRIM(%[2]s) != '')", pathColumn, basenameColumn))
|
clause := makeClause(fmt.Sprintf("%s IS NOT NULL AND TRIM(%[1]s) != '' AND %s IS NOT NULL AND TRIM(%[2]s) != ''", pathColumn, basenameColumn))
|
||||||
clause2 := makeClause(fmt.Sprintf("(%s IS NOT NULL AND TRIM(%[1]s) != '')", folderPathColumn))
|
clause2 := makeClause(fmt.Sprintf("%s IS NOT NULL AND TRIM(%[1]s) != ''", folderPathColumn))
|
||||||
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
f.whereClauses = append(f.whereClauses, orClauses(clause, clause2))
|
||||||
default:
|
default:
|
||||||
panic("unsupported string filter modifier")
|
panic("unsupported string filter modifier")
|
||||||
|
|||||||
@@ -700,7 +700,8 @@ func (qb *ImageStore) makeQuery(ctx context.Context, imageFilter *models.ImageFi
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
searchColumns := []string{"images.title", "folders.path", "files.basename", "files_fingerprints.fingerprint"}
|
filepathColumn := "folders.path || '" + string(filepath.Separator) + "' || files.basename"
|
||||||
|
searchColumns := []string{"images.title", filepathColumn, "files_fingerprints.fingerprint"}
|
||||||
query.parseQueryString(searchColumns, *q)
|
query.parseQueryString(searchColumns, *q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -933,7 +933,8 @@ func (qb *SceneStore) Query(ctx context.Context, options models.SceneQueryOption
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
searchColumns := []string{"scenes.title", "scenes.details", "folders.path", "files.basename", "files_fingerprints.fingerprint", "scene_markers.title"}
|
filepathColumn := "folders.path || '" + string(filepath.Separator) + "' || files.basename"
|
||||||
|
searchColumns := []string{"scenes.title", "scenes.details", filepathColumn, "files_fingerprints.fingerprint", "scene_markers.title"}
|
||||||
query.parseQueryString(searchColumns, *q)
|
query.parseQueryString(searchColumns, *q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2097,42 +2097,6 @@ func TestSceneQueryPath(t *testing.T) {
|
|||||||
[]int{sceneIdx},
|
[]int{sceneIdx},
|
||||||
[]int{otherSceneIdx},
|
[]int{otherSceneIdx},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"equals folder name",
|
|
||||||
models.StringCriterionInput{
|
|
||||||
Value: folder,
|
|
||||||
Modifier: models.CriterionModifierEquals,
|
|
||||||
},
|
|
||||||
[]int{sceneIdx},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"equals folder name trailing slash",
|
|
||||||
models.StringCriterionInput{
|
|
||||||
Value: folder + string(filepath.Separator),
|
|
||||||
Modifier: models.CriterionModifierEquals,
|
|
||||||
},
|
|
||||||
[]int{sceneIdx},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"equals base name",
|
|
||||||
models.StringCriterionInput{
|
|
||||||
Value: basename,
|
|
||||||
Modifier: models.CriterionModifierEquals,
|
|
||||||
},
|
|
||||||
[]int{sceneIdx},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"equals base name leading slash",
|
|
||||||
models.StringCriterionInput{
|
|
||||||
Value: string(filepath.Separator) + basename,
|
|
||||||
Modifier: models.CriterionModifierEquals,
|
|
||||||
},
|
|
||||||
[]int{sceneIdx},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"equals full path wildcard",
|
"equals full path wildcard",
|
||||||
models.StringCriterionInput{
|
models.StringCriterionInput{
|
||||||
@@ -2151,24 +2115,6 @@ func TestSceneQueryPath(t *testing.T) {
|
|||||||
[]int{otherSceneIdx},
|
[]int{otherSceneIdx},
|
||||||
[]int{sceneIdx},
|
[]int{sceneIdx},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"not equals folder name",
|
|
||||||
models.StringCriterionInput{
|
|
||||||
Value: folder,
|
|
||||||
Modifier: models.CriterionModifierNotEquals,
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
[]int{sceneIdx},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"not equals basename",
|
|
||||||
models.StringCriterionInput{
|
|
||||||
Value: basename,
|
|
||||||
Modifier: models.CriterionModifierNotEquals,
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
[]int{sceneIdx},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"includes folder name",
|
"includes folder name",
|
||||||
models.StringCriterionInput{
|
models.StringCriterionInput{
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
* Added tag description filter criterion. ([#3011](https://github.com/stashapp/stash/pull/3011))
|
* Added tag description filter criterion. ([#3011](https://github.com/stashapp/stash/pull/3011))
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
* Fix path filter behaviour to be consistent with previous behaviour. ([#3041](https://github.com/stashapp/stash/pull/3041))
|
||||||
Reference in New Issue
Block a user