Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
moocss committed Dec 17, 2024
1 parent 4c18a0c commit e93befa
Show file tree
Hide file tree
Showing 25 changed files with 2,009 additions and 246 deletions.
46 changes: 46 additions & 0 deletions db/sqlx/sql_helper/sql_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package sql_helper

import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -61,3 +64,46 @@ func NewNullTime(val time.Time) sql.NullTime {
func NewNullBytes(val []byte) sql.NullString {
return sql.NullString{String: string(val), Valid: len(val) > 0}
}

// JsonColumn 代表存储字段的 json 类型
// 主要用于没有提供默认 json 类型的数据库
// T 可以是结构体,也可以是切片或者 map
// 理论上来说一切可以被 json 库所处理的类型都能被用作 T
// 不建议使用指针作为 T 的类型
// 如果 T 是指针,那么在 Val 为 nil 的情况下,一定要把 Valid 设置为 false
type JsonColumn[T any] struct {
Val T
Valid bool
}

// Value 返回一个 json 串。类型是 []byte
func (j JsonColumn[T]) Value() (driver.Value, error) {
if !j.Valid {
return nil, nil
}
res, err := json.Marshal(j.Val)
return res, err
}

// Scan 将 src 转化为对象
// src 的类型必须是 []byte, string 或者 nil
// 如果是 nil,我们不会做任何处理
func (j *JsonColumn[T]) Scan(src any) error {
var bs []byte
switch val := src.(type) {
case nil:
return nil
case []byte:
bs = val
case string:
bs = []byte(val)
default:
return fmt.Errorf("ekit:JsonColumn.Scan 不支持 src 类型 %v", src)
}

if err := json.Unmarshal(bs, &j.Val); err != nil {
return err
}
j.Valid = true
return nil
}
132 changes: 132 additions & 0 deletions db/sqlx/sql_helper/sql_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package sql_helper

import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -215,3 +218,132 @@ func TestNewNullTime(t *testing.T) {
})
}
}

func TestJsonColumn_Value(t *testing.T) {
testCases := []struct {
name string
valuer driver.Valuer
wantRes any
wantErr error
}{
{
name: "user",
valuer: JsonColumn[User]{Valid: true, Val: User{Name: "Tom"}},
wantRes: []byte(`{"Name":"Tom"}`),
},
{
name: "invalid",
valuer: JsonColumn[User]{},
},
{
name: "nil",
valuer: JsonColumn[*User]{},
},
{
name: "nil but valid",
valuer: JsonColumn[*User]{Valid: true},
wantRes: []uint8("null"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
value, err := tc.valuer.Value()
assert.Equal(t, tc.wantErr, err)
if err != nil {
return
}
assert.Equal(t, tc.wantRes, value)
})
}
}

func TestJsonColumn_Scan(t *testing.T) {
testCases := []struct {
name string
src any
wantErr error
wantValid bool
wantVal User
}{
{
name: "nil",
wantVal: User{},
},
{
name: "string",
src: `{"Name":"Tom"}`,
wantVal: User{Name: "Tom"},
wantValid: true,
},
{
name: "bytes",
src: []byte(`{"Name":"Tom"}`),
wantVal: User{Name: "Tom"},
wantValid: true,
},
{
name: "int",
src: 123,
wantErr: errors.New("ekit:JsonColumn.Scan 不支持 src 类型 123"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
js := &JsonColumn[User]{}
err := js.Scan(tc.src)
assert.Equal(t, tc.wantErr, err)
if err != nil {
return
}
assert.Equal(t, tc.wantValid, js.Valid)
if !js.Valid {
return
}
assert.Equal(t, tc.wantVal, js.Val)
})
}
}

func TestJsonColumn_ScanTypes(t *testing.T) {
jsSlice := JsonColumn[[]string]{}
err := jsSlice.Scan(`["a", "b", "c"]`)
assert.Nil(t, err)
assert.Equal(t, []string{"a", "b", "c"}, jsSlice.Val)
val, err := jsSlice.Value()
assert.Nil(t, err)
assert.Equal(t, []byte(`["a","b","c"]`), val)

jsMap := JsonColumn[map[string]string]{}
err = jsMap.Scan(`{"a":"a value"}`)
assert.Nil(t, err)
val, err = jsMap.Value()
assert.Nil(t, err)
assert.Equal(t, []byte(`{"a":"a value"}`), val)
}

type User struct {
Name string
}

func ExampleJsonColumn_Value() {
js := JsonColumn[User]{Valid: true, Val: User{Name: "Tom"}}
value, err := js.Value()
if err != nil {
fmt.Println(err)
}
fmt.Print(string(value.([]byte)))
// Output:
// {"Name":"Tom"}
}

func ExampleJsonColumn_Scan() {
js := JsonColumn[User]{}
err := js.Scan(`{"Name":"Tom"}`)
if err != nil {
fmt.Println(err)
}
fmt.Print(js.Val)
// Output:
// {Tom}
}
Loading

0 comments on commit e93befa

Please sign in to comment.