Skip to content

Add connection hook

Angus Dippenaar requested to merge angaz/sqlite:connection_hook into master

Add a connection hook, so the user can add their own functionality to the process of creating a connection.

My use-case is to add extra ATTACH databases. IDK if this kind of functionality should be integrated into the project directly, I think since it's not standard sqlite syntax, I think leaving it up to users to create hooks is useful. I have this kind of thing copy-pasted in a few projects, so I think I would create a library for building the connection string and adding the attach hook.

Here's example usage:

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"fmt"
	"net/url"

	"modernc.org/sqlite"
)

func OpenSQLite() *sql.DB {
	attachQuery := url.Values{
		"_name": []string{
			"test2",
		},
		"_pragma": []string{
			"journal_mode=wal",
		},
	}

	attachDSN := url.URL{
		Path:     "test2.db",
		RawQuery: attachQuery.Encode(),
	}

	query := url.Values{
		"_pragma": []string{
			"auto_vacuum=incremental",
			"journal_mode=wal",
		},
		"_attach": []string{
			attachDSN.String(),
		},
	}

	dsn := url.URL{
		Path:     "test.db",
		RawQuery: query.Encode(),
	}

	// test.db?_attach=test2.db%3F_name%3Dtest2%26_pragma%3Djournal_mode%253Dwal&_pragma=auto_vacuum%3Dincremental&_pragma=journal_mode%3Dwal
	db, err := sql.Open("sqlite", dsn.String())
	if err != nil {
		panic(err)
	}

	return db
}

func init() {
	sqlite.RegisterConnectionHook(attachHook)
}

func attachHook(
	conn interface {
		driver.ExecerContext
		driver.QueryerContext
	}, dsn string,
) error {
	uri, err := url.Parse(dsn)
	if err != nil {
		return fmt.Errorf("parse dsn: %w", err)
	}

	query := uri.Query()

	if !query.Has("_attach") {
		return nil
	}

	for _, attachStr := range query["_attach"] {
		unescapedAttachStr, err := url.QueryUnescape(attachStr)
		if err != nil {
			return fmt.Errorf(
				"unescape attach filename: %s: %w", attachStr, err)
		}

		attachURI, err := url.Parse(unescapedAttachStr)
		if err != nil {
			return fmt.Errorf(
				"parse attach uri: %s: %w", unescapedAttachStr, err)
		}

		err = attachDatabase(conn, attachURI)
		if err != nil {
			return fmt.Errorf("attach database: %w", err)
		}
	}

	return nil
}

func attachDatabase(conn driver.ExecerContext, uri *url.URL) error {
	query := uri.Query()
	databaseName := query.Get("_name")

	_, err := conn.ExecContext(
		context.Background(),
		fmt.Sprintf(`ATTACH DATABASE '%s' AS %s`, uri.Path, databaseName),
		nil,
	)
	if err != nil {
		return fmt.Errorf("attach exec failed: %w", err)
	}

	for _, value := range query["_pragma"] {
		cmd := "pragma " + databaseName + "." + value

		_, err = conn.ExecContext(context.Background(), cmd, nil)
		if err != nil {
			return fmt.Errorf("pragma exec failed: %w", err)
		}
	}

	return nil
}
Edited by Angus Dippenaar

Merge request reports

Loading