go.uber.org/cadence@v1.2.9/workflow/workflow.go (about)

     1  // Copyright (c) 2017-2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package workflow
    22  
    23  import (
    24  	"github.com/uber-go/tally"
    25  	"go.uber.org/zap"
    26  
    27  	"go.uber.org/cadence/encoded"
    28  	"go.uber.org/cadence/internal"
    29  )
    30  
    31  type (
    32  
    33  	// ChildWorkflowFuture represents the result of a child workflow execution
    34  	ChildWorkflowFuture = internal.ChildWorkflowFuture
    35  
    36  	// Type identifies a workflow type.
    37  	Type = internal.WorkflowType
    38  
    39  	// Execution Details.
    40  	Execution = internal.WorkflowExecution
    41  
    42  	// Version represents a change version. See GetVersion call.
    43  	Version = internal.Version
    44  
    45  	// ChildWorkflowOptions stores all child workflow specific parameters that will be stored inside of a Context.
    46  	ChildWorkflowOptions = internal.ChildWorkflowOptions
    47  
    48  	// Bugports contains opt-in flags for backports of old, buggy behavior that has been fixed.
    49  	// See the internal docs for details.
    50  	Bugports = internal.Bugports
    51  
    52  	// RegisterOptions consists of options for registering a workflow
    53  	RegisterOptions = internal.RegisterWorkflowOptions
    54  
    55  	// Info information about currently executing workflow
    56  	Info = internal.WorkflowInfo
    57  )
    58  
    59  // Register - registers a workflow function with the framework.
    60  // A workflow takes a workflow context and input and returns a (result, error) or just error.
    61  // Examples:
    62  //
    63  //	func sampleWorkflow(ctx workflow.Context, input []byte) (result []byte, err error)
    64  //	func sampleWorkflow(ctx workflow.Context, arg1 int, arg2 string) (result []byte, err error)
    65  //	func sampleWorkflow(ctx workflow.Context) (result []byte, err error)
    66  //	func sampleWorkflow(ctx workflow.Context, arg1 int) (result string, err error)
    67  //
    68  // Serialization of all primitive types, structures is supported ... except channels, functions, variadic, unsafe pointer.
    69  // This method calls panic if workflowFunc doesn't comply with the expected format.
    70  // Deprecated: Global workflow registration methods are replaced by equivalent Worker instance methods.
    71  // This method is kept to maintain backward compatibility and should not be used.
    72  func Register(workflowFunc interface{}) {
    73  	internal.RegisterWorkflow(workflowFunc)
    74  }
    75  
    76  // RegisterWithOptions registers the workflow function with options
    77  // The user can use options to provide an external name for the workflow or leave it empty if no
    78  // external name is required. This can be used as
    79  //
    80  //	client.RegisterWorkflow(sampleWorkflow, RegisterOptions{})
    81  //	client.RegisterWorkflow(sampleWorkflow, RegisterOptions{Name: "foo"})
    82  //
    83  // A workflow takes a workflow context and input and returns a (result, error) or just error.
    84  // Examples:
    85  //
    86  //	func sampleWorkflow(ctx workflow.Context, input []byte) (result []byte, err error)
    87  //	func sampleWorkflow(ctx workflow.Context, arg1 int, arg2 string) (result []byte, err error)
    88  //	func sampleWorkflow(ctx workflow.Context) (result []byte, err error)
    89  //	func sampleWorkflow(ctx workflow.Context, arg1 int) (result string, err error)
    90  //
    91  // Serialization of all primitive types, structures is supported ... except channels, functions, variadic, unsafe pointer.
    92  // This method calls panic if workflowFunc doesn't comply with the expected format.
    93  // Deprecated: Global workflow registration methods are replaced by equivalent Worker instance methods.
    94  // This method is kept to maintain backward compatibility and should not be used.
    95  func RegisterWithOptions(workflowFunc interface{}, opts RegisterOptions) {
    96  	internal.RegisterWorkflowWithOptions(workflowFunc, opts)
    97  }
    98  
    99  // GetRegisteredWorkflowTypes returns the registered workflow function/alias names.
   100  func GetRegisteredWorkflowTypes() []string {
   101  	return internal.GetRegisteredWorkflowTypes()
   102  }
   103  
   104  // ExecuteActivity requests activity execution in the context of a workflow.
   105  // Context can be used to pass the settings for this activity.
   106  // For example: task list that this need to be routed, timeouts that need to be configured.
   107  // Use ActivityOptions to pass down the options.
   108  //
   109  //	 ao := ActivityOptions{
   110  //		    TaskList: "exampleTaskList",
   111  //		    ScheduleToStartTimeout: 10 * time.Second,
   112  //		    StartToCloseTimeout: 5 * time.Second,
   113  //		    ScheduleToCloseTimeout: 10 * time.Second,
   114  //		    HeartbeatTimeout: 0,
   115  //		}
   116  //		ctx := WithActivityOptions(ctx, ao)
   117  //
   118  // Or to override a single option
   119  //
   120  //	ctx := WithTaskList(ctx, "exampleTaskList")
   121  //
   122  // Input activity is either an activity name (string) or a function representing an activity that is getting scheduled.
   123  // Input args are the arguments that need to be passed to the scheduled activity.
   124  //
   125  // If the activity failed to complete then the future get error would indicate the failure, and it can be one of
   126  // CustomError, TimeoutError, CanceledError, PanicError, GenericError.
   127  // You can cancel the pending activity using context(workflow.WithCancel(ctx)) and that will fail the activity with
   128  // error CanceledError.
   129  //
   130  // ExecuteActivity returns Future with activity result or failure.
   131  func ExecuteActivity(ctx Context, activity interface{}, args ...interface{}) Future {
   132  	return internal.ExecuteActivity(ctx, activity, args...)
   133  }
   134  
   135  // ExecuteLocalActivity requests to run a local activity. A local activity is like a regular activity with some key
   136  // differences:
   137  //
   138  // • Local activity is scheduled and run by the workflow worker locally.
   139  //
   140  // • Local activity does not need Cadence server to schedule activity task and does not rely on activity worker.
   141  //
   142  // • No need to register local activity.
   143  //
   144  // • The parameter activity to ExecuteLocalActivity() must be a function.
   145  //
   146  // • Local activity is for short living activities (usually finishes within seconds).
   147  //
   148  // • Local activity cannot heartbeat.
   149  //
   150  // Context can be used to pass the settings for this local activity.
   151  // For now there is only one setting for timeout to be set:
   152  //
   153  //	lao := LocalActivityOptions{
   154  //		ScheduleToCloseTimeout: 5 * time.Second,
   155  //	}
   156  //	ctx := WithLocalActivityOptions(ctx, lao)
   157  //
   158  // The timeout here should be relative shorter than the DecisionTaskStartToCloseTimeout of the workflow. If you need a
   159  // longer timeout, you probably should not use local activity and instead should use regular activity. Local activity is
   160  // designed to be used for short living activities (usually finishes within seconds).
   161  //
   162  // Input args are the arguments that will to be passed to the local activity. The input args will be hand over directly
   163  // to local activity function without serialization/deserialization because we don't need to pass the input across process
   164  // boundary. However, the result will still go through serialization/deserialization because we need to record the result
   165  // as history to cadence server so if the workflow crashes, a different worker can replay the history without running
   166  // the local activity again.
   167  //
   168  // If the activity failed to complete then the future get error would indicate the failure, and it can be one of
   169  // CustomError, TimeoutError, CanceledError, PanicError, GenericError.
   170  // You can cancel the pending activity by cancel the context(workflow.WithCancel(ctx)) and that will fail the activity
   171  // with error CanceledError.
   172  //
   173  // ExecuteLocalActivity returns Future with local activity result or failure.
   174  func ExecuteLocalActivity(ctx Context, activity interface{}, args ...interface{}) Future {
   175  	return internal.ExecuteLocalActivity(ctx, activity, args...)
   176  }
   177  
   178  // ExecuteChildWorkflow requests child workflow execution in the context of a workflow.
   179  // Context can be used to pass the settings for the child workflow.
   180  // For example: task list that this child workflow should be routed, timeouts that need to be configured.
   181  // Use ChildWorkflowOptions to pass down the options.
   182  //
   183  //	 cwo := ChildWorkflowOptions{
   184  //		    ExecutionStartToCloseTimeout: 10 * time.Minute,
   185  //		    TaskStartToCloseTimeout: time.Minute,
   186  //		}
   187  //	 ctx := WithChildOptions(ctx, cwo)
   188  //
   189  // Input childWorkflow is either a workflow name or a workflow function that is getting scheduled.
   190  // Input args are the arguments that need to be passed to the child workflow function represented by childWorkflow.
   191  // If the child workflow failed to complete then the future get error would indicate the failure and it can be one of
   192  // CustomError, TimeoutError, CanceledError, GenericError.
   193  // You can cancel the pending child workflow using context(workflow.WithCancel(ctx)) and that will fail the workflow with
   194  // error CanceledError.
   195  // ExecuteChildWorkflow returns ChildWorkflowFuture.
   196  func ExecuteChildWorkflow(ctx Context, childWorkflow interface{}, args ...interface{}) ChildWorkflowFuture {
   197  	return internal.ExecuteChildWorkflow(ctx, childWorkflow, args...)
   198  }
   199  
   200  // GetInfo extracts info of a current workflow from a context.
   201  func GetInfo(ctx Context) *Info {
   202  	return internal.GetWorkflowInfo(ctx)
   203  }
   204  
   205  // GetLogger returns a logger to be used in workflow's context
   206  func GetLogger(ctx Context) *zap.Logger {
   207  	return internal.GetLogger(ctx)
   208  }
   209  
   210  // GetUnhandledSignalNames returns signal names that have  unconsumed signals.
   211  func GetUnhandledSignalNames(ctx Context) []string {
   212  	return internal.GetUnhandledSignalNames(ctx)
   213  }
   214  
   215  // GetMetricsScope returns a metrics scope to be used in workflow's context
   216  func GetMetricsScope(ctx Context) tally.Scope {
   217  	return internal.GetMetricsScope(ctx)
   218  }
   219  
   220  // GetTotalHistoryBytes returns the current history size of that workflow
   221  func GetTotalHistoryBytes(ctx Context) int64 {
   222  	return internal.GetTotalEstimatedHistoryBytes(ctx)
   223  }
   224  
   225  // GetHistoryCount returns the current number of history event of that workflow
   226  func GetHistoryCount(ctx Context) int64 {
   227  	return internal.GetHistoryCount(ctx)
   228  }
   229  
   230  // RequestCancelExternalWorkflow can be used to request cancellation of an external workflow.
   231  // Input workflowID is the workflow ID of target workflow.
   232  // Input runID indicates the instance of a workflow. Input runID is optional (default is ""). When runID is not specified,
   233  // then the currently running instance of that workflowID will be used.
   234  // By default, the current workflow's domain will be used as target domain. However, you can specify a different domain
   235  // of the target workflow using the context like:
   236  //
   237  //	ctx := WithWorkflowDomain(ctx, "domain-name")
   238  //
   239  // RequestCancelExternalWorkflow return Future with failure or empty success result.
   240  func RequestCancelExternalWorkflow(ctx Context, workflowID, runID string) Future {
   241  	return internal.RequestCancelExternalWorkflow(ctx, workflowID, runID)
   242  }
   243  
   244  // SignalExternalWorkflow can be used to send signal info to an external workflow.
   245  // Input workflowID is the workflow ID of target workflow.
   246  // Input runID indicates the instance of a workflow. Input runID is optional (default is ""). When runID is not specified,
   247  // then the currently running instance of that workflowID will be used.
   248  // By default, the current workflow's domain will be used as target domain. However, you can specify a different domain
   249  // of the target workflow using the context like:
   250  //
   251  //	ctx := WithWorkflowDomain(ctx, "domain-name")
   252  //
   253  // SignalExternalWorkflow return Future with failure or empty success result.
   254  func SignalExternalWorkflow(ctx Context, workflowID, runID, signalName string, arg interface{}) Future {
   255  	return internal.SignalExternalWorkflow(ctx, workflowID, runID, signalName, arg)
   256  }
   257  
   258  // GetSignalChannel returns channel corresponding to the signal name.
   259  func GetSignalChannel(ctx Context, signalName string) Channel {
   260  	return internal.GetSignalChannel(ctx, signalName)
   261  }
   262  
   263  // SideEffect executes the provided function once, records its result into the workflow history. The recorded result on
   264  // history will be returned without executing the provided function during replay. This guarantees the deterministic
   265  // requirement for workflow as the exact same result will be returned in replay.
   266  // Common use case is to run some short non-deterministic code in workflow, like getting random number or new UUID.
   267  // The only way to fail SideEffect is to panic which causes decision task failure. The decision task after timeout is
   268  // rescheduled and re-executed giving SideEffect another chance to succeed.
   269  //
   270  // Caution: do not use SideEffect to modify closures. Always retrieve result from SideEffect's encoded return value.
   271  // For example this code is BROKEN:
   272  //
   273  //	// Bad example:
   274  //	var random int
   275  //	workflow.SideEffect(func(ctx workflow.Context) interface{} {
   276  //	       random = rand.Intn(100)
   277  //	       return nil
   278  //	})
   279  //	// random will always be 0 in replay, thus this code is non-deterministic
   280  //	if random < 50 {
   281  //	       ....
   282  //	} else {
   283  //	       ....
   284  //	}
   285  //
   286  // On replay the provided function is not executed, the random will always be 0, and the workflow could takes a
   287  // different path breaking the determinism.
   288  //
   289  // Here is the correct way to use SideEffect:
   290  //
   291  //	// Good example:
   292  //	encodedRandom := SideEffect(func(ctx workflow.Context) interface{} {
   293  //	      return rand.Intn(100)
   294  //	})
   295  //	var random int
   296  //	encodedRandom.Get(&random)
   297  //	if random < 50 {
   298  //	       ....
   299  //	} else {
   300  //	       ....
   301  //	}
   302  func SideEffect(ctx Context, f func(ctx Context) interface{}) encoded.Value {
   303  	return internal.SideEffect(ctx, f)
   304  }
   305  
   306  // MutableSideEffect executes the provided function once, then it looks up the history for the value with the given id.
   307  // If there is no existing value, then it records the function result as a value with the given id on history;
   308  // otherwise, it compares whether the existing value from history has changed from the new function result by calling the
   309  // provided equals function. If they are equal, it returns the value without recording a new one in history;
   310  //
   311  //	otherwise, it records the new value with the same id on history.
   312  //
   313  // Caution: do not use MutableSideEffect to modify closures. Always retrieve result from MutableSideEffect's encoded
   314  // return value.
   315  //
   316  // The difference between MutableSideEffect() and SideEffect() is that every new SideEffect() call in non-replay will
   317  // result in a new marker being recorded on history. However, MutableSideEffect() only records a new marker if the value
   318  // changed. During replay, MutableSideEffect() will not execute the function again, but it will return the exact same
   319  // value as it was returning during the non-replay run.
   320  //
   321  // One good use case of MutableSideEffect() is to access dynamically changing config without breaking determinism.
   322  func MutableSideEffect(ctx Context, id string, f func(ctx Context) interface{}, equals func(a, b interface{}) bool) encoded.Value {
   323  	return internal.MutableSideEffect(ctx, id, f, equals)
   324  }
   325  
   326  // DefaultVersion is a version returned by GetVersion for code that wasn't versioned before
   327  const DefaultVersion Version = internal.DefaultVersion
   328  
   329  // GetVersion is used to safely perform backwards incompatible changes to workflow definitions.
   330  // It is not allowed to update workflow code while there are workflows running as it is going to break
   331  // determinism. The solution is to have both old code that is used to replay existing workflows
   332  // as well as the new one that is used when it is executed for the first time.
   333  // GetVersion returns maxSupported version when is executed for the first time. This version is recorded into the
   334  // workflow history as a marker event. Even if maxSupported version is changed the version that was recorded is
   335  // returned on replay. DefaultVersion constant contains version of code that wasn't versioned before.
   336  // For example initially workflow has the following code:
   337  //
   338  //	err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
   339  //
   340  // it should be updated to
   341  //
   342  //	err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
   343  //
   344  // The backwards compatible way to execute the update is
   345  //
   346  //	v :=  GetVersion(ctx, "fooChange", DefaultVersion, 1)
   347  //	if v  == DefaultVersion {
   348  //	    err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
   349  //	} else {
   350  //	    err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
   351  //	}
   352  //
   353  // Then bar has to be changed to baz:
   354  //
   355  //	v :=  GetVersion(ctx, "fooChange", DefaultVersion, 2)
   356  //	if v  == DefaultVersion {
   357  //	    err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
   358  //	} else if v == 1 {
   359  //	    err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
   360  //	} else {
   361  //	    err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
   362  //	}
   363  //
   364  // Later when there are no workflow executions running DefaultVersion the correspondent branch can be removed:
   365  //
   366  //	v :=  GetVersion(ctx, "fooChange", 1, 2)
   367  //	if v == 1 {
   368  //	    err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
   369  //	} else {
   370  //	    err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
   371  //	}
   372  //
   373  // It is recommended to keep the GetVersion() call even if single branch is left:
   374  //
   375  //	GetVersion(ctx, "fooChange", 2, 2)
   376  //	err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
   377  //
   378  // The reason to keep it is: 1) it ensures that if there is older version execution still running, it will fail here
   379  // and not proceed; 2) if you ever need to make more changes for “fooChange”, for example change activity from baz to qux,
   380  // you just need to update the maxVersion from 2 to 3.
   381  //
   382  // Note that, you only need to preserve the first call to GetVersion() for each changeID. All subsequent call to GetVersion()
   383  // with same changeID are safe to remove. However, if you really want to get rid of the first GetVersion() call as well,
   384  // you can do so, but you need to make sure: 1) all older version executions are completed; 2) you can no longer use “fooChange”
   385  // as changeID. If you ever need to make changes to that same part like change from baz to qux, you would need to use a
   386  // different changeID like “fooChange-fix2”, and start minVersion from DefaultVersion again. The code would looks like:
   387  //
   388  //	v := workflow.GetVersion(ctx, "fooChange-fix2", workflow.DefaultVersion, 1)
   389  //	if v == workflow.DefaultVersion {
   390  //	  err = workflow.ExecuteActivity(ctx, baz, data).Get(ctx, nil)
   391  //	} else {
   392  //	  err = workflow.ExecuteActivity(ctx, qux, data).Get(ctx, nil)
   393  //	}
   394  func GetVersion(ctx Context, changeID string, minSupported, maxSupported Version) Version {
   395  	return internal.GetVersion(ctx, changeID, minSupported, maxSupported)
   396  }
   397  
   398  // SetQueryHandler sets the query handler to handle workflow query. The queryType specify which query type this handler
   399  // should handle. The handler must be a function that returns 2 values. The first return value must be a serializable
   400  // result. The second return value must be an error. The handler function could receive any number of input parameters.
   401  // All the input parameter must be serializable. You should call workflow.SetQueryHandler() at the beginning of the workflow
   402  // code. When client calls Client.QueryWorkflow() to cadence server, a task will be generated on server that will be dispatched
   403  // to a workflow worker, which will replay the history events and then execute a query handler based on the query type.
   404  // The query handler will be invoked out of the context of the workflow, meaning that the handler code must not use workflow
   405  // context to do things like workflow.NewChannel(), workflow.Go() or to call any workflow blocking functions like
   406  // Channel.Get() or Future.Get(). Trying to do so in query handler code will fail the query and client will receive
   407  // QueryFailedError.
   408  // Example of workflow code that support query type "current_state":
   409  //
   410  //	func MyWorkflow(ctx workflow.Context, input string) error {
   411  //	  currentState := "started" // this could be any serializable struct
   412  //	  err := workflow.SetQueryHandler(ctx, "current_state", func() (string, error) {
   413  //	    return currentState, nil
   414  //	  })
   415  //	  if err != nil {
   416  //	    currentState = "failed to register query handler"
   417  //	    return err
   418  //	  }
   419  //	  // your normal workflow code begins here, and you update the currentState as the code makes progress.
   420  //	  currentState = "waiting timer"
   421  //	  err = NewTimer(ctx, time.Hour).Get(ctx, nil)
   422  //	  if err != nil {
   423  //	    currentState = "timer failed"
   424  //	    return err
   425  //	  }
   426  //
   427  //	  currentState = "waiting activity"
   428  //	  ctx = WithActivityOptions(ctx, myActivityOptions)
   429  //	  err = ExecuteActivity(ctx, MyActivity, "my_input").Get(ctx, nil)
   430  //	  if err != nil {
   431  //	    currentState = "activity failed"
   432  //	    return err
   433  //	  }
   434  //	  currentState = "done"
   435  //	  return nil
   436  //	}
   437  func SetQueryHandler(ctx Context, queryType string, handler interface{}) error {
   438  	return internal.SetQueryHandler(ctx, queryType, handler)
   439  }
   440  
   441  // IsReplaying returns whether the current workflow code is replaying.
   442  //
   443  // Warning! Never make decisions, like schedule activity/childWorkflow/timer or send/wait on future/channel, based on
   444  // this flag as it is going to break workflow determinism requirement.
   445  // The only reasonable use case for this flag is to avoid some external actions during replay, like custom logging or
   446  // metric reporting. Please note that Cadence already provide standard logging/metric via workflow.GetLogger(ctx) and
   447  // workflow.GetMetricsScope(ctx), and those standard mechanism are replay-aware and it will automatically suppress during
   448  // replay. Only use this flag if you need custom logging/metrics reporting, for example if you want to log to kafka.
   449  //
   450  // Warning! Any action protected by this flag should not fail or if it does fail should ignore that failure or panic
   451  // on the failure. If workflow don't want to be blocked on those failure, it should ignore those failure; if workflow do
   452  // want to make sure it proceed only when that action succeed then it should panic on that failure. Panic raised from a
   453  // workflow causes decision task to fail and cadence server will rescheduled later to retry.
   454  func IsReplaying(ctx Context) bool {
   455  	return internal.IsReplaying(ctx)
   456  }
   457  
   458  // HasLastCompletionResult checks if there is completion result from previous runs.
   459  // This is used in combination with cron schedule. A workflow can be started with an optional cron schedule.
   460  // If a cron workflow wants to pass some data to next schedule, it can return any data and that data will become
   461  // available when next run starts.
   462  // This HasLastCompletionResult() checks if there is such data available passing down from previous successful run.
   463  func HasLastCompletionResult(ctx Context) bool {
   464  	return internal.HasLastCompletionResult(ctx)
   465  }
   466  
   467  // GetLastCompletionResult extract last completion result from previous run for this cron workflow.
   468  // This is used in combination with cron schedule. A workflow can be started with an optional cron schedule.
   469  // If a cron workflow wants to pass some data to next schedule, it can return any data and that data will become
   470  // available when next run starts.
   471  // This GetLastCompletionResult() extract the data into expected data structure.
   472  // See TestWorkflowEnvironment.SetLastCompletionResult() for unit test support.
   473  func GetLastCompletionResult(ctx Context, d ...interface{}) error {
   474  	return internal.GetLastCompletionResult(ctx, d...)
   475  }
   476  
   477  // UpsertSearchAttributes is used to add or update workflow search attributes.
   478  // The search attributes can be used in query of List/Scan/Count workflow APIs.
   479  // The key and value type must be registered on cadence server side;
   480  // The value has to deterministic when replay;
   481  // The value has to be Json serializable.
   482  // UpsertSearchAttributes will merge attributes to existing map in workflow, for example workflow code:
   483  //
   484  //	  func MyWorkflow(ctx workflow.Context, input string) error {
   485  //		   attr1 := map[string]interface{}{
   486  //			   "CustomIntField": 1,
   487  //			   "CustomBoolField": true,
   488  //		   }
   489  //		   workflow.UpsertSearchAttributes(ctx, attr1)
   490  //
   491  //		   attr2 := map[string]interface{}{
   492  //			   "CustomIntField": 2,
   493  //			   "CustomKeywordField": "seattle",
   494  //		   }
   495  //		   workflow.UpsertSearchAttributes(ctx, attr2)
   496  //	  }
   497  //
   498  // will eventually have search attributes:
   499  //
   500  //	map[string]interface{}{
   501  //		"CustomIntField": 2,
   502  //		"CustomBoolField": true,
   503  //		"CustomKeywordField": "seattle",
   504  //	}
   505  //
   506  // This is only supported when using ElasticSearch.
   507  func UpsertSearchAttributes(ctx Context, attributes map[string]interface{}) error {
   508  	return internal.UpsertSearchAttributes(ctx, attributes)
   509  }