github.com/team-ide/go-dialect@v1.9.20/vitess/sqlparser/parser.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package sqlparser 18 19 import ( 20 "fmt" 21 "io" 22 "strconv" 23 "strings" 24 "sync" 25 26 "github.com/team-ide/go-dialect/vitess/vterrors" 27 28 vtrpcpb "github.com/team-ide/go-dialect/vitess/vtrpc" 29 ) 30 31 var versionFlagSync sync.Once 32 33 // parserPool is a pool for parser objects. 34 var parserPool = sync.Pool{ 35 New: func() interface{} { 36 return &yyParserImpl{} 37 }, 38 } 39 40 // zeroParser is a zero-initialized parser to help reinitialize the parser for pooling. 41 var zeroParser yyParserImpl 42 43 // yyParsePooled is a wrapper around yyParse that pools the parser objects. There isn't a 44 // particularly good reason to use yyParse directly, since it immediately discards its parser. 45 // 46 // N.B: Parser pooling means that you CANNOT take references directly to parse stack variables (e.g. 47 // $$ = &$4) in sql.y rules. You must instead add an intermediate reference like so: 48 // showCollationFilterOpt := $4 49 // $$ = &Show{Type: string($2), ShowCollationFilterOpt: &showCollationFilterOpt} 50 func yyParsePooled(yylex yyLexer) int { 51 parser := parserPool.Get().(*yyParserImpl) 52 defer func() { 53 *parser = zeroParser 54 parserPool.Put(parser) 55 }() 56 return parser.Parse(yylex) 57 } 58 59 // Instructions for creating new types: If a type 60 // needs to satisfy an interface, declare that function 61 // along with that interface. This will help users 62 // identify the list of types to which they can assert 63 // those interfaces. 64 // If the member of a type has a string with a predefined 65 // list of values, declare those values as const following 66 // the type. 67 // For interfaces that define dummy functions to consolidate 68 // a set of types, define the function as iTypeName. 69 // This will help avoid name collisions. 70 71 // Parse2 parses the SQL in full and returns a Statement, which 72 // is the AST representation of the query, and a set of BindVars, which are all the 73 // bind variables that were found in the original SQL query. If a DDL statement 74 // is partially parsed but still contains a syntax error, the 75 // error is ignored and the DDL is returned anyway. 76 func Parse2(sql string) (Statement, BindVars, error) { 77 tokenizer := NewStringTokenizer(sql) 78 if yyParsePooled(tokenizer) != 0 { 79 if tokenizer.partialDDL != nil { 80 if typ, val := tokenizer.Scan(); typ != 0 { 81 return nil, nil, fmt.Errorf("extra characters encountered after end of DDL: '%s'", string(val)) 82 } 83 switch x := tokenizer.partialDDL.(type) { 84 case DBDDLStatement: 85 x.SetFullyParsed(false) 86 case DDLStatement: 87 x.SetFullyParsed(false) 88 } 89 tokenizer.ParseTree = tokenizer.partialDDL 90 return tokenizer.ParseTree, tokenizer.BindVars, nil 91 } 92 return nil, nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, tokenizer.LastError.Error()) 93 } 94 if tokenizer.ParseTree == nil { 95 return nil, nil, ErrEmpty 96 } 97 return tokenizer.ParseTree, tokenizer.BindVars, nil 98 } 99 100 // convertMySQLVersionToCommentVersion converts the MySQL version into comment version format. 101 func convertMySQLVersionToCommentVersion(version string) (string, error) { 102 var res = make([]int, 3) 103 idx := 0 104 val := "" 105 for _, c := range version { 106 if c <= '9' && c >= '0' { 107 val += string(c) 108 } else if c == '.' { 109 v, err := strconv.Atoi(val) 110 if err != nil { 111 return "", err 112 } 113 val = "" 114 res[idx] = v 115 idx++ 116 if idx == 3 { 117 break 118 } 119 } else { 120 break 121 } 122 } 123 if val != "" { 124 v, err := strconv.Atoi(val) 125 if err != nil { 126 return "", err 127 } 128 res[idx] = v 129 idx++ 130 } 131 if idx == 0 { 132 return "", vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "MySQL version not correctly setup - %s.", version) 133 } 134 135 return fmt.Sprintf("%01d%02d%02d", res[0], res[1], res[2]), nil 136 } 137 138 // Parse behaves like Parse2 but does not return a set of bind variables 139 func Parse(sql string) (Statement, error) { 140 stmt, _, err := Parse2(sql) 141 return stmt, err 142 } 143 144 // ParseStrictDDL is the same as Parse except it errors on 145 // partially parsed DDL statements. 146 func ParseStrictDDL(sql string) (Statement, error) { 147 tokenizer := NewStringTokenizer(sql) 148 if yyParsePooled(tokenizer) != 0 { 149 return nil, tokenizer.LastError 150 } 151 if tokenizer.ParseTree == nil { 152 return nil, ErrEmpty 153 } 154 return tokenizer.ParseTree, nil 155 } 156 157 // ParseTokenizer is a raw interface to parse from the given tokenizer. 158 // This does not used pooled parsers, and should not be used in general. 159 func ParseTokenizer(tokenizer *Tokenizer) int { 160 return yyParse(tokenizer) 161 } 162 163 // ParseNext parses a single SQL statement from the tokenizer 164 // returning a Statement which is the AST representation of the query. 165 // The tokenizer will always read up to the end of the statement, allowing for 166 // the next call to ParseNext to parse any subsequent SQL statements. When 167 // there are no more statements to parse, a error of io.EOF is returned. 168 func ParseNext(tokenizer *Tokenizer) (Statement, error) { 169 return parseNext(tokenizer, false) 170 } 171 172 // ParseNextStrictDDL is the same as ParseNext except it errors on 173 // partially parsed DDL statements. 174 func ParseNextStrictDDL(tokenizer *Tokenizer) (Statement, error) { 175 return parseNext(tokenizer, true) 176 } 177 178 func parseNext(tokenizer *Tokenizer, strict bool) (Statement, error) { 179 if tokenizer.cur() == ';' { 180 tokenizer.skip(1) 181 tokenizer.skipBlank() 182 } 183 if tokenizer.cur() == eofChar { 184 return nil, io.EOF 185 } 186 187 tokenizer.reset() 188 tokenizer.multi = true 189 if yyParsePooled(tokenizer) != 0 { 190 if tokenizer.partialDDL != nil && !strict { 191 tokenizer.ParseTree = tokenizer.partialDDL 192 return tokenizer.ParseTree, nil 193 } 194 return nil, tokenizer.LastError 195 } 196 if tokenizer.ParseTree == nil { 197 return ParseNext(tokenizer) 198 } 199 return tokenizer.ParseTree, nil 200 } 201 202 // ErrEmpty is a sentinel error returned when parsing empty statements. 203 var ErrEmpty = vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.EmptyQuery, "query was empty") 204 205 // SplitStatement returns the first sql statement up to either a ; or EOF 206 // and the remainder from the given buffer 207 func SplitStatement(blob string) (string, string, error) { 208 tokenizer := NewStringTokenizer(blob) 209 tkn := 0 210 for { 211 tkn, _ = tokenizer.Scan() 212 if tkn == 0 || tkn == ';' || tkn == eofChar { 213 break 214 } 215 } 216 if tokenizer.LastError != nil { 217 return "", "", tokenizer.LastError 218 } 219 if tkn == ';' { 220 return blob[:tokenizer.Pos-1], blob[tokenizer.Pos:], nil 221 } 222 return blob, "", nil 223 } 224 225 // SplitStatementToPieces split raw sql statement that may have multi sql pieces to sql pieces 226 // returns the sql pieces blob contains; or error if sql cannot be parsed 227 func SplitStatementToPieces(blob string) (pieces []string, err error) { 228 // fast path: the vast majority of SQL statements do not have semicolons in them 229 if blob == "" { 230 return nil, nil 231 } 232 switch strings.IndexByte(blob, ';') { 233 case -1: // if there is no semicolon, return blob as a whole 234 return []string{blob}, nil 235 case len(blob) - 1: // if there's a single semicolon and it's the last character, return blob without it 236 return []string{blob[:len(blob)-1]}, nil 237 } 238 239 pieces = make([]string, 0, 16) 240 tokenizer := NewStringTokenizer(blob) 241 242 tkn := 0 243 var stmt string 244 stmtBegin := 0 245 emptyStatement := true 246 loop: 247 for { 248 tkn, _ = tokenizer.Scan() 249 switch tkn { 250 case ';': 251 stmt = blob[stmtBegin : tokenizer.Pos-1] 252 if !emptyStatement { 253 pieces = append(pieces, stmt) 254 emptyStatement = true 255 } 256 stmtBegin = tokenizer.Pos 257 case 0, eofChar: 258 blobTail := tokenizer.Pos - 1 259 if stmtBegin < blobTail { 260 stmt = blob[stmtBegin : blobTail+1] 261 if !emptyStatement { 262 pieces = append(pieces, stmt) 263 } 264 } 265 break loop 266 default: 267 emptyStatement = false 268 } 269 } 270 271 err = tokenizer.LastError 272 return 273 } 274 275 // String returns a string representation of an SQLNode. 276 func String(node SQLNode) string { 277 if node == nil { 278 return "<nil>" 279 } 280 281 buf := NewTrackedBuffer(nil) 282 node.formatFast(buf) 283 return buf.String() 284 }