DyRe is a interpreted request builder for a middleware service. The intent of DyRe is to help automate selections of multiple fields without removing any functionality for handling specific queries. DyRe is not a full query language and is not trying to be one. No two APIs are the same and it is impossible to know what changes are to come, so flexibility favored over ease of use.
Example API request
curl http://localhost:8080/Customers?Query=CustomerID:Active:==True
First you will need JSON file to build with. The name of the request is the thing being called. Fields are any independent SQL field on a table. Fields can be a string or an object with multiple params. DyRe is type aware and will reject certain unmatching typed requests. If a type is not declared make sure to setup a default type that works for you. Defaults to nullable = true and type = string
[
{
"name": "Customers",
"tableName": "Customers",
"schemaName": "dbo",
"fields": [
{
"name": "CustomerID"
"nullable" : false
},
"Name",
{
"name": "Active"
"type": "bool"
},
{
"name": "CreateDate"
"type": "date"
},
{
"name": "CustomerNumber"
"type": "int"
},
"Phone"
]
}
]
Writing a query statement calls column names then expressions.
A field is called by its name with a colon following the name.
fieldName:
Multiple fields can be called in sequence. DyRe will respect the order in which fields were called. If the same field is called twice, it's ordered in the last position it was called.
CustomerID:Name:Active:
Columns can use additional expressions for filtering. A boolean style expression can be given with a semicolon for the construction of a where statement.
CustomerNumber: > 100;
Active: == FALSE;
CustomerNumber: > 100 AND < 200;
Boolean expressions include:
- ==
- !=
- >
- <
- >=
- <=
- AND or && (keep in mind & is a reserved character for query params)
- OR or ||
When a conditional expression is given as prefix DyRe assumes you are referencing the column as the other part of the expression. If you want to format your expression with the prefix you can declare the '@' for reference to the column name.
CustomersNumber: > 200;
# Is the same as
CustomersNumber: @ > 200;
# Is the same as
CustomersNumber: 200 < @;
In any expression a column can be provided by using the @()
function
In the example
@('CustomerNumber') > 200;
would provide the same where condition on the SQL statement but exclude it from the result. Note, that columns are not required for an expression to be provided.
Also, If a SQL statement has multiple column conditions then independent SQL calls will be needed.
@('CustomerNumber') > 200 OR @('Balance') > 0;
Expressions can include builtin function calls for specific handling of fields.
For example datepart('year',@)
will return the year of a date.
CreateDate: datepart('year',@) > 2024
Expressions and can be given in a row for the use of multiple expressions including function calls.
Name: len(@) > 3; != NULL;
Columns can be represented as functions by using a :
at the end similar to how column names are called.
The most common column call is AS():
. This represents the alias call AS that is often used in select statements.
AS('allTrue', true)):
Expression functions can be used inside to provide a modified output.
In the example below datepart()
is being called similar to how it's used in SQL to modify the output of the expression statement
AS('year', datepart('year', @('CreateDate'))): > 2024
Warning: AS(): expression;
will wrap alias select statement to make a where statement possible. Avoid this kind of expression when possible.
Multiple fields and expression can be called. Expressions index of the most recent field called for inference.
CustomerID:CreateDate: > date('2025/04/03');Active: != NULL; == FASLE;
Grouped table results are similar to how column functions are called. In the example below grouping on the Active column returns the column as expected.
GROUP('Active'):
Grouping using an expression is possible when GROUP is provided with two arguments. The expression will be replicated in SQL to the Having statement to provided conditional outputs.
GROUP('year', datepart('year', @('createDate'))): > 2024;
Standard grouping functions such as SUM, MIN, MAX, etc..
GROUP('ID'):SUM('SumBalance', @('Balance')):
Warn: Grouping Functions cannot be mixed with regular column functions or expressions.
For a comprehensive list of all available operators and functions in DyRe, please refer to the operators documentation.
Starting an example server. You can opt for a global variable or pass it in through functions.
// Global Var for fetching request info
var Re map[string]dyre.DyRe_Request
func main() {
var dyre_err error
Re, dyre_err = dyre.Init("./dyre.json")
if dyre_err != nil {
log.Panicf("dyre init failed: %v", dyre_err)
}
}
get all you params then check the values against the response. Once a response has been validated for fields and groups its pretty easy to handle the rest.
func getCustomers(c *ex.Context) {
query_string, ok := c.GetQuery("Query")
if !ok {
query_string = "CustomerID:Active:==True"
}
q, err := Re.Request('Customers', query_string)
if err != nil {
g.String(500, "Failed to initialize request")
return
}
sql, err := q.EvaluateQuery()
if err != nil {
g.String(500, "Failed to build request")
return
}
table, err := read_db(sql)
if err != nil {
g.String(500, "Failed to make request")
return
}
output := make(map[string]any)
headers := valid.FieldNames()
output["Headers"] = headers
output["Table"] = table
c.JSON(200, output)
return
}
Joining tables as requests is possible in DyRe allowing for powerful queries from the front end. Each tables query is made separately so they can either be query parameters or post parameters if preferred. If a field is declared in the joined table it will be included in the result unless excluded. The top level tables fields take precedence then joined tables fields are added after.
func getCustomersWithBiling(c *ex.Context) {
query_string, ok := c.GetQuery("Customers")
if !ok {
query_string = "CustomerID:Active:==True"
}
billing_string, ok := c.GetQuery("Billing")
if !ok {
query_string = "CustomerID:Balance: > 0"
}
q, err := Re.Request('Customers', query_string)
if err != nil {
g.String(500, "Failed to initialize request")
return
}
_, err := q.INNERJOIN("Billing").ON("CustomerID","CustomerID").Query(billing_string)
if err != nil {
g.String(500, "Failed to initialize request")
return
}
sql, err := q.EvaluateQuery()
if err != nil {
g.String(500, "Failed to build request")
return
}
table, err := read_db(sql)
if err != nil {
g.String(500, "Failed to make request")
return
}
output := make(map[string]any)
headers := valid.FieldNames()
output["Headers"] = headers
output["Table"] = table
c.JSON(200, output)
return
}
Order by shares a similar syntax to query expressions where column names are declared followed by some expression.
Only accessible through the top level query.
Order by allows the call of any defined column / alias or a top level table column
CreateDate: DESC; CustomerID: ASC;
This is just representation of TOP to restrict the amount returned.
Only accessible through the top level query.
func getCustomersWithBiling(c *ex.Context) {
...
q, err := Re.Request('Customers', query_string)
if err != nil {
c.String(500, "Failed to initialize request")
return
}
q.LIMIT(100)
...
}