Files
o11y/pkg/sqlstore/sqlitesqlstore/provider.go
T
zeekay 12b414f972 State a version once
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.

Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.
2026-08-29 13:46:58 -07:00

123 lines
4.4 KiB
Go

package sqlitesqlstore
import (
"context"
"database/sql"
"log/slog"
"net/url"
"strconv"
"github.com/hanzoai/o11y/pkg/errors"
"github.com/hanzoai/o11y/pkg/factory"
"github.com/hanzoai/o11y/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/sqlitedialect"
// hanzoai/sqlite names a facade, not an engine. Importing it registers the
// "sqlite" database/sql driver exactly once — SQLCipher/csqlite under cgo,
// pure-Go otherwise — and supplies the backend-neutral pragma and
// constraint-error primitives used below. Naming an engine here instead would
// claim that driver name a second time in the cgo cloud binary, panicking at
// init, and would pin this package to one backend's DSN dialect.
"github.com/hanzoai/sqlite"
)
type provider struct {
settings factory.ScopedProviderSettings
sqldb *sql.DB
bundb *sqlstore.BunDB
dialect *dialect
formatter sqlstore.SQLFormatter
}
func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("sqlite"), func(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) {
hooks := make([]sqlstore.SQLStoreHook, len(hookFactories))
for i, hookFactory := range hookFactories {
hook, err := hookFactory.New(ctx, providerSettings, config)
if err != nil {
return nil, err
}
hooks[i] = hook
}
return New(ctx, providerSettings, config, hooks...)
})
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config, hooks ...sqlstore.SQLStoreHook) (sqlstore.SQLStore, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/hanzoai/o11y/pkg/sqlitesqlstore")
// Build the DSN with backend-correct pragma syntax. hanzoai/sqlite.PragmaDSN
// emits `_pragma=journal_mode(wal)` under the modernc backend and
// `_journal_mode=wal` under the mattn/SQLCipher backend, so the pragmas
// actually apply whichever backend the binary links — this package is built
// CGO=0 standalone but CGO=1 inside the cloud binary, and a single hardcoded
// syntax is silently dropped by the other backend. busy_timeout MUST lead
// (WAL cannot be enabled while another connection holds the db). _txlock is a
// driver connection param (not a pragma) that both backends honor; append it.
dsn := sqlite.PragmaDSN(config.Sqlite.Path, []sqlite.Pragma{
{Name: "busy_timeout", Value: strconv.FormatInt(config.Sqlite.BusyTimeout.Milliseconds(), 10)},
{Name: "journal_mode", Value: config.Sqlite.Mode},
{Name: "foreign_keys", Value: "1"},
}) + "&_txlock=" + url.QueryEscape(config.Sqlite.TransactionMode)
sqldb, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, err
}
settings.Logger().InfoContext(ctx, "connected to sqlite", slog.String("path", config.Sqlite.Path))
sqldb.SetMaxOpenConns(config.Connection.MaxOpenConns)
sqldb.SetConnMaxLifetime(config.Connection.MaxConnLifetime)
sqliteDialect := sqlitedialect.New()
bunDB := sqlstore.NewBunDB(settings, sqldb, sqliteDialect, hooks)
return &provider{
settings: settings,
sqldb: sqldb,
bundb: bunDB,
dialect: new(dialect),
formatter: newFormatter(bunDB.Dialect()),
}, nil
}
func (provider *provider) BunDB() *bun.DB {
return provider.bundb.DB
}
func (provider *provider) SQLDB() *sql.DB {
return provider.sqldb
}
func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *provider) Formatter() sqlstore.SQLFormatter {
return provider.formatter
}
func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB {
return provider.bundb.BunDBCtx(ctx)
}
func (provider *provider) RunInTxCtx(ctx context.Context, opts *sql.TxOptions, cb func(ctx context.Context) error) error {
return provider.bundb.RunInTxCtx(ctx, opts, cb)
}
func (provider *provider) WrapNotFoundErrf(err error, code errors.Code, format string, args ...any) error {
if err == sql.ErrNoRows {
return errors.Wrapf(err, errors.TypeNotFound, code, format, args...)
}
return err
}
func (provider *provider) WrapAlreadyExistsErrf(err error, code errors.Code, format string, args ...any) error {
if sqlite.IsConstraintUnique(err) || sqlite.IsConstraintPrimaryKey(err) || sqlite.IsConstraintForeignKey(err) {
return errors.Wrapf(err, errors.TypeAlreadyExists, code, format, args...)
}
return err
}