Query Parser (QP) is a Go library designed to streamline the creation of dynamic SQL queries for web applications. It provides an easy-to-use API for handling filtering through GET queries and manages translations and validations for user inputs.
To install the QP library, run the following command:
go get -u github.com/nex-gen-tech/qp
The inspiration for this library came from the article "REST API Design: Filtering, Sorting, and Pagination." The concepts in the article are useful for projects that involve lists with various filtering options.
Refer to the examples
folder and the test files for more comprehensive examples.
package main
import (
"errors"
"fmt"
"net/url"
"github.com/nex-gen-tech/qp"
)
func main() {
url, _ := url.Parse("http://localhost/?sort=name,-id&limit=10&id=1&i[eq]=5&s[eq]=one&email[like]=*tim*|name[like]=*tim*")
q, _ := qp.NewParse(url.Query(), qp.Validations{
"limit:required": qp.MinMax(10, 100), // limit must be present and between 10 and 100
"sort": qp.In("id", "name"), // sort could be "id" or "name"
"s": qp.In("one", "two"), // filter: s - string with equal comparison
"id:int": nil, // filter: id is an integer without additional validation
"i:int": func(value interface{}) error { // custom validation function for "i"
if value.(int) > 1 && value.(int) < 10 {
return nil
}
return errors.New("i: must be greater than 1 and less than 10")
},
"email": nil,
"name": nil,
})
fmt.Println(q.SQL("table")) // SELECT * FROM table WHERE id = ? AND i = ? AND s = ? AND (email LIKE ? OR name LIKE ?) ORDER BY name, id DESC LIMIT 10
fmt.Println(q.Where()) // id = ? AND i = ? AND s = ? AND (email LIKE ? OR name LIKE ?)
fmt.Println(q.Args()) // [1 5 one %tim% %tim%]
q.AddValidation("fields", rqp.In("id", "name"))
q.SetUrlString("http://localhost/?fields=id,name&limit=10")
q.Parse()
fmt.Println(q.SQL("table")) // SELECT id, name FROM table ORDER BY id LIMIT 10
fmt.Println(q.Select()) // id, name
fmt.Println(q.Args()) // []
}
fields
: Specifies the fields for the SELECT clause, separated by commas (","). For example,&fields=id,name
. If not provided, "*" is used by default. To use this filter, define a validation function. Userqp.In("id", "name")
to limit the fields in your query.sort
: Specifies the sorting fields list, separated by commas (","). This field must be validated. A +/- prefix indicates ASC/DESC sorting. For example,&sort=+id,-name
results inORDER BY id, name DESC
. Validate fields usingrqp.In("id", "name")
.limit
: Specifies the limit for the LIMIT clause. The value should be greater than 0 by default. Validation forlimit
is optional. Userqp.Max(100)
to set an upper threshold.offset
: Specifies the offset for the OFFSET clause. The value should be greater than or equal to 0 by default. Validation foroffset
is optional.
?fields=id,name&sort=-created_at&limit=20&offset=5
?sort=name,-id&limit=50&status[eq]=active&category[like]=tech
?fields=product_id,product_name&sort=price&price[gt]=100&in_stock[eq]=true
:required
: Indicates that the parameter is required and must be present in the query string. Raises an error if not found.:int
: Specifies that the parameter must be convertible to an integer type. Raises an error if not.:bool
: Specifies that the parameter must be convertible to a boolean type. Raises an error if not.
string
: The default type for all filters unless specified otherwise. Can be compared usingeq, ne, gt, lt, gte, lte, like, ilike, nlike, nilike, in, nin, is, not
methods. (nlike, nilike
representNOT LIKE, NOT ILIKE
;in, nin
representIN, NOT IN
; andis, not
are for NULL comparisons:IS NULL, IS NOT NULL
.)int
: Integer type. Must be specified with the tag ":int". Can be compared usingeq, ne, gt, lt, gte, lte, in, nin
methods.bool
: Boolean type. Must be specified with the tag ":bool". Can be compared using theeq
method.
Here's an example demonstrating how to handle date values:
import (
"fmt"
"net/url"
validation "github.com/go-ozzo/ozzo-validation/v4"
)
func main() {
url, _ := url.Parse("http://localhost/?created_at[eq]=2020-10-02")
q, _ := rqp.NewParse(url.Query(), rqp.Validations{
"created_at": func(v interface{}) error {
s, ok := v.(string)
if !ok {
return rqp.ErrBadFormat
}
return validation.Validate(s, validation.Date("2006-01-02"))
},
})
q.ReplaceNames(rqp.Replacer{"created_at": "DATE(created_at)"})
fmt.Println(q.SQL("table")) // SELECT * FROM table WHERE DATE(created_at) = ?
}
QP offers advanced features like custom validation functions, handling multiple OR conditions, and more.
You can define custom validation functions to perform specific checks on query parameters.
func validateAge(value interface{}) error {
age, ok := value.(int)
if !ok {
return rqp.ErrBadFormat
}
if age < 18 {
return errors.New("age must be at least 18")
}
return nil
}
QP supports handling multiple OR conditions within filters.
q.AddORFilters(func(query *rqp.Query) {
query.AddFilter("name", rqp.LIKE, "John")
query.AddFilter("email", rqp.LIKE, "[email protected]")
})
You can replace field names in filters, fields, and sorting using the ReplaceNames
method.
q.ReplaceNames(rqp.Replacer{"created_at": "DATE(created_at)"})
This project is licensed under the MIT License.
Contributions are welcome! Please open a pull request or issue on the GitHub repository to suggest improvements or report any issues.
Special thanks to the contributors and supporters of this project.