Skip to content

Commit 7f21ae0

Browse files
authored
Use a dedicated file for resolvers
* Use a dedicated file for resolvers * Minor formatting changes
1 parent e97cc70 commit 7f21ae0

File tree

3 files changed

+394
-364
lines changed

3 files changed

+394
-364
lines changed

graphql/keyspace_schema.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/gocql/gocql"
66
"github.com/graphql-go/graphql"
77
"github.com/riptano/data-endpoints/config"
8+
"github.com/riptano/data-endpoints/db"
89
"github.com/riptano/data-endpoints/types"
910
)
1011

@@ -125,7 +126,11 @@ func (s *KeyspaceGraphQLSchema) buildType(typeInfo gocql.TypeInfo, isInput bool)
125126
}
126127
}
127128

128-
func (s *KeyspaceGraphQLSchema) buildKeyValueType(key graphql.Output, value graphql.Output, isInput bool) graphql.Output {
129+
func (s *KeyspaceGraphQLSchema) buildKeyValueType(
130+
key graphql.Output,
131+
value graphql.Output,
132+
isInput bool,
133+
) graphql.Output {
129134
keyName := getTypeName(key)
130135
valueName := getTypeName(value)
131136
if keyName == "" || valueName == "" {
@@ -326,3 +331,66 @@ func (s *KeyspaceGraphQLSchema) buildResultTypes(keyspace *gocql.KeyspaceMetadat
326331
})
327332
}
328333
}
334+
335+
func (s *KeyspaceGraphQLSchema) adaptCondition(tableName string, data map[string]interface{}) []types.ConditionItem {
336+
result := make([]types.ConditionItem, 0, len(data))
337+
for key, value := range data {
338+
if value == nil {
339+
continue
340+
}
341+
mapValue := value.(map[string]interface{})
342+
343+
for operatorName, itemValue := range mapValue {
344+
result = append(result, types.ConditionItem{
345+
Column: s.naming.ToCQLColumn(tableName, key),
346+
Operator: cqlOperators[operatorName],
347+
Value: adaptParameterValue(itemValue),
348+
})
349+
}
350+
}
351+
return result
352+
}
353+
354+
func (s *KeyspaceGraphQLSchema) adaptResult(tableName string, values []map[string]interface{}) []map[string]interface{} {
355+
result := make([]map[string]interface{}, 0, len(values))
356+
for _, item := range values {
357+
resultItem := make(map[string]interface{})
358+
for k, v := range item {
359+
resultItem[s.naming.ToGraphQLField(tableName, k)] = adaptResultValue(v)
360+
}
361+
result = append(result, resultItem)
362+
}
363+
364+
return result
365+
}
366+
367+
func (s *KeyspaceGraphQLSchema) getModificationResult(
368+
tableName string,
369+
rs db.ResultSet,
370+
err error,
371+
) (*types.ModificationResult, error) {
372+
if err != nil {
373+
return nil, err
374+
}
375+
376+
rows := rs.Values()
377+
378+
if len(rows) == 0 {
379+
return &appliedModificationResult, nil
380+
}
381+
382+
result := types.ModificationResult{}
383+
row := rows[0]
384+
applied := row["[applied]"].(*bool)
385+
result.Applied = applied != nil && *applied
386+
387+
result.Value = make(map[string]interface{})
388+
for k, v := range row {
389+
if k == "[applied]" {
390+
continue
391+
}
392+
result.Value[s.naming.ToGraphQLField(tableName, k)] = adaptResultValue(v)
393+
}
394+
395+
return &result, nil
396+
}

graphql/resolvers.go

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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+
func (sg *SchemaGenerator) queryFieldResolver(
18+
keyspace *gocql.KeyspaceMetadata,
19+
ksSchema *KeyspaceGraphQLSchema,
20+
) graphql.FieldResolveFn {
21+
return func(params graphql.ResolveParams) (interface{}, error) {
22+
fieldName := params.Info.FieldName
23+
switch fieldName {
24+
case "table":
25+
return ksSchema.getTable(keyspace, params.Args)
26+
case "tables":
27+
return ksSchema.getTables(keyspace)
28+
default:
29+
var table *gocql.TableMetadata
30+
table, tableFound := keyspace.Tables[ksSchema.naming.ToCQLTable(fieldName)]
31+
var data map[string]interface{}
32+
if params.Args["data"] != nil {
33+
data = params.Args["data"].(map[string]interface{})
34+
} else {
35+
data = params.Args["filter"].(map[string]interface{})
36+
}
37+
38+
var whereClause []types.ConditionItem
39+
40+
if tableFound {
41+
whereClause = make([]types.ConditionItem, 0, len(data))
42+
for key, value := range data {
43+
whereClause = append(whereClause, types.ConditionItem{
44+
Column: ksSchema.naming.ToCQLColumn(table.Name, key),
45+
Operator: "=",
46+
Value: adaptParameterValue(value),
47+
})
48+
}
49+
} else if strings.HasSuffix(fieldName, "Filter") {
50+
table, tableFound = keyspace.Tables[ksSchema.naming.ToCQLTable(strings.TrimSuffix(fieldName, "Filter"))]
51+
if !tableFound {
52+
return nil, fmt.Errorf("unable to find table '%s'", params.Info.FieldName)
53+
}
54+
55+
whereClause = ksSchema.adaptCondition(table.Name, data)
56+
} else {
57+
return nil, fmt.Errorf("Unable to find table for '%s'", params.Info.FieldName)
58+
}
59+
60+
var orderBy []interface{}
61+
var options types.QueryOptions
62+
if err := mapstructure.Decode(params.Args["options"], &options); err != nil {
63+
return nil, err
64+
}
65+
66+
if params.Args["orderBy"] != nil {
67+
orderBy = params.Args["orderBy"].([]interface{})
68+
}
69+
70+
userOrRole, err := sg.checkUserOrRoleAuth(params)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
result, err := sg.dbClient.Select(
76+
&db.SelectInfo{
77+
Keyspace: keyspace.Name,
78+
Table: table.Name,
79+
Where: whereClause,
80+
OrderBy: parseColumnOrder(orderBy),
81+
Options: &options,
82+
},
83+
db.NewQueryOptions().
84+
WithUserOrRole(userOrRole).
85+
WithConsistency(gocql.Consistency(options.Consistency)).
86+
WithSerialConsistency(gocql.SerialConsistency(options.SerialConsistency)))
87+
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
return &types.QueryResult{
93+
PageState: result.PageState(),
94+
Values: ksSchema.adaptResult(table.Name, result.Values()),
95+
}, nil
96+
}
97+
}
98+
}
99+
100+
func (sg *SchemaGenerator) mutationFieldResolver(
101+
keyspace *gocql.KeyspaceMetadata,
102+
ksSchema *KeyspaceGraphQLSchema,
103+
) graphql.FieldResolveFn {
104+
return func(params graphql.ResolveParams) (interface{}, error) {
105+
fieldName := params.Info.FieldName
106+
switch fieldName {
107+
case "createTable":
108+
return sg.createTable(keyspace.Name, ksSchema, params)
109+
case "alterTableAdd":
110+
return sg.alterTableAdd(keyspace.Name, ksSchema, params)
111+
case "alterTableDrop":
112+
return sg.alterTableDrop(keyspace.Name, ksSchema, params)
113+
case "dropTable":
114+
return sg.dropTable(keyspace.Name, ksSchema, params)
115+
default:
116+
operation, typeName := mutationPrefix(fieldName)
117+
if table, ok := keyspace.Tables[ksSchema.naming.ToCQLTable(typeName)]; ok {
118+
data := params.Args["data"].(map[string]interface{})
119+
columnNames := make([]string, 0, len(data))
120+
queryParams := make([]interface{}, 0, len(data))
121+
122+
for key, value := range data {
123+
columnNames = append(columnNames, ksSchema.naming.ToCQLColumn(table.Name, key))
124+
queryParams = append(queryParams, adaptParameterValue(value))
125+
}
126+
127+
var options types.MutationOptions
128+
if err := mapstructure.Decode(params.Args["options"], &options); err != nil {
129+
return nil, err
130+
}
131+
132+
userOrRole, err := sg.checkUserOrRoleAuth(params)
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
queryOptions := db.NewQueryOptions().
138+
WithUserOrRole(userOrRole).
139+
WithConsistency(gocql.Consistency(options.Consistency)).
140+
WithSerialConsistency(gocql.SerialConsistency(options.SerialConsistency))
141+
142+
var result db.ResultSet
143+
144+
switch operation {
145+
case insertPrefix:
146+
ifNotExists := params.Args["ifNotExists"] == true
147+
result, err = 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+
result, err = 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+
result, err = 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+
default:
183+
return false, fmt.Errorf("operation '%s' not supported", operation)
184+
}
185+
186+
return ksSchema.getModificationResult(table.Name, result, err)
187+
} else {
188+
return nil, fmt.Errorf("unable to find table for type name '%s'", params.Info.FieldName)
189+
}
190+
}
191+
}
192+
}
193+
194+
func adaptParameterValue(value interface{}) interface{} {
195+
if value == nil {
196+
return nil
197+
}
198+
199+
switch value.(type) {
200+
case int8, int16, int, float32, float64, string, bool:
201+
// Avoid using reflection for common scalars
202+
// Ideally, the algorithm should function without this optimization
203+
return value
204+
}
205+
206+
return adaptCollectionParameter(value)
207+
}
208+
209+
func adaptCollectionParameter(value interface{}) interface{} {
210+
rv := reflect.ValueOf(value)
211+
switch rv.Type().Kind() {
212+
case reflect.Slice:
213+
// Type element (rv.Type().Elem()) is an interface{}
214+
// We have to inspect the first value
215+
length := rv.Len()
216+
if length == 0 {
217+
return value
218+
}
219+
firstElement := rv.Index(0)
220+
if reflect.TypeOf(firstElement.Interface()).Kind() != reflect.Map {
221+
return value
222+
}
223+
224+
result := make(map[interface{}]interface{})
225+
// It's a slice of maps that only contains two keys: 'key' and 'value'
226+
// It's the graphql representation of a map: [KeyValueType]
227+
for i := 0; i < length; i++ {
228+
element := rv.Index(i).Interface().(map[string]interface{})
229+
result[element["key"]] = adaptParameterValue(element["value"])
230+
}
231+
232+
return result
233+
}
234+
235+
return value
236+
}
237+
238+
func mutationPrefix(value string) (string, string) {
239+
mutationPrefixes := []string{insertPrefix, deletePrefix, updatePrefix}
240+
241+
for _, prefix := range mutationPrefixes {
242+
if strings.Index(value, prefix) == 0 {
243+
return prefix, value[len(prefix):]
244+
}
245+
}
246+
247+
panic("Unsupported mutation")
248+
}
249+
250+
func parseColumnOrder(values []interface{}) []db.ColumnOrder {
251+
result := make([]db.ColumnOrder, 0, len(values))
252+
253+
for _, value := range values {
254+
strValue := value.(string)
255+
index := strings.LastIndex(strValue, "_")
256+
result = append(result, db.ColumnOrder{
257+
Column: strValue[0:index],
258+
Order: strValue[index+1:],
259+
})
260+
}
261+
262+
return result
263+
}
264+
265+
func adaptResultValue(value interface{}) interface{} {
266+
if value == nil {
267+
return nil
268+
}
269+
270+
switch value.(type) {
271+
case *int8, *int16, *int, *float32, *float64, *int32, *string, *bool,
272+
*time.Time, *inf.Dec, *big.Int, *gocql.UUID, *[]byte:
273+
// Avoid reflection whenever possible
274+
return value
275+
}
276+
277+
rv := reflect.ValueOf(value)
278+
typeKind := rv.Type().Kind()
279+
280+
if typeKind == reflect.Ptr && rv.IsNil() {
281+
return nil
282+
}
283+
284+
if !(typeKind == reflect.Ptr && rv.Elem().Type().Kind() == reflect.Map) {
285+
return value
286+
}
287+
288+
rv = rv.Elem()
289+
290+
// Maps should be adapted to a slice of maps, each map containing 2 keys: 'key' and 'value'
291+
result := make([]map[string]interface{}, 0, rv.Len())
292+
iter := rv.MapRange()
293+
for iter.Next() {
294+
key := iter.Key()
295+
value := iter.Value()
296+
result = append(result, map[string]interface{}{
297+
"key": key.Interface(),
298+
"value": value.Interface(),
299+
})
300+
}
301+
302+
return result
303+
}

0 commit comments

Comments
 (0)