Adding upstream version 1.11.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
40df376a7f
commit
02cacc5b45
37 changed files with 7317 additions and 0 deletions
338
db.go
Normal file
338
db.go
Normal file
|
@ -0,0 +1,338 @@
|
|||
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package dbx provides a set of DB-agnostic and easy-to-use query building methods for relational databases.
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// LogFunc logs a message for each SQL statement being executed.
|
||||
// This method takes one or multiple parameters. If a single parameter
|
||||
// is provided, it will be treated as the log message. If multiple parameters
|
||||
// are provided, they will be passed to fmt.Sprintf() to generate the log message.
|
||||
LogFunc func(format string, a ...interface{})
|
||||
|
||||
// PerfFunc is called when a query finishes execution.
|
||||
// The query execution time is passed to this function so that the DB performance
|
||||
// can be profiled. The "ns" parameter gives the number of nanoseconds that the
|
||||
// SQL statement takes to execute, while the "execute" parameter indicates whether
|
||||
// the SQL statement is executed or queried (usually SELECT statements).
|
||||
PerfFunc func(ns int64, sql string, execute bool)
|
||||
|
||||
// QueryLogFunc is called each time when performing a SQL query.
|
||||
// The "t" parameter gives the time that the SQL statement takes to execute,
|
||||
// while rows and err are the result of the query.
|
||||
QueryLogFunc func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error)
|
||||
|
||||
// ExecLogFunc is called each time when a SQL statement is executed.
|
||||
// The "t" parameter gives the time that the SQL statement takes to execute,
|
||||
// while result and err refer to the result of the execution.
|
||||
ExecLogFunc func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error)
|
||||
|
||||
// BuilderFunc creates a Builder instance using the given DB instance and Executor.
|
||||
BuilderFunc func(*DB, Executor) Builder
|
||||
|
||||
// DB enhances sql.DB by providing a set of DB-agnostic query building methods.
|
||||
// DB allows easier query building and population of data into Go variables.
|
||||
DB struct {
|
||||
Builder
|
||||
|
||||
// FieldMapper maps struct fields to DB columns. Defaults to DefaultFieldMapFunc.
|
||||
FieldMapper FieldMapFunc
|
||||
// TableMapper maps structs to table names. Defaults to GetTableName.
|
||||
TableMapper TableMapFunc
|
||||
// LogFunc logs the SQL statements being executed. Defaults to nil, meaning no logging.
|
||||
LogFunc LogFunc
|
||||
// PerfFunc logs the SQL execution time. Defaults to nil, meaning no performance profiling.
|
||||
// Deprecated: Please use QueryLogFunc and ExecLogFunc instead.
|
||||
PerfFunc PerfFunc
|
||||
// QueryLogFunc is called each time when performing a SQL query that returns data.
|
||||
QueryLogFunc QueryLogFunc
|
||||
// ExecLogFunc is called each time when a SQL statement is executed.
|
||||
ExecLogFunc ExecLogFunc
|
||||
|
||||
sqlDB *sql.DB
|
||||
driverName string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Errors represents a list of errors.
|
||||
Errors []error
|
||||
)
|
||||
|
||||
// BuilderFuncMap lists supported BuilderFunc according to DB driver names.
|
||||
// You may modify this variable to add the builder support for a new DB driver.
|
||||
// If a DB driver is not listed here, the StandardBuilder will be used.
|
||||
var BuilderFuncMap = map[string]BuilderFunc{
|
||||
"sqlite": NewSqliteBuilder,
|
||||
"sqlite3": NewSqliteBuilder,
|
||||
"mysql": NewMysqlBuilder,
|
||||
"postgres": NewPgsqlBuilder,
|
||||
"pgx": NewPgsqlBuilder,
|
||||
"mssql": NewMssqlBuilder,
|
||||
"oci8": NewOciBuilder,
|
||||
}
|
||||
|
||||
// NewFromDB encapsulates an existing database connection.
|
||||
func NewFromDB(sqlDB *sql.DB, driverName string) *DB {
|
||||
db := &DB{
|
||||
driverName: driverName,
|
||||
sqlDB: sqlDB,
|
||||
FieldMapper: DefaultFieldMapFunc,
|
||||
TableMapper: GetTableName,
|
||||
}
|
||||
db.Builder = db.newBuilder(db.sqlDB)
|
||||
return db
|
||||
}
|
||||
|
||||
// Open opens a database specified by a driver name and data source name (DSN).
|
||||
// Note that Open does not check if DSN is specified correctly. It doesn't try to establish a DB connection either.
|
||||
// Please refer to sql.Open() for more information.
|
||||
func Open(driverName, dsn string) (*DB, error) {
|
||||
sqlDB, err := sql.Open(driverName, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewFromDB(sqlDB, driverName), nil
|
||||
}
|
||||
|
||||
// MustOpen opens a database and establishes a connection to it.
|
||||
// Please refer to sql.Open() and sql.Ping() for more information.
|
||||
func MustOpen(driverName, dsn string) (*DB, error) {
|
||||
db, err := Open(driverName, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.sqlDB.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Clone makes a shallow copy of DB.
|
||||
func (db *DB) Clone() *DB {
|
||||
db2 := &DB{
|
||||
driverName: db.driverName,
|
||||
sqlDB: db.sqlDB,
|
||||
FieldMapper: db.FieldMapper,
|
||||
TableMapper: db.TableMapper,
|
||||
PerfFunc: db.PerfFunc,
|
||||
LogFunc: db.LogFunc,
|
||||
QueryLogFunc: db.QueryLogFunc,
|
||||
ExecLogFunc: db.ExecLogFunc,
|
||||
}
|
||||
db2.Builder = db2.newBuilder(db.sqlDB)
|
||||
return db2
|
||||
}
|
||||
|
||||
// WithContext returns a new instance of DB associated with the given context.
|
||||
func (db *DB) WithContext(ctx context.Context) *DB {
|
||||
db2 := db.Clone()
|
||||
db2.ctx = ctx
|
||||
return db2
|
||||
}
|
||||
|
||||
// Context returns the context associated with the DB instance.
|
||||
// It returns nil if no context is associated.
|
||||
func (db *DB) Context() context.Context {
|
||||
return db.ctx
|
||||
}
|
||||
|
||||
// DB returns the sql.DB instance encapsulated by dbx.DB.
|
||||
func (db *DB) DB() *sql.DB {
|
||||
return db.sqlDB
|
||||
}
|
||||
|
||||
// Close closes the database, releasing any open resources.
|
||||
// It is rare to Close a DB, as the DB handle is meant to be
|
||||
// long-lived and shared between many goroutines.
|
||||
func (db *DB) Close() error {
|
||||
return db.sqlDB.Close()
|
||||
}
|
||||
|
||||
// Begin starts a transaction.
|
||||
func (db *DB) Begin() (*Tx, error) {
|
||||
var tx *sql.Tx
|
||||
var err error
|
||||
if db.ctx != nil {
|
||||
tx, err = db.sqlDB.BeginTx(db.ctx, nil)
|
||||
} else {
|
||||
tx, err = db.sqlDB.Begin()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{db.newBuilder(tx), tx}, nil
|
||||
}
|
||||
|
||||
// BeginTx starts a transaction with the given context and transaction options.
|
||||
func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||
tx, err := db.sqlDB.BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{db.newBuilder(tx), tx}, nil
|
||||
}
|
||||
|
||||
// Wrap encapsulates an existing transaction.
|
||||
func (db *DB) Wrap(sqlTx *sql.Tx) *Tx {
|
||||
return &Tx{db.newBuilder(sqlTx), sqlTx}
|
||||
}
|
||||
|
||||
// Transactional starts a transaction and executes the given function.
|
||||
// If the function returns an error, the transaction will be rolled back.
|
||||
// Otherwise, the transaction will be committed.
|
||||
func (db *DB) Transactional(f func(*Tx) error) (err error) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
tx.Rollback()
|
||||
panic(p)
|
||||
} else if err != nil {
|
||||
if err2 := tx.Rollback(); err2 != nil {
|
||||
if err2 == sql.ErrTxDone {
|
||||
return
|
||||
}
|
||||
err = Errors{err, err2}
|
||||
}
|
||||
} else {
|
||||
if err = tx.Commit(); err == sql.ErrTxDone {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = f(tx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// TransactionalContext starts a transaction and executes the given function with the given context and transaction options.
|
||||
// If the function returns an error, the transaction will be rolled back.
|
||||
// Otherwise, the transaction will be committed.
|
||||
func (db *DB) TransactionalContext(ctx context.Context, opts *sql.TxOptions, f func(*Tx) error) (err error) {
|
||||
tx, err := db.BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
tx.Rollback()
|
||||
panic(p)
|
||||
} else if err != nil {
|
||||
if err2 := tx.Rollback(); err2 != nil {
|
||||
if err2 == sql.ErrTxDone {
|
||||
return
|
||||
}
|
||||
err = Errors{err, err2}
|
||||
}
|
||||
} else {
|
||||
if err = tx.Commit(); err == sql.ErrTxDone {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = f(tx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DriverName returns the name of the DB driver.
|
||||
func (db *DB) DriverName() string {
|
||||
return db.driverName
|
||||
}
|
||||
|
||||
// QuoteTableName quotes the given table name appropriately.
|
||||
// If the table name contains DB schema prefix, it will be handled accordingly.
|
||||
// This method will do nothing if the table name is already quoted or if it contains parenthesis.
|
||||
func (db *DB) QuoteTableName(s string) string {
|
||||
if strings.Contains(s, "(") || strings.Contains(s, "{{") {
|
||||
return s
|
||||
}
|
||||
if !strings.Contains(s, ".") {
|
||||
return db.QuoteSimpleTableName(s)
|
||||
}
|
||||
parts := strings.Split(s, ".")
|
||||
for i, part := range parts {
|
||||
parts[i] = db.QuoteSimpleTableName(part)
|
||||
}
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
// QuoteColumnName quotes the given column name appropriately.
|
||||
// If the table name contains table name prefix, it will be handled accordingly.
|
||||
// This method will do nothing if the column name is already quoted or if it contains parenthesis.
|
||||
func (db *DB) QuoteColumnName(s string) string {
|
||||
if strings.Contains(s, "(") || strings.Contains(s, "{{") || strings.Contains(s, "[[") {
|
||||
return s
|
||||
}
|
||||
prefix := ""
|
||||
if pos := strings.LastIndex(s, "."); pos != -1 {
|
||||
prefix = db.QuoteTableName(s[:pos]) + "."
|
||||
s = s[pos+1:]
|
||||
}
|
||||
return prefix + db.QuoteSimpleColumnName(s)
|
||||
}
|
||||
|
||||
var (
|
||||
plRegex = regexp.MustCompile(`\{:\w+\}`)
|
||||
quoteRegex = regexp.MustCompile(`(\{\{[\w\-\. ]+\}\}|\[\[[\w\-\. ]+\]\])`)
|
||||
)
|
||||
|
||||
// processSQL replaces the named param placeholders in the given SQL with anonymous ones.
|
||||
// It also quotes table names and column names found in the SQL if these names are enclosed
|
||||
// within double square/curly brackets. The method will return the updated SQL and the list of parameter names.
|
||||
func (db *DB) processSQL(s string) (string, []string) {
|
||||
var placeholders []string
|
||||
count := 0
|
||||
s = plRegex.ReplaceAllStringFunc(s, func(m string) string {
|
||||
count++
|
||||
placeholders = append(placeholders, m[2:len(m)-1])
|
||||
return db.GeneratePlaceholder(count)
|
||||
})
|
||||
s = quoteRegex.ReplaceAllStringFunc(s, func(m string) string {
|
||||
if m[0] == '{' {
|
||||
return db.QuoteTableName(m[2 : len(m)-2])
|
||||
}
|
||||
return db.QuoteColumnName(m[2 : len(m)-2])
|
||||
})
|
||||
return s, placeholders
|
||||
}
|
||||
|
||||
// newBuilder creates a query builder based on the current driver name.
|
||||
func (db *DB) newBuilder(executor Executor) Builder {
|
||||
builderFunc, ok := BuilderFuncMap[db.driverName]
|
||||
if !ok {
|
||||
builderFunc = NewStandardBuilder
|
||||
}
|
||||
return builderFunc(db, executor)
|
||||
}
|
||||
|
||||
// Error returns the error string of Errors.
|
||||
func (errs Errors) Error() string {
|
||||
var b bytes.Buffer
|
||||
for i, e := range errs {
|
||||
if i > 0 {
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
b.WriteString(e.Error())
|
||||
}
|
||||
return b.String()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue