| // Copyright (c) 2012 The gocql Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gocql |
| |
| import ( |
| "fmt" |
| "math/big" |
| "net" |
| "reflect" |
| "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 |
| } |
| } |
| |
| func getCassandraType(name string, logger StdLogger) TypeInfo { |
| if strings.HasPrefix(name, "frozen<") { |
| return getCassandraType(strings.TrimPrefix(name[:len(name)-1], "frozen<"), logger) |
| } else if strings.HasPrefix(name, "set<") { |
| return CollectionType{ |
| NativeType: NativeType{typ: TypeSet}, |
| Elem: getCassandraType(strings.TrimPrefix(name[:len(name)-1], "set<"), logger), |
| } |
| } else if strings.HasPrefix(name, "list<") { |
| return CollectionType{ |
| NativeType: NativeType{typ: TypeList}, |
| Elem: getCassandraType(strings.TrimPrefix(name[:len(name)-1], "list<"), logger), |
| } |
| } else if strings.HasPrefix(name, "map<") { |
| names := splitCompositeTypes(strings.TrimPrefix(name[:len(name)-1], "map<")) |
| if len(names) != 2 { |
| logger.Printf("Error parsing map type, it has %d subelements, expecting 2\n", len(names)) |
| return NativeType{ |
| typ: TypeCustom, |
| } |
| } |
| return CollectionType{ |
| NativeType: NativeType{typ: TypeMap}, |
| Key: getCassandraType(names[0], logger), |
| Elem: getCassandraType(names[1], logger), |
| } |
| } else if strings.HasPrefix(name, "tuple<") { |
| names := splitCompositeTypes(strings.TrimPrefix(name[:len(name)-1], "tuple<")) |
| types := make([]TypeInfo, len(names)) |
| |
| for i, name := range names { |
| types[i] = getCassandraType(name, logger) |
| } |
| |
| return TupleTypeInfo{ |
| NativeType: NativeType{typ: TypeTuple}, |
| Elems: types, |
| } |
| } else { |
| return NativeType{ |
| typ: getCassandraBaseType(name), |
| } |
| } |
| } |
| |
| func splitCompositeTypes(name string) []string { |
| if !strings.Contains(name, "<") { |
| return strings.Split(name, ", ") |
| } |
| var parts []string |
| lessCount := 0 |
| segment := "" |
| for _, char := range name { |
| if char == ',' && lessCount == 0 { |
| if segment != "" { |
| parts = append(parts, strings.TrimSpace(segment)) |
| } |
| segment = "" |
| continue |
| } |
| segment += string(char) |
| if char == '<' { |
| lessCount++ |
| } else if char == '>' { |
| lessCount-- |
| } |
| } |
| if segment != "" { |
| parts = append(parts, strings.TrimSpace(segment)) |
| } |
| return parts |
| } |
| |
| func apacheToCassandraType(t string) string { |
| t = strings.Replace(t, apacheCassandraTypePrefix, "", -1) |
| t = strings.Replace(t, "(", "<", -1) |
| t = strings.Replace(t, ")", ">", -1) |
| types := strings.FieldsFunc(t, func(r rune) bool { |
| return r == '<' || r == '>' || r == ',' |
| }) |
| for _, typ := range types { |
| t = strings.Replace(t, typ, getApacheCassandraType(typ).String(), -1) |
| } |
| // This is done so it exactly matches what Cassandra returns |
| return strings.Replace(t, ",", ", ", -1) |
| } |
| |
| 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 |
| 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 { |
| 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) |
| } |
| |
| 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 { |
| 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 { |
| 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, _ := iter.RowData() |
| 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, _ := iter.RowData() |
| 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 |
| } |
| |
| // Not checking for the error because we just did |
| rowData, _ := iter.RowData() |
| |
| 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) |
| |
| } |