Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions ast/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package ast

import (
"bytes"
"strings"
"sync"
"unicode"
"unicode/utf8"
Expand Down Expand Up @@ -52,9 +53,40 @@ func (n *node) OriginTextPosition() int {
func (n *node) SetText(enc charset.Encoding, text string) {
n.enc = enc
n.text = text
if textConversionIsNoop(enc, text) {
// Text() would return text unchanged, so skip allocating the
// lazy-conversion Once and let Text() return n.text directly.
n.once = nil
return
}
n.once = &sync.Once{}
}

// textConversionIsNoop reports whether convertBinaryStringLiterals would
// provably return text unchanged, so that Text() can serve n.text without
// a per-node sync.Once. It must stay conservative: false only means the
// lazy path decides at Text() time.
//
// With no quote characters, convertBinaryStringLiterals is exactly
// enc.Transform(nil, text, OpDecodeReplace). That is the identity for the
// binary and latin1 encodings, and for the utf8 encoding when text is
// valid UTF-8.
func textConversionIsNoop(enc charset.Encoding, text string) bool {
if enc == nil {
return true
}
if strings.IndexByte(text, '\'') >= 0 || strings.IndexByte(text, '"') >= 0 {
return false
}
switch enc {
case charset.EncodingBinImpl, charset.EncodingLatin1Impl:
return true
case charset.EncodingUTF8Impl:
return utf8.ValidString(text)
}
return false
}

// SetNoBackslashEscapes marks that the SQL mode NO_BACKSLASH_ESCAPES was active
// when this node was parsed, so backslash is not treated as an escape character
// in string literals
Expand Down
68 changes: 57 additions & 11 deletions parser/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,15 +393,31 @@ func (*Scanner) handleIdent(lval *yySymType) int {
return underscoreCS
}

// isWhitespaceTable[b] == unicode.IsSpace(rune(b)) for every byte value.
var isWhitespaceTable = func() (t [256]bool) {
for i := range t {
t[i] = unicode.IsSpace(rune(i))
}
return
}()

func (s *Scanner) skipWhitespace() byte {
return s.r.incAsLongAs(func(b byte) bool {
return unicode.IsSpace(rune(b))
})
r := &s.r
for {
ch := r.peek()
if !isWhitespaceTable[ch] {
return ch
}
if r.eof() {
return 0
}
r.inc()
}
}

func (s *Scanner) scan() (tok int, pos Pos, lit string) {
ch0 := s.r.peek()
if unicode.IsSpace(rune(ch0)) {
if isWhitespaceTable[ch0] {
ch0 = s.skipWhitespace()
}
pos = s.r.pos()
Expand Down Expand Up @@ -658,7 +674,7 @@ func startWithAt(s *Scanner) (tok int, pos Pos, lit string) {

func scanIdentifier(s *Scanner) (int, Pos, string) {
pos := s.r.pos()
s.r.incAsLongAs(isIdentChar)
s.r.incIdent()
return identifier, pos, s.r.data(&pos)
}

Expand Down Expand Up @@ -841,7 +857,7 @@ func startWithNumber(s *Scanner) (tok int, pos Pos, lit string) {
p2 := s.r.pos()
// 0x, 0x7fz3 are identifier
if p1 == p2 || isDigit(s.r.peek()) {
s.r.incAsLongAs(isIdentChar)
s.r.incIdent()
return identifier, pos, s.r.data(&pos)
}
tok = hexLit
Expand All @@ -852,14 +868,14 @@ func startWithNumber(s *Scanner) (tok int, pos Pos, lit string) {
p2 := s.r.pos()
// 0b, 0b123, 0b1ab are identifier
if p1 == p2 || isDigit(s.r.peek()) {
s.r.incAsLongAs(isIdentChar)
s.r.incIdent()
return identifier, pos, s.r.data(&pos)
}
tok = bitLit
case ch1 == '.':
return s.scanFloat(&pos)
case ch1 == 'B':
s.r.incAsLongAs(isIdentChar)
s.r.incIdent()
return identifier, pos, s.r.data(&pos)
}
}
Expand All @@ -872,7 +888,7 @@ func startWithNumber(s *Scanner) (tok int, pos Pos, lit string) {

// Identifiers may begin with a digit but unless quoted may not consist solely of digits.
if !s.r.eof() && isIdentChar(ch0) {
s.r.incAsLongAs(isIdentChar)
s.r.incIdent()
return identifier, pos, s.r.data(&pos)
}
lit = s.r.data(&pos)
Expand Down Expand Up @@ -940,7 +956,7 @@ func (s *Scanner) scanFloat(beg *Pos) (tok int, pos Pos, lit string) {
// 9e9e = 9e9(float) + e(identifier)
// 9est = 9est(identifier)
s.r.updatePos(*beg)
s.r.incAsLongAs(isIdentChar)
s.r.incIdent()
tok = identifier
}
} else {
Expand All @@ -952,7 +968,7 @@ func (s *Scanner) scanFloat(beg *Pos) (tok int, pos Pos, lit string) {

func (s *Scanner) scanDigits() string {
pos := s.r.pos()
s.r.incAsLongAs(isDigit)
s.r.incDigits()
return s.r.data(&pos)
}

Expand Down Expand Up @@ -1100,6 +1116,36 @@ func (r *reader) incAsLongAs(fn func(b byte) bool) byte {
}
}

// incIdent is incAsLongAs(isIdentChar) without the per-byte closure call:
// identifier characters never include '\n', so only Offset and Col move.
// It returns the byte that stopped the scan (0 at EOF).
func (r *reader) incIdent() byte {
i := r.p.Offset
for i < r.l && isIdentCharTable[r.s[i]] {
i++
}
r.p.Col += i - r.p.Offset
r.p.Offset = i
if i >= r.l {
return 0
}
return r.s[i]
}

// incDigits is incAsLongAs(isDigit) with the same fast shape as incIdent.
func (r *reader) incDigits() byte {
i := r.p.Offset
for i < r.l && r.s[i] >= '0' && r.s[i] <= '9' {
i++
}
r.p.Col += i - r.p.Offset
r.p.Offset = i
if i >= r.l {
return 0
}
return r.s[i]
}

// skipRune skip mb character, return true indicate something has been skipped.
func (r *reader) skipRune(enc charset.Encoding) bool {
if r.s[r.p.Offset] <= unicode.MaxASCII {
Expand Down
4 changes: 4 additions & 0 deletions parser/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ func isInCorrectIdentifierName(name string) bool {
// Initialize a lookup table for isUserVarChar
var isUserVarCharTable [256]bool

// Lookup table for isIdentChar, used by the lexer's hottest scanning loop.
var isIdentCharTable [256]bool

func init() {
for i := range 256 {
ch := byte(i)
isUserVarCharTable[i] = isLetter(ch) || isDigit(ch) || ch == '_' || ch == '$' || ch == '.' || isIdentExtend(ch)
isIdentCharTable[i] = isIdentChar(ch)
}
}

Expand Down
12 changes: 10 additions & 2 deletions parser/parse_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,14 @@ func (r *rdParser) parseSimpleIdentAtom(start int) ast.ExprNode {
Args: args,
}, start)
}
name := &ast.ColumnName{}
// The expr and its ColumnName are allocated as one block: column
// references are the most-allocated node in typical queries, and the
// two objects always live and die together.
block := &struct {
expr ast.ColumnNameExpr
name ast.ColumnName
}{}
name := &block.name
if r.tok() == int('.') && isIdentifierTok(r.la(1)) {
r.advance()
second := r.parseIdentifier()
Expand All @@ -711,7 +718,8 @@ func (r *rdParser) parseSimpleIdentAtom(start int) ast.ExprNode {
} else {
name.Name = ast.NewCIStr(first)
}
col := &ast.ColumnNameExpr{Name: name}
col := &block.expr
col.Name = name
r.setOrigin(col, start)
switch r.tok() {
case jss:
Expand Down
54 changes: 44 additions & 10 deletions parser/rd_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ type rdParser struct {
done bool
marks []int // absolute indices pinned by speculative parses

// c points at the current token's window slot, kept in sync by
// advance and rewind (the only movers of i) so that cur and tok are
// call-free field loads. Slot pointers stay valid across window
// growth (the old backing array keeps the same values) and are
// recomputed after compaction, which rewrites slots in place.
c *rdToken

// stmtStart mirrors Scanner.stmtStartPos bookkeeping for stmtText().
stmtStart int
result []ast.StmtNode
Expand Down Expand Up @@ -137,51 +144,75 @@ func (parser *Parser) newRDScanner(sql string) *Scanner {
func (r *rdParser) lexOne() {
var v yySymType
tok := r.sc.Lex(&v)
t := rdToken{tok: tok, lit: v.ident, item: v.item, offset: v.offset}
// Extend the window and fill the slot in place rather than building
// an rdToken and copying it in: the struct is large and this is the
// hottest allocation-free path in the parser. Recycled slots hold
// stale tokens, so every field is (re)assigned.
if len(r.win) == cap(r.win) {
r.win = append(r.win, rdToken{})
} else {
r.win = r.win[:len(r.win)+1]
}
t := &r.win[len(r.win)-1]
t.tok, t.lit, t.item, t.offset = tok, v.ident, v.item, v.offset
p := r.sc.r.pos()
t.endOffset, t.endLine, t.endCol = p.Offset, p.Line, p.Col
if tok == hintComment {
t.hintPos = r.sc.lastHintPos
} else {
t.hintPos = Pos{}
}
if len(r.sc.errs) > 0 {
// A lexing problem already recorded its error(s).
// A lexing problem already recorded its error(s). The parse is
// abandoned, so the token left in the window is harmless.
panic(rdLexError{})
}
if tok == invalid {
// The parser side of an invalid token is a plain syntax error at
// its position.
panic(rdSyntaxError{offset: t.offset, err: r.buildSyntaxError(&t)})
panic(rdSyntaxError{offset: t.offset, err: r.buildSyntaxError(t)})
}
r.win = append(r.win, t)
if tok == 0 {
r.done = true
}
}

// at returns the token at absolute index abs, lexing forward as needed.
// Past EOF it returns the EOF token.
// Past EOF it returns the EOF token. The in-window fast path is kept
// small enough to inline into tok/la/cur, which are the parser's hottest
// calls.
func (r *rdParser) at(abs int) *rdToken {
idx := abs - r.base
if idx >= len(r.win) {
idx = r.fill(abs)
}
return &r.win[idx]
}

// fill lexes until the window covers abs (or EOF) and returns the window
// index to read.
func (r *rdParser) fill(abs int) int {
for abs-r.base >= len(r.win) {
if r.done {
return &r.win[len(r.win)-1]
return len(r.win) - 1
}
r.lexOne()
}
return &r.win[abs-r.base]
return abs - r.base
}

func (r *rdParser) cur() *rdToken { return r.at(r.i) }
func (r *rdParser) cur() *rdToken { return r.c }

// tok returns the current token id (0 at EOF).
func (r *rdParser) tok() int { return r.at(r.i).tok }
func (r *rdParser) tok() int { return r.c.tok }

// la returns the token id k positions ahead (la(0) == tok()).
func (r *rdParser) la(k int) int { return r.at(r.i + k).tok }

// advance moves past the current token and opportunistically drops window
// tokens that no active mark or the cursor can reach again.
func (r *rdParser) advance() {
if t := r.at(r.i); t.tok != 0 {
if r.c.tok != 0 {
r.i++
}
low := r.i
Expand All @@ -193,6 +224,7 @@ func (r *rdParser) advance() {
r.win = r.win[:n]
r.base = low
}
r.c = r.at(r.i)
}

// mark pins the current position for a speculative parse. Every mark is
Expand All @@ -208,6 +240,7 @@ func (r *rdParser) unmark() {

func (r *rdParser) rewind(m int) {
r.i = m
r.c = r.at(m)
r.unmark()
}

Expand Down Expand Up @@ -273,6 +306,7 @@ func (parser *Parser) parseRD(sql string) (stmts []ast.StmtNode, warns []error,
}
stmts, err = nil, lexErrs[0]
})
r.c = r.at(r.i)
r.parseStatementList()

lexWarns, lexErrs := r.sc.Errors()
Expand Down
Loading