Skip to content

Commit

Permalink
graphql: support un-nesting objects into parent; resolves cayleygraph…
Browse files Browse the repository at this point in the history
  • Loading branch information
dennwc committed Mar 3, 2018
1 parent 1467e17 commit 7f5761b
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 30 deletions.
44 changes: 44 additions & 0 deletions docs/GraphQL.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,48 @@ To expand all properties of an object, `*` can be used instead of property name:
follows {*}
}
}
```

### Un-nest objects

The following query will return objects with `{id: x, status: {name: y}}` structure:

```graphql
{
nodes{
id
status {
name
}
}
}
```

It is possible to un-nest `status` field object into parent:

```graphql
{
nodes{
id
status @unnest {
status: name
}
}
}
```

Resulted objects will have a flat structure: `{id: x, status: y}`.

Arrays fields cannot be un-nested. You can still un-nest such fields by
providing a limit directive (will select the first value from array):

```graphql
{
nodes{
id
statuses(first: 1) @unnest {
status: name
}
}
}
```
81 changes: 52 additions & 29 deletions query/graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,14 @@ type field struct {
Has []has
Fields []field
AllFields bool // fetch all fields
UnNest bool // all fields will be saved to parent object
}

func (f field) isSave() bool { return len(f.Has)+len(f.Fields) == 0 && !f.AllFields }

type object struct {
id graph.Value
fields map[string][]graph.Value
fields map[string]interface{}
}

func buildIterator(qs graph.QuadStore, p *path.Path) graph.Iterator {
Expand Down Expand Up @@ -221,7 +222,11 @@ func iterateObject(ctx context.Context, qs graph.QuadStore, f *field, p *path.Pa
}
return out, it.Err()
}
unnest := make(map[string]bool)
for _, f2 := range f.Fields {
if f2.UnNest {
unnest[f2.Alias] = true
}
if !f2.isSave() {
continue
}
Expand Down Expand Up @@ -251,7 +256,7 @@ func iterateObject(ctx context.Context, qs graph.QuadStore, f *field, p *path.Pa
}
tail()

// load object ids and flat keys
// first, collect result node ids and any tags associated with it (flat values)
it := buildIterator(qs, p)
defer it.Close()

Expand All @@ -265,32 +270,45 @@ func iterateObject(ctx context.Context, qs graph.QuadStore, f *field, p *path.Pa
if !it.Next(ctx) {
break
}
fields := make(map[string][]graph.Value)

tags := make(map[string]graph.Value)
it.TagResults(tags)
obj := object{id: it.Result()}
if len(tags) > 0 {
obj.fields = make(map[string][]graph.Value)
}
for k, v := range tags {
obj.fields[k] = []graph.Value{v}
fields[k] = []graph.Value{v}
}
for it.NextPath(ctx) {
select {
case <-ctx.Done():
return out, ctx.Err()
default:
}
tags := make(map[string]graph.Value)
tags = make(map[string]graph.Value)
it.TagResults(tags)
dedup:
for k, v := range tags {
vals := obj.fields[k]
vals := fields[k]
for _, v2 := range vals {
if graph.ToKey(v) == graph.ToKey(v2) {
continue dedup
}
}
obj.fields[k] = append(vals, v)
fields[k] = append(vals, v)
}
}
obj := object{id: it.Result()}
if len(fields) > 0 {
obj.fields = make(map[string]interface{}, len(fields))
for k, arr := range fields {
vals, err := graph.ValuesOf(ctx, qs, arr)
if err != nil {
return nil, err
}
if len(vals) == 1 {
obj.fields[k] = vals[0]
} else {
obj.fields[k] = vals
}
}
}
results = append(results, obj)
Expand All @@ -299,24 +317,17 @@ func iterateObject(ctx context.Context, qs graph.QuadStore, f *field, p *path.Pa
return out, err
}

// load values and complex keys
// next, load complex objects inside fields
for _, r := range results {
obj := make(map[string]interface{})
for k, arr := range r.fields {
vals, err := graph.ValuesOf(ctx, qs, arr)
if err != nil {
return nil, err
}
if len(vals) == 1 {
obj[k] = vals[0]
} else {
obj[k] = vals
}
obj := r.fields
if obj == nil {
obj = make(map[string]interface{})
}
for _, f2 := range f.Fields {
if f2.isSave() {
continue
continue // skip flat values
}
// start from saved id for a field node
p2 := path.StartPathNodes(qs, r.id)
if len(f2.Labels) != 0 {
p2 = p2.LabelContext(f2.Labels)
Expand All @@ -333,13 +344,23 @@ func iterateObject(ctx context.Context, qs graph.QuadStore, f *field, p *path.Pa
if err != nil {
return out, err
}
var v interface{}
if len(arr) == 1 {
v = arr[0]
} else if len(arr) > 1 {
v = arr
if f2.UnNest {
if len(arr) > 1 {
return nil, fmt.Errorf("cannot unnest more than one object on %q; use (%s: 1) to force",
f2.Alias, LimitKey)
}
for k, v := range arr[0] {
obj[k] = v
}
} else {
var v interface{}
if len(arr) == 1 {
v = arr[0]
} else if len(arr) > 1 {
v = arr
}
obj[f2.Alias] = v
}
obj[f2.Alias] = v
}
out = append(out, obj)
}
Expand Down Expand Up @@ -494,6 +515,8 @@ func convField(fld *ast.Field, labels []quad.Value) (out field, err error) {
out.Opt = true
case "label":
// already processed
case "unnest":
out.UnNest = true
default:
return out, fmt.Errorf("unknown directive: %q", d.Name.Value)
}
Expand Down
29 changes: 28 additions & 1 deletion query/graphql/graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var casesParse = []struct {
sname @label
}
isViewerFriend,
profilePicture(size: 50) {
profilePicture(size: 50) @unnest {
uri,
width @opt,
height @rev
Expand Down Expand Up @@ -75,6 +75,7 @@ var casesParse = []struct {
{Via: "width", Alias: "width", Opt: true},
{Via: "height", Alias: "height", Rev: true},
},
UnNest: true,
},
{Via: "sub", Alias: "sub", AllFields: true},
},
Expand Down Expand Up @@ -220,6 +221,32 @@ var casesExecute = []struct {
},
},
},
{
"unnest object",
`{
me(id: fred) {
id: ` + ValueKey + `
follows @unnest {
friend: ` + ValueKey + `
friend_status: status
followed: follows(` + LimitKey + `: 1) @rev @unnest {
fof: ` + ValueKey + `
}
}
}
}`,
map[string]interface{}{
"me": map[string]interface{}{
"id": quad.IRI("fred"),
"fof": quad.IRI("dani"),
"friend": quad.IRI("greg"),
"friend_status": []quad.Value{
quad.String("cool_person"),
quad.String("smart_person"),
},
},
},
},
}

func toJson(o interface{}) string {
Expand Down

0 comments on commit 7f5761b

Please sign in to comment.