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 }