go.temporal.io/server@v1.23.0/common/persistence/visibility/store/sql/visibility_store.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package sql
    26  
    27  import (
    28  	"context"
    29  	"errors"
    30  	"fmt"
    31  	"strings"
    32  	"time"
    33  
    34  	"go.temporal.io/api/common/v1"
    35  	enumspb "go.temporal.io/api/enums/v1"
    36  	"go.temporal.io/api/serviceerror"
    37  	"go.temporal.io/api/workflowservice/v1"
    38  
    39  	"go.temporal.io/server/common/config"
    40  	"go.temporal.io/server/common/log"
    41  	"go.temporal.io/server/common/namespace"
    42  	"go.temporal.io/server/common/persistence"
    43  	persistencesql "go.temporal.io/server/common/persistence/sql"
    44  	"go.temporal.io/server/common/persistence/sql/sqlplugin"
    45  	"go.temporal.io/server/common/persistence/visibility/manager"
    46  	"go.temporal.io/server/common/persistence/visibility/store"
    47  	"go.temporal.io/server/common/persistence/visibility/store/query"
    48  	"go.temporal.io/server/common/resolver"
    49  	"go.temporal.io/server/common/searchattribute"
    50  )
    51  
    52  type (
    53  	VisibilityStore struct {
    54  		sqlStore                       persistencesql.SqlStore
    55  		searchAttributesProvider       searchattribute.Provider
    56  		searchAttributesMapperProvider searchattribute.MapperProvider
    57  	}
    58  )
    59  
    60  var _ store.VisibilityStore = (*VisibilityStore)(nil)
    61  
    62  var maxTime, _ = time.Parse(time.RFC3339, "9999-12-31T23:59:59Z")
    63  
    64  // NewSQLVisibilityStore creates an instance of VisibilityStore
    65  func NewSQLVisibilityStore(
    66  	cfg config.SQL,
    67  	r resolver.ServiceResolver,
    68  	searchAttributesProvider searchattribute.Provider,
    69  	searchAttributesMapperProvider searchattribute.MapperProvider,
    70  	logger log.Logger,
    71  ) (*VisibilityStore, error) {
    72  	refDbConn := persistencesql.NewRefCountedDBConn(sqlplugin.DbKindVisibility, &cfg, r)
    73  	db, err := refDbConn.Get()
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	return &VisibilityStore{
    78  		sqlStore:                       persistencesql.NewSqlStore(db, logger),
    79  		searchAttributesProvider:       searchAttributesProvider,
    80  		searchAttributesMapperProvider: searchAttributesMapperProvider,
    81  	}, nil
    82  }
    83  
    84  func (s *VisibilityStore) Close() {
    85  	s.sqlStore.Close()
    86  }
    87  
    88  func (s *VisibilityStore) GetName() string {
    89  	return s.sqlStore.GetName()
    90  }
    91  
    92  func (s *VisibilityStore) GetIndexName() string {
    93  	return s.sqlStore.GetDbName()
    94  }
    95  
    96  func (s *VisibilityStore) ValidateCustomSearchAttributes(
    97  	searchAttributes map[string]any,
    98  ) (map[string]any, error) {
    99  	return searchAttributes, nil
   100  }
   101  
   102  func (s *VisibilityStore) RecordWorkflowExecutionStarted(
   103  	ctx context.Context,
   104  	request *store.InternalRecordWorkflowExecutionStartedRequest,
   105  ) error {
   106  	row, err := s.generateVisibilityRow(request.InternalVisibilityRequestBase)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	_, err = s.sqlStore.Db.InsertIntoVisibility(ctx, row)
   112  	return err
   113  }
   114  
   115  func (s *VisibilityStore) RecordWorkflowExecutionClosed(
   116  	ctx context.Context,
   117  	request *store.InternalRecordWorkflowExecutionClosedRequest,
   118  ) error {
   119  	row, err := s.generateVisibilityRow(request.InternalVisibilityRequestBase)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	row.CloseTime = &request.CloseTime
   125  	row.HistoryLength = &request.HistoryLength
   126  	row.HistorySizeBytes = &request.HistorySizeBytes
   127  	row.ExecutionDuration = &request.ExecutionDuration
   128  	row.StateTransitionCount = &request.StateTransitionCount
   129  
   130  	result, err := s.sqlStore.Db.ReplaceIntoVisibility(ctx, row)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	noRowsAffected, err := result.RowsAffected()
   135  	if err != nil {
   136  		return fmt.Errorf("RecordWorkflowExecutionClosed rowsAffected error: %v", err)
   137  	}
   138  	if noRowsAffected > 2 { // either adds a new row or deletes old row and adds new row
   139  		return fmt.Errorf(
   140  			"RecordWorkflowExecutionClosed unexpected numRows (%v) updated",
   141  			noRowsAffected,
   142  		)
   143  	}
   144  	return nil
   145  }
   146  
   147  func (s *VisibilityStore) UpsertWorkflowExecution(
   148  	ctx context.Context,
   149  	request *store.InternalUpsertWorkflowExecutionRequest,
   150  ) error {
   151  	row, err := s.generateVisibilityRow(request.InternalVisibilityRequestBase)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	result, err := s.sqlStore.Db.ReplaceIntoVisibility(ctx, row)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	noRowsAffected, err := result.RowsAffected()
   161  	if err != nil {
   162  		return err
   163  	}
   164  	if noRowsAffected > 2 { // either adds a new or deletes old row and adds new row
   165  		return fmt.Errorf("UpsertWorkflowExecution unexpected numRows (%v) updates", noRowsAffected)
   166  	}
   167  	return nil
   168  }
   169  
   170  func (s *VisibilityStore) ListOpenWorkflowExecutions(
   171  	ctx context.Context,
   172  	request *manager.ListWorkflowExecutionsRequest,
   173  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   174  	return s.ListWorkflowExecutions(
   175  		ctx,
   176  		&manager.ListWorkflowExecutionsRequestV2{
   177  			NamespaceID:   request.NamespaceID,
   178  			Namespace:     request.Namespace,
   179  			PageSize:      request.PageSize,
   180  			NextPageToken: request.NextPageToken,
   181  			Query: s.buildQueryStringFromListRequest(
   182  				request,
   183  				enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
   184  				"",
   185  				"",
   186  			),
   187  		},
   188  	)
   189  }
   190  
   191  func (s *VisibilityStore) ListClosedWorkflowExecutions(
   192  	ctx context.Context,
   193  	request *manager.ListWorkflowExecutionsRequest,
   194  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   195  	return s.ListWorkflowExecutions(
   196  		ctx,
   197  		&manager.ListWorkflowExecutionsRequestV2{
   198  			NamespaceID:   request.NamespaceID,
   199  			Namespace:     request.Namespace,
   200  			PageSize:      request.PageSize,
   201  			NextPageToken: request.NextPageToken,
   202  			Query: s.buildQueryStringFromListRequest(
   203  				request,
   204  				enumspb.WORKFLOW_EXECUTION_STATUS_UNSPECIFIED,
   205  				"",
   206  				"",
   207  			),
   208  		},
   209  	)
   210  }
   211  
   212  func (s *VisibilityStore) ListOpenWorkflowExecutionsByType(
   213  	ctx context.Context,
   214  	request *manager.ListWorkflowExecutionsByTypeRequest,
   215  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   216  	return s.ListWorkflowExecutions(
   217  		ctx,
   218  		&manager.ListWorkflowExecutionsRequestV2{
   219  			NamespaceID:   request.NamespaceID,
   220  			Namespace:     request.Namespace,
   221  			PageSize:      request.PageSize,
   222  			NextPageToken: request.NextPageToken,
   223  			Query: s.buildQueryStringFromListRequest(
   224  				request.ListWorkflowExecutionsRequest,
   225  				enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
   226  				"",
   227  				request.WorkflowTypeName,
   228  			),
   229  		},
   230  	)
   231  }
   232  
   233  func (s *VisibilityStore) ListClosedWorkflowExecutionsByType(
   234  	ctx context.Context,
   235  	request *manager.ListWorkflowExecutionsByTypeRequest,
   236  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   237  	return s.ListWorkflowExecutions(
   238  		ctx,
   239  		&manager.ListWorkflowExecutionsRequestV2{
   240  			NamespaceID:   request.NamespaceID,
   241  			Namespace:     request.Namespace,
   242  			PageSize:      request.PageSize,
   243  			NextPageToken: request.NextPageToken,
   244  			Query: s.buildQueryStringFromListRequest(
   245  				request.ListWorkflowExecutionsRequest,
   246  				enumspb.WORKFLOW_EXECUTION_STATUS_UNSPECIFIED,
   247  				"",
   248  				request.WorkflowTypeName,
   249  			),
   250  		},
   251  	)
   252  }
   253  
   254  func (s *VisibilityStore) ListOpenWorkflowExecutionsByWorkflowID(
   255  	ctx context.Context,
   256  	request *manager.ListWorkflowExecutionsByWorkflowIDRequest,
   257  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   258  	return s.ListWorkflowExecutions(
   259  		ctx,
   260  		&manager.ListWorkflowExecutionsRequestV2{
   261  			NamespaceID:   request.NamespaceID,
   262  			Namespace:     request.Namespace,
   263  			PageSize:      request.PageSize,
   264  			NextPageToken: request.NextPageToken,
   265  			Query: s.buildQueryStringFromListRequest(
   266  				request.ListWorkflowExecutionsRequest,
   267  				enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
   268  				request.WorkflowID,
   269  				"",
   270  			),
   271  		},
   272  	)
   273  }
   274  
   275  func (s *VisibilityStore) ListClosedWorkflowExecutionsByWorkflowID(
   276  	ctx context.Context,
   277  	request *manager.ListWorkflowExecutionsByWorkflowIDRequest,
   278  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   279  	return s.ListWorkflowExecutions(
   280  		ctx,
   281  		&manager.ListWorkflowExecutionsRequestV2{
   282  			NamespaceID:   request.NamespaceID,
   283  			Namespace:     request.Namespace,
   284  			PageSize:      request.PageSize,
   285  			NextPageToken: request.NextPageToken,
   286  			Query: s.buildQueryStringFromListRequest(
   287  				request.ListWorkflowExecutionsRequest,
   288  				enumspb.WORKFLOW_EXECUTION_STATUS_UNSPECIFIED,
   289  				request.WorkflowID,
   290  				"",
   291  			),
   292  		},
   293  	)
   294  }
   295  
   296  func (s *VisibilityStore) ListClosedWorkflowExecutionsByStatus(
   297  	ctx context.Context,
   298  	request *manager.ListClosedWorkflowExecutionsByStatusRequest,
   299  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   300  	return s.ListWorkflowExecutions(
   301  		ctx,
   302  		&manager.ListWorkflowExecutionsRequestV2{
   303  			NamespaceID:   request.NamespaceID,
   304  			Namespace:     request.Namespace,
   305  			PageSize:      request.PageSize,
   306  			NextPageToken: request.NextPageToken,
   307  			Query: s.buildQueryStringFromListRequest(
   308  				request.ListWorkflowExecutionsRequest,
   309  				request.Status,
   310  				"",
   311  				"",
   312  			),
   313  		},
   314  	)
   315  }
   316  
   317  func (s *VisibilityStore) DeleteWorkflowExecution(
   318  	ctx context.Context,
   319  	request *manager.VisibilityDeleteWorkflowExecutionRequest,
   320  ) error {
   321  	_, err := s.sqlStore.Db.DeleteFromVisibility(ctx, sqlplugin.VisibilityDeleteFilter{
   322  		NamespaceID: request.NamespaceID.String(),
   323  		RunID:       request.RunID,
   324  	})
   325  	if err != nil {
   326  		return serviceerror.NewUnavailable(err.Error())
   327  	}
   328  	return nil
   329  }
   330  
   331  func (s *VisibilityStore) ListWorkflowExecutions(
   332  	ctx context.Context,
   333  	request *manager.ListWorkflowExecutionsRequestV2,
   334  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   335  	saTypeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.GetIndexName(), false)
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	saMapper, err := s.searchAttributesMapperProvider.GetMapper(request.Namespace)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  
   345  	converter := NewQueryConverter(
   346  		s.GetName(),
   347  		request.Namespace,
   348  		request.NamespaceID,
   349  		saTypeMap,
   350  		saMapper,
   351  		request.Query,
   352  	)
   353  	selectFilter, err := converter.BuildSelectStmt(request.PageSize, request.NextPageToken)
   354  	if err != nil {
   355  		// Convert ConverterError to InvalidArgument and pass through all other errors (which should be only mapper errors).
   356  		var converterErr *query.ConverterError
   357  		if errors.As(err, &converterErr) {
   358  			return nil, converterErr.ToInvalidArgument()
   359  		}
   360  		return nil, err
   361  	}
   362  
   363  	rows, err := s.sqlStore.Db.SelectFromVisibility(ctx, *selectFilter)
   364  	if err != nil {
   365  		return nil, serviceerror.NewUnavailable(
   366  			fmt.Sprintf("ListWorkflowExecutions operation failed. Select failed: %v", err))
   367  	}
   368  	if len(rows) == 0 {
   369  		return &store.InternalListWorkflowExecutionsResponse{}, nil
   370  	}
   371  
   372  	var infos = make([]*store.InternalWorkflowExecutionInfo, len(rows))
   373  	for i, row := range rows {
   374  		infos[i], err = s.rowToInfo(&row, request.Namespace)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  	}
   379  
   380  	var nextPageToken []byte
   381  	if len(rows) == request.PageSize {
   382  		lastRow := rows[len(rows)-1]
   383  		closeTime := maxTime
   384  		if lastRow.CloseTime != nil {
   385  			closeTime = *lastRow.CloseTime
   386  		}
   387  		nextPageToken, err = serializePageToken(&pageToken{
   388  			CloseTime: closeTime,
   389  			StartTime: lastRow.StartTime,
   390  			RunID:     lastRow.RunID,
   391  		})
   392  		if err != nil {
   393  			return nil, err
   394  		}
   395  	}
   396  	return &store.InternalListWorkflowExecutionsResponse{
   397  		Executions:    infos,
   398  		NextPageToken: nextPageToken,
   399  	}, nil
   400  }
   401  
   402  func (s *VisibilityStore) ScanWorkflowExecutions(
   403  	ctx context.Context,
   404  	request *manager.ListWorkflowExecutionsRequestV2,
   405  ) (*store.InternalListWorkflowExecutionsResponse, error) {
   406  	return s.ListWorkflowExecutions(ctx, request)
   407  }
   408  
   409  func (s *VisibilityStore) CountWorkflowExecutions(
   410  	ctx context.Context,
   411  	request *manager.CountWorkflowExecutionsRequest,
   412  ) (*manager.CountWorkflowExecutionsResponse, error) {
   413  	saTypeMap, err := s.searchAttributesProvider.GetSearchAttributes(s.GetIndexName(), false)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	saMapper, err := s.searchAttributesMapperProvider.GetMapper(request.Namespace)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  
   423  	converter := NewQueryConverter(
   424  		s.GetName(),
   425  		request.Namespace,
   426  		request.NamespaceID,
   427  		saTypeMap,
   428  		saMapper,
   429  		request.Query,
   430  	)
   431  	selectFilter, err := converter.BuildCountStmt()
   432  	if err != nil {
   433  		// Convert ConverterError to InvalidArgument and pass through all other errors (which should be only mapper errors).
   434  		var converterErr *query.ConverterError
   435  		if errors.As(err, &converterErr) {
   436  			return nil, converterErr.ToInvalidArgument()
   437  		}
   438  		return nil, err
   439  	}
   440  
   441  	if len(selectFilter.GroupBy) > 0 {
   442  		return s.countGroupByWorkflowExecutions(ctx, selectFilter, saTypeMap)
   443  	}
   444  
   445  	count, err := s.sqlStore.Db.CountFromVisibility(ctx, *selectFilter)
   446  	if err != nil {
   447  		return nil, serviceerror.NewUnavailable(
   448  			fmt.Sprintf("CountWorkflowExecutions operation failed. Query failed: %v", err))
   449  	}
   450  
   451  	return &manager.CountWorkflowExecutionsResponse{Count: count}, nil
   452  }
   453  
   454  func (s *VisibilityStore) countGroupByWorkflowExecutions(
   455  	ctx context.Context,
   456  	selectFilter *sqlplugin.VisibilitySelectFilter,
   457  	saTypeMap searchattribute.NameTypeMap,
   458  ) (*manager.CountWorkflowExecutionsResponse, error) {
   459  	var err error
   460  	groupByTypes := make([]enumspb.IndexedValueType, len(selectFilter.GroupBy))
   461  	for i, fieldName := range selectFilter.GroupBy {
   462  		groupByTypes[i], err = saTypeMap.GetType(fieldName)
   463  		if err != nil {
   464  			return nil, err
   465  		}
   466  	}
   467  
   468  	rows, err := s.sqlStore.Db.CountGroupByFromVisibility(ctx, *selectFilter)
   469  	if err != nil {
   470  		return nil, serviceerror.NewUnavailable(
   471  			fmt.Sprintf("CountWorkflowExecutions operation failed. Query failed: %v", err))
   472  	}
   473  	resp := &manager.CountWorkflowExecutionsResponse{
   474  		Count:  0,
   475  		Groups: make([]*workflowservice.CountWorkflowExecutionsResponse_AggregationGroup, 0, len(rows)),
   476  	}
   477  	for _, row := range rows {
   478  		groupValues := make([]*common.Payload, len(row.GroupValues))
   479  		for i, val := range row.GroupValues {
   480  			groupValues[i], err = searchattribute.EncodeValue(val, groupByTypes[i])
   481  			if err != nil {
   482  				return nil, err
   483  			}
   484  		}
   485  		resp.Groups = append(
   486  			resp.Groups,
   487  			&workflowservice.CountWorkflowExecutionsResponse_AggregationGroup{
   488  				GroupValues: groupValues,
   489  				Count:       row.Count,
   490  			},
   491  		)
   492  		resp.Count += row.Count
   493  	}
   494  	return resp, nil
   495  }
   496  
   497  func (s *VisibilityStore) GetWorkflowExecution(
   498  	ctx context.Context,
   499  	request *manager.GetWorkflowExecutionRequest,
   500  ) (*store.InternalGetWorkflowExecutionResponse, error) {
   501  	row, err := s.sqlStore.Db.GetFromVisibility(ctx, sqlplugin.VisibilityGetFilter{
   502  		NamespaceID: request.NamespaceID.String(),
   503  		RunID:       request.RunID,
   504  	})
   505  	if err != nil {
   506  		return nil, serviceerror.NewUnavailable(
   507  			fmt.Sprintf("GetWorkflowExecution operation failed. Select failed: %v", err))
   508  	}
   509  	info, err := s.rowToInfo(row, request.Namespace)
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  	return &store.InternalGetWorkflowExecutionResponse{
   514  		Execution: info,
   515  	}, nil
   516  }
   517  
   518  func (s *VisibilityStore) generateVisibilityRow(
   519  	request *store.InternalVisibilityRequestBase,
   520  ) (*sqlplugin.VisibilityRow, error) {
   521  	searchAttributes, err := s.prepareSearchAttributesForDb(request)
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  
   526  	return &sqlplugin.VisibilityRow{
   527  		NamespaceID:      request.NamespaceID,
   528  		WorkflowID:       request.WorkflowID,
   529  		RunID:            request.RunID,
   530  		StartTime:        request.StartTime,
   531  		ExecutionTime:    request.ExecutionTime,
   532  		WorkflowTypeName: request.WorkflowTypeName,
   533  		Status:           int32(request.Status),
   534  		Memo:             request.Memo.Data,
   535  		Encoding:         request.Memo.EncodingType.String(),
   536  		TaskQueue:        request.TaskQueue,
   537  		SearchAttributes: searchAttributes,
   538  		ParentWorkflowID: request.ParentWorkflowID,
   539  		ParentRunID:      request.ParentRunID,
   540  	}, nil
   541  }
   542  
   543  func (s *VisibilityStore) prepareSearchAttributesForDb(
   544  	request *store.InternalVisibilityRequestBase,
   545  ) (*sqlplugin.VisibilitySearchAttributes, error) {
   546  	if request.SearchAttributes == nil {
   547  		return nil, nil
   548  	}
   549  
   550  	saTypeMap, err := s.searchAttributesProvider.GetSearchAttributes(
   551  		s.GetIndexName(),
   552  		false,
   553  	)
   554  	if err != nil {
   555  		return nil, serviceerror.NewUnavailable(
   556  			fmt.Sprintf("Unable to read search attributes types: %v", err))
   557  	}
   558  
   559  	var searchAttributes sqlplugin.VisibilitySearchAttributes
   560  	searchAttributes, err = searchattribute.Decode(request.SearchAttributes, &saTypeMap, false)
   561  	if err != nil {
   562  		return nil, err
   563  	}
   564  	// This is to prevent existing tasks to fail indefinitely.
   565  	// If it's only invalid values error, then silently continue without them.
   566  	searchAttributes, err = s.ValidateCustomSearchAttributes(searchAttributes)
   567  	if err != nil {
   568  		if _, ok := err.(*store.VisibilityStoreInvalidValuesError); !ok {
   569  			return nil, err
   570  		}
   571  	}
   572  
   573  	for name, value := range searchAttributes {
   574  		if value == nil {
   575  			delete(searchAttributes, name)
   576  			continue
   577  		}
   578  		tp, err := saTypeMap.GetType(name)
   579  		if err != nil {
   580  			return nil, err
   581  		}
   582  		if tp == enumspb.INDEXED_VALUE_TYPE_DATETIME {
   583  			if dt, ok := value.(time.Time); ok {
   584  				searchAttributes[name] = dt.Format(time.RFC3339Nano)
   585  			}
   586  		}
   587  	}
   588  	return &searchAttributes, nil
   589  }
   590  
   591  func (s *VisibilityStore) rowToInfo(
   592  	row *sqlplugin.VisibilityRow,
   593  	nsName namespace.Name,
   594  ) (*store.InternalWorkflowExecutionInfo, error) {
   595  	if row.ExecutionTime.UnixNano() == 0 {
   596  		row.ExecutionTime = row.StartTime
   597  	}
   598  	info := &store.InternalWorkflowExecutionInfo{
   599  		WorkflowID:    row.WorkflowID,
   600  		RunID:         row.RunID,
   601  		TypeName:      row.WorkflowTypeName,
   602  		StartTime:     row.StartTime,
   603  		ExecutionTime: row.ExecutionTime,
   604  		Status:        enumspb.WorkflowExecutionStatus(row.Status),
   605  		TaskQueue:     row.TaskQueue,
   606  		Memo:          persistence.NewDataBlob(row.Memo, row.Encoding),
   607  	}
   608  	if row.SearchAttributes != nil && len(*row.SearchAttributes) > 0 {
   609  		searchAttributes, err := s.processRowSearchAttributes(*row.SearchAttributes, nsName)
   610  		if err != nil {
   611  			return nil, err
   612  		}
   613  		info.SearchAttributes = searchAttributes
   614  	}
   615  	if row.CloseTime != nil {
   616  		info.CloseTime = *row.CloseTime
   617  	}
   618  	if row.HistoryLength != nil {
   619  		info.HistoryLength = *row.HistoryLength
   620  	}
   621  	if row.HistorySizeBytes != nil {
   622  		info.HistorySizeBytes = *row.HistorySizeBytes
   623  	}
   624  	if row.StateTransitionCount != nil {
   625  		info.StateTransitionCount = *row.StateTransitionCount
   626  	}
   627  	if row.ParentWorkflowID != nil {
   628  		info.ParentWorkflowID = *row.ParentWorkflowID
   629  	}
   630  	if row.ParentRunID != nil {
   631  		info.ParentRunID = *row.ParentRunID
   632  	}
   633  	return info, nil
   634  }
   635  
   636  func (s *VisibilityStore) processRowSearchAttributes(
   637  	rowSearchAttributes sqlplugin.VisibilitySearchAttributes,
   638  	nsName namespace.Name,
   639  ) (*common.SearchAttributes, error) {
   640  	saTypeMap, err := s.searchAttributesProvider.GetSearchAttributes(
   641  		s.GetIndexName(),
   642  		false,
   643  	)
   644  	if err != nil {
   645  		return nil, serviceerror.NewUnavailable(
   646  			fmt.Sprintf("Unable to read search attributes types: %v", err))
   647  	}
   648  	// In SQLite, keyword list can return a string when there's only one element.
   649  	// This changes it into a slice.
   650  	for name, value := range rowSearchAttributes {
   651  		tp, err := saTypeMap.GetType(name)
   652  		if err != nil {
   653  			return nil, err
   654  		}
   655  		if tp == enumspb.INDEXED_VALUE_TYPE_KEYWORD_LIST {
   656  			switch v := value.(type) {
   657  			case []string:
   658  				// no-op
   659  			case string:
   660  				(rowSearchAttributes)[name] = []string{v}
   661  			default:
   662  				return nil, serviceerror.NewInternal(
   663  					fmt.Sprintf("Unexpected data type for keyword list: %T (expected list of strings)", v),
   664  				)
   665  			}
   666  		}
   667  	}
   668  	searchAttributes, err := searchattribute.Encode(rowSearchAttributes, &saTypeMap)
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  	aliasedSas, err := searchattribute.AliasFields(
   673  		s.searchAttributesMapperProvider,
   674  		searchAttributes,
   675  		nsName.String(),
   676  	)
   677  	if err != nil {
   678  		return nil, err
   679  	}
   680  	if aliasedSas != nil {
   681  		searchAttributes = aliasedSas
   682  	}
   683  	return searchAttributes, nil
   684  }
   685  
   686  func (s *VisibilityStore) buildQueryStringFromListRequest(
   687  	request *manager.ListWorkflowExecutionsRequest,
   688  	executionStatus enumspb.WorkflowExecutionStatus,
   689  	workflowID string,
   690  	workflowTypeName string,
   691  ) string {
   692  	var queryTerms []string
   693  
   694  	switch executionStatus {
   695  	case enumspb.WORKFLOW_EXECUTION_STATUS_UNSPECIFIED:
   696  		queryTerms = append(
   697  			queryTerms,
   698  			fmt.Sprintf(
   699  				"%s != %d",
   700  				searchattribute.ExecutionStatus,
   701  				int32(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING),
   702  			),
   703  		)
   704  	default:
   705  		queryTerms = append(
   706  			queryTerms,
   707  			fmt.Sprintf("%s = %d", searchattribute.ExecutionStatus, int32(executionStatus)),
   708  		)
   709  	}
   710  
   711  	var timeAttr string
   712  	if executionStatus == enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING {
   713  		timeAttr = searchattribute.StartTime
   714  	} else {
   715  		timeAttr = searchattribute.CloseTime
   716  	}
   717  	queryTerms = append(
   718  		queryTerms,
   719  		fmt.Sprintf(
   720  			"%s BETWEEN '%s' AND '%s'",
   721  			timeAttr,
   722  			request.EarliestStartTime.UTC().Format(time.RFC3339Nano),
   723  			request.LatestStartTime.UTC().Format(time.RFC3339Nano),
   724  		),
   725  	)
   726  
   727  	if request.NamespaceDivision != "" {
   728  		queryTerms = append(
   729  			queryTerms,
   730  			fmt.Sprintf(
   731  				"%s = '%s'",
   732  				searchattribute.TemporalNamespaceDivision,
   733  				request.NamespaceDivision,
   734  			),
   735  		)
   736  	} else {
   737  		queryTerms = append(
   738  			queryTerms,
   739  			fmt.Sprintf("%s IS NULL", searchattribute.TemporalNamespaceDivision),
   740  		)
   741  	}
   742  
   743  	if workflowID != "" {
   744  		queryTerms = append(
   745  			queryTerms,
   746  			fmt.Sprintf("%s = '%s'", searchattribute.WorkflowID, workflowID),
   747  		)
   748  	}
   749  
   750  	if workflowTypeName != "" {
   751  		queryTerms = append(
   752  			queryTerms,
   753  			fmt.Sprintf("%s = '%s'", searchattribute.WorkflowType, workflowTypeName),
   754  		)
   755  	}
   756  
   757  	return strings.Join(queryTerms, " AND ")
   758  }