Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Oracle backend support #45

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,17 @@ Kine is an etcdshim that translates etcd API to sqlite, Postgres, Mysql, and dql
- Can be ran standalone so any k8s (not just k3s) can use Kine
- Implements a subset of etcdAPI (not usable at all for general purpose etcd)
- Translates etcdTX calls into the desired API (Create, Update, Delete)
- Backend drivers for dqlite, sqlite, Postgres, MySQL
- Backend drivers for dqlite, sqlite, Postgres, MySQL, Oracle (experimental)

### Using Oracle Backend (experimental)

The Oracle backend is backed by [godror/godror](https://github.com/godror/godror), that uses a C-based OCI wrapper,
so native OCI drivers are still required to start Kine.
However, as those drivers are proprietary and dynamically linked, you will have to manually download it yourself and
point your LD_LIBRARY_PATH to the right direction.

Oracle has outlined the procedure to use the drivers correctly. You can refer to their documentation
[here](https://docs.oracle.com/en/database/oracle/oracle-database/19/lnoci/instant-client.html#GUID-7D65474A-8790-4E81-B535-409010791C2F).

To download the drivers, please go [here](https://www.oracle.com/hk/database/technologies/instant-client/downloads.html)
and carefully read their licensing terms. (unless you want Oracle to send you the papers)
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ require (
github.com/Rican7/retry v0.1.0
github.com/canonical/go-dqlite v1.5.1
github.com/go-sql-driver/mysql v1.4.1
github.com/godror/godror v0.17.0
github.com/lib/pq v1.1.1
github.com/mattn/go-sqlite3 v1.10.0
github.com/pkg/errors v0.8.1
github.com/rancher/wrangler v0.4.0
github.com/sirupsen/logrus v1.4.2
github.com/urfave/cli v1.21.0
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
google.golang.org/grpc v1.23.1
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
Expand Down Expand Up @@ -123,6 +125,8 @@ github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godror/godror v0.17.0 h1:ClVuKG0qCj6f182BruhwgHS8xCgfpQp35pTrefSq4NE=
github.com/godror/godror v0.17.0/go.mod h1:DE94Br7LXn4dQGCexePriVrKotR9GkVzPPT5nnm8dj0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
Expand All @@ -146,6 +150,8 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
Expand Down Expand Up @@ -357,6 +363,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -401,6 +409,8 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
Expand Down
132 changes: 99 additions & 33 deletions pkg/drivers/generic/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var (
GROUP BY mkv.name) maxkv
ON maxkv.id = kv.id
WHERE
(kv.deleted = 0 OR ?)
kv.deleted = 0 OR kv.deleted = ?
ORDER BY kv.id ASC
`, revSQL, compactRevSQL, columns)
)
Expand All @@ -67,6 +67,7 @@ func (s Stripped) String() string {

type ErrRetry func(error) bool
type TranslateErr func(error) error
type TranslateLimit func(num int64) string

type ConnectionPoolConfig struct {
MaxIdle int // zero means defaultMaxIdleConns; negative means 0
Expand All @@ -77,23 +78,26 @@ type ConnectionPoolConfig struct {
type Generic struct {
sync.Mutex

LockWrites bool
LastInsertID bool
DB *sql.DB
GetCurrentSQL string
GetRevisionSQL string
RevisionSQL string
ListRevisionStartSQL string
GetRevisionAfterSQL string
CountSQL string
AfterSQL string
DeleteSQL string
UpdateCompactSQL string
InsertSQL string
FillSQL string
InsertLastInsertIDSQL string
Retry ErrRetry
TranslateErr TranslateErr
LockWrites bool
LastInsertID bool
InsertReturningInto bool
DB *sql.DB
GetCurrentSQL string
GetRevisionSQL string
RevisionSQL string
ListRevisionStartSQL string
GetRevisionAfterSQL string
CountSQL string
AfterSQL string
DeleteSQL string
UpdateCompactSQL string
InsertSQL string
FillSQL string
InsertLastInsertIDSQL string
InsertReturningIntoSQL string
Retry ErrRetry
TranslateErr TranslateErr
TranslateLimit TranslateLimit
}

func q(sql, param string, numbered bool) string {
Expand All @@ -113,17 +117,14 @@ func q(sql, param string, numbered bool) string {
}

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")
)
var count int64

if err := countKV.Scan(&count); err != nil || count == 0 {
if err := d.queryRow(ctx, "SELECT COUNT(*) FROM key_value").Scan(&count); err != nil || count == 0 {
logrus.WithError(err).Debug("no migration request demanded")
return
}

if err := countKine.Scan(&count); err != nil || count != 0 {
if err := d.queryRow(ctx, "SELECT COUNT(*) FROM kine").Scan(&count); err != nil || count != 0 {
return
}

Expand Down Expand Up @@ -234,20 +235,52 @@ func Open(ctx context.Context, driverName, dataSourceName string, connPoolConfig

FillSQL: q(`INSERT INTO kine(id, name, created, deleted, create_revision, prev_revision, lease, value, old_value)
values(?, ?, ?, ?, ?, ?, ?, ?, ?)`, paramCharacter, numbered),

InsertReturningIntoSQL: q(`INSERT INTO kine(name, created, deleted, create_revision, prev_revision, lease, value, old_value)
values(?, ?, ?, ?, ?, ?, ?, ?) RETURNING id INTO ?`, paramCharacter, numbered),
}, err
}

func (d *Generic) query(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error) {
logrus.Tracef("QUERY %v : %s", args, Stripped(sql))
var limitNum = regexp.MustCompile(`LIMIT[[:space:]]+(\d+)`)

func (d *Generic) doTranslateLimit(sql string) string {
if d.TranslateLimit == nil {
return sql
}

return limitNum.ReplaceAllStringFunc(sql, func(s string) string {
// This should be a necessary condition
if n, err := strconv.ParseInt(limitNum.FindStringSubmatch(s)[1], 10, 64); err == nil {
return d.TranslateLimit(n)
} else {
// ???
panic(err)
}
})
}

func (d *Generic) query(ctx context.Context, sql string, args ...interface{}) (rows *sql.Rows, err error) {
sql = d.doTranslateLimit(sql)
logrus.
WithField("args", args).
WithField("sql", Stripped(sql)).
WithField("mode", nil).
Trace("query")
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))
sql = d.doTranslateLimit(sql)
logrus.
WithField("args", args).
WithField("sql", Stripped(sql)).
WithField("mode", "row").
Trace("query")
return d.DB.QueryRowContext(ctx, sql, args...)
}

func (d *Generic) execute(ctx context.Context, sql string, args ...interface{}) (result sql.Result, err error) {
sql = d.doTranslateLimit(sql)
if d.LockWrites {
d.Lock()
defer d.Unlock()
Expand All @@ -256,9 +289,17 @@ func (d *Generic) execute(ctx context.Context, sql string, args ...interface{})
wait := strategy.Backoff(backoff.Linear(100 + time.Millisecond))
for i := uint(0); i < 20; i++ {
if i > 2 {
logrus.Debugf("EXEC (try: %d) %v : %s", i, args, Stripped(sql))
logrus.
WithField("try", i).
WithField("args", args).
WithField("sql", Stripped(sql)).
Debug("exec")
} else {
logrus.Tracef("EXEC (try: %d) %v : %s", i, args, Stripped(sql))
logrus.
WithField("try", i).
WithField("args", args).
WithField("sql", Stripped(sql)).
Trace("exec")
}
result, err = d.DB.ExecContext(ctx, sql, args...)
if err != nil && d.Retry != nil && d.Retry(err) {
Expand Down Expand Up @@ -299,7 +340,13 @@ func (d *Generic) ListCurrent(ctx context.Context, prefix string, limit int64, i
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
return d.query(ctx, sql, prefix, includeDeleted)
// don't ask me why, ask golang
return d.query(ctx, sql, prefix, func() int {
if includeDeleted {
return 1
}
return 0
}())
}

func (d *Generic) List(ctx context.Context, prefix, startKey string, limit, revision int64, includeDeleted bool) (*sql.Rows, error) {
Expand All @@ -308,14 +355,24 @@ func (d *Generic) List(ctx context.Context, prefix, startKey string, limit, revi
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
return d.query(ctx, sql, prefix, revision, includeDeleted)
return d.query(ctx, sql, prefix, revision, func() int {
if includeDeleted {
return 1
}
return 0
}())
}

sql := d.GetRevisionAfterSQL
if limit > 0 {
sql = fmt.Sprintf("%s LIMIT %d", sql, limit)
}
return d.query(ctx, sql, prefix, revision, startKey, revision, includeDeleted)
return d.query(ctx, sql, prefix, revision, startKey, revision, func() int {
if includeDeleted {
return 1
}
return 0
}())
}

func (d *Generic) Count(ctx context.Context, prefix string) (int64, int64, error) {
Expand Down Expand Up @@ -382,6 +439,15 @@ func (d *Generic) Insert(ctx context.Context, key string, create, delete bool, c
return row.LastInsertId()
}

if d.InsertReturningInto {
_, err := d.execute(ctx, d.InsertReturningIntoSQL, key, cVal, dVal, createRevision, previousRevision, ttl, value, prevValue, sql.Out{Dest: &id})
if err != nil {
return 0, err
}
logrus.WithField("id", id).Debug("insert returning into")
return id, nil
}

row := d.queryRow(ctx, d.InsertSQL, key, cVal, dVal, createRevision, previousRevision, ttl, value, prevValue)
err = row.Scan(&id)
return id, err
Expand Down
Loading