Skip to content

Commit 48b2eec

Browse files
committed
Use a dedicated file for resolvers
1 parent dc5dc34 commit 48b2eec

File tree

3 files changed

+361
-328
lines changed

3 files changed

+361
-328
lines changed

graphql/keyspace_schema.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ func (s *KeyspaceGraphQLSchema) buildType(typeInfo gocql.TypeInfo, isInput bool)
125125
}
126126
}
127127

128-
func (s *KeyspaceGraphQLSchema) buildKeyValueType(key graphql.Output, value graphql.Output, isInput bool) graphql.Output {
128+
func (s *KeyspaceGraphQLSchema) buildKeyValueType(
129+
key graphql.Output,
130+
value graphql.Output,
131+
isInput bool,
132+
) graphql.Output {
129133
keyName := getTypeName(key)
130134
valueName := getTypeName(value)
131135
if keyName == "" || valueName == "" {

graphql/resolvers.go

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
package graphql
2+
3+
import (
4+
"fmt"
5+
"github.com/gocql/gocql"
6+
"github.com/graphql-go/graphql"
7+
"github.com/mitchellh/mapstructure"
8+
"github.com/riptano/data-endpoints/db"
9+
"github.com/riptano/data-endpoints/types"
10+
"gopkg.in/inf.v0"
11+
"math/big"
12+
"reflect"
13+
"strings"
14+
"time"
15+
)
16+
17+
// returns the resolver function to be used for the graphql query operations
18+
func (sg *SchemaGenerator) queryFieldResolver(
19+
keyspace *gocql.KeyspaceMetadata,
20+
ksSchema *KeyspaceGraphQLSchema,
21+
) graphql.FieldResolveFn {
22+
return func(params graphql.ResolveParams) (interface{}, error) {
23+
fieldName := params.Info.FieldName
24+
switch fieldName {
25+
case "table":
26+
return ksSchema.getTable(keyspace, params.Args)
27+
case "tables":
28+
return ksSchema.getTables(keyspace)
29+
default:
30+
var table *gocql.TableMetadata
31+
table, tableFound := keyspace.Tables[ksSchema.naming.ToCQLTable(fieldName)]
32+
var data map[string]interface{}
33+
if params.Args["data"] != nil {
34+
data = params.Args["data"].(map[string]interface{})
35+
} else {
36+
data = params.Args["filter"].(map[string]interface{})
37+
}
38+
39+
var whereClause []types.ConditionItem
40+
41+
if tableFound {
42+
whereClause = make([]types.ConditionItem, 0, len(data))
43+
for key, value := range data {
44+
whereClause = append(whereClause, types.ConditionItem{
45+
Column: ksSchema.naming.ToCQLColumn(table.Name, key),
46+
Operator: "=",
47+
Value: adaptParameterValue(value),
48+
})
49+
}
50+
} else if strings.HasSuffix(fieldName, "Filter") {
51+
table, tableFound = keyspace.Tables[ksSchema.naming.ToCQLTable(strings.TrimSuffix(fieldName, "Filter"))]
52+
if !tableFound {
53+
return nil, fmt.Errorf("unable to find table '%s'", params.Info.FieldName)
54+
}
55+
56+
whereClause = ksSchema.adaptCondition(table.Name, data)
57+
} else {
58+
return nil, fmt.Errorf("Unable to find table for '%s'", params.Info.FieldName)
59+
}
60+
61+
var orderBy []interface{}
62+
var options types.QueryOptions
63+
if err := mapstructure.Decode(params.Args["options"], &options); err != nil {
64+
return nil, err
65+
}
66+
67+
if params.Args["orderBy"] != nil {
68+
orderBy = params.Args["orderBy"].([]interface{})
69+
}
70+
71+
userOrRole, err := sg.checkUserOrRoleAuth(params)
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
result, err := sg.dbClient.Select(
77+
&db.SelectInfo{
78+
Keyspace: keyspace.Name,
79+
Table: table.Name,
80+
Where: whereClause,
81+
OrderBy: parseColumnOrder(orderBy),
82+
Options: &options,
83+
},
84+
db.NewQueryOptions().
85+
WithUserOrRole(userOrRole).
86+
WithConsistency(gocql.Consistency(options.Consistency)).
87+
WithSerialConsistency(gocql.SerialConsistency(options.SerialConsistency)))
88+
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
return &types.QueryResult{
94+
PageState: result.PageState(),
95+
Values: ksSchema.adaptResult(table.Name, result.Values()),
96+
}, nil
97+
}
98+
}
99+
}
100+
101+
// returns the resolver function to be used for the mutation operations
102+
func (sg *SchemaGenerator) mutationFieldResolver(
103+
keyspace *gocql.KeyspaceMetadata,
104+
ksSchema *KeyspaceGraphQLSchema,
105+
) graphql.FieldResolveFn {
106+
return func(params graphql.ResolveParams) (interface{}, error) {
107+
fieldName := params.Info.FieldName
108+
switch fieldName {
109+
case "createTable":
110+
return sg.createTable(keyspace.Name, ksSchema, params)
111+
case "alterTableAdd":
112+
return sg.alterTableAdd(keyspace.Name, ksSchema, params)
113+
case "alterTableDrop":
114+
return sg.alterTableDrop(keyspace.Name, ksSchema, params)
115+
case "dropTable":
116+
return sg.dropTable(keyspace.Name, ksSchema, params)
117+
default:
118+
operation, typeName := mutationPrefix(fieldName)
119+
if table, ok := keyspace.Tables[ksSchema.naming.ToCQLTable(typeName)]; ok {
120+
data := params.Args["data"].(map[string]interface{})
121+
columnNames := make([]string, 0, len(data))
122+
queryParams := make([]interface{}, 0, len(data))
123+
124+
for key, value := range data {
125+
columnNames = append(columnNames, ksSchema.naming.ToCQLColumn(table.Name, key))
126+
queryParams = append(queryParams, adaptParameterValue(value))
127+
}
128+
129+
var options types.MutationOptions
130+
if err := mapstructure.Decode(params.Args["options"], &options); err != nil {
131+
return nil, err
132+
}
133+
134+
userOrRole, err := sg.checkUserOrRoleAuth(params)
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
queryOptions := db.NewQueryOptions().
140+
WithUserOrRole(userOrRole).
141+
WithConsistency(gocql.Consistency(options.Consistency)).
142+
WithSerialConsistency(gocql.SerialConsistency(options.SerialConsistency))
143+
144+
switch operation {
145+
case insertPrefix:
146+
ifNotExists := params.Args["ifNotExists"] == true
147+
return sg.dbClient.Insert(&db.InsertInfo{
148+
Keyspace: keyspace.Name,
149+
Table: table.Name,
150+
Columns: columnNames,
151+
QueryParams: queryParams,
152+
IfNotExists: ifNotExists,
153+
TTL: options.TTL,
154+
}, queryOptions)
155+
case deletePrefix:
156+
var ifCondition []types.ConditionItem
157+
if params.Args["ifCondition"] != nil {
158+
ifCondition = ksSchema.adaptCondition(
159+
table.Name, params.Args["ifCondition"].(map[string]interface{}))
160+
}
161+
return sg.dbClient.Delete(&db.DeleteInfo{
162+
Keyspace: keyspace.Name,
163+
Table: table.Name,
164+
Columns: columnNames,
165+
QueryParams: queryParams,
166+
IfCondition: ifCondition,
167+
IfExists: params.Args["ifExists"] == true}, queryOptions)
168+
case updatePrefix:
169+
var ifCondition []types.ConditionItem
170+
if params.Args["ifCondition"] != nil {
171+
ifCondition = ksSchema.adaptCondition(
172+
table.Name, params.Args["ifCondition"].(map[string]interface{}))
173+
}
174+
return sg.dbClient.Update(&db.UpdateInfo{
175+
Keyspace: keyspace.Name,
176+
Table: table,
177+
Columns: columnNames,
178+
QueryParams: queryParams,
179+
IfCondition: ifCondition,
180+
TTL: options.TTL,
181+
IfExists: params.Args["ifExists"] == true}, queryOptions)
182+
}
183+
184+
return false, fmt.Errorf("operation '%s' not supported", operation)
185+
} else {
186+
return nil, fmt.Errorf("unable to find table for type name '%s'", params.Info.FieldName)
187+
}
188+
189+
}
190+
}
191+
}
192+
193+
func (s *KeyspaceGraphQLSchema) adaptCondition(tableName string, data map[string]interface{}) []types.ConditionItem {
194+
result := make([]types.ConditionItem, 0, len(data))
195+
for key, value := range data {
196+
if value == nil {
197+
continue
198+
}
199+
mapValue := value.(map[string]interface{})
200+
201+
for operatorName, itemValue := range mapValue {
202+
result = append(result, types.ConditionItem{
203+
Column: s.naming.ToCQLColumn(tableName, key),
204+
Operator: cqlOperators[operatorName],
205+
Value: adaptParameterValue(itemValue),
206+
})
207+
}
208+
}
209+
return result
210+
}
211+
212+
func (s *KeyspaceGraphQLSchema) adaptResult(tableName string, values []map[string]interface{}) []map[string]interface{} {
213+
result := make([]map[string]interface{}, 0, len(values))
214+
for _, item := range values {
215+
resultItem := make(map[string]interface{})
216+
for k, v := range item {
217+
resultItem[s.naming.ToGraphQLField(tableName, k)] = adaptResultValue(v)
218+
}
219+
result = append(result, resultItem)
220+
}
221+
222+
return result
223+
}
224+
225+
func adaptResultValue(value interface{}) interface{} {
226+
if value == nil {
227+
return nil
228+
}
229+
230+
switch value.(type) {
231+
case *int8, *int16, *int, *float32, *float64, *int32, *string, *bool,
232+
*time.Time, *inf.Dec, *big.Int, *gocql.UUID, *[]byte:
233+
// Avoid reflection whenever possible
234+
return value
235+
}
236+
237+
rv := reflect.ValueOf(value)
238+
typeKind := rv.Type().Kind()
239+
240+
if typeKind == reflect.Ptr && rv.IsNil() {
241+
return nil
242+
}
243+
244+
if !(typeKind == reflect.Ptr && rv.Elem().Type().Kind() == reflect.Map) {
245+
return value
246+
}
247+
248+
rv = rv.Elem()
249+
250+
// Maps should be adapted to a slice of maps, each map containing 2 keys: 'key' and 'value'
251+
result := make([]map[string]interface{}, 0, rv.Len())
252+
iter := rv.MapRange()
253+
for iter.Next() {
254+
key := iter.Key()
255+
value := iter.Value()
256+
result = append(result, map[string]interface{}{
257+
"key": key.Interface(),
258+
"value": value.Interface(),
259+
})
260+
}
261+
262+
return result
263+
}
264+
265+
func adaptParameterValue(value interface{}) interface{} {
266+
if value == nil {
267+
return nil
268+
}
269+
270+
switch value.(type) {
271+
case int8, int16, int, float32, float64, string, bool:
272+
// Avoid using reflection for common scalars
273+
// Ideally, the algorithm should function without this optimization
274+
return value
275+
}
276+
277+
return adaptCollectionParameter(value)
278+
}
279+
280+
func adaptCollectionParameter(value interface{}) interface{} {
281+
rv := reflect.ValueOf(value)
282+
switch rv.Type().Kind() {
283+
case reflect.Slice:
284+
// Type element (rv.Type().Elem()) is an interface{}
285+
// We have to inspect the first value
286+
length := rv.Len()
287+
if length == 0 {
288+
return value
289+
}
290+
firstElement := rv.Index(0)
291+
if reflect.TypeOf(firstElement.Interface()).Kind() != reflect.Map {
292+
return value
293+
}
294+
295+
result := make(map[interface{}]interface{})
296+
// It's a slice of maps that only contains two keys: 'key' and 'value'
297+
// It's the graphql representation of a map: [KeyValueType]
298+
for i := 0; i < length; i++ {
299+
element := rv.Index(i).Interface().(map[string]interface{})
300+
result[element["key"]] = adaptParameterValue(element["value"])
301+
}
302+
303+
return result
304+
}
305+
306+
return value
307+
}
308+
309+
func mutationPrefix(value string) (string, string) {
310+
mutationPrefixes := []string{insertPrefix, deletePrefix, updatePrefix}
311+
312+
for _, prefix := range mutationPrefixes {
313+
if strings.Index(value, prefix) == 0 {
314+
return prefix, value[len(prefix):]
315+
}
316+
}
317+
318+
panic("Unsupported mutation")
319+
}
320+
321+
func parseColumnOrder(values []interface{}) []db.ColumnOrder {
322+
result := make([]db.ColumnOrder, 0, len(values))
323+
324+
for _, value := range values {
325+
strValue := value.(string)
326+
index := strings.LastIndex(strValue, "_")
327+
result = append(result, db.ColumnOrder{
328+
Column: strValue[0:index],
329+
Order: strValue[index+1:],
330+
})
331+
}
332+
333+
return result
334+
}

0 commit comments

Comments
 (0)