mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-28 12:40:16 -05:00
Union of scalar and empty struct (#3160)
Add support for unions containing scalar values by "boxing" those scalars in that context. Also add a hash field to Struct so that empty structs have at least one field
This commit is contained in:
Executable
BIN
Binary file not shown.
+3
-2
@@ -20,6 +20,7 @@ const (
|
||||
countKey = "count"
|
||||
keyKey = "key"
|
||||
rootQueryKey = "Root"
|
||||
scalarValue = "scalarValue"
|
||||
sizeKey = "size"
|
||||
targetHashKey = "targetHash"
|
||||
targetValueKey = "targetValue"
|
||||
@@ -30,9 +31,9 @@ const (
|
||||
vrKey = "vr"
|
||||
)
|
||||
|
||||
func constructQueryType(rootValue types.Value, tm typeMap) *graphql.Object {
|
||||
func constructQueryType(rootValue types.Value, tm *typeMap) *graphql.Object {
|
||||
rootNomsType := rootValue.Type()
|
||||
rootType := nomsTypeToGraphQLType(rootNomsType, tm)
|
||||
rootType := nomsTypeToGraphQLType(rootNomsType, false, tm)
|
||||
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: rootQueryKey,
|
||||
|
||||
@@ -62,6 +62,12 @@ func (suite *QueryGraphQLSuite) TestStructBasic() {
|
||||
suite.assertQueryResult(s1, "{root{a c}}", `{"data":{"root":{"a":"aaa","c":0.1}}}`)
|
||||
}
|
||||
|
||||
func (suite *QueryGraphQLSuite) TestEmptyStruct() {
|
||||
s1 := types.NewStruct("", types.StructData{})
|
||||
|
||||
suite.assertQueryResult(s1, "{root{hash}}", `{"data":{"root":{"hash":"c66c33bb6na2m5mk0bek7eqqrl2t7gmv"}}}`)
|
||||
}
|
||||
|
||||
func (suite *QueryGraphQLSuite) TestEmbeddedStruct() {
|
||||
s1 := types.NewStruct("Foo", types.StructData{
|
||||
"a": types.String("aaa"),
|
||||
@@ -237,6 +243,32 @@ func (suite *QueryGraphQLSuite) TestListOfUnionOfStructs() {
|
||||
suite.assertQueryResult(list, "{root{elements{... on FooStruct{a b} ... on BarStruct{b} ... on BazStruct{c}}}}", `{"data":{"root":{"elements":[{"a":28,"b":"baz"},{"b":"bar"},{"c":true}]}}}`)
|
||||
}
|
||||
|
||||
func (suite *QueryGraphQLSuite) TestListOfUnionOfStructsConflictingFieldTypes() {
|
||||
list := types.NewList(
|
||||
types.NewStruct("Foo", types.StructData{
|
||||
"a": types.Number(28),
|
||||
}),
|
||||
types.NewStruct("Bar", types.StructData{
|
||||
"a": types.String("bar"),
|
||||
}),
|
||||
types.NewStruct("Baz", types.StructData{
|
||||
"a": types.Bool(true),
|
||||
}),
|
||||
)
|
||||
|
||||
suite.assertQueryResult(list, "{root{elements{... on FooStruct{a} ... on BarStruct{b: a} ... on BazStruct{c: a}}}}", `{"data":{"root":{"elements":[{"a":28},{"b":"bar"},{"c":true}]}}}`)
|
||||
}
|
||||
|
||||
func (suite *QueryGraphQLSuite) TestListOfUnionOfScalars() {
|
||||
list := types.NewList(
|
||||
types.Number(28),
|
||||
types.String("bar"),
|
||||
types.Bool(true),
|
||||
)
|
||||
|
||||
suite.assertQueryResult(list, "{root{elements{... on BooleanValue{b: scalarValue} ... on StringValue{s: scalarValue} ... on NumberValue{n: scalarValue}}}}", `{"data":{"root":{"elements":[{"n":28},{"s":"bar"},{"b":true}]}}}`)
|
||||
}
|
||||
|
||||
func (suite *QueryGraphQLSuite) TestCyclicStructs() {
|
||||
typ := types.MakeStructTypeFromFields("A", types.FieldMap{
|
||||
"a": types.StringType,
|
||||
|
||||
+79
-27
@@ -16,10 +16,15 @@ import (
|
||||
"github.com/graphql-go/graphql"
|
||||
)
|
||||
|
||||
type typeMap *map[hash.Hash]graphql.Type
|
||||
type typeMap map[typeMapKey]graphql.Type
|
||||
|
||||
func newTypeMap() typeMap {
|
||||
return &map[hash.Hash]graphql.Type{}
|
||||
type typeMapKey struct {
|
||||
h hash.Hash
|
||||
boxedIfScalar bool
|
||||
}
|
||||
|
||||
func newTypeMap() *typeMap {
|
||||
return &typeMap{}
|
||||
}
|
||||
|
||||
// In terms of resolving a graph of data, there are three types of value: scalars, lists and maps.
|
||||
@@ -32,9 +37,29 @@ type getFieldFn func(v interface{}, fieldName string, ctx context.Context) types
|
||||
// returning one or more *noms* values whose presence is indicated by the provided arguments.
|
||||
type getSubvaluesFn func(v types.Value, args map[string]interface{}) (interface{}, error)
|
||||
|
||||
// GraphQL requires all memberTypes in a Union to be Structs, so when a noms union contains a
|
||||
// scalar, we represent it in that context as a "boxed" value. E.g.
|
||||
// Boolean! =>
|
||||
// type BooleanValue {
|
||||
// scalarValue: Boolean!
|
||||
// }
|
||||
func scalarToValue(nomsType *types.Type, scalarType graphql.Type, tm *typeMap) graphql.Type {
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: fmt.Sprintf("%sValue", getTypeName(nomsType)),
|
||||
Fields: graphql.Fields{
|
||||
scalarValue: &graphql.Field{
|
||||
Type: graphql.NewNonNull(scalarType),
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
return p.Source, nil // p.Source is already a go-native scalar type
|
||||
},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
// Note: Always returns a graphql.NonNull() as the outer type.
|
||||
func nomsTypeToGraphQLType(nomsType *types.Type, tm typeMap) graphql.Type {
|
||||
gqlType, ok := (*tm)[nomsType.Hash()]
|
||||
func nomsTypeToGraphQLType(nomsType *types.Type, boxedIfScalar bool, tm *typeMap) graphql.Type {
|
||||
key := typeMapKey{nomsType.Hash(), boxedIfScalar}
|
||||
gqlType, ok := (*tm)[key]
|
||||
if ok {
|
||||
return gqlType
|
||||
}
|
||||
@@ -43,17 +68,26 @@ func nomsTypeToGraphQLType(nomsType *types.Type, tm typeMap) graphql.Type {
|
||||
// creating any subtypes. Since all noms-types are non-nullable, the graphql NonNull creates a
|
||||
// handy piece of state for us to mutate once the subtype is fully created
|
||||
newNonNull := &graphql.NonNull{}
|
||||
(*tm)[nomsType.Hash()] = newNonNull
|
||||
(*tm)[key] = newNonNull
|
||||
|
||||
switch nomsType.Kind() {
|
||||
case types.NumberKind:
|
||||
newNonNull.OfType = graphql.Float
|
||||
if boxedIfScalar {
|
||||
newNonNull.OfType = scalarToValue(nomsType, newNonNull.OfType, tm)
|
||||
}
|
||||
|
||||
case types.StringKind:
|
||||
newNonNull.OfType = graphql.String
|
||||
if boxedIfScalar {
|
||||
newNonNull.OfType = scalarToValue(nomsType, newNonNull.OfType, tm)
|
||||
}
|
||||
|
||||
case types.BoolKind:
|
||||
newNonNull.OfType = graphql.Boolean
|
||||
if boxedIfScalar {
|
||||
newNonNull.OfType = scalarToValue(nomsType, newNonNull.OfType, tm)
|
||||
}
|
||||
|
||||
case types.StructKind:
|
||||
newNonNull.OfType = structToGQLObject(nomsType, tm)
|
||||
@@ -62,7 +96,7 @@ func nomsTypeToGraphQLType(nomsType *types.Type, tm typeMap) graphql.Type {
|
||||
nomsValueType := nomsType.Desc.(types.CompoundDesc).ElemTypes[0]
|
||||
var valueType graphql.Type
|
||||
if !isEmptyNomsUnion(nomsValueType) {
|
||||
valueType = nomsTypeToGraphQLType(nomsValueType, tm)
|
||||
valueType = nomsTypeToGraphQLType(nomsValueType, false, tm)
|
||||
}
|
||||
|
||||
newNonNull.OfType = collectionToGraphQLObject(nomsType, valueType, tm)
|
||||
@@ -102,36 +136,54 @@ func isEmptyNomsUnion(nomsType *types.Type) bool {
|
||||
}
|
||||
|
||||
// Creates a union of structs type.
|
||||
func unionToGQLUnion(nomsType *types.Type, tm typeMap) *graphql.Union {
|
||||
func unionToGQLUnion(nomsType *types.Type, tm *typeMap) *graphql.Union {
|
||||
nomsMemberTypes := nomsType.Desc.(types.CompoundDesc).ElemTypes
|
||||
memberTypes := make([]*graphql.Object, len(nomsMemberTypes))
|
||||
|
||||
for i, nomsUnionType := range nomsMemberTypes {
|
||||
if nomsUnionType.Kind() != types.StructKind {
|
||||
panic("booh: grqphql-go only supports unions of structs")
|
||||
}
|
||||
|
||||
memberTypes[i] = nomsTypeToGraphQLType(nomsUnionType, tm).(*graphql.NonNull).OfType.(*graphql.Object)
|
||||
// Member types cannot be non-null and must be struct (graphl.Object)
|
||||
memberTypes[i] = nomsTypeToGraphQLType(nomsUnionType, true, tm).(*graphql.NonNull).OfType.(*graphql.Object)
|
||||
}
|
||||
|
||||
return graphql.NewUnion(graphql.UnionConfig{
|
||||
Name: getTypeName(nomsType),
|
||||
Types: memberTypes,
|
||||
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
|
||||
tm := p.Context.Value(tmKey).(typeMap)
|
||||
nomsType := p.Value.(types.Value).Type()
|
||||
gqlType := (*tm)[nomsType.Hash()].(*graphql.NonNull).OfType.(*graphql.Object)
|
||||
return gqlType
|
||||
tm := p.Context.Value(tmKey).(*typeMap)
|
||||
var nomsType *types.Type
|
||||
if v, ok := p.Value.(types.Value); ok {
|
||||
nomsType = v.Type()
|
||||
} else {
|
||||
switch p.Value.(type) {
|
||||
case float64:
|
||||
nomsType = types.NumberType
|
||||
case string:
|
||||
nomsType = types.StringType
|
||||
case bool:
|
||||
nomsType = types.BoolType
|
||||
}
|
||||
}
|
||||
key := typeMapKey{nomsType.Hash(), true}
|
||||
memberType := (*tm)[key]
|
||||
// Member types cannot be non-null and must be struct (graphl.Object)
|
||||
return memberType.(*graphql.NonNull).OfType.(*graphql.Object)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func structToGQLObject(nomsType *types.Type, tm typeMap) *graphql.Object {
|
||||
func structToGQLObject(nomsType *types.Type, tm *typeMap) *graphql.Object {
|
||||
structDesc := nomsType.Desc.(types.StructDesc)
|
||||
fields := graphql.Fields{}
|
||||
fields := graphql.Fields{
|
||||
"hash": &graphql.Field{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
return p.Source.(types.Struct).Hash().String(), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
structDesc.IterFields(func(name string, nomsFieldType *types.Type) {
|
||||
fieldType := nomsTypeToGraphQLType(nomsFieldType, tm)
|
||||
fieldType := nomsTypeToGraphQLType(nomsFieldType, false, tm)
|
||||
|
||||
fields[name] = &graphql.Field{
|
||||
Type: fieldType,
|
||||
@@ -260,9 +312,9 @@ type mapEntry struct {
|
||||
// key: <KeyType>!
|
||||
// value: <ValueType>!
|
||||
// }
|
||||
func mapEntryToGraphQLObject(nomsKeyType, nomsValueType *types.Type, tm typeMap) *graphql.Object {
|
||||
keyType := nomsTypeToGraphQLType(nomsKeyType, tm)
|
||||
valueType := nomsTypeToGraphQLType(nomsValueType, tm)
|
||||
func mapEntryToGraphQLObject(nomsKeyType, nomsValueType *types.Type, tm *typeMap) *graphql.Object {
|
||||
keyType := nomsTypeToGraphQLType(nomsKeyType, false, tm)
|
||||
valueType := nomsTypeToGraphQLType(nomsValueType, false, tm)
|
||||
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: fmt.Sprintf("%s%sEntry", getTypeName(nomsKeyType), getTypeName(nomsValueType)),
|
||||
@@ -345,7 +397,7 @@ func getTypeName(nomsType *types.Type) string {
|
||||
}
|
||||
}
|
||||
|
||||
func collectionToGraphQLObject(nomsType *types.Type, listType graphql.Type, tm typeMap) *graphql.Object {
|
||||
func collectionToGraphQLObject(nomsType *types.Type, listType graphql.Type, tm *typeMap) *graphql.Object {
|
||||
fields := graphql.Fields{
|
||||
sizeKey: &graphql.Field{
|
||||
Type: graphql.Float,
|
||||
@@ -396,15 +448,15 @@ func collectionToGraphQLObject(nomsType *types.Type, listType graphql.Type, tm t
|
||||
// targetHash: String!
|
||||
// targetValue: <ValueType>!
|
||||
// }
|
||||
func refToGraphQLObject(nomsType *types.Type, tm typeMap) *graphql.Object {
|
||||
func refToGraphQLObject(nomsType *types.Type, tm *typeMap) *graphql.Object {
|
||||
nomsTargetType := nomsType.Desc.(types.CompoundDesc).ElemTypes[0]
|
||||
targetType := nomsTypeToGraphQLType(nomsTargetType, tm)
|
||||
targetType := nomsTypeToGraphQLType(nomsTargetType, false, tm)
|
||||
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: getTypeName(nomsType),
|
||||
Fields: graphql.Fields{
|
||||
targetHashKey: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
r := p.Source.(types.Ref)
|
||||
return maybeGetScalar(types.String(r.TargetHash().String())), nil
|
||||
|
||||
Reference in New Issue
Block a user