query

package
v0.0.14 Latest Latest
Warning

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

Go to latest
Published: Jan 4, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package query provides a query language parser and builder for the CMS plugin. It supports URL-based and JSON-based query syntax for filtering, sorting, and pagination.

Index

Constants

View Source
const MetaPrefix = "_meta."

MetaPrefix is the prefix used for system/internal fields to avoid conflicts with user-defined fields

Variables

View Source
var OperatorAliases = map[string]FilterOperator{
	"=":            OpEqual,
	"==":           OpEqual,
	"eq":           OpEqual,
	"equals":       OpEqual,
	"!=":           OpNotEqual,
	"<>":           OpNotEqual,
	"ne":           OpNotEqual,
	"neq":          OpNotEqual,
	"notEquals":    OpNotEqual,
	">":            OpGreaterThan,
	"gt":           OpGreaterThan,
	">=":           OpGreaterThanEqual,
	"gte":          OpGreaterThanEqual,
	"<":            OpLessThan,
	"lt":           OpLessThan,
	"<=":           OpLessThanEqual,
	"lte":          OpLessThanEqual,
	"like":         OpLike,
	"ilike":        OpILike,
	"contains":     OpContains,
	"startsWith":   OpStartsWith,
	"endsWith":     OpEndsWith,
	"in":           OpIn,
	"notIn":        OpNotIn,
	"nin":          OpNotIn,
	"all":          OpAll,
	"any":          OpAny,
	"null":         OpNull,
	"isNull":       OpNull,
	"exists":       OpExists,
	"between":      OpBetween,
	"jsonContains": OpJsonContains,
	"jsonHasKey":   OpJsonHasKey,
}

OperatorAliases maps common aliases to operators

View Source
var SystemFields = map[string]bool{
	"id":          true,
	"status":      true,
	"version":     true,
	"createdAt":   true,
	"updatedAt":   true,
	"publishedAt": true,
	"scheduledAt": true,
	"createdBy":   true,
	"updatedBy":   true,
}

SystemFields lists all valid system field names (without the _meta prefix)

Functions

func GetSystemFieldName

func GetSystemFieldName(field string) string

GetSystemFieldName extracts the actual field name from a potentially _meta prefixed field e.g., "_meta.status" -> "status", "status" -> "status"

func IsMetaField

func IsMetaField(field string) bool

IsMetaField returns true if the field uses the _meta prefix format

func IsSystemField

func IsSystemField(field string) bool

IsSystemField returns true if the field is a system field (not user-defined) Supports both legacy format (e.g., "status") and new meta format (e.g., "_meta.status")

func PopulateEntryDTO

func PopulateEntryDTO(entry *schema.ContentEntry, dto *core.ContentEntryDTO)

PopulateEntryDTO populates relation fields in an entry DTO

Types

type AggregateOperator

type AggregateOperator string

AggregateOperator represents an aggregation function

const (
	AggCount AggregateOperator = "count"
	AggSum   AggregateOperator = "sum"
	AggAvg   AggregateOperator = "avg"
	AggMin   AggregateOperator = "min"
	AggMax   AggregateOperator = "max"
)

func (AggregateOperator) IsValid

func (op AggregateOperator) IsValid() bool

IsValid returns true if the operator is valid

type AggregateQuery

type AggregateQuery struct {
	// GroupBy specifies fields to group by
	GroupBy []string `json:"groupBy,omitempty"`

	// Aggregations specifies aggregation operations
	Aggregations []Aggregation `json:"aggregations"`

	// Filters to apply before aggregation
	Filters *FilterGroup `json:"filters,omitempty"`

	// Having conditions (filters on aggregated values)
	Having *FilterGroup `json:"having,omitempty"`

	// Sort the aggregation results
	Sort []SortField `json:"sort,omitempty"`

	// Limit the number of results
	Limit int `json:"limit,omitempty"`
}

AggregateQuery represents an aggregation query

type AggregateResult

type AggregateResult struct {
	// GroupKey holds the group by values (if any)
	GroupKey map[string]interface{} `json:"groupKey,omitempty"`

	// Values holds the aggregated values
	Values map[string]interface{} `json:"values"`
}

AggregateResult holds the result of an aggregation

type Aggregation

type Aggregation struct {
	// Operator is the aggregation function (count, sum, avg, min, max)
	Operator AggregateOperator `json:"operator"`

	// Field is the field to aggregate (not needed for count)
	Field string `json:"field,omitempty"`

	// Alias is the name for the aggregated value in the result
	Alias string `json:"alias"`
}

Aggregation represents a single aggregation operation

type Aggregator

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

Aggregator handles aggregation queries

func NewAggregator

func NewAggregator(db *bun.DB) *Aggregator

NewAggregator creates a new aggregator

func (*Aggregator) GetCMSStats

func (a *Aggregator) GetCMSStats(ctx context.Context, appID, envID xid.ID) (*core.CMSStatsDTO, error)

GetCMSStats returns overall CMS statistics

func (*Aggregator) GetEntryStats

func (a *Aggregator) GetEntryStats(ctx context.Context, contentTypeID xid.ID) (*core.ContentTypeStatsDTO, error)

GetEntryStats returns statistics for entries of a content type

func (*Aggregator) GetTimeSeriesStats

func (a *Aggregator) GetTimeSeriesStats(ctx context.Context, contentTypeID xid.ID, dateTrunc string, limit int) ([]map[string]any, error)

GetTimeSeriesStats returns entry counts over time

func (*Aggregator) SimpleAggregate

func (a *Aggregator) SimpleAggregate(ctx context.Context, contentTypeID xid.ID, config *SimpleAggregateConfig) ([]*SimpleAggregateResult, error)

SimpleAggregate performs a simple aggregation query on content entries

type FilterCondition

type FilterCondition struct {
	// Field is the field name to filter on
	Field string `json:"field"`

	// Operator is the comparison operator
	Operator FilterOperator `json:"operator"`

	// Value is the value to compare against
	Value interface{} `json:"value"`

	// Type hint for value parsing (optional)
	Type string `json:"type,omitempty"`
}

FilterCondition represents a single filter condition

type FilterGroup

type FilterGroup struct {
	// Operator is AND, OR, or NOT
	Operator LogicalOperator `json:"operator,omitempty"`

	// Conditions are the filter conditions in this group
	Conditions []FilterCondition `json:"conditions,omitempty"`

	// Groups are nested filter groups (for complex queries)
	Groups []*FilterGroup `json:"groups,omitempty"`
}

FilterGroup represents a group of filters with a logical operator

type FilterOperator

type FilterOperator string

FilterOperator represents a comparison operator

const (
	// Comparison operators
	OpEqual            FilterOperator = "eq"
	OpNotEqual         FilterOperator = "ne"
	OpGreaterThan      FilterOperator = "gt"
	OpGreaterThanEqual FilterOperator = "gte"
	OpLessThan         FilterOperator = "lt"
	OpLessThanEqual    FilterOperator = "lte"

	// String operators
	OpLike       FilterOperator = "like"     // Case-sensitive pattern match
	OpILike      FilterOperator = "ilike"    // Case-insensitive pattern match
	OpContains   FilterOperator = "contains" // String contains
	OpStartsWith FilterOperator = "startsWith"
	OpEndsWith   FilterOperator = "endsWith"

	// Array operators
	OpIn    FilterOperator = "in"  // Value is in array
	OpNotIn FilterOperator = "nin" // Value is not in array
	OpAll   FilterOperator = "all" // Array contains all values
	OpAny   FilterOperator = "any" // Array contains any value

	// Null operators
	OpNull   FilterOperator = "null"   // Field is null (value: true) or not null (value: false)
	OpExists FilterOperator = "exists" // Field exists (for JSON fields)

	// JSON operators
	OpJsonContains FilterOperator = "jsonContains" // JSON contains
	OpJsonHasKey   FilterOperator = "jsonHasKey"   // JSON has key

	// Date operators
	OpBetween FilterOperator = "between" // Value is between two values
)

func ParseOperator

func ParseOperator(s string) (FilterOperator, bool)

ParseOperator parses an operator string

func ResolveOperator

func ResolveOperator(s string) (FilterOperator, bool)

ResolveOperator resolves an operator string to a FilterOperator

func (FilterOperator) IsValid

func (op FilterOperator) IsValid() bool

IsValid returns true if the operator is valid

func (FilterOperator) RequiresValue

func (op FilterOperator) RequiresValue() bool

RequiresValue returns true if the operator requires a value

type JSONParser

type JSONParser struct{}

JSONParser parses JSON query bodies into Query objects

func NewJSONParser

func NewJSONParser() *JSONParser

NewJSONParser creates a new JSON parser

func (*JSONParser) Parse

func (p *JSONParser) Parse(data []byte) (*Query, error)

Parse parses JSON data into a Query object

func (*JSONParser) ParseJSONQuery

func (p *JSONParser) ParseJSONQuery(jq *JSONQuery) (*Query, error)

ParseJSONQuery parses a JSONQuery struct into a Query object

type JSONQuery

type JSONQuery struct {
	// Filters can be a simple object or complex nested structure
	// Simple: {"status": "published", "title": {"$contains": "hello"}}
	// Complex: {"$and": [{"status": "published"}, {"$or": [...]}]}
	Filters interface{} `json:"filters,omitempty"`

	// Filter is an alias for filters (singular form)
	Filter interface{} `json:"filter,omitempty"`

	// Alternative filter syntax
	Where interface{} `json:"where,omitempty"`

	// Sort can be a string or array
	// String: "-createdAt" or "createdAt:desc"
	// Array: ["-createdAt", "title"]
	Sort interface{} `json:"sort,omitempty"`

	// Select fields to return
	Select []string `json:"select,omitempty"`

	// Fields is an alias for select
	Fields []string `json:"fields,omitempty"`

	// Populate relations
	Populate interface{} `json:"populate,omitempty"`

	// Include is an alias for populate
	Include interface{} `json:"include,omitempty"`

	// Pagination
	Page     int `json:"page,omitempty"`
	PageSize int `json:"pageSize,omitempty"`
	PerPage  int `json:"perPage,omitempty"`
	Offset   int `json:"offset,omitempty"`
	Limit    int `json:"limit,omitempty"`

	// Search for full-text search
	Search string `json:"search,omitempty"`
	Q      string `json:"q,omitempty"`

	// Status shorthand
	Status string `json:"status,omitempty"`
}

JSONQuery represents a JSON query body

type LogicalOperator

type LogicalOperator string

LogicalOperator represents a logical operator for combining filters

const (
	LogicalAnd LogicalOperator = "and"
	LogicalOr  LogicalOperator = "or"
	LogicalNot LogicalOperator = "not"
)

func (LogicalOperator) IsValid

func (op LogicalOperator) IsValid() bool

IsValid returns true if the operator is valid

type OperatorInfo

type OperatorInfo struct {
	// Operator is the operator code
	Operator FilterOperator `json:"operator"`

	// Name is the human-readable name
	Name string `json:"name"`

	// Description describes what the operator does
	Description string `json:"description"`

	// Example shows usage example
	Example string `json:"example"`

	// ApplicableTo lists field types this operator can be used with
	ApplicableTo []string `json:"applicableTo"`

	// ValueType describes the expected value type
	ValueType string `json:"valueType"`
}

OperatorInfo provides metadata about an operator

func GetAllOperators

func GetAllOperators() []OperatorInfo

GetAllOperators returns information about all available operators

func GetOperatorInfo

func GetOperatorInfo(op FilterOperator) *OperatorInfo

GetOperatorInfo returns information about a specific operator

func GetOperatorsForFieldType

func GetOperatorsForFieldType(fieldType string) []OperatorInfo

GetOperatorsForFieldType returns operators applicable to a field type

type PopulateConfig

type PopulateConfig struct {
	// Fields to populate (comma-separated or array)
	Fields []string
	// MaxDepth limits recursive population (default: 1)
	MaxDepth int
	// SelectFields limits which fields to return from related entries
	SelectFields map[string][]string
}

PopulateConfig configures relation population

func DefaultPopulateConfig

func DefaultPopulateConfig() *PopulateConfig

DefaultPopulateConfig returns the default populate configuration

func ParsePopulate

func ParsePopulate(populate string) *PopulateConfig

ParsePopulate parses a populate string into config Format: "field1,field2" or "field1.subfield,field2"

type PopulateOption

type PopulateOption struct {
	// Path is the field path to populate (e.g., "author" or "author.avatar")
	Path string `json:"path"`

	// Select specifies which fields to include from the related entity
	Select []string `json:"select,omitempty"`

	// Populate nested relations
	Populate []PopulateOption `json:"populate,omitempty"`
}

PopulateOption specifies how to populate a relation

type Populator

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

Populator handles relation population for queries

func NewPopulator

func NewPopulator(db *bun.DB) *Populator

NewPopulator creates a new populator

func (*Populator) GetRelatedEntries

func (p *Populator) GetRelatedEntries(ctx context.Context, entryID xid.ID, fieldSlug string) ([]*schema.ContentEntry, error)

GetRelatedEntries returns the related entries for a field

func (*Populator) GetRelatedEntryIDs

func (p *Populator) GetRelatedEntryIDs(ctx context.Context, entryID xid.ID, fieldSlug string) ([]xid.ID, error)

GetRelatedEntryIDs returns the IDs of related entries for a field

func (*Populator) GetReverseRelatedEntries

func (p *Populator) GetReverseRelatedEntries(ctx context.Context, entryID xid.ID, fieldSlug string) ([]*schema.ContentEntry, error)

GetReverseRelatedEntries returns entries that reference this entry

func (*Populator) PopulateEntries

func (p *Populator) PopulateEntries(ctx context.Context, entries []*schema.ContentEntry, config *PopulateConfig) error

PopulateEntries populates relations for a slice of entries

func (*Populator) PopulateEntry

func (p *Populator) PopulateEntry(ctx context.Context, entry *schema.ContentEntry, config *PopulateConfig) error

PopulateEntry populates relations for a single entry

type Query

type Query struct {
	// Filters contains all filter conditions
	Filters *FilterGroup `json:"filters,omitempty"`

	// Sort specifies the sort order
	Sort []SortField `json:"sort,omitempty"`

	// Select specifies which fields to return (projection)
	Select []string `json:"select,omitempty"`

	// Populate specifies which relations to populate
	Populate []PopulateOption `json:"populate,omitempty"`

	// Pagination options
	Page     int `json:"page,omitempty"`
	PageSize int `json:"pageSize,omitempty"`
	Offset   int `json:"offset,omitempty"`
	Limit    int `json:"limit,omitempty"`

	// Search for full-text search
	Search string `json:"search,omitempty"`

	// Status filter (shortcut)
	Status string `json:"status,omitempty"`
}

Query represents a parsed content query

func NewQuery

func NewQuery() *Query

NewQuery creates a new empty query

func (*Query) AddFilter

func (q *Query) AddFilter(field string, operator FilterOperator, value interface{}) *Query

AddFilter adds a filter condition to the query

func (*Query) AddPopulate

func (q *Query) AddPopulate(path string, selectFields ...string) *Query

AddPopulate adds a relation to populate

func (*Query) AddSelect

func (q *Query) AddSelect(fields ...string) *Query

AddSelect adds fields to the select list

func (*Query) AddSort

func (q *Query) AddSort(field string, descending bool) *Query

AddSort adds a sort field to the query

func (*Query) SetOffsetLimit

func (q *Query) SetOffsetLimit(offset, limit int) *Query

SetOffsetLimit sets offset-based pagination

func (*Query) SetPagination

func (q *Query) SetPagination(page, pageSize int) *Query

SetPagination sets pagination options

func (*Query) Validate

func (q *Query) Validate(fields map[string]*core.ContentFieldDTO) error

Validate validates the query

type QueryBuilder

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

QueryBuilder builds Bun queries from parsed Query objects

func NewQueryBuilder

func NewQueryBuilder(db *bun.DB, contentTypeID xid.ID, fields []*schema.ContentField) *QueryBuilder

NewQueryBuilder creates a new query builder

func (*QueryBuilder) Build

func (b *QueryBuilder) Build(q *Query) *bun.SelectQuery

Build builds a Bun select query from a parsed Query

func (*QueryBuilder) BuildCount

func (b *QueryBuilder) BuildCount(q *Query) *bun.SelectQuery

BuildCount builds a count query from a parsed Query

type QueryExecutor

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

QueryExecutor executes queries and returns results

func NewQueryExecutor

func NewQueryExecutor(db *bun.DB) *QueryExecutor

NewQueryExecutor creates a new query executor

func (*QueryExecutor) Execute

func (e *QueryExecutor) Execute(ctx context.Context, contentType *schema.ContentType, q *Query) (*QueryResult, error)

Execute executes a query and returns the results

func (*QueryExecutor) ExecuteAggregate

func (e *QueryExecutor) ExecuteAggregate(ctx context.Context, contentType *schema.ContentType, q *AggregateQuery) ([]AggregateResult, error)

ExecuteAggregate executes an aggregation query

func (*QueryExecutor) ExecuteByID

func (e *QueryExecutor) ExecuteByID(ctx context.Context, entryID xid.ID) (*schema.ContentEntry, error)

ExecuteByID executes a query to find a single entry by ID

func (*QueryExecutor) ExecuteCount

func (e *QueryExecutor) ExecuteCount(ctx context.Context, contentType *schema.ContentType, q *Query) (int, error)

ExecuteCount executes a count query

func (*QueryExecutor) ExecuteDistinct

func (e *QueryExecutor) ExecuteDistinct(ctx context.Context, contentType *schema.ContentType, field string, q *Query) ([]interface{}, error)

ExecuteDistinct returns distinct values for a field

func (*QueryExecutor) ExecuteExists

func (e *QueryExecutor) ExecuteExists(ctx context.Context, contentType *schema.ContentType, q *Query) (bool, error)

ExecuteExists checks if any entries match the query

func (*QueryExecutor) ExecuteIDs

func (e *QueryExecutor) ExecuteIDs(ctx context.Context, contentType *schema.ContentType, q *Query) ([]xid.ID, error)

ExecuteIDs returns just the IDs matching a query

type QueryResult

type QueryResult struct {
	Entries    []*schema.ContentEntry `json:"entries"`
	Page       int                    `json:"page"`
	PageSize   int                    `json:"pageSize"`
	TotalItems int                    `json:"totalItems"`
	TotalPages int                    `json:"totalPages"`
}

QueryResult holds the result of a query execution

type SearchConfig

type SearchConfig struct {
	// Query is the search query string
	Query string
	// Fields to search (if empty, searches all text fields)
	Fields []string
	// Language for text search (default: english)
	Language string
	// IncludeHighlights returns highlighted snippets
	IncludeHighlights bool
	// HighlightStartTag is the tag for highlight start (default: <mark>)
	HighlightStartTag string
	// HighlightEndTag is the tag for highlight end (default: </mark>)
	HighlightEndTag string
	// MinScore filters out low-relevance results
	MinScore float64
}

SearchConfig configures full-text search behavior

func DefaultSearchConfig

func DefaultSearchConfig() *SearchConfig

DefaultSearchConfig returns the default search configuration

type SearchResult

type SearchResult struct {
	Entry      *schema.ContentEntry
	Score      float64
	Highlights map[string]string
}

SearchResult represents a search result with ranking

type Searcher

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

Searcher handles full-text search operations

func NewSearcher

func NewSearcher(db *bun.DB) *Searcher

NewSearcher creates a new searcher

func (*Searcher) Search

func (s *Searcher) Search(ctx context.Context, contentTypeID xid.ID, config *SearchConfig, page, pageSize int) ([]*SearchResult, int, error)

Search performs a full-text search on content entries

func (*Searcher) SearchAll

func (s *Searcher) SearchAll(ctx context.Context, appID, envID xid.ID, config *SearchConfig, page, pageSize int) ([]*SearchResult, int, error)

SearchAll searches across all content types

func (*Searcher) SuggestSearch

func (s *Searcher) SuggestSearch(ctx context.Context, contentTypeID xid.ID, prefix string, limit int) ([]string, error)

SuggestSearch returns search suggestions based on existing content

type SimpleAggregateConfig

type SimpleAggregateConfig struct {
	// Operator is the aggregation type (count, sum, avg, min, max)
	Operator AggregateOperator
	// Field is the field to aggregate (for sum, avg, min, max)
	Field string
	// GroupBy is the field to group results by
	GroupBy string
	// Filters are optional filters to apply
	Filters map[string]any
	// DateTrunc truncates dates for time-based grouping (day, week, month, year)
	DateTrunc string
}

SimpleAggregateConfig configures a simple aggregation query This is a simpler interface than AggregateQuery for common use cases

type SimpleAggregateResult

type SimpleAggregateResult struct {
	GroupValue any     `json:"groupValue,omitempty"`
	Count      int     `json:"count,omitempty"`
	Sum        float64 `json:"sum,omitempty"`
	Avg        float64 `json:"avg,omitempty"`
	Min        any     `json:"min,omitempty"`
	Max        any     `json:"max,omitempty"`
}

SimpleAggregateResult represents a simple aggregation result

type SortField

type SortField struct {
	// Field is the field name to sort by
	Field string `json:"field"`

	// Descending is true for DESC, false for ASC
	Descending bool `json:"descending"`
}

SortField represents a field to sort by

type URLParser

type URLParser struct{}

URLParser parses query parameters from URLs into Query objects

func NewURLParser

func NewURLParser() *URLParser

NewURLParser creates a new URL parser

func (*URLParser) Parse

func (p *URLParser) Parse(values url.Values) (*Query, error)

Parse parses URL query parameters into a Query object Supported formats:

  • filter[field]=op.value (e.g., filter[_meta.status]=eq.published)
  • filter[field]=value (shorthand for eq)
  • sort=-field (descending) or sort=field (ascending)
  • page=1&pageSize=20
  • offset=0&limit=20
  • select=field1,field2
  • populate=relation1,relation2
  • search=text

System fields use _meta prefix: _meta.status, _meta.createdAt, _meta.updatedAt, etc. Legacy format (e.g., "status") is still supported for backward compatibility.

func (*URLParser) ToURLValues

func (p *URLParser) ToURLValues(q *Query) url.Values

ToURLValues converts a Query back to URL query parameters

Jump to

Keyboard shortcuts

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