filter

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 24 Imported by: 0

README

parse/filter

Validated filter ASTs and parameterized PostgreSQL/SQLite query plans, including sort, keyset pagination, search, facets, and relation memberships. Use parse.Resource for the common struct-tag path; use these lower-level builders to integrate with existing SQL.

Usage and configuration

Guide Contents
Operators, list queries, examples, and every builder option Consumer guide
Relation filters and includes Consumer guide
HTTP parameters and errors Consumer guide

Start with the runnable package example. The focused filter example is compiled and output-checked in the unit suite.

API reference

  • Schemas: GenerateFilterSchema[T], GenerateSortSchema[T], GenerateSearchSchema[T], OperatorsFor, DefaultFilterOps, ResourceSchema (PrimaryKey: the declared final keyset column), FieldSchema, SortSchema, SearchSchema, FilterMetaFromSchema
  • Parsing: NewFilterParser (Parse, ParseBytes, ParseMap, And — filters parsed apart, combined and bounded as one), ParseSort, NewQueryParamParser, NewParsedFilter, ResolveRelativeTime
  • SQL: Dialect (Condition, ReadColumn: how a statement reads a column of a declared core.SQLType, DecimalParam: a bound decimal's canonical text as the dialect's exact number), PostgresDialect, SQLiteDialect, DialectFromDSN, NewSQLBuilder, AndSearch
  • Lists: NewListQuery (Build, WithFilterConfig, WithSearchSchema), ListResult, NewPage, Page[T], CursorValueByTag, CursorValue, Keyset, CursorKey, CursorPlan, EncodeCursor, DecodeCursor, ValidateCursor, ReverseDirection
  • Facets / includes: BuildFacetQuery (rows in a fixed order: request order; terms by count then key; ranges as declared; declared relations count visible related keys, WithFacetRelatedPolicies), ScanFacetUnion, FacetMap (results keyed by field, as Page.Facets carries them), FacetBuckets (that order for a backend counting in Go; a list facet counts each distinct element once per row), ParseFacetTag, ParseIncludes, BuildIncludeQuery (declared relations read visible related rows; text keys use code-point order before deduplication/capping; WithIncludeRelatedPolicies), IncludeQuery.Scan → IncludeRows (IDs, owners Truncated at the cap), ResolveInclude / IncludeLookup (the same projection outside SQL through batched owner/target pairs), Page.SetInclude (canonical page projection; empty results omitted), MaxIncludes, Rows
  • Policy: PolicyConstraints (Merge), PolicyConstraint (UnknownIfInvalid: a value that does not apply to the field compiles to SQL's unknown — no row satisfies it, nor its negation — instead of an error), UnknownConstraint (that unknown, stated), ApplyPolicyConstraints (values normalized as request values, without request limits), BuildBaseConstraints
  • Memberships: PolicyConstraint.Member (*Lookup: the field is among the values a lookup table or view projects — a closure's descendants, granted ids, a pair relation's resources), LookupCond (WhereEq, WhereIn, WhereNull, WhereAll, WhereAny), ResolveMemberships (a memory backend's once-per-request resolution through its MembershipLookup into the $in SQL's subquery equals; no lookup is SQL's unknown), MatchLookupRow (a lookup row's condition with SQL equality)
  • Relations: FieldSchema.Related (the target's schema), FieldSchema.RelatedBy (the owner's correlation field), OpSome / OpNone ($some, $none; $is, $is_not parse to them), MaxRelationDepth, MaxRelationPredicates, WithRelatedPolicies / ListRelatedPolicies, ErrRelatedPolicyRequired
  • Array elements: FieldSchema.Elements (a JSON array's element paths, from slice-of-struct members or the elements tag), $some / $none on it, DocumentElements
  • Memberships outside SQL: Lookup (Where, Filter), WhereEq / WhereIn / WhereNull / WhereNotNull / WhereMember / WhereAll / WhereAny, MembershipLookup, RelationLookup (lookups with a filter), ResolveMemberships (policy memberships and relation predicates) with ResolveTenant, ResolveRelatedPolicies, ResolveUnscopedRelations, MatchLookupRow
  • JSON paths: JSONPath, JSONStorage (StorageJSONB, StorageJSON, StorageText), PathDecl, ResourceSchema.WithPaths (a request's copy-on-write view), SortSchema.WithPaths (its sort keys), FieldExpr (a field's value expression in a dialect), Dialect.PathExpr, PathValue (the reference value rule a memory backend evaluates), SortFieldSchema.Path / CursorKey.Path, KeyValue (a row's canonical value for any key, paths included), PathDecl.Facet, MaxPathDepth; SortResult.ToSQL(dialect) renders ORDER BY as the keyset orders
  • Reference semantics: MatchLike, EscapeLike (a literal as a LIKE pattern; append % for a prefix), JSONEqual, JSONContains, JSONHasKey, PathValue
  • Geo: GeoPoint, GeoDistance, GeoBoundingBox, GeoPolygon, GeoFieldConfig, ParseDistance, ValidateGeoPoint, ValidateGeoBoundingBox, ValidateGeoPolygon
  • Errors: ECode* (core.Code constants), Codes

Code map

File Responsibility
filter.go FilterParser — parse, normalize, depth / condition / cost limits
schema.go Operators, ResourceSchema, FieldSchema, sort schema types, SQLResult
operators.go OperatorsFor — the operator × type table
generate.go Schema generation from struct tags; default operators
sql.go Dialect, SQLBuilder, relation EXISTS, junction tenancy
sql_postgres.go / sql_sqlite.go Dialect predicates (strings, decimals, documents, arrays, geo)
like.go LIKE patterns: escaping, GLOB translation, MatchLike
jsondoc.go JSONEqual, JSONContains, JSONHasKey
path.go JSONPath, FieldExpr, PathValue, both dialects' PathExpr
path_declare.go Path declaration: paths / storage tags, struct members, PathDecl, WithPaths
list.go ListQuery, ListOptions, ListResult
page.go Resource Cursor and PageInfo wire types
keyset.go Keyset plan, cursors, NewPage, Page[T] (items, page info, Relations, Truncated, Facets)
facet.go / facet_union.go / facet_buckets.go Facet schema and the single facet query
relation_parse.go / relation_sql.go Relation predicates: target filters and key shorthand; one correlated EXISTS per predicate with per-depth aliases
elements.go Element paths of JSON arrays; one EXISTS over the elements per $some / $none
membership.go / relation_resolve.go Lookups and their SQL; ResolveMemberships for backends outside SQL, relation predicates included
search.go Search schema and condition
include_resolve.go ResolveInclude, IncludeLookup, IncludePair — tenant- and policy-scoped projections outside SQL, distinct keys ordered before the per-owner cap
include.go include relation projections — ParseIncludes / ParseIncludeNames, per-owner-capped BuildIncludeQuery, IncludeQuery.Scan, Page.SetInclude
policy.go Policy constraints
queryparam.go Flat query parameters — QueryParamParser (QueryParamAccept, QueryParamLenient, QueryParamFieldsOnly), IsListParam
params.go Param* list request parameters
geo.go Geo types, validation, tag parsing, WKT
time_token.go Relative-time tokens
rows.go Rows
errors.go E-codes
filtertest/ Conformance fixture: the operator × type cases (Run), the membership cases with their lookup tables and reference lookup (RunMembership, LookupTables, Lookups), and the JSON path cases (RunPaths, PathRecords, PathSchema)

Dependencies

  • Imports: parse/core, parse/internal/dialect, parse/internal/cursor, stdlib uuid
  • Imported by: parse, forge/dal/*, forge/feature, forge/fibermw/httputil
  • Package boundary: standalone Parse module

References

Package overview · Integration recipes

Documentation

Overview

Package filter provides secure parsing of query filters with multi-dialect SQL generation, faceted aggregation, cursor pagination, full-text search, and query param parsing.

Struct Tags

Filter schema generation is driven by struct tags. The following tags are recognized by GenerateFilterSchema, GenerateSortSchema, and GenerateSearchSchema:

parse:"filter"          — include in filter schema
parse:"filter,facet"    — include in filter + facet schemas
parse:"filter,search"   — include in filter + search schemas
filter_ops:"eq,ne,gt"   — allowed filter operators (each must apply to the type: [OperatorsFor])
enum:"active,inactive"  — restrict to these values
indexed:"true"          — cost estimation hint (indexed = cheaper)
parse:"…,sort"          — mark as sortable
sort_default:"desc"     — default sort field + direction
relation:"tbl,src,tgt[,pk[,tenant_col]]" — M2M relation (correlated EXISTS)
db:"column_name"        — SQL column name (falls back to snake_case of json name)
label:"Display Name"    — UI label for schema response
description:"Help text" — UI description
group:"section"         — UI sidebar section grouping
order:"1"               — UI display order within group
facet:"terms"           — facet type: "terms", "terms:20", "range:0-50,50-100,100+"
search_weight:"A"       — full-text search weight: A (highest) through D (lowest)
parse:"filter,include"  — relation field projectable with the include parameter
geo:"jsonb"             — geo field (jsonb | geography | geometry); geo_srid, geo_lat, geo_lon refine it

Filter Operators

Supported operators: eq, ne, gt, gte, lt, lte, in, nin, like, ilike, null, not_null, exists, contains, near, within, intersects. Logical operators: $and, $or, $not. OperatorsFor lists the operators each field type admits; each executes with one meaning on every dialect (see the filtertest conformance fixture).

Query Param Parsing

QueryParamParser translates flat URL query parameters into a FilterAST using suffix conventions: _gt, _gte, _lt, _lte, _ne, _in, _nin, _like, _ilike, _null, _contains. No suffix means equality.

SQL Dialects

PostgresDialect generates PostgreSQL SQL ($1 placeholders, jsonb documents, native arrays, PostGIS geo). SQLiteDialect generates SQLite SQL (?1 placeholders, JSON text documents and arrays matched through json_each). A dialect's Condition builds a predicate's SQL and arguments together.

ListResult Composition

ListResult returns all SQL parts separately (Where, OrderBy, Args, CountSQL, Facets) so callers can compose with custom SQL fragments.

Index

Constants

View Source
const (
	ECodeQueryTooExpensive core.Code = "E2061"
	ECodeInvalidArray      core.Code = "E2062"
	ECodeInputTooLarge     core.Code = "E2063"
	ECodeCursorTooLarge    core.Code = "E2065"

	ECodeUnknownField        core.Code = "E2071"
	ECodeInvalidOperator     core.Code = "E2072"
	ECodeValueTypeMismatch   core.Code = "E2073"
	ECodeDepthExceeded       core.Code = "E2074"
	ECodeSchemaNotFound      core.Code = "E2078"
	ECodeSortFieldNotAllowed core.Code = "E2079"
	ECodeSQLGenerationFailed core.Code = "E2080"
	ECodeSortFieldLimit      core.Code = "E2081"
	ECodeInvalidCursor       core.Code = "E2082"
	ECodeInvalidRelation     core.Code = "E2083"
	ECodeInvalidJSON         core.Code = "E2086"
	ECodeInvalidEnumValue    core.Code = "E2087"
	ECodeArrayTooLarge       core.Code = "E2088"
	ECodeMaxConditions       core.Code = "E2089"

	// Geo-spatial error codes.
	ECodeInvalidGeoCoords  core.Code = "E2112"
	ECodeInvalidDistance   core.Code = "E2113"
	ECodeInvalidGeoPolygon core.Code = "E2114"
	ECodeGeoNotSupported   core.Code = "E2115"

	// malformed UUID at the filter boundary returns 400 instead
	// of zero rows.
	ECodeInvalidUUID core.Code = "E2116"

	// ECodeInvalidListParam is a list request parameter that is malformed or
	// that the resource cannot honor: a page size that is not a positive
	// integer, a boolean flag that is not a boolean, search on a resource
	// without search fields.
	ECodeInvalidListParam core.Code = "E2067"
)

Error codes for filter and list operations.

View Source
const (
	ParamFilter         = "filter"          // JSON filter expression
	ParamSort           = "sort"            // "-price,name"
	ParamCursor         = "cursor"          // page_info cursor
	ParamPageSize       = "page_size"       // items per page
	ParamIncludeTotal   = "include_total"   // "true": populate page_info.total_count
	ParamIncludeDeleted = "include_deleted" // "true": include soft-deleted rows
	ParamSearch         = "search"          // free-text search
	ParamFacets         = "facets"          // comma-separated facet fields (OPTIONS)
	ParamFields         = "fields"          // comma-separated response field mask
	ParamInclude        = "include"         // comma-separated relation fields to project as _relations
)

List request query parameters: the HTTP contract of a list endpoint built on this package. HTTP handlers read them, the TypeScript SDK sends them, and QueryParamParser skips them, so a flat query-param filter never mistakes one for a field.

View Source
const (
	MaxRelationDepth      = 2
	MaxRelationPredicates = 8
)

Relation predicate limits: relations nest at most MaxRelationDepth deep (devices → commands → receipts), and one filter holds at most MaxRelationPredicates of them, nested ones included.

View Source
const DefaultListLimit = 50

DefaultListLimit is the default page size when no limit is specified.

View Source
const DefaultMaxIncludePerRelation = 100

DefaultMaxIncludePerRelation caps the target rows an include query returns per owner, so an entity with thousands of related rows cannot turn one page into an unbounded read. Per-call override via WithMaxIncludePerRelation. .

View Source
const MaxIncludes = 3

MaxIncludes is how many relations one list may include.

View Source
const MaxListLimit = 1000

MaxListLimit is the maximum allowed page size.

View Source
const MaxPathDepth = 8

MaxPathDepth bounds how many members deep a declared path may reach.

Variables

View Source
var (
	// CollationPgCUTF8 is pg_c_utf8 (PostgreSQL 17+), the zero value:
	// code-point order with Unicode case mapping, so ILIKE on a column
	// declared with it still folds non-ASCII letters.
	CollationPgCUTF8 = PostgresCollation{}
	// CollationC is "C", for PostgreSQL 14–16: the same order (UTF-8 bytes
	// sort as their code points), but a column declared with it folds only
	// ASCII letters under ILIKE.
	CollationC = PostgresCollation{/* contains filtered or unexported fields */}
)
View Source
var ErrRelatedPolicyRequired = errors.New("filter: a policy-scoped relation is built without the target's policy")

ErrRelatedPolicyRequired is a relation predicate on a policy-scoped relation built without the server's policy for the target table: the related rows the caller may see are undecided, so nothing is built.

View Source
var ErrUnknownDSN = dialect.ErrUnknownDSN

ErrUnknownDSN is returned by DialectFromDSN for a DSN whose scheme names neither PostgreSQL nor SQLite.

View Source
var ErrUnsupportedDialect = errors.New("unsupported SQL dialect")

ErrUnsupportedDialect is returned when a DSN references an unsupported database.

View Source
var OperatorFromString = map[string]FilterOperator{
	"$eq":         OpEq,
	"$ne":         OpNe,
	"$gt":         OpGt,
	"$gte":        OpGte,
	"$lt":         OpLt,
	"$lte":        OpLte,
	"$in":         OpIn,
	"$nin":        OpNin,
	"$like":       OpLike,
	"$ilike":      OpILike,
	"$null":       OpNull,
	"$not_null":   OpNotNull,
	"$exists":     OpExists,
	"$contains":   OpContains,
	"$has_key":    OpHasKey,
	"$near":       OpNear,
	"$within":     OpWithin,
	"$intersects": OpIntersects,
	"$some":       OpSome,
	"$none":       OpNone,
	"$is":         OpIs,
	"$is_not":     OpIsNot,
	"$or":         OpOr,
	"$and":        OpAnd,
	"$not":        OpNot,
}

OperatorFromString maps a JSON operator key (e.g. "$eq") to FilterOperator.

ValidFilterOps is the set of all recognized filter operators. Used by the schema generator to validate filter_ops tag values.

Functions

func AllOperatorStrings

func AllOperatorStrings() []string

AllOperatorStrings returns every supported value-comparison operator as a stable, sorted string slice. Used by the OPTIONS endpoint to advertise the operator vocabulary to clients.

Logical operators ($and/$or/$not) and the geo operators are intentionally excluded — the former are query-tree shape, not field-level comparisons, and the latter only apply to geo-typed fields whose schema entry already declares them.

func AndSearch

func AndSearch(schema *SearchSchema, d Dialect, where string, args []any, offset int, query string) (string, []any, error)

AndSearch ANDs a free-text search onto a WHERE fragment, numbering the search's placeholders after args (themselves after offset earlier placeholders). An empty query, or a schema with no searchable fields, returns the fragment unchanged. Count, list and facet queries fold search through it, so all three describe the same population.

func Codes

func Codes() []core.Code

Codes returns the codes defined by this package.

func CompareKeyValues

func CompareKeyValues(k CursorKey, a, b any) int

CompareKeyValues orders two canonical key values (see CursorValue) under the keyset model: NULL greatest, numbers and decimals exactly, timestamps by instant, strings bytewise, false before true. Direction is not applied.

func CursorValue

func CursorValue(v any) (any, error)

CursorValue reduces a row value to the canonical form a token carries and SQL binds: nil, string, bool, int64, uint64 or float64. Pointers are dereferenced (nil → NULL), decimals (core.IsDecimalType) become their canonical text, driver.Valuer values are resolved, times become RFC 3339 UTC strings (the form SQLite stores), and text-marshalling types such as uuid.UUID become their text. Anything else cannot resume a page and is an error, never an omitted cursor.

func CursorValueByTag

func CursorValueByTag(entity any, tagKey, tagValue string) (any, error)

CursorValueByTag extracts a struct field value by matching a struct tag, for use as NewPage's value function (tagKey "db", tagValue the row column). Fields promoted from embedded structs match too. Returns an error if no field matches.

func DefaultFilterOps

func DefaultFilterOps() map[core.FieldType][]FilterOperator

DefaultFilterOps returns a defensive copy of the package's default FieldType -> operators map. Callers can mutate the returned map without affecting the package-level defaults or other callers.

func DocumentElements

func DocumentElements(doc any) ([]any, error)

DocumentElements is a JSON array document's elements, decoded as PathValue decodes documents; nil for a document that is not an array. A backend evaluating `$some` / `$none` in memory tests the node's child on each element, reading element paths with PathValue(element, field).

func EncodeCursor

func EncodeCursor(token *PageToken) (string, error)

EncodeCursor encodes a PageToken to an opaque base64url token. Values must already be canonical. A token over the decode limit is E2065.

func EscapeLike

func EscapeLike(s string) string

EscapeLike returns s as a `$like` pattern matching s literally; append `%` for a prefix match.

func FacetMap

func FacetMap(results []FacetResult) map[string][]FacetBucket

FacetMap keys facet results by field, the shape a list page and a facet response carry; nil for none.

func FieldExpr

func FieldExpr(d Dialect, f *FieldSchema) string

FieldExpr is the SQL expression of a field's value: its column, or for a path the dialect's typed extraction.

func IsListParam

func IsListParam(name string) bool

IsListParam reports whether name is one of the list request parameters (Param*), which a query string never carries as a field filter.

func JSONContains

func JSONContains(doc, needle any) bool

JSONContains reports whether doc contains needle: an object contains every needle member (recursively), an array contains every needle element somewhere in it (order and multiplicity ignored), a scalar contains an equal scalar, and — at the top level only — an array contains a scalar needle that is one of its elements.

func JSONEqual

func JSONEqual(a, b any) bool

JSONEqual reports whether two documents are equal: objects by key set and member values, arrays element by element in order, numbers by exact value (1 equals 1.0), other scalars by value.

func JSONHasKey

func JSONHasKey(doc any, key string) bool

JSONHasKey is the reference semantics of `$has_key` (jsonb `?`): doc is a decoded document holding key as a top-level object key (whatever its value, null included), as a string element of a top-level array, or as itself when it is a string.

func KeyValue

func KeyValue(k CursorKey, raw any) (any, error)

KeyValue is a row's canonical value for key k, given raw, the row's value at k.Column: CursorValue of it, or for a path key the member the path rule extracts (nil when absent). A decimal key's value is its canonical text (core.DecimalText), whatever form its column scans to. Cursors and a memory backend's ordering read keys through it alike.

func MatchLike

func MatchLike(s, pattern string, fold bool) bool

MatchLike reports whether s matches the LIKE pattern, folding case when fold is set. It is the reference semantics of `$like` / `$ilike`; a malformed pattern matches nothing (the parser rejects it first).

func MatchLookupRow

func MatchLookupRow(cond *LookupCond, column func(name string) (value any, ok bool)) (bool, error)

MatchLookupRow reports whether cond selects a lookup row, given its column values: values compare as SQL compares stored values — numbers by exact value, times by instant, UUIDs as their canonical text, strings by content and a NULL column selects nothing. A nil cond selects every row; a column the row lacks is an error, so a misspelt column cannot silently select nothing.

func ParseDistance

func ParseDistance(s string) (float64, error)

ParseDistance parses a distance string (e.g., "5km", "500m", "3mi") into meters.

func ParseIncludeNames

func ParseIncludeNames(schema *ResourceSchema, names []string) ([]string, error)

ParseIncludeNames is ParseIncludes over names already split: each is trimmed, empty and repeated names are dropped, and the rest must be includable relation fields, at most MaxIncludes of them.

func ParseIncludes

func ParseIncludes(schema *ResourceSchema, param string) ([]string, error)

ParseIncludes validates a list request's `include` parameter (ParamInclude): comma-separated relation fields to project as `_relations`, at most MaxIncludes. Each must be an includable relation field; duplicates collapse and order is kept.

func PathValue

func PathValue(doc any, f *FieldSchema) (any, bool, error)

PathValue is the reference evaluator of a path: the member doc holds at f.Path, typed as the filter compares it, or present false when the member is absent — missing, JSON null, another JSON kind, or behind a non-object. SQL dialects render the same rule; a memory backend evaluates paths with it. doc is the column's value: JSON text, json.RawMessage, a decoded document, or any Go value that encodes to one.

Strings come back as string, numbers as int64 when the literal is an integer within int64 and float64 otherwise, booleans as bool, timestamps as their canonical text and UUIDs lower-cased. A decimal member is a JSON number, or a string of at most core.MaxDecimalDigits characters in plain decimal notation, and comes back as its canonical text (core.DecimalText).

func ResolveRelativeTime

func ResolveRelativeTime(s string, now time.Time) (t time.Time, isToken bool, err error)

ResolveRelativeTime resolves a `now` / `now±N<unit>` token against now. isToken reports whether s has the token shape at all; err is non-nil when a token-shaped value cannot be resolved because its offset overflows the ±292-year range of time.Duration. Recognition and resolution are one step so a token can never validate and then fail to resolve.

func ToAnySlice

func ToAnySlice(ss []string) []any

ToAnySlice converts a []string to []any.

func ValidateCursor

func ValidateCursor(token *PageToken, keys []CursorKey) error

ValidateCursor rejects a token whose field list disagrees with the active keyset — a cursor minted under one sort must not be replayed under another . Values must match each key's type, numeric range and nullability. On success values are normalized in place; on failure the token is unchanged. Keys without scalar schema metadata (including custom scalar marshalers) are checked only for a canonical scalar and nullability.

func ValidateGeoBoundingBox

func ValidateGeoBoundingBox(bb *GeoBoundingBox) error

ValidateGeoBoundingBox checks that the bounding box is valid.

func ValidateGeoPoint

func ValidateGeoPoint(lat, lon float64) error

ValidateGeoPoint checks that lat/lon are within valid ranges.

func ValidateGeoPolygon

func ValidateGeoPolygon(p *GeoPolygon) error

ValidateGeoPolygon checks that the polygon has valid points.

func VisibleRelated

func VisibleRelated(field *FieldSchema, dialect Dialect, alias string, tenant any, related map[string]*PolicyConstraints, bind func(any) string) (string, error)

VisibleRelated is the predicate holding a declared many-to-many's target rows, under alias, to those a request may relate: in the tenant (or admitted by the target's policy) and live. It begins with " AND ", is "" when nothing limits the targets, and binds every value with bind — for a statement another package numbers, such as a membership patch's guarded insert.

Types

type BuildFacetQueryOption

type BuildFacetQueryOption func(*facetConfig)

BuildFacetQueryOption configures BuildFacetQuery behavior.

func WithFacetRelatedPolicies

func WithFacetRelatedPolicies(policies map[string]*PolicyConstraints) BuildFacetQueryOption

WithFacetRelatedPolicies supplies the policies of policy-scoped relations' target tables (WithRelatedPolicies).

func WithFacetTenant

func WithFacetTenant(value any) BuildFacetQueryOption

WithFacetTenant binds the tenant every tenant-scoped relation facet's junction join is restricted to. Required when a requested relation facet declares a TenantColumn; without it (or WithFacetUnscopedRelations) the build fails.

func WithFacetUnscopedRelations

func WithFacetUnscopedRelations() BuildFacetQueryOption

WithFacetUnscopedRelations deliberately aggregates tenant-scoped relation facets without a tenant predicate. Never use it on a request path.

type Cursor

type Cursor string

Cursor is an opaque position in a paginated result set.

func (Cursor) IsZero

func (c Cursor) IsZero() bool

IsZero reports whether the cursor is empty.

type CursorField

type CursorField struct {
	Name      string        `msgpack:"n" json:"name"`
	Value     any           `msgpack:"v" json:"value"`
	Direction SortDirection `msgpack:"d" json:"direction"`
}

CursorField is one keyset component of a PageToken: the API sort-field name, the boundary row's canonical value (see CursorValue), and the presentation direction.

type CursorKey

type CursorKey struct {
	Name     string
	SQLExpr  string
	Column   string
	DataType core.FieldType
	// NumberKind retains the schema numeric range (Invalid uses int64/float64).
	NumberKind reflect.Kind
	Direction  SortDirection
	Nullable   bool
	// Path is the document member the key orders by (SortFieldSchema.Path).
	Path *JSONPath
}

CursorKey is one component of the effective keyset: the API-facing name carried in tokens, the SQL expression emitted into ORDER BY and seek predicates, the row column a token value is read from, and how the key orders.

func Keyset

func Keyset(sort *SortResult, tieBreaker CursorKey) []CursorKey

Keyset derives the effective keys for a sort: its fields followed by the tie-breaker unless the sort already contains it. The tie-breaker inherits the primary direction so a uniform-direction list keeps native row-value comparison available. SQL repositories get this from ListQuery.Build; the memory repository calls it directly so every backend walks one key list.

type CursorPlan

type CursorPlan struct {
	Keys      []CursorKey
	Reverse   bool
	HasCursor bool
}

CursorPlan is what a repository needs to turn a fetched row set into a page: the effective keyset in presentation order, whether the query walked backwards, and whether the request carried a cursor.

func (CursorPlan) Token

func (p CursorPlan) Token(reverse bool, get func(column string) (any, error)) (*PageToken, error)

Token mints the PageToken positioned at a row. get resolves a key's value from the row by row column. reverse marks a token that seeks backwards (a page's start cursor).

type Dialect

type Dialect interface {
	dialect.Dialect

	// Condition renders the predicate `field <op> value`. bind appends one
	// argument and returns its placeholder, so SQL text and arguments are
	// built together and cannot disagree in count or order. value is the
	// parser's normalized value (see FilterParser.normalizeValue); a shape
	// the dialect cannot evaluate is a *core.ParseError (E2115 for geo).
	// Every predicate is NULL when the field is NULL, except the NULL tests
	// and an empty `$in` / `$nin`, so negation keeps SQL's three-valued
	// meaning.
	Condition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, error)

	// CTEMaterializeHint returns the dialect-specific hint string emitted
	// after "WITH base AS" so the planner does not inline a CTE referenced
	// multiple times. Postgres returns " MATERIALIZED" (PG ≥ 12); SQLite
	// returns "" (CTEs are always materialized).
	//
	// Used by BuildFacetQuery when composing the single CTE + UNION ALL
	// facet plan; the filter scan must happen exactly once.
	CTEMaterializeHint() string

	// NativeRowCompare reports whether keyset seeks may use a native
	// row-value comparison `(a, b) > ($1, $2)` (used only when no key is
	// nullable and every key walks the same direction). Dialects without it
	// get the lexicographic OR-expansion.
	NativeRowCompare() bool

	// CodePointOrder returns expr under code-point (bytewise) collation, the
	// order string comparisons, keyset walks and the memory backend share; a
	// column's linguistic collation would order "B" before "a".
	CodePointOrder(expr string) string

	// RowLock is the clause that locks the rows a SELECT reads until its
	// transaction ends — " FOR UPDATE" — or "" where the database locks
	// writers as a whole (SQLite).
	RowLock() string

	// PathExpr renders a JSON path's typed value: the member when it has
	// type t's JSON kind (and shape), else NULL.
	PathExpr(p *JSONPath, t core.FieldType) string

	// ReadColumn renders how a statement reads column, whose database type
	// the resource declared as t: an expression the column's string field
	// scans, or column itself when the database's own encoding scans.
	ReadColumn(column string, t core.SQLType) string

	// DecimalParam renders the placeholder of a bound decimal, its
	// canonical text (core.DecimalText), as the exact number a decimal
	// column or path compares and orders with.
	DecimalParam(placeholder string) string
}

Dialect generates database-specific SQL.

func DialectFromDSN

func DialectFromDSN(dsn string) (Dialect, error)

DialectFromDSN returns the Dialect of a database DSN: postgres:// (and postgresql://, cockroach://, crdb://) or a SQLite file or :memory:.

type FacetBucket

type FacetBucket struct {
	Key   string `json:"key"`
	Count int64  `json:"count"`
}

FacetBucket is a single aggregation bucket.

func FacetBuckets

func FacetBuckets(cfg *FacetSchema, counts map[string]int64, limit int) []FacetBucket

FacetBuckets orders one facet's counts as BuildFacetQuery returns them, for a backend that counts in Go: a terms facet by count, ties by key in code-point order, cut to limit (none when limit <= 0); a range facet in its declared order. Keys without a count are dropped.

type FacetQuery

type FacetQuery struct {
	SQL  string
	Args []any
}

FacetQuery is a generated facet query: SQL plus its bound arguments.

func BuildFacetQuery

func BuildFacetQuery(
	schema *ResourceSchema,
	dialect Dialect,
	filterWhere string,
	filterArgs []any,
	facets []FacetRequest,
	opts ...BuildFacetQueryOption,
) (FacetQuery, error)

BuildFacetQuery composes a single CTE + UNION ALL query covering every requested facet. The policy-merged filter scans once in the base CTE; each facet aggregates over the materialized set.

Result row shape (uniform across dialects):

facet_field  TEXT      – which facet a row belongs to
facet_key    TEXT      – bucket key
facet_count  BIGINT    – count of matching rows

Range facets are pre-bucketed in the CTE projection so outer GROUP BYs are uniform. Terms facets enforce per-field LIMIT N via inline subqueries, keeping the highest counts and, among equal counts, the lowest keys in code-point order. Rows come back facet by facet in request order: terms buckets by count then key, range buckets as declared. Pair with ScanFacetUnion to re-group rows into per-field [FacetResult]s.

Relation predicates in filterWhere are correlated EXISTS, so the base CTE holds one row per matching owner and every count is a count of owners . Relation-typed facet fields JOIN the junction inside their own UNION ALL branch. The base CTE projects the owner's PK under the alias `__owner_pk`; the relation branch joins on `j.<source> = base.__owner_pk` and aggregates over `j.<target>`. Range facets on a relation field are rejected here.

Errors when the schema is nil, no facets are requested, or any requested field is missing or not FieldSchema.Facetable.

type FacetRange

type FacetRange struct {
	Key string
	Min *float64
	Max *float64
}

FacetRange defines a bucket boundary for range facets.

type FacetRequest

type FacetRequest struct {
	Field string
	Limit int
}

FacetRequest names a field to facet plus an optional per-request limit.

Limit resolution: FacetRequest.Limit > 0 → that. Else FieldSchema.FacetSchema.Limit > 0 → that. Else defaultTermsLimit (50). Range facets ignore Limit (bucket count is fixed by FacetSchema.Ranges).

type FacetResult

type FacetResult struct {
	Field   string        `json:"field"`
	Buckets []FacetBucket `json:"buckets"`
}

FacetResult holds the aggregated facet data after query execution.

func ScanFacetUnion

func ScanFacetUnion(rows Rows) ([]FacetResult, error)

ScanFacetUnion reads (facet_field, facet_key, facet_count) rows produced by BuildFacetQuery and groups them into per-field [FacetResult]s in the query's order: facets as requested; a terms facet's buckets by count, ties by key; a range facet's as declared.

NULL keys are dropped (BuildFacetQuery's WHERE filters them, but defense in depth).

Caller owns the iterator's Close — sql.Rows.Close returns an error while pgx.Rows.Close does not, so the contract here stays narrow.

type FacetSchema

type FacetSchema struct {
	Field   string
	SQLExpr string
	Type    FacetType
	Limit   int
	Ranges  []FacetRange
}

FacetSchema defines a single facetable field.

func ParseFacetTag

func ParseFacetTag(tag string) (*FacetSchema, error)

ParseFacetTag parses the `facet` struct tag.

type FacetType

type FacetType string

FacetType determines how aggregation SQL is generated.

const (
	FacetTerms     FacetType = "terms"
	FacetTypeRange FacetType = "range"
)

type FieldSchema

type FieldSchema struct {
	ExternalName string
	SQLExpr      string
	DataType     core.FieldType
	// NumberKind is the Go numeric kind the field decodes into
	// (reflect.Int8 … reflect.Float64). Integer and float values are
	// range-checked against it; reflect.Invalid (hand-built schemas)
	// validates against the widest domain, int64 / float64.
	NumberKind reflect.Kind
	// Elem is the element type of a FieldTypeArray field; `$contains`
	// values normalize to it (and NumberKind is the element's kind).
	Elem             core.FieldType
	AllowedOperators []FilterOperator
	Indexed          bool
	EnumValues       []string
	Validate         func(value any) error
	Relation         *core.RelationSchema
	// Related is the relation target's filter schema: requests filter the
	// related rows with it (`$some`, `$none`, `$is`, `$is_not`). Nil for a
	// relation compared by key alone.
	Related *ResourceSchema
	// RelatedBy is the owner's field a relation correlates on (its key, or
	// a belongs-to's foreign key), for backends that evaluate relation
	// predicates as memberships (ResolveMemberships).
	RelatedBy *FieldSchema
	// Path makes the field a typed member of a document column; its value
	// expression is Dialect.PathExpr (render with FieldExpr) and SQLExpr is
	// empty.
	Path *JSONPath
	// Storage is how a document column holds its JSON (storage tag);
	// empty means jsonb.
	Storage JSONStorage
	// Elements is a JSON array document's element schema: paths into one
	// element, which `$some` / `$none` filters test on one element at a
	// time. Nil for a document without element paths.
	Elements *ResourceSchema

	// UI metadata (populated from struct tags, used by SchemaResponse).
	Label       string
	Description string
	Group       string
	Order       int
	Facetable   bool
	Searchable  bool
	Immutable   bool
	// Includable opts a relation field into the `?_include=<field>`
	// projection. Valid only when Relation is non-nil.
	Includable bool

	FacetSchema *FacetSchema    // Parsed facet configuration (from `facet` tag).
	GeoConfig   *GeoFieldConfig // Geo-spatial metadata (from `geo` tag). Non-nil when DataType == FieldTypeGeo.
}

FieldSchema defines a single filterable field.

func (*FieldSchema) AllowsOperator

func (f *FieldSchema) AllowsOperator(op FilterOperator) bool

AllowsOperator checks whether op is in the field's allowed operators list. The NULL tests — null, not_null and exists — are one capability: each is the negation or alias of another, so a field that declares any of them allows all three.

type FilterAST

type FilterAST struct {
	Root *FilterNode
}

FilterAST is the root of a parsed filter tree.

func ApplyPolicyConstraints

func ApplyPolicyConstraints(schema *ResourceSchema, userAST *FilterAST, pc *PolicyConstraints) (*FilterAST, error)

ApplyPolicyConstraints merges policy constraints into the filter AST. Constraint values are normalized as request values are, since dialects and the memory backend read canonical values only; request limits do not apply, as policy lists (allowed IDs) are trusted and may be long. Relative-time tokens resolve against the wall clock.

type FilterFieldMeta

type FilterFieldMeta struct {
	Name      string   `json:"name"`
	Type      string   `json:"type"`
	Operators []string `json:"operators,omitempty"`
}

FilterFieldMeta describes a single filterable field.

type FilterMeta

type FilterMeta struct {
	Fields []FilterFieldMeta `json:"fields"`
}

FilterMeta describes available filter fields for an entity.

func FilterMetaFromSchema

func FilterMetaFromSchema(schema *ResourceSchema) *FilterMeta

FilterMetaFromSchema derives FilterMeta from a ResourceSchema.

type FilterNode

type FilterNode struct {
	Field    *FieldSchema
	Op       FilterOperator
	Value    any
	Children []*FilterNode

	// RawSQL/RawArgs hold a pre-built SQL expression for OpRawSQL nodes.
	// Placeholder params are rewritten by the SQL builder to use the correct
	// dialect-specific parameter indices.
	RawSQL  string
	RawArgs []any

	// Member is the set an OpMember node tests Field against.
	Member *Lookup
}

FilterNode represents a node in the filter AST.

type FilterOperator

type FilterOperator string

FilterOperator represents a comparison or logical operator.

const (
	OpEq       FilterOperator = "eq"
	OpNe       FilterOperator = "ne"
	OpGt       FilterOperator = "gt"
	OpGte      FilterOperator = "gte"
	OpLt       FilterOperator = "lt"
	OpLte      FilterOperator = "lte"
	OpIn       FilterOperator = "in"
	OpNin      FilterOperator = "nin"
	OpLike     FilterOperator = "like"
	OpILike    FilterOperator = "ilike"
	OpNull     FilterOperator = "null"
	OpNotNull  FilterOperator = "not_null"
	OpExists   FilterOperator = "exists"
	OpRawSQL   FilterOperator = "raw_sql" // pre-built SQL expression (subqueries, etc.)
	OpContains FilterOperator = "contains"
	// OpHasKey tests a document for a top-level key, a string element of a
	// top-level array, or a scalar string equal to the key: jsonb `?`.
	OpHasKey FilterOperator = "has_key"
	// OpMember is a membership constraint (PolicyConstraint.Member): the
	// field is among the values a Lookup projects. Only policies build it.
	OpMember FilterOperator = "member"

	// Relation predicates on a field with a Related schema:
	// some related row satisfies the node's child (the target's own
	// filter; no child means any row), or none does. `$is` / `$is_not` on
	// a belongs-to parse to OpSome / OpNone.
	OpSome  FilterOperator = "some"
	OpNone  FilterOperator = "none"
	OpIs    FilterOperator = "is"
	OpIsNot FilterOperator = "is_not"

	// Geo-spatial operators.
	OpNear       FilterOperator = "near"
	OpWithin     FilterOperator = "within"
	OpIntersects FilterOperator = "intersects"

	// Logical operators.
	OpAnd FilterOperator = "and"
	OpOr  FilterOperator = "or"
	OpNot FilterOperator = "not"
)

func OperatorsFor

func OperatorsFor(dt core.FieldType, relation bool) []FilterOperator

OperatorsFor returns the operators a field of data type dt may declare. A relation field compares target IDs of type dt; `$contains` has no relation meaning (membership is `$eq`), so it is excluded.

type FilterParser

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

FilterParser parses JSON filter expressions into a FilterAST.

func NewFilterParser

func NewFilterParser(schema *ResourceSchema, config *core.ParserConfig) *FilterParser

NewFilterParser creates a new FilterParser for the given schema and config.

func (*FilterParser) And

func (p *FilterParser) And(filters ...*FilterAST) (*FilterAST, error)

And combines filters parsed apart — a request's JSON filter and its flat field parameters — into one that matches rows matching all of them, and bounds the combination by the parser's limits as one filter: two filters each within MaxCost can be over it together. Empty filters are dropped.

func (*FilterParser) Parse

func (p *FilterParser) Parse(queryParam string) (*FilterAST, error)

Parse parses a JSON filter string into a FilterAST.

func (*FilterParser) ParseBytes

func (p *FilterParser) ParseBytes(data []byte) (*FilterAST, error)

ParseBytes parses a JSON filter from raw bytes into a FilterAST.

func (*FilterParser) ParseMap

func (p *FilterParser) ParseMap(raw map[string]any) (*FilterAST, error)

ParseMap parses a pre-decoded filter map into a FilterAST. Use this when the input has already been decoded from a non-JSON format (protobuf, msgpack, YAML, form data, etc.).

type GeoBoundingBox

type GeoBoundingBox struct {
	TopLeft     GeoPoint `json:"top_left"`
	BottomRight GeoPoint `json:"bottom_right"`
}

GeoBoundingBox specifies a rectangular area for $within queries.

type GeoDistance

type GeoDistance struct {
	Lat      float64 `json:"lat"`
	Lon      float64 `json:"lon"`
	Distance string  `json:"distance"` // "10km", "5mi", "500m"
}

GeoDistance specifies a center point and radius for $near queries.

type GeoFieldConfig

type GeoFieldConfig struct {
	StorageType GeoStorageType // jsonb, geography, geometry
	// SRID of a native geometry column; shapes are transformed into it.
	// JSONB points and geography are 4326.
	SRID   int
	LatKey string // JSONB member holding latitude (default "lat")
	LonKey string // JSONB member holding longitude (default "lon")
}

GeoFieldConfig holds geo-specific metadata parsed from struct tags. Filter coordinates are always WGS 84 longitude/latitude (SRID 4326).

type GeoPoint

type GeoPoint struct {
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

GeoPoint represents a geographic coordinate.

type GeoPolygon

type GeoPolygon struct {
	Points []GeoPoint `json:"points"` // min 3, auto-closed
}

GeoPolygon specifies a closed polygon area for $within queries.

type GeoStorageType

type GeoStorageType string

GeoStorageType describes how geo data is stored in the database column.

const (
	GeoStorageJSONB     GeoStorageType = "jsonb"
	GeoStorageGeography GeoStorageType = "geography"
	GeoStorageGeometry  GeoStorageType = "geometry"
)

type IncludeLookup

type IncludeLookup interface {
	MembershipLookup
	LookupPairs(ctx context.Context, l *Lookup, ownerColumn string) (iter.Seq2[IncludePair, error], error)
}

IncludeLookup reads relation pairs on a backend that does not run SQL. LookupPairs projects ownerColumn and l.Project from the rows both l.Where and l.Filter select. A store that cannot evaluate the filter must refuse it.

type IncludePair

type IncludePair struct {
	Owner  any
	Target any
}

IncludePair is one related target key and the owner key it belongs to.

type IncludeQuery

type IncludeQuery struct {
	// Field is the include-path name on the wire (e.g. "tags").
	Field string
	// Schema points to the relation descriptor used to render SQL.
	Schema *core.RelationSchema
	// SQL is the parameterized SELECT.
	SQL string
	// Args are the bound values: owner PKs first, then the scope's.
	Args []any
	// Limit is the per-owner cap: an owner with more related rows gets the
	// first Limit and is reported truncated.
	Limit int
}

IncludeQuery is a single batched read of related IDs: one SQL statement that returns (owner_pk, target_id) rows for every owner in ownerPKs, at most Limit+1 per owner so Scan can tell a cut list from a complete one.

func BuildIncludeQuery

func BuildIncludeQuery(field *FieldSchema, dialect Dialect, ownerPKs []any, opts ...IncludeQueryOption) (IncludeQuery, error)

BuildIncludeQuery emits the batched read of one relation field's related IDs across the supplied owner PK set, the lowest target IDs first, at most the per-owner cap plus one (IncludeQuery.Scan cuts and reports it). Text keys use code-point order; numeric and UUID keys use native order. Both IDs come back as text in the database's canonical spelling, the form items carry, whatever the column type and driver (a native UUID scanned into any is a byte array):

SELECT CAST(owner_pk AS TEXT), CAST(target_id AS TEXT) FROM (
  SELECT owner_pk, target_id,
         ROW_NUMBER() OVER (PARTITION BY owner_pk ORDER BY target_id) AS include_rank
  FROM (SELECT DISTINCT j.<source> AS owner_pk, j.<target> AS target_id FROM <junction> j
        [JOIN <target> t ON t.<key> = j.<target>]
        WHERE j.<source> IN ($1, $2, ...) [AND <scope>]) pairs__) include__
WHERE include_rank <= <cap+1> ORDER BY include__.owner_pk, include__.target_id

Pairs are distinct before they are ranked: a junction holding two rows for one pair (two typed edges between the same rows) names the target once and spends one place of the cap. The cap applies in the database: truncating after the scan would still transfer every junction row. A declared relation reads only visible related rows: a has-many's live children in the tenant, a many-to-many's pairs whose target is live and in the tenant — or, for a policy-scoped relation, admitted by its target policy (ErrRelatedPolicyRequired without it).

func (IncludeQuery) Scan

func (q IncludeQuery) Scan(rows Rows) (IncludeRows, error)

Scan reads the (owner_pk, target_id) rows of q, grouped by owner key as text: an owner with more than q.Limit IDs keeps the first Limit and is listed in Truncated, in key order. Caller owns the iterator's Close.

type IncludeQueryOption

type IncludeQueryOption func(*includeQueryConfig)

IncludeQueryOption configures BuildIncludeQuery and ResolveInclude.

func WithIncludeRelatedPolicies

func WithIncludeRelatedPolicies(policies map[string]*PolicyConstraints) IncludeQueryOption

WithIncludeRelatedPolicies supplies the policies of policy-scoped relations' target tables (WithRelatedPolicies).

func WithIncludeTenant

func WithIncludeTenant(value any) IncludeQueryOption

WithIncludeTenant binds the tenant a tenant-scoped relation's junction read is restricted to. Required when the relation declares a TenantColumn; without it (or WithIncludeUnscoped) the build fails .

func WithIncludeUnscoped

func WithIncludeUnscoped() IncludeQueryOption

WithIncludeUnscoped deliberately reads a tenant-scoped junction without a tenant predicate. Never use it on a request path.

func WithMaxIncludePerRelation

func WithMaxIncludePerRelation(n int) IncludeQueryOption

WithMaxIncludePerRelation overrides DefaultMaxIncludePerRelation for this call; n <= 0 keeps the default. The cap must leave room for one extra row that detects truncation.

type IncludeRows

type IncludeRows struct {
	IDs       map[string][]string
	Truncated []string
}

IncludeRows is an include's result: the related IDs grouped by owner key, and the owners whose IDs were cut at the limit.

func ResolveInclude

func ResolveInclude(ctx context.Context, field *FieldSchema, ownerPKs []any, lookup IncludeLookup, opts ...IncludeQueryOption) (IncludeRows, error)

ResolveInclude answers BuildIncludeQuery's projection outside SQL. It uses the relation predicate resolver for tenant, live-row and target-policy visibility; keys are distinct and ordered before the per-owner cap.

type JSONPath

type JSONPath struct {
	// Column is the document column's trusted SQL expression.
	Column string
	// Segments are the member names from the column down.
	Segments []string
	Storage  JSONStorage
}

JSONPath locates a typed scalar member inside a document column. A FieldSchema with a Path filters, sorts and facets on the member; its SQLExpr is empty and its value expression comes from Dialect.PathExpr.

type JSONStorage

type JSONStorage string

JSONStorage is how a document column holds its JSON. Documents are jsonb on PostgreSQL unless declared otherwise; SQLite always holds JSON text.

const (
	StorageJSONB JSONStorage = "jsonb"
	StorageJSON  JSONStorage = "json"
	StorageText  JSONStorage = "text"
)

type ListOption

type ListOption func(*listConfig)

ListOption configures a single Build call.

func ListCursor

func ListCursor(cursor string) ListOption

ListCursor sets the opaque cursor string.

func ListFacets

func ListFacets(fields ...string) ListOption

ListFacets requests facet aggregation queries.

func ListFilter

func ListFilter(filterJSON string) ListOption

ListFilter sets the JSON filter string.

func ListFilterAST

func ListFilterAST(ast *FilterAST) ListOption

ListFilterAST sets a pre-parsed FilterAST.

func ListLimit

func ListLimit(limit int) ListOption

ListLimit sets the page size.

func ListMaxLimit

func ListMaxLimit(limit int) ListOption

ListMaxLimit overrides the maximum allowed limit.

func ListPolicy

func ListPolicy(pc *PolicyConstraints) ListOption

ListPolicy sets policy constraints.

func ListRelatedPolicies

func ListRelatedPolicies(policies map[string]*PolicyConstraints) ListOption

ListRelatedPolicies supplies the server's policy for the target tables of policy-scoped relations, keyed by table (WithRelatedPolicies).

func ListSearch

func ListSearch(query string) ListOption

ListSearch sets the per-request full-text search query. The schema comes from the ListQuery (ListQuery.WithSearchSchema); an empty query or an unset schema is a no-op.

func ListSort

func ListSort(sortParam string) ListOption

ListSort sets the sort parameter string.

func ListSortResult

func ListSortResult(sr *SortResult) ListOption

ListSortResult sets a pre-parsed SortResult.

func ListStartParam

func ListStartParam(n int) ListOption

ListStartParam sets the starting parameter index offset. Use this when the SQL query has preceding parameters (e.g., SET clause args in an UPDATE). The filter's first placeholder will be startParam+1.

func ListTenant

func ListTenant(value any) ListOption

ListTenant binds the tenant every tenant-scoped relation junction in the filter and facets is restricted to. Required whenever the resource declares relations with a TenantColumn.

func ListUnscopedRelations

func ListUnscopedRelations() ListOption

ListUnscopedRelations deliberately builds tenant-scoped relation junctions without a tenant predicate. Never use it on a request path.

func ListWithCount

func ListWithCount(enable bool) ListOption

ListWithCount enables COUNT(*) query generation.

type ListQuery

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

ListQuery builds a complete SQL fragment for list endpoints.

func NewListQuery

func NewListQuery(filterSchema *ResourceSchema, sortSchema *SortSchema, dialect Dialect) *ListQuery

NewListQuery creates a ListQuery builder.

func (*ListQuery) Build

func (lq *ListQuery) Build(opts ...ListOption) (*ListResult, error)

Build parses all inputs and composes them into a ListResult.

func (*ListQuery) WithFilterConfig

func (lq *ListQuery) WithFilterConfig(config *core.ParserConfig) *ListQuery

WithFilterConfig sets custom filter parsing config.

func (*ListQuery) WithSearchSchema

func (lq *ListQuery) WithSearchSchema(schema *SearchSchema) *ListQuery

WithSearchSchema sets the full-text search schema. When set, a ListSearch query composes a dialect-aware search predicate (postgres full-text via to_tsvector/plainto_tsquery; LIKE fallback elsewhere) ANDed with the filter. Search is WHERE-only — it never reorders results, so keyset (cursor) pagination stays stable on the configured sort.

type ListResult

type ListResult struct {
	Fragment   string // WHERE … ORDER BY … LIMIT n, appended to SELECT … FROM <table>
	Where      string
	OrderBy    string
	Args       []any
	FetchLimit int
	Limit      int
	Sort       *SortResult
	// Cursor is the keyset plan for building the page from the fetched
	// rows: pass it with Limit to NewPage.
	Cursor    CursorPlan
	CountSQL  string
	CountArgs []any
	// Facets is the single CTE + UNION ALL facet query (see BuildFacetQuery)
	// when ListFacets was requested; scan its rows with ScanFacetUnion.
	Facets *FacetQuery
}

ListResult contains the composed SQL fragments.

type Lookup

type Lookup struct {
	Table   string
	Project string
	// Where selects the lookup rows; nil selects every row.
	Where *LookupCond
	// Filter further selects the lookup rows by a filter over the lookup
	// table's own schema: a relation's target filter, its tenancy, soft
	// deletion and policy composed in. SQL renders it in the
	// subquery's WHERE; ResolveMemberships sends a lookup with a Filter
	// only to a RelationLookup, so a store that cannot evaluate it refuses
	// the request instead of ignoring it.
	Filter *ParsedFilter
}

Lookup is the set a membership constraint tests a row's field against: the Project column of the rows of Table that Where selects. SQL backends compile it to `field IN (SELECT project FROM table WHERE …)`; a backend that holds rows in memory resolves it once per request with ResolveMemberships. Identifiers are quoted, so they match names as created: Table is a table or view, optionally schema-qualified; Project and every condition column are plain column names.

type LookupCond

type LookupCond struct {
	Column string
	Op     LookupOp
	Value  any     // LookupOpEq
	Values []any   // LookupOpIn
	Lookup *Lookup // LookupOpMember

	And []*LookupCond
	Or  []*LookupCond
}

LookupCond is a condition over one lookup row: a leaf comparing Column, or a group of conditions all of which (And) or one of which (Or) hold. It has no negation, so a comparison with a NULL column selects nothing, exactly as in SQL. Build one with WhereEq, WhereIn, WhereNull, WhereAll and WhereAny.

func WhereAll

func WhereAll(conds ...*LookupCond) *LookupCond

WhereAll selects rows every condition selects.

func WhereAny

func WhereAny(conds ...*LookupCond) *LookupCond

WhereAny selects rows at least one condition selects.

func WhereEq

func WhereEq(column string, v any) *LookupCond

WhereEq selects rows whose column equals v.

func WhereIn

func WhereIn(column string, vs ...any) *LookupCond

WhereIn selects rows whose column is one of vs; no values select no row.

func WhereMember

func WhereMember(column string, l *Lookup) *LookupCond

WhereMember selects rows whose column is among the values l projects.

func WhereNotNull

func WhereNotNull(column string) *LookupCond

WhereNotNull selects rows whose column is not NULL.

func WhereNull

func WhereNull(column string) *LookupCond

WhereNull selects rows whose column is NULL.

type LookupOp

type LookupOp string

LookupOp is the comparison a LookupCond leaf makes.

const (
	// LookupOpEq selects rows whose column equals Value.
	LookupOpEq LookupOp = "eq"
	// LookupOpIn selects rows whose column is one of Values; an empty list
	// selects no row.
	LookupOpIn LookupOp = "in"
	// LookupOpNull selects rows whose column is NULL.
	LookupOpNull LookupOp = "null"
	// LookupOpNotNull selects rows whose column is not NULL.
	LookupOpNotNull LookupOp = "not_null"
	// LookupOpMember selects rows whose column is among the values Lookup
	// projects. ResolveMemberships answers the inner lookup first, so a
	// store sees LookupOpIn in its place.
	LookupOpMember LookupOp = "member"
)

type MembershipLookup

type MembershipLookup interface {
	LookupValues(ctx context.Context, l *Lookup) ([]any, error)
}

MembershipLookup resolves the lookups of membership constraints for a backend that does not run them as SQL. LookupValues returns the Project values of the rows of l.Table that l.Where selects (MatchLookupRow), as ctx — its tenant and transaction — sees them and with no other scoping, so the answer is the SQL subquery's. NULL values are returned as nil. An error refuses the request.

type Page

type Page[T any] struct {
	Items    []T      `json:"items"`
	PageInfo PageInfo `json:"page_info"`

	// Relations holds projected M2M relation IDs grouped by owner PK,
	// keyed by include-field name, when the request asked for `include`
	// projections.
	Relations map[string]map[string][]string `json:"_relations,omitempty"`
	// Truncated names, per include field, the owners whose related IDs
	// were cut at the per-owner cap (IncludeRows.Truncated).
	Truncated map[string][]string `json:"_truncated,omitempty"`
	// Facets counts the matched population by each requested facet field
	// (FacetMap), when the request asked for `facets`.
	Facets map[string][]FacetBucket `json:"facets,omitempty"`
}

Page is the list-response envelope, with cursor metadata and optional relation and facet projections. Its JSON members are flat.

func NewPage

func NewPage[T any](items []T, limit int, plan CursorPlan, value func(item T, column string) (any, error)) (*Page[T], error)

NewPage builds a Page from rows fetched with a Build result's query: up to limit+1 rows in query order. It truncates to limit, restores presentation order after a backward seek, derives has_next_page / has_prev_page, and mints start and end cursors. value resolves a key's value from a row by row column (e.g. CursorValueByTag with the "db" tag). A cursor that cannot be minted is an error — a page that reports more rows but carries no cursor strands the client.

func (*Page[T]) SetInclude

func (p *Page[T]) SetInclude(field string, rows IncludeRows)

SetInclude replaces a field's related IDs and truncated owners. Empty projections are omitted. The page retains the supplied maps and slices.

type PageInfo

type PageInfo struct {
	HasNextPage bool   `json:"has_next_page"`
	HasPrevPage bool   `json:"has_prev_page"`
	StartCursor Cursor `json:"start_cursor,omitempty"`
	EndCursor   Cursor `json:"end_cursor,omitempty"`
	// TotalCount is populated only when requested.
	TotalCount *int64 `json:"total_count,omitempty"`
}

PageInfo describes a page's position in the matched result set.

type PageToken

type PageToken struct {
	Fields  []CursorField `msgpack:"f" json:"fields"`
	Reverse bool          `msgpack:"r,omitempty" json:"reverse,omitempty"`
}

PageToken encodes a composite keyset cursor position. The field list mirrors the effective keyset; Reverse marks a backward seek (a page's start cursor).

func DecodeCursor

func DecodeCursor(encoded string) (*PageToken, error)

DecodeCursor decodes an opaque cursor to a PageToken whose values are canonical (see CursorValue); a token carrying any other value type is rejected as malformed.

type ParsedFilter

type ParsedFilter struct {
	AST            *FilterAST
	EstimatedCost  int
	ConditionCount int
}

ParsedFilter wraps a FilterAST with metadata.

func NewParsedFilter

func NewParsedFilter(ast *FilterAST) *ParsedFilter

NewParsedFilter wraps a FilterAST with computed cost and condition count.

func ResolveMemberships

func ResolveMemberships(ctx context.Context, f *ParsedFilter, lookup MembershipLookup, opts ...ResolveOption) (*ParsedFilter, error)

ResolveMemberships returns f with every membership constraint replaced by the predicate its resolved set amounts to, which a backend evaluating rows in memory already implements:

  • the field `$in` the non-NULL values, normalized as policy values are — no rows make it false, NULL or not, as SQL's `x IN (empty subquery)`;
  • OR'd with SQL's unknown when a resolved value is NULL, as SQL's `x IN (…, NULL)` is unknown unless x is a member.

Each lookup is resolved once, before any row is read. With a nil lookup every membership is SQL's unknown: it admits no row the constraint would refuse, under AND, OR and NOT. f itself is not modified.

Relation predicates resolve the same way (RelationLookup): `$some` is the owner's correlation field `$in` the related rows' correlation values, `$none` its negation or a NULL correlation, as NOT EXISTS admits one. They need a lookup and, for tenant-scoped relations, ResolveTenant; without one they are refused rather than answered empty.

func (*ParsedFilter) AuditInfo

func (f *ParsedFilter) AuditInfo() map[string]any

AuditInfo returns a map of audit-relevant information for the parsed filter.

type PathDecl

type PathDecl struct {
	// Path is the document field's name, then its members: "labels.region".
	Path string
	Type core.FieldType
	// Ops are the operators allowed on the path; nil means the type's
	// defaults with the NULL tests (a path can always be absent).
	Ops     []FilterOperator
	Indexed bool
	// Sort also makes the path a sort key (SortSchema.WithPaths).
	Sort bool
	// Facet makes the path facetable with this configuration (terms or
	// range), as a column's `facet` tag does.
	Facet *FacetSchema
	Label string
}

PathDecl declares a typed member of a document field, for paths a struct tag cannot carry — such as a tenant's custom fields, declared per request.

type PolicyConstraint

type PolicyConstraint struct {
	Field    string
	Operator FilterOperator
	Value    any
	Negate   bool
	// UnknownIfInvalid compiles a value that does not apply to the field —
	// its type, or the operator's — to SQL's unknown instead of an error:
	// no row satisfies it, nor its negation, so it narrows whatever group
	// holds it. Authorization sets it: a policy value no row can hold
	// neither fails a list nor widens one.
	UnknownIfInvalid bool

	// RawSQL is a pre-built SQL expression injected directly into the WHERE clause.
	// When set, Field/Operator/Value are ignored. Use parameterized placeholders ($1, $2)
	// with RawArgs. This enables subquery-based constraints like grant-aware scoping.
	RawSQL  string
	RawArgs []any

	// Member makes the constraint a membership: Field's value is one of the
	// values the lookup projects. Operator, Value and RawSQL
	// are unused. Every backend evaluates it — SQL as a subquery, memory
	// through ResolveMemberships — unlike RawSQL, which memory cannot.
	Member *Lookup
}

PolicyConstraint represents a single policy-derived filter condition.

func UnknownConstraint

func UnknownConstraint() *PolicyConstraint

UnknownConstraint returns the constraint whose value is SQL's unknown: no row satisfies it, nor its negation. A group holding it admits no row; a negated group holding it still excludes the rows its other constraints match.

type PolicyConstraints

type PolicyConstraints struct {
	Constraints []*PolicyConstraint
	Or          []*PolicyConstraints
	And         []*PolicyConstraints
	// Not negates the group: a row is admitted when it does not satisfy it.
	Not      bool
	Source   string
	PolicyID string
}

PolicyConstraints is a composable collection of constraints.

func BuildBaseConstraints

func BuildBaseConstraints(tenantCol, tenantID, deleteCol string, includeSoftDeleted bool) *PolicyConstraints

BuildBaseConstraints builds PolicyConstraints for tenant isolation and soft-delete.

Fail-closed tenant scoping: when tenantCol is set, the tenant equality predicate is ALWAYS emitted. An empty tenantID yields an impossible predicate (`tenant_col = ”` — zero rows, or a type error on UUID columns) instead of silently widening the query to every tenant. Repos additionally reject the empty-tenant case up front with a typed error; the predicate here is defense in depth for any caller that bypasses that gate.

func ComposeConstraints

func ComposeConstraints(scope RepositoryScope, governance, authz *PolicyConstraints) *PolicyConstraints

ComposeConstraints is the policy tree a repository read applies, the same on every backend: governance when present — it scopes tenancy itself, grants included — otherwise the tenant floor; soft-delete masking; then authorization. The parts are ANDed under a new root, so neither input is modified and a cached governance tree is never extended.

func (*PolicyConstraints) IsEmpty

func (pc *PolicyConstraints) IsEmpty() bool

IsEmpty returns true when there are no constraints.

func (*PolicyConstraints) Merge

Merge combines two PolicyConstraints with AND semantics.

type PostgresCollation

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

PostgresCollation is a PostgreSQL collation that orders strings by code point — the order SQLite's BINARY, the memory backend and cursor values use — so the values are closed: CollationPgCUTF8 and CollationC.

func (PostgresCollation) String

func (c PostgresCollation) String() string

String is the collation's SQL name.

type PostgresDialect

type PostgresDialect struct {
	dialect.Postgres
	Collation PostgresCollation
}

PostgresDialect generates PostgreSQL-compatible SQL. JSON documents are jsonb columns and arrays native array columns (core.TypeOf); geo operators use PostGIS. Collation is the code-point collation string sorts, keyset seeks and string ranges compile under; the zero value is pg_c_utf8 (PostgreSQL 17+).

func (PostgresDialect) CTEMaterializeHint

func (PostgresDialect) CTEMaterializeHint() string

CTEMaterializeHint returns " MATERIALIZED" so PG ≥ 12 doesn't inline a multi-referenced CTE. Critical for BuildFacetQuery so the policy-merged filter scan happens exactly once for the whole facet response.

func (PostgresDialect) CodePointOrder

func (d PostgresDialect) CodePointOrder(expr string) string

CodePointOrder applies the dialect's Collation. A column declared with the same collation is indexed in the order every keyset sort and string range filter compiles to.

func (PostgresDialect) Condition

func (d PostgresDialect) Condition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, error)

Condition renders one predicate; see Dialect.Condition.

func (PostgresDialect) DecimalParam added in v0.4.0

func (PostgresDialect) DecimalParam(placeholder string) string

DecimalParam casts the parameter to numeric: pgx sends a string in text format, which numeric parses exactly.

func (PostgresDialect) MarshalValue

func (d PostgresDialect) MarshalValue(value any, t core.TypeInfo) (any, error)

MarshalValue serializes a value for PostgreSQL parameter binding.

pgx handles per-column-type encoding from Go values: Go slices like []string / []int become native PG arrays (text[], int[], uuid[]), while maps and unknown structs become JSONB via pgx's default codec. Pre-marshaling to JSON text breaks native array columns, which reject the JSON literal `"[]"` (PG expects `'{}'`).

Map/object payloads still need an up-front JSON encode because pgx can't decide between a hstore and a JSONB column for an arbitrary map[string]any without an OID hint, and the project standardizes on JSONB for those.

func (PostgresDialect) NativeRowCompare

func (PostgresDialect) NativeRowCompare() bool

NativeRowCompare reports true: PostgreSQL evaluates row comparison lexicographically with full composite-index support.

func (PostgresDialect) PathExpr

func (PostgresDialect) PathExpr(p *JSONPath, t core.FieldType) string

PathExpr renders p's typed value for PostgreSQL. The document is read as jsonb; jsonb_typeof selects the member's JSON kind before any cast, so a wrong-typed member is NULL rather than a cast error.

func (PostgresDialect) ReadColumn

func (PostgresDialect) ReadColumn(column string, t core.SQLType) string

ReadColumn reads network and interval values as text, since pgx scans their binary encoding into no string: an inet in its display form (abbrev drops a host's /32 or /128), cidr and interval in their output form, and an array element by element, in order, with NULL kept.

func (PostgresDialect) RowLock

func (PostgresDialect) RowLock() string

RowLock locks selected rows until the transaction ends.

type QueryParamOption

type QueryParamOption func(*queryParamConfig)

QueryParamOption configures a QueryParamParser.

func QueryParamAccept

func QueryParamAccept(names ...string) QueryParamOption

QueryParamAccept names parameters the endpoint reads itself. The parser leaves them alone, as it does the list parameters (Param*); any other parameter that names no filterable field is E2071.

func QueryParamConfig

func QueryParamConfig(cfg *core.ParserConfig) QueryParamOption

QueryParamConfig applies parser limits (MaxArraySize, MaxConditions, MaxCost, MaxDepth), the relative-time clock and the codec to query-param filters. Default: core.DefaultConfig().

func QueryParamFieldsOnly

func QueryParamFieldsOnly() QueryParamOption

QueryParamFieldsOnly reads every parameter as a field filter, for values that hold nothing else: the list parameter names (Param*) are not skipped, so a field named like one is filtered on, and any other name that is no filterable field is E2071.

func QueryParamLenient

func QueryParamLenient() QueryParamOption

QueryParamLenient ignores parameters that name no filterable field instead of refusing them. A misspelt filter then widens the result instead of failing, so use it only on an endpoint whose parameters are not all known in advance; QueryParamAccept is the narrower choice.

type QueryParamParser

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

QueryParamParser translates flat URL query params into a FilterAST. Params compile to the same operator map the JSON grammar uses and run through FilterParser.ParseMap, so operator allowlists, enums, custom validators, array limits and cost limits apply identically on both ingresses .

func NewQueryParamParser

func NewQueryParamParser(schema *ResourceSchema, opts ...QueryParamOption) *QueryParamParser

NewQueryParamParser creates a QueryParamParser for the given schema. A parameter that is neither a list parameter (Param*), a name QueryParamAccept lists, nor a filterable field is E2071, so a misspelt filter cannot widen a list; QueryParamLenient ignores it instead.

func (*QueryParamParser) Parse

func (p *QueryParamParser) Parse(params url.Values) (*FilterAST, error)

Parse converts url.Values to a FilterAST.

func (*QueryParamParser) ParseHTTP

func (p *QueryParamParser) ParseHTTP(r *http.Request) (*FilterAST, error)

ParseHTTP extracts query params from an *http.Request and parses them.

type RelationLookup

type RelationLookup interface {
	MembershipLookup
	LookupFilteredValues(ctx context.Context, l *Lookup) ([]any, error)
}

RelationLookup is a MembershipLookup that also answers lookups with a Filter — relation predicates on a backend that does not run SQL. LookupFilteredValues returns the Project values of the rows of l.Table that both l.Where (MatchLookupRow) and l.Filter select, the filter evaluated by the store's own filter evaluator over the table's rows.

type RepositoryScope

type RepositoryScope struct {
	TenantColumn   string
	TenantID       string
	DeleteColumn   string
	IncludeDeleted bool
}

RepositoryScope is the floor of a repository read: the request tenant when the table is tenant-scoped, and soft-delete masking.

type ResolveOption

type ResolveOption func(*resolveConfig)

ResolveOption configures ResolveMemberships' relation scoping, as the SQL builder's options scope relation subqueries.

func ResolveRelatedPolicies

func ResolveRelatedPolicies(policies map[string]*PolicyConstraints) ResolveOption

ResolveRelatedPolicies supplies the policies of policy-scoped relations' target tables (WithRelatedPolicies).

func ResolveTenant

func ResolveTenant(value any) ResolveOption

ResolveTenant binds the tenant tenant-scoped relations' related rows are held to (WithTenant).

func ResolveUnscopedRelations

func ResolveUnscopedRelations() ResolveOption

ResolveUnscopedRelations deliberately resolves tenant-scoped relations without a tenant (WithUnscopedRelations). Never use it on a request path.

type ResourceSchema

type ResourceSchema struct {
	Name string
	// PrimaryKey is the unique column appended to every list's keyset.
	// Resource construction declares it; it is never request input.
	PrimaryKey   string
	Fields       map[string]*FieldSchema
	SystemFields map[string]*FieldSchema
}

ResourceSchema defines filterable fields for a resource.

func GenerateFilterSchema

func GenerateFilterSchema[T any](g *core.SchemaGenerator) (*ResourceSchema, error)

GenerateFilterSchema generates a ResourceSchema from struct tags on type T.

func (*ResourceSchema) WithPaths

func (s *ResourceSchema) WithPaths(decls ...PathDecl) (*ResourceSchema, error)

WithPaths returns a copy of s with decls added as fields; s is unchanged, so a request can declare paths without touching the shared schema. A path's document field must be a filterable json or object field of s.

type Rows

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Err() error
}

Rows is the row reader the Scan helpers (ScanFacetUnion, IncludeQuery.Scan) consume. *database/sql.Rows and pgx.Rows both satisfy it; the caller owns Close.

type SQLBuilder

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

SQLBuilder generates parameterized SQL from a filter AST using a dialect.

func NewSQLBuilder

func NewSQLBuilder(schema *ResourceSchema, dialect Dialect, opts ...SQLBuilderOption) *SQLBuilder

NewSQLBuilder creates a new SQLBuilder for the given schema and dialect.

func (*SQLBuilder) Build

func (b *SQLBuilder) Build(ast *FilterAST) (*SQLResult, error)

Build walks the AST and generates a parameterized SQL WHERE clause.

type SQLBuilderOption

type SQLBuilderOption func(*sqlBuilderConfig)

SQLBuilderOption configures a SQLBuilder.

func WithRelatedPolicies

func WithRelatedPolicies(policies map[string]*PolicyConstraints) SQLBuilderOption

WithRelatedPolicies supplies the server's policy for the target tables of policy-scoped relations (core.RelationSchema.Policy), keyed by table. A relation predicate on one without its table's policy fails with ErrRelatedPolicyRequired.

func WithStartParam

func WithStartParam(n int) SQLBuilderOption

WithStartParam sets the starting parameter index offset. The first generated placeholder will be startParam+1. For example, WithStartParam(3) produces $4, $5, ... (Postgres) or ?4, ?5, ... (SQLite). Default is 0 (first param $1).

func WithTenant

func WithTenant(value any) SQLBuilderOption

WithTenant binds the tenant every tenant-scoped relation junction is restricted to. Required whenever a filter touches a relation whose RelationSchema.TenantColumn is set; without it (or WithUnscopedRelations) the build fails rather than matching owners through other tenants' junction rows.

func WithUnscopedRelations

func WithUnscopedRelations() SQLBuilderOption

WithUnscopedRelations deliberately builds tenant-scoped relation junctions without a tenant predicate — for platform-level tooling that operates across tenants. Never use it on a request path.

type SQLResult

type SQLResult struct {
	Where      string
	Args       []any
	ParamCount int // Number of parameters consumed (highest param index minus start offset).
}

SQLResult contains the generated SQL WHERE clause and bound arguments.

type SQLiteDialect

type SQLiteDialect struct{ dialect.SQLite }

SQLiteDialect generates SQLite-compatible SQL. JSON documents and arrays are stored as JSON text; document operators compile the needle into json_each predicates with PostgreSQL jsonb semantics.

func (SQLiteDialect) CTEMaterializeHint

func (SQLiteDialect) CTEMaterializeHint() string

CTEMaterializeHint returns "" — SQLite always materializes CTEs.

func (SQLiteDialect) CodePointOrder

func (SQLiteDialect) CodePointOrder(expr string) string

CodePointOrder applies BINARY collation, overriding a column declared NOCASE or RTRIM.

func (SQLiteDialect) Condition

func (d SQLiteDialect) Condition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, error)

Condition renders one predicate; see Dialect.Condition.

func (SQLiteDialect) DecimalParam added in v0.4.0

func (SQLiteDialect) DecimalParam(placeholder string) string

DecimalParam casts the parameter to NUMERIC: an integer when the text holds one exactly, otherwise a REAL — how a NUMERIC or REAL column stores the value, so the two compare as numbers, never as text. A bare text parameter compares as text against a path's number or a column of no numeric affinity.

func (SQLiteDialect) MarshalValue

func (d SQLiteDialect) MarshalValue(value any, t core.TypeInfo) (any, error)

MarshalValue serializes a value for SQLite parameter binding. JSON/Array/Object types are marshaled to JSON strings for TEXT storage.

func (SQLiteDialect) NativeRowCompare

func (SQLiteDialect) NativeRowCompare() bool

NativeRowCompare reports false: row-value comparison is unevenly supported across the SQLite build matrix, and the expansion stays planner-friendly on single-column indexes.

func (SQLiteDialect) PathExpr

func (SQLiteDialect) PathExpr(p *JSONPath, t core.FieldType) string

PathExpr renders p's typed value for SQLite, whose documents are JSON text: json_type selects the member's kind; json_extract reads it.

func (SQLiteDialect) ReadColumn

func (SQLiteDialect) ReadColumn(column string, t core.SQLType) string

ReadColumn returns column: SQLite stores network and interval values as TEXT, which a string field scans.

func (SQLiteDialect) RowLock

func (SQLiteDialect) RowLock() string

RowLock is empty: SQLite serializes writers, so a write transaction already holds what a row lock would.

type SearchField

type SearchField struct {
	Name    string
	SQLExpr string
	Weight  SearchWeight
}

SearchField describes a single field included in full-text search.

type SearchSchema

type SearchSchema struct {
	Fields   []SearchField
	Language string
	// Param is the query-string parameter the free-text search box binds
	// to: GeneratorOptions.SearchParam, defaulting to ParamSearch — the
	// parameter list handlers read.
	Param string
}

SearchSchema defines full-text search configuration.

func GenerateSearchSchema

func GenerateSearchSchema[T any](g *core.SchemaGenerator) (*SearchSchema, error)

GenerateSearchSchema generates a SearchSchema from struct tags on type T.

type SearchWeight

type SearchWeight string

SearchWeight controls the relative importance of a field in full-text search.

const (
	SearchWeightA SearchWeight = "A"
	SearchWeightB SearchWeight = "B"
	SearchWeightC SearchWeight = "C"
	SearchWeightD SearchWeight = "D"
)

type SortDirection

type SortDirection string

SortDirection represents ascending or descending sort order.

const (
	SortAsc  SortDirection = "asc"
	SortDesc SortDirection = "desc"
)

func ReverseDirection

func ReverseDirection(d SortDirection) SortDirection

ReverseDirection returns the opposite sort direction.

type SortField

type SortField struct {
	Name      string
	Schema    *SortFieldSchema
	Direction SortDirection
}

SortField represents a single parsed sort field with direction.

type SortFieldSchema

type SortFieldSchema struct {
	// SQLExpr is the expression emitted into ORDER BY and keyset
	// predicates, usually table-qualified (`items.rank`).
	SQLExpr string
	// Column is the row column the key's value is read from when a cursor
	// is minted — the bare column name, never the qualified expression
	//. Empty falls back to SQLExpr's last identifier
	// segment (see RowColumn).
	Column string
	// DataType lets keyset values bind and compare by type (timestamps
	// chronologically, numbers exactly).
	DataType core.FieldType
	// NumberKind is the Go numeric kind used to range-check cursor values.
	NumberKind reflect.Kind
	// Nullable marks keys that can hold NULL; they order NULL-greatest with
	// explicit NULLS FIRST/LAST and a NULL-aware keyset predicate.
	Nullable bool
	Indexed  bool
	// Path makes the key a typed member of a document column: ORDER BY and
	// seeks render Dialect.PathExpr, Column is the document's row column and
	// a cursor reads the member with the path value rule. Path keys are
	// nullable: an absent member sorts as NULL.
	Path *JSONPath
}

SortFieldSchema defines a single sortable field.

func (*SortFieldSchema) RowColumn

func (s *SortFieldSchema) RowColumn() string

RowColumn returns the row column for the key: Column, or the last segment of a plain (optionally table-qualified) identifier SQLExpr, or "" when the expression is computed and has no row column.

type SortResult

type SortResult struct {
	Fields []SortField
}

SortResult holds parsed sort fields.

func ParseSort

func ParseSort(schema *SortSchema, sortParam string) (*SortResult, error)

ParseSort parses a sort parameter string.

func (*SortResult) ToSQL

func (s *SortResult) ToSQL(d Dialect) string

ToSQL renders the sort's ORDER BY in dialect d, as a keyset orders it: code-point text, NULL greatest on nullable keys, paths through Dialect.PathExpr. A list query appends its tie-breaker itself.

type SortSchema

type SortSchema struct {
	Fields       map[string]*SortFieldSchema
	DefaultField string
	DefaultDir   SortDirection
	MaxFields    int
}

SortSchema defines allowed sort fields for a resource.

func GenerateSortSchema

func GenerateSortSchema[T any](g *core.SchemaGenerator) (*SortSchema, error)

GenerateSortSchema generates a SortSchema from struct tags on type T.

func (*SortSchema) WithPaths

func (s *SortSchema) WithPaths(fields *ResourceSchema, decls ...PathDecl) (*SortSchema, error)

WithPaths returns a copy of s with the decls marked Sort added as sort keys, their documents taken from fields (the resource's filter schema); s is unchanged.

Directories

Path Synopsis
Package filtertest is the filter conformance fixture: one entity with a field of every filterable type, its rows, and the rows every backend must return for each filter.
Package filtertest is the filter conformance fixture: one entity with a field of every filterable type, its rows, and the rows every backend must return for each filter.

Jump to

Keyboard shortcuts

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