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     */
198    private int yylineLen;
199    String yylvalstr;
200    public String  dolqstart = "";//postgresql, start part of Dollar-quoted String Constants
201    char yylastchar,yyactchar,yytablechar;
202    boolean yydone,yyreject;
203    char[] yytextbuf;
204    char[] buf;
205    int bufsize;
206    boolean endOfInput;
207
208    //StringBuffer literalbuf;
209    StringBuilder literalbuf;
210    int literallen,literalalloc,xcdepth,nchars,slashstar,dashdash;
211    boolean isqmarktoident;
212    public boolean insqlpluscmd;
213    char dummych1,dummych2,dummych3;
214    boolean utf8NoBreakSpaceReady = false;
215
216    int nestedLessThan = 0;
217
218    boolean isReadyForFunctionBody = false, isInFunctionBody = false;
219    int   functionBodyDelimiterIndex = -1;
220    ArrayList<String> functionBodyDelimiter = new ArrayList<>();
221
222    public static int keyword_type_reserved = 0x0001;
223    public static int keyword_type_keyword = 0x0002;
224    public static int keyword_type_identifier = 0x0004;
225    public static int keyword_type_column = 0x0008;
226
227    public char delimiterchar;
228    public String defaultDelimiterStr;
229    public String tmpDelimiter;
230    
231    final static int intial_bufsize = 16384;
232    final static char lf = (char)10;
233    final static int max_chars = 65536*10*2;
234    final static int max_rules = 256*2*10;
235    int  max_matches = 1024*20*10*2;
236
237
238    // 下面这些常量按照在 l 文件中出现的次序,必须以  +2 的方式递加. 为什么以 +2 的方式递加 原因忘了,尚未搞清楚。
239    final static int init = 2;
240    final static int xc = 4;
241    final static int xd = 6;
242    final static int xq = 8;
243    final static int xqq = 10;  //oracle
244    final static int xdolq = 10;//postgresql
245    final static int xdbracket = 10;
246    final static int xdbrace = 12;
247    final static int xbacktick = 12;
248
249    final static int xbracketrs = 12; //redshift
250    final static int xdolq_clickhouse = 14;//clickhouse: 7th %start in its .l, after xdbracket(10) and xbacktick(12)
251    final static int xqtriple = 14;//bigquery
252    final static int xdtriple = 16;//bigquery
253
254
255
256    //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server
257    final static int namedatalen = 8060;//255;
258    
259    final static int cmtslashstar = 257;
260    final static int cmtdoublehyphen = 258;
261    final static int lexspace = 259;
262    final static int lexnewline = 260;
263    final static int fconst  = 261;
264    final static int sconst = 262;
265    final static int iconst = 263;
266    final static int ident = 264;
267    final static int op = 265;
268    final static int cmpop = 266;
269    final static int bind_v = 267;
270    final static int assign_sign = 268;
271    final static int double_dot = 269;
272    final static int label_begin = 270;
273    final static int label_end = 271;
274    final static int substitution_v  = 272;
275    final static int filepath_sign = TBaseType.filepath_sign;
276    final static int sqlpluscmd = 273;
277    final static int atversion = TBaseType.atversion; //databricks
278    final static int error = 274;
279    final static int variable = 275;
280    final static int mslabel = 276;
281    public final static int bconst = TBaseType.bconst; //postgresql
282    final static int leftjoin_op = 277;
283    final static int odbc_esc_prefix = 277;
284    final static int rightjoin_op = 278;
285    final static int odbc_esc_terminator = 278;
286    final static int db2label = 279;
287    public final static int xconst = TBaseType.xconst; //postgresql
288    final static int ref_arrow = 280;
289    final static int rw_scriptoptions = 281;
290    public final static int UNICODE_ENCODE_ID = 281;
291    final static int mysqllabel = 282;
292    final static int NAMED_PARAMETER_SIGN = 282; //oracle,db2,snowflake CALL update_order (5000, NEW_STATUS => 'Shipped')
293    final static int QUOTED_IDENT = 282;//used in mdx
294    final static int BTEQCMD = 282;
295    final static int concatenationop = 283;
296    final static int pipe_greater = TBaseType.pipe_greater; // StarRocks pipe operator |>
297    final static int rw_not_deferrable = 284;
298    final static int rw_for1 = 285;
299    final static int stmt_delimiter = 286;
300    final static int AMP_QUOTED_ID = 285; //used in mdx
301    final static int AMP_UNQUOTED_ID = 286; //used in mdx
302    final static int m_clause = 287;
303    final static int MySQL_CHARSET_NAME = 287;
304    final static int typecast = TBaseType.typecast;//postgresql
305    final static int k_clause = 288;
306    final static int slash_dot = 288;
307    final static int outer_join = 289;
308
309    final static int not_equal = 290;
310
311    final static int param = TBaseType.param;
312    final static int mysql_null = TBaseType.rrw_mysql_null;
313
314    final static int rw_locktable = 296;
315    final static int rw_foreign2 = 297;
316    final static int rw_constraint2 = 298;
317    final static int rw_primary2 = 299;
318    final static int rw_unique2 = 300;
319    final static int     NEXT_PARAM = TBaseType.NEXT_PARAM;
320    final static int     POSITIONAL_PARAM = TBaseType.POSITIONAL_PARAM;
321    final static int     NAMED_PARAM = TBaseType.NAMED_PARAM;
322
323    final static int castoperator = TBaseType.castoperator;
324    final static int twocolons = TBaseType.twocolons;
325    final static int compoundAssignmentOperator = TBaseType.compoundAssignmentOperator;
326    final static int postgresql_function_delimiter = TBaseType.rrw_postgresql_function_delimiter;
327    final static int greenplum_function_delimiter = TBaseType.rrw_greenplum_function_delimiter;
328
329    final static int redshift_function_delimiter = TBaseType.rrw_redshift_function_delimiter;
330    final static int snowflake_function_delimiter = TBaseType.rrw_snowflake_function_delimiter;
331
332
333
334    int[] yypos;// = new int[max_rules + 1];      // 1 based in delphi, Position 0 was not used here
335    int[] yystack;// = new int[max_matches + 1];  // 1 based in delphi, Position 0 was not used here
336  //  ArrayList yystack;
337
338    //String keywordvaluefile,keywordfile,yyk_file,yym_file,yykl_file;
339    //String yykh_file,yyml_file,yymh_file,yytl_file,yyth_file,yytint_file,yyt_file;
340
341    EDbVendor dbvendor;
342    TSourceToken prevToken = null;
343
344    public void setSqlCharset(String sqlCharset) {
345        this.sqlCharset = sqlCharset;
346    }
347
348    public String getSqlCharset() {
349        return sqlCharset;
350    }
351
352    private String sqlCharset = null;
353    
354    /**
355     * Check if token code represents a single character operator
356     */
357    protected boolean isSingleCharOperator(int tokenCode) {
358        return tokenCode == '(' || tokenCode == ')' || 
359               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    }
371    
372    /**
373     * Check if token code represents a keyword
374     */
375    protected boolean isKeyword(int tokenCode) {
376        // Check if it's in the reserved word range
377        return tokenCode >= TBaseType.rrw_select && tokenCode < TBaseType.rrw_abort;
378    }
379
380    public TCustomLexer(){
381       //this.yyinput = pbuf;
382       yytextbufsize = intial_bufsize - 1;
383       yytextbuf = new char[intial_bufsize];
384       checkyytextbuf(yytextbufsize);
385
386       bufsize = intial_bufsize - 1;
387       buf = new char[intial_bufsize];
388       checkbuf(bufsize);
389
390       //literalbuf = new StringBuffer();
391        literalbuf = new StringBuilder();
392        //keywordList = new TreeMap();
393        delimiterchar = ';';
394        tmpDelimiter = "";
395
396        xcdepth = 0;
397        nchars = 0;
398        isqmarktoident = true;
399
400       yylvalstr = "";
401        yysstate = 0;
402        yylstate = 0;
403        yymatches = 0;
404        yysleng = 0;
405       bufptr = 0;
406       yylineno = 0;
407       yycolno = 0;
408       offset = -1;
409       yylineno_p = 1;
410       yycolno_p = 1;
411       offset_p = 0;
412
413       yypos = new int[max_rules + 1];
414       max_matches = TBaseType.LEXER_INIT_MAX_MATCHES;
415       yystack = new int[max_matches + 1];
416
417        prevToken = null;
418    }
419
420    /*
421     * this function is not used. 
422    private void getkeywordvaluefromfile(){
423        int i;
424        keywordValueList.clear();
425        for(i=0; i<keywordlist.length; i++){
426           // System.out.println(keywordlist[i]);
427            String[] ss = keywordlist[i].split("[=]");
428            keywordValueList.put(ss[0].toUpperCase(),ss[1]);
429        }
430    }
431     */
432
433public  int iskeyword(String str){
434    return -1;
435}
436
437public boolean isAtBeginOfLine(){
438    return (yyretval == lexnewline || yyretval == 0);
439}
440
441//public boolean canBeColumnName(int tokencode){
442//    return false;
443//}
444
445
446public String getStringByCode(int tokenCode){
447    return null;
448}
449
450    public  int getkeywordvalue(String keyword){
451        return 0;
452    }
453
454
455    /**
456     * @deprecated , please use keywordChecker.isKeyword() instead.
457     *
458     * because there are so many non-reserved keywords in some databases, it's not suitable to put those
459     * non-reserved keywords in lexer and parser.
460     *
461     * @param keyword
462     * @param keywordValueList
463     * @param keywordTypeList
464     * @return
465     */
466    public static EKeywordType getKeywordType(String keyword, HashMap<String, Integer> keywordValueList,HashMap<Integer, Integer> keywordTypeList){
467        EKeywordType ret = EKeywordType.NOT_A_KEYWORD;
468        Integer s = keywordValueList.get(keyword.toUpperCase(Locale.ENGLISH));
469        if( s == null) return ret;
470
471        Integer i = keywordTypeList.get(s);
472        if (i == 1) return EKeywordType.RESERVED_WORD;
473        else if (i == 2) return EKeywordType.NON_RESERVED_KEYWORD;
474        else return  ret;
475    }
476
477    /**
478     * 如果是ascii 字符,直接返回,如果是unicode 字符,需要进行转换。否则 String.charAt() 返回的unicode字符不是我们想要的字符,
479     * 例如中文的括号,我们实际需要的ascii的括号
480     *
481     * @param pYylvalstr
482     * @param index
483     * @return
484     */
485   char lexer_charAt(String pYylvalstr,int index){
486        char ret = pYylvalstr.charAt(index);
487        if (ret > 255){
488            // this is a unicode code
489            if ((ret == 0xFF08)){
490                // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=65280&number=128
491                // Unicode code point for FULLWIDTH LEFT PARENTHESIS (, 0xFF08
492                //System.out.println(c);
493                ret = '(';
494            }
495            if ( (ret == 0xFF09)){
496                // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=65280&number=128
497                // Unicode code point for FULLWIDTH RIGHT PARENTHESIS ), 0xFF09
498                // System.out.println(c);
499                ret = ')';
500            }
501        }
502        return ret;
503   }
504   void totablechar(){
505       //System.out.println("char:"+yyactchar+" ,hex:"+String.format("%04x", (int) yyactchar));
506       //System.out.println(String.format("0x%08X", (int)yyactchar)+", "+(char)yyactchar);
507
508    if (((int) yyactchar == 0) && !endOfInput) {
509        yytablechar = (char)255;
510        return;
511    }
512
513     if ((int)(yyactchar) < 228){ // 228 is ä in unicode
514       yytablechar = yyactchar;
515         if ((((int)(yyactchar) == 160)&&(utf8NoBreakSpaceReady))||(yyactchar == 0xA0)){
516             yytablechar = (char)32;
517         }
518       utf8NoBreakSpaceReady = false;
519//         if (yyactchar == 0x27){
520//             insideSingleQuoteStr = !insideSingleQuoteStr;
521//          }
522     }else{
523         yytablechar = (char)'a';//(char)255;
524
525         if ((int)(yyactchar) == 914) { // c2 a0, utf-8 NO-BREAK SPACE
526             utf8NoBreakSpaceReady = true;
527             yytablechar = (char) 32;
528         }else if ((yyactchar == 0x2018)||(yyactchar == 0x2019)){
529             if (stringLiteralStartWithUnicodeSingleQuote){
530                 // WHERE Name LIKE ‘Acme%’
531                 // 如上,如果string literal 以unicode quote 开始,则不管当前是否在string literal中,新碰到的unicode quote都看成是string literal的结尾符,
532                 yytablechar = 0x27; // treat  Unicode Character 'LEFT SINGLE QUOTATION MARK' as the ascii char ', but don't change it
533             }else {
534                 if (insideSingleQuoteStr){
535                     // don't change the unicode quote char
536                 }else {
537                     yytablechar = 0x27; // treat  Unicode Character 'LEFT SINGLE QUOTATION MARK' as the ascii char ', but don't change it
538                 }
539             }
540
541         }else if ((yyactchar == 0x200B)||(yyactchar == 0x3000)||(yyactchar >= 0x2000 && yyactchar <= 0x200A)){
542             // Unicode code point 0x200B: treat  Unicode Character ZERO WIDTH SPACE as the ascii char space, but don't change it
543             // Unicode code point 0x3000: treat  Unicode Character IDEOGRAPHIC SPACE (UTF-8: e3 80 80) as the ascii char space, but don't change it
544             // Unicode code points 0x2000-0x200A: General Punctuation space characters (EN QUAD, EM QUAD, EN SPACE, EM SPACE, THREE-PER-EM SPACE, etc.)
545             yytablechar = 0x20;
546         }else if (yyactchar == 0xFF08){
547             yytablechar = '('; // treat  Unicode code point for FULLWIDTH LEFT PARENTHESIS  as the ascii char (, but don't change it
548         }else if (yyactchar == 0xFF09){
549             yytablechar = ')'; // treat  Unicode code point for FULLWIDTH RIGHT PARENTHESIS  as the ascii char ), but don't change it
550         }else if (yyactchar == 0xFF0C){
551             yytablechar = ','; // treat  Unicode code point for FULLWIDTH COMMA  as the ascii char comma, but don't change it
552         }else {
553             utf8NoBreakSpaceReady = false;
554         }
555     }
556   }
557
558    String getyytext(){
559      return new String(yytextbuf,0,yytextlen);
560    }
561
562
563    void checkyytextbuf(int size){
564       while ( size >= yytextbufsize){
565          yytextbufsize = yytextbufsize * 2 > intial_bufsize ? yytextbufsize * 2: intial_bufsize;
566          char[] tmp = new char[yytextbufsize];
567           System.arraycopy(yytextbuf,0,tmp,0, yytextbuf.length);
568           yytextbuf = tmp;
569       }
570    }
571    
572    void checkbuf(int size){
573       // System.out.println("while begin2"+" size:"+size+" bufsize:"+bufsize);
574       while ( size >= bufsize){
575          bufsize = bufsize * 2 > intial_bufsize ? bufsize * 2: intial_bufsize;
576          char[] tmp = new char[bufsize];
577           System.arraycopy(buf,0,tmp,0, buf.length);
578           buf = tmp;
579       }
580       // System.out.println("while end2");
581    }
582
583    boolean eof(BufferedReader pbuf){
584        try{
585        return !pbuf.ready();
586        }catch(IOException e){
587          return true;
588        }
589    }
590
591    void yynew(){
592        if (yylastchar != (char)0){
593          if(yylastchar == lf){
594            yylstate = 1;
595          }else{
596              yylstate = 0;
597          }
598        }
599
600        yystate = yysstate + yylstate;
601        checkyytextbuf(0);
602        yytextlen = 0;
603        yymatches = 0;
604        yydone = false;
605    }
606
607    void yyscan(){
608        yyactchar = get_char();
609        checkyytextbuf(yytextlen + 1);
610        yytextlen++;
611        yytextbuf[yytextlen - 1] = yyactchar;
612    }
613
614    void yymark(int n){
615        if (n > max_rules ){
616           System.out.println("n > max_rules ");
617        }
618        yypos[n] = yytextlen;
619    }
620
621    void yymatch(int n){
622        yymatches++;
623        if(yymatches > max_matches){
624
625            int new_yystack[] = new int[max_matches*2+1];
626            System.arraycopy(yystack, 0, new_yystack, 0, max_matches);
627            yystack = new_yystack;
628            max_matches = max_matches * 2;
629
630           // this is valid in JDK 1.6, proguard will report warning and stop
631           // yystack = Arrays.copyOf(yystack,max_matches+1);
632
633        }
634        yystack [yymatches] = n;
635    }
636
637    int yyfind(){
638        //return -1 mean not found
639        int ret = -1;
640
641        yyreject = false;
642        
643        while (( yymatches > 0 ) && ( yypos[yystack[yymatches]] == 0 )) {
644           yymatches-- ;
645        }
646        
647
648        if (yymatches > 0){
649          yysleng = yytextlen;
650          ret = yystack[yymatches];
651          yyless( yypos[ret] );
652          yypos[ret] = 0;
653          if (yytextlen >0){
654            yylastchar = yytextbuf [yytextlen-1];
655          }else{
656            yylastchar = (char)0;
657          }
658        }else{
659          yyless( 0 );
660          yylastchar = (char)0;
661        }
662
663       return ret;
664    }
665
666    boolean yydefault(){
667        boolean ret;
668
669        yyreject = false;
670        yyactchar = get_char();
671        if (yyactchar != (char)0){
672          //put_char( yyactchar );
673          ret = true;
674        }else{
675          yylstate = 1;
676          ret = false;
677        }
678        yylastchar = yyactchar;
679        return ret;
680    }
681    void yyless(int n){
682        for(int i= yytextlen; i> n; i--){
683            unget_char(yytextbuf[i - 1]);
684        }
685        checkyytextbuf(n);
686        yytextlen = n;
687    }
688    void returni(int n){
689        yyretval = n;
690        yydone = true;
691    }
692    void returnc(char c){
693        yyretval = (int)c;
694        yydone = true;
695    }
696    void yyclear(){
697        bufptr = 0;
698        yysstate = 0;
699        yylstate = 1;
700        yylastchar = (char)0;
701        yytextlen = 0;
702        yylineno = 0;
703        yycolno = 0;
704        offset = -1;
705       // yystext := '';
706
707        yylineno_p = 1;
708        yycolno_p = 1;
709        offset_p = 0;
710
711    }
712
713    
714    boolean yywrap(){
715        return true;
716    }
717    int getyysstate(){
718        return yysstate;
719    }
720    void start(int pstate){
721        yysstate = pstate;
722        if (pstate == xq){
723            insideSingleQuoteStr = true;
724            if ((yylvalstr.charAt(0) == 0x2018)||(yylvalstr.charAt(0) == 0x2019)){
725                stringLiteralStartWithUnicodeSingleQuote = true;
726            }else{
727                stringLiteralStartWithUnicodeSingleQuote = false;
728            }
729        }else{
730            insideSingleQuoteStr = false;
731        }
732    }
733
734
735    void unget_char(char pchar){
736        // get_char() uses NUL as its end-of-input sentinel and does not
737        // advance yycolno/offset when returning it.  Pushing that sentinel
738        // back used to decrement both counters as if a real source character
739        // had been consumed.  A following yyless() split at EOF could then
740        // assign the next real token the preceding token's source span (for
741        // example, DB2 "k=?" gave '?' the '=' offset).  Only ignore the EOF
742        // sentinel: a real NUL input character does advance the position and
743        // must still be restored.
744        if (pchar == (char)0 && endOfInput) {
745            endOfInput = false;
746            return;
747        }
748        if(bufptr == max_chars)
749        {
750            System.out.println("input buffer overflow");
751        }
752      //  if (bufptr > 0) {
753        bufptr++;
754        yycolno--;
755        offset--;
756        checkbuf(bufptr+1);
757        buf[bufptr] = pchar;
758      //  }
759
760    }
761
762    public void reset(){
763        insideSingleQuoteStr = false;
764        nestedLessThan = 0;
765    }
766
767    public boolean insideSingleQuoteStr = false;
768    public boolean stringLiteralStartWithUnicodeSingleQuote = false;
769
770
771    // Previous implementation of readln, 2025-05-04
772    // char[] readln()  throws IOException {
773    //     int c;
774    //     char[] buffer = new char[80];
775    //     int bufferSize = 0;
776
777    //     while ((c = yyinput.read()) != -1) {
778    //         if (bufferSize >= buffer.length) {
779    //             char[] newBuffer = new char[buffer.length * 2];
780    //             System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
781    //             buffer = newBuffer;
782    //         }
783
784    //         buffer[bufferSize++] = (char)c;
785
786    //         if (c == '\n' || c == '\r') {
787    //             break;
788    //         }
789    //     }
790
791    //     if (bufferSize > 0 && buffer[bufferSize - 1] == '\r') {
792    //         yyinput.mark(1);
793    //         c = yyinput.read();
794    //         if (c == '\n') {
795    //             if (bufferSize >= buffer.length) {
796    //                 char[] newBuffer = new char[buffer.length + 1];
797    //                 System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
798    //                 buffer = newBuffer;
799    //             }
800    //             buffer[bufferSize++] = '\n';
801    //         } else {
802    //             yyinput.reset();
803    //         }
804    //     }
805
806    //     if (bufferSize == 0) {
807    //         return null;
808    //     }else{
809    //         char[] result = new char[bufferSize];
810    //         System.arraycopy(buffer, 0, result, 0, bufferSize);
811    //         return result;
812    //     }
813    // }
814
815/**
816 * High-performance line reader with optimal buffer management.
817 * Reuses lineReadBuffer across calls to reduce per-line allocations.
818 * @return char array containing the line including line ending, or null if end of stream
819 */
820char[] readln() throws IOException {
821    if (yyinput == null) return null;
822
823    int position = 0;
824    int c;
825
826    // Read characters until line ending or EOF
827    while ((c = yyinput.read()) != -1) {
828        // Expand buffer if needed (expanded buffer stays for reuse)
829        if (position >= lineReadBuffer.length) {
830            char[] newBuffer = new char[lineReadBuffer.length * 2];
831            System.arraycopy(lineReadBuffer, 0, newBuffer, 0, lineReadBuffer.length);
832            lineReadBuffer = newBuffer;
833        }
834
835        // Store character
836        lineReadBuffer[position++] = (char)c;
837
838        // Check for line endings
839        if (c == '\n') {
840            break; // LF - end of line
841        } else if (c == '\r') {
842            // Need to check for CR+LF sequence
843            yyinput.mark(1);
844            c = yyinput.read();
845
846            if (c == '\n') {
847                // CR+LF sequence - include LF in result
848                if (position >= lineReadBuffer.length) {
849                    char[] newBuffer = new char[lineReadBuffer.length + 1];
850                    System.arraycopy(lineReadBuffer, 0, newBuffer, 0, lineReadBuffer.length);
851                    lineReadBuffer = newBuffer;
852                }
853                lineReadBuffer[position++] = '\n';
854            } else {
855                // CR only - reset stream to keep the character after CR
856                yyinput.reset();
857            }
858            break;
859        }
860    }
861
862    // Return null if no characters were read (end of stream)
863    if (position == 0) {
864        yylineLen = 0;
865        return null;
866    }
867
868    // Return lineReadBuffer directly, avoiding per-line array allocation.
869    // yylineLen holds the actual content length (replaces yyline.length semantic).
870    yylineLen = position;
871    return lineReadBuffer;
872}
873
874    char get_char(){
875
876        char ret ;
877         boolean readlineok = true;
878
879        if ((bufptr == 0) && !eof(yyinput) )
880        {
881            try{
882               endOfInput = false;
883               yyline = readln();//yyinput.readLine();
884              // System.out.println("readln: "+yyline);
885                if (yyline == null){
886                  readlineok = false;
887                }  else{
888                    yylineno++;
889                    yycolno = 0;
890                    // Use yylineLen instead of yyline.length since lineReadBuffer is reused
891                    bufptr = yylineLen;
892                    checkbuf(bufptr+1);
893                    for(int k=1;k<=bufptr;k++){
894                        buf[k] = yyline[bufptr - k];
895                    }
896                }
897            }catch(IOException e){
898              readlineok = false;
899            }
900        }
901
902        if (! readlineok){
903          endOfInput = true;
904          return (char)0;
905        }
906
907       if (bufptr > 0){
908         bufptr--;
909         yycolno++;
910         offset++;
911
912         return buf[bufptr+1];
913         //return yyline.charAt(yyline.length()  - (bufptr + 1));
914       }else{
915       // bufptr--;
916           endOfInput = true;
917        return  (char)0;
918       }
919
920    }
921
922    void startlit(){
923        literalbuf.setLength(0);
924        literallen = 0;
925        literalalloc = 0;
926    }
927
928    void addlit(String ytext, int yleng){
929        literallen = literallen + yleng;
930        literalbuf.append(ytext,0,yleng);
931    }
932
933    void addlitchar(char ychar){
934        literallen++;
935        literalbuf.append(ychar);
936    }
937
938    String litbufdup(){
939        return literalbuf.toString();//.intern();
940    }
941
942    boolean isopchar(char ch){
943        switch (ch) {
944            case '~':
945            case '!':
946            case '@':
947            case '#':
948            case '^':
949            case '&':
950            case '|':
951            case '`':
952            case '?':
953            case '$':
954            case '%':
955                return true;
956            default:
957                return false;
958        }
959    }
960
961    boolean isselfchar(char ch){
962        switch (ch) {
963            case ',':
964            case '(':
965            case ')':
966            case '[':
967            case ']':
968            case '.':
969            case ';':
970            case '$':
971            case ':':
972            case '+':
973            case '-':
974            case '*':
975            case '/':
976            case '%':
977            case '^':
978            case '<':
979            case '>':
980            case '=':
981            case '!':
982            case '{':
983            case '}':
984                return true;
985            default:
986                return false;
987        }
988    }
989
990    boolean charinarray(char c, char[] a){
991        int len = a.length;
992        for (int i = 0; i < len; i++) {
993            if (a[i] == c)
994                return true;
995        }
996        return false;
997    }
998
999    void setlengthofliteralbuf(int plen){
1000      literalbuf.setLength(plen);
1001    }
1002
1003    void yyaction(int yyruleno){
1004    }
1005
1006    int yylex(){
1007        return 0;
1008    }
1009
1010
1011    public int yylexwrap(TSourceToken psourcetoken) {
1012        // Get token code and handle EOF
1013        if ((psourcetoken.tokencode = yylex()) == 0) return 0;
1014
1015        // Store token text - use shared strings for single ASCII chars to reduce allocations
1016        if (yylvalstr == null) {
1017            if (yytextlen == 1 && yytextbuf[0] < 128) {
1018                yylvalstr = SINGLE_CHAR_STRINGS[yytextbuf[0]];
1019            } else {
1020                yylvalstr = new String(yytextbuf, 0, yytextlen);
1021            }
1022        }
1023        psourcetoken.setAstext(yylvalstr);
1024    
1025        // Record token position information
1026        psourcetoken.lineNo = yylineno_p;
1027        psourcetoken.columnNo = yycolno_p;
1028        psourcetoken.offset = offset_p;
1029        yylineno_p = yylineno;
1030        yycolno_p = yycolno + 1;
1031        offset_p = offset + 1;
1032        
1033        // Track token in token table for analysis
1034        setTokenTableValue(psourcetoken);
1035    
1036        // Handle token types based on token code
1037        switch (psourcetoken.tokencode) {
1038            case cmtdoublehyphen:
1039                psourcetoken.tokentype = ETokenType.ttsimplecomment;
1040                if (dbvendor == EDbVendor.dbvmdx && psourcetoken.toString().startsWith("/")) {
1041                    psourcetoken.tokentype = ETokenType.ttCPPComment;
1042                }
1043                break;
1044                
1045            case cmtslashstar:
1046                psourcetoken.tokentype = ETokenType.ttbracketedcomment;
1047                break;
1048                
1049            case lexspace:
1050                psourcetoken.tokentype = ETokenType.ttwhitespace;
1051                break;
1052                
1053            case lexnewline:
1054                psourcetoken.tokentype = ETokenType.ttreturn;
1055                break;
1056                
1057            case bind_v:
1058                psourcetoken.tokentype = ETokenType.ttbindvar;
1059                if (dbvendor == EDbVendor.dbvoracle) {
1060                    psourcetoken.setAstext(psourcetoken.getAstext().replace(TBaseType.newline, ""));
1061                }
1062                break;
1063                
1064            case stmt_delimiter:
1065                psourcetoken.tokentype = ETokenType.ttstmt_delimiter;
1066                psourcetoken.tokencode = cmtslashstar;
1067                break;
1068                
1069            case concatenationop:
1070                psourcetoken.tokentype = ETokenType.ttconcatenationop;
1071                break;
1072                
1073            case variable:
1074                psourcetoken.tokentype = ETokenType.ttsqlvar;
1075                break;
1076                
1077            case fconst:
1078            case iconst:
1079                psourcetoken.tokentype = ETokenType.ttnumber;
1080                break;
1081                
1082            case sconst:
1083                psourcetoken.tokentype = ETokenType.ttsqstring;
1084                psourcetoken.dolqstart = dolqstart;
1085                dolqstart = "";
1086                break;
1087                
1088            case ident:
1089            case QUOTED_IDENT:
1090                handleIdentifierToken(psourcetoken);
1091                break;
1092                
1093            case cmpop:
1094                handleComparisonOperator(psourcetoken);
1095                break;
1096                
1097            case op:
1098                handleOperatorToken(psourcetoken);
1099                break;
1100                
1101            default:
1102                handleDefaultToken(psourcetoken);
1103                break;
1104        }
1105    
1106        prevToken = psourcetoken;
1107        return psourcetoken.tokencode;
1108    }
1109    
1110    // Helper methods to better organize the complex token handling logic
1111    private void handleIdentifierToken(TSourceToken psourcetoken) {
1112        psourcetoken.tokentype = ETokenType.ttidentifier;
1113        String tokenText = psourcetoken.toString().trim();
1114        
1115        if (tokenText.startsWith("\"")) {
1116            psourcetoken.tokentype = ETokenType.ttdqstring;
1117        } else if (tokenText.startsWith("[")) {
1118            if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) {
1119                psourcetoken.tokentype = ETokenType.ttdbstring;
1120            }
1121        } else if (tokenText.startsWith("{")) {
1122            if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) {
1123                psourcetoken.tokentype = ETokenType.ttbrstring;
1124                if (tokenText.toLowerCase().startsWith("{escape")) {
1125                    psourcetoken.tokencode = TBaseType.rrw_sqlserver_odbc_escape;
1126                }
1127            }
1128        } else if (tokenText.startsWith("&")) {
1129            if (dbvendor == EDbVendor.dbvmdx) {
1130                if (psourcetoken.tokencode == QUOTED_IDENT) {
1131                    psourcetoken.tokencode = AMP_QUOTED_ID;
1132                } else if (psourcetoken.tokencode == ident) {
1133                    psourcetoken.tokencode = AMP_UNQUOTED_ID;
1134                }
1135            }
1136        } else if (tokenText.startsWith(".")) {
1137            if (dbvendor == EDbVendor.dbvteradata) {
1138                psourcetoken.tokentype = ETokenType.ttBTEQCmd;
1139            }
1140        }
1141    }
1142    
1143    private void handleComparisonOperator(TSourceToken psourcetoken) {
1144        psourcetoken.tokentype = ETokenType.ttmulticharoperator;
1145        String token = yylvalstr;
1146
1147        // Oracle 26c vector distance operators (3-char, Oracle only)
1148        if (dbvendor == EDbVendor.dbvoracle) {
1149            if (token.equals("<=>")) {
1150                psourcetoken.tokencode = TBaseType.vector_cosine_distance;
1151                return;
1152            } else if (token.equals("<->")) {
1153                psourcetoken.tokencode = TBaseType.vector_euclidean_distance;
1154                return;
1155            } else if (token.equals("<#>")) {
1156                psourcetoken.tokencode = TBaseType.vector_dot_product;
1157                return;
1158            }
1159        }
1160
1161        if ((token.startsWith("!") && token.endsWith("=")) ||
1162            (token.startsWith("^") && token.endsWith("=")) ||
1163            (token.startsWith("~") && token.endsWith("=")) ||
1164            (token.startsWith("<") && token.endsWith(">"))) {
1165
1166            psourcetoken.tokencode = TBaseType.not_equal;
1167
1168            // Handle MySQL NULL-safe equal
1169            if (token.indexOf("=", 1) > 0 &&
1170                token.startsWith("<") && token.endsWith(">")) {
1171                psourcetoken.tokencode = (int)'=';
1172            }
1173        } else if (token.startsWith(">") && token.endsWith("=")) {
1174            psourcetoken.tokencode = TBaseType.great_equal;
1175        } else if (token.startsWith("<") && token.endsWith("=")) {
1176            psourcetoken.tokencode = TBaseType.less_equal;
1177        } else if ((token.startsWith("!") && token.endsWith("<")) ||
1178                   (token.startsWith("^") && token.endsWith("<"))) {
1179            psourcetoken.tokencode = TBaseType.not_less;
1180        } else if ((token.startsWith("!") && token.endsWith(">")) ||
1181                   (token.startsWith("^") && token.endsWith(">"))) {
1182            psourcetoken.tokencode = TBaseType.not_great;
1183        } else if (token.length() == 2 && token.charAt(0) == ':' && token.charAt(1) == '=') {
1184            psourcetoken.tokencode = assign_sign;
1185        }
1186    }
1187    
1188    private void handleOperatorToken(TSourceToken psourcetoken) {
1189        psourcetoken.tokentype = ETokenType.ttmulticharoperator;
1190        String token = yylvalstr;
1191        int tokenLength = token.length();
1192        char firstChar = tokenLength > 0 ? token.charAt(0) : '\0';
1193        char secondChar = tokenLength > 1 ? token.charAt(1) : '\0';
1194        
1195        // Handle question mark specially
1196        if (token.equals("?") && isqmarktoident) {
1197            handleQuestionMark(psourcetoken);
1198            return;
1199        }
1200        
1201        // Handle special two-character operators
1202        if (tokenLength == 2) {
1203            if (handleTwoCharOperator(psourcetoken, firstChar, secondChar)) {
1204                return;
1205            }
1206        }
1207        
1208        // Handle special three-character operators
1209        if (tokenLength == 3) {
1210            if (handleThreeCharOperator(psourcetoken, firstChar, secondChar, token.charAt(2))) {
1211                return;
1212            }
1213        }
1214        
1215        // Handle comparison operators
1216        if (handleComparisonOp(psourcetoken, token)) {
1217            return;
1218        }
1219        
1220        // Handle single character operators
1221        if (tokenLength == 1) {
1222            handleSingleCharOperator(psourcetoken, firstChar);
1223        }
1224    }
1225    
1226    private boolean handleTwoCharOperator(TSourceToken psourcetoken, char firstChar, char secondChar) {
1227        switch (firstChar) {
1228            case '<':
1229                if (secondChar == '<') {
1230                    return handleLeftShiftOperator(psourcetoken);
1231                } else if (secondChar == '@') {
1232                    psourcetoken.tokencode = TBaseType.JSON_RIGHT_CONTAIN;
1233                    return true;
1234                }
1235                break;
1236                
1237            case '>':
1238                if (secondChar == '>') {
1239                    return handleRightShiftOperator(psourcetoken);
1240                }
1241                break;
1242                
1243            case '=':
1244                if (secondChar == '>') {
1245                    if (dbvendor == EDbVendor.dbvodbc) {
1246                        psourcetoken.tokencode = TBaseType.great_equal;
1247                    } else if (dbvendor == EDbVendor.dbvpostgresql || dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb) {
1248                        psourcetoken.tokencode = TBaseType.assign_sign;
1249                    } else {
1250                        psourcetoken.tokencode = NAMED_PARAMETER_SIGN;
1251                    }
1252                    return true;
1253                } else if (secondChar == '*') {
1254                    if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) {
1255                        psourcetoken.tokencode = rightjoin_op;
1256                    }
1257                    return true;
1258                } else if (secondChar == '<') {
1259                    if (dbvendor == EDbVendor.dbvodbc) {
1260                        psourcetoken.tokencode = TBaseType.less_equal;
1261                    }
1262                    return true;
1263                } else if (secondChar == '=') {
1264                    if (dbvendor == EDbVendor.dbvsparksql || dbvendor == EDbVendor.dbvclickhouse) {
1265                        psourcetoken.tokencode = '=';
1266                    }
1267                    return true;
1268                }
1269                break;
1270                
1271            case '-':
1272                if (secondChar == '>') {
1273                    if (dbvendor == EDbVendor.dbvpostgresql || dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb 
1274                        || dbvendor == EDbVendor.dbvgreenplum || dbvendor == EDbVendor.dbvmysql) {
1275                        psourcetoken.tokencode = TBaseType.JSON_GET_OBJECT;
1276                    } else {
1277                        psourcetoken.tokencode = ref_arrow;
1278                    }
1279                    return true;
1280                } else if (secondChar == '=') {
1281                    if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) {
1282                        psourcetoken.tokencode = compoundAssignmentOperator;
1283                    }
1284                    return true;
1285                }
1286                break;
1287                
1288            case '.':
1289                if (secondChar == '.') {
1290                    if (dbvendor == EDbVendor.dbvdb2 || dbvendor == EDbVendor.dbvoracle 
1291                        || dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvhana) {
1292                        psourcetoken.tokencode = double_dot;
1293                    }
1294                    return true;
1295                }
1296                break;
1297                
1298            case '*':
1299                if (secondChar == '=') {
1300                    if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) {
1301                        psourcetoken.tokencode = leftjoin_op;
1302                    }
1303                    return true;
1304                } else if (secondChar == '*') {
1305                    if (dbvendor == EDbVendor.dbvteradata || dbvendor == EDbVendor.dbvnetezza) {
1306                        psourcetoken.tokencode = TBaseType.exponentiate;
1307                    }
1308                    return true;
1309                }
1310                break;
1311                
1312            case '|':
1313                if (secondChar == '|') {
1314                    if (dbvendor == EDbVendor.dbvmysql) {
1315                        psourcetoken.tokencode = TBaseType.logical_or;
1316                    } else if (isStringConcatVendor(dbvendor)) {
1317                        psourcetoken.tokencode = TBaseType.concatenationop;
1318                    }
1319                    return true;
1320                } else if (secondChar == '>') {
1321                    if (dbvendor == EDbVendor.dbvsparksql) {
1322                        psourcetoken.tokencode = TBaseType.sparksql_pipe_arrow;
1323                    } else {
1324                        psourcetoken.tokencode = TBaseType.pipe_greater;
1325                    }
1326                    return true;
1327                } else if (secondChar == '/') {
1328                    if (dbvendor == EDbVendor.dbvredshift) {
1329                        psourcetoken.tokencode = TBaseType.square_root;
1330                    }
1331                    return true;
1332                }
1333                break;
1334                
1335            case '&':
1336                if (secondChar == '&') {
1337                    if (dbvendor == EDbVendor.dbvmysql) {
1338                        psourcetoken.tokencode = TBaseType.logical_and;
1339                    }
1340                    return true;
1341                }
1342                break;
1343                
1344            case '?':
1345                if (secondChar == '|') {
1346                    psourcetoken.tokencode = TBaseType.JSON_ANY_EXIST;
1347                    return true;
1348                } else if (secondChar == '&') {
1349                    psourcetoken.tokencode = TBaseType.JSON_ALL_EXIST;
1350                    return true;
1351                }
1352                break;
1353                
1354            case '@':
1355                if (secondChar == '>') {
1356                    psourcetoken.tokencode = TBaseType.JSON_LEFT_CONTAIN;
1357                    return true;
1358                }
1359                break;
1360                
1361            case '#':
1362                if (secondChar == '>') {
1363                    psourcetoken.tokencode = TBaseType.JSON_GET_OBJECT_AT_PATH;
1364                    return true;
1365                }
1366                break;
1367                
1368            case ':':
1369                if (secondChar == '=') {
1370                    psourcetoken.tokencode = assign_sign;
1371                    return true;
1372                }
1373                break;
1374        }
1375        
1376        // Handle compound assignment operators
1377        if ((firstChar == '+' || firstChar == '-' || firstChar == '*' || 
1378             firstChar == '/' || firstChar == '%' || firstChar == '&' || 
1379             firstChar == '^' || firstChar == '|') && secondChar == '=') {
1380            if (dbvendor == EDbVendor.dbvmssql || dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq) {
1381                psourcetoken.tokencode = compoundAssignmentOperator;
1382                return true;
1383            } else if (dbvendor == EDbVendor.dbvmysql && firstChar == '^' && secondChar == '=') {
1384                psourcetoken.tokencode = not_equal;
1385                return true;
1386            }
1387        }
1388        
1389        return false;
1390    }
1391    
1392    private boolean handleThreeCharOperator(TSourceToken psourcetoken, char firstChar, char secondChar, char thirdChar) {
1393        if (firstChar == '-' && secondChar == '>' && thirdChar == '>') {
1394            psourcetoken.tokencode = TBaseType.JSON_GET_TEXT;
1395            return true;
1396        } else if (firstChar == '#' && secondChar == '>' && thirdChar == '>') {
1397            psourcetoken.tokencode = TBaseType.JSON_GET_TEXT_AT_PATH;
1398            return true;
1399        } else if (firstChar == '|' && secondChar == '|' && thirdChar == '/') {
1400            if (dbvendor == EDbVendor.dbvredshift) {
1401                psourcetoken.tokencode = TBaseType.cube_root;
1402                return true;
1403            }
1404        }
1405        return false;
1406    }
1407    
1408    private boolean handleComparisonOp(TSourceToken psourcetoken, String token) {
1409        if ((token.startsWith("!") && token.endsWith("=")) ||
1410            (token.startsWith("^") && token.endsWith("=")) ||
1411            (token.startsWith("<") && token.endsWith(">"))) {
1412            psourcetoken.tokencode = TBaseType.not_equal;
1413            return true;
1414        } else if (token.startsWith(">") && token.endsWith("=")) {
1415            psourcetoken.tokencode = TBaseType.great_equal;
1416            return true;
1417        } else if (token.startsWith("<") && token.endsWith("=")) {
1418            psourcetoken.tokencode = TBaseType.less_equal;
1419            return true;
1420        } else if ((token.startsWith("!") && token.endsWith("<")) ||
1421                   (token.startsWith("^") && token.endsWith("<"))) {
1422            psourcetoken.tokencode = TBaseType.not_less;
1423            return true;
1424        } else if ((token.startsWith("!") && token.endsWith(">")) ||
1425                   (token.startsWith("^") && token.endsWith(">"))) {
1426            psourcetoken.tokencode = TBaseType.not_great;
1427            return true;
1428        }
1429        return false;
1430    }
1431    
1432    private void handleSingleCharOperator(TSourceToken psourcetoken, char ch) {
1433        switch (ch) {
1434            case '~':
1435                if (dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvredshift || 
1436                    dbvendor == EDbVendor.dbvsnowflake) {
1437                    psourcetoken.tokencode = (int)'~';
1438                }
1439                break;
1440                
1441            case '#':
1442                if (dbvendor == EDbVendor.dbvmssql) {
1443                    psourcetoken.tokencode = (int)'#';
1444                }
1445                break;
1446                
1447            case '&':
1448                if (dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvvertica || 
1449                    dbvendor == EDbVendor.dbvsparksql) {
1450                    psourcetoken.tokencode = (int)'&';
1451                }
1452                break;
1453                
1454            case '|':
1455                if (dbvendor == EDbVendor.dbvmysql || dbvendor == EDbVendor.dbvvertica) {
1456                    psourcetoken.tokencode = (int)'|';
1457                }
1458                break;
1459        }
1460    }
1461    
1462    private void handleQuestionMark(TSourceToken psourcetoken) {
1463        if (dbvendor == EDbVendor.dbvpostgresql || dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb || 
1464            dbvendor == EDbVendor.dbvgreenplum) {
1465            psourcetoken.tokencode = TBaseType.JSON_EXIST;
1466        } else if (dbvendor == EDbVendor.dbvodbc) {
1467            psourcetoken.tokencode = '?';
1468        } else if (dbvendor == EDbVendor.dbvsnowflake) {
1469            psourcetoken.tokencode = bind_v;
1470            psourcetoken.tokentype = ETokenType.ttquestionmark;
1471        } else {
1472            psourcetoken.tokencode = ident;
1473        }
1474    }
1475    
1476    private boolean handleLeftShiftOperator(TSourceToken psourcetoken) {
1477        if (dbvendor == EDbVendor.dbvoracle || dbvendor == EDbVendor.dbvmssql ||
1478            dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq || dbvendor == EDbVendor.dbvpostgresql ||
1479            dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb || dbvendor == EDbVendor.dbvaccess ||
1480            dbvendor == EDbVendor.dbvgreenplum || dbvendor == EDbVendor.dbvsnowflake) {
1481            psourcetoken.tokencode = label_begin;
1482        } else if (dbvendor == EDbVendor.dbvmysql) {
1483            psourcetoken.tokencode = TBaseType.rrw_left_shift;
1484        } else if (dbvendor == EDbVendor.dbvredshift) {
1485            psourcetoken.tokencode = TBaseType.bitwise_shift_left;
1486        } else if (dbvendor == EDbVendor.dbvnetezza) {
1487            psourcetoken.tokencode = TBaseType.rrw_netezza_op_less_less;
1488        }
1489        return true;
1490    }
1491    
1492    private boolean handleRightShiftOperator(TSourceToken psourcetoken) {
1493        if (dbvendor == EDbVendor.dbvoracle || dbvendor == EDbVendor.dbvmssql ||
1494            dbvendor == EDbVendor.dbvsybase || dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq || dbvendor == EDbVendor.dbvpostgresql ||
1495            dbvendor == EDbVendor.dbvgaussdb || dbvendor == EDbVendor.dbvedb || dbvendor == EDbVendor.dbvgreenplum ||
1496            dbvendor == EDbVendor.dbvaccess || dbvendor == EDbVendor.dbvsnowflake) {
1497            psourcetoken.tokencode = label_end;
1498        } else if (dbvendor == EDbVendor.dbvmysql) {
1499            psourcetoken.tokencode = TBaseType.rrw_right_shift;
1500        } else if (dbvendor == EDbVendor.dbvredshift) {
1501            psourcetoken.tokencode = TBaseType.bitwise_shift_right;
1502        } else if (dbvendor == EDbVendor.dbvnetezza) {
1503            psourcetoken.tokencode = TBaseType.rrw_netezza_op_great_great;
1504        }
1505        return true;
1506    }
1507    
1508    private boolean isStringConcatVendor(EDbVendor vendor) {
1509        return vendor == EDbVendor.dbvdb2 || vendor == EDbVendor.dbvnetezza || 
1510               vendor == EDbVendor.dbvpostgresql || vendor == EDbVendor.dbvgaussdb || vendor == EDbVendor.dbvedb ||
1511               vendor == EDbVendor.dbvredshift || vendor == EDbVendor.dbvgreenplum || 
1512               vendor == EDbVendor.dbvbigquery || vendor == EDbVendor.dbvsnowflake || 
1513               vendor == EDbVendor.dbvsparksql || vendor == EDbVendor.dbvvertica;
1514    }
1515    
1516    private void handleDefaultToken(TSourceToken psourcetoken) {
1517        psourcetoken.tokentype = ETokenType.ttkeyword;
1518        
1519        if (psourcetoken.tokencode < 255) {
1520            // Single character operators (ASCII characters)
1521            psourcetoken.setAstext(Character.toString(yylvalstr.charAt(0)));
1522            psourcetoken.tokentype = ETokenType.ttsinglecharoperator;
1523            
1524            switch (psourcetoken.tokencode) {
1525                case ',':
1526                    psourcetoken.tokentype = ETokenType.ttcomma;
1527                    break;
1528                case '(':
1529                    psourcetoken.tokentype = ETokenType.ttleftparenthesis;
1530                    break;
1531                case ')':
1532                    psourcetoken.tokentype = ETokenType.ttrightparenthesis;
1533                    break;
1534                case '[':
1535                    psourcetoken.tokentype = ETokenType.ttleftbracket;
1536                    break;
1537                case ']':
1538                    psourcetoken.tokentype = ETokenType.ttrightbracket;
1539                    break;
1540                case '.':
1541                    psourcetoken.tokentype = ETokenType.ttperiod;
1542                    break;
1543                case ';':
1544                    psourcetoken.tokentype = ETokenType.ttsemicolon;
1545                    break;
1546                case '$':
1547                    psourcetoken.tokentype = ETokenType.ttdolorsign;
1548                    break;
1549                case ':':
1550                    psourcetoken.tokentype = ETokenType.ttcolon;
1551                    break;
1552                case '+':
1553                    psourcetoken.tokentype = ETokenType.ttplussign;
1554                    break;
1555                case '-':
1556                    psourcetoken.tokentype = ETokenType.ttminussign;
1557                    break;
1558                case '*':
1559                    psourcetoken.tokentype = ETokenType.ttasterisk;
1560                    break;
1561                case '/':
1562                    psourcetoken.tokentype = ETokenType.ttslash;
1563                    break;
1564                case '^':
1565                    psourcetoken.tokentype = ETokenType.ttcaret;
1566                    break;
1567                case '<':
1568                    psourcetoken.tokentype = ETokenType.ttlessthan;
1569                    break;
1570                case '>':
1571                    psourcetoken.tokentype = ETokenType.ttgreaterthan;
1572                    break;
1573                case '=':
1574                    psourcetoken.tokentype = ETokenType.ttequals;
1575                    break;
1576                case '@':
1577                    if (delimiterchar == '@') {
1578                        psourcetoken.tokencode = (int)';';
1579                        psourcetoken.tokentype = ETokenType.ttsemicolon;
1580                    } else {
1581                        psourcetoken.tokentype = ETokenType.ttatsign;
1582                    }
1583                    break;
1584                case '~':
1585                    psourcetoken.tokentype = ETokenType.tttilde;
1586                    break;
1587                case '&':
1588                    psourcetoken.tokentype = ETokenType.ttampersand;
1589                    break;
1590                case '|':
1591                    psourcetoken.tokentype = ETokenType.ttverticalbar;
1592                    break;
1593                case '?':
1594                    if (isqmarktoident && dbvendor != EDbVendor.dbvodbc &&
1595                        dbvendor != EDbVendor.dbvpostgresql && dbvendor != EDbVendor.dbvgaussdb && dbvendor != EDbVendor.dbvedb && dbvendor != EDbVendor.dbvduckdb) {
1596                        psourcetoken.tokencode = ident;
1597                    }
1598                    break;
1599            }
1600        } else if (dbvendor == EDbVendor.dbvhive && psourcetoken.tokencode == TBaseType.hive_equal) {
1601            psourcetoken.tokentype = ETokenType.ttequals;
1602        }
1603    }
1604
1605}