gorql

package module
v0.1.15 Latest Latest
Warning

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

Go to latest
Published: Mar 21, 2024 License: MIT Imports: 16 Imported by: 0

README

GORQL

Overview

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.

Installation

go get -u github.com/douglaslim/gorql

Getting Started

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:

  1. int (8,16,32,64) - Round number
  2. uint (8,16,32,64) - Round number and greater than or equal to 0
  3. float (32,64): - Number
  4. bool - Boolean
  5. string - String
  6. time.Time, and other types that convertible to time.Time - The default layout is time.RFC3339 format (JS format), and parsable to time.Time. It's possible to override the time.Time layout format with custom one. You can either use one of the standard layouts in the time 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.

RQL Rules

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

Drivers

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)

Usage

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

Contributions are welcome! If you encounter any bugs, issues, or have feature requests, please open an issue. Pull requests are also appreciated.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	DefaultTagName  = "rql"
	DefaultFieldSep = "_"
	DefaultLimit    = 25
	DefaultMaxLimit = 100
)
View Source
const (
	OffsetOp = "offset"
	LimitOp  = "limit"
	SelectOp = "select"
	SortOp   = "sort"
)

Variables

View Source
var (
	ErrBlocValue                 = errors.New("bloc is a value")
	ErrBlocBracket               = errors.New("bloc is a square bracket")
	ErrParenthesisMalformed      = errors.New("parenthesis bloc is malformed")
	ErrUnregonizedBloc           = errors.New("unrecognized bloc")
	ErrInvalidPlacementSqrBrBloc = errors.New("invalid formation of square brackets bloc")
)
View Source
var (
	ReservedRunes = []rune{' ', '&', '(', ')', ',', '=', '/', ';', '?', '|', '[', ']'}
)

Functions

func Column

func Column(s string) string

Column is the default function that converts field name into a database column. It used to convert the struct fields into their database names. For example:

Username => username
FullName => full_name
HTTPCode => http_code

func IsDigit

func IsDigit(ch rune) bool

IsDigit returns true if the rune is a digit.

func IsLetter

func IsLetter(ch rune) bool

IsLetter returns true if the rune is a letter.

func IsValidField added in v0.1.3

func IsValidField(s string) bool

Types

type Config

type Config struct {
	// TagName is an optional tag name for configuration. t defaults to "rql".
	TagName string
	// Model is the resource definition. The parser is configured based on its definition.
	// For example, given the following struct definition:
	//
	//	type User struct {
	//		Age	 int	`rql:"filter,sort"`
	// 		Name string	`rql:"filter"`
	// 	}
	//
	// In order to create a parser for the given resource, you will do it like so:
	//
	//	var QueryParser = rql.NewParser(&rql.Config{
	// 		Model: User{},
	// 	})
	//
	Model interface{}
	// FieldSep is the separator for nested fields in a struct. For example, given the following struct:
	//
	//	type User struct {
	// 		Name 	string	`rql:"filter"`
	//		Address	struct {
	//			City string `rql:"filter"“
	//		}
	// 	}
	//
	// We assume the schema for this struct contains a column named "address_city". Therefore, the default
	// separator is underscore ("_"). But, you can change it to "." for convenience or readability reasons.
	// The parser will automatically convert it to underscore ("_"). If you want to control the name of
	// the column, use the "column" option in the struct definition. For example:
	//
	//	type User struct {
	// 		Name 	string	`rql:"filter,column=full_name"`
	// 	}
	//
	FieldSep string
	// ColumnFn is the function that translate the struct field string into a table column.
	// For example, given the following fields and their column names:
	//
	//	FullName => "full_name"
	// 	HTTPPort => "http_port"
	//
	// It is preferred that you will follow the same convention that your ORM or other DB helper use.
	// For example, If you are using `gorm` you want to se this option like this:
	//
	//	var QueryParser = rql.MustNewParser(
	// 		ColumnFn: gorm.ToDBName,
	// 	})
	//
	ColumnFn func(string) string
	// Log the logging function used to log debug information in the initialization of the parser.
	// It defaults `to log.Printf`.
	Log func(string, ...interface{})
	// DefaultLimit is the default value for the `Limit` field that returns when no limit supplied by the caller.
	// It defaults to 25.
	DefaultLimit int
	// LimitMaxValue is the upper boundary for the limit field. User will get an error if the given value is greater
	// than this value. It defaults to 100.
	LimitMaxValue int
}

Config is the configuration for the parser.

type Parser

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

func NewParser

func NewParser(c *Config) (*Parser, error)

func (*Parser) GetFieldValidationFunc

func (p *Parser) GetFieldValidationFunc() ValidationFunc

func (*Parser) Parse

func (p *Parser) Parse(r io.Reader) (root *RqlRootNode, err error)

Parse constructs an AST for code transformation

func (*Parser) ParseURL

func (p *Parser) ParseURL(q url.Values) (root *RqlRootNode, err error)

ParseURL constructs an AST from url.Values for code transformation

type RqlNode

type RqlNode struct {
	Op   string
	Args []interface{}
}

type RqlRootNode

type RqlRootNode struct {
	Node *RqlNode
	// contains filtered or unexported fields
}

func (*RqlRootNode) Limit

func (r *RqlRootNode) Limit() string

func (*RqlRootNode) Offset

func (r *RqlRootNode) Offset() string

func (*RqlRootNode) Sort

func (r *RqlRootNode) Sort() []Sort

type Scanner

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

func NewScanner

func NewScanner() *Scanner

func (*Scanner) Scan

func (s *Scanner) Scan(r io.Reader) (out []TokenString, err error)

Scan returns the next token and literal value.

func (*Scanner) ScanToken

func (s *Scanner) ScanToken() (tok Token, lit string)

type Sort

type Sort struct {
	By   string
	Desc bool
}

type Token

type Token int
const (
	// Special tokens
	Illegal Token = iota
	Eof

	// Literals
	Ident // fields, function names

	// Reserved characters
	Space                //
	Ampersand            // &
	OpeningParenthesis   // (
	ClosingParenthesis   // )
	Comma                // ,
	EqualSign            // =
	Slash                // /
	SemiColon            // ;
	QuestionMark         // ?
	AtSymbol             // @
	Pipe                 // |
	OpeningSquareBracket // [
	ClosingSquareBracket // ]

	// Keywords
	And
	Or
	Equal
	Greater
	GreaterOrEqual
	Lower
	LowerOrEqual
	NotEqual
)

type TokenBloc

type TokenBloc []TokenString

func (TokenBloc) String

func (tb TokenBloc) String() (s string)

String print the TokenBloc value for test purpose only

type TokenString

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

func NewTokenString

func NewTokenString(t Token, s string) TokenString

type ValidationFunc

type ValidationFunc func(*RqlNode) error

Directories

Path Synopsis
pkg

Jump to

Keyboard shortcuts

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