Skip to content

Commit

Permalink
Support expression evaluation in SELECT (#12)
Browse files Browse the repository at this point in the history
* Support expression evaluation in SELECT

Close #11

* Fix tests
  • Loading branch information
pgollangi authored Feb 3, 2023
1 parent c82e135 commit cd801d4
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 22 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ go 1.19

require (
cloud.google.com/go/firestore v1.9.0
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/c-bata/go-prompt v0.2.6
github.com/google/go-cmp v0.5.9
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.6.1
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
Expand All @@ -20,7 +22,6 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2A
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI=
github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down
87 changes: 68 additions & 19 deletions pkg/select/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/Knetic/govaluate"
"github.com/pgollangi/fireql/pkg/support"
"github.com/pgollangi/fireql/pkg/util"
"github.com/xwb1989/sqlparser"
Expand Down Expand Up @@ -185,6 +186,26 @@ func readColumnValue(document *firestore.DocumentSnapshot, data *map[string]inte
}
val = funcVal
break
case Expr:
evalExpr, err := govaluate.NewEvaluableExpressionWithFunctions(column.field, support.GetEvalFunctions())
if err != nil {
return nil, fmt.Errorf("couldn't parse expression %s while reading: %v", column.field, err)
}

params := map[string]interface{}{}
for _, param := range column.params {
paramVal, err := readColumnValue(document, data, param)
if err != nil {
return nil, err
}
params[param.field] = paramVal
}

exprResult, err := evalExpr.Evaluate(params)
if err != nil {
return nil, fmt.Errorf("couldn't evauluate expression %s: %v", column.field, err)
}
return exprResult, err
}
return val, nil
}
Expand All @@ -195,6 +216,7 @@ const (
Field ColumnType = 0
Function = 1
Star = 2
Expr = 3
)

type selectColumn struct {
Expand Down Expand Up @@ -231,11 +253,16 @@ loop:
paramFields := sel.collectSelectFields(col.params)
fields = append(fields, paramFields...)
break
case Expr:
exprFields := sel.collectSelectFields(col.params)
fields = append(fields, exprFields...)
break
case Star:
// Don't select fields on firestore.Query to return all fields
fields = []string{}
break loop
}

}
return fields
}
Expand All @@ -252,40 +279,62 @@ func (sel *SelectStatement) collectSelectColumns(qSelects sqlparser.SelectExprs)
})
break
case *sqlparser.AliasedExpr:
alias := qSelect.As.String()
if alias == "" {
alias = sqlparser.String(qSelect.Expr)
}
switch colExpr := qSelect.Expr.(type) {
case *sqlparser.ColName:
field := colExpr.Name.String()
alias := qSelect.As.String()
if alias == "" {
alias = field
}
columns = append(columns, &selectColumn{
field: field,
alias: alias,
colType: Field,
})
break
case *sqlparser.FuncExpr:
name := colExpr.Name.String()
alias := qSelect.As.String()
if alias == "" {
alias = name
}
params, err := sel.collectSelectColumns(colExpr.Exprs)
//case *sqlparser.FuncExpr:
// name := colExpr.Name.String()
// alias := qSelect.As.String()
// if alias == "" {
// alias = name
// }
// params, err := sel.collectSelectColumns(colExpr.Exprs)
// if err != nil {
// return nil, err
// }
// err = support.ValidateFunc(name, params)
// if err != nil {
// return nil, err
// }
// columns = append(columns, &selectColumn{
// field: name,
// alias: alias,
// colType: Function,
// params: params,
// })
// break
default:
expr := sqlparser.String(qSelect.Expr)
evalExpr, err := govaluate.NewEvaluableExpressionWithFunctions(expr, support.GetEvalFunctions())
if err != nil {
return nil, err
return nil, fmt.Errorf("couldn't parse expression %s: %v", expr, err)
}
err = support.ValidateFunc(name, params)
if err != nil {
return nil, err

var fields []*selectColumn
for _, token := range evalExpr.Tokens() {
if token.Kind == govaluate.VARIABLE {
fields = append(fields, &selectColumn{
field: token.Value.(string),
colType: Field,
})
}
}
columns = append(columns, &selectColumn{
field: name,
field: expr,
alias: alias,
colType: Function,
params: params,
colType: Expr,
params: fields,
})
break
}
break
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/select/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,20 @@ var selectTests = []TestExpect{
query: "select LENGTH(username) as uLen from users where id = 8",
columns: []string{"uLen"},
length: "1",
records: [][]interface{}{[]interface{}{6}},
records: [][]interface{}{[]interface{}{float64(6)}},
},
TestExpect{
query: "select id from users where `address.city` = 'Glendale' and name = 'Eleanora'",
columns: []string{"id"},
length: "1",
records: [][]interface{}{[]interface{}{float64(10)}},
},
TestExpect{
query: "select id > 0 as has_id from users where `address.city` = 'Glendale' and name = 'Eleanora'",
columns: []string{"has_id"},
length: "1",
records: [][]interface{}{[]interface{}{true}},
},
}

func newFirestoreTestClient(ctx context.Context) *firestore.Client {
Expand Down
13 changes: 12 additions & 1 deletion pkg/support/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package support

import (
"fmt"
"github.com/Knetic/govaluate"
"reflect"
"strings"
)
Expand Down Expand Up @@ -55,5 +56,15 @@ func Length(data []interface{}) (result interface{}, err error) {
err = fmt.Errorf(`LENGTH of type "%v" is not supported`, e.(*reflect.ValueError).Kind)
}
}()
return reflect.ValueOf(data[0]).Len(), nil
return float64(reflect.ValueOf(data[0]).Len()), nil
}

func GetEvalFunctions() map[string]govaluate.ExpressionFunction {
result := map[string]govaluate.ExpressionFunction{}
for name, fun := range functions {
result[name] = func(args ...interface{}) (interface{}, error) {
return fun.function(args)
}
}
return result
}

0 comments on commit cd801d4

Please sign in to comment.