From 1c0042c4c22ae31341dafba9e5e56f7d0a29dffa Mon Sep 17 00:00:00 2001 From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:48:13 +0200 Subject: [PATCH] Fix path filters (#3041) * Fix path filters * Replace getPathSearchClause * Remove incorrect tests --- pkg/sqlite/file.go | 3 +- pkg/sqlite/filter.go | 48 ++++------------------- pkg/sqlite/gallery.go | 25 ++++++------ pkg/sqlite/image.go | 3 +- pkg/sqlite/scene.go | 3 +- pkg/sqlite/scene_test.go | 54 -------------------------- ui/v2.5/src/docs/en/Changelog/v0180.md | 3 ++ 7 files changed, 31 insertions(+), 108 deletions(-) diff --git a/pkg/sqlite/file.go b/pkg/sqlite/file.go index c79002b11..0cd21ee4b 100644 --- a/pkg/sqlite/file.go +++ b/pkg/sqlite/file.go @@ -796,7 +796,8 @@ func (qb *FileStore) Query(ctx context.Context, options models.FileQueryOptions) distinctIDs(&query, fileTable) 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) } diff --git a/pkg/sqlite/filter.go b/pkg/sqlite/filter.go index e6211a91e..d3bc32144 100644 --- a/pkg/sqlite/filter.go +++ b/pkg/sqlite/filter.go @@ -454,17 +454,19 @@ func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, bas f.setError(err) 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: if _, err := regexp.Compile(c.Value); err != nil { f.setError(err) 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: - 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: - 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: 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 { - // 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 { p = "%" + p + "%" - basename += "%" - dir = "%" + dir } - var ret sqlClause - - 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) - } + filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn) + ret := makeClause(fmt.Sprintf("%s LIKE ?", filepathColumn), p) if not { ret = ret.not() diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index f2fbea9f5..56101819c 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -719,7 +719,8 @@ func (qb *GalleryStore) makeQuery(ctx context.Context, galleryFilter *models.Gal ) // 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) } @@ -785,12 +786,12 @@ func (qb *GalleryStore) galleryPathCriterionHandler(c *models.StringCriterionInp if modifier := c.Modifier; c.Modifier.IsValid() { switch modifier { 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) f.whereClauses = append(f.whereClauses, orClauses(clause, clause2)) case models.CriterionModifierExcludes: 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) f.whereClauses = append(f.whereClauses, orClauses(clause, clause2)) case models.CriterionModifierEquals: @@ -809,22 +810,24 @@ func (qb *GalleryStore) galleryPathCriterionHandler(c *models.StringCriterionInp f.setError(err) 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) - clause2 := makeClause(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", folderPathColumn), c.Value) + filepathColumn := fmt.Sprintf("%s || '%s' || %s", pathColumn, string(filepath.Separator), basenameColumn) + 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)) case models.CriterionModifierNotMatchesRegex: if _, err := regexp.Compile(c.Value); err != nil { f.setError(err) 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) - f.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", folderPathColumn), 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) + f.addWhere(fmt.Sprintf("%s IS NULL OR %[1]s NOT regexp ?", folderPathColumn), c.Value) 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.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) = '' OR %s IS NULL OR TRIM(%[2]s) = ''", pathColumn, basenameColumn)) + f.addWhere(fmt.Sprintf("%s IS NULL OR TRIM(%[1]s) = ''", folderPathColumn)) 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)) - clause2 := makeClause(fmt.Sprintf("(%s IS NOT NULL AND TRIM(%[1]s) != '')", folderPathColumn)) + 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)) f.whereClauses = append(f.whereClauses, orClauses(clause, clause2)) default: panic("unsupported string filter modifier") diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 3224ea66d..e9db3589c 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -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) } diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index a61f0cbb4..8a1807c12 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -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) } diff --git a/pkg/sqlite/scene_test.go b/pkg/sqlite/scene_test.go index 2e4dda8b3..f5f4ac0c3 100644 --- a/pkg/sqlite/scene_test.go +++ b/pkg/sqlite/scene_test.go @@ -2097,42 +2097,6 @@ func TestSceneQueryPath(t *testing.T) { []int{sceneIdx}, []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", models.StringCriterionInput{ @@ -2151,24 +2115,6 @@ func TestSceneQueryPath(t *testing.T) { []int{otherSceneIdx}, []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", models.StringCriterionInput{ diff --git a/ui/v2.5/src/docs/en/Changelog/v0180.md b/ui/v2.5/src/docs/en/Changelog/v0180.md index bea6f15f3..de1d97e9f 100644 --- a/ui/v2.5/src/docs/en/Changelog/v0180.md +++ b/ui/v2.5/src/docs/en/Changelog/v0180.md @@ -1,2 +1,5 @@ ### ✨ New Features * 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)) \ No newline at end of file