go.temporal.io/server@v1.23.0/common/persistence/visibility/store/elasticsearch/visibility_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 elasticsearch
    26  
    27  import (
    28  	"bytes"
    29  	"context"
    30  	"encoding/base64"
    31  	"encoding/json"
    32  	"errors"
    33  	"fmt"
    34  	"io"
    35  	"math"
    36  	"strconv"
    37  	"strings"
    38  	"time"
    39  
    40  	"github.com/olivere/elastic/v7"
    41  	commonpb "go.temporal.io/api/common/v1"
    42  	enumspb "go.temporal.io/api/enums/v1"
    43  	"go.temporal.io/api/serviceerror"
    44  	"go.temporal.io/api/workflowservice/v1"
    45  
    46  	"go.temporal.io/server/common/dynamicconfig"
    47  	"go.temporal.io/server/common/metrics"
    48  	"go.temporal.io/server/common/namespace"
    49  	"go.temporal.io/server/common/persistence"
    50  	"go.temporal.io/server/common/persistence/visibility/manager"
    51  	"go.temporal.io/server/common/persistence/visibility/store"
    52  	"go.temporal.io/server/common/persistence/visibility/store/elasticsearch/client"
    53  	"go.temporal.io/server/common/persistence/visibility/store/query"
    54  	"go.temporal.io/server/common/searchattribute"
    55  )
    56  
    57  const (
    58  	PersistenceName = "elasticsearch"
    59  
    60  	delimiter                    = "~"
    61  	scrollKeepAliveInterval      = "1m"
    62  	pointInTimeKeepAliveInterval = "1m"
    63  )
    64  
    65  type (
    66  	visibilityStore struct {
    67  		esClient                       client.Client
    68  		index                          string
    69  		searchAttributesProvider       searchattribute.Provider
    70  		searchAttributesMapperProvider searchattribute.MapperProvider
    71  		processor                      Processor
    72  		processorAckTimeout            dynamicconfig.DurationPropertyFn
    73  		disableOrderByClause           dynamicconfig.BoolPropertyFnWithNamespaceFilter
    74  		enableManualPagination         dynamicconfig.BoolPropertyFnWithNamespaceFilter
    75  		metricsHandler                 metrics.Handler
    76  	}
    77  
    78  	visibilityPageToken struct {
    79  		SearchAfter []interface{}
    80  
    81  		// For ScanWorkflowExecutions API.
    82  		// For ES<7.10.0 and "oss" flavor.
    83  		ScrollID string
    84  		// For ES>=7.10.0 and "default" flavor.
    85  		PointInTimeID string
    86  	}
    87  
    88  	fieldSort struct {
    89  		name          string
    90  		desc          bool
    91  		missing_first bool
    92  	}
    93  )
    94  
    95  var _ store.VisibilityStore = (*visibilityStore)(nil)
    96  
    97  var (
    98  	errUnexpectedJSONFieldType = errors.New("unexpected JSON field type")
    99  
   100  	minTime         = time.Unix(0, 0).UTC()
   101  	maxTime         = time.Unix(0, math.MaxInt64).UTC()
   102  	maxStringLength = 32766
   103  
   104  	// Default sorter uses the sorting order defined in the index template.
   105  	// It is indirectly built so buildPaginationQuery can have access to
   106  	// the fields names to build the page query from the token.
   107  	defaultSorterFields = []fieldSort{
   108  		{searchattribute.CloseTime, true, true},
   109  		{searchattribute.StartTime, true, true},
   110  	}
   111  
   112  	defaultSorter = func() []elastic.Sorter {
   113  		ret := make([]elastic.Sorter, 0, len(defaultSorterFields))
   114  		for _, item := range defaultSorterFields {
   115  			fs := elastic.NewFieldSort(item.name)
   116  			if item.desc {
   117  				fs.Desc()
   118  			}
   119  			if item.missing_first {
   120  				fs.Missing("_first")
   121  			}
   122  			ret = append(ret, fs)
   123  		}
   124  		return ret
   125  	}()
   126  
   127  	docSorter = []elastic.Sorter{
   128  		elastic.SortByDoc{},
   129  	}
   130  )
   131  
   132  // NewVisibilityStore create a visibility store connecting to ElasticSearch
   133  func NewVisibilityStore(
   134  	esClient client.Client,
   135  	index string,
   136  	searchAttributesProvider searchattribute.Provider,
   137  	searchAttributesMapperProvider searchattribute.MapperProvider,
   138  	processor Processor,
   139  	processorAckTimeout dynamicconfig.DurationPropertyFn,
   140  	disableOrderByClause dynamicconfig.BoolPropertyFnWithNamespaceFilter,
   141  	enableManualPagination dynamicconfig.BoolPropertyFnWithNamespaceFilter,
   142  	metricsHandler metrics.Handler,
   143  ) *visibilityStore {
   144  
   145  	return &visibilityStore{
   146  		esClient:                       esClient,
   147  		index:                          index,
   148  		searchAttributesProvider:       searchAttributesProvider,
   149  		searchAttributesMapperProvider: searchAttributesMapperProvider,
   150  		processor:                      processor,
   151  		processorAckTimeout:            processorAckTimeout,
   152  		disableOrderByClause:           disableOrderByClause,
   153  		enableManualPagination:         enableManualPagination,
   154  		metricsHandler:                 metricsHandler.WithTags(metrics.OperationTag(metrics.ElasticsearchVisibility)),
   155  	}
   156  }
   157  
   158  func (s *visibilityStore) Close() {
   159  	// TODO (alex): visibilityStore shouldn't Stop processor. Processor should be stopped where it is created.
   160  	if s.processor != nil {
   161  		s.processor.Stop()
   162  	}
   163  }
   164  
   165  func (s *visibilityStore) GetName() string {
   166  	return PersistenceName
   167  }
   168  
   169  func (s *visibilityStore) GetIndexName() string {
   170  	return s.index
   171  }
   172  
   173  func (s *visibilityStore) ValidateCustomSearchAttributes(
   174  	searchAttributes map[string]any,
   175  ) (map[string]any, error) {
   176  	validatedSearchAttributes := make(map[string]any, len(searchAttributes))
   177  	var invalidValueErrs []error
   178  	for saName, saValue := range searchAttributes {
   179  		var err error
   180  		switch value := saValue.(type) {
   181  		case time.Time:
   182  			err = validateDatetime(value)
   183  		case []time.Time:
   184  			for _, item := range value {
   185  				if err = validateDatetime(item); err != nil {
   186  					break
   187  				}
   188  			}
   189  		case string:
   190  			err = validateString(value)
   191  		case []string:
   192  			for _, item := range value {
   193  				if err = validateString(item); err != nil {
   194  					break
   195  				}
   196  			}
   197  		}
   198  		if err != nil {
   199  			invalidValueErrs = append(invalidValueErrs, err)
   200  			continue
   201  		}
   202  		validatedSearchAttributes[saName] = saValue
   203  	}
   204  	var retError error
   205  	if len(invalidValueErrs) > 0 {
   206  		retError = store.NewVisibilityStoreInvalidValuesError(invalidValueErrs)
   207  	}
   208  	return validatedSearchAttributes, retError
   209  }
   210  
   211  func (s *visibilityStore) RecordWorkflowExecutionStarted(
   212  	ctx context.Context,
   213  	request *store.InternalRecordWorkflowExecutionStartedRequest,
   214  ) error {
   215  	visibilityTaskKey := getVisibilityTaskKey(request.ShardID, request.TaskID)
   216  	doc, err := s.generateESDoc(request.InternalVisibilityRequestBase, visibilityTaskKey)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	return s.addBulkIndexRequestAndWait(ctx, request.InternalVisibilityRequestBase, doc, visibilityTaskKey)
   222  }
   223  
   224  func (s *visibilityStore) RecordWorkflowExecutionClosed(
   225  	ctx context.Context,
   226  	request *store.InternalRecordWorkflowExecutionClosedRequest,
   227  ) error {
   228  	visibilityTaskKey := getVisibilityTaskKey(request.ShardID, request.TaskID)
   229  	doc, err := s.generateESDoc(request.InternalVisibilityRequestBase, visibilityTaskKey)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	doc[searchattribute.CloseTime] = request.CloseTime
   235  	doc[searchattribute.ExecutionDuration] = request.ExecutionDuration
   236  	doc[searchattribute.HistoryLength] = request.HistoryLength
   237  	doc[searchattribute.StateTransitionCount] = request.StateTransitionCount
   238  	doc[searchattribute.HistorySizeBytes] = request.HistorySizeBytes
   239  
   240  	return s.addBulkIndexRequestAndWait(ctx, request.InternalVisibilityRequestBase, doc, visibilityTaskKey)
   241  }
   242  
   243  func (s *visibilityStore) UpsertWorkflowExecution(
   244  	ctx context.Context,
   245  	request *store.InternalUpsertWorkflowExecutionRequest,
   246  ) error {
   247  	visibilityTaskKey := getVisibilityTaskKey(request.ShardID, request.TaskID)
   248  	doc, err := s.generateESDoc(request.InternalVisibilityRequestBase, visibilityTaskKey)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	return s.addBulkIndexRequestAndWait(ctx, request.InternalVisibilityRequestBase, doc, visibilityTaskKey)
   254  }
   255  
   256  func (s *visibilityStore) DeleteWorkflowExecution(
   257  	ctx context.Context,
   258  	request *manager.VisibilityDeleteWorkflowExecutionRequest,
   259  ) error {
   260  	docID := getDocID(request.WorkflowID, request.RunID)
   261  
   262  	bulkDeleteRequest := &client.BulkableRequest{
   263  		Index:       s.index,
   264  		ID:          docID,
   265  		Version:     request.TaskID,
   266  		RequestType: client.BulkableRequestTypeDelete,
   267  	}
   268  
   269  	return s.addBulkRequestAndWait(ctx, bulkDeleteRequest, docID)
   270  }
   271  
   272  func getDocID(workflowID string, runID string) string {
   273  	// From Elasticsearch doc: _id is limited to 512 bytes in size and larger values will be rejected.
   274  	const maxDocIDLength = 512
   275  	// Generally runID is guid and this should never be the case.
   276  	if len(runID)+len(delimiter) >= maxDocIDLength {
   277  		if len(runID) >= maxDocIDLength {
   278  			return runID[0:maxDocIDLength]
   279  		}
   280  		return runID[0 : maxDocIDLength-len(delimiter)]
   281  	}
   282  
   283  	if len(workflowID)+len(runID)+len(delimiter) > maxDocIDLength {
   284  		workflowID = workflowID[0 : maxDocIDLength-len(runID)-len(delimiter)]
   285  	}
   286  
   287  	return workflowID + delimiter + runID
   288  }
   289  
   290  func getVisibilityTaskKey(shardID int32, taskID int64) string {
   291  	return strconv.FormatInt(int64(shardID), 10) + delimiter + strconv.FormatInt(taskID, 10)
   292  }
   293  
   294  func (s *visibilityStore) addBulkIndexRequestAndWait(
   295  	ctx context.Context,
   296  	request *store.InternalVisibilityRequestBase,
   297  	esDoc map[string]interface{},
   298  	visibilityTaskKey string,
   299  ) error {
   300  	bulkIndexRequest := &client.BulkableRequest{
   301  		Index:       s.index,
   302  		ID:          getDocID(request.WorkflowID, request.RunID),
   303  		Version:     request.TaskID,
   304  		RequestType: client.BulkableRequestTypeIndex,
   305  		Doc:         esDoc,
   306  	}
   307  
   308  	return s.addBulkRequestAndWait(ctx, bulkIndexRequest, visibilityTaskKey)
   309  }
   310  
   311  func (s *visibilityStore) addBulkRequestAndWait(
   312  	_ context.Context,
   313  	bulkRequest *client.BulkableRequest,
   314  	visibilityTaskKey string,
   315  ) error {
   316  	s.checkProcessor()
   317  
   318  	// Add method is blocking. If bulk processor is busy flushing previous bulk, request will wait here.
   319  	ackF := s.processor.Add(bulkRequest, visibilityTaskKey)
   320  
   321  	// processorAckTimeout is a maximum duration for bulk processor to commit the bulk and unblock the `ackF`.
   322  	// Default value is 30s and this timeout should never have happened,
   323  	// because Elasticsearch must process a bulk within 30s.
   324  	// Parent context is not respected here because it has shorter timeout (3s),
   325  	// which might already expired here due to wait at Add method above.
   326  	ctx, cancel := context.WithTimeout(context.Background(), s.processorAckTimeout())
   327  	defer cancel()
   328  	ack, err := ackF.Get(ctx)
   329  
   330  	if err != nil {
   331  		if errors.Is(err, context.DeadlineExceeded) {
   332  			return &persistence.TimeoutError{Msg: fmt.Sprintf("visibility task %s timed out waiting for ACK after %v", visibilityTaskKey, s.processorAckTimeout())}
   333  		}
   334  		// Returns non-retryable Internal error here because these errors are unexpected.
   335  		// Visibility task processor retries all errors though, therefore new request will be generated for the same visibility task.
   336  		return serviceerror.NewInternal(fmt.Sprintf("visibility task %s received error %v", visibilityTaskKey, err))
   337  	}
   338  
   339  	if !ack {
   340  		// Returns retryable Unavailable error here because NACK from bulk processor
   341  		// means that this request wasn't processed successfully and needs to be retried.
   342  		// Visibility task processor retries all errors anyway, therefore new request will be generated for the same visibility task.
   343  		return serviceerror.NewUnavailable(fmt.Sprintf("visibility task %s received NACK", visibilityTaskKey))
   344  	}
   345  	return nil
   346  }
   347  
   348  func (s *visibilityStore) checkProcessor() {
   349  	if s.processor == nil {
   350  		// must be bug, check history setup
   351  		panic("Elasticsearch processor is nil")
   352  	}
   353  	if s.processorAckTimeout == nil {
   354  		// must be bug, check history setup
   355  		panic("config.ESProcessorAckTimeout is nil")
   356  	}
   357  }
   358  
   359  func (s *visibilityStore) ListOpenWorkflowExecutions(
   360  	ctx context.Context,
   361  	request *manager.ListWorkflowExecutionsRequest,
   362  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   363  
   364  	boolQuery := elastic.NewBoolQuery().
   365  		Filter(elastic.NewTermQuery(searchattribute.ExecutionStatus, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String()))
   366  
   367  	p, err := s.buildSearchParameters(request, boolQuery, true)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  
   372  	searchResult, err := s.esClient.Search(ctx, p)
   373  	if err != nil {
   374  		return nil, convertElasticsearchClientError("ListOpenWorkflowExecutions failed", err)
   375  	}
   376  
   377  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   378  }
   379  
   380  func (s *visibilityStore) ListClosedWorkflowExecutions(
   381  	ctx context.Context,
   382  	request *manager.ListWorkflowExecutionsRequest,
   383  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   384  
   385  	boolQuery := elastic.NewBoolQuery().
   386  		MustNot(elastic.NewTermQuery(searchattribute.ExecutionStatus, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String()))
   387  
   388  	p, err := s.buildSearchParameters(request, boolQuery, false)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  
   393  	searchResult, err := s.esClient.Search(ctx, p)
   394  	if err != nil {
   395  		return nil, convertElasticsearchClientError("ListClosedWorkflowExecutions failed", err)
   396  	}
   397  
   398  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   399  }
   400  
   401  func (s *visibilityStore) ListOpenWorkflowExecutionsByType(
   402  	ctx context.Context,
   403  	request *manager.ListWorkflowExecutionsByTypeRequest,
   404  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   405  
   406  	boolQuery := elastic.NewBoolQuery().
   407  		Filter(
   408  			elastic.NewTermQuery(searchattribute.WorkflowType, request.WorkflowTypeName),
   409  			elastic.NewTermQuery(searchattribute.ExecutionStatus, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String()))
   410  
   411  	p, err := s.buildSearchParameters(request.ListWorkflowExecutionsRequest, boolQuery, true)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	searchResult, err := s.esClient.Search(ctx, p)
   417  	if err != nil {
   418  		return nil, convertElasticsearchClientError("ListOpenWorkflowExecutionsByType failed", err)
   419  	}
   420  
   421  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   422  }
   423  
   424  func (s *visibilityStore) ListClosedWorkflowExecutionsByType(
   425  	ctx context.Context,
   426  	request *manager.ListWorkflowExecutionsByTypeRequest,
   427  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   428  
   429  	boolQuery := elastic.NewBoolQuery().
   430  		Filter(elastic.NewTermQuery(searchattribute.WorkflowType, request.WorkflowTypeName)).
   431  		MustNot(elastic.NewTermQuery(searchattribute.ExecutionStatus, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String()))
   432  
   433  	p, err := s.buildSearchParameters(request.ListWorkflowExecutionsRequest, boolQuery, false)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	searchResult, err := s.esClient.Search(ctx, p)
   439  	if err != nil {
   440  		return nil, convertElasticsearchClientError("ListClosedWorkflowExecutionsByType failed", err)
   441  	}
   442  
   443  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   444  }
   445  
   446  func (s *visibilityStore) ListOpenWorkflowExecutionsByWorkflowID(
   447  	ctx context.Context,
   448  	request *manager.ListWorkflowExecutionsByWorkflowIDRequest,
   449  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   450  
   451  	boolQuery := elastic.NewBoolQuery().
   452  		Filter(
   453  			elastic.NewTermQuery(searchattribute.WorkflowID, request.WorkflowID),
   454  			elastic.NewTermQuery(searchattribute.ExecutionStatus, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String()))
   455  
   456  	p, err := s.buildSearchParameters(request.ListWorkflowExecutionsRequest, boolQuery, true)
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	searchResult, err := s.esClient.Search(ctx, p)
   462  	if err != nil {
   463  		return nil, convertElasticsearchClientError("ListOpenWorkflowExecutionsByWorkflowID failed", err)
   464  	}
   465  
   466  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   467  }
   468  
   469  func (s *visibilityStore) ListClosedWorkflowExecutionsByWorkflowID(
   470  	ctx context.Context,
   471  	request *manager.ListWorkflowExecutionsByWorkflowIDRequest,
   472  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   473  
   474  	boolQuery := elastic.NewBoolQuery().
   475  		Filter(elastic.NewTermQuery(searchattribute.WorkflowID, request.WorkflowID)).
   476  		MustNot(elastic.NewTermQuery(searchattribute.ExecutionStatus, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String()))
   477  
   478  	p, err := s.buildSearchParameters(request.ListWorkflowExecutionsRequest, boolQuery, false)
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  
   483  	searchResult, err := s.esClient.Search(ctx, p)
   484  	if err != nil {
   485  		return nil, convertElasticsearchClientError("ListClosedWorkflowExecutionsByWorkflowID failed", err)
   486  	}
   487  
   488  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   489  }
   490  
   491  func (s *visibilityStore) ListClosedWorkflowExecutionsByStatus(
   492  	ctx context.Context,
   493  	request *manager.ListClosedWorkflowExecutionsByStatusRequest,
   494  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   495  
   496  	boolQuery := elastic.NewBoolQuery().
   497  		Filter(elastic.NewTermQuery(searchattribute.ExecutionStatus, request.Status.String()))
   498  
   499  	p, err := s.buildSearchParameters(request.ListWorkflowExecutionsRequest, boolQuery, false)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  
   504  	searchResult, err := s.esClient.Search(ctx, p)
   505  	if err != nil {
   506  		return nil, convertElasticsearchClientError("ListClosedWorkflowExecutionsByStatus failed", err)
   507  	}
   508  
   509  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   510  }
   511  
   512  func (s *visibilityStore) ListWorkflowExecutions(
   513  	ctx context.Context,
   514  	request *manager.ListWorkflowExecutionsRequestV2,
   515  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   516  	p, err := s.buildSearchParametersV2(request, s.getListFieldSorter)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	searchResult, err := s.esClient.Search(ctx, p)
   522  	if err != nil {
   523  		return nil, convertElasticsearchClientError("ListWorkflowExecutions failed", err)
   524  	}
   525  
   526  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   527  }
   528  
   529  func (s *visibilityStore) ScanWorkflowExecutions(
   530  	ctx context.Context,
   531  	request *manager.ListWorkflowExecutionsRequestV2,
   532  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   533  	// Point in time is only supported in Elasticsearch 7.10+ in default flavor.
   534  	if s.esClient.IsPointInTimeSupported(ctx) {
   535  		return s.scanWorkflowExecutionsWithPit(ctx, request)
   536  	}
   537  	return s.scanWorkflowExecutionsWithScroll(ctx, request)
   538  }
   539  
   540  func (s *visibilityStore) scanWorkflowExecutionsWithScroll(
   541  	ctx context.Context,
   542  	request *manager.ListWorkflowExecutionsRequestV2,
   543  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   544  	var (
   545  		searchResult *elastic.SearchResult
   546  		scrollErr    error
   547  	)
   548  
   549  	p, err := s.buildSearchParametersV2(request, s.getScanFieldSorter)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  
   554  	if len(request.NextPageToken) == 0 {
   555  		searchResult, scrollErr = s.esClient.OpenScroll(ctx, p, scrollKeepAliveInterval)
   556  	} else if p.ScrollID != "" {
   557  		searchResult, scrollErr = s.esClient.Scroll(ctx, p.ScrollID, scrollKeepAliveInterval)
   558  	} else {
   559  		return nil, serviceerror.NewInvalidArgument("scrollId must present in pagination token")
   560  	}
   561  
   562  	if scrollErr != nil && scrollErr != io.EOF {
   563  		return nil, convertElasticsearchClientError("ScanWorkflowExecutions failed", scrollErr)
   564  	}
   565  
   566  	// Both io.IOF and empty hits list indicate that this is a last page.
   567  	if (searchResult.Hits != nil && len(searchResult.Hits.Hits) < request.PageSize) ||
   568  		scrollErr == io.EOF {
   569  		err := s.esClient.CloseScroll(ctx, searchResult.ScrollId)
   570  		if err != nil {
   571  			return nil, convertElasticsearchClientError("Unable to close scroll", err)
   572  		}
   573  	}
   574  
   575  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   576  }
   577  
   578  func (s *visibilityStore) scanWorkflowExecutionsWithPit(
   579  	ctx context.Context,
   580  	request *manager.ListWorkflowExecutionsRequestV2,
   581  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   582  	p, err := s.buildSearchParametersV2(request, s.getScanFieldSorter)
   583  	if err != nil {
   584  		return nil, err
   585  	}
   586  
   587  	// First call doesn't have token with PointInTimeID.
   588  	if len(request.NextPageToken) == 0 {
   589  		pitID, err := s.esClient.OpenPointInTime(ctx, s.index, pointInTimeKeepAliveInterval)
   590  		if err != nil {
   591  			return nil, convertElasticsearchClientError("Unable to create point in time", err)
   592  		}
   593  		p.PointInTime = elastic.NewPointInTimeWithKeepAlive(pitID, pointInTimeKeepAliveInterval)
   594  	} else if p.PointInTime == nil {
   595  		return nil, serviceerror.NewInvalidArgument("pointInTimeId must present in pagination token")
   596  	}
   597  
   598  	searchResult, err := s.esClient.Search(ctx, p)
   599  	if err != nil {
   600  		return nil, convertElasticsearchClientError("ScanWorkflowExecutions failed", err)
   601  	}
   602  
   603  	// Number hits smaller than the page size indicate that this is the last page.
   604  	if searchResult.Hits != nil && len(searchResult.Hits.Hits) < request.PageSize {
   605  		_, err := s.esClient.ClosePointInTime(ctx, searchResult.PitId)
   606  		if err != nil {
   607  			return nil, convertElasticsearchClientError("Unable to close point in time", err)
   608  		}
   609  	}
   610  
   611  	return s.getListWorkflowExecutionsResponse(searchResult, request.Namespace, request.PageSize)
   612  }
   613  
   614  func (s *visibilityStore) CountWorkflowExecutions(
   615  	ctx context.Context,
   616  	request *manager.CountWorkflowExecutionsRequest,
   617  ) (*manager.CountWorkflowExecutionsResponse, error) {
   618  	queryParams, err := s.convertQuery(request.Namespace, request.NamespaceID, request.Query)
   619  	if err != nil {
   620  		return nil, err
   621  	}
   622  
   623  	if len(queryParams.GroupBy) > 0 {
   624  		return s.countGroupByWorkflowExecutions(ctx, queryParams)
   625  	}
   626  
   627  	count, err := s.esClient.Count(ctx, s.index, queryParams.Query)
   628  	if err != nil {
   629  		return nil, convertElasticsearchClientError("CountWorkflowExecutions failed", err)
   630  	}
   631  
   632  	response := &manager.CountWorkflowExecutionsResponse{Count: count}
   633  	return response, nil
   634  }
   635  
   636  func (s *visibilityStore) countGroupByWorkflowExecutions(
   637  	ctx context.Context,
   638  	queryParams *query.QueryParams,
   639  ) (*manager.CountWorkflowExecutionsResponse, error) {
   640  	groupByFields := queryParams.GroupBy
   641  
   642  	// Elasticsearch aggregation is nested. so need to loop backwards to build it.
   643  	// Example: when grouping by (field1, field2), the object looks like
   644  	// {
   645  	//   "aggs": {
   646  	//     "field1": {
   647  	//       "terms": {
   648  	//         "field": "field1"
   649  	//       },
   650  	//       "aggs": {
   651  	//         "field2": {
   652  	//           "terms": {
   653  	//             "field": "field2"
   654  	//           }
   655  	//         }
   656  	//       }
   657  	//     }
   658  	//   }
   659  	// }
   660  	termsAgg := elastic.NewTermsAggregation().Field(groupByFields[len(groupByFields)-1])
   661  	for i := len(groupByFields) - 2; i >= 0; i-- {
   662  		termsAgg = elastic.NewTermsAggregation().
   663  			Field(groupByFields[i]).
   664  			SubAggregation(groupByFields[i+1], termsAgg)
   665  	}
   666  	esResponse, err := s.esClient.CountGroupBy(
   667  		ctx,
   668  		s.index,
   669  		queryParams.Query,
   670  		groupByFields[0],
   671  		termsAgg,
   672  	)
   673  	if err != nil {
   674  		return nil, err
   675  	}
   676  	return s.parseCountGroupByResponse(esResponse, groupByFields)
   677  }
   678  
   679  func (s *visibilityStore) GetWorkflowExecution(
   680  	ctx context.Context,
   681  	request *manager.GetWorkflowExecutionRequest,
   682  ) (*store.InternalGetWorkflowExecutionResponse, error) {
   683  	docID := getDocID(request.WorkflowID, request.RunID)
   684  	result, err := s.esClient.Get(ctx, s.index, docID)
   685  	if err != nil {
   686  		return nil, convertElasticsearchClientError("GetWorkflowExecution failed", err)
   687  	}
   688  
   689  	typeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.index, false)
   690  	if err != nil {
   691  		return nil, serviceerror.NewUnavailable(
   692  			fmt.Sprintf("Unable to read search attribute types: %v", err),
   693  		)
   694  	}
   695  
   696  	if !result.Found {
   697  		return nil, serviceerror.NewNotFound(
   698  			fmt.Sprintf("Workflow execution with run id %s not found.", request.RunID),
   699  		)
   700  	}
   701  
   702  	workflowExecutionInfo, err := s.parseESDoc(result.Id, result.Source, typeMap, request.Namespace)
   703  	if err != nil {
   704  		return nil, err
   705  	}
   706  
   707  	return &store.InternalGetWorkflowExecutionResponse{
   708  		Execution: workflowExecutionInfo,
   709  	}, nil
   710  }
   711  
   712  func (s *visibilityStore) buildSearchParameters(
   713  	request *manager.ListWorkflowExecutionsRequest,
   714  	boolQuery *elastic.BoolQuery,
   715  	overStartTime bool,
   716  ) (*client.SearchParameters, error) {
   717  
   718  	token, err := s.deserializePageToken(request.NextPageToken)
   719  	if err != nil {
   720  		return nil, err
   721  	}
   722  
   723  	boolQuery.Filter(elastic.NewTermQuery(searchattribute.NamespaceID, request.NamespaceID.String()))
   724  
   725  	if request.NamespaceDivision == "" {
   726  		boolQuery.MustNot(elastic.NewExistsQuery(searchattribute.TemporalNamespaceDivision))
   727  	} else {
   728  		boolQuery.Filter(elastic.NewTermQuery(searchattribute.TemporalNamespaceDivision, request.NamespaceDivision))
   729  	}
   730  
   731  	if !request.EarliestStartTime.IsZero() || !request.LatestStartTime.IsZero() {
   732  		var rangeQuery *elastic.RangeQuery
   733  		if overStartTime {
   734  			rangeQuery = elastic.NewRangeQuery(searchattribute.StartTime)
   735  		} else {
   736  			rangeQuery = elastic.NewRangeQuery(searchattribute.CloseTime)
   737  		}
   738  
   739  		if !request.EarliestStartTime.IsZero() {
   740  			rangeQuery = rangeQuery.Gte(request.EarliestStartTime)
   741  		}
   742  
   743  		if !request.LatestStartTime.IsZero() {
   744  			rangeQuery = rangeQuery.Lte(request.LatestStartTime)
   745  		}
   746  		boolQuery.Filter(rangeQuery)
   747  	}
   748  
   749  	params := &client.SearchParameters{
   750  		Index:    s.index,
   751  		Query:    boolQuery,
   752  		PageSize: request.PageSize,
   753  		Sorter:   defaultSorter,
   754  	}
   755  
   756  	if token != nil && len(token.SearchAfter) > 0 {
   757  		params.SearchAfter = token.SearchAfter
   758  	}
   759  
   760  	return params, nil
   761  }
   762  
   763  func (s *visibilityStore) buildSearchParametersV2(
   764  	request *manager.ListWorkflowExecutionsRequestV2,
   765  	getFieldSorter func([]elastic.Sorter) ([]elastic.Sorter, error),
   766  ) (*client.SearchParameters, error) {
   767  	queryParams, err := s.convertQuery(
   768  		request.Namespace,
   769  		request.NamespaceID,
   770  		request.Query,
   771  	)
   772  	if err != nil {
   773  		return nil, err
   774  	}
   775  
   776  	searchParams := &client.SearchParameters{
   777  		Index:    s.index,
   778  		PageSize: request.PageSize,
   779  		Query:    queryParams.Query,
   780  	}
   781  
   782  	if len(queryParams.GroupBy) > 0 {
   783  		return nil, serviceerror.NewInvalidArgument("GROUP BY clause is not supported")
   784  	}
   785  
   786  	// TODO(rodrigozhou): investigate possible solutions to slow ORDER BY.
   787  	// ORDER BY clause can be slow if there is a large number of documents and
   788  	// using a field that was not indexed by ES. Since slow queries can block
   789  	// writes for unreasonably long, this option forbids the usage of ORDER BY
   790  	// clause to prevent slow down issues.
   791  	if s.disableOrderByClause(request.Namespace.String()) && len(queryParams.Sorter) > 0 {
   792  		return nil, serviceerror.NewInvalidArgument("ORDER BY clause is not supported")
   793  	}
   794  
   795  	if len(queryParams.Sorter) > 0 {
   796  		// If params.Sorter is not empty, then it's using custom order by.
   797  		s.metricsHandler.WithTags(metrics.NamespaceTag(request.Namespace.String())).
   798  			Counter(metrics.ElasticsearchCustomOrderByClauseCount.Name()).Record(1)
   799  	}
   800  
   801  	searchParams.Sorter, err = getFieldSorter(queryParams.Sorter)
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  
   806  	pageToken, err := s.deserializePageToken(request.NextPageToken)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	err = s.processPageToken(searchParams, pageToken, request.Namespace)
   811  	if err != nil {
   812  		return nil, err
   813  	}
   814  
   815  	return searchParams, nil
   816  }
   817  
   818  func (s *visibilityStore) processPageToken(
   819  	params *client.SearchParameters,
   820  	pageToken *visibilityPageToken,
   821  	namespaceName namespace.Name,
   822  ) error {
   823  	if pageToken == nil {
   824  		return nil
   825  	}
   826  	if pageToken.ScrollID != "" {
   827  		params.ScrollID = pageToken.ScrollID
   828  		return nil
   829  	}
   830  	if len(pageToken.SearchAfter) == 0 {
   831  		return nil
   832  	}
   833  	if pageToken.PointInTimeID != "" {
   834  		params.SearchAfter = pageToken.SearchAfter
   835  		params.PointInTime = elastic.NewPointInTimeWithKeepAlive(
   836  			pageToken.PointInTimeID,
   837  			pointInTimeKeepAliveInterval,
   838  		)
   839  		return nil
   840  	}
   841  	if len(pageToken.SearchAfter) != len(params.Sorter) {
   842  		return serviceerror.NewInvalidArgument(fmt.Sprintf(
   843  			"Invalid page token for given sort fields: expected %d fields, got %d",
   844  			len(params.Sorter),
   845  			len(pageToken.SearchAfter),
   846  		))
   847  	}
   848  	if !s.enableManualPagination(namespaceName.String()) || !isDefaultSorter(params.Sorter) {
   849  		params.SearchAfter = pageToken.SearchAfter
   850  		return nil
   851  	}
   852  
   853  	boolQuery, ok := params.Query.(*elastic.BoolQuery)
   854  	if !ok {
   855  		return serviceerror.NewInternal(fmt.Sprintf(
   856  			"Unexpected query type: expected *elastic.BoolQuery, got %T",
   857  			params.Query,
   858  		))
   859  	}
   860  
   861  	saTypeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.index, false)
   862  	if err != nil {
   863  		return serviceerror.NewUnavailable(
   864  			fmt.Sprintf("Unable to read search attribute types: %v", err),
   865  		)
   866  	}
   867  
   868  	// build pagination search query for default sorter
   869  	shouldQueries, err := buildPaginationQuery(defaultSorterFields, pageToken.SearchAfter, saTypeMap)
   870  	if err != nil {
   871  		return err
   872  	}
   873  
   874  	boolQuery.Should(shouldQueries...)
   875  	boolQuery.MinimumNumberShouldMatch(1)
   876  	return nil
   877  }
   878  
   879  func (s *visibilityStore) convertQuery(
   880  	namespace namespace.Name,
   881  	namespaceID namespace.ID,
   882  	requestQueryStr string,
   883  ) (*query.QueryParams, error) {
   884  	saTypeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.index, false)
   885  	if err != nil {
   886  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("Unable to read search attribute types: %v", err))
   887  	}
   888  	nameInterceptor := newNameInterceptor(namespace, s.index, saTypeMap, s.searchAttributesMapperProvider)
   889  	queryConverter := newQueryConverter(
   890  		nameInterceptor,
   891  		NewValuesInterceptor(namespace, saTypeMap, s.searchAttributesMapperProvider),
   892  	)
   893  	queryParams, err := queryConverter.ConvertWhereOrderBy(requestQueryStr)
   894  	if err != nil {
   895  		// Convert ConverterError to InvalidArgument and pass through all other errors (which should be only mapper errors).
   896  		var converterErr *query.ConverterError
   897  		if errors.As(err, &converterErr) {
   898  			return nil, converterErr.ToInvalidArgument()
   899  		}
   900  		return nil, err
   901  	}
   902  
   903  	// Create new bool query because request query might have only "should" (="or") queries.
   904  	namespaceFilterQuery := elastic.NewBoolQuery().Filter(elastic.NewTermQuery(searchattribute.NamespaceID, namespaceID.String()))
   905  
   906  	// If the query did not explicitly filter on TemporalNamespaceDivision somehow, then add a
   907  	// "must not exist" (i.e. "is null") query for it.
   908  	if !nameInterceptor.seenNamespaceDivision {
   909  		namespaceFilterQuery.MustNot(elastic.NewExistsQuery(searchattribute.TemporalNamespaceDivision))
   910  	}
   911  
   912  	if queryParams.Query != nil {
   913  		namespaceFilterQuery.Filter(queryParams.Query)
   914  	}
   915  
   916  	queryParams.Query = namespaceFilterQuery
   917  	return queryParams, nil
   918  }
   919  
   920  func (s *visibilityStore) getScanFieldSorter(fieldSorts []elastic.Sorter) ([]elastic.Sorter, error) {
   921  	// custom order is not supported by Scan API
   922  	if len(fieldSorts) > 0 {
   923  		return nil, serviceerror.NewInvalidArgument("ORDER BY clause is not supported")
   924  	}
   925  
   926  	return docSorter, nil
   927  }
   928  
   929  func (s *visibilityStore) getListFieldSorter(fieldSorts []elastic.Sorter) ([]elastic.Sorter, error) {
   930  	if len(fieldSorts) == 0 {
   931  		return defaultSorter, nil
   932  	}
   933  	res := make([]elastic.Sorter, len(fieldSorts)+1)
   934  	for i, fs := range fieldSorts {
   935  		res[i] = fs
   936  	}
   937  	// RunID is explicit tiebreaker.
   938  	res[len(res)-1] = elastic.NewFieldSort(searchattribute.RunID).Desc()
   939  
   940  	return res, nil
   941  }
   942  
   943  func (s *visibilityStore) getListWorkflowExecutionsResponse(
   944  	searchResult *elastic.SearchResult,
   945  	namespace namespace.Name,
   946  	pageSize int,
   947  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   948  
   949  	if searchResult.Hits == nil || len(searchResult.Hits.Hits) == 0 {
   950  		return &store.InternalListWorkflowExecutionsResponse{}, nil
   951  	}
   952  
   953  	typeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.index, false)
   954  	if err != nil {
   955  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("Unable to read search attribute types: %v", err))
   956  	}
   957  
   958  	response := &store.InternalListWorkflowExecutionsResponse{
   959  		Executions: make([]*store.InternalWorkflowExecutionInfo, 0, len(searchResult.Hits.Hits)),
   960  	}
   961  	var lastHitSort []interface{}
   962  	for _, hit := range searchResult.Hits.Hits {
   963  		workflowExecutionInfo, err := s.parseESDoc(hit.Id, hit.Source, typeMap, namespace)
   964  		if err != nil {
   965  			return nil, err
   966  		}
   967  		response.Executions = append(response.Executions, workflowExecutionInfo)
   968  		lastHitSort = hit.Sort
   969  	}
   970  
   971  	if len(searchResult.Hits.Hits) == pageSize { // this means the response might not the last page
   972  		response.NextPageToken, err = s.serializePageToken(&visibilityPageToken{
   973  			SearchAfter:   lastHitSort,
   974  			ScrollID:      searchResult.ScrollId,
   975  			PointInTimeID: searchResult.PitId,
   976  		})
   977  		if err != nil {
   978  			return nil, err
   979  		}
   980  	}
   981  
   982  	return response, nil
   983  }
   984  
   985  func (s *visibilityStore) deserializePageToken(data []byte) (*visibilityPageToken, error) {
   986  	if len(data) == 0 {
   987  		return nil, nil
   988  	}
   989  
   990  	var token *visibilityPageToken
   991  	dec := json.NewDecoder(bytes.NewReader(data))
   992  	// UseNumber will not lose precision on big int64.
   993  	dec.UseNumber()
   994  	err := dec.Decode(&token)
   995  	if err != nil {
   996  		return nil, serviceerror.NewInvalidArgument(fmt.Sprintf("unable to deserialize page token: %v", err))
   997  	}
   998  	return token, nil
   999  }
  1000  
  1001  func (s *visibilityStore) serializePageToken(token *visibilityPageToken) ([]byte, error) {
  1002  	if token == nil {
  1003  		return nil, nil
  1004  	}
  1005  
  1006  	data, err := json.Marshal(token)
  1007  	if err != nil {
  1008  		return nil, serviceerror.NewInternal(fmt.Sprintf("unable to serialize page token: %v", err))
  1009  	}
  1010  	return data, nil
  1011  }
  1012  
  1013  func (s *visibilityStore) generateESDoc(
  1014  	request *store.InternalVisibilityRequestBase,
  1015  	visibilityTaskKey string,
  1016  ) (map[string]interface{}, error) {
  1017  	doc := map[string]interface{}{
  1018  		searchattribute.VisibilityTaskKey: visibilityTaskKey,
  1019  		searchattribute.NamespaceID:       request.NamespaceID,
  1020  		searchattribute.WorkflowID:        request.WorkflowID,
  1021  		searchattribute.RunID:             request.RunID,
  1022  		searchattribute.WorkflowType:      request.WorkflowTypeName,
  1023  		searchattribute.StartTime:         request.StartTime,
  1024  		searchattribute.ExecutionTime:     request.ExecutionTime,
  1025  		searchattribute.ExecutionStatus:   request.Status.String(),
  1026  		searchattribute.TaskQueue:         request.TaskQueue,
  1027  	}
  1028  
  1029  	if request.ParentWorkflowID != nil {
  1030  		doc[searchattribute.ParentWorkflowID] = *request.ParentWorkflowID
  1031  	}
  1032  	if request.ParentRunID != nil {
  1033  		doc[searchattribute.ParentRunID] = *request.ParentRunID
  1034  	}
  1035  
  1036  	if len(request.Memo.GetData()) > 0 {
  1037  		doc[searchattribute.Memo] = request.Memo.GetData()
  1038  		doc[searchattribute.MemoEncoding] = request.Memo.GetEncodingType().String()
  1039  	}
  1040  
  1041  	typeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.index, false)
  1042  	if err != nil {
  1043  		s.metricsHandler.Counter(metrics.ElasticsearchDocumentGenerateFailuresCount.Name()).Record(1)
  1044  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("Unable to read search attribute types: %v", err))
  1045  	}
  1046  
  1047  	searchAttributes, err := searchattribute.Decode(request.SearchAttributes, &typeMap, true)
  1048  	if err != nil {
  1049  		s.metricsHandler.Counter(metrics.ElasticsearchDocumentGenerateFailuresCount.Name()).Record(1)
  1050  		return nil, serviceerror.NewInternal(fmt.Sprintf("Unable to decode search attributes: %v", err))
  1051  	}
  1052  	// This is to prevent existing tasks to fail indefinitely.
  1053  	// If it's only invalid values error, then silently continue without them.
  1054  	searchAttributes, err = s.ValidateCustomSearchAttributes(searchAttributes)
  1055  	if err != nil {
  1056  		if _, ok := err.(*store.VisibilityStoreInvalidValuesError); !ok {
  1057  			return nil, err
  1058  		}
  1059  	}
  1060  	for saName, saValue := range searchAttributes {
  1061  		if saValue == nil {
  1062  			// If search attribute value is `nil`, it means that it shouldn't be added to the document.
  1063  			// Empty slices are converted to `nil` while decoding.
  1064  			continue
  1065  		}
  1066  		doc[saName] = saValue
  1067  	}
  1068  
  1069  	return doc, nil
  1070  }
  1071  
  1072  func (s *visibilityStore) parseESDoc(docID string, docSource json.RawMessage, saTypeMap searchattribute.NameTypeMap, namespace namespace.Name) (*store.InternalWorkflowExecutionInfo, error) {
  1073  	logParseError := func(fieldName string, fieldValue interface{}, err error, docID string) error {
  1074  		s.metricsHandler.Counter(metrics.ElasticsearchDocumentParseFailuresCount.Name()).Record(1)
  1075  		return serviceerror.NewInternal(fmt.Sprintf("Unable to parse Elasticsearch document(%s) %q field value %q: %v", docID, fieldName, fieldValue, err))
  1076  	}
  1077  
  1078  	var sourceMap map[string]interface{}
  1079  	d := json.NewDecoder(bytes.NewReader(docSource))
  1080  	// Very important line. See finishParseJSONValue bellow.
  1081  	d.UseNumber()
  1082  	if err := d.Decode(&sourceMap); err != nil {
  1083  		s.metricsHandler.Counter(metrics.ElasticsearchDocumentParseFailuresCount.Name()).Record(1)
  1084  		return nil, serviceerror.NewInternal(fmt.Sprintf("Unable to unmarshal JSON from Elasticsearch document(%s): %v", docID, err))
  1085  	}
  1086  
  1087  	var (
  1088  		isValidType            bool
  1089  		memo                   []byte
  1090  		memoEncoding           string
  1091  		customSearchAttributes map[string]interface{}
  1092  	)
  1093  	record := &store.InternalWorkflowExecutionInfo{}
  1094  	for fieldName, fieldValue := range sourceMap {
  1095  		switch fieldName {
  1096  		case searchattribute.NamespaceID,
  1097  			searchattribute.ExecutionDuration,
  1098  			searchattribute.VisibilityTaskKey:
  1099  			// Ignore these fields.
  1100  			continue
  1101  		case searchattribute.Memo:
  1102  			var memoStr string
  1103  			if memoStr, isValidType = fieldValue.(string); !isValidType {
  1104  				return nil, logParseError(fieldName, fieldValue, fmt.Errorf("%w: expected string got %T", errUnexpectedJSONFieldType, fieldValue), docID)
  1105  			}
  1106  			var err error
  1107  			if memo, err = base64.StdEncoding.DecodeString(memoStr); err != nil {
  1108  				return nil, logParseError(fieldName, memoStr[:10], err, docID)
  1109  			}
  1110  			continue
  1111  		case searchattribute.MemoEncoding:
  1112  			if memoEncoding, isValidType = fieldValue.(string); !isValidType {
  1113  				return nil, logParseError(fieldName, fieldValue, fmt.Errorf("%w: expected string got %T", errUnexpectedJSONFieldType, fieldValue), docID)
  1114  			}
  1115  			continue
  1116  		}
  1117  
  1118  		fieldType, err := saTypeMap.GetType(fieldName)
  1119  		if err != nil {
  1120  			// Silently ignore ErrInvalidName because it indicates unknown field in Elasticsearch document.
  1121  			if errors.Is(err, searchattribute.ErrInvalidName) {
  1122  				continue
  1123  			}
  1124  			s.metricsHandler.Counter(metrics.ElasticsearchDocumentParseFailuresCount.Name()).Record(1)
  1125  			return nil, serviceerror.NewInternal(fmt.Sprintf("Unable to get type for Elasticsearch document(%s) field %q: %v", docID, fieldName, err))
  1126  		}
  1127  
  1128  		fieldValueParsed, err := finishParseJSONValue(fieldValue, fieldType)
  1129  		if err != nil {
  1130  			return nil, logParseError(fieldName, fieldValue, err, docID)
  1131  		}
  1132  
  1133  		switch fieldName {
  1134  		case searchattribute.WorkflowID:
  1135  			record.WorkflowID = fieldValueParsed.(string)
  1136  		case searchattribute.RunID:
  1137  			record.RunID = fieldValueParsed.(string)
  1138  		case searchattribute.WorkflowType:
  1139  			record.TypeName = fieldValue.(string)
  1140  		case searchattribute.StartTime:
  1141  			record.StartTime = fieldValueParsed.(time.Time)
  1142  		case searchattribute.ExecutionTime:
  1143  			record.ExecutionTime = fieldValueParsed.(time.Time)
  1144  		case searchattribute.CloseTime:
  1145  			record.CloseTime = fieldValueParsed.(time.Time)
  1146  		case searchattribute.TaskQueue:
  1147  			record.TaskQueue = fieldValueParsed.(string)
  1148  		case searchattribute.ExecutionStatus:
  1149  			status, err := enumspb.WorkflowExecutionStatusFromString(fieldValueParsed.(string))
  1150  			if err != nil {
  1151  				return nil, logParseError(fieldName, fieldValueParsed.(string), err, docID)
  1152  			}
  1153  			record.Status = status
  1154  		case searchattribute.HistoryLength:
  1155  			record.HistoryLength = fieldValueParsed.(int64)
  1156  		case searchattribute.StateTransitionCount:
  1157  			record.StateTransitionCount = fieldValueParsed.(int64)
  1158  		case searchattribute.HistorySizeBytes:
  1159  			record.HistorySizeBytes = fieldValueParsed.(int64)
  1160  		case searchattribute.ParentWorkflowID:
  1161  			record.ParentWorkflowID = fieldValueParsed.(string)
  1162  		case searchattribute.ParentRunID:
  1163  			record.ParentRunID = fieldValueParsed.(string)
  1164  		default:
  1165  			// All custom and predefined search attributes are handled here.
  1166  			if customSearchAttributes == nil {
  1167  				customSearchAttributes = map[string]interface{}{}
  1168  			}
  1169  			customSearchAttributes[fieldName] = fieldValueParsed
  1170  		}
  1171  	}
  1172  
  1173  	if customSearchAttributes != nil {
  1174  		var err error
  1175  		record.SearchAttributes, err = searchattribute.Encode(customSearchAttributes, &saTypeMap)
  1176  		if err != nil {
  1177  			s.metricsHandler.Counter(metrics.ElasticsearchDocumentParseFailuresCount.Name()).Record(1)
  1178  			return nil, serviceerror.NewInternal(fmt.Sprintf("Unable to encode custom search attributes of Elasticsearch document(%s): %v", docID, err))
  1179  		}
  1180  		aliasedSas, err := searchattribute.AliasFields(s.searchAttributesMapperProvider, record.SearchAttributes, namespace.String())
  1181  		if err != nil {
  1182  			return nil, err
  1183  		}
  1184  
  1185  		if aliasedSas != nil {
  1186  			record.SearchAttributes = aliasedSas
  1187  		}
  1188  	}
  1189  
  1190  	if memoEncoding != "" {
  1191  		record.Memo = persistence.NewDataBlob(memo, memoEncoding)
  1192  	} else if memo != nil {
  1193  		s.metricsHandler.Counter(metrics.ElasticsearchDocumentParseFailuresCount.Name()).Record(1)
  1194  		return nil, serviceerror.NewInternal(fmt.Sprintf("%q field is missing in Elasticsearch document(%s)", searchattribute.MemoEncoding, docID))
  1195  	}
  1196  
  1197  	return record, nil
  1198  }
  1199  
  1200  // Elasticsearch aggregation groups are returned as nested object.
  1201  // This function flattens the response into rows.
  1202  //
  1203  //nolint:revive // cognitive complexity 27 (> max enabled 25)
  1204  func (s *visibilityStore) parseCountGroupByResponse(
  1205  	searchResult *elastic.SearchResult,
  1206  	groupByFields []string,
  1207  ) (*manager.CountWorkflowExecutionsResponse, error) {
  1208  	response := &manager.CountWorkflowExecutionsResponse{}
  1209  	typeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.index, false)
  1210  	if err != nil {
  1211  		return nil, serviceerror.NewUnavailable(
  1212  			fmt.Sprintf("Unable to read search attribute types: %v", err),
  1213  		)
  1214  	}
  1215  	groupByTypes := make([]enumspb.IndexedValueType, len(groupByFields))
  1216  	for i, saName := range groupByFields {
  1217  		tp, err := typeMap.GetType(saName)
  1218  		if err != nil {
  1219  			return nil, err
  1220  		}
  1221  		groupByTypes[i] = tp
  1222  	}
  1223  
  1224  	parseJsonNumber := func(val any) (int64, error) {
  1225  		numberVal, isNumber := val.(json.Number)
  1226  		if !isNumber {
  1227  			return 0, fmt.Errorf("%w: expected json.Number, got %T", errUnexpectedJSONFieldType, val)
  1228  		}
  1229  		return numberVal.Int64()
  1230  	}
  1231  
  1232  	var parseInternal func(map[string]any, []*commonpb.Payload) error
  1233  	parseInternal = func(aggs map[string]any, bucketValues []*commonpb.Payload) error {
  1234  		if len(bucketValues) == len(groupByFields) {
  1235  			cnt, err := parseJsonNumber(aggs["doc_count"])
  1236  			if err != nil {
  1237  				return fmt.Errorf("Unable to parse 'doc_count' field: %w", err)
  1238  			}
  1239  			groupValues := make([]*commonpb.Payload, len(groupByFields))
  1240  			for i := range bucketValues {
  1241  				groupValues[i] = bucketValues[i]
  1242  			}
  1243  			response.Groups = append(
  1244  				response.Groups,
  1245  				&workflowservice.CountWorkflowExecutionsResponse_AggregationGroup{
  1246  					GroupValues: groupValues,
  1247  					Count:       cnt,
  1248  				},
  1249  			)
  1250  			response.Count += cnt
  1251  			return nil
  1252  		}
  1253  
  1254  		index := len(bucketValues)
  1255  		fieldName := groupByFields[index]
  1256  		buckets := aggs[fieldName].(map[string]any)["buckets"].([]any)
  1257  		for i := range buckets {
  1258  			bucket := buckets[i].(map[string]any)
  1259  			value, err := finishParseJSONValue(bucket["key"], groupByTypes[index])
  1260  			if err != nil {
  1261  				return fmt.Errorf("Failed to parse value %v: %w", bucket["key"], err)
  1262  			}
  1263  			payload, err := searchattribute.EncodeValue(value, groupByTypes[index])
  1264  			if err != nil {
  1265  				return fmt.Errorf("Failed to encode value %v: %w", value, err)
  1266  			}
  1267  			err = parseInternal(bucket, append(bucketValues, payload))
  1268  			if err != nil {
  1269  				return err
  1270  			}
  1271  		}
  1272  		return nil
  1273  	}
  1274  
  1275  	var bucketsJson map[string]any
  1276  	dec := json.NewDecoder(bytes.NewReader(searchResult.Aggregations[groupByFields[0]]))
  1277  	dec.UseNumber()
  1278  	if err := dec.Decode(&bucketsJson); err != nil {
  1279  		return nil, serviceerror.NewInternal(fmt.Sprintf("unable to unmarshal json response: %v", err))
  1280  	}
  1281  	if err := parseInternal(map[string]any{groupByFields[0]: bucketsJson}, nil); err != nil {
  1282  		return nil, err
  1283  	}
  1284  	return response, nil
  1285  }
  1286  
  1287  // finishParseJSONValue finishes JSON parsing after json.Decode.
  1288  // json.Decode returns:
  1289  //
  1290  //	bool, for JSON booleans
  1291  //	json.Number, for JSON numbers (because of d.UseNumber())
  1292  //	string, for JSON strings
  1293  //	[]interface{}, for JSON arrays
  1294  //	map[string]interface{}, for JSON objects (should never be a case)
  1295  //	nil for JSON null
  1296  func finishParseJSONValue(val interface{}, t enumspb.IndexedValueType) (interface{}, error) {
  1297  	// Custom search attributes support array of particular type.
  1298  	if arrayValue, isArray := val.([]interface{}); isArray {
  1299  		retArray := make([]interface{}, len(arrayValue))
  1300  		var lastErr error
  1301  		for i := 0; i < len(retArray); i++ {
  1302  			retArray[i], lastErr = finishParseJSONValue(arrayValue[i], t)
  1303  		}
  1304  		return retArray, lastErr
  1305  	}
  1306  
  1307  	switch t {
  1308  	case enumspb.INDEXED_VALUE_TYPE_TEXT,
  1309  		enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  1310  		enumspb.INDEXED_VALUE_TYPE_KEYWORD_LIST,
  1311  		enumspb.INDEXED_VALUE_TYPE_DATETIME:
  1312  		stringVal, isString := val.(string)
  1313  		if !isString {
  1314  			return nil, fmt.Errorf("%w: expected string got %T", errUnexpectedJSONFieldType, val)
  1315  		}
  1316  		if t == enumspb.INDEXED_VALUE_TYPE_DATETIME {
  1317  			return time.Parse(time.RFC3339Nano, stringVal)
  1318  		}
  1319  		return stringVal, nil
  1320  	case enumspb.INDEXED_VALUE_TYPE_INT, enumspb.INDEXED_VALUE_TYPE_DOUBLE:
  1321  		numberVal, isNumber := val.(json.Number)
  1322  		if !isNumber {
  1323  			return nil, fmt.Errorf("%w: expected json.Number got %T", errUnexpectedJSONFieldType, val)
  1324  		}
  1325  		if t == enumspb.INDEXED_VALUE_TYPE_INT {
  1326  			return numberVal.Int64()
  1327  		}
  1328  		return numberVal.Float64()
  1329  	case enumspb.INDEXED_VALUE_TYPE_BOOL:
  1330  		boolVal, isBool := val.(bool)
  1331  		if !isBool {
  1332  			return nil, fmt.Errorf("%w: expected bool got %T", errUnexpectedJSONFieldType, val)
  1333  		}
  1334  		return boolVal, nil
  1335  	}
  1336  
  1337  	panic(fmt.Sprintf("Unknown field type: %v", t))
  1338  }
  1339  
  1340  func convertElasticsearchClientError(message string, err error) error {
  1341  	errMessage := fmt.Sprintf("%s: %s", message, detailedErrorMessage(err))
  1342  	switch e := err.(type) {
  1343  	case *elastic.Error:
  1344  		switch e.Status {
  1345  		case 400: // BadRequest
  1346  			// Returning InvalidArgument error will prevent retry on a caller side.
  1347  			return serviceerror.NewInvalidArgument(errMessage)
  1348  		}
  1349  	}
  1350  	return serviceerror.NewUnavailable(errMessage)
  1351  }
  1352  
  1353  func detailedErrorMessage(err error) string {
  1354  	var elasticErr *elastic.Error
  1355  	if !errors.As(err, &elasticErr) ||
  1356  		elasticErr.Details == nil ||
  1357  		len(elasticErr.Details.RootCause) == 0 ||
  1358  		(len(elasticErr.Details.RootCause) == 1 && elasticErr.Details.RootCause[0].Reason == elasticErr.Details.Reason) {
  1359  		return err.Error()
  1360  	}
  1361  
  1362  	var sb strings.Builder
  1363  	sb.WriteString(elasticErr.Error())
  1364  	sb.WriteString(", root causes:")
  1365  	for i, rootCause := range elasticErr.Details.RootCause {
  1366  		sb.WriteString(fmt.Sprintf(" %s [type=%s]", rootCause.Reason, rootCause.Type))
  1367  		if i != len(elasticErr.Details.RootCause)-1 {
  1368  			sb.WriteRune(',')
  1369  		}
  1370  	}
  1371  	return sb.String()
  1372  }
  1373  
  1374  func isDefaultSorter(sorter []elastic.Sorter) bool {
  1375  	if len(sorter) != len(defaultSorter) {
  1376  		return false
  1377  	}
  1378  	for i := 0; i < len(defaultSorter); i++ {
  1379  		if &sorter[i] != &defaultSorter[i] {
  1380  			return false
  1381  		}
  1382  	}
  1383  	return true
  1384  }
  1385  
  1386  // buildPaginationQuery builds the Elasticsearch conditions for the next page based on searchAfter.
  1387  //
  1388  // For example, if sorterFields = [A, B, C] and searchAfter = [lastA, lastB, lastC],
  1389  // it will build the following conditions (assuming all values are non-null and orders are desc):
  1390  // - k = 0: A < lastA
  1391  // - k = 1: A = lastA AND B < lastB
  1392  // - k = 2: A = lastA AND B = lastB AND C < lastC
  1393  //
  1394  //nolint:revive // cyclomatic complexity
  1395  func buildPaginationQuery(
  1396  	sorterFields []fieldSort,
  1397  	searchAfter []any,
  1398  	saTypeMap searchattribute.NameTypeMap,
  1399  ) ([]elastic.Query, error) {
  1400  	n := len(sorterFields)
  1401  	if len(sorterFields) != len(searchAfter) {
  1402  		return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1403  			"Invalid page token for given sort fields: expected %d fields, got %d",
  1404  			len(sorterFields),
  1405  			len(searchAfter),
  1406  		))
  1407  	}
  1408  
  1409  	parsedSearchAfter := make([]any, n)
  1410  	for i := 0; i < n; i++ {
  1411  		tp, err := saTypeMap.GetType(sorterFields[i].name)
  1412  		if err != nil {
  1413  			return nil, err
  1414  		}
  1415  		parsedSearchAfter[i], err = parsePageTokenValue(sorterFields[i].name, searchAfter[i], tp)
  1416  		if err != nil {
  1417  			return nil, err
  1418  		}
  1419  	}
  1420  
  1421  	// Last field of sorter must be a tie breaker, and thus cannot contain null value.
  1422  	if parsedSearchAfter[len(parsedSearchAfter)-1] == nil {
  1423  		return nil, serviceerror.NewInternal(fmt.Sprintf(
  1424  			"Last field of sorter cannot be a nullable field: %q has null values",
  1425  			sorterFields[len(sorterFields)-1].name,
  1426  		))
  1427  	}
  1428  
  1429  	shouldQueries := make([]elastic.Query, 0, len(sorterFields))
  1430  	for k := 0; k < len(sorterFields); k++ {
  1431  		bq := elastic.NewBoolQuery()
  1432  		for i := 0; i <= k; i++ {
  1433  			field := sorterFields[i]
  1434  			value := parsedSearchAfter[i]
  1435  			if i == k {
  1436  				if value == nil {
  1437  					bq.Filter(elastic.NewExistsQuery(field.name))
  1438  				} else if field.desc {
  1439  					bq.Filter(elastic.NewRangeQuery(field.name).Lt(value))
  1440  				} else {
  1441  					bq.Filter(elastic.NewRangeQuery(field.name).Gt(value))
  1442  				}
  1443  			} else {
  1444  				if value == nil {
  1445  					bq.MustNot(elastic.NewExistsQuery(field.name))
  1446  				} else {
  1447  					bq.Filter(elastic.NewTermQuery(field.name, value))
  1448  				}
  1449  			}
  1450  		}
  1451  		shouldQueries = append(shouldQueries, bq)
  1452  	}
  1453  	return shouldQueries, nil
  1454  }
  1455  
  1456  // parsePageTokenValue parses the page token values to be used in the search query.
  1457  // The page token comes from the `sort` field from the previous response from Elasticsearch.
  1458  // Depending on the type of the field, the null value is represented differently:
  1459  //   - integer, bool, and datetime: MaxInt64 (desc) or MinInt64 (asc)
  1460  //   - double: "Infinity" (desc) or "-Infinity" (asc)
  1461  //   - keyword: nil
  1462  //
  1463  // Furthermore, for bool and datetime, they need to be converted to boolean or the RFC3339Nano
  1464  // formats respectively.
  1465  //
  1466  //nolint:revive // cyclomatic complexity
  1467  func parsePageTokenValue(
  1468  	fieldName string, jsonValue any,
  1469  	tp enumspb.IndexedValueType,
  1470  ) (any, error) {
  1471  	switch tp {
  1472  	case enumspb.INDEXED_VALUE_TYPE_INT,
  1473  		enumspb.INDEXED_VALUE_TYPE_BOOL,
  1474  		enumspb.INDEXED_VALUE_TYPE_DATETIME:
  1475  		jsonNumber, ok := jsonValue.(json.Number)
  1476  		if !ok {
  1477  			return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1478  				"Invalid page token: expected interger type, got %q", jsonValue))
  1479  		}
  1480  		num, err := jsonNumber.Int64()
  1481  		if err != nil {
  1482  			return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1483  				"Invalid page token: expected interger type, got %v", jsonValue))
  1484  		}
  1485  		if num == math.MaxInt64 || num == math.MinInt64 {
  1486  			return nil, nil
  1487  		}
  1488  		if tp == enumspb.INDEXED_VALUE_TYPE_BOOL {
  1489  			return num != 0, nil
  1490  		}
  1491  		if tp == enumspb.INDEXED_VALUE_TYPE_DATETIME {
  1492  			return time.Unix(0, num).UTC().Format(time.RFC3339Nano), nil
  1493  		}
  1494  		return num, nil
  1495  
  1496  	case enumspb.INDEXED_VALUE_TYPE_DOUBLE:
  1497  		switch v := jsonValue.(type) {
  1498  		case json.Number:
  1499  			num, err := v.Float64()
  1500  			if err != nil {
  1501  				return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1502  					"Invalid page token: expected float type, got %v", jsonValue))
  1503  			}
  1504  			return num, nil
  1505  		case string:
  1506  			// it can be the string representation of infinity
  1507  			if _, err := strconv.ParseFloat(v, 64); err != nil {
  1508  				return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1509  					"Invalid page token: expected float type, got %q", jsonValue))
  1510  			}
  1511  			return nil, nil
  1512  		default:
  1513  			// it should never reach here
  1514  			return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1515  				"Invalid page token: expected float type, got %#v", jsonValue))
  1516  		}
  1517  
  1518  	case enumspb.INDEXED_VALUE_TYPE_KEYWORD:
  1519  		if jsonValue == nil {
  1520  			return nil, nil
  1521  		}
  1522  		if _, ok := jsonValue.(string); !ok {
  1523  			return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1524  				"Invalid page token: expected string type, got %v", jsonValue))
  1525  		}
  1526  		return jsonValue, nil
  1527  
  1528  	default:
  1529  		return nil, serviceerror.NewInvalidArgument(fmt.Sprintf(
  1530  			"Invalid field type in sorter: cannot order by %q",
  1531  			fieldName,
  1532  		))
  1533  	}
  1534  }
  1535  
  1536  func validateDatetime(value time.Time) error {
  1537  	if value.Before(minTime) || value.After(maxTime) {
  1538  		return serviceerror.NewInvalidArgument(
  1539  			fmt.Sprintf("Date not supported in Elasticsearch: %v", value),
  1540  		)
  1541  	}
  1542  	return nil
  1543  }
  1544  
  1545  func validateString(value string) error {
  1546  	if len(value) > maxStringLength {
  1547  		return serviceerror.NewInvalidArgument(
  1548  			fmt.Sprintf(
  1549  				"Strings with more than %d bytes are not supported in Elasticsearch (got %s)",
  1550  				maxStringLength,
  1551  				value,
  1552  			),
  1553  		)
  1554  	}
  1555  	return nil
  1556  }