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  }