go.temporal.io/server@v1.23.0/common/persistence/tests/cassandra_test.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package tests
    26  
    27  import (
    28  	"context"
    29  	"strconv"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  	"github.com/stretchr/testify/suite"
    36  	"go.temporal.io/api/enums/v1"
    37  	"go.temporal.io/api/serviceerror"
    38  
    39  	persistencespb "go.temporal.io/server/api/persistence/v1"
    40  	"go.temporal.io/server/common/log"
    41  	"go.temporal.io/server/common/log/tag"
    42  	"go.temporal.io/server/common/persistence"
    43  	"go.temporal.io/server/common/persistence/cassandra"
    44  	"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
    45  	persistencetests "go.temporal.io/server/common/persistence/persistence-tests"
    46  	"go.temporal.io/server/common/persistence/persistencetest"
    47  	"go.temporal.io/server/common/persistence/serialization"
    48  	_ "go.temporal.io/server/common/persistence/sql/sqlplugin/mysql"
    49  )
    50  
    51  type (
    52  	// failingSession is a [gocql.Session] which fails any query whose template matches a string in failingQueries.
    53  	failingSession struct {
    54  		gocql.Session
    55  		failingQueries []string
    56  	}
    57  	// failingQuery is a [gocql.Query] which fails when executed.
    58  	failingQuery struct {
    59  		gocql.Query
    60  	}
    61  	// recordingSession is a [gocql.Session] which records all queries executed on it.
    62  	recordingSession struct {
    63  		gocql.Session
    64  		statements []statement
    65  	}
    66  	statement struct {
    67  		query string
    68  		args  []interface{}
    69  	}
    70  	// failingIter is a [gocql.Iter] which fails when iterated.
    71  	failingIter struct{}
    72  	// blockingSession is a [gocql.Session] designed for testing concurrent inserts.
    73  	// See cassandra.ErrQueueMessageIDConflict for more.
    74  	blockingSession struct {
    75  		gocql.Session
    76  		queryToBlockOn   string
    77  		queryStarted     chan struct{}
    78  		queryCanContinue chan struct{}
    79  	}
    80  	// enqueueMessageResult contains the result of a call to persistence.QueueV2.EnqueueMessage.
    81  	enqueueMessageResult struct {
    82  		// id of the inserted message
    83  		id int
    84  		// err if the call failed
    85  		err error
    86  	}
    87  	testQueueParams struct {
    88  		logger log.Logger
    89  	}
    90  	testLogger struct {
    91  		log.Logger
    92  		warningMsgs []string
    93  	}
    94  	rangeDeleteTestQueue struct {
    95  		persistence.QueueV2
    96  		session       *blockingSession
    97  		deleteErrs    chan error
    98  		maxIDToDelete int
    99  	}
   100  )
   101  
   102  func (f failingIter) Scan(...interface{}) bool {
   103  	return false
   104  }
   105  
   106  func (f failingIter) MapScan(map[string]interface{}) bool {
   107  	return false
   108  }
   109  
   110  func (f failingIter) PageState() []byte {
   111  	return nil
   112  }
   113  
   114  func (f failingIter) Close() error {
   115  	return assert.AnError
   116  }
   117  
   118  func (q failingQuery) Iter() gocql.Iter {
   119  	return failingIter{}
   120  }
   121  
   122  func (q failingQuery) Scan(...interface{}) error {
   123  	return assert.AnError
   124  }
   125  
   126  func (q failingQuery) Exec() error {
   127  	return assert.AnError
   128  }
   129  
   130  func (l *testLogger) Warn(msg string, _ ...tag.Tag) {
   131  	l.warningMsgs = append(l.warningMsgs, msg)
   132  }
   133  
   134  func (s *recordingSession) Query(query string, args ...interface{}) gocql.Query {
   135  	s.statements = append(s.statements, statement{
   136  		query: query,
   137  		args:  args,
   138  	})
   139  
   140  	return s.Session.Query(query, args...)
   141  }
   142  
   143  func TestCassandraShardStoreSuite(t *testing.T) {
   144  	testData, tearDown := setUpCassandraTest(t)
   145  	defer tearDown()
   146  
   147  	shardStore, err := testData.Factory.NewShardStore()
   148  	if err != nil {
   149  		t.Fatalf("unable to create Cassandra DB: %v", err)
   150  	}
   151  
   152  	s := NewShardSuite(
   153  		t,
   154  		shardStore,
   155  		serialization.NewSerializer(),
   156  		testData.Logger,
   157  	)
   158  	suite.Run(t, s)
   159  }
   160  
   161  func TestCassandraExecutionMutableStateStoreSuite(t *testing.T) {
   162  	testData, tearDown := setUpCassandraTest(t)
   163  	defer tearDown()
   164  
   165  	shardStore, err := testData.Factory.NewShardStore()
   166  	if err != nil {
   167  		t.Fatalf("unable to create Cassandra DB: %v", err)
   168  	}
   169  	executionStore, err := testData.Factory.NewExecutionStore()
   170  	if err != nil {
   171  		t.Fatalf("unable to create Cassandra DB: %v", err)
   172  	}
   173  
   174  	s := NewExecutionMutableStateSuite(
   175  		t,
   176  		shardStore,
   177  		executionStore,
   178  		serialization.NewSerializer(),
   179  		testData.Logger,
   180  	)
   181  	suite.Run(t, s)
   182  }
   183  
   184  func TestCassandraExecutionMutableStateTaskStoreSuite(t *testing.T) {
   185  	testData, tearDown := setUpCassandraTest(t)
   186  	defer tearDown()
   187  
   188  	shardStore, err := testData.Factory.NewShardStore()
   189  	if err != nil {
   190  		t.Fatalf("unable to create Cassandra DB: %v", err)
   191  	}
   192  	executionStore, err := testData.Factory.NewExecutionStore()
   193  	if err != nil {
   194  		t.Fatalf("unable to create Cassandra DB: %v", err)
   195  	}
   196  
   197  	s := NewExecutionMutableStateTaskSuite(
   198  		t,
   199  		shardStore,
   200  		executionStore,
   201  		serialization.NewSerializer(),
   202  		testData.Logger,
   203  	)
   204  	suite.Run(t, s)
   205  }
   206  
   207  // TODO: Merge persistence-tests into the tests directory.
   208  
   209  func TestCassandraHistoryStoreSuite(t *testing.T) {
   210  	testData, tearDown := setUpCassandraTest(t)
   211  	defer tearDown()
   212  
   213  	store, err := testData.Factory.NewExecutionStore()
   214  	if err != nil {
   215  		t.Fatalf("unable to create Cassandra DB: %v", err)
   216  	}
   217  
   218  	s := NewHistoryEventsSuite(t, store, testData.Logger)
   219  	suite.Run(t, s)
   220  }
   221  
   222  func TestCassandraTaskQueueSuite(t *testing.T) {
   223  	testData, tearDown := setUpCassandraTest(t)
   224  	defer tearDown()
   225  
   226  	taskQueueStore, err := testData.Factory.NewTaskStore()
   227  	if err != nil {
   228  		t.Fatalf("unable to create Cassandra DB: %v", err)
   229  	}
   230  
   231  	s := NewTaskQueueSuite(t, taskQueueStore, testData.Logger)
   232  	suite.Run(t, s)
   233  }
   234  
   235  func TestCassandraTaskQueueTaskSuite(t *testing.T) {
   236  	testData, tearDown := setUpCassandraTest(t)
   237  	defer tearDown()
   238  
   239  	taskQueueStore, err := testData.Factory.NewTaskStore()
   240  	if err != nil {
   241  		t.Fatalf("unable to create Cassandra DB: %v", err)
   242  	}
   243  
   244  	s := NewTaskQueueTaskSuite(t, taskQueueStore, testData.Logger)
   245  	suite.Run(t, s)
   246  }
   247  
   248  func TestCassandraVisibilityPersistence(t *testing.T) {
   249  	s := &VisibilityPersistenceSuite{
   250  		TestBase: persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{}),
   251  	}
   252  	suite.Run(t, s)
   253  }
   254  
   255  func TestCassandraHistoryV2Persistence(t *testing.T) {
   256  	s := new(persistencetests.HistoryV2PersistenceSuite)
   257  	s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{})
   258  	s.TestBase.Setup(nil)
   259  	suite.Run(t, s)
   260  }
   261  
   262  func TestCassandraMetadataPersistenceV2(t *testing.T) {
   263  	s := new(persistencetests.MetadataPersistenceSuiteV2)
   264  	s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{})
   265  	s.TestBase.Setup(nil)
   266  	suite.Run(t, s)
   267  }
   268  
   269  func TestCassandraClusterMetadataPersistence(t *testing.T) {
   270  	s := new(persistencetests.ClusterMetadataManagerSuite)
   271  	s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{})
   272  	s.TestBase.Setup(nil)
   273  	suite.Run(t, s)
   274  }
   275  
   276  func TestCassandraQueuePersistence(t *testing.T) {
   277  	s := new(persistencetests.QueuePersistenceSuite)
   278  	s.TestBase = persistencetests.NewTestBaseWithCassandra(&persistencetests.TestBaseOptions{})
   279  	s.TestBase.Setup(nil)
   280  	suite.Run(t, s)
   281  }
   282  
   283  func TestCassandraQueueV2Persistence(t *testing.T) {
   284  	// This test function is split up into two parts:
   285  	// 1. Test the generic queue functionality, which is independent of the database choice (Cassandra here).
   286  	//   This is done by calling the generic RunQueueV2TestSuite function.
   287  	// 2. Test the Cassandra-specific implementation of the queue. For example, things like queue message ID conflicts
   288  	//   can only happen in Cassandra due to its lack of transactions, so we need to test those here.
   289  
   290  	t.Parallel()
   291  
   292  	cluster := persistencetests.NewTestClusterForCassandra(&persistencetests.TestBaseOptions{}, log.NewNoopLogger())
   293  	cluster.SetupTestDatabase()
   294  	t.Cleanup(cluster.TearDownTestDatabase)
   295  
   296  	t.Run("Generic", func(t *testing.T) {
   297  		t.Parallel()
   298  		RunQueueV2TestSuite(t, newQueueV2Store(cluster.GetSession()))
   299  	})
   300  	t.Run("CassandraSpecific", func(t *testing.T) {
   301  		t.Parallel()
   302  		testCassandraQueueV2(t, cluster)
   303  	})
   304  }
   305  
   306  func testCassandraQueueV2DataCorruption(t *testing.T, cluster *cassandra.TestCluster) {
   307  	t.Run("ErrInvalidQueueMessageEncodingType", func(t *testing.T) {
   308  		t.Parallel()
   309  		testCassandraQueueV2ErrInvalidQueueMessageEncodingType(t, cluster)
   310  	})
   311  	t.Run("ErrInvalidPayloadEncodingType", func(t *testing.T) {
   312  		t.Parallel()
   313  		testCassandraQueueV2ErrInvalidPayloadEncodingType(t, cluster)
   314  	})
   315  	t.Run("ErrInvalidPayload", func(t *testing.T) {
   316  		t.Parallel()
   317  		testCassandraQueueV2ErrInvalidPayload(t, cluster)
   318  	})
   319  }
   320  
   321  func testCassandraQueueV2(t *testing.T, cluster *cassandra.TestCluster) {
   322  	t.Run("DataCorruption", func(t *testing.T) {
   323  		t.Parallel()
   324  		testCassandraQueueV2DataCorruption(t, cluster)
   325  	})
   326  	t.Run("QueryErrors", func(t *testing.T) {
   327  		t.Parallel()
   328  		testCassandraQueueV2QueryErrors(t, cluster)
   329  	})
   330  	t.Run("RangeDeleteUpperBoundHigherThanMaxMessageID", func(t *testing.T) {
   331  		t.Parallel()
   332  		testCassandraQueueV2RangeDeleteUpperBoundHigherThanMaxMessageID(t, cluster)
   333  	})
   334  	t.Run("RepeatedRangeDelete", func(t *testing.T) {
   335  		t.Parallel()
   336  		testCassandraQueueV2RepeatedRangeDelete(t, cluster)
   337  	})
   338  	t.Run("MinMessageIDOptimization", func(t *testing.T) {
   339  		t.Parallel()
   340  		testCassandraQueueV2MinMessageIDOptimization(t, cluster)
   341  	})
   342  	t.Run("ConcurrentConflicts", func(t *testing.T) {
   343  		testCassandraQueueV2ConcurrentConflicts(t, cluster)
   344  	})
   345  	t.Run("MultiplePartitions", func(t *testing.T) {
   346  		testCassandraQueueV2MultiplePartitions(t, cluster)
   347  	})
   348  }
   349  
   350  func testCassandraQueueV2ConcurrentConflicts(t *testing.T, cluster *cassandra.TestCluster) {
   351  	t.Run("EnqueueMessage", func(t *testing.T) {
   352  		t.Parallel()
   353  		testCassandraQueueV2EnqueueErrEnqueueMessageConflict(t, cluster)
   354  	})
   355  	t.Run("RangeDeleteMessages", func(t *testing.T) {
   356  		t.Parallel()
   357  		testCassandraQueueV2ConcurrentRangeDeleteMessages(t, cluster)
   358  	})
   359  }
   360  
   361  func testCassandraQueueV2QueryErrors(t *testing.T, cluster *cassandra.TestCluster) {
   362  	t.Run("GetQueueQuery", func(t *testing.T) {
   363  		t.Parallel()
   364  		testCassandraQueueV2ErrGetQueueQuery(t, cluster)
   365  	})
   366  	t.Run("CreateQueueQuery", func(t *testing.T) {
   367  		t.Parallel()
   368  		testCassandraQueueV2ErrCreateQueueQuery(t, cluster)
   369  	})
   370  	t.Run("RangeDeleteMessagesQuery", func(t *testing.T) {
   371  		t.Parallel()
   372  		testCassandraQueueV2ErrRangeDeleteMessagesQuery(t, cluster)
   373  	})
   374  	t.Run("ErrReadMessagesQuery", func(t *testing.T) {
   375  		t.Parallel()
   376  		testCassandraQueueV2ErrGetMessagesQuery(t, cluster)
   377  	})
   378  	t.Run("ErrEnqueueMessageQuery", func(t *testing.T) {
   379  		t.Parallel()
   380  		testCassandraQueueV2ErrEnqueueMessageQuery(t, cluster)
   381  	})
   382  	t.Run("EnqueueMessageGetMaxMessageIDQuery", func(t *testing.T) {
   383  		t.Parallel()
   384  		testCassandraQueueV2ErrEnqueueMessageGetMaxMessageIDQuery(t, cluster)
   385  	})
   386  	t.Run("ListQueuesGetMaxMessageIDQuery", func(t *testing.T) {
   387  		t.Parallel()
   388  		testCassandraQueueV2ErrListQueuesGetMaxMessageIDQuery(t, cluster)
   389  	})
   390  	t.Run("RangeDeleteMessagesGetMaxMessageIDQuery", func(t *testing.T) {
   391  		t.Parallel()
   392  		testCassandraQueueV2ErrRangeDeleteMessagesGetMaxMessageIDQuery(t, cluster)
   393  	})
   394  	t.Run("RangeDeleteMessagesUpdateQueueQuery", func(t *testing.T) {
   395  		t.Parallel()
   396  		testCassandraQueueV2ErrRangeDeleteMessagesUpdateQueueQuery(t, cluster)
   397  	})
   398  }
   399  
   400  func testCassandraQueueV2ErrRangeDeleteMessagesUpdateQueueQuery(t *testing.T, cluster *cassandra.TestCluster) {
   401  	q := newQueueV2Store(failingSession{
   402  		Session:        cluster.GetSession(),
   403  		failingQueries: []string{cassandra.TemplateUpdateQueueMetadataQuery},
   404  	})
   405  	ctx := context.Background()
   406  	queueType := persistence.QueueTypeHistoryNormal
   407  	queueName := "test-queue-" + t.Name()
   408  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   409  		QueueType: queueType,
   410  		QueueName: queueName,
   411  	})
   412  	require.NoError(t, err)
   413  	persistencetest.EnqueueMessagesForDelete(t, q, queueName, queueType)
   414  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID)
   415  	require.Error(t, err)
   416  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   417  	assert.ErrorContains(t, err, assert.AnError.Error())
   418  	assert.ErrorContains(t, err, "QueueV2UpdateQueueMetadata")
   419  }
   420  
   421  func testCassandraQueueV2ErrRangeDeleteMessagesGetMaxMessageIDQuery(t *testing.T, cluster *cassandra.TestCluster) {
   422  	session := &failingSession{
   423  		Session:        cluster.GetSession(),
   424  		failingQueries: []string{},
   425  	}
   426  	q := newQueueV2Store(session)
   427  	ctx := context.Background()
   428  	queueType := persistence.QueueTypeHistoryNormal
   429  	queueName := "test-queue-" + t.Name()
   430  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   431  		QueueType: queueType,
   432  		QueueName: queueName,
   433  	})
   434  	require.NoError(t, err)
   435  	persistencetest.EnqueueMessagesForDelete(t, q, queueName, queueType)
   436  	session.failingQueries = []string{cassandra.TemplateGetMaxMessageIDQuery}
   437  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID)
   438  	require.Error(t, err)
   439  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   440  	assert.ErrorContains(t, err, assert.AnError.Error())
   441  	assert.ErrorContains(t, err, "QueueV2GetMaxMessageID")
   442  }
   443  
   444  func testCassandraQueueV2ErrEnqueueMessageGetMaxMessageIDQuery(t *testing.T, cluster *cassandra.TestCluster) {
   445  	q := newQueueV2Store(failingSession{
   446  		Session:        cluster.GetSession(),
   447  		failingQueries: []string{cassandra.TemplateGetMaxMessageIDQuery},
   448  	})
   449  	ctx := context.Background()
   450  	queueType := persistence.QueueTypeHistoryNormal
   451  	queueName := "test-queue-" + t.Name()
   452  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   453  		QueueType: queueType,
   454  		QueueName: queueName,
   455  	})
   456  	require.NoError(t, err)
   457  	_, err = persistencetest.EnqueueMessage(ctx, q, queueType, queueName)
   458  	require.Error(t, err)
   459  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   460  	assert.ErrorContains(t, err, assert.AnError.Error())
   461  	assert.ErrorContains(t, err, "QueueV2GetMaxMessageID")
   462  }
   463  
   464  func testCassandraQueueV2ErrListQueuesGetMaxMessageIDQuery(t *testing.T, cluster *cassandra.TestCluster) {
   465  	q := newQueueV2Store(failingSession{
   466  		Session:        cluster.GetSession(),
   467  		failingQueries: []string{cassandra.TemplateGetMaxMessageIDQuery},
   468  	})
   469  	ctx := context.Background()
   470  	queueType := persistence.QueueTypeHistoryDLQ
   471  	queueName := "test-queue-" + t.Name()
   472  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   473  		QueueType: queueType,
   474  		QueueName: queueName,
   475  	})
   476  	require.NoError(t, err)
   477  	_, err = q.ListQueues(ctx, &persistence.InternalListQueuesRequest{
   478  		QueueType: queueType,
   479  		PageSize:  100,
   480  	})
   481  	require.Error(t, err)
   482  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   483  	assert.ErrorContains(t, err, assert.AnError.Error())
   484  	assert.ErrorContains(t, err, "QueueV2GetMaxMessageID")
   485  }
   486  
   487  func testCassandraQueueV2MultiplePartitions(t *testing.T, cluster *cassandra.TestCluster) {
   488  	t.Run("RangeDeleteMessages", func(t *testing.T) {
   489  		t.Parallel()
   490  		testCassandraQueueV2MultiplePartitionsRangeDelete(t, cluster)
   491  	})
   492  	t.Run("ReadMessages", func(t *testing.T) {
   493  		t.Parallel()
   494  		testCassandraQueueV2MultiplePartitionsReadMessages(t, cluster)
   495  	})
   496  	t.Run("ListQueues", func(t *testing.T) {
   497  		t.Parallel()
   498  		testCassandraQueueV2MultiplePartitionsListQueues(t, cluster)
   499  	})
   500  }
   501  
   502  // Query checks if the query matches queryToBlockOn, and, if so, it notifies the test and then blocks until the test
   503  // unblocks it.
   504  func (f *blockingSession) Query(query string, args ...interface{}) gocql.Query {
   505  	if query == f.queryToBlockOn {
   506  		f.queryStarted <- struct{}{}
   507  		<-f.queryCanContinue
   508  	}
   509  
   510  	return f.Session.Query(query, args...)
   511  }
   512  
   513  // testCassandraQueueV2EnqueueErrEnqueueMessageConflict tests that when there are concurrent inserts to the queue, only one of
   514  // them is accepted if they try to enqueue a message with the same ID, and the other clients are given the correct
   515  // error.
   516  func testCassandraQueueV2EnqueueErrEnqueueMessageConflict(t *testing.T, cluster *cassandra.TestCluster) {
   517  	const numConcurrentWrites = 3
   518  
   519  	session := &blockingSession{
   520  		Session:          cluster.GetSession(),
   521  		queryToBlockOn:   cassandra.TemplateEnqueueMessageQuery,
   522  		queryStarted:     make(chan struct{}, numConcurrentWrites),
   523  		queryCanContinue: make(chan struct{}),
   524  	}
   525  
   526  	q := newQueueV2Store(session)
   527  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
   528  	t.Cleanup(cancel)
   529  
   530  	queueType := persistence.QueueTypeHistoryNormal
   531  	queueName := "test-queue-" + t.Name()
   532  
   533  	results := make(chan enqueueMessageResult, numConcurrentWrites)
   534  
   535  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   536  		QueueType: queueType,
   537  		QueueName: queueName,
   538  	})
   539  	require.NoError(t, err)
   540  	for i := 0; i < numConcurrentWrites; i++ {
   541  		go func() {
   542  			res, err := persistencetest.EnqueueMessage(ctx, q, queueType, queueName)
   543  			if err != nil {
   544  				select {
   545  				case <-ctx.Done():
   546  					return
   547  				case results <- enqueueMessageResult{err: err}:
   548  				}
   549  			} else {
   550  				select {
   551  				case <-ctx.Done():
   552  					return
   553  				case results <- enqueueMessageResult{id: int(res.Metadata.ID)}:
   554  				}
   555  			}
   556  		}()
   557  	}
   558  
   559  	for i := 0; i < numConcurrentWrites; i++ {
   560  		select {
   561  		case <-ctx.Done():
   562  			printResults(t, results)
   563  			t.Fatal("timed out waiting for enqueue to be called")
   564  		case <-session.queryStarted:
   565  		}
   566  	}
   567  	close(session.queryCanContinue)
   568  
   569  	numConflicts := 0
   570  	writtenMessageIDs := make([]int, 0, 1)
   571  
   572  	for i := 0; i < numConcurrentWrites; i++ {
   573  		var res enqueueMessageResult
   574  		select {
   575  		case <-ctx.Done():
   576  			t.Fatal("timed out waiting for enqueue to return")
   577  		case res = <-results:
   578  		}
   579  		if res.err != nil {
   580  			assert.ErrorIs(t, res.err, cassandra.ErrEnqueueMessageConflict)
   581  
   582  			numConflicts++
   583  		} else {
   584  			writtenMessageIDs = append(writtenMessageIDs, res.id)
   585  		}
   586  	}
   587  
   588  	assert.Equal(t, numConcurrentWrites-1, numConflicts,
   589  		"every query other than the accepted one should have failed")
   590  	assert.Len(t, writtenMessageIDs, 1,
   591  		"only one message should have been written")
   592  
   593  	messages, err := q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{
   594  		QueueType:     queueType,
   595  		QueueName:     queueName,
   596  		PageSize:      numConcurrentWrites,
   597  		NextPageToken: nil,
   598  	})
   599  
   600  	require.NoError(t, err)
   601  	require.Len(t, messages.Messages, 1,
   602  		"there should only be one message in the queue")
   603  	assert.Equal(t, writtenMessageIDs[0], int(messages.Messages[0].MetaData.ID),
   604  		"the message in the queue should be the one that Cassandra told us was accepted")
   605  }
   606  
   607  func printResults(t *testing.T, results chan enqueueMessageResult) {
   608  	for {
   609  		select {
   610  		case res := <-results:
   611  			if res.err != nil {
   612  				t.Error("got unexpected error:", res.err)
   613  			}
   614  		default:
   615  			return
   616  		}
   617  	}
   618  }
   619  
   620  func testCassandraQueueV2ErrInvalidQueueMessageEncodingType(t *testing.T, cluster *cassandra.TestCluster) {
   621  	session := cluster.GetSession()
   622  	q := newQueueV2Store(session)
   623  	queueType := persistence.QueueTypeHistoryNormal
   624  	queueName := "test-queue-" + t.Name()
   625  	_, err := q.CreateQueue(context.Background(), &persistence.InternalCreateQueueRequest{
   626  		QueueType: queueType,
   627  		QueueName: queueName,
   628  	})
   629  	require.NoError(t, err)
   630  	err = session.Query(
   631  		cassandra.TemplateEnqueueMessageQuery,
   632  		queueType,
   633  		queueName,
   634  		0, // partition
   635  		1, // messageID
   636  		[]byte("test"),
   637  		"bad-encoding-type",
   638  	).Exec()
   639  	require.NoError(t, err)
   640  	_, err = q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{
   641  		QueueType: queueType,
   642  		QueueName: queueName,
   643  		PageSize:  1,
   644  	})
   645  	require.Error(t, err)
   646  	assert.ErrorAs(t, err, new(*serialization.UnknownEncodingTypeError))
   647  }
   648  
   649  func (q failingQuery) MapScanCAS(map[string]interface{}) (bool, error) {
   650  	return false, assert.AnError
   651  }
   652  
   653  func (q failingQuery) WithContext(context.Context) gocql.Query {
   654  	return q
   655  }
   656  
   657  func (f failingSession) Query(query string, args ...interface{}) gocql.Query {
   658  	for _, q := range f.failingQueries {
   659  		if q == query {
   660  			return failingQuery{}
   661  		}
   662  	}
   663  	return f.Session.Query(query, args...)
   664  }
   665  
   666  func testCassandraQueueV2ErrGetMessagesQuery(t *testing.T, cluster *cassandra.TestCluster) {
   667  	q := newQueueV2Store(failingSession{
   668  		Session:        cluster.GetSession(),
   669  		failingQueries: []string{cassandra.TemplateGetMessagesQuery},
   670  	})
   671  	ctx := context.Background()
   672  	queueType := persistence.QueueTypeHistoryNormal
   673  	queueName := "test-queue-" + t.Name()
   674  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   675  		QueueType: queueType,
   676  		QueueName: queueName,
   677  	})
   678  	require.NoError(t, err)
   679  	_, err = q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{
   680  		QueueType: queueType,
   681  		QueueName: queueName,
   682  		PageSize:  1,
   683  	})
   684  	require.Error(t, err)
   685  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   686  	assert.ErrorContains(t, err, assert.AnError.Error())
   687  	assert.ErrorContains(t, err, "QueueV2ReadMessages")
   688  }
   689  
   690  func testCassandraQueueV2ErrEnqueueMessageQuery(t *testing.T, cluster *cassandra.TestCluster) {
   691  	q := newQueueV2Store(failingSession{
   692  		Session:        cluster.GetSession(),
   693  		failingQueries: []string{cassandra.TemplateEnqueueMessageQuery},
   694  	})
   695  	ctx := context.Background()
   696  	queueType := persistence.QueueTypeHistoryNormal
   697  	queueName := "test-queue-" + t.Name()
   698  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   699  		QueueType: queueType,
   700  		QueueName: queueName,
   701  	})
   702  	require.NoError(t, err)
   703  	_, err = persistencetest.EnqueueMessage(context.Background(), q, queueType, queueName)
   704  	require.Error(t, err)
   705  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   706  	assert.ErrorContains(t, err, assert.AnError.Error())
   707  	assert.ErrorContains(t, err, "QueueV2EnqueueMessage")
   708  }
   709  
   710  func testCassandraQueueV2ErrInvalidPayloadEncodingType(t *testing.T, cluster *cassandra.TestCluster) {
   711  	// Manually insert a row into the queues table that has an invalid metadata payload encoding type and then verify
   712  	// that we gracefully handle the error when we try to read from this queue.
   713  
   714  	session := cluster.GetSession()
   715  	q := newQueueV2Store(session)
   716  	// Using a different QueueType so that ListQueue tests are not failing because of corrupt queue metadata.
   717  	queueType := persistence.QueueV2Type(3)
   718  	queueName := "test-queue-" + t.Name()
   719  	err := session.Query(
   720  		cassandra.TemplateCreateQueueQuery,
   721  		queueType,
   722  		queueName,
   723  		[]byte("test"),      // payload
   724  		"bad-encoding-type", // payload encoding type
   725  		0,                   // version
   726  	).Exec()
   727  	require.NoError(t, err)
   728  	_, err = q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{
   729  		QueueType: queueType,
   730  		QueueName: queueName,
   731  		PageSize:  1,
   732  	})
   733  	require.Error(t, err)
   734  	assert.ErrorAs(t, err, new(*serialization.UnknownEncodingTypeError))
   735  	assert.ErrorContains(t, err, "bad-encoding-type")
   736  	assert.ErrorContains(t, err, strconv.Itoa(int(queueType)))
   737  	assert.ErrorContains(t, err, queueName)
   738  
   739  	_, err = q.ListQueues(context.Background(), &persistence.InternalListQueuesRequest{
   740  		QueueType: queueType,
   741  		PageSize:  100,
   742  	})
   743  	require.Error(t, err)
   744  	assert.ErrorAs(t, err, new(*serialization.UnknownEncodingTypeError))
   745  	assert.ErrorContains(t, err, "bad-encoding-type")
   746  	assert.ErrorContains(t, err, strconv.Itoa(int(queueType)))
   747  	assert.ErrorContains(t, err, queueName)
   748  }
   749  
   750  func testCassandraQueueV2ErrInvalidPayload(t *testing.T, cluster *cassandra.TestCluster) {
   751  	// Manually insert a row into the queues table that has some invalid bytes for the queue metadata proto payload and
   752  	// then verify that we gracefully handle the error when we try to read from this queue.
   753  
   754  	session := cluster.GetSession()
   755  	q := newQueueV2Store(session)
   756  	queueType := persistence.QueueTypeHistoryNormal
   757  	queueName := "test-queue-" + t.Name()
   758  	err := session.Query(
   759  		cassandra.TemplateCreateQueueQuery,
   760  		queueType,
   761  		queueName,
   762  		[]byte("invalid-payload"),           // payload
   763  		enums.ENCODING_TYPE_PROTO3.String(), // payload encoding type
   764  		0,                                   // version
   765  	).Exec()
   766  	require.NoError(t, err)
   767  	_, err = q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{
   768  		QueueType: queueType,
   769  		QueueName: queueName,
   770  		PageSize:  1,
   771  	})
   772  	require.Error(t, err)
   773  	assert.ErrorAs(t, err, new(*serialization.DeserializationError))
   774  	assert.ErrorContains(t, err, "unmarshal")
   775  	assert.ErrorContains(t, err, strconv.Itoa(int(queueType)))
   776  	assert.ErrorContains(t, err, queueName)
   777  }
   778  
   779  func testCassandraQueueV2ErrGetQueueQuery(t *testing.T, cluster *cassandra.TestCluster) {
   780  	q := newQueueV2Store(failingSession{
   781  		Session:        cluster.GetSession(),
   782  		failingQueries: []string{cassandra.TemplateGetQueueQuery},
   783  	})
   784  	ctx := context.Background()
   785  	queueType := persistence.QueueTypeHistoryNormal
   786  	queueName := "test-queue-" + t.Name()
   787  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   788  		QueueType: queueType,
   789  		QueueName: queueName,
   790  	})
   791  	require.NoError(t, err)
   792  	_, err = q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{
   793  		QueueType: queueType,
   794  		QueueName: queueName,
   795  		PageSize:  1,
   796  	})
   797  	require.Error(t, err)
   798  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   799  	assert.ErrorContains(t, err, assert.AnError.Error())
   800  	assert.ErrorContains(t, err, "QueueV2GetQueue")
   801  }
   802  
   803  func testCassandraQueueV2ErrCreateQueueQuery(t *testing.T, cluster *cassandra.TestCluster) {
   804  	q := newQueueV2Store(failingSession{
   805  		Session:        cluster.GetSession(),
   806  		failingQueries: []string{cassandra.TemplateCreateQueueQuery},
   807  	})
   808  	ctx := context.Background()
   809  	queueType := persistence.QueueTypeHistoryNormal
   810  	queueName := "test-queue-" + t.Name()
   811  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   812  		QueueType: queueType,
   813  		QueueName: queueName,
   814  	})
   815  	require.Error(t, err)
   816  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   817  	assert.ErrorContains(t, err, assert.AnError.Error())
   818  	assert.ErrorContains(t, err, "QueueV2CreateQueue")
   819  }
   820  
   821  func testCassandraQueueV2ErrRangeDeleteMessagesQuery(t *testing.T, cluster *cassandra.TestCluster) {
   822  	q := newQueueV2Store(failingSession{
   823  		Session:        cluster.GetSession(),
   824  		failingQueries: []string{cassandra.TemplateRangeDeleteMessagesQuery},
   825  	})
   826  	ctx := context.Background()
   827  	queueType := persistence.QueueTypeHistoryNormal
   828  	queueName := "test-queue-" + t.Name()
   829  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   830  		QueueType: queueType,
   831  		QueueName: queueName,
   832  	})
   833  	require.NoError(t, err)
   834  	persistencetest.EnqueueMessagesForDelete(t, q, queueName, queueType)
   835  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID)
   836  	require.Error(t, err)
   837  	assert.ErrorAs(t, err, new(*serviceerror.Unavailable))
   838  	assert.ErrorContains(t, err, assert.AnError.Error())
   839  	assert.ErrorContains(t, err, "QueueV2RangeDeleteMessages")
   840  }
   841  
   842  func testCassandraQueueV2MinMessageIDOptimization(t *testing.T, cluster *cassandra.TestCluster) {
   843  	session := &recordingSession{
   844  		Session: cluster.GetSession(),
   845  	}
   846  	q := newQueueV2Store(session)
   847  	ctx := context.Background()
   848  	queueType := persistence.QueueTypeHistoryNormal
   849  	queueName := "test-queue-" + t.Name()
   850  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   851  		QueueType: queueType,
   852  		QueueName: queueName,
   853  	})
   854  	require.NoError(t, err)
   855  	for i := 0; i < 2; i++ {
   856  		_, err = persistencetest.EnqueueMessage(context.Background(), q, queueType, queueName)
   857  		require.NoError(t, err)
   858  	}
   859  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID)
   860  	require.NoError(t, err)
   861  	pageSize := 10
   862  	response, err := q.ReadMessages(ctx, &persistence.InternalReadMessagesRequest{
   863  		QueueType: queueType,
   864  		QueueName: queueName,
   865  		PageSize:  pageSize,
   866  	})
   867  	require.NoError(t, err)
   868  	require.Len(t, response.Messages, 1)
   869  	assert.Equal(t, int64(persistence.FirstQueueMessageID+1), response.Messages[0].MetaData.ID)
   870  	var lastReadStmt *statement
   871  	for _, stmt := range session.statements {
   872  		if stmt.query == cassandra.TemplateGetMessagesQuery {
   873  			stmt := stmt
   874  			lastReadStmt = &stmt
   875  		}
   876  	}
   877  	require.NotNil(t, lastReadStmt, "expected to find a query to get messages")
   878  	args := lastReadStmt.args
   879  	require.Len(t, args, 5)
   880  	assert.Equal(t, queueType, args[0])
   881  	assert.Equal(t, queueName, args[1])
   882  	assert.Equal(t, 0, args[2])
   883  	assert.Equal(t, persistence.FirstQueueMessageID+1, args[3], "We should skip the first "+
   884  		"message ID because we deleted it")
   885  	assert.Equal(t, pageSize, args[4])
   886  }
   887  
   888  func testCassandraQueueV2ConcurrentRangeDeleteMessages(t *testing.T, cluster *cassandra.TestCluster) {
   889  	// This test simulates a race condition between two RangeDeleteMessages calls. First, we enqueue 3 messages, then we
   890  	// start a request to delete the first message, and then we start another request to delete the first two messages.
   891  	// We have two cases, one where the first request to delete the first message is the leader and one where the second
   892  	// request is the leader. Both requests rendezvous at the query to update queue metadata, but the leader query goes
   893  	// first, and the follower query goes only after the leader query has completely finished. In the first case, the
   894  	// first request should succeed and the second request should fail with a conflict error. In the second case, the
   895  	// second request should succeed and the first request should fail with a conflict error. In both cases, only the
   896  	// third message should be in the queue. More importantly, after we retry the failing request, it should succeed,
   897  	// and the queue metadata should now record the min_message_id as 2.
   898  
   899  	for _, tc := range []struct {
   900  		name                  string
   901  		smallerDeleteIsLeader bool
   902  	}{
   903  		{
   904  			name:                  "smaller delete goes first",
   905  			smallerDeleteIsLeader: true,
   906  		},
   907  		{
   908  			name:                  "larger delete goes first",
   909  			smallerDeleteIsLeader: false,
   910  		},
   911  	} {
   912  		tc := tc
   913  		t.Run(tc.name, func(t *testing.T) {
   914  			t.Parallel()
   915  
   916  			// Make two queue stores to simulate the leader and follower queries. We use two separate queue stores
   917  			// because it makes it easier to control which query goes first when they have separate blockingSession
   918  			// instances.
   919  			qs := make([]rangeDeleteTestQueue, 2)
   920  			for i, q := range qs {
   921  				// We need to use a blocking session here because we need to block the query to update the queue
   922  				// metadata.
   923  				q.session = &blockingSession{
   924  					Session:          cluster.GetSession(),
   925  					queryToBlockOn:   cassandra.TemplateUpdateQueueMetadataQuery,
   926  					queryStarted:     make(chan struct{}, 1),
   927  					queryCanContinue: make(chan struct{}, 1),
   928  				}
   929  				q.QueueV2 = newQueueV2Store(q.session)
   930  				q.deleteErrs = make(chan error, 1)
   931  				q.maxIDToDelete = persistence.FirstQueueMessageID + i
   932  				qs[i] = q
   933  			}
   934  
   935  			// Create the queue
   936  			ctx := context.Background()
   937  			queueType := persistence.QueueTypeHistoryNormal
   938  			queueName := "test-queue-" + t.Name()
   939  			_, err := qs[0].CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
   940  				QueueType: queueType,
   941  				QueueName: queueName,
   942  			})
   943  			require.NoError(t, err)
   944  
   945  			// Enqueue 3 messages
   946  			for i := 0; i < 3; i++ {
   947  				_, err := persistencetest.EnqueueMessage(ctx, qs[0].QueueV2, queueType, queueName)
   948  				require.NoError(t, err)
   949  			}
   950  
   951  			// Start both RangeDeleteMessages call
   952  			for _, q := range qs {
   953  				q := q
   954  				go func() {
   955  					err := deleteMessages(ctx, q.QueueV2, queueType, queueName, q.maxIDToDelete)
   956  					q.deleteErrs <- err
   957  				}()
   958  			}
   959  
   960  			// Wait for both queries to start
   961  			for i := 0; i < 2; i++ {
   962  				<-qs[i].session.queryStarted
   963  			}
   964  
   965  			// Choose which query will be the leader and which will be the follower
   966  			var leader, follower *rangeDeleteTestQueue
   967  			if tc.smallerDeleteIsLeader {
   968  				leader = &qs[0]
   969  				follower = &qs[1]
   970  			} else {
   971  				leader = &qs[1]
   972  				follower = &qs[0]
   973  			}
   974  
   975  			// Let the leader query finish
   976  			close(leader.session.queryCanContinue)
   977  			err = <-leader.deleteErrs
   978  			require.NoError(t, err)
   979  
   980  			// Let the follower query finish
   981  			close(follower.session.queryCanContinue)
   982  			err = <-follower.deleteErrs
   983  
   984  			// Verify that the follower query failed
   985  			require.Error(t, err)
   986  			assert.ErrorIs(t, err, cassandra.ErrUpdateQueueConflict)
   987  			assert.ErrorContains(t, err, strconv.Itoa(int(queueType)))
   988  			assert.ErrorContains(t, err, queueName)
   989  
   990  			// Verify that the queue metadata was updated by the leader query
   991  			q, err := cassandra.GetQueue(ctx, qs[0].session, queueName, queueType)
   992  			require.NoError(t, err)
   993  			require.Len(t, q.Metadata.Partitions, 1)
   994  			if tc.smallerDeleteIsLeader {
   995  				// The smaller delete should have updated the min message ID to 1 because it succeeded, and the follower
   996  				// query received a conflict. However, the follower query will get retried, so the min message ID will
   997  				// eventually be updated correctly.
   998  				assert.Equal(t, int64(persistence.FirstQueueMessageID+1), q.Metadata.Partitions[0].MinMessageId)
   999  			} else {
  1000  				// The bigger delete should have updated the min message ID to 2 because it succeeded, so the min
  1001  				// message ID is already correct. However, the follower query will still get retried, so we need to
  1002  				// verify later that this retry does not change the min message ID to 1.
  1003  				assert.Equal(t, int64(persistence.FirstQueueMessageID+2), q.Metadata.Partitions[0].MinMessageId)
  1004  			}
  1005  
  1006  			// Retry the follower query. Note that this would fail if it actually tried to update the queue metadata
  1007  			// since we haven't unblocked it, so this implicitly tests that both operations are idempotent.
  1008  			err = deleteMessages(ctx, follower.QueueV2, queueType, queueName, follower.maxIDToDelete)
  1009  			require.NoError(t, err)
  1010  
  1011  			// Verify that the queue metadata was updated to reflect the new min message ID
  1012  			q, err = cassandra.GetQueue(ctx, qs[0].session, queueName, queueType)
  1013  			require.NoError(t, err)
  1014  			require.Len(t, q.Metadata.Partitions, 1)
  1015  			assert.Equal(t, int64(persistence.FirstQueueMessageID+2), q.Metadata.Partitions[0].MinMessageId)
  1016  
  1017  			// Verify that the first two messages were deleted no matter which query was the leader
  1018  			response, err := qs[0].ReadMessages(ctx, &persistence.InternalReadMessagesRequest{
  1019  				QueueType: queueType,
  1020  				QueueName: queueName,
  1021  				PageSize:  10,
  1022  			})
  1023  			require.NoError(t, err)
  1024  			require.Len(t, response.Messages, 1)
  1025  			assert.Equal(t, int64(persistence.FirstQueueMessageID+2), response.Messages[0].MetaData.ID)
  1026  		})
  1027  	}
  1028  }
  1029  
  1030  func testCassandraQueueV2MultiplePartitionsRangeDelete(t *testing.T, cluster *cassandra.TestCluster) {
  1031  	// Manually insert a row into the queues table that has multiple partitions and then verify that we gracefully
  1032  	// handle the error when we try to range delete messages from this queue.
  1033  
  1034  	session := cluster.GetSession()
  1035  	logger := &testLogger{}
  1036  	q := newQueueV2Store(session, func(params *testQueueParams) {
  1037  		params.logger = logger
  1038  	})
  1039  	queueType := persistence.QueueTypeHistoryNormal
  1040  	queueName := "test-queue-" + t.Name()
  1041  	insertQueueMetadataWithMultiplePartitions(t, session, queueType, queueName)
  1042  	err := deleteMessages(context.Background(), q, queueType, queueName, 1)
  1043  	require.Error(t, err)
  1044  	assert.ErrorContains(t, err, "partitions")
  1045  }
  1046  
  1047  func testCassandraQueueV2MultiplePartitionsReadMessages(t *testing.T, cluster *cassandra.TestCluster) {
  1048  	// Manually insert a row into the queues table that has multiple partitions and then verify that we gracefully
  1049  	// handle the error when we try to read messages from this queue.
  1050  
  1051  	session := cluster.GetSession()
  1052  	logger := &testLogger{}
  1053  	q := newQueueV2Store(session, func(params *testQueueParams) {
  1054  		params.logger = logger
  1055  	})
  1056  	queueType := persistence.QueueTypeHistoryNormal
  1057  	queueName := "test-queue-" + t.Name()
  1058  	insertQueueMetadataWithMultiplePartitions(t, session, queueType, queueName)
  1059  	_, err := q.ReadMessages(context.Background(), &persistence.InternalReadMessagesRequest{
  1060  		QueueType: queueType,
  1061  		QueueName: queueName,
  1062  		PageSize:  1,
  1063  	})
  1064  	require.Error(t, err)
  1065  	assert.ErrorContains(t, err, "partitions")
  1066  }
  1067  
  1068  func testCassandraQueueV2MultiplePartitionsListQueues(t *testing.T, cluster *cassandra.TestCluster) {
  1069  	// Manually insert a row into the queues table that has multiple partitions and then verify that we gracefully
  1070  	// handle the error when we try to list queues.
  1071  
  1072  	session := cluster.GetSession()
  1073  	logger := &testLogger{}
  1074  	q := newQueueV2Store(session, func(params *testQueueParams) {
  1075  		params.logger = logger
  1076  	})
  1077  	queueType := persistence.QueueTypeHistoryNormal
  1078  	queueName := "test-queue-" + t.Name()
  1079  	insertQueueMetadataWithMultiplePartitions(t, session, queueType, queueName)
  1080  	_, err := q.ListQueues(context.Background(), &persistence.InternalListQueuesRequest{
  1081  		QueueType: queueType,
  1082  		PageSize:  100,
  1083  	})
  1084  	require.Error(t, err)
  1085  	assert.ErrorContains(t, err, "partitions")
  1086  }
  1087  
  1088  func testCassandraQueueV2RangeDeleteUpperBoundHigherThanMaxMessageID(t *testing.T, cluster *cassandra.TestCluster) {
  1089  	q := newQueueV2Store(cluster.GetSession())
  1090  	ctx := context.Background()
  1091  	queueType := persistence.QueueTypeHistoryNormal
  1092  	queueName := "test-queue-" + t.Name()
  1093  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
  1094  		QueueType: queueType,
  1095  		QueueName: queueName,
  1096  	})
  1097  	require.NoError(t, err)
  1098  	_, err = persistencetest.EnqueueMessage(ctx, q, queueType, queueName)
  1099  	require.NoError(t, err)
  1100  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID+2)
  1101  	require.NoError(t, err)
  1102  	res, err := persistencetest.EnqueueMessage(ctx, q, queueType, queueName)
  1103  	require.NoError(t, err)
  1104  	assert.Equal(t, int64(persistence.FirstQueueMessageID+1), res.Metadata.ID)
  1105  }
  1106  
  1107  func testCassandraQueueV2RepeatedRangeDelete(t *testing.T, cluster *cassandra.TestCluster) {
  1108  	// We never delete the last message from the queue. However, on a subsequent delete, the previous message with the
  1109  	// min_message_id of the queue should be deleted. This test verifies that.
  1110  
  1111  	q := newQueueV2Store(cluster.GetSession())
  1112  	ctx := context.Background()
  1113  	queueType := persistence.QueueTypeHistoryNormal
  1114  	queueName := "test-queue-" + t.Name()
  1115  	_, err := q.CreateQueue(ctx, &persistence.InternalCreateQueueRequest{
  1116  		QueueType: queueType,
  1117  		QueueName: queueName,
  1118  	})
  1119  	require.NoError(t, err)
  1120  	numMessages := 3
  1121  	for i := 0; i < numMessages; i++ {
  1122  		_, err := persistencetest.EnqueueMessage(ctx, q, queueType, queueName)
  1123  		require.NoError(t, err)
  1124  	}
  1125  	numRemainingMessages := getNumMessages(t, cluster, queueType, queueName, numMessages)
  1126  	assert.Equal(t, 3, numRemainingMessages)
  1127  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID)
  1128  	require.NoError(t, err)
  1129  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID+1)
  1130  	require.NoError(t, err)
  1131  	err = deleteMessages(ctx, q, queueType, queueName, persistence.FirstQueueMessageID+2)
  1132  	require.NoError(t, err)
  1133  	numRemainingMessages = getNumMessages(t, cluster, queueType, queueName, numMessages)
  1134  	assert.Equal(t, 1, numRemainingMessages, "expected only one message to remain in the queue"+
  1135  		" because we never delete the last message, but the first two messages should have been deleted")
  1136  }
  1137  
  1138  func getNumMessages(
  1139  	t *testing.T,
  1140  	cluster *cassandra.TestCluster,
  1141  	queueType persistence.QueueV2Type,
  1142  	queueName string,
  1143  	numMessages int,
  1144  ) int {
  1145  	// We query the database directly here to get the actual count of messages deleted. If we just rely on the store
  1146  	// method to read messages, that would indicate that we're updating min_message_id correctly, but it wouldn't verify
  1147  	// that any messages less than min_message_id are actually deleted from the database.
  1148  
  1149  	iter := cluster.GetSession().Query(
  1150  		cassandra.TemplateGetMessagesQuery,
  1151  		queueType,
  1152  		queueName,
  1153  		0,                               // partition
  1154  		persistence.FirstQueueMessageID, // minMessageID
  1155  		numMessages,                     // limit
  1156  	).Iter()
  1157  	numRemainingMessages := 0
  1158  	for iter.MapScan(map[string]interface{}{}) {
  1159  		numRemainingMessages++
  1160  	}
  1161  	require.NoError(t, iter.Close())
  1162  	return numRemainingMessages
  1163  }
  1164  
  1165  func newQueueV2Store(session gocql.Session, opts ...func(params *testQueueParams)) persistence.QueueV2 {
  1166  	p := testQueueParams{
  1167  		logger: log.NewTestLogger(),
  1168  	}
  1169  	for _, opt := range opts {
  1170  		opt(&p)
  1171  	}
  1172  	return cassandra.NewQueueV2Store(session, p.logger)
  1173  }
  1174  
  1175  func insertQueueMetadataWithMultiplePartitions(
  1176  	t *testing.T,
  1177  	session gocql.Session,
  1178  	queueType persistence.QueueV2Type,
  1179  	queueName string,
  1180  ) {
  1181  	t.Helper()
  1182  
  1183  	queuePB := persistencespb.Queue{
  1184  		Partitions: map[int32]*persistencespb.QueuePartition{
  1185  			0: {},
  1186  			1: {},
  1187  		},
  1188  	}
  1189  	bytes, _ := queuePB.Marshal()
  1190  	err := session.Query(
  1191  		cassandra.TemplateCreateQueueQuery,
  1192  		queueType,
  1193  		queueName,
  1194  		bytes,                               // payload
  1195  		enums.ENCODING_TYPE_PROTO3.String(), // payload encoding type
  1196  		0,                                   // version
  1197  	).Exec()
  1198  	require.NoError(t, err)
  1199  }
  1200  
  1201  func deleteMessages(
  1202  	ctx context.Context,
  1203  	q persistence.QueueV2,
  1204  	queueType persistence.QueueV2Type,
  1205  	queueName string,
  1206  	maxID int,
  1207  ) error {
  1208  	_, err := q.RangeDeleteMessages(ctx, &persistence.InternalRangeDeleteMessagesRequest{
  1209  		QueueType: queueType,
  1210  		QueueName: queueName,
  1211  		InclusiveMaxMessageMetadata: persistence.MessageMetadata{
  1212  			ID: int64(maxID),
  1213  		},
  1214  	})
  1215  
  1216  	return err
  1217  }