First commit

This commit is contained in:
Vikram Rangnekar
2019-03-24 09:57:29 -04:00
commit b9d38a5e9d
153 changed files with 18120 additions and 0 deletions

391
qcode/lex.go Normal file
View File

@ -0,0 +1,391 @@
package qcode
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// Pos represents a byte position in the original input text from which
// this template was parsed.
type Pos int
func (p Pos) Position() Pos {
return p
}
// item represents a token or text string returned from the scanner.
type item struct {
typ itemType // The type of this item.
pos Pos // The starting position, in bytes, of this item in the input string.
val string // The value of this item.
line int // The line number at the start of this item.
}
func (i *item) String() string {
var v string
switch i.typ {
case itemEOF:
v = "EOF"
case itemError:
v = "error"
case itemName:
v = "name"
case itemQuery:
v = "query"
case itemMutation:
v = "mutation"
case itemSub:
v = "subscription"
case itemPunctuator:
v = "punctuator"
case itemDirective:
v = "directive"
case itemVariable:
v = "variable"
case itemIntVal:
v = "int"
case itemFloatVal:
v = "float"
case itemStringVal:
v = "string"
}
return fmt.Sprintf("%s %q", v, i.val)
}
// itemType identifies the type of lex items.
type itemType int
const (
itemError itemType = iota // error occurred; value is text of error
itemEOF
itemName
itemQuery
itemMutation
itemSub
itemPunctuator
itemArgsOpen
itemArgsClose
itemListOpen
itemListClose
itemObjOpen
itemObjClose
itemColon
itemEquals
itemDirective
itemVariable
itemSpread
itemIntVal
itemFloatVal
itemStringVal
itemBoolVal
)
// !$():=@[]{|}
var punctuators = map[rune]itemType{
'{': itemObjOpen,
'}': itemObjClose,
'[': itemListOpen,
']': itemListClose,
'(': itemArgsOpen,
')': itemArgsClose,
':': itemColon,
'=': itemEquals,
}
const eof = -1
// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn
// lexer holds the state of the scanner.
type lexer struct {
name string // the name of the input; used only for error reports
input string // the string being scanned
pos Pos // current position in the input
start Pos // start position of this item
width Pos // width of last rune read from input
items []item // array of scanned items
line int // 1+number of newlines seen
}
// next returns the next rune in the input.
func (l *lexer) next() rune {
if int(l.pos) >= len(l.input) {
l.width = 0
return eof
}
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
l.width = Pos(w)
l.pos += l.width
if r == '\n' {
l.line++
}
return r
}
// peek returns but does not consume the next rune in the input.
func (l *lexer) peek() rune {
r := l.next()
l.backup()
return r
}
// backup steps back one rune. Can only be called once per call of next.
func (l *lexer) backup() {
l.pos -= l.width
// Correct newline count.
if l.width == 1 && l.input[l.pos] == '\n' {
l.line--
}
}
func (l *lexer) current() string {
return l.input[l.start:l.pos]
}
// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
l.items = append(l.items, item{t, l.start, l.input[l.start:l.pos], l.line})
// Some items contain text internally. If so, count their newlines.
switch t {
case itemName:
l.line += strings.Count(l.input[l.start:l.pos], "\n")
}
l.start = l.pos
}
// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
l.line += strings.Count(l.input[l.start:l.pos], "\n")
l.start = l.pos
}
// accept consumes the next rune if it's from the valid set.
func (l *lexer) accept(valid string) bool {
if strings.ContainsRune(valid, l.next()) {
return true
}
l.backup()
return false
}
// accept onsumes a run of runes while they are alpha nums
func (l *lexer) acceptAlphaNum() bool {
n := 0
for r := l.next(); isAlphaNumeric(r); r = l.next() {
n++
}
l.backup()
return (n != 0)
}
// acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptRun(valid string) {
for strings.ContainsRune(valid, l.next()) {
}
l.backup()
}
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.items = append(l.items, item{itemError, l.start,
fmt.Sprintf(format, args...), l.line})
return nil
}
// lex creates a new scanner for the input string.
func lex(input string) (*lexer, error) {
l := &lexer{
input: input,
items: make([]item, 0, 100),
line: 1,
}
l.run()
if last := l.items[len(l.items)-1]; last.typ == itemError {
return nil, fmt.Errorf(last.val)
}
return l, nil
}
// run runs the state machine for the lexer.
func (l *lexer) run() {
for state := lexRoot; state != nil; {
state = state(l)
}
}
// lexInsideAction scans the elements inside action delimiters.
func lexRoot(l *lexer) stateFn {
r := l.next()
switch {
case r == eof:
l.emit(itemEOF)
return nil
case isEndOfLine(r):
l.ignore()
case isSpace(r):
l.ignore()
case r == '@':
l.ignore()
if l.acceptAlphaNum() {
l.emit(itemDirective)
}
case r == '$':
l.ignore()
if l.acceptAlphaNum() {
l.emit(itemVariable)
}
case strings.ContainsRune("!():=[]{|}", r):
if item, ok := punctuators[r]; ok {
l.emit(item)
} else {
l.emit(itemPunctuator)
}
case r == '"' || r == '\'':
l.backup()
return lexString
case r == '.':
if len(l.input) >= 3 {
if strings.HasSuffix(l.input[:l.pos], "...") {
l.emit(itemSpread)
return lexRoot
}
}
fallthrough // '.' can start a number.
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
l.backup()
return lexNumber
case isAlphaNumeric(r):
l.backup()
return lexName
default:
return l.errorf("unrecognized character in action: %#U", r)
}
return lexRoot
}
// lexName scans a name.
func lexName(l *lexer) stateFn {
for {
r := l.next()
if r == eof {
l.emit(itemEOF)
return nil
}
if !isAlphaNumeric(r) {
l.backup()
v := l.current()
if len(v) == 0 {
switch {
case strings.EqualFold(v, "query"):
l.emit(itemQuery)
break
case strings.EqualFold(v, "mutation"):
l.emit(itemMutation)
break
case strings.EqualFold(v, "subscription"):
l.emit(itemSub)
break
}
}
switch {
case strings.EqualFold(v, "true"):
l.emit(itemBoolVal)
case strings.EqualFold(v, "false"):
l.emit(itemBoolVal)
default:
l.emit(itemName)
}
break
}
}
return lexRoot
}
// lexString scans a string.
func lexString(l *lexer) stateFn {
if l.accept("\"'") {
l.ignore()
for {
r := l.next()
if r == eof {
l.emit(itemEOF)
return nil
}
if r == '\'' || r == '"' {
l.backup()
l.emit(itemStringVal)
if l.accept("\"'") {
l.ignore()
}
break
}
}
}
return lexRoot
}
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
// and "089" - but when it's wrong the input is invalid and the parser (via
// strconv) will notice.
func lexNumber(l *lexer) stateFn {
var it itemType
// Optional leading sign.
l.accept("+-")
// Is it integer
digits := "0123456789"
if l.accept(digits) {
l.acceptRun(digits)
it = itemIntVal
}
// Is it float
if l.peek() == '.' {
if l.accept(".") {
if l.accept(digits) {
l.acceptRun(digits)
it = itemFloatVal
}
} else {
l.backup()
}
}
// Next thing mustn't be alphanumeric.
if isAlphaNumeric(l.peek()) {
l.next()
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
}
if it != 0 {
l.emit(it)
}
return lexRoot
}
// isSpace reports whether r is a space character.
func isSpace(r rune) bool {
return r == ',' || r == ' ' || r == '\t'
}
// isEndOfLine reports whether r is an end-of-line character.
func isEndOfLine(r rune) bool {
return r == '\r' || r == '\n'
}
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
func isAlphaNumeric(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}

454
qcode/parse.go Normal file
View File

@ -0,0 +1,454 @@
package qcode
import (
"errors"
"fmt"
"github.com/dosco/super-graph/util"
)
var (
errEOT = errors.New("end of tokens")
)
type parserType int16
const (
parserError parserType = iota
parserEOF
opQuery
opMutate
opSub
nodeStr
nodeInt
nodeFloat
nodeBool
nodeObj
nodeList
nodeVar
)
func (t parserType) String() string {
var v string
switch t {
case parserEOF:
v = "EOF"
case parserError:
v = "error"
case opQuery:
v = "query"
case opMutate:
v = "mutation"
case opSub:
v = "subscription"
case nodeStr:
v = "node-string"
case nodeInt:
v = "node-int"
case nodeFloat:
v = "node-float"
case nodeBool:
v = "node-bool"
case nodeObj:
v = "node-obj"
case nodeList:
v = "node-list"
}
return fmt.Sprintf("<%s>", v)
}
type Operation struct {
Type parserType
Name string
Args []*Arg
Fields []*Field
}
type Field struct {
Name string
Alias string
Args []*Arg
Parent *Field
Children []*Field
}
type Arg struct {
Name string
Val *Node
}
type Node struct {
Type parserType
Name string
Val string
Parent *Node
Children []*Node
}
type Parser struct {
pos int
items []item
depth int
err error
}
func Parse(gql string) (*Operation, error) {
if len(gql) == 0 {
return nil, errors.New("blank query")
}
l, err := lex(gql)
if err != nil {
return nil, err
}
p := &Parser{
pos: -1,
items: l.items,
}
return p.parseOp()
}
func ParseQuery(gql string) (*Operation, error) {
return parseByType(gql, opQuery)
}
func ParseArgValue(argVal string) (*Node, error) {
l, err := lex(argVal)
if err != nil {
return nil, err
}
p := &Parser{
pos: -1,
items: l.items,
}
return p.parseValue()
}
func parseByType(gql string, ty parserType) (*Operation, error) {
l, err := lex(gql)
if err != nil {
return nil, err
}
p := &Parser{
pos: -1,
items: l.items,
}
return p.parseOpByType(ty)
}
func (p *Parser) next() item {
n := p.pos + 1
if n >= len(p.items) {
p.err = errEOT
return item{typ: itemEOF}
}
p.pos = n
return p.items[p.pos]
}
func (p *Parser) ignore() {
n := p.pos + 1
if n >= len(p.items) {
p.err = errEOT
return
}
p.pos = n
}
func (p *Parser) current() item {
return p.items[p.pos]
}
func (p *Parser) eof() bool {
n := p.pos + 1
return p.items[n].typ == itemEOF
}
func (p *Parser) peek(types ...itemType) bool {
n := p.pos + 1
if p.items[n].typ == itemEOF {
return false
}
if n >= len(p.items) {
return false
}
for i := 0; i < len(types); i++ {
if p.items[n].typ == types[i] {
return true
}
}
return false
}
func (p *Parser) parseOpByType(ty parserType) (*Operation, error) {
op := &Operation{Type: ty}
var err error
if p.peek(itemName) {
op.Name = p.next().val
}
if p.peek(itemArgsOpen) {
p.ignore()
op.Args, err = p.parseArgs()
if err != nil {
return nil, err
}
}
if p.peek(itemObjOpen) {
p.ignore()
op.Fields, err = p.parseFields()
if err != nil {
return nil, err
}
}
if p.peek(itemObjClose) {
p.ignore()
}
return op, nil
}
func (p *Parser) parseOp() (*Operation, error) {
if p.peek(itemQuery, itemMutation, itemSub) == false {
err := fmt.Errorf("expecting a query, mutation or subscription (not '%s')", p.next().val)
return nil, err
}
item := p.next()
switch item.typ {
case itemQuery:
return p.parseOpByType(opQuery)
case itemMutation:
return p.parseOpByType(opMutate)
case itemSub:
return p.parseOpByType(opSub)
}
return nil, errors.New("unknown operation type")
}
func (p *Parser) parseFields() ([]*Field, error) {
var roots []*Field
st := util.NewStack()
for {
if p.peek(itemObjClose) {
p.ignore()
st.Pop()
if st.Len() == 0 {
break
}
continue
}
if p.peek(itemName) == false {
return nil, errors.New("expecting an alias or field name")
}
field, err := p.parseField()
if err != nil {
return nil, err
}
if st.Len() == 0 {
roots = append(roots, field)
} else {
intf := st.Peek()
parent, ok := intf.(*Field)
if !ok || parent == nil {
return nil, fmt.Errorf("unexpected value encountered %v", intf)
}
field.Parent = parent
parent.Children = append(parent.Children, field)
}
if p.peek(itemObjOpen) {
p.ignore()
st.Push(field)
}
}
return roots, nil
}
func (p *Parser) parseField() (*Field, error) {
var err error
field := &Field{Name: p.next().val}
if p.peek(itemColon) {
p.ignore()
if p.peek(itemName) {
field.Alias = field.Name
field.Name = p.next().val
} else {
return nil, errors.New("expecting an aliased field name")
}
}
if p.peek(itemArgsOpen) {
p.ignore()
if field.Args, err = p.parseArgs(); err != nil {
return nil, err
}
}
return field, nil
}
func (p *Parser) parseArgs() ([]*Arg, error) {
var args []*Arg
var err error
for {
if p.peek(itemArgsClose) {
p.ignore()
break
}
if p.peek(itemName) == false {
return nil, errors.New("expecting an argument name")
}
arg := &Arg{Name: p.next().val}
if p.peek(itemColon) == false {
return nil, errors.New("missing ':' after argument name")
}
p.ignore()
arg.Val, err = p.parseValue()
if err != nil {
return nil, err
}
args = append(args, arg)
}
return args, nil
}
func (p *Parser) parseList(parent *Node) ([]*Node, error) {
var nodes []*Node
var ty parserType
if parent == nil {
return nil, errors.New("list needs a parent")
}
for {
if p.peek(itemListClose) {
p.ignore()
break
}
node, err := p.parseValue()
if err != nil {
return nil, err
}
if ty == 0 {
ty = node.Type
} else {
if ty != node.Type {
return nil, errors.New("All values in a list must be of the same type")
}
}
nodes = append(nodes, node)
}
if len(nodes) == 0 {
return nil, errors.New("List cannot be empty")
}
parent.Type = nodeList
parent.Children = nodes
return nodes, nil
}
func (p *Parser) parseObj(parent *Node) ([]*Node, error) {
var nodes []*Node
if parent == nil {
return nil, errors.New("object needs a parent")
}
for {
if p.peek(itemObjClose) {
p.ignore()
break
}
if p.peek(itemName) == false {
return nil, errors.New("expecting an argument name")
}
nodeName := p.next().val
if p.peek(itemColon) == false {
return nil, errors.New("missing ':' after Field argument name")
}
p.ignore()
node, err := p.parseValue()
if err != nil {
return nil, err
}
node.Name = nodeName
node.Parent = parent
nodes = append(nodes, node)
}
parent.Type = nodeObj
parent.Children = nodes
return nodes, nil
}
func (p *Parser) parseValue() (*Node, error) {
node := &Node{}
var done bool
var err error
if p.peek(itemListOpen) {
p.ignore()
node.Children, err = p.parseList(node)
done = true
}
if p.peek(itemObjOpen) {
p.ignore()
node.Children, err = p.parseObj(node)
done = true
}
if err != nil {
return nil, err
}
if !done {
item := p.next()
switch item.typ {
case itemIntVal:
node.Type = nodeInt
case itemFloatVal:
node.Type = nodeFloat
case itemStringVal:
node.Type = nodeStr
case itemBoolVal:
node.Type = nodeBool
case itemName:
node.Type = nodeStr
case itemVariable:
node.Type = nodeVar
default:
return nil, fmt.Errorf("expecting a number, string, object, list or variable as an argument value (not %s)", p.next().val)
}
node.Val = item.val
}
return node, nil
}

53
qcode/parse_test.go Normal file
View File

@ -0,0 +1,53 @@
package qcode
import (
"errors"
"fmt"
"reflect"
"testing"
)
func compareOp(op1, op2 Operation) error {
if op1.Type != op2.Type {
return errors.New("operator type mismatch")
}
if op1.Name != op2.Name {
return errors.New("operator name mismatch")
}
if len(op1.Args) != len(op2.Args) {
return errors.New("operator args length mismatch")
}
for i := range op1.Args {
if !reflect.DeepEqual(op1.Args[i], op2.Args[i]) {
return fmt.Errorf("operator args: %v != %v", op1.Args[i], op2.Args[i])
}
}
if len(op1.Fields) != len(op2.Fields) {
return errors.New("operator field length mismatch")
}
for i := range op1.Fields {
if !reflect.DeepEqual(op1.Fields[i].Args, op2.Fields[i].Args) {
return fmt.Errorf("operator field args: %v != %v", op1.Fields[i].Args, op2.Fields[i].Args)
}
}
for i := range op1.Fields {
if !reflect.DeepEqual(op1.Fields[i].Children, op2.Fields[i].Children) {
return fmt.Errorf("operator field fields: %v != %v", op1.Fields[i].Children, op2.Fields[i].Children)
}
}
return nil
}
func TestParse(t *testing.T) {
}
func BenchmarkParse(b *testing.B) {
}

619
qcode/qcode.go Normal file
View File

@ -0,0 +1,619 @@
package qcode
import (
"fmt"
"regexp"
"strings"
"github.com/dosco/super-graph/util"
"github.com/jinzhu/inflection"
)
type QCode struct {
Query *Query
}
type Query struct {
Select *Select
}
type Column struct {
Table string
Name string
FieldName string
}
type Select struct {
ID int32
AsList bool
Table string
Singular string
FieldName string
Cols []*Column
Where *Exp
OrderBy []*OrderBy
DistinctOn []string
Paging Paging
Joins []*Select
}
type Exp struct {
Op ExpOp
Col string
NestedCol bool
Type ValType
Val string
ListType ValType
ListVal []string
Children []*Exp
}
type OrderBy struct {
Col string
Order Order
}
type Paging struct {
Limit string
Offset string
}
type ExpOp int
const (
OpAnd ExpOp = iota + 1
OpOr
OpNot
OpEquals
OpNotEquals
OpGreaterOrEquals
OpLesserOrEquals
OpGreaterThan
OpLesserThan
OpIn
OpNotIn
OpLike
OpNotLike
OpILike
OpNotILike
OpSimilar
OpNotSimilar
OpContains
OpContainedIn
OpHasKey
OpHasKeyAny
OpHasKeyAll
OpIsNull
)
type ValType int
const (
ValStr ValType = iota + 1
ValInt
ValFloat
ValBool
ValList
ValVar
)
type AggregrateOp int
const (
AgCount AggregrateOp = iota + 1
AgSum
AgAvg
AgMax
AgMin
)
type Order int
const (
OrderAsc Order = iota + 1
OrderDesc
OrderAscNullsFirst
OrderAscNullsLast
OrderDescNullsFirst
OrderDescNullsLast
)
type FilterMap map[string]*Exp
type Blacklist *regexp.Regexp
func CompileFilter(filter string) (*Exp, error) {
node, err := ParseArgValue(filter)
if err != nil {
return nil, err
}
return (&Compiler{}).compileArgNode(node)
}
type Compiler struct {
fm FilterMap
bl *regexp.Regexp
}
func NewCompiler(fm FilterMap, bl Blacklist) *Compiler {
return &Compiler{fm, bl}
}
func (com *Compiler) CompileQuery(query string) (*QCode, error) {
var qc QCode
var err error
op, err := ParseQuery(query)
if err != nil {
return nil, err
}
switch op.Type {
case opQuery:
qc.Query, err = com.compileQuery(op)
case opMutate:
case opSub:
default:
err = fmt.Errorf("Unknown operation type %d", op.Type)
}
if err != nil {
return nil, err
}
return &qc, nil
}
func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
var selRoot *Select
st := util.NewStack()
id := int32(0)
fmap := make(map[*Field]*Select)
for i := range op.Fields {
st.Push(op.Fields[i])
}
for {
if st.Len() == 0 {
break
}
intf := st.Pop()
field, ok := intf.(*Field)
if !ok || field == nil {
return nil, fmt.Errorf("unexpected value poped out %v", intf)
}
if com.bl != nil && com.bl.MatchString(field.Name) {
continue
}
fn := strings.ToLower(field.Name)
s := &Select{
ID: id,
Table: inflection.Plural(fn),
Singular: inflection.Singular(fn),
}
if fn == s.Table {
s.AsList = true
} else {
s.Paging.Limit = "1"
}
if len(field.Alias) != 0 {
s.FieldName = field.Alias
} else if s.AsList {
s.FieldName = s.Table
} else {
s.FieldName = s.Singular
}
id++
fmap[field] = s
err := com.compileArgs(s, field.Args)
if err != nil {
return nil, err
}
for i := range field.Children {
f := field.Children[i]
if com.bl != nil && com.bl.MatchString(f.Name) {
continue
}
if f.Children == nil {
col := &Column{Name: f.Name}
if len(f.Alias) != 0 {
col.FieldName = f.Alias
} else {
col.FieldName = f.Name
}
s.Cols = append(s.Cols, col)
} else {
st.Push(f)
}
}
if field.Parent == nil {
selRoot = s
} else if sp, ok := fmap[field.Parent]; ok {
sp.Joins = append(sp.Joins, s)
} else {
return nil, fmt.Errorf("no select found for parent %#v", field.Parent)
}
}
if fil, ok := com.fm[selRoot.Table]; ok {
if selRoot.Where != nil {
selRoot.Where = &Exp{Op: OpAnd, Children: []*Exp{fil, selRoot.Where}}
} else {
selRoot.Where = fil
}
}
return &Query{selRoot}, nil
}
func (com *Compiler) compileArgs(sel *Select, args []*Arg) error {
var err error
for i := range args {
if args[i] == nil {
return fmt.Errorf("[Args] unexpected nil argument found")
}
switch strings.ToLower(args[i].Name) {
case "where":
err = com.compileArgWhere(sel, args[i])
case "orderby", "order_by", "order":
err = com.compileArgOrderBy(sel, args[i])
case "distinct_on", "distinct":
err = com.compileArgDistinctOn(sel, args[i])
case "limit":
err = com.compileArgLimit(sel, args[i])
case "offset":
err = com.compileArgOffset(sel, args[i])
}
}
return err
}
type expT struct {
parent *Exp
node *Node
}
func (com *Compiler) compileArgObj(arg *Arg) (*Exp, error) {
if arg.Val.Type != nodeObj {
return nil, fmt.Errorf("[Where] expecting an object")
}
return com.compileArgNode(arg.Val)
}
func (com *Compiler) compileArgNode(val *Node) (*Exp, error) {
st := util.NewStack()
var root *Exp
st.Push(&expT{nil, val.Children[0]})
for {
if st.Len() == 0 {
break
}
intf := st.Pop()
eT, ok := intf.(*expT)
if !ok || eT == nil {
return nil, fmt.Errorf("[Where] unexpected value poped out %v", intf)
}
node := eT.node
if com.bl != nil && com.bl.MatchString(node.Name) {
continue
}
ex := &Exp{}
name := strings.ToLower(node.Name)
if name[0] == '_' {
name = name[1:]
}
switch name {
case "and":
ex.Op = OpAnd
pushChildren(st, ex, node)
case "or":
ex.Op = OpOr
pushChildren(st, ex, node)
case "not":
ex.Op = OpNot
pushChildren(st, ex, node)
case "eq", "equals":
ex.Op = OpEquals
ex.Val = node.Val
case "neq", "not_equals":
ex.Op = OpNotEquals
ex.Val = node.Val
case "gt", "greater_than":
ex.Op = OpGreaterThan
ex.Val = node.Val
case "lt", "lesser_than":
ex.Op = OpLesserThan
ex.Val = node.Val
case "gte", "greater_or_equals":
ex.Op = OpGreaterOrEquals
ex.Val = node.Val
case "lte", "lesser_or_equals":
ex.Op = OpLesserOrEquals
ex.Val = node.Val
case "in":
ex.Op = OpIn
setListVal(ex, node)
case "nin", "not_in":
ex.Op = OpNotIn
setListVal(ex, node)
case "like":
ex.Op = OpLike
ex.Val = node.Val
case "nlike", "not_like":
ex.Op = OpNotLike
ex.Val = node.Val
case "ilike":
ex.Op = OpILike
ex.Val = node.Val
case "nilike", "not_ilike":
ex.Op = OpILike
ex.Val = node.Val
case "similar":
ex.Op = OpSimilar
ex.Val = node.Val
case "nsimilar", "not_similar":
ex.Op = OpNotSimilar
ex.Val = node.Val
case "contains":
ex.Op = OpContains
ex.Val = node.Val
case "contained_in":
ex.Op = OpContainedIn
ex.Val = node.Val
case "has_key":
ex.Op = OpHasKey
ex.Val = node.Val
case "has_key_any":
ex.Op = OpHasKeyAny
ex.Val = node.Val
case "has_key_all":
ex.Op = OpHasKeyAll
ex.Val = node.Val
case "is_null":
ex.Op = OpIsNull
ex.Val = node.Val
default:
pushChildren(st, eT.parent, node)
continue // skip node
}
if ex.Op != OpAnd && ex.Op != OpOr && ex.Op != OpNot {
switch node.Type {
case nodeStr:
ex.Type = ValStr
case nodeInt:
ex.Type = ValInt
case nodeBool:
ex.Type = ValBool
case nodeFloat:
ex.Type = ValFloat
case nodeList:
ex.Type = ValList
case nodeVar:
ex.Type = ValVar
default:
return nil, fmt.Errorf("[Where] valid values include string, int, float, boolean and list: %s", node.Type)
}
setWhereColName(ex, node)
}
if eT.parent == nil {
root = ex
} else {
eT.parent.Children = append(eT.parent.Children, ex)
}
}
return root, nil
}
func (com *Compiler) compileArgWhere(sel *Select, arg *Arg) error {
var err error
sel.Where, err = com.compileArgObj(arg)
if err != nil {
return err
}
return nil
}
func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) error {
if arg.Val.Type != nodeObj {
return fmt.Errorf("expecting an object")
}
st := util.NewStack()
for i := range arg.Val.Children {
st.Push(arg.Val.Children[i])
}
for {
if st.Len() == 0 {
break
}
intf := st.Pop()
node, ok := intf.(*Node)
if !ok || node == nil {
return fmt.Errorf("OrderBy: unexpected value poped out %v", intf)
}
if com.bl != nil && com.bl.MatchString(node.Name) {
continue
}
if node.Type == nodeObj {
for i := range node.Children {
st.Push(node.Children[i])
}
continue
}
ob := &OrderBy{}
val := strings.ToLower(node.Val)
switch val {
case "asc":
ob.Order = OrderAsc
case "desc":
ob.Order = OrderDesc
case "asc_nulls_first":
ob.Order = OrderAscNullsFirst
case "desc_nulls_first":
ob.Order = OrderDescNullsFirst
case "asc_nulls_last":
ob.Order = OrderAscNullsLast
case "desc_nulls_last":
ob.Order = OrderDescNullsLast
default:
return fmt.Errorf("valid values include asc, desc, asc_nulls_first and desc_nulls_first")
}
setOrderByColName(ob, node)
sel.OrderBy = append(sel.OrderBy, ob)
}
return nil
}
func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) error {
node := arg.Val
if com.bl != nil && com.bl.MatchString(node.Name) {
return nil
}
if node.Type != nodeList && node.Type != nodeStr {
return fmt.Errorf("expecting a list of strings or just a string")
}
if node.Type == nodeStr {
sel.DistinctOn = append(sel.DistinctOn, node.Val)
}
for i := range node.Children {
sel.DistinctOn = append(sel.DistinctOn, node.Children[i].Val)
}
return nil
}
func (com *Compiler) compileArgLimit(sel *Select, arg *Arg) error {
node := arg.Val
if node.Type != nodeInt {
return fmt.Errorf("expecting an integer")
}
sel.Paging.Limit = node.Val
return nil
}
func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) error {
node := arg.Val
if node.Type != nodeInt {
return fmt.Errorf("expecting an integer")
}
sel.Paging.Offset = node.Val
return nil
}
func compileMutate() (*Query, error) {
return nil, nil
}
func compileSub() (*Query, error) {
return nil, nil
}
func setListVal(ex *Exp, node *Node) {
if len(node.Children) != 0 {
switch node.Children[0].Type {
case nodeStr:
ex.ListType = ValStr
case nodeInt:
ex.ListType = ValInt
case nodeBool:
ex.ListType = ValBool
case nodeFloat:
ex.ListType = ValFloat
}
}
for i := range node.Children {
ex.ListVal = append(ex.ListVal, node.Children[i].Val)
}
}
func setWhereColName(ex *Exp, node *Node) {
var list []string
for n := node.Parent; n != nil; n = n.Parent {
if n.Type != nodeObj {
continue
}
k := strings.ToLower(n.Name)
if k == "and" || k == "or" || k == "not" ||
k == "_and" || k == "_or" || k == "_not" {
continue
}
if len(k) != 0 {
list = append([]string{k}, list...)
}
}
if len(list) == 1 {
ex.Col = list[0]
} else if len(list) > 2 {
ex.Col = strings.Join(list, ".")
ex.NestedCol = true
}
}
func setOrderByColName(ob *OrderBy, node *Node) {
var list []string
for n := node; n != nil; n = n.Parent {
k := strings.ToLower(n.Name)
if len(k) != 0 {
list = append([]string{k}, list...)
}
}
if len(list) != 0 {
ob.Col = strings.Join(list, ".")
}
}
func pushChildren(st *util.Stack, ex *Exp, node *Node) {
for i := range node.Children {
st.Push(&expT{ex, node.Children[i]})
}
}