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 }