fix(asDBCond): check string when driver.Valuer return string instead of pointer (#2541)

Description:
  the bug is due to `driver.Valuer()` return a non-pointer type value, no check this value and return true directly.  [decimal](https://github.com/shopspring/decimal) is a widely used type in most business scenarios, so I think it's necessary to compatible with the [decimal](https://github.com/shopspring/decimal)

What's changes:
 - check string value
 - be compatible  with [decimal](https://github.com/shopspring/decimal)

close #2411

Test Result:
`
=== RUN   TestColumnsStringGeneration
--- PASS: TestColumnsStringGeneration (0.00s)
=== RUN   TestConvertSQLOrArgs
--- PASS: TestConvertSQLOrArgs (0.00s)
=== RUN   TestAsDbCondWhenFieldValueIsDriverValuer
--- PASS: TestAsDbCondWhenFieldValueIsDriverValuer (0.00s)
PASS
`

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2541
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Jinhan He <[email protected]>
Co-committed-by: Jinhan He <[email protected]>
This commit is contained in:
Jinhan He
2025-10-19 18:30:08 +00:00
committed by Lunny Xiao
parent b16696b837
commit 469c06e1e3
5 changed files with 164 additions and 0 deletions
+5
View File
@@ -436,6 +436,11 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect
val, _ := valNul.Value()
if val == nil && !requiredField {
return nil, false, nil
} else if parsed, ok := val.(string); ok {
// parsed == "0" is to be compatible with the github.com/shopspring/decimal
if (parsed == "" || parsed == "0") && !requiredField {
return nil, false, nil
}
}
return val, true, nil
} else {
+48
View File
@@ -5,6 +5,7 @@
package statements
import (
"database/sql/driver"
"os"
"reflect"
"strings"
@@ -207,3 +208,50 @@ func BenchmarkColumnsStringGeneration(b *testing.B) {
}
}
}
type ZeroDecimal struct{}
func (ZeroDecimal) Value() (driver.Value, error) {
return "0", nil
}
type EmptyDecimal struct{}
func (EmptyDecimal) Value() (driver.Value, error) {
return "", nil
}
func TestAsDbCondWhenFieldValueIsDriverValuer(t *testing.T) {
statement, err := createTestStatement()
assert.NoError(t, err)
zeroDecimal := ZeroDecimal{}
val, ok, err := statement.asDBCond(
reflect.ValueOf(zeroDecimal),
reflect.TypeOf(zeroDecimal),
&schemas.Column{
Name: "zero_decimal",
TableName: "test",
},
false,
false,
)
assert.Nil(t, val)
assert.False(t, ok)
assert.NoError(t, err)
emptyDecimal := EmptyDecimal{}
val, ok, err = statement.asDBCond(
reflect.ValueOf(emptyDecimal),
reflect.TypeOf(emptyDecimal),
&schemas.Column{
Name: "empty_decimal",
TableName: "test",
},
false,
false,
)
assert.Nil(t, val)
assert.False(t, ok)
assert.NoError(t, err)
}
+37
View File
@@ -7,8 +7,10 @@ package tests
import (
"testing"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"xorm.io/builder"
"xorm.io/xorm/schemas"
)
func TestCount(t *testing.T) {
@@ -190,3 +192,38 @@ func TestCountWithLimit(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 2, cnt)
}
func TestCountStructWithDecimal(t *testing.T) {
assert.NoError(t, PrepareEngine())
type ProductCount struct {
Uid int `xorm:"pk autoincr"`
Price decimal.Decimal `xorm:"decimal(35,30)"`
}
assert.NoError(t, testEngine.Sync(new(ProductCount)))
session := testEngine.NewSession()
defer session.Close()
var err error
if testEngine.Dialect().URI().DBType == schemas.MSSQL {
err = session.Begin()
assert.NoError(t, err)
_, err = session.Exec("SET IDENTITY_INSERT `product_count` ON")
assert.NoError(t, err)
}
cnt, err := session.Insert(&ProductCount{Uid: 2, Price: decimal.NewFromFloat(0.8)})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
cnt, err = session.Insert(&ProductCount{Uid: 3, Price: decimal.NewFromFloat(1.5)})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
if testEngine.Dialect().URI().DBType == schemas.MSSQL {
err = session.Commit()
assert.NoError(t, err)
}
// Expected SQL: SELECT count(*) FROM `product_count` WHERE price>?
// Wrong SQL: SELECT count(*) FROM `product_count` WHERE price>? AND `price`=?
total, err := testEngine.Where(builder.Gt{"price": decimal.NewFromInt(1)}).Count(new(ProductCount))
assert.NoError(t, err)
assert.Equal(t, int64(1), total)
}
+37
View File
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/shopspring/decimal"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/internal/utils"
@@ -1306,3 +1307,39 @@ func TestFindInMaxID(t *testing.T) {
err := testEngine.In("id", builder.Select("max(id)").From(testEngine.Quote(tableName))).Find(&res)
assert.NoError(t, err)
}
func TestFindStructWithDecimal(t *testing.T) {
assert.NoError(t, PrepareEngine())
type ProductFind struct {
Uid int `xorm:"pk autoincr"`
Price decimal.Decimal `xorm:"decimal(35,30)"`
}
assert.NoError(t, testEngine.Sync(new(ProductFind)))
session := testEngine.NewSession()
defer session.Close()
var err error
if testEngine.Dialect().URI().DBType == schemas.MSSQL {
err = session.Begin()
assert.NoError(t, err)
_, err = session.Exec("SET IDENTITY_INSERT `product_find` ON")
assert.NoError(t, err)
}
cnt, err := session.Insert(&ProductFind{Uid: 2, Price: decimal.NewFromFloat(0.8)})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
cnt, err = session.Insert(&ProductFind{Uid: 3, Price: decimal.NewFromFloat(1.5)})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
if testEngine.Dialect().URI().DBType == schemas.MSSQL {
err = session.Commit()
assert.NoError(t, err)
}
// Expected SQL: SELECT `uid`, `price` FROM `product_find` WHERE price>?
// Wrong SQL: SELECT `uid`, `price` FROM `product_find` WHERE price>? AND `price`=?
products := make([]*ProductFind, 0)
err = testEngine.Where(builder.Gt{"price": decimal.NewFromInt(1)}).Find(&products)
assert.NoError(t, err)
assert.Equal(t, int(1), len(products))
}
+37
View File
@@ -12,6 +12,7 @@ import (
"testing"
"time"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
"xorm.io/xorm/convert"
@@ -1021,3 +1022,39 @@ func TestGetBytesVars(t *testing.T) {
assert.True(t, has)
assert.EqualValues(t, gbv.Id, myID)
}
func TestGetStructWithDecimal(t *testing.T) {
assert.NoError(t, PrepareEngine())
type ProductGet struct {
Uid int `xorm:"pk autoincr"`
Price decimal.Decimal `xorm:"decimal(35,30)"`
}
assert.NoError(t, testEngine.Sync(new(ProductGet)))
session := testEngine.NewSession()
defer session.Close()
var err error
if testEngine.Dialect().URI().DBType == schemas.MSSQL {
err = session.Begin()
assert.NoError(t, err)
_, err = session.Exec("SET IDENTITY_INSERT `product_get` ON")
assert.NoError(t, err)
}
cnt, err := session.Insert(&ProductGet{Uid: 2, Price: decimal.NewFromFloat(0.8)})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
cnt, err = session.Insert(&ProductGet{Uid: 3, Price: decimal.NewFromFloat(1.5)})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
if testEngine.Dialect().URI().DBType == schemas.MSSQL {
err = session.Commit()
assert.NoError(t, err)
}
// Expected SQL: SELECT `uid`, `price` FROM `product_get` WHERE price>? LIMIT 1
// Wrong SQL: SELECT `uid`, `price` FROM `product_get` WHERE price>? AND `price`=? LIMIT 1
product := new(ProductGet)
exist, err := testEngine.Where(builder.Gt{"price": decimal.NewFromInt(1)}).Get(product)
assert.NoError(t, err)
assert.True(t, exist)
}