diff --git a/go/ngql/query.go b/go/ngql/query.go index 22baeb6fe6..b9f8c7998f 100644 --- a/go/ngql/query.go +++ b/go/ngql/query.go @@ -9,16 +9,18 @@ import ( "encoding/json" "io" - "github.com/attic-labs/noms/go/d" - "github.com/attic-labs/noms/go/types" "github.com/attic-labs/graphql" "github.com/attic-labs/graphql/gqlerrors" + "github.com/attic-labs/noms/go/d" + "github.com/attic-labs/noms/go/types" ) const ( atKey = "at" countKey = "count" + elementsKey = "elements" keyKey = "key" + rootKey = "root" rootQueryKey = "Root" scalarValue = "scalarValue" sizeKey = "size" @@ -26,8 +28,6 @@ const ( targetValueKey = "targetValue" tmKey = "tm" valueKey = "value" - rootKey = "root" - elementsKey = "elements" vrKey = "vr" ) diff --git a/go/ngql/query_test.go b/go/ngql/query_test.go index 3e4b36739b..cd56769c2d 100644 --- a/go/ngql/query_test.go +++ b/go/ngql/query_test.go @@ -104,6 +104,12 @@ func (suite *QueryGraphQLSuite) TestListBasic() { suite.assertQueryResult(list, "{root{elements}}", `{"data":{"root":{"elements":[1,1.1,-100]}}}`) suite.assertQueryResult(list, "{root{elements(at:1,count:2)}}", `{"data":{"root":{"elements":[1.1,-100]}}}`) + + list = types.NewList(types.String("a"), types.String("b"), types.String("c")) + suite.assertQueryResult(list, "{root{elements(at:4)}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(list, "{root{elements(count:0)}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(list, "{root{elements(count:10)}}", `{"data":{"root":{"elements":["a","b","c"]}}}`) + suite.assertQueryResult(list, "{root{elements(at:-1)}}", `{"data":{"root":{"elements":["a","b","c"]}}}`) } func (suite *QueryGraphQLSuite) TestListOfStruct() { @@ -147,6 +153,18 @@ func (suite *QueryGraphQLSuite) TestSetBasic() { suite.assertQueryResult(set, "{root{elements}}", `{"data":{"root":{"elements":[-100,1,1.1]}}}`) suite.assertQueryResult(set, "{root{elements(count:2)}}", `{"data":{"root":{"elements":[-100,1]}}}`) + + set = types.NewSet(types.String("a"), types.String("b"), types.String("c"), types.String("d")) + suite.assertQueryResult(set, "{root{elements(count:0)}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(set, "{root{elements(count:2)}}", `{"data":{"root":{"elements":["a","b"]}}}`) + + suite.assertQueryResult(set, "{root{elements(at:0,count:2)}}", `{"data":{"root":{"elements":["a","b"]}}}`) + suite.assertQueryResult(set, "{root{elements(at:-1,count:2)}}", `{"data":{"root":{"elements":["a","b"]}}}`) + suite.assertQueryResult(set, "{root{elements(at:1,count:2)}}", `{"data":{"root":{"elements":["b","c"]}}}`) + suite.assertQueryResult(set, "{root{elements(at:2)}}", `{"data":{"root":{"elements":["c","d"]}}}`) + suite.assertQueryResult(set, "{root{elements(at:2,count:1)}}", `{"data":{"root":{"elements":["c"]}}}`) + suite.assertQueryResult(set, "{root{elements(at:2,count:0)}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(set, "{root{elements(at:2,count:10)}}", `{"data":{"root":{"elements":["c","d"]}}}`) } func (suite *QueryGraphQLSuite) TestSetOfStruct() { @@ -175,15 +193,29 @@ func (suite *QueryGraphQLSuite) TestMapBasic() { suite.assertQueryResult(m, "{root{elements}}", `{"data":{"root":{}}}`) m = types.NewMap( - types.String("foo"), types.Number(1), - types.String("bar"), types.Number(2), - types.String("baz"), types.Number(3), + types.String("a"), types.Number(1), + types.String("b"), types.Number(2), + types.String("c"), types.Number(3), + types.String("d"), types.Number(4), ) - suite.assertQueryResult(m, "{root{elements{key value}}}", `{"data":{"root":{"elements":[{"key":"bar","value":2},{"key":"baz","value":3},{"key":"foo","value":1}]}}}`) - suite.assertQueryResult(m, "{root{size}}", `{"data":{"root":{"size":3}}}`) - suite.assertQueryResult(m, "{root{elements(count:2){value}}}", `{"data":{"root":{"elements":[{"value":2},{"value":3}]}}}`) - suite.assertQueryResult(m, "{root{elements(count:3){key}}}", `{"data":{"root":{"elements":[{"key":"bar"},{"key":"baz"},{"key":"foo"}]}}}`) + suite.assertQueryResult(m, "{root{elements{key value}}}", `{"data":{"root":{"elements":[{"key":"a","value":1},{"key":"b","value":2},{"key":"c","value":3},{"key":"d","value":4}]}}}`) + suite.assertQueryResult(m, "{root{size}}", `{"data":{"root":{"size":4}}}`) + + suite.assertQueryResult(m, "{root{elements(count:0){value}}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(m, "{root{elements(count:2){value}}}", `{"data":{"root":{"elements":[{"value":1},{"value":2}]}}}`) + suite.assertQueryResult(m, "{root{elements(count:3){key}}}", `{"data":{"root":{"elements":[{"key":"a"},{"key":"b"},{"key":"c"}]}}}`) + suite.assertQueryResult(m, "{root{elements(count: -1){key}}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(m, "{root{elements(count:5){value}}}", `{"data":{"root":{"elements":[{"value":1},{"value":2},{"value":3},{"value":4}]}}}`) + + suite.assertQueryResult(m, "{root{elements(at:-1,count:2){value}}}", `{"data":{"root":{"elements":[{"value":1},{"value":2}]}}}`) + suite.assertQueryResult(m, "{root{elements(at:0,count:2){value}}}", `{"data":{"root":{"elements":[{"value":1},{"value":2}]}}}`) + suite.assertQueryResult(m, "{root{elements(at:1,count:2){value}}}", `{"data":{"root":{"elements":[{"value":2},{"value":3}]}}}`) + suite.assertQueryResult(m, "{root{elements(at:2){value}}}", `{"data":{"root":{"elements":[{"value":3},{"value":4}]}}}`) + suite.assertQueryResult(m, "{root{elements(at:2,count:1){value}}}", `{"data":{"root":{"elements":[{"value":3}]}}}`) + suite.assertQueryResult(m, "{root{elements(at:2,count:0){value}}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(m, "{root{elements(at:5){value}}}", `{"data":{"root":{"elements":[]}}}`) + suite.assertQueryResult(m, "{root{elements(at:2,count:10){value}}}", `{"data":{"root":{"elements":[{"value":3},{"value":4}]}}}`) } func (suite *QueryGraphQLSuite) TestMapOfStruct() { diff --git a/go/ngql/types.go b/go/ngql/types.go index d02bd4a7ab..47b2375246 100644 --- a/go/ngql/types.go +++ b/go/ngql/types.go @@ -10,10 +10,10 @@ import ( "strings" + "github.com/attic-labs/graphql" "github.com/attic-labs/noms/go/d" "github.com/attic-labs/noms/go/hash" "github.com/attic-labs/noms/go/types" - "github.com/attic-labs/graphql" ) type typeMap map[typeMapKey]graphql.Type @@ -222,29 +222,35 @@ var listArgs = graphql.FieldConfigArgument{ countKey: &graphql.ArgumentConfig{Type: graphql.Int}, } -func getListValues(v types.Value, args map[string]interface{}) (interface{}, error) { - l := v.(types.List) - idx := uint64(0) - count := l.Len() +func getBounds(l uint64, args map[string]interface{}) (uint64, uint64, bool) { + len := int64(l) + idx := int64(0) + count := int64(len) if at, ok := args[atKey].(int); ok { - idx = uint64(at) + idx = int64(at) } if c, ok := args[countKey].(int); ok { - count = uint64(c) + count = int64(c) } // Clamp ranges + if count <= 0 || idx >= len { + return 0, 0, true + } if idx < 0 { idx = 0 } - if idx > l.Len() { - idx = l.Len() + if idx+count > len { + count = len - idx } - if count < 0 { - count = 0 - } - if idx+count > l.Len() { - count = l.Len() - idx + return uint64(idx), uint64(count), false +} + +func getListValues(v types.Value, args map[string]interface{}) (interface{}, error) { + l := v.(types.List) + idx, count, empty := getBounds(l.Len(), args) + if empty { + return ([]interface{})(nil), nil } values := make([]interface{}, count) @@ -257,63 +263,46 @@ func getListValues(v types.Value, args map[string]interface{}) (interface{}, err } var setArgs = graphql.FieldConfigArgument{ + atKey: &graphql.ArgumentConfig{Type: graphql.Int}, countKey: &graphql.ArgumentConfig{Type: graphql.Int}, } func getSetValues(v types.Value, args map[string]interface{}) (interface{}, error) { + // TODO: Refactor to share code between the collections. s := v.(types.Set) - - count := s.Len() - if c, ok := args[countKey].(int); ok { - count = uint64(c) - } - - // Clamp ranges - if count < 0 { - count = 0 - } - if count > s.Len() { - count = s.Len() + idx, count, empty := getBounds(s.Len(), args) + if empty { + return ([]interface{})(nil), nil } values := make([]interface{}, count) - i := uint64(0) - s.Iter(func(v types.Value) bool { - values[i] = maybeGetScalar(v) - i++ - return i >= count - }) + iter := s.IteratorAt(idx) + for i := uint64(0); i < count; i++ { + values[i] = maybeGetScalar(iter.Next()) + } return values, nil } var mapArgs = graphql.FieldConfigArgument{ + atKey: &graphql.ArgumentConfig{Type: graphql.Int}, countKey: &graphql.ArgumentConfig{Type: graphql.Int}, } func getMapValues(v types.Value, args map[string]interface{}) (interface{}, error) { + // TODO: Refactor to share code between the collections. m := v.(types.Map) - - count := m.Len() - if c, ok := args[countKey].(int); ok { - count = uint64(c) - } - - // Clamp ranges - if count < 0 { - count = 0 - } - if count > m.Len() { - count = m.Len() + idx, count, empty := getBounds(m.Len(), args) + if empty { + return ([]interface{})(nil), nil } values := make([]mapEntry, count) - i := uint64(0) - m.Iter(func(k, v types.Value) bool { + iter := m.IteratorAt(idx) + for i := uint64(0); i < count; i++ { + k, v := iter.Next() values[i] = mapEntry{k, v} - i++ - return i >= count - }) + } return values, nil }