go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/connection.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package db 9 10 import ( 11 "context" 12 "database/sql" 13 "reflect" 14 "sync" 15 "time" 16 ) 17 18 // New returns a new Connection. 19 // It will use very bare bones defaults for the config. 20 func New(options ...Option) (*Connection, error) { 21 var c Connection 22 var err error 23 for _, opt := range options { 24 if err = opt(&c); err != nil { 25 return nil, err 26 } 27 } 28 return &c, nil 29 } 30 31 // MustNew returns a new connection and panics on error. 32 func MustNew(options ...Option) *Connection { 33 c, err := New(options...) 34 if err != nil { 35 panic(err) 36 } 37 return c 38 } 39 40 // Connection is a 41 type Connection struct { 42 Config 43 44 conn *sql.DB 45 bp *BufferPool 46 mcmu sync.RWMutex 47 mc map[string]*TypeMeta 48 onQuery []func(QueryEvent) 49 } 50 51 // RegisterOnQuery adds an on query listener. 52 func (dbc *Connection) RegisterOnQuery(fn func(QueryEvent)) { 53 dbc.onQuery = append(dbc.onQuery, fn) 54 } 55 56 // Stats returns the stats for the connection. 57 func (dbc *Connection) Stats() sql.DBStats { 58 return dbc.conn.Stats() 59 } 60 61 // Open returns a connection object, either a cached connection object or creating a new one in the process. 62 func (dbc *Connection) Open() error { 63 // bail if we've already opened the connection. 64 if dbc.conn != nil { 65 return ErrConnectionAlreadyOpen 66 } 67 if dbc.Config.IsZero() { 68 return ErrConfigUnset 69 } 70 if dbc.bp == nil { 71 dbc.bp = NewBufferPool(dbc.Config.BufferPoolSize) 72 } 73 if dbc.mc == nil { 74 dbc.mc = make(map[string]*TypeMeta) 75 } 76 77 dbConn, err := sql.Open(dbc.Config.Engine, dbc.Config.CreateDSN()) 78 if err != nil { 79 return err 80 } 81 dbc.conn = dbConn 82 dbc.conn.SetConnMaxLifetime(dbc.Config.MaxLifetime) 83 dbc.conn.SetConnMaxIdleTime(dbc.Config.MaxIdleTime) 84 dbc.conn.SetMaxIdleConns(dbc.Config.IdleConnections) 85 dbc.conn.SetMaxOpenConns(dbc.Config.MaxConnections) 86 return nil 87 } 88 89 // Close implements a closer. 90 func (dbc *Connection) Close() error { 91 return dbc.conn.Close() 92 } 93 94 // TypeMeta returns the TypeMeta for an object. 95 func (dbc *Connection) TypeMeta(object any) *TypeMeta { 96 objectType := reflect.TypeOf(object) 97 return dbc.TypeMetaFromType(newColumnCacheKey(objectType), objectType) 98 } 99 100 // TypeMetaFromType reflects a reflect.Type into a column collection. 101 // The results of this are cached for speed. 102 func (dbc *Connection) TypeMetaFromType(identifier string, t reflect.Type) *TypeMeta { 103 dbc.mcmu.RLock() 104 if value, ok := dbc.mc[identifier]; ok { 105 dbc.mcmu.RUnlock() 106 return value 107 } 108 dbc.mcmu.RUnlock() 109 110 dbc.mcmu.Lock() 111 defer dbc.mcmu.Unlock() 112 113 // check again once exclusive is acquired 114 // because we may have been waiting on another routine 115 // to set the identifier. 116 if value, ok := dbc.mc[identifier]; ok { 117 return value 118 } 119 120 metadata := NewTypeMetaFromColumns(generateColumnsForType(nil, t)...) 121 dbc.mc[identifier] = metadata 122 return metadata 123 } 124 125 // BeginTx starts a new transaction in a givent context. 126 func (dbc *Connection) BeginTx(ctx context.Context, opts ...func(*sql.TxOptions)) (*sql.Tx, error) { 127 if dbc.conn == nil { 128 return nil, ErrConnectionClosed 129 } 130 var txOptions sql.TxOptions 131 for _, opt := range opts { 132 opt(&txOptions) 133 } 134 tx, err := dbc.conn.BeginTx(ctx, &txOptions) 135 return tx, err 136 } 137 138 // Prepare prepares a statement within a given context. 139 // If a tx is provided, the tx is the target for the prepare. 140 // This will trigger tracing on prepare. 141 func (dbc *Connection) Prepare(ctx context.Context, statement string) (stmt *sql.Stmt, err error) { 142 stmt, err = dbc.conn.PrepareContext(ctx, statement) 143 return 144 } 145 146 // Invoke returns a new invocation. 147 func (dbc *Connection) Invoke(options ...InvocationOption) *Invocation { 148 i := Invocation{ 149 conn: dbc, 150 ctx: context.Background(), 151 started: time.Now().UTC(), 152 } 153 // we do a separate nil check here 154 // so that we don't end up with a non-nil pointer to nil 155 if dbc.conn != nil { 156 i.db = dbc.conn 157 } 158 for _, option := range options { 159 option(&i) 160 } 161 return &i 162 } 163 164 // Exec is a helper stub for .Invoke(...).Exec(...). 165 func (dbc *Connection) Exec(statement string, args ...interface{}) (sql.Result, error) { 166 return dbc.Invoke().Exec(statement, args...) 167 } 168 169 // ExecContext is a helper stub for .Invoke(OptContext(ctx)).Exec(...). 170 func (dbc *Connection) ExecContext(ctx context.Context, statement string, args ...interface{}) (sql.Result, error) { 171 return dbc.Invoke(OptContext(ctx)).Exec(statement, args...) 172 } 173 174 // Query is a helper stub for .Invoke(...).Query(...). 175 func (dbc *Connection) Query(statement string, args ...interface{}) *Query { 176 return dbc.Invoke().Query(statement, args...) 177 } 178 179 // QueryContext is a helper stub for .Invoke(OptContext(ctx)).Query(...). 180 func (dbc *Connection) QueryContext(ctx context.Context, statement string, args ...interface{}) *Query { 181 return dbc.Invoke(OptContext(ctx)).Query(statement, args...) 182 }