go.temporal.io/server@v1.23.0/common/persistence/visibility/store/elasticsearch/visibility_store_read_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 elasticsearch
    26  
    27  import (
    28  	"context"
    29  	"encoding/json"
    30  	"errors"
    31  	"fmt"
    32  	"math"
    33  	"strings"
    34  	"testing"
    35  	"time"
    36  
    37  	"github.com/golang/mock/gomock"
    38  	"github.com/olivere/elastic/v7"
    39  	"github.com/stretchr/testify/require"
    40  	"github.com/stretchr/testify/suite"
    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  	"google.golang.org/protobuf/types/known/timestamppb"
    46  
    47  	"go.temporal.io/api/temporalproto"
    48  	"go.temporal.io/server/common/debug"
    49  	"go.temporal.io/server/common/dynamicconfig"
    50  	"go.temporal.io/server/common/metrics"
    51  	"go.temporal.io/server/common/namespace"
    52  	"go.temporal.io/server/common/persistence/visibility/manager"
    53  	"go.temporal.io/server/common/persistence/visibility/store/elasticsearch/client"
    54  	"go.temporal.io/server/common/persistence/visibility/store/query"
    55  	"go.temporal.io/server/common/searchattribute"
    56  	"go.temporal.io/server/common/testing/protorequire"
    57  )
    58  
    59  type (
    60  	ESVisibilitySuite struct {
    61  		suite.Suite
    62  		// override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test, not merely log an error
    63  		*require.Assertions
    64  		protorequire.ProtoAssertions
    65  		controller                         *gomock.Controller
    66  		visibilityStore                    *visibilityStore
    67  		mockESClient                       *client.MockClient
    68  		mockProcessor                      *MockProcessor
    69  		mockMetricsHandler                 *metrics.MockHandler
    70  		mockSearchAttributesMapperProvider *searchattribute.MockMapperProvider
    71  	}
    72  )
    73  
    74  var (
    75  	testIndex        = "test-index"
    76  	testNamespace    = namespace.Name("test-namespace")
    77  	testNamespaceID  = namespace.ID("bfd5c907-f899-4baf-a7b2-2ab85e623ebd")
    78  	testNSDivision   = "hidden-stuff"
    79  	testPageSize     = 5
    80  	testEarliestTime = time.Unix(0, 1547596872371000000).UTC()
    81  	testLatestTime   = time.Unix(0, 2547596872371000000).UTC()
    82  	testWorkflowType = "test-wf-type"
    83  	testWorkflowID   = "test-wid"
    84  	testRunID        = "test-rid"
    85  	testStatus       = enumspb.WORKFLOW_EXECUTION_STATUS_FAILED
    86  
    87  	testSearchResult = &elastic.SearchResult{
    88  		Hits: &elastic.SearchHits{},
    89  	}
    90  	errTestESSearch = errors.New("ES error")
    91  
    92  	filterOpen              = fmt.Sprintf("map[term:map[ExecutionStatus:%s]", enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING)
    93  	filterCloseRE           = fmt.Sprintf(`must_not:\[?map\[term:map\[ExecutionStatus:%s\]\]`, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING)
    94  	filterByType            = fmt.Sprintf("map[term:map[WorkflowType:%s]", testWorkflowType)
    95  	filterByWID             = fmt.Sprintf("map[term:map[WorkflowId:%s]", testWorkflowID)
    96  	filterByExecutionStatus = fmt.Sprintf("map[term:map[ExecutionStatus:%s]", testStatus.String())
    97  	filterByNSDivision      = fmt.Sprintf("map[term:map[TemporalNamespaceDivision:%s]", testNSDivision)
    98  
    99  	namespaceDivisionExists = elastic.NewExistsQuery(searchattribute.TemporalNamespaceDivision)
   100  )
   101  
   102  func createTestRequest() *manager.ListWorkflowExecutionsRequest {
   103  	return &manager.ListWorkflowExecutionsRequest{
   104  		NamespaceID:       testNamespaceID,
   105  		Namespace:         testNamespace,
   106  		PageSize:          testPageSize,
   107  		EarliestStartTime: testEarliestTime,
   108  		LatestStartTime:   testLatestTime,
   109  	}
   110  }
   111  
   112  func createTestRequestWithNSDivision() *manager.ListWorkflowExecutionsRequest {
   113  	req := createTestRequest()
   114  	req.NamespaceDivision = testNSDivision
   115  	return req
   116  }
   117  
   118  func TestESVisibilitySuite(t *testing.T) {
   119  	suite.Run(t, new(ESVisibilitySuite))
   120  }
   121  
   122  func (s *ESVisibilitySuite) SetupTest() {
   123  	// Have to define our overridden assertions in the test setup. If we did it earlier, s.T() will return nil
   124  	s.Assertions = require.New(s.T())
   125  	s.ProtoAssertions = protorequire.New(s.T())
   126  
   127  	esProcessorAckTimeout := dynamicconfig.GetDurationPropertyFn(1 * time.Minute * debug.TimeoutMultiplier)
   128  	visibilityDisableOrderByClause := dynamicconfig.GetBoolPropertyFnFilteredByNamespace(false)
   129  	visibilityEnableManualPagination := dynamicconfig.GetBoolPropertyFnFilteredByNamespace(true)
   130  
   131  	s.controller = gomock.NewController(s.T())
   132  	s.mockMetricsHandler = metrics.NewMockHandler(s.controller)
   133  	s.mockMetricsHandler.EXPECT().WithTags(metrics.OperationTag(metrics.ElasticsearchVisibility)).Return(s.mockMetricsHandler).AnyTimes()
   134  	s.mockProcessor = NewMockProcessor(s.controller)
   135  	s.mockESClient = client.NewMockClient(s.controller)
   136  	s.mockSearchAttributesMapperProvider = searchattribute.NewMockMapperProvider(s.controller)
   137  	s.visibilityStore = NewVisibilityStore(
   138  		s.mockESClient,
   139  		testIndex,
   140  		searchattribute.NewTestProvider(),
   141  		searchattribute.NewTestMapperProvider(nil),
   142  		s.mockProcessor,
   143  		esProcessorAckTimeout,
   144  		visibilityDisableOrderByClause,
   145  		visibilityEnableManualPagination,
   146  		s.mockMetricsHandler,
   147  	)
   148  }
   149  
   150  func (s *ESVisibilitySuite) TearDownTest() {
   151  	s.controller.Finish()
   152  }
   153  
   154  func (s *ESVisibilitySuite) TestListOpenWorkflowExecutions() {
   155  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   156  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   157  			source, _ := input.Query.Source()
   158  			s.Contains(fmt.Sprintf("%v", source), filterOpen)
   159  			return testSearchResult, nil
   160  		})
   161  	_, err := s.visibilityStore.ListOpenWorkflowExecutions(context.Background(), createTestRequest())
   162  	s.NoError(err)
   163  
   164  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   165  	_, err = s.visibilityStore.ListOpenWorkflowExecutions(context.Background(), createTestRequest())
   166  	s.Error(err)
   167  	_, ok := err.(*serviceerror.Unavailable)
   168  	s.True(ok)
   169  	s.Contains(err.Error(), "ListOpenWorkflowExecutions failed")
   170  }
   171  
   172  func (s *ESVisibilitySuite) TestListOpenWorkflowExecutionsWithNamespaceDivision() {
   173  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   174  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   175  			source, _ := input.Query.Source()
   176  			s.Contains(fmt.Sprintf("%v", source), filterOpen)
   177  			s.Contains(fmt.Sprintf("%v", source), filterByNSDivision)
   178  			return testSearchResult, nil
   179  		})
   180  	_, err := s.visibilityStore.ListOpenWorkflowExecutions(context.Background(), createTestRequestWithNSDivision())
   181  	s.NoError(err)
   182  }
   183  
   184  func (s *ESVisibilitySuite) TestListClosedWorkflowExecutions() {
   185  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   186  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   187  			source, _ := input.Query.Source()
   188  			s.Regexp(filterCloseRE, fmt.Sprintf("%v", source))
   189  			return testSearchResult, nil
   190  		})
   191  	_, err := s.visibilityStore.ListClosedWorkflowExecutions(context.Background(), createTestRequest())
   192  	s.NoError(err)
   193  
   194  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   195  	_, err = s.visibilityStore.ListClosedWorkflowExecutions(context.Background(), createTestRequest())
   196  	s.Error(err)
   197  	_, ok := err.(*serviceerror.Unavailable)
   198  	s.True(ok)
   199  	s.Contains(err.Error(), "ListClosedWorkflowExecutions failed")
   200  }
   201  
   202  func (s *ESVisibilitySuite) TestListOpenWorkflowExecutionsByType() {
   203  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   204  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   205  			source, _ := input.Query.Source()
   206  			s.Contains(fmt.Sprintf("%v", source), filterOpen)
   207  			s.Contains(fmt.Sprintf("%v", source), filterByType)
   208  			return testSearchResult, nil
   209  		})
   210  
   211  	testRequest := createTestRequest()
   212  	request := &manager.ListWorkflowExecutionsByTypeRequest{
   213  		ListWorkflowExecutionsRequest: testRequest,
   214  		WorkflowTypeName:              testWorkflowType,
   215  	}
   216  	_, err := s.visibilityStore.ListOpenWorkflowExecutionsByType(context.Background(), request)
   217  	s.NoError(err)
   218  
   219  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   220  	_, err = s.visibilityStore.ListOpenWorkflowExecutionsByType(context.Background(), request)
   221  	s.Error(err)
   222  	_, ok := err.(*serviceerror.Unavailable)
   223  	s.True(ok)
   224  	s.Contains(err.Error(), "ListOpenWorkflowExecutionsByType failed")
   225  }
   226  
   227  func (s *ESVisibilitySuite) TestListClosedWorkflowExecutionsByType() {
   228  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   229  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   230  			source, _ := input.Query.Source()
   231  			s.Regexp(filterCloseRE, fmt.Sprintf("%v", source))
   232  			s.Contains(fmt.Sprintf("%v", source), filterByType)
   233  			return testSearchResult, nil
   234  		})
   235  
   236  	testRequest := createTestRequest()
   237  	request := &manager.ListWorkflowExecutionsByTypeRequest{
   238  		ListWorkflowExecutionsRequest: testRequest,
   239  		WorkflowTypeName:              testWorkflowType,
   240  	}
   241  	_, err := s.visibilityStore.ListClosedWorkflowExecutionsByType(context.Background(), request)
   242  	s.NoError(err)
   243  
   244  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   245  	_, err = s.visibilityStore.ListClosedWorkflowExecutionsByType(context.Background(), request)
   246  	s.Error(err)
   247  	_, ok := err.(*serviceerror.Unavailable)
   248  	s.True(ok)
   249  	s.Contains(err.Error(), "ListClosedWorkflowExecutionsByType failed")
   250  }
   251  
   252  func (s *ESVisibilitySuite) TestListOpenWorkflowExecutionsByWorkflowID() {
   253  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   254  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   255  			source, _ := input.Query.Source()
   256  			s.Contains(fmt.Sprintf("%v", source), filterOpen)
   257  			s.Contains(fmt.Sprintf("%v", source), filterByWID)
   258  			return testSearchResult, nil
   259  		})
   260  
   261  	testRequest := createTestRequest()
   262  	request := &manager.ListWorkflowExecutionsByWorkflowIDRequest{
   263  		ListWorkflowExecutionsRequest: testRequest,
   264  		WorkflowID:                    testWorkflowID,
   265  	}
   266  	_, err := s.visibilityStore.ListOpenWorkflowExecutionsByWorkflowID(context.Background(), request)
   267  	s.NoError(err)
   268  
   269  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   270  	_, err = s.visibilityStore.ListOpenWorkflowExecutionsByWorkflowID(context.Background(), request)
   271  	s.Error(err)
   272  	_, ok := err.(*serviceerror.Unavailable)
   273  	s.True(ok)
   274  	s.Contains(err.Error(), "ListOpenWorkflowExecutionsByWorkflowID failed")
   275  }
   276  
   277  func (s *ESVisibilitySuite) TestListClosedWorkflowExecutionsByWorkflowID() {
   278  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   279  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   280  			source, _ := input.Query.Source()
   281  			s.Regexp(filterCloseRE, fmt.Sprintf("%v", source))
   282  			s.Contains(fmt.Sprintf("%v", source), filterByWID)
   283  			return testSearchResult, nil
   284  		})
   285  
   286  	testRequest := createTestRequest()
   287  	request := &manager.ListWorkflowExecutionsByWorkflowIDRequest{
   288  		ListWorkflowExecutionsRequest: testRequest,
   289  		WorkflowID:                    testWorkflowID,
   290  	}
   291  	_, err := s.visibilityStore.ListClosedWorkflowExecutionsByWorkflowID(context.Background(), request)
   292  	s.NoError(err)
   293  
   294  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   295  	_, err = s.visibilityStore.ListClosedWorkflowExecutionsByWorkflowID(context.Background(), request)
   296  	s.Error(err)
   297  	_, ok := err.(*serviceerror.Unavailable)
   298  	s.True(ok)
   299  	s.Contains(err.Error(), "ListClosedWorkflowExecutionsByWorkflowID failed")
   300  }
   301  
   302  func (s *ESVisibilitySuite) TestListClosedWorkflowExecutionsByStatus() {
   303  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
   304  		func(ctx context.Context, input *client.SearchParameters) (*elastic.SearchResult, error) {
   305  			source, _ := input.Query.Source()
   306  			s.Contains(fmt.Sprintf("%v", source), filterByExecutionStatus)
   307  			return testSearchResult, nil
   308  		})
   309  
   310  	testRequest := createTestRequest()
   311  	request := &manager.ListClosedWorkflowExecutionsByStatusRequest{
   312  		ListWorkflowExecutionsRequest: testRequest,
   313  		Status:                        testStatus,
   314  	}
   315  	_, err := s.visibilityStore.ListClosedWorkflowExecutionsByStatus(context.Background(), request)
   316  	s.NoError(err)
   317  
   318  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
   319  	_, err = s.visibilityStore.ListClosedWorkflowExecutionsByStatus(context.Background(), request)
   320  	s.Error(err)
   321  	_, ok := err.(*serviceerror.Unavailable)
   322  	s.True(ok)
   323  	s.Contains(err.Error(), "ListClosedWorkflowExecutionsByStatus failed")
   324  }
   325  
   326  func (s *ESVisibilitySuite) TestBuildSearchParameters() {
   327  	request := createTestRequest()
   328  
   329  	matchNamespaceQuery := elastic.NewTermQuery(searchattribute.NamespaceID, request.NamespaceID.String())
   330  	runningQuery := elastic.NewTermQuery(searchattribute.ExecutionStatus, int(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING))
   331  
   332  	token := &visibilityPageToken{SearchAfter: []interface{}{1528358645123456789, "qwe"}}
   333  	var err error
   334  	request.NextPageToken, err = s.visibilityStore.serializePageToken(token)
   335  	s.NoError(err)
   336  
   337  	// test for open
   338  	rangeQuery := elastic.NewRangeQuery(searchattribute.StartTime).Gte(request.EarliestStartTime).Lte(request.LatestStartTime)
   339  	boolQuery := elastic.NewBoolQuery().Filter(runningQuery).Filter(matchNamespaceQuery).Filter(rangeQuery).MustNot(namespaceDivisionExists)
   340  	p, err := s.visibilityStore.buildSearchParameters(request, elastic.NewBoolQuery().Filter(elastic.NewTermQuery(searchattribute.ExecutionStatus, int(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING))), true)
   341  	s.NoError(err)
   342  	s.Equal(&client.SearchParameters{
   343  		Index:       testIndex,
   344  		Query:       boolQuery,
   345  		SearchAfter: []interface{}{json.Number("1528358645123456789"), "qwe"},
   346  		PageSize:    testPageSize,
   347  		Sorter:      defaultSorter,
   348  	}, p)
   349  
   350  	// test request latestTime overflow
   351  	request.LatestStartTime = time.Unix(0, math.MaxInt64).UTC()
   352  	rangeQuery = elastic.NewRangeQuery(searchattribute.StartTime).Gte(request.EarliestStartTime).Lte(request.LatestStartTime)
   353  	boolQuery = elastic.NewBoolQuery().Filter(runningQuery).Filter(matchNamespaceQuery).Filter(rangeQuery).MustNot(namespaceDivisionExists)
   354  	p, err = s.visibilityStore.buildSearchParameters(request, elastic.NewBoolQuery().Filter(elastic.NewTermQuery(searchattribute.ExecutionStatus, int(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING))), true)
   355  	s.NoError(err)
   356  	s.Equal(&client.SearchParameters{
   357  		Index:       testIndex,
   358  		Query:       boolQuery,
   359  		SearchAfter: []interface{}{json.Number("1528358645123456789"), "qwe"},
   360  		PageSize:    testPageSize,
   361  		Sorter:      defaultSorter,
   362  	}, p)
   363  	request = createTestRequest() // revert
   364  
   365  	// test for closed
   366  	rangeQuery = elastic.NewRangeQuery(searchattribute.CloseTime).Gte(request.EarliestStartTime).Lte(request.LatestStartTime)
   367  	boolQuery = elastic.NewBoolQuery().MustNot(runningQuery).Filter(matchNamespaceQuery).Filter(rangeQuery).MustNot(namespaceDivisionExists)
   368  	p, err = s.visibilityStore.buildSearchParameters(request, elastic.NewBoolQuery().MustNot(elastic.NewTermQuery(searchattribute.ExecutionStatus, int(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING))), false)
   369  	s.NoError(err)
   370  	s.Equal(&client.SearchParameters{
   371  		Index:       testIndex,
   372  		Query:       boolQuery,
   373  		SearchAfter: nil,
   374  		PageSize:    testPageSize,
   375  		Sorter:      defaultSorter,
   376  	}, p)
   377  
   378  	// test for additional boolQuery
   379  	rangeQuery = elastic.NewRangeQuery(searchattribute.StartTime).Gte(request.EarliestStartTime).Lte(request.LatestStartTime)
   380  	matchQuery := elastic.NewTermQuery(searchattribute.ExecutionStatus, int32(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING))
   381  	boolQuery = elastic.NewBoolQuery().Filter(matchQuery).Filter(matchNamespaceQuery).Filter(rangeQuery).MustNot(namespaceDivisionExists)
   382  	p, err = s.visibilityStore.buildSearchParameters(request, elastic.NewBoolQuery().Filter(matchQuery), true)
   383  	s.NoError(err)
   384  	s.Equal(&client.SearchParameters{
   385  		Index:       testIndex,
   386  		Query:       boolQuery,
   387  		SearchAfter: nil,
   388  		PageSize:    testPageSize,
   389  		Sorter:      defaultSorter,
   390  	}, p)
   391  
   392  	// test for search after
   393  	token = &visibilityPageToken{SearchAfter: []interface{}{json.Number("1528358645123456789"), "qwe"}}
   394  	request.NextPageToken, err = s.visibilityStore.serializePageToken(token)
   395  	s.NoError(err)
   396  
   397  	rangeQuery = elastic.NewRangeQuery(searchattribute.StartTime).Gte(request.EarliestStartTime).Lte(request.LatestStartTime)
   398  	boolQuery = elastic.NewBoolQuery().Filter(matchNamespaceQuery).Filter(rangeQuery).MustNot(namespaceDivisionExists)
   399  	p, err = s.visibilityStore.buildSearchParameters(request, elastic.NewBoolQuery(), true)
   400  	s.NoError(err)
   401  	s.Equal(&client.SearchParameters{
   402  		Index:       testIndex,
   403  		Query:       boolQuery,
   404  		SearchAfter: token.SearchAfter,
   405  		PageSize:    testPageSize,
   406  		Sorter:      defaultSorter,
   407  	}, p)
   408  	request = createTestRequest() // revert
   409  
   410  	// test for nil token
   411  	rangeQuery = elastic.NewRangeQuery(searchattribute.StartTime).Gte(request.EarliestStartTime).Lte(request.LatestStartTime)
   412  	boolQuery = elastic.NewBoolQuery().Filter(matchNamespaceQuery).Filter(rangeQuery).MustNot(namespaceDivisionExists)
   413  	request.NextPageToken = nil
   414  	p, err = s.visibilityStore.buildSearchParameters(request, elastic.NewBoolQuery(), true)
   415  	s.NoError(err)
   416  	s.Equal(&client.SearchParameters{
   417  		Index:       testIndex,
   418  		Query:       boolQuery,
   419  		PageSize:    testPageSize,
   420  		SearchAfter: nil,
   421  		Sorter:      defaultSorter,
   422  	}, p)
   423  }
   424  
   425  func (s *ESVisibilitySuite) TestGetListFieldSorter() {
   426  
   427  	// test defaultSorter is returned when fieldSorts is empty
   428  	fieldSorts := make([]elastic.Sorter, 0)
   429  	sorter, err := s.visibilityStore.getListFieldSorter(fieldSorts)
   430  	s.NoError(err)
   431  	s.Equal(defaultSorter, sorter)
   432  
   433  	// test passing non-empty fieldSorts
   434  	testFieldSorts := []elastic.Sorter{elastic.NewFieldSort("_test"), elastic.NewFieldSort("_second_tes")}
   435  	sorter, err = s.visibilityStore.getListFieldSorter(testFieldSorts[:])
   436  	expectedSorter := make([]elastic.Sorter, len(testFieldSorts)+1)
   437  	expectedSorter[0] = testFieldSorts[0]
   438  	expectedSorter[1] = testFieldSorts[1]
   439  	expectedSorter[2] = elastic.NewFieldSort(searchattribute.RunID).Desc()
   440  	s.NoError(err)
   441  	s.Equal(expectedSorter, sorter)
   442  
   443  }
   444  
   445  func (s *ESVisibilitySuite) TestGetScanFieldSorter() {
   446  	// test docSorter is returned when fieldSorts is empty
   447  	fieldSorts := make([]elastic.Sorter, 0)
   448  	sorter, err := s.visibilityStore.getScanFieldSorter(fieldSorts)
   449  	s.NoError(err)
   450  	s.Equal(docSorter, sorter)
   451  
   452  	// test error is returned if fieldSorts is not empty
   453  	testFieldSorts := []elastic.Sorter{elastic.NewFieldSort("_test"), elastic.NewFieldSort("_second_tes")}
   454  	sorter, err = s.visibilityStore.getScanFieldSorter(testFieldSorts[:])
   455  	s.Error(err)
   456  	s.Nil(sorter)
   457  }
   458  
   459  func (s *ESVisibilitySuite) TestBuildSearchParametersV2() {
   460  	request := &manager.ListWorkflowExecutionsRequestV2{
   461  		NamespaceID: testNamespaceID,
   462  		Namespace:   testNamespace,
   463  		PageSize:    testPageSize,
   464  	}
   465  
   466  	matchNamespaceQuery := elastic.NewTermQuery(searchattribute.NamespaceID, request.NamespaceID.String())
   467  	matchNSDivision := elastic.NewMatchQuery(searchattribute.TemporalNamespaceDivision, "hidden-stuff")
   468  
   469  	// test for open
   470  	request.Query = `WorkflowId="guid-2208"`
   471  	filterQuery := elastic.NewBoolQuery().Filter(elastic.NewMatchQuery(searchattribute.WorkflowID, "guid-2208"))
   472  	boolQuery := elastic.NewBoolQuery().Filter(matchNamespaceQuery, filterQuery).MustNot(namespaceDivisionExists)
   473  	p, err := s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getListFieldSorter)
   474  	s.NoError(err)
   475  	s.Equal(&client.SearchParameters{
   476  		Index:       testIndex,
   477  		Query:       boolQuery,
   478  		SearchAfter: nil,
   479  		PointInTime: nil,
   480  		PageSize:    testPageSize,
   481  		Sorter:      defaultSorter,
   482  	}, p)
   483  	request.Query = ""
   484  
   485  	// test for open with namespace division
   486  	request.Query = `WorkflowId="guid-2208" and TemporalNamespaceDivision="hidden-stuff"`
   487  	// note namespace division appears in the filterQuery, not the boolQuery like the negative version
   488  	filterQuery = elastic.NewBoolQuery().Filter(elastic.NewMatchQuery(searchattribute.WorkflowID, "guid-2208"), matchNSDivision)
   489  	boolQuery = elastic.NewBoolQuery().Filter(matchNamespaceQuery, filterQuery)
   490  	p, err = s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getListFieldSorter)
   491  	s.NoError(err)
   492  	s.Equal(&client.SearchParameters{
   493  		Index:       testIndex,
   494  		Query:       boolQuery,
   495  		SearchAfter: nil,
   496  		PointInTime: nil,
   497  		PageSize:    testPageSize,
   498  		Sorter:      defaultSorter,
   499  	}, p)
   500  	request.Query = ""
   501  
   502  	// test custom sort
   503  	request.Query = `Order bY WorkflowId`
   504  	boolQuery = elastic.NewBoolQuery().Filter(matchNamespaceQuery).MustNot(namespaceDivisionExists)
   505  	s.mockMetricsHandler.EXPECT().WithTags(metrics.NamespaceTag(request.Namespace.String())).Return(s.mockMetricsHandler)
   506  	s.mockMetricsHandler.EXPECT().Counter(metrics.ElasticsearchCustomOrderByClauseCount.Name()).Return(metrics.NoopCounterMetricFunc)
   507  	p, err = s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getListFieldSorter)
   508  	s.NoError(err)
   509  	s.Equal(&client.SearchParameters{
   510  		Index:       testIndex,
   511  		Query:       boolQuery,
   512  		SearchAfter: nil,
   513  		PointInTime: nil,
   514  		PageSize:    testPageSize,
   515  		Sorter: []elastic.Sorter{
   516  			elastic.NewFieldSort(searchattribute.WorkflowID).Asc(),
   517  			elastic.NewFieldSort(searchattribute.RunID).Desc(),
   518  		},
   519  	}, p)
   520  	request.Query = ""
   521  
   522  	// test with Scan API
   523  	request.Query = `WorkflowId="guid-2208"`
   524  	filterQuery = elastic.NewBoolQuery().Filter(elastic.NewMatchQuery(searchattribute.WorkflowID, "guid-2208"))
   525  	boolQuery = elastic.NewBoolQuery().Filter(matchNamespaceQuery, filterQuery).MustNot(namespaceDivisionExists)
   526  	p, err = s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getScanFieldSorter)
   527  	s.NoError(err)
   528  	s.Equal(&client.SearchParameters{
   529  		Index:       testIndex,
   530  		Query:       boolQuery,
   531  		SearchAfter: nil,
   532  		PointInTime: nil,
   533  		PageSize:    testPageSize,
   534  		Sorter:      docSorter,
   535  	}, p)
   536  	request.Query = ""
   537  
   538  	// test with Scan API with custom sort
   539  	request.Query = `Order bY WorkflowId`
   540  	s.mockMetricsHandler.EXPECT().WithTags(metrics.NamespaceTag(request.Namespace.String())).Return(s.mockMetricsHandler)
   541  	s.mockMetricsHandler.EXPECT().Counter(metrics.ElasticsearchCustomOrderByClauseCount.Name()).Return(metrics.NoopCounterMetricFunc)
   542  	p, err = s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getScanFieldSorter)
   543  	s.Error(err)
   544  	s.Nil(p)
   545  	request.Query = ""
   546  
   547  	// test for wrong query
   548  	request.Query = "invalid query"
   549  	p, err = s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getListFieldSorter)
   550  	s.Nil(p)
   551  	s.Error(err)
   552  	request.Query = ""
   553  }
   554  
   555  func (s *ESVisibilitySuite) TestBuildSearchParametersV2DisableOrderByClause() {
   556  	request := &manager.ListWorkflowExecutionsRequestV2{
   557  		NamespaceID: testNamespaceID,
   558  		Namespace:   testNamespace,
   559  		PageSize:    testPageSize,
   560  	}
   561  
   562  	matchNamespaceQuery := elastic.NewTermQuery(searchattribute.NamespaceID, request.NamespaceID.String())
   563  
   564  	// disable ORDER BY clause
   565  	s.visibilityStore.disableOrderByClause = dynamicconfig.GetBoolPropertyFnFilteredByNamespace(true)
   566  
   567  	// test valid query
   568  	request.Query = `WorkflowId="guid-2208"`
   569  	filterQuery := elastic.NewBoolQuery().Filter(elastic.NewMatchQuery(searchattribute.WorkflowID, "guid-2208"))
   570  	boolQuery := elastic.NewBoolQuery().Filter(matchNamespaceQuery, filterQuery).MustNot(namespaceDivisionExists)
   571  	p, err := s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getListFieldSorter)
   572  	s.NoError(err)
   573  	s.Equal(&client.SearchParameters{
   574  		Index:       testIndex,
   575  		Query:       boolQuery,
   576  		SearchAfter: nil,
   577  		PointInTime: nil,
   578  		PageSize:    testPageSize,
   579  		Sorter:      defaultSorter,
   580  	}, p)
   581  	request.Query = ""
   582  
   583  	// test invalid query with ORDER BY
   584  	request.Query = `ORDER BY WorkflowId`
   585  	p, err = s.visibilityStore.buildSearchParametersV2(request, s.visibilityStore.getListFieldSorter)
   586  	s.Nil(p)
   587  	s.Error(err)
   588  	var invalidArgumentErr *serviceerror.InvalidArgument
   589  	s.ErrorAs(err, &invalidArgumentErr)
   590  	s.EqualError(err, "ORDER BY clause is not supported")
   591  	request.Query = ""
   592  }
   593  
   594  func (s *ESVisibilitySuite) queryToJSON(q elastic.Query) string {
   595  	m, err := q.Source()
   596  	s.NoError(err)
   597  	b, err := json.Marshal(m)
   598  	s.NoError(err)
   599  	return string(b)
   600  }
   601  
   602  func (s *ESVisibilitySuite) sorterToJSON(sorters []elastic.Sorter) string {
   603  	var ms []interface{}
   604  	for _, sorter := range sorters {
   605  		m, err := sorter.Source()
   606  		s.NoError(err)
   607  		ms = append(ms, m)
   608  	}
   609  	b, err := json.Marshal(ms)
   610  	s.NoError(err)
   611  	return string(b)
   612  }
   613  
   614  func (s *ESVisibilitySuite) Test_convertQuery() {
   615  	query := `WorkflowId = 'wid'`
   616  	queryParams, err := s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   617  	s.NoError(err)
   618  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"match":{"WorkflowId":{"query":"wid"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   619  	s.Nil(queryParams.Sorter)
   620  
   621  	query = `WorkflowId = 'wid' or WorkflowId = 'another-wid'`
   622  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   623  	s.NoError(err)
   624  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"should":[{"match":{"WorkflowId":{"query":"wid"}}},{"match":{"WorkflowId":{"query":"another-wid"}}}]}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   625  	s.Nil(queryParams.Sorter)
   626  
   627  	query = `WorkflowId = 'wid' order by StartTime desc`
   628  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   629  	s.NoError(err)
   630  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"match":{"WorkflowId":{"query":"wid"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   631  	s.Equal(`[{"StartTime":{"order":"desc"}}]`, s.sorterToJSON(queryParams.Sorter))
   632  
   633  	query = `WorkflowId = 'wid' and CloseTime is null`
   634  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   635  	s.NoError(err)
   636  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":[{"match":{"WorkflowId":{"query":"wid"}}},{"bool":{"must_not":{"exists":{"field":"CloseTime"}}}}]}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   637  	s.Nil(queryParams.Sorter)
   638  
   639  	query = `WorkflowId = 'wid' or CloseTime is null`
   640  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   641  	s.NoError(err)
   642  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"should":[{"match":{"WorkflowId":{"query":"wid"}}},{"bool":{"must_not":{"exists":{"field":"CloseTime"}}}}]}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   643  	s.Nil(queryParams.Sorter)
   644  
   645  	query = `CloseTime is null order by CloseTime desc`
   646  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   647  	s.NoError(err)
   648  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"must_not":{"exists":{"field":"CloseTime"}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   649  	s.Equal(`[{"CloseTime":{"order":"desc"}}]`, s.sorterToJSON(queryParams.Sorter))
   650  
   651  	query = `StartTime = "2018-06-07T15:04:05.123456789-08:00"`
   652  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   653  	s.NoError(err)
   654  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"match":{"StartTime":{"query":"2018-06-07T15:04:05.123456789-08:00"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   655  	s.Nil(queryParams.Sorter)
   656  
   657  	query = `WorkflowId = 'wid' and StartTime > "2018-06-07T15:04:05+00:00"`
   658  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   659  	s.NoError(err)
   660  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":[{"match":{"WorkflowId":{"query":"wid"}}},{"range":{"StartTime":{"from":"2018-06-07T15:04:05+00:00","include_lower":false,"include_upper":true,"to":null}}}]}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   661  	s.Nil(queryParams.Sorter)
   662  
   663  	query = `ExecutionTime < 1000000`
   664  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   665  	s.NoError(err)
   666  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"range":{"ExecutionTime":{"from":null,"include_lower":true,"include_upper":false,"to":"1970-01-01T00:00:00.001Z"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   667  	s.Nil(queryParams.Sorter)
   668  
   669  	query = `ExecutionTime between 1 and 2`
   670  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   671  	s.NoError(err)
   672  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"range":{"ExecutionTime":{"from":"1970-01-01T00:00:00.000000001Z","include_lower":true,"include_upper":true,"to":"1970-01-01T00:00:00.000000002Z"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   673  	s.Nil(queryParams.Sorter)
   674  
   675  	query = `ExecutionTime < 1000000 or ExecutionTime > 2000000`
   676  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   677  	s.NoError(err)
   678  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"should":[{"range":{"ExecutionTime":{"from":null,"include_lower":true,"include_upper":false,"to":"1970-01-01T00:00:00.001Z"}}},{"range":{"ExecutionTime":{"from":"1970-01-01T00:00:00.002Z","include_lower":false,"include_upper":true,"to":null}}}]}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   679  	s.Nil(queryParams.Sorter)
   680  
   681  	query = `order by ExecutionTime`
   682  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   683  	s.NoError(err)
   684  	s.Equal(`{"bool":{"filter":{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   685  	s.Equal(`[{"ExecutionTime":{"order":"asc"}}]`, s.sorterToJSON(queryParams.Sorter))
   686  
   687  	query = `order by StartTime desc, CloseTime asc`
   688  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   689  	s.NoError(err)
   690  	s.Equal(`{"bool":{"filter":{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   691  	s.Equal(`[{"StartTime":{"order":"desc"}},{"CloseTime":{"order":"asc"}}]`, s.sorterToJSON(queryParams.Sorter))
   692  
   693  	query = `order by CustomTextField desc`
   694  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   695  	s.Error(err)
   696  	s.IsType(&serviceerror.InvalidArgument{}, err)
   697  	s.Equal(err.(*serviceerror.InvalidArgument).Error(), "invalid query: unable to convert 'order by' column name: unable to sort by field of Text type, use field of type Keyword")
   698  
   699  	query = `order by CustomIntField asc`
   700  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   701  	s.NoError(err)
   702  	s.Equal(`{"bool":{"filter":{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   703  	s.Equal(`[{"CustomIntField":{"order":"asc"}}]`, s.sorterToJSON(queryParams.Sorter))
   704  
   705  	query = `ExecutionTime < "unable to parse"`
   706  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   707  	s.Error(err)
   708  	s.IsType(&serviceerror.InvalidArgument{}, err)
   709  	s.Equal(err.Error(), "invalid query: unable to convert filter expression: unable to convert values of comparison expression: invalid value for search attribute ExecutionTime of type Datetime: \"unable to parse\"")
   710  
   711  	// invalid union injection
   712  	query = `WorkflowId = 'wid' union select * from dummy`
   713  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   714  	s.Error(err)
   715  	s.Nil(queryParams)
   716  }
   717  
   718  func (s *ESVisibilitySuite) Test_convertQuery_Mapper() {
   719  	s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(testNamespace).
   720  		Return(&searchattribute.TestMapper{}, nil).AnyTimes()
   721  
   722  	s.visibilityStore.searchAttributesMapperProvider = s.mockSearchAttributesMapperProvider
   723  
   724  	query := `WorkflowId = 'wid'`
   725  	queryParams, err := s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   726  	s.NoError(err)
   727  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"match":{"WorkflowId":{"query":"wid"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   728  	s.Nil(queryParams.Sorter)
   729  
   730  	query = `AliasForCustomKeywordField = 'pid'`
   731  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   732  	s.NoError(err)
   733  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"match":{"CustomKeywordField":{"query":"pid"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   734  	s.Nil(queryParams.Sorter)
   735  
   736  	query = `CustomKeywordField = 'pid'`
   737  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   738  	s.Error(err)
   739  	var invalidArgumentErr *serviceerror.InvalidArgument
   740  	s.ErrorAs(err, &invalidArgumentErr)
   741  	s.EqualError(err, "mapper error")
   742  
   743  	query = `AliasForUnknownField = 'pid'`
   744  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   745  	s.Error(err)
   746  	s.ErrorAs(err, &invalidArgumentErr)
   747  	s.EqualError(err, "invalid query: unable to convert filter expression: unable to convert left side of \"AliasForUnknownField = 'pid'\": invalid search attribute: AliasForUnknownField")
   748  
   749  	query = `order by ExecutionTime`
   750  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   751  	s.NoError(err)
   752  	s.Equal(`{"bool":{"filter":{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   753  	s.Equal(`[{"ExecutionTime":{"order":"asc"}}]`, s.sorterToJSON(queryParams.Sorter))
   754  
   755  	query = `order by AliasForCustomKeywordField asc`
   756  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   757  	s.NoError(err)
   758  	s.Equal(`{"bool":{"filter":{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   759  	s.Equal(`[{"CustomKeywordField":{"order":"asc"}}]`, s.sorterToJSON(queryParams.Sorter))
   760  
   761  	query = `order by CustomKeywordField asc`
   762  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   763  	s.Error(err)
   764  	s.ErrorAs(err, &invalidArgumentErr)
   765  	s.EqualError(err, "mapper error")
   766  
   767  	query = `order by AliasForUnknownField asc`
   768  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   769  	s.Error(err)
   770  	s.ErrorAs(err, &invalidArgumentErr)
   771  	s.EqualError(err, "invalid query: unable to convert 'order by' column name: invalid search attribute: AliasForUnknownField")
   772  	s.visibilityStore.searchAttributesMapperProvider = nil
   773  }
   774  
   775  func (s *ESVisibilitySuite) Test_convertQuery_Mapper_Error() {
   776  	s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(testNamespace).
   777  		Return(&searchattribute.TestMapper{}, nil).AnyTimes()
   778  
   779  	s.visibilityStore.searchAttributesMapperProvider = s.mockSearchAttributesMapperProvider
   780  
   781  	query := `WorkflowId = 'wid'`
   782  	queryParams, err := s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   783  	s.NoError(err)
   784  	s.Equal(`{"bool":{"filter":[{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},{"bool":{"filter":{"match":{"WorkflowId":{"query":"wid"}}}}}],"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   785  	s.Nil(queryParams.Sorter)
   786  
   787  	query = `ProductId = 'pid'`
   788  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   789  	s.Error(err)
   790  	var invalidArgumentErr *serviceerror.InvalidArgument
   791  	s.ErrorAs(err, &invalidArgumentErr)
   792  	s.EqualError(err, "mapper error")
   793  
   794  	query = `order by ExecutionTime`
   795  	queryParams, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   796  	s.NoError(err)
   797  	s.Equal(`{"bool":{"filter":{"term":{"NamespaceId":"bfd5c907-f899-4baf-a7b2-2ab85e623ebd"}},"must_not":{"exists":{"field":"TemporalNamespaceDivision"}}}}`, s.queryToJSON(queryParams.Query))
   798  	s.Equal(`[{"ExecutionTime":{"order":"asc"}}]`, s.sorterToJSON(queryParams.Sorter))
   799  
   800  	query = `order by CustomIntField asc`
   801  	_, err = s.visibilityStore.convertQuery(testNamespace, testNamespaceID, query)
   802  	s.Error(err)
   803  	s.ErrorAs(err, &invalidArgumentErr)
   804  	s.EqualError(err, "mapper error")
   805  
   806  	s.visibilityStore.searchAttributesMapperProvider = nil
   807  }
   808  
   809  func (s *ESVisibilitySuite) TestGetListWorkflowExecutionsResponse() {
   810  	// test for empty hits
   811  	searchResult := &elastic.SearchResult{
   812  		Hits: &elastic.SearchHits{
   813  			TotalHits: &elastic.TotalHits{},
   814  		}}
   815  	resp, err := s.visibilityStore.getListWorkflowExecutionsResponse(searchResult, testNamespace, 1)
   816  	s.NoError(err)
   817  	s.Equal(0, len(resp.NextPageToken))
   818  	s.Equal(0, len(resp.Executions))
   819  
   820  	// test for one hits
   821  	data := []byte(`{"ExecutionStatus": "Running",
   822            "CloseTime": "2021-06-11T16:04:07.980-07:00",
   823            "NamespaceId": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
   824            "HistoryLength": 29,
   825            "StateTransitionCount": 22,
   826            "VisibilityTaskKey": "7-619",
   827            "RunId": "e481009e-14b3-45ae-91af-dce6e2a88365",
   828            "StartTime": "2021-06-11T15:04:07.980-07:00",
   829            "WorkflowId": "6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256",
   830            "WorkflowType": "basic.stressWorkflowExecute"}`)
   831  	source := json.RawMessage(data)
   832  	searchHit := &elastic.SearchHit{
   833  		Source: source,
   834  		Sort:   []interface{}{1547596872371234567, "e481009e-14b3-45ae-91af-dce6e2a88365"},
   835  	}
   836  	searchResult.Hits.Hits = []*elastic.SearchHit{searchHit}
   837  	searchResult.Hits.TotalHits.Value = 1
   838  	resp, err = s.visibilityStore.getListWorkflowExecutionsResponse(searchResult, testNamespace, 1)
   839  	s.NoError(err)
   840  	serializedToken, _ := s.visibilityStore.serializePageToken(&visibilityPageToken{SearchAfter: []interface{}{1547596872371234567, "e481009e-14b3-45ae-91af-dce6e2a88365"}})
   841  	s.Equal(serializedToken, resp.NextPageToken)
   842  	s.Equal(1, len(resp.Executions))
   843  
   844  	// test for last page hits
   845  	resp, err = s.visibilityStore.getListWorkflowExecutionsResponse(searchResult, testNamespace, 2)
   846  	s.NoError(err)
   847  	s.Equal(0, len(resp.NextPageToken))
   848  	s.Equal(1, len(resp.Executions))
   849  
   850  	// test for search after
   851  	searchResult.Hits.Hits = []*elastic.SearchHit{}
   852  	for i := int64(0); i < searchResult.Hits.TotalHits.Value; i++ {
   853  		searchResult.Hits.Hits = append(searchResult.Hits.Hits, searchHit)
   854  	}
   855  	numOfHits := len(searchResult.Hits.Hits)
   856  	resp, err = s.visibilityStore.getListWorkflowExecutionsResponse(searchResult, testNamespace, numOfHits)
   857  	s.NoError(err)
   858  	s.Equal(numOfHits, len(resp.Executions))
   859  	nextPageToken, err := s.visibilityStore.deserializePageToken(resp.NextPageToken)
   860  	s.NoError(err)
   861  	resultSortValue, err := nextPageToken.SearchAfter[0].(json.Number).Int64()
   862  	s.NoError(err)
   863  	s.Equal(int64(1547596872371234567), resultSortValue)
   864  	s.Equal("e481009e-14b3-45ae-91af-dce6e2a88365", nextPageToken.SearchAfter[1])
   865  	// for last page
   866  	resp, err = s.visibilityStore.getListWorkflowExecutionsResponse(searchResult, testNamespace, numOfHits+1)
   867  	s.NoError(err)
   868  	s.Equal(0, len(resp.NextPageToken))
   869  	s.Equal(numOfHits, len(resp.Executions))
   870  }
   871  
   872  func (s *ESVisibilitySuite) TestDeserializePageToken() {
   873  	badInput := []byte("bad input")
   874  	result, err := s.visibilityStore.deserializePageToken(badInput)
   875  	s.Error(err)
   876  	s.Nil(result)
   877  	err, ok := err.(*serviceerror.InvalidArgument)
   878  	s.True(ok)
   879  	s.Contains(err.Error(), "unable to deserialize page token")
   880  
   881  	result, err = s.visibilityStore.deserializePageToken(nil)
   882  	s.NoError(err)
   883  	s.Nil(result)
   884  
   885  	token := &visibilityPageToken{SearchAfter: []interface{}{int64(1629936710090695939), "unique"}}
   886  	data, err := s.visibilityStore.serializePageToken(token)
   887  	s.NoError(err)
   888  	result, err = s.visibilityStore.deserializePageToken(data)
   889  	s.NoError(err)
   890  	resultSortValue, err := result.SearchAfter[0].(json.Number).Int64()
   891  	s.NoError(err)
   892  	s.Equal(token.SearchAfter[0].(int64), resultSortValue)
   893  }
   894  
   895  func (s *ESVisibilitySuite) TestSerializePageToken() {
   896  	data, err := s.visibilityStore.serializePageToken(nil)
   897  	s.NoError(err)
   898  	s.Nil(data)
   899  	token, err := s.visibilityStore.deserializePageToken(data)
   900  	s.NoError(err)
   901  	s.Nil(token)
   902  
   903  	sortTime := int64(123)
   904  	tieBreaker := "unique"
   905  	newToken := &visibilityPageToken{SearchAfter: []interface{}{sortTime, tieBreaker}}
   906  	data, err = s.visibilityStore.serializePageToken(newToken)
   907  	s.NoError(err)
   908  	s.True(len(data) > 0)
   909  	token, err = s.visibilityStore.deserializePageToken(data)
   910  	s.NoError(err)
   911  	resultSortValue, err := token.SearchAfter[0].(json.Number).Int64()
   912  	s.NoError(err)
   913  	s.Equal(newToken.SearchAfter[0], resultSortValue)
   914  	s.Equal(newToken.SearchAfter[1], token.SearchAfter[1])
   915  }
   916  
   917  func (s *ESVisibilitySuite) TestParseESDoc() {
   918  	docSource := []byte(`{"ExecutionStatus": "Running",
   919            "NamespaceId": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
   920            "HistoryLength": 29,
   921            "StateTransitionCount": 10,
   922            "VisibilityTaskKey": "7-619",
   923            "RunId": "e481009e-14b3-45ae-91af-dce6e2a88365",
   924            "StartTime": "2021-06-11T15:04:07.980-07:00",
   925            "WorkflowId": "6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256",
   926            "WorkflowType": "TestWorkflowExecute"}`)
   927  	// test for open
   928  	info, err := s.visibilityStore.parseESDoc("", docSource, searchattribute.TestNameTypeMap, testNamespace)
   929  	s.NoError(err)
   930  	s.NotNil(info)
   931  	s.Equal("6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256", info.WorkflowID)
   932  	s.Equal("e481009e-14b3-45ae-91af-dce6e2a88365", info.RunID)
   933  	s.Equal("TestWorkflowExecute", info.TypeName)
   934  	s.Equal(int64(10), info.StateTransitionCount)
   935  	s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, info.Status)
   936  	expectedStartTime, err := time.Parse(time.RFC3339Nano, "2021-06-11T15:04:07.980-07:00")
   937  	s.NoError(err)
   938  	s.Equal(expectedStartTime, info.StartTime)
   939  	s.Nil(info.SearchAttributes)
   940  
   941  	// test for close
   942  	docSource = []byte(`{"ExecutionStatus": "Completed",
   943            "CloseTime": "2021-06-11T16:04:07Z",
   944            "NamespaceId": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
   945            "HistoryLength": 29,
   946            "StateTransitionCount": 20,
   947            "VisibilityTaskKey": "7-619",
   948            "RunId": "e481009e-14b3-45ae-91af-dce6e2a88365",
   949            "StartTime": "2021-06-11T15:04:07.980-07:00",
   950            "WorkflowId": "6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256",
   951            "WorkflowType": "TestWorkflowExecute"}`)
   952  	info, err = s.visibilityStore.parseESDoc("", docSource, searchattribute.TestNameTypeMap, testNamespace)
   953  	s.NoError(err)
   954  	s.NotNil(info)
   955  	s.Equal("6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256", info.WorkflowID)
   956  	s.Equal("e481009e-14b3-45ae-91af-dce6e2a88365", info.RunID)
   957  	s.Equal("TestWorkflowExecute", info.TypeName)
   958  	s.Equal(int64(20), info.StateTransitionCount)
   959  	expectedStartTime, err = time.Parse(time.RFC3339Nano, "2021-06-11T15:04:07.980-07:00")
   960  	s.NoError(err)
   961  	expectedCloseTime, err := time.Parse(time.RFC3339Nano, "2021-06-11T16:04:07Z")
   962  	s.NoError(err)
   963  	s.Equal(expectedStartTime, info.StartTime)
   964  	s.Equal(expectedCloseTime, info.CloseTime)
   965  	s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, info.Status)
   966  	s.Equal(int64(29), info.HistoryLength)
   967  	s.Nil(info.SearchAttributes)
   968  
   969  	// test for error case
   970  	docSource = []byte(`corrupted data`)
   971  	s.mockMetricsHandler.EXPECT().Counter(metrics.ElasticsearchDocumentParseFailuresCount.Name()).Return(metrics.NoopCounterMetricFunc)
   972  	info, err = s.visibilityStore.parseESDoc("", docSource, searchattribute.TestNameTypeMap, testNamespace)
   973  	s.Error(err)
   974  	s.Nil(info)
   975  }
   976  
   977  func (s *ESVisibilitySuite) TestParseESDoc_SearchAttributes() {
   978  	docSource := []byte(`{"ExecutionStatus": "Completed",
   979            "TemporalChangeVersion": ["ver1", "ver2"],
   980            "CustomKeywordField": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
   981            "CustomTextField": "text text",
   982            "CustomDatetimeField": ["2014-08-28T03:15:00.000-07:00", "2016-04-21T05:00:00.000-07:00"],
   983            "CustomDoubleField": [1234.1234,5678.5678],
   984            "CustomIntField": [111,222],
   985            "CustomBoolField": true,
   986            "UnknownField": "random"}`)
   987  	info, err := s.visibilityStore.parseESDoc("", docSource, searchattribute.TestNameTypeMap, testNamespace)
   988  	s.NoError(err)
   989  	s.NotNil(info)
   990  	customSearchAttributes, err := searchattribute.Decode(info.SearchAttributes, &searchattribute.TestNameTypeMap, true)
   991  	s.NoError(err)
   992  
   993  	s.Len(customSearchAttributes, 7)
   994  
   995  	s.Equal([]string{"ver1", "ver2"}, customSearchAttributes["TemporalChangeVersion"])
   996  
   997  	s.Equal("bfd5c907-f899-4baf-a7b2-2ab85e623ebd", customSearchAttributes["CustomKeywordField"])
   998  
   999  	s.Equal("text text", customSearchAttributes["CustomTextField"])
  1000  
  1001  	date1, err := time.Parse(time.RFC3339Nano, "2014-08-28T03:15:00.000-07:00")
  1002  	s.NoError(err)
  1003  	date2, err := time.Parse(time.RFC3339Nano, "2016-04-21T05:00:00.000-07:00")
  1004  	s.NoError(err)
  1005  	s.Equal([]time.Time{date1, date2}, customSearchAttributes["CustomDatetimeField"])
  1006  
  1007  	s.Equal([]float64{1234.1234, 5678.5678}, customSearchAttributes["CustomDoubleField"])
  1008  
  1009  	s.Equal(true, customSearchAttributes["CustomBoolField"])
  1010  
  1011  	s.Equal([]int64{int64(111), int64(222)}, customSearchAttributes["CustomIntField"])
  1012  
  1013  	_, ok := customSearchAttributes["UnknownField"]
  1014  	s.False(ok)
  1015  
  1016  	s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, info.Status)
  1017  }
  1018  
  1019  func (s *ESVisibilitySuite) TestParseESDoc_SearchAttributes_WithMapper() {
  1020  	docSource := []byte(`{"ExecutionStatus": "Completed",
  1021            "TemporalChangeVersion": ["ver1", "ver2"],
  1022            "CustomKeywordField": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
  1023            "CustomTextField": "text text",
  1024            "CustomDatetimeField": ["2014-08-28T03:15:00.000-07:00", "2016-04-21T05:00:00.000-07:00"],
  1025            "CustomDoubleField": [1234.1234,5678.5678],
  1026            "CustomIntField": [111,222],
  1027            "CustomBoolField": true,
  1028            "UnknownField": "random"}`)
  1029  	s.visibilityStore.searchAttributesMapperProvider = s.mockSearchAttributesMapperProvider
  1030  
  1031  	s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(testNamespace).
  1032  		Return(&searchattribute.TestMapper{}, nil).AnyTimes()
  1033  
  1034  	info, err := s.visibilityStore.parseESDoc("", docSource, searchattribute.TestNameTypeMap, testNamespace)
  1035  	s.NoError(err)
  1036  	s.NotNil(info)
  1037  
  1038  	s.Len(info.SearchAttributes.GetIndexedFields(), 7)
  1039  	s.Contains(info.SearchAttributes.GetIndexedFields(), "TemporalChangeVersion")
  1040  	s.Contains(info.SearchAttributes.GetIndexedFields(), "AliasForCustomKeywordField")
  1041  	s.Contains(info.SearchAttributes.GetIndexedFields(), "AliasForCustomTextField")
  1042  	s.Contains(info.SearchAttributes.GetIndexedFields(), "AliasForCustomDatetimeField")
  1043  	s.Contains(info.SearchAttributes.GetIndexedFields(), "AliasForCustomDoubleField")
  1044  	s.Contains(info.SearchAttributes.GetIndexedFields(), "AliasForCustomBoolField")
  1045  	s.Contains(info.SearchAttributes.GetIndexedFields(), "AliasForCustomIntField")
  1046  	s.NotContains(info.SearchAttributes.GetIndexedFields(), "UnknownField")
  1047  	s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, info.Status)
  1048  
  1049  	s.visibilityStore.searchAttributesMapperProvider = nil
  1050  }
  1051  
  1052  func (s *ESVisibilitySuite) TestListWorkflowExecutions() {
  1053  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
  1054  		func(ctx context.Context, p *client.SearchParameters) (*elastic.SearchResult, error) {
  1055  			s.Equal(testIndex, p.Index)
  1056  			s.Equal(
  1057  				elastic.NewBoolQuery().Filter(
  1058  					elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String()),
  1059  					elastic.NewBoolQuery().Filter(elastic.NewMatchQuery("ExecutionStatus", "Terminated")),
  1060  				).MustNot(namespaceDivisionExists),
  1061  				p.Query,
  1062  			)
  1063  			return testSearchResult, nil
  1064  		})
  1065  
  1066  	request := &manager.ListWorkflowExecutionsRequestV2{
  1067  		NamespaceID: testNamespaceID,
  1068  		Namespace:   testNamespace,
  1069  		PageSize:    10,
  1070  		Query:       `ExecutionStatus = "Terminated"`,
  1071  	}
  1072  	_, err := s.visibilityStore.ListWorkflowExecutions(context.Background(), request)
  1073  	s.NoError(err)
  1074  
  1075  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
  1076  	_, err = s.visibilityStore.ListWorkflowExecutions(context.Background(), request)
  1077  	s.Error(err)
  1078  	_, ok := err.(*serviceerror.Unavailable)
  1079  	s.True(ok)
  1080  	s.Contains(err.Error(), "ListWorkflowExecutions failed")
  1081  
  1082  	request.Query = `invalid query`
  1083  	_, err = s.visibilityStore.ListWorkflowExecutions(context.Background(), request)
  1084  	s.Error(err)
  1085  	_, ok = err.(*serviceerror.InvalidArgument)
  1086  	s.True(ok)
  1087  	s.True(strings.HasPrefix(err.Error(), "invalid query"))
  1088  }
  1089  
  1090  func (s *ESVisibilitySuite) TestListWorkflowExecutions_Error() {
  1091  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
  1092  		func(ctx context.Context, p *client.SearchParameters) (*elastic.SearchResult, error) {
  1093  			return nil, &elastic.Error{
  1094  				Status: 400,
  1095  				Details: &elastic.ErrorDetails{
  1096  					Reason: "error reason",
  1097  				},
  1098  			}
  1099  		})
  1100  
  1101  	request := &manager.ListWorkflowExecutionsRequestV2{
  1102  		NamespaceID: testNamespaceID,
  1103  		Namespace:   testNamespace,
  1104  		PageSize:    10,
  1105  		Query:       `ExecutionStatus = "Terminated"`,
  1106  	}
  1107  	_, err := s.visibilityStore.ListWorkflowExecutions(context.Background(), request)
  1108  	s.Error(err)
  1109  	var invalidArgErr *serviceerror.InvalidArgument
  1110  	s.ErrorAs(err, &invalidArgErr)
  1111  	s.Equal("ListWorkflowExecutions failed: elastic: Error 400 (Bad Request): error reason [type=]", invalidArgErr.Message)
  1112  
  1113  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).DoAndReturn(
  1114  		func(ctx context.Context, p *client.SearchParameters) (*elastic.SearchResult, error) {
  1115  			return nil, &elastic.Error{
  1116  				Status: 500,
  1117  				Details: &elastic.ErrorDetails{
  1118  					Reason: "error reason",
  1119  				},
  1120  			}
  1121  		})
  1122  	_, err = s.visibilityStore.ListWorkflowExecutions(context.Background(), request)
  1123  	var unavailableErr *serviceerror.Unavailable
  1124  	s.ErrorAs(err, &unavailableErr)
  1125  	s.Equal("ListWorkflowExecutions failed: elastic: Error 500 (Internal Server Error): error reason [type=]", unavailableErr.Message)
  1126  }
  1127  
  1128  func (s *ESVisibilitySuite) TestScanWorkflowExecutions_Scroll() {
  1129  	scrollID := "scrollID"
  1130  	request := &manager.ListWorkflowExecutionsRequestV2{
  1131  		NamespaceID: testNamespaceID,
  1132  		Namespace:   testNamespace,
  1133  		PageSize:    1,
  1134  	}
  1135  
  1136  	data := []byte(`{"ExecutionStatus": "Running",
  1137            "CloseTime": "2021-06-11T16:04:07.980-07:00",
  1138            "NamespaceId": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
  1139            "HistoryLength": 29,
  1140            "StateTransitionCount": 22,
  1141            "VisibilityTaskKey": "7-619",
  1142            "RunId": "e481009e-14b3-45ae-91af-dce6e2a88365",
  1143            "StartTime": "2021-06-11T15:04:07.980-07:00",
  1144            "WorkflowId": "6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256",
  1145            "WorkflowType": "basic.stressWorkflowExecute"}`)
  1146  	source := json.RawMessage(data)
  1147  	searchResult := &elastic.SearchResult{
  1148  		Hits: &elastic.SearchHits{
  1149  			Hits: []*elastic.SearchHit{
  1150  				{
  1151  					Source: source,
  1152  				},
  1153  			},
  1154  		},
  1155  		ScrollId: scrollID,
  1156  	}
  1157  
  1158  	s.mockESClient.EXPECT().IsPointInTimeSupported(gomock.Any()).Return(false).AnyTimes()
  1159  
  1160  	// test bad request
  1161  	request.Query = `invalid query`
  1162  	_, err := s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1163  	s.Error(err)
  1164  	_, ok := err.(*serviceerror.InvalidArgument)
  1165  	s.True(ok)
  1166  	s.True(strings.HasPrefix(err.Error(), "invalid query"))
  1167  
  1168  	// test search
  1169  	request.Query = `ExecutionStatus = "Running"`
  1170  	s.mockESClient.EXPECT().OpenScroll(
  1171  		gomock.Any(),
  1172  		&client.SearchParameters{
  1173  			Index: testIndex,
  1174  			Query: elastic.NewBoolQuery().
  1175  				Filter(
  1176  					elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String()),
  1177  					elastic.NewBoolQuery().Filter(
  1178  						elastic.NewMatchQuery(
  1179  							searchattribute.ExecutionStatus,
  1180  							enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String(),
  1181  						),
  1182  					),
  1183  				).
  1184  				MustNot(elastic.NewExistsQuery(searchattribute.TemporalNamespaceDivision)),
  1185  			PageSize: 1,
  1186  			Sorter:   docSorter,
  1187  		},
  1188  		gomock.Any(),
  1189  	).Return(searchResult, nil)
  1190  
  1191  	token := &visibilityPageToken{ScrollID: scrollID}
  1192  	tokenBytes, err := s.visibilityStore.serializePageToken(token)
  1193  	s.NoError(err)
  1194  
  1195  	result, err := s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1196  	s.NoError(err)
  1197  	s.Equal(tokenBytes, result.NextPageToken)
  1198  
  1199  	// test last page
  1200  	request.NextPageToken = tokenBytes
  1201  	searchResult = &elastic.SearchResult{
  1202  		Hits: &elastic.SearchHits{
  1203  			Hits: []*elastic.SearchHit{},
  1204  		},
  1205  		ScrollId: scrollID,
  1206  	}
  1207  	s.mockESClient.EXPECT().Scroll(gomock.Any(), scrollID, gomock.Any()).Return(searchResult, nil)
  1208  	s.mockESClient.EXPECT().CloseScroll(gomock.Any(), scrollID).Return(nil)
  1209  	result, err = s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1210  	s.NoError(err)
  1211  	s.Nil(result.NextPageToken)
  1212  
  1213  	// test unavailable error
  1214  	request.NextPageToken = nil
  1215  	s.mockESClient.EXPECT().OpenScroll(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
  1216  	_, err = s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1217  	s.Error(err)
  1218  	_, ok = err.(*serviceerror.Unavailable)
  1219  	s.True(ok)
  1220  	s.Contains(err.Error(), "ScanWorkflowExecutions failed")
  1221  }
  1222  
  1223  func (s *ESVisibilitySuite) TestScanWorkflowExecutions_Pit() {
  1224  	pitID := "pitID"
  1225  	request := &manager.ListWorkflowExecutionsRequestV2{
  1226  		NamespaceID: testNamespaceID,
  1227  		Namespace:   testNamespace,
  1228  		PageSize:    1,
  1229  	}
  1230  
  1231  	data := []byte(`{"ExecutionStatus": "Running",
  1232            "CloseTime": "2021-06-11T16:04:07.980-07:00",
  1233            "NamespaceId": "bfd5c907-f899-4baf-a7b2-2ab85e623ebd",
  1234            "HistoryLength": 29,
  1235            "StateTransitionCount": 22,
  1236            "VisibilityTaskKey": "7-619",
  1237            "RunId": "e481009e-14b3-45ae-91af-dce6e2a88365",
  1238            "StartTime": "2021-06-11T15:04:07.980-07:00",
  1239            "WorkflowId": "6bfbc1e5-6ce4-4e22-bbfb-e0faa9a7a604-1-2256",
  1240            "WorkflowType": "basic.stressWorkflowExecute"}`)
  1241  	source := json.RawMessage(data)
  1242  	searchAfter := []any{json.Number("123")}
  1243  	searchResult := &elastic.SearchResult{
  1244  		Hits: &elastic.SearchHits{
  1245  			Hits: []*elastic.SearchHit{
  1246  				{
  1247  					Source: source,
  1248  					Sort:   searchAfter,
  1249  				},
  1250  			},
  1251  		},
  1252  		PitId: pitID,
  1253  	}
  1254  
  1255  	s.mockESClient.EXPECT().IsPointInTimeSupported(gomock.Any()).Return(true).AnyTimes()
  1256  
  1257  	// test bad request
  1258  	request.Query = `invalid query`
  1259  	_, err := s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1260  	s.Error(err)
  1261  	_, ok := err.(*serviceerror.InvalidArgument)
  1262  	s.True(ok)
  1263  	s.True(strings.HasPrefix(err.Error(), "invalid query"))
  1264  
  1265  	request.Query = `ExecutionStatus = "Running"`
  1266  	s.mockESClient.EXPECT().OpenPointInTime(gomock.Any(), testIndex, gomock.Any()).Return(pitID, nil)
  1267  	s.mockESClient.EXPECT().Search(
  1268  		gomock.Any(),
  1269  		&client.SearchParameters{
  1270  			Index: testIndex,
  1271  			Query: elastic.NewBoolQuery().
  1272  				Filter(
  1273  					elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String()),
  1274  					elastic.NewBoolQuery().Filter(
  1275  						elastic.NewMatchQuery(
  1276  							searchattribute.ExecutionStatus,
  1277  							enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String(),
  1278  						),
  1279  					),
  1280  				).
  1281  				MustNot(elastic.NewExistsQuery(searchattribute.TemporalNamespaceDivision)),
  1282  			PageSize:    1,
  1283  			Sorter:      docSorter,
  1284  			PointInTime: elastic.NewPointInTimeWithKeepAlive(pitID, pointInTimeKeepAliveInterval),
  1285  		},
  1286  	).Return(searchResult, nil)
  1287  
  1288  	token := &visibilityPageToken{
  1289  		SearchAfter:   searchAfter,
  1290  		PointInTimeID: pitID,
  1291  	}
  1292  	tokenBytes, err := s.visibilityStore.serializePageToken(token)
  1293  	s.NoError(err)
  1294  
  1295  	result, err := s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1296  	s.NoError(err)
  1297  	s.Equal(tokenBytes, result.NextPageToken)
  1298  
  1299  	// test last page
  1300  	request.NextPageToken = tokenBytes
  1301  	searchResult = &elastic.SearchResult{
  1302  		Hits: &elastic.SearchHits{
  1303  			Hits: []*elastic.SearchHit{},
  1304  		},
  1305  		PitId: pitID,
  1306  	}
  1307  	s.mockESClient.EXPECT().ClosePointInTime(gomock.Any(), pitID).Return(true, nil)
  1308  	s.mockESClient.EXPECT().Search(
  1309  		gomock.Any(),
  1310  		&client.SearchParameters{
  1311  			Index: testIndex,
  1312  			Query: elastic.NewBoolQuery().
  1313  				Filter(
  1314  					elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String()),
  1315  					elastic.NewBoolQuery().Filter(
  1316  						elastic.NewMatchQuery(
  1317  							searchattribute.ExecutionStatus,
  1318  							enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING.String(),
  1319  						),
  1320  					),
  1321  				).
  1322  				MustNot(elastic.NewExistsQuery(searchattribute.TemporalNamespaceDivision)),
  1323  			PageSize:    1,
  1324  			Sorter:      docSorter,
  1325  			SearchAfter: token.SearchAfter,
  1326  			PointInTime: elastic.NewPointInTimeWithKeepAlive(pitID, pointInTimeKeepAliveInterval),
  1327  		},
  1328  	).Return(searchResult, nil)
  1329  	result, err = s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1330  	s.NoError(err)
  1331  	s.Nil(result.NextPageToken)
  1332  
  1333  	// test unavailable error
  1334  	s.mockESClient.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, errTestESSearch)
  1335  	_, err = s.visibilityStore.ScanWorkflowExecutions(context.Background(), request)
  1336  	s.Error(err)
  1337  	_, ok = err.(*serviceerror.Unavailable)
  1338  	s.True(ok)
  1339  	s.Contains(err.Error(), "ScanWorkflowExecutions failed")
  1340  }
  1341  
  1342  func (s *ESVisibilitySuite) TestCountWorkflowExecutions() {
  1343  	s.mockESClient.EXPECT().Count(gomock.Any(), testIndex, gomock.Any()).DoAndReturn(
  1344  		func(ctx context.Context, index string, query elastic.Query) (int64, error) {
  1345  			s.Equal(
  1346  				elastic.NewBoolQuery().Filter(
  1347  					elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String()),
  1348  					elastic.NewBoolQuery().Filter(elastic.NewMatchQuery("ExecutionStatus", "Terminated")),
  1349  				).MustNot(namespaceDivisionExists),
  1350  				query,
  1351  			)
  1352  			return int64(1), nil
  1353  		})
  1354  
  1355  	request := &manager.CountWorkflowExecutionsRequest{
  1356  		NamespaceID: testNamespaceID,
  1357  		Namespace:   testNamespace,
  1358  		Query:       `ExecutionStatus = "Terminated"`,
  1359  	}
  1360  	resp, err := s.visibilityStore.CountWorkflowExecutions(context.Background(), request)
  1361  	s.NoError(err)
  1362  	s.Equal(int64(1), resp.Count)
  1363  
  1364  	// test unavailable error
  1365  	s.mockESClient.EXPECT().Count(gomock.Any(), testIndex, gomock.Any()).DoAndReturn(
  1366  		func(ctx context.Context, index string, query elastic.Query) (int64, error) {
  1367  			s.Equal(
  1368  				elastic.NewBoolQuery().Filter(
  1369  					elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String()),
  1370  					elastic.NewBoolQuery().Filter(elastic.NewMatchQuery("ExecutionStatus", "Terminated")),
  1371  				).MustNot(namespaceDivisionExists),
  1372  				query,
  1373  			)
  1374  			return int64(0), errTestESSearch
  1375  		})
  1376  
  1377  	_, err = s.visibilityStore.CountWorkflowExecutions(context.Background(), request)
  1378  	s.Error(err)
  1379  	_, ok := err.(*serviceerror.Unavailable)
  1380  	s.True(ok)
  1381  	s.Contains(err.Error(), "CountWorkflowExecutions failed")
  1382  
  1383  	// test bad request
  1384  	request.Query = `invalid query`
  1385  	_, err = s.visibilityStore.CountWorkflowExecutions(context.Background(), request)
  1386  	s.Error(err)
  1387  	_, ok = err.(*serviceerror.InvalidArgument)
  1388  	s.True(ok)
  1389  	s.True(strings.HasPrefix(err.Error(), "invalid query"), err.Error())
  1390  }
  1391  
  1392  func (s *ESVisibilitySuite) TestCountWorkflowExecutions_GroupBy() {
  1393  	request := &manager.CountWorkflowExecutionsRequest{
  1394  		NamespaceID: testNamespaceID,
  1395  		Namespace:   testNamespace,
  1396  		Query:       "GROUP BY ExecutionStatus",
  1397  	}
  1398  	s.mockESClient.EXPECT().
  1399  		CountGroupBy(
  1400  			gomock.Any(),
  1401  			testIndex,
  1402  			elastic.NewBoolQuery().
  1403  				Filter(elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String())).
  1404  				MustNot(namespaceDivisionExists),
  1405  			searchattribute.ExecutionStatus,
  1406  			elastic.NewTermsAggregation().Field(searchattribute.ExecutionStatus),
  1407  		).
  1408  		Return(
  1409  			&elastic.SearchResult{
  1410  				Aggregations: map[string]json.RawMessage{
  1411  					searchattribute.ExecutionStatus: json.RawMessage(
  1412  						`{"buckets":[{"key":"Completed","doc_count":100},{"key":"Running","doc_count":10}]}`,
  1413  					),
  1414  				},
  1415  			},
  1416  			nil,
  1417  		)
  1418  	resp, err := s.visibilityStore.CountWorkflowExecutions(context.Background(), request)
  1419  	s.NoError(err)
  1420  	payload1, _ := searchattribute.EncodeValue(
  1421  		enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED,
  1422  		enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  1423  	)
  1424  	payload2, _ := searchattribute.EncodeValue(
  1425  		enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
  1426  		enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  1427  	)
  1428  	s.True(temporalproto.DeepEqual(
  1429  		&manager.CountWorkflowExecutionsResponse{
  1430  			Count: 110,
  1431  			Groups: []*workflowservice.CountWorkflowExecutionsResponse_AggregationGroup{
  1432  				{
  1433  					GroupValues: []*commonpb.Payload{payload1},
  1434  					Count:       100,
  1435  				},
  1436  				{
  1437  					GroupValues: []*commonpb.Payload{payload2},
  1438  					Count:       10,
  1439  				},
  1440  			},
  1441  		},
  1442  		resp),
  1443  	)
  1444  
  1445  	// test only allowed to group by a single field
  1446  	request.Query = "GROUP BY ExecutionStatus, WorkflowType"
  1447  	resp, err = s.visibilityStore.CountWorkflowExecutions(context.Background(), request)
  1448  	s.Error(err)
  1449  	s.Contains(err.Error(), "'group by' clause supports only a single field")
  1450  	s.Nil(resp)
  1451  
  1452  	// test only allowed to group by ExecutionStatus
  1453  	request.Query = "GROUP BY WorkflowType"
  1454  	resp, err = s.visibilityStore.CountWorkflowExecutions(context.Background(), request)
  1455  	s.Error(err)
  1456  	s.Contains(err.Error(), "'group by' clause is only supported for ExecutionStatus search attribute")
  1457  	s.Nil(resp)
  1458  }
  1459  
  1460  func (s *ESVisibilitySuite) TestCountGroupByWorkflowExecutions() {
  1461  	statusCompletedPayload, _ := searchattribute.EncodeValue(
  1462  		enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED,
  1463  		enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  1464  	)
  1465  	statusRunningPayload, _ := searchattribute.EncodeValue(
  1466  		enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
  1467  		enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  1468  	)
  1469  	wfType1Payload, _ := searchattribute.EncodeValue("wf-type-1", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1470  	wfType2Payload, _ := searchattribute.EncodeValue("wf-type-2", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1471  	wfId1Payload, _ := searchattribute.EncodeValue("wf-id-1", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1472  	wfId2Payload, _ := searchattribute.EncodeValue("wf-id-2", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1473  	wfId3Payload, _ := searchattribute.EncodeValue("wf-id-3", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1474  	wfId4Payload, _ := searchattribute.EncodeValue("wf-id-4", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1475  	wfId5Payload, _ := searchattribute.EncodeValue("wf-id-5", enumspb.INDEXED_VALUE_TYPE_KEYWORD)
  1476  
  1477  	testCases := []struct {
  1478  		name         string
  1479  		groupBy      []string
  1480  		aggName      string
  1481  		agg          elastic.Aggregation
  1482  		mockResponse *elastic.SearchResult
  1483  		response     *manager.CountWorkflowExecutionsResponse
  1484  	}{
  1485  		{
  1486  			name:    "group by one field",
  1487  			groupBy: []string{searchattribute.ExecutionStatus},
  1488  			aggName: searchattribute.ExecutionStatus,
  1489  			agg:     elastic.NewTermsAggregation().Field(searchattribute.ExecutionStatus),
  1490  			mockResponse: &elastic.SearchResult{
  1491  				Aggregations: map[string]json.RawMessage{
  1492  					searchattribute.ExecutionStatus: json.RawMessage(
  1493  						`{
  1494  							"buckets":[
  1495  								{
  1496  									"key": "Completed",
  1497  									"doc_count": 100
  1498  								},
  1499  								{
  1500  									"key": "Running",
  1501  									"doc_count": 10
  1502  								}
  1503  							]
  1504  						}`,
  1505  					),
  1506  				},
  1507  			},
  1508  			response: &manager.CountWorkflowExecutionsResponse{
  1509  				Count: 110,
  1510  				Groups: []*workflowservice.CountWorkflowExecutionsResponse_AggregationGroup{
  1511  					{
  1512  						GroupValues: []*commonpb.Payload{statusCompletedPayload},
  1513  						Count:       100,
  1514  					},
  1515  					{
  1516  						GroupValues: []*commonpb.Payload{statusRunningPayload},
  1517  						Count:       10,
  1518  					},
  1519  				},
  1520  			},
  1521  		},
  1522  
  1523  		{
  1524  			name:    "group by two fields",
  1525  			groupBy: []string{searchattribute.ExecutionStatus, searchattribute.WorkflowType},
  1526  			aggName: searchattribute.ExecutionStatus,
  1527  			agg: elastic.NewTermsAggregation().Field(searchattribute.ExecutionStatus).SubAggregation(
  1528  				searchattribute.WorkflowType,
  1529  				elastic.NewTermsAggregation().Field(searchattribute.WorkflowType),
  1530  			),
  1531  			mockResponse: &elastic.SearchResult{
  1532  				Aggregations: map[string]json.RawMessage{
  1533  					searchattribute.ExecutionStatus: json.RawMessage(
  1534  						`{
  1535  							"buckets":[
  1536  								{
  1537  									"key": "Completed",
  1538  									"doc_count": 100,
  1539  									"WorkflowType": {
  1540  										"buckets": [
  1541  											{
  1542  												"key": "wf-type-1",
  1543  												"doc_count": 75
  1544  											},
  1545  											{
  1546  												"key": "wf-type-2",
  1547  												"doc_count": 25
  1548  											}
  1549  										]
  1550  									}
  1551  								},
  1552  								{
  1553  									"key": "Running",
  1554  									"doc_count": 10,
  1555  									"WorkflowType": {
  1556  										"buckets": [
  1557  											{
  1558  												"key": "wf-type-1",
  1559  												"doc_count": 7
  1560  											},
  1561  											{
  1562  												"key": "wf-type-2",
  1563  												"doc_count": 3
  1564  											}
  1565  										]
  1566  									}
  1567  								}
  1568  							]
  1569  						}`,
  1570  					),
  1571  				},
  1572  			},
  1573  			response: &manager.CountWorkflowExecutionsResponse{
  1574  				Count: 110,
  1575  				Groups: []*workflowservice.CountWorkflowExecutionsResponse_AggregationGroup{
  1576  					{
  1577  						GroupValues: []*commonpb.Payload{statusCompletedPayload, wfType1Payload},
  1578  						Count:       75,
  1579  					},
  1580  					{
  1581  						GroupValues: []*commonpb.Payload{statusCompletedPayload, wfType2Payload},
  1582  						Count:       25,
  1583  					},
  1584  					{
  1585  						GroupValues: []*commonpb.Payload{statusRunningPayload, wfType1Payload},
  1586  						Count:       7,
  1587  					},
  1588  					{
  1589  						GroupValues: []*commonpb.Payload{statusRunningPayload, wfType2Payload},
  1590  						Count:       3,
  1591  					},
  1592  				},
  1593  			},
  1594  		},
  1595  
  1596  		{
  1597  			name: "group by three fields",
  1598  			groupBy: []string{
  1599  				searchattribute.ExecutionStatus,
  1600  				searchattribute.WorkflowType,
  1601  				searchattribute.WorkflowID,
  1602  			},
  1603  			aggName: searchattribute.ExecutionStatus,
  1604  			agg: elastic.NewTermsAggregation().Field(searchattribute.ExecutionStatus).SubAggregation(
  1605  				searchattribute.WorkflowType,
  1606  				elastic.NewTermsAggregation().Field(searchattribute.WorkflowType).SubAggregation(
  1607  					searchattribute.WorkflowID,
  1608  					elastic.NewTermsAggregation().Field(searchattribute.WorkflowID),
  1609  				),
  1610  			),
  1611  			mockResponse: &elastic.SearchResult{
  1612  				Aggregations: map[string]json.RawMessage{
  1613  					searchattribute.ExecutionStatus: json.RawMessage(
  1614  						`{
  1615  							"buckets":[
  1616  								{
  1617  									"key": "Completed",
  1618  									"doc_count": 100,
  1619  									"WorkflowType": {
  1620  										"buckets": [
  1621  											{
  1622  												"key": "wf-type-1",
  1623  												"doc_count": 75,
  1624  												"WorkflowId": {
  1625  													"buckets": [
  1626  														{
  1627  															"key": "wf-id-1",
  1628  															"doc_count": 75
  1629  														}
  1630  													]
  1631  												}
  1632  											},
  1633  											{
  1634  												"key": "wf-type-2",
  1635  												"doc_count": 25,
  1636  												"WorkflowId": {
  1637  													"buckets": [
  1638  														{
  1639  															"key": "wf-id-2",
  1640  															"doc_count": 20
  1641  														},
  1642  														{
  1643  															"key": "wf-id-3",
  1644  															"doc_count": 5
  1645  														}
  1646  													]
  1647  												}
  1648  											}
  1649  										]
  1650  									}
  1651  								},
  1652  								{
  1653  									"key": "Running",
  1654  									"doc_count": 10,
  1655  									"WorkflowType": {
  1656  										"buckets": [
  1657  											{
  1658  												"key": "wf-type-1",
  1659  												"doc_count": 7,
  1660  												"WorkflowId": {
  1661  													"buckets": [
  1662  														{
  1663  															"key": "wf-id-4",
  1664  															"doc_count": 7
  1665  														}
  1666  													]
  1667  												}
  1668  											},
  1669  											{
  1670  												"key": "wf-type-2",
  1671  												"doc_count": 3,
  1672  												"WorkflowId": {
  1673  													"buckets": [
  1674  														{
  1675  															"key": "wf-id-5",
  1676  															"doc_count": 3
  1677  														}
  1678  													]
  1679  												}
  1680  											}
  1681  										]
  1682  									}
  1683  								}
  1684  							]
  1685  						}`,
  1686  					),
  1687  				},
  1688  			},
  1689  			response: &manager.CountWorkflowExecutionsResponse{
  1690  				Count: 110,
  1691  				Groups: []*workflowservice.CountWorkflowExecutionsResponse_AggregationGroup{
  1692  					{
  1693  						GroupValues: []*commonpb.Payload{statusCompletedPayload, wfType1Payload, wfId1Payload},
  1694  						Count:       75,
  1695  					},
  1696  					{
  1697  						GroupValues: []*commonpb.Payload{statusCompletedPayload, wfType2Payload, wfId2Payload},
  1698  						Count:       20,
  1699  					},
  1700  					{
  1701  						GroupValues: []*commonpb.Payload{statusCompletedPayload, wfType2Payload, wfId3Payload},
  1702  						Count:       5,
  1703  					},
  1704  					{
  1705  						GroupValues: []*commonpb.Payload{statusRunningPayload, wfType1Payload, wfId4Payload},
  1706  						Count:       7,
  1707  					},
  1708  					{
  1709  						GroupValues: []*commonpb.Payload{statusRunningPayload, wfType2Payload, wfId5Payload},
  1710  						Count:       3,
  1711  					},
  1712  				},
  1713  			},
  1714  		},
  1715  	}
  1716  
  1717  	for _, tc := range testCases {
  1718  		s.T().Run(tc.name, func(t *testing.T) {
  1719  			searchParams := &query.QueryParams{
  1720  				Query: elastic.NewBoolQuery().
  1721  					Filter(elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String())).
  1722  					MustNot(namespaceDivisionExists),
  1723  				GroupBy: tc.groupBy,
  1724  			}
  1725  			s.mockESClient.EXPECT().
  1726  				CountGroupBy(
  1727  					gomock.Any(),
  1728  					testIndex,
  1729  					elastic.NewBoolQuery().
  1730  						Filter(elastic.NewTermQuery(searchattribute.NamespaceID, testNamespaceID.String())).
  1731  						MustNot(namespaceDivisionExists),
  1732  					tc.aggName,
  1733  					tc.agg,
  1734  				).
  1735  				Return(tc.mockResponse, nil)
  1736  			resp, err := s.visibilityStore.countGroupByWorkflowExecutions(context.Background(), searchParams)
  1737  			s.NoError(err)
  1738  			s.True(temporalproto.DeepEqual(tc.response, resp))
  1739  		})
  1740  	}
  1741  }
  1742  
  1743  func (s *ESVisibilitySuite) TestGetWorkflowExecution() {
  1744  	now := timestamppb.New(time.Now())
  1745  	s.mockESClient.EXPECT().Get(gomock.Any(), testIndex, gomock.Any()).DoAndReturn(
  1746  		func(ctx context.Context, index string, docID string) (*elastic.GetResult, error) {
  1747  			s.Equal(testIndex, index)
  1748  			s.Equal(testWorkflowID+delimiter+testRunID, docID)
  1749  			data := map[string]interface{}{
  1750  				"ExecutionStatus":      "Running",
  1751  				"NamespaceId":          testNamespaceID.String(),
  1752  				"StateTransitionCount": 22,
  1753  				"VisibilityTaskKey":    "7-619",
  1754  				"RunId":                testRunID,
  1755  				"StartTime":            "2021-06-11T15:04:07.980-07:00",
  1756  				"WorkflowId":           testWorkflowID,
  1757  				"WorkflowType":         "basic.stressWorkflowExecute",
  1758  			}
  1759  			source, _ := json.Marshal(data)
  1760  			return &elastic.GetResult{Found: true, Source: source}, nil
  1761  		})
  1762  
  1763  	request := &manager.GetWorkflowExecutionRequest{
  1764  		NamespaceID: testNamespaceID,
  1765  		Namespace:   testNamespace,
  1766  		WorkflowID:  testWorkflowID,
  1767  		RunID:       testRunID,
  1768  		CloseTime:   now.AsTime(),
  1769  	}
  1770  	_, err := s.visibilityStore.GetWorkflowExecution(context.Background(), request)
  1771  	s.NoError(err)
  1772  
  1773  	// test unavailable error
  1774  	s.mockESClient.EXPECT().Get(gomock.Any(), testIndex, gomock.Any()).DoAndReturn(
  1775  		func(ctx context.Context, index string, docID string) (*elastic.GetResult, error) {
  1776  			s.Equal(testIndex, index)
  1777  			s.Equal(testWorkflowID+delimiter+testRunID, docID)
  1778  			return nil, errTestESSearch
  1779  		})
  1780  
  1781  	_, err = s.visibilityStore.GetWorkflowExecution(context.Background(), request)
  1782  	s.Error(err)
  1783  	_, ok := err.(*serviceerror.Unavailable)
  1784  	s.True(ok)
  1785  	s.Contains(err.Error(), "GetWorkflowExecution failed")
  1786  }
  1787  
  1788  func (s *ESVisibilitySuite) Test_detailedErrorMessage() {
  1789  	err := errors.New("test message")
  1790  	s.Equal("test message", detailedErrorMessage(err))
  1791  
  1792  	err = &elastic.Error{
  1793  		Status: 500,
  1794  	}
  1795  	s.Equal("elastic: Error 500 (Internal Server Error)", detailedErrorMessage(err))
  1796  
  1797  	err = &elastic.Error{
  1798  		Status: 500,
  1799  		Details: &elastic.ErrorDetails{
  1800  			Type:   "some type",
  1801  			Reason: "some reason",
  1802  		},
  1803  	}
  1804  	s.Equal("elastic: Error 500 (Internal Server Error): some reason [type=some type]", detailedErrorMessage(err))
  1805  
  1806  	err = &elastic.Error{
  1807  		Status: 500,
  1808  		Details: &elastic.ErrorDetails{
  1809  			Type:   "some type",
  1810  			Reason: "some reason",
  1811  			RootCause: []*elastic.ErrorDetails{
  1812  				{
  1813  					Type:   "some type",
  1814  					Reason: "some reason",
  1815  				},
  1816  			},
  1817  		},
  1818  	}
  1819  	s.Equal("elastic: Error 500 (Internal Server Error): some reason [type=some type]", detailedErrorMessage(err))
  1820  
  1821  	err = &elastic.Error{
  1822  		Status: 500,
  1823  		Details: &elastic.ErrorDetails{
  1824  			Type:   "some type",
  1825  			Reason: "some reason",
  1826  			RootCause: []*elastic.ErrorDetails{
  1827  				{
  1828  					Type:   "some other type1",
  1829  					Reason: "some other reason1",
  1830  				},
  1831  				{
  1832  					Type:   "some other type2",
  1833  					Reason: "some other reason2",
  1834  				},
  1835  			},
  1836  		},
  1837  	}
  1838  	s.Equal("elastic: Error 500 (Internal Server Error): some reason [type=some type], root causes: some other reason1 [type=some other type1], some other reason2 [type=some other type2]", detailedErrorMessage(err))
  1839  }
  1840  
  1841  func (s *ESVisibilitySuite) TestProcessPageToken() {
  1842  	closeTime := time.Now().UTC()
  1843  	startTime := closeTime.Add(-1 * time.Minute)
  1844  	baseQuery := elastic.NewBoolQuery().
  1845  		Filter(elastic.NewTermQuery(searchattribute.NamespaceID, testNamespace.String()))
  1846  
  1847  	testCases := []struct {
  1848  		name             string
  1849  		manualPagination bool
  1850  		sorter           []elastic.Sorter
  1851  		pageToken        *visibilityPageToken
  1852  		resSearchAfter   []any
  1853  		resQuery         elastic.Query
  1854  		resError         error
  1855  	}{
  1856  		{
  1857  			name:             "nil page token",
  1858  			manualPagination: false,
  1859  			sorter:           docSorter,
  1860  			pageToken:        nil,
  1861  			resSearchAfter:   nil,
  1862  			resQuery:         baseQuery,
  1863  			resError:         nil,
  1864  		},
  1865  		{
  1866  			name:             "empty page token",
  1867  			manualPagination: false,
  1868  			sorter:           docSorter,
  1869  			pageToken:        &visibilityPageToken{SearchAfter: []any{}},
  1870  			resSearchAfter:   nil,
  1871  			resQuery:         baseQuery,
  1872  			resError:         nil,
  1873  		},
  1874  		{
  1875  			name:             "page token doesn't match sorter size",
  1876  			manualPagination: false,
  1877  			sorter:           docSorter,
  1878  			pageToken:        &visibilityPageToken{SearchAfter: []any{"foo", "bar"}},
  1879  			resSearchAfter:   nil,
  1880  			resQuery:         baseQuery,
  1881  			resError:         serviceerror.NewInvalidArgument("Invalid page token for given sort fields: expected 1 fields, got 2"),
  1882  		},
  1883  		{
  1884  			name:             "not using default sorter",
  1885  			manualPagination: false,
  1886  			sorter:           docSorter,
  1887  			pageToken:        &visibilityPageToken{SearchAfter: []any{123}},
  1888  			resSearchAfter:   []any{123},
  1889  			resQuery:         baseQuery,
  1890  			resError:         nil,
  1891  		},
  1892  		{
  1893  			name:             "default sorter without manual pagination",
  1894  			manualPagination: false,
  1895  			sorter:           defaultSorter,
  1896  			pageToken: &visibilityPageToken{
  1897  				SearchAfter: []any{
  1898  					json.Number(fmt.Sprintf("%d", closeTime.UnixNano())),
  1899  					json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  1900  				},
  1901  			},
  1902  			resSearchAfter: []any{
  1903  				json.Number(fmt.Sprintf("%d", closeTime.UnixNano())),
  1904  				json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  1905  			},
  1906  			resQuery: baseQuery,
  1907  			resError: nil,
  1908  		},
  1909  		{
  1910  			name:             "default sorter with manual pagination",
  1911  			manualPagination: true,
  1912  			sorter:           defaultSorter,
  1913  			pageToken: &visibilityPageToken{
  1914  				SearchAfter: []any{
  1915  					json.Number(fmt.Sprintf("%d", closeTime.UnixNano())),
  1916  					json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  1917  				},
  1918  			},
  1919  			resSearchAfter: nil,
  1920  			resQuery: baseQuery.MinimumNumberShouldMatch(1).Should(
  1921  				elastic.NewBoolQuery().Filter(
  1922  					elastic.NewRangeQuery(searchattribute.CloseTime).Lt(closeTime.Format(time.RFC3339Nano)),
  1923  				),
  1924  				elastic.NewBoolQuery().Filter(
  1925  					elastic.NewTermQuery(searchattribute.CloseTime, closeTime.Format(time.RFC3339Nano)),
  1926  					elastic.NewRangeQuery(searchattribute.StartTime).Lt(startTime.Format(time.RFC3339Nano)),
  1927  				),
  1928  			),
  1929  			resError: nil,
  1930  		},
  1931  	}
  1932  
  1933  	for _, tc := range testCases {
  1934  		s.T().Run(tc.name, func(t *testing.T) {
  1935  			visibilityStore := NewVisibilityStore(
  1936  				s.mockESClient,
  1937  				testIndex,
  1938  				searchattribute.NewTestProvider(),
  1939  				searchattribute.NewTestMapperProvider(nil),
  1940  				s.mockProcessor,
  1941  				dynamicconfig.GetDurationPropertyFn(1*time.Minute*debug.TimeoutMultiplier),
  1942  				dynamicconfig.GetBoolPropertyFnFilteredByNamespace(false),
  1943  				dynamicconfig.GetBoolPropertyFnFilteredByNamespace(tc.manualPagination),
  1944  				s.mockMetricsHandler,
  1945  			)
  1946  			params := &client.SearchParameters{
  1947  				Index:  testIndex,
  1948  				Query:  baseQuery,
  1949  				Sorter: tc.sorter,
  1950  			}
  1951  			err := visibilityStore.processPageToken(params, tc.pageToken, testNamespace)
  1952  			s.Equal(tc.resSearchAfter, params.SearchAfter)
  1953  			s.Equal(tc.resQuery, params.Query)
  1954  			s.Equal(tc.resError, err)
  1955  		})
  1956  	}
  1957  }
  1958  
  1959  func (s *ESVisibilitySuite) Test_buildPaginationQuery() {
  1960  	startTime := time.Now().UTC()
  1961  	closeTime := startTime.Add(1 * time.Minute)
  1962  	datetimeNull := json.Number(fmt.Sprintf("%d", math.MaxInt64))
  1963  
  1964  	testCases := []struct {
  1965  		name         string
  1966  		sorterFields []fieldSort
  1967  		searchAfter  []any
  1968  		res          []elastic.Query
  1969  		err          error
  1970  	}{
  1971  		{
  1972  			name:         "one field",
  1973  			sorterFields: []fieldSort{{searchattribute.StartTime, true, true}},
  1974  			searchAfter:  []any{json.Number(fmt.Sprintf("%d", startTime.UnixNano()))},
  1975  			res: []elastic.Query{
  1976  				elastic.NewBoolQuery().Filter(
  1977  					elastic.NewRangeQuery(searchattribute.StartTime).Lt(startTime.Format(time.RFC3339Nano)),
  1978  				),
  1979  			},
  1980  			err: nil,
  1981  		},
  1982  		{
  1983  			name: "two fields one null",
  1984  			sorterFields: []fieldSort{
  1985  				{searchattribute.CloseTime, true, true},
  1986  				{searchattribute.StartTime, true, true},
  1987  			},
  1988  			searchAfter: []any{
  1989  				datetimeNull,
  1990  				json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  1991  			},
  1992  			res: []elastic.Query{
  1993  				elastic.NewBoolQuery().Filter(elastic.NewExistsQuery(searchattribute.CloseTime)),
  1994  				elastic.NewBoolQuery().
  1995  					MustNot(elastic.NewExistsQuery(searchattribute.CloseTime)).
  1996  					Filter(
  1997  						elastic.NewRangeQuery(searchattribute.StartTime).Lt(startTime.Format(time.RFC3339Nano)),
  1998  					),
  1999  			},
  2000  			err: nil,
  2001  		},
  2002  		{
  2003  			name: "two fields no null",
  2004  			sorterFields: []fieldSort{
  2005  				{searchattribute.CloseTime, true, true},
  2006  				{searchattribute.StartTime, true, true},
  2007  			},
  2008  			searchAfter: []any{
  2009  				json.Number(fmt.Sprintf("%d", closeTime.UnixNano())),
  2010  				json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  2011  			},
  2012  			res: []elastic.Query{
  2013  				elastic.NewBoolQuery().Filter(
  2014  					elastic.NewRangeQuery(searchattribute.CloseTime).Lt(closeTime.Format(time.RFC3339Nano)),
  2015  				),
  2016  				elastic.NewBoolQuery().
  2017  					Filter(
  2018  						elastic.NewTermQuery(searchattribute.CloseTime, closeTime.Format(time.RFC3339Nano)),
  2019  						elastic.NewRangeQuery(searchattribute.StartTime).Lt(startTime.Format(time.RFC3339Nano)),
  2020  					),
  2021  			},
  2022  			err: nil,
  2023  		},
  2024  		{
  2025  			name: "three fields",
  2026  			sorterFields: []fieldSort{
  2027  				{searchattribute.CloseTime, true, true},
  2028  				{searchattribute.StartTime, true, true},
  2029  				{searchattribute.RunID, false, true},
  2030  			},
  2031  			searchAfter: []any{
  2032  				json.Number(fmt.Sprintf("%d", closeTime.UnixNano())),
  2033  				json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  2034  				"random-run-id",
  2035  			},
  2036  			res: []elastic.Query{
  2037  				elastic.NewBoolQuery().Filter(
  2038  					elastic.NewRangeQuery(searchattribute.CloseTime).Lt(closeTime.Format(time.RFC3339Nano)),
  2039  				),
  2040  				elastic.NewBoolQuery().
  2041  					Filter(
  2042  						elastic.NewTermQuery(searchattribute.CloseTime, closeTime.Format(time.RFC3339Nano)),
  2043  						elastic.NewRangeQuery(searchattribute.StartTime).Lt(startTime.Format(time.RFC3339Nano)),
  2044  					),
  2045  				elastic.NewBoolQuery().
  2046  					Filter(
  2047  						elastic.NewTermQuery(searchattribute.CloseTime, closeTime.Format(time.RFC3339Nano)),
  2048  						elastic.NewTermQuery(searchattribute.StartTime, startTime.Format(time.RFC3339Nano)),
  2049  						elastic.NewRangeQuery(searchattribute.RunID).Gt("random-run-id"),
  2050  					),
  2051  			},
  2052  			err: nil,
  2053  		},
  2054  		{
  2055  			name: "invalid token: wrong size",
  2056  			sorterFields: []fieldSort{
  2057  				{searchattribute.CloseTime, true, true},
  2058  				{searchattribute.StartTime, true, true},
  2059  				{searchattribute.RunID, false, true},
  2060  			},
  2061  			searchAfter: []any{
  2062  				json.Number(fmt.Sprintf("%d", closeTime.UnixNano())),
  2063  				json.Number(fmt.Sprintf("%d", startTime.UnixNano())),
  2064  			},
  2065  			res: nil,
  2066  			err: serviceerror.NewInvalidArgument("Invalid page token for given sort fields: expected 3 fields, got 2"),
  2067  		},
  2068  		{
  2069  			name: "invalid token: last value null",
  2070  			sorterFields: []fieldSort{
  2071  				{searchattribute.CloseTime, true, true},
  2072  			},
  2073  			searchAfter: []any{datetimeNull},
  2074  			res:         nil,
  2075  			err:         serviceerror.NewInternal("Last field of sorter cannot be a nullable field: \"CloseTime\" has null values"),
  2076  		},
  2077  	}
  2078  
  2079  	for _, tc := range testCases {
  2080  		s.T().Run(tc.name, func(t *testing.T) {
  2081  			res, err := buildPaginationQuery(tc.sorterFields, tc.searchAfter, searchattribute.TestNameTypeMap)
  2082  			s.Equal(tc.err, err)
  2083  			s.Equal(tc.res, res)
  2084  		})
  2085  	}
  2086  }
  2087  
  2088  func (s *ESVisibilitySuite) Test_parsePageTokenValue() {
  2089  	testCases := []struct {
  2090  		name  string
  2091  		value any
  2092  		tp    enumspb.IndexedValueType
  2093  		res   any
  2094  		err   error
  2095  	}{
  2096  		{
  2097  			name:  "IntField",
  2098  			value: 123,
  2099  			tp:    enumspb.INDEXED_VALUE_TYPE_INT,
  2100  			res:   int64(123),
  2101  			err:   nil,
  2102  		},
  2103  		{
  2104  			name:  "NullMaxIntField",
  2105  			value: math.MaxInt64,
  2106  			tp:    enumspb.INDEXED_VALUE_TYPE_INT,
  2107  			res:   nil,
  2108  			err:   nil,
  2109  		},
  2110  		{
  2111  			name:  "NullMinIntField",
  2112  			value: math.MinInt64,
  2113  			tp:    enumspb.INDEXED_VALUE_TYPE_INT,
  2114  			res:   nil,
  2115  			err:   nil,
  2116  		},
  2117  		{
  2118  			name:  "BoolFieldTrue",
  2119  			value: 1,
  2120  			tp:    enumspb.INDEXED_VALUE_TYPE_BOOL,
  2121  			res:   true,
  2122  			err:   nil,
  2123  		},
  2124  		{
  2125  			name:  "BoolFieldFalse",
  2126  			value: 0,
  2127  			tp:    enumspb.INDEXED_VALUE_TYPE_BOOL,
  2128  			res:   false,
  2129  			err:   nil,
  2130  		},
  2131  		{
  2132  			name:  "NullMaxBoolField",
  2133  			value: math.MaxInt64,
  2134  			tp:    enumspb.INDEXED_VALUE_TYPE_BOOL,
  2135  			res:   nil,
  2136  			err:   nil,
  2137  		},
  2138  		{
  2139  			name:  "NullMinBoolField",
  2140  			value: math.MinInt64,
  2141  			tp:    enumspb.INDEXED_VALUE_TYPE_BOOL,
  2142  			res:   nil,
  2143  			err:   nil,
  2144  		},
  2145  		{
  2146  			name:  "DatetimeField",
  2147  			value: 1683221689123456789,
  2148  			tp:    enumspb.INDEXED_VALUE_TYPE_DATETIME,
  2149  			res:   "2023-05-04T17:34:49.123456789Z",
  2150  			err:   nil,
  2151  		},
  2152  		{
  2153  			name:  "NullMaxDatetimeField",
  2154  			value: math.MaxInt64,
  2155  			tp:    enumspb.INDEXED_VALUE_TYPE_DATETIME,
  2156  			res:   nil,
  2157  			err:   nil,
  2158  		},
  2159  		{
  2160  			name:  "NullMinDatetimeField",
  2161  			value: math.MinInt64,
  2162  			tp:    enumspb.INDEXED_VALUE_TYPE_DATETIME,
  2163  			res:   nil,
  2164  			err:   nil,
  2165  		},
  2166  		{
  2167  			name:  "DoubleField",
  2168  			value: 3.14,
  2169  			tp:    enumspb.INDEXED_VALUE_TYPE_DOUBLE,
  2170  			res:   float64(3.14),
  2171  			err:   nil,
  2172  		},
  2173  		{
  2174  			name:  "NullMaxDoubleField",
  2175  			value: "Infinity",
  2176  			tp:    enumspb.INDEXED_VALUE_TYPE_DOUBLE,
  2177  			res:   nil,
  2178  			err:   nil,
  2179  		},
  2180  		{
  2181  			name:  "NullMinDoubleField",
  2182  			value: "-Infinity",
  2183  			tp:    enumspb.INDEXED_VALUE_TYPE_DOUBLE,
  2184  			res:   nil,
  2185  			err:   nil,
  2186  		},
  2187  		{
  2188  			name:  "KeywordField",
  2189  			value: "foo",
  2190  			tp:    enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  2191  			res:   "foo",
  2192  			err:   nil,
  2193  		},
  2194  		{
  2195  			name:  "NullKeywordField",
  2196  			value: nil,
  2197  			tp:    enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  2198  			res:   nil,
  2199  			err:   nil,
  2200  		},
  2201  		{
  2202  			name:  "IntFieldError",
  2203  			value: "123",
  2204  			tp:    enumspb.INDEXED_VALUE_TYPE_INT,
  2205  			res:   nil,
  2206  			err:   serviceerror.NewInvalidArgument("Invalid page token: expected interger type, got \"123\""),
  2207  		},
  2208  		{
  2209  			name:  "DoubleFieldError",
  2210  			value: "foo",
  2211  			tp:    enumspb.INDEXED_VALUE_TYPE_DOUBLE,
  2212  			res:   nil,
  2213  			err:   serviceerror.NewInvalidArgument("Invalid page token: expected float type, got \"foo\""),
  2214  		},
  2215  		{
  2216  			name:  "KeywordFieldError",
  2217  			value: 123,
  2218  			tp:    enumspb.INDEXED_VALUE_TYPE_KEYWORD,
  2219  			res:   nil,
  2220  			err:   serviceerror.NewInvalidArgument("Invalid page token: expected string type, got 123"),
  2221  		},
  2222  		{
  2223  			name:  "TextFieldError",
  2224  			value: "foo",
  2225  			tp:    enumspb.INDEXED_VALUE_TYPE_TEXT,
  2226  			res:   nil,
  2227  			err:   serviceerror.NewInvalidArgument("Invalid field type in sorter: cannot order by \"TextFieldError\""),
  2228  		},
  2229  	}
  2230  
  2231  	pageToken := &visibilityPageToken{}
  2232  	for _, tc := range testCases {
  2233  		pageToken.SearchAfter = append(pageToken.SearchAfter, tc.value)
  2234  	}
  2235  	jsonToken, _ := json.Marshal(pageToken)
  2236  	pageToken, err := s.visibilityStore.deserializePageToken(jsonToken)
  2237  	s.NoError(err)
  2238  	s.Equal(len(testCases), len(pageToken.SearchAfter))
  2239  	for i, tc := range testCases {
  2240  		s.T().Run(tc.name, func(t *testing.T) {
  2241  			res, err := parsePageTokenValue(tc.name, pageToken.SearchAfter[i], tc.tp)
  2242  			s.Equal(tc.err, err)
  2243  			s.Equal(tc.res, res)
  2244  		})
  2245  	}
  2246  }