Handle NULL in regex criteria (#1208)

This commit is contained in:
WithoutPants
2021-03-16 11:13:14 +11:00
committed by GitHub
parent f7cd9cb00d
commit 7e6127975d
7 changed files with 73 additions and 6 deletions

View File

@@ -311,13 +311,13 @@ func stringCriterionHandler(c *models.StringCriterionInput, column string) crite
f.setError(err) f.setError(err)
return return
} }
f.addWhere(column+" regexp ?", c.Value) f.addWhere(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", column), 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(column+" NOT regexp ?", c.Value) f.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", column), c.Value)
default: default:
clause, count := getSimpleCriterionClause(modifier, "?") clause, count := getSimpleCriterionClause(modifier, "?")

View File

@@ -530,7 +530,7 @@ func TestStringCriterionHandlerMatchesRegex(t *testing.T) {
}, column)) }, column))
assert.Len(f.whereClauses, 1) assert.Len(f.whereClauses, 1)
assert.Equal(fmt.Sprintf("%[1]s regexp ?", column), f.whereClauses[0].sql) assert.Equal(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", column), f.whereClauses[0].sql)
assert.Len(f.whereClauses[0].args, 1) assert.Len(f.whereClauses[0].args, 1)
assert.Equal(validValue, f.whereClauses[0].args[0]) assert.Equal(validValue, f.whereClauses[0].args[0])
@@ -558,7 +558,7 @@ func TestStringCriterionHandlerNotMatchesRegex(t *testing.T) {
}, column)) }, column))
assert.Len(f.whereClauses, 1) assert.Len(f.whereClauses, 1)
assert.Equal(fmt.Sprintf("%[1]s NOT regexp ?", column), f.whereClauses[0].sql) assert.Equal(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", column), f.whereClauses[0].sql)
assert.Len(f.whereClauses[0].args, 1) assert.Len(f.whereClauses[0].args, 1)
assert.Equal(validValue, f.whereClauses[0].args[0]) assert.Equal(validValue, f.whereClauses[0].args[0])

View File

@@ -228,6 +228,46 @@ func verifyPerformerAge(t *testing.T, ageCriterion models.IntCriterionInput) {
}) })
} }
func TestPerformerQueryCareerLength(t *testing.T) {
const value = "2005"
careerLengthCriterion := models.StringCriterionInput{
Value: value,
Modifier: models.CriterionModifierEquals,
}
verifyPerformerCareerLength(t, careerLengthCriterion)
careerLengthCriterion.Modifier = models.CriterionModifierNotEquals
verifyPerformerCareerLength(t, careerLengthCriterion)
careerLengthCriterion.Modifier = models.CriterionModifierMatchesRegex
verifyPerformerCareerLength(t, careerLengthCriterion)
careerLengthCriterion.Modifier = models.CriterionModifierNotMatchesRegex
verifyPerformerCareerLength(t, careerLengthCriterion)
}
func verifyPerformerCareerLength(t *testing.T, criterion models.StringCriterionInput) {
withTxn(func(r models.Repository) error {
qb := r.Performer()
performerFilter := models.PerformerFilterType{
CareerLength: &criterion,
}
performers, _, err := qb.Query(&performerFilter, nil)
if err != nil {
t.Errorf("Error querying performer: %s", err.Error())
}
for _, performer := range performers {
cl := performer.CareerLength
verifyNullString(t, cl, criterion)
}
return nil
})
}
func queryPerformers(t *testing.T, qb models.PerformerReader, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) []*models.Performer { func queryPerformers(t *testing.T, qb models.PerformerReader, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) []*models.Performer {
performers, _, err := qb.Query(performerFilter, findFilter) performers, _, err := qb.Query(performerFilter, findFilter)
if err != nil { if err != nil {

View File

@@ -1,6 +1,7 @@
package sqlite package sqlite
import ( import (
"fmt"
"regexp" "regexp"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
@@ -127,14 +128,14 @@ func (qb *queryBuilder) handleStringCriterionInput(c *models.StringCriterionInpu
qb.err = err qb.err = err
return return
} }
qb.addWhere(column + " regexp ?") qb.addWhere(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", column))
qb.addArg(c.Value) qb.addArg(c.Value)
case models.CriterionModifierNotMatchesRegex: case models.CriterionModifierNotMatchesRegex:
if _, err := regexp.Compile(c.Value); err != nil { if _, err := regexp.Compile(c.Value); err != nil {
qb.err = err qb.err = err
return return
} }
qb.addWhere(column + " NOT regexp ?") qb.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", column))
qb.addArg(c.Value) qb.addArg(c.Value)
case models.CriterionModifierIsNull: case models.CriterionModifierIsNull:
qb.addWhere("(" + column + " IS NULL OR TRIM(" + column + ") = '')") qb.addWhere("(" + column + " IS NULL OR TRIM(" + column + ") = '')")

View File

@@ -356,6 +356,17 @@ func verifyNullString(t *testing.T, value sql.NullString, criterion models.Strin
if criterion.Modifier == models.CriterionModifierNotEquals { if criterion.Modifier == models.CriterionModifierNotEquals {
assert.NotEqual(criterion.Value, value.String) assert.NotEqual(criterion.Value, value.String)
} }
if criterion.Modifier == models.CriterionModifierMatchesRegex {
assert.True(value.Valid)
assert.Regexp(regexp.MustCompile(criterion.Value), value)
}
if criterion.Modifier == models.CriterionModifierNotMatchesRegex {
if !value.Valid {
// correct
return
}
assert.NotRegexp(regexp.MustCompile(criterion.Value), value)
}
} }
func verifyString(t *testing.T, value string, criterion models.StringCriterionInput) { func verifyString(t *testing.T, value string, criterion models.StringCriterionInput) {

View File

@@ -587,6 +587,15 @@ func getPerformerBirthdate(index int) string {
return birthdate.Format("2006-01-02") return birthdate.Format("2006-01-02")
} }
func getPerformerCareerLength(index int) *string {
if index%5 == 0 {
return nil
}
ret := fmt.Sprintf("20%2d", index)
return &ret
}
//createPerformers creates n performers with plain Name and o performers with camel cased NaMe included //createPerformers creates n performers with plain Name and o performers with camel cased NaMe included
func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error { func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error {
const namePlain = "Name" const namePlain = "Name"
@@ -613,6 +622,11 @@ func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error {
}, },
} }
careerLength := getPerformerCareerLength(i)
if careerLength != nil {
performer.CareerLength = models.NullString(*careerLength)
}
created, err := pqb.Create(performer) created, err := pqb.Create(performer)
if err != nil { if err != nil {

View File

@@ -20,6 +20,7 @@
* Added Rescan button to scene, image, gallery details overflow button. * Added Rescan button to scene, image, gallery details overflow button.
### 🐛 Bug fixes ### 🐛 Bug fixes
* Fix SQL error when filtering nullable string fields with regex.
* Fix incorrect folders being excluded during scanning. * Fix incorrect folders being excluded during scanning.
* Filter out streaming resolution options that are over the maximum streaming resolution. * Filter out streaming resolution options that are over the maximum streaming resolution.
* Fix `cover.jpg` not being detected as cover image when in sub-directory. * Fix `cover.jpg` not being detected as cover image when in sub-directory.