diff --git a/batcher.go b/batcher.go index 1deb9b6..dfee7a6 100644 --- a/batcher.go +++ b/batcher.go @@ -22,7 +22,7 @@ type batchQueryRunner struct { records []Record } -var ErrNoMoreRows = errors.New("kallax: there are no more rows in the result set") +var errNoMoreRows = errors.New("kallax: there are no more rows in the result set") func newBatchQueryRunner(schema Schema, db squirrel.DBProxy, q Query) *batchQueryRunner { cols, builder := q.compile() @@ -53,7 +53,7 @@ func newBatchQueryRunner(schema Schema, db squirrel.DBProxy, q Query) *batchQuer func (r *batchQueryRunner) next() (Record, error) { if r.eof { - return nil, ErrNoMoreRows + return nil, errNoMoreRows } if len(r.records) == 0 { @@ -64,7 +64,7 @@ func (r *batchQueryRunner) next() (Record, error) { if len(records) == 0 { r.eof = true - return nil, ErrNoMoreRows + return nil, errNoMoreRows } r.total += len(records) diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..f8eb846 --- /dev/null +++ b/doc.go @@ -0,0 +1,13 @@ +// Kallax is a PostgreSQL typesafe ORM for the Go language. +// +// Kallax aims to provide a way of programmatically write queries and interact +// with a PostgreSQL database without having to write a single line of SQL, +// use strings to refer to columns and use values of any type in queries. +// For that reason, the first priority of kallax is to provide type safety to +// the data access layer. +// Another of the goals of kallax is make sure all models are, first and +// foremost, Go structs without having to use database-specific types such as, +// for example, `sql.NullInt64. +// Support for arrays of all basic Go types and all JSON and arrays operators is +// provided as well. +package kallax diff --git a/fixtures/fixtures.go b/fixtures/fixtures.go deleted file mode 100644 index fd578b7..0000000 --- a/fixtures/fixtures.go +++ /dev/null @@ -1,57 +0,0 @@ -package fixtures - -import ( - "errors" - "strings" - - kallax "github.com/src-d/go-kallax" -) - -type User struct { - kallax.Model `table:"users"` - Username string - Email string - Password Password - Websites []string - Emails []*Email - Settings *Settings -} - -func newUser(username, email string) (*User, error) { - if strings.Contains(email, "@spam.org") { - return nil, errors.New("kallax: is spam!") - } - return &User{Username: username, Email: email}, nil -} - -type Email struct { - kallax.Model `table:"emails"` - Address string - Primary bool -} - -func newProfile(address string, primary bool) *Email { - return &Email{Address: address, Primary: primary} -} - -type Password string - -// Kids, don't do this at home -func (p *Password) Set(pwd string) { - *p = Password("such cypher" + pwd + "much secure") -} - -type Settings struct { - NotificationsActive bool - NotifyByEmail bool -} - -type Variadic struct { - kallax.Model - Foo []string - Bar string -} - -func newVariadic(bar string, foo ...string) *Variadic { - return &Variadic{Foo: foo, Bar: bar} -} diff --git a/generator/generator.go b/generator/generator.go index e49c166..bc858ef 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -1,3 +1,5 @@ +// Package generator implements the processor of source code and generator of +// kallax models based on Go source code. package generator import ( diff --git a/generator/types_test.go b/generator/types_test.go index 0ce77db..3f72468 100644 --- a/generator/types_test.go +++ b/generator/types_test.go @@ -1,8 +1,11 @@ package generator import ( - "os" - "path/filepath" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" "reflect" "testing" @@ -160,10 +163,80 @@ type ModelSuite struct { variadic *Model } +const fixturesSource = ` +package fixtures + +import ( + "errors" + "strings" + + kallax "github.com/src-d/go-kallax" +) + +type User struct { + kallax.Model ` + "`table:\"users\"`" + ` + Username string + Email string + Password Password + Websites []string + Emails []*Email + Settings *Settings +} + +func newUser(username, email string) (*User, error) { + if strings.Contains(email, "@spam.org") { + return nil, errors.New("kallax: is spam!") + } + return &User{Username: username, Email: email}, nil +} + +type Email struct { + kallax.Model ` + "`table:\"models\"`" + ` + Address string + Primary bool +} + +func newProfile(address string, primary bool) *Email { + return &Email{Address: address, Primary: primary} +} + +type Password string + +// Kids, don't do this at home +func (p *Password) Set(pwd string) { + *p = Password("such cypher" + pwd + "much secure") +} + +type Settings struct { + NotificationsActive bool + NotifyByEmail bool +} + +type Variadic struct { + kallax.Model + Foo []string + Bar string +} + +func newVariadic(bar string, foo ...string) *Variadic { + return &Variadic{Foo: foo, Bar: bar} +} +` + func (s *ModelSuite) SetupSuite() { - path := filepath.Join(os.Getenv("GOPATH"), "src", "github.com/src-d/go-kallax/fixtures") - p := NewProcessor(path, nil) - pkg, err := p.Do() + fset := &token.FileSet{} + astFile, err := parser.ParseFile(fset, "fixture.go", fixturesSource, 0) + s.Nil(err) + + cfg := &types.Config{ + Importer: importer.For("gc", nil), + } + p, err := cfg.Check("foo", fset, []*ast.File{astFile}, nil) + s.Nil(err) + + prc := NewProcessor("fixture", []string{"foo.go"}) + prc.Package = p + pkg, err := prc.processPackage() s.Nil(err) s.Len(pkg.Models, 3, "there should exist 3 models") diff --git a/model.go b/model.go index 9750b04..ff371b3 100644 --- a/model.go +++ b/model.go @@ -7,7 +7,26 @@ import ( uuid "github.com/satori/go.uuid" ) -// Model is the base type of the items that are stored +// Model contains all the basic fields that make something a model, that is, +// the ID and some internal things used by kallax. +// To make a struct a model, it only needs to have Model embedded. +// +// type MyModel struct { +// kallax.Model +// Foo string +// } +// +// Custom name for the table can be specified using the struct tag `table` when +// embedding the Model. +// +// type MyModel struct { +// kallax.Model `table:"custom_name"` +// } +// +// Otherwise, the default name of the table is the name of the model converted +// to lower snake case. E.g: MyModel => my_model. +// No pluralization is done right now, but might be done in the future, so +// please, set the name of the tables yourself. type Model struct { ID ID virtualColumns map[string]interface{} @@ -15,7 +34,8 @@ type Model struct { writable bool } -// NewModel creates and return a new Model +// NewModel creates a new Model that is writable, not persisted and identified +// with a newly generated ID. func NewModel() Model { m := Model{ persisted: false, @@ -26,7 +46,8 @@ func NewModel() Model { return m } -// IsPersisted returns whether this Model is new in the store or not. +// IsPersisted returns whether the Model has already been persisted to the +// database or not. func (m *Model) IsPersisted() bool { return m.persisted } @@ -35,8 +56,7 @@ func (m *Model) setPersisted() { m.persisted = true } -// IsWritable returns whether this Model can be sent back to the database to be -// stored with its changes. +// IsWritable returns whether this Model can be saved into the database. func (m *Model) IsWritable() bool { return m.writable } @@ -45,24 +65,28 @@ func (m *Model) setWritable(w bool) { m.writable = w } -// GetID returns the ID. +// GetID returns the ID of the model. func (m *Model) GetID() ID { return m.ID } -// SetID overrides the ID. -// The ID should not be modified once it has been set and stored in the DB -// WARNING: Not to be used by final users! +// SetID sets the ID of the model. +// The ID should not be modified once it has been set and stored in the +// database, so use it with caution. func (m *Model) SetID(id ID) { m.ID = id } // ClearVirtualColumns clears all the previous virtual columns. +// This method is only intended for internal use. It is only exposed for +// technical reasons. func (m *Model) ClearVirtualColumns() { m.virtualColumns = make(map[string]interface{}) } // AddVirtualColumn adds a new virtual column with the given name and value. +// This method is only intended for internal use. It is only exposed for +// technical reasons. func (m *Model) AddVirtualColumn(name string, v interface{}) { if m.virtualColumns == nil { m.ClearVirtualColumns() @@ -71,6 +95,8 @@ func (m *Model) AddVirtualColumn(name string, v interface{}) { } // VirtualColumn returns the value of the virtual column with the given column name. +// This method is only intended for internal use. It is only exposed for +// technical reasons. func (m *Model) VirtualColumn(name string) interface{} { if m.virtualColumns == nil { m.ClearVirtualColumns() @@ -78,15 +104,15 @@ func (m *Model) VirtualColumn(name string) interface{} { return m.virtualColumns[name] } -// Identifiable must be implemented by those values that can be identified by an ID +// Identifiable must be implemented by those values that can be identified by an ID. type Identifiable interface { // GetID returns the ID. GetID() ID - // SetID overrides the ID. + // SetID sets the ID. SetID(id ID) } -// Persistable must be implemented by those values that can be persisted +// Persistable must be implemented by those values that can be persisted. type Persistable interface { // IsPersisted returns whether this Model is new in the store or not. IsPersisted() bool @@ -96,17 +122,15 @@ type Persistable interface { // Writable must be implemented by those values that defines internally // if they can be sent back to the database to be stored with its changes. type Writable interface { - // IsWritable returns whether this Model can be sent back to the database - // to be stored with its changes. + // IsWritable returns whether this Model can be saved into the database. IsWritable() bool setWritable(bool) } -// ColumnAddresser must be implemented by those values that expose their properties -// under pointers, identified by its property names +// ColumnAddresser provides the pointer addresses of columns. type ColumnAddresser interface { - // ColumnAddress returns a pointer to the object property identified by the - // column name or an error if that property does not exist + // ColumnAddress returns the pointer to the column value of the given + // column name, or an error if it does not exist in the model. ColumnAddress(string) (interface{}, error) } @@ -119,11 +143,10 @@ type Relationable interface { SetRelationship(string, interface{}) error } -// Valuer must be implemented by those object that expose their properties -// identified by its property names +// Valuer provides the values for columns. type Valuer interface { - // Value returns the value under the object property identified by the passed - // string or an error if that property does not exist + // Value returns the value of the given column, or an error if it does not + // exist in the model. Value(string) (interface{}, error) } @@ -152,7 +175,7 @@ func RecordValues(record Valuer, columns ...string) ([]interface{}, error) { return values, nil } -// Record is the interface that must be implemented by models that can be stored. +// Record is something that can be stored as a row in the database. type Record interface { Identifiable Persistable @@ -163,10 +186,11 @@ type Record interface { VirtualColumnContainer } -// ID is the Kallax identifier type. +// ID is the identifier type used in models. It is an UUID, so it should +// be stored as `uuid` in PostgreSQL. type ID uuid.UUID -// NewID returns a new kallax ID. +// NewID returns a new ID. func NewID() ID { return ID(uuid.NewV4()) } @@ -181,11 +205,13 @@ func (id ID) Value() (driver.Value, error) { return uuid.UUID(id).Value() } -// IsEmpty returns true if the ID is not set +// IsEmpty returns whether the ID is empty or not. An empty ID means it has not +// been set yet. func (id ID) IsEmpty() bool { return uuid.Equal(uuid.UUID(id), uuid.Nil) } +// String returns the string representation of the ID. func (id ID) String() string { return uuid.UUID(id).String() } @@ -195,10 +221,13 @@ type virtualColumn struct { col string } +// VirtualColumn returns a sql.Scanner that will scan the given column as a +// virtual column in the given record. func VirtualColumn(col string, r Record) sql.Scanner { return &virtualColumn{r, col} } +// Scan implements the scanner interface. func (c *virtualColumn) Scan(src interface{}) error { var id ID if err := (&id).Scan(src); err != nil { diff --git a/query.go b/query.go index 0cd724a..8229069 100644 --- a/query.go +++ b/query.go @@ -6,7 +6,9 @@ import ( "github.com/Masterminds/squirrel" ) -// Query returns information about some query settings and compiles the query. +// Query is the common interface all queries must satisfy. The basic abilities +// of a query are compiling themselves to something executable and return +// some query settings. type Query interface { compile() ([]string, squirrel.SelectBuilder) getRelationships() []Relationship @@ -65,7 +67,7 @@ func (cs columnSet) copy() []SchemaField { return result } -// BaseQuery is a generic query builder. +// BaseQuery is a generic query builder to build queries programmatically. type BaseQuery struct { schema Schema columns columnSet @@ -82,8 +84,7 @@ type BaseQuery struct { limit uint64 } -// NewBaseQuery creates a new BaseQuery for querying the given table -// and the given selected columns. +// NewBaseQuery creates a new BaseQuery for querying the table of the given schema. func NewBaseQuery(schema Schema) *BaseQuery { return &BaseQuery{ builder: squirrel.StatementBuilder. @@ -96,6 +97,7 @@ func NewBaseQuery(schema Schema) *BaseQuery { } } +// Schema returns the Schema of the query. func (q *BaseQuery) Schema() Schema { return q.schema } @@ -267,14 +269,15 @@ func (q *BaseQuery) compile() ([]string, squirrel.SelectBuilder) { ) } -// String returns the SQL generated by the +// String returns the SQL generated by the query. If the query is malformed, +// it will return an empty string, as errors compiling the SQL are ignored. func (q *BaseQuery) String() string { _, builder := q.compile() sql, _, _ := builder.ToSql() return sql } -// ColumnOrder is a column name with its order. +// ColumnOrder represents a column name with its order. type ColumnOrder interface { // ToSql returns the SQL representation of the column with its order. ToSql(Schema) string @@ -286,6 +289,7 @@ type colOrder struct { col SchemaField } +// ToSql returns the SQL representation of the column with its order. func (o *colOrder) ToSql(schema Schema) string { return fmt.Sprintf("%s %s", o.col.QualifiedName(schema), o.order) } diff --git a/resultset.go b/resultset.go index 2bbde4d..a2d73e2 100644 --- a/resultset.go +++ b/resultset.go @@ -143,7 +143,7 @@ type BatchingResultSet struct { // It will return false when there are no more rows. func (rs *BatchingResultSet) Next() bool { rs.last, rs.lastErr = rs.runner.next() - if rs.lastErr == ErrNoMoreRows { + if rs.lastErr == errNoMoreRows { return false } diff --git a/timestamps.go b/timestamps.go index f29b217..574215e 100644 --- a/timestamps.go +++ b/timestamps.go @@ -2,30 +2,31 @@ package kallax import "time" -// Timestamp modelates an object that knows about when was created and updated. +// Timestamps contains the dates of the last time the model was created +// or deleted. Because this is such a common functionality in models, it is +// provided by default by the library. It is intended to be embedded in the +// model. +// +// type MyModel struct { +// kallax.Model +// kallax.Timestamps +// Foo string +// } type Timestamps struct { // CreatedAt is the time where the object was created. CreatedAt time.Time // UpdatedAt is the time where the object was updated. UpdatedAt time.Time - now func() time.Time } -// Timestamp updates the UpdatedAt and creates a new CreatedAt if it does not exist. -func (t *Timestamps) Timestamp() { - if t.now == nil { - t.now = time.Now - } - +// BeforeSave updates the last time the model was updated every single time the +// model is saved, and the last time the model was created only if the model +// has no date of creation yet. +func (t *Timestamps) BeforeSave() error { if t.CreatedAt.IsZero() { - t.CreatedAt = t.now() + t.CreatedAt = time.Now() } - t.UpdatedAt = t.now() -} - -// BeforeSave updates the UpdatedAt and creates a new CreatedAt if it does not exist. -func (t *Timestamps) BeforeSave() error { - t.Timestamp() + t.UpdatedAt = time.Now() return nil } diff --git a/timestamps_test.go b/timestamps_test.go index d20d6ea..8497032 100644 --- a/timestamps_test.go +++ b/timestamps_test.go @@ -3,35 +3,23 @@ package kallax import ( "testing" - "github.com/stretchr/testify/suite" + "github.com/stretchr/testify/require" ) -func TestTimestampable(t *testing.T) { - suite.Run(t, new(TimestampableSuite)) -} +func TestTimestampsBeforeSave(t *testing.T) { + s := require.New(t) -type TimestampableSuite struct { - suite.Suite -} + var ts Timestamps + s.True(ts.CreatedAt.IsZero()) + s.True(ts.UpdatedAt.IsZero()) -func (s *TimestampableSuite) TestTimestamp() { - var item Timestamps - s.True(item.CreatedAt.IsZero()) - s.True(item.UpdatedAt.IsZero()) - item.Timestamp() - createdAt := item.CreatedAt - updatedAt := item.CreatedAt - s.False(createdAt.IsZero()) - s.False(updatedAt.IsZero()) - item.Timestamp() - s.Equal(createdAt, item.CreatedAt) - s.NotEqual(updatedAt, item.UpdatedAt) -} + s.NoError(ts.BeforeSave()) + s.False(ts.CreatedAt.IsZero()) + s.False(ts.UpdatedAt.IsZero()) -func (s *TimestampableSuite) TestTimestampBeforeSave() { - var item Timestamps - err := item.BeforeSave() - s.Nil(err) - s.False(item.CreatedAt.IsZero()) - s.False(item.UpdatedAt.IsZero()) + createdAt := ts.CreatedAt + updatedAt := ts.CreatedAt + s.NoError(ts.BeforeSave()) + s.Equal(createdAt, ts.CreatedAt) + s.NotEqual(updatedAt, ts.UpdatedAt) } diff --git a/types/slices.go b/types/slices.go index c25a912..aea57b0 100644 --- a/types/slices.go +++ b/types/slices.go @@ -17,12 +17,13 @@ type slice struct { // Slice wraps a slice value so it can be scanned from and converted to // PostgreSQL arrays. The following values can be used with this function: -// - slices of basic types +// - slices of all Go basic types // - slices of *url.URL and url.URL // - slices of types that implement sql.Scanner and driver.Valuer (take into // account that these make use of reflection for scan/value) // -// NOTE: Keep in mind to always use the following types in the database schema to keep it in sync with the values allowed in Go. +// NOTE: Keep in mind to always use the following types in the database schema +// to keep it in sync with the values allowed in Go. // - int64: bigint // - uint64: numeric(20) // - int: bigint @@ -708,6 +709,69 @@ func (a Uint8Array) Value() (driver.Value, error) { return "{}", nil } +// Float32Array represents a one-dimensional array of the PostgreSQL real type. +type Float32Array []float32 + +// Scan implements the sql.Scanner interface. +func (a *Float32Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("kallax: cannot convert %T to Float32Array", src) +} + +func (a *Float32Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Float32Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Float32Array, len(elems)) + for i, v := range elems { + val, err := strconv.ParseFloat(string(v), 32) + if err != nil { + return fmt.Errorf("kallax: parsing array element index %d: %v", i, err) + } + b[i] = float32(val) + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Float32Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 64) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 64) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + // parseArray extracts the dimensions and elements of an array represented in // text format. Only representations emitted by the backend are supported. // Notably, whitespace around brackets and delimiters is significant, and NULL @@ -823,69 +887,6 @@ Close: return } -// Float32Array represents a one-dimensional array of the PostgreSQL real type. -type Float32Array []float32 - -// Scan implements the sql.Scanner interface. -func (a *Float32Array) Scan(src interface{}) error { - switch src := src.(type) { - case []byte: - return a.scanBytes(src) - case string: - return a.scanBytes([]byte(src)) - case nil: - *a = nil - return nil - } - - return fmt.Errorf("kallax: cannot convert %T to Float32Array", src) -} - -func (a *Float32Array) scanBytes(src []byte) error { - elems, err := scanLinearArray(src, []byte{','}, "Float32Array") - if err != nil { - return err - } - if *a != nil && len(elems) == 0 { - *a = (*a)[:0] - } else { - b := make(Float32Array, len(elems)) - for i, v := range elems { - val, err := strconv.ParseFloat(string(v), 32) - if err != nil { - return fmt.Errorf("kallax: parsing array element index %d: %v", i, err) - } - b[i] = float32(val) - } - *a = b - } - return nil -} - -// Value implements the driver.Valuer interface. -func (a Float32Array) Value() (driver.Value, error) { - if a == nil { - return nil, nil - } - - if n := len(a); n > 0 { - // There will be at least two curly brackets, N bytes of values, - // and N-1 bytes of delimiters. - b := make([]byte, 1, 1+2*n) - b[0] = '{' - - b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 64) - for i := 1; i < n; i++ { - b = append(b, ',') - b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 64) - } - - return string(append(b, '}')), nil - } - - return "{}", nil -} - func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) { dims, elems, err := parseArray(src, del) if err != nil { diff --git a/types/types.go b/types/types.go index 1b1bed8..aa71c00 100644 --- a/types/types.go +++ b/types/types.go @@ -1,3 +1,4 @@ +// Package types provides implementation of some wrapper SQL types. package types import ( @@ -12,9 +13,22 @@ import ( "github.com/lib/pq" ) -// Nullable gives the ability to scan nil values to the given type -// only if they implement sql.Scanner. -func Nullable(typ interface{}) interface{} { +// SQLType is the common interface a type has to fulfill to be considered a +// SQL type. +type SQLType interface { + sql.Scanner + driver.Valuer +} + +// Nullable converts the given type (which should be a pointer ideally) into +// a nullable type. For that, it must be either a pointer of a basic Go type or +// a type that implements sql.Scanner itself. +// time.Time and time.Duration are also supported, even though they are none of +// the above. +// If the given types does not fall into any of the above categories, it will +// actually return a valid sql.Scanner that will fail only when the Scan is +// performed. +func Nullable(typ interface{}) sql.Scanner { switch typ := typ.(type) { case *string: return &nullString{typ} @@ -60,7 +74,7 @@ type nullableErr struct { } func (n *nullableErr) Scan(_ interface{}) error { - return fmt.Errorf("type %T is not nullable and cannot be scanned", n.v) + return fmt.Errorf("kallax: type %T is not nullable and cannot be scanned", n.v) } type nullable struct { @@ -309,39 +323,16 @@ func (u URL) Value() (driver.Value, error) { return (&url).String(), nil } -// ScanJSON scans json v into dst. -// WARNING: This is here temporarily, might be removed in the future, use -// `JSON` instead. -func ScanJSON(v interface{}, dst interface{}) error { - switch v := v.(type) { - case []byte: - return json.Unmarshal(v, dst) - case string: - return ScanJSON([]byte(v), dst) - } - - return fmt.Errorf("kallax: cannot scan type %s into JSON type", reflect.TypeOf(v)) -} - -// JSONValue converts something into json. -// WARNING: This is here temporarily, might be removed in the future, use -// `JSON` instead. -func JSONValue(v interface{}) (driver.Value, error) { - return json.Marshal(v) -} - -// SQLType is the common interface a type has to fulfill to be considered a -// SQL type. -type SQLType interface { - sql.Scanner - driver.Valuer -} - type array struct { val reflect.Value size int } +// Array returns an SQLType for the given array type with a specific size. +// Note that the actual implementation of this relies on reflection, so be +// cautious with its usage. +// The array is scanned using a slice of the same type, so the same +// restrictions as the `Slice` function of this package are applied. func Array(v interface{}, size int) SQLType { return &array{reflect.ValueOf(v), size} } @@ -351,7 +342,7 @@ func (a *array) Scan(v interface{}) error { newSlice := reflect.MakeSlice(sliceTyp, 0, 0) slicePtr := reflect.New(sliceTyp) slicePtr.Elem().Set(newSlice) - if err := pq.Array(slicePtr.Interface()).Scan(v); err != nil { + if err := Slice(slicePtr.Interface()).Scan(v); err != nil { return err } @@ -396,9 +387,16 @@ func JSON(v interface{}) SQLType { } func (j *sqlJSON) Scan(v interface{}) error { - return ScanJSON(v, j.val) + switch v := v.(type) { + case []byte: + return json.Unmarshal(v, j.val) + case string: + return j.Scan([]byte(v)) + } + + return fmt.Errorf("kallax: cannot scan type %s into JSON type", reflect.TypeOf(v)) } func (j *sqlJSON) Value() (driver.Value, error) { - return JSONValue(j.val) + return json.Marshal(j.val) }