You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by jo...@apache.org on 2025/07/03 13:42:03 UTC

(cassandra-gocql-driver) branch trunk updated: Fix tinyint silent unmarshal error and vector SliceMap bug

This is an automated email from the ASF dual-hosted git repository.

joaoreis pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-gocql-driver.git


The following commit(s) were added to refs/heads/trunk by this push:
     new a7f7f31d Fix tinyint silent unmarshal error and vector SliceMap bug
a7f7f31d is described below

commit a7f7f31d6f4b33d01c3aa2eb4d03a7d7ea3e89f5
Author: João Reis <jo...@apache.org>
AuthorDate: Thu Jul 3 13:53:32 2025 +0100

    Fix tinyint silent unmarshal error and vector SliceMap bug
    
    Tinyint unmarshal silently errors and unmarshals tinyint values as 0 in some situations.
    This patch also removes other cases of silent errors in decode functions.
    
    Fix issue where reading vectors with SliceMap is not possible.
    
    Patch by João Reis; reviewed by James Hartig for CASSGO-82, CASSGO-83
---
 CHANGELOG.md        |   2 +
 integration_test.go | 705 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 marshal.go          | 159 ++++++++----
 marshal_test.go     | 115 ++++++++-
 vector.go           |  12 +-
 5 files changed, 929 insertions(+), 64 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e550f83..5c047fd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,6 +64,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fix deadlock in refresh debouncer stop (CASSGO-41)
 - Endless query execution fix (CASSGO-50)
 - Accept peers with empty rack (CASSGO-6)
+- Fix tinyint unmarshal regression (CASSGO-82)
+- Vector columns can't be used with SliceMap() (CASSGO-83)
 
 ## [1.7.0] - 2024-09-23
 
diff --git a/integration_test.go b/integration_test.go
index 18acaf4a..bd6ccb5c 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -30,9 +30,15 @@ package gocql
 // This file groups integration tests where Cassandra has to be set up with some special integration variables
 import (
 	"context"
+	"fmt"
+	"math/big"
+	"net"
 	"reflect"
+	"strings"
 	"testing"
 	"time"
+
+	inf "gopkg.in/inf.v0"
 )
 
 // TestAuthentication verifies that gocql will work with a host configured to only accept authenticated connections
@@ -272,3 +278,702 @@ func TestUDF(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+// SliceMapTypesTestCase defines a test case for validating SliceMap/MapScan behavior
+type SliceMapTypesTestCase struct {
+	CQLType           string
+	CQLValue          string      // Non-NULL value to insert
+	ExpectedValue     interface{} // Expected value for non-NULL case
+	ExpectedNullValue interface{} // Expected value for NULL
+}
+
+// compareCollectionValues compares collection values (lists, sets, maps) with special handling
+func compareCollectionValues(t *testing.T, cqlType string, expected, actual interface{}) bool {
+	switch {
+	case strings.HasPrefix(cqlType, "set<"):
+		// Sets are returned as slices, but order is not guaranteed
+		expectedSlice := reflect.ValueOf(expected)
+		actualSlice := reflect.ValueOf(actual)
+		if expectedSlice.Kind() != reflect.Slice || actualSlice.Kind() != reflect.Slice {
+			return false
+		}
+		if expectedSlice.Len() != actualSlice.Len() {
+			return false
+		}
+
+		// Convert to maps for unordered comparison
+		expectedSet := make(map[interface{}]bool)
+		for i := 0; i < expectedSlice.Len(); i++ {
+			expectedSet[expectedSlice.Index(i).Interface()] = true
+		}
+
+		actualSet := make(map[interface{}]bool)
+		for i := 0; i < actualSlice.Len(); i++ {
+			actualSet[actualSlice.Index(i).Interface()] = true
+		}
+
+		return reflect.DeepEqual(expectedSet, actualSet)
+
+	default:
+		// For lists, maps, and other collections, reflect.DeepEqual works fine
+		return reflect.DeepEqual(expected, actual)
+	}
+}
+
+// compareValues compares expected and actual values with type-specific logic
+func compareValues(t *testing.T, cqlType string, expected, actual interface{}) bool {
+	switch cqlType {
+	case "varint":
+		// big.Int needs Cmp() for proper comparison, but handle nil pointers safely
+		if expectedBig, ok := expected.(*big.Int); ok {
+			if actualBig, ok := actual.(*big.Int); ok {
+				// Handle nil cases
+				if expectedBig == nil && actualBig == nil {
+					return true
+				}
+				if expectedBig == nil || actualBig == nil {
+					return false
+				}
+				return expectedBig.Cmp(actualBig) == 0
+			}
+		}
+		return reflect.DeepEqual(expected, actual)
+
+	case "decimal":
+		// inf.Dec needs Cmp() for proper comparison, but handle nil pointers safely
+		if expectedDec, ok := expected.(*inf.Dec); ok {
+			if actualDec, ok := actual.(*inf.Dec); ok {
+				// Handle nil cases
+				if expectedDec == nil && actualDec == nil {
+					return true
+				}
+				if expectedDec == nil || actualDec == nil {
+					return false
+				}
+				return expectedDec.Cmp(actualDec) == 0
+			}
+		}
+		return reflect.DeepEqual(expected, actual)
+
+	default:
+		// reflect.DeepEqual handles nil vs empty slice/map distinction correctly for all types
+		// including inet (net.IP), blob ([]byte), collections ([]T, map[K]V), etc.
+		// This is critical for catching zero value behavior changes in the driver
+		return reflect.DeepEqual(expected, actual)
+	}
+}
+
+// TestSliceMapMapScanTypes tests SliceMap and MapScan with various CQL types
+func TestSliceMapMapScanTypes(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+
+	// Create test table
+	tableCQL := `
+		CREATE TABLE IF NOT EXISTS gocql_test.slicemap_test (
+			id int PRIMARY KEY,
+			tinyint_col tinyint,
+			smallint_col smallint,
+			int_col int,
+			bigint_col bigint,
+			float_col float,
+			double_col double,
+			boolean_col boolean,
+			text_col text,
+			ascii_col ascii,
+			varchar_col varchar,
+			timestamp_col timestamp,
+			uuid_col uuid,
+			timeuuid_col timeuuid,
+			inet_col inet,
+			blob_col blob,
+			varint_col varint,
+			decimal_col decimal,
+			date_col date,
+			time_col time,
+			duration_col duration
+		)`
+
+	if err := createTable(session, tableCQL); err != nil {
+		t.Fatal("Failed to create test table:", err)
+	}
+
+	// Clear existing data
+	if err := session.Query("TRUNCATE gocql_test.slicemap_test").Exec(); err != nil {
+		t.Fatal("Failed to truncate test table:", err)
+	}
+
+	testCases := []SliceMapTypesTestCase{
+		{"tinyint", "42", int8(42), int8(0)},
+		{"smallint", "1234", int16(1234), int16(0)},
+		{"int", "123456", int(123456), int(0)},
+		{"bigint", "1234567890", int64(1234567890), int64(0)},
+		{"float", "3.14", float32(3.14), float32(0)},
+		{"double", "2.718281828", float64(2.718281828), float64(0)},
+		{"boolean", "true", true, false},
+		{"text", "'hello world'", "hello world", ""},
+		{"ascii", "'hello ascii'", "hello ascii", ""},
+		{"varchar", "'hello varchar'", "hello varchar", ""},
+		{"timestamp", "1388534400000", time.Unix(1388534400, 0).UTC(), time.Time{}},
+		{"uuid", "550e8400-e29b-41d4-a716-446655440000", mustParseUUID("550e8400-e29b-41d4-a716-446655440000"), UUID{}},
+		{"timeuuid", "60d79c23-5793-11f0-8afe-bcfce78b517a", mustParseUUID("60d79c23-5793-11f0-8afe-bcfce78b517a"), UUID{}},
+		{"inet", "'127.0.0.1'", net.ParseIP("127.0.0.1").To4(), net.IP(nil)},
+		{"blob", "0x48656c6c6f", []byte("Hello"), []byte(nil)},
+		{"varint", "123456789012345678901234567890", mustParseBigInt("123456789012345678901234567890"), (*big.Int)(nil)},
+		{"decimal", "123.45", mustParseDecimal("123.45"), (*inf.Dec)(nil)},
+		{"date", "'2015-05-03'", time.Date(2015, 5, 3, 0, 0, 0, 0, time.UTC), time.Time{}},
+		{"time", "'13:30:54.234'", 13*time.Hour + 30*time.Minute + 54*time.Second + 234*time.Millisecond, time.Duration(0)},
+		{"duration", "1y2mo3d4h5m6s789ms", mustCreateDuration(14, 3, 4*time.Hour+5*time.Minute+6*time.Second+789*time.Millisecond), Duration{}},
+	}
+
+	for i, tc := range testCases {
+		t.Run(tc.CQLType, func(t *testing.T) {
+			testSliceMapMapScanSimple(t, session, tc, i)
+		})
+	}
+}
+
+// Simplified test function that tests both SliceMap and MapScan with both NULL and non-NULL values
+func testSliceMapMapScanSimple(t *testing.T, session *Session, tc SliceMapTypesTestCase, id int) {
+	colName := tc.CQLType + "_col"
+
+	// Test non-NULL value
+	t.Run("NonNull", func(t *testing.T) {
+		// Insert non-NULL value
+		insertQuery := fmt.Sprintf("INSERT INTO gocql_test.slicemap_test (id, %s) VALUES (?, %s)", colName, tc.CQLValue)
+		if err := session.Query(insertQuery, id*2).Exec(); err != nil {
+			t.Fatalf("Failed to insert non-NULL value: %v", err)
+		}
+
+		// Test both SliceMap and MapScan
+		for _, method := range []string{"SliceMap", "MapScan"} {
+			t.Run(method, func(t *testing.T) {
+				result := queryAndExtractValue(t, session, colName, id*2, method)
+				validateResult(t, tc.CQLType, tc.ExpectedValue, result, method, "non-NULL")
+			})
+		}
+	})
+
+	// Test NULL value
+	t.Run("Null", func(t *testing.T) {
+		// Insert NULL value
+		insertQuery := fmt.Sprintf("INSERT INTO gocql_test.slicemap_test (id, %s) VALUES (?, NULL)", colName)
+		if err := session.Query(insertQuery, id*2+1).Exec(); err != nil {
+			t.Fatalf("Failed to insert NULL value: %v", err)
+		}
+
+		// Test both SliceMap and MapScan
+		for _, method := range []string{"SliceMap", "MapScan"} {
+			t.Run(method, func(t *testing.T) {
+				result := queryAndExtractValue(t, session, colName, id*2+1, method)
+				validateResult(t, tc.CQLType, tc.ExpectedNullValue, result, method, "NULL")
+			})
+		}
+	})
+}
+
+// Helper function to query and extract value using either SliceMap or MapScan
+func queryAndExtractValue(t *testing.T, session *Session, colName string, id int, method string) interface{} {
+	selectQuery := fmt.Sprintf("SELECT %s FROM gocql_test.slicemap_test WHERE id = ?", colName)
+
+	switch method {
+	case "SliceMap":
+		iter := session.Query(selectQuery, id).Iter()
+		sliceResults, err := iter.SliceMap()
+		iter.Close()
+		if err != nil {
+			t.Fatalf("SliceMap failed: %v", err)
+		}
+		if len(sliceResults) != 1 {
+			t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+		}
+		return sliceResults[0][colName]
+
+	case "MapScan":
+		mapResult := make(map[string]interface{})
+		if err := session.Query(selectQuery, id).MapScan(mapResult); err != nil {
+			t.Fatalf("MapScan failed: %v", err)
+		}
+		return mapResult[colName]
+
+	default:
+		t.Fatalf("Unknown method: %s", method)
+		return nil
+	}
+}
+
+// Helper function to validate results
+func validateResult(t *testing.T, cqlType string, expected, actual interface{}, method, valueType string) {
+	// Check type
+	if expected != nil && actual != nil {
+		expectedType := reflect.TypeOf(expected)
+		actualType := reflect.TypeOf(actual)
+		if expectedType != actualType {
+			t.Errorf("%s %s %s: expected type %v, got %v", method, valueType, cqlType, expectedType, actualType)
+		}
+	}
+
+	// Check value
+	if !compareValues(t, cqlType, expected, actual) {
+		t.Errorf("%s %s %s: expected value %v (type %T), got %v (type %T)",
+			method, valueType, cqlType, expected, expected, actual, actual)
+	}
+}
+
+// Helper function to parse UUID (for test data)
+func mustParseUUID(s string) UUID {
+	u, err := ParseUUID(s)
+	if err != nil {
+		panic(err)
+	}
+	return u
+}
+
+// Helper function to parse big.Int (for test data)
+func mustParseBigInt(s string) *big.Int {
+	i := new(big.Int)
+	if _, ok := i.SetString(s, 10); !ok {
+		panic("failed to parse big.Int: " + s)
+	}
+	return i
+}
+
+// Helper function to parse inf.Dec (for test data)
+func mustParseDecimal(s string) *inf.Dec {
+	dec := new(inf.Dec)
+	if _, ok := dec.SetString(s); !ok {
+		panic("failed to parse inf.Dec: " + s)
+	}
+	return dec
+}
+
+// Helper function to create Duration (for test data)
+func mustCreateDuration(months int32, days int32, timeDuration time.Duration) Duration {
+	return Duration{
+		Months:      months,
+		Days:        days,
+		Nanoseconds: timeDuration.Nanoseconds(),
+	}
+}
+
+// TestSliceMapMapScanCounterTypes tests counter types separately since they have special restrictions
+// (counter columns can't be mixed with other column types in the same table)
+func TestSliceMapMapScanCounterTypes(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+
+	// Create separate table for counter types
+	if err := createTable(session, `
+		CREATE TABLE IF NOT EXISTS gocql_test.slicemap_counter_test (
+			id int PRIMARY KEY,
+			counter_col counter
+		)
+	`); err != nil {
+		t.Fatal("Failed to create counter test table:", err)
+	}
+
+	// Clear existing data
+	if err := session.Query("TRUNCATE gocql_test.slicemap_counter_test").Exec(); err != nil {
+		t.Fatal("Failed to truncate counter test table:", err)
+	}
+
+	testID := 1
+	expectedValue := int64(42)
+
+	// Increment counter (can't INSERT into counter, must UPDATE)
+	err := session.Query("UPDATE gocql_test.slicemap_counter_test SET counter_col = counter_col + 42 WHERE id = ?", testID).Exec()
+	if err != nil {
+		t.Fatalf("Failed to increment counter: %v", err)
+	}
+
+	// Test both SliceMap and MapScan
+	for _, method := range []string{"SliceMap", "MapScan"} {
+		t.Run(method, func(t *testing.T) {
+			var result interface{}
+
+			selectQuery := "SELECT counter_col FROM gocql_test.slicemap_counter_test WHERE id = ?"
+			if method == "SliceMap" {
+				iter := session.Query(selectQuery, testID).Iter()
+				sliceResults, err := iter.SliceMap()
+				iter.Close()
+				if err != nil {
+					t.Fatalf("SliceMap failed: %v", err)
+				}
+				if len(sliceResults) != 1 {
+					t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+				}
+				result = sliceResults[0]["counter_col"]
+			} else {
+				mapResult := make(map[string]interface{})
+				if err := session.Query(selectQuery, testID).MapScan(mapResult); err != nil {
+					t.Fatalf("MapScan failed: %v", err)
+				}
+				result = mapResult["counter_col"]
+			}
+
+			validateResult(t, "counter", expectedValue, result, method, "incremented")
+		})
+	}
+}
+
+// TestSliceMapMapScanTupleTypes tests tuple types separately since they have special handling
+// (tuple elements get split into individual columns)
+func TestSliceMapMapScanTupleTypes(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+
+	// Create test table with tuple column
+	if err := createTable(session, `
+		CREATE TABLE IF NOT EXISTS gocql_test.slicemap_tuple_test (
+			id int PRIMARY KEY,
+			tuple_col tuple<int, text>
+		)
+	`); err != nil {
+		t.Fatal("Failed to create tuple test table:", err)
+	}
+
+	// Clear existing data
+	if err := session.Query("TRUNCATE gocql_test.slicemap_tuple_test").Exec(); err != nil {
+		t.Fatal("Failed to truncate tuple test table:", err)
+	}
+
+	// Test non-NULL tuple
+	t.Run("NonNull", func(t *testing.T) {
+		testID := 1
+		// Insert tuple value
+		err := session.Query("INSERT INTO gocql_test.slicemap_tuple_test (id, tuple_col) VALUES (?, (42, 'hello'))", testID).Exec()
+		if err != nil {
+			t.Fatalf("Failed to insert tuple value: %v", err)
+		}
+
+		// Test both SliceMap and MapScan
+		for _, method := range []string{"SliceMap", "MapScan"} {
+			t.Run(method, func(t *testing.T) {
+				var result map[string]interface{}
+
+				selectQuery := "SELECT tuple_col FROM gocql_test.slicemap_tuple_test WHERE id = ?"
+				if method == "SliceMap" {
+					iter := session.Query(selectQuery, testID).Iter()
+					sliceResults, err := iter.SliceMap()
+					iter.Close()
+					if err != nil {
+						t.Fatalf("SliceMap failed: %v", err)
+					}
+					if len(sliceResults) != 1 {
+						t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+					}
+					result = sliceResults[0]
+				} else {
+					result = make(map[string]interface{})
+					if err := session.Query(selectQuery, testID).MapScan(result); err != nil {
+						t.Fatalf("MapScan failed: %v", err)
+					}
+				}
+
+				// Check tuple elements (tuples get split into individual columns)
+				elem0Key := TupleColumnName("tuple_col", 0)
+				elem1Key := TupleColumnName("tuple_col", 1)
+
+				if result[elem0Key] != 42 {
+					t.Errorf("%s tuple[0]: expected 42, got %v", method, result[elem0Key])
+				}
+				if result[elem1Key] != "hello" {
+					t.Errorf("%s tuple[1]: expected 'hello', got %v", method, result[elem1Key])
+				}
+			})
+		}
+	})
+
+	// Test NULL tuple
+	t.Run("Null", func(t *testing.T) {
+		testID := 2
+		// Insert NULL tuple
+		err := session.Query("INSERT INTO gocql_test.slicemap_tuple_test (id, tuple_col) VALUES (?, NULL)", testID).Exec()
+		if err != nil {
+			t.Fatalf("Failed to insert NULL tuple: %v", err)
+		}
+
+		// Test both SliceMap and MapScan
+		for _, method := range []string{"SliceMap", "MapScan"} {
+			t.Run(method, func(t *testing.T) {
+				var result map[string]interface{}
+
+				selectQuery := "SELECT tuple_col FROM gocql_test.slicemap_tuple_test WHERE id = ?"
+				if method == "SliceMap" {
+					iter := session.Query(selectQuery, testID).Iter()
+					sliceResults, err := iter.SliceMap()
+					iter.Close()
+					if err != nil {
+						t.Fatalf("SliceMap failed: %v", err)
+					}
+					if len(sliceResults) != 1 {
+						t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+					}
+					result = sliceResults[0]
+				} else {
+					result = make(map[string]interface{})
+					if err := session.Query(selectQuery, testID).MapScan(result); err != nil {
+						t.Fatalf("MapScan failed: %v", err)
+					}
+				}
+
+				// Check tuple elements (NULL tuple gives zero values)
+				elem0Key := TupleColumnName("tuple_col", 0)
+				elem1Key := TupleColumnName("tuple_col", 1)
+
+				if result[elem0Key] != 0 {
+					t.Errorf("%s NULL tuple[0]: expected 0, got %v", method, result[elem0Key])
+				}
+				if result[elem1Key] != "" {
+					t.Errorf("%s NULL tuple[1]: expected '', got %v", method, result[elem1Key])
+				}
+			})
+		}
+	})
+}
+
+// TestSliceMapMapScanVectorTypes tests vector types separately since they need Cassandra 5.0+ and special table setup
+// (vectors need separate tables and version checks)
+func TestSliceMapMapScanVectorTypes(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+
+	// Vector types require Cassandra 5.0+
+	if session.control.getConn().host.Version().Before(5, 0, 0) {
+		t.Skip("Vector types require Cassandra 5.0+")
+	}
+
+	// Create test table with vector columns
+	if err := createTable(session, `
+		CREATE TABLE IF NOT EXISTS gocql_test.slicemap_vector_test (
+			id int PRIMARY KEY,
+			vector_float_col vector<float, 3>,
+			vector_text_col vector<text, 2>
+		)
+	`); err != nil {
+		t.Fatal("Failed to create vector test table:", err)
+	}
+
+	// Clear existing data
+	if err := session.Query("TRUNCATE gocql_test.slicemap_vector_test").Exec(); err != nil {
+		t.Fatal("Failed to truncate vector test table:", err)
+	}
+
+	testCases := []struct {
+		colName       string
+		cqlValue      string
+		expectedValue interface{}
+		expectedNull  interface{}
+	}{
+		{"vector_float_col", "[1.0, 2.5, -3.0]", []float32{1.0, 2.5, -3.0}, []float32(nil)},
+		{"vector_text_col", "['hello', 'world']", []string{"hello", "world"}, []string(nil)},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.colName, func(t *testing.T) {
+			// Test non-NULL value
+			t.Run("NonNull", func(t *testing.T) {
+				testID := 1
+				// Insert non-NULL value
+				insertQuery := fmt.Sprintf("INSERT INTO gocql_test.slicemap_vector_test (id, %s) VALUES (?, %s)", tc.colName, tc.cqlValue)
+				if err := session.Query(insertQuery, testID).Exec(); err != nil {
+					t.Fatalf("Failed to insert non-NULL value: %v", err)
+				}
+
+				// Test both SliceMap and MapScan
+				for _, method := range []string{"SliceMap", "MapScan"} {
+					t.Run(method, func(t *testing.T) {
+						var result interface{}
+
+						selectQuery := fmt.Sprintf("SELECT %s FROM gocql_test.slicemap_vector_test WHERE id = ?", tc.colName)
+						if method == "SliceMap" {
+							iter := session.Query(selectQuery, testID).Iter()
+							sliceResults, err := iter.SliceMap()
+							iter.Close()
+							if err != nil {
+								t.Fatalf("SliceMap failed: %v", err)
+							}
+							if len(sliceResults) != 1 {
+								t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+							}
+							result = sliceResults[0][tc.colName]
+						} else {
+							mapResult := make(map[string]interface{})
+							if err := session.Query(selectQuery, testID).MapScan(mapResult); err != nil {
+								t.Fatalf("MapScan failed: %v", err)
+							}
+							result = mapResult[tc.colName]
+						}
+
+						validateResult(t, tc.colName, tc.expectedValue, result, method, "non-NULL")
+					})
+				}
+			})
+
+			// Test NULL value
+			t.Run("Null", func(t *testing.T) {
+				testID := 2
+				// Insert NULL value
+				insertQuery := fmt.Sprintf("INSERT INTO gocql_test.slicemap_vector_test (id, %s) VALUES (?, NULL)", tc.colName)
+				if err := session.Query(insertQuery, testID).Exec(); err != nil {
+					t.Fatalf("Failed to insert NULL value: %v", err)
+				}
+
+				// Test both SliceMap and MapScan
+				for _, method := range []string{"SliceMap", "MapScan"} {
+					t.Run(method, func(t *testing.T) {
+						var result interface{}
+
+						selectQuery := fmt.Sprintf("SELECT %s FROM gocql_test.slicemap_vector_test WHERE id = ?", tc.colName)
+						if method == "SliceMap" {
+							iter := session.Query(selectQuery, testID).Iter()
+							sliceResults, err := iter.SliceMap()
+							iter.Close()
+							if err != nil {
+								t.Fatalf("SliceMap failed: %v", err)
+							}
+							if len(sliceResults) != 1 {
+								t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+							}
+							result = sliceResults[0][tc.colName]
+						} else {
+							mapResult := make(map[string]interface{})
+							if err := session.Query(selectQuery, testID).MapScan(mapResult); err != nil {
+								t.Fatalf("MapScan failed: %v", err)
+							}
+							result = mapResult[tc.colName]
+						}
+
+						// Vectors should return nil slices for NULL values for consistency
+						validateResult(t, tc.colName, tc.expectedNull, result, method, "NULL")
+					})
+				}
+			})
+		})
+	}
+}
+
+// TestSliceMapMapScanCollectionTypes tests collection types separately since they have special handling
+// (collections should return nil slices/maps for NULL values for consistency with other slice-based types)
+func TestSliceMapMapScanCollectionTypes(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+
+	// Create test table with collection columns
+	if err := createTable(session, `
+		CREATE TABLE IF NOT EXISTS gocql_test.slicemap_collection_test (
+			id int PRIMARY KEY,
+			list_col list<text>,
+			set_col set<int>,
+			map_col map<text, int>
+		)
+	`); err != nil {
+		t.Fatal("Failed to create collection test table:", err)
+	}
+
+	// Clear existing data
+	if err := session.Query("TRUNCATE gocql_test.slicemap_collection_test").Exec(); err != nil {
+		t.Fatal("Failed to truncate collection test table:", err)
+	}
+
+	testCases := []struct {
+		colName       string
+		cqlValue      string
+		expectedValue interface{}
+		expectedNull  interface{}
+	}{
+		{"list_col", "['a', 'b', 'c']", []string{"a", "b", "c"}, []string(nil)},
+		{"set_col", "{1, 2, 3}", []int{1, 2, 3}, []int(nil)},
+		{"map_col", "{'key1': 1, 'key2': 2}", map[string]int{"key1": 1, "key2": 2}, map[string]int(nil)},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.colName, func(t *testing.T) {
+			// Test non-NULL value
+			t.Run("NonNull", func(t *testing.T) {
+				testID := 1
+				// Insert non-NULL value
+				insertQuery := fmt.Sprintf("INSERT INTO gocql_test.slicemap_collection_test (id, %s) VALUES (?, %s)", tc.colName, tc.cqlValue)
+				if err := session.Query(insertQuery, testID).Exec(); err != nil {
+					t.Fatalf("Failed to insert non-NULL value: %v", err)
+				}
+
+				// Test both SliceMap and MapScan
+				for _, method := range []string{"SliceMap", "MapScan"} {
+					t.Run(method, func(t *testing.T) {
+						var result interface{}
+
+						selectQuery := fmt.Sprintf("SELECT %s FROM gocql_test.slicemap_collection_test WHERE id = ?", tc.colName)
+						if method == "SliceMap" {
+							iter := session.Query(selectQuery, testID).Iter()
+							sliceResults, err := iter.SliceMap()
+							iter.Close()
+							if err != nil {
+								t.Fatalf("SliceMap failed: %v", err)
+							}
+							if len(sliceResults) != 1 {
+								t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+							}
+							result = sliceResults[0][tc.colName]
+						} else {
+							mapResult := make(map[string]interface{})
+							if err := session.Query(selectQuery, testID).MapScan(mapResult); err != nil {
+								t.Fatalf("MapScan failed: %v", err)
+							}
+							result = mapResult[tc.colName]
+						}
+
+						// For sets, we need special comparison since order is not guaranteed
+						if strings.HasPrefix(tc.colName, "set_") {
+							if !compareCollectionValues(t, tc.colName, tc.expectedValue, result) {
+								t.Errorf("%s non-NULL %s: expected %v, got %v", method, tc.colName, tc.expectedValue, result)
+							}
+						} else {
+							validateResult(t, tc.colName, tc.expectedValue, result, method, "non-NULL")
+						}
+					})
+				}
+			})
+
+			// Test NULL value
+			t.Run("Null", func(t *testing.T) {
+				testID := 2
+				// Insert NULL value
+				insertQuery := fmt.Sprintf("INSERT INTO gocql_test.slicemap_collection_test (id, %s) VALUES (?, NULL)", tc.colName)
+				if err := session.Query(insertQuery, testID).Exec(); err != nil {
+					t.Fatalf("Failed to insert NULL value: %v", err)
+				}
+
+				// Test both SliceMap and MapScan
+				for _, method := range []string{"SliceMap", "MapScan"} {
+					t.Run(method, func(t *testing.T) {
+						var result interface{}
+
+						selectQuery := fmt.Sprintf("SELECT %s FROM gocql_test.slicemap_collection_test WHERE id = ?", tc.colName)
+						if method == "SliceMap" {
+							iter := session.Query(selectQuery, testID).Iter()
+							sliceResults, err := iter.SliceMap()
+							iter.Close()
+							if err != nil {
+								t.Fatalf("SliceMap failed: %v", err)
+							}
+							if len(sliceResults) != 1 {
+								t.Fatalf("Expected 1 result, got %d", len(sliceResults))
+							}
+							result = sliceResults[0][tc.colName]
+						} else {
+							mapResult := make(map[string]interface{})
+							if err := session.Query(selectQuery, testID).MapScan(mapResult); err != nil {
+								t.Fatalf("MapScan failed: %v", err)
+							}
+							result = mapResult[tc.colName]
+						}
+
+						// Collections should return nil slices/maps for NULL values for consistency
+						validateResult(t, tc.colName, tc.expectedNull, result, method, "NULL")
+					})
+				}
+			})
+		})
+	}
+}
diff --git a/marshal.go b/marshal.go
index 5133a96d..5562d9fd 100644
--- a/marshal.go
+++ b/marshal.go
@@ -432,15 +432,19 @@ func (smallIntTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (s smallIntTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decShort(data)
+	if err != nil {
+		return unmarshalErrorf("%s", err.Error())
+	}
 	if iptr, ok := value.(*interface{}); ok && iptr != nil {
 		var v int16
-		if err := unmarshalIntlike(TypeSmallInt, int64(decShort(data)), data, &v); err != nil {
+		if err := unmarshalIntlike(TypeSmallInt, int64(decodedData), data, &v); err != nil {
 			return err
 		}
 		*iptr = v
 		return nil
 	}
-	return unmarshalIntlike(TypeSmallInt, int64(decShort(data)), data, value)
+	return unmarshalIntlike(TypeSmallInt, int64(decodedData), data, value)
 }
 
 type tinyIntTypeInfo struct{}
@@ -540,15 +544,19 @@ func (tinyIntTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (t tinyIntTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decTiny(data)
+	if err != nil {
+		return unmarshalErrorf("%s", err.Error())
+	}
 	if iptr, ok := value.(*interface{}); ok && iptr != nil {
 		var v int8
-		if err := unmarshalIntlike(TypeSmallInt, int64(decShort(data)), data, &v); err != nil {
+		if err := unmarshalIntlike(TypeTinyInt, int64(decodedData), data, &v); err != nil {
 			return err
 		}
 		*iptr = v
 		return nil
 	}
-	return unmarshalIntlike(TypeTinyInt, int64(decTiny(data)), data, value)
+	return unmarshalIntlike(TypeTinyInt, int64(decodedData), data, value)
 }
 
 type intTypeInfo struct{}
@@ -636,26 +644,34 @@ func (intTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (i intTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decInt(data)
+	if err != nil {
+		return unmarshalErrorf("%s", err.Error())
+	}
 	if iptr, ok := value.(*interface{}); ok && iptr != nil {
 		var v int
-		if err := unmarshalIntlike(TypeInt, int64(decInt(data)), data, &v); err != nil {
+		if err := unmarshalIntlike(TypeInt, int64(decodedData), data, &v); err != nil {
 			return err
 		}
 		*iptr = v
 		return nil
 	}
-	return unmarshalIntlike(TypeInt, int64(decInt(data)), data, value)
+	return unmarshalIntlike(TypeInt, int64(decodedData), data, value)
 }
 
 func encInt(x int32) []byte {
 	return []byte{byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x)}
 }
 
-func decInt(x []byte) int32 {
+func decInt(x []byte) (int32, error) {
+	if x == nil || len(x) == 0 {
+		// len(x)==0 is to keep old behavior from 1.x (empty values can be in the DB and are different from NULL)
+		return 0, nil
+	}
 	if len(x) != 4 {
-		return 0
+		return 0, fmt.Errorf("expected 4 bytes decoding int but got %v", len(x))
 	}
-	return int32(x[0])<<24 | int32(x[1])<<16 | int32(x[2])<<8 | int32(x[3])
+	return int32(x[0])<<24 | int32(x[1])<<16 | int32(x[2])<<8 | int32(x[3]), nil
 }
 
 func encShort(x int16) []byte {
@@ -665,18 +681,26 @@ func encShort(x int16) []byte {
 	return p
 }
 
-func decShort(p []byte) int16 {
+func decShort(p []byte) (int16, error) {
+	if p == nil || len(p) == 0 {
+		// len(p)==0 is to keep old behavior from 1.x (empty values can be in the DB and are different from NULL)
+		return 0, nil
+	}
 	if len(p) != 2 {
-		return 0
+		return 0, fmt.Errorf("expected 2 bytes decoding short but got %v", len(p))
 	}
-	return int16(p[0])<<8 | int16(p[1])
+	return int16(p[0])<<8 | int16(p[1]), nil
 }
 
-func decTiny(p []byte) int8 {
+func decTiny(p []byte) (int8, error) {
+	if p == nil || len(p) == 0 {
+		// len(p)==0 is to keep old behavior from 1.x (empty values can be in the DB and are different from NULL)
+		return 0, nil
+	}
 	if len(p) != 1 {
-		return 0
+		return 0, fmt.Errorf("expected 1 byte decoding tinyint but got %v", len(p))
 	}
-	return int8(p[0])
+	return int8(p[0]), nil
 }
 
 type bigIntLikeTypeInfo struct {
@@ -774,15 +798,19 @@ func bytesToUint64(data []byte) (ret uint64) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (b bigIntLikeTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decBigInt(data)
+	if err != nil {
+		return unmarshalErrorf("can not unmarshal bigint: %s", err.Error())
+	}
 	if iptr, ok := value.(*interface{}); ok && iptr != nil {
 		var v int64
-		if err := unmarshalIntlike(b.typ, decBigInt(data), data, &v); err != nil {
+		if err := unmarshalIntlike(b.typ, decodedData, data, &v); err != nil {
 			return err
 		}
 		*iptr = v
 		return nil
 	}
-	return unmarshalIntlike(b.typ, decBigInt(data), data, value)
+	return unmarshalIntlike(b.typ, decodedData, data, value)
 }
 
 type varintTypeInfo struct{}
@@ -1083,14 +1111,18 @@ func unmarshalIntlike(typ Type, int64Val int64, data []byte, value interface{})
 	return unmarshalErrorf("can not unmarshal int-like into %T. Accepted types: big.Int, int8, uint8, int16, uint16, int, uint, int32, uint32, int64, uint64, string, *interface{}.", value)
 }
 
-func decBigInt(data []byte) int64 {
+func decBigInt(data []byte) (int64, error) {
+	if data == nil || len(data) == 0 {
+		// len(data)==0 is to keep old behavior from 1.x (empty values can be in the DB and are different from NULL)
+		return 0, nil
+	}
 	if len(data) != 8 {
-		return 0
+		return 0, fmt.Errorf("expected 8 bytes, got %d", len(data))
 	}
 	return int64(data[0])<<56 | int64(data[1])<<48 |
 		int64(data[2])<<40 | int64(data[3])<<32 |
 		int64(data[4])<<24 | int64(data[5])<<16 |
-		int64(data[6])<<8 | int64(data[7])
+		int64(data[6])<<8 | int64(data[7]), nil
 }
 
 type booleanTypeInfo struct{}
@@ -1130,12 +1162,16 @@ func (b booleanTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (b booleanTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decBool(data)
+	if err != nil {
+		return unmarshalErrorf("can not unmarshal boolean: %s", err.Error())
+	}
 	switch v := value.(type) {
 	case *bool:
-		*v = decBool(data)
+		*v = decodedData
 		return nil
 	case *interface{}:
-		*v = decBool(data)
+		*v = decodedData
 		return nil
 	}
 	rv := reflect.ValueOf(value)
@@ -1145,7 +1181,7 @@ func (b booleanTypeInfo) Unmarshal(data []byte, value interface{}) error {
 	rv = rv.Elem()
 	switch rv.Type().Kind() {
 	case reflect.Bool:
-		rv.SetBool(decBool(data))
+		rv.SetBool(decodedData)
 		return nil
 	}
 	return unmarshalErrorf("can not unmarshal boolean into %T. Accepted types: *bool, *interface{}.", value)
@@ -1158,11 +1194,15 @@ func encBool(v bool) []byte {
 	return []byte{0}
 }
 
-func decBool(v []byte) bool {
-	if len(v) == 0 {
-		return false
+func decBool(v []byte) (bool, error) {
+	if v == nil || len(v) == 0 {
+		// len(v)==0 is to keep old behavior from 1.x (empty values can be in the DB and are different from NULL)
+		return false, nil
+	}
+	if len(v) != 1 {
+		return false, fmt.Errorf("expected 1 byte, got %d", len(v))
 	}
-	return v[0] != 0
+	return v[0] != 0, nil
 }
 
 type floatTypeInfo struct{}
@@ -1200,12 +1240,16 @@ func (floatTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (floatTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decInt(data)
+	if err != nil {
+		return err
+	}
 	switch v := value.(type) {
 	case *float32:
-		*v = math.Float32frombits(uint32(decInt(data)))
+		*v = math.Float32frombits(uint32(decodedData))
 		return nil
 	case *interface{}:
-		*v = math.Float32frombits(uint32(decInt(data)))
+		*v = math.Float32frombits(uint32(decodedData))
 		return nil
 	}
 	rv := reflect.ValueOf(value)
@@ -1215,7 +1259,7 @@ func (floatTypeInfo) Unmarshal(data []byte, value interface{}) error {
 	rv = rv.Elem()
 	switch rv.Type().Kind() {
 	case reflect.Float32:
-		rv.SetFloat(float64(math.Float32frombits(uint32(decInt(data)))))
+		rv.SetFloat(float64(math.Float32frombits(uint32(decodedData))))
 		return nil
 	}
 	return unmarshalErrorf("can not unmarshal float into %T. Accepted types: *float32, *interface{}, UnsetValue.", value)
@@ -1254,12 +1298,17 @@ func (doubleTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (doubleTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decBigInt(data)
+	if err != nil {
+		return unmarshalErrorf("can not unmarshal double: %s", err.Error())
+	}
+	decodedUint64 := uint64(decodedData)
 	switch v := value.(type) {
 	case *float64:
-		*v = math.Float64frombits(uint64(decBigInt(data)))
+		*v = math.Float64frombits(decodedUint64)
 		return nil
 	case *interface{}:
-		*v = math.Float64frombits(uint64(decBigInt(data)))
+		*v = math.Float64frombits(decodedUint64)
 		return nil
 	}
 	rv := reflect.ValueOf(value)
@@ -1269,7 +1318,7 @@ func (doubleTypeInfo) Unmarshal(data []byte, value interface{}) error {
 	rv = rv.Elem()
 	switch rv.Type().Kind() {
 	case reflect.Float64:
-		rv.SetFloat(math.Float64frombits(uint64(decBigInt(data))))
+		rv.SetFloat(math.Float64frombits(decodedUint64))
 		return nil
 	}
 	return unmarshalErrorf("can not unmarshal double into %T. Accepted types: *float64, *interface{}.", value)
@@ -1312,20 +1361,22 @@ func (decimalTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (decimalTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	if len(data) < 4 {
+		return unmarshalErrorf("inf.Dec needs at least 4 bytes, while value has only %d", len(data))
+	}
+
+	decodedData, err := decInt(data[0:4])
+	if err != nil {
+		return err
+	}
 	switch v := value.(type) {
 	case *inf.Dec:
-		if len(data) < 4 {
-			return unmarshalErrorf("inf.Dec needs at least 4 bytes, while value has only %d", len(data))
-		}
-		scale := decInt(data[0:4])
+		scale := decodedData
 		unscaled := decBigInt2C(data[4:], nil)
 		*v = *inf.NewDecBig(unscaled, inf.Scale(scale))
 		return nil
 	case *interface{}:
-		if len(data) < 4 {
-			return unmarshalErrorf("inf.Dec needs at least 4 bytes, while value has only %d", len(data))
-		}
-		scale := decInt(data[0:4])
+		scale := decodedData
 		unscaled := decBigInt2C(data[4:], nil)
 		*v = inf.NewDecBig(unscaled, inf.Scale(scale))
 		return nil
@@ -1414,16 +1465,20 @@ func (timestampTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (timestampTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decBigInt(data)
+	if err != nil {
+		return unmarshalErrorf("can not unmarshal timestamp: %s", err.Error())
+	}
 	switch v := value.(type) {
 	case *int64:
-		*v = decBigInt(data)
+		*v = decodedData
 		return nil
 	case *time.Time:
 		if len(data) == 0 {
 			*v = time.Time{}
 			return nil
 		}
-		x := decBigInt(data)
+		x := decodedData
 		sec := x / 1000
 		nsec := (x - sec*1000) * 1000000
 		*v = time.Unix(sec, nsec).In(time.UTC)
@@ -1433,7 +1488,7 @@ func (timestampTypeInfo) Unmarshal(data []byte, value interface{}) error {
 			*v = time.Time{}
 			return nil
 		}
-		x := decBigInt(data)
+		x := decodedData
 		sec := x / 1000
 		nsec := (x - sec*1000) * 1000000
 		*v = time.Unix(sec, nsec).In(time.UTC)
@@ -1447,7 +1502,7 @@ func (timestampTypeInfo) Unmarshal(data []byte, value interface{}) error {
 	rv = rv.Elem()
 	switch rv.Type().Kind() {
 	case reflect.Int64:
-		rv.SetInt(decBigInt(data))
+		rv.SetInt(decodedData)
 		return nil
 	}
 	return unmarshalErrorf("can not unmarshal timestamp into %T. Accepted types: *int64, *time.Time, *interface{}.", value)
@@ -1490,15 +1545,19 @@ func (timeTypeInfo) Marshal(value interface{}) ([]byte, error) {
 
 // Unmarshal unmarshals the byte slice into the value.
 func (timeTypeInfo) Unmarshal(data []byte, value interface{}) error {
+	decodedData, err := decBigInt(data)
+	if err != nil {
+		return unmarshalErrorf("can not unmarshal time: %s", err.Error())
+	}
 	switch v := value.(type) {
 	case *int64:
-		*v = decBigInt(data)
+		*v = decodedData
 		return nil
 	case *time.Duration:
-		*v = time.Duration(decBigInt(data))
+		*v = time.Duration(decodedData)
 		return nil
 	case *interface{}:
-		*v = time.Duration(decBigInt(data))
+		*v = time.Duration(decodedData)
 		return nil
 	}
 
@@ -1509,7 +1568,7 @@ func (timeTypeInfo) Unmarshal(data []byte, value interface{}) error {
 	rv = rv.Elem()
 	switch rv.Type().Kind() {
 	case reflect.Int64:
-		rv.SetInt(decBigInt(data))
+		rv.SetInt(decodedData)
 		return nil
 	}
 	return unmarshalErrorf("can not unmarshal time into %T. Accepted types: *int64, *time.Duration, *interface{}.", value)
@@ -2010,7 +2069,7 @@ func (c CollectionType) unmarshalListSet(data []byte, value interface{}) error {
 		}
 		return nil
 	}
-	return unmarshalErrorf("can not unmarshal collection into %T. Accepted types: *slice, *array.", value)
+	return unmarshalErrorf("can not unmarshal collection into %T. Accepted types: *slice, *array, *interface{}.", value)
 }
 
 type mapCQLType struct {
diff --git a/marshal_test.go b/marshal_test.go
index 5b518b13..8a7b989e 100644
--- a/marshal_test.go
+++ b/marshal_test.go
@@ -1704,9 +1704,85 @@ func TestMarshalTime(t *testing.T) {
 			t.Errorf("marshalTest[%d]: %v", i, err)
 			continue
 		}
+		decoded, err := decBigInt(test.Data)
+		if err != nil {
+			t.Error(err)
+		}
 		if !bytes.Equal(data, test.Data) {
 			t.Errorf("marshalTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
-				test.Data, decInt(test.Data), data, decInt(data), test.Value)
+				test.Data, decoded, data, decoded, test.Value)
+		}
+	}
+}
+
+func TestUnmarshalTimestamp(t *testing.T) {
+	var marshalTimestampTests = []struct {
+		Info  TypeInfo
+		Data  []byte
+		Value interface{}
+	}{
+		{
+			timestampTypeInfo{},
+			[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
+			time.Date(2013, time.August, 13, 9, 52, 3, 0, time.UTC),
+		},
+		{
+			timestampTypeInfo{},
+			[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
+			int64(1376387523000),
+		},
+		{
+			// 9223372036854 is the maximum time representable in ms since the epoch
+			// with int64 if using UnixNano to convert
+			timestampTypeInfo{},
+			[]byte("\x00\x00\x08\x63\x7b\xd0\x5a\xf6"),
+			time.Date(2262, time.April, 11, 23, 47, 16, 854775807, time.UTC),
+		},
+		{
+			// One nanosecond after causes overflow when using UnixNano
+			// Instead it should resolve to the same time in ms
+			timestampTypeInfo{},
+			[]byte("\x00\x00\x08\x63\x7b\xd0\x5a\xf6"),
+			time.Date(2262, time.April, 11, 23, 47, 16, 854775808, time.UTC),
+		},
+		{
+			// -9223372036855 is the minimum time representable in ms since the epoch
+			// with int64 if using UnixNano to convert
+			timestampTypeInfo{},
+			[]byte("\xff\xff\xf7\x9c\x84\x2f\xa5\x09"),
+			time.Date(1677, time.September, 21, 00, 12, 43, 145224192, time.UTC),
+		},
+		{
+			// One nanosecond earlier causes overflow when using UnixNano
+			// it should resolve to the same time in ms
+			timestampTypeInfo{},
+			[]byte("\xff\xff\xf7\x9c\x84\x2f\xa5\x09"),
+			time.Date(1677, time.September, 21, 00, 12, 43, 145224191, time.UTC),
+		},
+		{
+			// Store the zero time as a blank slice
+			timestampTypeInfo{},
+			[]byte{},
+			time.Time{},
+		},
+		{
+			// Store the zero time as a nil slice
+			timestampTypeInfo{},
+			[]byte(nil),
+			time.Time{},
+		},
+	}
+
+	for i, test := range marshalTimestampTests {
+		v := reflect.New(reflect.TypeOf(test.Value)).Interface()
+		err := Unmarshal(test.Info, test.Data, &v)
+		if err != nil {
+			t.Errorf("marshalTest[%d]: %v", i, err)
+			continue
+		}
+		if reflect.DeepEqual(v, test.Value) {
+			t.Errorf("marshalTest[%d]: expected %v, got %v", i,
+				test.Value, v)
 		}
 	}
 }
@@ -1761,6 +1837,12 @@ func TestMarshalTimestamp(t *testing.T) {
 			[]byte{},
 			time.Time{},
 		},
+		{
+			// Store the zero time as a nil slice
+			timestampTypeInfo{},
+			[]byte(nil),
+			time.Time{},
+		},
 	}
 
 	for i, test := range marshalTimestampTests {
@@ -1770,8 +1852,8 @@ func TestMarshalTimestamp(t *testing.T) {
 			continue
 		}
 		if !bytes.Equal(data, test.Data) {
-			t.Errorf("marshalTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
-				test.Data, decBigInt(test.Data), data, decBigInt(data), test.Value)
+			t.Errorf("marshalTest[%d]: expected %x, got %x for time %s", i,
+				test.Data, data, test.Value)
 		}
 	}
 }
@@ -1897,23 +1979,22 @@ func TestMarshalTuple(t *testing.T) {
 		},
 	}
 
-	for _, tc := range testCases {
+	for i, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			data, err := Marshal(info, tc.value)
 			if err != nil {
-				t.Errorf("marshalTest: %v", err)
+				t.Errorf("marshalTest[%d]: %v", i, err)
 				return
 			}
-
 			if !bytes.Equal(data, tc.expected) {
-				t.Errorf("marshalTest: expected %x (%v), got %x (%v)",
-					tc.expected, decBigInt(tc.expected), data, decBigInt(data))
+				t.Errorf("marshalTest[%d]: expected %x, got %x",
+					i, tc.expected, data)
 				return
 			}
 
 			err = Unmarshal(info, data, tc.checkValue)
 			if err != nil {
-				t.Errorf("unmarshalTest: %v", err)
+				t.Errorf("marshalTest[%d]: %v", i, err)
 				return
 			}
 
@@ -2284,9 +2365,13 @@ func TestMarshalDate(t *testing.T) {
 			t.Errorf("marshalTest[%d]: %v", i, err)
 			continue
 		}
+		decoded, err := decInt(test.Data)
+		if err != nil {
+			t.Error(err)
+		}
 		if !bytes.Equal(data, test.Data) {
 			t.Errorf("marshalTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
-				test.Data, decInt(test.Data), data, decInt(data), test.Value)
+				test.Data, decoded, data, decoded, test.Value)
 		}
 	}
 }
@@ -2323,9 +2408,13 @@ func TestLargeDate(t *testing.T) {
 			t.Errorf("largeDateTest[%d]: %v", i, err)
 			continue
 		}
+		decoded, err := decInt(test.Data)
+		if err != nil {
+			t.Error(err)
+		}
 		if !bytes.Equal(data, test.Data) {
 			t.Errorf("largeDateTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
-				test.Data, decInt(test.Data), data, decInt(data), test.Value)
+				test.Data, decoded, data, decoded, test.Value)
 		}
 
 		var date time.Time
@@ -2379,8 +2468,8 @@ func TestMarshalDuration(t *testing.T) {
 			continue
 		}
 		if !bytes.Equal(data, test.Data) {
-			t.Errorf("marshalTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
-				test.Data, decInt(test.Data), data, decInt(data), test.Value)
+			t.Errorf("marshalTest[%d]: expected %x, got %x for time %s", i,
+				test.Data, data, test.Value)
 		}
 	}
 }
diff --git a/vector.go b/vector.go
index 266e341e..72a11c2c 100644
--- a/vector.go
+++ b/vector.go
@@ -124,6 +124,13 @@ func (v VectorType) Unmarshal(data []byte, value interface{}) error {
 	}
 	rv = rv.Elem()
 	t := rv.Type()
+	if t.Kind() == reflect.Interface {
+		if t.NumMethod() != 0 {
+			return unmarshalErrorf("can not unmarshal into non-empty interface %T", value)
+		}
+		t = reflect.TypeOf(v.Zero())
+	}
+
 	k := t.Kind()
 	switch k {
 	case reflect.Slice, reflect.Array:
@@ -143,6 +150,9 @@ func (v VectorType) Unmarshal(data []byte, value interface{}) error {
 			}
 		} else {
 			rv.Set(reflect.MakeSlice(t, v.Dimensions, v.Dimensions))
+			if rv.Kind() == reflect.Interface {
+				rv = rv.Elem()
+			}
 		}
 		elemSize := len(data) / v.Dimensions
 		for i := 0; i < v.Dimensions; i++ {
@@ -173,7 +183,7 @@ func (v VectorType) Unmarshal(data []byte, value interface{}) error {
 		}
 		return nil
 	}
-	return unmarshalErrorf("can not unmarshal %s into %T. Accepted types: slice, array.", v, value)
+	return unmarshalErrorf("can not unmarshal %s into %T. Accepted types: *slice, *array, *interface{}.", v, value)
 }
 
 // isVectorVariableLengthType determines if a type requires explicit length serialization within a vector.


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org