enginesql

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package enginesql is r3's SQL engine, serving two roles:

  1. Full CRUD via BaseCRUD: a generic r3.CRUD over database/sql with reflection-based struct scanning. Raw SQL drivers (pq, pgx, mysql, sqlite3) embed it and configure only a Flavor.

  2. Shared query preparation via PreparedListQuery: ORM drivers (GORM, Bun, go-pg) execute through their own API but reuse this to translate r3 filters, sorts, and pagination into SQL-ready pieces instead of duplicating it.

Key types: BaseCRUD, BaseRaw (arbitrary-query escape hatch), StructMeta (reflected columns/PK/relations/soft-delete), Flavor, PreparedListQuery.

Public so third-party drivers can reuse it.

Index

Constants

View Source
const (
	// RelHasMany is a one-to-many relationship (City has many Translations).
	RelHasMany = r3tag.RelHasMany
	// RelBelongsTo is a many-to-one relationship (Location belongs to City).
	RelBelongsTo = r3tag.RelBelongsTo
	// RelManyToMany is a many-to-many relationship via a join table.
	RelManyToMany = r3tag.RelManyToMany
)

Variables

View Source
var (
	FlavorPostgres = Flavor{
		Placeholder:          PlaceholderDollar,
		SupportsRETURNING:    true,
		TimestampFunc:        "NOW()",
		UsesOnConflictClause: true,
	}
	FlavorSQLite = Flavor{
		Placeholder:          PlaceholderQuestion,
		SupportsRETURNING:    true,
		TimestampFunc:        "datetime('now')",
		UsesOnConflictClause: true,
	}
	FlavorMySQL = Flavor{
		Placeholder:       PlaceholderQuestion,
		SupportsRETURNING: false,
		TimestampFunc:     "CURRENT_TIMESTAMP",
		IdentifierQuote:   "`",
	}
)

Pre-defined flavors for common databases.

Functions

func ColumnsString

func ColumnsString(cols []string) string

ColumnsString joins column names with commas (SQL-specific formatting).

func FieldsToColumns deprecated

func FieldsToColumns(fields r3.Fields) []string

FieldsToColumns converts r3.Fields to a []string of column names.

Deprecated: Use r3.FieldsToStrings directly.

func FinalizeCount deprecated

func FinalizeCount[T any](entities []T, paginatedCount int64, isPaginated bool) ([]T, int64)

FinalizeCount returns (entities, totalCount), using len(entities) when pagination was inactive.

Deprecated: Use r3.FinalizeCount directly.

func ManagedTimestampColumns

func ManagedTimestampColumns(meta StructMeta, naming r3.NamingConfig, op r3.WriteOp) []string

ManagedTimestampColumns returns the model's system-managed timestamp columns the engine writes for op: created_at AND updated_at on Create, updated_at on Update/Patch. The system stamps these with server time even though they are read-only to callers - Creatable/Mutable mean "no caller may set this", not "never written". Soft-delete is excluded (managed by Delete/Restore).

func NewBaseQuerier

func NewBaseQuerier[T any, ID comparable](db *sql.DB, flavor Flavor, opts ...r3.Option) r3.Querier[T, ID]

NewBaseQuerier builds a full BaseCRUD but returns only r3.Querier (Get, List), giving a compile-time guarantee that the caller cannot write.

func RequireMutableColumns

func RequireMutableColumns(ctx context.Context, schema r3.Schema, cols []string) error

RequireMutableColumns enforces the schema's Mutable capability for an explicit Patch column set, returning a typed ErrInvalidPatchField for the first non-mutable column - the capability layer over the structural floor from StructMeta.ValidatePatchColumns. Skipped under the write-guard bypass or a zero schema.

func RunPreloads

func RunPreloads(
	ctx context.Context,
	executor SQLExecutor,
	meta *StructMeta,
	flavor Flavor,
	entitiesPtr any,
	preloads r3.Preloads,
) error

RunPreloads loads the requested relations for entities (a *[]T, e.g. *[]City), setting the relation fields in place. Has-many: WHERE fk IN (parent PKs), grouped by FK into each parent's slice field. Belongs-to: WHERE pk IN (parent FKs), assigned to each parent's pointer field.

func ScanAggregateRows added in v0.0.3

func ScanAggregateRows(rows *sql.Rows) ([]r3.AggregateRow, error)

ScanAggregateRows scans a dynamic-column aggregate result set into r3.AggregateRow maps keyed by result column name. []byte values normalize to string (MySQL returns text and decimals as []byte); everything else stays backend-native, coerced on read by the AggregateRow accessors. Shared by the raw-SQL engine and the ORM drivers. The caller owns rows (including Close).

func ScanEntities

func ScanEntities[T any](rows *sql.Rows, meta *StructMeta) ([]T, error)

ScanEntities scans sql.Rows into a slice of T using the given StructMeta.

func SetTimeColumns

func SetTimeColumns(meta StructMeta, entityPtr any, cols []string, t time.Time)

SetTimeColumns sets the named columns on the entity to t. Only time.Time and *time.Time fields are written; any other shape is left to the DB default.

func WriteColumns

func WriteColumns(ctx context.Context, schema r3.Schema, cols []string, op r3.WriteOp) []string

WriteColumns returns the subset of cols the op may write under the schema. The capability check is skipped (all cols returned) under the write-guard bypass or a zero schema. The structural floor (PK never in cols; computed attributes have no column) is the caller's, so it can't be widened here.

Types

type BaseCRUD

type BaseCRUD[T any, ID comparable] struct {
	// Executor is the *sql.DB or *sql.Tx used for all queries.
	Executor SQLExecutor

	DefaultsManager

	Meta   StructMeta
	Schema r3.Schema // logical schema for read validation and write-shaping
	Flavor Flavor
	Config r3.Config
	Raw    *BaseRaw[T, ID]
	// contains filtered or unexported fields
}

BaseCRUD is a generic r3.CRUD[T, ID] over database/sql, using reflection-based struct scanning and the r3 SQL dialect. Drivers embed it and may override methods (e.g. MySQL overrides Create, lacking RETURNING).

func NewBaseCRUD

func NewBaseCRUD[T any, ID comparable](db *sql.DB, flavor Flavor, opts ...r3.Option) *BaseCRUD[T, ID]

NewBaseCRUD builds a BaseCRUD for db and flavor. Columns map via `db:"col"` tags; the PK is tagged `db:"id,pk"` (defaults to "id"). Options configure framework-level behavior (e.g. r3.WithConfig).

func (*BaseCRUD[T, ID]) Aggregate added in v0.0.3

func (r *BaseCRUD[T, ID]) Aggregate(ctx context.Context, qarg ...r3.Query) ([]r3.AggregateRow, error)

Aggregate computes grouped aggregates over the matching records. See r3.Aggregator for the semantics.

func (*BaseCRUD[T, ID]) BeginTx

func (r *BaseCRUD[T, ID]) BeginTx(ctx context.Context) (r3.TxCRUD[T, ID], error)

BeginTx starts a transaction and returns an r3.TxCRUD running all ops within it; the caller must Commit or Rollback. Errors if this BaseCRUD is already inside a transaction (holds no *sql.DB).

func (*BaseCRUD[T, ID]) Count

func (r *BaseCRUD[T, ID]) Count(ctx context.Context, qarg ...r3.Query) (int64, error)

Count returns the number of records matching the query's filters.

func (*BaseCRUD[T, ID]) Create

func (r *BaseCRUD[T, ID]) Create(ctx context.Context, entity T) (T, error)

Create inserts a record and returns it with the auto-generated PK populated, via RETURNING (Postgres, SQLite 3.35+). MySQL drivers override this.

func (*BaseCRUD[T, ID]) Delete

func (r *BaseCRUD[T, ID]) Delete(ctx context.Context, id ID) error

Delete removes a record by ID. With a soft-delete column (tagged `r3:"soft_delete"`) it sets that column to the current timestamp; otherwise it hard-deletes.

func (*BaseCRUD[T, ID]) Get

func (r *BaseCRUD[T, ID]) Get(ctx context.Context, id ID, qarg ...r3.Query) (T, error)

Get retrieves a record by its ID.

func (*BaseCRUD[T, ID]) HardDelete

func (r *BaseCRUD[T, ID]) HardDelete(ctx context.Context, id ID) error

HardDelete permanently removes a record, ignoring soft-delete.

func (*BaseCRUD[T, ID]) List

func (r *BaseCRUD[T, ID]) List(ctx context.Context, qarg ...r3.Query) ([]T, int64, error)

List retrieves records based on the provided query parameters.

func (*BaseCRUD[T, ID]) Patch

func (r *BaseCRUD[T, ID]) Patch(ctx context.Context, entity T, fields r3.Fields) (T, error)

Patch updates only the columns named by fields; entity must have its PK set. Returns the full entity after the update.

func (*BaseCRUD[T, ID]) PatchWhere added in v0.0.4

func (r *BaseCRUD[T, ID]) PatchWhere(
	ctx context.Context, filters r3.Filters, entity T, fields r3.Fields,
) (int64, error)

PatchWhere sets fields (from entity's values) on every row matching filters and returns the affected-row count - the multi-row Patch: same column validation and capability gating (ValidatePatchColumns + RequireMutableColumns), managed updated_at bump, and write-guard bypass, but rows are selected by filters.

Filters are schema-validated (an unknown or non-filterable field is a typed error, as in List). Soft-deleted rows are excluded by default. Relationship ("has") filters are not supported and return an error.

func (*BaseCRUD[T, ID]) Restore

func (r *BaseCRUD[T, ID]) Restore(ctx context.Context, id ID) error

Restore clears the soft-delete column, un-deleting a record. Errors if the model has no soft-delete column.

func (*BaseCRUD[T, ID]) SQLDB

func (r *BaseCRUD[T, ID]) SQLDB() *sql.DB

SQLDB returns the underlying *sql.DB for advanced use. Panics inside a transaction (use the transaction's CRUD instead).

func (*BaseCRUD[T, ID]) Update

func (r *BaseCRUD[T, ID]) Update(ctx context.Context, entity T) (T, error)

Update modifies the record identified by its primary key.

Only Mutable columns are written, so a full Update cannot clobber created_at or resurrect a soft-deleted row; a model with no mutable columns makes it a verified no-op. Bypass the write guard for audited system/worker writes (see r3.WithoutWriteGuard).

func (*BaseCRUD[T, ID]) Upsert added in v0.0.4

func (r *BaseCRUD[T, ID]) Upsert(ctx context.Context, entity T, opts ...r3.UpsertOption) (T, error)

Upsert inserts entity, or updates the colliding row on a conflict against the conflict target (the PK by default), via Flavor.UpsertClause (Postgres/SQLite ON CONFLICT ... DO UPDATE, MySQL ON DUPLICATE KEY UPDATE).

The insert branch obeys Creatable and stamps created_at/updated_at; the update branch obeys Mutable (a restricted UpdateOnConflict set is rejected like Patch) and bumps updated_at. The write-guard bypass skips the capability check as in Create/Patch. The stored row comes back via RETURNING where supported, else a re-fetch by the conflict target.

type BaseRaw

type BaseRaw[T any, ID any] struct {
	Executor SQLExecutor // *sql.DB or *sql.Tx
	Meta     StructMeta
}

BaseRaw executes arbitrary SQL with automatic StructMeta-based struct scanning.

func NewBaseRaw

func NewBaseRaw[T any, ID comparable](executor SQLExecutor, meta StructMeta) *BaseRaw[T, ID]

NewBaseRaw creates a BaseRaw.

func (*BaseRaw[T, ID]) Exec

func (r *BaseRaw[T, ID]) Exec(ctx context.Context, query string, args ...any) (sql.Result, error)

Exec runs a raw non-returning query (INSERT, UPDATE, DELETE).

func (*BaseRaw[T, ID]) Query

func (r *BaseRaw[T, ID]) Query(ctx context.Context, query string, args ...any) ([]T, error)

Query runs a raw query and scans the rows into a slice of T.

func (*BaseRaw[T, ID]) QueryRow

func (r *BaseRaw[T, ID]) QueryRow(ctx context.Context, query string, args ...any) (T, error)

QueryRow runs a raw single-row query and scans it into T.

type Defaults deprecated

type Defaults = r3.Defaults

Defaults is an alias for r3.Defaults.

Deprecated: Use r3.Defaults directly.

func NewDefaults deprecated

func NewDefaults() Defaults

NewDefaults returns Defaults with reasonable default queries.

Deprecated: Use r3.NewDefaults directly.

type DefaultsManager deprecated

type DefaultsManager = r3.DefaultsManager

DefaultsManager is an alias for r3.DefaultsManager.

Deprecated: Use r3.DefaultsManager directly.

func NewDefaultsManager deprecated

func NewDefaultsManager() DefaultsManager

NewDefaultsManager creates a DefaultsManager with reasonable defaults.

Deprecated: Use r3.NewDefaultsManager directly.

type Flavor

type Flavor struct {
	// Placeholder controls the parameter placeholder style (? vs $N).
	Placeholder PlaceholderStyle

	// SupportsRETURNING reports whether INSERT supports RETURNING (Postgres,
	// SQLite 3.35+). When false, Create() falls back to INSERT + last-insert-id +
	// SELECT.
	SupportsRETURNING bool

	// TimestampFunc is the current-timestamp SQL expression used by soft-delete to
	// set deleted_at: "NOW()" (Postgres), "CURRENT_TIMESTAMP" (MySQL),
	// "datetime('now')" (SQLite).
	TimestampFunc string

	// IdentifierQuote is this backend's identifier quote char; the engine rewrites
	// the dialect's ANSI double quotes to it (see QuoteIdentifiers). Empty = ANSI
	// double quotes (Postgres, SQLite). MySQL needs backticks: without ANSI_QUOTES
	// mode it reads `"col"` as a string literal, silently breaking every filter and
	// sort.
	IdentifierQuote string

	// UsesOnConflictClause selects the upsert dialect. True = Postgres/SQLite
	// `ON CONFLICT (cols) DO UPDATE SET ...` (explicit target; EXCLUDED is the
	// would-be-inserted row). False = MySQL `ON DUPLICATE KEY UPDATE col =
	// VALUES(col)` (target is any unique key, so cols are ignored).
	UsesOnConflictClause bool
}

Flavor describes the SQL dialect differences between backends, passed to NewBaseCRUD to configure SQL generation.

func (Flavor) ConvertPlaceholders

func (f Flavor) ConvertPlaceholders(clause string, startIdx int) (string, int)

ConvertPlaceholders rewrites `?` placeholders in clause to `$1, $2, ...` from startIdx, returning the result and the next free index. No-op (but still advances the index) for `?`-style flavors.

func (Flavor) Placeholders

func (f Flavor) Placeholders(count int, startIdx int) string

Placeholders builds a placeholder list: "$1, $2, $3" from startIdx (dollar) or "?, ?, ?" (question).

func (Flavor) QuoteIdentifiers

func (f Flavor) QuoteIdentifiers(sql string) string

QuoteIdentifiers rewrites the dialect's ANSI double-quoted identifiers (`"col"`, `"tbl"."col"`) to this flavor's quote char; a no-op for ANSI flavors (Postgres, SQLite).

Safe on a fully assembled query: values are always bound as parameters, never inlined, so the only double quotes in the SQL are identifier quotes. A doubled embedded quote (`""`, the dialect's escape) maps to the doubled target quote, which is also correct - so escaping survives. (Identifiers are validated to contain no quotes, so this never actually occurs.)

func (Flavor) SetExprs

func (f Flavor) SetExprs(cols []string, startIdx int) string

SetExprs builds an UPDATE SET body: "col1 = $1, col2 = $2, ..." or "col1 = ?, col2 = ?, ...".

func (Flavor) UpsertClause added in v0.0.4

func (f Flavor) UpsertClause(conflictCols, updateCols []string) string

UpsertClause builds an upsert INSERT's trailing on-conflict clause: `ON CONFLICT (target) DO UPDATE SET col = EXCLUDED.col, ...` (Postgres/SQLite) or `ON DUPLICATE KEY UPDATE col = VALUES(col), ...` (MySQL). conflictCols is the target (MySQL ignores it, keying off any unique index). Empty updateCols yields DO NOTHING (a self-assign no-op on MySQL), leaving the existing row untouched.

func (Flavor) WhereEq

func (f Flavor) WhereEq(column string, nextIdx int) string

WhereEq builds "column = $N" or "column = ?". nextIdx is the 1-based parameter index (dollar-style only).

type PlaceholderStyle

type PlaceholderStyle int

PlaceholderStyle controls how SQL parameter placeholders are generated.

const (
	// PlaceholderQuestion uses `?` placeholders (MySQL, SQLite).
	PlaceholderQuestion PlaceholderStyle = iota
	// PlaceholderDollar uses `$1, $2, ...` numbered placeholders (PostgreSQL).
	PlaceholderDollar
)

type PreparedAggregateQuery added in v0.0.3

type PreparedAggregateQuery struct {
	// PreparedListQuery carries WHERE/joins, pagination, and the merged Query. Its
	// Sorts are already restricted to group fields and aggregate aliases (see
	// r3.Query.AggregateSorts); cursor pagination doesn't apply to grouped rows.
	PreparedListQuery

	// SelectList is the full select list in declaration order: quoted group columns
	// first (each aliased to its raw field name, so result keys are stable across
	// backends), then aggregate expressions with their aliases.
	SelectList []string

	// GroupBy is the quoted group-by column list (empty = whole-set row).
	GroupBy []string

	// Having is the HAVING clause with args (zero when no Having filters). Aliases
	// are already lowered to their aggregate expressions - Postgres disallows
	// select-list aliases in HAVING.
	Having r3sql.SQLClause
}

PreparedAggregateQuery is the aggregate sibling of PreparedListQuery, consumed by any driver (database/sql, GORM, Bun, go-pg). WHERE comes from the embedded PreparedListQuery; the aggregate-specific parts are the select list, GROUP BY, HAVING, and alias-aware ORDER BY.

func PrepareAggregateQuery added in v0.0.3

func PrepareAggregateQuery(
	dm *DefaultsManager, schema r3.Schema, qarg ...r3.Query,
) (PreparedAggregateQuery, error)

PrepareAggregateQuery merges defaults with args, validates against the schema (a zero r3.Schema does structural-only validation), and converts to SQL-ready aggregate components.

func PrepareMergedAggregateQuery added in v0.0.3

func PrepareMergedAggregateQuery(schema r3.Schema, q r3.Query) (PreparedAggregateQuery, error)

PrepareMergedAggregateQuery builds the SQL aggregate components for an already-merged query, exposed separately (like PrepareMergedListQuery) so a driver can transform it first (e.g. lower relationship filters).

type PreparedListQuery

type PreparedListQuery struct {
	// Clauses is the SQL WHERE clauses with their args and joins.
	Clauses r3sql.SQLClauses

	// Sorts is the SQL ORDER BY expressions.
	Sorts []r3sql.SQLSort

	// IsPaginated reports offset-based pagination; Limit/Offset are then set.
	IsPaginated bool
	Limit       int
	Offset      int

	// IsCursorPaginated reports cursor/keyset pagination. When true, use
	// CursorClause/CursorLimit and no OFFSET or COUNT query.
	IsCursorPaginated bool

	// CursorClause is the keyset WHERE clause (empty for the first page).
	CursorClause r3sql.SQLClause

	// CursorLimit is the row limit for cursor pagination.
	CursorLimit int

	// CursorBackward marks a "before" cursor. Backward keyset pagination scans in
	// reversed sort order (so LIMIT takes the rows immediately preceding the
	// cursor), then the caller reverses the slice back. See OrderBySorts.
	CursorBackward bool

	// Query is the merged r3.Query (defaults + args), for Preloads, IncludeTrashed,
	// and other driver-specific fields.
	Query r3.Query
}

PreparedListQuery holds the SQL components derived from an r3.Query - filters, sorts, and pagination converted into SQL-ready pieces that any driver (GORM, Bun, go-pg, database/sql) can consume, so the r3-to-SQL translation lives once.

func PrepareListQuery

func PrepareListQuery(dm *DefaultsManager, qarg ...r3.Query) (PreparedListQuery, error)

PrepareListQuery merges defaults with args, then converts filters, sorts, and pagination into SQL components. Drivers call it once at the start of List().

func PrepareMergedListQuery

func PrepareMergedListQuery(q r3.Query) (PreparedListQuery, error)

PrepareMergedListQuery builds the SQL components for an already-merged query - the half of PrepareListQuery after the merge, exposed so a driver can transform the query first (e.g. lower relationship "has" filters into key-set In filters).

It applies no value codecs; a driver whose schema declares codecs must call PrepareMergedListQuerySchema to encode codec'd filter/cursor args to stored form.

func PrepareMergedListQuerySchema added in v0.0.7

func PrepareMergedListQuerySchema(schema r3.Schema, q r3.Query) (PreparedListQuery, error)

PrepareMergedListQuerySchema is PrepareMergedListQuery with schema-driven value codecs applied: filter args and decoded cursor keys on codec'd attributes are encoded to stored form so predicates compare against stored column values. A zero schema (no codecs) behaves exactly like PrepareMergedListQuery.

func (*PreparedListQuery) Joins

func (p *PreparedListQuery) Joins() []r3sql.SQLColumn

Joins returns the deduplicated list of SQL joins from the clauses.

func (*PreparedListQuery) OrderBySorts

func (p *PreparedListQuery) OrderBySorts() ([]r3sql.SQLSort, error)

OrderBySorts returns the main SELECT's ORDER BY. Forward queries use Sorts; a backward ("before") cursor reverses each direction, so the rows preceding the cursor are scanned under LIMIT and reversed back by the caller (see CursorBackward).

type RelationKind

type RelationKind = r3tag.RelationKind

RelationKind is an alias of r3tag.RelationKind.

type RelationMeta

type RelationMeta struct {
	FieldName  string       // Go field name (matched against PreloadSpec.Name)
	FieldIndex int          // struct field index for reflection-based assignment
	Kind       RelationKind // has-many, belongs-to, or many-to-many
	FKColumn   string       // FK column (on the "many" side, or left FK for M2M)
	RefColumn  string       // right FK column in join table (M2M only)
	JoinTable  string       // join table name (M2M only)
	Owned      bool         // children are lifecycle-bound to parent (has-many only)
	TargetMeta StructMeta   // metadata for the related entity type
	TargetType reflect.Type // target element type (not slice/ptr)
}

RelationMeta describes a struct field that represents a relation.

func RelationMetaFromSpec added in v0.0.5

func RelationMetaFromSpec(spec r3.RelationSpec) RelationMeta

RelationMetaFromSpec converts a core r3.RelationSpec (a relation declared by table and column names) into a RelationMeta. The target Go type is unknown - the point of an explicit spec - so TargetType is nil and FieldIndex is -1: the relation is filterable and aggregatable but has no struct field to preload into.

type SQLExecutor

type SQLExecutor interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}

SQLExecutor is the common interface of *sql.DB and *sql.Tx, letting BaseCRUD run transparently in normal and transactional modes.

type StructMeta

type StructMeta struct {
	TableName string   // e.g. "cities"
	Columns   []string // column names in order
	Fields    []int    // struct field index per column
	PKColumn  string   // primary key column (defaults to "id")
	PKField   int      // index into Columns/Fields for the PK entry

	// SoftDeleteColumn is the soft-delete column (e.g. "deleted_at"), detected via
	// `r3:"soft_delete"`. Empty means soft-delete is off.
	SoftDeleteColumn string

	// Relations holds relations detected via `r3` tags, e.g.
	// `r3:"rel:has-many,fk:city_id"`.
	Relations []RelationMeta
}

StructMeta is the reflected metadata for a struct type T, used by BaseCRUD and BaseRaw to build queries and scan results.

func GetStructMeta

func GetStructMeta[T any]() StructMeta

GetStructMeta derives table name and columns from T.

Tag priority: `r3` first, `db` as fallback; a "-" value in either is ignored. Pointer-to-basic types are nullable columns; slices, maps, and struct fields (except time.Time) are relation fields.

func WithDeclaredRelations added in v0.0.5

func WithDeclaredRelations(meta StructMeta, specs []r3.RelationSpec) StructMeta

WithDeclaredRelations returns a copy of meta with specs added to its Relations. A declared relation whose name matches a tag-reflected one replaces it (explicit spec wins). The input meta is never mutated (shared and cached); a nil/empty specs returns meta unchanged.

func (*StructMeta) FieldIndicesForColumns

func (m *StructMeta) FieldIndicesForColumns(selectedCols []string) ([]string, []int)

FieldIndicesForColumns returns the Columns/Fields subset (columns and their field indices, in order) matching selectedCols; all of them when empty.

func (*StructMeta) FieldValues

func (m *StructMeta) FieldValues(entity any) []any

FieldValues extracts the column values from a struct in the same order as Columns.

func (*StructMeta) FieldValuesForColumns

func (m *StructMeta) FieldValuesForColumns(entity any, columns []string) []any

FieldValuesForColumns extracts an entity's values for the given columns, in order. Columns absent from the struct are silently skipped (validate first).

func (*StructMeta) NonPKColumns

func (m *StructMeta) NonPKColumns() []string

NonPKColumns returns all column names except the primary key.

func (*StructMeta) NonPKFieldValues

func (m *StructMeta) NonPKFieldValues(entity any) []any

NonPKFieldValues extracts column values excluding the PK, for INSERT/UPDATE.

func (*StructMeta) PKValue

func (m *StructMeta) PKValue(entity any) any

PKValue extracts the primary key value from an entity.

func (*StructMeta) ScanDest

func (m *StructMeta) ScanDest(entityPtr any) []any

ScanDest returns a slice of pointers to the struct fields, suitable for sql.Row.Scan(). entityPtr must be a pointer to the entity.

func (*StructMeta) ScanDestForColumns

func (m *StructMeta) ScanDestForColumns(entityPtr any, selectedCols []string) []any

ScanDestForColumns returns scan destinations for selectedCols only; like ScanDest (all columns) when empty.

func (*StructMeta) SetPKValue

func (m *StructMeta) SetPKValue(entityPtr any, val any)

SetPKValue sets the primary key value on an entity (via pointer).

func (*StructMeta) ValidatePatchColumns

func (m *StructMeta) ValidatePatchColumns(columns []string) ([]string, error)

ValidatePatchColumns is the structural floor for Patch: it rejects an empty set, or any unknown, primary-key, or soft-delete column. On success it returns columns unchanged.

Jump to

Keyboard shortcuts

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