浏览代码

Merge pull request #352 from Zariel/v3-tuples

V3 tuples
Ben Hood 10 年之前
父节点
当前提交
ecfc8f8133
共有 10 个文件被更改,包括 610 次插入366 次删除
  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)
+	}
+}