123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- package mysql
-
- import (
- "database/sql/driver"
- "encoding/json"
- "errors"
- "reflect"
-
- "upper.io/db.v3/lib/sqlbuilder"
- )
-
- // JSON represents a MySQL's JSON value:
- // https://www.mysql.org/docs/9.6/static/datatype-json.html. JSON
- // satisfies sqlbuilder.ScannerValuer.
- type JSON struct {
- V interface{}
- }
-
- // MarshalJSON encodes the wrapper value as JSON.
- func (j JSON) MarshalJSON() ([]byte, error) {
- return json.Marshal(j.V)
- }
-
- // UnmarshalJSON decodes the given JSON into the wrapped value.
- func (j *JSON) UnmarshalJSON(b []byte) error {
- var v interface{}
- if err := json.Unmarshal(b, &v); err != nil {
- return err
- }
- j.V = v
- return nil
- }
-
- // Scan satisfies the sql.Scanner interface.
- func (j *JSON) Scan(src interface{}) error {
- if src == nil {
- j.V = nil
- return nil
- }
-
- b, ok := src.([]byte)
- if !ok {
- return errors.New("Scan source was not []bytes")
- }
-
- if err := json.Unmarshal(b, &j.V); err != nil {
- return err
- }
- return nil
- }
-
- // Value satisfies the driver.Valuer interface.
- func (j JSON) Value() (driver.Value, error) {
- // See https://github.com/lib/pq/issues/528#issuecomment-257197239 on why are
- // we returning string instead of []byte.
- if j.V == nil {
- return nil, nil
- }
- if v, ok := j.V.(json.RawMessage); ok {
- return string(v), nil
- }
- b, err := json.Marshal(j.V)
- if err != nil {
- return nil, err
- }
- return string(b), nil
- }
-
- // JSONMap represents a map of interfaces with string keys
- // (`map[string]interface{}`) that is compatible with MySQL's JSON type.
- // JSONMap satisfies sqlbuilder.ScannerValuer.
- type JSONMap map[string]interface{}
-
- // Value satisfies the driver.Valuer interface.
- func (m JSONMap) Value() (driver.Value, error) {
- return JSONValue(m)
- }
-
- // Scan satisfies the sql.Scanner interface.
- func (m *JSONMap) Scan(src interface{}) error {
- *m = map[string]interface{}(nil)
- return ScanJSON(m, src)
- }
-
- // JSONArray represents an array of any type (`[]interface{}`) that is
- // compatible with MySQL's JSON type. JSONArray satisfies
- // sqlbuilder.ScannerValuer.
- type JSONArray []interface{}
-
- // Value satisfies the driver.Valuer interface.
- func (a JSONArray) Value() (driver.Value, error) {
- return JSONValue(a)
- }
-
- // Scan satisfies the sql.Scanner interface.
- func (a *JSONArray) Scan(src interface{}) error {
- return ScanJSON(a, src)
- }
-
- // JSONValue takes an interface and provides a driver.Value that can be
- // stored as a JSON column.
- func JSONValue(i interface{}) (driver.Value, error) {
- v := JSON{i}
- return v.Value()
- }
-
- // ScanJSON decodes a JSON byte stream into the passed dst value.
- func ScanJSON(dst interface{}, src interface{}) error {
- v := JSON{dst}
- return v.Scan(src)
- }
-
- // EncodeJSON is deprecated and going to be removed. Use ScanJSON instead.
- func EncodeJSON(i interface{}) (driver.Value, error) {
- return JSONValue(i)
- }
-
- // DecodeJSON is deprecated and going to be removed. Use JSONValue instead.
- func DecodeJSON(dst interface{}, src interface{}) error {
- return ScanJSON(dst, src)
- }
-
- // JSONConverter provides a helper method WrapValue that satisfies
- // sqlbuilder.ValueWrapper, can be used to encode Go structs into JSON
- // MySQL types and vice versa.
- //
- // Example:
- //
- // type MyCustomStruct struct {
- // ID int64 `db:"id" json:"id"`
- // Name string `db:"name" json:"name"`
- // ...
- // mysql.JSONConverter
- // }
- type JSONConverter struct {
- }
-
- // WrapValue satisfies sqlbuilder.ValueWrapper
- func (obj *JSONConverter) WrapValue(src interface{}) interface{} {
- return &JSON{src}
- }
-
- func autoWrap(elem reflect.Value, v interface{}) interface{} {
- kind := elem.Kind()
-
- if kind == reflect.Invalid {
- return v
- }
-
- if elem.Type().Implements(sqlbuilder.ScannerType) {
- return v
- }
-
- if elem.Type().Implements(sqlbuilder.ValuerType) {
- return v
- }
-
- if elem.Type().Implements(sqlbuilder.ValueWrapperType) {
- if elem.Type().Kind() == reflect.Ptr {
- w := reflect.ValueOf(v)
- if w.Kind() == reflect.Ptr {
- z := reflect.Zero(w.Elem().Type())
- w.Elem().Set(z)
- return &JSON{v}
- }
- }
- vw := elem.Interface().(sqlbuilder.ValueWrapper)
- return vw.WrapValue(elem.Interface())
- }
-
- switch kind {
- case reflect.Ptr:
- return autoWrap(elem.Elem(), v)
- case reflect.Slice:
- return &JSON{v}
- case reflect.Map:
- if reflect.TypeOf(v).Kind() == reflect.Ptr {
- w := reflect.ValueOf(v)
- z := reflect.New(w.Elem().Type())
- w.Elem().Set(z.Elem())
- }
- return &JSON{v}
- }
-
- return v
- }
-
- // Type checks.
- var (
- _ sqlbuilder.ValueWrapper = &JSONConverter{}
- _ sqlbuilder.ScannerValuer = &JSONMap{}
- _ sqlbuilder.ScannerValuer = &JSONArray{}
- )
|