Documentation
¶
Overview ¶
Package query provides a safe query engine for GORM list endpoints. It parses bounded HTTP queries, validates scalar and relationship paths against explicit endpoint policy, and either derives GORM scopes or executes stable offset or forward-cursor pages without multiplying roots for to-many predicates.
Index ¶
- Constants
- func Apply[T any](db *gorm.DB, schema *ModelSchema[T], q Query) (*gorm.DB, error)
- func ValidateConfig[T any](db *gorm.DB, config Config[T]) error
- type ComparisonExpr
- type ComparisonOperator
- type Config
- type DateValue
- type DecimalValue
- type EndpointDescription
- type Engine
- func (e *Engine[T]) Apply(ctx context.Context, base *gorm.DB, q Query) (*gorm.DB, error)
- func (e *Engine[T]) Describe() EndpointDescription
- func (e *Engine[T]) From(base *gorm.DB) Session[T]
- func (e *Engine[T]) List(ctx context.Context, values url.Values) (Page[T], error)
- func (e *Engine[T]) Parse(values url.Values) (Query, error)
- type Error
- type ErrorCode
- type Expr
- type FieldDescription
- type FieldOption
- type Kind
- type Limits
- type Literal
- type LiteralKind
- type LogicalExpr
- type LogicalOperator
- type ModelSchema
- type NestedPolicy
- type NotExpr
- type OrderTerm
- type Page
- type PageInfo
- type PaginationDescription
- type ParseOption
- type Position
- type QuantifierExpr
- type QuantifierOperator
- type Query
- type RelationshipCardinality
- type RelationshipDescription
- type RelationshipOption
- type ScalarCodec
- type SchemaBuilder
- func (b *SchemaBuilder[T]) Bind(db *gorm.DB) (*ModelSchema[T], error)
- func (b *SchemaBuilder[T]) Build() (*ModelSchema[T], error)
- func (b *SchemaBuilder[T]) Expose(publicName string, options ...FieldOption) *SchemaBuilder[T]
- func (b *SchemaBuilder[T]) Field(publicName string, kind Kind, options ...FieldOption) *SchemaBuilder[T]
- func (b *SchemaBuilder[T]) Relation(publicName string, policy NestedPolicy, options ...RelationshipOption) *SchemaBuilder[T]
- type SchemaDescription
- type SearchFunc
- type Session
- type SortTerm
- type Span
- type UUIDValue
Examples ¶
Constants ¶
const SyntaxVersion = "v1"
SyntaxVersion is the stable query-language version implemented by this release. Tooling can use it to reject incompatible descriptions.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type ComparisonExpr ¶
type ComparisonExpr struct {
Field string
Operator ComparisonOperator
Literal Literal
Literals []Literal
Source Span
FieldSource Span
OpSource Span
}
ComparisonExpr compares one unresolved public field with a syntactic literal. Field resolution and literal conversion happen during validation.
func (*ComparisonExpr) Span ¶
func (e *ComparisonExpr) Span() Span
type ComparisonOperator ¶
type ComparisonOperator uint8
ComparisonOperator is a closed set of V1 field comparison operators.
const ( Eq ComparisonOperator = iota Ne Gt Gte Lt Lte Contains StartsWith EndsWith In NotIn IsNull IsNotNull )
func (ComparisonOperator) MarshalText ¶
func (op ComparisonOperator) MarshalText() ([]byte, error)
func (ComparisonOperator) String ¶
func (op ComparisonOperator) String() string
type Config ¶
type Config[T any] struct { Policy *SchemaBuilder[T] DefaultLimit int MaxLimit int MaxOffset int MaxSortTerms int MaxSearchBytes int MaxQueryBytes int MaxFilterBytes int MaxTokens int MaxLiteralBytes int MaxInValues int MaxExpressionDepth int MaxNodes int MaxPathDepth int MaxQuantifierDepth int MaxCursorBytes int AllowCount bool AllowCompatibilityAliases bool Search SearchFunc }
Config defines an immutable query engine for one model and endpoint policy.
type DateValue ¶
type DateValue string
DateValue marks a timezone-free ISO calendar date (YYYY-MM-DD) for schema inference. It is bound to the database as its canonical string form.
type DecimalValue ¶
type DecimalValue string
DecimalValue marks an exact decimal represented without binary floating point conversion.
type EndpointDescription ¶
type EndpointDescription struct {
SyntaxVersion string `json:"syntaxVersion"`
Schema SchemaDescription `json:"schema"`
Pagination PaginationDescription `json:"pagination"`
Limits Limits `json:"limits"`
Count bool `json:"count"`
Search bool `json:"search"`
CompatibilityAliases bool `json:"compatibilityAliases"`
}
EndpointDescription is safe to publish as endpoint documentation. It only describes public query names and effective limits.
type Engine ¶
type Engine[T any] struct { // contains filtered or unexported fields }
Engine parses, validates, applies, and optionally executes list queries for T. It is immutable after New returns and safe for concurrent use.
func New ¶
New constructs and validates an immutable engine using the active GORM naming strategy and model metadata.
func (*Engine[T]) Describe ¶
func (e *Engine[T]) Describe() EndpointDescription
Describe returns the endpoint's detached, effective public policy.
func (*Engine[T]) From ¶
From returns a session that preserves base for data and count queries.
Example ¶
package main
import (
"context"
"fmt"
"net/url"
query "github.com/dmedovich/gotq"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type exampleTenantUser struct {
ID uint `json:"id"`
TenantID uint `json:"-"`
Name string `json:"name"`
}
func main() {
db, err := gorm.Open(sqlite.Open("file:example-tenant?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
panic(err)
}
if err := db.AutoMigrate(&exampleTenantUser{}); err != nil {
panic(err)
}
if err := db.Create(&[]exampleTenantUser{
{TenantID: 1, Name: "Alice"},
{TenantID: 1, Name: "Cara"},
{TenantID: 2, Name: "Mallory"},
}).Error; err != nil {
panic(err)
}
users, err := query.New(db, query.Config[exampleTenantUser]{
Policy: query.Schema[exampleTenantUser]().
Expose("id", query.Sortable()).
Expose("name", query.Filterable(query.Eq), query.Sortable()),
DefaultLimit: 20,
MaxLimit: 100,
MaxOffset: 1_000,
AllowCount: true,
})
if err != nil {
panic(err)
}
base := db.Where("tenant_id = ?", 1)
page, err := users.From(base).List(
context.Background(),
url.Values{
"sort": {"name"},
"count": {"true"},
},
)
if err != nil {
panic(err)
}
fmt.Println(page.Items[0].Name, page.Items[1].Name, *page.Total)
}
Output: Alice Cara 2
func (*Engine[T]) List ¶
List parses and executes a page using the engine's default database.
Example ¶
db, err := gorm.Open(sqlite.Open("file:example-list?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
panic(err)
}
if err := db.AutoMigrate(&User{}); err != nil {
panic(err)
}
if err := db.Create(&[]User{
{Name: "Alice", Age: 31},
{Name: "Bob", Age: 16},
{Name: "Cara", Age: 24},
{Name: "Daria", Age: 28},
}).Error; err != nil {
panic(err)
}
users, err := buildUserEngine(db)
if err != nil {
panic(err)
}
page, err := users.List(
context.Background(),
url.Values{
"filter": {"age gte 18"},
"sort": {"name"},
"limit": {"2"},
},
)
if err != nil {
panic(err)
}
fmt.Println(page.Items[0].Name, page.Items[1].Name, page.Page.HasMore)
Output: Alice Cara true
type Error ¶
type Error struct {
Code ErrorCode `json:"code"`
Parameter string `json:"parameter,omitempty"`
Position *Position `json:"position,omitempty"`
Message string `json:"message"`
Field string `json:"field,omitempty"`
Kind *Kind `json:"kind,omitempty"`
Operator *ComparisonOperator `json:"operator,omitempty"`
AllowedOperators []ComparisonOperator `json:"allowedOperators,omitempty"`
Cause error `json:"-"`
}
Error is returned for invalid client queries and invalid model schemas. Message is human-readable; Code and the typed fields are stable.
type ErrorCode ¶
type ErrorCode string
ErrorCode is stable and intended for machine-readable client errors.
const ( CodeInvalidParameter ErrorCode = "invalid_parameter" CodeInvalidToken ErrorCode = "invalid_token" CodeInvalidSyntax ErrorCode = "invalid_syntax" CodeLimitExceeded ErrorCode = "limit_exceeded" CodeUnknownField ErrorCode = "unknown_field" CodeNotFilterable ErrorCode = "field_not_filterable" CodeNotSortable ErrorCode = "field_not_sortable" CodeOperatorNotAllowed ErrorCode = "operator_not_allowed" CodeInvalidLiteral ErrorCode = "invalid_literal" CodeInvalidSchema ErrorCode = "invalid_schema" CodeExecutionFailed ErrorCode = "execution_failed" CodeInvalidRelationship ErrorCode = "invalid_relationship" CodeInvalidCursor ErrorCode = "invalid_cursor" )
type Expr ¶
type Expr interface {
Span() Span
// contains filtered or unexported methods
}
Expr is a syntactic V1 filter expression. Its implementations are closed so validators and compilers can handle every possible node kind exhaustively.
type FieldDescription ¶
type FieldDescription struct {
Name string `json:"name"`
Kind Kind `json:"kind"`
Nullable bool `json:"nullable"`
Filterable bool `json:"filterable"`
Sortable bool `json:"sortable"`
Operators []ComparisonOperator `json:"operators"`
Codec string `json:"codec,omitempty"`
}
FieldDescription is the public, storage-independent view of one field. It intentionally contains neither Go field names nor database identifiers.
type FieldOption ¶
type FieldOption interface {
// contains filtered or unexported methods
}
FieldOption configures one schema field. Implementations are sealed.
func Column ¶
func Column(name string) FieldOption
Column explicitly selects the whitelisted database column.
func Filterable ¶
func Filterable(operators ...ComparisonOperator) FieldOption
Filterable enables filtering. With no arguments it selects the kind's default operators; arguments replace that default with an explicit subset.
func GoField ¶
func GoField(name string) FieldOption
GoField explicitly selects an exported top-level Go struct field.
func WithCodec ¶
func WithCodec(codec ScalarCodec) FieldOption
WithCodec assigns a constrained custom scalar codec to a field.
type Kind ¶
type Kind uint8
Kind describes a model field's scalar semantics, independently of its database type.
func (Kind) MarshalText ¶
type Limits ¶
type Limits struct {
MaxQueryBytes int `json:"maxQueryBytes"`
MaxFilterBytes int `json:"maxFilterBytes"`
MaxTokens int `json:"maxTokens"`
MaxLiteralBytes int `json:"maxLiteralBytes"`
MaxInValues int `json:"maxInValues"`
MaxLimit int `json:"maxLimit"`
MaxOffset int `json:"maxOffset"`
MaxSortTerms int `json:"maxSortTerms"`
MaxSearchBytes int `json:"maxSearchBytes"`
MaxExpressionDepth int `json:"maxExpressionDepth"`
MaxNodes int `json:"maxNodes"`
MaxPathDepth int `json:"maxPathDepth"`
MaxQuantifierDepth int `json:"maxQuantifierDepth"`
MaxCursorBytes int `json:"maxCursorBytes"`
}
Limits bounds client-controlled query complexity.
type Literal ¶
type Literal struct {
Kind LiteralKind
Raw string
Value any
Source Span
}
Literal preserves raw syntax and its syntax-level value.
type LiteralKind ¶
type LiteralKind uint8
LiteralKind classifies syntax before a model schema supplies a target kind.
const ( StringLiteral LiteralKind = iota NumberLiteral BoolLiteral )
type LogicalExpr ¶
type LogicalExpr struct {
Operator LogicalOperator
Left Expr
Right Expr
Source Span
}
LogicalExpr joins two expressions with and/or.
func (*LogicalExpr) Span ¶
func (e *LogicalExpr) Span() Span
type LogicalOperator ¶
type LogicalOperator uint8
LogicalOperator is a closed set of V1 boolean operators.
const ( And LogicalOperator = iota Or )
func (LogicalOperator) MarshalText ¶
func (op LogicalOperator) MarshalText() ([]byte, error)
func (LogicalOperator) String ¶
func (op LogicalOperator) String() string
type ModelSchema ¶
type ModelSchema[T any] struct { // contains filtered or unexported fields }
ModelSchema is an immutable whitelist and type map for one model.
func (*ModelSchema[T]) Describe ¶
func (s *ModelSchema[T]) Describe() SchemaDescription
Describe returns a detached public description. Mutating its slices cannot change the immutable schema.
type NestedPolicy ¶
type NestedPolicy interface {
// contains filtered or unexported methods
}
NestedPolicy is a sealed relationship target policy. Values returned by Schema[T] implement it; arbitrary implementations are not accepted.
type OrderTerm ¶
type OrderTerm = SortTerm
OrderTerm is retained as a source-compatible alias for the pre-release API.
type Page ¶
type Page[T any] struct { Items []T `json:"items"` Page PageInfo `json:"page"` Total *int64 `json:"total,omitempty"` }
Page is the result of a list operation.
type PageInfo ¶
type PageInfo struct {
Limit int `json:"limit"`
Offset int `json:"offset"`
HasMore bool `json:"hasMore"`
NextCursor string `json:"nextCursor,omitempty"`
}
PageInfo describes the effective page and optional forward continuation.
type PaginationDescription ¶
type PaginationDescription struct {
DefaultLimit int `json:"defaultLimit"`
MaxLimit int `json:"maxLimit"`
MaxOffset int `json:"maxOffset"`
Cursor bool `json:"cursor"`
}
PaginationDescription documents the effective offset and cursor policy.
type ParseOption ¶
type ParseOption interface {
// contains filtered or unexported methods
}
ParseOption configures ParseHTTP. Implementations are sealed.
func WithCompatibilityAliases ¶
func WithCompatibilityAliases() ParseOption
WithCompatibilityAliases accepts the pre-release orderby/top/skip parameter names in addition to the canonical sort/limit/offset names.
func WithLimits ¶
func WithLimits(limits Limits) ParseOption
WithLimits replaces every default query limit. All values must be positive.
type Position ¶
type Position struct {
Offset int `json:"offset"`
Line int `json:"line"`
Column int `json:"column"`
}
Position identifies a byte in a decoded parameter value.
type QuantifierExpr ¶
type QuantifierExpr struct {
Relationship string
Operator QuantifierOperator
Variable string
Predicate Expr
Source Span
RelationshipSource Span
OperatorSource Span
VariableSource Span
}
QuantifierExpr applies any/all to an explicitly exposed to-many relationship. Predicate field paths are rooted at Variable.
func (*QuantifierExpr) Span ¶
func (e *QuantifierExpr) Span() Span
type QuantifierOperator ¶
type QuantifierOperator uint8
QuantifierOperator is a closed set of collection relationship predicates.
const ( Any QuantifierOperator = iota All )
func (QuantifierOperator) MarshalText ¶
func (op QuantifierOperator) MarshalText() ([]byte, error)
func (QuantifierOperator) String ¶
func (op QuantifierOperator) String() string
type Query ¶
type Query struct {
Filter Expr
Sort []SortTerm
Limit *int
Offset *int
Cursor *string
Count *bool
Search *string
// contains filtered or unexported fields
}
Query is the transport-level query returned by ParseHTTP.
func ParseHTTP ¶
func ParseHTTP(values url.Values, options ...ParseOption) (Query, error)
ParseHTTP parses the V1 query parameters from decoded URL values. It does not know a model schema and does not access a database.
Example ¶
package main
import (
"fmt"
"net/url"
query "github.com/dmedovich/gotq"
)
func main() {
values := url.Values{
"filter": {"age gte 18 and name contains 'ann'"},
"sort": {"-createdAt,name"},
"limit": {"20"},
}
parsed, err := query.ParseHTTP(values)
if err != nil {
panic(err)
}
fmt.Println(parsed.Filter != nil, len(parsed.Sort), *parsed.Limit)
}
Output: true 2 20
type RelationshipCardinality ¶
type RelationshipCardinality string
RelationshipCardinality describes whether a relationship resolves to one resource or a collection. Relationship declarations are introduced in M5; the type is present now so consumers can rely on a stable description shape.
const ( RelationshipOne RelationshipCardinality = "one" RelationshipMany RelationshipCardinality = "many" )
type RelationshipDescription ¶
type RelationshipDescription struct {
Name string `json:"name"`
Cardinality RelationshipCardinality `json:"cardinality"`
Filterable bool `json:"filterable"`
Sortable bool `json:"sortable"`
Schema SchemaDescription `json:"schema"`
}
RelationshipDescription is the public view of a relationship policy.
type RelationshipOption ¶
type RelationshipOption interface {
// contains filtered or unexported methods
}
RelationshipOption configures one explicitly exposed relationship. Implementations are sealed.
func RelationGoField ¶
func RelationGoField(name string) RelationshipOption
RelationGoField explicitly selects an exported top-level Go association field when the public relationship name does not match its JSON name.
type ScalarCodec ¶
type ScalarCodec interface {
Name() string
ValidateType(reflect.Type) error
ParseLiteral(Literal, reflect.Type) (any, error)
}
ScalarCodec converts syntax into a typed bound value for a custom scalar. It has no access to SQL compilation or identifiers.
type SchemaBuilder ¶
type SchemaBuilder[T any] struct { // contains filtered or unexported fields }
SchemaBuilder accumulates model field declarations. Call Build once startup configuration is complete; the resulting ModelSchema is immutable.
func (*SchemaBuilder[T]) Bind ¶
func (b *SchemaBuilder[T]) Bind(db *gorm.DB) (*ModelSchema[T], error)
Bind validates declarations against the configured GORM model schema. It supports inferred fields and honors the active naming strategy.
func (*SchemaBuilder[T]) Build ¶
func (b *SchemaBuilder[T]) Build() (*ModelSchema[T], error)
Build validates reflection mappings and returns an immutable schema.
func (*SchemaBuilder[T]) Expose ¶
func (b *SchemaBuilder[T]) Expose(publicName string, options ...FieldOption) *SchemaBuilder[T]
Expose adds a public field whose scalar kind and database column are inferred from the GORM model schema when Bind is called.
func (*SchemaBuilder[T]) Field ¶
func (b *SchemaBuilder[T]) Field(publicName string, kind Kind, options ...FieldOption) *SchemaBuilder[T]
Field adds a public model field declaration. Errors are accumulated as declarations and reported deterministically by Build.
func (*SchemaBuilder[T]) Relation ¶
func (b *SchemaBuilder[T]) Relation(publicName string, policy NestedPolicy, options ...RelationshipOption) *SchemaBuilder[T]
Relation explicitly exposes one GORM association and its nested public policy. Merely existing in GORM metadata never exposes a relationship.
Example ¶
package main
import (
query "github.com/dmedovich/gotq"
)
type exampleCompany struct {
ID uint `json:"id"`
Name string `json:"name"`
}
type exampleOrder struct {
ID uint `json:"id"`
UserID uint `json:"userId"`
Total int `json:"total"`
}
type exampleRelationshipUser struct {
ID uint `json:"id"`
CompanyID uint `json:"companyId"`
Company exampleCompany `json:"company"`
Orders []exampleOrder `json:"orders" gorm:"foreignKey:UserID"`
}
func main() {
company := query.Schema[exampleCompany]().
Expose("name", query.Filterable(query.Eq), query.Sortable())
orders := query.Schema[exampleOrder]().
Expose("total", query.Filterable(query.Gt, query.Gte))
policy := query.Schema[exampleRelationshipUser]().
Expose("id", query.Sortable()).
Relation("company", company).
Relation("orders", orders)
_ = policy
}
Output:
type SchemaDescription ¶
type SchemaDescription struct {
Fields []FieldDescription `json:"fields"`
Relationships []RelationshipDescription `json:"relationships"`
}
SchemaDescription is a deterministic, read-only projection of a model policy. Fields and relationships are sorted by public name.
type SearchFunc ¶
SearchFunc applies an endpoint-owned search predicate. It is trusted application code and must bind all values derived from term.
type Session ¶
type Session[T any] struct { // contains filtered or unexported fields }
Session is a request-local engine view using a caller-owned base scope.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
gotq-playground
command
Command gotq-playground runs a local, database-free query parser playground.
|
Command gotq-playground runs a local, database-free query parser playground. |
|
Package openapi generates an OpenAPI 3.1 operation from a gotq endpoint description.
|
Package openapi generates an OpenAPI 3.1 operation from a gotq endpoint description. |
|
Package queryhttp contains optional net/http helpers for gotq endpoints.
|
Package queryhttp contains optional net/http helpers for gotq endpoints. |