github.com/snowflakedb/gosnowflake@v1.9.0/monitoring.go (about) 1 // Copyright (c) 2021-2022 Snowflake Computing Inc. All rights reserved. 2 3 package gosnowflake 4 5 import ( 6 "context" 7 "database/sql/driver" 8 "encoding/json" 9 "fmt" 10 "net/url" 11 "strconv" 12 ) 13 14 const urlQueriesResultFmt = "/queries/%s/result" 15 16 // queryResultStatus is status returned from server 17 type queryResultStatus int 18 19 // Query Status defined at server side 20 const ( 21 SFQueryRunning queryResultStatus = iota 22 SFQueryAborting 23 SFQuerySuccess 24 SFQueryFailedWithError 25 SFQueryAborted 26 SFQueryQueued 27 SFQueryFailedWithIncident 28 SFQueryDisconnected 29 SFQueryResumingWarehouse 30 // SFQueryQueueRepairingWarehouse present in QueryDTO.java. 31 SFQueryQueueRepairingWarehouse 32 SFQueryRestarted 33 // SFQueryBlocked is when a statement is waiting on a lock on resource held 34 // by another statement. 35 SFQueryBlocked 36 SFQueryNoData 37 ) 38 39 func (qs queryResultStatus) String() string { 40 return [...]string{"RUNNING", "ABORTING", "SUCCESS", "FAILED_WITH_ERROR", 41 "ABORTED", "QUEUED", "FAILED_WITH_INCIDENT", "DISCONNECTED", 42 "RESUMING_WAREHOUSE", "QUEUED_REPAIRING_WAREHOUSE", "RESTARTED", 43 "BLOCKED", "NO_DATA"}[qs] 44 } 45 46 func (qs queryResultStatus) isRunning() bool { 47 switch qs { 48 case SFQueryRunning, SFQueryResumingWarehouse, SFQueryQueued, 49 SFQueryQueueRepairingWarehouse, SFQueryNoData: 50 return true 51 default: 52 return false 53 } 54 } 55 56 func (qs queryResultStatus) isError() bool { 57 switch qs { 58 case SFQueryAborting, SFQueryFailedWithError, SFQueryAborted, 59 SFQueryFailedWithIncident, SFQueryDisconnected, SFQueryBlocked: 60 return true 61 default: 62 return false 63 } 64 } 65 66 var strQueryStatusMap = map[string]queryResultStatus{"RUNNING": SFQueryRunning, 67 "ABORTING": SFQueryAborting, "SUCCESS": SFQuerySuccess, 68 "FAILED_WITH_ERROR": SFQueryFailedWithError, "ABORTED": SFQueryAborted, 69 "QUEUED": SFQueryQueued, "FAILED_WITH_INCIDENT": SFQueryFailedWithIncident, 70 "DISCONNECTED": SFQueryDisconnected, 71 "RESUMING_WAREHOUSE": SFQueryResumingWarehouse, 72 "QUEUED_REPAIRING_WAREHOUSE": SFQueryQueueRepairingWarehouse, 73 "RESTARTED": SFQueryRestarted, 74 "BLOCKED": SFQueryBlocked, "NO_DATA": SFQueryNoData} 75 76 type retStatus struct { 77 Status string `json:"status"` 78 SQLText string `json:"sqlText"` 79 StartTime int64 `json:"startTime"` 80 EndTime int64 `json:"endTime"` 81 ErrorCode string `json:"errorCode"` 82 ErrorMessage string `json:"errorMessage"` 83 Stats retStats `json:"stats"` 84 } 85 86 type retStats struct { 87 ScanBytes int64 `json:"scanBytes"` 88 ProducedRows int64 `json:"producedRows"` 89 } 90 91 type statusResponse struct { 92 Data struct { 93 Queries []retStatus `json:"queries"` 94 } `json:"data"` 95 Message string `json:"message"` 96 Code string `json:"code"` 97 Success bool `json:"success"` 98 } 99 100 func strToQueryStatus(in string) queryResultStatus { 101 return strQueryStatusMap[in] 102 } 103 104 // SnowflakeQueryStatus is the query status metadata of a snowflake query 105 type SnowflakeQueryStatus struct { 106 SQLText string 107 StartTime int64 108 EndTime int64 109 ErrorCode string 110 ErrorMessage string 111 ScanBytes int64 112 ProducedRows int64 113 } 114 115 // SnowflakeConnection is a wrapper to snowflakeConn that exposes API functions 116 type SnowflakeConnection interface { 117 GetQueryStatus(ctx context.Context, queryID string) (*SnowflakeQueryStatus, error) 118 } 119 120 // checkQueryStatus returns the status given the query ID. If successful, 121 // the error will be nil, indicating there is a complete query result to fetch. 122 // Other than nil, there are three error types that can be returned: 123 // 1. ErrQueryStatus, if GS cannot return any kind of status due to any reason, 124 // i.e. connection, permission, if a query was just submitted, etc. 125 // 2, ErrQueryReportedError, if the requested query was terminated or aborted 126 // and GS returned an error status included in query. SFQueryFailedWithError 127 // 3, ErrQueryIsRunning, if the requested query is still running and might have 128 // a complete result later, these statuses were listed in query. SFQueryRunning 129 func (sc *snowflakeConn) checkQueryStatus( 130 ctx context.Context, 131 qid string) ( 132 *retStatus, error) { 133 headers := make(map[string]string) 134 param := make(url.Values) 135 param.Add(requestGUIDKey, NewUUID().String()) 136 if tok, _, _ := sc.rest.TokenAccessor.GetTokens(); tok != "" { 137 headers[headerAuthorizationKey] = fmt.Sprintf(headerSnowflakeToken, tok) 138 } 139 resultPath := fmt.Sprintf("%s/%s", monitoringQueriesPath, qid) 140 url := sc.rest.getFullURL(resultPath, ¶m) 141 142 res, err := sc.rest.FuncGet(ctx, sc.rest, url, headers, sc.rest.RequestTimeout) 143 if err != nil { 144 logger.WithContext(ctx).Errorf("failed to get response. err: %v", err) 145 return nil, err 146 } 147 defer res.Body.Close() 148 var statusResp = statusResponse{} 149 if err = json.NewDecoder(res.Body).Decode(&statusResp); err != nil { 150 logger.WithContext(ctx).Errorf("failed to decode JSON. err: %v", err) 151 return nil, err 152 } 153 154 if !statusResp.Success || len(statusResp.Data.Queries) == 0 { 155 logger.WithContext(ctx).Errorf("status query returned not-success or no status returned.") 156 return nil, (&SnowflakeError{ 157 Number: ErrQueryStatus, 158 Message: "status query returned not-success or no status returned. Please retry", 159 }).exceptionTelemetry(sc) 160 } 161 162 queryRet := statusResp.Data.Queries[0] 163 if queryRet.ErrorCode != "" { 164 return &queryRet, (&SnowflakeError{ 165 Number: ErrQueryStatus, 166 Message: errMsgQueryStatus, 167 MessageArgs: []interface{}{queryRet.ErrorCode, queryRet.ErrorMessage}, 168 IncludeQueryID: true, 169 QueryID: qid, 170 }).exceptionTelemetry(sc) 171 } 172 173 // returned errorCode is 0. Now check what is the returned status of the query. 174 qStatus := strToQueryStatus(queryRet.Status) 175 if qStatus.isError() { 176 return &queryRet, (&SnowflakeError{ 177 Number: ErrQueryReportedError, 178 Message: fmt.Sprintf("%s: status from server: [%s]", 179 queryRet.ErrorMessage, queryRet.Status), 180 IncludeQueryID: true, 181 QueryID: qid, 182 }).exceptionTelemetry(sc) 183 } 184 185 if qStatus.isRunning() { 186 return &queryRet, (&SnowflakeError{ 187 Number: ErrQueryIsRunning, 188 Message: fmt.Sprintf("%s: status from server: [%s]", 189 queryRet.ErrorMessage, queryRet.Status), 190 IncludeQueryID: true, 191 QueryID: qid, 192 }).exceptionTelemetry(sc) 193 } 194 //success 195 return &queryRet, nil 196 } 197 198 func (sc *snowflakeConn) getQueryResultResp( 199 ctx context.Context, 200 resultPath string) ( 201 *execResponse, error) { 202 headers := getHeaders() 203 paramsMutex.Lock() 204 if serviceName, ok := sc.cfg.Params[serviceName]; ok { 205 headers[httpHeaderServiceName] = *serviceName 206 } 207 paramsMutex.Unlock() 208 param := make(url.Values) 209 param.Add(requestIDKey, getOrGenerateRequestIDFromContext(ctx).String()) 210 param.Add("clientStartTime", strconv.FormatInt(sc.currentTimeProvider.currentTime(), 10)) 211 param.Add(requestGUIDKey, NewUUID().String()) 212 token, _, _ := sc.rest.TokenAccessor.GetTokens() 213 if token != "" { 214 headers[headerAuthorizationKey] = fmt.Sprintf(headerSnowflakeToken, token) 215 } 216 url := sc.rest.getFullURL(resultPath, ¶m) 217 218 respd, err := getQueryResultWithRetriesForAsyncMode(ctx, sc.rest, url, headers, sc.rest.RequestTimeout) 219 if err != nil { 220 logger.WithContext(ctx).Errorf("error: %v", err) 221 return nil, err 222 } 223 return respd, nil 224 } 225 226 // Fetch query result for a query id from /queries/<qid>/result endpoint. 227 func (sc *snowflakeConn) rowsForRunningQuery( 228 ctx context.Context, qid string, 229 rows *snowflakeRows) error { 230 resultPath := fmt.Sprintf(urlQueriesResultFmt, qid) 231 resp, err := sc.getQueryResultResp(ctx, resultPath) 232 if err != nil { 233 logger.WithContext(ctx).Errorf("error: %v", err) 234 return err 235 } 236 237 if !resp.Success { 238 code, err := strconv.Atoi(resp.Code) 239 if err != nil { 240 return err 241 } 242 return (&SnowflakeError{ 243 Number: code, 244 SQLState: resp.Data.SQLState, 245 Message: resp.Message, 246 QueryID: resp.Data.QueryID, 247 }).exceptionTelemetry(sc) 248 } 249 rows.addDownloader(populateChunkDownloader(ctx, sc, resp.Data)) 250 return nil 251 } 252 253 // prepare a Rows object to return for query of 'qid' 254 func (sc *snowflakeConn) buildRowsForRunningQuery( 255 ctx context.Context, 256 qid string) ( 257 driver.Rows, error) { 258 rows := new(snowflakeRows) 259 rows.sc = sc 260 rows.queryID = qid 261 if err := sc.rowsForRunningQuery(ctx, qid, rows); err != nil { 262 return nil, err 263 } 264 err := rows.ChunkDownloader.start() 265 return rows, err 266 }