Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
* added support for Go modules
* added support for SQL

## [0.4.0] - 2021-08-01
### Changed
Expand Down
65 changes: 53 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
go-rsql
=======
# go-rsql

## overview

# overview
RSQL is a query language for parametrized filtering of entries in APIs.
It is based on FIQL (Feed Item Query Language) – an URI-friendly syntax for expressing filters across the entries in an Atom Feed.
FIQL is great for use in URI; there are no unsafe characters, so URL encoding is not required.
Expand All @@ -11,9 +11,10 @@ so RSQL also provides a friendlier syntax for logical operators and some compari
This is a small RSQL helper library, written in golang.
It can be used to parse a RSQL string and turn it into a database query string.

Currently, only mongodb is supported out of the box (however it is very easy to extend the parser if needed).
Currently, mongodb and SQL is supported out of the box. It is very easy to create a new parser if needed.

## basic usage (mongodb)

# basic usage
```go
package main

Expand All @@ -38,9 +39,46 @@ func main(){
}
```

## basic usage (SQL)

```go
package main

import (
"log"

"github.com/rbicker/go-rsql"
)

func main() {
parser, err := rsql.NewParser(rsql.SQL())
if err != nil {
log.Fatalf("error while creating parser: %s", err)
}
s := `status=="A",qty=lt=30`
res, err := parser.Process(s)
if err != nil {
log.Fatalf("error while parsing: %s", err)
}
log.Println(res)
// status = "A" OR qty < 30

// example use
qry := "SELECT * FROM books"
// This example is simplified. but in a real world example you may not
// have a url query string and res will be empty.
if res != "" {
qry += " WHERE " + res
}

log.Println(qry)
// SELECT * FROM books WHERE status = "A" OR qty < 30
}
```

## supported operators

# supported operators
The library supports the following basic operators by default:
go-rsql supports the following basic operators by default:

| Basic Operator | Description |
|----------------|---------------------|
Expand All @@ -53,17 +91,18 @@ The library supports the following basic operators by default:
| =in= | In |
| =out= | Not in |

The following table lists two joining operators:
go-rsql supports two joining operators:

| Composite Operator | Description |
|--------------------|---------------------|
| ; | Logical AND |
| , | Logical OR |


# advanced usage
## advanced usage

### custom operators

## custom operators
The library makes it easy to define custom operators:
```go
package main
Expand Down Expand Up @@ -126,7 +165,8 @@ func main(){
}
```

## transform keys
### transform keys

If your database key naming scheme is different from the one used in your rsql statements, you can add functions to transform your keys.

```go
Expand Down Expand Up @@ -156,7 +196,8 @@ func main() {
}
```

## define allowed or forbidden keys
### define allowed or forbidden keys

```go
package main

Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/rbicker/go-rsql

go 1.24
103 changes: 103 additions & 0 deletions rsql.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,109 @@ func Mongo() func(parser *Parser) error {
}
}

// SQL adds the default SQL operators to the parser
func SQL() func(parser *Parser) error {
return func(parser *Parser) error {
// operators
var operators = []Operator{
{
"==",
func(key, value string) string {
return fmt.Sprintf(`%s = %s`, key, value)
},
},
{
"!=",
func(key, value string) string {
return fmt.Sprintf(`%s <> %s`, key, value)
},
},
{
"=gt=",
func(key, value string) string {
return fmt.Sprintf(`%s > %s`, key, value)
},
},
{
"=ge=",
func(key, value string) string {
return fmt.Sprintf(`%s >= %s`, key, value)
},
},
{
"=lt=",
func(key, value string) string {
return fmt.Sprintf(`%s < %s`, key, value)
},
},
{
"=le=",
func(key, value string) string {
return fmt.Sprintf(`%s <= %s`, key, value)
},
},
{
"=in=",
func(key, value string) string {
// remove parentheses
value = value[1 : len(value)-1]
return fmt.Sprintf(`%s IN (%s)`, key, value)
},
},
{
"=out=",
func(key, value string) string {
// remove parentheses
value = value[1 : len(value)-1]
return fmt.Sprintf(`%s NOT IN (%s)`, key, value)
},
},
}
parser.operators = append(parser.operators, operators...)
// AND formatter
parser.andFormatter = func(ss []string) string {
if len(ss) == 0 {
return ""
}
if len(ss) == 1 {
return ss[0]
}

// Add parentheses around expressions that contain AND or OR
for i, s := range ss {
if strings.Contains(s, " AND ") || strings.Contains(s, " OR ") {
if !strings.HasPrefix(s, "(") || !strings.HasSuffix(s, ")") {
ss[i] = "(" + s + ")"
}
}
}

return strings.Join(ss, " AND ")
}
// OR formatter
parser.orFormatter = func(ss []string) string {
if len(ss) == 0 {
return ""
}
if len(ss) == 1 {
return ss[0]
}

// Add parentheses around expressions that contain AND or OR
for i, s := range ss {
if strings.Contains(s, " AND ") || strings.Contains(s, " OR ") {
if !strings.HasPrefix(s, "(") || !strings.HasSuffix(s, ")") {
ss[i] = "(" + s + ")"
}
}
}

return strings.Join(ss, " OR ")
}
return nil
}
}

// WithOperator adds custom operators to the parser
func WithOperators(operators ...Operator) func(parser *Parser) error {
return func(parser *Parser) error {
Expand Down
Loading