Skip to content

Commit c2679f3

Browse files
committed
Use a dedicated file for resolvers
1 parent 5d80977 commit c2679f3

File tree

3 files changed

+397
-363
lines changed

3 files changed

+397
-363
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: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
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+
var result db.ResultSet
145+
146+
switch operation {
147+
case insertPrefix:
148+
ifNotExists := params.Args["ifNotExists"] == true
149+
result, err = sg.dbClient.Insert(&db.InsertInfo{
150+
Keyspace: keyspace.Name,
151+
Table: table.Name,
152+
Columns: columnNames,
153+
QueryParams: queryParams,
154+
IfNotExists: ifNotExists,
155+
TTL: options.TTL,
156+
}, queryOptions)
157+
case deletePrefix:
158+
var ifCondition []types.ConditionItem
159+
if params.Args["ifCondition"] != nil {
160+
ifCondition = ksSchema.adaptCondition(
161+
table.Name, params.Args["ifCondition"].(map[string]interface{}))
162+
}
163+
result, err = sg.dbClient.Delete(&db.DeleteInfo{
164+
Keyspace: keyspace.Name,
165+
Table: table.Name,
166+
Columns: columnNames,
167+
QueryParams: queryParams,
168+
IfCondition: ifCondition,
169+
IfExists: params.Args["ifExists"] == true}, queryOptions)
170+
case updatePrefix:
171+
var ifCondition []types.ConditionItem
172+
if params.Args["ifCondition"] != nil {
173+
ifCondition = ksSchema.adaptCondition(
174+
table.Name, params.Args["ifCondition"].(map[string]interface{}))
175+
}
176+
result, err = sg.dbClient.Update(&db.UpdateInfo{
177+
Keyspace: keyspace.Name,
178+
Table: table,
179+
Columns: columnNames,
180+
QueryParams: queryParams,
181+
IfCondition: ifCondition,
182+
TTL: options.TTL,
183+
IfExists: params.Args["ifExists"] == true}, queryOptions)
184+
default:
185+
return false, fmt.Errorf("operation '%s' not supported", operation)
186+
}
187+
188+
return ksSchema.getModificationResult(table.Name, result, err)
189+
} else {
190+
return nil, fmt.Errorf("unable to find table for type name '%s'", params.Info.FieldName)
191+
}
192+
193+
}
194+
}
195+
}
196+
197+
func (s *KeyspaceGraphQLSchema) adaptCondition(tableName string, data map[string]interface{}) []types.ConditionItem {
198+
result := make([]types.ConditionItem, 0, len(data))
199+
for key, value := range data {
200+
if value == nil {
201+
continue
202+
}
203+
mapValue := value.(map[string]interface{})
204+
205+
for operatorName, itemValue := range mapValue {
206+
result = append(result, types.ConditionItem{
207+
Column: s.naming.ToCQLColumn(tableName, key),
208+
Operator: cqlOperators[operatorName],
209+
Value: adaptParameterValue(itemValue),
210+
})
211+
}
212+
}
213+
return result
214+
}
215+
216+
func (s *KeyspaceGraphQLSchema) adaptResult(tableName string, values []map[string]interface{}) []map[string]interface{} {
217+
result := make([]map[string]interface{}, 0, len(values))
218+
for _, item := range values {
219+
resultItem := make(map[string]interface{})
220+
for k, v := range item {
221+
resultItem[s.naming.ToGraphQLField(tableName, k)] = adaptResultValue(v)
222+
}
223+
result = append(result, resultItem)
224+
}
225+
226+
return result
227+
}
228+
229+
func adaptResultValue(value interface{}) interface{} {
230+
if value == nil {
231+
return nil
232+
}
233+
234+
switch value.(type) {
235+
case *int8, *int16, *int, *float32, *float64, *int32, *string, *bool,
236+
*time.Time, *inf.Dec, *big.Int, *gocql.UUID, *[]byte:
237+
// Avoid reflection whenever possible
238+
return value
239+
}
240+
241+
rv := reflect.ValueOf(value)
242+
typeKind := rv.Type().Kind()
243+
244+
if typeKind == reflect.Ptr && rv.IsNil() {
245+
return nil
246+
}
247+
248+
if !(typeKind == reflect.Ptr && rv.Elem().Type().Kind() == reflect.Map) {
249+
return value
250+
}
251+
252+
rv = rv.Elem()
253+
254+
// Maps should be adapted to a slice of maps, each map containing 2 keys: 'key' and 'value'
255+
result := make([]map[string]interface{}, 0, rv.Len())
256+
iter := rv.MapRange()
257+
for iter.Next() {
258+
key := iter.Key()
259+
value := iter.Value()
260+
result = append(result, map[string]interface{}{
261+
"key": key.Interface(),
262+
"value": value.Interface(),
263+
})
264+
}
265+
266+
return result
267+
}
268+
269+
func adaptParameterValue(value interface{}) interface{} {
270+
if value == nil {
271+
return nil
272+
}
273+
274+
switch value.(type) {
275+
case int8, int16, int, float32, float64, string, bool:
276+
// Avoid using reflection for common scalars
277+
// Ideally, the algorithm should function without this optimization
278+
return value
279+
}
280+
281+
return adaptCollectionParameter(value)
282+
}
283+
284+
func adaptCollectionParameter(value interface{}) interface{} {
285+
rv := reflect.ValueOf(value)
286+
switch rv.Type().Kind() {
287+
case reflect.Slice:
288+
// Type element (rv.Type().Elem()) is an interface{}
289+
// We have to inspect the first value
290+
length := rv.Len()
291+
if length == 0 {
292+
return value
293+
}
294+
firstElement := rv.Index(0)
295+
if reflect.TypeOf(firstElement.Interface()).Kind() != reflect.Map {
296+
return value
297+
}
298+
299+
result := make(map[interface{}]interface{})
300+
// It's a slice of maps that only contains two keys: 'key' and 'value'
301+
// It's the graphql representation of a map: [KeyValueType]
302+
for i := 0; i < length; i++ {
303+
element := rv.Index(i).Interface().(map[string]interface{})
304+
result[element["key"]] = adaptParameterValue(element["value"])
305+
}
306+
307+
return result
308+
}
309+
310+
return value
311+
}
312+
313+
func mutationPrefix(value string) (string, string) {
314+
mutationPrefixes := []string{insertPrefix, deletePrefix, updatePrefix}
315+
316+
for _, prefix := range mutationPrefixes {
317+
if strings.Index(value, prefix) == 0 {
318+
return prefix, value[len(prefix):]
319+
}
320+
}
321+
322+
panic("Unsupported mutation")
323+
}
324+
325+
func parseColumnOrder(values []interface{}) []db.ColumnOrder {
326+
result := make([]db.ColumnOrder, 0, len(values))
327+
328+
for _, value := range values {
329+
strValue := value.(string)
330+
index := strings.LastIndex(strValue, "_")
331+
result = append(result, db.ColumnOrder{
332+
Column: strValue[0:index],
333+
Order: strValue[index+1:],
334+
})
335+
}
336+
337+
return result
338+
}
339+
340+
func (s *KeyspaceGraphQLSchema) getModificationResult(
341+
tableName string,
342+
rs db.ResultSet,
343+
err error,
344+
) (*types.ModificationResult, error) {
345+
if err != nil {
346+
return nil, err
347+
}
348+
349+
rows := rs.Values()
350+
351+
if len(rows) == 0 {
352+
return &appliedModificationResult, nil
353+
}
354+
355+
result := types.ModificationResult{}
356+
row := rows[0]
357+
applied := row["[applied]"].(*bool)
358+
result.Applied = applied != nil && *applied
359+
360+
result.Value = make(map[string]interface{})
361+
for k, v := range row {
362+
if k == "[applied]" {
363+
continue
364+
}
365+
result.Value[s.naming.ToGraphQLField(tableName, k)] = adaptResultValue(v)
366+
}
367+
368+
return &result, nil
369+
}

0 commit comments

Comments
 (0)