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 }