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, &param)
   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, &param)
   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  }