A lightweight Go package for constructing Resource Query Language (RQL) queries, commonly used for filtering, sorting, and querying data. It provides a simple and intuitive API for building RQL queries dynamically.
go get -u github.com/douglaslim/gorql
In order to start using rql, you can optionally configure the parser. Let's go over a basic example of how to do this.
To build a parser, use gorql.NewParser(*gorql.Config)
.
type User struct {
ID uint `rql:"filter,sort"`
Admin bool `rql:"filter"`
Name string `rql:"filter"`
AddressName string `rql:"filter"`
CreatedAt time.Time `rql:"filter,sort"`
}
var Parser = gorql.NewParser(&gorql.Config{
// User if the resource we want to query.
Model: User{},
// Use your own custom logger. This logger is used only in the building stage.
Log: logrus.Printf,
// Default limit returned by the `Parse` function if no limit provided by the user.
DefaultLimit: 100,
// Accept only requests that pass limit value that is greater than or equal to 200.
LimitMaxValue: 200,
})
gorql uses reflection in the build process to detect the type of each field, and create a set of validation rules for each one. If one of the validation rules fails or rql encounters an unknown field, it returns an informative error to the user. Don't worry about the usage of reflection, it happens only once when you build the parser. Let's go over the validation rules:
int
(8,16,32,64) - Round numberuint
(8,16,32,64) - Round number and greater than or equal to 0float
(32,64): - Numberbool
- Booleanstring
- Stringtime.Time
, and other types that convertible totime.Time
- The default layout is time.RFC3339 format (JS format), and parsable totime.Time
. It's possible to override thetime.Time
layout format with custom one. You can either use one of the standard layouts in thetime
package, or use a custom one. For example:type User struct { T1 time.Time `rql:"filter"` // time.RFC3339 T2 time.Time `rql:"filter,layout=UnixDate"` // time.UnixDate T3 time.Time `rql:"filter,layout=2006-01-02 15:04"` // 2006-01-02 15:04 (custom) }
Note that all rules are applied to pointers as well. It means, if you have a field Name *string
in your struct, we still use the string validation rule for it.
Here is a definition of the common operators:
- and(<query>,<query>,...) - Applies all the given queries
- or(<query>,<query>,...) - The union of the given queries
- in(<property>,<array-of-values>) - Filters for objects where the specified property's value is in the provided array
- like(<property>,<value>) - Filters records where property contains value as a substring. This applies to strings or arrays of strings.
- match(<property>,<value | expression>) - Filters for objects where the specified property's value is an array and the array contains any value that equals the provided value or satisfies the provided expression.
- eq(<property>,<value>) - Filters for objects where the specified property's value is equal to the provided value
- lt(<property>,<value>) - Filters for objects where the specified property's value is less than the provided value
- le(<property>,<value>) - Filters for objects where the specified property's value is less than or equal to the provided value
- gt(<property>,<value>) - Filters for objects where the specified property's value is greater than the provided value
- ge(<property>,<value>) - Filters for objects where the specified property's value is greater than or equal to the provided value
- not(<query>,<query>,...) - Filters for objects where the results of the query that is passed to this operator is inverted
There are some special operators defined as well and their definition is listed as follows:
- $sort=<+|-><property>,... - Sorts by the given property in order specified by the prefix (+ for ascending, - for descending)
- $select=<property>,<property>,... - Trims each object down to the set of properties defined in the arguments
- $limit=<property> - Returns the given range of objects from the result set
- $offset=<property> - Determines the starting point for fetching data within a result set
gorql
currently supports the following drivers:
- SQL: Generate SQL queries for SQL databases.
func NewSqlTranslator(r *gorql.RqlRootNode) (st *Translator)
- MongoDB: Generate MongoDB queries for MongoDB databases. Depending on the MongoDB library (mongo-driver,mgo) you are using, you would need to unmarshal the JSON string of the MongoDB query.
func NewMongoTranslator(r *gorql.RqlRootNode) (mt *Translator)
- Cosmos: Generate CosmosDB queries for Azure Cosmos databases.
func NewCosmosTranslator(r *gorql.RqlRootNode) (ct *Translator)
Here's a quick example of how to use gorql to construct an RQL query and translate to mongo query:
package main
import (
"fmt"
"gorql"
"gorql/pkg/driver/mongo"
"strings"
)
func main() {
p, err := gorql.NewParser(nil)
if err != nil {
panic(fmt.Sprintf("New parser error :%s", err))
}
query := `and(eq(foo,3),lt(price,10))&$sort=+price&$limit=10&$offset=20`
rqlNode, err := p.Parse(strings.NewReader(query))
if err != nil {
panic(err)
}
mongoTranslator := mongo.NewMongoTranslator(rqlNode)
w, err := mongoTranslator.Where()
if err != nil {
panic(err)
}
fmt.Println(w) // {"$and": [{"$and": [{"foo": {"$eq": "3"}}, {"price": {"$lt": "10"}}]}]}
sort := mongoTranslator.Sort()
if err != nil {
panic(err)
}
fmt.Println(sort) // {"$sort": {"price": 1}}
limit := mongoTranslator.Limit()
if err != nil {
panic(err)
}
fmt.Println(limit) // {"$limit": 10}
offset := mongoTranslator.Offset()
if err != nil {
panic(err)
}
fmt.Println(offset) // {"$skip": 20}
}
Contributions are welcome! If you encounter any bugs, issues, or have feature requests, please open an issue. Pull requests are also appreciated.
This project is licensed under the MIT License. See the LICENSE file for details.