mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-10 15:20:56 +00:00
a1bcc15458
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4080
554 lines
13 KiB
Go
554 lines
13 KiB
Go
// Copyright 2014 The ql Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// database/sql/driver
|
|
|
|
package ql
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
_ driver.Conn = (*driverConn)(nil)
|
|
_ driver.Driver = (*sqlDriver)(nil)
|
|
_ driver.Execer = (*driverConn)(nil)
|
|
_ driver.Queryer = (*driverConn)(nil)
|
|
_ driver.Result = (*driverResult)(nil)
|
|
_ driver.Rows = (*driverRows)(nil)
|
|
_ driver.Stmt = (*driverStmt)(nil)
|
|
_ driver.Tx = (*driverConn)(nil)
|
|
|
|
txBegin = MustCompile("BEGIN TRANSACTION;")
|
|
txCommit = MustCompile("COMMIT;")
|
|
txRollback = MustCompile("ROLLBACK;")
|
|
|
|
errNoResult = errors.New("query statement does not produce a result set (no top level SELECT)")
|
|
)
|
|
|
|
type errList []error
|
|
|
|
func (e *errList) append(err error) {
|
|
if err != nil {
|
|
*e = append(*e, err)
|
|
}
|
|
}
|
|
|
|
func (e errList) error() error {
|
|
if len(e) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return e
|
|
}
|
|
|
|
func (e errList) Error() string {
|
|
a := make([]string, len(e))
|
|
for i, v := range e {
|
|
a[i] = v.Error()
|
|
}
|
|
return strings.Join(a, "\n")
|
|
}
|
|
|
|
func params(args []driver.Value) []interface{} {
|
|
r := make([]interface{}, len(args))
|
|
for i, v := range args {
|
|
r[i] = interface{}(v)
|
|
}
|
|
return r
|
|
}
|
|
|
|
var (
|
|
fileDriver = &sqlDriver{dbs: map[string]*driverDB{}}
|
|
fileDriverOnce sync.Once
|
|
memDriver = &sqlDriver{isMem: true, dbs: map[string]*driverDB{}}
|
|
memDriverOnce sync.Once
|
|
)
|
|
|
|
// RegisterDriver registers a QL database/sql/driver[0] named "ql". The name
|
|
// parameter of
|
|
//
|
|
// sql.Open("ql", name)
|
|
//
|
|
// is interpreted as a path name to a named DB file which will be created if
|
|
// not present. The underlying QL database data are persisted on db.Close().
|
|
// RegisterDriver can be safely called multiple times, it'll register the
|
|
// driver only once.
|
|
//
|
|
// The name argument can be optionally prefixed by "file://". In that case the
|
|
// prefix is stripped before interpreting it as a file name.
|
|
//
|
|
// The name argument can be optionally prefixed by "memory://". In that case
|
|
// the prefix is stripped before interpreting it as a name of a memory-only,
|
|
// volatile DB.
|
|
//
|
|
// [0]: http://golang.org/pkg/database/sql/driver/
|
|
func RegisterDriver() {
|
|
fileDriverOnce.Do(func() { sql.Register("ql", fileDriver) })
|
|
}
|
|
|
|
// RegisterMemDriver registers a QL memory database/sql/driver[0] named
|
|
// "ql-mem". The name parameter of
|
|
//
|
|
// sql.Open("ql-mem", name)
|
|
//
|
|
// is interpreted as an unique memory DB name which will be created if not
|
|
// present. The underlying QL memory database data are not persisted on
|
|
// db.Close(). RegisterMemDriver can be safely called multiple times, it'll
|
|
// register the driver only once.
|
|
//
|
|
// [0]: http://golang.org/pkg/database/sql/driver/
|
|
func RegisterMemDriver() {
|
|
memDriverOnce.Do(func() { sql.Register("ql-mem", memDriver) })
|
|
}
|
|
|
|
type driverDB struct {
|
|
db *DB
|
|
name string
|
|
refcount int
|
|
}
|
|
|
|
func newDriverDB(db *DB, name string) *driverDB {
|
|
return &driverDB{db: db, name: name, refcount: 1}
|
|
}
|
|
|
|
// sqlDriver implements the interface required by database/sql/driver.
|
|
type sqlDriver struct {
|
|
dbs map[string]*driverDB
|
|
isMem bool
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (d *sqlDriver) lock() func() {
|
|
d.mu.Lock()
|
|
return d.mu.Unlock
|
|
}
|
|
|
|
// Open returns a new connection to the database. The name is a string in a
|
|
// driver-specific format.
|
|
//
|
|
// Open may return a cached connection (one previously closed), but doing so is
|
|
// unnecessary; the sql package maintains a pool of idle connections for
|
|
// efficient re-use.
|
|
//
|
|
// The returned connection is only used by one goroutine at a time.
|
|
//
|
|
// The name supported URL parameters:
|
|
//
|
|
// headroom Size of the WAL headroom. See https://github.com/cznic/ql/issues/140.
|
|
func (d *sqlDriver) Open(name string) (driver.Conn, error) {
|
|
switch {
|
|
case d == fileDriver:
|
|
if !strings.Contains(name, "://") && !strings.HasPrefix(name, "file") {
|
|
name = "file://" + name
|
|
}
|
|
case d == memDriver:
|
|
if !strings.Contains(name, "://") && !strings.HasPrefix(name, "memory") {
|
|
name = "memory://" + name
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("open: unexpected/unsupported instance of driver.Driver: %p", d)
|
|
}
|
|
|
|
name = filepath.ToSlash(name) // Ensure / separated URLs on Windows
|
|
uri, err := url.Parse(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch uri.Scheme {
|
|
case "file":
|
|
// ok
|
|
case "memory":
|
|
d = memDriver
|
|
default:
|
|
return nil, fmt.Errorf("open: unexpected/unsupported scheme: %s", uri.Scheme)
|
|
}
|
|
|
|
name = filepath.Clean(filepath.Join(uri.Host, uri.Path))
|
|
if d == fileDriver && (name == "" || name == "." || name == string(os.PathSeparator)) {
|
|
return nil, fmt.Errorf("invalid DB name %q", name)
|
|
}
|
|
|
|
var headroom int64
|
|
if a := uri.Query()["headroom"]; len(a) != 0 {
|
|
if headroom, err = strconv.ParseInt(a[0], 10, 64); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
defer d.lock()()
|
|
db := d.dbs[name]
|
|
if db == nil {
|
|
var err error
|
|
var db0 *DB
|
|
switch d.isMem {
|
|
case true:
|
|
db0, err = OpenMem()
|
|
default:
|
|
db0, err = OpenFile(name, &Options{CanCreate: true, Headroom: headroom})
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db = newDriverDB(db0, name)
|
|
d.dbs[name] = db
|
|
return newDriverConn(d, db), nil
|
|
}
|
|
|
|
db.refcount++
|
|
return newDriverConn(d, db), nil
|
|
}
|
|
|
|
// driverConn is a connection to a database. It is not used concurrently by
|
|
// multiple goroutines.
|
|
//
|
|
// Conn is assumed to be stateful.
|
|
type driverConn struct {
|
|
ctx *TCtx
|
|
db *driverDB
|
|
driver *sqlDriver
|
|
stop map[*driverStmt]struct{}
|
|
tnl int
|
|
}
|
|
|
|
func newDriverConn(d *sqlDriver, ddb *driverDB) driver.Conn {
|
|
r := &driverConn{
|
|
db: ddb,
|
|
driver: d,
|
|
stop: map[*driverStmt]struct{}{},
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Prepare returns a prepared statement, bound to this connection.
|
|
func (c *driverConn) Prepare(query string) (driver.Stmt, error) {
|
|
list, err := Compile(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := &driverStmt{conn: c, stmt: list}
|
|
c.stop[s] = struct{}{}
|
|
return s, nil
|
|
}
|
|
|
|
// Close invalidates and potentially stops any current prepared statements and
|
|
// transactions, marking this connection as no longer in use.
|
|
//
|
|
// Because the sql package maintains a free pool of connections and only calls
|
|
// Close when there's a surplus of idle connections, it shouldn't be necessary
|
|
// for drivers to do their own connection caching.
|
|
func (c *driverConn) Close() error {
|
|
var err errList
|
|
for s := range c.stop {
|
|
err.append(s.Close())
|
|
}
|
|
defer c.driver.lock()()
|
|
dbs, name := c.driver.dbs, c.db.name
|
|
v := dbs[name]
|
|
v.refcount--
|
|
if v.refcount == 0 {
|
|
err.append(c.db.db.Close())
|
|
delete(dbs, name)
|
|
}
|
|
return err.error()
|
|
}
|
|
|
|
// Begin starts and returns a new transaction.
|
|
func (c *driverConn) Begin() (driver.Tx, error) {
|
|
if c.ctx == nil {
|
|
c.ctx = NewRWCtx()
|
|
}
|
|
|
|
if _, _, err := c.db.db.Execute(c.ctx, txBegin); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.tnl++
|
|
return c, nil
|
|
}
|
|
|
|
func (c *driverConn) Commit() error {
|
|
if c.tnl == 0 || c.ctx == nil {
|
|
return errCommitNotInTransaction
|
|
}
|
|
|
|
if _, _, err := c.db.db.Execute(c.ctx, txCommit); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.tnl--
|
|
if c.tnl == 0 {
|
|
c.ctx = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *driverConn) Rollback() error {
|
|
if c.tnl == 0 || c.ctx == nil {
|
|
return errRollbackNotInTransaction
|
|
}
|
|
|
|
if _, _, err := c.db.db.Execute(c.ctx, txRollback); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.tnl--
|
|
if c.tnl == 0 {
|
|
c.ctx = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Execer is an optional interface that may be implemented by a Conn.
|
|
//
|
|
// If a Conn does not implement Execer, the sql package's DB.Exec will first
|
|
// prepare a query, execute the statement, and then close the statement.
|
|
//
|
|
// Exec may return driver.ErrSkip.
|
|
func (c *driverConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
|
list, err := Compile(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return driverExec(c.db, c.ctx, list, args)
|
|
}
|
|
|
|
func driverExec(db *driverDB, ctx *TCtx, list List, args []driver.Value) (driver.Result, error) {
|
|
if _, _, err := db.db.Execute(ctx, list, params(args)...); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(list.l) == 1 {
|
|
switch list.l[0].(type) {
|
|
case *createTableStmt, *dropTableStmt, *alterTableAddStmt,
|
|
*alterTableDropColumnStmt, *truncateTableStmt:
|
|
return driver.ResultNoRows, nil
|
|
}
|
|
}
|
|
|
|
r := &driverResult{}
|
|
if ctx != nil {
|
|
r.lastInsertID, r.rowsAffected = ctx.LastInsertID, ctx.RowsAffected
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// Queryer is an optional interface that may be implemented by a Conn.
|
|
//
|
|
// If a Conn does not implement Queryer, the sql package's DB.Query will first
|
|
// prepare a query, execute the statement, and then close the statement.
|
|
//
|
|
// Query may return driver.ErrSkip.
|
|
func (c *driverConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
|
list, err := Compile(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return driverQuery(c.db, c.ctx, list, args)
|
|
}
|
|
|
|
func driverQuery(db *driverDB, ctx *TCtx, list List, args []driver.Value) (driver.Rows, error) {
|
|
rss, _, err := db.db.Execute(ctx, list, params(args)...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch n := len(rss); n {
|
|
case 0:
|
|
return nil, errNoResult
|
|
case 1:
|
|
return newdriverRows(rss[len(rss)-1]), nil
|
|
default:
|
|
return nil, fmt.Errorf("query produced %d result sets, expected only one", n)
|
|
}
|
|
}
|
|
|
|
// driverResult is the result of a query execution.
|
|
type driverResult struct {
|
|
lastInsertID int64
|
|
rowsAffected int64
|
|
}
|
|
|
|
// LastInsertId returns the database's auto-generated ID after, for example, an
|
|
// INSERT into a table with primary key.
|
|
func (r *driverResult) LastInsertId() (int64, error) { // -golint
|
|
return r.lastInsertID, nil
|
|
}
|
|
|
|
// RowsAffected returns the number of rows affected by the query.
|
|
func (r *driverResult) RowsAffected() (int64, error) {
|
|
return r.rowsAffected, nil
|
|
}
|
|
|
|
// driverRows is an iterator over an executed query's results.
|
|
type driverRows struct {
|
|
rs Recordset
|
|
done chan int
|
|
rows chan interface{}
|
|
}
|
|
|
|
func newdriverRows(rs Recordset) *driverRows {
|
|
r := &driverRows{
|
|
rs: rs,
|
|
done: make(chan int),
|
|
rows: make(chan interface{}, 500),
|
|
}
|
|
go func() {
|
|
err := io.EOF
|
|
if e := r.rs.Do(false, func(data []interface{}) (bool, error) {
|
|
select {
|
|
case r.rows <- data:
|
|
return true, nil
|
|
case <-r.done:
|
|
return false, nil
|
|
}
|
|
}); e != nil {
|
|
err = e
|
|
}
|
|
|
|
select {
|
|
case r.rows <- err:
|
|
case <-r.done:
|
|
}
|
|
}()
|
|
return r
|
|
}
|
|
|
|
// Columns returns the names of the columns. The number of columns of the
|
|
// result is inferred from the length of the slice. If a particular column
|
|
// name isn't known, an empty string should be returned for that entry.
|
|
func (r *driverRows) Columns() []string {
|
|
f, _ := r.rs.Fields()
|
|
return f
|
|
}
|
|
|
|
// Close closes the rows iterator.
|
|
func (r *driverRows) Close() error {
|
|
close(r.done)
|
|
return nil
|
|
}
|
|
|
|
// Next is called to populate the next row of data into the provided slice. The
|
|
// provided slice will be the same size as the Columns() are wide.
|
|
//
|
|
// The dest slice may be populated only with a driver Value type, but excluding
|
|
// string. All string values must be converted to []byte.
|
|
//
|
|
// Next should return io.EOF when there are no more rows.
|
|
func (r *driverRows) Next(dest []driver.Value) error {
|
|
select {
|
|
case rx := <-r.rows:
|
|
switch x := rx.(type) {
|
|
case error:
|
|
return x
|
|
case []interface{}:
|
|
if g, e := len(x), len(dest); g != e {
|
|
return fmt.Errorf("field count mismatch: got %d, need %d", g, e)
|
|
}
|
|
|
|
for i, xi := range x {
|
|
switch v := xi.(type) {
|
|
case nil, int64, float64, bool, []byte, time.Time:
|
|
dest[i] = v
|
|
case complex64, complex128, *big.Int, *big.Rat:
|
|
var buf bytes.Buffer
|
|
fmt.Fprintf(&buf, "%v", v)
|
|
dest[i] = buf.Bytes()
|
|
case int8:
|
|
dest[i] = int64(v)
|
|
case int16:
|
|
dest[i] = int64(v)
|
|
case int32:
|
|
dest[i] = int64(v)
|
|
case int:
|
|
dest[i] = int64(v)
|
|
case uint8:
|
|
dest[i] = int64(v)
|
|
case uint16:
|
|
dest[i] = int64(v)
|
|
case uint32:
|
|
dest[i] = int64(v)
|
|
case uint64:
|
|
dest[i] = int64(v)
|
|
case uint:
|
|
dest[i] = int64(v)
|
|
case time.Duration:
|
|
dest[i] = int64(v)
|
|
case string:
|
|
dest[i] = []byte(v)
|
|
default:
|
|
return fmt.Errorf("internal error 004")
|
|
}
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("internal error 005")
|
|
}
|
|
case <-r.done:
|
|
return io.EOF
|
|
}
|
|
}
|
|
|
|
// driverStmt is a prepared statement. It is bound to a driverConn and not used
|
|
// by multiple goroutines concurrently.
|
|
type driverStmt struct {
|
|
conn *driverConn
|
|
stmt List
|
|
}
|
|
|
|
// Close closes the statement.
|
|
//
|
|
// As of Go 1.1, a Stmt will not be closed if it's in use by any queries.
|
|
func (s *driverStmt) Close() error {
|
|
delete(s.conn.stop, s)
|
|
return nil
|
|
}
|
|
|
|
// NumInput returns the number of placeholder parameters.
|
|
//
|
|
// If NumInput returns >= 0, the sql package will sanity check argument counts
|
|
// from callers and return errors to the caller before the statement's Exec or
|
|
// Query methods are called.
|
|
//
|
|
// NumInput may also return -1, if the driver doesn't know its number of
|
|
// placeholders. In that case, the sql package will not sanity check Exec or
|
|
// Query argument counts.
|
|
func (s *driverStmt) NumInput() int {
|
|
if x := s.stmt; len(x.l) == 1 {
|
|
return x.params
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// Exec executes a query that doesn't return rows, such as an INSERT or UPDATE.
|
|
func (s *driverStmt) Exec(args []driver.Value) (driver.Result, error) {
|
|
c := s.conn
|
|
return driverExec(c.db, c.ctx, s.stmt, args)
|
|
}
|
|
|
|
// Exec executes a query that may return rows, such as a SELECT.
|
|
func (s *driverStmt) Query(args []driver.Value) (driver.Rows, error) {
|
|
c := s.conn
|
|
return driverQuery(c.db, c.ctx, s.stmt, args)
|
|
}
|