Browse Source

Merge branch 'master' into 2.1_v2

Ben Hood 11 years ago
parent
commit
ee0d8dd204
26 changed files with 1012 additions and 38 deletions
  1. 7 2
      .travis.yml
  2. 7 0
      AUTHORS
  3. 8 2
      CONTRIBUTING.md
  4. 11 0
      README.md
  5. 150 11
      cassandra_test.go
  6. 1 0
      cluster.go
  7. 38 0
      compressor_test.go
  8. 48 2
      conn.go
  9. 51 0
      conn_test.go
  10. 2 1
      connectionpool.go
  11. 14 1
      helpers.go
  12. 8 3
      integration.sh
  13. 4 1
      marshal.go
  14. 15 2
      policies.go
  15. 47 11
      session.go
  16. 236 0
      session_test.go
  17. BIN
      testdata/pki/.keystore
  18. BIN
      testdata/pki/.truststore
  19. 20 0
      testdata/pki/ca.crt
  20. 30 0
      testdata/pki/ca.key
  21. 83 0
      testdata/pki/cassandra.crt
  22. 27 0
      testdata/pki/cassandra.key
  23. 83 0
      testdata/pki/gocql.crt
  24. 27 0
      testdata/pki/gocql.key
  25. 53 0
      topology_test.go
  26. 42 2
      uuid_test.go

+ 7 - 2
.travis.yml

@@ -9,17 +9,22 @@ go:
   - 1.2
   - 1.3
 
-before_script:
+before_install:
+  - sudo apt-get update
+
+install:
   - sudo apt-get install -y libjna-java python-pip
   - sudo pip install cql PyYAML six
   - go get code.google.com/p/go.tools/cmd/vet
+  - go get code.google.com/p/go.tools/cmd/cover
   - git clone https://github.com/pcmanus/ccm.git
   - pushd ccm
   - sudo ./setup.py install
   - popd
+  - go get .
 
 script:
-  - bash integration.sh $CASS
+  - bash -x integration.sh $CASS
   - go vet .
 
 notifications:

+ 7 - 0
AUTHORS

@@ -31,3 +31,10 @@ Muir Manders <muir@retailnext.net>
 Sankar P <sankar.curiosity@gmail.com>
 Julien Da Silva <julien.dasilva@gmail.com>
 Dan Kennedy <daniel@firstcs.co.uk>
+Nick Dhupia<nick.dhupia@gmail.com>
+Yasuharu Goto <matope.ono@gmail.com>
+Jeremy Schlatter <jeremy.schlatter@gmail.com>
+Matthias Kadenbach <matthias.kadenbach@gmail.com>
+Dean Elbaz <elbaz.dean@gmail.com>
+Mike Berman <evencode@gmail.com>
+

+ 8 - 2
CONTRIBUTING.md

@@ -12,20 +12,26 @@ The goal of the gocql project is to provide a stable and robust CQL driver for G
 
 The following is a check list of requirements that need to be satisfied in order for us to merge your patch:
 
+* You should raise a pull request to gocql/gocql on Github
 * The pull request has a title that clearly summarizes the purpose of the patch
 * The motivation behind the patch is clearly defined in the pull request summary
 * Your name and email have been added to the `AUTHORS` file (for copyright purposes)
 * The patch will merge cleanly
+* The test coverage does not fall below the critical threshold (currently 64%) 
 * The merge commit passes the regression test suite on Travis
 * `go fmt` has been applied to the submitted code
 * Functional changes (i.e. new features or changed behavior) are appropriately documented, either as a godoc or in the README (non-functional changes such as bug fixes may not require documentation)
 
+If there are any requirements that can't be reasonably satifisfied, please state this either on the pull request or as part of discussion on the mailing list. Where appropriate, the core team may apply discretion and make an exception to these requirements.
+
 ## Beyond The Checklist
 
 In addition to stating the hard requirements, there are a bunch of things that we consider when assessing changes to the library. These soft requirements are helpful pointers of how to get a patch landed quicker and with less fuss.
 
 ### General QA Approach
 
+The gocql team needs to consider the ongoing maintainability of the library at all times. Patches that look like they will introduce maintenance issues for the team will not be accepted.
+
 Your patch will get merged quicker if you have decent test cases that provide test coverage for the new behavior you wish to introduce.
 
 Unit tests are good, integration tests are even better. An example of a unit test is `marshal_test.go` - this tests the serialization code in isolation. `cassandra_test.go` is an integration test suite that is executed against every version of Cassandra that gocql supports as part of the CI process on Travis.
@@ -57,8 +63,8 @@ There are some long term plans for gocql that have to be taken into account when
 
 Currently, the officiallly supported versions of the Cassandra server include:
 
-* 2.0.6
-* 2.0.7
+* 1.2.18
+* 2.0.9
 
 Chances are that gocql will work with many other versions. If you would like us to support a particular version of Cassandra, please start a conversation about what version you'd like us to consider. We are more likely to accept a new version if you help out by extending the regression suite to cover the new version to be supported.
 

+ 11 - 0
README.md

@@ -45,6 +45,7 @@ Features
   * Optional automatic discovery of nodes
   * Optional support for periodic node discovery via system.peers
 * Iteration over paged results with configurable page size
+* Support for TLS/SSL
 * Optional frame compression (using snappy)
 * Automatic query preparation
 * Support for query tracing
@@ -149,6 +150,16 @@ There are various ways to bind application level data structures to CQL statemen
 * Building on top of the gocql driver, [cqlr](https://github.com/relops/cqlr) adds the ability to auto-bind a CQL iterator to a struct or to bind a struct to an INSERT statement.
 * Another external project that layers on top of gocql is [cqlc](http://relops.com/cqlc) which generates gocql compliant code from your Cassandra schema so that you can write type safe CQL statements in Go with a natural query syntax.
 
+Ecosphere
+---------
+
+The following community maintained tools are known to integrate with gocql:
+
+* [migrate](https://github.com/mattes/migrate) is a migration handling tool written in Go with Cassandra support.
+* [negronicql](https://github.com/mikebthun/negronicql) is gocql middleware for Negroni.
+* [cqlr](https://github.com/relops/cqlr) adds the ability to auto-bind a CQL iterator to a struct or to bind a struct to an INSERT statement.
+* [cqlc](http://relops.com/cqlc) which generates gocql compliant code from your Cassandra schema so that you can write type safe CQL statements in Go with a natural query syntax.
+
 Other Projects
 --------------
 

+ 150 - 11
cassandra_test.go

@@ -24,23 +24,35 @@ import (
 )
 
 var (
-	flagCluster  = flag.String("cluster", "127.0.0.1", "a comma-separated list of host:port tuples")
-	flagProto    = flag.Int("proto", 2, "protcol version")
-	flagCQL      = flag.String("cql", "3.0.0", "CQL version")
-	flagRF       = flag.Int("rf", 1, "replication factor for test keyspace")
-	clusterSize  = flag.Int("clusterSize", 1, "the expected size of the cluster")
-	flagRetry    = flag.Int("retries", 5, "number of times to retry queries")
-	flagAutoWait = flag.Duration("autowait", 1000*time.Millisecond, "time to wait for autodiscovery to fill the hosts poll")
-	clusterHosts []string
+	flagCluster    = flag.String("cluster", "127.0.0.1", "a comma-separated list of host:port tuples")
+	flagProto      = flag.Int("proto", 2, "protcol version")
+	flagCQL        = flag.String("cql", "3.0.0", "CQL version")
+	flagRF         = flag.Int("rf", 1, "replication factor for test keyspace")
+	clusterSize    = flag.Int("clusterSize", 1, "the expected size of the cluster")
+	flagRetry      = flag.Int("retries", 5, "number of times to retry queries")
+	flagAutoWait   = flag.Duration("autowait", 1000*time.Millisecond, "time to wait for autodiscovery to fill the hosts poll")
+	flagRunSslTest = flag.Bool("runssl", false, "Set to true to run ssl test")
+	clusterHosts   []string
 )
 
 func init() {
-
 	flag.Parse()
 	clusterHosts = strings.Split(*flagCluster, ",")
 	log.SetFlags(log.Lshortfile | log.LstdFlags)
 }
 
+func addSslOptions(cluster *ClusterConfig) *ClusterConfig {
+	if *flagRunSslTest {
+		cluster.SslOpts = &SslOptions{
+			CertPath:               "testdata/pki/gocql.crt",
+			KeyPath:                "testdata/pki/gocql.key",
+			CaPath:                 "testdata/pki/ca.crt",
+			EnableHostVerification: false,
+		}
+	}
+	return cluster
+}
+
 var initOnce sync.Once
 
 func createTable(s *Session, table string) error {
@@ -61,7 +73,7 @@ func createCluster() *ClusterConfig {
 	if *flagRetry > 0 {
 		cluster.RetryPolicy = &SimpleRetryPolicy{NumRetries: *flagRetry}
 	}
-
+	cluster = addSslOptions(cluster)
 	return cluster
 }
 
@@ -114,7 +126,7 @@ func TestRingDiscovery(t *testing.T) {
 		cluster.RetryPolicy = &SimpleRetryPolicy{NumRetries: *flagRetry}
 	}
 	cluster.DiscoverHosts = true
-
+	cluster = addSslOptions(cluster)
 	session, err := cluster.CreateSession()
 	if err != nil {
 		t.Errorf("got error connecting to the cluster %v", err)
@@ -136,6 +148,7 @@ func TestRingDiscovery(t *testing.T) {
 
 func TestEmptyHosts(t *testing.T) {
 	cluster := NewCluster()
+	cluster = addSslOptions(cluster)
 	if session, err := cluster.CreateSession(); err == nil {
 		session.Close()
 		t.Error("expected err, got nil")
@@ -162,6 +175,7 @@ func TestInvalidKeyspace(t *testing.T) {
 	cluster.ProtoVersion = *flagProto
 	cluster.CQLVersion = *flagCQL
 	cluster.Keyspace = "invalidKeyspace"
+	cluster = addSslOptions(cluster)
 	session, err := cluster.CreateSession()
 	if err != nil {
 		if err != ErrNoConnectionsStarted {
@@ -298,6 +312,48 @@ func TestCAS(t *testing.T) {
 	}
 }
 
+func TestMapScanCAS(t *testing.T) {
+	if *flagProto == 1 {
+		t.Skip("lightweight transactions not supported. Please use Cassandra >= 2.0")
+	}
+
+	session := createSession(t)
+	defer session.Close()
+
+	if err := createTable(session, `CREATE TABLE cas_table2 (
+			title         varchar,
+			revid   	  timeuuid,
+			last_modified timestamp,
+			deleted boolean,
+			PRIMARY KEY (title, revid)
+		)`); err != nil {
+		t.Fatal("create:", err)
+	}
+
+	title, revid, modified, deleted := "baz", TimeUUID(), time.Now(), false
+	mapCAS := map[string]interface{}{}
+
+	if applied, err := session.Query(`INSERT INTO cas_table2 (title, revid, last_modified, deleted)
+		VALUES (?, ?, ?, ?) IF NOT EXISTS`,
+		title, revid, modified, deleted).MapScanCAS(mapCAS); err != nil {
+		t.Fatal("insert:", err)
+	} else if !applied {
+		t.Fatal("insert should have been applied")
+	}
+
+	mapCAS = map[string]interface{}{}
+	if applied, err := session.Query(`INSERT INTO cas_table2 (title, revid, last_modified, deleted)
+		VALUES (?, ?, ?, ?) IF NOT EXISTS`,
+		title, revid, modified, deleted).MapScanCAS(mapCAS); err != nil {
+		t.Fatal("insert:", err)
+	} else if applied {
+		t.Fatal("insert should not have been applied")
+	} else if title != mapCAS["title"] || revid != mapCAS["revid"] || deleted != mapCAS["deleted"] {
+		t.Fatalf("expected %s/%v/%v/%v but got %s/%v/%v%v", title, revid, modified, false, mapCAS["title"], mapCAS["revid"], mapCAS["last_modified"], mapCAS["deleted"])
+	}
+
+}
+
 func TestBatch(t *testing.T) {
 	if *flagProto == 1 {
 		t.Skip("atomic batches not supported. Please use Cassandra >= 2.0")
@@ -430,6 +486,7 @@ func TestCreateSessionTimeout(t *testing.T) {
 		t.Fatal("no startup timeout")
 	}()
 	c := NewCluster("127.0.0.1:1")
+	c = addSslOptions(c)
 	_, err := c.CreateSession()
 
 	if err == nil {
@@ -440,6 +497,64 @@ func TestCreateSessionTimeout(t *testing.T) {
 	}
 }
 
+type FullName struct {
+	FirstName string
+	LastName  string
+}
+
+func (n FullName) MarshalCQL(info *TypeInfo) ([]byte, error) {
+	return []byte(n.FirstName + " " + n.LastName), nil
+}
+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
+}
+
+func TestMapScanWithRefMap(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+	if err := createTable(session, `CREATE TABLE scan_map_ref_table (
+			testtext       text PRIMARY KEY,
+			testfullname   text,
+			testint        int,
+		)`); err != nil {
+		t.Fatal("create table:", err)
+	}
+	m := make(map[string]interface{})
+	m["testtext"] = "testtext"
+	m["testfullname"] = FullName{"John", "Doe"}
+	m["testint"] = 100
+
+	if err := session.Query(`INSERT INTO scan_map_ref_table (testtext, testfullname, testint) values (?,?,?)`, m["testtext"], m["testfullname"], m["testint"]).Exec(); err != nil {
+		t.Fatal("insert:", err)
+	}
+
+	var testText string
+	var testFullName FullName
+	ret := map[string]interface{}{
+		"testtext":     &testText,
+		"testfullname": &testFullName,
+		// testint is not set here.
+	}
+	iter := session.Query(`SELECT * FROM scan_map_ref_table`).Iter()
+	if ok := iter.MapScan(ret); !ok {
+		t.Fatal("select:", iter.Close())
+	} else {
+		if ret["testtext"] != "testtext" {
+			t.Fatal("returned testtext did not match")
+		}
+		f := ret["testfullname"].(FullName)
+		if f.FirstName != "John" || f.LastName != "Doe" {
+			t.Fatal("returned testfullname did not match")
+		}
+		if ret["testint"] != 100 {
+			t.Fatal("returned testinit did not match")
+		}
+	}
+
+}
+
 func TestSliceMap(t *testing.T) {
 	session := createSession(t)
 	defer session.Close()
@@ -1328,3 +1443,27 @@ func TestNilInQuery(t *testing.T) {
 		t.Fatalf("expected id to be 1, got %v", id)
 	}
 }
+
+// Don't initialize time.Time bind variable if cassandra timestamp column is empty
+func TestEmptyTimestamp(t *testing.T) {
+	session := createSession(t)
+	defer session.Close()
+
+	if err := createTable(session, "CREATE TABLE test_empty_timestamp (id int, time timestamp, num int, PRIMARY KEY (id))"); err != nil {
+		t.Fatalf("failed to create table with error '%v'", err)
+	}
+
+	if err := session.Query("INSERT INTO test_empty_timestamp (id, num) VALUES (?,?)", 1, 561).Exec(); err != nil {
+		t.Fatalf("failed to insert with err: %v", err)
+	}
+
+	var timeVal time.Time
+
+	if err := session.Query("SELECT time FROM test_empty_timestamp where id = ?", 1).Scan(&timeVal); err != nil {
+		t.Fatalf("failed to select with err: %v", err)
+	}
+
+	if !timeVal.IsZero() {
+		t.Errorf("time.Time bind variable should still be empty (was %s)", timeVal)
+	}
+}

+ 1 - 0
cluster.go

@@ -62,6 +62,7 @@ type ClusterConfig struct {
 	DiscoverHosts    bool          // If set, gocql will attempt to automatically discover other members of the Cassandra cluster (default: false)
 	MaxPreparedStmts int           // Sets the maximum cache size for prepared statements globally for gocql (default: 1000)
 	Discovery        DiscoveryConfig
+	SslOpts          *SslOptions
 }
 
 // NewCluster generates a new config for the default cluster implementation.

+ 38 - 0
compressor_test.go

@@ -0,0 +1,38 @@
+package gocql
+
+import (
+	"bytes"
+	"code.google.com/p/snappy-go/snappy"
+	"testing"
+)
+
+func TestSnappyCompressor(t *testing.T) {
+	c := SnappyCompressor{}
+	if c.Name() != "snappy" {
+		t.Fatalf("expected name to be 'snappy', got %v", c.Name())
+	}
+
+	str := "My Test String"
+	//Test Encoding
+	if expected, err := snappy.Encode(nil, []byte(str)); err != nil {
+		t.Fatalf("failed to encode '%v' with error %v", str, err)
+	} else if res, err := c.Encode([]byte(str)); err != nil {
+		t.Fatalf("failed to encode '%v' with error %v", str, err)
+	} else if bytes.Compare(expected, res) != 0 {
+		t.Fatal("failed to match the expected encoded value with the result encoded value.")
+	}
+
+	val, err := c.Encode([]byte(str))
+	if err != nil {
+		t.Fatalf("failed to encode '%v' with error '%v'", str, err)
+	}
+
+	//Test Decoding
+	if expected, err := snappy.Decode(nil, val); err != nil {
+		t.Fatalf("failed to decode '%v' with error %v", val, err)
+	} else if res, err := c.Decode(val); err != nil {
+		t.Fatalf("failed to decode '%v' with error %v", val, err)
+	} else if bytes.Compare(expected, res) != 0 {
+		t.Fatal("failed to match the expected decoded value with the result decoded value.")
+	}
+}

+ 48 - 2
conn.go

@@ -6,8 +6,11 @@ package gocql
 
 import (
 	"bufio"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"net"
 	"sync"
 	"sync/atomic"
@@ -44,6 +47,16 @@ func (p PasswordAuthenticator) Success(data []byte) error {
 	return nil
 }
 
+type SslOptions struct {
+	CertPath string
+	KeyPath  string
+	CaPath   string //optional depending on server config
+	// If you want to verify the hostname and server cert (like a wildcard for cass cluster) then you should turn this on
+	// This option is basically the inverse of InSecureSkipVerify
+	// See InSecureSkipVerify in http://golang.org/pkg/crypto/tls/ for more info
+	EnableHostVerification bool
+}
+
 type ConnConfig struct {
 	ProtoVersion  int
 	CQLVersion    string
@@ -52,6 +65,7 @@ type ConnConfig struct {
 	Compressor    Compressor
 	Authenticator Authenticator
 	Keepalive     time.Duration
+	SslOpts       *SslOptions
 }
 
 // Conn is a single connection to a Cassandra node. It can be used to execute
@@ -80,8 +94,36 @@ type Conn struct {
 // Connect establishes a connection to a Cassandra node.
 // You must also call the Serve method before you can execute any queries.
 func Connect(addr string, cfg ConnConfig, pool ConnectionPool) (*Conn, error) {
-	conn, err := net.DialTimeout("tcp", addr, cfg.Timeout)
-	if err != nil {
+	var (
+		err  error
+		conn net.Conn
+	)
+
+	if cfg.SslOpts != nil {
+		certPool := x509.NewCertPool()
+		//ca cert is optional
+		if cfg.SslOpts.CaPath != "" {
+			pem, err := ioutil.ReadFile(cfg.SslOpts.CaPath)
+			if err != nil {
+				return nil, err
+			}
+			if !certPool.AppendCertsFromPEM(pem) {
+				return nil, errors.New("Failed parsing or appending certs")
+			}
+		}
+		mycert, err := tls.LoadX509KeyPair(cfg.SslOpts.CertPath, cfg.SslOpts.KeyPath)
+		if err != nil {
+			return nil, err
+		}
+		config := tls.Config{
+			Certificates: []tls.Certificate{mycert},
+			RootCAs:      certPool,
+		}
+		config.InsecureSkipVerify = !cfg.SslOpts.EnableHostVerification
+		if conn, err = tls.Dial("tcp", addr, &config); err != nil {
+			return nil, err
+		}
+	} else if conn, err = net.DialTimeout("tcp", addr, cfg.Timeout); err != nil {
 		return nil, err
 	}
 
@@ -463,6 +505,10 @@ func (c *Conn) Address() string {
 	return c.addr
 }
 
+func (c *Conn) AvailableStreams() int {
+	return len(c.uniq)
+}
+
 func (c *Conn) UseKeyspace(keyspace string) error {
 	resp, err := c.exec(&queryFrame{Stmt: `USE "` + keyspace + `"`, Cons: Any}, nil)
 	if err != nil {

+ 51 - 0
conn_test.go

@@ -12,6 +12,9 @@ import (
 	"sync/atomic"
 	"testing"
 	"time"
+	"crypto/tls"
+	"crypto/x509"
+	"io/ioutil"
 )
 
 type TestServer struct {
@@ -36,6 +39,31 @@ func TestSimple(t *testing.T) {
 	}
 }
 
+func TestSSLSimple(t *testing.T) {
+	srv := NewSSLTestServer(t)
+	defer srv.Stop()
+
+	db, err := createTestSslCluster(srv.Address).CreateSession()
+	if err != nil {
+		t.Errorf("NewCluster: %v", err)
+	}
+
+	if err := db.Query("void").Exec(); err != nil {
+		t.Error(err)
+	}
+}
+
+func createTestSslCluster(hosts string) *ClusterConfig {
+	cluster := NewCluster(hosts)
+	cluster.SslOpts = &SslOptions{
+		CertPath:               "testdata/pki/gocql.crt",
+		KeyPath:                "testdata/pki/gocql.key",
+		CaPath:                 "testdata/pki/ca.crt",
+		EnableHostVerification: false,
+	}
+	return cluster
+}
+
 func TestClosed(t *testing.T) {
 	srv := NewTestServer(t)
 	defer srv.Stop()
@@ -211,6 +239,29 @@ func NewTestServer(t *testing.T) *TestServer {
 	return srv
 }
 
+func NewSSLTestServer(t *testing.T) *TestServer {
+	pem, err := ioutil.ReadFile("testdata/pki/ca.crt")
+	certPool := x509.NewCertPool()
+	if !certPool.AppendCertsFromPEM(pem) {
+		t.Errorf("Failed parsing or appending certs")
+	}
+	mycert, err := tls.LoadX509KeyPair("testdata/pki/cassandra.crt", "testdata/pki/cassandra.key")
+	if err != nil {
+		t.Errorf("could not load cert")
+	}
+	config := &tls.Config{
+		Certificates: []tls.Certificate{mycert},
+		RootCAs:      certPool,
+	}
+	listen, err := tls.Listen("tcp", "127.0.0.1:0", config)
+	if err != nil {
+		t.Fatal(err)
+	}
+	srv := &TestServer{Address: listen.Addr().String(), listen: listen, t: t}
+	go srv.serve()
+	return srv
+}
+
 func (srv *TestServer) serve() {
 	defer srv.listen.Close()
 	for {

+ 2 - 1
connectionpool.go

@@ -169,6 +169,7 @@ func (c *SimplePool) connect(addr string) error {
 		Compressor:    c.cfg.Compressor,
 		Authenticator: c.cfg.Authenticator,
 		Keepalive:     c.cfg.SocketKeepalive,
+		SslOpts:       c.cfg.SslOpts,
 	}
 
 	conn, err := Connect(addr, cfg, c)
@@ -262,7 +263,7 @@ func (c *SimplePool) fillPool() {
 		//Create connections for host synchronously to mitigate flooding the host.
 		go func(a string, conns int) {
 			for ; conns < c.cfg.NumConns; conns++ {
-				c.connect(addr)
+				c.connect(a)
 			}
 		}(addr, numConns)
 	}

+ 14 - 1
helpers.go

@@ -107,7 +107,14 @@ func getApacheCassandraType(class string) Type {
 
 func (r *RowData) rowMap(m map[string]interface{}) {
 	for i, column := range r.Columns {
-		m[column] = dereference(r.Values[i])
+		val := dereference(r.Values[i])
+		if valVal := reflect.ValueOf(val); valVal.Kind() == reflect.Slice {
+			valCopy := reflect.MakeSlice(valVal.Type(), valVal.Len(), valVal.Cap())
+			reflect.Copy(valCopy, valVal)
+			m[column] = valCopy.Interface()
+		} else {
+			m[column] = val
+		}
 	}
 }
 
@@ -160,6 +167,12 @@ func (iter *Iter) MapScan(m map[string]interface{}) bool {
 	// Not checking for the error because we just did
 	rowData, _ := iter.RowData()
 
+	for i, col := range rowData.Columns {
+		if dest, ok := m[col]; ok {
+			rowData.Values[i] = dest
+		}
+	}
+
 	if iter.Scan(rowData.Values...) {
 		rowData.rowMap(m)
 		return true

+ 8 - 3
integration.sh

@@ -11,7 +11,7 @@ function run_tests() {
 	sed -i '/#MAX_HEAP_SIZE/c\MAX_HEAP_SIZE="256M"' ~/.ccm/repository/$version/conf/cassandra-env.sh
 	sed -i '/#HEAP_NEWSIZE/c\HEAP_NEWSIZE="100M"' ~/.ccm/repository/$version/conf/cassandra-env.sh
 
-	ccm updateconf 'concurrent_reads: 2' 'concurrent_writes: 2' 'rpc_server_type: sync' 'rpc_min_threads: 2' 'rpc_max_threads: 2' 'write_request_timeout_in_ms: 5000' 'read_request_timeout_in_ms: 5000'
+	ccm updateconf 'client_encryption_options.enabled: true' 'client_encryption_options.keystore: testdata/pki/.keystore' 'client_encryption_options.keystore_password: cassandra' 'client_encryption_options.require_client_auth: true' 'client_encryption_options.truststore: testdata/pki/.truststore' 'client_encryption_options.truststore_password: cassandra' 'concurrent_reads: 2' 'concurrent_writes: 2' 'rpc_server_type: sync' 'rpc_min_threads: 2' 'rpc_max_threads: 2' 'write_request_timeout_in_ms: 5000' 'read_request_timeout_in_ms: 5000'
 	ccm start
 	ccm status
 
@@ -20,9 +20,14 @@ function run_tests() {
 		proto=1
 	fi
 
-	go test -v -proto=$proto -rf=3 -cluster=$(ccm liveset) -clusterSize=$clusterSize -autowait=2000ms ./...
+	go test -cover -v -runssl -proto=$proto -rf=3 -cluster=$(ccm liveset) -clusterSize=$clusterSize -autowait=2000ms ./... > results
 
+	cat results
+	cover=`cat results | grep coverage: | grep -o "[0-9]\{1,3\}" | head -n 1`
+	if [[ $cover -lt "64" ]]; then
+		echo "--- FAIL: expected coverage of at least 64 %, but coverage was $cover %"
+		exit 1
+	fi
 	ccm clear
 }
-
 run_tests $1

+ 4 - 1
marshal.go

@@ -752,7 +752,7 @@ func marshalTimestamp(info *TypeInfo, value interface{}) ([]byte, error) {
 	case int64:
 		return encBigInt(v), nil
 	case time.Time:
-		x := v.In(time.UTC).UnixNano() / int64(time.Millisecond)
+		x := v.UnixNano() / int64(1000000)
 		return encBigInt(x), nil
 	}
 	rv := reflect.ValueOf(value)
@@ -773,6 +773,9 @@ func unmarshalTimestamp(info *TypeInfo, data []byte, value interface{}) error {
 		*v = decBigInt(data)
 		return nil
 	case *time.Time:
+		if len(data) == 0 {
+			return nil
+		}
 		x := decBigInt(data)
 		sec := x / 1000
 		nsec := (x - sec*1000) * 1000000

+ 15 - 2
policies.go

@@ -8,18 +8,31 @@ package gocql
 //exposes the correct functions for the retry policy logic to evaluate correctly.
 type RetryableQuery interface {
 	Attempts() int
+	GetConsistency() Consistency
 }
 
 // RetryPolicy interace is used by gocql to determine if a query can be attempted
 // again after a retryable error has been received. The interface allows gocql
 // users to implement their own logic to determine if a query can be attempted
 // again.
-// See SimpleRetryPolicy as an example of implementing the RetryPolicy interface.
+//
+// See SimpleRetryPolicy as an example of implementing and using a RetryPolicy
+// interface.
 type RetryPolicy interface {
 	Attempt(RetryableQuery) bool
 }
 
-// SimpleRetryPolicy has simple logic for attempting a query a fixed number of times.
+/*
+SimpleRetryPolicy has simple logic for attempting a query a fixed number of times.
+
+See below for examples of usage:
+
+	//Assign to the cluster
+	cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
+
+	//Assign to a query
+ 	query.RetryPolicy(&gocql.SimpleRetryPolicy{NumRetries: 1})
+*/
 type SimpleRetryPolicy struct {
 	NumRetries int //Number of times to retry a query
 }

+ 47 - 11
session.go

@@ -243,6 +243,12 @@ func (q *Query) Consistency(c Consistency) *Query {
 	return q
 }
 
+// GetConsistency returns the currently configured consistency level for
+// the query.
+func (q *Query) GetConsistency() Consistency {
+	return q.cons
+}
+
 // Trace enables tracing of this query. Look at the documentation of the
 // Tracer interface to learn more about tracing.
 func (q *Query) Trace(trace Tracer) *Query {
@@ -322,11 +328,8 @@ func (q *Query) Iter() *Iter {
 // were selected, ErrNotFound is returned.
 func (q *Query) Scan(dest ...interface{}) error {
 	iter := q.Iter()
-	if iter.err != nil {
-		return iter.err
-	}
-	if len(iter.rows) == 0 {
-		return ErrNotFound
+	if err := iter.checkErrAndNotFound(); err != nil {
+		return err
 	}
 	iter.Scan(dest...)
 	return iter.Close()
@@ -334,15 +337,12 @@ func (q *Query) Scan(dest ...interface{}) error {
 
 // ScanCAS executes a lightweight transaction (i.e. an UPDATE or INSERT
 // statement containing an IF clause). If the transaction fails because
-// the existing values did not match, the previos values will be stored
+// the existing values did not match, the previous values will be stored
 // in dest.
 func (q *Query) ScanCAS(dest ...interface{}) (applied bool, err error) {
 	iter := q.Iter()
-	if iter.err != nil {
-		return false, iter.err
-	}
-	if len(iter.rows) == 0 {
-		return false, ErrNotFound
+	if err := iter.checkErrAndNotFound(); err != nil {
+		return false, err
 	}
 	if len(iter.Columns()) > 1 {
 		dest = append([]interface{}{&applied}, dest...)
@@ -353,6 +353,26 @@ func (q *Query) ScanCAS(dest ...interface{}) (applied bool, err error) {
 	return applied, iter.Close()
 }
 
+// MapScanCAS executes a lightweight transaction (i.e. an UPDATE or INSERT
+// statement containing an IF clause). If the transaction fails because
+// the existing values did not match, the previous values will be stored
+// in dest map.
+//
+// As for INSERT .. IF NOT EXISTS, previous values will be returned as if
+// SELECT * FROM. So using ScanCAS with INSERT is inherently prone to
+// column mismatching. MapScanCAS is added to capture them safely.
+func (q *Query) MapScanCAS(dest map[string]interface{}) (applied bool, err error) {
+	iter := q.Iter()
+	if err := iter.checkErrAndNotFound(); err != nil {
+		return false, err
+	}
+	iter.MapScan(dest)
+	applied = dest["[applied]"].(bool)
+	delete(dest, "[applied]")
+
+	return applied, iter.Close()
+}
+
 // Iter represents an iterator that can be used to iterate over all rows that
 // were returned by a query. The iterator might send additional queries to the
 // database during the iteration if paging was enabled.
@@ -415,6 +435,16 @@ func (iter *Iter) Close() error {
 	return iter.err
 }
 
+// checkErrAndNotFound handle error and NotFound in one method.
+func (iter *Iter) checkErrAndNotFound() error {
+	if iter.err != nil {
+		return iter.err
+	} else if len(iter.rows) == 0 {
+		return ErrNotFound
+	}
+	return nil
+}
+
 type nextIter struct {
 	qry  Query
 	pos  int
@@ -461,6 +491,12 @@ func (b *Batch) Latency() int64 {
 	return 0
 }
 
+// GetConsistency returns the currently configured consistency level for the batch
+// operation.
+func (b *Batch) GetConsistency() Consistency {
+	return b.Cons
+}
+
 // Query adds the query to the batch operation
 func (b *Batch) Query(stmt string, args ...interface{}) {
 	b.Entries = append(b.Entries, BatchEntry{Stmt: stmt, Args: args})

+ 236 - 0
session_test.go

@@ -0,0 +1,236 @@
+package gocql
+
+import (
+	"testing"
+)
+
+func TestSessionAPI(t *testing.T) {
+
+	cfg := ClusterConfig{}
+	pool := NewSimplePool(&cfg)
+
+	s := NewSession(pool, cfg)
+
+	s.SetConsistency(All)
+	if s.cons != All {
+		t.Fatalf("expected consistency 'All', got '%v'", s.cons)
+	}
+
+	s.SetPageSize(100)
+	if s.pageSize != 100 {
+		t.Fatalf("expected pageSize 100, got %v", s.pageSize)
+	}
+
+	s.SetPrefetch(0.75)
+	if s.prefetch != 0.75 {
+		t.Fatalf("expceted prefetch 0.75, got %v", s.prefetch)
+	}
+
+	trace := &traceWriter{}
+
+	s.SetTrace(trace)
+	if s.trace != trace {
+		t.Fatalf("expected traceWriter '%v',got '%v'", trace, s.trace)
+	}
+
+	qry := s.Query("test", 1)
+	if v, ok := qry.values[0].(int); !ok {
+		t.Fatalf("expected qry.values[0] to be an int, got %v", qry.values[0])
+	} else if v != 1 {
+		t.Fatalf("expceted qry.values[0] to be 1, got %v", v)
+	} else if qry.stmt != "test" {
+		t.Fatalf("expected qry.stmt to be 'test', got '%v'", qry.stmt)
+	}
+
+	boundQry := s.Bind("test", func(q *QueryInfo) ([]interface{}, error) {
+		return nil, nil
+	})
+	if boundQry.binding == nil {
+		t.Fatal("expected qry.binding to be defined, got nil")
+	} else if boundQry.stmt != "test" {
+		t.Fatalf("expected qry.stmt to be 'test', got '%v'", boundQry.stmt)
+	}
+
+	itr := s.executeQuery(qry)
+	if itr.err != ErrNoConnections {
+		t.Fatalf("expected itr.err to be '%v', got '%v'", ErrNoConnections, itr.err)
+	}
+
+	testBatch := s.NewBatch(LoggedBatch)
+	testBatch.Query("test")
+	err := s.ExecuteBatch(testBatch)
+
+	if err != ErrNoConnections {
+		t.Fatalf("expected session.ExecuteBatch to return '%v', got '%v'", ErrNoConnections, err)
+	}
+
+	s.Close()
+	if !s.Closed() {
+		t.Fatal("expected s.Closed() to be true, got false")
+	}
+	//Should just return cleanly
+	s.Close()
+
+	err = s.ExecuteBatch(testBatch)
+	if err != ErrSessionClosed {
+		t.Fatalf("expected session.ExecuteBatch to return '%v', got '%v'", ErrSessionClosed, err)
+	}
+}
+
+func TestQueryBasicAPI(t *testing.T) {
+	qry := &Query{}
+
+	if qry.Latency() != 0 {
+		t.Fatalf("expected Query.Latency() to return 0, got %v", qry.Latency())
+	}
+
+	qry.attempts = 2
+	qry.totalLatency = 4
+	if qry.Attempts() != 2 {
+		t.Fatalf("expected Query.Attempts() to return 2, got %v", qry.Attempts())
+	}
+	if qry.Latency() != 2 {
+		t.Fatalf("expected Query.Latency() to return 2, got %v", qry.Latency())
+	}
+
+	qry.Consistency(All)
+	if qry.GetConsistency() != All {
+		t.Fatalf("expected Query.GetConsistency to return 'All', got '%s'", qry.GetConsistency())
+	}
+
+	trace := &traceWriter{}
+	qry.Trace(trace)
+	if qry.trace != trace {
+		t.Fatalf("expected Query.Trace to be '%v', got '%v'", trace, qry.trace)
+	}
+
+	qry.PageSize(10)
+	if qry.pageSize != 10 {
+		t.Fatalf("expected Query.PageSize to be 10, got %v", qry.pageSize)
+	}
+
+	qry.Prefetch(0.75)
+	if qry.prefetch != 0.75 {
+		t.Fatalf("expected Query.Prefetch to be 0.75, got %v", qry.prefetch)
+	}
+
+	rt := &SimpleRetryPolicy{NumRetries: 3}
+	if qry.RetryPolicy(rt); qry.rt != rt {
+		t.Fatalf("expected Query.RetryPolicy to be '%v', got '%v'", rt, qry.rt)
+	}
+
+	qry.Bind(qry)
+	if qry.values[0] != qry {
+		t.Fatalf("expected Query.Values[0] to be '%v', got '%v'", qry, qry.values[0])
+	}
+}
+
+func TestQueryShouldPrepare(t *testing.T) {
+	toPrepare := []string{"select * ", "INSERT INTO", "update table", "delete from", "begin batch"}
+	cantPrepare := []string{"create table", "USE table", "LIST keyspaces", "alter table", "drop table", "grant user", "revoke user"}
+	q := &Query{}
+
+	for i := 0; i < len(toPrepare); i++ {
+		q.stmt = toPrepare[i]
+		if !q.shouldPrepare() {
+			t.Fatalf("expected Query.shouldPrepare to return true, got false for statement '%v'", toPrepare[i])
+		}
+	}
+
+	for i := 0; i < len(cantPrepare); i++ {
+		q.stmt = cantPrepare[i]
+		if q.shouldPrepare() {
+			t.Fatalf("expected Query.shouldPrepare to return false, got true for statement '%v'", cantPrepare[i])
+		}
+	}
+}
+
+func TestBatchBasicAPI(t *testing.T) {
+
+	cfg := ClusterConfig{}
+	cfg.RetryPolicy = &SimpleRetryPolicy{NumRetries: 2}
+	pool := NewSimplePool(&cfg)
+
+	s := NewSession(pool, cfg)
+	b := s.NewBatch(UnloggedBatch)
+	if b.Type != UnloggedBatch {
+		t.Fatalf("expceted batch.Type to be '%v', got '%v'", UnloggedBatch, b.Type)
+	} else if b.rt != cfg.RetryPolicy {
+		t.Fatalf("expceted batch.RetryPolicy to be '%v', got '%v'", cfg.RetryPolicy, b.rt)
+	}
+
+	b = NewBatch(LoggedBatch)
+	if b.Type != LoggedBatch {
+		t.Fatalf("expected batch.Type to be '%v', got '%v'", LoggedBatch, b.Type)
+	}
+
+	b.attempts = 1
+	if b.Attempts() != 1 {
+		t.Fatalf("expceted batch.Attempts() to return %v, got %v", 1, b.Attempts())
+	}
+
+	if b.Latency() != 0 {
+		t.Fatalf("expected batch.Latency() to be 0, got %v", b.Latency())
+	}
+
+	b.totalLatency = 4
+	if b.Latency() != 4 {
+		t.Fatalf("expected batch.Latency() to return %v, got %v", 4, b.Latency())
+	}
+
+	b.Cons = One
+	if b.GetConsistency() != One {
+		t.Fatalf("expected batch.GetConsistency() to return 'One', got '%s'", b.GetConsistency())
+	}
+
+	b.Query("test", 1)
+	if b.Entries[0].Stmt != "test" {
+		t.Fatalf("expected batch.Entries[0].Stmt to be 'test', got '%v'", b.Entries[0].Stmt)
+	} else if b.Entries[0].Args[0].(int) != 1 {
+		t.Fatalf("expected batch.Entries[0].Args[0] to be 1, got %v", b.Entries[0].Args[0])
+	}
+
+	b.Bind("test2", func(q *QueryInfo) ([]interface{}, error) {
+		return nil, nil
+	})
+
+	if b.Entries[1].Stmt != "test2" {
+		t.Fatalf("expected batch.Entries[1].Stmt to be 'test2', got '%v'", b.Entries[1].Stmt)
+	} else if b.Entries[1].binding == nil {
+		t.Fatal("expected batch.Entries[1].binding to be defined, got nil")
+	}
+	r := &SimpleRetryPolicy{NumRetries: 4}
+
+	b.RetryPolicy(r)
+	if b.rt != r {
+		t.Fatalf("expected batch.RetryPolicy to be '%v', got '%v'", r, b.rt)
+	}
+
+	if b.Size() != 2 {
+		t.Fatalf("expected batch.Size() to return 2, got %v", b.Size())
+	}
+
+}
+
+func TestConsistencyNames(t *testing.T) {
+	names := map[Consistency]string{
+		0:           "default",
+		Any:         "any",
+		One:         "one",
+		Two:         "two",
+		Three:       "three",
+		Quorum:      "quorum",
+		All:         "all",
+		LocalQuorum: "localquorum",
+		EachQuorum:  "eachquorum",
+		Serial:      "serial",
+		LocalSerial: "localserial",
+		LocalOne:    "localone",
+	}
+
+	for k, v := range names {
+		if k.String() != v {
+			t.Fatalf("expected '%v', got '%v'", v, k.String())
+		}
+	}
+}

BIN
testdata/pki/.keystore


BIN
testdata/pki/.truststore


+ 20 - 0
testdata/pki/ca.crt

@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIJAIKbAXgemwsjMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
+BAMTCWNhc3NhbmRyYTAeFw0xNDA5MTkyMTE4MTNaFw0yNDA5MTYyMTE4MTNaMBQx
+EjAQBgNVBAMTCWNhc3NhbmRyYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL5fX0l1WDNa+mO1krxw7k8lfUQn+Ec4L3Mqv6IstGoNdCPq4YRA+SXRD5YC
+k/UXrFBWh9Hbs849GiuTYMPdj9HDLYz40RaQjM9GbieS23iy3UStQ0tKhxaaG6FN
+6XBypXFKCTsanu0TkEoDGhAkSzAMcCAC3gkFBzMrZ5qt4HEzjY9rasZ2gthN+xop
+nq3t4dDkE8HGaiFJcFvqTor7xmrnAaPjrPzUpvOF/ObIC09omwg/KXdPRx4DKPon
+gCMKEE3ckebKnJvbsRX3WO8H5nTHBYZ6v1JxLZz5pqmV+P0NGxldCARM0gCQUBz5
+wjMJkD/3e1ETC+q6uwfnAG0hlD8CAwEAAaOBgzCBgDAdBgNVHQ4EFgQUjHzn0nYF
+iXEaI1vUWbRR4lwKXOgwRAYDVR0jBD0wO4AUjHzn0nYFiXEaI1vUWbRR4lwKXOih
+GKQWMBQxEjAQBgNVBAMTCWNhc3NhbmRyYYIJAIKbAXgemwsjMAwGA1UdEwQFMAMB
+Af8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBCYDdIhtf/Y12Et947
+am1B8TzSX+/iQ1V1J3JtvgD5F4fvNjfArat/I3D277WREUTAc76o16BCp2OBGqzO
+zf9MvZPkjkAUoyU0TtPUEHyqxq4gZxbWKugIZGYkmQ1hCvSIgA5UnjRL3dylMmZb
+Y33JJA2QY63FZwnhmWsM8FYZwh+8MzVCQx3mgXC/k/jS6OuYyIT/KjxQHHjyr5ZS
+zAAQln1IcZycLfh1w5MtCFahCIethFcVDnWUWYPcPGDGgMJW7WBpNZdHbLxYY8cI
+eCc3Hcrbdc/CG5CaLJeqUidBayjnlUIO/NNgglkJ1KhQzkM6bd+37e0AX1hLIqx7
+gIZR
+-----END CERTIFICATE-----

+ 30 - 0
testdata/pki/ca.key

@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,54C8072C0FF3B3A3
+
+27eijmHdgB+s3beNPmU0+iz+muxMD0BVvWkDzyec/uawMv/Cn4c3mYXOcsFxS3BL
++qLT9MEttOmjqhHSaVrDYOPKoJIMpn+bVeKiR08V89icO36shEPy1feGqanagKtw
+ecgzFDBTA8ZbqjAhftXlhTwxADebvNms/2aDh5Aw04vIcbo8nQ/8z1Wz8O7Firsn
+kaseSTMTC6lxc+pa2V1X6mN0/2UpDi55bZbx1Z/mQ3+1CsdHOx0p7m/KY2m3ysov
+XluaC0sqmzHkcwNgDhUs3Jh+apE33vXzLGU+W4BDOwrYJiL6KpspZW/mJj3OEx8B
+8xdAZU3a/ei8NUA/lDStGmcYX+dOysExwJ6GMrCBm9iufZiefDQCQ8yRqWnr6Zop
+lsFd+CqHNWYxfWDI1pSUBw3bsgIjevI0f0B7PxkFEF0DmIhCgB324/uqToRzGsOF
+4MSVg6cSK7Sjo/u3r8r75A3aUAcY8NbR3peiZfAPMsTiUcfp4DoU+MJTqkX5PyQq
+FNxHOJoARZqjjQ2IhZiUQWfIINHvZ8F9G2K7VaES8A0EATyUghqaRyeLbyI3IYdW
+pGZBzrpGtdFlk9AVetHDDlY+gQiurtYhxOsxvlxJJuTj8FV+A5NWSElfPele0OiR
+iprE3xkFSk3whHu5L1vnzamvdSlnBWOAE7pQD7kQA6NmcEw/tqnXK0dVdAw8RIFh
+4BKgv0sNrXzBgnzE8+bKLUf1a2Byc/YKuBrI7EpSZ9/VHYvOcgmOxNxMmRS6NYd1
+Ly+agQn0AyvsDmSlBZBp8GCzVp6JYBMDKSXyPVN8+wjK9OQM0PZdEdXouMwPCOVN
+oNSjhmMtfjOsnG2SZ9tRas3p0qFdfh/N/E6Q7QHG3WD3cUIEweFV9ji1FTSRUrIa
+shuKug8MUfNjvDJNMsdGyf6Hi/7Iik++42Rq3ZdTy0ZVkj5snv5yBN77pr2M/J4b
+M+dsXjyXPO4SDW3kP/e3RnLRlWmUv1PNdOmNDdjBBUTKgVZ3ur+4HmSY1iDvhlUF
+/hz2tz3/XUKQwYuv3KJVlBhLrniXeES36GK+JQadIszrjwb5N4q4p6xrIdIR7XgR
+TJCSL1NGPLeQyjK6byWLNPRcCGrvnxWs0k0ev6trMRJL1EjsIFDCJam9szhcXkZP
+iYl1d7ZMKPS3cAqCjdaFRSe65cZ+qI/cqxiv122orq/jkDY7ZSA9rWywY4YnYQ7A
+BqvcPzC/6K0bteXqmMQkIy/84aSnEts6ecb/4s5e5xXLhHe0dchG0HkasC/Gb+v/
+m9NOqACTerWvSD+Ecv9OvnBjP+GTlA1g7xTiRANLXsTJuiJomtxewXcV6kGZEMmZ
+QWerGtPJGGUx36WRWrMiPeBfWZoIbjYGPmOO5mYNXMTjABGGWcFnKAqWUKsFihi9
+pC0OpZ7A0dtc9uSm0ZmsHUc3XENMHTeeEN+qgWxVKcMzRKEcnapu/0OcHrOUHDZf
+qPoG4EkNnG9kPMq3HzvFPx3qbQ017yl87vAkWy/Edo+ojfHoNghRBVGCw1zt/BMN
+eJbFFHop+rQ87omz8WIL4K+zVf91rJ0REVAJssQVDo16O5wrMo+f+c8v2GANQks5
+-----END RSA PRIVATE KEY-----

+ 83 - 0
testdata/pki/cassandra.crt

@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=cassandra
+        Validity
+            Not Before: Sep 19 21:18:48 2014 GMT
+            Not After : Sep 16 21:18:48 2024 GMT
+        Subject: CN=cassandra
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:e5:9c:20:9e:de:98:73:44:41:0d:37:4c:62:c3:
+                    9f:87:5f:9b:4f:aa:cf:f6:90:6e:a5:e0:89:88:7a:
+                    00:c6:bb:d7:80:87:69:2e:fa:f0:35:59:80:6e:82:
+                    25:c8:b3:6c:f6:a4:97:97:93:93:ea:f0:70:70:a4:
+                    e1:b7:aa:da:c1:99:66:9b:93:04:3a:ce:0b:83:07:
+                    06:22:3d:a6:db:7f:68:0f:49:80:bd:86:a8:bb:54:
+                    6d:38:5f:0f:b0:fa:1b:97:24:ae:cc:9d:37:98:7e:
+                    76:cc:e3:1b:45:1b:21:25:17:02:c0:1a:c5:fb:76:
+                    c3:8b:93:d7:c5:85:14:0a:5c:a4:12:e7:18:69:98:
+                    f5:76:cd:78:cd:99:5a:29:65:f1:68:20:97:d3:be:
+                    09:b3:68:1b:f2:a3:a2:9a:73:58:53:7e:ed:86:32:
+                    a3:5a:d5:46:03:f9:b3:b4:ec:63:71:ba:bb:fb:6f:
+                    f9:82:63:e4:55:47:7a:7a:e4:7b:17:6b:d7:e6:cf:
+                    3b:c9:ab:0c:30:15:c9:ed:c7:d6:fc:b6:72:b2:14:
+                    7d:c7:f3:7f:8a:f4:63:70:64:8e:0f:db:e8:3a:45:
+                    47:cd:b9:7b:ae:c8:31:c1:52:d1:3e:34:12:b7:73:
+                    e7:ba:89:86:9a:36:ed:a0:5a:69:d0:d4:e3:b6:16:
+                    85:af
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Subject Key Identifier: 
+                4A:D3:EC:63:07:E0:8F:1A:4E:F5:09:43:90:9F:7A:C5:31:D1:8F:D8
+            X509v3 Authority Key Identifier: 
+                keyid:8C:7C:E7:D2:76:05:89:71:1A:23:5B:D4:59:B4:51:E2:5C:0A:5C:E8
+                DirName:/CN=cassandra
+                serial:82:9B:01:78:1E:9B:0B:23
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 Key Usage: 
+                Digital Signature, Key Encipherment
+    Signature Algorithm: sha256WithRSAEncryption
+        ac:bc:80:82:2d:6d:f1:a0:46:eb:00:05:d2:25:9a:83:66:57:
+        40:51:6e:ff:db:e3:28:04:7b:16:63:74:ec:55:a0:c0:5b:47:
+        13:e1:5a:a5:6d:22:d0:e5:fe:c1:51:e8:f6:c6:9c:f9:be:b7:
+        be:82:14:e4:a0:b2:0b:9f:ee:68:bc:ac:17:0d:13:50:c6:9e:
+        52:91:8c:a0:98:db:4e:2d:f6:3d:6e:85:0a:bb:b9:dd:01:bf:
+        ad:52:dd:6e:e4:41:01:a5:93:58:dd:3f:cf:bf:15:e6:25:aa:
+        a0:4f:98:0d:75:8a:3f:5b:ba:67:37:f6:b1:0b:3f:21:34:97:
+        50:9a:85:97:2b:b6:05:41:9a:f3:cf:c4:92:23:06:ab:3e:87:
+        98:30:eb:cb:d3:83:ab:04:7d:5c:b9:f0:12:d1:43:b3:c5:7d:
+        33:9a:2e:2b:80:3a:66:be:f1:8c:08:37:7a:93:9c:9b:60:60:
+        53:71:16:70:86:df:ca:5f:a9:0b:e2:8b:3d:af:02:62:3b:61:
+        30:da:53:89:e3:d8:0b:88:04:9a:93:6a:f6:28:f8:dd:0d:8f:
+        0c:82:5b:c0:e5:f8:0d:ad:06:76:a7:3b:4b:ae:54:37:25:15:
+        f5:0c:67:0f:77:c5:c4:97:68:09:c3:02:a7:a0:46:10:1c:d1:
+        95:3a:4c:94
+-----BEGIN CERTIFICATE-----
+MIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwljYXNz
+YW5kcmEwHhcNMTQwOTE5MjExODQ4WhcNMjQwOTE2MjExODQ4WjAUMRIwEAYDVQQD
+EwljYXNzYW5kcmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlnCCe
+3phzREENN0xiw5+HX5tPqs/2kG6l4ImIegDGu9eAh2ku+vA1WYBugiXIs2z2pJeX
+k5Pq8HBwpOG3qtrBmWabkwQ6zguDBwYiPabbf2gPSYC9hqi7VG04Xw+w+huXJK7M
+nTeYfnbM4xtFGyElFwLAGsX7dsOLk9fFhRQKXKQS5xhpmPV2zXjNmVopZfFoIJfT
+vgmzaBvyo6Kac1hTfu2GMqNa1UYD+bO07GNxurv7b/mCY+RVR3p65HsXa9fmzzvJ
+qwwwFcntx9b8tnKyFH3H83+K9GNwZI4P2+g6RUfNuXuuyDHBUtE+NBK3c+e6iYaa
+Nu2gWmnQ1OO2FoWvAgMBAAGjgZUwgZIwCQYDVR0TBAIwADAdBgNVHQ4EFgQUStPs
+YwfgjxpO9QlDkJ96xTHRj9gwRAYDVR0jBD0wO4AUjHzn0nYFiXEaI1vUWbRR4lwK
+XOihGKQWMBQxEjAQBgNVBAMTCWNhc3NhbmRyYYIJAIKbAXgemwsjMBMGA1UdJQQM
+MAoGCCsGAQUFBwMBMAsGA1UdDwQEAwIFoDANBgkqhkiG9w0BAQsFAAOCAQEArLyA
+gi1t8aBG6wAF0iWag2ZXQFFu/9vjKAR7FmN07FWgwFtHE+FapW0i0OX+wVHo9sac
++b63voIU5KCyC5/uaLysFw0TUMaeUpGMoJjbTi32PW6FCru53QG/rVLdbuRBAaWT
+WN0/z78V5iWqoE+YDXWKP1u6Zzf2sQs/ITSXUJqFlyu2BUGa88/EkiMGqz6HmDDr
+y9ODqwR9XLnwEtFDs8V9M5ouK4A6Zr7xjAg3epOcm2BgU3EWcIbfyl+pC+KLPa8C
+YjthMNpTiePYC4gEmpNq9ij43Q2PDIJbwOX4Da0Gdqc7S65UNyUV9QxnD3fFxJdo
+CcMCp6BGEBzRlTpMlA==
+-----END CERTIFICATE-----

+ 27 - 0
testdata/pki/cassandra.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA5Zwgnt6Yc0RBDTdMYsOfh1+bT6rP9pBupeCJiHoAxrvXgIdp
+LvrwNVmAboIlyLNs9qSXl5OT6vBwcKTht6rawZlmm5MEOs4LgwcGIj2m239oD0mA
+vYaou1RtOF8PsPoblySuzJ03mH52zOMbRRshJRcCwBrF+3bDi5PXxYUUClykEucY
+aZj1ds14zZlaKWXxaCCX074Js2gb8qOimnNYU37thjKjWtVGA/mztOxjcbq7+2/5
+gmPkVUd6euR7F2vX5s87yasMMBXJ7cfW/LZyshR9x/N/ivRjcGSOD9voOkVHzbl7
+rsgxwVLRPjQSt3PnuomGmjbtoFpp0NTjthaFrwIDAQABAoIBAQChjdjl73kUoVGk
+GuSEGWCFv59nzqfEtJsl23bpr+4b5s8agCxiAe5Bm1fiaXBsZtKkN+rxm8TX6ZUz
+rM+ki3KgBW9Mx4SSW6d96dNHBFoC1wJAv1b2A2l1ZVHz9+7ydwgysHzNO1GC2nh8
+cM8fMJeBoU8uG6hx5n5wFvYa5CfVoUQh8+Oq0b+mVxEFKHmRPnWp9/jPzL5eBIdr
+ulbDt9S3dKJtouHgHBUNdkq/7Ex3QeHrUOahX6Y4eX1rzLnfLYY+0J4EA2PCKvgQ
+bfKCxVnnzL6ywviH8eS3ql6OvTfnbK9kCRw7WxX9CC50qKj3EmwC/51MPhWohWlq
+jw3qf38BAoGBAPPNyb3vUiyUqoErZxxIPFc2ob3vCjj06cvi7uKpOgrkdgC3iBhz
+aCFQ28r7LrxLAHaKvNvwp71Lc7WYo8WWkLI1DVn0dx+GiQYW3DbNcwZOS40ZQz5L
+zsjEcG4+cnZmuqGZBMNvQ+xUjkuucxvxPWKpEKM18GfDjgEkKbmDr+uNAoGBAPEY
+kVSfSZGtP0MoXIfRkrxBlhvCj9m+p60P37pyHrJBrlrwvxB7x3Oz8S70D6kV8s2g
+vVHgOS3VPj17VaQG8a3jBLKjzp5JLe34G8D1Ny8GqDc2wzOBtZySpJbifXuSUSPk
+cqF7yiu1cD/wRPlwyWxBX9ZbaxvxnIUwLLd3ygkrAoGBAKQaw42uVkCdvPr/DQOT
+d9I4erxO9zGJYQmU8bjtsZz9VJR89QWIQPIT7C3/zuB9F42zKxZcMXwQGo2EddAc
+3b6mSRtgmwJEW10W7BmTRrZa4y3RcFqxSjoHR6pdLEyYL01woy0taqnb7H/yp5aK
+VghfxkwllXEyxxXrko5FnpdNAoGBANeJLBunz2BxrnW+doJhZDnytFya4nk6TbKU
+12FaNoEL4PCh+12kGtogSwS74eg6m/citT2mI9gKpHrYcOaT4qmeo4uEj+nH6Eyv
+Gzi0wCHFZMr/pSC92/teyc+uKZo4Y1ugFq6w+Tt8GB7BERiisR+bji8XSTkRFemn
++MIIUFFDAoGAM8Va2Q5aTUkfg2mYlNLqT2tUAXVEhbmzjPA6laSo25PQEYWmX7vj
+hiU0DPCDJQ/PlPI23xYtDDLNk83Zbx+Oj29GO5pawJY9NvFI8n60EFXfLbP1nEdG
+j077QZNZOKfcgJirWi3+RrHSAK4tFftCe7rkV8ZmlMRBY3SDxzKOGcc=
+-----END RSA PRIVATE KEY-----

+ 83 - 0
testdata/pki/gocql.crt

@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=cassandra
+        Validity
+            Not Before: Sep 19 21:18:33 2014 GMT
+            Not After : Sep 16 21:18:33 2024 GMT
+        Subject: CN=gocql
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ae:e9:fa:9e:fd:e2:69:85:1d:08:0f:35:68:bc:
+                    63:7b:92:50:7f:73:50:fc:42:43:35:06:b3:5c:9e:
+                    27:1e:16:05:69:ec:88:d5:9c:4f:ef:e8:13:69:7a:
+                    b5:b3:7f:66:6d:14:00:2e:d6:af:5b:ff:2c:90:91:
+                    a6:11:07:72:5e:b0:37:c0:6d:ff:7b:76:2b:fe:de:
+                    4c:d2:8d:ce:43:3b:1a:c4:1d:de:b6:d8:26:08:25:
+                    89:59:a1:4b:94:a3:57:9e:19:46:28:6e:97:11:7c:
+                    e6:b7:41:96:8f:42:dd:66:da:86:d2:53:dd:d8:f5:
+                    20:cd:24:8b:0f:ab:df:c4:10:b2:64:20:1d:e0:0f:
+                    f4:2d:f6:ca:94:be:83:ac:3e:a8:4a:77:b6:08:97:
+                    3a:7e:7b:e0:3e:ab:68:cf:ee:f6:a1:8e:bf:ec:be:
+                    06:d1:ad:6c:ed:4f:35:d1:04:97:08:33:b1:65:5b:
+                    61:32:8d:4b:f0:30:35:4b:8b:6b:06:f2:1a:72:8c:
+                    69:bd:f3:b2:c4:a4:a4:70:45:e3:67:a2:7a:9f:2e:
+                    cb:28:2d:9f:68:03:f1:c7:d9:4f:83:c9:3d:8c:34:
+                    04:0a:3b:13:87:92:e1:f7:e3:79:7e:ab:c0:25:b1:
+                    e5:38:09:44:3e:31:df:12:d4:dc:7b:0e:35:bf:ee:
+                    25:5f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Subject Key Identifier: 
+                9F:F1:B2:C4:82:34:D0:2F:FF:E9:7F:19:F1:3B:51:57:BF:E8:95:BB
+            X509v3 Authority Key Identifier: 
+                keyid:8C:7C:E7:D2:76:05:89:71:1A:23:5B:D4:59:B4:51:E2:5C:0A:5C:E8
+                DirName:/CN=cassandra
+                serial:82:9B:01:78:1E:9B:0B:23
+
+            X509v3 Extended Key Usage: 
+                TLS Web Client Authentication
+            X509v3 Key Usage: 
+                Digital Signature
+    Signature Algorithm: sha256WithRSAEncryption
+        12:aa:1b:a6:58:27:52:32:c9:46:19:32:d3:69:ae:95:ad:23:
+        55:ad:12:65:da:2c:4c:72:f3:29:bd:2b:5a:97:3b:b7:68:8b:
+        68:80:77:55:e6:32:81:f1:f5:20:54:ba:0e:2b:86:90:d8:44:
+        cf:f2:9f:ec:4d:39:67:4e:36:6c:9b:49:4a:80:e6:c1:ed:a4:
+        41:39:19:16:d2:88:df:17:0c:46:5a:b9:88:53:f5:67:19:f0:
+        1f:9a:51:40:1b:40:12:bc:57:db:de:dd:d3:f5:a8:93:68:30:
+        ac:ba:4e:ee:6b:af:f8:13:3d:11:1a:fa:90:93:d0:68:ce:77:
+        5f:85:8b:a4:95:2a:4c:25:7b:53:9c:44:43:b1:d9:fe:0c:83:
+        b8:19:2a:88:cc:d8:d1:d9:b3:04:eb:45:9b:30:5e:cb:61:e0:
+        e1:88:23:9c:b0:34:79:62:82:0d:f8:10:ed:96:bb:a0:fd:0d:
+        02:cb:c5:d3:47:1f:35:a7:e3:39:31:56:d5:b3:eb:2f:93:8f:
+        18:b4:b7:3c:00:03:a7:b4:1c:17:72:91:7e:b6:f6:36:17:3d:
+        f6:54:3b:87:84:d1:9b:43:d1:88:42:64:20:7a:e3:cc:f7:05:
+        98:0e:1c:51:da:20:b7:9b:49:88:e8:c6:e1:de:0d:f5:56:4f:
+        79:41:d0:7f
+-----BEGIN CERTIFICATE-----
+MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwljYXNz
+YW5kcmEwHhcNMTQwOTE5MjExODMzWhcNMjQwOTE2MjExODMzWjAQMQ4wDAYDVQQD
+EwVnb2NxbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7p+p794mmF
+HQgPNWi8Y3uSUH9zUPxCQzUGs1yeJx4WBWnsiNWcT+/oE2l6tbN/Zm0UAC7Wr1v/
+LJCRphEHcl6wN8Bt/3t2K/7eTNKNzkM7GsQd3rbYJggliVmhS5SjV54ZRihulxF8
+5rdBlo9C3WbahtJT3dj1IM0kiw+r38QQsmQgHeAP9C32ypS+g6w+qEp3tgiXOn57
+4D6raM/u9qGOv+y+BtGtbO1PNdEElwgzsWVbYTKNS/AwNUuLawbyGnKMab3zssSk
+pHBF42eiep8uyygtn2gD8cfZT4PJPYw0BAo7E4eS4ffjeX6rwCWx5TgJRD4x3xLU
+3HsONb/uJV8CAwEAAaOBlTCBkjAJBgNVHRMEAjAAMB0GA1UdDgQWBBSf8bLEgjTQ
+L//pfxnxO1FXv+iVuzBEBgNVHSMEPTA7gBSMfOfSdgWJcRojW9RZtFHiXApc6KEY
+pBYwFDESMBAGA1UEAxMJY2Fzc2FuZHJhggkAgpsBeB6bCyMwEwYDVR0lBAwwCgYI
+KwYBBQUHAwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBAQASqhumWCdS
+MslGGTLTaa6VrSNVrRJl2ixMcvMpvStalzu3aItogHdV5jKB8fUgVLoOK4aQ2ETP
+8p/sTTlnTjZsm0lKgObB7aRBORkW0ojfFwxGWrmIU/VnGfAfmlFAG0ASvFfb3t3T
+9aiTaDCsuk7ua6/4Ez0RGvqQk9BozndfhYuklSpMJXtTnERDsdn+DIO4GSqIzNjR
+2bME60WbMF7LYeDhiCOcsDR5YoIN+BDtlrug/Q0Cy8XTRx81p+M5MVbVs+svk48Y
+tLc8AAOntBwXcpF+tvY2Fz32VDuHhNGbQ9GIQmQgeuPM9wWYDhxR2iC3m0mI6Mbh
+3g31Vk95QdB/
+-----END CERTIFICATE-----

+ 27 - 0
testdata/pki/gocql.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArun6nv3iaYUdCA81aLxje5JQf3NQ/EJDNQazXJ4nHhYFaeyI
+1ZxP7+gTaXq1s39mbRQALtavW/8skJGmEQdyXrA3wG3/e3Yr/t5M0o3OQzsaxB3e
+ttgmCCWJWaFLlKNXnhlGKG6XEXzmt0GWj0LdZtqG0lPd2PUgzSSLD6vfxBCyZCAd
+4A/0LfbKlL6DrD6oSne2CJc6fnvgPqtoz+72oY6/7L4G0a1s7U810QSXCDOxZVth
+Mo1L8DA1S4trBvIacoxpvfOyxKSkcEXjZ6J6ny7LKC2faAPxx9lPg8k9jDQECjsT
+h5Lh9+N5fqvAJbHlOAlEPjHfEtTcew41v+4lXwIDAQABAoIBAQCCP9XSwzfwX6Fo
+uPqKjY5/HEs5PQPXdPha6ixyEYsLilZptCuI9adI/MZHy4q2qW36V+Ry/IcEuJXU
+6cCB+cue2xYJA2A17Z+BYMRQHiy0P7UEyUFpYrefZWRMDCIeAyxhnGxz+zYfXaTo
+Xbzh3WbFCoFO6gjPYGoWmNm8x74PXyunNaMa/gWFECX5MMBXoOk5xSFGbHzI2Cds
+iT7sdCQJVbBs7yidYwNqPWQuOwrskFinPIFSc7bZ0Sx9wO3XTIrQFCE94v/AN6yR
+9Q37ida54g5tgtoeg/5EGsUM++i4wqJVoT3tWUHv1jBozO4Lm65uWR/1HcrusVnr
+x0TM9SaBAoGBAOMeaZdUrCJXnIiSoqCGDvZmylTAeOo6n2RAiviOYxVB4GP/SSjh
+8VeddFhYT1GCmZ+YjIXnRWK+dSqVukzCuf5xW5mWY7PDNGZe2P6O78lXnY4cb8Nc
+Uo9/S2aPnNmNHL2TYVBYUiZj+t2azIQEFvRth4Vu/AHRUG41/USxpwm/AoGBAMUo
+GX0xgSFAVpHnTLdzWrHNRrzHgYN8ywPKFgNOASvdgW0BFoqXEvVGc1Ak6uW82m1/
+L9ChOzWjCY7CoT+LPmdUVyGT9/UAPtWeLfo8Owl4tG91jQjePmJFvLoXErryCFRt
+SOOvCsTTTq2gN3PREHxY3dj2kJqaCBLCEzx3cYxhAoGBAIUxdrc6/t/9BV3KsPj2
+5Zt3WL0vSzoCOyut9lIiHtV+lrvOIPeK2eCKBIsy7wFcV/+SlQaKRNTN4SSiPml5
+4V3o2NFPsxTfK8HFafiPluw7J7kJ0Dl/0SM6gduZ6WBkMzCyV+WohjTheWOwvrPF
+OjkKaunD1qKyQDsCCo/Yp589AoGAdKgnfNZf68bf8nEECcBtt6sY4fbCgYTDszhO
+EiKDuurT/CWaquJ9SzgmXxOZEdrO+9838aCVIkWYECrFso23nPhgnfOp0gQVKdzw
+o5Ij9JTBXvoVO1wVWZyd8RZZ9Nflad9IM8CNBK1rbnzQkuzvbkQ+8HPkWDYv9Ll1
+HGAohcECgYBQeirIumumj1B17WD/KmNe0U0qCHHp+oSW4W2r7pjlEVZzeQmggX4O
+anbEngyQaZKeUiUOj9snBDmzLv7S+j5p7Us4d1fbp70sCKuK6tcAnROU8gK8IGiI
+I01ypD8Z1Mb556qek56eRWlr71sy6wI1lbQa856cUBvePajUOKsKsw==
+-----END RSA PRIVATE KEY-----

+ 53 - 0
topology_test.go

@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The gocql Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocql
+
+import (
+	"testing"
+)
+
+// fakeNode is used as a simple structure to test the RoundRobin API
+type fakeNode struct {
+	conn   *Conn
+	closed bool
+}
+
+// Pick is needed to satisfy the Node interface
+func (n *fakeNode) Pick(qry *Query) *Conn {
+	if n.conn == nil {
+		n.conn = &Conn{}
+	}
+	return n.conn
+}
+
+//Close is needed to satisfy the Node interface
+func (n *fakeNode) Close() {
+	n.closed = true
+}
+
+//TestRoundRobinAPI tests the exported methods of the RoundRobin struct
+//to make sure the API behaves accordingly.
+func TestRoundRobinAPI(t *testing.T) {
+	node := &fakeNode{}
+	rr := NewRoundRobin()
+	rr.AddNode(node)
+
+	if rr.Size() != 1 {
+		t.Fatalf("expected size to be 1, got %v", rr.Size())
+	}
+
+	if c := rr.Pick(nil); c != node.conn {
+		t.Fatalf("expected conn %v, got %v", node.conn, c)
+	}
+
+	rr.Close()
+	if rr.pool != nil {
+		t.Fatalf("expected rr.pool to be nil, got %v", rr.pool)
+	}
+
+	if !node.closed {
+		t.Fatal("expected node.closed to be true, got false")
+	}
+}

+ 42 - 2
uuid_test.go

@@ -6,6 +6,7 @@ package gocql
 
 import (
 	"bytes"
+	"strings"
 	"testing"
 	"time"
 )
@@ -24,6 +25,7 @@ var testsUUID = []struct {
 	version int
 }{
 	{"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4},
+	{"B4F00409-CEF8-4822-802C-DEB20704C365", VariantIETF, 4}, //Use capital letters
 	{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
 	{"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
 	{"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1},
@@ -45,7 +47,7 @@ func TestPredefinedUUID(t *testing.T) {
 			continue
 		}
 
-		if str := uuid.String(); str != testsUUID[i].input {
+		if str := uuid.String(); str != strings.ToLower(testsUUID[i].input) {
 			t.Errorf("String #%d: expected %q got %q", i, testsUUID[i].input, str)
 			continue
 		}
@@ -64,7 +66,7 @@ func TestPredefinedUUID(t *testing.T) {
 		if err != nil {
 			t.Errorf("MarshalJSON #%d: %v", i, err)
 		}
-		expectedJson := `"` + testsUUID[i].input + `"`
+		expectedJson := `"` + strings.ToLower(testsUUID[i].input) + `"`
 		if string(json) != expectedJson {
 			t.Errorf("MarshalJSON #%d: expected %v got %v", i, expectedJson, string(json))
 		}
@@ -80,6 +82,25 @@ func TestPredefinedUUID(t *testing.T) {
 	}
 }
 
+func TestInvalidUUIDCharacter(t *testing.T) {
+	_, err := ParseUUID("z4f00409-cef8-4822-802c-deb20704c365")
+	if err == nil || !strings.Contains(err.Error(), "invalid UUID") {
+		t.Fatalf("expected invalid UUID error, got '%v' ", err)
+	}
+}
+
+func TestInvalidUUIDLength(t *testing.T) {
+	_, err := ParseUUID("4f00")
+	if err == nil || !strings.Contains(err.Error(), "invalid UUID") {
+		t.Fatalf("expected invalid UUID error, got '%v' ", err)
+	}
+
+	_, err = UUIDFromBytes(TimeUUID().Bytes()[:15])
+	if err == nil || err.Error() != "UUIDs must be exactly 16 bytes long" {
+		t.Fatalf("expected error '%v', got '%v'", "UUIDs must be exactly 16 bytes long", err)
+	}
+}
+
 func TestRandomUUID(t *testing.T) {
 	for i := 0; i < 20; i++ {
 		uuid, err := RandomUUID()
@@ -95,6 +116,25 @@ func TestRandomUUID(t *testing.T) {
 	}
 }
 
+func TestRandomUUIDInvalidAPICalls(t *testing.T) {
+	uuid, err := RandomUUID()
+	if err != nil {
+		t.Fatalf("unexpected error %v", err)
+	}
+
+	if node := uuid.Node(); node != nil {
+		t.Fatalf("expected nil, got %v", node)
+	}
+
+	if stamp := uuid.Timestamp(); stamp != 0 {
+		t.Fatalf("expceted 0, got %v", stamp)
+	}
+	zeroT := time.Time{}
+	if to := uuid.Time(); to != zeroT {
+		t.Fatalf("expected %v, got %v", zeroT, to)
+	}
+}
+
 func TestUUIDFromTime(t *testing.T) {
 	date := time.Date(1982, 5, 5, 12, 34, 56, 0, time.UTC)
 	uuid := UUIDFromTime(date)