go.temporal.io/server@v1.23.0/common/persistence/visibility/store/sql/query_converter_postgresql.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 package sql 26 27 import ( 28 "fmt" 29 "strings" 30 31 "github.com/temporalio/sqlparser" 32 "go.temporal.io/server/common/namespace" 33 "go.temporal.io/server/common/persistence/sql/sqlplugin" 34 "go.temporal.io/server/common/persistence/visibility/store/query" 35 "go.temporal.io/server/common/searchattribute" 36 ) 37 38 type ( 39 pgCastExpr struct { 40 sqlparser.Expr 41 Value sqlparser.Expr 42 Type *sqlparser.ConvertType 43 } 44 45 pgQueryConverter struct{} 46 ) 47 48 const ( 49 jsonBuildArrayFuncName = "jsonb_build_array" 50 jsonContainsOp = "@>" 51 ftsMatchOp = "@@" 52 ) 53 54 var ( 55 convertTypeTSQuery = &sqlparser.ConvertType{Type: "tsquery"} 56 ) 57 58 var _ sqlparser.Expr = (*pgCastExpr)(nil) 59 var _ pluginQueryConverter = (*pgQueryConverter)(nil) 60 61 func (node *pgCastExpr) Format(buf *sqlparser.TrackedBuffer) { 62 buf.Myprintf("%v::%v", node.Value, node.Type) 63 } 64 65 func newPostgreSQLQueryConverter( 66 namespaceName namespace.Name, 67 namespaceID namespace.ID, 68 saTypeMap searchattribute.NameTypeMap, 69 saMapper searchattribute.Mapper, 70 queryString string, 71 ) *QueryConverter { 72 return newQueryConverterInternal( 73 &pgQueryConverter{}, 74 namespaceName, 75 namespaceID, 76 saTypeMap, 77 saMapper, 78 queryString, 79 ) 80 } 81 82 func (c *pgQueryConverter) getDatetimeFormat() string { 83 return "2006-01-02 15:04:05.999999" 84 } 85 86 func (c *pgQueryConverter) getCoalesceCloseTimeExpr() sqlparser.Expr { 87 return newFuncExpr( 88 coalesceFuncName, 89 closeTimeSaColName, 90 newUnsafeSQLString(maxDatetimeValue.Format(c.getDatetimeFormat())), 91 ) 92 } 93 94 func (c *pgQueryConverter) convertKeywordListComparisonExpr( 95 expr *sqlparser.ComparisonExpr, 96 ) (sqlparser.Expr, error) { 97 if !isSupportedKeywordListOperator(expr.Operator) { 98 return nil, query.NewConverterError( 99 "%s: operator '%s' not supported for KeywordList type search attribute in `%s`", 100 query.InvalidExpressionErrMessage, 101 expr.Operator, 102 formatComparisonExprStringForError(*expr), 103 ) 104 } 105 106 switch expr.Operator { 107 case sqlparser.EqualStr, sqlparser.NotEqualStr: 108 newExpr := c.newJsonContainsExpr(expr.Left, expr.Right) 109 if expr.Operator == sqlparser.NotEqualStr { 110 newExpr = &sqlparser.NotExpr{Expr: newExpr} 111 } 112 return newExpr, nil 113 case sqlparser.InStr, sqlparser.NotInStr: 114 valTupleExpr, isValTuple := expr.Right.(sqlparser.ValTuple) 115 if !isValTuple { 116 return nil, query.NewConverterError( 117 "%s: unexpected value type (expected tuple of strings, got %s)", 118 query.InvalidExpressionErrMessage, 119 sqlparser.String(expr.Right), 120 ) 121 } 122 var newExpr sqlparser.Expr = &sqlparser.ParenExpr{ 123 Expr: c.convertInExpr(expr.Left, valTupleExpr), 124 } 125 if expr.Operator == sqlparser.NotInStr { 126 newExpr = &sqlparser.NotExpr{Expr: newExpr} 127 } 128 return newExpr, nil 129 default: 130 // this should never happen since isSupportedKeywordListOperator should already fail 131 return nil, query.NewConverterError( 132 "%s: operator '%s' not supported for KeywordList type search attribute in `%s`", 133 query.InvalidExpressionErrMessage, 134 expr.Operator, 135 formatComparisonExprStringForError(*expr), 136 ) 137 } 138 } 139 140 func (c *pgQueryConverter) convertInExpr( 141 leftExpr sqlparser.Expr, 142 values sqlparser.ValTuple, 143 ) sqlparser.Expr { 144 exprs := make([]sqlparser.Expr, len(values)) 145 for i, value := range values { 146 exprs[i] = c.newJsonContainsExpr(leftExpr, value) 147 } 148 for len(exprs) > 1 { 149 k := 0 150 for i := 0; i < len(exprs); i += 2 { 151 if i+1 < len(exprs) { 152 exprs[k] = &sqlparser.OrExpr{ 153 Left: exprs[i], 154 Right: exprs[i+1], 155 } 156 } else { 157 exprs[k] = exprs[i] 158 } 159 k++ 160 } 161 exprs = exprs[:k] 162 } 163 return exprs[0] 164 } 165 166 func (c *pgQueryConverter) convertTextComparisonExpr( 167 expr *sqlparser.ComparisonExpr, 168 ) (sqlparser.Expr, error) { 169 if !isSupportedTextOperator(expr.Operator) { 170 return nil, query.NewConverterError( 171 "%s: operator '%s' not supported for Text type search attribute in `%s`", 172 query.InvalidExpressionErrMessage, 173 expr.Operator, 174 formatComparisonExprStringForError(*expr), 175 ) 176 } 177 valueExpr, ok := expr.Right.(*unsafeSQLString) 178 if !ok { 179 return nil, query.NewConverterError( 180 "%s: unexpected value type (expected string, got %s)", 181 query.InvalidExpressionErrMessage, 182 sqlparser.String(expr.Right), 183 ) 184 } 185 tokens := tokenizeTextQueryString(valueExpr.Val) 186 if len(tokens) == 0 { 187 return nil, query.NewConverterError( 188 "%s: unexpected value for Text type search attribute (no tokens found in %s)", 189 query.InvalidExpressionErrMessage, 190 sqlparser.String(expr.Right), 191 ) 192 } 193 valueExpr.Val = strings.Join(tokens, " | ") 194 var newExpr sqlparser.Expr = &sqlparser.ComparisonExpr{ 195 Operator: ftsMatchOp, 196 Left: expr.Left, 197 Right: &pgCastExpr{ 198 Value: expr.Right, 199 Type: convertTypeTSQuery, 200 }, 201 } 202 if expr.Operator == sqlparser.NotEqualStr { 203 newExpr = &sqlparser.NotExpr{Expr: newExpr} 204 } 205 return newExpr, nil 206 } 207 208 func (c *pgQueryConverter) newJsonContainsExpr( 209 jsonExpr sqlparser.Expr, 210 valueExpr sqlparser.Expr, 211 ) sqlparser.Expr { 212 return &sqlparser.ComparisonExpr{ 213 Operator: jsonContainsOp, 214 Left: jsonExpr, 215 Right: newFuncExpr(jsonBuildArrayFuncName, valueExpr), 216 } 217 } 218 219 func (c *pgQueryConverter) buildSelectStmt( 220 namespaceID namespace.ID, 221 queryString string, 222 pageSize int, 223 token *pageToken, 224 ) (string, []any) { 225 var whereClauses []string 226 var queryArgs []any 227 228 whereClauses = append( 229 whereClauses, 230 fmt.Sprintf("%s = ?", searchattribute.GetSqlDbColName(searchattribute.NamespaceID)), 231 ) 232 queryArgs = append(queryArgs, namespaceID.String()) 233 234 if len(queryString) > 0 { 235 whereClauses = append(whereClauses, queryString) 236 } 237 238 if token != nil { 239 whereClauses = append( 240 whereClauses, 241 fmt.Sprintf( 242 "((%s = ? AND %s = ? AND %s > ?) OR (%s = ? AND %s < ?) OR %s < ?)", 243 sqlparser.String(c.getCoalesceCloseTimeExpr()), 244 searchattribute.GetSqlDbColName(searchattribute.StartTime), 245 searchattribute.GetSqlDbColName(searchattribute.RunID), 246 sqlparser.String(c.getCoalesceCloseTimeExpr()), 247 searchattribute.GetSqlDbColName(searchattribute.StartTime), 248 sqlparser.String(c.getCoalesceCloseTimeExpr()), 249 ), 250 ) 251 queryArgs = append( 252 queryArgs, 253 token.CloseTime, 254 token.StartTime, 255 token.RunID, 256 token.CloseTime, 257 token.StartTime, 258 token.CloseTime, 259 ) 260 } 261 262 queryArgs = append(queryArgs, pageSize) 263 264 return fmt.Sprintf( 265 `SELECT %s 266 FROM executions_visibility 267 WHERE %s 268 ORDER BY %s DESC, %s DESC, %s 269 LIMIT ?`, 270 strings.Join(sqlplugin.DbFields, ", "), 271 strings.Join(whereClauses, " AND "), 272 sqlparser.String(c.getCoalesceCloseTimeExpr()), 273 searchattribute.GetSqlDbColName(searchattribute.StartTime), 274 searchattribute.GetSqlDbColName(searchattribute.RunID), 275 ), queryArgs 276 } 277 278 func (c *pgQueryConverter) buildCountStmt( 279 namespaceID namespace.ID, 280 queryString string, 281 groupBy []string, 282 ) (string, []any) { 283 var whereClauses []string 284 var queryArgs []any 285 286 whereClauses = append( 287 whereClauses, 288 fmt.Sprintf("(%s = ?)", searchattribute.GetSqlDbColName(searchattribute.NamespaceID)), 289 ) 290 queryArgs = append(queryArgs, namespaceID.String()) 291 292 if len(queryString) > 0 { 293 whereClauses = append(whereClauses, queryString) 294 } 295 296 groupByClause := "" 297 if len(groupBy) > 0 { 298 groupByClause = fmt.Sprintf("GROUP BY %s", strings.Join(groupBy, ", ")) 299 } 300 301 return fmt.Sprintf( 302 "SELECT %s FROM executions_visibility WHERE %s %s", 303 strings.Join(append(groupBy, "COUNT(*)"), ", "), 304 strings.Join(whereClauses, " AND "), 305 groupByClause, 306 ), queryArgs 307 }