go.temporal.io/server@v1.23.0/common/persistence/visibility/store/sql/query_converter_sqlite.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 33 "go.temporal.io/server/common/namespace" 34 "go.temporal.io/server/common/persistence/sql/sqlplugin" 35 "go.temporal.io/server/common/persistence/visibility/store/query" 36 "go.temporal.io/server/common/searchattribute" 37 ) 38 39 type ( 40 sqliteQueryConverter struct{} 41 ) 42 43 var _ pluginQueryConverter = (*sqliteQueryConverter)(nil) 44 45 const ( 46 keywordListTypeFtsTableName = "executions_visibility_fts_keyword_list" 47 textTypeFtsTableName = "executions_visibility_fts_text" 48 ) 49 50 func newSqliteQueryConverter( 51 namespaceName namespace.Name, 52 namespaceID namespace.ID, 53 saTypeMap searchattribute.NameTypeMap, 54 saMapper searchattribute.Mapper, 55 queryString string, 56 ) *QueryConverter { 57 return newQueryConverterInternal( 58 &sqliteQueryConverter{}, 59 namespaceName, 60 namespaceID, 61 saTypeMap, 62 saMapper, 63 queryString, 64 ) 65 } 66 67 func (c *sqliteQueryConverter) getDatetimeFormat() string { 68 return "2006-01-02 15:04:05.999999-07:00" 69 } 70 71 func (c *sqliteQueryConverter) getCoalesceCloseTimeExpr() sqlparser.Expr { 72 return newFuncExpr( 73 coalesceFuncName, 74 closeTimeSaColName, 75 newUnsafeSQLString(maxDatetimeValue.Format(c.getDatetimeFormat())), 76 ) 77 } 78 79 func (c *sqliteQueryConverter) convertKeywordListComparisonExpr( 80 expr *sqlparser.ComparisonExpr, 81 ) (sqlparser.Expr, error) { 82 if !isSupportedKeywordListOperator(expr.Operator) { 83 return nil, query.NewConverterError( 84 "%s: operator '%s' not supported for KeywordList type search attribute in `%s`", 85 query.InvalidExpressionErrMessage, 86 expr.Operator, 87 formatComparisonExprStringForError(*expr), 88 ) 89 } 90 91 saColNameExpr, isSAColNameExpr := expr.Left.(*saColName) 92 if !isSAColNameExpr { 93 return nil, query.NewConverterError( 94 "%s: must be a search attribute column name but was %T", 95 query.InvalidExpressionErrMessage, 96 expr.Left, 97 ) 98 } 99 100 var ftsQuery string 101 switch expr.Operator { 102 case sqlparser.EqualStr, sqlparser.NotEqualStr: 103 valueExpr, ok := expr.Right.(*unsafeSQLString) 104 if !ok { 105 return nil, query.NewConverterError( 106 "%s: unexpected value type (expected string, got %s)", 107 query.InvalidExpressionErrMessage, 108 sqlparser.String(expr.Right), 109 ) 110 } 111 ftsQuery = buildFtsQueryString(saColNameExpr.dbColName.Name, valueExpr.Val) 112 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 values, err := getUnsafeStringTupleValues(valTupleExpr) 123 if err != nil { 124 return nil, err 125 } 126 ftsQuery = buildFtsQueryString(saColNameExpr.dbColName.Name, values...) 127 128 default: 129 // this should never happen since isSupportedKeywordListOperator should already fail 130 return nil, query.NewConverterError( 131 "%s: operator '%s' not supported for KeywordList type search attribute in `%s`", 132 query.InvalidExpressionErrMessage, 133 expr.Operator, 134 formatComparisonExprStringForError(*expr), 135 ) 136 } 137 138 var oper string 139 switch expr.Operator { 140 case sqlparser.EqualStr, sqlparser.InStr: 141 oper = sqlparser.InStr 142 case sqlparser.NotEqualStr, sqlparser.NotInStr: 143 oper = sqlparser.NotInStr 144 default: 145 // this should never happen since isSupportedKeywordListOperator should already fail 146 return nil, query.NewConverterError( 147 "%s: operator '%s' not supported for KeywordList type search attribute in `%s`", 148 query.InvalidExpressionErrMessage, 149 expr.Operator, 150 formatComparisonExprStringForError(*expr), 151 ) 152 } 153 154 newExpr := sqlparser.ComparisonExpr{ 155 Operator: oper, 156 Left: newColName("rowid"), 157 Right: &sqlparser.Subquery{ 158 Select: c.buildFtsSelectStmt(keywordListTypeFtsTableName, ftsQuery), 159 }, 160 } 161 return &newExpr, nil 162 } 163 164 func (c *sqliteQueryConverter) convertTextComparisonExpr( 165 expr *sqlparser.ComparisonExpr, 166 ) (sqlparser.Expr, error) { 167 if !isSupportedTextOperator(expr.Operator) { 168 return nil, query.NewConverterError( 169 "%s: operator '%s' not supported for Text type search attribute in `%s`", 170 query.InvalidExpressionErrMessage, 171 expr.Operator, 172 formatComparisonExprStringForError(*expr), 173 ) 174 } 175 176 saColNameExpr, isSAColNameExpr := expr.Left.(*saColName) 177 if !isSAColNameExpr { 178 return nil, query.NewConverterError( 179 "%s: must be a search attribute column name but was %T", 180 query.InvalidExpressionErrMessage, 181 expr.Left, 182 ) 183 } 184 185 valueExpr, ok := expr.Right.(*unsafeSQLString) 186 if !ok { 187 return nil, query.NewConverterError( 188 "%s: unexpected value type (expected string, got %s)", 189 query.InvalidExpressionErrMessage, 190 sqlparser.String(expr.Right), 191 ) 192 } 193 tokens := tokenizeTextQueryString(valueExpr.Val) 194 if len(tokens) == 0 { 195 return nil, query.NewConverterError( 196 "%s: unexpected value for Text type search attribute (no tokens found in %s)", 197 query.InvalidExpressionErrMessage, 198 sqlparser.String(expr.Right), 199 ) 200 } 201 202 var oper string 203 switch expr.Operator { 204 case sqlparser.EqualStr: 205 oper = sqlparser.InStr 206 case sqlparser.NotEqualStr: 207 oper = sqlparser.NotInStr 208 default: 209 // this should never happen since isSupportedTextOperator should already fail 210 return nil, query.NewConverterError( 211 "%s: operator '%s' not supported for Text type search attribute in `%s`", 212 query.InvalidExpressionErrMessage, 213 expr.Operator, 214 formatComparisonExprStringForError(*expr), 215 ) 216 } 217 218 ftsQuery := buildFtsQueryString(saColNameExpr.dbColName.Name, tokens...) 219 newExpr := sqlparser.ComparisonExpr{ 220 Operator: oper, 221 Left: newColName("rowid"), 222 Right: &sqlparser.Subquery{ 223 Select: c.buildFtsSelectStmt(textTypeFtsTableName, ftsQuery), 224 }, 225 } 226 return &newExpr, nil 227 } 228 229 func (c *sqliteQueryConverter) buildSelectStmt( 230 namespaceID namespace.ID, 231 queryString string, 232 pageSize int, 233 token *pageToken, 234 ) (string, []any) { 235 var whereClauses []string 236 var queryArgs []any 237 238 whereClauses = append( 239 whereClauses, 240 fmt.Sprintf("%s = ?", searchattribute.GetSqlDbColName(searchattribute.NamespaceID)), 241 ) 242 queryArgs = append(queryArgs, namespaceID.String()) 243 244 if len(queryString) > 0 { 245 whereClauses = append(whereClauses, queryString) 246 } 247 248 if token != nil { 249 whereClauses = append( 250 whereClauses, 251 fmt.Sprintf( 252 "((%s = ? AND %s = ? AND %s > ?) OR (%s = ? AND %s < ?) OR %s < ?)", 253 sqlparser.String(c.getCoalesceCloseTimeExpr()), 254 searchattribute.GetSqlDbColName(searchattribute.StartTime), 255 searchattribute.GetSqlDbColName(searchattribute.RunID), 256 sqlparser.String(c.getCoalesceCloseTimeExpr()), 257 searchattribute.GetSqlDbColName(searchattribute.StartTime), 258 sqlparser.String(c.getCoalesceCloseTimeExpr()), 259 ), 260 ) 261 queryArgs = append( 262 queryArgs, 263 token.CloseTime, 264 token.StartTime, 265 token.RunID, 266 token.CloseTime, 267 token.StartTime, 268 token.CloseTime, 269 ) 270 } 271 272 queryArgs = append(queryArgs, pageSize) 273 274 return fmt.Sprintf( 275 `SELECT %s 276 FROM executions_visibility 277 WHERE %s 278 ORDER BY %s DESC, %s DESC, %s 279 LIMIT ?`, 280 strings.Join(sqlplugin.DbFields, ", "), 281 strings.Join(whereClauses, " AND "), 282 sqlparser.String(c.getCoalesceCloseTimeExpr()), 283 searchattribute.GetSqlDbColName(searchattribute.StartTime), 284 searchattribute.GetSqlDbColName(searchattribute.RunID), 285 ), queryArgs 286 } 287 288 // buildFtsSelectStmt builds the following statement for querying FTS: 289 // 290 // SELECT rowid FROM tableName WHERE tableName = '%s' 291 func (c *sqliteQueryConverter) buildFtsSelectStmt( 292 tableName string, 293 queryString string, 294 ) sqlparser.SelectStatement { 295 return &sqlparser.Select{ 296 SelectExprs: sqlparser.SelectExprs{ 297 &sqlparser.AliasedExpr{ 298 Expr: newColName("rowid"), 299 }, 300 }, 301 From: sqlparser.TableExprs{ 302 &sqlparser.AliasedTableExpr{ 303 Expr: &sqlparser.TableName{ 304 Name: sqlparser.NewTableIdent(tableName), 305 }, 306 }, 307 }, 308 Where: sqlparser.NewWhere( 309 sqlparser.WhereStr, 310 &sqlparser.ComparisonExpr{ 311 Operator: sqlparser.EqualStr, 312 Left: newColName(tableName), 313 Right: newUnsafeSQLString(queryString), 314 }, 315 ), 316 } 317 } 318 319 func (c *sqliteQueryConverter) buildCountStmt( 320 namespaceID namespace.ID, 321 queryString string, 322 groupBy []string, 323 ) (string, []any) { 324 var whereClauses []string 325 var queryArgs []any 326 327 whereClauses = append( 328 whereClauses, 329 fmt.Sprintf("(%s = ?)", searchattribute.GetSqlDbColName(searchattribute.NamespaceID)), 330 ) 331 queryArgs = append(queryArgs, namespaceID.String()) 332 333 if len(queryString) > 0 { 334 whereClauses = append(whereClauses, queryString) 335 } 336 337 groupByClause := "" 338 if len(groupBy) > 0 { 339 groupByClause = fmt.Sprintf("GROUP BY %s", strings.Join(groupBy, ", ")) 340 } 341 342 return fmt.Sprintf( 343 "SELECT %s FROM executions_visibility WHERE %s %s", 344 strings.Join(append(groupBy, "COUNT(*)"), ", "), 345 strings.Join(whereClauses, " AND "), 346 groupByClause, 347 ), queryArgs 348 } 349 350 func buildFtsQueryString(colname string, values ...string) string { 351 // FTS query format: 'colname : ("token1" OR "token2" OR ...)' 352 return fmt.Sprintf(`%s : ("%s")`, colname, strings.Join(values, `" OR "`)) 353 }