Skip to content
rakunlabsPublic

About

query parser statements

Topics

Resources

Stars

0 stars

Watchers

3 watching

Forks

Latest commit

 

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Query

License Coverage GitHub Workflow Status Go PKG

Query is an adaptor of http query to expressions. Check adapters to convert it to sql or other expressions.

go get github.com/rakunlabs/query

Usage

Parse url and extract query parameters with RAW, give to query.Parse to convert it to expression.
Use an adapter to convert it to sql or other expressions.

urlStr := "http://example.com?name=foo,bar|nick=bar&age[lt]=1&_sort=-age&_limit=10&_offset=5&_fields=id,name"
parsedURL, err := url.Parse(urlStr)
// ...
query, err := query.Parse(parsedURL.RawQuery)
// ...
sql, params, err := adaptergoqu.Select(query, goqu.From("test")).ToSQL()
// ...

// Output:
// SQL: SELECT "id", "name" FROM "test" WHERE ((("name" IN (?, ?)) OR ("nick" = ?)) AND ("age" < ?)) ORDER BY "age" DESC LIMIT ? OFFSET ?
// Params: [foo bar bar 1 10 5]

If some value separated by , it will be converted to IN operator.
There are a list of [ ] operators that can be used in the query string:
eq, ne, gt, lt, gte, lte, like, ilike, nlike, nilike, in, nin, is, not, kv, jin, njin

Operator Description Example SQL
eq Equal name[eq]=foo or name=foo name = 'foo'
ne Not equal name[ne]=foo name != 'foo'
gt Greater than age[gt]=18 age > 18
lt Less than age[lt]=18 age < 18
gte Greater than or equal age[gte]=18 age >= 18
lte Less than or equal age[lte]=18 age <= 18
like LIKE pattern name[like]=%foo% name LIKE '%foo%'
ilike Case-insensitive LIKE name[ilike]=%foo% name ILIKE '%foo%'
nlike NOT LIKE name[nlike]=%foo% name NOT LIKE '%foo%'
nilike Case-insensitive NOT LIKE name[nilike]=%foo% name NOT ILIKE '%foo%'
in IN list name[in]=foo,bar or name=foo,bar name IN ('foo', 'bar')
nin NOT IN list name[nin]=foo,bar name NOT IN ('foo', 'bar')
is IS NULL name[is]= name IS NULL
not IS NOT NULL name[not]= name IS NOT NULL
kv JSONB containment (@>) meta[kv]=eyJhIjoxfQ meta @> '{"a":1}'
jin JSONB array has any (?|) tags[jin]=admin,editor tags ?&#124; array['admin','editor']
njin JSONB array has none (NOT ?|) tags[njin]=admin,editor NOT (tags ?&#124; array['admin','editor'])

_limit and _offset are used to limit the number of rows returned. 0 limit means no limit.
_fields is used to select the fields to be returned, comma separated.
_sort is used to sort the result set, can be prefixed with - to indicate descending order and comma separated to indicate multiple fields.
[] empty operator means in operator.
Paranteses () can be used to group expressions, | is used for OR operation and & is used for AND operation.

Pass URL.RawQuery without unescaping it first. Literal ampersands and pipes in values must be encoded as %26 and %7C: name=R%26D matches R&D, while name=foo|bar is an OR expression. Values are URL-decoded once, after logical operators are separated. Encoded parentheses (%28 and %29) remain supported as grouping syntax; comma-separated values retain their existing list behavior.

Reusable Parser

Use query.NewParser to configure a parser once and reuse it across requests:

// API in package query:
func NewParser(opts ...OptionQuery) *Parser
func (parser *Parser) Parse(raw string) (*Query, error)
func (parser *Parser) ParseWithValidator(raw string, validator *Validator) (*Query, error)

Options run once at construction, not on each parse. This API reuses configuration; it adds no cache, parsing rewrite, or depth limit. Existing query.Parse and query.ParseWithValidator remain available.

// At application startup:
parser := query.NewParser(query.WithKeyOperator("name", query.OperatorLike))

// Reuse when parsing:
q, err := parser.Parse("name=foo")
// Or validate the parsed query:
q, err = parser.ParseWithValidator("name=foo", validator)

A parser is safe for concurrent parsing provided its transform callbacks are concurrency safe and shared custom expression values are immutable. Default limit/offset and injected comparisons are copied for each result; injected []string and []bool values are also copied. Other reference-valued ExpressionCmp.Value types remain shared and must not be mutated. For request-dependent authorization options, use the existing query.Parse per request or keep authorization in separate logic. Never capture request context in a shared parser.

A handler factory keeps static options near the handler without rebuilding the parser on each request:

func searchHandler() http.HandlerFunc {
    parser := query.NewParser(query.WithKeyOperator("name", query.OperatorLike))
    return func(w http.ResponseWriter, r *http.Request) {
        q, err := parser.Parse(r.URL.RawQuery)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        // Use q with an adapter to execute the search.
        _ = q
    }
}

// Instantiate the factory once during route registration, not per request:
mux.HandleFunc("/search", searchHandler())

Parse Options

Options can be passed to query.Parse or query.NewParser to customize parsing behavior:

WithKeyOperator

Sets the default operator for a specific key when no bracket operator is specified. This allows you to write cleaner query strings without explicit [op] brackets.

// Without WithKeyOperator: name=foo → name[eq]=foo (default)
// With WithKeyOperator:    name=foo → name[like]=foo
q, err := query.Parse("name=foo", query.WithKeyOperator("name", query.OperatorLike))

// Useful for JSONB array columns:
// tags=admin,editor → tags ?| array['admin','editor']
q, err := query.Parse("tags=admin,editor", query.WithKeyOperator("tags", query.OperatorJIn))

Explicit bracket operators always take priority over WithKeyOperator. If the query contains name[eq]=foo, the eq operator is used regardless of the WithKeyOperator setting.

WithKeyAlias

Rewrites public query keys before parsing. The target can include a fixed operator, which is useful for exposing range filters without bracket syntax:

q, err := query.Parse("insert_from=2026-01-01&insert_to=2026-01-31",
    query.WithKeyAlias(
        "insert_from", "insert_date[gte]",
        "insert_to", "insert_date[lte]",
    ),
)
// Result: insert_date[gte]=2026-01-01&insert_date[lte]=2026-01-31

When an alias target contains an operator, that operator is fixed. For example, insert_from[lt]=2026-01-01 still becomes insert_date[gte]=2026-01-01. If the exact target expression is also present, its value takes priority over the alias.

WithKeyValueTransform

Sets a value transform function for a specific key. The function is applied to the raw value string before parsing, regardless of whether a bracket operator is present.

// Wrap value with % for LIKE queries
q, err := query.Parse("name=foo",
    query.WithKeyOperator("name", query.OperatorLike),
    query.WithKeyValueTransform("name", func(v string) string {
        return "%" + v + "%"
    }),
)
// Result: name[like]=%foo%
// SQL:    name LIKE '%foo%'

This also works with explicit bracket operators:

// name[ilike]=foo → name[ilike]=%foo%
q, err := query.Parse("name[ilike]=foo",
    query.WithKeyValueTransform("name", func(v string) string {
        return "%" + v + "%"
    }),
)

Validation

query.WithField is used to validate the field names.

  • WithNotIn is used to validate the field names that are not allowed.
  • WithIn is used to validate the field names that are allowed.
  • WithNotAllowed is used to validate the field names totally not allowed.

query.WithValues is used to validate the values of the fields.

  • WithNotIn is used to validate the values that are not allowed.
  • WithIn is used to validate the values that are allowed.
  • WithNotAllowed is used to validate the values totally not allowed.

query.WithValue is used to validate the values of the fields.

  • WithRequired is used to validate the values that are required.
  • WithNotEmpty is used to validate the values that are not empty.
  • WithNotIn is used to validate the values that are not allowed.
  • WithIn is used to validate the values that are allowed.
  • WithNotAllowed is used to validate the value that are not allowed.
  • WithOperator is used to validate the operator that is allowed.
  • WithNotOperator is used to validate the operator that is not allowed.
  • WithMax is used to validate the maximum of value, value must be a number.
  • WithMin is used to validate the minimum of value, value must be a number.

For per-field value validation, WithIn, WithNotIn, WithMin, and WithMax check non-null eq and in comparisons. Combine them with WithOperator when other operators must be rejected. These value checks do not replace application authorization or trusted access-control predicates.

query.WithOffset is used to validate the offset value.

  • WithMax is used to validate the maximum of offset, value must be a number.
  • WithMin is used to validate the minimum of offset, value must be a number.
  • WithNotAllowed is used to validate the offset value that are not allowed.

query.WithLimit is used to validate the limit value.

  • WithMax is used to validate the maximum of limit, value must be a number.
  • WithMin is used to validate the minimum of limit, value must be a number.
  • WithNotAllowed is used to validate the limit value that are not allowed.

query.WithSort is used to validate the sort value.

  • WithNotIn is used to validate the sort value that are not allowed.
  • WithIn is used to validate the sort value that are allowed.
  • WithNotAllowed is used to validate the sort value that are not allowed.

Example of validation:

validator := query.NewValidator(
    WithValue("member", query.WithRequired(), query.WithNotIn("O", "P", "S")),
    WithValues(query.WithIn("age", "test", "member")),
    WithValue("age", query.WithOperator(OperatorEq), query.WithNotOperator(OperatorIn)),
    WithField(query.WithNotAllowed()),
)

// after that use it to validate
err := validator.Validate(query)
if err != nil {
    // handle error
}

// or pass it when parsing
query, err := query.ParseWithValidator(rawQuery, validator)
// ...

About

query parser statements

Topics

Resources

Stars

0 stars

Watchers

3 watching

Forks

Used by

Contributors

Languages