sqlcore

package
v0.2.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package sqlcore provides a database/sql-backed DBAdapter that works with both PostgreSQL ($N placeholders) and SQLite (? placeholders).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Quote

func Quote(name string) string

Quote is the exported version of q for packages that need to build SQL fragments with consistent identifier quoting (e.g. validate.UniqueField composing parameterised SELECT COUNT(*) queries). The encoding is ANSI SQL §5.2: double-quote delimiters with embedded quotes doubled. Works with both Postgres and SQLite.

Types

type Adapter

type Adapter struct {
	// contains filtered or unexported fields
}

Adapter is a generic SQL DBAdapter backed by a *sql.DB.

func New

func New(writeDb *sql.DB, readDb *sql.DB, driver maniflex.DriverType, reg maniflex.RegistryAccessor) *Adapter

New wraps an existing *sql.DB and returns a maniflex.DBAdapter. Pass the same RegistryAccessor you gave to maniflex.New — the adapter uses it to resolve related models for include population and migrations.

func (*Adapter) AutoMigrate

func (a *Adapter) AutoMigrate(ctx context.Context, reg maniflex.RegistryAccessor) error

AutoMigrate synchronises the database schema with the registered models.

For each model it:

  1. Creates the table if it does not exist (original behaviour, safe no-op on re-run).
  2. Introspects the live table's columns and compares them to the model's fields.
  3. Issues ALTER TABLE ADD COLUMN for every field present in the model but absent from the table — allowing structs to grow without manual DDL.
  4. Logs a WARNING (slog.LevelWarn) for every column present in the table but absent from the model, indicating a likely removed field. It never drops columns automatically.

The entire migrate-and-diff sequence for each table runs inside a transaction (BeginTx → CREATE / introspect / ALTER … / CREATE INDEX → Commit) so it is atomic against concurrent startup processes: two replicas starting simultaneously each see either the pre-migration or post-migration schema, never a half-applied state.

func (*Adapter) BeginTx

func (a *Adapter) BeginTx(ctx context.Context, opts *maniflex.TxOptions) (maniflex.Tx, error)

BeginTx starts a database transaction and returns a maniflex.Tx that routes all CRUD operations through the same *sql.Tx connection.

func (*Adapter) Close

func (a *Adapter) Close() error

Close closes the underlying *sql.DB.

func (*Adapter) Create

func (a *Adapter) Create(ctx context.Context, model *maniflex.ModelMeta, record any) (any, error)

func (*Adapter) Delete

func (a *Adapter) Delete(ctx context.Context, model *maniflex.ModelMeta, id string) error

func (*Adapter) DriverName

func (a *Adapter) DriverName() string

DriverName returns "postgres" or "sqlite" so dialect-aware helpers (e.g. maniflex.Migrate) can choose the right placeholder syntax and column types without importing this package.

func (*Adapter) DriverType

func (a *Adapter) DriverType() maniflex.DriverType

DriverType returns the SQL dialect used by this adapter. Implements the optional maniflex.adapterDriverTyper interface so that ServerContext.DriverType() can surface the dialect to packages (e.g. pkg/ledger) that need to build driver-specific parameterised queries.

func (*Adapter) ExistsInScope added in v0.2.4

func (a *Adapter) ExistsInScope(ctx context.Context, model *maniflex.ModelMeta, id string,
	filters []*maniflex.FilterExpr,
) (bool, error)

ExistsInScope implements maniflex.ScopeChecker.

func (*Adapter) FindByID

func (a *Adapter) FindByID(ctx context.Context, model *maniflex.ModelMeta, id string, qp *maniflex.QueryParams) (any, error)

FindByID returns the matching record as a field-populated *T scanned directly from the row (typed-models Phase 4). Includes are populated onto the record's extra carrier. createMap/updateMap still use findByIDMap for their map tails.

func (*Adapter) FindByIDForUpdate

func (a *Adapter) FindByIDForUpdate(ctx context.Context, model *maniflex.ModelMeta, id string) (any, error)

FindByIDForUpdate fetches the row and acquires a pessimistic write lock. Postgres appends FOR UPDATE and routes through the write pool so the lock participates in an enclosing transaction. SQLite does a plain SELECT — the lock is at the transaction level, taken at BEGIN (db/sqlite opens write connections with _txlock=immediate).

func (*Adapter) FindMany

func (a *Adapter) FindMany(ctx context.Context, model *maniflex.ModelMeta, qp *maniflex.QueryParams) ([]any, int64, error)

func (*Adapter) Ping

func (a *Adapter) Ping(ctx context.Context) error

Ping verifies that both the write and read database connections are alive. It is called by the health handler when Config.HealthCheckDB is true. Ping implements the maniflex.Pinger interface.

func (*Adapter) Raw

func (a *Adapter) Raw(ctx context.Context, query string, args ...any) maniflex.RawResult

func (*Adapter) Restore added in v0.2.4

func (a *Adapter) Restore(ctx context.Context, model *maniflex.ModelMeta, id string,
	qp *maniflex.QueryParams,
) (any, error)

Restore implements maniflex.Restorer: it clears the soft-delete marker on one row and returns the restored record (5.19).

It has to be its own statement rather than an Update, because every read and update path applies softDeleteCond — the row it targets is invisible to all of them. It is the mirror image of softDelete, down to the guard: softDelete requires the row to be live, this requires it to be deleted, so restoring a row that was never deleted is a 404 rather than a silent success.

Only the marker is written. updated_at is deliberately left alone so a restore does not read as an edit to anything watching that column.

func (*Adapter) SetErrorNormalizer

func (a *Adapter) SetErrorNormalizer(fn ErrorNormalizer)

SetErrorNormalizer registers the driver-specific constraint-error normalizer. Driver packages call this from their Open constructor. When no normalizer is registered, driver errors from Create/Update pass through unchanged.

func (*Adapter) SetLogger

func (a *Adapter) SetLogger(l *slog.Logger)

SetLogger sets the logger used for adapter-level output (AutoMigrate column-drift warnings, column additions). the Server framework calls this automatically before AutoMigrate when Config.Logger is set, so callers normally do not need to call it directly.

func (*Adapter) Update

func (a *Adapter) Update(ctx context.Context, model *maniflex.ModelMeta, id string, record any, present map[string]struct{}) (any, error)

Update applies the present columns of the record (*T) and returns the updated representation (*T). Transition (Phase 3): the record is bridged to its present-column map, the existing map update runs, and the result is wrapped back to a *T.

func (*Adapter) WriteDB

func (a *Adapter) WriteDB() *sql.DB

WriteDB exposes the underlying writer *sql.DB. It is used by maniflex.Migrate to run versioned migrations inside a transaction. Application code should prefer the DBAdapter CRUD methods or Raw; this accessor exists so that cross-cutting helpers (migrations, custom maintenance scripts) can drive the same connection that the adapter uses for writes.

type ErrorNormalizer

type ErrorNormalizer func(err error, table string) error

ErrorNormalizer converts a raw driver error into a *maniflex.ErrConstraint when it represents a known constraint violation (unique or foreign-key), so the DB step can return 409 Conflict instead of an opaque 500. Errors that are not constraint violations must be returned unchanged.

Constraint errors are driver-specific, so sqlcore does not handle them itself: lib/pq surfaces a typed *pq.Error with SQLSTATE codes, while modernc.org/sqlite reports them only as message strings. Each driver package (db/postgres, db/sqlite) implements its own ErrorNormalizer and registers it via Adapter.SetErrorNormalizer, keeping this package free of any driver dependency.

table is the offending model's table name, used to populate ErrConstraint.Table.

type PlaceholderBuilder

type PlaceholderBuilder struct {
	// contains filtered or unexported fields
}

PlaceholderBuilder is the exported version of the placeholder helper, allowing other packages (e.g. jobs/sql) to reuse this logic instead of reimplementing it locally.

Usage:

pb := sqlcore.NewPlaceholderBuilder(driver)
query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", pb.Add(userID))
rows, err := db.QueryContext(ctx, query, pb.Args()...)

func NewPlaceholderBuilder

func NewPlaceholderBuilder(driver maniflex.DriverType) *PlaceholderBuilder

NewPlaceholderBuilder creates a PlaceholderBuilder for the given driver.

func (*PlaceholderBuilder) Add

func (pb *PlaceholderBuilder) Add(v any) string

Add appends a value to the argument list and returns the appropriate placeholder ($1, $2... for Postgres; ? for SQLite).

func (*PlaceholderBuilder) Args

func (pb *PlaceholderBuilder) Args() []any

Args returns the collected arguments for the query.

type RawSqlResult

type RawSqlResult struct {
	// contains filtered or unexported fields
}

── Raw SQL ──────────────────────────────────────────────────────────────

func (RawSqlResult) LastInsertId

func (r RawSqlResult) LastInsertId() (*int64, error)

func (RawSqlResult) Rows

func (r RawSqlResult) Rows() (*sql.Rows, error)

func (RawSqlResult) RowsAffected

func (r RawSqlResult) RowsAffected() (*int64, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL