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. A `$in` / `$nin` set binds as one array parameter whatever its size: PostgreSQL compares a column with `= ANY`, typed by the column, and joins a path's set unnested; SQLite reads the set with json_each. SQLite has no exact decimal type, so it compares and orders decimals through two deterministic functions the database must have, which parse/parsesqlite registers.
A TwoValuedDialect renders some predicates a second way that an index serves, used where an even number of $not (usually none) enclose them: PostgreSQL compares a jsonb path's `$eq` and small `$in` sets by containment, which a GIN index on the document serves. SQLResult.Where is therefore a selection predicate.
Evaluating Outside SQL ¶
Evaluate is the reference evaluator: the three-valued Truth SQL gives a filter on one Row, for backends that hold rows in memory. ColumnRow reads a row of Go values by column and ColumnValue one value; resolve memberships and relation predicates first with ResolveMemberships.
ListResult Composition ¶
ListResult returns all SQL parts separately (Where, OrderBy, Args, CountSQL, Facets) so callers can compose with custom SQL fragments.
Index ¶
- Constants
- Variables
- func AllOperatorStrings() []string
- func AndSearch(schema *SearchSchema, d Dialect, where string, args []any, offset int, ...) (string, []any, error)
- func Codes() []core.Code
- func ColumnValue(f *FieldSchema, v any) (any, bool, error)
- func CompareKeyValues(k CursorKey, a, b any) int
- func CursorValue(v any) (any, error)
- func CursorValueByTag(entity any, tagKey, tagValue string) (any, error)
- func DefaultFilterOps() map[core.FieldType][]FilterOperator
- func DocumentElements(doc any) ([]any, error)
- func EncodeCursor(token *PageToken) (string, error)
- func EscapeLike(s string) string
- func FacetKeys(v any, f *FieldSchema) ([]string, error)
- func FacetMap(results []FacetResult) map[string][]FacetBucket
- func FieldExpr(d Dialect, f *FieldSchema) string
- func IsListParam(name string) bool
- func JSONContains(doc, needle any) bool
- func JSONEqual(a, b any) bool
- func JSONHasKey(doc any, key string) bool
- func KeyValue(k CursorKey, raw any) (any, error)
- func MatchLike(s, pattern string, fold bool) bool
- func MatchLookupRow(cond *LookupCond, column func(name string) (value any, ok bool)) (bool, error)
- func OrderByKeys(d Dialect, keys []CursorKey, reverse bool) string
- func ParseDistance(s string) (float64, error)
- func ParseIncludeNames(schema *ResourceSchema, names []string) ([]string, error)
- func ParseIncludes(schema *ResourceSchema, param string) ([]string, error)
- func PathValue(doc any, f *FieldSchema) (any, bool, error)
- func ResolveRelativeTime(s string, now time.Time) (t time.Time, isToken bool, err error)
- func SeekPredicate(d Dialect, keys []CursorKey, token *PageToken, bind func(any) string) (string, error)
- func ToAnySlice(ss []string) []any
- func ValidateCursor(token *PageToken, keys []CursorKey) error
- func ValidateGeoBoundingBox(bb *GeoBoundingBox) error
- func ValidateGeoPoint(lat, lon float64) error
- func ValidateGeoPolygon(p *GeoPolygon) error
- func VisibleRelated(field *FieldSchema, dialect Dialect, alias string, tenant any, ...) (string, error)
- type BuildFacetQueryOption
- type ColumnRow
- type Cursor
- type CursorField
- type CursorKey
- type CursorPlan
- type Dialect
- type Document
- type FacetBucket
- type FacetQuery
- type FacetRange
- type FacetRequest
- type FacetResult
- type FacetSchema
- type FacetType
- type FieldSchema
- type FilterAST
- type FilterFieldMeta
- type FilterMeta
- type FilterNode
- type FilterOperator
- type FilterParser
- type GeoBoundingBox
- type GeoDistance
- type GeoFieldConfig
- type GeoPoint
- type GeoPolygon
- type GeoStorageType
- type IncludeLookup
- type IncludePair
- type IncludeQuery
- type IncludeQueryOption
- type IncludeRows
- type JSONPath
- type JSONStorage
- type ListOption
- func ListCursor(cursor string) ListOption
- func ListFacets(fields ...string) ListOption
- func ListFilter(filterJSON string) ListOption
- func ListFilterAST(ast *FilterAST) ListOption
- func ListLimit(limit int) ListOption
- func ListMaxLimit(limit int) ListOption
- func ListPolicy(pc *PolicyConstraints) ListOption
- func ListRelatedPolicies(policies map[string]*PolicyConstraints) ListOption
- func ListSearch(query string) ListOption
- func ListSort(sortParam string) ListOption
- func ListSortResult(sr *SortResult) ListOption
- func ListStartParam(n int) ListOption
- func ListTenant(value any) ListOption
- func ListUnscopedRelations() ListOption
- func ListWithCount(enable bool) ListOption
- type ListQuery
- type ListResult
- type Lookup
- type LookupCond
- func WhereAll(conds ...*LookupCond) *LookupCond
- func WhereAny(conds ...*LookupCond) *LookupCond
- func WhereEq(column string, v any) *LookupCond
- func WhereIn(column string, vs ...any) *LookupCond
- func WhereMember(column string, l *Lookup) *LookupCond
- func WhereNotNull(column string) *LookupCond
- func WhereNull(column string) *LookupCond
- type LookupOp
- type MembershipLookup
- type Page
- type PageInfo
- type PageToken
- type ParsedFilter
- type PathDecl
- type PolicyConstraint
- type PolicyConstraints
- type PostgresCollation
- type PostgresDialect
- func (PostgresDialect) CTEMaterializeHint() string
- func (d PostgresDialect) CodePointOrder(expr string) string
- func (d PostgresDialect) Condition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, error)
- func (PostgresDialect) DecimalColumn(column string) string
- func (PostgresDialect) DecimalOrder(value string) string
- func (PostgresDialect) DecimalParam(placeholder string) string
- func (d PostgresDialect) MarshalValue(value any, t core.TypeInfo) (any, error)
- func (PostgresDialect) NativeRowCompare() bool
- func (PostgresDialect) PathExpr(p *JSONPath, t core.FieldType) string
- func (PostgresDialect) ReadColumn(column string, t core.SQLType) string
- func (PostgresDialect) RowLock() string
- func (d PostgresDialect) TwoValuedCondition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, bool, error)
- type QueryParamOption
- type QueryParamParser
- type RelationLookup
- type RepositoryScope
- type ResolveOption
- type ResourceSchema
- type Row
- type RowFunc
- type Rows
- type SQLBuilder
- type SQLBuilderOption
- type SQLResult
- type SQLiteDialect
- func (SQLiteDialect) CTEMaterializeHint() string
- func (SQLiteDialect) CodePointOrder(expr string) string
- func (d SQLiteDialect) Condition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, error)
- func (SQLiteDialect) DecimalColumn(column string) string
- func (SQLiteDialect) DecimalOrder(value string) string
- func (SQLiteDialect) DecimalParam(placeholder string) string
- func (d SQLiteDialect) MarshalValue(value any, t core.TypeInfo) (any, error)
- func (SQLiteDialect) NativeRowCompare() bool
- func (SQLiteDialect) PathExpr(p *JSONPath, t core.FieldType) string
- func (SQLiteDialect) ReadColumn(column string, t core.SQLType) string
- func (SQLiteDialect) RowLock() string
- type SearchField
- type SearchSchema
- type SearchWeight
- type SortDirection
- type SortField
- type SortFieldSchema
- type SortResult
- type SortSchema
- type Truth
- type TwoValuedDialect
Constants ¶
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.
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.
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.
const DefaultContainmentMaxValues = 256
DefaultContainmentMaxValues is the largest `$in` set a jsonb path compares by containment when PostgresDialect.ContainmentMaxValues is 0. A larger set joins its members as one unnested array, which a hash serves in any plan.
const DefaultListLimit = 50
DefaultListLimit is the default page size when no limit is specified.
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. .
const MaxIncludes = 3
MaxIncludes is how many relations one list may include.
const MaxListLimit = 1000
MaxListLimit is the maximum allowed page size.
const MaxPathDepth = 8
MaxPathDepth bounds how many members deep a declared path may reach.
Variables ¶
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 */} )
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.
var ErrUnknownDSN = dialect.ErrUnknownDSN
ErrUnknownDSN is returned by DialectFromDSN for a DSN whose scheme names neither PostgreSQL nor SQLite.
var ErrUnsupportedDialect = errors.New("unsupported SQL dialect")
ErrUnsupportedDialect is returned when a DSN references an unsupported database.
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.
var ValidFilterOps = map[FilterOperator]bool{ OpEq: true, OpNe: true, OpGt: true, OpGte: true, OpLt: true, OpLte: true, OpIn: true, OpNin: true, OpLike: true, OpILike: true, OpNull: true, OpNotNull: true, OpExists: true, OpContains: true, OpHasKey: true, OpNear: true, OpWithin: true, OpIntersects: true, }
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 ColumnValue ¶ added in v0.12.0
func ColumnValue(f *FieldSchema, v any) (any, bool, error)
ColumnValue is a stored column value v of field f in the domain Row describes, and false for SQL NULL: a nil pointer, interface, map or slice, or a driver.Valuer whose value is nil. A document is a Document's decoded value, or decoded from its JSON text (string, []byte, json.RawMessage) or encoding; an array's elements normalize to f.Elem; a scalar is reduced as CursorValue reduces it — so drivers' Valuers, decimals, times and text-marshalling types such as UUIDs compare as SQL compares them — then normalized to f's type.
func CompareKeyValues ¶
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 ¶
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 ¶
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 ¶
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 ¶
EncodeCursor encodes a PageToken to an opaque base64url token. Values must already be canonical. A token over the decode limit is E2065.
func EscapeLike ¶
EscapeLike returns s as a `$like` pattern matching s literally; append `%` for a prefix match.
func FacetKeys ¶ added in v0.13.0
func FacetKeys(v any, f *FieldSchema) ([]string, error)
FacetKeys returns the terms buckets a value of f counts in, keyed as BuildFacetQuery keys them, for a backend that counts in Go: none for NULL or an absent member, one for a scalar, and each distinct element of a list once. v is f's value, or for a JSON path its column's document, read as PathValue reads one.
A key is the value's canonical text: a string as itself, a boolean as true or false, a number as its canonical decimal text (core.DecimalText) — a path member's exactly as written, a float32 field's at float32 precision — a date as YYYY-MM-DD, a timestamp as core.FormatTimestamp and a UUID lower-cased. A non-finite float has none. BuildFacetQuery keys a timestamp column by the database's own text of it.
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 (a decimal column as the dialect's exact decimal), or for a path the dialect's typed extraction.
func IsListParam ¶
IsListParam reports whether name is one of the list request parameters (Param*), which a query string never carries as a field filter.
func JSONContains ¶
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 ¶
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 ¶
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 ¶
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 from the column's document, read as PathValue reads it (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 ¶
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 ¶
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 OrderByKeys ¶ added in v0.13.0
OrderByKeys renders the ORDER BY clause that walks keys forwards, or backwards when reverse is set, in the keyset's order: NULL greatest on nullable keys, text by code point, decimals through Dialect.DecimalOrder. It is "" for no keys. ListQuery orders its pages with it; a statement ListQuery does not build, such as one that also orders by a computed key (a CursorKey whose SQLExpr is the expression), pages the same way with OrderByKeys and SeekPredicate.
func ParseDistance ¶
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: a Document, read as it was decoded, or JSON text, json.RawMessage, a decoded document or any Go value that encodes to one, decoded on every call.
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).
An array member (FieldTypeArray) comes back as []any of its elements of type f.Elem, each read as a member of that type is; elements of another kind are left out. It is absent when no element is left: a JSON array with no element of the type has no value, as a search engine's `exists` reads one.
func ResolveRelativeTime ¶
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 SeekPredicate ¶ added in v0.13.0
func SeekPredicate(d Dialect, keys []CursorKey, token *PageToken, bind func(any) string) (string, error)
SeekPredicate renders the predicate holding the rows strictly after token in the walk order of keys (token.Reverse walks backwards), for a statement ordered by OrderByKeys(d, keys, token.Reverse). It checks token against keys first (ValidateCursor), normalizing its values, and refuses a token minted under another keyset with E2082 (ECodeInvalidCursor). bind binds one value and returns its placeholder; every non-null value is bound, never spliced into the SQL.
func ValidateCursor ¶
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 ¶
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 WithFacetFrom ¶ added in v0.13.0
func WithFacetFrom(from string) BuildFacetQueryOption
WithFacetFrom reads the facets' base rows FROM from instead of the schema's table: an aliased table or a join, whose columns filterWhere and the facet fields' expressions name. A relation facet projects the owner's primary key as schema.Name.<pk>, so from names the owner table schema.Name.
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 ColumnRow ¶ added in v0.12.0
ColumnRow is a Row stored by column: it returns the Go value held under a column, named by the expression the schema holds — a field's SQLExpr, or for a path its document's Path.Column, table-qualified as generated schemas qualify them (records.name) — and false when the row has no such column. A missing column is an error, so a misspelt column cannot read as NULL. Columns read through ColumnValue, paths through PathValue.
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 ¶
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.
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
// DecimalColumn renders a decimal column as the dialect's exact decimal
// value, the value PathExpr reads a decimal member as and DecimalParam
// binds: equal decimals are equal values.
DecimalColumn(column string) string
// DecimalParam renders the placeholder of a bound decimal, its
// canonical text (core.DecimalText), as the dialect's exact decimal
// value.
DecimalParam(placeholder string) string
// DecimalOrder renders a decimal value (DecimalColumn, PathExpr or
// DecimalParam) as what orders decimals exactly: the value itself where
// the database's own type orders it, a key that orders as it does where
// there is none.
DecimalOrder(value string) string
}
Dialect generates database-specific SQL.
func DialectFromDSN ¶
DialectFromDSN returns the Dialect of a database DSN: postgres:// (and postgresql://, cockroach://, crdb://) or a SQLite file or :memory:.
type Document ¶ added in v0.13.0
type Document struct {
// contains filtered or unexported fields
}
Document is a JSON document decoded once, for a backend that reads its paths many times: PathValue, ColumnValue, ColumnRow, DocumentElements and KeyValue read a Document without decoding it again, where JSON text is decoded on every read. Its numbers are exact, as json.Number. The zero Document is the JSON null document; a column that is SQL NULL is nil, not a Document.
func DecodeDocument ¶ added in v0.13.0
DecodeDocument decodes data, JSON text, as every reader of a document decodes it: empty text and JSON null are the JSON null document.
type FacetBucket ¶
FacetBucket is a single aggregation bucket.
func FacetBuckets ¶
func FacetBuckets(field *FieldSchema, counts map[string]int64, limit int) []FacetBucket
FacetBuckets orders field's counts as BuildFacetQuery returns them, for a backend that counts in Go: a terms facet by count, ties in the order of the field's type, cut to limit (none when limit <= 0); a range facet in its declared order. Keys without a count are dropped. A nil field is a terms facet of strings.
type FacetQuery ¶
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 values in the field's type order: numbers by value, other keys in code-point order. Rows come back facet by facet in request order: terms buckets in that order, 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 ¶
FacetRange defines a bucket boundary for range facets.
type FacetRequest ¶
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 in the field's type order; 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 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
// Element is a scalar array's element, an array column's or an array
// path's: `$some` / `$none` conditions compare it, one element at a
// time, with the element type's operators. Nil for any other field.
Element *FieldSchema
// 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.
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 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 ¶
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 ¶
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
// contains filtered or unexported fields
}
JSONPath locates a typed member inside a document column: a scalar, or an array of scalars (FieldTypeArray with Elem). 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 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 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 ¶
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 ¶
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 ¶
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 is the member's type: a scalar, or FieldTypeArray for an array
// of Elem.
Type core.FieldType
// Elem is an array member's element type, a scalar a path may hold.
Elem 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). An array
// member is no sort key.
Sort bool
// Facet makes the path facetable with this configuration (terms or
// range), as a column's `facet` tag does. An array member's facet is
// terms, counting each row once per distinct element.
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 ¶
func (pc *PolicyConstraints) Merge(other *PolicyConstraints) *PolicyConstraints
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
ContainmentMaxValues int
}
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+).
ContainmentMaxValues bounds the `$in` sets a jsonb path compares by containment, which a GIN index serves member by member (TwoValuedCondition): 0 is DefaultContainmentMaxValues, and a negative value compares no set by containment.
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) DecimalColumn ¶ added in v0.12.0
func (PostgresDialect) DecimalColumn(column string) string
DecimalColumn returns column: a decimal column is numeric, exact as it is.
func (PostgresDialect) DecimalOrder ¶ added in v0.12.0
func (PostgresDialect) DecimalOrder(value string) string
DecimalOrder returns value: numeric orders exactly.
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 ¶
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. An element path reads its row's element, jsonb_array_elements' v.
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.
func (PostgresDialect) TwoValuedCondition ¶ added in v0.12.0
func (d PostgresDialect) TwoValuedCondition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (string, bool, error)
TwoValuedCondition compares a string, boolean or number member of a jsonb path by containment, which a GIN index on the document serves: `$eq` as `doc @> fragment`, and `$in` with at most ContainmentMaxValues members as `doc @> ANY(fragments)`, each fragment the path's objects down to the value. Containment is False where the typed comparison is Unknown (a missing member, or one of another JSON type), and compares numbers by value and a member holding an array apart from its elements, as the typed comparison does. An array member of such elements takes `$contains` as `doc @> fragment`, the fragment's array holding the listed elements: it is False where the member has no element of its type, where `$contains` is Unknown. Decimals keep the typed comparison (a decimal member may be held as a string), as does a path through a segment `#>` would read as an array index, and an array's element: containment of a scalar holds for an element that is an array holding it.
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.
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 Row ¶ added in v0.12.0
type Row interface {
Value(f *FieldSchema) (v any, present bool, err error)
}
Row is one row a filter is evaluated on. Value is the value of field f in the canonical domain filter values are normalized to, and present false for SQL NULL:
- strings, UUIDs (lower-case text), timestamps (core.TimestampLayout text) and dates (civil date text) as string;
- integers as int64, or uint64 above math.MaxInt64; floats as finite float64; decimals as canonical text (core.DecimalText);
- booleans as bool;
- documents decoded from JSON (map[string]any, []any, string, bool, nil, json.Number);
- arrays as []any of their elements, each in its element type's domain.
A path's value is what PathValue reads from its document. ColumnRow reads a row stored as Go values into this domain; ColumnValue converts one value.
type RowFunc ¶ added in v0.12.0
type RowFunc func(f *FieldSchema) (any, bool, error)
RowFunc adapts a function to Row.
type Rows ¶
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.
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. Where is a selection predicate: it selects exactly the rows the filter holds True, for a WHERE, ON or FILTER clause. On a row the filter holds Unknown it may be False (TwoValuedDialect), so it is not a value to negate or project.
type SQLiteDialect ¶
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) DecimalColumn ¶ added in v0.12.0
func (SQLiteDialect) DecimalColumn(column string) string
DecimalColumn reads the column through parse_decimal (parsesqlite): its canonical decimal text, exact for an INTEGER or for TEXT holding the number, where CAST(… AS NUMERIC) keeps 15 significant digits.
func (SQLiteDialect) DecimalOrder ¶ added in v0.12.0
func (SQLiteDialect) DecimalOrder(value string) string
DecimalOrder reads the value through parse_decimal_key (parsesqlite), a BLOB that orders bytewise as the decimal does. parse_decimal_key reads what parse_decimal reads, so a value DecimalColumn or PathExpr read is keyed from its operand: one function call per row, not two.
func (SQLiteDialect) DecimalParam ¶ added in v0.4.0
func (SQLiteDialect) DecimalParam(placeholder string) string
DecimalParam returns the placeholder: the bound canonical text is the exact value DecimalColumn and PathExpr read a decimal as.
func (SQLiteDialect) MarshalValue ¶
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. A decimal member is read as the JSON literal `->` returns, which keeps the number as written where json_extract would round it to a double, through parse_decimal (parsesqlite). An element path reads its json_each row: its type and value are json_type's and json_extract's of the element, and its literal is the source document's (the hidden json column) at its fullkey.
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.
type Truth ¶ added in v0.12.0
type Truth uint8
Truth is the value of a filter on one row under SQL's three-valued logic. A row is selected only when its filter is True.
func Evaluate ¶ added in v0.12.0
func Evaluate(root *FilterNode, row Row) (Truth, error)
Evaluate is the reference evaluator of a filter: the truth SQL gives root on row, under the rules the SQL dialects render and filtertest holds every backend to. A nil root is True.
- $and, $or and $not are three-valued; a comparison with a NULL value is Unknown.
- $in: [] is False and $nin: [] True, NULL or not.
- Numbers and decimals compare by exact value; strings, UUIDs, timestamps and dates by code point; booleans as false < true.
- Documents compare as jsonb does (JSONEqual, JSONContains, JSONHasKey); an array $contains every listed element; $like and $ilike match as MatchLike; $some / $none test a document's elements, or a scalar array's (an array column's, or an array path's elements of its type).
- A membership or raw-SQL predicate is Unknown: it never admits a row its SQL would refuse. Resolve memberships and relation predicates first (ResolveMemberships); an unresolved relation predicate is an error.
- A geo operator is an error (E2115).
type TwoValuedDialect ¶ added in v0.12.0
type TwoValuedDialect interface {
Dialect
// TwoValuedCondition renders `field <op> value` as a predicate True
// exactly where Condition's is True. ok is false when it has no such
// rendering for the predicate; it then binds nothing.
TwoValuedCondition(field *FieldSchema, op FilterOperator, value any, bind func(any) string) (sql string, ok bool, err error)
}
TwoValuedDialect is a Dialect with a second rendering of some predicates, one an index serves better, that is exact only where a predicate's Unknown and False select alike. SQLBuilder uses it for a condition an even number of negations (usually none) enclose within its statement or subquery: the statement is then monotone in the condition (False < Unknown < True), and a Kleene result that is True with the condition Unknown is True whatever it is, so a row is selected exactly when it would be with the condition False.
Source Files
¶
- doc.go
- document.go
- elements.go
- errors.go
- evaluate.go
- facet.go
- facet_buckets.go
- facet_keys.go
- facet_union.go
- filter.go
- generate.go
- geo.go
- include.go
- include_resolve.go
- jsondoc.go
- keyset.go
- like.go
- list.go
- membership.go
- operators.go
- page.go
- params.go
- path.go
- path_declare.go
- policy.go
- queryparam.go
- relation_parse.go
- relation_resolve.go
- relation_sql.go
- rows.go
- schema.go
- search.go
- sql.go
- sql_postgres.go
- sql_sqlite.go
- time_token.go
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. |