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
|
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
|
// Param
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (p *parser) parseWhat() (mul []Expr, err error) {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
tok, lit, err := p.shouldBe(IDENT, THING, PARAM)
|
tok, lit, err := p.shouldBe(IDENT, THING, PARAM, MODEL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseError{Found: lit, Expected: []string{"table, or thing"}}
|
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)
|
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
|
// Check to see if the next token is a comma
|
||||||
// and if not, then break out of the loop,
|
// and if not, then break out of the loop,
|
||||||
// otherwise repeat until we find no comma.
|
// otherwise repeat until we find no comma.
|
||||||
|
|
152
sql/scanner.go
152
sql/scanner.go
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -136,6 +137,7 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) {
|
||||||
return OR, "OR", val
|
return OR, "OR", val
|
||||||
default:
|
default:
|
||||||
s.undo()
|
s.undo()
|
||||||
|
return s.scanModel(ch)
|
||||||
}
|
}
|
||||||
case '&':
|
case '&':
|
||||||
chn := s.next()
|
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{}) {
|
func (s *scanner) scanNumber(chp ...rune) (tok Token, lit string, val interface{}) {
|
||||||
|
|
||||||
tok = NUMBER
|
tok = NUMBER
|
||||||
|
@ -783,6 +930,11 @@ func isNumber(ch rune) bool {
|
||||||
return (ch >= '0' && ch <= '9')
|
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.
|
// isLetter returns true if the rune is a letter.
|
||||||
func isLetter(ch rune) bool {
|
func isLetter(ch rune) bool {
|
||||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == 'µ'
|
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == 'µ'
|
||||||
|
|
|
@ -37,6 +37,7 @@ const (
|
||||||
EXPR // something[0].value
|
EXPR // something[0].value
|
||||||
IDENT // something
|
IDENT // something
|
||||||
THING // @class:id
|
THING // @class:id
|
||||||
|
MODEL // [person|1..1000]
|
||||||
STRING // "something"
|
STRING // "something"
|
||||||
REGION // "a multiline \n string"
|
REGION // "a multiline \n string"
|
||||||
NUMBER // 123456
|
NUMBER // 123456
|
||||||
|
@ -214,6 +215,7 @@ var tokens = [...]string{
|
||||||
EXPR: "EXPR",
|
EXPR: "EXPR",
|
||||||
IDENT: "IDENT",
|
IDENT: "IDENT",
|
||||||
THING: "THING",
|
THING: "THING",
|
||||||
|
MODEL: "MODEL",
|
||||||
STRING: "STRING",
|
STRING: "STRING",
|
||||||
REGION: "REGION",
|
REGION: "REGION",
|
||||||
NUMBER: "NUMBER",
|
NUMBER: "NUMBER",
|
||||||
|
|
Loading…
Reference in a new issue