diff --git a/backends/clickhouse/column_test.go b/backends/clickhouse/column_test.go index 778a198..968928c 100644 --- a/backends/clickhouse/column_test.go +++ b/backends/clickhouse/column_test.go @@ -21,6 +21,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/sqlchemy" ) @@ -282,3 +283,61 @@ func TestConvertString(t *testing.T) { } } } + +func TestIdempotentConvertValue(t *testing.T) { + cases := []struct { + in interface{} + want interface{} + col sqlchemy.IColumnSpec + }{ + { + in: tristate.False, + want: uint8(0), + col: &triCol, + }, + { + in: tristate.True, + want: uint8(1), + col: &triCol, + }, + { + in: tristate.None, + want: sql.NullInt32{}, + col: &triCol, + }, + { + in: true, + want: uint8(1), + col: &boolCol, + }, + { + in: false, + want: uint8(0), + col: &boolCol, + }, + { + in: "0", + want: uint8(0), + col: &boolCol, + }, + { + in: "1", + want: uint8(1), + col: &boolCol, + }, + { + in: "2025-05-31T12:00:00Z", + want: func() time.Time { + tm, _ := timeutils.ParseTimeStr("2025-05-31T12:00:00Z") + return tm + }(), + col: &dateCol, + }, + } + for _, c := range cases { + got := c.col.ConvertFromValue(c.col.ConvertFromValue(c.in)) + if got != c.want { + t.Errorf("%s [%#v] want: %#v got: %#v", c.col.DefinitionString(), c.in, c.want, got) + } + } +} diff --git a/backends/dameng/column_test.go b/backends/dameng/column_test.go index b8e1e05..32cd465 100644 --- a/backends/dameng/column_test.go +++ b/backends/dameng/column_test.go @@ -21,6 +21,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/sqlchemy" ) @@ -229,3 +230,61 @@ func TestConvertString(t *testing.T) { } } } + +func TestIdempotentConvertValue(t *testing.T) { + cases := []struct { + in interface{} + want interface{} + col sqlchemy.IColumnSpec + }{ + { + in: tristate.False, + want: 0, + col: &triCol, + }, + { + in: tristate.True, + want: 1, + col: &triCol, + }, + { + in: tristate.None, + want: sql.NullInt32{}, + col: &triCol, + }, + { + in: true, + want: 1, + col: &boolCol, + }, + { + in: false, + want: 0, + col: &boolCol, + }, + { + in: "0", + want: 0, + col: &boolCol, + }, + { + in: "1", + want: 1, + col: &boolCol, + }, + { + in: "2025-05-31T12:00:00Z", + want: func() time.Time { + tm, _ := timeutils.ParseTimeStr("2025-05-31T12:00:00Z") + return tm + }(), + col: &dateCol, + }, + } + for _, c := range cases { + got := c.col.ConvertFromValue(c.col.ConvertFromValue(c.in)) + if got != c.want { + t.Errorf("%s [%#v] want: %#v got: %#v", c.col.DefinitionString(), c.in, c.want, got) + } + } +} diff --git a/backends/mysql/column_test.go b/backends/mysql/column_test.go index 251280c..a9b4154 100644 --- a/backends/mysql/column_test.go +++ b/backends/mysql/column_test.go @@ -17,9 +17,11 @@ package mysql import ( "database/sql" "testing" + "time" "yunion.io/x/jsonutils" "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/sqlchemy" ) @@ -239,3 +241,61 @@ func TestConvertString(t *testing.T) { } } } + +func TestIdempotentConvertValue(t *testing.T) { + cases := []struct { + in interface{} + want interface{} + col sqlchemy.IColumnSpec + }{ + { + in: tristate.False, + want: 0, + col: &triCol, + }, + { + in: tristate.True, + want: 1, + col: &triCol, + }, + { + in: tristate.None, + want: sql.NullInt32{}, + col: &triCol, + }, + { + in: true, + want: 1, + col: &boolCol, + }, + { + in: false, + want: 0, + col: &boolCol, + }, + { + in: "0", + want: 0, + col: &boolCol, + }, + { + in: "1", + want: 1, + col: &boolCol, + }, + { + in: "2025-05-31T12:00:00Z", + want: func() time.Time { + tm, _ := timeutils.ParseTimeStr("2025-05-31T12:00:00Z") + return tm + }(), + col: &dateCol, + }, + } + for _, c := range cases { + got := c.col.ConvertFromValue(c.col.ConvertFromValue(c.in)) + if got != c.want { + t.Errorf("%s [%#v] want: %#v got: %#v", c.col.DefinitionString(), c.in, c.want, got) + } + } +} diff --git a/backends/sqlite/column_test.go b/backends/sqlite/column_test.go index 16c62c6..d554f33 100644 --- a/backends/sqlite/column_test.go +++ b/backends/sqlite/column_test.go @@ -17,10 +17,12 @@ package sqlite import ( "database/sql" "testing" + "time" "yunion.io/x/jsonutils" "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/sqlchemy" ) @@ -200,3 +202,61 @@ func TestConvertString(t *testing.T) { } } } + +func TestIdempotentConvertValue(t *testing.T) { + cases := []struct { + in interface{} + want interface{} + col sqlchemy.IColumnSpec + }{ + { + in: tristate.False, + want: 0, + col: &triCol, + }, + { + in: tristate.True, + want: 1, + col: &triCol, + }, + { + in: tristate.None, + want: sql.NullInt32{}, + col: &triCol, + }, + { + in: true, + want: 1, + col: &boolCol, + }, + { + in: false, + want: 0, + col: &boolCol, + }, + { + in: "0", + want: 0, + col: &boolCol, + }, + { + in: "1", + want: 1, + col: &boolCol, + }, + { + in: "2025-05-31T12:00:00Z", + want: func() time.Time { + tm, _ := timeutils.ParseTimeStr("2025-05-31T12:00:00Z") + return tm + }(), + col: &dateCol, + }, + } + for _, c := range cases { + got := c.col.ConvertFromValue(c.col.ConvertFromValue(c.in)) + if got != c.want { + t.Errorf("%s [%#v] want: %#v got: %#v", c.col.DefinitionString(), c.in, c.want, got) + } + } +} diff --git a/reflect.go b/reflect.go index ddba638..27c84d4 100644 --- a/reflect.go +++ b/reflect.go @@ -15,6 +15,7 @@ package sqlchemy import ( + "database/sql" "fmt" "reflect" "strconv" @@ -382,35 +383,80 @@ func ConvertValueToBool(val interface{}) bool { case float64: return v > 0 case *string: + if gotypes.IsNil(v) { + return false + } nv := strings.ToLower(*v) return nv == "1" || nv == "true" || nv == "ok" || nv == "yes" || nv == "on" case *bool: + if gotypes.IsNil(v) { + return false + } return *v case *tristate.TriState: + if gotypes.IsNil(v) { + return false + } return *v == tristate.True case *int8: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *int16: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *int: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *int32: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *int64: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *uint8: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *uint16: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *uint: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *uint32: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *uint64: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *float32: + if gotypes.IsNil(v) { + return false + } return *v > 0 case *float64: + if gotypes.IsNil(v) { + return false + } return *v > 0 } return false @@ -435,115 +481,99 @@ func ConvertValueToTriState(val interface{}) tristate.TriState { } else { return tristate.False } + case sql.NullInt32: + if v.Valid { + return ConvertValueToTriState(v.Int32) + } + return tristate.None case int8: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case int16: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case int: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case int32: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case int64: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case uint8: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case uint16: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case uint: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case uint32: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case uint64: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case float32: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case float64: switch { case v > 0: return tristate.True - case v < 0: - return tristate.False default: - return tristate.None + return tristate.False } case *string: + if gotypes.IsNil(v) { + return tristate.None + } switch strings.ToLower(*v) { case "true", "yes", "on", "ok", "1": return tristate.True @@ -553,6 +583,9 @@ func ConvertValueToTriState(val interface{}) tristate.TriState { return tristate.False } case *bool: + if gotypes.IsNil(v) { + return tristate.None + } if *v { return tristate.True } else { @@ -561,104 +594,113 @@ func ConvertValueToTriState(val interface{}) tristate.TriState { case *tristate.TriState: return *v case *int8: - switch { - case *v > 0: + if gotypes.IsNil(v) { + return tristate.None + } + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *int16: + if gotypes.IsNil(v) { return tristate.None } - case *int: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *int: + if gotypes.IsNil(v) { return tristate.None } - case *int32: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *int32: + if gotypes.IsNil(v) { return tristate.None } - case *int64: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *int64: + if gotypes.IsNil(v) { return tristate.None } - case *uint8: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *uint8: + if gotypes.IsNil(v) { return tristate.None } - case *uint16: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *uint16: + if gotypes.IsNil(v) { return tristate.None } - case *uint: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *uint: + if gotypes.IsNil(v) { return tristate.None } - case *uint32: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *uint32: + if gotypes.IsNil(v) { return tristate.None } - case *uint64: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *uint64: + if gotypes.IsNil(v) { return tristate.None } - case *float32: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *float32: + if gotypes.IsNil(v) { return tristate.None } - case *float64: - switch { - case *v > 0: + if *v > 0 { return tristate.True - case *v < 0: + } else { return tristate.False - default: + } + case *float64: + if gotypes.IsNil(v) { return tristate.None } + if *v > 0 { + return tristate.True + } else { + return tristate.False + } } return tristate.None } diff --git a/reflect_test.go b/reflect_test.go index aa0bc12..476b7ec 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -276,3 +276,128 @@ func TestSetValueBySQLString(t *testing.T) { } } } + +func TestConvertValueToTime(t *testing.T) { + cases := []struct { + in interface{} + want time.Time + }{ + { + in: "2021-10-01T00:00:00Z", + want: func() time.Time { + tm, _ := timeutils.ParseTimeStr("2021-10-01T00:00:00Z") + return tm + }(), + }, + } + for _, c := range cases { + got := ConvertValueToTime(c.in) + if got != c.want { + t.Errorf("want %s got %s for %s", c.want, got, c.in) + } + } +} + +func TestConvertValueToBool(t *testing.T) { + cases := []struct { + in interface{} + want bool + }{ + { + in: "0", + want: false, + }, + { + in: "1", + want: true, + }, + { + in: tristate.True, + want: true, + }, + { + in: tristate.False, + want: false, + }, + { + in: tristate.None, + want: false, + }, + } + for _, c := range cases { + got := ConvertValueToBool(c.in) + if got != c.want { + t.Errorf("want %v got %v for %s", c.want, got, c.in) + } + } +} + +func TestConvertValueToTriState(t *testing.T) { + cases := []struct { + in interface{} + want tristate.TriState + }{ + { + in: "0", + want: tristate.False, + }, + { + in: "1", + want: tristate.True, + }, + { + in: "2", + want: tristate.False, + }, + { + in: "none", + want: tristate.None, + }, + { + in: "true", + want: tristate.True, + }, + { + in: "false", + want: tristate.False, + }, + { + in: tristate.False, + want: tristate.False, + }, + { + in: tristate.True, + want: tristate.True, + }, + { + in: tristate.None, + want: tristate.None, + }, + { + in: nil, + want: tristate.None, + }, + { + in: 1, + want: tristate.True, + }, + { + in: 0, + want: tristate.False, + }, + { + in: uint8(1), + want: tristate.True, + }, + { + in: uint8(0), + want: tristate.False, + }, + } + for _, c := range cases { + got := ConvertValueToTriState(c.in) + if got != c.want { + t.Errorf("want %s got %s for %s", c.want, got, c.in) + } + } +}