This is an automated email from the ASF dual-hosted git repository.
worryg0d 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 94c308f1 Prevent synthetic CREATED events on
HostSelectionPolicy.KeyspaceChanged during session init
94c308f1 is described below
commit 94c308f18566b3409f5b50e772804d6fecf439f4
Author: Bohdan Siryk <[email protected]>
AuthorDate: Tue Jun 30 10:25:37 2026 +0300
Prevent synthetic CREATED events on HostSelectionPolicy.KeyspaceChanged
during session init
This patch fixes regression introduced in CASSGO-104 that caused the driver
to fire synthetic
CREATED events on HostSelectionPolicy.KeyspaceChanged for each existing
keyspace during session initialization.
Patch by Bohdan Siryk; reviewed by João Reis for CASSGO-132
---
CHANGELOG.md | 3 ++
cluster.go | 23 ++++++++++++++
cluster_test.go | 68 ++++++++++++++++++++++++++++++++++++++++
common_test.go | 4 +++
hostpool/hostpool.go | 6 ++++
integration_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++
metadata.go | 28 ++++++++++-------
policies.go | 47 ++++++++++++++++++++++++++++
schema_events_test.go | 5 ---
session.go | 32 +++++++++++--------
10 files changed, 273 insertions(+), 29 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b6f1045..071f2e93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,10 +13,13 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- Improve host_source locking and ring refresh concurrency (CASSGO-121)
- Add PreparedMetadata (Keyspace, Table) and IsPrepared fields to
ObservedQuery, and parallel PreparedMetadata / IsPrepared slices to
ObservedBatch, for statement-level observability without CQL parsing
(CASSGO-119)
- Query.Binding() method to override binding function for a query object.
+- MetadataRequiredPolicy interface to allow users to disable metadata if host
selection policy does not need it (CASSGO-132)
### Fixed
+
- Correct protocol negotiation with non-Cassandra servers (CASSGO-131)
- LZ4 test fails on arm64 (CASSGO-128)
+- Prevent synthetic CREATED events on HostSelectionPolicy.KeyspaceChanged
during session init (CASSGO-132)
## [2.1.2]
diff --git a/cluster.go b/cluster.go
index 0c687d20..eeb12b92 100644
--- a/cluster.go
+++ b/cluster.go
@@ -405,10 +405,27 @@ func (cfg *ClusterConfig) filterHost(host *HostInfo) bool
{
return !(cfg.HostFilter == nil || cfg.HostFilter.Accept(host))
}
+func (cfg *ClusterConfig) validate() error {
+ logger := cfg.newLogger()
+
+ if cfg.Metadata.CacheMode == Disabled {
+ mrp, ok :=
cfg.PoolConfig.HostSelectionPolicy.(MetadataRequiredPolicy)
+ if !ok {
+ logger.Warning(metadataCacheDisabledWarningMsg)
+ } else if mrp.MetadataRequired() {
+ return ErrMetadataCacheRequired
+ }
+ }
+
+ return nil
+}
+
// MetadataConfig configures driver's internal metadata caching and event
listening.
type MetadataConfig struct {
// CacheMode controls how the driver reads and caches schema metadata
from Cassandra system tables.
//
+ // It is required to be enabled for [TokenAwareHostPolicy] and custom
host selection policies.
+ //
// Also, it affects the behavior of schema change listeners.
//
// If CacheMode is [KeyspaceOnly], only [KeyspaceChangeListener] will
be notified,
@@ -466,3 +483,9 @@ var (
// Deprecated: Never used or returned by the driver.
ErrHostQueryFailed = errors.New("unable to populate Hosts")
)
+
+const (
+ metadataCacheDisabledWarningMsg = "Metadata cache is disabled and the
host selection policy does not implement MetadataRequiredPolicy interface. " +
+ "This might result in unexpected behavior if your custom policy
requires keyspace metadata. " +
+ "To avoid this warning, please implement the
MetadataRequiredPolicy interface."
+)
diff --git a/cluster_test.go b/cluster_test.go
index 16d94ebe..61ee8113 100644
--- a/cluster_test.go
+++ b/cluster_test.go
@@ -30,6 +30,7 @@ package gocql
import (
"net"
"reflect"
+ "strings"
"testing"
"time"
)
@@ -83,3 +84,70 @@ func TestClusterConfig_translateAddressAndPort_Success(t
*testing.T) {
assertTrue(t, "translated address",
net.ParseIP("10.10.10.10").Equal(newAddr))
assertEqual(t, "translated port", 5432, newPort)
}
+
+type testHostSelectionPolicy struct {
+ HostSelectionPolicy
+}
+
+func TestClusterConfig_validate_HostSelectionPolicyRequiresMetadata(t
*testing.T) {
+ type testCase struct {
+ name string
+ cacheMode MetadataCacheMode
+ hostSelectionPolicy HostSelectionPolicy
+ expectedError error
+
+ // In case when policy doesn't implement MetadataRequiredPolicy
interface, and cache is disabled, we expect a warning.
+ expectsWarning bool
+ }
+
+ testCases := []testCase{
+ {
+ name: "Metadata cache is disabled and
policy does not require metadata",
+ cacheMode: Disabled,
+ hostSelectionPolicy: RoundRobinHostPolicy(),
+ expectedError: nil,
+ },
+ {
+ name: "Metadata cache is disabled and
policy requires metadata",
+ cacheMode: Disabled,
+ hostSelectionPolicy:
TokenAwareHostPolicy(RoundRobinHostPolicy()),
+ expectedError: ErrMetadataCacheRequired,
+ },
+ {
+ name: "Metadata cache is enabled and
policy requires metadata",
+ cacheMode: Full,
+ hostSelectionPolicy:
TokenAwareHostPolicy(RoundRobinHostPolicy()),
+ expectedError: nil,
+ },
+ {
+ name: "Metadata cache is disabled and
the host selection policy does not implement MetadataRequiredPolicy interface",
+ cacheMode: Disabled,
+ hostSelectionPolicy: testHostSelectionPolicy{},
+ expectedError: nil,
+ expectsWarning: true,
+ },
+ {
+ name: "Metadata cache is enabled",
+ cacheMode: Full,
+ hostSelectionPolicy: RoundRobinHostPolicy(),
+ expectedError: nil,
+ expectsWarning: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ logger := newTestLogger(LogLevelInfo)
+ cfg := ClusterConfig{
+ Metadata: MetadataConfig{CacheMode:
testCase.cacheMode},
+ PoolConfig: PoolConfig{HostSelectionPolicy:
testCase.hostSelectionPolicy},
+ Logger: logger,
+ }
+ err := cfg.validate()
+ assertEqual(t, "error", testCase.expectedError, err)
+ if testCase.expectsWarning {
+ assertTrue(t, "has warning",
strings.Contains(logger.String(), metadataCacheDisabledWarningMsg))
+ }
+ })
+ }
+}
diff --git a/common_test.go b/common_test.go
index 0ae75015..16996099 100644
--- a/common_test.go
+++ b/common_test.go
@@ -303,6 +303,10 @@ func randomText(size int) string {
return string(result)
}
+func randomNameWithPrefix(prefix string) string {
+ return prefix + strings.ToLower(randomText(10))
+}
+
func assertEqual(t *testing.T, description string, expected, actual
interface{}) {
t.Helper()
if expected != actual {
diff --git a/hostpool/hostpool.go b/hostpool/hostpool.go
index bc250c0e..62fe4efd 100644
--- a/hostpool/hostpool.go
+++ b/hostpool/hostpool.go
@@ -43,6 +43,8 @@ func HostPoolHostPolicy(hp hostpool.HostPool)
*hostPoolHostPolicy {
return &hostPoolHostPolicy{hostMap: map[string]*gocql.HostInfo{}, hp:
hp}
}
+var _ gocql.MetadataRequiredPolicy = (*hostPoolHostPolicy)(nil)
+
type hostPoolHostPolicy struct {
hp hostpool.HostPool
mu sync.RWMutex
@@ -141,6 +143,10 @@ func (r *hostPoolHostPolicy) Pick(qry
gocql.ExecutableStatement) gocql.NextHost
}
}
+func (r *hostPoolHostPolicy) MetadataRequired() bool {
+ return false
+}
+
// selectedHostPoolHost is a host returned by the hostPoolHostPolicy and
// implements the SelectedHost interface
type selectedHostPoolHost struct {
diff --git a/integration_test.go b/integration_test.go
index 8bd2e94c..27127200 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -35,9 +35,11 @@ import (
"net"
"reflect"
"strings"
+ "sync"
"testing"
"time"
+ "github.com/stretchr/testify/require"
inf "gopkg.in/inf.v0"
)
@@ -1028,3 +1030,87 @@ func TestSmallTimeoutNoPoolErrors(t *testing.T) {
errorCount, logOutput)
}
}
+
+// Emulates round-robin host selection policy and captures KeyspaceChanged
events.
+type capturingRRTestPolicy struct {
+ roundRobinHostPolicy
+
+ capturedEvents []KeyspaceUpdateEvent
+ mu sync.Mutex
+}
+
+func (c *capturingRRTestPolicy) KeyspaceChanged(event KeyspaceUpdateEvent) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.capturedEvents = append(c.capturedEvents, event)
+ c.roundRobinHostPolicy.KeyspaceChanged(event)
+}
+
+func (c *capturingRRTestPolicy) CapturedEvents() []KeyspaceUpdateEvent {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ return append([]KeyspaceUpdateEvent{}, c.capturedEvents...)
+}
+
+func (c *capturingRRTestPolicy) ResetEvents() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.capturedEvents = nil
+}
+
+func TestHostSelectionPolicyKeyspaceChangedEvents(t *testing.T) {
+ cluster := createCluster()
+ policy := capturingRRTestPolicy{}
+ cluster.Metadata.CacheMode = Full
+ cluster.PoolConfig.HostSelectionPolicy = &policy
+
+ session, err := cluster.CreateSession()
+ require.NoError(t, err)
+ defer session.Close()
+
+ // Regression test for CASSGO-132: KeyspaceChanged events are fired
during session creation.
+ events := policy.CapturedEvents()
+ require.Empty(t, events, "KeyspaceChanged events should not be fired
during session creation")
+
+ // Check keyspace events are fired
+
+ // CREATE
+ ksName := randomNameWithPrefix("gocql_test_")
+ err = session.Query(fmt.Sprintf("CREATE KEYSPACE %s WITH replication =
{'class': 'SimpleStrategy', 'replication_factor': '1'}",
ksName)).ExecContext(context.Background())
+ require.NoError(t, err)
+
+ require.Eventually(t, func() bool {
+ events = policy.CapturedEvents()
+ return len(events) == 1
+ }, time.Second*5, time.Millisecond*100)
+
+ require.Len(t, events, 1)
+ require.Equal(t, ksName, events[0].Keyspace)
+ require.Equal(t, SchemaChangeTypeCreated, events[0].Change)
+
+ // ALTER
+ policy.ResetEvents()
+ err = session.Query(fmt.Sprintf("ALTER KEYSPACE %s WITH replication =
{'class': 'SimpleStrategy', 'replication_factor': '2'}",
ksName)).ExecContext(context.Background())
+ require.NoError(t, err)
+ require.Eventually(t, func() bool {
+ events = policy.CapturedEvents()
+ return len(events) == 1
+ }, time.Second*5, time.Millisecond*100)
+
+ require.Len(t, events, 1)
+ require.Equal(t, ksName, events[0].Keyspace)
+ require.Equal(t, SchemaChangeTypeUpdated, events[0].Change)
+
+ // DROP
+ policy.ResetEvents()
+ err = session.Query(fmt.Sprintf("DROP KEYSPACE %s",
ksName)).ExecContext(context.Background())
+ require.NoError(t, err)
+ require.Eventually(t, func() bool {
+ events = policy.CapturedEvents()
+ return len(events) == 1
+ }, time.Second*5, time.Millisecond*100)
+
+ require.Len(t, events, 1)
+ require.Equal(t, ksName, events[0].Keyspace)
+ require.Equal(t, SchemaChangeTypeDropped, events[0].Change)
+}
diff --git a/metadata.go b/metadata.go
index 6e93f769..db282510 100644
--- a/metadata.go
+++ b/metadata.go
@@ -575,6 +575,10 @@ type schemaDescriber struct {
// Schema change events are server-initiated messages sent to clients that
have registered
// for schema change notifications. These events indicate modifications to
keyspaces, tables,
// user-defined types, functions, or aggregates.
+//
+// When a schema change event is received, the driver will update its internal
metadata
+// by comparing the old and new metadata. If the metadata has changed, the
driver will
+// notify [HostSelectionPolicy.KeyspaceChanged] method about the change.
const (
SchemaChangeTypeCreated = "CREATED" // Schema object was created
SchemaChangeTypeUpdated = "UPDATED" // Schema object was modified
@@ -858,9 +862,6 @@ func refreshSchemas(session *Session) error {
// Notify policy if it supports schema refresh notifications
notifier, supportsRefresh := session.policy.(schemaRefreshNotifier)
- hasKeyspaceListener := session.schemaListeners.hasKeyspace() &&
sessionInitialized
-
- // Notify the policy if it supports schema refresh notifications
if supportsRefresh {
notifier.schemaRefreshed(session.schemaDescriber.getSchemaMetaForRead())
}
@@ -871,31 +872,36 @@ func refreshSchemas(session *Session) error {
NewLogFieldInt("dropped_keyspaces_count",
len(droppedKeyspaces)),
)
- // If we don't support schemaRefreshed OR we have listeners, we need to
loop
- if !supportsRefresh || hasKeyspaceListener {
+ // We should not call KeyspaceChanged method if policy implements
schemaRefreshNotifier.
+ shouldCallKeyspaceChanged := !supportsRefresh && sessionInitialized
+ // We should not notify keyspace listeners if they are not set or
session is not initialized yet.
+ shouldNotifyKeyspaceListener := session.schemaListeners.hasKeyspace()
&& sessionInitialized
+
+ // Loop only if we need to notify policy or keyspace listeners.
+ if shouldCallKeyspaceChanged || shouldNotifyKeyspaceListener {
for _, name := range newKeyspaces {
- if !supportsRefresh {
+ if shouldCallKeyspaceChanged {
session.policy.KeyspaceChanged(KeyspaceUpdateEvent{Keyspace: name, Change:
SchemaChangeTypeCreated})
}
- if hasKeyspaceListener {
+ if shouldNotifyKeyspaceListener {
session.schemaListeners.OnKeyspaceCreated(OnKeyspaceCreatedEvent{Keyspace:
keyspaces[name].Clone()})
}
}
for _, name := range droppedKeyspaces {
- if !supportsRefresh {
+ if shouldCallKeyspaceChanged {
session.policy.KeyspaceChanged(KeyspaceUpdateEvent{Keyspace: name, Change:
SchemaChangeTypeDropped})
}
- if hasKeyspaceListener {
+ if shouldNotifyKeyspaceListener {
session.schemaListeners.OnKeyspaceDropped(OnKeyspaceDroppedEvent{Keyspace:
oldKeyspaceMeta[name].Clone()})
}
}
for _, name := range updatedKeyspaces {
- if !supportsRefresh {
+ if shouldCallKeyspaceChanged {
session.policy.KeyspaceChanged(KeyspaceUpdateEvent{Keyspace: name, Change:
SchemaChangeTypeUpdated})
}
- if hasKeyspaceListener {
+ if shouldNotifyKeyspaceListener {
session.schemaListeners.OnKeyspaceUpdated(OnKeyspaceUpdatedEvent{
Old: oldKeyspaceMeta[name].Clone(),
New: keyspaces[name].Clone(),
diff --git a/policies.go b/policies.go
index 3d880e97..1162ed5c 100644
--- a/policies.go
+++ b/policies.go
@@ -38,6 +38,14 @@ import (
"time"
)
+var (
+ _ MetadataRequiredPolicy = (*roundRobinHostPolicy)(nil)
+ _ MetadataRequiredPolicy = (*tokenAwareHostPolicy)(nil)
+ _ MetadataRequiredPolicy = (*dcAwareRR)(nil)
+ _ MetadataRequiredPolicy = (*rackAwareRR)(nil)
+ _ MetadataRequiredPolicy = (*singleHostReadyPolicy)(nil)
+)
+
// cowHostList implements a copy on write host list, its equivalent type is
[]*HostInfo
type cowHostList struct {
list atomic.Value
@@ -340,6 +348,15 @@ type schemaRefreshNotifier interface {
schemaRefreshed(meta *schemaMeta)
}
+// MetadataRequiredPolicy is an optional interface that can be implemented by
HostSelectionPolicy.
+// If implemented, the policy will be queried to determine if metadata is
required for the policy to work.
+// If metadata is required and MetadataCacheMode is not set to at least
KeyspaceOnly, it will result in an error during session creation.
+//
+// All built-in policies implement this interface.
+type MetadataRequiredPolicy interface {
+ MetadataRequired() bool
+}
+
// SelectedHost is an interface returned when picking a host from a host
// selection policy.
type SelectedHost interface {
@@ -396,6 +413,10 @@ func (r *roundRobinHostPolicy) HostDown(host *HostInfo) {
r.RemoveHost(host)
}
+func (r *roundRobinHostPolicy) MetadataRequired() bool {
+ return false
+}
+
// ShuffleReplicas returns an option function to enable shuffling of replicas
in token-aware host selection.
// When enabled, the set of replicas for a partition key will be traversed in
randomized order.
func ShuffleReplicas() func(*tokenAwareHostPolicy) {
@@ -427,6 +448,9 @@ func NonLocalReplicasFallback() func(policy
*tokenAwareHostPolicy) {
}
// ShuffledTokenAwareHostPolicy is a token aware host selection policy that
shuffles replicas.
+//
+// Note: TokenAwareHostPolicy requires the metadata cache to be enabled.
+// See [ClusterConfig.Metadata] for more details.
func ShuffledTokenAwareHostPolicy(fallback HostSelectionPolicy, opts
...func(*tokenAwareHostPolicy)) HostSelectionPolicy {
p := &tokenAwareHostPolicy{
fallback: fallback,
@@ -442,6 +466,9 @@ func ShuffledTokenAwareHostPolicy(fallback
HostSelectionPolicy, opts ...func(*to
// TokenAwareHostPolicy is a token aware host selection policy, where hosts are
// selected based on the partition key, so queries are sent to the host which
// owns the partition. Fallback is used when routing information is not
available.
+//
+// Note: TokenAwareHostPolicy requires the metadata cache to be enabled.
+// See [ClusterConfig.Metadata] for more details.
func TokenAwareHostPolicy(fallback HostSelectionPolicy, opts
...func(*tokenAwareHostPolicy)) HostSelectionPolicy {
p := &tokenAwareHostPolicy{fallback: fallback}
for _, opt := range opts {
@@ -799,6 +826,11 @@ func (t *tokenAwareHostPolicy) Pick(qry
ExecutableStatement) NextHost {
}
}
+// Always require metadata for token-aware host selection.
+func (t *tokenAwareHostPolicy) MetadataRequired() bool {
+ return true
+}
+
type dcAwareRR struct {
local string
localHosts cowHostList
@@ -840,6 +872,10 @@ func (d *dcAwareRR) RemoveHost(host *HostInfo) {
func (d *dcAwareRR) HostUp(host *HostInfo) { d.AddHost(host) }
func (d *dcAwareRR) HostDown(host *HostInfo) { d.RemoveHost(host) }
+func (d *dcAwareRR) MetadataRequired() bool {
+ return false
+}
+
// This function is supposed to be called in a fashion
// roundRobbin(offset, hostsPriority1, hostsPriority2, hostsPriority3 ... )
//
@@ -948,6 +984,10 @@ func (d *rackAwareRR) Pick(q ExecutableStatement) NextHost
{
return roundRobbin(int(nextStartOffset), d.hosts[0].get(),
d.hosts[1].get(), d.hosts[2].get())
}
+func (d *rackAwareRR) MetadataRequired() bool {
+ return false
+}
+
// ReadyPolicy defines a policy for when a HostSelectionPolicy can be used.
After
// each host connects during session initialization, the Ready method will be
// called. If you only need a single Host to be up you can wrap a
@@ -993,6 +1033,13 @@ func (s *singleHostReadyPolicy) Ready() bool {
return true
}
+// Requires metadata if the wrapped policy requires it.
+// If the wrapped policy does not implement MetadataRequiredPolicy, returns
false.
+func (s *singleHostReadyPolicy) MetadataRequired() bool {
+ fallback, ok := s.HostSelectionPolicy.(MetadataRequiredPolicy)
+ return ok && fallback.MetadataRequired()
+}
+
// ConvictionPolicy interface is used by gocql to determine if a host should be
// marked as DOWN based on the error and host info
type ConvictionPolicy interface {
diff --git a/schema_events_test.go b/schema_events_test.go
index ae9e2454..00175e1d 100644
--- a/schema_events_test.go
+++ b/schema_events_test.go
@@ -23,7 +23,6 @@ package gocql
import (
"fmt"
- "strings"
"testing"
"time"
@@ -263,10 +262,6 @@ func testSchemaEventsKeyspace(t *testing.T, session
*Session, listener *schemaCh
require.Equal(t, ks, listener.KeyspaceDroppedEvents[0].Keyspace.Name,
"Expected keyspace dropped event to have correct keyspace name")
}
-func randomNameWithPrefix(prefix string) string {
- return prefix + strings.ToLower(randomText(10))
-}
-
func testSchemaEventsTable(t *testing.T, session *Session, listener
*schemaChangesTestListener) {
table := randomNameWithPrefix("gocql_table_events_")
diff --git a/session.go b/session.go
index 1b4a1112..bfe42573 100644
--- a/session.go
+++ b/session.go
@@ -149,6 +149,14 @@ func NewSession(cfg ClusterConfig) (*Session, error) {
return nil, fmt.Errorf("the default SerialConsistency level is
not allowed to be anything else but SERIAL or LOCAL_SERIAL. Recived value: %v",
cfg.SerialConsistency)
}
+ if cfg.PoolConfig.HostSelectionPolicy == nil {
+ cfg.PoolConfig.HostSelectionPolicy = RoundRobinHostPolicy()
+ }
+
+ if err := cfg.validate(); err != nil {
+ return nil, err
+ }
+
// TODO: we should take a context in here at some point
ctx, cancel := context.WithCancel(context.TODO())
@@ -222,9 +230,6 @@ func NewSession(cfg ClusterConfig) (*Session, error) {
}
s.connCfg = connCfg
- if cfg.PoolConfig.HostSelectionPolicy == nil {
- cfg.PoolConfig.HostSelectionPolicy = RoundRobinHostPolicy()
- }
s.pool = cfg.PoolConfig.buildPool(s)
s.policy = cfg.PoolConfig.HostSelectionPolicy
@@ -2525,16 +2530,17 @@ func (e Error) Error() string {
}
var (
- ErrNotFound = errors.New("not found")
- ErrUnavailable = errors.New("unavailable")
- ErrUnsupported = errors.New("feature not supported")
- ErrTooManyStmts = errors.New("too many statements")
- ErrUseStmt = errors.New("use statements aren't supported.
Please see https://github.com/apache/cassandra-gocql-driver for explanation.")
- ErrSessionClosed = errors.New("session has been closed")
- ErrNoConnections = errors.New("gocql: no hosts available in the
pool")
- ErrNoKeyspace = errors.New("no keyspace provided")
- ErrKeyspaceDoesNotExist = errors.New("keyspace does not exist")
- ErrNoMetadata = errors.New("no metadata available")
+ ErrNotFound = errors.New("not found")
+ ErrUnavailable = errors.New("unavailable")
+ ErrUnsupported = errors.New("feature not supported")
+ ErrTooManyStmts = errors.New("too many statements")
+ ErrUseStmt = errors.New("use statements aren't supported.
Please see https://github.com/apache/cassandra-gocql-driver for explanation.")
+ ErrSessionClosed = errors.New("session has been closed")
+ ErrNoConnections = errors.New("gocql: no hosts available in the
pool")
+ ErrNoKeyspace = errors.New("no keyspace provided")
+ ErrKeyspaceDoesNotExist = errors.New("keyspace does not exist")
+ ErrNoMetadata = errors.New("no metadata available")
+ ErrMetadataCacheRequired = errors.New("metadata cache must be enabled
for the selected host selection policy")
)
// ErrProtocol represents a protocol-level error.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]