001package gudusoft.gsqlparser; 002 003 004import java.io.BufferedReader; 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.HashMap; 009import java.util.Locale; 010 011/** 012 * Base lexer of all databases - Core tokenization engine for SQL parsing. 013 * 014 * The lexer reads SQL text character by character and produces tokens that represent 015 * the syntactic units of SQL. This process involves several key components and stages: 016 * 017 * <h3>1. Input Management and Buffering</h3> 018 * <ul> 019 * <li><b>yyinput (BufferedReader)</b>: Primary input source for SQL text</li> 020 * <li><b>yyline (char[])</b>: Current line buffer read from input via readln()</li> 021 * <li><b>buf (char[])</b>: Reversed line buffer for character-by-character processing</li> 022 * <li><b>bufptr</b>: Current position in buf, decrements as characters are consumed</li> 023 * </ul> 024 * 025 * <h3>2. Token Text Formation Process</h3> 026 * <pre> 027 * SQL Input → readln() → yyline[] → reversed into buf[] → get_char() → yytextbuf[] 028 * ↓ 029 * yylex() processing 030 * ↓ 031 * yylvalstr (String) 032 * ↓ 033 * TSourceToken.astext 034 * </pre> 035 * 036 * <h4>Key Variables in Token Text Storage:</h4> 037 * <ul> 038 * <li><b>yytextbuf (char[])</b>: Accumulator buffer for current token being formed</li> 039 * <li><b>yytextlen</b>: Current length of text in yytextbuf</li> 040 * <li><b>yytextbufsize</b>: Allocated size of yytextbuf (dynamically grows)</li> 041 * <li><b>yylvalstr (String)</b>: Final token text string created from yytextbuf</li> 042 * <li><b>literalbuf (StringBuilder)</b>: Special buffer for string literals and complex tokens</li> 043 * </ul> 044 * 045 * <h3>3. Position Tracking System</h3> 046 * 047 * The lexer maintains precise position information for every token: 048 * <ul> 049 * <li><b>yylineno</b>: Current line number (1-based)</li> 050 * <li><b>yycolno</b>: Current column number (0-based)</li> 051 * <li><b>offset</b>: Absolute character offset from start of input</li> 052 * <li><b>yylineno_p, yycolno_p, offset_p</b>: Previous position values for token start</li> 053 * </ul> 054 * 055 * <h3>4. Token Creation Workflow</h3> 056 * <ol> 057 * <li>Characters are read via get_char() from buf[] into yytextbuf[]</li> 058 * <li>yylex() identifies token boundaries and type</li> 059 * <li>Token text is extracted: yylvalstr = new String(yytextbuf, 0, yytextlen)</li> 060 * <li>yylexwrap() creates TSourceToken with: 061 * <ul> 062 * <li>astext = yylvalstr (full token text copy)</li> 063 * <li>lineNo = yylineno_p (start line)</li> 064 * <li>columnNo = yycolno_p (start column)</li> 065 * <li>offset = offset_p (absolute position)</li> 066 * </ul> 067 * </li> 068 * </ol> 069 * 070 * <h3>5. Memory Management and Text Copying</h3> 071 * 072 * <b>Current Implementation (Eager Loading):</b> 073 * <ul> 074 * <li>Every token immediately copies its text from yytextbuf to TSourceToken.astext</li> 075 * <li>Original SQL text in yyline is discarded after processing each line</li> 076 * <li>No direct link maintained between token and original input position</li> 077 * </ul> 078 * 079 * <h3>6. Tracing Back to Original Position</h3> 080 * 081 * <b>Currently Possible:</b> 082 * <ul> 083 * <li>Token stores lineNo, columnNo, and offset</li> 084 * <li>These can theoretically locate position in original input</li> 085 * </ul> 086 * 087 * <b>Current Limitations:</b> 088 * <ul> 089 * <li>Original input text is not retained after line processing</li> 090 * <li>yyline buffer is overwritten for each new line</li> 091 * <li>No mechanism to retrieve original text from position alone</li> 092 * </ul> 093 * 094 * @author Gudu Software 095 */ 096public class TCustomLexer { 097 098 // 在 lexer level 创建 token table, 按照 token code存储所有 token 的一些关键信息,主要用于处理一个关键字token被用作column,table name的情况 099 public static int MAX_TOKEN_SIZE = 2048; // 所有可能的token的数量 100 public static int MAX_TOKEN_COLUMN_SIZE = 10; 101 102 // 定义一个具有 MAX_TOKEN_SIZE 个元素的常量数组,每个元素有 MAX_TOKEN_COLUMN_SIZE 列, 列的类型为整数 103 // column 0: 代表该token出现的次数 104 // column 1: 代表该token第一次出现的 x position 105 // column 2: 代表该token第一次出现的 y position 106 // column 3: 代表该token最后一次出现的 x position 107 // column 4: 代表该token最后一次出现的 y position 108 // column 5: 代表该token第一次出现的 position in the token list 109 // column 6: 代表该token最后一次出现的 position in the token list 110 111 public static int COLUMN0_COUNT = 0; 112 public static int COLUMN1_FIRST_X = 1; 113 public static int COLUMN2_FIRST_Y = 2; 114 public static int COLUMN3_LAST_X = 3; 115 public static int COLUMN4_LAST_Y = 4; 116 public static int COLUMN5_FIRST_POS = 5; 117 public static int COLUMN6_LAST_POS = 6; 118 119 /** 120 * Pre-allocated strings for single ASCII characters (0-127). 121 * Used to avoid creating new String objects for common single-char tokens 122 * like '(', ')', ',', ';', '+', '-', '*', '/', etc. 123 * This significantly reduces GC pressure in the lexer hot path. 124 */ 125 private static final String[] SINGLE_CHAR_STRINGS = new String[128]; 126 static { 127 for (int i = 0; i < 128; i++) { 128 SINGLE_CHAR_STRINGS[i] = String.valueOf((char) i); 129 } 130 } 131 132 public long[][] TOKEN_TABLE = new long[MAX_TOKEN_SIZE][MAX_TOKEN_COLUMN_SIZE]; 133 134 /** 135 * Tracks which tokenIds have been written to TOKEN_TABLE during current parse. 136 * Used for incremental reset - only clear entries that were actually used. 137 */ 138 private int[] usedTokenIds = new int[512]; // Typical SQL uses <200 distinct token types 139 private int usedTokenCount = 0; 140 141 /** 142 * Reset TOKEN_TABLE by only clearing entries that were used (incremental clear). 143 * This is O(usedTokenCount) instead of O(MAX_TOKEN_SIZE * MAX_TOKEN_COLUMN_SIZE). 144 * For typical SQL with ~100 distinct token types, this saves clearing ~20,000 entries. 145 */ 146 public void resetTokenTable() { 147 for (int i = 0; i < usedTokenCount; i++) { 148 int tokenId = usedTokenIds[i]; 149 for (int j = 0; j < MAX_TOKEN_COLUMN_SIZE; j++) { 150 TOKEN_TABLE[tokenId][j] = 0L; 151 } 152 } 153 usedTokenCount = 0; 154 } 155 156 // define a function to set value when token is found, input is token id, a token with TSourceToken type 157 public void setTokenTableValue( TSourceToken token) { 158 if (token == null) return; 159 int tokenId = token.tokencode; 160 161 if (tokenId < 0 || tokenId >= MAX_TOKEN_SIZE) { 162 return; 163 } 164 if (TOKEN_TABLE[tokenId][COLUMN0_COUNT] == 0) { 165 // Track this tokenId for incremental reset 166 if (usedTokenCount < usedTokenIds.length) { 167 usedTokenIds[usedTokenCount++] = tokenId; 168 } 169 TOKEN_TABLE[tokenId][COLUMN0_COUNT] = 1; 170 TOKEN_TABLE[tokenId][COLUMN1_FIRST_X] = token.lineNo; 171 TOKEN_TABLE[tokenId][COLUMN2_FIRST_Y] = token.columnNo; 172 TOKEN_TABLE[tokenId][COLUMN3_LAST_X] = token.lineNo; 173 TOKEN_TABLE[tokenId][COLUMN4_LAST_Y] = token.columnNo; 174 TOKEN_TABLE[tokenId][COLUMN5_FIRST_POS] = token.posinlist; 175 TOKEN_TABLE[tokenId][COLUMN6_LAST_POS] = token.posinlist; 176 } else { 177 TOKEN_TABLE[tokenId][COLUMN0_COUNT] += 1; 178 TOKEN_TABLE[tokenId][COLUMN3_LAST_X] = token.lineNo; 179 TOKEN_TABLE[tokenId][COLUMN4_LAST_Y] = token.columnNo; 180 TOKEN_TABLE[tokenId][COLUMN6_LAST_POS] = token.posinlist; 181 } 182 } 183 184 public BufferedReader yyinput; 185 long yylineno,yycolno,offset,yylineno_p,yycolno_p,offset_p; 186 int bufptr,yystate,yysstate,yylstate,yytextlen,yyretval, yytextbufsize, 187 yymatches,yysleng; 188 char[] yyline; 189 /** 190 * Reusable buffer for readln() to reduce per-line allocations. 191 * Expands as needed for long lines and stays expanded for reuse. 192 */ 193 private char[] lineReadBuffer = new char[4096]; 194 /** 195 * Actual content length in lineReadBuffer/yyline. 196 * Used instead of yyline.length since lineReadBuffer is reused without copying. 197 * Like yyline, it still describes the last line read once the input is exhausted. 198 */ 199 private int yylineLen; 200 String yylvalstr; 201 public String dolqstart = "";//postgresql, start part of Dollar-quoted String Constants 202 char yylastchar,yyactchar,yytablechar; 203 boolean yydone,yyreject; 204 char[] yytextbuf; 205 char[] buf; 206 int bufsize; 207 boolean endOfInput; 208 209 //StringBuffer literalbuf; 210 StringBuilder literalbuf; 211 int literallen,literalalloc,xcdepth,nchars,slashstar,dashdash; 212 boolean isqmarktoident; 213 public boolean insqlpluscmd; 214 char dummych1,dummych2,dummych3; 215 boolean utf8NoBreakSpaceReady = false; 216 217 int nestedLessThan = 0; 218 219 boolean isReadyForFunctionBody = false, isInFunctionBody = false; 220 int functionBodyDelimiterIndex = -1; 221 ArrayList<String> functionBodyDelimiter = new ArrayList<>(); 222 223 public static int keyword_type_reserved = 0x0001; 224 public static int keyword_type_keyword = 0x0002; 225 public static int keyword_type_identifier = 0x0004; 226 public static int keyword_type_column = 0x0008; 227 228 public char delimiterchar; 229 public String defaultDelimiterStr; 230 public String tmpDelimiter; 231 232 final static int intial_bufsize = 16384; 233 final static char lf = (char)10; 234 final static int max_chars = 65536*10*2; 235 final static int max_rules = 256*2*10; 236 int max_matches = 1024*20*10*2; 237 238 239 // 下面这些常量按照在 l 文件中出现的次序,必须以 +2 的方式递加. 为什么以 +2 的方式递加 原因忘了,尚未搞清楚。 240 final static int init = 2; 241 final static int xc = 4; 242 final static int xd = 6; 243 final static int xq = 8; 244 final static int xqq = 10; //oracle 245 final static int xdolq = 10;//postgresql 246 final static int xdbracket = 10; 247 final static int xdbrace = 12; 248 final static int xbacktick = 12; 249 250 final static int xbracketrs = 12; //redshift 251 final static int xdolq_clickhouse = 14;//clickhouse: 7th %start in its .l, after xdbracket(10) and xbacktick(12) 252 final static int xqtriple = 14;//bigquery 253 final static int xdtriple = 16;//bigquery 254 255 256 257 //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server 258 final static int namedatalen = 8060;//255; 259 260 final static int cmtslashstar = 257; 261 final static int cmtdoublehyphen = 258; 262 final static int lexspace = 259; 263 final static int lexnewline = 260; 264 final static int fconst = 261; 265 final static int sconst = 262; 266 final static int iconst = 263; 267 final static int ident = 264; 268 final static int op = 265; 269 final static int cmpop = 266; 270 final static int bind_v = 267; 271 final static int assign_sign = 268; 272 final static int double_dot = 269; 273 final static int label_begin = 270; 274 final static int label_end = 271; 275 final static int substitution_v = 272; 276 final static int filepath_sign = TBaseType.filepath_sign; 277 final static int sqlpluscmd = 273; 278 final static int atversion = TBaseType.atversion; //databricks 279 final static int error = 274; 280 final static int variable = 275; 281 final static int mslabel = 276; 282 public final static int bconst = TBaseType.bconst; //postgresql 283 final static int leftjoin_op = 277; 284 final static int odbc_esc_prefix = 277; 285 final static int rightjoin_op = 278; 286 final static int odbc_esc_terminator = 278; 287 final static int db2label = 279; 288 public final static int xconst = TBaseType.xconst; //postgresql 289 final static int ref_arrow = 280; 290 final static int rw_scriptoptions = 281; 291 public final static int UNICODE_ENCODE_ID = 281; 292 final static int mysqllabel = 282; 293 final static int NAMED_PARAMETER_SIGN = 282; //oracle,db2,snowflake CALL update_order (5000, NEW_STATUS => 'Shipped') 294 final static int QUOTED_IDENT = 282;//used in mdx 295 final static int BTEQCMD = 282; 296 final static int concatenationop = 283; 297 final static int pipe_greater = TBaseType.pipe_greater; // StarRocks pipe operator |> 298 final static int rw_not_deferrable = 284; 299 final static int rw_for1 = 285; 300 final static int stmt_delimiter = 286; 301 final static int AMP_QUOTED_ID = 285; //used in mdx 302 final static int AMP_UNQUOTED_ID = 286; //used in mdx 303 final static int m_clause = 287; 304 final static int MySQL_CHARSET_NAME = 287; 305 final static int typecast = TBaseType.typecast;//postgresql 306 final static int k_clause = 288; 307 final static int slash_dot = 288; 308 final static int outer_join = 289; 309 310 final static int not_equal = 290; 311 312 final static int param = TBaseType.param; 313 final static int mysql_null = TBaseType.rrw_mysql_null; 314 315 final static int rw_locktable = 296; 316 final static int rw_foreign2 = 297; 317 final static int rw_constraint2 = 298; 318 final static int rw_primary2 = 299; 319 final static int rw_unique2 = 300; 320 final static int NEXT_PARAM = TBaseType.NEXT_PARAM; 321 final static int POSITIONAL_PARAM = TBaseType.POSITIONAL_PARAM; 322 final static int NAMED_PARAM = TBaseType.NAMED_PARAM; 323 324 final static int castoperator = TBaseType.castoperator; 325 final static int twocolons = TBaseType.twocolons; 326 final static int compoundAssignmentOperator = TBaseType.compoundAssignmentOperator; 327 final static int postgresql_function_delimiter = TBaseType.rrw_postgresql_function_delimiter; 328 final static int greenplum_function_delimiter = TBaseType.rrw_greenplum_function_delimiter; 329 330 final static int redshift_function_delimiter = TBaseType.rrw_redshift_function_delimiter; 331 final static int snowflake_function_delimiter = TBaseType.rrw_snowflake_function_delimiter; 332 333 334 335 int[] yypos;// = new int[max_rules + 1]; // 1 based in delphi, Position 0 was not used here 336 int[] yystack;// = new int[max_matches + 1]; // 1 based in delphi, Position 0 was not used here 337 // ArrayList yystack; 338 339 //String keywordvaluefile,keywordfile,yyk_file,yym_file,yykl_file; 340 //String yykh_file,yyml_file,yymh_file,yytl_file,yyth_file,yytint_file,yyt_file; 341 342 EDbVendor dbvendor; 343 TSourceToken prevToken = null; 344 345 public void setSqlCharset(String sqlCharset) { 346 this.sqlCharset = sqlCharset; 347 } 348 349 public String getSqlCharset() { 350 return sqlCharset; 351 } 352 353 private String sqlCharset = null; 354 355 /** 356 * Check if token code represents a single character operator 357 */ 358 protected boolean isSingleCharOperator(int tokenCode) { 359 return tokenCode == '(' || tokenCode == ')' || 360 tokenCode == '[' || tokenCode == ']' || 361 tokenCode == '{' || tokenCode == '}' || 362 tokenCode == ',' || tokenCode == ';' || 363 tokenCode == '.' || tokenCode == ':' || 364 tokenCode == '+' || tokenCode == '-' || 365 tokenCode == '*' || tokenCode == '/' || 366 tokenCode == '%' || tokenCode == '=' || 367 tokenCode == '<' || tokenCode == '>' || 368 tokenCode == '!' || tokenCode == '&' || 369 tokenCode == '|' || tokenCode == '^' || 370 tokenCode == '~' || tokenCode == '?'; 371 } 372 373 /** 374 * Check if token code represents a keyword 375 */ 376 protected boolean isKeyword(int tokenCode) { 377 // Check if it's in the reserved word range 378 return tokenCode >= TBaseType.rrw_select && tokenCode < TBaseType.rrw_abort; 379 } 380 381 public TCustomLexer(){ 382 //this.yyinput = pbuf; 383 yytextbufsize = intial_bufsize - 1; 384 yytextbuf = new char[intial_bufsize]; 385 checkyytextbuf(yytextbufsize); 386 387 bufsize = intial_bufsize - 1; 388 buf = new char[intial_bufsize]; 389 checkbuf(bufsize); 390 391 //literalbuf = new StringBuffer(); 392 literalbuf = new StringBuilder(); 393 //keywordList = new TreeMap(); 394 delimiterchar = ';'; 395 tmpDelimiter = ""; 396 397 xcdepth = 0; 398 nchars = 0; 399 isqmarktoident = true; 400 401 yylvalstr = ""; 402 yysstate = 0; 403 yylstate = 0; 404 yymatches = 0; 405 yysleng = 0; 406 bufptr = 0; 407 yylineno = 0; 408 yycolno = 0; 409 offset = -1; 410 yylineno_p = 1; 411 yycolno_p = 1; 412 offset_p = 0; 413 414 yypos = new int[max_rules + 1]; 415 max_matches = TBaseType.LEXER_INIT_MAX_MATCHES; 416 yystack = new int[max_matches + 1]; 417 418 prevToken = null; 419 } 420 421 /* 422 * this function is not used. 423 private void getkeywordvaluefromfile(){ 424 int i; 425 keywordValueList.clear(); 426 for(i=0; i<keywordlist.length; i++){ 427 // System.out.println(keywordlist[i]); 428 String[] ss = keywordlist[i].split("[=]"); 429 keywordValueList.put(ss[0].toUpperCase(Locale.ROOT),ss[1]); 430 } 431 } 432 */ 433 434public int iskeyword(String str){ 435 return -1; 436} 437 438public boolean isAtBeginOfLine(){ 439 return (yyretval == lexnewline || yyretval == 0); 440} 441 442//public boolean canBeColumnName(int tokencode){ 443// return false; 444//} 445 446 447public String getStringByCode(int tokenCode){ 448 return null; 449} 450 451 public int getkeywordvalue(String keyword){ 452 return 0; 453 } 454 455 456 /** 457 * @deprecated , please use keywordChecker.isKeyword() instead. 458 * 459 * because there are so many non-reserved keywords in some databases, it's not suitable to put those 460 * non-reserved keywords in lexer and parser. 461 * 462 * @param keyword 463 * @param keywordValueList 464 * @param keywordTypeList 465 * @return 466 */ 467 public static EKeywordType getKeywordType(String keyword, HashMap<String, Integer> keywordValueList,HashMap<Integer, Integer> keywordTypeList){ 468 EKeywordType ret = EKeywordType.NOT_A_KEYWORD; 469 Integer s = keywordValueList.get(keyword.toUpperCase(Locale.ENGLISH)); 470 if( s == null) return ret; 471 472 Integer i = keywordTypeList.get(s); 473 if (i == 1) return EKeywordType.RESERVED_WORD; 474 else if (i == 2) return EKeywordType.NON_RESERVED_KEYWORD; 475 else return ret; 476 } 477 478 /** 479 * 如果是ascii 字符,直接返回,如果是unicode 字符,需要进行转换。否则 String.charAt() 返回的unicode字符不是我们想要的字符, 480 * 例如中文的括号,我们实际需要的ascii的括号 481 * 482 * @param pYylvalstr 483 * @param index 484 * @return 485 */ 486 char lexer_charAt(String pYylvalstr,int index){ 487 char ret = pYylvalstr.charAt(index); 488 if (ret > 255){ 489 // this is a unicode code 490 if ((ret == 0xFF08)){ 491 // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=65280&number=128 492 // Unicode code point for FULLWIDTH LEFT PARENTHESIS (, 0xFF08 493 //System.out.println(c); 494 ret = '('; 495 } 496 if ( (ret == 0xFF09)){ 497 // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=65280&number=128 498 // Unicode code point for FULLWIDTH RIGHT PARENTHESIS ), 0xFF09 499 // System.out.println(c); 500 ret = ')'; 501 } 502 if (ret == 0xFF0C && dbvendor == EDbVendor.dbvoracle) { 503 // totablechar already selects the comma rule. Return its token code, 504 // while retaining the original character in literals, comments and SQL text. 505 ret = ','; 506 } 507 } 508 return ret; 509 } 510 void totablechar(){ 511 //System.out.println("char:"+yyactchar+" ,hex:"+String.format("%04x", (int) yyactchar)); 512 //System.out.println(String.format("0x%08X", (int)yyactchar)+", "+(char)yyactchar); 513 514 if (((int) yyactchar == 0) && !endOfInput) { 515 yytablechar = (char)255; 516 return; 517 } 518 519 if ((int)(yyactchar) < 228){ // 228 is ä in unicode 520 yytablechar = yyactchar; 521 if ((((int)(yyactchar) == 160)&&(utf8NoBreakSpaceReady))||(yyactchar == 0xA0)){ 522 yytablechar = (char)32; 523 } 524 utf8NoBreakSpaceReady = false; 525// if (yyactchar == 0x27){ 526// insideSingleQuoteStr = !insideSingleQuoteStr; 527// } 528 }else{ 529 yytablechar = (char)'a';//(char)255; 530 531 if ((int)(yyactchar) == 914) { // c2 a0, utf-8 NO-BREAK SPACE 532 utf8NoBreakSpaceReady = true; 533 yytablechar = (char) 32; 534 }else if ((yyactchar == 0x2018)||(yyactchar == 0x2019)){ 535 if (stringLiteralStartWithUnicodeSingleQuote){ 536 // WHERE Name LIKE ‘Acme%’ 537 // 如上,如果string literal 以unicode quote 开始,则不管当前是否在string literal中,新碰到的unicode quote都看成是string literal的结尾符, 538 yytablechar = 0x27; // treat Unicode Character 'LEFT SINGLE QUOTATION MARK' as the ascii char ', but don't change it 539 }else { 540 if (insideSingleQuoteStr){ 541 // don't change the unicode quote char 542 }else { 543 yytablechar = 0x27; // treat Unicode Character 'LEFT SINGLE QUOTATION MARK' as the ascii char ', but don't change it 544 } 545 } 546 547 }else if ((yyactchar == 0x200B)||(yyactchar == 0x3000)||(yyactchar >= 0x2000 && yyactchar <= 0x200A)){ 548 // Unicode code point 0x200B: treat Unicode Character ZERO WIDTH SPACE as the ascii char space, but don't change it 549 // Unicode code point 0x3000: treat Unicode Character IDEOGRAPHIC SPACE (UTF-8: e3 80 80) as the ascii char space, but don't change it 550 // Unicode code points 0x2000-0x200A: General Punctuation space characters (EN QUAD, EM QUAD, EN SPACE, EM SPACE, THREE-PER-EM SPACE, etc.) 551 yytablechar = 0x20; 552 }else if (yyactchar == 0xFF08){ 553 yytablechar = '('; // treat Unicode code point for FULLWIDTH LEFT PARENTHESIS as the ascii char (, but don't change it 554 }else if (yyactchar == 0xFF09){ 555 yytablechar = ')'; // treat Unicode code point for FULLWIDTH RIGHT PARENTHESIS as the ascii char ), but don't change it 556 }else if (yyactchar == 0xFF0C){ 557 yytablechar = ','; // treat Unicode code point for FULLWIDTH COMMA as the ascii char comma, but don't change it 558 }else { 559 utf8NoBreakSpaceReady = false; 560 } 561 } 562 } 563 564 String getyytext(){ 565 return new String(yytextbuf,0,yytextlen); 566 } 567 568 569 void checkyytextbuf(int size){ 570 while ( size >= yytextbufsize){ 571 yytextbufsize = yytextbufsize * 2 > intial_bufsize ? yytextbufsize * 2: intial_bufsize; 572 char[] tmp = new char[yytextbufsize]; 573 System.arraycopy(yytextbuf,0,tmp,0, yytextbuf.length); 574 yytextbuf = tmp; 575 } 576 } 577 578 void checkbuf(int size){ 579 // System.out.println("while begin2"+" size:"+size+" bufsize:"+bufsize); 580 while ( size >= bufsize){ 581 bufsize = bufsize * 2 > intial_bufsize ? bufsize * 2: intial_bufsize; 582 char[] tmp = new char[bufsize]; 583 System.arraycopy(buf,0,tmp,0, buf.length); 584 buf = tmp; 585 } 586 // System.out.println("while end2"); 587 } 588 589 boolean eof(BufferedReader pbuf){ 590 try{ 591 return !pbuf.ready(); 592 }catch(IOException e){ 593 return true; 594 } 595 } 596 597 void yynew(){ 598 if (yylastchar != (char)0){ 599 if(yylastchar == lf){ 600 yylstate = 1; 601 }else{ 602 yylstate = 0; 603 } 604 } 605 606 yystate = yysstate + yylstate; 607 checkyytextbuf(0); 608 yytextlen = 0; 609 yymatches = 0; 610 yydone = false; 611 } 612 613 void yyscan(){ 614 yyactchar = get_char(); 615 checkyytextbuf(yytextlen + 1); 616 yytextlen++; 617 yytextbuf[yytextlen - 1] = yyactchar; 618 } 619 620 void yymark(int n){ 621 if (n > max_rules ){ 622 System.out.println("n > max_rules "); 623 } 624 yypos[n] = yytextlen; 625 } 626 627 void yymatch(int n){ 628 yymatches++; 629 if(yymatches > max_matches){ 630 631 int new_yystack[] = new int[max_matches*2+1]; 632 System.arraycopy(yystack, 0, new_yystack, 0, max_matches); 633 yystack = new_yystack; 634 max_matches = max_matches * 2; 635 636 // this is valid in JDK 1.6, proguard will report warning and stop 637 // yystack = Arrays.copyOf(yystack,max_matches+1); 638 639 } 640 yystack [yymatches] = n; 641 } 642 643 int yyfind(){ 644 //return -1 mean not found 645 int ret = -1; 646 647 yyreject = false; 648 649 while (( yymatches > 0 ) && ( yypos[yystack[yymatches]] == 0 )) { 650 yymatches-- ; 651 } 652 653 654 if (yymatches > 0){ 655 yysleng = yytextlen; 656 ret = yystack[yymatches]; 657 yyless( yypos[ret] ); 658 yypos[ret] = 0; 659 if (yytextlen >0){ 660 yylastchar = yytextbuf [yytextlen-1]; 661 }else{ 662 yylastchar = (char)0; 663 } 664 }else{ 665 yyless( 0 ); 666 yylastchar = (char)0; 667 } 668 669 return ret; 670 } 671 672 boolean yydefault(){ 673 boolean ret; 674 675 yyreject = false; 676 yyactchar = get_char(); 677 if (yyactchar != (char)0){ 678 //put_char( yyactchar ); 679 ret = true; 680 }else{ 681 yylstate = 1; 682 ret = false; 683 } 684 yylastchar = yyactchar; 685 return ret; 686 } 687 void yyless(int n){ 688 for(int i= yytextlen; i> n; i--){ 689 unget_char(yytextbuf[i - 1]); 690 } 691 checkyytextbuf(n); 692 yytextlen = n; 693 } 694 void returni(int n){ 695 yyretval = n; 696 yydone = true; 697 } 698 void returnc(char c){ 699 yyretval = (int)c; 700 yydone = true; 701 } 702 void yyclear(){ 703 // the generated lexer clears its state when the input ends: an end inside the literal startlit() opened is 704 // kept for openLiteralAtEnd (Mantis 4842) 705 literalOpenAtEnd = literalPending && (yysstate == xd) && (literalState == xd) && (literalbuf != null) 706 && (literalbuf.length() > 0); 707 literalPending = false; 708 if (literalOpenAtEnd) { 709 // where the literal starts: right after the last token returned 710 literalLineNo = yylineno_p; 711 literalColumnNo = yycolno_p; 712 literalOffset = offset_p; 713 } 714 bufptr = 0; 715 yysstate = 0; 716 yylstate = 1; 717 yylastchar = (char)0; 718 yytextlen = 0; 719 yylineno = 0; 720 yycolno = 0; 721 offset = -1; 722 // yystext := ''; 723 724 yylineno_p = 1; 725 yycolno_p = 1; 726 offset_p = 0; 727 literalState = 0; 728 729 } 730 731 732 boolean yywrap(){ 733 return true; 734 } 735 int getyysstate(){ 736 return yysstate; 737 } 738 void start(int pstate){ 739 yysstate = pstate; 740 if (pstate == xq){ 741 insideSingleQuoteStr = true; 742 if ((yylvalstr.charAt(0) == 0x2018)||(yylvalstr.charAt(0) == 0x2019)){ 743 stringLiteralStartWithUnicodeSingleQuote = true; 744 }else{ 745 stringLiteralStartWithUnicodeSingleQuote = false; 746 } 747 }else{ 748 insideSingleQuoteStr = false; 749 } 750 } 751 752 753 void unget_char(char pchar){ 754 // get_char() uses NUL as its end-of-input sentinel and does not 755 // advance yycolno/offset when returning it. Pushing that sentinel 756 // back used to decrement both counters as if a real source character 757 // had been consumed. A following yyless() split at EOF could then 758 // assign the next real token the preceding token's source span (for 759 // example, DB2 "k=?" gave '?' the '=' offset). Only ignore the EOF 760 // sentinel: a real NUL input character does advance the position and 761 // must still be restored. 762 if (pchar == (char)0 && endOfInput) { 763 endOfInput = false; 764 return; 765 } 766 if(bufptr == max_chars) 767 { 768 System.out.println("input buffer overflow"); 769 } 770 // if (bufptr > 0) { 771 bufptr++; 772 yycolno--; 773 offset--; 774 checkbuf(bufptr+1); 775 buf[bufptr] = pchar; 776 // } 777 778 } 779 780 public void reset(){ 781 insideSingleQuoteStr = false; 782 nestedLessThan = 0; 783 literalState = 0; 784 literalPending = false; 785 literalOpenAtEnd = false; 786 } 787 788 public boolean insideSingleQuoteStr = false; 789 public boolean stringLiteralStartWithUnicodeSingleQuote = false; 790 791 792 // Previous implementation of readln, 2025-05-04 793 // char[] readln() throws IOException { 794 // int c; 795 // char[] buffer = new char[80]; 796 // int bufferSize = 0; 797 798 // while ((c = yyinput.read()) != -1) { 799 // if (bufferSize >= buffer.length) { 800 // char[] newBuffer = new char[buffer.length * 2]; 801 // System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); 802 // buffer = newBuffer; 803 // } 804 805 // buffer[bufferSize++] = (char)c; 806 807 // if (c == '\n' || c == '\r') { 808 // break; 809 // } 810 // } 811 812 // if (bufferSize > 0 && buffer[bufferSize - 1] == '\r') { 813 // yyinput.mark(1); 814 // c = yyinput.read(); 815 // if (c == '\n') { 816 // if (bufferSize >= buffer.length) { 817 // char[] newBuffer = new char[buffer.length + 1]; 818 // System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); 819 // buffer = newBuffer; 820 // } 821 // buffer[bufferSize++] = '\n'; 822 // } else { 823 // yyinput.reset(); 824 // } 825 // } 826 827 // if (bufferSize == 0) { 828 // return null; 829 // }else{ 830 // char[] result = new char[bufferSize]; 831 // System.arraycopy(buffer, 0, result, 0, bufferSize); 832 // return result; 833 // } 834 // } 835 836/** 837 * High-performance line reader with optimal buffer management. 838 * Reuses lineReadBuffer across calls to reduce per-line allocations. 839 * @return char array containing the line including line ending, or null if end of stream 840 */ 841char[] readln() throws IOException { 842 if (yyinput == null) return null; 843 844 int position = 0; 845 int c; 846 847 // Read characters until line ending or EOF 848 while ((c = yyinput.read()) != -1) { 849 // Expand buffer if needed (expanded buffer stays for reuse) 850 if (position >= lineReadBuffer.length) { 851 char[] newBuffer = new char[lineReadBuffer.length * 2]; 852 System.arraycopy(lineReadBuffer, 0, newBuffer, 0, lineReadBuffer.length); 853 lineReadBuffer = newBuffer; 854 } 855 856 // Store character 857 lineReadBuffer[position++] = (char)c; 858 859 // Check for line endings 860 if (c == '\n') { 861 break; // LF - end of line 862 } else if (c == '\r') { 863 // Need to check for CR+LF sequence 864 yyinput.mark(1); 865 c = yyinput.read(); 866 867 if (c == '\n') { 868 // CR+LF sequence - include LF in result 869 if (position >= lineReadBuffer.length) { 870 char[] newBuffer = new char[lineReadBuffer.length + 1]; 871 System.arraycopy(lineReadBuffer, 0, newBuffer, 0, lineReadBuffer.length); 872 lineReadBuffer = newBuffer; 873 } 874 lineReadBuffer[position++] = '\n'; 875 } else { 876 // CR only - reset stream to keep the character after CR 877 yyinput.reset(); 878 } 879 break; 880 } 881 } 882 883 // Return null if no characters were read (end of stream). yylineLen keeps the 884 // length of the last line, which yyline still holds (see currentLineText()). 885 if (position == 0) { 886 return null; 887 } 888 889 // Return lineReadBuffer directly, avoiding per-line array allocation. 890 // yylineLen holds the actual content length (replaces yyline.length semantic). 891 yylineLen = position; 892 return lineReadBuffer; 893} 894 895 /** 896 * Text of the line being scanned: only the characters of the last line read, 897 * never the tail that a longer earlier line left in the reused buffer. At end 898 * of input it is still the last line, because the action of the final token 899 * runs after the reader is exhausted. Empty before the first line is read. 900 */ 901 String currentLineText(){ 902 return (yyline == null) ? "" : new String(yyline, 0, yylineLen); 903 } 904 905 char get_char(){ 906 907 char ret ; 908 boolean readlineok = true; 909 910 if ((bufptr == 0) && !eof(yyinput) ) 911 { 912 try{ 913 endOfInput = false; 914 char[] line = readln();//yyinput.readLine(); 915 // System.out.println("readln: "+yyline); 916 if (line == null){ 917 // End of input. Keep yyline on the last line read: the action of the 918 // token that ends the input runs after this call and may still read it 919 // (the SQL Server "--" rule does, Mantis 4951). 920 readlineok = false; 921 } else{ 922 yyline = line; 923 yylineno++; 924 yycolno = 0; 925 // Use yylineLen instead of yyline.length since lineReadBuffer is reused 926 bufptr = yylineLen; 927 checkbuf(bufptr+1); 928 for(int k=1;k<=bufptr;k++){ 929 buf[k] = yyline[bufptr - k]; 930 } 931 } 932 }catch(IOException e){ 933 readlineok = false; 934 } 935 } 936 937 if (! readlineok){ 938 endOfInput = true; 939 return (char)0; 940 } 941 942 if (bufptr > 0){ 943 bufptr--; 944 yycolno++; 945 offset++; 946 947 return buf[bufptr+1]; 948 //return yyline.charAt(yyline.length() - (bufptr + 1)); 949 }else{ 950 // bufptr--; 951 endOfInput = true; 952 return (char)0; 953 } 954 955 } 956 957 void startlit(){ 958 literalbuf.setLength(0); 959 literallen = 0; 960 literalalloc = 0; 961 literalState = yysstate; 962 literalPending = true; 963 } 964 965 /** The start state the literal in {@link #literalbuf} was opened in, 0 when none (Mantis 4842). */ 966 private int literalState; 967 /** Whether no token was returned since {@link #startlit()} opened that literal (Mantis 4842). */ 968 private boolean literalPending; 969 /** Whether the input ended inside the literal {@link #startlit()} opened, not yet returned (Mantis 4842). */ 970 private boolean literalOpenAtEnd; 971 /** Where that literal starts. */ 972 private long literalLineNo, literalColumnNo, literalOffset; 973 974 /** 975 * The double-quoted identifier the input ends in, as one more token (Mantis 4842). The lexer reaches the end inside 976 * the identifier's start state and returns no token for it, so its opening quote and everything after it were 977 * lost: {@code SELECT 1 id";} parsed as {@code SELECT 1 id}, and {@code SELECT 1 id"; SELECT 2;} lost its second 978 * statement. The token's code, the double quote character, is in no grammar, so the parse fails at it. An input 979 * ending inside a string or a comment still ends there: corpora and customer scripts end that way, some because a 980 * lexer misreads a string (U&'', Vertica's $$), and need a fix of their own first. 981 * 982 * @return the token's code, or 0 when the input ends outside a literal 983 */ 984 private int openLiteralAtEnd(TSourceToken psourcetoken) { 985 if (!literalOpenAtEnd) { 986 return 0; 987 } 988 literalOpenAtEnd = false; 989 psourcetoken.tokencode = '"'; 990 psourcetoken.tokentype = ETokenType.tttokenlizererrortoken; 991 psourcetoken.setAstext(literalbuf.toString()); 992 psourcetoken.lineNo = literalLineNo; 993 psourcetoken.columnNo = literalColumnNo; 994 psourcetoken.offset = literalOffset; 995 return psourcetoken.tokencode; 996 } 997 998 void addlit(String ytext, int yleng){ 999 literallen = literallen + yleng; 1000 literalbuf.append(ytext,0,yleng); 1001 } 1002 1003 void addlitchar(char ychar){ 1004 literallen++; 1005 literalbuf.append(ychar); 1006 } 1007 1008 String litbufdup(){ 1009 return literalbuf.toString();//.intern(); 1010 } 1011 1012 boolean isopchar(char ch){ 1013 switch (ch) { 1014 case '~': 1015 case '!': 1016 case '@': 1017 case '#': 1018 case '^': 1019 case '&': 1020 case '|': 1021 case '`': 1022 case '?': 1023 case '$': 1024 case '%': 1025 return true; 1026 default: 1027 return false; 1028 } 1029 } 1030 1031 boolean isselfchar(char ch){ 1032 switch (ch) { 1033 case ',': 1034 case '(': 1035 case ')': 1036 case '[': 1037 case ']': 1038 case '.': 1039 case ';': 1040 case '$': 1041 case ':': 1042 case '+': 1043 case '-': 1044 case '*': 1045 case '/': 1046 case '%': 1047 case '^': 1048 case '<': 1049 case '>': 1050 case '=': 1051 case '!': 1052 case '{': 1053 case '}': 1054 return true; 1055 default: 1056 return false; 1057 } 1058 } 1059 1060 boolean charinarray(char c, char[] a){ 1061 int len = a.length; 1062 for (int i = 0; i < len; i++) { 1063 if (a[i] == c) 1064 return true; 1065 } 1066 return false; 1067 } 1068 1069 void setlengthofliteralbuf(int plen){ 1070 literalbuf.setLength(plen); 1071 } 1072 1073 void yyaction(int yyruleno){ 1074 } 1075 1076 int yylex(){ 1077 return 0; 1078 } 1079 1080 1081 public int yylexwrap(TSourceToken psourcetoken) { 1082 // Get token code and handle EOF 1083 if ((psourcetoken.tokencode = yylex()) == 0) return openLiteralAtEnd(psourcetoken); 1084 // a lexer may return the literal and stay in its start state (Snowflake ""name""): it is no longer open 1085 literalPending = false; 1086 1087 // Store token text - use shared strings for single ASCII chars to reduce allocations 1088 if (yylvalstr == null) { 1089 if (yytextlen == 1 && yytextbuf[0] < 128) { 1090 yylvalstr = SINGLE_CHAR_STRINGS[yytextbuf[0]]; 1091 } else { 1092 yylvalstr = new String(yytextbuf, 0, yytextlen); 1093 } 1094 } 1095 psourcetoken.setAstext(yylvalstr); 1096 1097 // Record token position information 1098 psourcetoken.lineNo = yylineno_p; 1099 psourcetoken.columnNo = yycolno_p; 1100 psourcetoken.offset = offset_p; 1101 yylineno_p = yylineno; 1102 yycolno_p = yycolno + 1; 1103 offset_p = offset + 1; 1104 1105 // Track token in token table for analysis 1106 setTokenTableValue(psourcetoken); 1107 1108 // Handle token types based on token code 1109 switch (psourcetoken.tokencode) { 1110 case cmtdoublehyphen: 1111 psourcetoken.tokentype = ETokenType.ttsimplecomment; 1112 if (dbvendor == EDbVendor.dbvmdx && psourcetoken.toString().startsWith("/")) { 1113 psourcetoken.tokentype = ETokenType.ttCPPComment; 1114 } 1115 break; 1116 1117 case cmtslashstar: 1118 psourcetoken.tokentype = ETokenType.ttbracketedcomment; 1119 break; 1120 1121 case lexspace: 1122 psourcetoken.tokentype = ETokenType.ttwhitespace; 1123 break; 1124 1125 case lexnewline: 1126 psourcetoken.tokentype = ETokenType.ttreturn; 1127 break; 1128 1129 case bind_v: 1130 psourcetoken.tokentype = ETokenType.ttbindvar; 1131 if (dbvendor == EDbVendor.dbvoracle) { 1132 psourcetoken.setAstext(psourcetoken.getAstext().replace(TBaseType.newline, "")); 1133 } 1134 break; 1135 1136 case stmt_delimiter: 1137 psourcetoken.tokentype = ETokenType.ttstmt_delimiter; 1138 psourcetoken.tokencode = cmtslashstar; 1139 break; 1140 1141 case concatenationop: 1142 psourcetoken.tokentype = ETokenType.ttconcatenationop; 1143 break; 1144 1145 case variable: 1146 psourcetoken.tokentype = ETokenType.ttsqlvar; 1147 break; 1148 1149 case fconst: 1150 case iconst: 1151 psourcetoken.tokentype = ETokenType.ttnumber; 1152 break; 1153 1154 case sconst: 1155 psourcetoken.tokentype = ETokenType.ttsqstring; 1156 psourcetoken.dolqstart = dolqstart; 1157 dolqstart = ""; 1158 break; 1159 1160 case ident: 1161 case QUOTED_IDENT: 1162 handleIdentifierToken(psourcetoken); 1163 break; 1164 1165 case cmpop: 1166 handleComparisonOperator(psourcetoken); 1167 break; 1168 1169 case op: 1170 handleOperatorToken(psourcetoken); 1171 break; 1172 1173 default: 1174 handleDefaultToken(psourcetoken); 1175 break; 1176 } 1177 1178 prevToken = psourcetoken; 1179 return psourcetoken.tokencode; 1180 } 1181 1182 // Helper methods to better organize the complex token handling logic 1183 private void handleIdentifierToken(TSourceToken psourcetoken) { 1184 psourcetoken.tokentype = ETokenType.ttidentifier; 1185 String tokenText = psourcetoken.toString().trim(); 1186 1187 if (tokenText.startsWith("\"")) { 1188 psourcetoken.tokentype = ETokenType.ttdqstring; 1189 } else if (tokenText.startsWith("[")) { 1190 if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) { 1191 psourcetoken.tokentype = ETokenType.ttdbstring; 1192 } 1193 } else if (tokenText.startsWith("{")) { 1194 if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) { 1195 psourcetoken.tokentype = ETokenType.ttbrstring; 1196 if (tokenText.toLowerCase(Locale.ROOT).startsWith("{escape")) { 1197 psourcetoken.tokencode = TBaseType.rrw_sqlserver_odbc_escape; 1198 } 1199 } 1200 } else if (tokenText.startsWith("&")) { 1201 if (dbvendor == EDbVendor.dbvmdx) { 1202 if (psourcetoken.tokencode == QUOTED_IDENT) { 1203 psourcetoken.tokencode = AMP_QUOTED_ID; 1204 } else if (psourcetoken.tokencode == ident) { 1205 psourcetoken.tokencode = AMP_UNQUOTED_ID; 1206 } 1207 } 1208 } else if (tokenText.startsWith(".")) { 1209 if (dbvendor == EDbVendor.dbvteradata) { 1210 psourcetoken.tokentype = ETokenType.ttBTEQCmd; 1211 } 1212 } 1213 } 1214 1215 private void handleComparisonOperator(TSourceToken psourcetoken) { 1216 psourcetoken.tokentype = ETokenType.ttmulticharoperator; 1217 String token = yylvalstr; 1218 1219 // Oracle 26c vector distance operators (3-char, Oracle only) 1220 if (dbvendor == EDbVendor.dbvoracle) { 1221 if (token.equals("<=>")) { 1222 psourcetoken.tokencode = TBaseType.vector_cosine_distance; 1223 return; 1224 } else if (token.equals("<->")) { 1225 psourcetoken.tokencode = TBaseType.vector_euclidean_distance; 1226 return; 1227 } else if (token.equals("<#>")) { 1228 psourcetoken.tokencode = TBaseType.vector_dot_product; 1229 return; 1230 } 1231 } 1232 1233 if ((token.startsWith("!") && token.endsWith("=")) || 1234 (token.startsWith("^") && token.endsWith("=")) || 1235 (token.startsWith("~") && token.endsWith("=")) || 1236 (token.startsWith("<") && token.endsWith(">"))) { 1237 1238 psourcetoken.tokencode = TBaseType.not_equal; 1239 1240 // Handle MySQL NULL-safe equal 1241 if (token.indexOf("=", 1) > 0 && 1242 token.startsWith("<") && token.endsWith(">")) { 1243 psourcetoken.tokencode = (int)'='; 1244 } 1245 } else if (token.startsWith(">") && token.endsWith("=")) { 1246 psourcetoken.tokencode = TBaseType.great_equal; 1247 } else if (token.startsWith("<") && token.endsWith("=")) { 1248 psourcetoken.tokencode = TBaseType.less_equal; 1249 } else if ((token.startsWith("!") && token.endsWith("<")) || 1250 (token.startsWith("^") && token.endsWith("<"))) { 1251 psourcetoken.tokencode = TBaseType.not_less; 1252 } else if ((token.startsWith("!") && token.endsWith(">")) || 1253 (token.startsWith("^") && token.endsWith(">"))) { 1254 psourcetoken.tokencode = TBaseType.not_great; 1255 } else if (token.length() == 2 && token.charAt(0) == ':' && token.charAt(1) == '=') { 1256 psourcetoken.tokencode = assign_sign; 1257 } 1258 } 1259 1260 private void handleOperatorToken(TSourceToken psourcetoken) { 1261 psourcetoken.tokentype = ETokenType.ttmulticharoperator; 1262 String token = yylvalstr; 1263 int tokenLength = token.length(); 1264 char firstChar = tokenLength > 0 ? token.charAt(0) : '\0'; 1265 char secondChar = tokenLength > 1 ? token.charAt(1) : '\0'; 1266 1267 // Handle question mark specially 1268 if (token.equals("?") && isqmarktoident) { 1269 handleQuestionMark(psourcetoken); 1270 return; 1271 } 1272 1273 // Handle special two-character operators 1274 if (tokenLength == 2) { 1275 if (handleTwoCharOperator(psourcetoken, firstChar, secondChar)) { 1276 return; 1277 } 1278 } 1279 1280 // Handle special three-character operators 1281 if (tokenLength == 3) { 1282 if (handleThreeCharOperator(psourcetoken, firstChar, secondChar, token.charAt(2))) { 1283 return; 1284 } 1285 } 1286 1287 // Handle comparison operators 1288 if (handleComparisonOp(psourcetoken, token)) { 1289 return; 1290 } 1291 1292 // Handle single character operators 1293 if (tokenLength == 1) { 1294 handleSingleCharOperator(psourcetoken, firstChar); 1295 } 1296 } 1297 1298 private boolean handleTwoCharOperator(TSourceToken psourcetoken, char firstChar, char secondChar) { 1299 switch (firstChar) { 1300 case '<': 1301 if (secondChar == '<') { 1302 return handleLeftShiftOperator(psourcetoken); 1303 } else if (secondChar == '@') { 1304 psourcetoken.tokencode = TBaseType.JSON_RIGHT_CONTAIN; 1305 return true; 1306 } 1307 break; 1308 1309 case '>': 1310 if (secondChar == '>') { 1311 return handleRightShiftOperator(psourcetoken); 1312 } 1313 break; 1314 1315 case '=': 1316 if (secondChar == '>') { 1317 if (dbvendor == EDbVendor.dbvodbc) { 1318 psourcetoken.tokencode = TBaseType.great_equal; 1319 } else if (dbvendor == EDbVendor.dbvpostgresql || dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb) { 1320 psourcetoken.tokencode = TBaseType.assign_sign; 1321 } else { 1322 psourcetoken.tokencode = NAMED_PARAMETER_SIGN; 1323 } 1324 return true; 1325 } else if (secondChar == '*') { 1326 if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) { 1327 psourcetoken.tokencode = rightjoin_op; 1328 } 1329 return true; 1330 } else if (secondChar == '<') { 1331 if (dbvendor == EDbVendor.dbvodbc) { 1332 psourcetoken.tokencode = TBaseType.less_equal; 1333 } 1334 return true; 1335 } else if (secondChar == '=') { 1336 if (dbvendor == EDbVendor.dbvsparksql || dbvendor == EDbVendor.dbvclickhouse) { 1337 psourcetoken.tokencode = '='; 1338 } 1339 return true; 1340 } 1341 break; 1342 1343 case '-': 1344 if (secondChar == '>') { 1345 if (dbvendor == EDbVendor.dbvpostgresql || dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb 1346 || dbvendor == EDbVendor.dbvgreenplum || dbvendor == EDbVendor.dbvmysql) { 1347 psourcetoken.tokencode = TBaseType.JSON_GET_OBJECT; 1348 } else { 1349 psourcetoken.tokencode = ref_arrow; 1350 } 1351 return true; 1352 } else if (secondChar == '=') { 1353 if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) { 1354 psourcetoken.tokencode = compoundAssignmentOperator; 1355 } 1356 return true; 1357 } 1358 break; 1359 1360 case '.': 1361 if (secondChar == '.') { 1362 if (dbvendor == EDbVendor.dbvdb2 || dbvendor == EDbVendor.dbvoracle 1363 || dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvhana) { 1364 psourcetoken.tokencode = double_dot; 1365 } 1366 return true; 1367 } 1368 break; 1369 1370 case '*': 1371 if (secondChar == '=') { 1372 if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) { 1373 psourcetoken.tokencode = leftjoin_op; 1374 } 1375 return true; 1376 } else if (secondChar == '*') { 1377 if (dbvendor == EDbVendor.dbvteradata || dbvendor == EDbVendor.dbvnetezza) { 1378 psourcetoken.tokencode = TBaseType.exponentiate; 1379 } 1380 return true; 1381 } 1382 break; 1383 1384 case '|': 1385 if (secondChar == '|') { 1386 if (dbvendor == EDbVendor.dbvmysql) { 1387 psourcetoken.tokencode = TBaseType.logical_or; 1388 } else if (isStringConcatVendor(dbvendor)) { 1389 psourcetoken.tokencode = TBaseType.concatenationop; 1390 } 1391 return true; 1392 } else if (secondChar == '>') { 1393 if (dbvendor == EDbVendor.dbvsparksql) { 1394 psourcetoken.tokencode = TBaseType.sparksql_pipe_arrow; 1395 } else { 1396 psourcetoken.tokencode = TBaseType.pipe_greater; 1397 } 1398 return true; 1399 } else if (secondChar == '/') { 1400 if (dbvendor == EDbVendor.dbvredshift) { 1401 psourcetoken.tokencode = TBaseType.square_root; 1402 } 1403 return true; 1404 } 1405 break; 1406 1407 case '&': 1408 if (secondChar == '&') { 1409 if (dbvendor == EDbVendor.dbvmysql) { 1410 psourcetoken.tokencode = TBaseType.logical_and; 1411 } 1412 return true; 1413 } 1414 break; 1415 1416 case '?': 1417 if (secondChar == '|') { 1418 psourcetoken.tokencode = TBaseType.JSON_ANY_EXIST; 1419 return true; 1420 } else if (secondChar == '&') { 1421 psourcetoken.tokencode = TBaseType.JSON_ALL_EXIST; 1422 return true; 1423 } 1424 break; 1425 1426 case '@': 1427 if (secondChar == '>') { 1428 psourcetoken.tokencode = TBaseType.JSON_LEFT_CONTAIN; 1429 return true; 1430 } 1431 break; 1432 1433 case '#': 1434 if (secondChar == '>') { 1435 psourcetoken.tokencode = TBaseType.JSON_GET_OBJECT_AT_PATH; 1436 return true; 1437 } 1438 break; 1439 1440 case ':': 1441 if (secondChar == '=') { 1442 psourcetoken.tokencode = assign_sign; 1443 return true; 1444 } 1445 break; 1446 } 1447 1448 // Handle compound assignment operators 1449 if ((firstChar == '+' || firstChar == '-' || firstChar == '*' || 1450 firstChar == '/' || firstChar == '%' || firstChar == '&' || 1451 firstChar == '^' || firstChar == '|') && secondChar == '=') { 1452 if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) { 1453 psourcetoken.tokencode = compoundAssignmentOperator; 1454 return true; 1455 } else if (dbvendor == EDbVendor.dbvmysql && firstChar == '^' && secondChar == '=') { 1456 psourcetoken.tokencode = not_equal; 1457 return true; 1458 } 1459 } 1460 1461 return false; 1462 } 1463 1464 private boolean handleThreeCharOperator(TSourceToken psourcetoken, char firstChar, char secondChar, char thirdChar) { 1465 if (firstChar == '-' && secondChar == '>' && thirdChar == '>') { 1466 psourcetoken.tokencode = TBaseType.JSON_GET_TEXT; 1467 return true; 1468 } else if (firstChar == '#' && secondChar == '>' && thirdChar == '>') { 1469 psourcetoken.tokencode = TBaseType.JSON_GET_TEXT_AT_PATH; 1470 return true; 1471 } else if (firstChar == '|' && secondChar == '|' && thirdChar == '/') { 1472 if (dbvendor == EDbVendor.dbvredshift) { 1473 psourcetoken.tokencode = TBaseType.cube_root; 1474 return true; 1475 } 1476 } 1477 return false; 1478 } 1479 1480 private boolean handleComparisonOp(TSourceToken psourcetoken, String token) { 1481 if ((token.startsWith("!") && token.endsWith("=")) || 1482 (token.startsWith("^") && token.endsWith("=")) || 1483 (token.startsWith("<") && token.endsWith(">"))) { 1484 psourcetoken.tokencode = TBaseType.not_equal; 1485 return true; 1486 } else if (token.startsWith(">") && token.endsWith("=")) { 1487 psourcetoken.tokencode = TBaseType.great_equal; 1488 return true; 1489 } else if (token.startsWith("<") && token.endsWith("=")) { 1490 psourcetoken.tokencode = TBaseType.less_equal; 1491 return true; 1492 } else if ((token.startsWith("!") && token.endsWith("<")) || 1493 (token.startsWith("^") && token.endsWith("<"))) { 1494 psourcetoken.tokencode = TBaseType.not_less; 1495 return true; 1496 } else if ((token.startsWith("!") && token.endsWith(">")) || 1497 (token.startsWith("^") && token.endsWith(">"))) { 1498 psourcetoken.tokencode = TBaseType.not_great; 1499 return true; 1500 } 1501 return false; 1502 } 1503 1504 private void handleSingleCharOperator(TSourceToken psourcetoken, char ch) { 1505 switch (ch) { 1506 case '~': 1507 if (dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvredshift || 1508 dbvendor == EDbVendor.dbvsnowflake) { 1509 psourcetoken.tokencode = (int)'~'; 1510 } 1511 break; 1512 1513 case '#': 1514 if (dbvendor == EDbVendor.dbvmssql) { 1515 psourcetoken.tokencode = (int)'#'; 1516 } 1517 break; 1518 1519 case '&': 1520 if (dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvvertica || 1521 dbvendor == EDbVendor.dbvsparksql) { 1522 psourcetoken.tokencode = (int)'&'; 1523 } 1524 break; 1525 1526 case '|': 1527 if (dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvvertica) { 1528 psourcetoken.tokencode = (int)'|'; 1529 } 1530 break; 1531 } 1532 } 1533 1534 private void handleQuestionMark(TSourceToken psourcetoken) { 1535 if (dbvendor == EDbVendor.dbvpostgresql || dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb || 1536 dbvendor == EDbVendor.dbvgreenplum) { 1537 psourcetoken.tokencode = TBaseType.JSON_EXIST; 1538 } else if (dbvendor == EDbVendor.dbvodbc) { 1539 psourcetoken.tokencode = '?'; 1540 } else if (dbvendor == EDbVendor.dbvsnowflake) { 1541 psourcetoken.tokencode = bind_v; 1542 psourcetoken.tokentype = ETokenType.ttquestionmark; 1543 } else { 1544 psourcetoken.tokencode = ident; 1545 } 1546 } 1547 1548 private boolean handleLeftShiftOperator(TSourceToken psourcetoken) { 1549 if (dbvendor == EDbVendor.dbvoracle || dbvendor == EDbVendor.dbvmssql || 1550 dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq || dbvendor == EDbVendor.dbvpostgresql || 1551 dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb || dbvendor == EDbVendor.dbvaccess || 1552 dbvendor == EDbVendor.dbvgreenplum || dbvendor == EDbVendor.dbvsnowflake) { 1553 psourcetoken.tokencode = label_begin; 1554 } else if (dbvendor == EDbVendor.dbvmysql) { 1555 psourcetoken.tokencode = TBaseType.rrw_left_shift; 1556 } else if (dbvendor == EDbVendor.dbvredshift) { 1557 psourcetoken.tokencode = TBaseType.bitwise_shift_left; 1558 } else if (dbvendor == EDbVendor.dbvnetezza) { 1559 psourcetoken.tokencode = TBaseType.rrw_netezza_op_less_less; 1560 } 1561 return true; 1562 } 1563 1564 private boolean handleRightShiftOperator(TSourceToken psourcetoken) { 1565 if (dbvendor == EDbVendor.dbvoracle || dbvendor == EDbVendor.dbvmssql || 1566 dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq || dbvendor == EDbVendor.dbvpostgresql || 1567 dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb || dbvendor == EDbVendor.dbvgreenplum || 1568 dbvendor == EDbVendor.dbvaccess || dbvendor == EDbVendor.dbvsnowflake) { 1569 psourcetoken.tokencode = label_end; 1570 } else if (dbvendor == EDbVendor.dbvmysql) { 1571 psourcetoken.tokencode = TBaseType.rrw_right_shift; 1572 } else if (dbvendor == EDbVendor.dbvredshift) { 1573 psourcetoken.tokencode = TBaseType.bitwise_shift_right; 1574 } else if (dbvendor == EDbVendor.dbvnetezza) { 1575 psourcetoken.tokencode = TBaseType.rrw_netezza_op_great_great; 1576 } else if (dbvendor == EDbVendor.dbvdoris) { 1577 psourcetoken.tokencode = TBaseType.rrw_doris_right_shift; 1578 } else if (dbvendor == EDbVendor.dbvstarrocks) { 1579 psourcetoken.tokencode = TBaseType.rrw_starrocks_right_shift; 1580 } 1581 return true; 1582 } 1583 1584 private boolean isStringConcatVendor(EDbVendor vendor) { 1585 return vendor == EDbVendor.dbvdb2 || vendor == EDbVendor.dbvnetezza || 1586 vendor == EDbVendor.dbvpostgresql || vendor == EDbVendor.dbvgaussdb || vendor == EDbVendor.dbvedb || 1587 vendor == EDbVendor.dbvredshift || vendor == EDbVendor.dbvgreenplum || 1588 vendor == EDbVendor.dbvbigquery || vendor == EDbVendor.dbvsnowflake || 1589 vendor == EDbVendor.dbvsparksql || vendor == EDbVendor.dbvvertica; 1590 } 1591 1592 private void handleDefaultToken(TSourceToken psourcetoken) { 1593 psourcetoken.tokentype = ETokenType.ttkeyword; 1594 1595 if (psourcetoken.tokencode < 255) { 1596 // Single character operators (ASCII characters) 1597 psourcetoken.setAstext(Character.toString(yylvalstr.charAt(0))); 1598 psourcetoken.tokentype = ETokenType.ttsinglecharoperator; 1599 1600 switch (psourcetoken.tokencode) { 1601 case ',': 1602 psourcetoken.tokentype = ETokenType.ttcomma; 1603 break; 1604 case '(': 1605 psourcetoken.tokentype = ETokenType.ttleftparenthesis; 1606 break; 1607 case ')': 1608 psourcetoken.tokentype = ETokenType.ttrightparenthesis; 1609 break; 1610 case '[': 1611 psourcetoken.tokentype = ETokenType.ttleftbracket; 1612 break; 1613 case ']': 1614 psourcetoken.tokentype = ETokenType.ttrightbracket; 1615 break; 1616 case '.': 1617 psourcetoken.tokentype = ETokenType.ttperiod; 1618 break; 1619 case ';': 1620 psourcetoken.tokentype = ETokenType.ttsemicolon; 1621 break; 1622 case '$': 1623 psourcetoken.tokentype = ETokenType.ttdolorsign; 1624 break; 1625 case ':': 1626 psourcetoken.tokentype = ETokenType.ttcolon; 1627 break; 1628 case '+': 1629 psourcetoken.tokentype = ETokenType.ttplussign; 1630 break; 1631 case '-': 1632 psourcetoken.tokentype = ETokenType.ttminussign; 1633 break; 1634 case '*': 1635 psourcetoken.tokentype = ETokenType.ttasterisk; 1636 break; 1637 case '/': 1638 psourcetoken.tokentype = ETokenType.ttslash; 1639 break; 1640 case '^': 1641 psourcetoken.tokentype = ETokenType.ttcaret; 1642 break; 1643 case '<': 1644 psourcetoken.tokentype = ETokenType.ttlessthan; 1645 break; 1646 case '>': 1647 psourcetoken.tokentype = ETokenType.ttgreaterthan; 1648 break; 1649 case '=': 1650 psourcetoken.tokentype = ETokenType.ttequals; 1651 break; 1652 case '@': 1653 if (delimiterchar == '@') { 1654 psourcetoken.tokencode = (int)';'; 1655 psourcetoken.tokentype = ETokenType.ttsemicolon; 1656 } else { 1657 psourcetoken.tokentype = ETokenType.ttatsign; 1658 } 1659 break; 1660 case '~': 1661 psourcetoken.tokentype = ETokenType.tttilde; 1662 break; 1663 case '&': 1664 psourcetoken.tokentype = ETokenType.ttampersand; 1665 break; 1666 case '|': 1667 psourcetoken.tokentype = ETokenType.ttverticalbar; 1668 break; 1669 case '?': 1670 if (isqmarktoident && dbvendor != EDbVendor.dbvodbc && 1671 dbvendor != EDbVendor.dbvpostgresql && dbvendor != EDbVendor.dbvgaussdb && dbvendor != EDbVendor.dbvedb && dbvendor != EDbVendor.dbvduckdb) { 1672 psourcetoken.tokencode = ident; 1673 } 1674 break; 1675 } 1676 } else if (dbvendor == EDbVendor.dbvhive && psourcetoken.tokencode == TBaseType.hive_equal) { 1677 psourcetoken.tokentype = ETokenType.ttequals; 1678 } 1679 } 1680 1681}