mirror of
https://gitee.com/rulego/streamsql.git
synced 2025-07-12 10:36:23 +00:00
159 lines
3.1 KiB
Go
159 lines
3.1 KiB
Go
package rsql
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Parser struct {
|
|
lexer *Lexer
|
|
}
|
|
|
|
func NewParser(input string) *Parser {
|
|
return &Parser{
|
|
lexer: NewLexer(input),
|
|
}
|
|
}
|
|
|
|
func (p *Parser) Parse() (*SelectStatement, error) {
|
|
stmt := &SelectStatement{}
|
|
|
|
// 解析SELECT子句
|
|
if err := p.parseSelect(stmt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 解析FROM子句
|
|
if err := p.parseFrom(stmt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 解析WHERE子句
|
|
if err := p.parseWhere(stmt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 解析GROUP BY子句
|
|
if err := p.parseGroupBy(stmt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return stmt, nil
|
|
}
|
|
|
|
func (p *Parser) parseSelect(stmt *SelectStatement) error {
|
|
p.lexer.NextToken() // 跳过SELECT
|
|
|
|
for {
|
|
tok := p.lexer.NextToken()
|
|
if tok.Type == TokenFROM {
|
|
break
|
|
}
|
|
|
|
field := Field{Expression: tok.Value}
|
|
if p.lexer.peekChar() == ' ' {
|
|
if aliasTok := p.lexer.NextToken(); aliasTok.Type == TokenAS {
|
|
field.Alias = p.lexer.NextToken().Value
|
|
}
|
|
}
|
|
stmt.Fields = append(stmt.Fields, field)
|
|
|
|
if p.lexer.NextToken().Type != TokenComma {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseFrom(stmt *SelectStatement) error {
|
|
tok := p.lexer.NextToken()
|
|
if tok.Type != TokenIdent {
|
|
return errors.New("expected source identifier after FROM")
|
|
}
|
|
stmt.Source = tok.Value
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseWhere(stmt *SelectStatement) error {
|
|
var conditions []string
|
|
p.lexer.NextToken() // 跳过WHERE
|
|
|
|
for {
|
|
tok := p.lexer.NextToken()
|
|
switch tok.Type {
|
|
case TokenIdent, TokenNumber, TokenString:
|
|
conditions = append(conditions, tok.Value)
|
|
case TokenEQ:
|
|
conditions = append(conditions, "==")
|
|
case TokenAND:
|
|
conditions = append(conditions, "&&")
|
|
case TokenOR:
|
|
conditions = append(conditions, "||")
|
|
default:
|
|
stmt.Condition = strings.Join(conditions, " ")
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseGroupBy(stmt *SelectStatement) error {
|
|
p.lexer.NextToken() // 跳过GROUP
|
|
p.lexer.NextToken() // 跳过BY
|
|
|
|
for {
|
|
tok := p.lexer.NextToken()
|
|
if tok.Type == TokenTumbling || tok.Type == TokenSliding {
|
|
return p.parseWindowFunction(stmt, tok.Value)
|
|
}
|
|
|
|
stmt.GroupBy = append(stmt.GroupBy, tok.Value)
|
|
|
|
if p.lexer.NextToken().Type != TokenComma {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseWindowFunction(stmt *SelectStatement, winType string) error {
|
|
p.lexer.NextToken() // 跳过函数名
|
|
params := make(map[string]interface{})
|
|
|
|
for p.lexer.peekChar() != ')' {
|
|
keyTok := p.lexer.NextToken()
|
|
if keyTok.Type != TokenIdent {
|
|
return fmt.Errorf("expected parameter key, got %v", keyTok)
|
|
}
|
|
|
|
valTok := p.lexer.NextToken()
|
|
params[keyTok.Value] = convertValue(valTok.Value)
|
|
}
|
|
|
|
stmt.Window = WindowDefinition{
|
|
Type: winType,
|
|
Params: params,
|
|
}
|
|
return nil
|
|
|
|
}
|
|
func convertValue(s string) interface{} {
|
|
if s == "true" {
|
|
return true
|
|
}
|
|
if s == "false" {
|
|
return false
|
|
}
|
|
if i, err := strconv.Atoi(s); err == nil {
|
|
return i
|
|
}
|
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
|
return f
|
|
}
|
|
if strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'") {
|
|
return strings.Trim(s, "'")
|
|
}
|
|
return s
|
|
}
|