Skip to content

Commit

Permalink
Skipping floats that cannot be marshalled (#5163)
Browse files Browse the repository at this point in the history
  • Loading branch information
gja authored and danielmai committed Apr 24, 2020
1 parent d5fe189 commit 8f55a6c
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 64 deletions.
60 changes: 60 additions & 0 deletions query/fastjson_test.go
@@ -0,0 +1,60 @@
package query

import (
"math"
"testing"

"github.com/dgraph-io/dgraph/protos/pb"
"github.com/dgraph-io/dgraph/task"
"github.com/stretchr/testify/require"
)

func subgraphWithSingleResultAndSingleValue(val *pb.TaskValue) *SubGraph {
return &SubGraph{
Params: params{Alias: "query"},
SrcUIDs: &pb.List{Uids: []uint64{1}},
DestUIDs: &pb.List{Uids: []uint64{1}},
uidMatrix: []*pb.List{&pb.List{Uids: []uint64{1}}},
Children: []*SubGraph{
&SubGraph{
Attr: "val",
SrcUIDs: &pb.List{Uids: []uint64{1}},
uidMatrix: []*pb.List{&pb.List{}},
valueMatrix: []*pb.ValueList{
// UID 1
&pb.ValueList{
Values: []*pb.TaskValue{val},
},
},
},
},
}
}

func assertJSON(t *testing.T, expected string, sg *SubGraph) {
buf, err := ToJson(&Latency{}, []*SubGraph{sg})
require.Nil(t, err)
require.Equal(t, expected, string(buf))
}

func TestSubgraphToFastJSON(t *testing.T) {
t.Run("With a string result", func(t *testing.T) {
sg := subgraphWithSingleResultAndSingleValue(task.FromString("ABC"))
assertJSON(t, `{"query":[{"val":"ABC"}]}`, sg)
})

t.Run("With an integer result", func(t *testing.T) {
sg := subgraphWithSingleResultAndSingleValue(task.FromInt(42))
assertJSON(t, `{"query":[{"val":42}]}`, sg)
})

t.Run("With a valid float result", func(t *testing.T) {
sg := subgraphWithSingleResultAndSingleValue(task.FromFloat(42.0))
assertJSON(t, `{"query":[{"val":42.000000}]}`, sg)
})

t.Run("With invalid floating points", func(t *testing.T) {
assertJSON(t, `{"query":[]}`, subgraphWithSingleResultAndSingleValue(task.FromFloat(math.NaN())))
assertJSON(t, `{"query":[]}`, subgraphWithSingleResultAndSingleValue(task.FromFloat(math.Inf(1))))
})
}
128 changes: 64 additions & 64 deletions query/outputnode.go
Expand Up @@ -340,77 +340,55 @@ func (fj *fastJsonNode) encode(out *bytes.Buffer) error {
a.order = i
}

// This is a scalar value
if len(fj.attrs) == 0 {
_, err := out.Write(fj.scalarVal)
return err
}

i := 0
if i < len(fj.attrs) {
if _, err := out.WriteRune('{'); err != nil {
return err
if _, err := out.WriteRune('{'); err != nil {
return err
}
cur := fj.attrs[i]
i++
cnt := 1
last := false
inArray := false
for {
var next *fastJsonNode
if i < len(fj.attrs) {
next = fj.attrs[i]
i++
} else {
last = true
}
cur := fj.attrs[i]
i++
cnt := 1
last := false
inArray := false
for {
var next *fastJsonNode
if i < len(fj.attrs) {
next = fj.attrs[i]
i++
} else {
last = true
}

if !last {
if cur.attr == next.attr {
if cnt == 1 {
if err := cur.writeKey(out); err != nil {
return err
}
if _, err := out.WriteRune('['); err != nil {
return err
}
inArray = true
}
if err := cur.encode(out); err != nil {
if !last {
if cur.attr == next.attr {
if cnt == 1 {
if err := cur.writeKey(out); err != nil {
return err
}
cnt++
} else {
if cnt == 1 {
if err := cur.writeKey(out); err != nil {
return err
}
if cur.list {
if _, err := out.WriteRune('['); err != nil {
return err
}
inArray = true
}
}
if err := cur.encode(out); err != nil {
if _, err := out.WriteRune('['); err != nil {
return err
}
if cnt != 1 || cur.list {
if _, err := out.WriteRune(']'); err != nil {
return err
}
inArray = false
}
cnt = 1
inArray = true
}
if _, err := out.WriteRune(','); err != nil {
if err := cur.encode(out); err != nil {
return err
}

cur = next
cnt++
} else {
if cnt == 1 {
if err := cur.writeKey(out); err != nil {
return err
}
}
if cur.list && !inArray {
if _, err := out.WriteRune('['); err != nil {
return err
if cur.list {
if _, err := out.WriteRune('['); err != nil {
return err
}
inArray = true
}
}
if err := cur.encode(out); err != nil {
Expand All @@ -420,17 +398,39 @@ func (fj *fastJsonNode) encode(out *bytes.Buffer) error {
if _, err := out.WriteRune(']'); err != nil {
return err
}
inArray = false
}
break
cnt = 1
}
if _, err := out.WriteRune(','); err != nil {
return err
}

cur = next
} else {
if cnt == 1 {
if err := cur.writeKey(out); err != nil {
return err
}
}
if cur.list && !inArray {
if _, err := out.WriteRune('['); err != nil {
return err
}
}
if err := cur.encode(out); err != nil {
return err
}
if cnt != 1 || cur.list {
if _, err := out.WriteRune(']'); err != nil {
return err
}
}
break
}
if _, err := out.WriteRune('}'); err != nil {
return err
}
} else {
if _, err := out.Write(fj.scalarVal); err != nil {
return err
}
}
if _, err := out.WriteRune('}'); err != nil {
return err
}

return nil
Expand Down
20 changes: 20 additions & 0 deletions query/query.go
Expand Up @@ -216,6 +216,26 @@ type Function struct {

// SubGraph is the way to represent data. It contains both the request parameters and the response.
// Once generated, this can then be encoded to other client convenient formats, like GraphQL / JSON.
// SubGraphs are recursively nested. Each SubGraph contain the following:
// * SrcUIDS: A list of UIDs that were sent to this query. If this subgraph is a child graph, then the
// DestUIDs of the parent must match the SrcUIDs of the children.
// * DestUIDs: A list of UIDs for which there can be output found in the Children field
// * Children: A list of child results for this query
// * valueMatrix: A list of values, against a single attribute, such as name (for a scalar subgraph).
// This must be the same length as the SrcUIDs
// * uidMatrix: A list of outgoing edges. This must be same length as the SrcUIDs list.
// Example, say we are creating a SubGraph for a query "users", which returns one user with name 'Foo', you may get
// SubGraph
// Params: { Alias: "users" }
// SrcUIDs: [1]
// DestUIDs: [1]
// uidMatrix: [[1]]
// Children:
// SubGraph:
// Attr: "name"
// SrcUIDs: [1]
// uidMatrix: [[]]
// valueMatrix: [["Foo"]]
type SubGraph struct {
ReadTs uint64
Cache int
Expand Down
28 changes: 28 additions & 0 deletions task/conversion.go
Expand Up @@ -18,6 +18,7 @@ package task

import (
"encoding/binary"
"math"

"github.com/dgraph-io/dgraph/protos/pb"
)
Expand All @@ -37,6 +38,7 @@ func FromInt(val int) *pb.TaskValue {
}

// ToInt converts the given pb.TaskValue object into an integer.
// Note, this panics if there are not enough bytes in val.Val
func ToInt(val *pb.TaskValue) int64 {
result := binary.LittleEndian.Uint64(val.Val)
return int64(result)
Expand All @@ -58,3 +60,29 @@ func ToBool(val *pb.TaskValue) bool {
result := ToInt(val)
return result != 0
}

// FromString converts the given string in to a pb.TaskValue object.
func FromString(val string) *pb.TaskValue {
return &pb.TaskValue{
Val: []byte(val),
ValType: pb.Posting_STRING,
}
}

// ToString converts the given pb.TaskValue object into a string.
func ToString(val *pb.TaskValue) string {
return string(val.Val)
}

// FromFloat converts the given float64 value into a pb.TaskValue object.
func FromFloat(val float64) *pb.TaskValue {
bs := make([]byte, 8)
binary.LittleEndian.PutUint64(bs[:], math.Float64bits(val))
return &pb.TaskValue{Val: []byte(bs), ValType: pb.Posting_FLOAT}
}

// ToFloat converts the given pb.TaskValue object into an integer.
// Note, this panics if there are not enough bytes in val.Val
func ToFloat(val *pb.TaskValue) float64 {
return math.Float64frombits(binary.LittleEndian.Uint64(val.Val))
}

0 comments on commit 8f55a6c

Please sign in to comment.