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 }