data

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package data defines the data access contract.

There is no ORM. Queries are plain parameterized SQL -- generated by sqlc from .sql files once phase 2 lands -- which makes SQL injection impossible and the query plan predictable. What this package adds on top is:

  1. a security.Grant required by every operation (the mandatory path);
  2. tenant scoping taken from the Grant, never from a parameter;
  3. automatic instrumentation into the Collector.

Index

Constants

View Source
const MigrationsTable = "arandu_migrations"

MigrationsTable is where applied migration ids are recorded.

Variables

This section is empty.

Functions

func AppliedMigrations

func AppliedMigrations(ctx context.Context, db *DB) (map[string]bool, error)

AppliedMigrations returns the set of ids already recorded.

func Migrate

func Migrate(ctx context.Context, db *DB, migrations []Migration) ([]string, error)

Migrate applies the pending migrations, in the given order, and returns the ids it applied.

Each migration runs inside its own transaction together with the insert into the tracking table, so a failure halfway cannot leave the schema ahead of the record. It stops at the first failure: applying later migrations over a broken schema turns one clear error into an unrecoverable database.

The statements target PostgreSQL, the supported database of phase 1. Phase 2 moves this to Atlas; see docs/03-roadmap-fases.md.

func Tenant

func Tenant(g security.Grant) string

Tenant returns the tenant from the Grant. Every multi-tenant statement must take this value, never a tenant that came in with the request.

Types

type DB

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

DB wraps *sql.DB to instrument the Collector. Repositories use this type rather than *sql.DB, which is what makes every query show up on the debug page with the file and line that issued it.

It holds no driver import: the driver is chosen by the application, so the core keeps its two dependencies.

func Wrap

func Wrap(db *sql.DB) *DB

Wrap returns an instrumented handle over an open *sql.DB.

func (*DB) BeginTx

func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)

BeginTx starts a transaction on the underlying handle.

Statements inside the transaction are not recorded by the Collector yet: that needs a wrapper over *sql.Tx, which arrives with the generated repositories in phase 2.

func (*DB) ExecContext

func (d *DB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext runs a statement and records it, with the affected row count.

func (*DB) PingContext

func (d *DB) PingContext(ctx context.Context) error

PingContext verifies the connection. It feeds module health checks.

func (*DB) QueryContext

func (d *DB) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

QueryContext runs a query and records it.

func (*DB) QueryRowContext

func (d *DB) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext runs a single-row query and records it.

The duration measured here covers issuing the query only: database/sql defers the actual work to Row.Scan, so a slow row shows up on the timeline as scan time rather than query time.

func (*DB) Unwrap

func (d *DB) Unwrap() *sql.DB

Unwrap returns the underlying handle, for the rare case that needs a driver specific feature. Prefer the wrapper: what goes through Unwrap does not show up on the debug page.

type Migration

type Migration struct {
	ID   string
	Up   string
	Down string
}

Migration is a versioned, immutable-once-published schema change.

The id carries its own order -- "20260729_0001_create_users" -- because a migration that sorts differently on two machines applies in a different order on two machines.

func Pending

func Pending(ctx context.Context, db *DB, migrations []Migration) ([]Migration, error)

Pending returns the migrations that have not been applied yet, in order.

type Query

type Query struct {
	Limit  int
	Cursor string
	Sort   string
	Filter map[string]any
}

Query is pagination and ordering with an allowlist. The sort field is NEVER interpolated directly: the repository validates it against a permitted set, or ordering becomes injection through another door.

type Repository

type Repository[T any, ID comparable] interface {
	Find(ctx context.Context, g security.Grant, id ID) (T, error)
	List(ctx context.Context, g security.Grant, q Query) ([]T, error)
	Create(ctx context.Context, g security.Grant, entity T) (T, error)
	Update(ctx context.Context, g security.Grant, entity T) (T, error)
	Delete(ctx context.Context, g security.Grant, id ID) error
}

Repository is the contract every module repository implements.

Look at the signature: security.Grant is mandatory and comes before the id. Because a Grant cannot be constructed outside the security package, there is no path from a handler to the database that skips a Policy.

Jump to

Keyboard shortcuts

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