mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Add penis length and circumcision stats to performers. (#3627)
* Add penis length stat to performers. * Modified the UI to display and edit the stat. * Added the ability to filter floats to allow filtering by penis length. * Add circumcision stat to performer. * Refactor enum filtering * Change boolean filter to radio buttons * Return null for empty enum values --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -32,7 +32,7 @@ const (
|
||||
dbConnTimeout = 30
|
||||
)
|
||||
|
||||
var appSchemaVersion uint = 45
|
||||
var appSchemaVersion uint = 46
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsBox embed.FS
|
||||
|
||||
@@ -426,6 +426,29 @@ func stringCriterionHandler(c *models.StringCriterionInput, column string) crite
|
||||
}
|
||||
}
|
||||
|
||||
func enumCriterionHandler(modifier models.CriterionModifier, values []string, column string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if modifier.IsValid() {
|
||||
switch modifier {
|
||||
case models.CriterionModifierIncludes, models.CriterionModifierEquals:
|
||||
if len(values) > 0 {
|
||||
f.whereClauses = append(f.whereClauses, getEnumSearchClause(column, values, false))
|
||||
}
|
||||
case models.CriterionModifierExcludes, models.CriterionModifierNotEquals:
|
||||
if len(values) > 0 {
|
||||
f.whereClauses = append(f.whereClauses, getEnumSearchClause(column, values, true))
|
||||
}
|
||||
case models.CriterionModifierIsNull:
|
||||
f.addWhere("(" + column + " IS NULL OR TRIM(" + column + ") = '')")
|
||||
case models.CriterionModifierNotNull:
|
||||
f.addWhere("(" + column + " IS NOT NULL AND TRIM(" + column + ") != '')")
|
||||
default:
|
||||
panic("unsupported string filter modifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
@@ -525,6 +548,15 @@ func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn f
|
||||
}
|
||||
}
|
||||
|
||||
func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
clause, args := getFloatCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
|
||||
2
pkg/sqlite/migrations/46_penis_stats.up.sql
Normal file
2
pkg/sqlite/migrations/46_penis_stats.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `performers` ADD COLUMN `penis_length` float;
|
||||
ALTER TABLE `performers` ADD COLUMN `circumcised` varchar[10];
|
||||
@@ -42,6 +42,8 @@ type performerRow struct {
|
||||
Height null.Int `db:"height"`
|
||||
Measurements zero.String `db:"measurements"`
|
||||
FakeTits zero.String `db:"fake_tits"`
|
||||
PenisLength null.Float `db:"penis_length"`
|
||||
Circumcised zero.String `db:"circumcised"`
|
||||
CareerLength zero.String `db:"career_length"`
|
||||
Tattoos zero.String `db:"tattoos"`
|
||||
Piercings zero.String `db:"piercings"`
|
||||
@@ -64,7 +66,7 @@ func (r *performerRow) fromPerformer(o models.Performer) {
|
||||
r.ID = o.ID
|
||||
r.Name = o.Name
|
||||
r.Disambigation = zero.StringFrom(o.Disambiguation)
|
||||
if o.Gender.IsValid() {
|
||||
if o.Gender != nil && o.Gender.IsValid() {
|
||||
r.Gender = zero.StringFrom(o.Gender.String())
|
||||
}
|
||||
r.URL = zero.StringFrom(o.URL)
|
||||
@@ -79,6 +81,10 @@ func (r *performerRow) fromPerformer(o models.Performer) {
|
||||
r.Height = intFromPtr(o.Height)
|
||||
r.Measurements = zero.StringFrom(o.Measurements)
|
||||
r.FakeTits = zero.StringFrom(o.FakeTits)
|
||||
r.PenisLength = null.FloatFromPtr(o.PenisLength)
|
||||
if o.Circumcised != nil && o.Circumcised.IsValid() {
|
||||
r.Circumcised = zero.StringFrom(o.Circumcised.String())
|
||||
}
|
||||
r.CareerLength = zero.StringFrom(o.CareerLength)
|
||||
r.Tattoos = zero.StringFrom(o.Tattoos)
|
||||
r.Piercings = zero.StringFrom(o.Piercings)
|
||||
@@ -100,7 +106,6 @@ func (r *performerRow) resolve() *models.Performer {
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Disambiguation: r.Disambigation.String,
|
||||
Gender: models.GenderEnum(r.Gender.String),
|
||||
URL: r.URL.String,
|
||||
Twitter: r.Twitter.String,
|
||||
Instagram: r.Instagram.String,
|
||||
@@ -111,6 +116,7 @@ func (r *performerRow) resolve() *models.Performer {
|
||||
Height: nullIntPtr(r.Height),
|
||||
Measurements: r.Measurements.String,
|
||||
FakeTits: r.FakeTits.String,
|
||||
PenisLength: nullFloatPtr(r.PenisLength),
|
||||
CareerLength: r.CareerLength.String,
|
||||
Tattoos: r.Tattoos.String,
|
||||
Piercings: r.Piercings.String,
|
||||
@@ -126,6 +132,16 @@ func (r *performerRow) resolve() *models.Performer {
|
||||
IgnoreAutoTag: r.IgnoreAutoTag,
|
||||
}
|
||||
|
||||
if r.Gender.ValueOrZero() != "" {
|
||||
v := models.GenderEnum(r.Gender.String)
|
||||
ret.Gender = &v
|
||||
}
|
||||
|
||||
if r.Circumcised.ValueOrZero() != "" {
|
||||
v := models.CircumisedEnum(r.Circumcised.String)
|
||||
ret.Circumcised = &v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -147,6 +163,8 @@ func (r *performerRowRecord) fromPartial(o models.PerformerPartial) {
|
||||
r.setNullInt("height", o.Height)
|
||||
r.setNullString("measurements", o.Measurements)
|
||||
r.setNullString("fake_tits", o.FakeTits)
|
||||
r.setNullFloat64("penis_length", o.PenisLength)
|
||||
r.setNullString("circumcised", o.Circumcised)
|
||||
r.setNullString("career_length", o.CareerLength)
|
||||
r.setNullString("tattoos", o.Tattoos)
|
||||
r.setNullString("piercings", o.Piercings)
|
||||
@@ -597,6 +615,15 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
|
||||
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Measurements, tableName+".measurements"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.FakeTits, tableName+".fake_tits"))
|
||||
query.handleCriterion(ctx, floatCriterionHandler(filter.PenisLength, tableName+".penis_length", nil))
|
||||
|
||||
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if circumcised := filter.Circumcised; circumcised != nil {
|
||||
v := utils.StringerSliceToStringSlice(circumcised.Value)
|
||||
enumCriterionHandler(circumcised.Modifier, v, tableName+".circumcised")(ctx, f)
|
||||
}
|
||||
}))
|
||||
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Piercings, tableName+".piercings"))
|
||||
|
||||
@@ -52,6 +52,8 @@ func Test_PerformerStore_Create(t *testing.T) {
|
||||
height = 134
|
||||
measurements = "measurements"
|
||||
fakeTits = "fakeTits"
|
||||
penisLength = 1.23
|
||||
circumcised = models.CircumisedEnumCut
|
||||
careerLength = "careerLength"
|
||||
tattoos = "tattoos"
|
||||
piercings = "piercings"
|
||||
@@ -81,7 +83,7 @@ func Test_PerformerStore_Create(t *testing.T) {
|
||||
models.Performer{
|
||||
Name: name,
|
||||
Disambiguation: disambiguation,
|
||||
Gender: gender,
|
||||
Gender: &gender,
|
||||
URL: url,
|
||||
Twitter: twitter,
|
||||
Instagram: instagram,
|
||||
@@ -92,6 +94,8 @@ func Test_PerformerStore_Create(t *testing.T) {
|
||||
Height: &height,
|
||||
Measurements: measurements,
|
||||
FakeTits: fakeTits,
|
||||
PenisLength: &penisLength,
|
||||
Circumcised: &circumcised,
|
||||
CareerLength: careerLength,
|
||||
Tattoos: tattoos,
|
||||
Piercings: piercings,
|
||||
@@ -196,6 +200,8 @@ func Test_PerformerStore_Update(t *testing.T) {
|
||||
height = 134
|
||||
measurements = "measurements"
|
||||
fakeTits = "fakeTits"
|
||||
penisLength = 1.23
|
||||
circumcised = models.CircumisedEnumCut
|
||||
careerLength = "careerLength"
|
||||
tattoos = "tattoos"
|
||||
piercings = "piercings"
|
||||
@@ -226,7 +232,7 @@ func Test_PerformerStore_Update(t *testing.T) {
|
||||
ID: performerIDs[performerIdxWithGallery],
|
||||
Name: name,
|
||||
Disambiguation: disambiguation,
|
||||
Gender: gender,
|
||||
Gender: &gender,
|
||||
URL: url,
|
||||
Twitter: twitter,
|
||||
Instagram: instagram,
|
||||
@@ -237,6 +243,8 @@ func Test_PerformerStore_Update(t *testing.T) {
|
||||
Height: &height,
|
||||
Measurements: measurements,
|
||||
FakeTits: fakeTits,
|
||||
PenisLength: &penisLength,
|
||||
Circumcised: &circumcised,
|
||||
CareerLength: careerLength,
|
||||
Tattoos: tattoos,
|
||||
Piercings: piercings,
|
||||
@@ -327,6 +335,7 @@ func clearPerformerPartial() models.PerformerPartial {
|
||||
nullString := models.OptionalString{Set: true, Null: true}
|
||||
nullDate := models.OptionalDate{Set: true, Null: true}
|
||||
nullInt := models.OptionalInt{Set: true, Null: true}
|
||||
nullFloat := models.OptionalFloat64{Set: true, Null: true}
|
||||
|
||||
// leave mandatory fields
|
||||
return models.PerformerPartial{
|
||||
@@ -342,6 +351,8 @@ func clearPerformerPartial() models.PerformerPartial {
|
||||
Height: nullInt,
|
||||
Measurements: nullString,
|
||||
FakeTits: nullString,
|
||||
PenisLength: nullFloat,
|
||||
Circumcised: nullString,
|
||||
CareerLength: nullString,
|
||||
Tattoos: nullString,
|
||||
Piercings: nullString,
|
||||
@@ -372,6 +383,8 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
||||
height = 143
|
||||
measurements = "measurements"
|
||||
fakeTits = "fakeTits"
|
||||
penisLength = 1.23
|
||||
circumcised = models.CircumisedEnumCut
|
||||
careerLength = "careerLength"
|
||||
tattoos = "tattoos"
|
||||
piercings = "piercings"
|
||||
@@ -415,6 +428,8 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
||||
Height: models.NewOptionalInt(height),
|
||||
Measurements: models.NewOptionalString(measurements),
|
||||
FakeTits: models.NewOptionalString(fakeTits),
|
||||
PenisLength: models.NewOptionalFloat64(penisLength),
|
||||
Circumcised: models.NewOptionalString(circumcised.String()),
|
||||
CareerLength: models.NewOptionalString(careerLength),
|
||||
Tattoos: models.NewOptionalString(tattoos),
|
||||
Piercings: models.NewOptionalString(piercings),
|
||||
@@ -453,7 +468,7 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
||||
ID: performerIDs[performerIdxWithDupName],
|
||||
Name: name,
|
||||
Disambiguation: disambiguation,
|
||||
Gender: gender,
|
||||
Gender: &gender,
|
||||
URL: url,
|
||||
Twitter: twitter,
|
||||
Instagram: instagram,
|
||||
@@ -464,6 +479,8 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
||||
Height: &height,
|
||||
Measurements: measurements,
|
||||
FakeTits: fakeTits,
|
||||
PenisLength: &penisLength,
|
||||
Circumcised: &circumcised,
|
||||
CareerLength: careerLength,
|
||||
Tattoos: tattoos,
|
||||
Piercings: piercings,
|
||||
@@ -957,16 +974,30 @@ func TestPerformerQuery(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
{
|
||||
"alias",
|
||||
"circumcised (cut)",
|
||||
nil,
|
||||
&models.PerformerFilterType{
|
||||
Aliases: &models.StringCriterionInput{
|
||||
Value: getPerformerStringValue(performerIdxWithGallery, "alias"),
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Circumcised: &models.CircumcisionCriterionInput{
|
||||
Value: []models.CircumisedEnum{models.CircumisedEnumCut},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
[]int{performerIdxWithGallery},
|
||||
[]int{performerIdxWithScene},
|
||||
[]int{performerIdx1WithScene},
|
||||
[]int{performerIdxWithScene, performerIdx2WithScene},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"circumcised (excludes cut)",
|
||||
nil,
|
||||
&models.PerformerFilterType{
|
||||
Circumcised: &models.CircumcisionCriterionInput{
|
||||
Value: []models.CircumisedEnum{models.CircumisedEnumCut},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
},
|
||||
[]int{performerIdx2WithScene},
|
||||
// performerIdxWithScene has null value
|
||||
[]int{performerIdx1WithScene, performerIdxWithScene},
|
||||
false,
|
||||
},
|
||||
}
|
||||
@@ -995,6 +1026,107 @@ func TestPerformerQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerformerQueryPenisLength(t *testing.T) {
|
||||
var upper = 4.0
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
modifier models.CriterionModifier
|
||||
value float64
|
||||
value2 *float64
|
||||
}{
|
||||
{
|
||||
"equals",
|
||||
models.CriterionModifierEquals,
|
||||
1,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.CriterionModifierNotEquals,
|
||||
1,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"greater than",
|
||||
models.CriterionModifierGreaterThan,
|
||||
1,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"between",
|
||||
models.CriterionModifierBetween,
|
||||
2,
|
||||
&upper,
|
||||
},
|
||||
{
|
||||
"greater than",
|
||||
models.CriterionModifierNotBetween,
|
||||
2,
|
||||
&upper,
|
||||
},
|
||||
{
|
||||
"null",
|
||||
models.CriterionModifierIsNull,
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.CriterionModifierNotNull,
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
filter := &models.PerformerFilterType{
|
||||
PenisLength: &models.FloatCriterionInput{
|
||||
Modifier: tt.modifier,
|
||||
Value: tt.value,
|
||||
Value2: tt.value2,
|
||||
},
|
||||
}
|
||||
|
||||
performers, _, err := db.Performer.Query(ctx, filter, nil)
|
||||
if err != nil {
|
||||
t.Errorf("PerformerStore.Query() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
verifyFloat(t, p.PenisLength, *filter.PenisLength)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func verifyFloat(t *testing.T, value *float64, criterion models.FloatCriterionInput) bool {
|
||||
t.Helper()
|
||||
assert := assert.New(t)
|
||||
switch criterion.Modifier {
|
||||
case models.CriterionModifierEquals:
|
||||
return assert.NotNil(value) && assert.Equal(criterion.Value, *value)
|
||||
case models.CriterionModifierNotEquals:
|
||||
return assert.NotNil(value) && assert.NotEqual(criterion.Value, *value)
|
||||
case models.CriterionModifierGreaterThan:
|
||||
return assert.NotNil(value) && assert.Greater(*value, criterion.Value)
|
||||
case models.CriterionModifierLessThan:
|
||||
return assert.NotNil(value) && assert.Less(*value, criterion.Value)
|
||||
case models.CriterionModifierBetween:
|
||||
return assert.NotNil(value) && assert.GreaterOrEqual(*value, criterion.Value) && assert.LessOrEqual(*value, *criterion.Value2)
|
||||
case models.CriterionModifierNotBetween:
|
||||
return assert.NotNil(value) && assert.True(*value < criterion.Value || *value > *criterion.Value2)
|
||||
case models.CriterionModifierIsNull:
|
||||
return assert.Nil(value)
|
||||
case models.CriterionModifierNotNull:
|
||||
return assert.NotNil(value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func TestPerformerQueryForAutoTag(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
tqb := db.Performer
|
||||
|
||||
@@ -3,6 +3,7 @@ package sqlite
|
||||
import (
|
||||
"github.com/doug-martin/goqu/v9/exp"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"gopkg.in/guregu/null.v4"
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
)
|
||||
|
||||
@@ -77,11 +78,11 @@ func (r *updateRecord) setFloat64(destField string, v models.OptionalFloat64) {
|
||||
}
|
||||
}
|
||||
|
||||
// func (r *updateRecord) setNullFloat64(destField string, v models.OptionalFloat64) {
|
||||
// if v.Set {
|
||||
// r.set(destField, null.FloatFromPtr(v.Ptr()))
|
||||
// }
|
||||
// }
|
||||
func (r *updateRecord) setNullFloat64(destField string, v models.OptionalFloat64) {
|
||||
if v.Set {
|
||||
r.set(destField, null.FloatFromPtr(v.Ptr()))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *updateRecord) setSQLiteTimestamp(destField string, v models.OptionalTime) {
|
||||
if v.Set {
|
||||
|
||||
@@ -1331,6 +1331,29 @@ func getPerformerCareerLength(index int) *string {
|
||||
return &ret
|
||||
}
|
||||
|
||||
func getPerformerPenisLength(index int) *float64 {
|
||||
if index%5 == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := float64(index)
|
||||
return &ret
|
||||
}
|
||||
|
||||
func getPerformerCircumcised(index int) *models.CircumisedEnum {
|
||||
var ret models.CircumisedEnum
|
||||
switch {
|
||||
case index%3 == 0:
|
||||
return nil
|
||||
case index%3 == 1:
|
||||
ret = models.CircumisedEnumCut
|
||||
default:
|
||||
ret = models.CircumisedEnumUncut
|
||||
}
|
||||
|
||||
return &ret
|
||||
}
|
||||
|
||||
func getIgnoreAutoTag(index int) bool {
|
||||
return index%5 == 0
|
||||
}
|
||||
@@ -1372,6 +1395,8 @@ func createPerformers(ctx context.Context, n int, o int) error {
|
||||
DeathDate: getPerformerDeathDate(i),
|
||||
Details: getPerformerStringValue(i, "Details"),
|
||||
Ethnicity: getPerformerStringValue(i, "Ethnicity"),
|
||||
PenisLength: getPerformerPenisLength(i),
|
||||
Circumcised: getPerformerCircumcised(i),
|
||||
Rating: getIntPtr(getRating(i)),
|
||||
IgnoreAutoTag: getIgnoreAutoTag(i),
|
||||
TagIDs: models.NewRelatedIDs(tids),
|
||||
|
||||
@@ -159,6 +159,22 @@ func getStringSearchClause(columns []string, q string, not bool) sqlClause {
|
||||
return makeClause("("+likes+")", args...)
|
||||
}
|
||||
|
||||
func getEnumSearchClause(column string, enumVals []string, not bool) sqlClause {
|
||||
var args []interface{}
|
||||
|
||||
notStr := ""
|
||||
if not {
|
||||
notStr = " NOT"
|
||||
}
|
||||
|
||||
clause := fmt.Sprintf("(%s%s IN %s)", column, notStr, getInBinding(len(enumVals)))
|
||||
for _, enumVal := range enumVals {
|
||||
args = append(args, enumVal)
|
||||
}
|
||||
|
||||
return makeClause(clause, args...)
|
||||
}
|
||||
|
||||
func getInBinding(length int) string {
|
||||
bindings := strings.Repeat("?, ", length)
|
||||
bindings = strings.TrimRight(bindings, ", ")
|
||||
@@ -175,8 +191,26 @@ func getIntWhereClause(column string, modifier models.CriterionModifier, value i
|
||||
upper = &u
|
||||
}
|
||||
|
||||
args := []interface{}{value}
|
||||
betweenArgs := []interface{}{value, *upper}
|
||||
args := []interface{}{value, *upper}
|
||||
return getNumericWhereClause(column, modifier, args)
|
||||
}
|
||||
|
||||
func getFloatCriterionWhereClause(column string, input models.FloatCriterionInput) (string, []interface{}) {
|
||||
return getFloatWhereClause(column, input.Modifier, input.Value, input.Value2)
|
||||
}
|
||||
|
||||
func getFloatWhereClause(column string, modifier models.CriterionModifier, value float64, upper *float64) (string, []interface{}) {
|
||||
if upper == nil {
|
||||
u := 0.0
|
||||
upper = &u
|
||||
}
|
||||
|
||||
args := []interface{}{value, *upper}
|
||||
return getNumericWhereClause(column, modifier, args)
|
||||
}
|
||||
|
||||
func getNumericWhereClause(column string, modifier models.CriterionModifier, args []interface{}) (string, []interface{}) {
|
||||
singleArgs := args[0:1]
|
||||
|
||||
switch modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
@@ -184,20 +218,20 @@ func getIntWhereClause(column string, modifier models.CriterionModifier, value i
|
||||
case models.CriterionModifierNotNull:
|
||||
return fmt.Sprintf("%s IS NOT NULL", column), nil
|
||||
case models.CriterionModifierEquals:
|
||||
return fmt.Sprintf("%s = ?", column), args
|
||||
return fmt.Sprintf("%s = ?", column), singleArgs
|
||||
case models.CriterionModifierNotEquals:
|
||||
return fmt.Sprintf("%s != ?", column), args
|
||||
return fmt.Sprintf("%s != ?", column), singleArgs
|
||||
case models.CriterionModifierBetween:
|
||||
return fmt.Sprintf("%s BETWEEN ? AND ?", column), betweenArgs
|
||||
return fmt.Sprintf("%s BETWEEN ? AND ?", column), args
|
||||
case models.CriterionModifierNotBetween:
|
||||
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), betweenArgs
|
||||
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), args
|
||||
case models.CriterionModifierLessThan:
|
||||
return fmt.Sprintf("%s < ?", column), args
|
||||
return fmt.Sprintf("%s < ?", column), singleArgs
|
||||
case models.CriterionModifierGreaterThan:
|
||||
return fmt.Sprintf("%s > ?", column), args
|
||||
return fmt.Sprintf("%s > ?", column), singleArgs
|
||||
}
|
||||
|
||||
panic("unsupported int modifier type " + modifier)
|
||||
panic("unsupported numeric modifier type " + modifier)
|
||||
}
|
||||
|
||||
func getDateCriterionWhereClause(column string, input models.DateCriterionInput) (string, []interface{}) {
|
||||
|
||||
@@ -24,6 +24,15 @@ func nullIntPtr(i null.Int) *int {
|
||||
return &v
|
||||
}
|
||||
|
||||
func nullFloatPtr(i null.Float) *float64 {
|
||||
if !i.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := float64(i.Float64)
|
||||
return &v
|
||||
}
|
||||
|
||||
func nullIntFolderIDPtr(i null.Int) *file.FolderID {
|
||||
if !i.Valid {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user