Skip to content

Commit b16b611

Browse files
[BREAKING] Introduce new facets format (#5424)
Fixes: #4798, #4581, #4907 DGRAPH-1109, DGRAPH-1062, DGRAPH-1143 This is PR changes facets format as discussed in the post: https://discuss.dgraph.io/t/facets-format-in-mutation-requests-and-query-responses/6416 After this PR is merged response/requests formats will look like as below: Current UID predicate facets query response: { "data": { "q": [ { "name": "San Francisco", "state": { "name": "California" }, "state|capital": false } ] } } New UID predicate facets query response: { "data": { "q": [ { "name": "San Francisco", "state": { "name": "California", "state|capital": false } } ] } } Current UID list predicate facets query response: { "data": { "q": [ { "name": "Alice", "speaks": [ { "name": "Spanish" }, { "name": "Chinese" } ], "speaks|fluent": { "0": true, "1": false } } ] } } New UID list predicate facets query response: { "data": { "q": [ { "name": "Alice", "speaks": [ { "name": "Spanish", "speaks|fluent": true }, { "name": "Chinese", "speaks|fluent": false } ] } ] } } Current scalar list predicate facets mutation request: { "set": [ { "uid": "_:1", "nickname": "Joshua", "nickname|kind": "official" }, { "uid": "_:1", "nickname": "David" }, { "uid": "_:1", "nickname": "Josh", "nickname|kind": "friends" } ] } New scalar list predicate facets mutation request: { "set": { "uid": "_:1", "nickname": ["Joshua", "David", "Josh"], "nickname|kind": { "0": "official", "2": "friends" } } } NOTE: there is no change in the request/response facets format for scalar predicate type.
1 parent 1ef0982 commit b16b611

File tree

8 files changed

+1460
-1223
lines changed

8 files changed

+1460
-1223
lines changed

chunker/json_parser.go

Lines changed: 156 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,77 @@ func stripSpaces(str string) string {
4646
}, str)
4747
}
4848

49-
func parseFacetsJSON(m map[string]interface{}, prefix string) ([]*api.Facet, error) {
49+
// handleBasicFacetsType parses a facetVal to string/float64/bool/datetime type.
50+
func handleBasicFacetsType(key string, facetVal interface{}) (*api.Facet, error) {
51+
var jsonValue interface{}
52+
var valueType api.Facet_ValType
53+
switch v := facetVal.(type) {
54+
case string:
55+
if t, err := types.ParseTime(v); err == nil {
56+
valueType = api.Facet_DATETIME
57+
jsonValue = t
58+
} else {
59+
facet, err := facets.FacetFor(key, strconv.Quote(v))
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
// FacetFor function already converts the value to binary so there is no need
65+
// for the conversion again after the switch block.
66+
return facet, nil
67+
}
68+
case json.Number:
69+
number := facetVal.(json.Number)
70+
if strings.Contains(number.String(), ".") {
71+
jsonFloat, err := number.Float64()
72+
if err != nil {
73+
return nil, err
74+
}
75+
jsonValue = jsonFloat
76+
valueType = api.Facet_FLOAT
77+
} else {
78+
jsonInt, err := number.Int64()
79+
if err != nil {
80+
return nil, err
81+
}
82+
jsonValue = jsonInt
83+
valueType = api.Facet_INT
84+
}
85+
case bool:
86+
jsonValue = v
87+
valueType = api.Facet_BOOL
88+
default:
89+
return nil, errors.Errorf("facet value can only be string/number/bool.")
90+
}
91+
92+
// Convert facet val interface{} to binary.
93+
binaryValueFacet, err := facets.ToBinary(key, jsonValue, valueType)
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
return binaryValueFacet, nil
99+
}
100+
101+
// parseMapFacets parses facets which are of map type. Facets for scalar list predicates are
102+
// specified in map format. For example below predicate nickname and kind facet associated with it.
103+
// Here nickname "bob" doesn't have any facet associated with it.
104+
// {
105+
// "nickname": ["alice", "bob", "josh"],
106+
// "nickname|kind": {
107+
// "0": "friends",
108+
// "2": "official"
109+
// }
110+
// }
111+
// Parsed response would a slice of maps[int]*api.Facet, one map for each facet.
112+
// Map key would be the index of scalar value for respective facets.
113+
func parseMapFacets(m map[string]interface{}, prefix string) ([]map[int]*api.Facet, error) {
50114
// This happens at root.
51115
if prefix == "" {
52116
return nil, nil
53117
}
54118

55-
var facetsForPred []*api.Facet
119+
var mapSlice []map[int]*api.Facet
56120
for fname, facetVal := range m {
57121
if facetVal == nil {
58122
continue
@@ -61,56 +125,54 @@ func parseFacetsJSON(m map[string]interface{}, prefix string) ([]*api.Facet, err
61125
continue
62126
}
63127

64-
key := fname[len(prefix):]
65-
var jsonValue interface{}
66-
var valueType api.Facet_ValType
67-
switch v := facetVal.(type) {
68-
case string:
69-
if t, err := types.ParseTime(v); err == nil {
70-
valueType = api.Facet_DATETIME
71-
jsonValue = t
72-
} else {
73-
facet, err := facets.FacetFor(key, strconv.Quote(v))
74-
if err != nil {
75-
return nil, err
76-
}
128+
fm, ok := facetVal.(map[string]interface{})
129+
if !ok {
130+
return nil, errors.Errorf("facets format should be of type map for "+
131+
"scalarlist predicates, found: %v for facet: %v", facetVal, fname)
132+
}
77133

78-
// the FacetFor function already converts the value to binary
79-
// so there is no need for the conversion again after the switch block
80-
facetsForPred = append(facetsForPred, facet)
81-
continue
134+
idxMap := make(map[int]*api.Facet, len(fm))
135+
for sidx, val := range fm {
136+
key := fname[len(prefix):]
137+
facet, err := handleBasicFacetsType(key, val)
138+
if err != nil {
139+
return nil, errors.Wrapf(err, "facet: %s, index: %s", fname, sidx)
82140
}
83-
case json.Number:
84-
number := facetVal.(json.Number)
85-
if strings.Contains(number.String(), ".") {
86-
jsonFloat, err := number.Float64()
87-
if err != nil {
88-
return nil, err
89-
}
90-
jsonValue = jsonFloat
91-
valueType = api.Facet_FLOAT
92-
} else {
93-
jsonInt, err := number.Int64()
94-
if err != nil {
95-
return nil, err
96-
}
97-
jsonValue = jsonInt
98-
valueType = api.Facet_INT
141+
idx, err := strconv.Atoi(sidx)
142+
if err != nil {
143+
return nil, errors.Wrapf(err, "facet: %s, index: %s", fname, sidx)
99144
}
100-
case bool:
101-
jsonValue = v
102-
valueType = api.Facet_BOOL
103-
default:
104-
return nil, errors.Errorf("Facet value for key: %s can only be string/float64/bool.",
105-
fname)
145+
idxMap[idx] = facet
106146
}
147+
mapSlice = append(mapSlice, idxMap)
148+
}
107149

108-
// convert facet val interface{} to binary
109-
binaryValueFacet, err := facets.ToBinary(key, jsonValue, valueType)
150+
return mapSlice, nil
151+
}
152+
153+
// parseScalarFacets parses facets which should be of type string/json.Number/bool.
154+
// It returns []*api.Facet, one *api.Facet for each facet.
155+
func parseScalarFacets(m map[string]interface{}, prefix string) ([]*api.Facet, error) {
156+
// This happens at root.
157+
if prefix == "" {
158+
return nil, nil
159+
}
160+
161+
var facetsForPred []*api.Facet
162+
for fname, facetVal := range m {
163+
if facetVal == nil {
164+
continue
165+
}
166+
if !strings.HasPrefix(fname, prefix) {
167+
continue
168+
}
169+
170+
key := fname[len(prefix):]
171+
facet, err := handleBasicFacetsType(key, facetVal)
110172
if err != nil {
111-
return nil, err
173+
return nil, errors.Wrapf(err, "facet: %s", fname)
112174
}
113-
facetsForPred = append(facetsForPred, binaryValueFacet)
175+
facetsForPred = append(facetsForPred, facet)
114176
}
115177

116178
return facetsForPred, nil
@@ -383,18 +445,21 @@ func (buf *NQuadBuffer) mapToNquads(m map[string]interface{}, op int, parentPred
383445
continue
384446
}
385447

386-
prefix := pred + x.FacetDelimeter
387-
// TODO - Maybe do an initial pass and build facets for all predicates. Then we don't have
388-
// to call parseFacets everytime.
389-
fts, err := parseFacetsJSON(m, prefix)
390-
if err != nil {
391-
return mr, err
392-
}
393-
394448
nq := api.NQuad{
395449
Subject: mr.uid,
396450
Predicate: pred,
397-
Facets: fts,
451+
}
452+
453+
prefix := pred + x.FacetDelimeter
454+
// TODO - Maybe do an initial pass and build facets for all predicates. Then we don't have
455+
// to call parseFacets everytime.
456+
// Only call parseBasicFacets when value type for the predicate is not list.
457+
if _, ok := v.([]interface{}); !ok {
458+
fts, err := parseScalarFacets(m, prefix)
459+
if err != nil {
460+
return mr, err
461+
}
462+
nq.Facets = fts
398463
}
399464

400465
// Here we split predicate and lang directive (ex: "name@en"), if needed. With JSON
@@ -435,7 +500,14 @@ func (buf *NQuadBuffer) mapToNquads(m map[string]interface{}, op int, parentPred
435500
buf.PushPredHint(pred, pb.Metadata_SINGLE)
436501
case []interface{}:
437502
buf.PushPredHint(pred, pb.Metadata_LIST)
438-
for _, item := range v {
503+
// TODO(Ashish): We need to call this only in case of scalarlist, for other lists
504+
// this can be avoided.
505+
facetsMapSlice, err := parseMapFacets(m, prefix)
506+
if err != nil {
507+
return mr, err
508+
}
509+
510+
for idx, item := range v {
439511
nq := api.NQuad{
440512
Subject: mr.uid,
441513
Predicate: pred,
@@ -446,6 +518,34 @@ func (buf *NQuadBuffer) mapToNquads(m map[string]interface{}, op int, parentPred
446518
if err := handleBasicType(pred, iv, op, &nq); err != nil {
447519
return mr, err
448520
}
521+
// Here populate facets from facetsMapSlice. Each map has mapping for single
522+
// facet from item(one of predicate value) idx to *api.Facet.
523+
// {
524+
// "friend": ["Joshua", "David", "Josh"],
525+
// "friend|from": {
526+
// "0": "school"
527+
// },
528+
// "friend|age": {
529+
// "1": 20
530+
// }
531+
// }
532+
// facetMapSlice looks like below. First map is for friend|from facet and second
533+
// map is for friend|age facet.
534+
// [
535+
// map[int]*api.Facet{
536+
// 0: *api.Facet
537+
// },
538+
// map[int]*api.Facet{
539+
// 1: *api.Facet
540+
// }
541+
// ]
542+
var fts []*api.Facet
543+
for _, fm := range facetsMapSlice {
544+
if ft, ok := fm[idx]; ok {
545+
fts = append(fts, ft)
546+
}
547+
}
548+
nq.Facets = fts
449549
buf.Push(&nq)
450550
case map[string]interface{}:
451551
// map[string]interface{} can mean geojson or a connecting entity.
@@ -475,7 +575,7 @@ func (buf *NQuadBuffer) mapToNquads(m map[string]interface{}, op int, parentPred
475575
}
476576
}
477577

478-
fts, err := parseFacetsJSON(m, parentPred+x.FacetDelimeter)
578+
fts, err := parseScalarFacets(m, parentPred+x.FacetDelimeter)
479579
mr.fcts = fts
480580
return mr, err
481581
}

0 commit comments

Comments
 (0)