Documentation Index Fetch the complete documentation index at: https://mintlify.com/raystack/salt/llms.txt
Use this file to discover all available pages before exploring further.
The rql (REST Query Language) package provides a powerful query parser for REST APIs, enabling advanced filtering, sorting, pagination, and grouping through JSON query parameters.
Features
Advanced Filtering : Support for multiple operators (eq, neq, like, gt, lt, etc.)
Type Validation : Validates query parameters against struct definitions
Sorting : Multi-field sorting with ascending/descending order
Pagination : Built-in offset and limit support
Search : Fuzzy search across multiple fields
Group By : Result grouping capabilities
Type Safety : Strong typing with struct tags
Installation
go get github.com/raystack/salt/rql
Quick Start
Define Your Model
type Organization struct {
Id int `rql:"name=id,type=number"`
Title string `rql:"name=title,type=string"`
BillingPlanName string `rql:"name=plan_name,type=string"`
MemberCount int `rql:"name=member_count,type=number"`
CreatedAt time . Time `rql:"name=created_at,type=datetime"`
Enabled bool `rql:"name=enabled,type=bool"`
}
Parse and Validate Query
package main
import (
" encoding/json "
" fmt "
" log "
" github.com/raystack/salt/rql "
)
func main () {
queryJSON := `{
"filters": [
{"name": "enabled", "operator": "eq", "value": true},
{"name": "created_at", "operator": "gte", "value": "2025-01-01T00:00:00Z"}
],
"sort": [
{"name": "title", "order": "asc"}
],
"offset": 0,
"limit": 50
}`
query := & rql . Query {}
err := json . Unmarshal ([] byte ( queryJSON ), query )
if err != nil {
log . Fatal ( err )
}
// Validate against model
err = rql . ValidateQuery ( query , Organization {})
if err != nil {
log . Fatal ( "Invalid query:" , err )
}
fmt . Printf ( "Valid query with %d filters \n " , len ( query . Filters ))
}
Core Types
Query
type Query struct {
Filters [] Filter `json:"filters"`
GroupBy [] string `json:"group_by"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Search string `json:"search"`
Sort [] Sort `json:"sort"`
}
Main query structure containing all query parameters.
Filter
type Filter struct {
Name string `json:"name"`
Operator string `json:"operator"`
Value any `json:"value"`
}
Represents a single filter condition.
Sort
type Sort struct {
Name string `json:"name"` // Field name
Order string `json:"order"` // "asc" or "desc"
}
Defines sorting criteria.
Use rql tags to define field properties:
type Model struct {
FieldName Type `rql:"name=api_name,type=datatype"`
}
Parameters:
name: API field name (used in queries)
type: Data type (number, string, datetime, bool)
Example:
type User struct {
ID int `rql:"name=id,type=number"`
Email string `rql:"name=email,type=string"`
CreatedAt time . Time `rql:"name=created_at,type=datetime"`
IsActive bool `rql:"name=is_active,type=bool"`
}
Data Types
Supported data types and their operators:
Number
Type: number
Operators: eq, neq, gt, lt, gte, lte
Age int `rql:"name=age,type=number"`
Query example:
{ "name" : "age" , "operator" : "gte" , "value" : 18 }
String
Type: string
Operators: eq, neq, like, notlike, in, notin, empty, notempty
Name string `rql:"name=name,type=string"`
Query examples:
{ "name" : "name" , "operator" : "like" , "value" : "john" }
{ "name" : "status" , "operator" : "in" , "value" : "active,pending" }
{ "name" : "description" , "operator" : "notempty" , "value" : null }
Boolean
Type: bool
Operators: eq, neq
IsActive bool `rql:"name=is_active,type=bool"`
Query example:
{ "name" : "is_active" , "operator" : "eq" , "value" : true }
DateTime
Type: datetime
Operators: eq, neq, gt, lt, gte, lte
Format: RFC3339 (ISO 8601)
CreatedAt time . Time `rql:"name=created_at,type=datetime"`
Query example:
{ "name" : "created_at" , "operator" : "gte" , "value" : "2025-01-01T00:00:00Z" }
Operators
Operator Description Example eqEquals {"operator": "eq", "value": 10}neqNot equals {"operator": "neq", "value": 0}gtGreater than {"operator": "gt", "value": 100}ltLess than {"operator": "lt", "value": 50}gteGreater than or equal {"operator": "gte", "value": 18}lteLess than or equal {"operator": "lte", "value": 65}likePattern match {"operator": "like", "value": "john"}notlikePattern not match {"operator": "notlike", "value": "test"}inIn list {"operator": "in", "value": "a,b,c"}notinNot in list {"operator": "notin", "value": "x,y"}emptyIs empty/null {"operator": "empty"}notemptyIs not empty {"operator": "notempty"}
Validation
ValidateQuery
func ValidateQuery ( q * Query , checkStruct interface {}) error
Validates a query against a struct definition.
Checks:
Field names exist in struct
Data types match struct definitions
Operators are valid for field types
Sort fields are valid
Group by fields are valid
query := & rql . Query { /* ... */ }
err := rql . ValidateQuery ( query , Organization {})
if err != nil {
// Handle validation error
}
GetDataTypeOfField
func GetDataTypeOfField ( fieldName string , checkStruct interface {}) ( string , error )
Returns the data type of a specific field.
dataType , err := rql . GetDataTypeOfField ( "created_at" , Organization {})
if err != nil {
log . Fatal ( err )
}
fmt . Println ( dataType ) // "datetime"
Complete Example: REST API Handler
package main
import (
" encoding/json "
" net/http "
" time "
" github.com/doug-martin/goqu/v9 "
" github.com/raystack/salt/rql "
)
type Organization struct {
Id int `db:"id" rql:"name=id,type=number"`
Title string `db:"title" rql:"name=title,type=string"`
PlanName string `db:"plan_name" rql:"name=plan_name,type=string"`
MemberCount int `db:"member_count" rql:"name=member_count,type=number"`
CreatedAt time . Time `db:"created_at" rql:"name=created_at,type=datetime"`
Enabled bool `db:"enabled" rql:"name=enabled,type=bool"`
}
func listOrganizations ( w http . ResponseWriter , r * http . Request ) {
// Parse request body
var query rql . Query
if err := json . NewDecoder ( r . Body ). Decode ( & query ); err != nil {
http . Error ( w , "Invalid request" , http . StatusBadRequest )
return
}
// Validate query
if err := rql . ValidateQuery ( & query , Organization {}); err != nil {
http . Error ( w , err . Error (), http . StatusBadRequest )
return
}
// Build SQL query using goqu
sqlQuery := goqu . From ( "organizations" )
// Apply filters
for _ , filter := range query . Filters {
sqlQuery = sqlQuery . Where ( goqu . Ex {
filter . Name : goqu . Op { filter . Operator : filter . Value },
})
}
// Apply search (fuzzy search on multiple columns)
if query . Search != "" {
fuzzyColumns := [] string { "title" , "plan_name" }
expressions := make ([] goqu . Expression , len ( fuzzyColumns ))
for i , col := range fuzzyColumns {
expressions [ i ] = goqu . Ex { col : goqu . Op { "like" : "%" + query . Search + "%" }}
}
sqlQuery = sqlQuery . Where ( goqu . Or ( expressions ... ))
}
// Apply sorting
for _ , sort := range query . Sort {
if sort . Order == "asc" {
sqlQuery = sqlQuery . Order ( goqu . C ( sort . Name ). Asc ())
} else {
sqlQuery = sqlQuery . Order ( goqu . C ( sort . Name ). Desc ())
}
}
// Apply pagination
sqlQuery = sqlQuery . Offset ( uint ( query . Offset )). Limit ( uint ( query . Limit ))
// Execute query
sql , args , _ := sqlQuery . ToSQL ()
// ... execute SQL and return results
w . Header (). Set ( "Content-Type" , "application/json" )
json . NewEncoder ( w ). Encode ( map [ string ] interface {}{
"query" : sql ,
"args" : args ,
})
}
func main () {
http . HandleFunc ( "/api/organizations" , listOrganizations )
http . ListenAndServe ( ":8080" , nil )
}
Query Examples
Basic Filtering
{
"filters" : [
{ "name" : "enabled" , "operator" : "eq" , "value" : true },
{ "name" : "member_count" , "operator" : "gte" , "value" : 10 }
],
"limit" : 50 ,
"offset" : 0
}
With Sorting
{
"filters" : [
{ "name" : "plan_name" , "operator" : "eq" , "value" : "premium" }
],
"sort" : [
{ "name" : "created_at" , "order" : "desc" },
{ "name" : "title" , "order" : "asc" }
],
"limit" : 20
}
With Search
{
"search" : "tech" ,
"filters" : [
{ "name" : "enabled" , "operator" : "eq" , "value" : true }
],
"limit" : 10
}
Date Range Query
{
"filters" : [
{ "name" : "created_at" , "operator" : "gte" , "value" : "2025-01-01T00:00:00Z" },
{ "name" : "created_at" , "operator" : "lt" , "value" : "2025-12-31T23:59:59Z" }
]
}
Complex Query
{
"filters" : [
{ "name" : "enabled" , "operator" : "eq" , "value" : true },
{ "name" : "plan_name" , "operator" : "in" , "value" : "premium,enterprise" },
{ "name" : "member_count" , "operator" : "gte" , "value" : 5 },
{ "name" : "title" , "operator" : "like" , "value" : "tech" }
],
"sort" : [
{ "name" : "member_count" , "order" : "desc" }
],
"offset" : 20 ,
"limit" : 10 ,
"search" : "startup"
}
Best Practices
Always validate user queries before execution: if err := rql . ValidateQuery ( query , Model {}); err != nil {
return http . StatusBadRequest , err
}
Protect against large queries: if query . Limit == 0 || query . Limit > 100 {
query . Limit = 50 // Default
}
Use Struct Tags Consistently
Document Supported Fields
Provide API documentation showing:
Available fields for filtering
Supported operators per field
Valid sort fields