helpers.go (462 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Content before git sha 34fdeebefcbf183ed7f916f931aa0586fdaa1b40
* Copyright (c) 2012, The Gocql authors,
* provided under the BSD-3-Clause License.
* See the NOTICE file distributed with this work for additional information.
*/
package gocql
import (
"encoding/hex"
"fmt"
"math/big"
"net"
"reflect"
"strconv"
"strings"
"time"
"gopkg.in/inf.v0"
)
type RowData struct {
Columns []string
Values []interface{}
}
func goType(t TypeInfo) (reflect.Type, error) {
switch t.Type() {
case TypeVarchar, TypeAscii, TypeInet, TypeText:
return reflect.TypeOf(*new(string)), nil
case TypeBigInt, TypeCounter:
return reflect.TypeOf(*new(int64)), nil
case TypeTime:
return reflect.TypeOf(*new(time.Duration)), nil
case TypeTimestamp:
return reflect.TypeOf(*new(time.Time)), nil
case TypeBlob:
return reflect.TypeOf(*new([]byte)), nil
case TypeBoolean:
return reflect.TypeOf(*new(bool)), nil
case TypeFloat:
return reflect.TypeOf(*new(float32)), nil
case TypeDouble:
return reflect.TypeOf(*new(float64)), nil
case TypeInt:
return reflect.TypeOf(*new(int)), nil
case TypeSmallInt:
return reflect.TypeOf(*new(int16)), nil
case TypeTinyInt:
return reflect.TypeOf(*new(int8)), nil
case TypeDecimal:
return reflect.TypeOf(*new(*inf.Dec)), nil
case TypeUUID, TypeTimeUUID:
return reflect.TypeOf(*new(UUID)), nil
case TypeList, TypeSet:
elemType, err := goType(t.(CollectionType).Elem)
if err != nil {
return nil, err
}
return reflect.SliceOf(elemType), nil
case TypeMap:
keyType, err := goType(t.(CollectionType).Key)
if err != nil {
return nil, err
}
valueType, err := goType(t.(CollectionType).Elem)
if err != nil {
return nil, err
}
return reflect.MapOf(keyType, valueType), nil
case TypeVarint:
return reflect.TypeOf(*new(*big.Int)), nil
case TypeTuple:
// what can we do here? all there is to do is to make a list of interface{}
tuple := t.(TupleTypeInfo)
return reflect.TypeOf(make([]interface{}, len(tuple.Elems))), nil
case TypeUDT:
return reflect.TypeOf(make(map[string]interface{})), nil
case TypeDate:
return reflect.TypeOf(*new(time.Time)), nil
case TypeDuration:
return reflect.TypeOf(*new(Duration)), nil
default:
return nil, fmt.Errorf("cannot create Go type for unknown CQL type %s", t)
}
}
func dereference(i interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(i)).Interface()
}
func getCassandraBaseType(name string) Type {
switch name {
case "ascii":
return TypeAscii
case "bigint":
return TypeBigInt
case "blob":
return TypeBlob
case "boolean":
return TypeBoolean
case "counter":
return TypeCounter
case "date":
return TypeDate
case "decimal":
return TypeDecimal
case "double":
return TypeDouble
case "duration":
return TypeDuration
case "float":
return TypeFloat
case "int":
return TypeInt
case "smallint":
return TypeSmallInt
case "tinyint":
return TypeTinyInt
case "time":
return TypeTime
case "timestamp":
return TypeTimestamp
case "uuid":
return TypeUUID
case "varchar":
return TypeVarchar
case "text":
return TypeText
case "varint":
return TypeVarint
case "timeuuid":
return TypeTimeUUID
case "inet":
return TypeInet
case "MapType":
return TypeMap
case "ListType":
return TypeList
case "SetType":
return TypeSet
case "TupleType":
return TypeTuple
default:
return TypeCustom
}
}
// TODO: Cover with unit tests.
// Parses long Java-style type definition to internal data structures.
func getCassandraLongType(name string, protoVer byte, logger StdLogger) TypeInfo {
if strings.HasPrefix(name, SET_TYPE) {
return CollectionType{
NativeType: NewNativeType(protoVer, TypeSet),
Elem: getCassandraLongType(unwrapCompositeTypeDefinition(name, SET_TYPE, '('), protoVer, logger),
}
} else if strings.HasPrefix(name, LIST_TYPE) {
return CollectionType{
NativeType: NewNativeType(protoVer, TypeList),
Elem: getCassandraLongType(unwrapCompositeTypeDefinition(name, LIST_TYPE, '('), protoVer, logger),
}
} else if strings.HasPrefix(name, MAP_TYPE) {
names := splitJavaCompositeTypes(name, MAP_TYPE)
if len(names) != 2 {
logger.Printf("gocql: error parsing map type, it has %d subelements, expecting 2\n", len(names))
return NewNativeType(protoVer, TypeCustom)
}
return CollectionType{
NativeType: NewNativeType(protoVer, TypeMap),
Key: getCassandraLongType(names[0], protoVer, logger),
Elem: getCassandraLongType(names[1], protoVer, logger),
}
} else if strings.HasPrefix(name, TUPLE_TYPE) {
names := splitJavaCompositeTypes(name, TUPLE_TYPE)
types := make([]TypeInfo, len(names))
for i, name := range names {
types[i] = getCassandraLongType(name, protoVer, logger)
}
return TupleTypeInfo{
NativeType: NewNativeType(protoVer, TypeTuple),
Elems: types,
}
} else if strings.HasPrefix(name, UDT_TYPE) {
names := splitJavaCompositeTypes(name, UDT_TYPE)
fields := make([]UDTField, len(names)-2)
for i := 2; i < len(names); i++ {
spec := strings.Split(names[i], ":")
fieldName, _ := hex.DecodeString(spec[0])
fields[i-2] = UDTField{
Name: string(fieldName),
Type: getCassandraLongType(spec[1], protoVer, logger),
}
}
udtName, _ := hex.DecodeString(names[1])
return UDTTypeInfo{
NativeType: NewNativeType(protoVer, TypeUDT),
KeySpace: names[0],
Name: string(udtName),
Elements: fields,
}
} else if strings.HasPrefix(name, VECTOR_TYPE) {
names := splitJavaCompositeTypes(name, VECTOR_TYPE)
subType := getCassandraLongType(strings.TrimSpace(names[0]), protoVer, logger)
dim, err := strconv.Atoi(strings.TrimSpace(names[1]))
if err != nil {
logger.Printf("gocql: error parsing vector dimensions: %v\n", err)
return NewNativeType(protoVer, TypeCustom)
}
return VectorType{
NativeType: NewCustomType(protoVer, TypeCustom, VECTOR_TYPE),
SubType: subType,
Dimensions: dim,
}
} else {
// basic type
return NativeType{
proto: protoVer,
typ: getApacheCassandraType(name),
}
}
}
// Parses short CQL type representation (e.g. map<text, text>) to internal data structures.
func getCassandraType(name string, protoVer byte, logger StdLogger) TypeInfo {
if strings.HasPrefix(name, "frozen<") {
return getCassandraType(unwrapCompositeTypeDefinition(name, "frozen", '<'), protoVer, logger)
} else if strings.HasPrefix(name, "set<") {
return CollectionType{
NativeType: NewNativeType(protoVer, TypeSet),
Elem: getCassandraType(unwrapCompositeTypeDefinition(name, "set", '<'), protoVer, logger),
}
} else if strings.HasPrefix(name, "list<") {
return CollectionType{
NativeType: NewNativeType(protoVer, TypeList),
Elem: getCassandraType(unwrapCompositeTypeDefinition(name, "list", '<'), protoVer, logger),
}
} else if strings.HasPrefix(name, "map<") {
names := splitCQLCompositeTypes(name, "map")
if len(names) != 2 {
logger.Printf("Error parsing map type, it has %d subelements, expecting 2\n", len(names))
return NewNativeType(protoVer, TypeCustom)
}
return CollectionType{
NativeType: NewNativeType(protoVer, TypeMap),
Key: getCassandraType(names[0], protoVer, logger),
Elem: getCassandraType(names[1], protoVer, logger),
}
} else if strings.HasPrefix(name, "tuple<") {
names := splitCQLCompositeTypes(name, "tuple")
types := make([]TypeInfo, len(names))
for i, name := range names {
types[i] = getCassandraType(name, protoVer, logger)
}
return TupleTypeInfo{
NativeType: NewNativeType(protoVer, TypeTuple),
Elems: types,
}
} else if strings.HasPrefix(name, "vector<") {
names := splitCQLCompositeTypes(name, "vector")
subType := getCassandraType(strings.TrimSpace(names[0]), protoVer, logger)
dim, _ := strconv.Atoi(strings.TrimSpace(names[1]))
return VectorType{
NativeType: NewCustomType(protoVer, TypeCustom, VECTOR_TYPE),
SubType: subType,
Dimensions: dim,
}
} else {
return NativeType{
proto: protoVer,
typ: getCassandraBaseType(name),
}
}
}
func splitCQLCompositeTypes(name string, typeName string) []string {
return splitCompositeTypes(name, typeName, '<', '>')
}
func splitJavaCompositeTypes(name string, typeName string) []string {
return splitCompositeTypes(name, typeName, '(', ')')
}
func unwrapCompositeTypeDefinition(name string, typeName string, typeOpen int32) string {
return strings.TrimPrefix(name[:len(name)-1], typeName+string(typeOpen))
}
func splitCompositeTypes(name string, typeName string, typeOpen int32, typeClose int32) []string {
def := unwrapCompositeTypeDefinition(name, typeName, typeOpen)
if !strings.Contains(def, string(typeOpen)) {
parts := strings.Split(def, ",")
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
return parts
}
var parts []string
lessCount := 0
segment := ""
for _, char := range def {
if char == ',' && lessCount == 0 {
if segment != "" {
parts = append(parts, strings.TrimSpace(segment))
}
segment = ""
continue
}
segment += string(char)
if char == typeOpen {
lessCount++
} else if char == typeClose {
lessCount--
}
}
if segment != "" {
parts = append(parts, strings.TrimSpace(segment))
}
return parts
}
func getApacheCassandraType(class string) Type {
switch strings.TrimPrefix(class, apacheCassandraTypePrefix) {
case "AsciiType":
return TypeAscii
case "LongType":
return TypeBigInt
case "BytesType":
return TypeBlob
case "BooleanType":
return TypeBoolean
case "CounterColumnType":
return TypeCounter
case "DecimalType":
return TypeDecimal
case "DoubleType":
return TypeDouble
case "FloatType":
return TypeFloat
case "Int32Type":
return TypeInt
case "ShortType":
return TypeSmallInt
case "ByteType":
return TypeTinyInt
case "TimeType":
return TypeTime
case "DateType", "TimestampType":
return TypeTimestamp
case "UUIDType", "LexicalUUIDType":
return TypeUUID
case "UTF8Type":
return TypeVarchar
case "IntegerType":
return TypeVarint
case "TimeUUIDType":
return TypeTimeUUID
case "InetAddressType":
return TypeInet
case "MapType":
return TypeMap
case "ListType":
return TypeList
case "SetType":
return TypeSet
case "TupleType":
return TypeTuple
case "DurationType":
return TypeDuration
case "SimpleDateType":
return TypeDate
case "UserType":
return TypeUDT
default:
return TypeCustom
}
}
func (r *RowData) rowMap(m map[string]interface{}) {
for i, column := range r.Columns {
val := dereference(r.Values[i])
if valVal := reflect.ValueOf(val); valVal.Kind() == reflect.Slice && !valVal.IsNil() {
valCopy := reflect.MakeSlice(valVal.Type(), valVal.Len(), valVal.Cap())
reflect.Copy(valCopy, valVal)
m[column] = valCopy.Interface()
} else {
m[column] = val
}
}
}
// TupeColumnName will return the column name of a tuple value in a column named
// c at index n. It should be used if a specific element within a tuple is needed
// to be extracted from a map returned from SliceMap or MapScan.
func TupleColumnName(c string, n int) string {
return fmt.Sprintf("%s[%d]", c, n)
}
// RowData returns the RowData for the iterator.
func (iter *Iter) RowData() (RowData, error) {
if iter.err != nil {
return RowData{}, iter.err
}
columns := make([]string, 0, len(iter.Columns()))
values := make([]interface{}, 0, len(iter.Columns()))
for _, column := range iter.Columns() {
if c, ok := column.TypeInfo.(TupleTypeInfo); !ok {
val, err := column.TypeInfo.NewWithError()
if err != nil {
iter.err = err
return RowData{}, err
}
columns = append(columns, column.Name)
values = append(values, val)
} else {
for i, elem := range c.Elems {
columns = append(columns, TupleColumnName(column.Name, i))
val, err := elem.NewWithError()
if err != nil {
iter.err = err
return RowData{}, err
}
values = append(values, val)
}
}
}
rowData := RowData{
Columns: columns,
Values: values,
}
return rowData, nil
}
// TODO(zariel): is it worth exporting this?
func (iter *Iter) rowMap() (map[string]interface{}, error) {
if iter.err != nil {
return nil, iter.err
}
rowData, err := iter.RowData()
if err != nil {
return nil, err
}
iter.Scan(rowData.Values...)
m := make(map[string]interface{}, len(rowData.Columns))
rowData.rowMap(m)
return m, nil
}
// SliceMap is a helper function to make the API easier to use
// returns the data from the query in the form of []map[string]interface{}
func (iter *Iter) SliceMap() ([]map[string]interface{}, error) {
if iter.err != nil {
return nil, iter.err
}
// Not checking for the error because we just did
rowData, err := iter.RowData()
if err != nil {
return nil, err
}
dataToReturn := make([]map[string]interface{}, 0)
for iter.Scan(rowData.Values...) {
m := make(map[string]interface{}, len(rowData.Columns))
rowData.rowMap(m)
dataToReturn = append(dataToReturn, m)
}
if iter.err != nil {
return nil, iter.err
}
return dataToReturn, nil
}
// MapScan takes a map[string]interface{} and populates it with a row
// that is returned from cassandra.
//
// Each call to MapScan() must be called with a new map object.
// During the call to MapScan() any pointers in the existing map
// are replaced with non pointer types before the call returns
//
// iter := session.Query(`SELECT * FROM mytable`).Iter()
// for {
// // New map each iteration
// row := make(map[string]interface{})
// if !iter.MapScan(row) {
// break
// }
// // Do things with row
// if fullname, ok := row["fullname"]; ok {
// fmt.Printf("Full Name: %s\n", fullname)
// }
// }
//
// You can also pass pointers in the map before each call
//
// var fullName FullName // Implements gocql.Unmarshaler and gocql.Marshaler interfaces
// var address net.IP
// var age int
// iter := session.Query(`SELECT * FROM scan_map_table`).Iter()
// for {
// // New map each iteration
// row := map[string]interface{}{
// "fullname": &fullName,
// "age": &age,
// "address": &address,
// }
// if !iter.MapScan(row) {
// break
// }
// fmt.Printf("First: %s Age: %d Address: %q\n", fullName.FirstName, age, address)
// }
func (iter *Iter) MapScan(m map[string]interface{}) bool {
if iter.err != nil {
return false
}
rowData, err := iter.RowData()
if err != nil {
return false
}
for i, col := range rowData.Columns {
if dest, ok := m[col]; ok {
rowData.Values[i] = dest
}
}
if iter.Scan(rowData.Values...) {
rowData.rowMap(m)
return true
}
return false
}
func copyBytes(p []byte) []byte {
b := make([]byte, len(p))
copy(b, p)
return b
}
var failDNS = false
func LookupIP(host string) ([]net.IP, error) {
if failDNS {
return nil, &net.DNSError{}
}
return net.LookupIP(host)
}