go.temporal.io/server@v1.23.0/common/persistence/cassandra/matching_task_store.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 cassandra
    26  
    27  import (
    28  	"context"
    29  	"fmt"
    30  	"strings"
    31  	"time"
    32  
    33  	enumspb "go.temporal.io/api/enums/v1"
    34  	"go.temporal.io/api/serviceerror"
    35  	"google.golang.org/protobuf/types/known/timestamppb"
    36  
    37  	"go.temporal.io/server/common/convert"
    38  	"go.temporal.io/server/common/log"
    39  	p "go.temporal.io/server/common/persistence"
    40  	"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
    41  	"go.temporal.io/server/common/primitives/timestamp"
    42  )
    43  
    44  const (
    45  	templateCreateTaskQuery = `INSERT INTO tasks (` +
    46  		`namespace_id, task_queue_name, task_queue_type, type, task_id, task, task_encoding) ` +
    47  		`VALUES(?, ?, ?, ?, ?, ?, ?)`
    48  
    49  	templateCreateTaskWithTTLQuery = `INSERT INTO tasks (` +
    50  		`namespace_id, task_queue_name, task_queue_type, type, task_id, task, task_encoding) ` +
    51  		`VALUES(?, ?, ?, ?, ?, ?, ?) USING TTL ?`
    52  
    53  	templateGetTasksQuery = `SELECT task_id, task, task_encoding ` +
    54  		`FROM tasks ` +
    55  		`WHERE namespace_id = ? ` +
    56  		`and task_queue_name = ? ` +
    57  		`and task_queue_type = ? ` +
    58  		`and type = ? ` +
    59  		`and task_id >= ? ` +
    60  		`and task_id < ?`
    61  
    62  	templateCompleteTaskQuery = `DELETE FROM tasks ` +
    63  		`WHERE namespace_id = ? ` +
    64  		`and task_queue_name = ? ` +
    65  		`and task_queue_type = ? ` +
    66  		`and type = ? ` +
    67  		`and task_id = ?`
    68  
    69  	templateCompleteTasksLessThanQuery = `DELETE FROM tasks ` +
    70  		`WHERE namespace_id = ? ` +
    71  		`AND task_queue_name = ? ` +
    72  		`AND task_queue_type = ? ` +
    73  		`AND type = ? ` +
    74  		`AND task_id < ? `
    75  
    76  	templateGetTaskQueueQuery = `SELECT ` +
    77  		`range_id, ` +
    78  		`task_queue, ` +
    79  		`task_queue_encoding ` +
    80  		`FROM tasks ` +
    81  		`WHERE namespace_id = ? ` +
    82  		`and task_queue_name = ? ` +
    83  		`and task_queue_type = ? ` +
    84  		`and type = ? ` +
    85  		`and task_id = ?`
    86  
    87  	templateInsertTaskQueueQuery = `INSERT INTO tasks (` +
    88  		`namespace_id, ` +
    89  		`task_queue_name, ` +
    90  		`task_queue_type, ` +
    91  		`type, ` +
    92  		`task_id, ` +
    93  		`range_id, ` +
    94  		`task_queue, ` +
    95  		`task_queue_encoding ` +
    96  		`) VALUES (?, ?, ?, ?, ?, ?, ?, ?) IF NOT EXISTS`
    97  
    98  	templateUpdateTaskQueueQuery = `UPDATE tasks SET ` +
    99  		`range_id = ?, ` +
   100  		`task_queue = ?, ` +
   101  		`task_queue_encoding = ? ` +
   102  		`WHERE namespace_id = ? ` +
   103  		`and task_queue_name = ? ` +
   104  		`and task_queue_type = ? ` +
   105  		`and type = ? ` +
   106  		`and task_id = ? ` +
   107  		`IF range_id = ?`
   108  
   109  	templateUpdateTaskQueueQueryWithTTLPart1 = `INSERT INTO tasks (` +
   110  		`namespace_id, ` +
   111  		`task_queue_name, ` +
   112  		`task_queue_type, ` +
   113  		`type, ` +
   114  		`task_id ` +
   115  		`) VALUES (?, ?, ?, ?, ?) USING TTL ?`
   116  
   117  	templateUpdateTaskQueueQueryWithTTLPart2 = `UPDATE tasks USING TTL ? SET ` +
   118  		`range_id = ?, ` +
   119  		`task_queue = ?, ` +
   120  		`task_queue_encoding = ? ` +
   121  		`WHERE namespace_id = ? ` +
   122  		`and task_queue_name = ? ` +
   123  		`and task_queue_type = ? ` +
   124  		`and type = ? ` +
   125  		`and task_id = ? ` +
   126  		`IF range_id = ?`
   127  
   128  	templateDeleteTaskQueueQuery = `DELETE FROM tasks ` +
   129  		`WHERE namespace_id = ? ` +
   130  		`AND task_queue_name = ? ` +
   131  		`AND task_queue_type = ? ` +
   132  		`AND type = ? ` +
   133  		`AND task_id = ? ` +
   134  		`IF range_id = ?`
   135  
   136  	templateGetTaskQueueUserDataQuery = `SELECT data, data_encoding, version
   137  	    FROM task_queue_user_data
   138  		WHERE namespace_id = ? AND build_id = ''
   139  		AND task_queue_name = ?`
   140  
   141  	templateUpdateTaskQueueUserDataQuery = `UPDATE task_queue_user_data SET
   142  		data = ?,
   143  		data_encoding = ?,
   144  		version = ?
   145  		WHERE namespace_id = ?
   146  		AND build_id = ''
   147  		AND task_queue_name = ?
   148  		IF version = ?`
   149  
   150  	templateInsertTaskQueueUserDataQuery = `INSERT INTO task_queue_user_data
   151  		(namespace_id, build_id, task_queue_name, data, data_encoding, version) VALUES
   152  		(?           , ''      , ?              , ?   , ?            , 1      ) IF NOT EXISTS`
   153  
   154  	templateInsertBuildIdTaskQueueMappingQuery = `INSERT INTO task_queue_user_data
   155  	(namespace_id, build_id, task_queue_name) VALUES
   156  	(?           , ?       , ?)`
   157  	templateDeleteBuildIdTaskQueueMappingQuery = `DELETE FROM task_queue_user_data
   158  	WHERE namespace_id = ? AND build_id = ? AND task_queue_name = ?`
   159  	templateListTaskQueueUserDataQuery       = `SELECT task_queue_name, data, data_encoding, version FROM task_queue_user_data WHERE namespace_id = ? AND build_id = ''`
   160  	templateListTaskQueueNamesByBuildIdQuery = `SELECT task_queue_name FROM task_queue_user_data WHERE namespace_id = ? AND build_id = ?`
   161  	templateCountTaskQueueByBuildIdQuery     = `SELECT COUNT(*) FROM task_queue_user_data WHERE namespace_id = ? AND build_id = ?`
   162  
   163  	// Not much of a need to make this configurable, we're just reading some strings
   164  	listTaskQueueNamesByBuildIdPageSize = 100
   165  )
   166  
   167  type (
   168  	MatchingTaskStore struct {
   169  		Session gocql.Session
   170  		Logger  log.Logger
   171  	}
   172  )
   173  
   174  func NewMatchingTaskStore(
   175  	session gocql.Session,
   176  	logger log.Logger,
   177  ) *MatchingTaskStore {
   178  	return &MatchingTaskStore{
   179  		Session: session,
   180  		Logger:  logger,
   181  	}
   182  }
   183  
   184  func (d *MatchingTaskStore) CreateTaskQueue(
   185  	ctx context.Context,
   186  	request *p.InternalCreateTaskQueueRequest,
   187  ) error {
   188  	query := d.Session.Query(templateInsertTaskQueueQuery,
   189  		request.NamespaceID,
   190  		request.TaskQueue,
   191  		request.TaskType,
   192  		rowTypeTaskQueue,
   193  		taskQueueTaskID,
   194  		request.RangeID,
   195  		request.TaskQueueInfo.Data,
   196  		request.TaskQueueInfo.EncodingType.String(),
   197  	).WithContext(ctx)
   198  
   199  	previous := make(map[string]interface{})
   200  	applied, err := query.MapScanCAS(previous)
   201  	if err != nil {
   202  		return gocql.ConvertError("CreateTaskQueue", err)
   203  	}
   204  
   205  	if !applied {
   206  		previousRangeID := previous["range_id"]
   207  		return &p.ConditionFailedError{
   208  			Msg: fmt.Sprintf("CreateTaskQueue: TaskQueue:%v, TaskQueueType:%v, PreviousRangeID:%v",
   209  				request.TaskQueue, request.TaskType, previousRangeID),
   210  		}
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  func (d *MatchingTaskStore) GetTaskQueue(
   217  	ctx context.Context,
   218  	request *p.InternalGetTaskQueueRequest,
   219  ) (*p.InternalGetTaskQueueResponse, error) {
   220  	query := d.Session.Query(templateGetTaskQueueQuery,
   221  		request.NamespaceID,
   222  		request.TaskQueue,
   223  		request.TaskType,
   224  		rowTypeTaskQueue,
   225  		taskQueueTaskID,
   226  	).WithContext(ctx)
   227  
   228  	var rangeID int64
   229  	var tlBytes []byte
   230  	var tlEncoding string
   231  	if err := query.Scan(&rangeID, &tlBytes, &tlEncoding); err != nil {
   232  		return nil, gocql.ConvertError("GetTaskQueue", err)
   233  	}
   234  
   235  	return &p.InternalGetTaskQueueResponse{
   236  		RangeID:       rangeID,
   237  		TaskQueueInfo: p.NewDataBlob(tlBytes, tlEncoding),
   238  	}, nil
   239  }
   240  
   241  // UpdateTaskQueue update task queue
   242  func (d *MatchingTaskStore) UpdateTaskQueue(
   243  	ctx context.Context,
   244  	request *p.InternalUpdateTaskQueueRequest,
   245  ) (*p.UpdateTaskQueueResponse, error) {
   246  	var err error
   247  	var applied bool
   248  	previous := make(map[string]interface{})
   249  	if request.TaskQueueKind == enumspb.TASK_QUEUE_KIND_STICKY { // if task_queue is sticky, then update with TTL
   250  		if request.ExpiryTime == nil {
   251  			return nil, serviceerror.NewInternal("ExpiryTime cannot be nil for sticky task queue")
   252  		}
   253  		expiryTTL := convert.Int64Ceil(time.Until(timestamp.TimeValue(request.ExpiryTime)).Seconds())
   254  		if expiryTTL >= maxCassandraTTL {
   255  			expiryTTL = maxCassandraTTL
   256  		}
   257  		batch := d.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
   258  		batch.Query(templateUpdateTaskQueueQueryWithTTLPart1,
   259  			request.NamespaceID,
   260  			request.TaskQueue,
   261  			request.TaskType,
   262  			rowTypeTaskQueue,
   263  			taskQueueTaskID,
   264  			expiryTTL,
   265  		)
   266  		batch.Query(templateUpdateTaskQueueQueryWithTTLPart2,
   267  			expiryTTL,
   268  			request.RangeID,
   269  			request.TaskQueueInfo.Data,
   270  			request.TaskQueueInfo.EncodingType.String(),
   271  			request.NamespaceID,
   272  			request.TaskQueue,
   273  			request.TaskType,
   274  			rowTypeTaskQueue,
   275  			taskQueueTaskID,
   276  			request.PrevRangeID,
   277  		)
   278  		applied, _, err = d.Session.MapExecuteBatchCAS(batch, previous)
   279  	} else {
   280  		query := d.Session.Query(templateUpdateTaskQueueQuery,
   281  			request.RangeID,
   282  			request.TaskQueueInfo.Data,
   283  			request.TaskQueueInfo.EncodingType.String(),
   284  			request.NamespaceID,
   285  			request.TaskQueue,
   286  			request.TaskType,
   287  			rowTypeTaskQueue,
   288  			taskQueueTaskID,
   289  			request.PrevRangeID,
   290  		).WithContext(ctx)
   291  		applied, err = query.MapScanCAS(previous)
   292  	}
   293  
   294  	if err != nil {
   295  		return nil, gocql.ConvertError("UpdateTaskQueue", err)
   296  	}
   297  
   298  	if !applied {
   299  		var columns []string
   300  		for k, v := range previous {
   301  			columns = append(columns, fmt.Sprintf("%s=%v", k, v))
   302  		}
   303  
   304  		return nil, &p.ConditionFailedError{
   305  			Msg: fmt.Sprintf("Failed to update task queue. name: %v, type: %v, rangeID: %v, columns: (%v)",
   306  				request.TaskQueue, request.TaskType, request.RangeID, strings.Join(columns, ",")),
   307  		}
   308  	}
   309  
   310  	return &p.UpdateTaskQueueResponse{}, nil
   311  }
   312  
   313  func (d *MatchingTaskStore) ListTaskQueue(
   314  	_ context.Context,
   315  	_ *p.ListTaskQueueRequest,
   316  ) (*p.InternalListTaskQueueResponse, error) {
   317  	return nil, serviceerror.NewUnavailable("unsupported operation")
   318  }
   319  
   320  func (d *MatchingTaskStore) DeleteTaskQueue(
   321  	ctx context.Context,
   322  	request *p.DeleteTaskQueueRequest,
   323  ) error {
   324  	query := d.Session.Query(
   325  		templateDeleteTaskQueueQuery,
   326  		request.TaskQueue.NamespaceID,
   327  		request.TaskQueue.TaskQueueName,
   328  		request.TaskQueue.TaskQueueType,
   329  		rowTypeTaskQueue,
   330  		taskQueueTaskID,
   331  		request.RangeID,
   332  	).WithContext(ctx)
   333  	previous := make(map[string]interface{})
   334  	applied, err := query.MapScanCAS(previous)
   335  	if err != nil {
   336  		return gocql.ConvertError("DeleteTaskQueue", err)
   337  	}
   338  	if !applied {
   339  		return &p.ConditionFailedError{
   340  			Msg: fmt.Sprintf("DeleteTaskQueue operation failed: expected_range_id=%v but found %+v", request.RangeID, previous),
   341  		}
   342  	}
   343  	return nil
   344  }
   345  
   346  // CreateTasks add tasks
   347  func (d *MatchingTaskStore) CreateTasks(
   348  	ctx context.Context,
   349  	request *p.InternalCreateTasksRequest,
   350  ) (*p.CreateTasksResponse, error) {
   351  	batch := d.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
   352  	namespaceID := request.NamespaceID
   353  	taskQueue := request.TaskQueue
   354  	taskQueueType := request.TaskType
   355  
   356  	for _, task := range request.Tasks {
   357  		ttl := GetTaskTTL(task.ExpiryTime)
   358  
   359  		if ttl <= 0 || ttl > maxCassandraTTL {
   360  			batch.Query(templateCreateTaskQuery,
   361  				namespaceID,
   362  				taskQueue,
   363  				taskQueueType,
   364  				rowTypeTask,
   365  				task.TaskId,
   366  				task.Task.Data,
   367  				task.Task.EncodingType.String())
   368  		} else {
   369  			batch.Query(templateCreateTaskWithTTLQuery,
   370  				namespaceID,
   371  				taskQueue,
   372  				taskQueueType,
   373  				rowTypeTask,
   374  				task.TaskId,
   375  				task.Task.Data,
   376  				task.Task.EncodingType.String(),
   377  				ttl)
   378  		}
   379  	}
   380  
   381  	// The following query is used to ensure that range_id didn't change
   382  	batch.Query(templateUpdateTaskQueueQuery,
   383  		request.RangeID,
   384  		request.TaskQueueInfo.Data,
   385  		request.TaskQueueInfo.EncodingType.String(),
   386  		namespaceID,
   387  		taskQueue,
   388  		taskQueueType,
   389  		rowTypeTaskQueue,
   390  		taskQueueTaskID,
   391  		request.RangeID,
   392  	)
   393  
   394  	previous := make(map[string]interface{})
   395  	applied, _, err := d.Session.MapExecuteBatchCAS(batch, previous)
   396  	if err != nil {
   397  		return nil, gocql.ConvertError("CreateTasks", err)
   398  	}
   399  	if !applied {
   400  		rangeID := previous["range_id"]
   401  		return nil, &p.ConditionFailedError{
   402  			Msg: fmt.Sprintf("Failed to create task. TaskQueue: %v, taskQueueType: %v, rangeID: %v, db rangeID: %v",
   403  				taskQueue, taskQueueType, request.RangeID, rangeID),
   404  		}
   405  	}
   406  
   407  	return &p.CreateTasksResponse{}, nil
   408  }
   409  
   410  func GetTaskTTL(expireTime *timestamppb.Timestamp) int64 {
   411  	var ttl int64 = 0
   412  	if expireTime != nil && !expireTime.AsTime().IsZero() {
   413  		expiryTtl := convert.Int64Ceil(time.Until(expireTime.AsTime()).Seconds())
   414  
   415  		// 0 means no ttl, we dont want that.
   416  		// Todo: Come back and correctly ignore expired in-memory tasks before persisting
   417  		if expiryTtl < 1 {
   418  			expiryTtl = 1
   419  		}
   420  
   421  		ttl = expiryTtl
   422  	}
   423  	return ttl
   424  }
   425  
   426  // GetTasks get a task
   427  func (d *MatchingTaskStore) GetTasks(
   428  	ctx context.Context,
   429  	request *p.GetTasksRequest,
   430  ) (*p.InternalGetTasksResponse, error) {
   431  	// Reading taskqueue tasks need to be quorum level consistent, otherwise we could lose tasks
   432  	query := d.Session.Query(templateGetTasksQuery,
   433  		request.NamespaceID,
   434  		request.TaskQueue,
   435  		request.TaskType,
   436  		rowTypeTask,
   437  		request.InclusiveMinTaskID,
   438  		request.ExclusiveMaxTaskID,
   439  	).WithContext(ctx)
   440  	iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
   441  
   442  	response := &p.InternalGetTasksResponse{}
   443  	task := make(map[string]interface{})
   444  	for iter.MapScan(task) {
   445  		_, ok := task["task_id"]
   446  		if !ok { // no tasks, but static column record returned
   447  			continue
   448  		}
   449  
   450  		rawTask, ok := task["task"]
   451  		if !ok {
   452  			return nil, newFieldNotFoundError("task", task)
   453  		}
   454  		taskVal, ok := rawTask.([]byte)
   455  		if !ok {
   456  			var byteSliceType []byte
   457  			return nil, newPersistedTypeMismatchError("task", byteSliceType, rawTask, task)
   458  
   459  		}
   460  
   461  		rawEncoding, ok := task["task_encoding"]
   462  		if !ok {
   463  			return nil, newFieldNotFoundError("task_encoding", task)
   464  		}
   465  		encodingVal, ok := rawEncoding.(string)
   466  		if !ok {
   467  			var byteSliceType []byte
   468  			return nil, newPersistedTypeMismatchError("task_encoding", byteSliceType, rawEncoding, task)
   469  		}
   470  		response.Tasks = append(response.Tasks, p.NewDataBlob(taskVal, encodingVal))
   471  
   472  		task = make(map[string]interface{}) // Reinitialize map as initialized fails on unmarshalling
   473  	}
   474  	if len(iter.PageState()) > 0 {
   475  		response.NextPageToken = iter.PageState()
   476  	}
   477  
   478  	if err := iter.Close(); err != nil {
   479  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetTasks operation failed. Error: %v", err))
   480  	}
   481  	return response, nil
   482  }
   483  
   484  // CompleteTask delete a task
   485  func (d *MatchingTaskStore) CompleteTask(
   486  	ctx context.Context,
   487  	request *p.CompleteTaskRequest,
   488  ) error {
   489  	tli := request.TaskQueue
   490  	query := d.Session.Query(templateCompleteTaskQuery,
   491  		tli.NamespaceID,
   492  		tli.TaskQueueName,
   493  		tli.TaskQueueType,
   494  		rowTypeTask,
   495  		request.TaskID,
   496  	).WithContext(ctx)
   497  
   498  	err := query.Exec()
   499  	if err != nil {
   500  		return gocql.ConvertError("CompleteTask", err)
   501  	}
   502  
   503  	return nil
   504  }
   505  
   506  // CompleteTasksLessThan deletes all tasks less than the given task id. This API ignores the
   507  // Limit request parameter i.e. either all tasks leq the task_id will be deleted or an error will
   508  // be returned to the caller
   509  func (d *MatchingTaskStore) CompleteTasksLessThan(
   510  	ctx context.Context,
   511  	request *p.CompleteTasksLessThanRequest,
   512  ) (int, error) {
   513  	query := d.Session.Query(
   514  		templateCompleteTasksLessThanQuery,
   515  		request.NamespaceID,
   516  		request.TaskQueueName,
   517  		request.TaskType,
   518  		rowTypeTask,
   519  		request.ExclusiveMaxTaskID,
   520  	).WithContext(ctx)
   521  	err := query.Exec()
   522  	if err != nil {
   523  		return 0, gocql.ConvertError("CompleteTasksLessThan", err)
   524  	}
   525  	return p.UnknownNumRowsAffected, nil
   526  }
   527  
   528  func (d *MatchingTaskStore) GetTaskQueueUserData(
   529  	ctx context.Context,
   530  	request *p.GetTaskQueueUserDataRequest,
   531  ) (*p.InternalGetTaskQueueUserDataResponse, error) {
   532  	query := d.Session.Query(templateGetTaskQueueUserDataQuery,
   533  		request.NamespaceID,
   534  		request.TaskQueue,
   535  	).WithContext(ctx)
   536  	var version int64
   537  	var userDataBytes []byte
   538  	var encoding string
   539  	if err := query.Scan(&userDataBytes, &encoding, &version); err != nil {
   540  		return nil, gocql.ConvertError("GetTaskQueueData", err)
   541  	}
   542  
   543  	return &p.InternalGetTaskQueueUserDataResponse{
   544  		Version:  version,
   545  		UserData: p.NewDataBlob(userDataBytes, encoding),
   546  	}, nil
   547  }
   548  
   549  func (d *MatchingTaskStore) UpdateTaskQueueUserData(
   550  	ctx context.Context,
   551  	request *p.InternalUpdateTaskQueueUserDataRequest,
   552  ) error {
   553  	batch := d.Session.NewBatch(gocql.UnloggedBatch).WithContext(ctx)
   554  
   555  	if request.Version == 0 {
   556  		batch.Query(templateInsertTaskQueueUserDataQuery,
   557  			request.NamespaceID,
   558  			request.TaskQueue,
   559  			request.UserData.Data,
   560  			request.UserData.EncodingType.String(),
   561  		)
   562  	} else {
   563  		batch.Query(templateUpdateTaskQueueUserDataQuery,
   564  			request.UserData.Data,
   565  			request.UserData.EncodingType.String(),
   566  			request.Version+1,
   567  			request.NamespaceID,
   568  			request.TaskQueue,
   569  			request.Version,
   570  		)
   571  	}
   572  	for _, buildId := range request.BuildIdsAdded {
   573  		batch.Query(templateInsertBuildIdTaskQueueMappingQuery, request.NamespaceID, buildId, request.TaskQueue)
   574  	}
   575  	for _, buildId := range request.BuildIdsRemoved {
   576  		batch.Query(templateDeleteBuildIdTaskQueueMappingQuery, request.NamespaceID, buildId, request.TaskQueue)
   577  	}
   578  
   579  	previous := make(map[string]interface{})
   580  	applied, iter, err := d.Session.MapExecuteBatchCAS(batch, previous)
   581  
   582  	if err != nil {
   583  		return gocql.ConvertError("UpdateTaskQueueUserData", err)
   584  	}
   585  
   586  	// We only care about the conflict in the first query
   587  	err = iter.Close()
   588  	if err != nil {
   589  		return gocql.ConvertError("UpdateTaskQueueUserData", err)
   590  	}
   591  
   592  	if !applied {
   593  		var columns []string
   594  		for k, v := range previous {
   595  			columns = append(columns, fmt.Sprintf("%s=%v", k, v))
   596  		}
   597  
   598  		return &p.ConditionFailedError{
   599  			Msg: fmt.Sprintf("Failed to update task queue. name: %v, version: %v, columns: (%v)",
   600  				request.TaskQueue, request.Version, strings.Join(columns, ",")),
   601  		}
   602  	}
   603  
   604  	return nil
   605  }
   606  
   607  func (d *MatchingTaskStore) ListTaskQueueUserDataEntries(ctx context.Context, request *p.ListTaskQueueUserDataEntriesRequest) (*p.InternalListTaskQueueUserDataEntriesResponse, error) {
   608  	query := d.Session.Query(templateListTaskQueueUserDataQuery, request.NamespaceID).WithContext(ctx)
   609  	iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
   610  
   611  	response := &p.InternalListTaskQueueUserDataEntriesResponse{}
   612  	row := make(map[string]interface{})
   613  	for iter.MapScan(row) {
   614  		taskQueueRaw, ok := row["task_queue_name"]
   615  		if !ok {
   616  			return nil, newFieldNotFoundError("task_queue_name", row)
   617  		}
   618  		taskQueue, ok := taskQueueRaw.(string)
   619  		if !ok {
   620  			return nil, newPersistedTypeMismatchError("task_queue_name", taskQueue, taskQueueRaw, row)
   621  		}
   622  
   623  		dataRaw, ok := row["data"]
   624  		if !ok {
   625  			return nil, newFieldNotFoundError("data", row)
   626  		}
   627  		data, ok := dataRaw.([]byte)
   628  		if !ok {
   629  			return nil, newPersistedTypeMismatchError("data", data, dataRaw, row)
   630  		}
   631  
   632  		dataEncodingRaw, ok := row["data_encoding"]
   633  		if !ok {
   634  			return nil, newFieldNotFoundError("data_encoding", row)
   635  		}
   636  		dataEncoding, ok := dataEncodingRaw.(string)
   637  		if !ok {
   638  			return nil, newPersistedTypeMismatchError("data_encoding", dataEncoding, dataEncodingRaw, row)
   639  		}
   640  
   641  		versionRaw, ok := row["version"]
   642  		if !ok {
   643  			return nil, newFieldNotFoundError("version", row)
   644  		}
   645  		version, ok := versionRaw.(int64)
   646  		if !ok {
   647  			return nil, newPersistedTypeMismatchError("version", version, versionRaw, row)
   648  		}
   649  
   650  		response.Entries = append(response.Entries, p.InternalTaskQueueUserDataEntry{TaskQueue: taskQueue, Data: p.NewDataBlob(data, dataEncoding), Version: version})
   651  
   652  		row = make(map[string]interface{}) // Reinitialize map as initialized fails on unmarshalling
   653  	}
   654  	if len(iter.PageState()) > 0 {
   655  		response.NextPageToken = iter.PageState()
   656  	}
   657  
   658  	if err := iter.Close(); err != nil {
   659  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("ListTaskQueueUserDataEntries operation failed. Error: %v", err))
   660  	}
   661  	return response, nil
   662  }
   663  
   664  func (d *MatchingTaskStore) GetTaskQueuesByBuildId(ctx context.Context, request *p.GetTaskQueuesByBuildIdRequest) ([]string, error) {
   665  	query := d.Session.Query(templateListTaskQueueNamesByBuildIdQuery, request.NamespaceID, request.BuildID).WithContext(ctx)
   666  	iter := query.PageSize(listTaskQueueNamesByBuildIdPageSize).Iter()
   667  
   668  	var taskQueues []string
   669  	row := make(map[string]interface{})
   670  
   671  	for {
   672  		for iter.MapScan(row) {
   673  			taskQueueRaw, ok := row["task_queue_name"]
   674  			if !ok {
   675  				return nil, newFieldNotFoundError("task_queue_name", row)
   676  			}
   677  			taskQueue, ok := taskQueueRaw.(string)
   678  			if !ok {
   679  				var stringType string
   680  				return nil, newPersistedTypeMismatchError("task_queue_name", stringType, taskQueueRaw, row)
   681  			}
   682  
   683  			taskQueues = append(taskQueues, taskQueue)
   684  
   685  			row = make(map[string]interface{}) // Reinitialize map as initialized fails on unmarshalling
   686  		}
   687  		if len(iter.PageState()) == 0 {
   688  			break
   689  		}
   690  	}
   691  
   692  	if err := iter.Close(); err != nil {
   693  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetTaskQueuesByBuildId operation failed. Error: %v", err))
   694  	}
   695  	return taskQueues, nil
   696  }
   697  
   698  func (d *MatchingTaskStore) CountTaskQueuesByBuildId(ctx context.Context, request *p.CountTaskQueuesByBuildIdRequest) (int, error) {
   699  	var count int
   700  	query := d.Session.Query(templateCountTaskQueueByBuildIdQuery, request.NamespaceID, request.BuildID).WithContext(ctx)
   701  	err := query.Scan(&count)
   702  	return count, err
   703  }
   704  
   705  func (d *MatchingTaskStore) GetName() string {
   706  	return cassandraPersistenceName
   707  }
   708  
   709  func (d *MatchingTaskStore) Close() {
   710  	if d.Session != nil {
   711  		d.Session.Close()
   712  	}
   713  }