Browse Source

Merge pull request #352 from Zariel/v3-tuples

V3 tuples
Ben Hood 11 năm trước cách đây
mục cha
commit
ecfc8f8133
10 tập tin đã thay đổi với 610 bổ sung và 366 xóa
  1. 12 12
      cassandra_test.go
  2. 2 2
      conn.go
  3. 52 26
      frame.go
  4. 49 50
      helpers.go
  5. 182 79
      marshal.go
  6. 126 100
      marshal_test.go
  7. 23 17
      metadata.go
  8. 73 66
      metadata_test.go
  9. 40 14
      session.go
  10. 51 0
      tuple_test.go

+ 12 - 12
cassandra_test.go

@@ -530,11 +530,11 @@ type FullName struct {
 	LastName  string
 }
 
-func (n FullName) MarshalCQL(info *TypeInfo) ([]byte, error) {
+func (n FullName) MarshalCQL(info TypeInfo) ([]byte, error) {
 	return []byte(n.FirstName + " " + n.LastName), nil
 }
 
-func (n *FullName) UnmarshalCQL(info *TypeInfo, data []byte) error {
+func (n *FullName) UnmarshalCQL(info TypeInfo, data []byte) error {
 	t := strings.SplitN(string(data), " ", 2)
 	n.FirstName, n.LastName = t[0], t[1]
 	return nil
@@ -1009,8 +1009,8 @@ func injectInvalidPreparedStatement(t *testing.T, session *Session, table string
 					Keyspace: "gocql_test",
 					Table:    table,
 					Name:     "foo",
-					TypeInfo: &TypeInfo{
-						Type: TypeVarchar,
+					TypeInfo: NativeType{
+						typ: TypeVarchar,
 					},
 				},
 			}},
@@ -1769,8 +1769,8 @@ func TestRoutingKey(t *testing.T) {
 	if routingKeyInfo.types[0] == nil {
 		t.Fatal("Expected routing key types[0] to be non-nil")
 	}
-	if routingKeyInfo.types[0].Type != TypeInt {
-		t.Fatalf("Expected routing key types[0].Type to be %v but was %v", TypeInt, routingKeyInfo.types[0])
+	if routingKeyInfo.types[0].Type() != TypeInt {
+		t.Fatalf("Expected routing key types[0].Type to be %v but was %v", TypeInt, routingKeyInfo.types[0].Type())
 	}
 
 	// verify the cache is working
@@ -1790,8 +1790,8 @@ func TestRoutingKey(t *testing.T) {
 	if routingKeyInfo.types[0] == nil {
 		t.Fatal("Expected routing key types[0] to be non-nil")
 	}
-	if routingKeyInfo.types[0].Type != TypeInt {
-		t.Fatalf("Expected routing key types[0] to be %v but was %v", TypeInt, routingKeyInfo.types[0])
+	if routingKeyInfo.types[0].Type() != TypeInt {
+		t.Fatalf("Expected routing key types[0] to be %v but was %v", TypeInt, routingKeyInfo.types[0].Type())
 	}
 	cacheSize := session.routingKeyInfoCache.lru.Len()
 	if cacheSize != 1 {
@@ -1830,14 +1830,14 @@ func TestRoutingKey(t *testing.T) {
 	if routingKeyInfo.types[0] == nil {
 		t.Fatal("Expected routing key types[0] to be non-nil")
 	}
-	if routingKeyInfo.types[0].Type != TypeInt {
-		t.Fatalf("Expected routing key types[0] to be %v but was %v", TypeInt, routingKeyInfo.types[0])
+	if routingKeyInfo.types[0].Type() != TypeInt {
+		t.Fatalf("Expected routing key types[0] to be %v but was %v", TypeInt, routingKeyInfo.types[0].Type())
 	}
 	if routingKeyInfo.types[1] == nil {
 		t.Fatal("Expected routing key types[1] to be non-nil")
 	}
-	if routingKeyInfo.types[1].Type != TypeInt {
-		t.Fatalf("Expected routing key types[0] to be %v but was %v", TypeInt, routingKeyInfo.types[1])
+	if routingKeyInfo.types[1].Type() != TypeInt {
+		t.Fatalf("Expected routing key types[0] to be %v but was %v", TypeInt, routingKeyInfo.types[1].Type())
 	}
 
 	query = session.Query("SELECT * FROM test_composite_routing_key WHERE second_id=? AND first_id=?", 1, 2)

+ 2 - 2
conn.go

@@ -503,8 +503,8 @@ func (c *Conn) executeQuery(qry *Query) *Iter {
 		return &Iter{}
 	case *resultRowsFrame:
 		iter := &Iter{
-			columns: x.meta.columns,
-			rows:    x.rows,
+			meta: x.meta,
+			rows: x.rows,
 		}
 
 		if len(x.meta.pagingState) > 0 {

+ 52 - 26
frame.go

@@ -569,38 +569,52 @@ func (f *framer) writePrepareFrame(stream int, statement string) error {
 	return f.finishWrite()
 }
 
-func (f *framer) readTypeInfo() *TypeInfo {
+func (f *framer) readTypeInfo() TypeInfo {
+	// TODO: factor this out so the same code paths can be used to parse custom
+	// types and other types, as much of the logic will be duplicated.
 	id := f.readShort()
-	typ := &TypeInfo{
-		// we need to pass proto to the marshaller here
-		Proto: f.proto,
-		Type:  Type(id),
+
+	simple := NativeType{
+		proto: f.proto,
+		typ:   Type(id),
 	}
 
-	switch typ.Type {
-	case TypeCustom:
-		typ.Custom = f.readString()
-		if cassType := getApacheCassandraType(typ.Custom); cassType != TypeCustom {
-			typ = &TypeInfo{
-				Proto: f.proto,
-				Type:  cassType,
-			}
-			switch typ.Type {
-			case TypeMap:
-				typ.Key = f.readTypeInfo()
-				fallthrough
-			case TypeList, TypeSet:
-				typ.Elem = f.readTypeInfo()
-			}
+	if simple.typ == TypeCustom {
+		simple.custom = f.readString()
+		if cassType := getApacheCassandraType(simple.custom); cassType != TypeCustom {
+			simple.typ = cassType
 		}
-	case TypeMap:
-		typ.Key = f.readTypeInfo()
-		fallthrough
-	case TypeList, TypeSet:
-		typ.Elem = f.readTypeInfo()
 	}
 
-	return typ
+	switch simple.typ {
+	case TypeTuple:
+		n := f.readShort()
+		tuple := TupleTypeInfo{
+			NativeType: simple,
+			Elems:      make([]TypeInfo, n),
+		}
+
+		for i := 0; i < int(n); i++ {
+			tuple.Elems[i] = f.readTypeInfo()
+		}
+
+		return tuple
+
+	case TypeMap, TypeList, TypeSet:
+		collection := CollectionType{
+			NativeType: simple,
+		}
+
+		if simple.typ == TypeMap {
+			collection.Key = f.readTypeInfo()
+		}
+
+		collection.Elem = f.readTypeInfo()
+
+		return collection
+	}
+
+	return simple
 }
 
 type resultMetadata struct {
@@ -610,6 +624,11 @@ type resultMetadata struct {
 	pagingState []byte
 
 	columns []ColumnInfo
+
+	// this is a count of the total number of columns which can be scanned,
+	// it is at minimum len(columns) but may be larger, for instance when a column
+	// is a UDT or tuple.
+	actualColCount int
 }
 
 func (r resultMetadata) String() string {
@@ -622,6 +641,7 @@ func (f *framer) parseResultMetadata() resultMetadata {
 	}
 
 	colCount := f.readInt()
+	meta.actualColCount = colCount
 
 	if meta.flags&flagHasMorePages == flagHasMorePages {
 		meta.pagingState = f.readBytes()
@@ -653,6 +673,12 @@ func (f *framer) parseResultMetadata() resultMetadata {
 
 		col.Name = f.readString()
 		col.TypeInfo = f.readTypeInfo()
+		switch v := col.TypeInfo.(type) {
+		// maybe also UDT
+		case TupleTypeInfo:
+			// -1 because we already included the tuple column
+			meta.actualColCount += len(v.Elems) - 1
+		}
 	}
 
 	meta.columns = cols

+ 49 - 50
helpers.go

@@ -18,14 +18,8 @@ type RowData struct {
 	Values  []interface{}
 }
 
-// New creates a pointer to an empty version of whatever type
-// is referenced by the TypeInfo receiver
-func (t *TypeInfo) New() interface{} {
-	return reflect.New(goType(t)).Interface()
-}
-
-func goType(t *TypeInfo) reflect.Type {
-	switch t.Type {
+func goType(t TypeInfo) reflect.Type {
+	switch t.Type() {
 	case TypeVarchar, TypeAscii, TypeInet:
 		return reflect.TypeOf(*new(string))
 	case TypeBigInt, TypeCounter:
@@ -47,11 +41,15 @@ func goType(t *TypeInfo) reflect.Type {
 	case TypeUUID, TypeTimeUUID:
 		return reflect.TypeOf(*new(UUID))
 	case TypeList, TypeSet:
-		return reflect.SliceOf(goType(t.Elem))
+		return reflect.SliceOf(goType(t.(CollectionType).Elem))
 	case TypeMap:
-		return reflect.MapOf(goType(t.Key), goType(t.Elem))
+		return reflect.MapOf(goType(t.(CollectionType).Key), goType(t.(CollectionType).Elem))
 	case TypeVarint:
 		return reflect.TypeOf(*new(*big.Int))
+	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)))
 	default:
 		return nil
 	}
@@ -62,47 +60,48 @@ func dereference(i interface{}) interface{} {
 }
 
 func getApacheCassandraType(class string) Type {
-	if strings.HasPrefix(class, apacheCassandraTypePrefix) {
-		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 "DateType", "TimestampType":
-			return TypeTimestamp
-		case "UUIDType":
-			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
-		}
+	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 "DateType", "TimestampType":
+		return TypeTimestamp
+	case "UUIDType":
+		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
+	default:
+		return TypeCustom
 	}
-	return TypeCustom
 }
 
 func (r *RowData) rowMap(m map[string]interface{}) {

+ 182 - 79
marshal.go

@@ -25,22 +25,22 @@ var (
 // Marshaler is the interface implemented by objects that can marshal
 // themselves into values understood by Cassandra.
 type Marshaler interface {
-	MarshalCQL(info *TypeInfo) ([]byte, error)
+	MarshalCQL(info TypeInfo) ([]byte, error)
 }
 
 // Unmarshaler is the interface implemented by objects that can unmarshal
 // a Cassandra specific description of themselves.
 type Unmarshaler interface {
-	UnmarshalCQL(info *TypeInfo, data []byte) error
+	UnmarshalCQL(info TypeInfo, data []byte) error
 }
 
 // Marshal returns the CQL encoding of the value for the Cassandra
 // internal type described by the info parameter.
-func Marshal(info *TypeInfo, value interface{}) ([]byte, error) {
+func Marshal(info TypeInfo, value interface{}) ([]byte, error) {
 	if value == nil {
 		return nil, nil
 	}
-	if info.Proto < protoVersion1 {
+	if info.Version() < protoVersion1 {
 		panic("protocol version not set")
 	}
 
@@ -56,7 +56,7 @@ func Marshal(info *TypeInfo, value interface{}) ([]byte, error) {
 		}
 	}
 
-	switch info.Type {
+	switch info.Type() {
 	case TypeVarchar, TypeAscii, TypeBlob:
 		return marshalVarchar(info, value)
 	case TypeBoolean:
@@ -91,7 +91,7 @@ func Marshal(info *TypeInfo, value interface{}) ([]byte, error) {
 // Unmarshal parses the CQL encoded data based on the info parameter that
 // describes the Cassandra internal data type and stores the result in the
 // value pointed by value.
-func Unmarshal(info *TypeInfo, data []byte, value interface{}) error {
+func Unmarshal(info TypeInfo, data []byte, value interface{}) error {
 	if v, ok := value.(Unmarshaler); ok {
 		return v.UnmarshalCQL(info, data)
 	}
@@ -99,7 +99,7 @@ func Unmarshal(info *TypeInfo, data []byte, value interface{}) error {
 		return unmarshalNullable(info, data, value)
 	}
 
-	switch info.Type {
+	switch info.Type() {
 	case TypeVarchar, TypeAscii, TypeBlob:
 		return unmarshalVarchar(info, data, value)
 	case TypeBoolean:
@@ -128,6 +128,8 @@ func Unmarshal(info *TypeInfo, data []byte, value interface{}) error {
 		return unmarshalUUID(info, data, value)
 	case TypeInet:
 		return unmarshalInet(info, data, value)
+	case TypeTuple:
+		return unmarshalTuple(info, data, value)
 	}
 	// TODO(tux21b): add the remaining types
 	return fmt.Errorf("can not unmarshal %s into %T", info, value)
@@ -138,25 +140,25 @@ func isNullableValue(value interface{}) bool {
 	return v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Ptr
 }
 
-func isNullData(info *TypeInfo, data []byte) bool {
+func isNullData(info TypeInfo, data []byte) bool {
 	return len(data) == 0
 }
 
-func unmarshalNullable(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalNullable(info TypeInfo, data []byte, value interface{}) error {
 	valueRef := reflect.ValueOf(value)
 
 	if isNullData(info, data) {
 		nilValue := reflect.Zero(valueRef.Type().Elem())
 		valueRef.Elem().Set(nilValue)
 		return nil
-	} else {
-		newValue := reflect.New(valueRef.Type().Elem().Elem())
-		valueRef.Elem().Set(newValue)
-		return Unmarshal(info, data, newValue.Interface())
 	}
+
+	newValue := reflect.New(valueRef.Type().Elem().Elem())
+	valueRef.Elem().Set(newValue)
+	return Unmarshal(info, data, newValue.Interface())
 }
 
-func marshalVarchar(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalVarchar(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -177,7 +179,7 @@ func marshalVarchar(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func unmarshalVarchar(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalVarchar(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -216,7 +218,7 @@ func unmarshalVarchar(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
-func marshalInt(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalInt(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -291,7 +293,7 @@ func decInt(x []byte) int32 {
 	return int32(x[0])<<24 | int32(x[1])<<16 | int32(x[2])<<8 | int32(x[3])
 }
 
-func marshalBigInt(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalBigInt(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -357,15 +359,15 @@ func bytesToInt64(data []byte) (ret int64) {
 	return ret
 }
 
-func unmarshalBigInt(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalBigInt(info TypeInfo, data []byte, value interface{}) error {
 	return unmarshalIntlike(info, decBigInt(data), data, value)
 }
 
-func unmarshalInt(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalInt(info TypeInfo, data []byte, value interface{}) error {
 	return unmarshalIntlike(info, int64(decInt(data)), data, value)
 }
 
-func unmarshalVarint(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalVarint(info TypeInfo, data []byte, value interface{}) error {
 	switch value.(type) {
 	case *big.Int:
 		return unmarshalIntlike(info, 0, data, value)
@@ -382,7 +384,7 @@ func unmarshalVarint(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalIntlike(info, int64Val, data, value)
 }
 
-func marshalVarint(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalVarint(info TypeInfo, value interface{}) ([]byte, error) {
 	var (
 		retBytes []byte
 		err      error
@@ -431,7 +433,7 @@ func marshalVarint(info *TypeInfo, value interface{}) ([]byte, error) {
 	return retBytes, err
 }
 
-func unmarshalIntlike(info *TypeInfo, int64Val int64, data []byte, value interface{}) error {
+func unmarshalIntlike(info TypeInfo, int64Val int64, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case *int:
 		if ^uint(0) == math.MaxUint32 && (int64Val < math.MinInt32 || int64Val > math.MaxInt32) {
@@ -576,7 +578,7 @@ func decBigInt(data []byte) int64 {
 		int64(data[6])<<8 | int64(data[7])
 }
 
-func marshalBool(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalBool(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -598,7 +600,7 @@ func encBool(v bool) []byte {
 	return []byte{0}
 }
 
-func unmarshalBool(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalBool(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -626,7 +628,7 @@ func decBool(v []byte) bool {
 	return v[0] != 0
 }
 
-func marshalFloat(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalFloat(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -641,7 +643,7 @@ func marshalFloat(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func unmarshalFloat(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalFloat(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -662,7 +664,7 @@ func unmarshalFloat(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
-func marshalDouble(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalDouble(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -677,7 +679,7 @@ func marshalDouble(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func unmarshalDouble(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalDouble(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -698,7 +700,7 @@ func unmarshalDouble(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
-func marshalDecimal(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalDecimal(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -716,7 +718,7 @@ func marshalDecimal(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func unmarshalDecimal(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalDecimal(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -769,7 +771,7 @@ func encBigInt2C(n *big.Int) []byte {
 	return nil
 }
 
-func marshalTimestamp(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalTimestamp(info TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:
 		return v.MarshalCQL(info)
@@ -787,7 +789,7 @@ func marshalTimestamp(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func unmarshalTimestamp(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalTimestamp(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -817,8 +819,8 @@ func unmarshalTimestamp(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
-func writeCollectionSize(info *TypeInfo, n int, buf *bytes.Buffer) error {
-	if info.Proto > protoVersion2 {
+func writeCollectionSize(info CollectionType, n int, buf *bytes.Buffer) error {
+	if info.proto > protoVersion2 {
 		if n > math.MaxInt32 {
 			return marshalErrorf("marshal: collection too large")
 		}
@@ -839,7 +841,12 @@ func writeCollectionSize(info *TypeInfo, n int, buf *bytes.Buffer) error {
 	return nil
 }
 
-func marshalList(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalList(info TypeInfo, value interface{}) ([]byte, error) {
+	listInfo, ok := info.(CollectionType)
+	if !ok {
+		return nil, marshalErrorf("marshal: can not marshal non collection type into list")
+	}
+
 	rv := reflect.ValueOf(value)
 	t := rv.Type()
 	k := t.Kind()
@@ -851,23 +858,22 @@ func marshalList(info *TypeInfo, value interface{}) ([]byte, error) {
 		buf := &bytes.Buffer{}
 		n := rv.Len()
 
-		if err := writeCollectionSize(info, n, buf); err != nil {
+		if err := writeCollectionSize(listInfo, n, buf); err != nil {
 			return nil, err
 		}
 
 		for i := 0; i < n; i++ {
-			item, err := Marshal(info.Elem, rv.Index(i).Interface())
+			item, err := Marshal(listInfo.Elem, rv.Index(i).Interface())
 			if err != nil {
 				return nil, err
 			}
-			if err := writeCollectionSize(info, len(item), buf); err != nil {
+			if err := writeCollectionSize(listInfo, len(item), buf); err != nil {
 				return nil, err
 			}
 			buf.Write(item)
 		}
 		return buf.Bytes(), nil
-	}
-	if k == reflect.Map {
+	case reflect.Map:
 		elem := t.Elem()
 		if elem.Kind() == reflect.Struct && elem.NumField() == 0 {
 			rkeys := rv.MapKeys()
@@ -875,14 +881,14 @@ func marshalList(info *TypeInfo, value interface{}) ([]byte, error) {
 			for i := 0; i < len(keys); i++ {
 				keys[i] = rkeys[i].Interface()
 			}
-			return marshalList(info, keys)
+			return marshalList(listInfo, keys)
 		}
 	}
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func readCollectionSize(info *TypeInfo, data []byte) (size, read int) {
-	if info.Proto > protoVersion2 {
+func readCollectionSize(info CollectionType, data []byte) (size, read int) {
+	if info.proto > protoVersion2 {
 		size = int(data[0])<<24 | int(data[1])<<16 | int(data[2])<<8 | int(data[3])
 		read = 4
 	} else {
@@ -892,7 +898,12 @@ func readCollectionSize(info *TypeInfo, data []byte) (size, read int) {
 	return
 }
 
-func unmarshalList(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalList(info TypeInfo, data []byte, value interface{}) error {
+	listInfo, ok := info.(CollectionType)
+	if !ok {
+		return unmarshalErrorf("unmarshal: can not unmarshal none collection type into list")
+	}
+
 	rv := reflect.ValueOf(value)
 	if rv.Kind() != reflect.Ptr {
 		return unmarshalErrorf("can not unmarshal into non-pointer %T", value)
@@ -913,7 +924,7 @@ func unmarshalList(info *TypeInfo, data []byte, value interface{}) error {
 		if len(data) < 2 {
 			return unmarshalErrorf("unmarshal list: unexpected eof")
 		}
-		n, p := readCollectionSize(info, data)
+		n, p := readCollectionSize(listInfo, data)
 		data = data[p:]
 		if k == reflect.Array {
 			if rv.Len() != n {
@@ -928,9 +939,9 @@ func unmarshalList(info *TypeInfo, data []byte, value interface{}) error {
 			if len(data) < 2 {
 				return unmarshalErrorf("unmarshal list: unexpected eof")
 			}
-			m, p := readCollectionSize(info, data)
+			m, p := readCollectionSize(listInfo, data)
 			data = data[p:]
-			if err := Unmarshal(info.Elem, data[:m], rv.Index(i).Addr().Interface()); err != nil {
+			if err := Unmarshal(listInfo.Elem, data[:m], rv.Index(i).Addr().Interface()); err != nil {
 				return err
 			}
 			data = data[m:]
@@ -940,7 +951,12 @@ func unmarshalList(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
-func marshalMap(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalMap(info TypeInfo, value interface{}) ([]byte, error) {
+	mapInfo, ok := info.(CollectionType)
+	if !ok {
+		return nil, marshalErrorf("marshal: can not marshal none collection type into map")
+	}
+
 	rv := reflect.ValueOf(value)
 	t := rv.Type()
 	if t.Kind() != reflect.Map {
@@ -952,26 +968,26 @@ func marshalMap(info *TypeInfo, value interface{}) ([]byte, error) {
 	buf := &bytes.Buffer{}
 	n := rv.Len()
 
-	if err := writeCollectionSize(info, n, buf); err != nil {
+	if err := writeCollectionSize(mapInfo, n, buf); err != nil {
 		return nil, err
 	}
 
 	keys := rv.MapKeys()
 	for _, key := range keys {
-		item, err := Marshal(info.Key, key.Interface())
+		item, err := Marshal(mapInfo.Key, key.Interface())
 		if err != nil {
 			return nil, err
 		}
-		if err := writeCollectionSize(info, len(item), buf); err != nil {
+		if err := writeCollectionSize(mapInfo, len(item), buf); err != nil {
 			return nil, err
 		}
 		buf.Write(item)
 
-		item, err = Marshal(info.Elem, rv.MapIndex(key).Interface())
+		item, err = Marshal(mapInfo.Elem, rv.MapIndex(key).Interface())
 		if err != nil {
 			return nil, err
 		}
-		if err := writeCollectionSize(info, len(item), buf); err != nil {
+		if err := writeCollectionSize(mapInfo, len(item), buf); err != nil {
 			return nil, err
 		}
 		buf.Write(item)
@@ -979,7 +995,12 @@ func marshalMap(info *TypeInfo, value interface{}) ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
-func unmarshalMap(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalMap(info TypeInfo, data []byte, value interface{}) error {
+	mapInfo, ok := info.(CollectionType)
+	if !ok {
+		return unmarshalErrorf("unmarshal: can not unmarshal none collection type into map")
+	}
+
 	rv := reflect.ValueOf(value)
 	if rv.Kind() != reflect.Ptr {
 		return unmarshalErrorf("can not unmarshal into non-pointer %T", value)
@@ -997,24 +1018,24 @@ func unmarshalMap(info *TypeInfo, data []byte, value interface{}) error {
 	if len(data) < 2 {
 		return unmarshalErrorf("unmarshal map: unexpected eof")
 	}
-	n, p := readCollectionSize(info, data)
+	n, p := readCollectionSize(mapInfo, data)
 	data = data[p:]
 	for i := 0; i < n; i++ {
 		if len(data) < 2 {
 			return unmarshalErrorf("unmarshal list: unexpected eof")
 		}
-		m, p := readCollectionSize(info, data)
+		m, p := readCollectionSize(mapInfo, data)
 		data = data[p:]
 		key := reflect.New(t.Key())
-		if err := Unmarshal(info.Key, data[:m], key.Interface()); err != nil {
+		if err := Unmarshal(mapInfo.Key, data[:m], key.Interface()); err != nil {
 			return err
 		}
 		data = data[m:]
 
-		m, p = readCollectionSize(info, data)
+		m, p = readCollectionSize(mapInfo, data)
 		data = data[p:]
 		val := reflect.New(t.Elem())
-		if err := Unmarshal(info.Elem, data[:m], val.Interface()); err != nil {
+		if err := Unmarshal(mapInfo.Elem, data[:m], val.Interface()); err != nil {
 			return err
 		}
 		data = data[m:]
@@ -1024,7 +1045,7 @@ func unmarshalMap(info *TypeInfo, data []byte, value interface{}) error {
 	return nil
 }
 
-func marshalUUID(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalUUID(info TypeInfo, value interface{}) ([]byte, error) {
 	switch val := value.(type) {
 	case UUID:
 		return val.Bytes(), nil
@@ -1042,7 +1063,7 @@ func marshalUUID(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
 
-func unmarshalUUID(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalUUID(info TypeInfo, data []byte, value interface{}) error {
 	if data == nil || len(data) == 0 {
 		switch v := value.(type) {
 		case *string:
@@ -1077,7 +1098,7 @@ func unmarshalUUID(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal X %s into %T", info, value)
 }
 
-func unmarshalTimeUUID(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalTimeUUID(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -1095,7 +1116,7 @@ func unmarshalTimeUUID(info *TypeInfo, data []byte, value interface{}) error {
 	}
 }
 
-func marshalInet(info *TypeInfo, value interface{}) ([]byte, error) {
+func marshalInet(info TypeInfo, value interface{}) ([]byte, error) {
 	// we return either the 4 or 16 byte representation of an
 	// ip address here otherwise the db value will be prefixed
 	// with the remaining byte values e.g. ::ffff:127.0.0.1 and not 127.0.0.1
@@ -1120,7 +1141,7 @@ func marshalInet(info *TypeInfo, value interface{}) ([]byte, error) {
 	return nil, marshalErrorf("cannot marshal %T into %s", value, info)
 }
 
-func unmarshalInet(info *TypeInfo, data []byte, value interface{}) error {
+func unmarshalInet(info TypeInfo, data []byte, value interface{}) error {
 	switch v := value.(type) {
 	case Unmarshaler:
 		return v.UnmarshalCQL(info, data)
@@ -1148,29 +1169,107 @@ func unmarshalInet(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("cannot unmarshal %s into %T", info, value)
 }
 
+// currently only support unmarshal into a list of values, this makes it possible
+// to support tuples without changing the query API. In the future this can be extend
+// to allow unmarshalling into custom tuple types.
+func unmarshalTuple(info TypeInfo, data []byte, value interface{}) error {
+	if v, ok := value.(Unmarshaler); ok {
+		return v.UnmarshalCQL(info, data)
+	}
+
+	tuple := info.(TupleTypeInfo)
+	switch v := value.(type) {
+	case []interface{}:
+		for i, elem := range tuple.Elems {
+			// each element inside data is a [bytes]
+			size := readInt(data)
+			data = data[4:]
+
+			err := Unmarshal(elem, data[:size], v[i])
+			if err != nil {
+				return err
+			}
+			data = data[size:]
+		}
+
+		return nil
+	}
+
+	return unmarshalErrorf("cannot unmarshal %s into %T", info, value)
+}
+
 // TypeInfo describes a Cassandra specific data type.
-type TypeInfo struct {
-	Proto  byte // version of the protocol
-	Type   Type
-	Key    *TypeInfo // only used for TypeMap
-	Elem   *TypeInfo // only used for TypeMap, TypeList and TypeSet
-	Custom string    // only used for TypeCustom
+type TypeInfo interface {
+	Type() Type
+	Version() byte
+	Custom() string
+
+	// New creates a pointer to an empty version of whatever type
+	// is referenced by the TypeInfo receiver
+	New() interface{}
 }
 
-// String returns a human readable name for the Cassandra datatype
-// described by t.
-func (t TypeInfo) String() string {
-	switch t.Type {
+type NativeType struct {
+	proto  byte
+	typ    Type
+	custom string // only used for TypeCustom
+}
+
+func (t NativeType) New() interface{} {
+	return reflect.New(goType(t)).Interface()
+}
+
+func (s NativeType) Type() Type {
+	return s.typ
+}
+
+func (s NativeType) Version() byte {
+	return s.proto
+}
+
+func (s NativeType) Custom() string {
+	return s.custom
+}
+
+func (s NativeType) String() string {
+	switch s.typ {
+	case TypeCustom:
+		return fmt.Sprintf("%s(%s)", s.typ, s.custom)
+	default:
+		return s.typ.String()
+	}
+}
+
+type CollectionType struct {
+	NativeType
+	Key  TypeInfo // only used for TypeMap
+	Elem TypeInfo // only used for TypeMap, TypeList and TypeSet
+}
+
+func (t CollectionType) New() interface{} {
+	return reflect.New(goType(t)).Interface()
+}
+
+func (c CollectionType) String() string {
+	switch c.typ {
 	case TypeMap:
-		return fmt.Sprintf("%s(%s, %s)", t.Type, t.Key, t.Elem)
+		return fmt.Sprintf("%s(%s, %s)", c.typ, c.Key, c.Elem)
 	case TypeList, TypeSet:
-		return fmt.Sprintf("%s(%s)", t.Type, t.Elem)
+		return fmt.Sprintf("%s(%s)", c.typ, c.Elem)
 	case TypeCustom:
-		return fmt.Sprintf("%s(%s)", t.Type, t.Custom)
+		return fmt.Sprintf("%s(%s)", c.typ, c.custom)
+	default:
+		return c.typ.String()
 	}
-	return t.Type.String()
 }
 
+type TupleTypeInfo struct {
+	NativeType
+	Elems []TypeInfo
+}
+
+// String returns a human readable name for the Cassandra datatype
+// described by t.
 // Type is the identifier of a Cassandra internal datatype.
 type Type int
 
@@ -1194,6 +1293,8 @@ const (
 	TypeList           = 0x0020
 	TypeMap            = 0x0021
 	TypeSet            = 0x0022
+	TypeUDT            = 0x0030
+	TypeTuple          = 0x0031
 )
 
 // String returns the name of the identifier.
@@ -1237,8 +1338,10 @@ func (t Type) String() string {
 		return "set"
 	case TypeVarint:
 		return "varint"
+	case TypeTuple:
+		return "tuple"
 	default:
-		return fmt.Sprintf("unkown_type_%d", t)
+		return fmt.Sprintf("unknown_type_%d", t)
 	}
 }
 

+ 126 - 100
marshal_test.go

@@ -16,47 +16,47 @@ import (
 )
 
 var marshalTests = []struct {
-	Info  *TypeInfo
+	Info  TypeInfo
 	Data  []byte
 	Value interface{}
 }{
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte("hello world"),
 		[]byte("hello world"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte("hello world"),
 		"hello world",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte(nil),
 		[]byte(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte("hello world"),
 		MyString("hello world"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte("HELLO WORLD"),
 		CustomString("hello world"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBlob},
+		NativeType{proto: 2, typ: TypeBlob},
 		[]byte("hello\x00"),
 		[]byte("hello\x00"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBlob},
+		NativeType{proto: 2, typ: TypeBlob},
 		[]byte(nil),
 		[]byte(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimeUUID},
+		NativeType{proto: 2, typ: TypeTimeUUID},
 		[]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
 		func() UUID {
 			x, _ := UUIDFromBytes([]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0})
@@ -64,217 +64,235 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x00\x00\x00\x00"),
 		0,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x01\x02\x03\x04"),
 		int(16909060),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x80\x00\x00\x00"),
 		int32(math.MinInt32),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x7f\xff\xff\xff"),
 		int32(math.MaxInt32),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x00\x00\x00\x00"),
 		"0",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x01\x02\x03\x04"),
 		"16909060",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x80\x00\x00\x00"),
 		"-2147483648", // math.MinInt32
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x7f\xff\xff\xff"),
 		"2147483647", // math.MaxInt32
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
 		0,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x01\x02\x03\x04\x05\x06\x07\x08"),
 		72623859790382856,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x80\x00\x00\x00\x00\x00\x00\x00"),
 		int64(math.MinInt64),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x7f\xff\xff\xff\xff\xff\xff\xff"),
 		int64(math.MaxInt64),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
 		"0",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x01\x02\x03\x04\x05\x06\x07\x08"),
 		"72623859790382856",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x80\x00\x00\x00\x00\x00\x00\x00"),
 		"-9223372036854775808", // math.MinInt64
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBigInt},
+		NativeType{proto: 2, typ: TypeBigInt},
 		[]byte("\x7f\xff\xff\xff\xff\xff\xff\xff"),
 		"9223372036854775807", // math.MaxInt64
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBoolean},
+		NativeType{proto: 2, typ: TypeBoolean},
 		[]byte("\x00"),
 		false,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBoolean},
+		NativeType{proto: 2, typ: TypeBoolean},
 		[]byte("\x01"),
 		true,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeFloat},
+		NativeType{proto: 2, typ: TypeFloat},
 		[]byte("\x40\x49\x0f\xdb"),
 		float32(3.14159265),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDouble},
+		NativeType{proto: 2, typ: TypeDouble},
 		[]byte("\x40\x09\x21\xfb\x53\xc8\xd4\xf1"),
 		float64(3.14159265),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x00\x00"),
 		inf.NewDec(0, 0),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x00\x64"),
 		inf.NewDec(100, 0),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x02\x19"),
 		decimalize("0.25"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x13\xD5\a;\x20\x14\xA2\x91"),
 		decimalize("-0.0012095473475870063"), // From the iconara/cql-rb test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x13*\xF8\xC4\xDF\xEB]o"),
 		decimalize("0.0012095473475870063"), // From the iconara/cql-rb test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x12\xF2\xD8\x02\xB6R\x7F\x99\xEE\x98#\x99\xA9V"),
 		decimalize("-1042342234234.123423435647768234"), // From the iconara/cql-rb test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\r\nJ\x04\"^\x91\x04\x8a\xb1\x18\xfe"),
 		decimalize("1243878957943.1234124191998"), // From the datastax/python-driver test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x06\xe5\xde]\x98Y"),
 		decimalize("-112233.441191"), // From the datastax/python-driver test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x14\x00\xfa\xce"),
 		decimalize("0.00000000000000064206"), // From the datastax/python-driver test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\x00\x00\x00\x14\xff\x052"),
 		decimalize("-0.00000000000000064206"), // From the datastax/python-driver test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDecimal},
+		NativeType{proto: 2, typ: TypeDecimal},
 		[]byte("\xff\xff\xff\x9c\x00\xfa\xce"),
 		inf.NewDec(64206, -100), // From the datastax/python-driver test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimestamp},
+		NativeType{proto: 2, typ: TypeTimestamp},
 		[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
 		time.Date(2013, time.August, 13, 9, 52, 3, 0, time.UTC),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimestamp},
+		NativeType{proto: 2, typ: TypeTimestamp},
 		[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
 		int64(1376387523000),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeList, Elem: &TypeInfo{Proto: 2, Type: TypeInt}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeList},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
+		},
 		[]byte("\x00\x02\x00\x04\x00\x00\x00\x01\x00\x04\x00\x00\x00\x02"),
 		[]int{1, 2},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeList, Elem: &TypeInfo{Proto: 2, Type: TypeInt}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeList},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
+		},
 		[]byte("\x00\x02\x00\x04\x00\x00\x00\x01\x00\x04\x00\x00\x00\x02"),
 		[2]int{1, 2},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeSet, Elem: &TypeInfo{Proto: 2, Type: TypeInt}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeSet},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
+		},
 		[]byte("\x00\x02\x00\x04\x00\x00\x00\x01\x00\x04\x00\x00\x00\x02"),
 		[]int{1, 2},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeSet, Elem: &TypeInfo{Proto: 2, Type: TypeInt}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeSet},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
+		},
 		[]byte(nil),
 		[]int(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeMap,
-			Key:  &TypeInfo{Proto: 2, Type: TypeVarchar},
-			Elem: &TypeInfo{Proto: 2, Type: TypeInt},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeMap},
+			Key:        NativeType{proto: 2, typ: TypeVarchar},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
 		},
 		[]byte("\x00\x01\x00\x03foo\x00\x04\x00\x00\x00\x01"),
 		map[string]int{"foo": 1},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeMap,
-			Key:  &TypeInfo{Proto: 2, Type: TypeVarchar},
-			Elem: &TypeInfo{Proto: 2, Type: TypeInt},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeMap},
+			Key:        NativeType{proto: 2, typ: TypeVarchar},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
 		},
 		[]byte(nil),
 		map[string]int(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeList, Elem: &TypeInfo{Proto: 2, Type: TypeVarchar}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeList},
+			Elem:       NativeType{proto: 2, typ: TypeVarchar},
+		},
 		bytes.Join([][]byte{
 			[]byte("\x00\x01\xFF\xFF"),
 			bytes.Repeat([]byte("X"), 65535)}, []byte("")),
 		[]string{strings.Repeat("X", 65535)},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeMap,
-			Key:  &TypeInfo{Proto: 2, Type: TypeVarchar},
-			Elem: &TypeInfo{Proto: 2, Type: TypeVarchar},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeMap},
+			Key:        NativeType{proto: 2, typ: TypeVarchar},
+			Elem:       NativeType{proto: 2, typ: TypeVarchar},
 		},
 		bytes.Join([][]byte{
 			[]byte("\x00\x01\xFF\xFF"),
@@ -286,82 +304,82 @@ var marshalTests = []struct {
 		},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarint},
+		NativeType{proto: 2, typ: TypeVarint},
 		[]byte("\x00"),
 		0,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarint},
+		NativeType{proto: 2, typ: TypeVarint},
 		[]byte("\x37\xE2\x3C\xEC"),
 		int32(937573612),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarint},
+		NativeType{proto: 2, typ: TypeVarint},
 		[]byte("\x37\xE2\x3C\xEC"),
 		big.NewInt(937573612),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarint},
+		NativeType{proto: 2, typ: TypeVarint},
 		[]byte("\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a["),
 		bigintize("1231312312331283012830129382342342412123"), // From the iconara/cql-rb test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarint},
+		NativeType{proto: 2, typ: TypeVarint},
 		[]byte("\xC9v\x8D:\x86"),
 		big.NewInt(-234234234234), // From the iconara/cql-rb test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarint},
+		NativeType{proto: 2, typ: TypeVarint},
 		[]byte("f\x1e\xfd\xf2\xe3\xb1\x9f|\x04_\x15"),
 		bigintize("123456789123456789123456789"), // From the datastax/python-driver test suite
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\x7F\x00\x00\x01"),
 		net.ParseIP("127.0.0.1").To4(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\xFF\xFF\xFF\xFF"),
 		net.ParseIP("255.255.255.255").To4(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\x7F\x00\x00\x01"),
 		"127.0.0.1",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\xFF\xFF\xFF\xFF"),
 		"255.255.255.255",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\x21\xDA\x00\xd3\x00\x00\x2f\x3b\x02\xaa\x00\xff\xfe\x28\x9c\x5a"),
 		"21da:d3:0:2f3b:2aa:ff:fe28:9c5a",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x02\xb3\xff\xfe\x1e\x83\x29"),
 		"fe80::202:b3ff:fe1e:8329",
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\x21\xDA\x00\xd3\x00\x00\x2f\x3b\x02\xaa\x00\xff\xfe\x28\x9c\x5a"),
 		net.ParseIP("21da:d3:0:2f3b:2aa:ff:fe28:9c5a"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x02\xb3\xff\xfe\x1e\x83\x29"),
 		net.ParseIP("fe80::202:b3ff:fe1e:8329"),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte(nil),
 		nil,
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte("nullable string"),
 		func() *string {
 			value := "nullable string"
@@ -369,12 +387,12 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeVarchar},
+		NativeType{proto: 2, typ: TypeVarchar},
 		[]byte{},
 		(*string)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte("\x7f\xff\xff\xff"),
 		func() *int {
 			var value int = math.MaxInt32
@@ -382,22 +400,22 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInt},
+		NativeType{proto: 2, typ: TypeInt},
 		[]byte(nil),
 		(*int)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimeUUID},
+		NativeType{proto: 2, typ: TypeTimeUUID},
 		[]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
 		&UUID{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimeUUID},
+		NativeType{proto: 2, typ: TypeTimeUUID},
 		[]byte{},
 		(*UUID)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimestamp},
+		NativeType{proto: 2, typ: TypeTimestamp},
 		[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
 		func() *time.Time {
 			t := time.Date(2013, time.August, 13, 9, 52, 3, 0, time.UTC)
@@ -405,12 +423,12 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeTimestamp},
+		NativeType{proto: 2, typ: TypeTimestamp},
 		[]byte(nil),
 		(*time.Time)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBoolean},
+		NativeType{proto: 2, typ: TypeBoolean},
 		[]byte("\x00"),
 		func() *bool {
 			b := false
@@ -418,7 +436,7 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBoolean},
+		NativeType{proto: 2, typ: TypeBoolean},
 		[]byte("\x01"),
 		func() *bool {
 			b := true
@@ -426,12 +444,12 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeBoolean},
+		NativeType{proto: 2, typ: TypeBoolean},
 		[]byte(nil),
 		(*bool)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeFloat},
+		NativeType{proto: 2, typ: TypeFloat},
 		[]byte("\x40\x49\x0f\xdb"),
 		func() *float32 {
 			f := float32(3.14159265)
@@ -439,12 +457,12 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeFloat},
+		NativeType{proto: 2, typ: TypeFloat},
 		[]byte(nil),
 		(*float32)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDouble},
+		NativeType{proto: 2, typ: TypeDouble},
 		[]byte("\x40\x09\x21\xfb\x53\xc8\xd4\xf1"),
 		func() *float64 {
 			d := float64(3.14159265)
@@ -452,12 +470,12 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeDouble},
+		NativeType{proto: 2, typ: TypeDouble},
 		[]byte(nil),
 		(*float64)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte("\x7F\x00\x00\x01"),
 		func() *net.IP {
 			ip := net.ParseIP("127.0.0.1").To4()
@@ -465,12 +483,15 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeInet},
+		NativeType{proto: 2, typ: TypeInet},
 		[]byte(nil),
 		(*net.IP)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeList, Elem: &TypeInfo{Proto: 2, Type: TypeInt}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeList},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
+		},
 		[]byte("\x00\x02\x00\x04\x00\x00\x00\x01\x00\x04\x00\x00\x00\x02"),
 		func() *[]int {
 			l := []int{1, 2}
@@ -478,14 +499,18 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeList, Elem: &TypeInfo{Proto: 2, Type: TypeInt}},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeList},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
+		},
 		[]byte(nil),
 		(*[]int)(nil),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeMap,
-			Key:  &TypeInfo{Proto: 2, Type: TypeVarchar},
-			Elem: &TypeInfo{Proto: 2, Type: TypeInt},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeMap},
+			Key:        NativeType{proto: 2, typ: TypeVarchar},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
 		},
 		[]byte("\x00\x01\x00\x03foo\x00\x04\x00\x00\x00\x01"),
 		func() *map[string]int {
@@ -494,9 +519,10 @@ var marshalTests = []struct {
 		}(),
 	},
 	{
-		&TypeInfo{Proto: 2, Type: TypeMap,
-			Key:  &TypeInfo{Proto: 2, Type: TypeVarchar},
-			Elem: &TypeInfo{Proto: 2, Type: TypeInt},
+		CollectionType{
+			NativeType: NativeType{proto: 2, typ: TypeMap},
+			Key:        NativeType{proto: 2, typ: TypeVarchar},
+			Elem:       NativeType{proto: 2, typ: TypeInt},
 		},
 		[]byte(nil),
 		(*map[string]int)(nil),
@@ -610,7 +636,7 @@ func TestMarshalVarint(t *testing.T) {
 	}
 
 	for i, test := range varintTests {
-		data, err := Marshal(&TypeInfo{Proto: 2, Type: TypeVarint}, test.Value)
+		data, err := Marshal(NativeType{proto: 2, typ: TypeVarint}, test.Value)
 		if err != nil {
 			t.Errorf("error marshaling varint: %v (test #%d)", err, i)
 		}
@@ -620,7 +646,7 @@ func TestMarshalVarint(t *testing.T) {
 		}
 
 		binder := new(big.Int)
-		err = Unmarshal(&TypeInfo{Proto: 2, Type: TypeVarint}, test.Marshaled, binder)
+		err = Unmarshal(NativeType{proto: 2, typ: TypeVarint}, test.Marshaled, binder)
 		if err != nil {
 			t.Errorf("error unmarshaling varint: %v (test #%d)", err, i)
 		}
@@ -633,10 +659,10 @@ func TestMarshalVarint(t *testing.T) {
 
 type CustomString string
 
-func (c CustomString) MarshalCQL(info *TypeInfo) ([]byte, error) {
+func (c CustomString) MarshalCQL(info TypeInfo) ([]byte, error) {
 	return []byte(strings.ToUpper(string(c))), nil
 }
-func (c *CustomString) UnmarshalCQL(info *TypeInfo, data []byte) error {
+func (c *CustomString) UnmarshalCQL(info TypeInfo, data []byte) error {
 	*c = CustomString(strings.ToLower(string(data)))
 	return nil
 }

+ 23 - 17
metadata.go

@@ -227,7 +227,7 @@ func compileV1Metadata(tables []TableMetadata) {
 		if comparatorParsed.isComposite {
 			if len(comparatorParsed.collections) != 0 ||
 				(len(table.ColumnAliases) == size-1 &&
-					comparatorParsed.types[size-1].Type == TypeVarchar) {
+					comparatorParsed.types[size-1].Type() == TypeVarchar) {
 				size = size - 1
 			}
 		} else {
@@ -603,9 +603,9 @@ func (t *typeParser) parse() typeParserResult {
 		return typeParserResult{
 			isComposite: false,
 			types: []TypeInfo{
-				TypeInfo{
-					Type:   TypeCustom,
-					Custom: t.input,
+				NativeType{
+					typ:    TypeCustom,
+					custom: t.input,
 				},
 			},
 			reversed:    []bool{false},
@@ -681,33 +681,39 @@ func (t *typeParser) parse() typeParserResult {
 func (class *typeParserClassNode) asTypeInfo() TypeInfo {
 	if strings.HasPrefix(class.name, LIST_TYPE) {
 		elem := class.params[0].class.asTypeInfo()
-		return TypeInfo{
-			Type: TypeList,
-			Elem: &elem,
+		return CollectionType{
+			NativeType: NativeType{
+				typ: TypeList,
+			},
+			Elem: elem,
 		}
 	}
 	if strings.HasPrefix(class.name, SET_TYPE) {
 		elem := class.params[0].class.asTypeInfo()
-		return TypeInfo{
-			Type: TypeSet,
-			Elem: &elem,
+		return CollectionType{
+			NativeType: NativeType{
+				typ: TypeSet,
+			},
+			Elem: elem,
 		}
 	}
 	if strings.HasPrefix(class.name, MAP_TYPE) {
 		key := class.params[0].class.asTypeInfo()
 		elem := class.params[1].class.asTypeInfo()
-		return TypeInfo{
-			Type: TypeMap,
-			Key:  &key,
-			Elem: &elem,
+		return CollectionType{
+			NativeType: NativeType{
+				typ: TypeMap,
+			},
+			Key:  key,
+			Elem: elem,
 		}
 	}
 
 	// must be a simple type or custom type
-	info := TypeInfo{Type: getApacheCassandraType(class.name)}
-	if info.Type == TypeCustom {
+	info := NativeType{typ: getApacheCassandraType(class.name)}
+	if info.typ == TypeCustom {
 		// add the entire class definition
-		info.Custom = class.input
+		info.custom = class.input
 	}
 	return info
 }

+ 73 - 66
metadata_test.go

@@ -92,14 +92,14 @@ func TestCompileMetadata(t *testing.T) {
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "key",
-							Type: TypeInfo{Type: TypeBlob},
+							Type: NativeType{typ: TypeBlob},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{},
 					Columns: map[string]*ColumnMetadata{
 						"key": &ColumnMetadata{
 							Name: "key",
-							Type: TypeInfo{Type: TypeBlob},
+							Type: NativeType{typ: TypeBlob},
 							Kind: PARTITION_KEY,
 						},
 					},
@@ -108,42 +108,42 @@ func TestCompileMetadata(t *testing.T) {
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "target_id",
-							Type: TypeInfo{Type: TypeUUID},
+							Type: NativeType{typ: TypeUUID},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name:  "hint_id",
-							Type:  TypeInfo{Type: TypeTimeUUID},
+							Type:  NativeType{typ: TypeTimeUUID},
 							Order: ASC,
 						},
 						&ColumnMetadata{
 							Name:  "message_version",
-							Type:  TypeInfo{Type: TypeInt},
+							Type:  NativeType{typ: TypeInt},
 							Order: ASC,
 						},
 					},
 					Columns: map[string]*ColumnMetadata{
 						"target_id": &ColumnMetadata{
 							Name: "target_id",
-							Type: TypeInfo{Type: TypeUUID},
+							Type: NativeType{typ: TypeUUID},
 							Kind: PARTITION_KEY,
 						},
 						"hint_id": &ColumnMetadata{
 							Name:  "hint_id",
-							Type:  TypeInfo{Type: TypeTimeUUID},
+							Type:  NativeType{typ: TypeTimeUUID},
 							Order: ASC,
 							Kind:  CLUSTERING_KEY,
 						},
 						"message_version": &ColumnMetadata{
 							Name:  "message_version",
-							Type:  TypeInfo{Type: TypeInt},
+							Type:  NativeType{typ: TypeInt},
 							Order: ASC,
 							Kind:  CLUSTERING_KEY,
 						},
 						"mutation": &ColumnMetadata{
 							Name: "mutation",
-							Type: TypeInfo{Type: TypeBlob},
+							Type: NativeType{typ: TypeBlob},
 							Kind: REGULAR,
 						},
 					},
@@ -152,53 +152,53 @@ func TestCompileMetadata(t *testing.T) {
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "peer",
-							Type: TypeInfo{Type: TypeInet},
+							Type: NativeType{typ: TypeInet},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{},
 					Columns: map[string]*ColumnMetadata{
 						"peer": &ColumnMetadata{
 							Name: "peer",
-							Type: TypeInfo{Type: TypeInet},
+							Type: NativeType{typ: TypeInet},
 							Kind: PARTITION_KEY,
 						},
-						"data_center":     &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "data_center", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UTF8Type", Type: TypeInfo{Type: TypeVarchar}},
-						"host_id":         &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "host_id", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UUIDType", Type: TypeInfo{Type: TypeUUID}},
-						"rack":            &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "rack", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UTF8Type", Type: TypeInfo{Type: TypeVarchar}},
-						"release_version": &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "release_version", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UTF8Type", Type: TypeInfo{Type: TypeVarchar}},
-						"rpc_address":     &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "rpc_address", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.InetAddressType", Type: TypeInfo{Type: TypeInet}},
-						"schema_version":  &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "schema_version", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UUIDType", Type: TypeInfo{Type: TypeUUID}},
-						"tokens":          &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "tokens", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)", Type: TypeInfo{Type: TypeSet}},
+						"data_center":     &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "data_center", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UTF8Type", Type: NativeType{typ: TypeVarchar}},
+						"host_id":         &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "host_id", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UUIDType", Type: NativeType{typ: TypeUUID}},
+						"rack":            &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "rack", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UTF8Type", Type: NativeType{typ: TypeVarchar}},
+						"release_version": &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "release_version", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UTF8Type", Type: NativeType{typ: TypeVarchar}},
+						"rpc_address":     &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "rpc_address", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.InetAddressType", Type: NativeType{typ: TypeInet}},
+						"schema_version":  &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "schema_version", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.UUIDType", Type: NativeType{typ: TypeUUID}},
+						"tokens":          &ColumnMetadata{Keyspace: "V1Keyspace", Table: "peers", Kind: REGULAR, Name: "tokens", ComponentIndex: 0, Validator: "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)", Type: CollectionType{NativeType: NativeType{typ: TypeSet}}},
 					},
 				},
 				"IndexInfo": &TableMetadata{
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "table_name",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name:  "index_name",
-							Type:  TypeInfo{Type: TypeVarchar},
+							Type:  NativeType{typ: TypeVarchar},
 							Order: ASC,
 						},
 					},
 					Columns: map[string]*ColumnMetadata{
 						"table_name": &ColumnMetadata{
 							Name: "table_name",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: PARTITION_KEY,
 						},
 						"index_name": &ColumnMetadata{
 							Name: "index_name",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: CLUSTERING_KEY,
 						},
 						"value": &ColumnMetadata{
 							Name: "value",
-							Type: TypeInfo{Type: TypeBlob},
+							Type: NativeType{typ: TypeBlob},
 							Kind: REGULAR,
 						},
 					},
@@ -207,25 +207,25 @@ func TestCompileMetadata(t *testing.T) {
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "title",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name:  "revid",
-							Type:  TypeInfo{Type: TypeTimeUUID},
+							Type:  NativeType{typ: TypeTimeUUID},
 							Order: ASC,
 						},
 					},
 					Columns: map[string]*ColumnMetadata{
 						"title": &ColumnMetadata{
 							Name: "title",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: PARTITION_KEY,
 						},
 						"revid": &ColumnMetadata{
 							Name: "revid",
-							Type: TypeInfo{Type: TypeTimeUUID},
+							Type: NativeType{typ: TypeTimeUUID},
 							Kind: CLUSTERING_KEY,
 						},
 					},
@@ -289,14 +289,14 @@ func TestCompileMetadata(t *testing.T) {
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "Key1",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{},
 					Columns: map[string]*ColumnMetadata{
 						"Key1": &ColumnMetadata{
 							Name: "Key1",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: PARTITION_KEY,
 						},
 					},
@@ -305,29 +305,29 @@ func TestCompileMetadata(t *testing.T) {
 					PartitionKey: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "Column1",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 						},
 					},
 					ClusteringColumns: []*ColumnMetadata{
 						&ColumnMetadata{
 							Name: "Column2",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 						},
 					},
 					Columns: map[string]*ColumnMetadata{
 						"Column1": &ColumnMetadata{
 							Name: "Column1",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: PARTITION_KEY,
 						},
 						"Column2": &ColumnMetadata{
 							Name: "Column2",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: CLUSTERING_KEY,
 						},
 						"Column3": &ColumnMetadata{
 							Name: "Column3",
-							Type: TypeInfo{Type: TypeVarchar},
+							Type: NativeType{typ: TypeVarchar},
 							Kind: REGULAR,
 						},
 					},
@@ -364,8 +364,8 @@ func assertKeyspaceMetadata(t *testing.T, actual, expected *KeyspaceMetadata) {
 					if keyT != at.PartitionKey[i].Table {
 						t.Errorf("Expected %s.Tables[%s].PartitionKey[%d].Table to be '%v' but was '%v'", expected.Name, keyT, i, keyT, at.PartitionKey[i].Table)
 					}
-					if et.PartitionKey[i].Type.Type != at.PartitionKey[i].Type.Type {
-						t.Errorf("Expected %s.Tables[%s].PartitionKey[%d].Type.Type to be %v but was %v", expected.Name, keyT, i, et.PartitionKey[i].Type.Type, at.PartitionKey[i].Type.Type)
+					if et.PartitionKey[i].Type.Type() != at.PartitionKey[i].Type.Type() {
+						t.Errorf("Expected %s.Tables[%s].PartitionKey[%d].Type.Type to be %v but was %v", expected.Name, keyT, i, et.PartitionKey[i].Type.Type(), at.PartitionKey[i].Type.Type())
 					}
 					if i != at.PartitionKey[i].ComponentIndex {
 						t.Errorf("Expected %s.Tables[%s].PartitionKey[%d].ComponentIndex to be %v but was %v", expected.Name, keyT, i, i, at.PartitionKey[i].ComponentIndex)
@@ -388,8 +388,8 @@ func assertKeyspaceMetadata(t *testing.T, actual, expected *KeyspaceMetadata) {
 					if keyT != at.ClusteringColumns[i].Table {
 						t.Errorf("Expected %s.Tables[%s].ClusteringColumns[%d].Table to be '%v' but was '%v'", expected.Name, keyT, i, keyT, at.ClusteringColumns[i].Table)
 					}
-					if et.ClusteringColumns[i].Type.Type != at.ClusteringColumns[i].Type.Type {
-						t.Errorf("Expected %s.Tables[%s].ClusteringColumns[%d].Type.Type to be %v but was %v", expected.Name, keyT, i, et.ClusteringColumns[i].Type.Type, at.ClusteringColumns[i].Type.Type)
+					if et.ClusteringColumns[i].Type.Type() != at.ClusteringColumns[i].Type.Type() {
+						t.Errorf("Expected %s.Tables[%s].ClusteringColumns[%d].Type.Type to be %v but was %v", expected.Name, keyT, i, et.ClusteringColumns[i].Type.Type(), at.ClusteringColumns[i].Type.Type())
 					}
 					if i != at.ClusteringColumns[i].ComponentIndex {
 						t.Errorf("Expected %s.Tables[%s].ClusteringColumns[%d].ComponentIndex to be %v but was %v", expected.Name, keyT, i, i, at.ClusteringColumns[i].ComponentIndex)
@@ -429,8 +429,8 @@ func assertKeyspaceMetadata(t *testing.T, actual, expected *KeyspaceMetadata) {
 						if keyT != ac.Table {
 							t.Errorf("Expected %s.Tables[%s].Columns[%s].Table to be '%v' but was '%v'", expected.Name, keyT, keyC, keyT, ac.Table)
 						}
-						if ec.Type.Type != ac.Type.Type {
-							t.Errorf("Expected %s.Tables[%s].Columns[%s].Type.Type to be %v but was %v", expected.Name, keyT, keyC, ec.Type.Type, ac.Type.Type)
+						if ec.Type.Type() != ac.Type.Type() {
+							t.Errorf("Expected %s.Tables[%s].Columns[%s].Type.Type to be %v but was %v", expected.Name, keyT, keyC, ec.Type.Type(), ac.Type.Type())
 						}
 						if ec.Order != ac.Order {
 							t.Errorf("Expected %s.Tables[%s].Columns[%s].Order to be %v but was %v", expected.Name, keyT, keyC, ec.Order, ac.Order)
@@ -633,38 +633,45 @@ func assertParseNonCompositeTypes(
 		}
 
 		// check the type
-		if typeActual.Type != typeExpected.Type {
-			t.Errorf("%s: Expected to parse Type to %s but was %s", context, typeExpected.Type, typeActual.Type)
+		if typeActual.Type() != typeExpected.Type {
+			t.Errorf("%s: Expected to parse Type to %s but was %s", context, typeExpected.Type, typeActual.Type())
 		}
 		// check the custom
-		if typeActual.Custom != typeExpected.Custom {
-			t.Errorf("%s: Expected to parse Custom %s but was %s", context, typeExpected.Custom, typeActual.Custom)
+		if typeActual.Custom() != typeExpected.Custom {
+			t.Errorf("%s: Expected to parse Custom %s but was %s", context, typeExpected.Custom, typeActual.Custom())
 		}
+
+		collection, _ := typeActual.(CollectionType)
 		// check the elem
-		if typeActual.Elem == nil && typeExpected.Elem != nil {
-			t.Errorf("%s: Expected to parse Elem, but was nil ", context)
-		} else if typeExpected.Elem == nil && typeActual.Elem != nil {
-			t.Errorf("%s: Expected to not parse Elem, but was %+v", context, typeActual.Elem)
-		} else if typeActual.Elem != nil && typeExpected.Elem != nil {
-			assertParseNonCompositeTypes(
-				t,
-				context+".Elem",
-				[]assertTypeInfo{*typeExpected.Elem},
-				[]TypeInfo{*typeActual.Elem},
-			)
+		if typeExpected.Elem != nil {
+			if collection.Elem == nil {
+				t.Errorf("%s: Expected to parse Elem, but was nil ", context)
+			} else {
+				assertParseNonCompositeTypes(
+					t,
+					context+".Elem",
+					[]assertTypeInfo{*typeExpected.Elem},
+					[]TypeInfo{collection.Elem},
+				)
+			}
+		} else if collection.Elem != nil {
+			t.Errorf("%s: Expected to not parse Elem, but was %+v", context, collection.Elem)
 		}
+
 		// check the key
-		if typeActual.Key == nil && typeExpected.Key != nil {
-			t.Errorf("%s: Expected to parse Key, but was nil ", context)
-		} else if typeExpected.Key == nil && typeActual.Key != nil {
-			t.Errorf("%s: Expected to not parse Key, but was %+v", context, typeActual.Key)
-		} else if typeActual.Key != nil && typeExpected.Key != nil {
-			assertParseNonCompositeTypes(
-				t,
-				context+".Key",
-				[]assertTypeInfo{*typeExpected.Key},
-				[]TypeInfo{*typeActual.Key},
-			)
+		if typeExpected.Key != nil {
+			if collection.Key == nil {
+				t.Errorf("%s: Expected to parse Key, but was nil ", context)
+			} else {
+				assertParseNonCompositeTypes(
+					t,
+					context+".Key",
+					[]assertTypeInfo{*typeExpected.Key},
+					[]TypeInfo{collection.Key},
+				)
+			}
+		} else if collection.Key != nil {
+			t.Errorf("%s: Expected to not parse Key, but was %+v", context, collection.Key)
 		}
 	}
 }

+ 40 - 14
session.go

@@ -285,7 +285,7 @@ func (s *Session) routingKeyInfo(stmt string) (*routingKeyInfo, error) {
 	size := len(partitionKey)
 	routingKeyInfo := &routingKeyInfo{
 		indexes: make([]int, size),
-		types:   make([]*TypeInfo, size),
+		types:   make([]TypeInfo, size),
 	}
 	for keyIndex, keyColumn := range partitionKey {
 		// set an indicator for checking if the mapping is missing
@@ -598,16 +598,16 @@ func (q *Query) MapScanCAS(dest map[string]interface{}) (applied bool, err error
 // were returned by a query. The iterator might send additional queries to the
 // database during the iteration if paging was enabled.
 type Iter struct {
-	err     error
-	pos     int
-	rows    [][][]byte
-	columns []ColumnInfo
-	next    *nextIter
+	err  error
+	pos  int
+	rows [][][]byte
+	meta resultMetadata
+	next *nextIter
 }
 
 // Columns returns the name and type of the selected columns.
 func (iter *Iter) Columns() []ColumnInfo {
-	return iter.columns
+	return iter.meta.columns
 }
 
 // Scan consumes the next row of the iterator and copies the columns of the
@@ -632,20 +632,46 @@ func (iter *Iter) Scan(dest ...interface{}) bool {
 	if iter.next != nil && iter.pos == iter.next.pos {
 		go iter.next.fetch()
 	}
-	if len(dest) != len(iter.columns) {
+
+	// currently only support scanning into an expand tuple, such that its the same
+	// as scanning in more values from a single column
+	if len(dest) != iter.meta.actualColCount {
 		iter.err = errors.New("count mismatch")
 		return false
 	}
-	for i := 0; i < len(iter.columns); i++ {
+
+	// i is the current position in dest, could posible replace it and just use
+	// slices of dest
+	i := 0
+	for c, col := range iter.meta.columns {
 		if dest[i] == nil {
+			i++
 			continue
 		}
-		err := Unmarshal(iter.columns[i].TypeInfo, iter.rows[iter.pos][i], dest[i])
-		if err != nil {
-			iter.err = err
+
+		// how can we allow users to pass in a single struct to unmarshal into
+		if col.TypeInfo.Type() == TypeTuple {
+			// this will panic, actually a bug, please report
+			tuple := col.TypeInfo.(TupleTypeInfo)
+
+			count := len(tuple.Elems)
+			// here we pass in a slice of the struct which has the number number of
+			// values as elements in the tuple
+			iter.err = Unmarshal(col.TypeInfo, iter.rows[iter.pos][c], dest[i:i+count])
+			if iter.err != nil {
+				return false
+			}
+			i += count
+			continue
+		}
+
+		iter.err = Unmarshal(col.TypeInfo, iter.rows[iter.pos][c], dest[i])
+		if iter.err != nil {
 			return false
 		}
+		i++
 	}
+
 	iter.pos++
 	return true
 }
@@ -759,7 +785,7 @@ type ColumnInfo struct {
 	Keyspace string
 	Table    string
 	Name     string
-	TypeInfo *TypeInfo
+	TypeInfo TypeInfo
 }
 
 func (c ColumnInfo) String() string {
@@ -774,7 +800,7 @@ type routingKeyInfoLRU struct {
 
 type routingKeyInfo struct {
 	indexes []int
-	types   []*TypeInfo
+	types   []TypeInfo
 }
 
 func (r *routingKeyInfoLRU) Remove(key string) {

+ 51 - 0
tuple_test.go

@@ -0,0 +1,51 @@
+// +build all integration
+
+package gocql
+
+import "testing"
+
+func TestTupleSimple(t *testing.T) {
+	if *flagProto < protoVersion3 {
+		t.Skip("tuple types are only available of proto>=3")
+	}
+
+	session := createSession(t)
+	defer session.Close()
+
+	err := createTable(session, `CREATE TABLE tuple_test(
+		id int,
+		coord frozen<tuple<int, int>>,
+
+		primary key(id))`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = session.Query("INSERT INTO tuple_test(id, coord) VALUES(?, (?, ?))", 1, 100, -100).Exec()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var (
+		id    int
+		coord struct {
+			x int
+			y int
+		}
+	)
+
+	iter := session.Query("SELECT id, coord FROM tuple_test WHERE id=?", 1)
+	if err := iter.Scan(&id, &coord.x, &coord.y); err != nil {
+		t.Fatal(err)
+	}
+
+	if id != 1 {
+		t.Errorf("expected to get id=1 got: %v", id)
+	}
+	if coord.x != 100 {
+		t.Errorf("expected to get coord.x=100 got: %v", coord.x)
+	}
+	if coord.y != -100 {
+		t.Errorf("expected to get coord.y=-100 got: %v", coord.y)
+	}
+}