go.temporal.io/server@v1.23.0/common/archiver/s3store/query_parser.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  //go:generate mockgen -copyright_file ../../../LICENSE -package $GOPACKAGE -source query_parser.go -destination query_parser_mock.go -mock_names Interface=MockQueryParser
    26  
    27  package s3store
    28  
    29  import (
    30  	"errors"
    31  	"fmt"
    32  	"strconv"
    33  	"time"
    34  
    35  	"github.com/temporalio/sqlparser"
    36  
    37  	"go.temporal.io/server/common/convert"
    38  	"go.temporal.io/server/common/primitives/timestamp"
    39  )
    40  
    41  type (
    42  	// QueryParser parses a limited SQL where clause into a struct
    43  	QueryParser interface {
    44  		Parse(query string) (*parsedQuery, error)
    45  	}
    46  
    47  	queryParser struct{}
    48  
    49  	parsedQuery struct {
    50  		workflowTypeName *string
    51  		workflowID       *string
    52  		startTime        *time.Time
    53  		closeTime        *time.Time
    54  		searchPrecision  *string
    55  	}
    56  )
    57  
    58  // All allowed fields for filtering
    59  const (
    60  	WorkflowTypeName = "WorkflowTypeName"
    61  	WorkflowID       = "WorkflowId"
    62  	StartTime        = "StartTime"
    63  	CloseTime        = "CloseTime"
    64  	SearchPrecision  = "SearchPrecision"
    65  )
    66  
    67  // Precision specific values
    68  const (
    69  	PrecisionDay    = "Day"
    70  	PrecisionHour   = "Hour"
    71  	PrecisionMinute = "Minute"
    72  	PrecisionSecond = "Second"
    73  )
    74  const (
    75  	queryTemplate         = "select * from dummy where %s"
    76  	defaultDateTimeFormat = time.RFC3339
    77  )
    78  
    79  // NewQueryParser creates a new query parser for filestore
    80  func NewQueryParser() QueryParser {
    81  	return &queryParser{}
    82  }
    83  
    84  func (p *queryParser) Parse(query string) (*parsedQuery, error) {
    85  	stmt, err := sqlparser.Parse(fmt.Sprintf(queryTemplate, query))
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	whereExpr := stmt.(*sqlparser.Select).Where.Expr
    90  	parsedQuery := &parsedQuery{}
    91  	if err := p.convertWhereExpr(whereExpr, parsedQuery); err != nil {
    92  		return nil, err
    93  	}
    94  	if parsedQuery.workflowID == nil && parsedQuery.workflowTypeName == nil {
    95  		return nil, errors.New("WorkflowId or WorkflowTypeName is required in query")
    96  	}
    97  	if parsedQuery.workflowID != nil && parsedQuery.workflowTypeName != nil {
    98  		return nil, errors.New("only one of WorkflowId or WorkflowTypeName can be specified in a query")
    99  	}
   100  	if parsedQuery.closeTime != nil && parsedQuery.startTime != nil {
   101  		return nil, errors.New("only one of StartTime or CloseTime can be specified in a query")
   102  	}
   103  	if (parsedQuery.closeTime != nil || parsedQuery.startTime != nil) && parsedQuery.searchPrecision == nil {
   104  		return nil, errors.New("SearchPrecision is required when searching for a StartTime or CloseTime")
   105  	}
   106  
   107  	if parsedQuery.closeTime == nil && parsedQuery.startTime == nil && parsedQuery.searchPrecision != nil {
   108  		return nil, errors.New("SearchPrecision requires a StartTime or CloseTime")
   109  	}
   110  	return parsedQuery, nil
   111  }
   112  
   113  func (p *queryParser) convertWhereExpr(expr sqlparser.Expr, parsedQuery *parsedQuery) error {
   114  	if expr == nil {
   115  		return errors.New("where expression is nil")
   116  	}
   117  
   118  	switch expr := expr.(type) {
   119  	case *sqlparser.ComparisonExpr:
   120  		return p.convertComparisonExpr(expr, parsedQuery)
   121  	case *sqlparser.AndExpr:
   122  		return p.convertAndExpr(expr, parsedQuery)
   123  	case *sqlparser.ParenExpr:
   124  		return p.convertParenExpr(expr, parsedQuery)
   125  	default:
   126  		return errors.New("only comparison and \"and\" expression is supported")
   127  	}
   128  }
   129  
   130  func (p *queryParser) convertParenExpr(parenExpr *sqlparser.ParenExpr, parsedQuery *parsedQuery) error {
   131  	return p.convertWhereExpr(parenExpr.Expr, parsedQuery)
   132  }
   133  
   134  func (p *queryParser) convertAndExpr(andExpr *sqlparser.AndExpr, parsedQuery *parsedQuery) error {
   135  	if err := p.convertWhereExpr(andExpr.Left, parsedQuery); err != nil {
   136  		return err
   137  	}
   138  	return p.convertWhereExpr(andExpr.Right, parsedQuery)
   139  }
   140  
   141  func (p *queryParser) convertComparisonExpr(compExpr *sqlparser.ComparisonExpr, parsedQuery *parsedQuery) error {
   142  	colName, ok := compExpr.Left.(*sqlparser.ColName)
   143  	if !ok {
   144  		return fmt.Errorf("invalid filter name: %s", sqlparser.String(compExpr.Left))
   145  	}
   146  	colNameStr := sqlparser.String(colName)
   147  	op := compExpr.Operator
   148  	valExpr, ok := compExpr.Right.(*sqlparser.SQLVal)
   149  	if !ok {
   150  		return fmt.Errorf("invalid value: %s", sqlparser.String(compExpr.Right))
   151  	}
   152  	valStr := sqlparser.String(valExpr)
   153  
   154  	switch colNameStr {
   155  	case WorkflowTypeName:
   156  		val, err := extractStringValue(valStr)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		if op != "=" {
   161  			return fmt.Errorf("only operation = is support for %s", WorkflowTypeName)
   162  		}
   163  		if parsedQuery.workflowTypeName != nil {
   164  			return fmt.Errorf("can not query %s multiple times", WorkflowTypeName)
   165  		}
   166  		parsedQuery.workflowTypeName = convert.StringPtr(val)
   167  	case WorkflowID:
   168  		val, err := extractStringValue(valStr)
   169  		if err != nil {
   170  			return err
   171  		}
   172  		if op != "=" {
   173  			return fmt.Errorf("only operation = is support for %s", WorkflowID)
   174  		}
   175  		if parsedQuery.workflowID != nil {
   176  			return fmt.Errorf("can not query %s multiple times", WorkflowID)
   177  		}
   178  		parsedQuery.workflowID = convert.StringPtr(val)
   179  	case CloseTime:
   180  		timestamp, err := convertToTime(valStr)
   181  		if err != nil {
   182  			return err
   183  		}
   184  		if op != "=" {
   185  			return fmt.Errorf("only operation = is support for %s", CloseTime)
   186  		}
   187  		parsedQuery.closeTime = &timestamp
   188  	case StartTime:
   189  		timestamp, err := convertToTime(valStr)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		if op != "=" {
   194  			return fmt.Errorf("only operation = is support for %s", CloseTime)
   195  		}
   196  		parsedQuery.startTime = &timestamp
   197  	case SearchPrecision:
   198  		val, err := extractStringValue(valStr)
   199  		if err != nil {
   200  			return err
   201  		}
   202  		if op != "=" {
   203  			return fmt.Errorf("only operation = is support for %s", SearchPrecision)
   204  		}
   205  		if parsedQuery.searchPrecision != nil && *parsedQuery.searchPrecision != val {
   206  			return fmt.Errorf("only one expression is allowed for %s", SearchPrecision)
   207  		}
   208  		switch val {
   209  		case PrecisionDay:
   210  		case PrecisionHour:
   211  		case PrecisionMinute:
   212  		case PrecisionSecond:
   213  		default:
   214  			return fmt.Errorf("invalid value for %s: %s", SearchPrecision, val)
   215  		}
   216  		parsedQuery.searchPrecision = convert.StringPtr(val)
   217  
   218  	default:
   219  		return fmt.Errorf("unknown filter name: %s", colNameStr)
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func convertToTime(timeStr string) (time.Time, error) {
   226  	ts, err := strconv.ParseInt(timeStr, 10, 64)
   227  	if err == nil {
   228  		return timestamp.UnixOrZeroTime(ts), nil
   229  	}
   230  	timestampStr, err := extractStringValue(timeStr)
   231  	if err != nil {
   232  		return time.Time{}, err
   233  	}
   234  	parsedTime, err := time.Parse(defaultDateTimeFormat, timestampStr)
   235  	if err != nil {
   236  		return time.Time{}, err
   237  	}
   238  	return parsedTime, nil
   239  }
   240  
   241  func extractStringValue(s string) (string, error) {
   242  	if len(s) >= 2 && s[0] == '\'' && s[len(s)-1] == '\'' {
   243  		return s[1 : len(s)-1], nil
   244  	}
   245  	return "", fmt.Errorf("value %s is not a string value", s)
   246  }