k3s/vendor/github.com/k3s-io/kine/pkg/drivers/generic/generic.go

414 lines
11 KiB
Go
Raw Normal View History

2019-08-22 05:12:46 +00:00
package generic
import (
"context"
"database/sql"
"errors"
2019-08-22 05:12:46 +00:00
"fmt"
"regexp"
"strconv"
"strings"
2019-11-08 21:45:10 +00:00
"sync"
"time"
"github.com/Rican7/retry/backoff"
"github.com/Rican7/retry/strategy"
"github.com/k3s-io/kine/pkg/server"
2019-08-22 05:12:46 +00:00
"github.com/sirupsen/logrus"
)
const (
defaultMaxIdleConns = 2 // copied from database/sql
)
// explicit interface check
var _ server.Dialect = (*Generic)(nil)
2019-08-22 05:12:46 +00:00
var (
columns = "kv.id AS theid, kv.name, kv.created, kv.deleted, kv.create_revision, kv.prev_revision, kv.lease, kv.value, kv.old_value"
2019-08-22 05:12:46 +00:00
revSQL = `
SELECT MAX(rkv.id) AS id
FROM kine AS rkv`
2019-08-22 05:12:46 +00:00
compactRevSQL = `
SELECT MAX(crkv.prev_revision) AS prev_revision
FROM kine AS crkv
WHERE crkv.name = 'compact_rev_key'`
2019-08-22 05:12:46 +00:00
idOfKey = `
AND
mkv.id <= ? AND
mkv.id > (
SELECT MAX(ikv.id) AS id
FROM kine AS ikv
2019-08-22 05:12:46 +00:00
WHERE
ikv.name = ? AND
ikv.id <= ?)`
2019-08-22 05:12:46 +00:00
listSQL = fmt.Sprintf(`
SELECT (%s), (%s), %s
FROM kine AS kv
2019-08-22 05:12:46 +00:00
JOIN (
SELECT MAX(mkv.id) AS id
FROM kine AS mkv
2019-08-22 05:12:46 +00:00
WHERE
mkv.name LIKE ?
%%s
GROUP BY mkv.name) maxkv
ON maxkv.id = kv.id
WHERE
(kv.deleted = 0 OR ?)
ORDER BY kv.id ASC
`, revSQL, compactRevSQL, columns)
)
type Stripped string
func (s Stripped) String() string {
str := strings.ReplaceAll(string(s), "\n", "")
return regexp.MustCompile("[\t ]+").ReplaceAllString(str, " ")
}
2019-11-08 21:45:10 +00:00
type ErrRetry func(error) bool
2019-11-13 06:05:20 +00:00
type TranslateErr func(error) error
2019-11-08 21:45:10 +00:00
type ConnectionPoolConfig struct {
MaxIdle int // zero means defaultMaxIdleConns; negative means 0
MaxOpen int // <= 0 means unlimited
MaxLifetime time.Duration // maximum amount of time a connection may be reused
}
2019-08-22 05:12:46 +00:00
type Generic struct {
2019-11-08 21:45:10 +00:00
sync.Mutex
LockWrites bool
2019-08-22 05:12:46 +00:00
LastInsertID bool
DB *sql.DB
GetCurrentSQL string
GetRevisionSQL string
RevisionSQL string
ListRevisionStartSQL string
GetRevisionAfterSQL string
CountSQL string
AfterSQL string
DeleteSQL string
CompactSQL string
2019-08-22 05:12:46 +00:00
UpdateCompactSQL string
InsertSQL string
2019-11-02 00:05:00 +00:00
FillSQL string
2019-08-22 05:12:46 +00:00
InsertLastInsertIDSQL string
GetSizeSQL string
2019-11-08 21:45:10 +00:00
Retry ErrRetry
2019-11-13 06:05:20 +00:00
TranslateErr TranslateErr
2019-08-22 05:12:46 +00:00
}
func q(sql, param string, numbered bool) string {
if param == "?" && !numbered {
return sql
}
regex := regexp.MustCompile(`\?`)
n := 0
return regex.ReplaceAllStringFunc(sql, func(string) string {
if numbered {
n++
return param + strconv.Itoa(n)
}
return param
})
}
func (d *Generic) Migrate(ctx context.Context) {
var (
count = 0
countKV = d.queryRow(ctx, "SELECT COUNT(*) FROM key_value")
countKine = d.queryRow(ctx, "SELECT COUNT(*) FROM kine")
)
if err := countKV.Scan(&count); err != nil || count == 0 {
return
}
if err := countKine.Scan(&count); err != nil || count != 0 {
return
}
logrus.Infof("Migrating content from old table")
_, err := d.execute(ctx,
`INSERT INTO kine(deleted, create_revision, prev_revision, name, value, created, lease)
SELECT 0, 0, 0, kv.name, kv.value, 1, CASE WHEN kv.ttl > 0 THEN 15 ELSE 0 END
FROM key_value kv
WHERE kv.id IN (SELECT MAX(kvd.id) FROM key_value kvd GROUP BY kvd.name)`)
if err != nil {
logrus.Errorf("Migration failed: %v", err)
}
}
func configureConnectionPooling(connPoolConfig ConnectionPoolConfig, db *sql.DB, driverName string) {
// behavior copied from database/sql - zero means defaultMaxIdleConns; negative means 0
if connPoolConfig.MaxIdle < 0 {
connPoolConfig.MaxIdle = 0
} else if connPoolConfig.MaxIdle == 0 {
connPoolConfig.MaxIdle = defaultMaxIdleConns
}
logrus.Infof("Configuring %s database connection pooling: maxIdleConns=%d, maxOpenConns=%d, connMaxLifetime=%s", driverName, connPoolConfig.MaxIdle, connPoolConfig.MaxOpen, connPoolConfig.MaxLifetime)
db.SetMaxIdleConns(connPoolConfig.MaxIdle)
db.SetMaxOpenConns(connPoolConfig.MaxOpen)
db.SetConnMaxLifetime(connPoolConfig.MaxLifetime)
}
2019-12-12 01:27:03 +00:00
func openAndTest(driverName, dataSourceName string) (*sql.DB, error) {
2019-08-22 05:12:46 +00:00
db, err := sql.Open(driverName, dataSourceName)
if err != nil {
return nil, err
}
2019-12-12 01:27:03 +00:00
for i := 0; i < 3; i++ {
if err := db.Ping(); err != nil {
db.Close()
return nil, err
}
}
return db, nil
}
func Open(ctx context.Context, driverName, dataSourceName string, connPoolConfig ConnectionPoolConfig, paramCharacter string, numbered bool) (*Generic, error) {
2019-12-12 01:27:03 +00:00
var (
db *sql.DB
err error
)
for i := 0; i < 300; i++ {
db, err = openAndTest(driverName, dataSourceName)
if err == nil {
break
}
logrus.Errorf("failed to ping connection: %v", err)
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(time.Second):
}
}
configureConnectionPooling(connPoolConfig, db, driverName)
2019-08-22 05:12:46 +00:00
return &Generic{
DB: db,
GetRevisionSQL: q(fmt.Sprintf(`
SELECT
0, 0, %s
FROM kine AS kv
2019-08-22 05:12:46 +00:00
WHERE kv.id = ?`, columns), paramCharacter, numbered),
GetCurrentSQL: q(fmt.Sprintf(listSQL, ""), paramCharacter, numbered),
ListRevisionStartSQL: q(fmt.Sprintf(listSQL, "AND mkv.id <= ?"), paramCharacter, numbered),
GetRevisionAfterSQL: q(fmt.Sprintf(listSQL, idOfKey), paramCharacter, numbered),
CountSQL: q(fmt.Sprintf(`
SELECT (%s), COUNT(c.theid)
FROM (
%s
) c`, revSQL, fmt.Sprintf(listSQL, "")), paramCharacter, numbered),
AfterSQL: q(fmt.Sprintf(`
SELECT (%s), (%s), %s
FROM kine AS kv
2019-08-22 05:12:46 +00:00
WHERE
kv.name LIKE ? AND
kv.id > ?
ORDER BY kv.id ASC`, revSQL, compactRevSQL, columns), paramCharacter, numbered),
DeleteSQL: q(`
DELETE FROM kine AS kv
WHERE kv.id = ?`, paramCharacter, numbered),
2019-08-22 05:12:46 +00:00
UpdateCompactSQL: q(`
UPDATE kine
SET prev_revision = ?
WHERE name = 'compact_rev_key'`, paramCharacter, numbered),
InsertLastInsertIDSQL: q(`INSERT INTO kine(name, created, deleted, create_revision, prev_revision, lease, value, old_value)
values(?, ?, ?, ?, ?, ?, ?, ?)`, paramCharacter, numbered),
InsertSQL: q(`INSERT INTO kine(name, created, deleted, create_revision, prev_revision, lease, value, old_value)
values(?, ?, ?, ?, ?, ?, ?, ?) RETURNING id`, paramCharacter, numbered),
2019-11-02 00:05:00 +00:00
FillSQL: q(`INSERT INTO kine(id, name, created, deleted, create_revision, prev_revision, lease, value, old_value)
values(?, ?, ?, ?, ?, ?, ?, ?, ?)`, paramCharacter, numbered),
2019-12-12 01:27:03 +00:00
}, err
2019-08-22 05:12:46 +00:00
}
func (d *Generic) query(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error) {
logrus.Tracef("QUERY %v : %s", args, Stripped(sql))
return d.DB.QueryContext(ctx, sql, args...)
}
func (d *Generic) queryRow(ctx context.Context, sql string, args ...interface{}) *sql.Row {
logrus.Tracef("QUERY ROW %v : %s", args, Stripped(sql))
return d.DB.QueryRowContext(ctx, sql, args...)
}
2019-11-08 21:45:10 +00:00
func (d *Generic) execute(ctx context.Context, sql string, args ...interface{}) (result sql.Result, err error) {
if d.LockWrites {
d.Lock()
defer d.Unlock()
}
wait := strategy.Backoff(backoff.Linear(100 + time.Millisecond))
for i := uint(0); i < 20; i++ {
logrus.Tracef("EXEC (try: %d) %v : %s", i, args, Stripped(sql))
2019-11-08 21:45:10 +00:00
result, err = d.DB.ExecContext(ctx, sql, args...)
if err != nil && d.Retry != nil && d.Retry(err) {
wait(i)
continue
}
return result, err
}
return
2019-08-22 05:12:46 +00:00
}
func (d *Generic) GetCompactRevision(ctx context.Context) (int64, error) {
var id int64
row := d.queryRow(ctx, compactRevSQL)
err := row.Scan(&id)
if err == sql.ErrNoRows {
return 0, nil
}
return id, err
}
func (d *Generic) SetCompactRevision(ctx context.Context, revision int64) error {
logrus.Tracef("SETCOMPACTREVISION %v", revision)
_, err := d.execute(ctx, d.UpdateCompactSQL, revision)
2019-08-22 05:12:46 +00:00
return err
}
func (d *Generic) Compact(ctx context.Context, revision int64) (int64, error) {
logrus.Tracef("COMPACT %v", revision)
res, err := d.execute(ctx, d.CompactSQL, revision, revision)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
2019-08-22 05:12:46 +00:00
func (d *Generic) GetRevision(ctx context.Context, revision int64) (*sql.Rows, error) {
return d.query(ctx, d.GetRevisionSQL, revision)
}
func (d *Generic) DeleteRevision(ctx context.Context, revision int64) error {
logrus.Tracef("DELETEREVISION %v", revision)
2019-08-22 05:12:46 +00:00
_, err := d.execute(ctx, d.DeleteSQL, revision)
return err
}
func (d *Generic) ListCurrent(ctx context.Context, prefix string, limit int64, includeDeleted bool) (*sql.Rows, error) {
sql := d.GetCurrentSQL
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
return d.query(ctx, sql, prefix, includeDeleted)
}
func (d *Generic) List(ctx context.Context, prefix, startKey string, limit, revision int64, includeDeleted bool) (*sql.Rows, error) {
if startKey == "" {
sql := d.ListRevisionStartSQL
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
return d.query(ctx, sql, prefix, revision, includeDeleted)
}
sql := d.GetRevisionAfterSQL
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
return d.query(ctx, sql, prefix, revision, startKey, revision, includeDeleted)
}
func (d *Generic) Count(ctx context.Context, prefix string) (int64, int64, error) {
var (
rev sql.NullInt64
id int64
)
row := d.queryRow(ctx, d.CountSQL, prefix, false)
err := row.Scan(&rev, &id)
return rev.Int64, id, err
}
func (d *Generic) CurrentRevision(ctx context.Context) (int64, error) {
var id int64
row := d.queryRow(ctx, revSQL)
err := row.Scan(&id)
if err == sql.ErrNoRows {
return 0, nil
}
return id, err
}
func (d *Generic) After(ctx context.Context, prefix string, rev, limit int64) (*sql.Rows, error) {
2019-08-22 05:12:46 +00:00
sql := d.AfterSQL
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
2019-08-22 05:12:46 +00:00
return d.query(ctx, sql, prefix, rev)
}
2019-11-02 00:05:00 +00:00
func (d *Generic) Fill(ctx context.Context, revision int64) error {
_, err := d.execute(ctx, d.FillSQL, revision, fmt.Sprintf("gap-%d", revision), 0, 1, 0, 0, 0, nil, nil)
return err
}
func (d *Generic) IsFill(key string) bool {
return strings.HasPrefix(key, "gap-")
}
2019-08-22 05:12:46 +00:00
func (d *Generic) Insert(ctx context.Context, key string, create, delete bool, createRevision, previousRevision int64, ttl int64, value, prevValue []byte) (id int64, err error) {
2019-11-13 06:05:20 +00:00
if d.TranslateErr != nil {
defer func() {
if err != nil {
err = d.TranslateErr(err)
}
}()
}
2019-08-22 05:12:46 +00:00
cVal := 0
dVal := 0
if create {
cVal = 1
}
if delete {
dVal = 1
}
if d.LastInsertID {
row, err := d.execute(ctx, d.InsertLastInsertIDSQL, key, cVal, dVal, createRevision, previousRevision, ttl, value, prevValue)
if err != nil {
2019-11-13 06:05:20 +00:00
return 0, err
2019-08-22 05:12:46 +00:00
}
return row.LastInsertId()
}
row := d.queryRow(ctx, d.InsertSQL, key, cVal, dVal, createRevision, previousRevision, ttl, value, prevValue)
err = row.Scan(&id)
return id, err
}
func (d *Generic) GetSize(ctx context.Context) (int64, error) {
if d.GetSizeSQL == "" {
return 0, errors.New("driver does not support size reporting")
}
var size int64
row := d.queryRow(ctx, d.GetSizeSQL)
if err := row.Scan(&size); err != nil {
return 0, err
}
return size, nil
}