Skip to content

Commit

Permalink
sql: do not marshal strings containing JSON as a string in convert
Browse files Browse the repository at this point in the history
Fixes src-d#709

When a string containing JSON (e.g. `{"a": 1, "b": true}`) is passed
to JSON.Convert, it only did `json.Marshal`, so it was marshalled as
a string (e.g. `"{\"a\":1,\"b\":true}"`), which made it impossible
to use a string with JSON_EXTRACT, which would only receive the
string.

Now, JSON.Convert does a first check for strings. If it can be
unmarshalled into JSON, then that JSON is marshalled and returned.
Otherwise, it's a string and marshalled as such.

Signed-off-by: Miguel Molina <miguel@erizocosmi.co>
  • Loading branch information
erizocosmico committed May 14, 2019
1 parent 33c1da4 commit 4cb8e85
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 7 deletions.
4 changes: 4 additions & 0 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,10 @@ var queries = []struct {
`SELECT i AS foo FROM mytable ORDER BY mytable.i`,
[]sql.Row{{int64(1)}, {int64(2)}, {int64(3)}},
},
{
`SELECT JSON_EXTRACT('[1, 2, 3]', '$.[0]')`,
[]sql.Row{{float64(1)}},
},
}

func TestQueries(t *testing.T) {
Expand Down
21 changes: 15 additions & 6 deletions sql/expression/function/json_extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,11 @@ func (j *JSONExtract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
return nil, err
}

js, err = sql.JSON.Convert(js)
doc, err := unmarshalVal(js)
if err != nil {
return nil, err
}

var doc interface{}
if err := json.Unmarshal(js.([]byte), &doc); err != nil {
return nil, err
}

var result = make([]interface{}, len(j.Paths))
for i, p := range j.Paths {
path, err := p.Eval(ctx, row)
Expand Down Expand Up @@ -84,6 +79,20 @@ func (j *JSONExtract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
return result, nil
}

func unmarshalVal(v interface{}) (interface{}, error) {
v, err := sql.JSON.Convert(v)
if err != nil {
return nil, err
}

var doc interface{}
if err := json.Unmarshal(v.([]byte), &doc); err != nil {
return nil, err
}

return doc, nil
}

// IsNullable implements the sql.Expression interface.
func (j *JSONExtract) IsNullable() bool {
for _, p := range j.Paths {
Expand Down
11 changes: 10 additions & 1 deletion sql/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,16 @@ func (t jsonT) SQL(v interface{}) sqltypes.Value {

// Convert implements Type interface.
func (t jsonT) Convert(v interface{}) (interface{}, error) {
return json.Marshal(v)
switch v := v.(type) {
case string:
var doc interface{}
if err := json.Unmarshal([]byte(v), &doc); err != nil {
return json.Marshal(v)
}
return json.Marshal(doc)
default:
return json.Marshal(v)
}
}

// Compare implements Type interface.
Expand Down
1 change: 1 addition & 0 deletions sql/type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func TestBlob(t *testing.T) {
func TestJSON(t *testing.T) {
convert(t, JSON, "", []byte(`""`))
convert(t, JSON, []int{1, 2}, []byte("[1,2]"))
convert(t, JSON, `{"a": true, "b": 3}`, []byte(`{"a":true,"b":3}`))

lt(t, JSON, []byte("A"), []byte("B"))
eq(t, JSON, []byte("A"), []byte("A"))
Expand Down

0 comments on commit 4cb8e85

Please sign in to comment.