filterx

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package filterx is the reusable column-allowlist registry for list queries.

It translates caller-supplied filter values and a chosen sort into squirrel conditions, ORDER BY clauses, and keyset pagination predicates. Column identifiers and SQL expressions live in the registry as fixed constants — the registry *is* the allowlist (ADR-007): callers supply only values, which squirrel parameterises. Views declare one Registry and route their filters, sort, and cursor through it instead of hand-writing the SQL each time.

Index

Constants

View Source
const MaxValues = 200

MaxValues caps the number of values accepted in a single multi-value filter. It keeps request URLs small and the bound-parameter count far below SQLite's per-statement variable limit. Callers needing more should switch to a POST search endpoint with a JSON body.

Variables

View Source
var ErrInvalidFilter = errors.New("invalid filter")

ErrInvalidFilter is returned for an unknown column, an operator a column does not allow, a missing value list where one is required, or an over-cap value list. Callers map it to a 400.

Functions

func Int64Values

func Int64Values[T ~int64](values *[]T) []any

Int64Values converts a caller-supplied slice of an int64-kinded API type (typically a typed ID) into the []any values a Filter expects. Returns nil for a nil input.

func StringValues

func StringValues[T ~string](values *[]T) []any

StringValues converts a caller-supplied slice of a string-kinded API type (a generated enum, a typed string) into the []any values a Filter expects. Returns nil for a nil input, which parseFilter-style callers treat as "no filter supplied".

Types

type ColumnSpec

type ColumnSpec struct {
	// Expr is the scalar SQL column expression, e.g. "g.country_code". Empty when
	// Rel is set.
	Expr string
	// Nullable enables the NULL-safe rewrite for not_in / not_contains (so NULL
	// rows are not silently dropped) and is required for is_null / not_null.
	Nullable bool
	// Rel, when set, translates the column as an EXISTS subquery instead of a
	// scalar comparison.
	Rel *Relational
	// Ops is the set of operators this column allows.
	Ops []Operator
}

ColumnSpec defines how one filterable column translates to SQL.

type Cursor

type Cursor struct {
	Sort  string
	Order string
	ID    int64
	// contains filtered or unexported fields
}

Cursor is the decoded form of an opaque pagination token: the sort it was issued under, the last row's sort value (nil for a NULL value), and the last row's tiebreaker id.

type Filter

type Filter struct {
	Column string
	Op     Operator
	Values []any
}

Filter is a request to constrain one column. Values is empty for is_null / not_null. For scalar string operators the values must be strings; for relational and scalar-id columns they are the column's value type.

func ParseFilter

func ParseFilter[T ~string](column string, values []any, opPtr *T) (Filter, bool, error)

ParseFilter resolves a value column's operator and decides whether it contributes a filter. A present operator with no values (and no null check) is treated as no filter; null operators apply with no values.

type Operator

type Operator string

Operator is the closed set of filter operators a column may permit.

const (
	OpIn          Operator = "in"
	OpNotIn       Operator = "not_in"
	OpContains    Operator = "contains"
	OpNotContains Operator = "not_contains"
	OpIsNull      Operator = "is_null"
	OpNotNull     Operator = "not_null"
)

func ParseOperator

func ParseOperator(s string) (Operator, error)

ParseOperator maps a request string to an Operator, defaulting to OpIn when empty. An unrecognised value is rejected with ErrInvalidFilter.

type Registry

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

Registry is the per-view allowlist of filterable columns and sortable columns.

func NewRegistry

func NewRegistry(cols map[string]ColumnSpec, sorts map[string]SortSpec, tieBreaker string) *Registry

NewRegistry builds a registry from a column allowlist, a sortable-column allowlist, and the tiebreaker column appended to every ORDER BY and keyset predicate (a unique, non-null column such as the primary key).

func (*Registry) Condition

func (r *Registry) Condition(f Filter) (sq.Sqlizer, error)

Condition validates the filter and returns its squirrel condition.

func (*Registry) DecodeCursor

func (r *Registry) DecodeCursor(token string) (Cursor, error)

DecodeCursor parses a token, validating the embedded sort/order against the registry and coercing the value to the sort column's kind.

func (*Registry) EncodeCursor

func (r *Registry) EncodeCursor(sortKey, order string, value any, id int64) (string, error)

EncodeCursor produces an opaque token from the last row of a page: the active sort, order, the row's sort value, and its tiebreaker id. The token is server-issued only; clients pass it back verbatim.

func (*Registry) Keyset

func (r *Registry) Keyset(c Cursor) (sq.Sqlizer, error)

Keyset returns the WHERE predicate that selects rows strictly after the cursor row under its sort and order. The tiebreaker id breaks ties on the (non-unique) sort column so adjacent pages never skip or repeat rows. For nullable sort columns the predicate mirrors SQLite's default NULL placement (NULLs first in ASC, last in DESC).

func (*Registry) OrderBy

func (r *Registry) OrderBy(sortKey, order string) (string, error)

OrderBy returns the ORDER BY clause for a sort/order pair, always appending the tiebreaker so non-unique sort columns page deterministically.

func (*Registry) SortableExists

func (r *Registry) SortableExists(sortKey string) bool

SortableExists reports whether sortKey is an allowlisted sortable column.

func (*Registry) Validate

func (r *Registry) Validate(f Filter) error

Validate reports whether a filter is acceptable without building its SQL.

type Relational

type Relational struct {
	// Correlated is the subquery up to but excluding the value predicate, e.g.
	//   "SELECT 1 FROM access_log_contributors c WHERE c.access_log_id = ral.id"
	// in / not_in append "AND <ValueCol> IN (…)"; is_null / not_null wrap it in
	// NOT EXISTS / EXISTS as-is.
	Correlated string
	// ValueCol is the column the IN list is matched against, e.g. "c.device_id".
	ValueCol string
}

Relational describes a column filtered via an EXISTS subquery correlated to the parent row, rather than a scalar comparison — e.g. matching any access_log_contributors row for the parent access_log entry.

type SortSpec

type SortSpec struct {
	Expr     string
	Kind     ValueKind
	Nullable bool
}

SortSpec describes a sortable column. Expr is the ORDER BY expression and the keyset comparison column; Kind drives cursor (de)serialization; Nullable selects NULL-aware keyset predicates (SQLite orders NULLs first in ASC, last in DESC, which the predicates below mirror).

type ValueKind

type ValueKind int

ValueKind classifies a sortable column's value so an opaque cursor can round-trip it through JSON without losing type fidelity.

const (
	KindString ValueKind = iota
	KindInt
	KindTime
)

Jump to

Keyboard shortcuts

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