Skip to content

Commit a28a597

Browse files
authored
Merge pull request #3297 from dolthub/elian/10050
dolthub/dolt#10050: Fix JSON conversion for `dolt` when using `TextStorage`
2 parents e128bf9 + fea27df commit a28a597

File tree

3 files changed

+95
-30
lines changed

3 files changed

+95
-30
lines changed

enginetest/queries/json_scripts.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,35 @@ package queries
1717
import (
1818
"github.com/dolthub/vitess/go/vt/sqlparser"
1919

20+
"github.com/dolthub/go-mysql-server/sql/plan"
21+
2022
"github.com/dolthub/go-mysql-server/sql"
2123
"github.com/dolthub/go-mysql-server/sql/types"
2224
)
2325

2426
var JsonScripts = []ScriptTest{
27+
{
28+
// https://github.com/dolthub/dolt/issues/10050
29+
Name: "TextStorage converts to JSON when using dolt wrapper",
30+
SetUpScript: []string{
31+
"CREATE TABLE pages (id INT PRIMARY KEY, text_col TEXT, text_json JSON)",
32+
"INSERT INTO pages VALUES (1, '{\"message\":\"hello\"}', NULL)",
33+
},
34+
Assertions: []ScriptTestAssertion{
35+
{
36+
Query: "UPDATE pages SET text_json = text_col",
37+
Expected: []sql.Row{
38+
{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}},
39+
},
40+
},
41+
{
42+
Query: "SELECT text_json FROM pages",
43+
Expected: []sql.Row{
44+
{types.MustJSON("{\"message\":\"hello\"}")},
45+
},
46+
},
47+
},
48+
},
2549
{
2650
Name: "json_type scripts",
2751
Assertions: []ScriptTestAssertion{

sql/types/json.go

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,55 @@ func (t JsonType) Compare(ctx context.Context, a interface{}, b interface{}) (in
4545
return CompareJSON(ctx, a, b)
4646
}
4747

48+
// convertJSONValue parses JSON-encoded data if the input is a string or []byte, returning the resulting JSONDocument. For
49+
// other types, the value is returned if it can be marshalled.
50+
func convertJSONValue(v interface{}) (interface{}, sql.ConvertInRange, error) {
51+
var data []byte
52+
var charsetMaxLength int64 = 1
53+
switch x := v.(type) {
54+
case []byte:
55+
data = x
56+
case string:
57+
data = []byte(x)
58+
charsetMaxLength = sql.Collation_Default.CharacterSet().MaxLength()
59+
default:
60+
// if |v| can be marshalled, it contains
61+
// a valid JSON document representation
62+
if b, berr := json.Marshal(v); berr == nil {
63+
data = b
64+
} else {
65+
return nil, sql.InRange, nil
66+
}
67+
}
68+
69+
if int64(len(data))*charsetMaxLength > MaxJsonFieldByteLength {
70+
return nil, sql.InRange, ErrLengthTooLarge.New(len(data), MaxJsonFieldByteLength)
71+
}
72+
73+
var val interface{}
74+
if err := json.Unmarshal(data, &val); err != nil {
75+
return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error())
76+
}
77+
78+
return JSONDocument{Val: val}, sql.InRange, nil
79+
}
80+
4881
// Convert implements Type interface.
49-
func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, inRange sql.ConvertInRange, err error) {
82+
func (t JsonType) Convert(c context.Context, v interface{}) (interface{}, sql.ConvertInRange, error) {
5083
switch v := v.(type) {
5184
case sql.JSONWrapper:
5285
return v, sql.InRange, nil
5386
case []byte:
54-
if int64(len(v)) > MaxJsonFieldByteLength {
55-
return nil, sql.InRange, ErrLengthTooLarge.New(len(v), MaxJsonFieldByteLength)
56-
}
57-
err = json.Unmarshal(v, &doc)
58-
if err != nil {
59-
return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error())
60-
}
87+
return convertJSONValue(v)
6188
case string:
62-
charsetMaxLength := sql.Collation_Default.CharacterSet().MaxLength()
63-
length := int64(len(v)) * charsetMaxLength
64-
if length > MaxJsonFieldByteLength {
65-
return nil, sql.InRange, ErrLengthTooLarge.New(length, MaxJsonFieldByteLength)
66-
}
67-
err = json.Unmarshal([]byte(v), &doc)
89+
return convertJSONValue(v)
90+
// Text values may be stored in wrappers (e.g. Dolt's TextStorage), so unwrap to the raw string before decoding.
91+
case sql.StringWrapper:
92+
str, err := v.Unwrap(c)
6893
if err != nil {
69-
return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error())
94+
return nil, sql.OutOfRange, err
7095
}
96+
return convertJSONValue(str)
7197
case int8:
7298
return JSONDocument{Val: int64(v)}, sql.InRange, nil
7399
case int16:
@@ -91,22 +117,8 @@ func (t JsonType) Convert(c context.Context, v interface{}) (doc interface{}, in
91117
case decimal.Decimal:
92118
return JSONDocument{Val: v}, sql.InRange, nil
93119
default:
94-
// if |v| can be marshalled, it contains
95-
// a valid JSON document representation
96-
if b, berr := json.Marshal(v); berr == nil {
97-
if int64(len(b)) > MaxJsonFieldByteLength {
98-
return nil, sql.InRange, ErrLengthTooLarge.New(len(b), MaxJsonFieldByteLength)
99-
}
100-
err = json.Unmarshal(b, &doc)
101-
if err != nil {
102-
return nil, sql.OutOfRange, sql.ErrInvalidJson.New(err.Error())
103-
}
104-
}
105-
}
106-
if err != nil {
107-
return nil, sql.OutOfRange, err
120+
return convertJSONValue(v)
108121
}
109-
return JSONDocument{Val: doc}, sql.InRange, nil
110122
}
111123

112124
// Equals implements the Type interface.

sql/types/jsontests/json_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,34 @@ import (
2828
"github.com/dolthub/go-mysql-server/sql/types"
2929
)
3030

31+
type mockStringWrapper struct {
32+
val string
33+
}
34+
35+
func (m mockStringWrapper) Unwrap(ctx context.Context) (string, error) {
36+
return m.val, nil
37+
}
38+
39+
func (m mockStringWrapper) UnwrapAny(ctx context.Context) (interface{}, error) {
40+
return m.val, nil
41+
}
42+
43+
func (m mockStringWrapper) IsExactLength() bool {
44+
return false
45+
}
46+
47+
func (m mockStringWrapper) MaxByteLength() int64 {
48+
return int64(len(m.val))
49+
}
50+
51+
func (m mockStringWrapper) Compare(ctx context.Context, other interface{}) (int, bool, error) {
52+
return 0, false, nil
53+
}
54+
55+
func (m mockStringWrapper) Hash() interface{} {
56+
return m.val
57+
}
58+
3159
func TestJsonCompare(t *testing.T) {
3260
RunJsonCompareTests(t, JsonCompareTests, func(t *testing.T, left, right interface{}) (interface{}, interface{}) {
3361
return ConvertToJson(t, left), ConvertToJson(t, right)
@@ -58,6 +86,7 @@ func TestJsonConvert(t *testing.T) {
5886
{types.MustJSON(`{"field":"test"}`), types.MustJSON(`{"field":"test"}`), false},
5987
{[]string{}, types.MustJSON(`[]`), false},
6088
{[]string{`555-555-5555`}, types.MustJSON(`["555-555-5555"]`), false},
89+
{mockStringWrapper{val: `{"c": 1}`}, types.MustJSON(`{"c":1}`), false},
6190
}
6291

6392
for _, test := range tests {

0 commit comments

Comments
 (0)