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  }