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 }