Skip to content

Commit

Permalink
Merge pull request #294 from upper/issue-287
Browse files Browse the repository at this point in the history
Make prepared statement cache optional (and disabled by default)
  • Loading branch information
José Carlos authored Dec 11, 2016
2 parents 11fc27e + 142e33b commit a3ef9f0
Show file tree
Hide file tree
Showing 19 changed files with 364 additions and 161 deletions.
44 changes: 35 additions & 9 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,18 @@ type Settings interface {
SetLogger(Logger)
// Returns the currently configured logger.
Logger() Logger

// SetPreparedStatementCache enables or disables the prepared statement
// cache.
SetPreparedStatementCache(bool)
// PreparedStatementCacheEnabled returns true if the prepared statement cache
// is enabled, false otherwise.
PreparedStatementCacheEnabled() bool
}

type conf struct {
loggingEnabled uint32
loggingEnabled uint32
preparedStatementCacheEnabled uint32

queryLogger Logger
queryLoggerMu sync.RWMutex
Expand All @@ -65,20 +73,38 @@ func (c *conf) SetLogger(lg Logger) {
c.queryLogger = lg
}

func (c *conf) SetLogging(value bool) {
func (c *conf) binaryOption(opt *uint32) bool {
if atomic.LoadUint32(opt) == 1 {
return true
}
return false
}

func (c *conf) setBinaryOption(opt *uint32, value bool) {
if value {
atomic.StoreUint32(&c.loggingEnabled, 1)
atomic.StoreUint32(opt, 1)
return
}
atomic.StoreUint32(&c.loggingEnabled, 0)
atomic.StoreUint32(opt, 0)
}

func (c *conf) SetLogging(value bool) {
c.setBinaryOption(&c.loggingEnabled, value)
}

func (c *conf) LoggingEnabled() bool {
if v := atomic.LoadUint32(&c.loggingEnabled); v == 1 {
return true
}
return false
return c.binaryOption(&c.loggingEnabled)
}

func (c *conf) SetPreparedStatementCache(value bool) {
c.setBinaryOption(&c.preparedStatementCacheEnabled, value)
}

func (c *conf) PreparedStatementCacheEnabled() bool {
return c.binaryOption(&c.preparedStatementCacheEnabled)
}

// Conf provides global configuration settings for upper-db.
var Conf Settings = &conf{}
var Conf Settings = &conf{
preparedStatementCacheEnabled: 0,
}
12 changes: 12 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,18 @@ type Database interface {

// ClearCache clears all the cache mechanisms the adapter is using.
ClearCache()

// SetConnMaxLifetime sets the maximum amount of time a connection may be
// reused.
SetConnMaxLifetime(time.Duration)

// SetMaxIdleConns sets the maximum number of connections in the idle
// connection pool.
SetMaxIdleConns(int)

// SetMaxOpenConns sets the maximum number of open connections to the
// database.
SetMaxOpenConns(int)
}

// Tx has methods for transactions that can be either committed or rolled back.
Expand Down
165 changes: 132 additions & 33 deletions internal/sqladapter/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type HasCleanUp interface {

// HasStatementExec allows the adapter to have its own exec statement.
type HasStatementExec interface {
StatementExec(stmt *sql.Stmt, args ...interface{}) (sql.Result, error)
StatementExec(query string, args ...interface{}) (sql.Result, error)
}

// Database represents a SQL database.
Expand Down Expand Up @@ -73,6 +73,12 @@ type BaseDatabase interface {

BindTx(*sql.Tx) error
Transaction() BaseTx

SetConnMaxLifetime(time.Duration)
SetMaxIdleConns(int)
SetMaxOpenConns(int)

BindClone(PartialDatabase) (BaseDatabase, error)
}

// NewBaseDatabase provides a BaseDatabase given a PartialDatabase
Expand All @@ -98,6 +104,8 @@ type database struct {
sess *sql.DB
sessMu sync.Mutex

psMu sync.Mutex

sessID uint64
txID uint64

Expand Down Expand Up @@ -178,6 +186,30 @@ func (d *database) Ping() error {
return nil
}

// SetConnMaxLifetime sets the maximum amount of time a connection may be
// reused.
func (d *database) SetConnMaxLifetime(t time.Duration) {
if sess := d.Session(); sess != nil {
sess.SetConnMaxLifetime(t)
}
}

// SetMaxIdleConns sets the maximum number of connections in the idle
// connection pool.
func (d *database) SetMaxIdleConns(n int) {
if sess := d.Session(); sess != nil {
sess.SetMaxIdleConns(n)
}
}

// SetMaxOpenConns sets the maximum number of open connections to the
// database.
func (d *database) SetMaxOpenConns(n int) {
if sess := d.Session(); sess != nil {
sess.SetMaxOpenConns(n)
}
}

// ClearCache removes all caches.
func (d *database) ClearCache() {
d.collectionMu.Lock()
Expand All @@ -189,6 +221,20 @@ func (d *database) ClearCache() {
}
}

// BindClone binds a clone that is linked to the current
// session. This is commonly done before creating a transaction
// session.
func (d *database) BindClone(p PartialDatabase) (BaseDatabase, error) {
nd := NewBaseDatabase(p).(*database)
nd.name = d.name
nd.sess = d.sess
if err := nd.Ping(); err != nil {
return nil, err
}
nd.sessID = newSessionID()
return nd, nil
}

// Close terminates the current database session
func (d *database) Close() error {
defer func() {
Expand All @@ -201,6 +247,7 @@ func (d *database) Close() error {
if cleaner, ok := d.PartialDatabase.(HasCleanUp); ok {
cleaner.CleanUp()
}

d.cachedCollections.Clear()
d.cachedStatements.Clear() // Closes prepared statements as well.

Expand All @@ -212,6 +259,7 @@ func (d *database) Close() error {

if !tx.Committed() {
tx.Rollback()
return nil
}
}
return nil
Expand Down Expand Up @@ -267,18 +315,33 @@ func (d *database) StatementExec(stmt *exql.Statement, args ...interface{}) (res
}(time.Now())
}

var p *Stmt
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
if execer, ok := d.PartialDatabase.(HasStatementExec); ok {
query = d.compileStatement(stmt)
res, err = execer.StatementExec(query, args...)
return
}
defer p.Close()

if execer, ok := d.PartialDatabase.(HasStatementExec); ok {
res, err = execer.StatementExec(p.Stmt, args...)
tx := d.Transaction()

if db.Conf.PreparedStatementCacheEnabled() && tx == nil {
var p *Stmt
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
}
defer p.Close()

res, err = p.Exec(args...)
return
}

query = d.compileStatement(stmt)

if tx != nil {
res, err = tx.(*sqlTx).Exec(query, args...)
return
}

res, err = p.Exec(args...)
res, err = d.sess.Exec(query, args...)
return
}

Expand All @@ -300,14 +363,28 @@ func (d *database) StatementQuery(stmt *exql.Statement, args ...interface{}) (ro
}(time.Now())
}

var p *Stmt
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
tx := d.Transaction()

if db.Conf.PreparedStatementCacheEnabled() && tx == nil {
var p *Stmt
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
}
defer p.Close()

rows, err = p.Query(args...)
return
}

query = d.compileStatement(stmt)
if tx != nil {
rows, err = tx.(*sqlTx).Query(query, args...)
return
}
defer p.Close()

rows, err = p.Query(args...)
rows, err = d.sess.Query(query, args...)
return

}

// StatementQueryRow compiles and executes a statement that returns at most one
Expand All @@ -329,13 +406,26 @@ func (d *database) StatementQueryRow(stmt *exql.Statement, args ...interface{})
}(time.Now())
}

var p *Stmt
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
tx := d.Transaction()

if db.Conf.PreparedStatementCacheEnabled() && tx == nil {
var p *Stmt
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
}
defer p.Close()

row = p.QueryRow(args...)
return
}
defer p.Close()

row, err = p.QueryRow(args...), nil
query = d.compileStatement(stmt)
if tx != nil {
row = tx.(*sqlTx).QueryRow(query, args...)
return
}

row = d.sess.QueryRow(query, args...)
return
}

Expand All @@ -348,11 +438,19 @@ func (d *database) Driver() interface{} {
return d.sess
}

// prepareStatement converts a *exql.Statement representation into an actual
// *sql.Stmt. This method will attempt to used a cached prepared statement, if
// available.
// compileStatement compiles the given statement into a string.
func (d *database) compileStatement(stmt *exql.Statement) string {
return d.PartialDatabase.CompileStatement(stmt)
}

// prepareStatement compiles a query and tries to use previously generated
// statement.
func (d *database) prepareStatement(stmt *exql.Statement) (*Stmt, string, error) {
if d.sess == nil && d.Transaction() == nil {
d.sessMu.Lock()
defer d.sessMu.Unlock()

sess, tx := d.sess, d.Transaction()
if sess == nil && tx == nil {
return nil, "", db.ErrNotConnected
}

Expand All @@ -365,22 +463,23 @@ func (d *database) prepareStatement(stmt *exql.Statement) (*Stmt, string, error)
}
}

// Plain SQL query.
query := d.PartialDatabase.CompileStatement(stmt)

sqlStmt, err := func() (*sql.Stmt, error) {
if d.Transaction() != nil {
return d.Transaction().(*sqlTx).Prepare(query)
query := d.compileStatement(stmt)
sqlStmt, err := func(query *string) (*sql.Stmt, error) {
if tx != nil {
return tx.(*sqlTx).Prepare(*query)
}
return d.sess.Prepare(query)
}()
return sess.Prepare(*query)
}(&query)
if err != nil {
return nil, query, err
return nil, "", err
}

p := NewStatement(sqlStmt, query)
p, err := NewStatement(sqlStmt, query).Open()
if err != nil {
return nil, query, err
}
d.cachedStatements.Write(stmt, p)
return p, query, nil
return p, p.query, nil
}

var waitForConnMu sync.Mutex
Expand Down
Loading

0 comments on commit a3ef9f0

Please sign in to comment.