github.com/royge/pop@v4.13.1+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  }