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