snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
package ql
import (
"github.com/cortezaproject/corteza/server/pkg/ql"
"github.com/doug-martin/goqu/v9/exp"
)
type (
refHandler func(*ql.ASTNode, ...exp.Expression) (exp.Expression, error)
symHandler func(*ql.ASTNode) (exp.Expression, error)
valHandler func(*ql.ASTNode) (exp.Expression, error)
// converts query and AST from the parsed query into goqu expression
// that can be used in the SQL
converter struct {
parser *ql.Parser
refHandler refHandler
symHandler symHandler
valHandler valHandler
}
op func(*converter)
)
// Converter initializes new converter
func Converter(oo ...op) *converter {
c := &converter{
parser: ql.NewParser(),
refHandler: DefaultRefHandler,
symHandler: DefaultSymbolHandler,
valHandler: DefaultValueHandler,
}
for _, o := range oo {
o(c)
}
return c
}
// RefHandler sets custom ref handler
func RefHandler(h refHandler) op {
return func(c *converter) {
c.refHandler = h
}
}
// SymHandler sets custom symbol handler
func SymHandler(h symHandler) op {
return func(c *converter) {
c.symHandler = h
}
}
// ValHandler sets custom value handler
func ValHandler(h valHandler) op {
return func(c *converter) {
c.valHandler = h
}
}
func (c *converter) Parse(q string) (exp.Expression, error) {
n, err := c.parser.Parse(q)
if err != nil {
return nil, err
}
return c.Convert(n)
}
// Convert recursively walks through AST and converts the entire tree into expression
// that can be used in goqu SQL builder
func (c *converter) Convert(n *ql.ASTNode) (_ exp.Expression, err error) {
switch {
case n.Symbol != "":
return c.symHandler(n)
case n.Value != nil:
return c.valHandler(n)
}
args := make([]exp.Expression, len(n.Args))
for i, a := range n.Args {
args[i], err = c.Convert(a)
if err != nil {
return
}
}
return c.refHandler(n, args...)
}
// DefaultRefHandler converts ref from the AST node using ref2exp
func DefaultRefHandler(n *ql.ASTNode, args ...exp.Expression) (exp.Expression, error) {
return ref2exp.RefHandler(n, args...)
}
// DefaultSymbolHandler parses symbol from the AST node into an identifier
func DefaultSymbolHandler(n *ql.ASTNode) (exp.Expression, error) {
return exp.ParseIdentifier(n.Symbol), nil
}
// DefaultValueHandler converts node into placeholder and a new attribute
func DefaultValueHandler(n *ql.ASTNode) (exp.Expression, error) {
return exp.NewLiteralExpression("?", n.Value.V.Get()), nil
}

View File

@@ -0,0 +1,100 @@
package ql
import (
"strings"
"testing"
"github.com/doug-martin/goqu/v9"
"github.com/stretchr/testify/require"
)
func TestConverter(t *testing.T) {
const WHERE = " WHERE "
var (
base = goqu.Dialect("sqlite").Select().Prepared(true)
conv = Converter()
cases = []struct {
qry string
sql string
args []any
}{
{
qry: `foo = 1`,
sql: `("foo" = ?)`,
args: []any{int64(1)},
},
{
qry: `foo = 1 AND bar = baz`,
sql: `(("foo" = ?) AND ("bar" = "baz"))`,
args: []any{int64(1)},
},
{
qry: `foo = 1 OR (bar = 'baz')`,
sql: `(("foo" = ?) OR ("bar" = ?))`,
args: []any{int64(1), "baz"},
},
{
qry: `foo is not null`,
sql: `("foo" IS NOT NULL)`,
args: []any{},
},
{
qry: `!foo`,
sql: `(NOT "foo")`,
args: []any{},
},
{
qry: `one + 2 / 3 * 4 < 10`,
sql: `("one" + ? / ? * ? < ?)`,
args: []any{int64(2), int64(3), int64(4), int64(10)},
},
{
qry: `concat('foo', 'bar')`,
sql: `CONCAT(?, ?)`,
args: []any{"foo", "bar"},
},
{
qry: `quarter('2022-07-21')`,
sql: `QUARTER(?)`,
args: []any{"2022-07-21"},
},
{
qry: `year('2022-07-21')`,
sql: `YEAR(?)`,
args: []any{"2022-07-21"},
},
{
qry: `month('2022-07-21')`,
sql: `MONTH(?)`,
args: []any{"2022-07-21"},
},
{
qry: `date('2022-07-21')`,
sql: `DAY(?)`,
args: []any{"2022-07-21"},
},
}
)
for _, c := range cases {
t.Run(c.qry, func(t *testing.T) {
req := require.New(t)
t.Log(c.qry)
ee, err := conv.Parse(c.qry)
req.NoError(err)
sql, args, err := base.Where(ee).ToSQL()
req.NoError(err)
p := strings.Index(sql, WHERE)
req.Positive(p)
sql = sql[p+len(WHERE):]
req.Equal(c.sql, sql)
req.Equal(c.args, args)
})
}
}

View File

@@ -0,0 +1,11 @@
package ql
import (
"github.com/cortezaproject/corteza/server/pkg/ql"
)
// Aliasing types from the pkg/ql to simplify imports
type (
ASTNode = ql.ASTNode
)

View File

@@ -0,0 +1,335 @@
package ql
import (
"fmt"
"strings"
"github.com/cortezaproject/corteza/server/pkg/ql"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
)
type (
ExprHandlerMap map[string]*ExprHandler
ExprHandler struct {
Handler func(...exp.Expression) exp.Expression
HandlerE func(...exp.Expression) (exp.Expression, error)
RangeHandler func(exp.Expression, exp.RangeVal) exp.Expression
}
)
var (
ref2exp = ExprHandlerMap{
// keywords
"null": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("NULL")
},
},
"nnull": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("NOT NULL")
},
},
// operators
"not": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("(NOT ?)", args[0])
},
},
// - bool
"and": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewExpressionList(exp.AndType, args...)
},
},
"or": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewExpressionList(exp.OrType, args...)
},
},
// - comp.
"eq": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.EqOp, args[0], args[1])
},
},
"ne": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.NeqOp, args[0], args[1])
},
},
"lt": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.LtOp, args[0], args[1])
},
},
"le": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.LteOp, args[0], args[1])
},
},
"gt": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.GtOp, args[0], args[1])
},
},
"ge": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.GteOp, args[0], args[1])
},
},
"in": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.InOp, args[0], args[1])
},
},
"nin": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.NotInOp, args[0], args[1])
},
},
// - math
"add": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("? + ?", args[0], args[1])
},
},
"sub": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("? - ?", args[0], args[1])
},
},
"mult": {
Handler: func(args ...exp.Expression) exp.Expression {
// Handling COUNT(*) scenario:
// ql parser interprets * as a multiplier
//
// in situations where there are 0 arguments we'll just
// return star
if len(args) == 0 {
return exp.Star()
}
return exp.NewLiteralExpression("? * ?", args[0], args[1])
},
},
"div": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("? / ?", args[0], args[1])
},
},
// - strings
"concat": {
Handler: func(args ...exp.Expression) exp.Expression {
aa := make([]any, len(args))
for a := range args {
aa[a] = args[a]
}
return exp.NewSQLFunctionExpression("CONCAT", aa...)
},
},
"interval": {
Handler: func(args ...exp.Expression) exp.Expression {
// The problem here is similar to the one with PgSQL... but more complicated...
// The interval duration identifiers are keywords and can't be bound via value placeholders.
// This forces us to interpolate the thing when building the literal expression below.
//
// Since goqu doesn't let me get the raw value of the expression... I was forced
// to do this calamity
_, aa, err := goqu.Select(args[0]).ToSQL()
if err != nil {
// This error should never occur
panic(err)
}
intv := aa[0].(string)
return exp.NewLiteralExpression(fmt.Sprintf("INTERVAL ? %s", intv), args[1])
},
},
"date_add": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("DATE_ADD", args[0], args[1])
},
},
"date_sub": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("DATE_SUB", args[0], args[1])
},
},
// @todo better negation?
"like": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.ILikeOp, args[0], args[1])
},
},
"nlike": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.NotILikeOp, args[0], args[1])
},
},
// range operation
"between": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewRangeExpression(exp.BetweenOp, args[0], exp.NewRangeVal(args[1], args[2]))
},
},
"nbetween": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewRangeExpression(exp.NotBetweenOp, args[0], exp.NewRangeVal(args[1], args[2]))
},
},
"is": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.IsOp, args[0], args[1])
},
},
"nis": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewBooleanExpression(exp.IsNotOp, args[0], args[1])
},
},
"group": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("(?)", args[0])
},
},
// - filtering
"date_format": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("DATE_FORMAT", args[0], args[1])
},
},
"now": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("NOW")
},
},
"quarter": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("QUARTER", args[0])
},
},
"year": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("YEAR", args[0])
},
},
"month": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("MONTH", args[0])
},
},
"day": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("DAY", args[0])
},
},
"week": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("WEEK", args[0])
},
},
"date": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("DATE", args[0])
},
},
"timestamp": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("TIMESTAMP(?)", args[0])
},
},
"time": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewLiteralExpression("TIME(?)", args[0])
},
},
"count": {
Handler: func(args ...exp.Expression) exp.Expression {
var arg exp.Expression = exp.NewLiteralExpression("*")
if len(args) > 0 {
arg = args[0]
}
return exp.NewSQLFunctionExpression("COUNT", arg)
},
},
"sum": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("SUM", args[0])
},
},
"avg": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("AVG", args[0])
},
},
"min": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("MIN", args[0])
},
},
"max": {
Handler: func(args ...exp.Expression) exp.Expression {
return exp.NewSQLFunctionExpression("MAX", args[0])
},
},
"std": {
HandlerE: func(args ...exp.Expression) (exp.Expression, error) {
return nil, fmt.Errorf("STD function not supported on this database")
},
},
}
)
func (ee ExprHandlerMap) ExprHandlers() (out ExprHandlerMap) {
// Set the default values; make sure to copy the global map
out = make(map[string]*ExprHandler, len(ref2exp))
for name, expr := range ref2exp {
out[name] = expr
}
// Overwrite with customs
for name, expr := range ee {
out[name] = expr
}
return
}
func (ee ExprHandlerMap) RefHandler(n *ql.ASTNode, args ...exp.Expression) (exp.Expression, error) {
r := strings.ToLower(n.Ref)
if ee[r] == nil {
return nil, fmt.Errorf("unknown ref %q", r)
}
if ee[r].Handler != nil && ee[r].HandlerE != nil {
panic("invalid ql ref definition: both Handler and HandlerE are set")
}
if ee[r].Handler != nil {
return ee[r].Handler(args...), nil
}
return ee[r].HandlerE(args...)
}