go.temporal.io/server@v1.23.0/common/persistence/sql/sqlplugin/mysql/visibility_v8.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 mysql
    26  
    27  import (
    28  	"context"
    29  	"database/sql"
    30  	"errors"
    31  	"fmt"
    32  	"strings"
    33  
    34  	"go.temporal.io/server/common/persistence/sql/sqlplugin"
    35  )
    36  
    37  var (
    38  	templateInsertWorkflowExecution = fmt.Sprintf(
    39  		`INSERT INTO executions_visibility (%s)
    40  		VALUES (%s)
    41  		ON DUPLICATE KEY UPDATE run_id = VALUES(run_id)`,
    42  		strings.Join(sqlplugin.DbFields, ", "),
    43  		sqlplugin.BuildNamedPlaceholder(sqlplugin.DbFields...),
    44  	)
    45  
    46  	templateInsertCustomSearchAttributes = `
    47  		INSERT INTO custom_search_attributes (
    48  			namespace_id, run_id, search_attributes
    49  		) VALUES (:namespace_id, :run_id, :search_attributes)
    50  		ON DUPLICATE KEY UPDATE run_id = VALUES(run_id)`
    51  
    52  	templateUpsertWorkflowExecution = fmt.Sprintf(
    53  		`INSERT INTO executions_visibility (%s)
    54  		VALUES (%s)
    55  		%s`,
    56  		strings.Join(sqlplugin.DbFields, ", "),
    57  		sqlplugin.BuildNamedPlaceholder(sqlplugin.DbFields...),
    58  		buildOnDuplicateKeyUpdate(sqlplugin.DbFields...),
    59  	)
    60  
    61  	templateUpsertCustomSearchAttributes = `
    62  		INSERT INTO custom_search_attributes (
    63  			namespace_id, run_id, search_attributes
    64  		) VALUES (:namespace_id, :run_id, :search_attributes)
    65  		ON DUPLICATE KEY UPDATE search_attributes = VALUES(search_attributes)`
    66  
    67  	templateDeleteWorkflowExecution_v8 = `
    68  		DELETE FROM executions_visibility
    69  		WHERE namespace_id = :namespace_id AND run_id = :run_id`
    70  
    71  	templateDeleteCustomSearchAttributes = `
    72  		DELETE FROM custom_search_attributes
    73  		WHERE namespace_id = :namespace_id AND run_id = :run_id`
    74  
    75  	templateGetWorkflowExecution_v8 = fmt.Sprintf(
    76  		`SELECT %s FROM executions_visibility
    77  		WHERE namespace_id = :namespace_id AND run_id = :run_id`,
    78  		strings.Join(sqlplugin.DbFields, ", "),
    79  	)
    80  )
    81  
    82  func buildOnDuplicateKeyUpdate(fields ...string) string {
    83  	items := make([]string, len(fields))
    84  	for i, field := range fields {
    85  		items[i] = fmt.Sprintf("%s = VALUES(%s)", field, field)
    86  	}
    87  	return fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(items, ", "))
    88  }
    89  
    90  // InsertIntoVisibility inserts a row into visibility table. If an row already exist,
    91  // its left as such and no update will be made
    92  func (mdb *dbV8) InsertIntoVisibility(
    93  	ctx context.Context,
    94  	row *sqlplugin.VisibilityRow,
    95  ) (result sql.Result, retError error) {
    96  	finalRow := mdb.prepareRowForDB(row)
    97  	tx, err := mdb.db.db.BeginTxx(ctx, nil)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	defer func() {
   102  		err := tx.Rollback()
   103  		// If the error is sql.ErrTxDone, it means the transaction already closed, so ignore error.
   104  		if err != nil && !errors.Is(err, sql.ErrTxDone) {
   105  			// Transaction rollback error should never happen, unless db connection was lost.
   106  			retError = fmt.Errorf("transaction rollback failed: %w", retError)
   107  		}
   108  	}()
   109  	result, err = tx.NamedExecContext(ctx, templateInsertWorkflowExecution, finalRow)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	_, err = tx.NamedExecContext(ctx, templateInsertCustomSearchAttributes, finalRow)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	err = tx.Commit()
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return result, nil
   122  }
   123  
   124  // ReplaceIntoVisibility replaces an existing row if it exist or creates a new row in visibility table
   125  func (mdb *dbV8) ReplaceIntoVisibility(
   126  	ctx context.Context,
   127  	row *sqlplugin.VisibilityRow,
   128  ) (result sql.Result, retError error) {
   129  	finalRow := mdb.prepareRowForDB(row)
   130  	tx, err := mdb.db.db.BeginTxx(ctx, nil)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	defer func() {
   135  		err := tx.Rollback()
   136  		// If the error is sql.ErrTxDone, it means the transaction already closed, so ignore error.
   137  		if err != nil && !errors.Is(err, sql.ErrTxDone) {
   138  			// Transaction rollback error should never happen, unless db connection was lost.
   139  			retError = fmt.Errorf("transaction rollback failed: %w", retError)
   140  		}
   141  	}()
   142  	result, err = tx.NamedExecContext(ctx, templateUpsertWorkflowExecution, finalRow)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	_, err = tx.NamedExecContext(ctx, templateUpsertCustomSearchAttributes, finalRow)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	err = tx.Commit()
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	return result, nil
   155  }
   156  
   157  // DeleteFromVisibility deletes a row from visibility table if it exist
   158  func (mdb *dbV8) DeleteFromVisibility(
   159  	ctx context.Context,
   160  	filter sqlplugin.VisibilityDeleteFilter,
   161  ) (result sql.Result, retError error) {
   162  	tx, err := mdb.db.db.BeginTxx(ctx, nil)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	defer func() {
   167  		err := tx.Rollback()
   168  		// If the error is sql.ErrTxDone, it means the transaction already closed, so ignore error.
   169  		if err != nil && !errors.Is(err, sql.ErrTxDone) {
   170  			// Transaction rollback error should never happen, unless db connection was lost.
   171  			retError = fmt.Errorf("transaction rollback failed: %w", retError)
   172  		}
   173  	}()
   174  	_, err = mdb.conn.NamedExecContext(ctx, templateDeleteCustomSearchAttributes, filter)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	result, err = mdb.conn.NamedExecContext(ctx, templateDeleteWorkflowExecution_v8, filter)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	err = tx.Commit()
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	return result, nil
   187  }
   188  
   189  // SelectFromVisibility reads one or more rows from visibility table
   190  func (mdb *dbV8) SelectFromVisibility(
   191  	ctx context.Context,
   192  	filter sqlplugin.VisibilitySelectFilter,
   193  ) ([]sqlplugin.VisibilityRow, error) {
   194  	if len(filter.Query) == 0 {
   195  		// backward compatibility for existing tests
   196  		err := sqlplugin.GenerateSelectQuery(&filter, mdb.converter.ToMySQLDateTime)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  	}
   201  
   202  	var rows []sqlplugin.VisibilityRow
   203  	err := mdb.conn.SelectContext(ctx, &rows, filter.Query, filter.QueryArgs...)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	for i := range rows {
   208  		err = mdb.processRowFromDB(&rows[i])
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  	}
   213  	return rows, nil
   214  }
   215  
   216  // GetFromVisibility reads one row from visibility table
   217  func (mdb *dbV8) GetFromVisibility(
   218  	ctx context.Context,
   219  	filter sqlplugin.VisibilityGetFilter,
   220  ) (*sqlplugin.VisibilityRow, error) {
   221  	var row sqlplugin.VisibilityRow
   222  	stmt, err := mdb.conn.PrepareNamedContext(ctx, templateGetWorkflowExecution_v8)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	err = stmt.GetContext(ctx, &row, filter)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	err = mdb.processRowFromDB(&row)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	return &row, nil
   235  }
   236  
   237  func (mdb *dbV8) CountFromVisibility(
   238  	ctx context.Context,
   239  	filter sqlplugin.VisibilitySelectFilter,
   240  ) (int64, error) {
   241  	var count int64
   242  	err := mdb.conn.GetContext(ctx, &count, filter.Query, filter.QueryArgs...)
   243  	if err != nil {
   244  		return 0, err
   245  	}
   246  	return count, nil
   247  }
   248  
   249  func (mdb *dbV8) CountGroupByFromVisibility(
   250  	ctx context.Context,
   251  	filter sqlplugin.VisibilitySelectFilter,
   252  ) ([]sqlplugin.VisibilityCountRow, error) {
   253  	rows, err := mdb.db.db.QueryContext(ctx, filter.Query, filter.QueryArgs...)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	defer rows.Close()
   258  	return sqlplugin.ParseCountGroupByRows(rows, filter.GroupBy)
   259  }
   260  
   261  func (mdb *dbV8) prepareRowForDB(row *sqlplugin.VisibilityRow) *sqlplugin.VisibilityRow {
   262  	if row == nil {
   263  		return nil
   264  	}
   265  	finalRow := *row
   266  	finalRow.StartTime = mdb.converter.ToMySQLDateTime(finalRow.StartTime)
   267  	finalRow.ExecutionTime = mdb.converter.ToMySQLDateTime(finalRow.ExecutionTime)
   268  	if finalRow.CloseTime != nil {
   269  		*finalRow.CloseTime = mdb.converter.ToMySQLDateTime(*finalRow.CloseTime)
   270  	}
   271  	return &finalRow
   272  }
   273  
   274  func (mdb *dbV8) processRowFromDB(row *sqlplugin.VisibilityRow) error {
   275  	if row == nil {
   276  		return nil
   277  	}
   278  	row.StartTime = mdb.converter.FromMySQLDateTime(row.StartTime)
   279  	row.ExecutionTime = mdb.converter.FromMySQLDateTime(row.ExecutionTime)
   280  	if row.CloseTime != nil {
   281  		closeTime := mdb.converter.FromMySQLDateTime(*row.CloseTime)
   282  		row.CloseTime = &closeTime
   283  	}
   284  	if row.SearchAttributes != nil {
   285  		for saName, saValue := range *row.SearchAttributes {
   286  			switch typedSaValue := saValue.(type) {
   287  			case []interface{}:
   288  				// the only valid type is slice of strings
   289  				strSlice := make([]string, len(typedSaValue))
   290  				for i, item := range typedSaValue {
   291  					switch v := item.(type) {
   292  					case string:
   293  						strSlice[i] = v
   294  					default:
   295  						return fmt.Errorf("Unexpected data type in keyword list: %T (expected string)", v)
   296  					}
   297  				}
   298  				(*row.SearchAttributes)[saName] = strSlice
   299  			default:
   300  				// no-op
   301  			}
   302  		}
   303  	}
   304  	return nil
   305  }