github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/backends/db_fetcher/fetcher.go (about)

     1  package db_fetcher
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  
     7  	"github.com/lib/pq"
     8  
     9  	"github.com/golang/glog"
    10  	"github.com/prebid/prebid-server/v2/stored_requests"
    11  	"github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider"
    12  )
    13  
    14  func NewFetcher(
    15  	provider db_provider.DbProvider,
    16  	queryTemplate string,
    17  	responseQueryTemplate string,
    18  ) stored_requests.AllFetcher {
    19  
    20  	if provider == nil {
    21  		glog.Fatalf("The Database Stored Request Fetcher requires a database connection. Please report this as a bug.")
    22  	}
    23  	if queryTemplate == "" {
    24  		glog.Fatalf("The Database Stored Request Fetcher requires a queryTemplate. Please report this as a bug.")
    25  	}
    26  	if responseQueryTemplate == "" {
    27  		glog.Fatalf("The Database Stored Response Fetcher requires a responseQueryTemplate. Please report this as a bug.")
    28  	}
    29  	return &dbFetcher{
    30  		provider:              provider,
    31  		queryTemplate:         queryTemplate,
    32  		responseQueryTemplate: responseQueryTemplate,
    33  	}
    34  }
    35  
    36  // dbFetcher fetches Stored Requests from a database. This should be instantiated through the NewFetcher() function.
    37  type dbFetcher struct {
    38  	provider              db_provider.DbProvider
    39  	queryTemplate         string
    40  	responseQueryTemplate string
    41  }
    42  
    43  func (fetcher *dbFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage, []error) {
    44  	if len(requestIDs) < 1 && len(impIDs) < 1 {
    45  		return nil, nil, nil
    46  	}
    47  
    48  	requestIDsParam := make([]interface{}, len(requestIDs))
    49  	for i := 0; i < len(requestIDs); i++ {
    50  		requestIDsParam[i] = requestIDs[i]
    51  	}
    52  	impIDsParam := make([]interface{}, len(impIDs))
    53  	for i := 0; i < len(impIDs); i++ {
    54  		impIDsParam[i] = impIDs[i]
    55  	}
    56  
    57  	params := []db_provider.QueryParam{
    58  		{Name: "REQUEST_ID_LIST", Value: requestIDsParam},
    59  		{Name: "IMP_ID_LIST", Value: impIDsParam},
    60  	}
    61  
    62  	rows, err := fetcher.provider.QueryContext(ctx, fetcher.queryTemplate, params...)
    63  	if err != nil {
    64  		if err != context.DeadlineExceeded && !isBadInput(err) {
    65  			glog.Errorf("Error reading from Stored Request DB: %s", err.Error())
    66  			errs := appendErrors("Request", requestIDs, nil, nil)
    67  			errs = appendErrors("Imp", impIDs, nil, errs)
    68  			return nil, nil, errs
    69  		}
    70  		return nil, nil, []error{err}
    71  	}
    72  	defer func() {
    73  		if err := rows.Close(); err != nil {
    74  			glog.Errorf("error closing DB connection: %v", err)
    75  		}
    76  	}()
    77  
    78  	storedRequestData := make(map[string]json.RawMessage, len(requestIDs))
    79  	storedImpData := make(map[string]json.RawMessage, len(impIDs))
    80  	for rows.Next() {
    81  		var id string
    82  		var data []byte
    83  		var dataType string
    84  
    85  		// Fixes #338
    86  		if err := rows.Scan(&id, &data, &dataType); err != nil {
    87  			return nil, nil, []error{err}
    88  		}
    89  
    90  		switch dataType {
    91  		case "request":
    92  			storedRequestData[id] = data
    93  		case "imp":
    94  			storedImpData[id] = data
    95  		default:
    96  			glog.Errorf("Database result set with id=%s has invalid type: %s. This will be ignored.", id, dataType)
    97  		}
    98  	}
    99  
   100  	// Fixes #338
   101  	if rows.Err() != nil {
   102  		return nil, nil, []error{rows.Err()}
   103  	}
   104  
   105  	errs := appendErrors("Request", requestIDs, storedRequestData, nil)
   106  	errs = appendErrors("Imp", impIDs, storedImpData, errs)
   107  
   108  	return storedRequestData, storedImpData, errs
   109  }
   110  
   111  func (fetcher *dbFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
   112  	if len(ids) < 1 {
   113  		return nil, nil
   114  	}
   115  
   116  	idInterfaces := make([]interface{}, len(ids))
   117  	for i := 0; i < len(ids); i++ {
   118  		idInterfaces[i] = ids[i]
   119  	}
   120  	params := []db_provider.QueryParam{
   121  		{Name: "ID_LIST", Value: idInterfaces},
   122  	}
   123  
   124  	rows, err := fetcher.provider.QueryContext(ctx, fetcher.responseQueryTemplate, params...)
   125  	if err != nil {
   126  		return nil, []error{err}
   127  	}
   128  	defer func() {
   129  		if err := rows.Close(); err != nil {
   130  			glog.Errorf("error closing DB connection: %v", err)
   131  		}
   132  	}()
   133  
   134  	storedData := make(map[string]json.RawMessage, len(ids))
   135  	for rows.Next() {
   136  		var id string
   137  		var data []byte
   138  		var dataType string
   139  
   140  		if err := rows.Scan(&id, &data, &dataType); err != nil {
   141  			return nil, []error{err}
   142  		}
   143  		storedData[id] = data
   144  	}
   145  
   146  	if rows.Err() != nil {
   147  		return nil, []error{rows.Err()}
   148  	}
   149  
   150  	return storedData, errs
   151  
   152  }
   153  
   154  func (fetcher *dbFetcher) FetchAccount(ctx context.Context, accountDefaultsJSON json.RawMessage, accountID string) (json.RawMessage, []error) {
   155  	return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}}
   156  }
   157  
   158  func (fetcher *dbFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
   159  	return "", nil
   160  }
   161  
   162  func appendErrors(dataType string, ids []string, data map[string]json.RawMessage, errs []error) []error {
   163  	for _, id := range ids {
   164  		if _, ok := data[id]; !ok {
   165  			errs = append(errs, stored_requests.NotFoundError{
   166  				ID:       id,
   167  				DataType: dataType,
   168  			})
   169  		}
   170  	}
   171  	return errs
   172  }
   173  
   174  // Returns true if the Postgres error signifies some sort of bad user input, and false otherwise.
   175  //
   176  // These errors are documented here: https://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
   177  func isBadInput(err error) bool {
   178  	// Unfortunately, Postgres queries will fail if a non-UUID is passed into a query for a UUID column. For example:
   179  	//
   180  	//    SELECT uuid, data, dataType FROM stored_requests WHERE uuid IN ('abc');
   181  	//
   182  	// Since users can send us strings which are _not_ UUIDs, and we don't want the code to assume anything about
   183  	// the database schema, we can just convert these into standard NotFoundErrors.
   184  	if pqErr, ok := err.(*pq.Error); ok && string(pqErr.Code) == "22P02" {
   185  		return true
   186  	}
   187  
   188  	return false
   189  }