github.com/tsmith1024/pop@v4.12.2+incompatible/connection.go (about) 1 package pop 2 3 import ( 4 "sync/atomic" 5 "time" 6 7 "github.com/gobuffalo/pop/internal/defaults" 8 "github.com/gobuffalo/pop/internal/randx" 9 "github.com/jmoiron/sqlx" 10 "github.com/pkg/errors" 11 ) 12 13 // Connections contains all available connections 14 var Connections = map[string]*Connection{} 15 16 // Connection represents all necessary details to talk with a datastore 17 type Connection struct { 18 ID string 19 Store store 20 Dialect dialect 21 Elapsed int64 22 TX *Tx 23 eager bool 24 eagerFields []string 25 } 26 27 func (c *Connection) String() string { 28 return c.URL() 29 } 30 31 // URL returns the datasource connection string 32 func (c *Connection) URL() string { 33 return c.Dialect.URL() 34 } 35 36 // MigrationURL returns the datasource connection string used for running the migrations 37 func (c *Connection) MigrationURL() string { 38 return c.Dialect.MigrationURL() 39 } 40 41 // MigrationTableName returns the name of the table to track migrations 42 func (c *Connection) MigrationTableName() string { 43 return c.Dialect.Details().MigrationTableName() 44 } 45 46 // NewConnection creates a new connection, and sets it's `Dialect` 47 // appropriately based on the `ConnectionDetails` passed into it. 48 func NewConnection(deets *ConnectionDetails) (*Connection, error) { 49 err := deets.Finalize() 50 if err != nil { 51 return nil, err 52 } 53 c := &Connection{ 54 ID: randx.String(30), 55 } 56 57 if nc, ok := newConnection[deets.Dialect]; ok { 58 c.Dialect, err = nc(deets) 59 if err != nil { 60 return c, errors.Wrap(err, "could not create new connection") 61 } 62 return c, nil 63 } 64 return nil, errors.Errorf("could not found connection creator for %v", deets.Dialect) 65 } 66 67 // Connect takes the name of a connection, default is "development", and will 68 // return that connection from the available `Connections`. If a connection with 69 // that name can not be found an error will be returned. If a connection is 70 // found, and it has yet to open a connection with its underlying datastore, 71 // a connection to that store will be opened. 72 func Connect(e string) (*Connection, error) { 73 if len(Connections) == 0 { 74 err := LoadConfigFile() 75 if err != nil { 76 return nil, err 77 } 78 } 79 e = defaults.String(e, "development") 80 c := Connections[e] 81 if c == nil { 82 return c, errors.Errorf("could not find connection named %s", e) 83 } 84 err := c.Open() 85 return c, errors.Wrapf(err, "couldn't open connection for %s", e) 86 } 87 88 // Open creates a new datasource connection 89 func (c *Connection) Open() error { 90 if c.Store != nil { 91 return nil 92 } 93 if c.Dialect == nil { 94 return errors.New("invalid connection instance") 95 } 96 details := c.Dialect.Details() 97 driver := details.Dialect 98 if details.Driver != "" { 99 driver = details.Driver 100 } 101 102 db, err := sqlx.Open(driver, c.Dialect.URL()) 103 if err != nil { 104 return errors.Wrap(err, "could not open database connection") 105 } 106 db.SetMaxOpenConns(details.Pool) 107 if details.IdlePool != 0 { 108 db.SetMaxIdleConns(details.IdlePool) 109 } 110 if details.ConnMaxLifetime > 0 { 111 db.SetConnMaxLifetime(details.ConnMaxLifetime) 112 } 113 c.Store = &dB{db} 114 115 if d, ok := c.Dialect.(afterOpenable); ok { 116 err = d.AfterOpen(c) 117 if err != nil { 118 c.Store = nil 119 } 120 } 121 return errors.Wrap(err, "could not open database connection") 122 } 123 124 // Close destroys an active datasource connection 125 func (c *Connection) Close() error { 126 return errors.Wrap(c.Store.Close(), "couldn't close connection") 127 } 128 129 // Transaction will start a new transaction on the connection. If the inner function 130 // returns an error then the transaction will be rolled back, otherwise the transaction 131 // will automatically commit at the end. 132 func (c *Connection) Transaction(fn func(tx *Connection) error) error { 133 return c.Dialect.Lock(func() error { 134 var dberr error 135 cn, err := c.NewTransaction() 136 if err != nil { 137 return err 138 } 139 err = fn(cn) 140 if err != nil { 141 dberr = cn.TX.Rollback() 142 } else { 143 dberr = cn.TX.Commit() 144 } 145 if err != nil { 146 return err 147 } 148 return errors.Wrap(dberr, "error committing or rolling back transaction") 149 }) 150 151 } 152 153 // Rollback will open a new transaction and automatically rollback that transaction 154 // when the inner function returns, regardless. This can be useful for tests, etc... 155 func (c *Connection) Rollback(fn func(tx *Connection)) error { 156 cn, err := c.NewTransaction() 157 if err != nil { 158 return err 159 } 160 fn(cn) 161 return cn.TX.Rollback() 162 } 163 164 // NewTransaction starts a new transaction on the connection 165 func (c *Connection) NewTransaction() (*Connection, error) { 166 var cn *Connection 167 if c.TX == nil { 168 tx, err := c.Store.Transaction() 169 if err != nil { 170 return cn, errors.Wrap(err, "couldn't start a new transaction") 171 } 172 cn = &Connection{ 173 ID: randx.String(30), 174 Store: tx, 175 Dialect: c.Dialect, 176 TX: tx, 177 } 178 } else { 179 cn = c 180 } 181 return cn, nil 182 } 183 184 func (c *Connection) copy() *Connection { 185 return &Connection{ 186 ID: randx.String(30), 187 Store: c.Store, 188 Dialect: c.Dialect, 189 TX: c.TX, 190 } 191 } 192 193 // Q creates a new "empty" query for the current connection. 194 func (c *Connection) Q() *Query { 195 return Q(c) 196 } 197 198 // disableEager disables eager mode for current connection. 199 func (c *Connection) disableEager() { 200 c.eager = false 201 c.eagerFields = []string{} 202 } 203 204 // TruncateAll truncates all data from the datasource 205 func (c *Connection) TruncateAll() error { 206 return c.Dialect.TruncateAll(c) 207 } 208 209 func (c *Connection) timeFunc(name string, fn func() error) error { 210 start := time.Now() 211 err := fn() 212 atomic.AddInt64(&c.Elapsed, int64(time.Since(start))) 213 if err != nil { 214 return err 215 } 216 return nil 217 }