Enable model expressions in SQL parser
The SQL parser now supports model expressions for defining ranges using the |table:0,0.5..100| notation syntax.
This commit is contained in:
parent
2de5a8fa3f
commit
aa7e4ac561
4 changed files with 176 additions and 1 deletions
16
sql/ast.go
16
sql/ast.go
|
@ -551,6 +551,22 @@ type ContentExpression struct {
|
|||
Data Expr
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Model
|
||||
// --------------------------------------------------
|
||||
|
||||
// Model comment
|
||||
type Model struct {
|
||||
TB string
|
||||
MIN float64
|
||||
INC float64
|
||||
MAX float64
|
||||
}
|
||||
|
||||
func NewModel(TB string, MIN, INC, MAX float64) *Model {
|
||||
return &Model{TB: TB, MIN: MIN, INC: INC, MAX: MAX}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Param
|
||||
// --------------------------------------------------
|
||||
|
|
|
@ -28,7 +28,7 @@ func (p *parser) parseWhat() (mul []Expr, err error) {
|
|||
|
||||
for {
|
||||
|
||||
tok, lit, err := p.shouldBe(IDENT, THING, PARAM)
|
||||
tok, lit, err := p.shouldBe(IDENT, THING, PARAM, MODEL)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Found: lit, Expected: []string{"table, or thing"}}
|
||||
}
|
||||
|
@ -48,6 +48,11 @@ func (p *parser) parseWhat() (mul []Expr, err error) {
|
|||
mul = append(mul, one)
|
||||
}
|
||||
|
||||
if p.is(tok, MODEL) {
|
||||
one, _ := p.declare(MODEL, lit)
|
||||
mul = append(mul, one)
|
||||
}
|
||||
|
||||
// Check to see if the next token is a comma
|
||||
// and if not, then break out of the loop,
|
||||
// otherwise repeat until we find no comma.
|
||||
|
|
152
sql/scanner.go
152
sql/scanner.go
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -136,6 +137,7 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) {
|
|||
return OR, "OR", val
|
||||
default:
|
||||
s.undo()
|
||||
return s.scanModel(ch)
|
||||
}
|
||||
case '&':
|
||||
chn := s.next()
|
||||
|
@ -461,6 +463,151 @@ func (s *scanner) scanThing(chp ...rune) (tok Token, lit string, val interface{}
|
|||
|
||||
}
|
||||
|
||||
func (s *scanner) scanModel(chp ...rune) (tok Token, lit string, val interface{}) {
|
||||
|
||||
var com bool
|
||||
var dot bool
|
||||
|
||||
var tbv string
|
||||
var min float64 = 0
|
||||
var inc float64 = 0
|
||||
var max float64 = 0
|
||||
|
||||
// Create a buffer
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Read passed in runes
|
||||
for _, ch := range chp {
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
|
||||
if tok, tbv, _ = s.part(); tok == ILLEGAL {
|
||||
buf.WriteString(tbv)
|
||||
return ILLEGAL, buf.String(), val
|
||||
} else {
|
||||
buf.WriteString(tbv)
|
||||
}
|
||||
|
||||
if ch := s.next(); ch == ':' {
|
||||
buf.WriteRune(ch)
|
||||
} else {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
|
||||
if ch := s.next(); isSignal(ch) {
|
||||
tok, lit, _ = s.scanSignal(ch)
|
||||
buf.WriteString(lit)
|
||||
max, _ = strconv.ParseFloat(lit, 64)
|
||||
} else {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
|
||||
if ch := s.next(); ch == ',' {
|
||||
com = true
|
||||
buf.WriteRune(ch)
|
||||
if ch := s.next(); isSignal(ch) {
|
||||
tok, lit, _ = s.scanSignal(ch)
|
||||
buf.WriteString(lit)
|
||||
inc, _ = strconv.ParseFloat(lit, 64)
|
||||
} else {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
} else {
|
||||
s.undo()
|
||||
}
|
||||
|
||||
if ch := s.next(); ch == '.' {
|
||||
dot = true
|
||||
buf.WriteRune(ch)
|
||||
if ch := s.next(); ch == '.' {
|
||||
buf.WriteRune(ch)
|
||||
if ch := s.next(); isSignal(ch) {
|
||||
tok, lit, _ = s.scanSignal(ch)
|
||||
buf.WriteString(lit)
|
||||
min = max
|
||||
max, _ = strconv.ParseFloat(lit, 64)
|
||||
} else {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
} else {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
} else {
|
||||
s.undo()
|
||||
}
|
||||
|
||||
if ch := s.next(); ch == '|' {
|
||||
buf.WriteRune(ch)
|
||||
} else {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
|
||||
// If we have a comma, but the
|
||||
// value is below zero, we will
|
||||
// error as this will cause an
|
||||
// infinite loop in db.
|
||||
|
||||
if com == true && inc <= 0 {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
|
||||
// If we have a min, and a max
|
||||
// with .. notation, but no `inc`
|
||||
// is specified, set the `inc` to
|
||||
// a default of `1`.
|
||||
|
||||
if dot == true && inc <= 0 {
|
||||
inc = 1
|
||||
}
|
||||
|
||||
// If we have a comma, but no
|
||||
// max value is specified then
|
||||
// error, as we need a max with
|
||||
// incrementing integer ids.
|
||||
|
||||
if com == true && dot == false {
|
||||
return ILLEGAL, buf.String(), val
|
||||
}
|
||||
|
||||
return MODEL, buf.String(), NewModel(tbv, min, inc, max)
|
||||
|
||||
}
|
||||
|
||||
func (s *scanner) scanSignal(chp ...rune) (tok Token, lit string, val interface{}) {
|
||||
|
||||
// Create a buffer
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Read passed in runes
|
||||
for _, ch := range chp {
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
|
||||
// Read subsequent characters
|
||||
for {
|
||||
if ch := s.next(); ch == eof {
|
||||
break
|
||||
} else if isNumber(ch) {
|
||||
buf.WriteRune(ch)
|
||||
} else if ch == '.' {
|
||||
if s.next() == '.' {
|
||||
s.undo()
|
||||
s.undo()
|
||||
break
|
||||
} else {
|
||||
s.undo()
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
} else {
|
||||
s.undo()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return NUMBER, buf.String(), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *scanner) scanNumber(chp ...rune) (tok Token, lit string, val interface{}) {
|
||||
|
||||
tok = NUMBER
|
||||
|
@ -783,6 +930,11 @@ func isNumber(ch rune) bool {
|
|||
return (ch >= '0' && ch <= '9')
|
||||
}
|
||||
|
||||
// isSignal returns true if the rune is a number.
|
||||
func isSignal(ch rune) bool {
|
||||
return (ch >= '0' && ch <= '9') || ch == '-' || ch == '+'
|
||||
}
|
||||
|
||||
// isLetter returns true if the rune is a letter.
|
||||
func isLetter(ch rune) bool {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == 'µ'
|
||||
|
|
|
@ -37,6 +37,7 @@ const (
|
|||
EXPR // something[0].value
|
||||
IDENT // something
|
||||
THING // @class:id
|
||||
MODEL // [person|1..1000]
|
||||
STRING // "something"
|
||||
REGION // "a multiline \n string"
|
||||
NUMBER // 123456
|
||||
|
@ -214,6 +215,7 @@ var tokens = [...]string{
|
|||
EXPR: "EXPR",
|
||||
IDENT: "IDENT",
|
||||
THING: "THING",
|
||||
MODEL: "MODEL",
|
||||
STRING: "STRING",
|
||||
REGION: "REGION",
|
||||
NUMBER: "NUMBER",
|
||||
|
|
Loading…
Reference in a new issue