This library converts a JSON object into a SQL WHERE clause.
- Converts JSON to SQL.
- Supports nested conditions.
- Supports multiple database dialects (MySQL and PostgreSQL).
go get github.com/xbsoftware/querysql@v2.0.0
func GetSQL(data Filter, config *SQLConfig, dbArr ...DBDriver) (string, []interface{}, error)
data
: TheFilter
object, which can be created from JSON usingFromJSON
.config
: An optionalSQLConfig
for advanced configuration.dbArr
: An optionalDBDriver
for database-specific SQL generation. Defaults toMySQL{}
.
The Filter
struct is the main data structure for building queries.
type Filter struct {
Glue string `json:"glue"`
Field string `json:"field"`
Type string `json:"type"`
Predicate string `json:"predicate"`
Filter string `json:"filter"`
Value interface{} `json:"value"`
Includes []interface{} `json:"includes"`
Rules []Filter `json:"rules"`
}
The SQLConfig
struct allows you to customize the behavior of GetSQL
.
type SQLConfig struct {
WhitelistFunc CheckFunction
Whitelist map[string]bool
Operations map[string]CustomOperation
Predicates map[string]CustomPredicate
}
Whitelist
andWhitelistFunc
: Restrict which fields can be used in the query.Operations
: Define custom operations.Predicates
: Define custom predicates.
A CustomPredicate
allows you to modify the field name in the SQL query, for example, by wrapping it in a function call.
Example:
You can define a custom predicate to extract the year from a date field.
jsonString := `{
"field": "created_at",
"predicate": "year",
"filter": "equal",
"value": 2024
}`
filter, _ := querysql.FromJSON([]byte(jsonString))
config := &querysql.SQLConfig{
Predicates: map[string]querysql.CustomPredicate{
"year": func(n string, p string) (string, error) {
return fmt.Sprintf("YEAR(%s)", n), nil
},
},
}
sql, values, _ := querysql.GetSQL(filter, config)
A CustomOperation
allows you to define a new, custom filter operation.
Example:
You can define a custom is_empty
operation to check for empty strings.
jsonString := `{
"field": "name",
"filter": "is_empty"
}`
filter, _ := querysql.FromJSON([]byte(jsonString))
config := &querysql.SQLConfig{
Operations: map[string]querysql.CustomOperation{
"is_empty": func(field string, filter string, values []interface{}) (string, []interface{}, error) {
return fmt.Sprintf("%s = ''", field), []interface{}{}, nil
},
},
}
sql, values, _ := querysql.GetSQL(filter, config)
Here is a basic example of how to use the library:
package main
import (
"fmt"
"github.com/xbsoftware/querysql"
)
func main() {
jsonString := `{
"glue": "and",
"rules": [{
"field": "age",
"filter": "less",
"value": 42
}, {
"field": "region",
"includes": [1, 2, 6]
}]
}`
filter, err := querysql.FromJSON([]byte(jsonString))
if err != nil {
panic(err)
}
// Using the default MySQL driver
sql, values, err := querysql.GetSQL(filter, nil)
if err != nil {
panic(err)
}
fmt.Println(sql) // ( age < ? AND region IN(?,?,?) )
fmt.Println(values) // [42 1 2 6]
// Using the PostgreSQL driver
sql, values, err = querysql.GetSQL(filter, nil, &querysql.PostgreSQL{})
if err != nil {
panic(err)
}
fmt.Println(sql) // ( age < $1 AND region IN($2,$3,$4) )
fmt.Println(values) // [42 1 2 6]
}
The V2 release introduces several breaking changes. Here's how to migrate your code.
The Filter
struct has been redesigned.
V1:
type Filter struct {
Glue string `json:"glue"`
Field string `json:"field"`
Condition Condition `json:"condition"`
Includes []interface{} `json:"includes"`
Kids []Filter `json:"rules"`
}
V2:
type Filter struct {
Glue string `json:"glue"`
Field string `json:"field"`
Type string `json:"type"`
Predicate string `json:"predicate"`
Filter string `json:"filter"`
Value interface{} `json:"value"`
Includes []interface{} `json:"includes"`
Rules []Filter `json:"rules"`
}
- The
Condition
field has been removed. Its functionality is now handled by theType
,Predicate
,Filter
, andValue
fields. - The
Kids
field has been renamed toRules
.
The CustomPredicate
function signature has been simplified.
V1:
type CustomPredicate func(fieldName string, predicateName string, values []interface{}) (string, []interface{}, error)
V2:
type CustomPredicate func(fieldName string, predicateName string) (string, error)
The values
parameter and return value have been removed.
The SQLConfig
struct now includes a Predicates
map to support custom predicates.
V1:
type SQLConfig struct {
WhitelistFunc CheckFunction
Whitelist map[string]bool
Operations map[string]CustomOperation
}
V2:
type SQLConfig struct {
WhitelistFunc CheckFunction
Whitelist map[string]bool
Operations map[string]CustomOperation
Predicates map[string]CustomPredicate
}
- equal
- notEqual
- contains
- notContains
- lessOrEqual
- greaterOrEqual
- less
- notBetween
- between
- greater
- beginsWith
- notBeginsWith
- endsWith
- notEndsWith
Blocks can be nested as follows:
{
"glue": "and",
"rules": [
ruleA,
{
"glue": "or",
"rules": [
ruleC,
ruleD
]
}
]
}
For these operations, both start
and end
values can be provided.
{
"field": "age",
"filter": "between",
"value": { "start": 10, "end": 99 }
}
If only start
or end
is provided, the operation will change to less
or greater
automatically.