go.temporal.io/server@v1.23.0/common/persistence/sql/sqlplugin/sqlite/visibility.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2021 Datadog, Inc.
     4  //
     5  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     6  //
     7  // Copyright (c) 2020 Uber Technologies, Inc.
     8  //
     9  // Permission is hereby granted, free of charge, to any person obtaining a copy
    10  // of this software and associated documentation files (the "Software"), to deal
    11  // in the Software without restriction, including without limitation the rights
    12  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    13  // copies of the Software, and to permit persons to whom the Software is
    14  // furnished to do so, subject to the following conditions:
    15  //
    16  // The above copyright notice and this permission notice shall be included in
    17  // all copies or substantial portions of the Software.
    18  //
    19  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    20  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    21  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    22  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    23  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    24  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    25  // THE SOFTWARE.
    26  
    27  package sqlite
    28  
    29  import (
    30  	"context"
    31  	"database/sql"
    32  	"fmt"
    33  	"strings"
    34  
    35  	"go.temporal.io/server/common/persistence/sql/sqlplugin"
    36  )
    37  
    38  var (
    39  	keywordListSeparator = "♡"
    40  
    41  	templateInsertWorkflowExecution = fmt.Sprintf(
    42  		`INSERT INTO executions_visibility (%s)
    43  		VALUES (%s)
    44  		ON CONFLICT (namespace_id, run_id) DO NOTHING`,
    45  		strings.Join(sqlplugin.DbFields, ", "),
    46  		sqlplugin.BuildNamedPlaceholder(sqlplugin.DbFields...),
    47  	)
    48  
    49  	templateUpsertWorkflowExecution = fmt.Sprintf(
    50  		`INSERT INTO executions_visibility (%s)
    51  		VALUES (%s)
    52  		%s`,
    53  		strings.Join(sqlplugin.DbFields, ", "),
    54  		sqlplugin.BuildNamedPlaceholder(sqlplugin.DbFields...),
    55  		buildOnDuplicateKeyUpdate(sqlplugin.DbFields...),
    56  	)
    57  
    58  	templateDeleteWorkflowExecution = `
    59  		DELETE FROM executions_visibility
    60  		WHERE namespace_id = :namespace_id AND run_id = :run_id`
    61  
    62  	templateGetWorkflowExecution = fmt.Sprintf(
    63  		`SELECT %s FROM executions_visibility
    64  		WHERE namespace_id = :namespace_id AND run_id = :run_id`,
    65  		strings.Join(sqlplugin.DbFields, ", "),
    66  	)
    67  )
    68  
    69  func buildOnDuplicateKeyUpdate(fields ...string) string {
    70  	items := make([]string, len(fields))
    71  	for i, field := range fields {
    72  		items[i] = fmt.Sprintf("%s = excluded.%s", field, field)
    73  	}
    74  	return fmt.Sprintf(
    75  		"ON CONFLICT (namespace_id, run_id) DO UPDATE SET %s",
    76  		strings.Join(items, ", "),
    77  	)
    78  }
    79  
    80  // InsertIntoVisibility inserts a row into visibility table. If an row already exist,
    81  // its left as such and no update will be made
    82  func (mdb *db) InsertIntoVisibility(
    83  	ctx context.Context,
    84  	row *sqlplugin.VisibilityRow,
    85  ) (sql.Result, error) {
    86  	finalRow := mdb.prepareRowForDB(row)
    87  	return mdb.conn.NamedExecContext(ctx, templateInsertWorkflowExecution, finalRow)
    88  }
    89  
    90  // ReplaceIntoVisibility replaces an existing row if it exist or creates a new row in visibility table
    91  func (mdb *db) ReplaceIntoVisibility(
    92  	ctx context.Context,
    93  	row *sqlplugin.VisibilityRow,
    94  ) (sql.Result, error) {
    95  	finalRow := mdb.prepareRowForDB(row)
    96  	return mdb.conn.NamedExecContext(ctx, templateUpsertWorkflowExecution, finalRow)
    97  }
    98  
    99  // DeleteFromVisibility deletes a row from visibility table if it exist
   100  func (mdb *db) DeleteFromVisibility(
   101  	ctx context.Context,
   102  	filter sqlplugin.VisibilityDeleteFilter,
   103  ) (sql.Result, error) {
   104  	return mdb.conn.NamedExecContext(ctx, templateDeleteWorkflowExecution, filter)
   105  }
   106  
   107  // SelectFromVisibility reads one or more rows from visibility table
   108  func (mdb *db) SelectFromVisibility(
   109  	ctx context.Context,
   110  	filter sqlplugin.VisibilitySelectFilter,
   111  ) ([]sqlplugin.VisibilityRow, error) {
   112  	if len(filter.Query) == 0 {
   113  		// backward compatibility for existing tests
   114  		err := sqlplugin.GenerateSelectQuery(&filter, mdb.converter.ToSQLiteDateTime)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  	}
   119  
   120  	var rows []sqlplugin.VisibilityRow
   121  	err := mdb.conn.SelectContext(ctx, &rows, filter.Query, filter.QueryArgs...)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	for i := range rows {
   126  		err = mdb.processRowFromDB(&rows[i])
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  	}
   131  	return rows, nil
   132  }
   133  
   134  // GetFromVisibility reads one row from visibility table
   135  func (mdb *db) GetFromVisibility(
   136  	ctx context.Context,
   137  	filter sqlplugin.VisibilityGetFilter,
   138  ) (*sqlplugin.VisibilityRow, error) {
   139  	var row sqlplugin.VisibilityRow
   140  	stmt, err := mdb.conn.PrepareNamedContext(ctx, templateGetWorkflowExecution)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	err = stmt.GetContext(ctx, &row, filter)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	err = mdb.processRowFromDB(&row)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	return &row, nil
   153  }
   154  
   155  func (mdb *db) CountFromVisibility(
   156  	ctx context.Context,
   157  	filter sqlplugin.VisibilitySelectFilter,
   158  ) (int64, error) {
   159  	var count int64
   160  	err := mdb.conn.GetContext(ctx, &count, filter.Query, filter.QueryArgs...)
   161  	if err != nil {
   162  		return 0, err
   163  	}
   164  	return count, nil
   165  }
   166  
   167  func (mdb *db) CountGroupByFromVisibility(
   168  	ctx context.Context,
   169  	filter sqlplugin.VisibilitySelectFilter,
   170  ) ([]sqlplugin.VisibilityCountRow, error) {
   171  	rows, err := mdb.db.QueryContext(ctx, filter.Query, filter.QueryArgs...)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	defer rows.Close()
   176  	return sqlplugin.ParseCountGroupByRows(rows, filter.GroupBy)
   177  }
   178  
   179  func (mdb *db) prepareRowForDB(row *sqlplugin.VisibilityRow) *sqlplugin.VisibilityRow {
   180  	if row == nil {
   181  		return nil
   182  	}
   183  	finalRow := *row
   184  	finalRow.StartTime = mdb.converter.ToSQLiteDateTime(finalRow.StartTime)
   185  	finalRow.ExecutionTime = mdb.converter.ToSQLiteDateTime(finalRow.ExecutionTime)
   186  	if finalRow.CloseTime != nil {
   187  		*finalRow.CloseTime = mdb.converter.ToSQLiteDateTime(*finalRow.CloseTime)
   188  	}
   189  	if finalRow.SearchAttributes != nil {
   190  		finalSearchAttributes := sqlplugin.VisibilitySearchAttributes{}
   191  		for name, value := range *finalRow.SearchAttributes {
   192  			switch v := value.(type) {
   193  			case []string:
   194  				finalSearchAttributes[name] = strings.Join(v, keywordListSeparator)
   195  			default:
   196  				finalSearchAttributes[name] = v
   197  			}
   198  		}
   199  		finalRow.SearchAttributes = &finalSearchAttributes
   200  	}
   201  	return &finalRow
   202  }
   203  
   204  func (mdb *db) processRowFromDB(row *sqlplugin.VisibilityRow) error {
   205  	if row == nil {
   206  		return nil
   207  	}
   208  	row.StartTime = mdb.converter.FromSQLiteDateTime(row.StartTime)
   209  	row.ExecutionTime = mdb.converter.FromSQLiteDateTime(row.ExecutionTime)
   210  	if row.CloseTime != nil {
   211  		closeTime := mdb.converter.FromSQLiteDateTime(*row.CloseTime)
   212  		row.CloseTime = &closeTime
   213  	}
   214  	if row.SearchAttributes != nil {
   215  		for saName, saValue := range *row.SearchAttributes {
   216  			switch typedSaValue := saValue.(type) {
   217  			case string:
   218  				if strings.Index(typedSaValue, keywordListSeparator) >= 0 {
   219  					// If the string contains the keywordListSeparator, then we need to split it
   220  					// into a list of keywords.
   221  					(*row.SearchAttributes)[saName] = strings.Split(typedSaValue, keywordListSeparator)
   222  				}
   223  			default:
   224  				// no-op
   225  			}
   226  		}
   227  	}
   228  	return nil
   229  }