Skip to content

Commit 4301d72

Browse files
committed
*Breaking* types change: JsonText -> JSONText, also document the []byte dest memory corruption bug and discourage JSONText and GzippedText both in package documentation and in the readme
1 parent 60a229c commit 4301d72

File tree

3 files changed

+31
-34
lines changed

3 files changed

+31
-34
lines changed

README.md

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,15 @@ explains how to use `database/sql` along with sqlx.
2020

2121
## Recent Changes
2222

23-
The addition of `sqlx.In` and `sqlx.Named`, which can be used to bind IN style
24-
queries and named queries respectively.
23+
* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
2524

26-
```go
27-
ids := []int{1, 2, 3}
28-
query, args, err := sqlx.In("SELECT * FROM person WHERE id IN(?);", ids)
29-
30-
chris := Person{First: "Christian", Last: "Cullen"}
31-
query, args, err := sqlx.Named("INSERT INTO person VALUES (:first, :last);", chris)
25+
This breaks backwards compatibility, but it's in a way that is trivially fixable
26+
(`s/JsonText/JSONText/g`). The `types` package is both experimental and not in
27+
active development currently.
3228

33-
// these can be combined:
34-
arg := map[string]interface{}{
35-
"published": true,
36-
"authors": []{8, 19, 32, 44},
37-
}
38-
query, args, err := sqlx.Named("SELECT * FROM articles WHERE published=:published AND author_id IN (:authors)", arg)
39-
query, args, err := sqlx.In(query, args...)
40-
// finally, if you're using eg. pg, you can rebind the query:
41-
query = db.Rebind(query)
42-
```
29+
More importantly, [golang bug #13905](https://github.com/golang/go/issues/13905)
30+
makes `types.JSONText` and `types.GzippedText` _potentially unsafe_, **especially**
31+
when used with common auto-scan sqlx idioms like `Select` and `Get`.
4332

4433
### Backwards Compatibility
4534

types/types.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212

1313
// GzippedText is a []byte which transparently gzips data being submitted to
1414
// a database and ungzips data being Scanned from a database.
15+
// WARNING: due to go issue https://github.com/golang/go/issues/13905 it is
16+
// potentially unsafe to use any []byte alias (like GzippedText) unless you
17+
// use it as if it had the behavior of sql.RawBytes (ie. it ceases to be
18+
// valid at the next scan)
1519
type GzippedText []byte
1620

1721
// Value implements the driver.Valuer interface, gzipping the raw value of
@@ -48,21 +52,25 @@ func (g *GzippedText) Scan(src interface{}) error {
4852
return nil
4953
}
5054

51-
// JsonText is a json.RawMessage, which is a []byte underneath.
55+
// JSONText is a json.RawMessage, which is a []byte underneath.
5256
// Value() validates the json format in the source, and returns an error if
53-
// the json is not valid. Scan does no validation. JsonText additionally
57+
// the json is not valid. Scan does no validation. JSONText additionally
5458
// implements `Unmarshal`, which unmarshals the json within to an interface{}
55-
type JsonText json.RawMessage
59+
// WARNING: due to go issue https://github.com/golang/go/issues/13905 it is
60+
// potentially unsafe to use any []byte alias (like JSONText) unless you
61+
// use it as if it had the behavior of sql.RawBytes (ie. it ceases to be
62+
// valid at the next scan)
63+
type JSONText json.RawMessage
5664

5765
// MarshalJSON returns the *j as the JSON encoding of j.
58-
func (j *JsonText) MarshalJSON() ([]byte, error) {
66+
func (j *JSONText) MarshalJSON() ([]byte, error) {
5967
return *j, nil
6068
}
6169

6270
// UnmarshalJSON sets *j to a copy of data
63-
func (j *JsonText) UnmarshalJSON(data []byte) error {
71+
func (j *JSONText) UnmarshalJSON(data []byte) error {
6472
if j == nil {
65-
return errors.New("JsonText: UnmarshalJSON on nil pointer")
73+
return errors.New("JSONText: UnmarshalJSON on nil pointer")
6674
}
6775
*j = append((*j)[0:0], data...)
6876
return nil
@@ -71,7 +79,7 @@ func (j *JsonText) UnmarshalJSON(data []byte) error {
7179

7280
// Value returns j as a value. This does a validating unmarshal into another
7381
// RawMessage. If j is invalid json, it returns an error.
74-
func (j JsonText) Value() (driver.Value, error) {
82+
func (j JSONText) Value() (driver.Value, error) {
7583
var m json.RawMessage
7684
var err = j.Unmarshal(&m)
7785
if err != nil {
@@ -81,26 +89,26 @@ func (j JsonText) Value() (driver.Value, error) {
8189
}
8290

8391
// Scan stores the src in *j. No validation is done.
84-
func (j *JsonText) Scan(src interface{}) error {
92+
func (j *JSONText) Scan(src interface{}) error {
8593
var source []byte
8694
switch src.(type) {
8795
case string:
8896
source = []byte(src.(string))
8997
case []byte:
9098
source = src.([]byte)
9199
default:
92-
return errors.New("Incompatible type for JsonText")
100+
return errors.New("Incompatible type for JSONText")
93101
}
94-
*j = JsonText(append((*j)[0:0], source...))
102+
*j = JSONText(append((*j)[0:0], source...))
95103
return nil
96104
}
97105

98106
// Unmarshal unmarshal's the json in j to v, as in json.Unmarshal.
99-
func (j *JsonText) Unmarshal(v interface{}) error {
107+
func (j *JSONText) Unmarshal(v interface{}) error {
100108
return json.Unmarshal([]byte(*j), v)
101109
}
102110

103-
// Pretty printing for JsonText types
104-
func (j JsonText) String() string {
111+
// Pretty printing for JSONText types
112+
func (j JSONText) String() string {
105113
return string(j)
106114
}

types/types_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ func TestGzipText(t *testing.T) {
1717
}
1818
}
1919

20-
func TestJsonText(t *testing.T) {
21-
j := JsonText(`{"foo": 1, "bar": 2}`)
20+
func TestJSONText(t *testing.T) {
21+
j := JSONText(`{"foo": 1, "bar": 2}`)
2222
v, err := j.Value()
2323
if err != nil {
2424
t.Errorf("Was not expecting an error")
@@ -34,7 +34,7 @@ func TestJsonText(t *testing.T) {
3434
t.Errorf("Expected valid json but got some garbage instead? %#v", m)
3535
}
3636

37-
j = JsonText(`{"foo": 1, invalid, false}`)
37+
j = JSONText(`{"foo": 1, invalid, false}`)
3838
v, err = j.Value()
3939
if err == nil {
4040
t.Errorf("Was expecting invalid json to fail!")

0 commit comments

Comments
 (0)