Accept WorkflowType in S3 visibility queries (#11383)

## What changed
- Allow the S3 visibility archiver query parser to accept
`WorkflowType`.
- Keep `WorkflowTypeName` as a deprecated compatibility alias.
- Rename the parsed query field to `workflowType` and update S3
visibility archiver tests.
- Fix the S3 parser StartTime test assertion and StartTime operator
error message.

## Why
- S3 visibility archiver queries only accepted `WorkflowTypeName`, while
filestore, gcloud, and non-archived visibility records use
`WorkflowType`. This keeps old queries working while accepting the
standard field name.
- Fix https://github.com/temporalio/temporal/issues/7821
This commit is contained in:
Yichao Yang
2026-07-31 19:02:49 -07:00
committed by GitHub
parent 91fb96ad3c
commit beb31c3569
5 changed files with 47 additions and 29 deletions

View File

@@ -37,12 +37,13 @@ The syntax for the query is based on SQL
Supported column names are
- WorkflowId *String*
- WorkflowTypeName *String*
- WorkflowType *String*
- WorkflowTypeName *String (legacy alias)*
- StartTime *Date*
- CloseTime *Date*
- SearchPrecision *String - Day, Hour, Minute, Second*
WorkflowId or WorkflowTypeName is required. If filtering on date use StartTime or CloseTime in combination with SearchPrecision.
WorkflowId or WorkflowType is required. WorkflowTypeName is still accepted as a legacy alias. If filtering on date use StartTime or CloseTime in combination with SearchPrecision.
Searching for a record will be done in times in the UTC timezone

View File

@@ -20,17 +20,19 @@ type (
queryParser struct{}
parsedQuery struct {
workflowTypeName *string
workflowID *string
startTime *time.Time
closeTime *time.Time
searchPrecision *string
workflowType *string
workflowID *string
startTime *time.Time
closeTime *time.Time
searchPrecision *string
}
)
// All allowed fields for filtering
const (
// Deprecated: use WorkflowType instead. This filter name is kept for backward compatibility.
WorkflowTypeName = "WorkflowTypeName"
WorkflowType = "WorkflowType"
WorkflowID = "WorkflowId"
StartTime = "StartTime"
CloseTime = "CloseTime"
@@ -60,11 +62,11 @@ func (p *queryParser) Parse(query string) (*parsedQuery, error) {
if err := p.convertWhereExpr(whereExpr, parsedQuery); err != nil {
return nil, err
}
if parsedQuery.workflowID == nil && parsedQuery.workflowTypeName == nil {
return nil, errors.New("WorkflowId or WorkflowTypeName is required in query")
if parsedQuery.workflowID == nil && parsedQuery.workflowType == nil {
return nil, errors.New("WorkflowId or WorkflowType is required in query")
}
if parsedQuery.workflowID != nil && parsedQuery.workflowTypeName != nil {
return nil, errors.New("only one of WorkflowId or WorkflowTypeName can be specified in a query")
if parsedQuery.workflowID != nil && parsedQuery.workflowType != nil {
return nil, errors.New("only one of WorkflowId or WorkflowType can be specified in a query")
}
if parsedQuery.closeTime != nil && parsedQuery.startTime != nil {
return nil, errors.New("only one of StartTime or CloseTime can be specified in a query")
@@ -121,18 +123,18 @@ func (p *queryParser) convertComparisonExpr(compExpr *sqlparser.ComparisonExpr,
valStr := sqlparser.String(valExpr)
switch colNameStr {
case WorkflowTypeName:
case WorkflowTypeName, WorkflowType:
val, err := sqlquery.ExtractStringValue(valStr)
if err != nil {
return err
}
if op != "=" {
return fmt.Errorf("only operation = is support for %s", WorkflowTypeName)
return fmt.Errorf("only operation = is support for %s", colNameStr)
}
if parsedQuery.workflowTypeName != nil {
return fmt.Errorf("can not query %s multiple times", WorkflowTypeName)
if parsedQuery.workflowType != nil {
return fmt.Errorf("can not query %s multiple times", colNameStr)
}
parsedQuery.workflowTypeName = new(val)
parsedQuery.workflowType = new(val)
case WorkflowID:
val, err := sqlquery.ExtractStringValue(valStr)
if err != nil {
@@ -160,7 +162,7 @@ func (p *queryParser) convertComparisonExpr(compExpr *sqlparser.ComparisonExpr,
return err
}
if op != "=" {
return fmt.Errorf("only operation = is support for %s", CloseTime)
return fmt.Errorf("only operation = is support for %s", StartTime)
}
parsedQuery.startTime = &timestamp
case SearchPrecision:

View File

@@ -41,13 +41,28 @@ func (s *queryParserSuite) TestParseWorkflowIDAndWorkflowTypeName() {
query: "WorkflowTypeName = \"random workflowTypeName\"",
expectErr: false,
parsedQuery: &parsedQuery{
workflowTypeName: new("random workflowTypeName"),
workflowType: new("random workflowTypeName"),
},
},
{
query: "WorkflowType = \"random workflowType\"",
expectErr: false,
parsedQuery: &parsedQuery{
workflowType: new("random workflowType"),
},
},
{
query: "WorkflowId = \"random workflowID\" and WorkflowTypeName = \"random workflowTypeName\"",
expectErr: true,
},
{
query: "WorkflowId = \"random workflowID\" and WorkflowType = \"random workflowTypeName\"",
expectErr: true,
},
{
query: "WorkflowTypeName = \"random workflowTypeName\" and WorkflowType = \"random workflowTypeName\"",
expectErr: true,
},
{
query: "WorkflowId = \"random workflowID\" and WorkflowId = \"random workflowID\"",
expectErr: true,
@@ -100,7 +115,7 @@ func (s *queryParserSuite) TestParseWorkflowIDAndWorkflowTypeName() {
}
s.NoError(err)
s.Equal(tc.parsedQuery.workflowID, parsedQuery.workflowID)
s.Equal(tc.parsedQuery.workflowTypeName, parsedQuery.workflowTypeName)
s.Equal(tc.parsedQuery.workflowType, parsedQuery.workflowType)
}
}
@@ -217,7 +232,7 @@ func (s *queryParserSuite) TestParseStartTime() {
query: commonQueryPart + "StartTime = 1000",
expectErr: false,
parsedQuery: &parsedQuery{
startTime: new(time.Unix(0, 1000)),
startTime: new(time.Unix(0, 1000).UTC()),
},
},
{
@@ -244,6 +259,6 @@ func (s *queryParserSuite) TestParseStartTime() {
continue
}
s.NoError(err)
s.Equal(tc.parsedQuery.closeTime, parsedQuery.closeTime)
s.Equal(tc.parsedQuery.startTime, parsedQuery.startTime)
}
}

View File

@@ -259,7 +259,7 @@ func (v *visibilityArchiver) query(
saTypeMap searchattribute.NameTypeMap,
) (*archiver.QueryVisibilityResponse, error) {
primaryIndex := primaryIndexKeyWorkflowTypeName
primaryIndexValue := request.parsedQuery.workflowTypeName
primaryIndexValue := request.parsedQuery.workflowType
if request.parsedQuery.workflowID != nil {
primaryIndex = primaryIndexKeyWorkflowID
primaryIndexValue = request.parsedQuery.workflowID

View File

@@ -533,9 +533,9 @@ func (s *visibilityArchiverSuite) TestArchiveAndQueryPrecisions() {
mockParser = NewMockQueryParser(s.controller)
mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
closeTime: new(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
searchPrecision: new(testData.precision),
workflowTypeName: new(testWorkflowTypeName),
closeTime: new(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
searchPrecision: new(testData.precision),
workflowType: new(testWorkflowTypeName),
}, nil).AnyTimes()
visibilityArchiver.queryParser = mockParser
@@ -546,9 +546,9 @@ func (s *visibilityArchiverSuite) TestArchiveAndQueryPrecisions() {
mockParser = NewMockQueryParser(s.controller)
mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
startTime: new(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
searchPrecision: new(testData.precision),
workflowTypeName: new(testWorkflowTypeName),
startTime: new(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
searchPrecision: new(testData.precision),
workflowType: new(testWorkflowTypeName),
}, nil).AnyTimes()
visibilityArchiver.queryParser = mockParser
@@ -601,7 +601,7 @@ func (s *visibilityArchiverSuite) TestArchiveAndQuery() {
mockParser = NewMockQueryParser(s.controller)
mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
workflowTypeName: new(testWorkflowTypeName),
workflowType: new(testWorkflowTypeName),
}, nil).AnyTimes()
visibilityArchiver.queryParser = mockParser
request = &archiver.QueryVisibilityRequest{