go.temporal.io/server@v1.23.0/common/persistence/sql/sqlplugin/postgresql/visibility_v12.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 postgresql
    26  
    27  import (
    28  	"context"
    29  	"database/sql"
    30  	"fmt"
    31  	"strings"
    32  
    33  	"go.temporal.io/server/common/persistence/sql/sqlplugin"
    34  )
    35  
    36  var (
    37  	templateInsertWorkflowExecution = fmt.Sprintf(
    38  		`INSERT INTO executions_visibility (%s)
    39  		VALUES (%s)
    40  		ON CONFLICT (namespace_id, run_id) DO NOTHING`,
    41  		strings.Join(sqlplugin.DbFields, ", "),
    42  		sqlplugin.BuildNamedPlaceholder(sqlplugin.DbFields...),
    43  	)
    44  
    45  	templateUpsertWorkflowExecution = fmt.Sprintf(
    46  		`INSERT INTO executions_visibility (%s)
    47  		VALUES (%s)
    48  		%s`,
    49  		strings.Join(sqlplugin.DbFields, ", "),
    50  		sqlplugin.BuildNamedPlaceholder(sqlplugin.DbFields...),
    51  		buildOnDuplicateKeyUpdate(sqlplugin.DbFields...),
    52  	)
    53  
    54  	templateDeleteWorkflowExecution_v12 = `
    55  		DELETE FROM executions_visibility
    56  		WHERE namespace_id = :namespace_id AND run_id = :run_id`
    57  
    58  	templateGetWorkflowExecution_v12 = fmt.Sprintf(
    59  		`SELECT %s FROM executions_visibility
    60  		WHERE namespace_id = :namespace_id AND run_id = :run_id`,
    61  		strings.Join(sqlplugin.DbFields, ", "),
    62  	)
    63  )
    64  
    65  func buildOnDuplicateKeyUpdate(fields ...string) string {
    66  	items := make([]string, len(fields))
    67  	for i, field := range fields {
    68  		items[i] = fmt.Sprintf("%s = excluded.%s", field, field)
    69  	}
    70  	return fmt.Sprintf(
    71  		"ON CONFLICT (namespace_id, run_id) DO UPDATE SET %s",
    72  		strings.Join(items, ", "),
    73  	)
    74  }
    75  
    76  // InsertIntoVisibility inserts a row into visibility table. If an row already exist,
    77  // its left as such and no update will be made
    78  func (pdb *dbV12) InsertIntoVisibility(
    79  	ctx context.Context,
    80  	row *sqlplugin.VisibilityRow,
    81  ) (sql.Result, error) {
    82  	finalRow := pdb.prepareRowForDB(row)
    83  	return pdb.conn.NamedExecContext(ctx, templateInsertWorkflowExecution, finalRow)
    84  }
    85  
    86  // ReplaceIntoVisibility replaces an existing row if it exist or creates a new row in visibility table
    87  func (pdb *dbV12) ReplaceIntoVisibility(
    88  	ctx context.Context,
    89  	row *sqlplugin.VisibilityRow,
    90  ) (sql.Result, error) {
    91  	finalRow := pdb.prepareRowForDB(row)
    92  	return pdb.conn.NamedExecContext(ctx, templateUpsertWorkflowExecution, finalRow)
    93  }
    94  
    95  // DeleteFromVisibility deletes a row from visibility table if it exist
    96  func (pdb *dbV12) DeleteFromVisibility(
    97  	ctx context.Context,
    98  	filter sqlplugin.VisibilityDeleteFilter,
    99  ) (sql.Result, error) {
   100  	return pdb.conn.NamedExecContext(ctx, templateDeleteWorkflowExecution_v12, filter)
   101  }
   102  
   103  // SelectFromVisibility reads one or more rows from visibility table
   104  func (pdb *dbV12) SelectFromVisibility(
   105  	ctx context.Context,
   106  	filter sqlplugin.VisibilitySelectFilter,
   107  ) ([]sqlplugin.VisibilityRow, error) {
   108  	if len(filter.Query) == 0 {
   109  		// backward compatibility for existing tests
   110  		err := sqlplugin.GenerateSelectQuery(&filter, pdb.converter.ToPostgreSQLDateTime)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  	}
   115  
   116  	// Rebind will replace default placeholder `?` with the right placeholder for PostgreSQL.
   117  	filter.Query = pdb.db.db.Rebind(filter.Query)
   118  	var rows []sqlplugin.VisibilityRow
   119  	err := pdb.conn.SelectContext(ctx, &rows, filter.Query, filter.QueryArgs...)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	for i := range rows {
   124  		err = pdb.processRowFromDB(&rows[i])
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  	}
   129  	return rows, nil
   130  }
   131  
   132  // GetFromVisibility reads one row from visibility table
   133  func (pdb *dbV12) GetFromVisibility(
   134  	ctx context.Context,
   135  	filter sqlplugin.VisibilityGetFilter,
   136  ) (*sqlplugin.VisibilityRow, error) {
   137  	var row sqlplugin.VisibilityRow
   138  	stmt, err := pdb.conn.PrepareNamedContext(ctx, templateGetWorkflowExecution_v12)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	err = stmt.GetContext(ctx, &row, filter)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	err = pdb.processRowFromDB(&row)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	return &row, nil
   151  }
   152  
   153  func (pdb *dbV12) CountFromVisibility(
   154  	ctx context.Context,
   155  	filter sqlplugin.VisibilitySelectFilter,
   156  ) (int64, error) {
   157  	var count int64
   158  	filter.Query = pdb.db.db.Rebind(filter.Query)
   159  	err := pdb.conn.GetContext(ctx, &count, filter.Query, filter.QueryArgs...)
   160  	if err != nil {
   161  		return 0, err
   162  	}
   163  	return count, nil
   164  }
   165  
   166  func (pdb *dbV12) CountGroupByFromVisibility(
   167  	ctx context.Context,
   168  	filter sqlplugin.VisibilitySelectFilter,
   169  ) ([]sqlplugin.VisibilityCountRow, error) {
   170  	filter.Query = pdb.db.db.Rebind(filter.Query)
   171  	rows, err := pdb.db.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 (pdb *dbV12) prepareRowForDB(row *sqlplugin.VisibilityRow) *sqlplugin.VisibilityRow {
   180  	if row == nil {
   181  		return nil
   182  	}
   183  	finalRow := *row
   184  	finalRow.StartTime = pdb.converter.ToPostgreSQLDateTime(finalRow.StartTime)
   185  	finalRow.ExecutionTime = pdb.converter.ToPostgreSQLDateTime(finalRow.ExecutionTime)
   186  	if finalRow.CloseTime != nil {
   187  		*finalRow.CloseTime = pdb.converter.ToPostgreSQLDateTime(*finalRow.CloseTime)
   188  	}
   189  	return &finalRow
   190  }
   191  
   192  func (pdb *dbV12) processRowFromDB(row *sqlplugin.VisibilityRow) error {
   193  	if row == nil {
   194  		return nil
   195  	}
   196  	row.StartTime = pdb.converter.FromPostgreSQLDateTime(row.StartTime)
   197  	row.ExecutionTime = pdb.converter.FromPostgreSQLDateTime(row.ExecutionTime)
   198  	if row.CloseTime != nil {
   199  		closeTime := pdb.converter.FromPostgreSQLDateTime(*row.CloseTime)
   200  		row.CloseTime = &closeTime
   201  	}
   202  	if row.SearchAttributes != nil {
   203  		for saName, saValue := range *row.SearchAttributes {
   204  			switch typedSaValue := saValue.(type) {
   205  			case []interface{}:
   206  				// the only valid type is slice of strings
   207  				strSlice := make([]string, len(typedSaValue))
   208  				for i, item := range typedSaValue {
   209  					switch v := item.(type) {
   210  					case string:
   211  						strSlice[i] = v
   212  					default:
   213  						return fmt.Errorf("Unexpected data type in keyword list: %T (expected string)", v)
   214  					}
   215  				}
   216  				(*row.SearchAttributes)[saName] = strSlice
   217  			default:
   218  				// no-op
   219  			}
   220  		}
   221  	}
   222  	// need to trim the run ID, or otherwise the returned value will
   223  	// come with lots of trailing spaces, probably due to the CHAR(64) type
   224  	row.RunID = strings.TrimSpace(row.RunID)
   225  	return nil
   226  }