internal/database: start on postgres storage implementation

This commit is contained in:
Cadey Ratio 2018-01-21 18:09:24 -08:00
parent 28bf366c70
commit a6fce981ef
7 changed files with 230 additions and 83 deletions

View File

@ -63,7 +63,7 @@ func NewBoltStorage(path string, key *[32]byte) (Storage, error) {
b.rs = &boltRouteStorage{b}
b.ts = &boltTokenStorage{b}
return Storage(b), nil
return b, nil
}
// Certs gets the certificate storage interface.

View File

@ -25,3 +25,19 @@ func newTestBoltStorage(t *testing.T) (Storage, string, context.Context, context
return st, p, ctx, cancel
}
func newTestPostgresStorage(t *testing.T, url string) (Storage, context.Context, context.CancelFunc) {
k, err := routecrypto.ParseKey(cryptoKey)
if err != nil {
t.Fatal(err)
}
st, err := NewPostgresStorage(url, k)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
return st, ctx, cancel
}

View File

@ -194,7 +194,7 @@ func _1513982254_tokensUpSql() (*asset, error) {
return a, nil
}
var _postgresSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x94\x51\xeb\xda\x30\x14\xc5\xdf\xf3\x29\xee\xdb\xdf\x42\xf3\xb0\xa7\xc1\x44\x8a\x68\xc6\x0a\x55\xa1\x76\xdb\xa3\xc4\xe6\xba\x85\xd5\x46\x92\x28\xf3\xdb\x8f\xd4\xa8\x69\xd5\x22\x43\xc1\x07\x49\xc9\x39\xbf\x9c\x7b\x12\x4a\xa1\xe6\x5b\xfc\x02\xb2\x36\xa8\x2d\x2d\x51\x5b\xb9\x91\x25\xb7\x48\x48\x3a\x5f\xb2\xbc\x80\x74\x5e\x2c\x60\x72\xfd\x60\x08\xc0\x40\xa8\x2d\x97\x75\x0c\x82\x5b\x1e\x91\x1f\xe3\xec\x3b\x5b\xba\xf5\x24\x86\x24\x1a\x12\x72\x11\xde\xc8\x5a\x50\x55\x63\x5b\x7a\xc9\x32\x36\x29\xdc\x06\x29\x62\x08\xc5\x62\x28\x35\x72\x8b\x62\xc5\x6d\x0c\x28\xe4\xf9\x2f\x2f\xad\x3c\x60\x44\xbe\xe6\x8b\x59\x1b\xe7\xe7\x37\x96\x33\x2f\x32\x4a\x48\x96\xce\xd2\x02\x3e\x85\x10\x1a\xb7\xea\x80\xb7\x18\x53\x96\xb1\x82\xfd\x97\xe4\x2f\xb4\x94\x57\x55\xa8\x67\x5e\x7b\xae\xd0\xcd\x8f\x47\xab\x7d\x67\x30\x04\x20\x77\x8b\x66\xd0\xc8\x2b\x1d\xc3\x6f\x65\xac\xdb\xf5\xdc\x58\x1a\x49\xba\x3e\x52\x29\xba\xf8\x37\x8a\xcf\x9d\xe1\xc4\xe3\x23\x94\xe2\x7e\x7c\xb7\xfe\xce\xe4\x1d\x04\xe7\xad\x3d\x1c\x6e\x8e\x0d\x87\xa1\x1b\xa5\xe9\xde\xa0\x7e\x07\x89\xd7\x18\x25\x21\x80\xc0\x0a\x2d\x5e\xa3\x68\x97\x32\xdc\x4f\xa0\x49\x13\xc6\xf3\x69\x6f\x33\x7d\x57\xac\xfa\x83\x75\xfb\x12\x17\x6e\xa9\xb9\xbe\x6b\x25\x8e\xc1\xa1\x4c\xa9\x76\x68\x62\xc0\xbf\x3b\xa9\xd1\xac\xb8\xed\x96\xe7\xf4\x8b\xba\x37\xc0\x61\x7b\xa3\x76\x5e\x0f\x0c\x5a\x99\x5d\xcc\x3a\xa1\x79\xcc\xfe\x02\xb5\xdc\x5d\x7f\x9c\xe3\xdb\x28\x9c\x50\xff\x3b\xd0\x70\x3c\xee\xcf\xcb\x48\xee\x96\x28\x78\xdf\xfc\x34\xc2\x12\x3d\x95\xa7\x97\x38\x41\x08\x7f\x9c\x1e\x9d\x2b\x2d\x50\x90\xb5\x45\x7d\xe0\x15\x7c\x7c\x06\xc1\x8f\xe6\x63\x48\xfe\x05\x00\x00\xff\xff\x1c\x82\x69\x4c\x5a\x06\x00\x00")
var _postgresSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x94\x4f\x8b\xea\x30\x14\xc5\xf7\xfd\x14\x77\x67\x85\x66\xf1\x56\x0f\xde\xe3\x21\xa2\x79\x4c\xa1\x2a\xd4\xce\xcc\x52\x62\x73\x9d\x09\x53\x1b\x49\xa2\x8c\xdf\x7e\x48\x5b\x35\xd1\xfa\x87\xa1\x03\x5d\x94\x40\xce\xf9\xdd\x73\x4f\x4b\x08\x94\x6c\x8d\x7f\x40\x94\x1a\x95\x21\x39\x2a\x23\x56\x22\x67\x06\x83\x78\x3a\xa7\x69\x06\xf1\x34\x9b\xc1\xe8\x74\xae\x43\x2e\xd7\x4c\x94\x11\x70\x66\x58\x1f\x5e\x86\xc9\x33\x9d\x43\x38\x88\x60\xd0\xff\x1b\x04\x47\xc9\x95\x28\x39\x91\x25\x7a\xa2\x73\x9a\xd0\x51\x06\x82\x47\xe0\xca\x44\x90\x2b\x64\x06\xf9\x82\x99\x08\x90\x8b\xc3\x2b\xcb\x8d\xd8\x21\xfc\x4f\x67\x13\x0f\x02\x5e\x9f\x68\x4a\x1b\x0d\xf8\x07\x03\x48\xe2\x49\x9c\xc1\x2f\x07\x40\xe1\x5a\xee\xf0\x02\x61\x4c\x13\x9a\xd1\xef\x49\xbe\xa1\x21\xac\x28\x5c\x3d\xdd\xd9\x4c\x8e\x4f\xb3\x0e\x25\xb7\x67\x8b\x48\xed\x89\x0e\x2b\x65\xa9\x22\x78\x97\xda\xd8\x2b\x67\x7b\x68\x5b\x43\x25\x46\x96\x7b\x22\xb8\x8b\x7c\x21\xf5\x10\x77\xcd\xd1\x44\x26\xf8\x95\xb8\x2e\xbd\xad\x49\xc7\xee\x87\x9b\xb7\x18\xec\xce\x2a\x06\x4d\x56\x52\x91\xad\x46\xd5\x31\x45\x23\x61\x21\x1c\x73\x8e\x05\x1a\x3c\x45\xe0\x95\xaf\x2d\xc3\xe1\x74\x7c\xbb\x81\x4d\x33\x8c\xfc\xc0\xd2\x6b\x46\x66\x4f\x74\xb8\x94\x7c\xef\x0c\xa4\x73\xb9\x41\x1d\x01\x7e\x6e\x84\x42\xbd\x60\xc6\x6f\x4a\xfd\xf4\xcf\x2a\x6e\x79\x6b\x07\x27\xa4\x2b\xca\x5e\x50\x47\x17\x3f\xa9\x9a\xed\x5e\x5b\x3c\x67\x5b\x16\xeb\xf8\x13\x04\x56\xe7\xce\x07\x5e\x31\xb4\x97\xa5\x2b\x8a\xf6\xc6\x38\x3f\xad\x7a\x03\x6e\x63\x1e\xcb\xb1\x91\xa8\x21\x78\x33\xca\x75\x9d\x13\x2c\x10\x10\xa5\x41\xb5\x63\x05\xf4\x7e\x03\x67\x7b\xdd\x0b\xbe\x02\x00\x00\xff\xff\x49\x2d\x7b\x8a\x23\x06\x00\x00")
func postgresSqlBytes() ([]byte, error) {
return bindataRead(
@ -209,7 +209,7 @@ func postgresSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "postgres.sql", size: 1626, mode: os.FileMode(420), modTime: time.Unix(1516564385, 0)}
info := bindataFileInfo{name: "postgres.sql", size: 1571, mode: os.FileMode(420), modTime: time.Unix(1516571679, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}

View File

@ -1,108 +1,42 @@
-- name: insert-certificate
INSERT INTO Certificates
(domain, data)
VALUES
(?, ?);
INSERT INTO Certificates(domain, data) VALUES ($1, $2);
-- name: find-one-certificate
SELECT
(id, domain, data, created_at, edited_at, active)
FROM Certificates
WHERE domain=?
LIMIT 1;
SELECT id, domain, data, created_at, edited_at, active FROM Certificates WHERE domain = $1 LIMIT 1;
-- name: remove-one-certificate
DELETE
FROM Certificates
WHERE domain=?
LIMIT 1;
DELETE FROM Certificates WHERE domain = $1 LIMIT 1;
-- name: get-all-certificates
SELECT
(id, domain, data, created_at, edited_at, active)
FROM Certificates;
SELECT id, domain, data, created_at, edited_at, active FROM Certificates;
-- name: insert-route
INSERT INTO
Routes(creator, hostname)
VALUES
(?, ?);
INSERT INTO Routes(creator, hostname) VALUES ($1, $2);
-- name: find-one-route-by-id
SELECT
(id, creator, hostname, created_at, edited_at, active)
FROM Routes
WHERE id=?
LIMIT 1;
SELECT id, creator, hostname, created_at, edited_at, active FROM Routes WHERE id = $1 LIMIT 1;
-- name: find-one-route-by-host
SELECT
(id, creator, hostname, created_at, edited_at, active)
FROM Routes
WHERE hostname=?
LIMIT 1;
SELECT id, creator, hostname, created_at, edited_at, active FROM Routes WHERE hostname = $1 LIMIT 1;
-- name: find-all-routes-for-user
SELECT
(id, creator, hostname, created_at, edited_at, active)
FROM Routes
WHERE creator=?;
SELECT id, creator, hostname, created_at, edited_at, active FROM Routes WHERE creator = $1;
-- name: delete-one-route
DELETE
FROM Routes
WHERE
id=? AND domain=?
LIMIT 1;
DELETE FROM Routes WHERE id = $1 AND domain = $2 LIMIT 1;
-- name: insert-token
INSERT INTO Tokens
(body, creator, scopes, expires_at)
VALUES
(?, ?, ?, ?);
INSERT INTO Tokens(body, creator, scopes, expires_at) VALUES ($1, $2, $3, $4);
-- name: get-one-token
SELECT
(id, body, creator, scopes, created_at, expires_at, active)
FROM Tokens
WHERE id=?
LIMIT 1;
SELECT id, body, creator, scopes, created_at, expires_at, active FROM Tokens WHERE id = $1 LIMIT 1;
-- name: get-one-token-by-body
SELECT
(id, body, creator, scopes, created_at, expires_at, active)
FROM Tokens
WHERE body=?
LIMIT 1;
SELECT id, body, creator, scopes, created_at, expires_at, active FROM Tokens WHERE body = $1 LIMIT 1;
-- name: get-all-tokens-for-user
SELECT
(id, body, creator, scopes, created_at, expires_at, active)
FROM Tokens
WHERE creator=?;
SELECT id, body, creator, scopes, created_at, expires_at, active FROM Tokens WHERE creator = $1;
-- name: remove-one-token
DELETE FROM Tokens WHERE id = $1 LIMIT 1;
DELETE
FROM Tokens
WHERE id=?
LIMIT 1;
-- name: remove-expired-tokens
DELETE
FROM Tokens
WHERE expires_at - interval '7 days';

View File

@ -0,0 +1,180 @@
package database
import (
"bytes"
"database/sql"
"log"
"git.xeserv.us/xena/route/internal/database/dmigrations"
"github.com/Xe/uuid"
"github.com/brandur/simplebox"
"github.com/gchaincl/dotsql"
_ "github.com/lib/pq"
"golang.org/x/net/context"
)
type Scanner interface {
Scan(...interface{}) error
}
type PostgresStorage struct {
ds *dotsql.DotSql
db *sql.DB
sb *simplebox.SimpleBox
//cs *postgresCertificateStorage
rs *postgresRouteStorage
//ts *postgresTokenStorage
}
type postgresCertificateStorage struct {
*PostgresStorage
}
type postgresRouteStorage struct {
*PostgresStorage
}
type postgresTokenStorage struct {
*PostgresStorage
}
// NewPostgresStorage creates a new Storage instance backed by postgres at the
// given URL.
func NewPostgresStorage(url string, key *[32]byte) (Storage, error) {
db, err := sql.Open("postgres", url)
if err != nil {
return nil, err
}
data, err := dmigrations.Asset("postgres.sql")
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(data)
ds, err := dotsql.Load(buf)
if err != nil {
return nil, err
}
for k := range ds.QueryMap() {
log.Printf("preparing %s", k)
stmt, err := ds.Prepare(db, k)
if err != nil {
db.Close()
return nil, err
}
defer stmt.Close()
}
p := &PostgresStorage{
db: db,
ds: ds,
sb: simplebox.NewFromSecretKey(key),
}
//p.cs = &postgresCertificateStorage{p}
p.rs = &postgresRouteStorage{p}
//p.ts = &postgresTokenStorage{p}
return p, nil
}
// Certs gets the certificate storage interface.
func (p *PostgresStorage) Certs() Certs { return nil }
// Routes gets the route storage interface.
func (p *PostgresStorage) Routes() Routes { return p.rs }
// Tokens gets the token storage interface.
func (p *PostgresStorage) Tokens() Tokens { return nil }
// Close cleans up resources for this Storage.
func (p *PostgresStorage) Close() error { return p.db.Close() }
// interface compliance
var (
_ Storage = &PostgresStorage{}
//_ Certs = &postgresCertificateStorage{}
_ Routes = &postgresRouteStorage{}
//_ Tokens = &postgresTokenStorage{}
)
func (p *postgresRouteStorage) getRouteInner(ctx context.Context, arg string, kind string) (Route, error) {
r, err := p.ds.QueryRow(p.db, kind, arg)
if err != nil {
return Route{}, err
}
rt := Route{}
err = (&rt).Scan(r)
if err != nil {
return Route{}, err
}
return rt, nil
}
func (p *postgresRouteStorage) Get(ctx context.Context, id string) (Route, error) {
return p.getRouteInner(ctx, id, "find-one-route-by-id")
}
func (p *postgresRouteStorage) GetHost(ctx context.Context, host string) (Route, error) {
return p.getRouteInner(ctx, host, "find-one-route-by-host")
}
func (p *postgresRouteStorage) GetAll(ctx context.Context, user string) ([]Route, error) {
var result []Route
rows, err := p.ds.Query(p.db, "find-all-routes-for-user", user)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
rt := &Route{}
if err := rt.Scan(rows); err != nil {
return nil, err
}
result = append(result, *rt)
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (p *postgresRouteStorage) Put(ctx context.Context, r Route) (Route, error) {
if r.ID == "" {
r.ID = uuid.New()
}
_, err := p.ds.Exec(p.db, "insert-route", r.ID, r.Hostname)
if err != nil {
return Route{}, err
}
return p.Get(ctx, r.ID)
}
func (p *postgresRouteStorage) Delete(ctx context.Context, r Route) (Route, error) {
rt, err := p.Get(ctx, r.ID)
if err != nil {
return Route{}, err
}
_, err = p.ds.Exec(p.db, "delete-one-route", rt.ID, rt.Hostname)
if err != nil {
return Route{}, err
}
return rt, nil
}

View File

@ -2,6 +2,7 @@ package database
import (
"io"
"time"
proto "git.xeserv.us/xena/route/proto"
"github.com/Xe/ln"
@ -26,6 +27,10 @@ type Route struct {
ID string `storm:"id"`
Creator string
Hostname string `storm:"unique"`
CreatedAt time.Time
EditedAt time.Time
Active bool
}
// F https://godoc.org/github.com/Xe/ln#F
@ -37,6 +42,10 @@ func (r Route) F() ln.F {
}
}
func (r *Route) Scan(row Scanner) error {
return row.Scan(&r.ID, &r.Creator, &r.Hostname, &r.CreatedAt, &r.EditedAt, &r.Active)
}
// AsProto converts this into a protobuf Route.
func (r Route) AsProto() *proto.Route {
return &proto.Route{

View File

@ -75,3 +75,11 @@ func TestBoltDBRouteStorage(t *testing.T) {
testRoutes(ctx, t, st.Routes())
}
func TestPostgresRouteStorage(t *testing.T) {
st, ctx, cancel := newTestPostgresStorage(t, os.Getenv("DATABASE_URL"))
defer st.Close()
defer cancel()
testRoutes(ctx, t, st.Routes())
}