github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/db/db_client/db_client_execute_retry.go (about) 1 package db_client 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/jackc/pgx/v5" 10 "github.com/sethvargo/go-retry" 11 typehelpers "github.com/turbot/go-kit/types" 12 "github.com/turbot/steampipe/pkg/constants" 13 "github.com/turbot/steampipe/pkg/db/db_common" 14 "github.com/turbot/steampipe/pkg/statushooks" 15 "github.com/turbot/steampipe/pkg/steampipeconfig" 16 ) 17 18 // execute query - if it fails with a "relation not found" error, determine whether this is because the required schema 19 // has not yet loaded and if so, wait for it to load and retry 20 func (c *DbClient) startQueryWithRetries(ctx context.Context, session *db_common.DatabaseSession, query string, args ...any) (pgx.Rows, error) { 21 log.Println("[TRACE] DbClient.startQueryWithRetries start") 22 defer log.Println("[TRACE] DbClient.startQueryWithRetries end") 23 24 // long timeout to give refresh connections a chance to finish 25 maxDuration := 10 * time.Minute 26 backoffInterval := 250 * time.Millisecond 27 backoff := retry.NewConstant(backoffInterval) 28 29 conn := session.Connection.Conn() 30 31 var res pgx.Rows 32 count := 0 33 err := retry.Do(ctx, retry.WithMaxDuration(maxDuration, backoff), func(ctx context.Context) error { 34 count++ 35 log.Println("[TRACE] starting", count) 36 rows, queryError := c.startQuery(ctx, conn, query, args...) 37 // if there is no error, just return 38 if queryError == nil { 39 log.Println("[TRACE] no queryError") 40 statushooks.SetStatus(ctx, "Loading results…") 41 res = rows 42 return nil 43 } 44 45 log.Println("[TRACE] queryError:", queryError) 46 // so there is an error - is it "relation not found"? 47 missingSchema, _, relationNotFound := db_common.GetMissingSchemaFromIsRelationNotFoundError(queryError) 48 if !relationNotFound { 49 log.Println("[TRACE] queryError not relation not found") 50 // just return it 51 return queryError 52 } 53 54 // get a connection from the system pool to query the connection state table 55 sysConn, err := c.managementPool.Acquire(ctx) 56 if err != nil { 57 return retry.RetryableError(err) 58 } 59 defer sysConn.Release() 60 // so this _was_ a "relation not found" error 61 // load the connection state and connection config to see if the missing schema is in there at all 62 // if there was a schema not found with an unqualified query, we keep trying until 63 // the first search path schema for each plugin has loaded 64 connectionStateMap, stateErr := steampipeconfig.LoadConnectionState(ctx, sysConn.Conn(), steampipeconfig.WithWaitUntilLoading()) 65 if stateErr != nil { 66 log.Println("[TRACE] could not load connection state map:", stateErr) 67 // just return the query error 68 return queryError 69 } 70 71 // if there are no connections, just return the error 72 if len(connectionStateMap) == 0 { 73 log.Println("[TRACE] no data in connection state map") 74 return queryError 75 } 76 77 // is this an unqualified query... 78 if missingSchema == "" { 79 log.Println("[TRACE] this was an unqualified query") 80 // refresh the search path, as now the connection state is in loading state, search paths may have been updated 81 if err := c.ensureSessionSearchPath(ctx, session); err != nil { 82 return queryError 83 } 84 85 // we need the first search path connection for each plugin to be loaded 86 searchPath := c.GetRequiredSessionSearchPath() 87 requiredConnections := connectionStateMap.GetFirstSearchPathConnectionForPlugins(searchPath) 88 // if required connections are ready (and have been for more than the backoff interval) , just return the relation not found error 89 if connectionStateMap.Loaded(requiredConnections...) && time.Since(connectionStateMap.ConnectionModTime()) > backoffInterval { 90 return queryError 91 } 92 93 // otherwise we need to wait for the first schema of everything plugin to load 94 if _, err := steampipeconfig.LoadConnectionState(ctx, sysConn.Conn(), steampipeconfig.WithWaitForSearchPath(searchPath)); err != nil { 95 return err 96 } 97 98 // so now the connections are loaded - retry the query 99 return retry.RetryableError(queryError) 100 } 101 102 // so a schema was specified 103 // verify it exists in the connection state and is not disabled 104 connectionState, missingSchemaExistsInStateMap := connectionStateMap[missingSchema] 105 if !missingSchemaExistsInStateMap { 106 log.Println("[TRACE] schema", missingSchema, "is not in schema map") 107 //, missing schema is not in connection state map - just return the error 108 return queryError 109 } 110 111 // so schema _is_ in the state map 112 if connectionState.Disabled() { 113 log.Println("[TRACE] schema", missingSchema, "is disabled") 114 return queryError 115 } 116 117 // if the connection is ready (and has been for more than the backoff interval) , just return the relation not found error 118 if connectionState.State == constants.ConnectionStateReady && time.Since(connectionState.ConnectionModTime) > backoffInterval { 119 log.Println("[TRACE] schema", missingSchema, "has been ready for a long time") 120 return queryError 121 } 122 123 // if connection is in error,return the connection error 124 if connectionState.State == constants.ConnectionStateError { 125 log.Println("[TRACE] schema", missingSchema, "is in error") 126 return fmt.Errorf("connection %s failed to load: %s", missingSchema, typehelpers.SafeString(connectionState.ConnectionError)) 127 } 128 129 // ok so we will retry 130 // build the status message to display with a spinner, if needed 131 statusMessage := steampipeconfig.GetLoadingConnectionStatusMessage(connectionStateMap, missingSchema) 132 statushooks.SetStatus(ctx, statusMessage) 133 return retry.RetryableError(queryError) 134 }) 135 136 return res, err 137 }