175 lines
4.1 KiB
Go
175 lines
4.1 KiB
Go
package dbr
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gocraft/dbr/dialect"
|
|
)
|
|
|
|
// Open instantiates a Connection for a given database/sql connection
|
|
// and event receiver
|
|
func Open(driver, dsn string, log EventReceiver) (*Connection, error) {
|
|
if log == nil {
|
|
log = nullReceiver
|
|
}
|
|
conn, err := sql.Open(driver, dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var d Dialect
|
|
switch driver {
|
|
case "mysql":
|
|
d = dialect.MySQL
|
|
case "postgres":
|
|
d = dialect.PostgreSQL
|
|
case "sqlite3":
|
|
d = dialect.SQLite3
|
|
default:
|
|
return nil, ErrNotSupported
|
|
}
|
|
return &Connection{DB: conn, EventReceiver: log, Dialect: d}, nil
|
|
}
|
|
|
|
const (
|
|
placeholder = "?"
|
|
)
|
|
|
|
// Connection is a connection to the database with an EventReceiver
|
|
// to send events, errors, and timings to
|
|
type Connection struct {
|
|
*sql.DB
|
|
Dialect Dialect
|
|
EventReceiver
|
|
}
|
|
|
|
// Session represents a business unit of execution for some connection
|
|
type Session struct {
|
|
*Connection
|
|
EventReceiver
|
|
Timeout time.Duration
|
|
}
|
|
|
|
func (s *Session) GetTimeout() time.Duration {
|
|
return s.Timeout
|
|
}
|
|
|
|
// NewSession instantiates a Session for the Connection
|
|
func (conn *Connection) NewSession(log EventReceiver) *Session {
|
|
if log == nil {
|
|
log = conn.EventReceiver // Use parent instrumentation
|
|
}
|
|
return &Session{Connection: conn, EventReceiver: log}
|
|
}
|
|
|
|
// Ensure that tx and session are session runner
|
|
var (
|
|
_ SessionRunner = (*Tx)(nil)
|
|
_ SessionRunner = (*Session)(nil)
|
|
)
|
|
|
|
// SessionRunner can do anything that a Session can except start a transaction.
|
|
type SessionRunner interface {
|
|
Select(column ...string) *SelectBuilder
|
|
SelectBySql(query string, value ...interface{}) *SelectBuilder
|
|
|
|
InsertInto(table string) *InsertBuilder
|
|
InsertBySql(query string, value ...interface{}) *InsertBuilder
|
|
|
|
Update(table string) *UpdateBuilder
|
|
UpdateBySql(query string, value ...interface{}) *UpdateBuilder
|
|
|
|
DeleteFrom(table string) *DeleteBuilder
|
|
DeleteBySql(query string, value ...interface{}) *DeleteBuilder
|
|
}
|
|
|
|
type runner interface {
|
|
GetTimeout() time.Duration
|
|
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
|
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
|
}
|
|
|
|
func exec(ctx context.Context, runner runner, log EventReceiver, builder Builder, d Dialect) (sql.Result, error) {
|
|
timeout := runner.GetTimeout()
|
|
if timeout > 0 {
|
|
var cancel func()
|
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
}
|
|
|
|
i := interpolator{
|
|
Buffer: NewBuffer(),
|
|
Dialect: d,
|
|
IgnoreBinary: true,
|
|
}
|
|
err := i.interpolate(placeholder, []interface{}{builder})
|
|
query, value := i.String(), i.Value()
|
|
if err != nil {
|
|
return nil, log.EventErrKv("dbr.exec.interpolate", err, kvs{
|
|
"sql": query,
|
|
"args": fmt.Sprint(value),
|
|
})
|
|
}
|
|
|
|
startTime := time.Now()
|
|
defer func() {
|
|
log.TimingKv("dbr.exec", time.Since(startTime).Nanoseconds(), kvs{
|
|
"sql": query,
|
|
})
|
|
}()
|
|
|
|
result, err := runner.ExecContext(ctx, query, value...)
|
|
if err != nil {
|
|
return result, log.EventErrKv("dbr.exec.exec", err, kvs{
|
|
"sql": query,
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func query(ctx context.Context, runner runner, log EventReceiver, builder Builder, d Dialect, dest interface{}) (int, error) {
|
|
timeout := runner.GetTimeout()
|
|
if timeout > 0 {
|
|
var cancel func()
|
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
}
|
|
|
|
i := interpolator{
|
|
Buffer: NewBuffer(),
|
|
Dialect: d,
|
|
IgnoreBinary: true,
|
|
}
|
|
err := i.interpolate(placeholder, []interface{}{builder})
|
|
query, value := i.String(), i.Value()
|
|
if err != nil {
|
|
return 0, log.EventErrKv("dbr.select.interpolate", err, kvs{
|
|
"sql": query,
|
|
"args": fmt.Sprint(value),
|
|
})
|
|
}
|
|
|
|
startTime := time.Now()
|
|
defer func() {
|
|
log.TimingKv("dbr.select", time.Since(startTime).Nanoseconds(), kvs{
|
|
"sql": query,
|
|
})
|
|
}()
|
|
|
|
rows, err := runner.QueryContext(ctx, query, value...)
|
|
if err != nil {
|
|
return 0, log.EventErrKv("dbr.select.load.query", err, kvs{
|
|
"sql": query,
|
|
})
|
|
}
|
|
count, err := Load(rows, dest)
|
|
if err != nil {
|
|
return 0, log.EventErrKv("dbr.select.load.scan", err, kvs{
|
|
"sql": query,
|
|
})
|
|
}
|
|
return count, nil
|
|
}
|