Categorygithub.com/shogo82148/go-sql-proxy
modulepackage
0.7.2
Repository: https://github.com/shogo82148/go-sql-proxy.git
Documentation: pkg.go.dev

# README

go-sql-proxy

Build Status Go Report Card PkgGoDev Coverage Status

The proxy package is a proxy driver for the database/sql package. You can hook SQL executions. It supports Go 1.8 or laster.

SYNOPSIS

Use Ready‐Made SQL tracer

proxy.RegisterTracer is a shortcut for registering a SQL query tracer.

package main

import (
	"context"
	"database/sql"

	"github.com/shogo82148/go-sql-proxy"
)

func main() {
	proxy.RegisterTracer()

	db, _ := sql.Open("origin:trace", "data source")
	db.Exec("CREATE TABLE t1 (id INTEGER PRIMARY KEY)")
	// STDERR: main.go:14: Exec: CREATE TABLE t1 (id INTEGER PRIMARY KEY); args = [] (0s)
}

Use proxy.NewTraceProxy to change the log output destination.

logger := New(os.Stderr, "", LstdFlags)
tracer := NewTraceProxy(&another.Driver{}, logger)
sql.Register("origin:trace", tracer)
db, err := sql.Open("origin:tracer", "data source")

Use with the context package

From Go version 1.8 onward, database/sql supports the context package. You can register your hooks into the context.

package main

import (
	"context"
	"database/sql"

	"github.com/shogo82148/go-sql-proxy"
)

var tracer = proxy.NewTraceHooks(proxy.TracerOptions{})

func main() {
	proxy.RegisterProxy()

	db, _ := sql.Open("origin:proxy", "data source")

	// The tracer is enabled in this context.
	ctx := proxy.WithHooks(context.Background(), tracer)
	db.ExecContext(ctx, "CREATE TABLE t1 (id INTEGER PRIMARY KEY)")
}

Create your own hooks

First, register new proxy driver.

hooks := &proxy.HooksContext{
	// Hook functions here(Open, Exec, Query, etc.)
	// See godoc for more details
}
sql.Register("new-proxy-name", proxy.NewProxyContext(&origin.Driver{}, hooks))

And then, open new database handle with the registered proxy driver.

db, err := sql.Open("new-proxy-name", "data source")

EXAMPLES

EXAMPLE: SQL tracer

package main

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"log"

	"github.com/mattn/go-sqlite3"
	"github.com/shogo82148/go-sql-proxy"
)

func main() {
	sql.Register("sqlite3-proxy", proxy.NewProxyContext(&sqlite3.SQLiteDriver{}, &proxy.HooksContext{
		Open: func(_ context.Context, _ interface{}, conn *proxy.Conn) error {
			log.Println("Open")
			return nil
		},
		Exec: func(_ context.Context, _ interface{}, stmt *proxy.Stmt, args []driver.NamedValue, result driver.Result) error {
			log.Printf("Exec: %s; args = %v\n", stmt.QueryString, args)
			return nil
		},
		Query: func(_ context.Context, _ interface{}, stmt *proxy.Stmt, args []driver.NamedValue, rows driver.Rows) error {
			log.Printf("Query: %s; args = %v\n", stmt.QueryString, args)
			return nil
		},
		Begin: func(_ context.Context, _ interface{}, conn *proxy.Conn) error {
			log.Println("Begin")
			return nil
		},
		Commit: func(_ context.Context, _ interface{}, tx *proxy.Tx) error {
			log.Println("Commit")
			return nil
		},
		Rollback: func(_ context.Context, _ interface{}, tx *proxy.Tx) error {
			log.Println("Rollback")
			return nil
		},

	}))

	db, err := sql.Open("sqlite3-proxy", ":memory:")
	if err != nil {
		log.Fatalf("Open filed: %v", err)
	}
	defer db.Close()

	_, err = db.Exec(
		"CREATE TABLE t1 (id INTEGER PRIMARY KEY)",
	)
	if err != nil {
		log.Fatal(err)
	}
}

EXAMPLE: elapsed time logger

package main

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"log"
	"time"

	"github.com/mattn/go-sqlite3"
	"github.com/shogo82148/go-sql-proxy"
)

func main() {
	sql.Register("sqlite3-proxy", proxy.NewProxyContext(&sqlite3.SQLiteDriver{}, &proxy.HooksContext{
		PreExec: func(_ context.Context, _ *proxy.Stmt, _ []driver.NamedValue) (interface{}, error) {
			// The first return value(time.Now()) is passed to both `Hooks.Exec` and `Hook.ExecPost` callbacks.
			return time.Now(), nil
		},
		PostExec: func(_ context.Context, ctx interface{}, stmt *proxy.Stmt, args []driver.NamedValue, _ driver.Result, _ error) error {
			// The `ctx` parameter is the return value supplied from the `Hooks.PreExec` method, and may be nil.
			log.Printf("Query: %s; args = %v (%s)\n", stmt.QueryString, args, time.Since(ctx.(time.Time)))
			return nil
		},
	}))

	db, err := sql.Open("sqlite3-proxy", ":memory:")
	if err != nil {
		log.Fatalf("Open filed: %v", err)
	}
	defer db.Close()

	_, err = db.Exec(
		"CREATE TABLE t1 (id INTEGER PRIMARY KEY)",
	)
	if err != nil {
		log.Fatal(err)
	}
}

LICENSE

This software is released under the MIT License, see LICENSE file.

godoc

See godoc for more information.

# Functions

NewConnector creates new proxied Connector.
NewProxy creates new Proxy driver.
NewProxyContext creates new Proxy driver.
NewTraceHooks creates new HooksContext which trace SQL queries.
NewTraceProxy generates a proxy that logs queries.
NewTraceProxyWithFilter generates a proxy that logs queries.
RegisterProxy creates proxies that do not anything by default, and registers the proxies as sql driver.
RegisterTracer creates proxies that log queries from the sql drivers already registered, and registers the proxies as sql driver.
WithHooks returns a copy of parent context in which the hooks associated.

# Variables

DefaultPackageFilter ignores some database util package.

# Structs

Conn adds hook points into "database/sql/driver".Conn.
Connector adds hook points into "database/sql/driver".Connector.
Hooks is callback functions for the proxy.
HooksContext is callback functions with context.Context for the proxy.
Proxy is a sql driver.
Stmt adds hook points into "database/sql/driver".Stmt.
TracerOptions holds the tracing option.
Tx adds hook points into "database/sql/driver".Tx.

# Interfaces

Filter is used by the tracing proxy for skipping database libraries (e.g.
Outputter is what is used by the tracing proxy created via `NewTraceProxy`.

# Type aliases

PackageFilter is an implementation of Filter.