mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
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.
123 lines
4.4 KiB
Go
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
|
|
}
|