mirror of https://github.com/jlelse/GoBlog
Refactor database hooks to make them more useful
This commit is contained in:
parent
2bf98dddaa
commit
da9ea7c0d7
101
database.go
101
database.go
|
@ -1,15 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gchaincl/sqlhooks/v2"
|
||||
sqlite "github.com/mattn/go-sqlite3"
|
||||
"github.com/schollz/sqlite3dump"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
@ -22,10 +21,11 @@ type database struct {
|
|||
sg singleflight.Group // singleflight group for prepared statements
|
||||
ps sync.Map // map with prepared statements
|
||||
// Other things
|
||||
pc singleflight.Group // persistant cache
|
||||
pcm sync.Mutex // post creation
|
||||
sp singleflight.Group // singleflight group for short path requests
|
||||
spc sync.Map // shortpath cache
|
||||
pc singleflight.Group // persistant cache
|
||||
pcm sync.Mutex // post creation
|
||||
sp singleflight.Group // singleflight group for short path requests
|
||||
spc sync.Map // shortpath cache
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (a *goBlog) initDatabase(logging bool) (err error) {
|
||||
|
@ -60,8 +60,8 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
|||
|
||||
func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
||||
// Register driver
|
||||
dbDriverName := generateRandomString(15)
|
||||
var dr driver.Driver = &sqlite.SQLiteDriver{
|
||||
dbDriverName := "goblog_db_" + generateRandomString(15)
|
||||
sql.Register(dbDriverName, &sqlite.SQLiteDriver{
|
||||
ConnectHook: func(c *sqlite.SQLiteConn) error {
|
||||
// Register functions
|
||||
for n, f := range map[string]interface{}{
|
||||
|
@ -79,13 +79,9 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if c := a.cfg.Db; c != nil && c.Debug {
|
||||
dr = sqlhooks.Wrap(dr, &dbHooks{})
|
||||
}
|
||||
sql.Register("goblog_db_"+dbDriverName, dr)
|
||||
})
|
||||
// Open db
|
||||
db, err := sql.Open("goblog_db_"+dbDriverName, file+"?mode=rwc&_journal_mode=WAL&_busy_timeout=100&cache=shared")
|
||||
db, err := sql.Open(dbDriverName, file+"?mode=rwc&_journal_mode=WAL&_busy_timeout=100&cache=shared")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -118,8 +114,14 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Debug
|
||||
debug := false
|
||||
if c := a.cfg.Db; c != nil && c.Debug {
|
||||
debug = true
|
||||
}
|
||||
return &database{
|
||||
db: db,
|
||||
db: db,
|
||||
debug: debug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -161,6 +163,9 @@ func (db *database) prepare(query string) (*sql.Stmt, error) {
|
|||
return st, nil
|
||||
})
|
||||
if err != nil {
|
||||
if db.debug {
|
||||
log.Printf(`Failed to prepare query "%s": %s`, query, err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return stmt.(*sql.Stmt), nil
|
||||
|
@ -172,45 +177,69 @@ func (db *database) exec(query string, args ...interface{}) (sql.Result, error)
|
|||
// Lock execution
|
||||
db.em.Lock()
|
||||
defer db.em.Unlock()
|
||||
// Check if prepared cache should be skipped
|
||||
// Check if no cache arg set
|
||||
cache := true
|
||||
if len(args) > 0 && args[0] == dbNoCache {
|
||||
return db.db.Exec(query, args[1:]...)
|
||||
cache = false
|
||||
args = args[1:]
|
||||
}
|
||||
// Use prepared statement
|
||||
st, _ := db.prepare(query)
|
||||
// Maybe prepare
|
||||
var st *sql.Stmt
|
||||
if cache {
|
||||
st, _ = db.prepare(query)
|
||||
}
|
||||
// Prepare context, call hook
|
||||
ctx := db.dbBefore(context.Background(), query, args...)
|
||||
defer db.dbAfter(ctx, query, args...)
|
||||
// Execute
|
||||
if st != nil {
|
||||
return st.Exec(args...)
|
||||
return st.ExecContext(ctx, args...)
|
||||
}
|
||||
// Or execute directly
|
||||
return db.db.Exec(query, args...)
|
||||
return db.db.ExecContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
func (db *database) query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
// Check if prepared cache should be skipped
|
||||
// Check if no cache arg set
|
||||
cache := true
|
||||
if len(args) > 0 && args[0] == dbNoCache {
|
||||
return db.db.Query(query, args[1:]...)
|
||||
cache = false
|
||||
args = args[1:]
|
||||
}
|
||||
// Use prepared statement
|
||||
st, _ := db.prepare(query)
|
||||
// Maybe prepare
|
||||
var st *sql.Stmt
|
||||
if cache {
|
||||
st, _ = db.prepare(query)
|
||||
}
|
||||
// Prepare context, call hook
|
||||
ctx := db.dbBefore(context.Background(), query, args...)
|
||||
defer db.dbAfter(ctx, query, args...)
|
||||
// Query
|
||||
if st != nil {
|
||||
return st.Query(args...)
|
||||
return st.QueryContext(ctx, args...)
|
||||
}
|
||||
// Or query directly
|
||||
return db.db.Query(query, args...)
|
||||
return db.db.QueryContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
func (db *database) queryRow(query string, args ...interface{}) (*sql.Row, error) {
|
||||
// Check if prepared cache should be skipped
|
||||
// Check if no cache arg set
|
||||
cache := true
|
||||
if len(args) > 0 && args[0] == dbNoCache {
|
||||
return db.db.QueryRow(query, args[1:]...), nil
|
||||
cache = false
|
||||
args = args[1:]
|
||||
}
|
||||
// Use prepared statement
|
||||
st, _ := db.prepare(query)
|
||||
// Maybe prepare
|
||||
var st *sql.Stmt
|
||||
if cache {
|
||||
st, _ = db.prepare(query)
|
||||
}
|
||||
// Prepare context, call hook
|
||||
ctx := db.dbBefore(context.Background(), query, args...)
|
||||
defer db.dbAfter(ctx, query, args...)
|
||||
// Query
|
||||
if st != nil {
|
||||
return st.QueryRow(args...), nil
|
||||
return st.QueryRowContext(ctx, args...), nil
|
||||
}
|
||||
// Or query directly
|
||||
return db.db.QueryRow(query, args...), nil
|
||||
return db.db.QueryRowContext(ctx, query, args...), nil
|
||||
}
|
||||
|
||||
// Other things
|
||||
|
|
|
@ -2,20 +2,62 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type dbHooks struct{}
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
const dbHooksBegin contextKey = "begin"
|
||||
|
||||
func (h *dbHooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
|
||||
return context.WithValue(ctx, dbHooksBegin, time.Now()), nil
|
||||
func (db *database) dbBefore(ctx context.Context, query string, args ...interface{}) context.Context {
|
||||
if !db.debug {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, dbHooksBegin, time.Now())
|
||||
}
|
||||
|
||||
func (h *dbHooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
|
||||
begin := ctx.Value(dbHooksBegin).(time.Time)
|
||||
log.Printf("SQL: %s %q (%s)\n", query, args, time.Since(begin))
|
||||
return ctx, nil
|
||||
func (db *database) dbAfter(ctx context.Context, query string, args ...interface{}) {
|
||||
if !db.debug {
|
||||
return
|
||||
}
|
||||
dur := time.Since(ctx.Value(dbHooksBegin).(time.Time))
|
||||
var logBuilder strings.Builder
|
||||
logBuilder.WriteString("\nQuery: ")
|
||||
logBuilder.WriteString(`"`)
|
||||
logBuilder.WriteString(query)
|
||||
logBuilder.WriteString(`"`)
|
||||
if len(args) > 0 {
|
||||
logBuilder.WriteString("\nArgs: ")
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
logBuilder.WriteString(", ")
|
||||
}
|
||||
if named, ok := arg.(sql.NamedArg); ok && named.Name != "" {
|
||||
logBuilder.WriteString("(")
|
||||
logBuilder.WriteString(named.Name)
|
||||
logBuilder.WriteString(`) "`)
|
||||
logBuilder.WriteString(argToString(named.Value))
|
||||
logBuilder.WriteString(`"`)
|
||||
} else {
|
||||
logBuilder.WriteString(`"`)
|
||||
logBuilder.WriteString(argToString(arg))
|
||||
logBuilder.WriteString(`"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
logBuilder.WriteString("\nDuration: ")
|
||||
logBuilder.WriteString(dur.String())
|
||||
log.Println(logBuilder.String())
|
||||
}
|
||||
|
||||
func argToString(arg interface{}) string {
|
||||
val := cast.ToString(arg)
|
||||
if val == "" {
|
||||
val = fmt.Sprintf("%v", arg)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -16,7 +16,6 @@ require (
|
|||
github.com/dgraph-io/ristretto v0.1.0
|
||||
github.com/elnormous/contenttype v1.0.0
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/gchaincl/sqlhooks/v2 v2.0.1
|
||||
github.com/go-chi/chi/v5 v5.0.3
|
||||
github.com/go-fed/httpsig v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
|
|
5
go.sum
5
go.sum
|
@ -111,8 +111,6 @@ github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW
|
|||
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gchaincl/sqlhooks/v2 v2.0.1 h1:j9WZAq1Tx/xngDfEdsgUww+o9iY3rLOYGYhYWcFxdmI=
|
||||
github.com/gchaincl/sqlhooks/v2 v2.0.1/go.mod h1:Qv7HXjGB9TehamVK52yW5H+a0RRhprIj3ESTcWOG2jw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
|
||||
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
|
@ -261,7 +259,6 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECae
|
|||
github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE=
|
||||
github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lopezator/migrator v0.3.0 h1:VW/rR+J8NYwPdkBxjrFdjwejpgvP59LbmANJxXuNbuk=
|
||||
|
@ -272,7 +269,6 @@ github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O
|
|||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
||||
|
@ -292,7 +288,6 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
|
||||
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=
|
||||
|
|
Loading…
Reference in New Issue