github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/cassandra/datastore_query.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package cassandra 22 23 import ( 24 "context" 25 "encoding/base64" 26 "sort" 27 28 "github.com/gocql/gocql" 29 "github.com/pkg/errors" 30 "github.com/uber-go/dosa" 31 ) 32 33 // ColumnCondition represents the condition of each column 34 type ColumnCondition struct { 35 Name string 36 Condition *dosa.Condition 37 } 38 39 type sortedColumnCondition []*ColumnCondition 40 41 func (list sortedColumnCondition) Len() int { return len(list) } 42 43 func (list sortedColumnCondition) Swap(i, j int) { list[i], list[j] = list[j], list[i] } 44 45 func (list sortedColumnCondition) Less(i, j int) bool { 46 si := list[i] 47 sj := list[j] 48 49 if si.Name != sj.Name { 50 return si.Name < sj.Name 51 } 52 53 return si.Condition.Op < sj.Condition.Op 54 } 55 56 func prepareConditions(columnConditions map[string][]*dosa.Condition) ([]*ColumnCondition, []interface{}, error) { 57 var cc []*ColumnCondition 58 59 for column, conds := range columnConditions { 60 for _, cond := range conds { 61 cc = append(cc, &ColumnCondition{ 62 Name: column, 63 Condition: cond}) 64 } 65 } 66 67 sort.Sort(sortedColumnCondition(cc)) 68 values := make([]interface{}, len(cc)) 69 for i, c := range cc { 70 values[i] = c.Condition.Value 71 var err error 72 // specially handling for uuid is needed 73 if u, ok := c.Condition.Value.(dosa.UUID); ok { 74 values[i], err = gocql.ParseUUID(string(u)) 75 if err != nil { 76 return nil, nil, errors.Wrapf(err, "invalid uuid %s", u) 77 } 78 } 79 } 80 return cc, values, nil 81 } 82 83 // Range querys the data based on the cluster keys 84 func (c *Connector) Range(ctx context.Context, ei *dosa.EntityInfo, columnConditions map[string][]*dosa.Condition, fieldsToRead []string, 85 token string, limit int) ([]map[string]dosa.FieldValue, string, error) { 86 return c.commonQuery(ctx, ei, columnConditions, fieldsToRead, token, limit) 87 } 88 89 func (c *Connector) commonQuery(ctx context.Context, ei *dosa.EntityInfo, columnConditions map[string][]*dosa.Condition, fieldsToRead []string, 90 token string, limit int) ([]map[string]dosa.FieldValue, string, error) { 91 keyspace := c.KsMapper.Keyspace(ei.Ref.Scope, ei.Ref.NamePrefix) 92 table := ei.Def.Name 93 94 tokenBytes, err := base64.StdEncoding.DecodeString(token) 95 if err != nil { 96 return nil, "", errors.Wrapf(err, "bad token %q", token) 97 } 98 99 fields := fieldsToRead 100 if len(fields) == 0 { 101 for _, c := range ei.Def.Columns { 102 fields = append(fields, c.Name) 103 } 104 } 105 106 sort.Strings(fields) 107 108 conds, values, err := prepareConditions(columnConditions) 109 stmt, err := SelectStmt( 110 Keyspace(keyspace), 111 Table(table), 112 Columns(fields), 113 Conditions(conds), 114 ) 115 if err != nil { 116 return nil, "", errors.Wrap(err, "failed to create cql statement") 117 } 118 119 iter := c.Session.Query(stmt, values...).PageState(tokenBytes).PageSize(limit).Iter() 120 resultSet := make([]map[string]interface{}, iter.NumRows()) 121 pos := 0 122 for { 123 // New map each iteration 124 row := make(map[string]interface{}) 125 if !iter.MapScan(row) { 126 break 127 } 128 resultSet[pos] = row 129 pos++ 130 } 131 if err := iter.Close(); err != nil { 132 return nil, "", errors.Wrapf(err, "failed execute range query in Cassandra: %s", stmt) 133 } 134 135 res := make([]map[string]dosa.FieldValue, len(resultSet)) 136 for i, row := range resultSet { 137 res[i] = convertToDOSATypes(ei, row) 138 } 139 140 nextToken := base64.StdEncoding.EncodeToString(iter.PageState()) 141 return res, nextToken, nil 142 } 143 144 // Scan returns the data from entire table 145 func (c *Connector) Scan(ctx context.Context, ei *dosa.EntityInfo, fieldsToRead []string, token string, 146 limit int) ([]map[string]dosa.FieldValue, string, error) { 147 return c.commonQuery(ctx, ei, nil, fieldsToRead, token, limit) 148 }