diff --git a/database.go b/database.go index 64d0fff..eb95541 100644 --- a/database.go +++ b/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 diff --git a/databaseHooks.go b/databaseHooks.go index 83d26fa..07c0497 100644 --- a/databaseHooks.go +++ b/databaseHooks.go @@ -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 } diff --git a/go.mod b/go.mod index dbaa080..31b55de 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index de90c86..4518272 100644 --- a/go.sum +++ b/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=