github.com/blend/go-sdk@v1.20220411.3/db/connection.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package db
     9  
    10  import (
    11  	"context"
    12  	"database/sql"
    13  
    14  	"github.com/blend/go-sdk/async"
    15  	"github.com/blend/go-sdk/bufferutil"
    16  	"github.com/blend/go-sdk/ex"
    17  	"github.com/blend/go-sdk/logger"
    18  )
    19  
    20  var (
    21  	_ async.Checker = (*Connection)(nil)
    22  )
    23  
    24  // --------------------------------------------------------------------------------
    25  // Connection
    26  // --------------------------------------------------------------------------------
    27  
    28  // New returns a new Connection.
    29  // It will use very bare bones defaults for the config.
    30  func New(options ...Option) (*Connection, error) {
    31  	var c Connection
    32  	var err error
    33  	for _, opt := range options {
    34  		if err = opt(&c); err != nil {
    35  			return nil, err
    36  		}
    37  	}
    38  	return &c, nil
    39  }
    40  
    41  // MustNew returns a new connection and panics on error.
    42  func MustNew(options ...Option) *Connection {
    43  	c, err := New(options...)
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  	return c
    48  }
    49  
    50  // Open opens a connection, testing an error and returning it if not nil, and if nil, opening the connection.
    51  // It's designed ot be used in conjunction with a constructor, i.e.
    52  //    conn, err := db.Open(db.NewFromConfig(cfg))
    53  func Open(conn *Connection, err error) (*Connection, error) {
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	if err = conn.Open(); err != nil {
    58  		return nil, err
    59  	}
    60  	return conn, nil
    61  }
    62  
    63  // Connection is the basic wrapper for connection parameters and saves a reference to the created sql.Connection.
    64  type Connection struct {
    65  	Connection           *sql.DB
    66  	BufferPool           *bufferutil.Pool
    67  	Config               Config
    68  	Log                  logger.Log
    69  	Tracer               Tracer
    70  	StatementInterceptor StatementInterceptor
    71  }
    72  
    73  // Close implements a closer.
    74  func (dbc *Connection) Close() error {
    75  	return dbc.Connection.Close()
    76  }
    77  
    78  // Open returns a connection object, either a cached connection object or creating a new one in the process.
    79  func (dbc *Connection) Open() error {
    80  	// bail if we've already opened the connection.
    81  	if dbc.Connection != nil {
    82  		return Error(ErrConnectionAlreadyOpen)
    83  	}
    84  	if dbc.Config.IsZero() {
    85  		return Error(ErrConfigUnset)
    86  	}
    87  	if dbc.BufferPool == nil {
    88  		dbc.BufferPool = bufferutil.NewPool(dbc.Config.BufferPoolSizeOrDefault())
    89  	}
    90  
    91  	dsn := dbc.Config.CreateDSN()
    92  	namedValues, err := ParseURL(dsn)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	// open the connection
    98  	dbConn, err := sql.Open(dbc.Config.EngineOrDefault(), namedValues)
    99  	if err != nil {
   100  		return Error(err)
   101  	}
   102  
   103  	dbc.Connection = dbConn
   104  	dbc.Connection.SetConnMaxLifetime(dbc.Config.MaxLifetimeOrDefault())
   105  	dbc.Connection.SetConnMaxIdleTime(dbc.Config.MaxIdleTimeOrDefault())
   106  	dbc.Connection.SetMaxIdleConns(dbc.Config.IdleConnectionsOrDefault())
   107  	dbc.Connection.SetMaxOpenConns(dbc.Config.MaxConnectionsOrDefault())
   108  	return nil
   109  }
   110  
   111  // Begin starts a new transaction.
   112  func (dbc *Connection) Begin(opts ...func(*sql.TxOptions)) (*sql.Tx, error) {
   113  	if dbc.Connection == nil {
   114  		return nil, ex.New(ErrConnectionClosed)
   115  	}
   116  	return dbc.BeginContext(context.Background(), opts...)
   117  }
   118  
   119  // BeginContext starts a new transaction in a givent context.
   120  func (dbc *Connection) BeginContext(ctx context.Context, opts ...func(*sql.TxOptions)) (*sql.Tx, error) {
   121  	if dbc.Connection == nil {
   122  		return nil, ex.New(ErrConnectionClosed)
   123  	}
   124  	var txOptions sql.TxOptions
   125  	for _, opt := range opts {
   126  		opt(&txOptions)
   127  	}
   128  	tx, err := dbc.Connection.BeginTx(ctx, &txOptions)
   129  	return tx, Error(err)
   130  }
   131  
   132  // PrepareContext prepares a statement within a given context.
   133  // If a tx is provided, the tx is the target for the prepare.
   134  // This will trigger tracing on prepare.
   135  func (dbc *Connection) PrepareContext(ctx context.Context, statement string, tx *sql.Tx) (stmt *sql.Stmt, err error) {
   136  	if dbc.Tracer != nil {
   137  		tf := dbc.Tracer.Prepare(ctx, dbc.Config, statement)
   138  		if tf != nil {
   139  			defer func() { tf.FinishPrepare(ctx, err) }()
   140  		}
   141  	}
   142  	if tx != nil {
   143  		stmt, err = tx.PrepareContext(ctx, statement)
   144  		return
   145  	}
   146  	stmt, err = dbc.Connection.PrepareContext(ctx, statement)
   147  	return
   148  }
   149  
   150  // --------------------------------------------------------------------------------
   151  // Invocation
   152  // --------------------------------------------------------------------------------
   153  
   154  // Invoke returns a new invocation.
   155  func (dbc *Connection) Invoke(options ...InvocationOption) *Invocation {
   156  	i := Invocation{
   157  		Config:               dbc.Config,
   158  		BufferPool:           dbc.BufferPool,
   159  		Context:              context.Background(),
   160  		Log:                  dbc.Log,
   161  		Tracer:               dbc.Tracer,
   162  		StatementInterceptor: dbc.StatementInterceptor,
   163  	}
   164  	if dbc.Connection != nil {
   165  		i.DB = dbc.Connection
   166  	}
   167  	for _, option := range options {
   168  		option(&i)
   169  	}
   170  	return &i
   171  }
   172  
   173  // Exec is a helper stub for .Invoke(...).Exec(...).
   174  func (dbc *Connection) Exec(statement string, args ...interface{}) (sql.Result, error) {
   175  	return dbc.Invoke().Exec(statement, args...)
   176  }
   177  
   178  // ExecContext is a helper stub for .Invoke(OptContext(ctx)).Exec(...).
   179  func (dbc *Connection) ExecContext(ctx context.Context, statement string, args ...interface{}) (sql.Result, error) {
   180  	return dbc.Invoke(OptContext(ctx)).Exec(statement, args...)
   181  }
   182  
   183  // Query is a helper stub for .Invoke(...).Query(...).
   184  func (dbc *Connection) Query(statement string, args ...interface{}) *Query {
   185  	return dbc.Invoke().Query(statement, args...)
   186  }
   187  
   188  // QueryContext is a helper stub for .Invoke(OptContext(ctx)).Query(...).
   189  func (dbc *Connection) QueryContext(ctx context.Context, statement string, args ...interface{}) *Query {
   190  	return dbc.Invoke(OptContext(ctx)).Query(statement, args...)
   191  }
   192  
   193  // Check implements a status check.
   194  func (dbc *Connection) Check(ctx context.Context) error {
   195  	_, err := dbc.QueryContext(ctx, "select 1").Any()
   196  	return err
   197  }