func (ls *lineState) beginStatement() {
	ls.stmt = &Statement{
		File:            ls.filePath,
		LineNo:          ls.lineNo,
		CharNo:          ls.charNo,
		DefaultDatabase: ls.defaultDatabase,
		delimiter:       ls.delimiter,
	}
}
beginStatement records the starting position of the next (not yet fully
tokenized) statement.
package fs
import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"
	"github.com/alecthomas/participle"
	"github.com/alecthomas/participle/lexer"
	"github.com/skeema/tengo"
)
type StatementType int
const (
	StatementTypeUnknown StatementType = iota
	StatementTypeNoop                   
	StatementTypeCommand                
	StatementTypeCreate
	StatementTypeAlter  
)
type Statement struct {
	File            string
	LineNo          int
	CharNo          int
	Text            string  
	DefaultDatabase string  
	Type            StatementType
	ObjectType      tengo.ObjectType
	ObjectName      string
	ObjectQualifier string
	FromFile        *TokenizedSQLFile
	delimiter       string
}
func (stmt *Statement) Location() string {
	if stmt.File == "" && stmt.LineNo == 0 && stmt.CharNo == 0 {
		return ""
	}
	if stmt.File == "" {
		return fmt.Sprintf("unknown:%d:%d", stmt.LineNo, stmt.CharNo)
	}
	return fmt.Sprintf("%s:%d:%d", stmt.File, stmt.LineNo, stmt.CharNo)
}
func (stmt *Statement) ObjectKey() tengo.ObjectKey {
	return tengo.ObjectKey{
		Type: stmt.ObjectType,
		Name: stmt.ObjectName,
	}
}
func (stmt *Statement) Schema() string {
	if stmt.ObjectQualifier != "" {
		return stmt.ObjectQualifier
	}
	return stmt.DefaultDatabase
}
func (stmt *Statement) Body() string {
	body, _ := stmt.SplitTextBody()
	if stmt.ObjectQualifier != "" {
		pattern := fmt.Sprintf("(?is)^(.*?create.+?%s.*?)(`%s`|%s)([\\s\\p{Zs}]*\\.[\\s\\p{Zs}]*)", string(stmt.ObjectType), stmt.ObjectQualifier, stmt.ObjectQualifier)
		re := regexp.MustCompile(pattern)
		body = re.ReplaceAllString(body, "$1")
	}
	return body
}
func (stmt *Statement) SplitTextBody() (body string, suffix string) {
	body = strings.TrimRight(stmt.Text, "\n\r\t ")
	body = strings.TrimSuffix(body, stmt.delimiter)
	body = strings.TrimRight(body, "\n\r\t ")
	return body, stmt.Text[len(body):]
}
func (stmt *Statement) Remove() {
	for i, comp := range stmt.FromFile.Statements {
		if stmt == comp {
			copy(stmt.FromFile.Statements[i:], stmt.FromFile.Statements[i+1:])
			stmt.FromFile.Statements[len(stmt.FromFile.Statements)-1] = nil
			stmt.FromFile.Statements = stmt.FromFile.Statements[:len(stmt.FromFile.Statements)-1]
			return
		}
	}
	panic(fmt.Errorf("Statement previously at %s not actually found in file", stmt.Location()))
}
func (stmt *Statement) isCreateWithBegin() bool {
	return stmt.Type == StatementTypeCreate &&
		(stmt.ObjectType == tengo.ObjectTypeProc || stmt.ObjectType == tengo.ObjectTypeFunc) &&
		strings.Contains(strings.ToLower(stmt.Text), "begin")
}
func CanParse(input string) (bool, error) {
	sqlStmt := &sqlStatement{}
	err := nameParser.ParseString(input, sqlStmt)
	return err == nil && !sqlStmt.forbidden(), err
}
type statementTokenizer struct {
	filePath  string
	delimiter string  
	result []*Statement  
	stmt   *Statement    
	buf    bytes.Buffer  
	lineNo          int     
	inRelevant      bool    
	inCComment      bool    
	inQuote         rune    
	defaultDatabase string  
}
type lineState struct {
	*statementTokenizer
	line   string  
	pos    int     
	charNo int     
}
func newStatementTokenizer(filePath, delimiter string) *statementTokenizer {
	return &statementTokenizer{
		filePath:  filePath,
		delimiter: delimiter,
	}
}
func (st *statementTokenizer) statements() ([]*Statement, error) {
	file, err := os.Open(st.filePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	reader := bufio.NewReader(file)
	for err != io.EOF {
		var line string
		line, err = reader.ReadString('\n')
		if err != nil && err != io.EOF {
			return st.result, err
		}
		st.processLine(line, err == io.EOF)
	}
	if st.inQuote != 0 {
		err = fmt.Errorf("File %s has unterminated quote %c", st.filePath, st.inQuote)
	} else if st.inCComment {
		err = fmt.Errorf("File %s has unterminated C-style comment", st.filePath)
	} else {
		err = nil
	}
	return st.result, err
}
func (st *statementTokenizer) processLine(line string, eof bool) {
	st.lineNo++
	ls := &lineState{
		statementTokenizer: st,
		line:               line,
	}
	for ls.pos < len(ls.line) {
		c, cLen := ls.nextRune()
		if ls.stmt == nil {
			ls.beginStatement()
		}
		if ls.inCComment {
			if c == '*' && ls.peekRune() == '/' {
				ls.nextRune()
				ls.inCComment = false
			}
			continue
		} else if ls.inQuote > 0 {
			if c == '\\' {
				ls.nextRune()
			} else if c == ls.inQuote {
				if ls.peekRune() == ls.inQuote {
					ls.nextRune()
				} else {
					ls.inQuote = 0
				}
			}
			continue
		}
		if c == '/' && ls.peekRune() == '*' {
			ls.inCComment = true
			ls.nextRune()
			continue
		}
		if c == '#' {
			ls.buf.WriteString(ls.line[ls.pos:])
			break
		}
		if c == '-' && ls.peekRune() == '-' {
			ls.nextRune()
			if unicode.IsSpace(ls.peekRune()) {
				ls.buf.WriteString(ls.line[ls.pos:])
				break
			}
		}
		if !ls.inRelevant && !unicode.IsSpace(c) {
			ls.doneStatement(cLen)
			ls.inRelevant = true
		}
		delimFirstRune, delimFirstRuneLen := utf8.DecodeRuneInString(st.delimiter)
		delimRuneCount := utf8.RuneCountInString(st.delimiter)
		switch c {
		case '\n':
			lowerBufStr := strings.ToLower(ls.buf.String())
			if strings.HasPrefix(lowerBufStr, "use ") || strings.HasPrefix(lowerBufStr, "delimiter ") {
				ls.doneStatement(0)
			}
		case '"', '`', '\'':
			ls.inQuote = c
		case delimFirstRune:
			if delimRuneCount > 1 {
				if ls.peekRunes(delimRuneCount-1) != st.delimiter[delimFirstRuneLen:] {
					break
				}
				for n := 0; n < delimRuneCount-1; n++ {
					ls.nextRune()
				}
			}
			if ls.peekRune() == '\n' {
				ls.nextRune()
			} else if ls.peekRunes(2) == "\r\n" {
				ls.nextRune()
				ls.nextRune()
			}
			ls.doneStatement(0)
		}
	}
	if eof {
		ls.doneStatement(0)
	}
}
func (ls *lineState) nextRune() (rune, int) {
	if ls.pos >= len(ls.line) {
		return 0, 0
	}
	c, cLen := utf8.DecodeRuneInString(ls.line[ls.pos:])
	ls.buf.WriteRune(c)
	ls.pos += cLen
	ls.charNo++
	return c, cLen
}
func (ls *lineState) peekRune() rune {
	if ls.pos >= len(ls.line) {
		return 0
	}
	c, _ := utf8.DecodeRuneInString(ls.line[ls.pos:])
	return c
}
func (ls *lineState) peekRunes(n int) string {
	pos := ls.pos
	for n > 0 && pos < len(ls.line) {
		_, runeLen := utf8.DecodeRuneInString(ls.line[pos:])
		pos += runeLen
		n--
	}
	return ls.line[ls.pos:pos]
}