|
|
@@ -31,7 +31,6 @@ import (
|
|
|
// and automatically sets a default consinstency level on all operations
|
|
|
// that do not have a consistency level set.
|
|
|
type Session struct {
|
|
|
- pool *policyConnPool
|
|
|
cons Consistency
|
|
|
pageSize int
|
|
|
prefetch float64
|
|
|
@@ -39,11 +38,17 @@ type Session struct {
|
|
|
schemaDescriber *schemaDescriber
|
|
|
trace Tracer
|
|
|
hostSource *ringDescriber
|
|
|
- ring ring
|
|
|
stmtsLRU *preparedLRU
|
|
|
|
|
|
connCfg *ConnConfig
|
|
|
|
|
|
+ executor *queryExecutor
|
|
|
+ pool *policyConnPool
|
|
|
+ policy HostSelectionPolicy
|
|
|
+
|
|
|
+ ring ring
|
|
|
+ metadata clusterMetadata
|
|
|
+
|
|
|
mu sync.RWMutex
|
|
|
|
|
|
control *controlConn
|
|
|
@@ -116,7 +121,17 @@ func NewSession(cfg ClusterConfig) (*Session, error) {
|
|
|
closeChan: make(chan bool),
|
|
|
}
|
|
|
|
|
|
- s.pool = cfg.PoolConfig.buildPool(s)
|
|
|
+ pool := cfg.PoolConfig.buildPool(s)
|
|
|
+ if cfg.PoolConfig.HostSelectionPolicy == nil {
|
|
|
+ cfg.PoolConfig.HostSelectionPolicy = RoundRobinHostPolicy()
|
|
|
+ }
|
|
|
+
|
|
|
+ s.pool = pool
|
|
|
+ s.policy = cfg.PoolConfig.HostSelectionPolicy
|
|
|
+ s.executor = &queryExecutor{
|
|
|
+ pool: pool,
|
|
|
+ policy: cfg.PoolConfig.HostSelectionPolicy,
|
|
|
+ }
|
|
|
|
|
|
var hosts []*HostInfo
|
|
|
|
|
|
@@ -284,44 +299,17 @@ func (s *Session) Closed() bool {
|
|
|
}
|
|
|
|
|
|
func (s *Session) executeQuery(qry *Query) *Iter {
|
|
|
-
|
|
|
// fail fast
|
|
|
if s.Closed() {
|
|
|
return &Iter{err: ErrSessionClosed}
|
|
|
}
|
|
|
|
|
|
- var iter *Iter
|
|
|
- qry.attempts = 0
|
|
|
- qry.totalLatency = 0
|
|
|
- for {
|
|
|
- host, conn := s.pool.Pick(qry)
|
|
|
-
|
|
|
- qry.attempts++
|
|
|
- //Assign the error unavailable to the iterator
|
|
|
- if conn == nil {
|
|
|
- if qry.rt == nil || !qry.rt.Attempt(qry) {
|
|
|
- iter = &Iter{err: ErrNoConnections}
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- t := time.Now()
|
|
|
- iter = conn.executeQuery(qry)
|
|
|
- qry.totalLatency += time.Now().Sub(t).Nanoseconds()
|
|
|
-
|
|
|
- // Update host
|
|
|
- host.Mark(iter.err)
|
|
|
-
|
|
|
- // Exit for loop if the query was successful
|
|
|
- if iter.err == nil {
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- if qry.rt == nil || !qry.rt.Attempt(qry) {
|
|
|
- break
|
|
|
- }
|
|
|
+ iter, err := s.executor.executeQuery(qry)
|
|
|
+ if err != nil {
|
|
|
+ return &Iter{err: err}
|
|
|
+ }
|
|
|
+ if iter == nil {
|
|
|
+ panic("nil iter")
|
|
|
}
|
|
|
|
|
|
return iter
|
|
|
@@ -348,6 +336,28 @@ func (s *Session) KeyspaceMetadata(keyspace string) (*KeyspaceMetadata, error) {
|
|
|
return s.schemaDescriber.getSchema(keyspace)
|
|
|
}
|
|
|
|
|
|
+func (s *Session) getConn() *Conn {
|
|
|
+ hosts := s.ring.allHosts()
|
|
|
+ var conn *Conn
|
|
|
+ for _, host := range hosts {
|
|
|
+ if !host.IsUp() {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ pool, ok := s.pool.getPool(host.Peer())
|
|
|
+ if !ok {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ conn = pool.Pick()
|
|
|
+ if conn != nil {
|
|
|
+ return conn
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
// returns routing key indexes and type info
|
|
|
func (s *Session) routingKeyInfo(stmt string) (*routingKeyInfo, error) {
|
|
|
s.routingKeyInfoCache.mu.Lock()
|
|
|
@@ -384,26 +394,23 @@ func (s *Session) routingKeyInfo(stmt string) (*routingKeyInfo, error) {
|
|
|
partitionKey []*ColumnMetadata
|
|
|
)
|
|
|
|
|
|
- // get the query info for the statement
|
|
|
- host, conn := s.pool.Pick(nil)
|
|
|
+ conn := s.getConn()
|
|
|
if conn == nil {
|
|
|
- // no connections
|
|
|
- inflight.err = ErrNoConnections
|
|
|
- // don't cache this error
|
|
|
- s.routingKeyInfoCache.Remove(stmt)
|
|
|
+ // TODO: better error?
|
|
|
+ inflight.err = errors.New("gocql: unable to fetch preapred info: no connection avilable")
|
|
|
return nil, inflight.err
|
|
|
}
|
|
|
|
|
|
+ // get the query info for the statement
|
|
|
info, inflight.err = conn.prepareStatement(stmt, nil)
|
|
|
if inflight.err != nil {
|
|
|
// don't cache this error
|
|
|
s.routingKeyInfoCache.Remove(stmt)
|
|
|
- host.Mark(inflight.err)
|
|
|
return nil, inflight.err
|
|
|
}
|
|
|
|
|
|
- // Mark host as OK
|
|
|
- host.Mark(nil)
|
|
|
+ // TODO: it would be nice to mark hosts here but as we are not using the policies
|
|
|
+ // to fetch hosts we cant
|
|
|
|
|
|
if info.request.colCount == 0 {
|
|
|
// no arguments, no routing key, and no error
|
|
|
@@ -455,6 +462,7 @@ func (s *Session) routingKeyInfo(stmt string) (*routingKeyInfo, error) {
|
|
|
indexes: make([]int, size),
|
|
|
types: make([]TypeInfo, size),
|
|
|
}
|
|
|
+
|
|
|
for keyIndex, keyColumn := range partitionKey {
|
|
|
// set an indicator for checking if the mapping is missing
|
|
|
routingKeyInfo.indexes[keyIndex] = -1
|
|
|
@@ -482,6 +490,10 @@ func (s *Session) routingKeyInfo(stmt string) (*routingKeyInfo, error) {
|
|
|
return routingKeyInfo, nil
|
|
|
}
|
|
|
|
|
|
+func (b *Batch) execute(conn *Conn) *Iter {
|
|
|
+ return conn.executeBatch(b)
|
|
|
+}
|
|
|
+
|
|
|
func (s *Session) executeBatch(batch *Batch) *Iter {
|
|
|
// fail fast
|
|
|
if s.Closed() {
|
|
|
@@ -495,45 +507,9 @@ func (s *Session) executeBatch(batch *Batch) *Iter {
|
|
|
return &Iter{err: ErrTooManyStmts}
|
|
|
}
|
|
|
|
|
|
- var iter *Iter
|
|
|
- batch.attempts = 0
|
|
|
- batch.totalLatency = 0
|
|
|
- for {
|
|
|
- host, conn := s.pool.Pick(nil)
|
|
|
-
|
|
|
- batch.attempts++
|
|
|
- if conn == nil {
|
|
|
- if batch.rt == nil || !batch.rt.Attempt(batch) {
|
|
|
- // Assign the error unavailable and break loop
|
|
|
- iter = &Iter{err: ErrNoConnections}
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- if conn == nil {
|
|
|
- iter = &Iter{err: ErrNoConnections}
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- t := time.Now()
|
|
|
-
|
|
|
- iter = conn.executeBatch(batch)
|
|
|
-
|
|
|
- batch.totalLatency += time.Since(t).Nanoseconds()
|
|
|
- // Exit loop if operation executed correctly
|
|
|
- if iter.err == nil {
|
|
|
- host.Mark(nil)
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- // Mark host with error if returned from Close
|
|
|
- host.Mark(iter.Close())
|
|
|
-
|
|
|
- if batch.rt == nil || !batch.rt.Attempt(batch) {
|
|
|
- break
|
|
|
- }
|
|
|
+ iter, err := s.executor.executeQuery(batch)
|
|
|
+ if err != nil {
|
|
|
+ return &Iter{err: err}
|
|
|
}
|
|
|
|
|
|
return iter
|
|
|
@@ -680,6 +656,20 @@ func (q *Query) RoutingKey(routingKey []byte) *Query {
|
|
|
return q
|
|
|
}
|
|
|
|
|
|
+func (q *Query) execute(conn *Conn) *Iter {
|
|
|
+ return conn.executeQuery(q)
|
|
|
+}
|
|
|
+
|
|
|
+func (q *Query) attempt(d time.Duration) {
|
|
|
+ q.attempts++
|
|
|
+ q.totalLatency += d.Nanoseconds()
|
|
|
+ // TODO: track latencies per host and things as well instead of just total
|
|
|
+}
|
|
|
+
|
|
|
+func (q *Query) retryPolicy() RetryPolicy {
|
|
|
+ return q.rt
|
|
|
+}
|
|
|
+
|
|
|
// GetRoutingKey gets the routing key to use for routing this query. If
|
|
|
// a routing key has not been explicitly set, then the routing key will
|
|
|
// be constructed if possible using the keyspace's schema and the query
|
|
|
@@ -689,6 +679,11 @@ func (q *Query) RoutingKey(routingKey []byte) *Query {
|
|
|
func (q *Query) GetRoutingKey() ([]byte, error) {
|
|
|
if q.routingKey != nil {
|
|
|
return q.routingKey, nil
|
|
|
+ } else if q.binding != nil && len(q.values) == 0 {
|
|
|
+ // If this query was created using session.Bind we wont have the query
|
|
|
+ // values yet, so we have to pass down to the next policy.
|
|
|
+ // TODO: Remove this and handle this case
|
|
|
+ return nil, nil
|
|
|
}
|
|
|
|
|
|
// try to determine the routing key
|
|
|
@@ -816,8 +811,7 @@ func (q *Query) NoSkipMetadata() *Query {
|
|
|
|
|
|
// Exec executes the query without returning any rows.
|
|
|
func (q *Query) Exec() error {
|
|
|
- iter := q.Iter()
|
|
|
- return iter.Close()
|
|
|
+ return q.Iter().Close()
|
|
|
}
|
|
|
|
|
|
func isUseStatement(stmt string) bool {
|
|
|
@@ -1107,6 +1101,10 @@ func (b *Batch) Bind(stmt string, bind func(q *QueryInfo) ([]interface{}, error)
|
|
|
b.Entries = append(b.Entries, BatchEntry{Stmt: stmt, binding: bind})
|
|
|
}
|
|
|
|
|
|
+func (b *Batch) retryPolicy() RetryPolicy {
|
|
|
+ return b.rt
|
|
|
+}
|
|
|
+
|
|
|
// RetryPolicy sets the retry policy to use when executing the batch operation
|
|
|
func (b *Batch) RetryPolicy(r RetryPolicy) *Batch {
|
|
|
b.rt = r
|
|
|
@@ -1141,6 +1139,17 @@ func (b *Batch) DefaultTimestamp(enable bool) *Batch {
|
|
|
return b
|
|
|
}
|
|
|
|
|
|
+func (b *Batch) attempt(d time.Duration) {
|
|
|
+ b.attempts++
|
|
|
+ b.totalLatency += d.Nanoseconds()
|
|
|
+ // TODO: track latencies per host and things as well instead of just total
|
|
|
+}
|
|
|
+
|
|
|
+func (b *Batch) GetRoutingKey() ([]byte, error) {
|
|
|
+ // TODO: use the first statement in the batch as the routing key?
|
|
|
+ return nil, nil
|
|
|
+}
|
|
|
+
|
|
|
type BatchType byte
|
|
|
|
|
|
const (
|
|
|
@@ -1285,7 +1294,7 @@ var (
|
|
|
ErrTooManyStmts = errors.New("too many statements")
|
|
|
ErrUseStmt = errors.New("use statements aren't supported. Please see https://github.com/gocql/gocql for explaination.")
|
|
|
ErrSessionClosed = errors.New("session has been closed")
|
|
|
- ErrNoConnections = errors.New("no connections available")
|
|
|
+ ErrNoConnections = errors.New("qocql: no hosts available in the pool")
|
|
|
ErrNoKeyspace = errors.New("no keyspace provided")
|
|
|
ErrNoMetadata = errors.New("no metadata available")
|
|
|
)
|