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&amp;'', 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}