Help us shape the future of Go! Take the 2026 Go Developer Survey

listquery

package
v0.3.6 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package listquery is the pure, ORM-free half of the list-query SDK a per-resource filter allow-list (Map) + sort allow-list (Set), a JSON filter-DSL parser that compiles a client filter string into the core/types Filter contract (gated by the allow-list so only allow-listed fields with type-checked values reach a query), and the shared Compile/CompileOrder fold that walks that IR through a core/interfaces.QueryBackend algebra. It imports only core/types + core/interfaces + core/errors + stdlib — no ORM: each datastore backend (Ent now; Mongo/Elasticsearch future) is a repos-tier implementation of the algebra the fold drives.

Index

Constants

View Source
const DefaultCountCap = 10000

DefaultCountCap is the fallback total_count cap when a reader leaves Bounds.CountCap unset, so the offset pager's count is ALWAYS bounded — never a full-table COUNT on a huge set. It mirrors the stores.count_cap config default; a store that wires config overrides it via Bounds.CountCap.

View Source
const KeysetIDColumn = tiebreakerID

KeysetIDColumn is the always-present final tiebreaker column — every entity compiled through this SDK carries a unique id. The keyset seek compiler pairs the cursor's id value with this column (and is exported so the repos-tier compiler need not restate the literal).

View Source
const SuggestFallbackLimit = 10

SuggestFallbackLimit bounds a suggest page when the caller passes a non-positive limit — the store's belt-and-suspenders after the transport edge normally clamps to the suggest maximum.

Variables

This section is empty.

Functions

func Compile

func Compile[Q, S any](
	b interfaces.QueryBackend[Q, S],
	resolve ColumnFn,
	f types.Filter,
) Q

Compile folds a validated Filter (the FilterClause | CompositeFilter sealed union) into a backend predicate Q through the backend algebra b, resolving each clause's DTO field to its storage column with resolve. A nil or empty filter compiles to b.Empty() (the no-op / match-all). Compile imports no ORM — every datastore specific lives in b — so it is the single, shared fold every backend reuses.

Compile trusts that f was produced by the listquery parser (its values already type-validated); a backend defensively guards a hand-built malformed value rather than re-validating here.

func CompileOrder

func CompileOrder[Q, S any](
	b interfaces.QueryBackend[Q, S],
	resolve ColumnFn,
	joinOf JoinFn,
	ordinalOf OrdinalFn,
	derivedOf DerivedFn,
	ofs []types.OrderField,
	tiebreaker string,
) []S

CompileOrder folds the ordered sort directives into backend sort nodes over the shared KeysetColumns sequence (caller sort fields → tiebreaker DESC → id DESC, deduped) — so the emitted ORDER BY and the keyset seek predicate are guaranteed to agree. See KeysetColumns for the tiebreaker + dedup semantics.

func CompileSeek

func CompileSeek[Q, S any](b interfaces.QueryBackend[Q, S], seek types.Filter) Q

CompileSeek folds a keyset SeekFilter (whose clauses carry already-resolved columns) into a backend predicate through the SAME QueryBackend algebra the user filter uses. A store ANDs the result into its scoped query, so the seek is not a special ORM artifact — just another filter.

func CountSkipped

func CountSkipped(ctx context.Context) bool

CountSkipped reports whether WithSkipCount marked this context — the read side of the public WithSkipCount writer, so a consumer (or a test verifying the count-cache decorator passes the flag to its inner List) can check it without depending on the private context key.

func DecodeToken

func DecodeToken(token string) (types.PageToken, error)

DecodeToken parses an opaque page token into its semantic PageToken. An empty token defaults to offset page 1; a malformed/undecodable token is an InvalidInput (never a panic), so a client carrying a stale/garbage cursor gets a clean reset-to-page-1 signal rather than a 500. A keyset cursor's SortValues come back as their JSON kinds (a number as float64) — Run restores a timestamp column's unix-µs number to time.Time before compiling the seek.

func EncodeToken

func EncodeToken(tok types.PageToken) (string, error)

EncodeToken serializes a PageToken to the opaque URL-safe base64 token. A keyset cursor's SortValues must already be JSON-encodable scalars (a timestamp as a unix-µs int64); the caller (Run) converts a time.Time before minting, so this never sees an unencodable value.

func IdentityColumn

func IdentityColumn(field string) string

IdentityColumn is a ColumnFn that returns its input unchanged. It is used to compile a keyset SeekFilter, whose clauses already carry resolved storage columns (from KeysetColumns), so no further field→column mapping is wanted. Pass it as a value (Resolve: listquery.IdentityColumn) wherever a ColumnFn is expected.

func MergeDistinct

func MergeDistinct(columns [][]string, limit, pageNum int) *types.Page[string]

MergeDistinct builds a suggest page for a field spanning MORE THAN ONE column — a member display_name over first_name + last_name (UNION ALL + GLOBAL dedup). Each input slice is one column's distinct prefix matches (each already capped at offset+limit+1 by its query, which is sufficient to fill the page). It merges them into one GLOBALLY distinct, ascending set BEFORE applying offset+limit, so a value present in more than one column appears exactly once with no page-boundary dup/skip. The windowed slice (limit+1 for the has-more probe) is handed to SuggestPage.

func Parse

func Parse(m *Map, jsonStr string) (types.Filter, error)

Parse compiles a JSON filter string, gated by the resource allow-list m, into the core Filter contract. Blank input (or "{}") yields an empty CompositeFilter (IsEmpty() true) — a store applies no WHERE. Malformed JSON, an unknown/duplicate operator, a non-allow-listed field, a type-incompatible value, or nesting past maxFilterDepth is a CodeInvalidInput error (ARCHITECTURE.md#error-codes). The result is the Filter interface — a FilterClause for a single comparison, a CompositeFilter for $and/$or — never *FilterClause, since a top-level $and/$or is composite.

func ResolveFilter

func ResolveFilter(resolve ColumnFn, joinOf JoinFn, f types.Filter) types.Filter

ResolveFilter rewrites a filter's DTO field names to their storage columns via resolve, and stamps each clause's joined-table target via joinOf (nil ⇒ no joins), returning a filter whose clauses carry resolved columns + join targets. It lets the pagination template resolve the user filter ONCE, up front, so the datastore ListRunner sees only resolved clauses and folds every predicate (user filter + keyset seek) with an identity resolver — the runner never needs the allow-list. A joined clause keeps its (unused-for-the-predicate) resolved Field for IsEmpty, and carries the JoinTarget the backend compiles to a correlated EXISTS.

func Run

func Run[T, D any](
	ctx context.Context,
	page types.PageRequest,
	r Reader[T, D],
) (*types.Page[D], error)

Run executes a paginated read and DISPATCHES on the decoded page token: an offset arm (OFFSET (N-1)*limit, + optional total_count) or a keyset arm (the forward seek). It resolves the user filter and the keyset-column order ONCE, up front, so everything handed to the ListRunner is resolved-column data; the runner never touches the allow-list. A keyset token against an offset-only reader (Keyset == nil) is rejected as unsupported.

func SeekFilter

func SeekFilter(cols []KeysetColumn, values []any) types.Filter

SeekFilter builds the forward keyset SEEK predicate as a plain types.Filter: the lexicographic OR-expansion selecting exactly the rows strictly after the cursor row in the query's total order. Because it is an ordinary Filter, it needs no ORM-specific compiler — a store folds it with the SAME QueryBackend it uses for the user filter, and the seek can never drift from the ORDER BY (both derive from the same KeysetColumns sequence).

cols is the fully-deterministic keyset column order (caller sort fields → tiebreaker → id, resolved, deduped); values[i] is the cursor's value for cols[i], already restored to its bound type (time.Time for a timestamp column, the id's native type for the id column). len(values) must equal len(cols).

The expansion for columns c0,c1,…,cn (with per-column strict operator `>` for ASC / `<` for DESC):

strict(c0)  OR  ( eq(c0) AND ( strict(c1) OR ( eq(c1) AND … strict(cn) ) ) )

Sortable columns are NOT NULL, so every term is a plain `>`/`<`/`=` comparison — no NULL-boundary handling. A nullable sortable column would require extending this with IS NULL terms.

func SuggestPage

func SuggestPage(values []string, limit, pageNum int) *types.Page[string]

SuggestPage builds a single-column suggest result page from values a store fetched with a has-more probe (limit+1 rows, already DISTINCT + prefix-matched + ascending in SQL). It slices to limit, mints the next 1-based cursor only when the probe row was present, and stamps the page size + number. A non-positive limit falls back to SuggestFallbackLimit.

func SuggestPageNumber

func SuggestPageNumber(token string) int

SuggestPageNumber parses a suggest result cursor — a bare 1-based page number (the same convention as the offset list pager). An empty, non-numeric, or non-positive token safely restarts at page 1, so a malformed cursor can never produce a negative OFFSET.

func WithSkipCount

func WithSkipCount(ctx context.Context) context.Context

WithSkipCount returns a CHILD context that tells Run to SKIP computing total_count for the offset arm — leaving Total 0 — even for a Count:true reader. The count-cache decorator sets it only for its ONE inner List call when it already holds a cached total, so the expensive bounded count is not recomputed and the cached value is injected instead.

It MUST be applied to a locally-derived child context passed ONLY into that one inner List call — never by reassigning/mutating the caller's own ctx — so the flag can never leak into a sibling List and silently zero a real total.

Types

type Bounds

type Bounds struct {
	// DefaultSize is the page size used when the request omits one (a final floor of 10
	// applies).
	DefaultSize int
	// MaxSize, when > 0, is the ceiling a client-supplied page size is clamped to.
	MaxSize int
	// CountCap bounds the offset arm's total_count: rows are counted only up to the cap,
	// so a per-page count never pays a full-table COUNT. ≤ 0 falls back to
	// DefaultCountCap, so a count is always bounded. A tally over the cap is reported as
	// the cap with an estimate flag.
	CountCap int
}

Bounds is the page-size policy for a read: DefaultSize when the client omits a size, MaxSize (when > 0) clamps an oversized client size so an unbounded LIMIT can't reach the query, and CountCap bounds the offset arm's total_count.

type ColumnFn

type ColumnFn func(field string) string

ColumnFn resolves an allow-listed DTO field name to its storage column. Build one from a resource's filter allow-list with (*Map).Column; it falls back to the field name when the field is not allow-listed.

type DerivedFn

type DerivedFn func(field string) *types.DerivedSort

DerivedFn resolves a sort field name to a COMPUTED / CORRELATED sort spec — a correlated aggregate (a Documents/Members count) or a timestamp-plus-interval (a connector's Next Sync) — or nil for a field whose sort is a plain/joined/ordinal column. Unlike JoinFn/OrdinalFn (built from the generated allow-list Map), a DerivedFn is supplied by the STORE, because a derived sort's correlation (which child table, which interval) is repos-tier knowledge, not a proto-declared column mapping. A nil DerivedFn (a resource with no derived sort fields) is treated as "no derived sorts".

type Field

type Field struct {
	// Join, when non-nil, resolves this field to a correlated-EXISTS predicate on a
	// joined table instead of the same-table Column. Filter-only.
	Join *types.JoinTarget
	// Name is the DTO field name a client uses in the filter JSON.
	Name string
	// Column is the storage column the filter compiles against (defaults to Name).
	Column string
	// Ordinal, when non-empty, is the Column's DB values in ascending sort order: sorting
	// the field orders by this value ordinal (a CASE) instead of lexically. Sort-only
	// (does not affect filtering).
	Ordinal []string
	// Aggregates are the aggregations an analytics query may apply to a RoleMeasure field.
	Aggregates []types.Aggregate
	// Type is the field's value type, gating value/element compatibility.
	Type FieldType
	// Role is how an analytics query may use the field (dimension by default).
	Role FieldRole
}

Field is one allow-listed filterable field: the DTO Name a client may filter on, the storage Column it maps to (defaults to Name), and its value Type. A field whose Join is set filters a JOINED table's column via a correlated EXISTS rather than the same-table Column.

func Bool

func Bool(name string) Field

Bool declares an allow-listed boolean field named name.

func Float

func Float(name string) Field

Float declares an allow-listed floating-point field named name.

func ID

func ID(name string) Field

ID declares an allow-listed identifier (UUID) field named name.

func Int

func Int(name string) Field

Int declares an allow-listed integer field named name.

func String

func String(
	name string,
) Field

String declares an allow-listed string field named name (Column defaults to name).

func Time

func Time(name string) Field

Time declares an allow-listed timestamp field named name (RFC3339 on the wire).

func (Field) Allows added in v0.3.2

func (f Field) Allows(agg types.Aggregate) bool

Allows reports whether agg is one of the field's permitted aggregates.

func (Field) AsMeasure added in v0.3.2

func (f Field) AsMeasure(aggs ...types.Aggregate) Field

AsMeasure returns a copy of the field declared as an analytics measure that permits exactly the given aggregates.

func (Field) AsTime added in v0.3.2

func (f Field) AsTime() Field

AsTime returns a copy of the field declared as the analytics time field.

func (Field) WithColumn

func (f Field) WithColumn(column string) Field

WithColumn returns a copy of the field whose storage column differs from its DTO Name (for resources where the wire field name is not the Ent column).

func (Field) WithJoin

func (f Field) WithJoin(join types.JoinTarget) Field

WithJoin returns a copy of the field that filters a JOINED table's column via a correlated EXISTS — set by the generator from a `(listquery.field).join` annotation.

func (Field) WithOrdinal

func (f Field) WithOrdinal(values []string) Field

WithOrdinal returns a copy of the field that SORTS by a value ordinal — the Column's DB values in ascending order — instead of lexically; set by the generator from a `(listquery.field).column.sort_ordinal` annotation. Sort-only.

type FieldRole added in v0.3.2

type FieldRole int

FieldRole is how an analytics query may use a field: grouped by (dimension), aggregated (measure), or bucketed by grain (time). Plain list-query fields are dimensions (the zero value).

const (
	// RoleDimension is a field grouped by and filtered on (the default).
	RoleDimension FieldRole = iota
	// RoleMeasure is a numeric field aggregated by one of its permitted Aggregates.
	RoleMeasure
	// RoleTime is the time field bucketed by a query's grain.
	RoleTime
)

type FieldSchema

type FieldSchema struct {
	// ValueSource is the decoded value-provisioning strategy for the field.
	ValueSource ValueSource
	// Join, when non-nil, marks this field as filtering a JOINED table's column; the
	// index-obligation gate reads Join.Table + Join.Columns so it checks the TARGET
	// table, not the resource's own. Populated from schema.json's `join` descriptor; nil
	// for a same-table field.
	Join *JoinTarget
	// Name is the DTO/proto field name — the filter/sort key clients use.
	Name string
	// AccessSensitivity is the field's declared access-sensitivity classification.
	AccessSensitivity string
	// Column is the backing storage column name.
	Column string
	// IndexKind is the declared index kind backing this field (checked by the
	// index-obligation gate); empty when the field declares no index.
	IndexKind string
	// Operators are the filter operators permitted on this field.
	Operators []string
	// Type is the field's value type (string, int, time, id, …).
	Type FieldType
	// CaseInsensitive reports whether string comparisons on this field are case-insensitive.
	CaseInsensitive bool
	// Sortable reports whether the field may be used in an ORDER BY.
	Sortable bool
	// Keysettable reports whether the field is the keyset-ordering (pagination
	// tiebreaker) column.
	Keysettable bool
}

FieldSchema is the full generated schema for one filterable/sortable field — every axis declared on the proto.

type FieldType

type FieldType int

FieldType is the value type of an allow-listed filter field. It gates which JSON values (and slice element types for in/not_in) are accepted for the field.

const (
	// FieldString is a text field (eq/ne/like/in/not_in).
	FieldString FieldType = iota
	// FieldInt is an integer field.
	FieldInt
	// FieldFloat is a floating-point field.
	FieldFloat
	// FieldBool is a boolean field (eq/ne).
	FieldBool
	// FieldTime is a timestamp field (RFC3339 in the wire JSON).
	FieldTime
	// FieldID is a UUID/identifier field (compared as a string).
	FieldID
)

func (FieldType) String

func (t FieldType) String() string

String returns the lowercase token for the field type (string/int/float/bool/time/id), matching the (listquery.field) proto type tokens — used when serializing the schema.

type JoinFn

type JoinFn func(field string) *types.JoinTarget

JoinFn resolves an allow-listed DTO field name to its joined-table target, or nil for a same-table field. Build one from a resource's allow-list with (*Map).Join. A nil JoinFn (a resource with no joined fields) is treated as "no joins" by ResolveFilter.

type JoinTarget

type JoinTarget = types.JoinTarget

JoinTarget is a re-export of core/types.JoinTarget, so the generated listqueryschema tier can construct a field's join with only the foundation/listquery import (no direct core/types import).

type Keyset

type Keyset[T any] struct {
	// RowValue returns a row's value for a resolved keyset column (the id column as its
	// string form), and false when the column is not one this store can extract — a guard
	// against a silently corrupt cursor if a sortable column is added without updating
	// the extractor. A timestamp column is returned as its time.Time; Run encodes it into
	// the token.
	RowValue func(row T, column string) (any, bool)

	// ParseID converts the token's id string to the value the seek binds against the id
	// column (e.g. uuid.Parse). Nil keeps the id a string. Kept here so foundation stays
	// uuid-free while the seek binds the id's native type.
	ParseID func(id string) (any, error)

	// TimestampCols names the keyset columns carried as time.Time, so the token encodes
	// them as a unix-microsecond int64 and restores them before the seek. Sourced from
	// the generated schema (ResourceSchema.TimeColumns), never hand-listed.
	TimestampCols map[string]bool
}

Keyset carries the per-resource data Run needs to serve the keyset (seek) arm and to mint the forward keyset cursor. A store opts into keyset pagination by setting Reader.Keyset; a store that has not (offset-only) leaves it nil. The order + fingerprint inputs (Resolve, Sort, Tiebreaker, Filter) live on the Reader, shared with the offset arm.

type KeysetColumn

type KeysetColumn struct {
	// Column is the resolved storage column (empty when Join is set — a joined sort has
	// no base column).
	Column string
	// Join, when non-nil, is a joined-table sort target: the ORDER BY is a correlated
	// subquery, not a base column. Only the caller's sort fields can be joined; the
	// appended tiebreaker + id are always same-table. A joined column never participates
	// in the keyset seek (a joined sort is always a custom sort → the offset pager), so
	// this is consumed only by the ORDER BY path.
	Join *types.JoinTarget
	// Derived, when non-nil, is a COMPUTED / CORRELATED sort — a correlated aggregate or
	// a timestamp-plus-interval — with no base column (Column is empty). Like Join, only
	// a caller sort field carries it (never a tiebreaker), and it rides the offset pager
	// (never the keyset seek).
	Derived *types.DerivedSort
	// Ordinal, when non-empty, sorts Column by a value ordinal instead of lexically — the
	// column's DB values in ascending order. Like Join, only a caller sort field carries
	// it (never a tiebreaker), and it rides the offset pager (never the keyset seek).
	Ordinal []string
	// Desc is true for a descending column, false for ascending.
	Desc bool
}

KeysetColumn is one column of the fully-deterministic keyset order: the resolved storage column and its sort direction. The ORDER BY (CompileOrder) and the keyset seek predicate (SeekFilter) are BOTH built from the same KeysetColumns sequence, so the WHERE seek can never drift from the ORDER BY it must mirror — the no-dup/no-skip precondition.

func KeysetColumns

func KeysetColumns(
	resolve ColumnFn,
	joinOf JoinFn,
	ordinalOf OrdinalFn,
	derivedOf DerivedFn,
	ofs []types.OrderField,
	tiebreaker string,
) []KeysetColumn

KeysetColumns returns the fully-deterministic ordered (column, direction) sequence a list query both orders by AND seeks on: the caller's resolved sort fields in their directions, then the resource's tiebreaker column DESC (created_at by default, or the resource's own keyset column, e.g. joined_at for a membership join table), then id DESC — with a column dedup (a tiebreaker the caller already sorts by, in either direction, is not re-appended). An empty tiebreaker falls back to created_at. It is the SINGLE source of the total order: CompileOrder renders it to ORDER BY nodes and the keyset seek compiler renders it to the WHERE predicate, so the two cannot drift. Precondition: the entity carries the tiebreaker column and a unique id column.

type Map

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

Map is a per-resource allow-list of filterable fields, keyed by DTO Name. Only fields added to the Map may be filtered; a filter on any other field is rejected — the SQL-safety boundary. Build it fluently: NewMap().Add(String("name"), ID("owner_id"), …).

func NewMap

func NewMap() *Map

NewMap returns an empty filter allow-list.

func (*Map) Add

func (m *Map) Add(fields ...Field) *Map

Add registers one or more allow-listed fields and returns the Map for chaining.

func (*Map) Column

func (m *Map) Column(field string) string

Column returns the storage column an allow-listed DTO field maps to, or the field name itself when it is not allow-listed — a safe fallback, since the compiler only ever receives fields the parser already validated against this Map. It adapts a Map into the fold's ColumnFn: Compile(backend, m.Column, filter).

func (*Map) Join

func (m *Map) Join(field string) *types.JoinTarget

Join returns the joined-table target for an allow-listed field that filters a joined column or nil for a same-table field (or an unknown field). It adapts a Map into the join resolver ResolveFilter uses to stamp a clause's JoinTarget: ResolveFilter(m.Column, m.Join, filter).

func (*Map) Lookup

func (m *Map) Lookup(name string) (Field, bool)

Lookup returns the allow-listed field for a DTO name and whether it is allow-listed.

func (*Map) Ordinal

func (m *Map) Ordinal(field string) []string

Ordinal returns the value-ordinal sort order for an allow-listed field, or nil for a field with no ordinal (or an unknown field). It adapts a Map into the OrdinalFn the ORDER BY path uses: KeysetColumns(m.Column, m.Join, m.Ordinal, sort, tiebreaker).

type OrdinalFn

type OrdinalFn func(field string) []string

OrdinalFn resolves an allow-listed DTO field name to its value-ordinal sort order — the column's DB values in ascending order — or nil for a field with no ordinal (plain lexical sort). Build one from a resource's allow-list with (*Map).Ordinal. A nil OrdinalFn is treated as "no ordinals".

type Reader

type Reader[T, D any] struct {
	// Runner is the datastore ListRunner: the store's scoped base query + typed execution.
	Runner interfaces.ListRunner[T]
	// Filter is the user's (unresolved) filter; Run resolves its columns via Resolve
	// before use.
	Filter types.Filter
	// ToDomain maps one datastore row to its domain entity.
	ToDomain func(T) (D, error)
	// Resolve maps a field name to its resolved storage column (the allow-list gate).
	Resolve ColumnFn
	// ResolveJoin maps a field name to its joined-table target, or nil for a same-table
	// field. Optional: a resource with no joined filter fields leaves it nil (no joins).
	// Build it from the resource's allow-list with (*Map).Join.
	ResolveJoin JoinFn
	// ResolveOrdinal maps a field name to its value-ordinal sort order, or nil for a
	// field with no ordinal. Optional: a resource with no ordinal-sorted fields leaves it
	// nil. Build it from the resource's allow-list with (*Map).Ordinal.
	ResolveOrdinal OrdinalFn
	// ResolveDerived maps a sort field name to a COMPUTED / CORRELATED sort spec — a
	// correlated aggregate (a Documents/Members count) or a timestamp-plus-interval (a
	// connector's Next Sync) — or nil for a plain/joined/ordinal field. Optional: a
	// resource with no derived sort fields leaves it nil. Supplied by the STORE (a
	// derived sort's correlation is repos-tier knowledge, not a proto-declared column),
	// unlike ResolveJoin/ResolveOrdinal which come from the generated allow-list.
	ResolveDerived DerivedFn
	// MapErr maps a datastore error to a domain error (nil ⇒ pass through unchanged).
	MapErr func(error) error
	// Keyset is the optional keyset spec; nil means this reader is offset-only (a keyset
	// token is rejected).
	Keyset *Keyset[T]
	// Tiebreaker is the resource's final ordering column (after Sort), before the id column.
	Tiebreaker string
	// Sort is the caller's ordering; empty ⇒ the default order (Tiebreaker, id), the only
	// keyset-eligible order.
	Sort []types.OrderField
	// Bounds carries the page-size limits and the total_count cap.
	Bounds Bounds
	// Count requests total_count on the offset arm (bounded by Bounds.CountCap; ignored
	// under skip-count).
	Count bool
}

Reader is the configuration for one paginated read. The store fills it and hands it to Run: the datastore ListRunner (its scoped base query + typed execution), the domain mapper, the resolver + sort + tiebreaker that define the order, the user filter, an optional keyset spec, and whether the offset arm computes total_count. Everything else — decode, seek, envelope, token — is Run's.

type ResourceSchema

type ResourceSchema struct {
	// Resource is the resource's Go/message name (e.g. "Document").
	Resource string
	// Fields are the resource's filterable/sortable fields, ordered by field name.
	Fields []FieldSchema
}

ResourceSchema is the generated list-query schema for one resource.

func (ResourceSchema) Field

func (s ResourceSchema) Field(name string) (FieldSchema, bool)

Field returns the schema for the named field and whether the resource declares it. It is the lookup the value-suggestion handler uses to resolve a requested field to its backing column + value_source (rejecting an unknown or non-suggestable field), so the suggestable surface is the proto-declared schema's, never the caller's.

func (ResourceSchema) KeysetColumn

func (s ResourceSchema) KeysetColumn() string

KeysetColumn returns the storage column of the resource's keyset-ordering field — the primary time tiebreaker CompileOrder appends to every ORDER BY for a stable total order (created_at for most resources, joined_at for the membership join tables). It falls back to created_at when no field is marked keysettable, matching the Timestamps-mixin default. The keysettable annotation is the single source of truth, so a store never hard-codes its tiebreaker column.

func (ResourceSchema) TimeColumns

func (s ResourceSchema) TimeColumns() map[string]bool

TimeColumns returns the set of storage columns whose field type is time (FieldTime). Keyset pagination carries these across the opaque base64/JSON token boundary as a unix-microsecond int64 — JSON has no native time type — and restores them to time.Time before compiling the seek predicate. Driving the round-trip off this generated set ('s single source) means a store never hand-lists which of its keyset columns are timestamps.

type Set

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

Set is a per-resource allow-list of sortable field names — the SQL-safety boundary for sorting: a client can never order by a field outside the Set (an arbitrary/unindexed, or non-existent, column). A field not in the Set is dropped, never substituted; the deterministic default order comes from the compiler's keyset tiebreaker (foundation/listquery.CompileOrder), which is resource-appropriate (created_at, joined_at, …), not a fixed column the resource may not even have.

func NewSet

func NewSet(fields ...string) *Set

NewSet returns a sort allow-list of the given field names.

func (*Set) ParseList

func (s *Set) ParseList(spec string) []types.OrderField

ParseList parses the scalar `sort` wire spec — a comma-separated list of `field[:dir]` tokens (e.g. "created_at:desc,name"; dir defaults to asc) — into an ordered []types.OrderField, dropping every non-allow-listed field. An empty spec, or one whose fields are all dropped, yields an empty result: the deterministic default order is then supplied by CompileOrder's keyset tiebreaker (resource-appropriate — created_at, joined_at, …), NOT a fixed column this resource may not have. It never errors and never string-concatenates client input, and it only ever returns allow-listed fields — the Set is the SQL-safety boundary for sorting. Wired at the transport edge.

type ValueSource

type ValueSource struct {
	// Kind is one of "enum", "suggest", "entityRef", "freetext".
	Kind string
	// EnumType is the fully-qualified proto enum type when Kind == "enum".
	EnumType string
	// EntityRef is the dimension name when Kind == "entityRef".
	EntityRef string
}

ValueSource is the decoded value-provisioning strategy for a field.

Jump to

Keyboard shortcuts

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