2014-08-03 15:59:56 +02:00
|
|
|
// Copyright 2014 by Sascha L. Teichmann
|
|
|
|
// Use of this source code is governed by the MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
|
|
|
|
2014-08-03 14:52:24 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"log"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
)
|
|
|
|
|
|
|
|
var globalLock sync.RWMutex
|
|
|
|
|
|
|
|
const (
|
|
|
|
fetchSql = "SELECT data FROM blocks WHERE pos = ?"
|
|
|
|
existsSql = "SELECT 1 FROM blocks WHERE pos = ?"
|
|
|
|
updateSql = "UPDATE blocks SET data = ? WHERE pos = ?"
|
|
|
|
insertSql = "INSERT INTO blocks (pos, data) VALUES (?, ?)"
|
|
|
|
)
|
|
|
|
|
|
|
|
type SqliteBackend struct {
|
|
|
|
db *sql.DB
|
|
|
|
existsStmt *sql.Stmt
|
|
|
|
fetchStmt *sql.Stmt
|
|
|
|
insertStmt *sql.Stmt
|
|
|
|
updateStmt *sql.Stmt
|
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
type SqliteSession struct {
|
|
|
|
backend *SqliteBackend
|
|
|
|
tx *sql.Tx
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ss *SqliteBackend) NewSession() (Session, error) {
|
|
|
|
return &SqliteSession{ss, nil}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ss *SqliteSession) Close() error {
|
|
|
|
t := ss.tx
|
|
|
|
if t != nil {
|
|
|
|
ss.tx = nil
|
|
|
|
return t.Rollback()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-08-03 14:52:24 +02:00
|
|
|
func NewSqliteBackend(path string) (sqlb *SqliteBackend, err error) {
|
|
|
|
|
|
|
|
res := SqliteBackend{}
|
|
|
|
|
|
|
|
if res.db, err = sql.Open("sqlite3", path); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.existsStmt, err = res.db.Prepare(existsSql); err != nil {
|
|
|
|
res.closeAll()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.fetchStmt, err = res.db.Prepare(fetchSql); err != nil {
|
|
|
|
res.closeAll()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.insertStmt, err = res.db.Prepare(insertSql); err != nil {
|
|
|
|
res.closeAll()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.updateStmt, err = res.db.Prepare(updateSql); err != nil {
|
|
|
|
res.closeAll()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlb = &res
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func closeStmt(stmt **sql.Stmt) error {
|
|
|
|
s := *stmt
|
|
|
|
if s != nil {
|
|
|
|
*stmt = nil
|
|
|
|
return s.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func closeDB(db **sql.DB) error {
|
|
|
|
d := *db
|
|
|
|
if d != nil {
|
|
|
|
*db = nil
|
|
|
|
return d.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sqlb *SqliteBackend) closeAll() error {
|
|
|
|
closeStmt(&sqlb.fetchStmt)
|
|
|
|
closeStmt(&sqlb.insertStmt)
|
|
|
|
closeStmt(&sqlb.updateStmt)
|
|
|
|
closeStmt(&sqlb.existsStmt)
|
|
|
|
return closeDB(&sqlb.db)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sqlb *SqliteBackend) Shutdown() error {
|
|
|
|
globalLock.Lock()
|
|
|
|
defer globalLock.Unlock()
|
|
|
|
|
|
|
|
return sqlb.closeAll()
|
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
func (ss *SqliteSession) txStmt(stmt *sql.Stmt) *sql.Stmt {
|
|
|
|
if ss.tx != nil {
|
|
|
|
return ss.tx.Stmt(stmt)
|
2014-08-03 14:52:24 +02:00
|
|
|
}
|
|
|
|
return stmt
|
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
func (ss *SqliteSession) Fetch(hash, key []byte) (data []byte, err error) {
|
2014-08-03 14:52:24 +02:00
|
|
|
var pos int64
|
|
|
|
if pos, err = bytes2pos(key); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
globalLock.RLock()
|
|
|
|
defer globalLock.RUnlock()
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
fetchStmt := ss.txStmt(ss.backend.fetchStmt)
|
2014-08-03 14:52:24 +02:00
|
|
|
err2 := fetchStmt.QueryRow(pos).Scan(&data)
|
|
|
|
if err2 == sql.ErrNoRows {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = err2
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
func (ss *SqliteSession) InTransaction() bool {
|
|
|
|
return ss.tx != nil
|
2014-08-03 14:52:24 +02:00
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
func (ss *SqliteSession) Store(hash, key, value []byte) (exists bool, err error) {
|
2014-08-03 14:52:24 +02:00
|
|
|
var pos int64
|
|
|
|
if pos, err = bytes2pos(key); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
globalLock.Lock()
|
|
|
|
defer globalLock.Unlock()
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
existsStmt := ss.txStmt(ss.backend.existsStmt)
|
2014-08-03 14:52:24 +02:00
|
|
|
var x int
|
|
|
|
err2 := existsStmt.QueryRow(pos).Scan(&x)
|
|
|
|
|
|
|
|
if err2 == sql.ErrNoRows {
|
|
|
|
exists = false
|
|
|
|
} else if err2 != nil {
|
|
|
|
err = err2
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
exists = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
2014-08-06 01:11:41 +02:00
|
|
|
updateStmt := ss.txStmt(ss.backend.updateStmt)
|
2014-08-03 14:52:24 +02:00
|
|
|
_, err = updateStmt.Exec(value, pos)
|
|
|
|
} else {
|
2014-08-06 01:11:41 +02:00
|
|
|
insertStmt := ss.txStmt(ss.backend.insertStmt)
|
2014-08-03 14:52:24 +02:00
|
|
|
_, err = insertStmt.Exec(pos, value)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
func (ss *SqliteSession) BeginTransaction() (err error) {
|
|
|
|
if ss.tx != nil {
|
2014-08-03 14:52:24 +02:00
|
|
|
log.Println("WARN: Already running transaction.")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
globalLock.Lock()
|
|
|
|
defer globalLock.Unlock()
|
2014-08-06 01:11:41 +02:00
|
|
|
ss.tx, err = ss.backend.db.Begin()
|
2014-08-03 14:52:24 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
func (ss *SqliteSession) CommitTransaction() error {
|
2014-08-03 14:52:24 +02:00
|
|
|
|
2014-08-06 01:11:41 +02:00
|
|
|
tx := ss.tx
|
2014-08-03 14:52:24 +02:00
|
|
|
if tx == nil {
|
|
|
|
log.Println("WARN: No transaction running.")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
globalLock.Lock()
|
|
|
|
defer globalLock.Unlock()
|
2014-08-06 01:11:41 +02:00
|
|
|
ss.tx = nil
|
2014-08-03 14:52:24 +02:00
|
|
|
return tx.Commit()
|
|
|
|
}
|