001package gudusoft.gsqlparser;
002
003import gudusoft.gsqlparser.compiler.TASTEvaluator;
004import gudusoft.gsqlparser.compiler.TContext;
005import gudusoft.gsqlparser.compiler.TGlobalScope;
006import gudusoft.gsqlparser.compiler.TFrame;
007import gudusoft.gsqlparser.nodes.*;
008import gudusoft.gsqlparser.nodes.teradata.TTeradataHelper;
009import gudusoft.gsqlparser.resolver.*;
010import gudusoft.gsqlparser.resolver2.TSQLResolver2;
011import gudusoft.gsqlparser.resolver2.TSQLResolverConfig;
012import gudusoft.gsqlparser.resolver2.binding.BindingDiagnostic;
013import gudusoft.gsqlparser.resolver2.binding.BindingResult;
014import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
015import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
016import gudusoft.gsqlparser.sqlenv.TSQLEnv;
017import gudusoft.gsqlparser.stmt.*;
018import gudusoft.gsqlparser.stmt.dax.TDaxEvaluateStmt;
019import gudusoft.gsqlparser.stmt.dax.TDaxExprStmt;
020import gudusoft.gsqlparser.stmt.greenplum.TSlashCommand;
021import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock;
022import gudusoft.gsqlparser.stmt.mssql.TMssqlCreateProcedure;
023import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute;
024import gudusoft.gsqlparser.stmt.mysql.TMySQLSource;
025import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage;
026import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement;
027import gudusoft.gsqlparser.stmt.snowflake.TCreateTaskStmt;
028import gudusoft.gsqlparser.stmt.teradata.TTeradataBTEQCmd;
029import gudusoft.gsqlparser.stmt.teradata.TTeradataFastExportCmd;
030import gudusoft.gsqlparser.stmt.teradata.TTeradataFastLoadCmd;
031import gudusoft.gsqlparser.stmt.teradata.TTeradataMultiLoadCmd;
032import gudusoft.gsqlparser.stmt.teradata.utilities.BteqCmdType;
033import gudusoft.gsqlparser.stmt.teradata.utilities.TeradataUtilityType;
034import gudusoft.gsqlparser.util.TSnowflakeParameterChecker;
035import gudusoft.gsqlparser.parser.SqlParser;
036import gudusoft.gsqlparser.parser.ParserContext;
037import gudusoft.gsqlparser.parser.SqlParseResult;
038import gudusoft.gsqlparser.parser.AbstractSqlParser;
039
040
041import java.io.*;
042import java.nio.charset.Charset;
043import java.security.MessageDigest;
044import java.security.NoSuchAlgorithmException;
045import java.text.DateFormat;
046import java.text.ParseException;
047import java.util.*;
048
049import static gudusoft.gsqlparser.ESqlStatementType.*;
050
051
052/**
053 * This is the first class people start to use this SQL parser library.
054 * This class includes a lexer and a parser. The lexer is used to tokenize the input SQL, turn the input SQL text
055 * into a list of source tokens. The parser use this list source token as input, using the grammar rule of the specified
056 * database vendor to check the syntax of the input SQL and build the parse tree of this SQL if there is no syntax error
057 * in the input SQL.
058 * <p></p>
059 * Creating a SQL parser by specifing {@link gudusoft.gsqlparser.EDbObjectType a database vendor},
060 * then set SQL script text via {@link #setSqltext} method or reading the input SQL from a file via
061 * {@link #setSqlfilename} method.
062 * <p></p>
063 * After that, call one of the following methods to achieve what you need:
064 * <ul>
065 *  <li>{@link #tokenizeSqltext}, turns the input SQL into a sequence of token which is the
066 *  basic lexis element of SQL syntax. Token is categorized as keyword, identifier,
067 *  number, operator, whitespace and other types. All source tokens can be fetched
068 *  via the {@link #getSourcetokenlist()} method</li>
069 *
070 *  <li>{@link #getrawsqlstatements}, separates the SQL statements in the input SQL script without
071 *  doing syntax check, use the {@link #getSqlstatements()} method to get a list of SQL statements
072 *  which is the sub-class of {@link TCustomSqlStatement}, get SQL statement type
073 *  via the {@link TCustomSqlStatement#sqlstatementtype} field, and string representation of
074 *  each SQL statement via the {@link TCustomSqlStatement#toString} method. All source tokens in this SQL statement
075 *  is available by using {@link TCustomSqlStatement#sourcetokenlist} filed.
076 *  Since no parse tree is built by calling this method, no further detailed information about the SQL statement is available.
077 *  </li>
078 *
079 *  <li>{@link #parse}, Check syntax of the input SQL, doing some kind of semantic analysis without connecting to
080 *  a real database.
081 *  This method will do a in-depth analysis of the input SQL such as building the link between table and columns.
082 *  The parse tree of the input SQL is available after calling this method.
083 *  </li>
084 *  </ul>
085 *
086 *  The parser checks the syntax of those SQL statements one by one. If syntax error is found in a SQL statement,
087 *  an error will be logged, no parse tree will be built for this SQL statement,
088 *  the error message can be fetched using the {@link #getErrormessage()} method.
089 *  <p></p>
090 *  The syntax error in one SQL statement doesn't prevent the parser continue to check the syntax of the next SQL statement.
091 *  After checking syntax of all SQL statements, use the {@link #getErrorCount()} method to get the total number of errors.
092 *  <p></p>
093 *  A syntax error in a SQL stored procedure will cease this parser to check syntax of the rest SQL statements
094 *  in this stored procedure.
095 *
096 * <p>Format SQL script can be done after calling {@link #parse()}.
097 * <code>
098 *
099 *      int ret = sqlparser.parse();
100 *       if (ret == 0){
101 *           GFmtOpt option = GFmtOptFactory.newInstance();
102 *           String result = FormatterFactory.pp(sqlparser, option);
103 *           System.out.println(result);
104 *       }else{
105 *           System.out.println(sqlparser.getErrormessage());
106 *       }
107 *
108 * </code>
109 *
110 * <p> After paring SQL script, all parse tree nodes are available for use, some of use cases
111 * are:
112 * <ul>
113 *     <li>Table/column impact analysis</li>
114 *     <li>SQL rewriting</li>
115 *     <li>SQL translate between different databases</li>
116 *     <li>SQL migration analysis</li>
117 *     <li>Help to anti SQL injection</li>
118 *     <li><a href="http://support.sqlparser.com/">More use cases</a></li>
119 *     </ul>
120 *
121 * <p>Typically, SQL parse tree nodes generated by this SQL Parser were closely related to SQL
122 * elements defined in database vendor's SQL reference book. here is a brief summary of some
123 * most used SQL elements and corresponding classes defined in this SQL parser.
124 * <ul>
125 *     <li>SQL identifier: {@link gudusoft.gsqlparser.nodes.TObjectName}</li>
126 *     <li>SQL literal: {@link gudusoft.gsqlparser.nodes.TConstant}</li>
127 *     <li>SQL datatype: {@link gudusoft.gsqlparser.nodes.TTypeName}</li>
128 *     <li>SQL function: {@link gudusoft.gsqlparser.nodes.TFunctionCall}</li>
129 *     <li>SQL constraint: {@link gudusoft.gsqlparser.nodes.TConstraint}</li>
130 *     <li>SQL expression/condition: {@link gudusoft.gsqlparser.nodes.TExpression}</li>
131 *     <li>SQL select list item: {@link gudusoft.gsqlparser.nodes.TResultColumn}</li>
132 *      <li>More: {@link gudusoft.gsqlparser.nodes}</li>
133 * </ul>
134 *
135 * <p> Some major SQL statements:
136 * <ul>
137 *      <li>Select: {@link gudusoft.gsqlparser.stmt.TSelectSqlStatement}</li>
138 *      <li>Delete: {@link gudusoft.gsqlparser.stmt.TDeleteSqlStatement}</li>
139 *      <li>Insert: {@link gudusoft.gsqlparser.stmt.TInsertSqlStatement}</li>
140 *      <li>Update: {@link gudusoft.gsqlparser.stmt.TUpdateSqlStatement}</li>
141 *      <li>Create table: {@link gudusoft.gsqlparser.stmt.TCreateTableSqlStatement}</li>
142 *      <li>More: {@link   gudusoft.gsqlparser.stmt}</li>
143 * </ul>
144 *
145 * <p>Stored procedure</p>
146 * <ul>
147 *     <li>Create function: {@link gudusoft.gsqlparser.stmt.db2.TDb2CreateFunction },
148 *          {@link gudusoft.gsqlparser.stmt.mssql.TMssqlCreateProcedure},
149 *          {@link gudusoft.gsqlparser.stmt.mysql.TMySQLCreateFunction},
150 *          {@link gudusoft.gsqlparser.stmt.oracle.TPlsqlCreateFunction}</li>
151 *     <li>Create procedure: {@link gudusoft.gsqlparser.stmt.db2.TDb2CreateProcedure},
152 *          {@link gudusoft.gsqlparser.stmt.mssql.TMssqlCreateProcedure},
153 *          {@link gudusoft.gsqlparser.stmt.mysql.TMySQLCreateProcedure},
154 *          {@link gudusoft.gsqlparser.stmt.oracle.TPlsqlCreateProcedure}</li>
155 *     <li>Create trigger: {@link gudusoft.gsqlparser.stmt.TCreateTriggerStmt},
156 *          {@link gudusoft.gsqlparser.stmt.oracle.TPlsqlCreateTrigger}</li>
157 *     <li>Create package: {@link gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage}</li>
158 * </ul>
159 *
160 * For all available SQL parse tree node classes, please check the API reference.
161 *
162 *
163 */
164public class TGSqlParser {
165
166
167
168
169    public void setSqlCharset(String sqlCharset) {
170        this.sqlCharset = sqlCharset;
171    }
172
173    public String getSqlCharset() {
174        return sqlCharset;
175    }
176
177    private String sqlCharset = null;
178
179    // Thread-local to ensure thread-safe per-thread vendor tracking
180    private static final ThreadLocal<EDbVendor> currentDBVendorThreadLocal = ThreadLocal.withInitial(() -> EDbVendor.dbvoracle);
181
182    /**
183     * @deprecated Use {@link #getCurrentDBVendor()} and {@link #setCurrentDBVendor(EDbVendor)} instead.
184     * Direct field access is not thread-safe.
185     */
186    public static EDbVendor currentDBVendor = EDbVendor.dbvoracle;
187
188    public static EDbVendor getCurrentDBVendor() {
189        return currentDBVendorThreadLocal.get();
190    }
191
192    public static void setCurrentDBVendor(EDbVendor vendor) {
193        currentDBVendorThreadLocal.set(vendor);
194        currentDBVendor = vendor; // maintain backward compatibility for single-threaded code
195    }
196
197
198    /**
199     * Turn the string name of database to dbvendor
200     * <ul>
201     *     <li>access: EDbVendor.dbvaccess</li>
202     *     <li>ansi: EDbVendor.dbvansi</li>
203     *     <li>bigquery: EDbVendor.dbvbigquery</li>
204     *     <li>couchbase: EDbVendor.dbvcouchbase</li>
205     *     <li>dax: EDbVendor.dbvdax</li>
206     *     <li>db2: EDbVendor.dbvdb2</li>
207     *     <li>firebird: EDbVendor.dbvfirebird</li>
208     *     <li>generic: EDbVendor.dbvgeneric</li>
209     *     <li>greenplum: EDbVendor.dbvgreenplum</li>
210     *     <li>hana: EDbVendor.dbvhana</li>
211     *     <li>hive: EDbVendor.dbvhive</li>
212     *     <li>impala: EDbVendor.dbvimpala</li>
213     *     <li>informix: EDbVendor.dbvinformix</li>
214     *     <li>mdx: EDbVendor.dbvmdx</li>
215     *     <li>mssql or sqlserver: EDbVendor.dbvmssql</li>
216     *     <li>mysql: EDbVendor.dbvmysql</li>
217     *     <li>netezza: EDbVendor.dbvnetezza</li>
218     *     <li>odbc: EDbVendor.dbvodbc</li>
219     *     <li>openedge: EDbVendor.dbvopenedge</li>
220     *     <li>oracle: EDbVendor.dbvoracle</li>
221     *     <li>postgresql or postgres: EDbVendor.dbvpostgresql</li>
222     *     <li>redshift: EDbVendor.dbvredshift</li>
223     *     <li>snowflake: EDbVendor.dbvsnowflake</li>
224     *     <li>sybase: EDbVendor.dbvsybase</li>
225     *     <li>teradata: EDbVendor.dbvteradata</li>
226     *     <li>vertica: EDbVendor.dbvvertica</li>
227     * </ul>
228     * @param dbVendorName
229     * @return dbvendor
230     */
231    public static EDbVendor getDBVendorByName(String dbVendorName){
232        return  EDbVendor.valueOfWithDefault(dbVendorName);
233    }
234
235//    public void  teradataCmds(){
236//       // int cnt = 0;
237//        //((TLexerTeradata)getFlexer()).
238////         for(int i=0;i<TLexerTeradata.bteqCmdList.size();i++){
239////             for(int j=0;j<TLexerTeradata.multiLoadCmdList.size();j++){
240////                 if (TLexerTeradata.bteqCmdList.get(i).toString().equalsIgnoreCase(TLexerTeradata.multiLoadCmdList.get(j).toString())){
241////                   System.out.println("multiLoad: "+TLexerTeradata.bteqCmdList.get(i).toString());
242////                 }
243////             }
244////             for(int j=0;j<TLexerTeradata.fastExportCmdList.size();j++){
245////                 if (TLexerTeradata.bteqCmdList.get(i).toString().equalsIgnoreCase(TLexerTeradata.fastExportCmdList.get(j).toString())){
246////                     System.out.println("fastExport: "+TLexerTeradata.bteqCmdList.get(i).toString());
247////                 }
248////             }
249////             for(int j=0;j<TLexerTeradata.fastLoadCmdList.size();j++){
250////                 if (TLexerTeradata.bteqCmdList.get(i).toString().equalsIgnoreCase(TLexerTeradata.fastLoadCmdList.get(j).toString())){
251////                     System.out.println("FastLoad: "+TLexerTeradata.bteqCmdList.get(i).toString());
252////                 }
253////             }
254////         } //bteqCmdList
255//
256////        for(int i=0;i<TLexerTeradata.fastLoadCmdList.size();i++){
257////            for(int j=0;j<TLexerTeradata.multiLoadCmdList.size();j++){
258////                if (TLexerTeradata.fastLoadCmdList.get(i).toString().equalsIgnoreCase(TLexerTeradata.multiLoadCmdList.get(j).toString())){
259////                    System.out.println("multiLoad: "+TLexerTeradata.fastLoadCmdList.get(i).toString());
260////                }
261////            }
262////            for(int j=0;j<TLexerTeradata.fastExportCmdList.size();j++){
263////                if (TLexerTeradata.fastLoadCmdList.get(i).toString().equalsIgnoreCase(TLexerTeradata.fastExportCmdList.get(j).toString())){
264////                    System.out.println("fastExport: "+TLexerTeradata.fastLoadCmdList.get(i).toString());
265////                }
266////            }
267////
268////        }
269//
270//    }
271
272    private Stack<TFrame> frameStack = null;
273
274    public Stack<TFrame> getFrameStack(){
275        if (frameStack == null){
276            frameStack = new Stack<TFrame>();
277        }
278
279        return  frameStack;
280    }
281
282    public void setFrameStack(Stack<TFrame> frameStack) {
283        this.frameStack = frameStack;
284    }
285
286    void closeFileStream(){
287        if (streamFromSqlFile != null) {
288            try {
289                streamFromSqlFile.close();
290            } catch (IOException e) {
291                e.printStackTrace();
292            }
293        }
294    }
295
296    FileInputStream streamFromSqlFile = null;
297    InputStreamReader sqlStreamReader = null;
298    /**
299     * A sequence of source tokens created by the lexer after tokenize the input SQL
300     *
301     * @return a sequence of source tokens
302     */
303    public TSourceTokenList getSourcetokenlist() {
304        return sourcetokenlist;
305    }
306
307    /**
308     * A list of SQL statements created by the parser.
309     * If this list is created after calling the {@link #getrawsqlstatements} method, the syntax of each SQL statement
310     * is not checked and the parse tree of each statement is not created. If the {@link #parse} method is called to build
311     * this SQL statement list, then every thing is ready.
312     *
313     * @return a list of SQL statement
314     */
315    public TStatementList getSqlstatements() {
316        return sqlstatements;
317    }
318
319    enum stored_procedure_status {start,is_as,body,bodyend,end, cursor_declare};
320    enum stored_procedure_type {function,procedure,package_spec,package_body, block_with_begin,block_with_declare,
321        create_trigger,create_library,cursor_in_package_spec,others};
322
323    static final int stored_procedure_nested_level = 1024;
324
325    /**
326    ** The input SQL Text.
327    ** If {@link #sqlfilename} is specified, then this field will be ignored.
328    */
329    public String sqltext;
330
331    /**
332     * set the input SQL text, If {@link #sqlfilename} is specified before this method, the parser will using
333     * the SQL text in this field instead of read SQL from {@link #sqlfilename}.
334     *
335     * @param sqltext the input SQL text
336     */
337    public void setSqltext(String sqltext) {
338        this.sqltext = sqltext;
339        this.sqlfilename = "";
340        this.sqlInputStream = null;
341    }
342
343    /**
344     *  The SQL text that being processed.
345     *
346     * @return the SQL text that being processed
347     */
348    public String getSqltext() {
349        return sqltext;
350    }
351
352
353    /**
354    ** The input SQL will be read from this file
355     *
356    ** If field is specified, then {@link #sqltext} will be ignored.
357     * This must be the full path to the file, relative path doesn't work.
358    */
359    public String sqlfilename;
360
361
362    /**
363     * set the filename from which the input SQL will be read.
364     *
365     * @param sqlfilename the SQL file name from which the input SQL will be read
366     */
367    public void setSqlfilename(String sqlfilename) {
368        this.sqlfilename = sqlfilename;
369        this.sqltext = "";
370        this.sqlInputStream = null;
371    }
372
373    /**
374     * The input SQL filename. This parser can process the unicode encoded SQL file.
375     *
376     * @return the input SQL filename
377     */
378    public String getSqlfilename() {
379        return sqlfilename;
380    }
381
382    /**
383     * set the InputStream from which SQL will be read.
384     * If this method is called, {@link #sqlfilename} and {@link #sqltext} will be ignored.
385     *
386     * @param sqlInputStream the InputStream from which SQL will be read
387     */
388    public void setSqlInputStream(InputStream sqlInputStream) {
389        if (sqlInputStream instanceof BufferedInputStream){
390            this.sqlInputStream = (BufferedInputStream)sqlInputStream;
391        }else{
392            this.sqlInputStream = new BufferedInputStream(sqlInputStream);
393        }
394
395        this.sqlfilename = "";
396        this.sqltext = "";
397    }
398
399    private BufferedInputStream  sqlInputStream;
400
401    /**
402     *  the InputStream from which SQL will be read
403     * @return the InputStream from which SQL will be read
404     */
405    public InputStream getSqlInputStream() {
406        return sqlInputStream;
407    }
408
409    /**
410    ** Tokens generated by lexer from the input SQL script.
411     * Tokens are always available even if there are syntax errors in input the SQL script.
412    */
413    public TSourceTokenList sourcetokenlist;
414
415    /**
416    ** SQL statements generated by this parser from the input SQL script.
417     * statements are always available even if there are syntax errors in input SQL script.
418     * if there is no syntax error in a statement, you can access the parse tree of the statement to fetch more information
419     * such as tables, columns, etc.
420    */
421    public TStatementList sqlstatements;
422
423    /**
424     * The TSQLResolver2 instance used for name resolution when ENABLE_RESOLVER2 is set.
425     * Unlike TSQLResolver which is created as a local variable, TSQLResolver2 is stored
426     * as a property so users can retrieve name resolution results after parsing.
427     *
428     * Usage:
429     * <pre>
430     * TBaseType.setEnableResolver(false);  // Disable old resolver
431     * TBaseType.setEnableResolver2(true);  // Enable new resolver
432     *
433     * TGSqlParser parser = new TGSqlParser(EDbVendor.dbvoracle);
434     * parser.sqltext = sqlContent;
435     * int ret = parser.parse();
436     *
437     * // Access resolver2 results
438     * TSQLResolver2 resolver2 = parser.getResolver2();
439     * if (resolver2 != null) {
440     *     // Use TSQLResolver2ResultFormatter to generate output
441     *     TSQLResolver2ResultFormatter formatter = new TSQLResolver2ResultFormatter(resolver2, config);
442     *     String result = formatter.format();
443     * }
444     * </pre>
445     */
446    private TSQLResolver2 resolver2;
447
448    /**
449     * Get the TSQLResolver2 instance used for name resolution.
450     * Returns null if resolver2 was not used or if parsing failed.
451     *
452     * @return the TSQLResolver2 instance or null
453     */
454    public TSQLResolver2 getResolver2() {
455        return resolver2;
456    }
457
458    /**
459     * The resolver type to use for name resolution.
460     * Default is EResolverType.DEFAULT which uses TBaseType settings.
461     */
462    private EResolverType resolverType = EResolverType.DEFAULT;
463
464    /**
465     * Get the resolver type used for name resolution.
466     *
467     * @return the resolver type
468     */
469    public EResolverType getResolverType() {
470        return resolverType;
471    }
472
473    /**
474     * Set the resolver type to use for name resolution.
475     *
476     * <p>This instance-level setting takes precedence over global TBaseType settings.
477     * When set to DEFAULT (the default value), behavior is determined by
478     * TBaseType.isEnableResolver() and TBaseType.isEnableResolver2().</p>
479     *
480     * <h3>Usage Example:</h3>
481     * <pre>
482     * TGSqlParser parser = new TGSqlParser(EDbVendor.dbvoracle);
483     * parser.setResolverType(EResolverType.RESOLVER2);  // Use new resolver
484     * parser.sqltext = "SELECT * FROM employees";
485     * parser.parse();
486     *
487     * // Access resolver2 results
488     * TSQLResolver2 resolver = parser.getResolver2();
489     * </pre>
490     *
491     * @param resolverType the resolver type to use
492     * @see EResolverType
493     */
494    public void setResolverType(EResolverType resolverType) {
495        this.resolverType = resolverType != null ? resolverType : EResolverType.DEFAULT;
496    }
497
498    /**
499     * Optional configuration for TSQLResolver2.
500     * If null, a default configuration will be created during parsing.
501     */
502    private TSQLResolverConfig resolver2Config;
503
504    /**
505     * Get the TSQLResolverConfig used for resolver2.
506     * Returns null if not explicitly set (default config will be used during parsing).
507     *
508     * @return the resolver2 config or null
509     */
510    public TSQLResolverConfig getResolver2Config() {
511        return resolver2Config;
512    }
513
514    /**
515     * Set the TSQLResolverConfig to use for resolver2.
516     *
517     * <p>This allows customizing resolver2 behavior such as:</p>
518     * <ul>
519     *   <li>guessColumnStrategy - how to handle ambiguous columns</li>
520     *   <li>legacyCompatibilityEnabled - sync results to legacy structures</li>
521     *   <li>maxIterations - maximum iterations for iterative resolution</li>
522     * </ul>
523     *
524     * <p>If not set, a default configuration will be created during parsing
525     * with the database vendor automatically set.</p>
526     *
527     * <h3>Usage Example:</h3>
528     * <pre>
529     * TGSqlParser parser = new TGSqlParser(EDbVendor.dbvoracle);
530     * parser.setResolverType(EResolverType.RESOLVER2);
531     *
532     * // Configure resolver2 to not pick ambiguous columns
533     * TSQLResolverConfig config = new TSQLResolverConfig();
534     * config.setGuessColumnStrategy(TSQLResolverConfig.GUESS_COLUMN_STRATEGY_NOT_PICKUP);
535     * parser.setResolver2Config(config);
536     *
537     * parser.sqltext = "SELECT id FROM users, orders";
538     * parser.parse();
539     * </pre>
540     *
541     * @param config the resolver2 configuration, or null for default
542     * @see TSQLResolverConfig
543     */
544    public void setResolver2Config(TSQLResolverConfig config) {
545        this.resolver2Config = config;
546    }
547
548    // ===== Binding Diagnostic API (plan §5.3, S2) =====
549
550    /**
551     * Pass-through accessor for the resolver2 binding result. Slice S2 ships
552     * an empty stub; S5 wires the populated post-pass result. Always non-null
553     * — including when {@link #parse()} returns non-zero (syntax error,
554     * plan §5.6.9) or when resolver2 was not invoked.
555     *
556     * @return the binding result; never null
557     */
558    public BindingResult getBindingResult() {
559        TSQLResolver2 r = this.resolver2;
560        if (r == null) {
561            return BindingResult.empty();
562        }
563        return r.getBindingResult();
564    }
565
566    /**
567     * Pass-through accessor for {@link
568     * gudusoft.gsqlparser.resolver2.binding.BindingResult#getDiagnostics()}.
569     *
570     * @return the diagnostics list; never null
571     */
572    public List<BindingDiagnostic> getBindingDiagnostics() {
573        TSQLResolver2 r = this.resolver2;
574        if (r == null) {
575            return Collections.<BindingDiagnostic>emptyList();
576        }
577        return r.getBindingDiagnostics();
578    }
579
580    /**
581     * Pass-through accessor for {@link
582     * gudusoft.gsqlparser.resolver2.binding.BindingResult#hasErrors()}.
583     *
584     * @return whether any ERROR-severity binding diagnostic was emitted
585     */
586    public boolean hasBindingErrors() {
587        TSQLResolver2 r = this.resolver2;
588        if (r == null) {
589            return false;
590        }
591        return r.hasBindingErrors();
592    }
593
594    /**
595     * The array of syntax error generated by the parser during checking the syntax of the input SQL,
596     * element of this list is type of {@link TSyntaxError}
597     *
598     * @return the array of errors
599     */
600    public ArrayList <TSyntaxError> getSyntaxErrors() {
601        return syntaxErrors;
602    }
603
604    private ArrayList <TSyntaxError> syntaxErrors;
605
606//    public ArrayList<TSyntaxError> getSyntaxHints() {
607//        return syntaxHints;
608//    }
609
610    //private ArrayList <TSyntaxError> syntaxHints;
611
612    /**
613     * The database vendor specified when creating this parser.
614     * The grammar rule of this database vendor will be used to validate the syntax of the input SQL.
615     *
616     * @return the database vendor
617     */
618    public EDbVendor getDbVendor() {
619        return dbVendor;
620    }
621
622    /**
623     * Get the OceanBase tenant compatibility mode for this parser.
624     *
625     * <p>Only meaningful when the parser is configured for
626     * {@link EDbVendor#dbvoceanbase}. For other vendors the value is ignored
627     * and defaults to {@link EOBTenantMode#MYSQL}.
628     *
629     * @return the current OceanBase tenant mode (never null)
630     * @since 4.0.1.4
631     */
632    public EOBTenantMode getOBTenantMode() {
633        return oceanBaseTenantMode;
634    }
635
636    /**
637     * Set the OceanBase tenant compatibility mode for this parser.
638     *
639     * <p>This selects which delegate parser ({@code MySqlSqlParser},
640     * {@code OracleSqlParser}, or in later phases the forked OceanBase
641     * grammar instances) is used when {@code dbvoceanbase} is the active
642     * vendor. The mode is permanent for the life of an OceanBase tenant on
643     * the server side; one mode per parser instance is the correct
644     * granularity. Do not switch modes mid-script.
645     *
646     * <p>Setting this invalidates any cached vendor parser instance so the
647     * next {@code parse()} call constructs the correct delegate. The mode is
648     * preserved across {@link #prepareForReuse()}.
649     *
650     * <p>{@code null} is coerced to {@link EOBTenantMode#MYSQL}.
651     *
652     * @param mode the desired tenant mode
653     * @since 4.0.1.4
654     */
655    public void setOBTenantMode(EOBTenantMode mode) {
656        if (mode == null) {
657            mode = EOBTenantMode.MYSQL;
658        }
659        if (this.oceanBaseTenantMode != mode) {
660            this.oceanBaseTenantMode = mode;
661            // Force the next parse() call to recreate the vendor parser so the
662            // OceanBaseSqlParser adapter picks up the new mode on construction.
663            this.vendorParser = null;
664
665            // Mode-dependent delimiter and command-resolver selection for
666            // OceanBase only. ORACLE mode requires '/' for PL/SQL block
667            // termination and Oracle-style splitter rules so that anonymous
668            // BEGIN/END blocks are recognized as a single statement instead
669            // of being split on the embedded semicolons. MYSQL/SYSTEM modes
670            // use '$' and the MySQL splitter to mirror MySQL DELIMITER
671            // override semantics. This is a no-op for non-OceanBase parsers
672            // because they never call this setter.
673            if (this.dbVendor == EDbVendor.dbvoceanbase) {
674                if (mode == EOBTenantMode.ORACLE) {
675                    this.delimiterchar = '/';
676                    this.defaultDelimiterStr = ";";
677                    // Re-bind the TGSqlParser-level splitter to Oracle rules
678                    // so PL/SQL blocks survive raw extraction. The splitter's
679                    // internal vendor field reports dbvoracle in this case;
680                    // that does NOT affect AST node identity, which comes
681                    // from TGSqlParser.dbVendor (still dbvoceanbase) via
682                    // the NodeFactory back-reference fixup in
683                    // doDelegatedRawParse().
684                    this.sqlcmds = SqlCmdsFactory.get(EDbVendor.dbvoracle);
685                } else {
686                    this.delimiterchar = '$';
687                    this.defaultDelimiterStr = "$";
688                    // Restore the MySQL-family splitter (the default chosen
689                    // at construction time when sqlcmds was first assigned).
690                    this.sqlcmds = SqlCmdsFactory.get(EDbVendor.dbvoceanbase);
691                }
692            }
693        }
694    }
695
696    /**
697     * @deprecated As of v1.4.3.4
698     * enable GSP to parse the rest of sql statements inside stored procedure
699     * when a SQL statement in the stored procedure cannot be parsed
700     *
701     * <p>Available to parse sybase stored procedure currently.
702     *
703     * @param enablePartialParsing set true to enable this partial parsing, default is false
704     */
705    public void setEnablePartialParsing(boolean enablePartialParsing) {
706        this.enablePartialParsing = enablePartialParsing;
707    }
708
709    /**
710     * enable GSP to parse the rest of sql statements inside stored procedure
711     * when a SQL statement in the stored procedure cannot be parsed
712     *
713     * <p>Available to parse sybase stored procedure currently.
714     *
715     * <p> default is false;
716     *
717     *  @deprecated As of v1.4.3.4
718     */
719    private boolean isEnablePartialParsing() {
720
721        return enablePartialParsing;
722    }
723
724    private boolean enablePartialParsing = false;
725
726    private boolean isSinglePLBlock = false;
727
728    public void setSinglePLBlock(boolean singlePLBlock) {
729        isSinglePLBlock = singlePLBlock;
730    }
731
732    private static String userName;
733    private static String machineId = null;
734    private static String licenseKey;
735    private static String licenseType;
736    private static boolean licenseOK = false;
737    private static String licenseMessage;
738
739    /**
740     * Not used.
741     *
742     * @return the user name
743     */
744    public static String getUserName() {
745        return userName;
746    }
747
748    /**
749     * Not used.
750     *
751     * @return the machine id
752     */
753    public static String getMachineId() {
754
755        return machineId;
756    }
757
758    /**
759     * Not used.
760     *
761     * @return the license message
762     */
763    public static String getLicenseMessage() {
764        return licenseMessage;
765    }
766
767
768    static {
769        licenseOK = validateLicense();
770    }
771
772    /**
773     * Not used.
774     *
775     * @return trial license or developer license or distribution license
776     */
777    public static String getLicenseType() {
778        return licenseType;
779    }
780
781    private EDbVendor dbVendor;
782    private EOBTenantMode oceanBaseTenantMode = EOBTenantMode.MYSQL;
783    private String errormessage;
784
785    /**
786     * The lexer which is used to tokenize the input SQL.
787     * For delegated vendors (MSSQL), lazily creates the vendor parser to get its lexer.
788     *
789     * @return the lexer
790     */
791    public TCustomLexer getFlexer() {
792        if (flexer == null) {
793            // Lazily create vendor parser to get its lexer
794            SqlParser vp = getOrCreateVendorParser();
795            if (vp instanceof gudusoft.gsqlparser.parser.MssqlSqlParser) {
796                // Cache the flexer from vendor parser
797                flexer = ((gudusoft.gsqlparser.parser.MssqlSqlParser) vp).flexer;
798            } else if (vp instanceof gudusoft.gsqlparser.parser.MySqlSqlParser) {
799                // Cache the flexer from vendor parser
800                flexer = ((gudusoft.gsqlparser.parser.MySqlSqlParser) vp).flexer;
801            } else if (vp instanceof gudusoft.gsqlparser.parser.PostgreSqlParser) {
802                // Cache the flexer from vendor parser
803                flexer = ((gudusoft.gsqlparser.parser.PostgreSqlParser) vp).flexer;
804            } else if (vp instanceof gudusoft.gsqlparser.parser.DuckdbSqlParser) {
805                flexer = ((gudusoft.gsqlparser.parser.DuckdbSqlParser) vp).flexer;
806            } else if (vp instanceof gudusoft.gsqlparser.parser.OracleSqlParser) {
807                // Cache the flexer from vendor parser
808                flexer = ((gudusoft.gsqlparser.parser.OracleSqlParser) vp).flexer;
809            } else if (vp instanceof gudusoft.gsqlparser.parser.BigQuerySqlParser) {
810                // Cache the flexer from vendor parser
811                flexer = ((gudusoft.gsqlparser.parser.BigQuerySqlParser) vp).flexer;
812            } else if (vp instanceof gudusoft.gsqlparser.parser.AthenaSqlParser) {
813                // Cache the flexer from vendor parser
814                flexer = ((gudusoft.gsqlparser.parser.AthenaSqlParser) vp).flexer;
815            } else if (vp instanceof gudusoft.gsqlparser.parser.CouchbaseSqlParser) {
816                // Cache the flexer from vendor parser
817                flexer = ((gudusoft.gsqlparser.parser.CouchbaseSqlParser) vp).flexer;
818            } else if (vp instanceof gudusoft.gsqlparser.parser.DatabricksSqlParser) {
819                // Cache the flexer from vendor parser
820                flexer = ((gudusoft.gsqlparser.parser.DatabricksSqlParser) vp).flexer;
821            } else if (vp instanceof gudusoft.gsqlparser.parser.DaxSqlParser) {
822                // Cache the flexer from vendor parser
823                flexer = ((gudusoft.gsqlparser.parser.DaxSqlParser) vp).flexer;
824            } else if (vp instanceof gudusoft.gsqlparser.parser.PowerQuerySqlParser) {
825                // Cache the flexer from vendor parser
826                flexer = ((gudusoft.gsqlparser.parser.PowerQuerySqlParser) vp).flexer;
827            } else if (vp instanceof gudusoft.gsqlparser.parser.Db2SqlParser) {
828                // Cache the flexer from vendor parser
829                flexer = ((gudusoft.gsqlparser.parser.Db2SqlParser) vp).flexer;
830            } else if (vp instanceof gudusoft.gsqlparser.parser.GaussDbSqlParser) {
831                // Cache the flexer from vendor parser
832                flexer = ((gudusoft.gsqlparser.parser.GaussDbSqlParser) vp).flexer;
833            } else if (vp instanceof gudusoft.gsqlparser.parser.GreenplumSqlParser) {
834                // Cache the flexer from vendor parser
835                flexer = ((gudusoft.gsqlparser.parser.GreenplumSqlParser) vp).flexer;
836            } else if (vp instanceof gudusoft.gsqlparser.parser.HiveSqlParser) {
837                // Cache the flexer from vendor parser
838                flexer = ((gudusoft.gsqlparser.parser.HiveSqlParser) vp).flexer;
839            } else if (vp instanceof gudusoft.gsqlparser.parser.HanaSqlParser) {
840                // Cache the flexer from vendor parser
841                flexer = ((gudusoft.gsqlparser.parser.HanaSqlParser) vp).flexer;
842            } else if (vp instanceof gudusoft.gsqlparser.parser.ImpalaSqlParser) {
843                // Cache the flexer from vendor parser
844                flexer = ((gudusoft.gsqlparser.parser.ImpalaSqlParser) vp).flexer;
845            } else if (vp instanceof gudusoft.gsqlparser.parser.InformixSqlParser) {
846                // Cache the flexer from vendor parser
847                flexer = ((gudusoft.gsqlparser.parser.InformixSqlParser) vp).flexer;
848            } else if (vp instanceof gudusoft.gsqlparser.parser.MdxSqlParser) {
849                // Cache the flexer from vendor parser
850                flexer = ((gudusoft.gsqlparser.parser.MdxSqlParser) vp).flexer;
851            } else if (vp instanceof gudusoft.gsqlparser.parser.NetezzaSqlParser) {
852                // Cache the flexer from vendor parser
853                flexer = ((gudusoft.gsqlparser.parser.NetezzaSqlParser) vp).flexer;
854            } else if (vp instanceof gudusoft.gsqlparser.parser.OdbcSqlParser) {
855                // Cache the flexer from vendor parser
856                flexer = ((gudusoft.gsqlparser.parser.OdbcSqlParser) vp).flexer;
857            } else if (vp instanceof gudusoft.gsqlparser.parser.OpenEdgeSqlParser) {
858                // Cache the flexer from vendor parser
859                flexer = ((gudusoft.gsqlparser.parser.OpenEdgeSqlParser) vp).flexer;
860            } else if (vp instanceof gudusoft.gsqlparser.parser.PrestoSqlParser) {
861                // Cache the flexer from vendor parser
862                flexer = ((gudusoft.gsqlparser.parser.PrestoSqlParser) vp).flexer;
863            } else if (vp instanceof gudusoft.gsqlparser.parser.RedshiftSqlParser) {
864                // Cache the flexer from vendor parser
865                flexer = ((gudusoft.gsqlparser.parser.RedshiftSqlParser) vp).flexer;
866            } else if (vp instanceof gudusoft.gsqlparser.parser.SnowflakeSqlParser) {
867                // Cache the flexer from vendor parser
868                flexer = ((gudusoft.gsqlparser.parser.SnowflakeSqlParser) vp).flexer;
869            } else if (vp instanceof gudusoft.gsqlparser.parser.SqliteSqlParser) {
870                // Cache the flexer from vendor parser
871                flexer = ((gudusoft.gsqlparser.parser.SqliteSqlParser) vp).flexer;
872            } else if (vp instanceof gudusoft.gsqlparser.parser.SoqlSqlParser) {
873                // Cache the flexer from vendor parser
874                flexer = ((gudusoft.gsqlparser.parser.SoqlSqlParser) vp).flexer;
875            } else if (vp instanceof gudusoft.gsqlparser.parser.SparksqlSqlParser) {
876                // Cache the flexer from vendor parser
877                flexer = ((gudusoft.gsqlparser.parser.SparksqlSqlParser) vp).flexer;
878            } else if (vp instanceof gudusoft.gsqlparser.parser.SybaseSqlParser) {
879                // Cache the flexer from vendor parser
880                flexer = ((gudusoft.gsqlparser.parser.SybaseSqlParser) vp).flexer;
881            } else if (vp instanceof gudusoft.gsqlparser.parser.VerticaSqlParser) {
882                // Cache the flexer from vendor parser
883                flexer = ((gudusoft.gsqlparser.parser.VerticaSqlParser) vp).flexer;
884            }
885        }
886        return flexer;
887    }
888
889    private TCustomLexer flexer;
890
891    TCustomParser fparser,fplsqlparser;
892
893    // Cached vendor-specific parser for delegated parsing (MSSQL, etc.)
894    // Created lazily in getFlexer() and reused in parse()
895    private SqlParser vendorParser;
896
897    BufferedReader finputstream = null; //used by lexer
898    TCustomSqlStatement gcurrentsqlstatement,nextStmt;
899    // Vendor-specific SQL command resolver
900    ISqlCmds sqlcmds;
901
902    HashMap sqlpluskeywordList;
903
904    char delimiterchar;
905    String defaultDelimiterStr;
906
907    /**
908     * Returns the delimiter character used to separate SQL statements.
909     * Uses the flexer's delimiter if available (lexer-dependent), otherwise falls back to parser's value.
910     * @return the delimiter character
911     */
912    public char getDelimiterChar() {
913        if (flexer != null) {
914            return flexer.delimiterchar;
915        }
916        return delimiterchar;
917    }
918
919    /**
920     * Returns the delimiter character for the given database vendor without creating a parser instance.
921     * This is a performance optimization for code that only needs the delimiter character.
922     * 
923     * @param vendor the database vendor
924     * @return the delimiter character for the vendor
925     */
926    public static char getDelimiterChar(EDbVendor vendor) {
927        switch(vendor){
928            case dbvoracle:
929            case dbvteradata:
930            case dbvpostgresql:
931            case dbvduckdb:
932            case dbvredshift:
933            case dbvgreenplum:
934                return '/';
935            case dbvdameng:
936                if (TBaseType.enterprise_edition || (!TBaseType.full_edition) || TBaseType.dameng_edition) {
937                    return '/';
938                }
939                return ';';
940            case dbvdb2:
941                return '@';
942            case dbvmysql:
943            case dbvoceanbase:
944                return '$';
945            case dbvdoris:
946                if (TBaseType.enterprise_edition || (!TBaseType.full_edition) || TBaseType.doris_edition) {
947                    return ';';
948                }
949                return ';';
950            case dbvstarrocks:
951                if (TBaseType.enterprise_edition || (!TBaseType.full_edition) || TBaseType.starrocks_edition) {
952                    return ';';
953                }
954                return ';';
955            default:
956                return ';';
957        }
958    }
959
960    private ISQLStatementHandle sqlStatementHandle = null;
961
962    public void setSqlStatementHandle(ISQLStatementHandle sqlStatementHandle) {
963        this.sqlStatementHandle = sqlStatementHandle;
964    }
965
966    private ITokenHandle tokenHandle = null;
967
968    /**
969     * Set an event handler which will be fired when a new source token is created by the lexer during tokenize the
970     * input SQL.
971     *
972     * @param tokenHandle the event handler to process the new created source token
973     */
974    public void setTokenHandle(ITokenHandle tokenHandle) {
975        this.tokenHandle = tokenHandle;
976    }
977
978    private ITokenListHandle tokenListHandle = null;
979
980    public void setTokenListHandle(ITokenListHandle tokenListHandle) {
981        this.tokenListHandle = tokenListHandle;
982    }
983
984    private IMetaDatabase metaDatabase = null;
985
986    /**
987     * @deprecated As of v2.0.3.1, please use {@link #getSqlEnv()} instead
988     *
989     *  set an instance of a class which implement the interface: {@link IMetaDatabase}.
990     *  The parser will call {@link IMetaDatabase#checkColumn} method when it needs to know
991     *  whether a column is belonged to a table.
992     *  <p></p>
993     *  The class that implements the interface: {@link IMetaDatabase} usually fetch the metadata from the database
994     *  by connecting to a database instance.
995     *  <p></p>
996     *  If the class is not provided, the parser has to guess the relationship between a un-qualified column and table
997     *  in the input SQL which may lead to a un-determined result between the column and table.
998     *
999     * @param metaDatabase a new instance of the class which implements the {@link IMetaDatabase} interface
1000     * @see IMetaDatabase
1001     */
1002    public void setMetaDatabase(IMetaDatabase metaDatabase) {
1003        this.metaDatabase = metaDatabase;
1004    }
1005
1006    /**
1007     * @deprecated As of v2.0.3.1, please use {@link #getSqlEnv()} instead
1008     *
1009     * a new instance of the class which implements the {@link IMetaDatabase} interface
1010     *
1011     * @return a new instance of the class which implements the {@link IMetaDatabase} interface
1012     * @see #setMetaDatabase
1013     */
1014    public IMetaDatabase getMetaDatabase() {
1015
1016        return metaDatabase;
1017    }
1018
1019    /**
1020     * Not used.
1021     *
1022     */
1023    public void freeParseTable() {
1024        flexer.yystack = null;
1025        flexer.yytextbuf = null;
1026        flexer.buf = null;
1027
1028//        TLexerOracle.yyk = null;
1029//        TLexerOracle.yykl = null;
1030//        TLexerOracle.yykh = null;
1031//        TLexerOracle.yym = null;
1032//        TLexerOracle.yyml = null;
1033//        TLexerOracle.yymh = null;
1034//        TLexerOracle.yyt = null;
1035//        TLexerOracle.yytl = null;
1036//        TLexerOracle.yyth = null;
1037//        TParserOracleSql.yyah = null;
1038//        TParserOracleSql.yyal = null;
1039//        TParserOracleSql.yygh = null;
1040//        TParserOracleSql.yygl = null;
1041//        TParserOracleSql.yyd = null;
1042//        TParserOracleSql.yya_sym= null;
1043//        TParserOracleSql.yya_act= null;
1044//        TParserOracleSql.yyr_len= null;
1045//        TParserOracleSql.yyr_sym= null;
1046//        TParserOracleSql.yyg_sym= null;
1047//        TParserOracleSql.yyg_act= null;
1048//
1049//        TParserOraclePLSql.yyah = null;
1050//        TParserOraclePLSql.yyal = null;
1051//        TParserOraclePLSql.yygh = null;
1052//        TParserOraclePLSql.yygl = null;
1053//        TParserOraclePLSql.yyd = null;
1054//        TParserOraclePLSql.yya_sym= null;
1055//        TParserOraclePLSql.yya_act= null;
1056//        TParserOraclePLSql.yyr_len= null;
1057//        TParserOraclePLSql.yyr_sym= null;
1058//        TParserOraclePLSql.yyg_sym= null;
1059//        TParserOraclePLSql.yyg_act= null;
1060//
1061//        TLexerMssql.yyk = null;
1062//        TLexerMssql.yykl = null;
1063//        TLexerMssql.yykh = null;
1064//        TLexerMssql.yym = null;
1065//        TLexerMssql.yyml = null;
1066//        TLexerMssql.yymh = null;
1067//        TLexerMssql.yyt = null;
1068//        TLexerMssql.yytl = null;
1069//        TLexerMssql.yyth = null;
1070//        TParserMssqlSql.yyah = null;
1071//        TParserMssqlSql.yyal = null;
1072//        TParserMssqlSql.yygh = null;
1073//        TParserMssqlSql.yygl = null;
1074//        TParserMssqlSql.yyd = null;
1075//        TParserMssqlSql.yya_sym= null;
1076//        TParserMssqlSql.yya_act= null;
1077//        TParserMssqlSql.yyr_len= null;
1078//        TParserMssqlSql.yyr_sym= null;
1079//        TParserMssqlSql.yyg_sym= null;
1080//        TParserMssqlSql.yyg_act= null;
1081//
1082//        TLexerMysql.yyk = null;
1083//        TLexerMysql.yykl = null;
1084//        TLexerMysql.yykh = null;
1085//        TLexerMysql.yym = null;
1086//        TLexerMysql.yyml = null;
1087//        TLexerMysql.yymh = null;
1088//        TLexerMysql.yyt = null;
1089//        TLexerMysql.yytl = null;
1090//        TLexerMysql.yyth = null;
1091//        TParserMysqlSql.yyah = null;
1092//        TParserMysqlSql.yyal = null;
1093//        TParserMysqlSql.yygh = null;
1094//        TParserMysqlSql.yygl = null;
1095//        TParserMysqlSql.yyd = null;
1096//        TParserMysqlSql.yya_sym= null;
1097//        TParserMysqlSql.yya_act= null;
1098//        TParserMysqlSql.yyr_len= null;
1099//        TParserMysqlSql.yyr_sym= null;
1100//        TParserMysqlSql.yyg_sym= null;
1101//        TParserMysqlSql.yyg_act= null;
1102    }
1103
1104    /**
1105     *  Class constructor, create a new instance of the parser by setting the database vendor
1106     *
1107     * @param pdbvendor the database vendor whose grammar rule will be used to validate the syntax of the input SQL
1108     */
1109    public TGSqlParser(EDbVendor pdbvendor) {
1110        dbVendor = pdbvendor;
1111        sqltext = "";
1112        sqlfilename = "";
1113
1114        // Use static method to get delimiter char for consistency
1115        delimiterchar = getDelimiterChar(pdbvendor);
1116        
1117        // Set default delimiter string based on delimiter char
1118        if (delimiterchar == '$') {
1119            defaultDelimiterStr = "$";
1120        } else {
1121            defaultDelimiterStr = ";";
1122        }
1123
1124        // Special handling for Azure SQL - maps to MSSQL
1125        if (pdbvendor == EDbVendor.dbvazuresql) {
1126            dbVendor = EDbVendor.dbvmssql;
1127        }
1128
1129        // fparser is null here - it will be set later in doDelegatedRawParse() from vendor parser result
1130        // All vendors are now delegated to vendor-specific parsers via getOrCreateVendorParser()
1131
1132        sourcetokenlist = new TSourceTokenList();
1133        sourcetokenlist.setGsqlparser(this);
1134        sqlstatements = new TStatementList();
1135        // Inject vendor-specific command resolver
1136        sqlcmds = SqlCmdsFactory.get(dbVendor);
1137        sqlpluskeywordList = new HashMap();
1138        syntaxErrors = new ArrayList();
1139
1140        errormessage = "";
1141
1142        if (TBaseType.license_expired_check){
1143            if (!check_license_time()) {
1144                flexer = null;
1145                fparser = null;
1146            }
1147        }
1148
1149        TGSqlParser.setCurrentDBVendor(dbVendor);
1150    }
1151
1152    TContext globalContext;
1153
1154
1155    /**
1156     * Create a select statement object from the parameter: subquery
1157     *
1158     * @param subquery a plain text select statement which need to be converted to a {@link gudusoft.gsqlparser.stmt.TSelectSqlStatement} object
1159     * @return a select statement object, return null if the input select string is syntax invalid.
1160     */
1161    public TSelectSqlStatement parseSubquery(String subquery){
1162        return parseSubquery(this.dbVendor,subquery);
1163    }
1164
1165    /**
1166     * this method is thread safe.
1167     * 
1168     * C:\prg\gsp_java\gsp_java_core\src\test\java\gudusoft\gsqlparser\ParseSubqueryThreadSafetyTest.java
1169     * 
1170     */
1171    public static TSelectSqlStatement parseSubquery(EDbVendor dbVendor, String subquery){
1172//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1173//        localParser.sqltext = subquery;
1174//        int iRet = localParser.doparse();
1175//        if (iRet != 0) {
1176//            return null;
1177//        }
1178
1179        TSingletonParser singletonParser = TSingletonParser.getInstance();
1180        TStatementList statements = singletonParser.getStmts(dbVendor,subquery);
1181        if (statements.size() == 0) return null;
1182
1183        return (TSelectSqlStatement)statements.get(0);
1184    }
1185
1186    /**
1187     * Create an expression object from the parameter: expr
1188     *
1189     * @param expr a plain text expression which will be converted to {@link gudusoft.gsqlparser.nodes.TExpression} object
1190     * @return an expression object, return null if the input expression string is not syntax valid.
1191     */
1192    public TExpression parseExpression(String expr){
1193        return parseExpression(this.dbVendor,expr);
1194    }
1195
1196    /*
1197     * this method is thread safe.
1198     * 
1199     * getStmts() is synchronized - only one thread can execute it at a time
1200     * getParser() is also synchronized
1201     * Each thread gets a new TStatementList instance
1202
1203     * this is the test case for this method.
1204     * C:\prg\gsp_java\gsp_java_core\src\test\java\gudusoft\gsqlparser\ParseExpressionThreadSafetyTest.java
1205     */
1206    public static TExpression parseExpression(EDbVendor dbVendor, String expr){
1207        TSingletonParser singletonParser = TSingletonParser.getInstance();
1208
1209        boolean e = TBaseType.isEnableResolver();
1210        TBaseType.setEnableResolver(false);
1211        TStatementList statements;
1212        try{
1213            statements  = singletonParser.getStmts(dbVendor,"select 1 from t where "+TBaseType.newline+expr);
1214        }finally {
1215            TBaseType.setEnableResolver(e);
1216        }
1217
1218        if (statements.size() == 0) return null;
1219
1220//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1221//        localParser.sqltext = "select 1 from t where "+TBaseType.newline+expr;
1222//        int iRet = localParser.doparse();
1223//        if (iRet != 0) {
1224//            return null;
1225//        }
1226
1227        return ((TSelectSqlStatement)statements.get(0)).getWhereClause().getCondition();
1228    }
1229
1230    /**
1231     *  Create a function object from the parameter: newFunction
1232     *
1233     * @param newFunction a plain text function which will be converted to {@link gudusoft.gsqlparser.nodes.TFunctionCall} object
1234     * @return a function object, or return null if the input string is not a valid function call.
1235     */
1236    public TFunctionCall parseFunctionCall(String newFunction){
1237        return parseFunctionCall(this.dbVendor,newFunction);
1238    }
1239
1240    public static TFunctionCall parseFunctionCall(EDbVendor dbVendor, String newFunction){
1241        TSingletonParser singletonParser = TSingletonParser.getInstance();
1242        TStatementList statements = singletonParser.getStmts(dbVendor,"select"+TBaseType.newline+newFunction+TBaseType.newline+"from t");
1243        if (statements.size() == 0) return null;
1244
1245
1246//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1247//        localParser.sqltext = "select"+TBaseType.newline+newFunction+TBaseType.newline+"from t";
1248//        int iRet = localParser.doparse();
1249//        if (iRet!= 0) {
1250//            return null;
1251//        }
1252
1253        return statements.get(0).getResultColumnList().getResultColumn(0).getExpr().getFunctionCall();
1254    }
1255
1256    /**
1257     * Create a database objectName from the parameter: newObjectName
1258     *
1259     * @param newObjectName a plain text objectName which will be converted to {@link gudusoft.gsqlparser.nodes.TObjectName} object
1260     * @return a database objectName object, return null is the input string is not a valid objectName.
1261     */
1262    public TObjectName parseObjectName(String newObjectName){
1263        //return parseObjectName(this.dbVendor,newObjectName);
1264        return TObjectName.createObjectName(this.dbVendor,EDbObjectType.column,newObjectName);
1265    }
1266
1267
1268
1269    /**
1270     *
1271     * @param dbVendor
1272     * @param newObjectName
1273     * @return
1274     */
1275    public static TObjectName parseObjectName(EDbVendor dbVendor, String newObjectName){
1276
1277        TSingletonParser singletonParser = TSingletonParser.getInstance();
1278        TStatementList statements = singletonParser.getStmts(dbVendor,"select"+TBaseType.newline+newObjectName+TBaseType.newline+"from t");
1279        if (statements.size() == 0) return null;
1280        TExpression e = statements.get(0).getResultColumnList().getResultColumn(0).getExpr();
1281
1282//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1283//        localParser.sqltext = "select"+TBaseType.newline+newObjectName+TBaseType.newline+"from t";
1284//        int iRet = localParser.doparse();
1285//        if (iRet!= 0) {
1286//            return null;
1287//        }
1288//        TExpression e = ((TSelectSqlStatement)localParser.sqlstatements.get(0)).getResultColumnList().getResultColumn(0).getExpr();
1289
1290        TObjectName lcResult = null;
1291        switch (e.getExpressionType()){
1292            case simple_object_name_t:
1293                lcResult = e.getObjectOperand();
1294                break;
1295            case simple_constant_t:
1296                lcResult = new TObjectName();
1297                lcResult.init(e.getConstantOperand().getValueToken());
1298                break;
1299            default:
1300                break;
1301        }
1302
1303        return lcResult;
1304    }
1305
1306    /**
1307     * Create an constant object from the parameter: newConstant
1308     *
1309     * @param newConstant a plian text constant which will be converted to {@link gudusoft.gsqlparser.nodes.TConstant} object
1310     * @return new constant object, returns null if the input is not a valid constant string.
1311     */
1312    public TConstant parseConstant(String newConstant){
1313        return parseConstant(this.dbVendor,newConstant);
1314    }
1315
1316    public static TConstant parseConstant(EDbVendor dbVendor, String newConstant){
1317//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1318//        localParser.sqltext = "select"+TBaseType.newline+newConstant+TBaseType.newline+"from t";
1319//        int iRet = localParser.doparse();
1320//        if (iRet!= 0) {
1321//            return null;
1322//        }
1323
1324        TSingletonParser singletonParser = TSingletonParser.getInstance();
1325        TStatementList statements = singletonParser.getStmts(dbVendor,"select"+TBaseType.newline+newConstant+TBaseType.newline+"from t");
1326        if (statements.size() == 0) return null;
1327
1328        return statements.get(0).getResultColumnList().getResultColumn(0).getExpr().getConstantOperand();
1329    }
1330
1331
1332    /**
1333     * The total number of syntax errors founded in the input SQL script.
1334     * <br>Only the first syntax error in a SQL statement is counted if there are more than one syntax errors in
1335     * a single SQL statement.
1336     * <br> In a SQL script, only the first syntax error in each SQL statement is counted.
1337     *
1338     * @return The total number of syntax errors founded in the input SQL script. Returns 0 if no syntax error is founded.
1339     */
1340    public int getErrorCount(){
1341        return   syntaxErrors.size();
1342    }
1343
1344    /**
1345     * The text of error message generated by iterating all items in {@link #getSyntaxErrors}.
1346     * User may generate error message in their own format by iterating all items in {@link #getSyntaxErrors}.
1347     *
1348     * @return error message
1349     */
1350    public String getErrormessage(){
1351
1352        String s="",hint="Syntax error";
1353        TSyntaxError t;
1354        for (int i= 0; i< syntaxErrors.size(); i++)
1355        {
1356            t = (TSyntaxError) syntaxErrors.get(i);
1357            if (t.hint.length() > 0) hint = t.hint;
1358            s= s+hint+"("+t.errorno+") near: "+t.tokentext;
1359            s=s+"("+t.lineNo;
1360            s=s+","+t.columnNo +", token code:"+t.tokencode+")";
1361                //s=s+" expected tokentext:"+t.hint;
1362
1363           // break;//get only one message, remove this one and uncomment next line to get all error messages
1364            if (i !=  syntaxErrors.size() - 1)
1365              s = s +TBaseType.linebreak;
1366        }
1367
1368        if (errormessage.length() > 0){
1369            s = errormessage+TBaseType.linebreak+s; 
1370        }
1371        return s;
1372
1373    }
1374
1375    /**
1376     * check syntax of the input SQL. This method works exactly the same as {@link #parse} method.
1377     *
1378     * @return   0 means parse SQL script successfully, otherwise, use {@link #getErrorCount}, {@link #getErrormessage}
1379     * to get detailed error information.
1380     * @see #parse
1381     */
1382    public int checkSyntax(){
1383        return doparse();
1384    }
1385
1386    /**
1387     *   Check syntax of the input SQL, doing some kind of semantic analysis without connecting to a real database.
1388     *   <p></p>
1389     *  This method will do a in-depth analysis of the input SQL such as building the link between table and columns.
1390     *  The parse tree of the input SQL is available after calling this method.
1391     *
1392     *  The parser checks the syntax of those SQL statements one by one. If syntax error is found in a SQL statement,
1393     *  an error will be logged, no parse tree will be built for this SQL statement,
1394     *  the error message can be fetched using the {@link #getErrormessage()} method.
1395     *  <p></p>
1396     *  The syntax error in one SQL statement doesn't prevent the parser continue to check the syntax of the next SQL statement.
1397     *  After checking syntax of all SQL statements, use the {@link #getErrorCount()} method to get the total number of errors.
1398     *  <p></p>
1399     *  A syntax error in a SQL stored procedure will cease this parser to check syntax of the rest SQL statements
1400     *  in this stored procedure.
1401     *
1402     * @return   0 means parse SQL script successfully, otherwise, use {@link #getErrorCount}, {@link #getErrormessage}
1403     * to get detailed error information.
1404     * @see #getSyntaxErrors
1405     */
1406
1407    public int parse(){
1408        return doparse();
1409    }
1410
1411    public int validate(){
1412        if (sqlstatements.size() == 0) return 0;
1413
1414        TRelationValidator relationValidate = new TRelationValidator(globalContext);
1415        for(TCustomSqlStatement sqlStatement:sqlstatements){
1416            sqlStatement.acceptChildren(relationValidate);
1417        }
1418        return 0;
1419    }
1420
1421    void setdelimiterchar(char ch){
1422        delimiterchar = ch;
1423        flexer.delimiterchar = ch;
1424    }
1425    char curdelimiterchar;
1426    String userDelimiterStr ="";
1427
1428    boolean includesqlstatementtype(ESqlStatementType search, ESqlStatementType[] src){
1429        boolean ret = false;
1430        for(int i=0;i<src.length;i++){
1431            if (src[i] == search){
1432                ret = true;
1433                break;
1434            }
1435        }
1436        return ret;
1437    }
1438
1439    // private int getfileEncodingType(FileInputStream inputStream){
1440    //     BufferedInputStream fr = new BufferedInputStream(inputStream,8);
1441    //     return getfileEncodingType(fr);
1442    // }
1443
1444    private int getfileEncodingType(BufferedInputStream fr){
1445        int ret = 0; // default, 1: utf-16, 2: utf-32
1446       // BufferedInputStream fr = new BufferedInputStream(inputStream,8);
1447        try {
1448            byte[] bom = new byte[4];
1449            fr.mark(bom.length+1);
1450
1451            fr.read(bom,0,bom.length);
1452            if ( ((bom[0] == (byte)0xFF) && (bom[1] == (byte)0xFE))
1453                    ||((bom[0] == (byte)0xFE) && (bom[1] == (byte)0xFF))
1454                    )
1455            {
1456                ret = 1;
1457                if ( ((bom[2] == (byte)0xFF) && (bom[3] == (byte)0xFE))
1458                        ||((bom[2] == (byte)0xFE) && (bom[3] == (byte)0xFF))
1459                        ){
1460                    ret = 2;
1461                }
1462            }else{
1463                if ((bom[0] == (byte)0xEF) && (bom[1] == (byte)0xBB)&& (bom[2] == (byte)0xBF)){
1464                    ret = 3; //UTF-8,EF BB BF
1465                }
1466            }
1467            fr.reset();
1468        } catch (FileNotFoundException e) {
1469            // e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1470        } catch (IOException e) {
1471            //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1472        }
1473        return ret;
1474    }
1475
1476    // private int getfileEncodingType(String fn){
1477    //     int ret = 0; // default, 1: utf-16, 2: utf-32
1478    //     try {
1479    //         FileInputStream fr =   new FileInputStream(fn);
1480    //         ret = getfileEncodingType(fr);
1481    //         fr.close();
1482
1483    //     } catch (FileNotFoundException e) {
1484    //        // e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1485    //     } catch (IOException e) {
1486    //         //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1487    //     }
1488    //     return ret;
1489    // }
1490
1491    int readsql(){
1492        int ret  = 0;
1493
1494        syntaxErrors.clear();
1495        //syntaxHints.clear();
1496
1497        if (((!TBaseType.full_edition))||(!TBaseType.need_license_file)){
1498            if (!TBaseType.need_license_file){
1499                licenseType = TBaseType.license_type_dist;
1500                userName = "dist";
1501            }else {
1502                licenseType = TBaseType.license_type_trial;
1503                userName = TBaseType.license_trail_username;
1504            }
1505           // machineId = new HardwareBinder().getMachineIdString();
1506        }else {
1507            if (!licenseOK){
1508                errormessage = licenseMessage;
1509                return -1;
1510            }
1511        }
1512
1513        try{
1514            if (finputstream != null) finputstream.close();
1515            if (flexer == null){
1516                ret = -1;
1517                errormessage = "requested database not supported:"+this.dbVendor.toString();
1518                return ret;
1519            }
1520            if (flexer.yyinput != null)    flexer.yyinput.close();
1521
1522        }catch(IOException e){
1523            ret = -1;
1524            errormessage = "requested database not supported";
1525        }
1526        
1527        if (sqltext.length() > 0){
1528            finputstream = new BufferedReader(new StringReader(sqltext), TBaseType.LEXER_INPUT_BUFFER_SIZE);
1529            if ((!TBaseType.full_edition) && (sqltext.length() > TBaseType.query_size_limitation)){
1530                errormessage = TBaseType.trail_version_query_message;
1531                ret = -1;
1532            }
1533        }else if (sqlfilename.length() > 0){
1534            try{
1535
1536             streamFromSqlFile =   new FileInputStream(sqlfilename);
1537            // Buffer it for mark/reset support
1538            BufferedInputStream bufferedStream = new BufferedInputStream(streamFromSqlFile,8);             
1539            int encodingtype = getfileEncodingType(bufferedStream);
1540            streamFromSqlFile.getChannel().position(0);
1541
1542           if(encodingtype == 1){
1543                sqlStreamReader = new InputStreamReader(streamFromSqlFile,"UTF-16");
1544           }else if(encodingtype == 2){
1545                sqlStreamReader = new InputStreamReader(streamFromSqlFile,"UTF-32");
1546           }else if(encodingtype == 3){
1547              // System.out.println("utf-8");
1548                sqlStreamReader = new InputStreamReader(streamFromSqlFile,"UTF-8");
1549           }
1550           else{
1551               if (sqlCharset == null){
1552                   sqlCharset = Charset.defaultCharset().name();
1553                   //System.out.println("Charset used: "+Charset.defaultCharset().name());
1554               }
1555                sqlStreamReader = new InputStreamReader(streamFromSqlFile, sqlCharset);
1556              // isr = new InputStreamReader(fr,"Cp737");
1557           }
1558
1559            finputstream =  new BufferedReader(sqlStreamReader, TBaseType.LEXER_INPUT_BUFFER_SIZE);
1560            if (encodingtype == 3){
1561                //EF BB BF was not stripped by the InputStreamReader, so we do it
1562                finputstream.skip(1);
1563            }
1564
1565            if ((!TBaseType.full_edition)){
1566                File file = new File(sqlfilename);
1567                if (!file.exists() || !file.isFile()) {
1568                    ret = -1;
1569                    errormessage = "not a valid sql file.";
1570                }else{
1571                    if (file.length() > TBaseType.query_size_limitation){
1572                        errormessage = TBaseType.trail_version_file_message;
1573                        ret = -1;
1574                    }
1575                }
1576            }
1577
1578            }catch(FileNotFoundException e){
1579                ret = -1;
1580                errormessage = e.toString();
1581            } catch (UnsupportedEncodingException e) {
1582                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1583            } catch (IOException e) {
1584                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1585            }
1586
1587        }else if(this.sqlInputStream != null){
1588            int encodingtype = getfileEncodingType(sqlInputStream);
1589            InputStream fr =   sqlInputStream;
1590
1591            InputStreamReader isr = null;
1592            try{
1593
1594                if(encodingtype == 1){
1595                    isr = new InputStreamReader(fr,"UTF-16");
1596                }else if(encodingtype == 2){
1597                    isr = new InputStreamReader(fr,"UTF-32");
1598                }else if(encodingtype == 3){
1599                    // System.out.println("utf-8");
1600                    isr = new InputStreamReader(fr,"UTF-8");
1601                }
1602                else{
1603                    if (sqlCharset == null){
1604                        sqlCharset = Charset.defaultCharset().name();
1605                    }
1606
1607                    isr = new InputStreamReader(fr, sqlCharset);
1608                }
1609
1610                finputstream =  new BufferedReader(isr, TBaseType.LEXER_INPUT_BUFFER_SIZE);
1611                if (encodingtype == 3){
1612                    //EF BB BF was not stripped by the InputStreamReader, so we do it
1613                    finputstream.skip(1);
1614                }
1615
1616            }catch(FileNotFoundException e){
1617                ret = -1;
1618                errormessage = e.toString();
1619            } catch (UnsupportedEncodingException e) {
1620                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1621            } catch (IOException e) {
1622                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1623            }
1624
1625            try {
1626                if ((!TBaseType.full_edition) && (sqlInputStream.available() > TBaseType.query_size_limitation)){
1627                    errormessage = TBaseType.trail_version_query_message;
1628                    ret = -1;
1629                }
1630            } catch (IOException e) {
1631                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1632            }
1633        }
1634
1635       if (finputstream == null) ret = -1;
1636
1637       if (ret == 0)
1638       {
1639           flexer.yyinput =  finputstream;
1640           flexer.setSqlCharset(this.sqlCharset);
1641           flexer.reset();
1642       }
1643
1644        sourcetokenlist.clear();
1645        sourcetokenlist.curpos = -1;
1646
1647       flexer.resetTokenTable();
1648
1649
1650        return ret;
1651    }
1652
1653    TSourceToken getanewsourcetoken(){
1654        TSourceToken pst = null,prevst;
1655        
1656        while (true) {
1657            pst = new TSourceToken("");
1658            if (flexer.yylexwrap(pst) == 0) { pst = null; break;}
1659            
1660            pst.setDbvendor(dbVendor);
1661            pst.tokenstatus = ETokenStatus.tsoriginal;
1662            if (pst.tokentype == ETokenType.ttreturn){
1663               pst.setAstext(towinlinebreak(pst.getAstext()));
1664            }
1665            //combine space && linebreak after a linebreak into one
1666            if ( (pst.tokentype == ETokenType.ttwhitespace)
1667             && (sourcetokenlist.curpos >= 0) )
1668              {
1669                prevst =  sourcetokenlist.get(sourcetokenlist.curpos);
1670                if ( prevst.tokentype == ETokenType.ttreturn )
1671                  {
1672                    //can't discard  whitespace after linebreak, it will be used
1673                    // to judge whether / at the { of the line is a sqlplus cmd or not
1674                    // check isvalidplacefordivtosqlpluscmd for more
1675                    prevst.setAstext(prevst.getAstext() + pst.getAstext());
1676                    //newst.free;
1677                    //newst = nil;
1678                    continue;
1679                  }
1680              }
1681
1682            if ( (pst.tokentype == ETokenType.ttreturn)
1683             && (sourcetokenlist.curpos >= 0) )
1684              {
1685                prevst =  sourcetokenlist.get(sourcetokenlist.curpos);
1686
1687                if ( prevst.tokentype == ETokenType.ttreturn )
1688                  {
1689                    prevst.setAstext(prevst.getAstext() + pst.getAstext());
1690                    //newst.free;
1691                    //newst = nil;
1692                    continue;
1693                  }
1694
1695                if ( prevst.tokentype == ETokenType.ttwhitespace )
1696                  {
1697                    //merge previous whitespace with this linebreak
1698                    //doesn't work for sqlplus cmd
1699
1700                   // prevst.sourcecode = newst.sourcecode;
1701                   // prevst.tokentype = newst.tokentype;
1702                   // prevst.tokencode = newst.tokencode;
1703                   // newst.free;
1704                   // newst = nil;
1705                   // continue;
1706
1707                   //prevst.prevsourcecode = prevst.astext;
1708                   //prevst.astext =  "";
1709                  }
1710              }
1711
1712            break;
1713
1714        }
1715
1716        if (pst != null){
1717            pst.container = sourcetokenlist;
1718            sourcetokenlist.curpos = sourcetokenlist.curpos+1;
1719            pst.posinlist = sourcetokenlist.curpos;
1720            if (tokenHandle != null){
1721                tokenHandle.processToken(pst);
1722            }
1723        }
1724
1725       // System.out.println(pst);
1726      //  flexer.setTokenTableValue(pst);
1727        return pst;
1728        
1729    }
1730
1731  String towinlinebreak(String s){
1732      return s;
1733// todo not implemented yet     
1734  }
1735
1736void checkconstarinttoken(TSourceToken lcprevtoken){
1737    TSourceTokenList lcStList = lcprevtoken.container;
1738    if (TBaseType.assigned(lcStList))
1739    {
1740         TSourceToken lcPPToken = lcStList.nextsolidtoken(lcprevtoken.posinlist,-2,false);
1741         if (TBaseType.assigned(lcPPToken))
1742        {
1743
1744             if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint"))
1745             {
1746                 //System.out.println(lcPPToken);
1747                 lcPPToken.tokencode = TBaseType.rw_constraint2;
1748             }
1749        }
1750    }
1751
1752}
1753
1754TSourceToken getprevtoken(TSourceToken ptoken){
1755   // ETokenType[] lcnonsolidtokenset = {ETokenType.ttwhitespace,ETokenType.ttreturn,ETokenType.ttsimplecomment,ETokenType.ttbracketedcomment} ;
1756    TSourceTokenList lcstlist = ptoken.container;
1757    if (TBaseType.assigned(lcstlist)){
1758        if ((ptoken.posinlist > 0)  && (lcstlist.size() > ptoken.posinlist-1))
1759        {
1760            if (!(
1761                    (lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttwhitespace)
1762                    ||(lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttreturn)
1763                    ||(lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttsimplecomment)
1764                    ||(lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttbracketedcomment)
1765                    )
1766                )
1767            {return lcstlist.get(ptoken.posinlist-1);}
1768            else{
1769            { return lcstlist.nextsolidtoken(ptoken.posinlist-1,-1,false);}
1770        }
1771        }
1772    }
1773    
1774    return null;
1775}
1776
1777void  docommonsqltexttotokenlist(){
1778
1779    TSourceToken asourcetoken,lcprevst;
1780    int yychar;
1781
1782    asourcetoken = getanewsourcetoken();
1783    if ( asourcetoken == null ) return;
1784    yychar = asourcetoken.tokencode;
1785
1786    while (yychar > 0)
1787    {
1788        sourcetokenlist.add(asourcetoken);
1789        asourcetoken = getanewsourcetoken();
1790        if ( asourcetoken == null ) break;
1791        yychar = asourcetoken.tokencode;
1792    }
1793
1794}
1795void dosqltexttotokenlist(){
1796    // Legacy path: Only called for non-delegated vendors (e.g., dbvfirebird, dbvexasol)
1797    // All delegated vendors (mssql, access, generic, mysql, oracle, etc.) use getOrCreateVendorParser()
1798    docommonsqltexttotokenlist();
1799
1800    doAfterTokenize();
1801
1802    TBaseType.resetTokenChain(sourcetokenlist,0);
1803
1804    processTokensInTokenTable(dbVendor);
1805    processTokensBeforeParse(dbVendor);
1806
1807    closeFileStream();
1808
1809    if (tokenListHandle != null){
1810        tokenListHandle.processTokenList(sourcetokenlist);
1811    }
1812}
1813
1814void doAfterTokenize(){
1815
1816    int leftParenCount = 0;
1817    int rightParenCount = 0;
1818    int leftIndex = 0;
1819    int rightIndex = sourcetokenlist.size() - 1;
1820
1821    // Count opening parentheses at the beginning
1822    while (leftIndex < sourcetokenlist.size() && sourcetokenlist.get(leftIndex).tokencode == '(') {
1823        leftParenCount++;
1824        leftIndex++;
1825    }
1826
1827    // Count closing parentheses at the end
1828    while (rightIndex >= 0 && sourcetokenlist.get(rightIndex).tokencode == ')') {
1829        rightParenCount++;
1830        rightIndex--;
1831    }
1832
1833    // Set matching parentheses to be ignored
1834    int parensToIgnore = Math.min(leftParenCount, rightParenCount);
1835    // if there is a semicolon before the right parenthesis, set the semicolon to be ignored
1836    // mantisbt/view.php?id=3690
1837
1838    if ((parensToIgnore > 0) && (sourcetokenlist.get(sourcetokenlist.size() - 1 - (parensToIgnore - 1) - 1).tokencode == ';')){
1839        // set to whitespace that this semicolon will be ignored during getting raw sql
1840        sourcetokenlist.get(sourcetokenlist.size() - 1 - (parensToIgnore - 1) - 1).tokentype = ETokenType.ttwhitespace;
1841        // set to ignore by yacc that this semicolon will be ignored during parsing
1842        sourcetokenlist.get(sourcetokenlist.size() - 1 - (parensToIgnore - 1) - 1).tokenstatus = ETokenStatus.tsignorebyyacc;
1843    }
1844//    for (int i = 0; i < parensToIgnore; i++) {
1845//        //sourcetokenlist.get(i).tokenstatus = ETokenStatus.tsignorebyyacc;
1846//        sourcetokenlist.get(i).tokencode = TBaseType.lexspace;
1847//
1848//       // sourcetokenlist.get(sourcetokenlist.size() - 1 - i).tokenstatus = ETokenStatus.tsignorebyyacc;
1849//        sourcetokenlist.get(sourcetokenlist.size() - 1 - i).tokencode = TBaseType.lexspace;
1850//    }
1851
1852}
1853
1854
1855
1856void processTokensBeforeParse(EDbVendor dbVendor){
1857
1858        // 为确保性能,只在snowflake数据库中处理,因为目前只有snowflake数据库中有连续的分号有用户提出这个需求,其他数据库中暂时不处理
1859   if (dbVendor != EDbVendor.dbvsnowflake) return;
1860
1861        // mantisbt/view.php?id=3579
1862        // if there are consecutive semicolon tokens, mark the second semi colon token as deleted token
1863    for(int i=0;i<sourcetokenlist.size();i++){
1864        TSourceToken st = sourcetokenlist.get(i);
1865        if (st.tokencode == ';'){
1866            TSourceToken nextToken = st.nextSolidToken();
1867            if (nextToken != null){
1868                if (nextToken.tokencode == ';'){
1869                    nextToken.tokenstatus = ETokenStatus.tsdeleted;
1870                }
1871            }
1872        }
1873    }
1874
1875}
1876
1877void processTokensInTokenTable(EDbVendor dbVendor){
1878    // 获得所有token后,根据需要对token code进行预处理, token table是 TBaseType.TOKEN_TABLE
1879     long[][] TOKEN_TABLE1 = flexer.TOKEN_TABLE;
1880
1881    switch (dbVendor){
1882        case dbvbigquery:
1883        case dbvsnowflake:
1884            // case 1, DO 关键字如果没有发现对于的 FOR, WHILE 等关键字,把 DO 关键字的token code设置为 TBaseType.ident
1885
1886            if (TOKEN_TABLE1[TBaseType.rrw_do][0] > 0){
1887                if ((TOKEN_TABLE1[TBaseType.rrw_while][0] == 0)&&(TOKEN_TABLE1[TBaseType.rrw_for][0] == 0)){
1888                    for(int i=0;i<sourcetokenlist.size();i++){
1889                        TSourceToken st = sourcetokenlist.get(i);
1890                        if (st.tokencode == TBaseType.rrw_do){
1891                            st.tokencode = TBaseType.ident;
1892                        }
1893                    }
1894                }
1895            }
1896
1897            break;
1898    }
1899
1900}
1901
1902boolean isDollarFunctionDelimiter(int tokencode, EDbVendor dbVendor){
1903        return ((tokencode == TBaseType.rrw_postgresql_function_delimiter)&&(dbVendor == EDbVendor.dbvpostgresql))
1904                ||((tokencode == TBaseType.rrw_greenplum_function_delimiter)&&(dbVendor == EDbVendor.dbvgreenplum))
1905                ||((tokencode == TBaseType.rrw_redshift_function_delimiter)&&(dbVendor == EDbVendor.dbvredshift))
1906                ||((tokencode == TBaseType.rrw_snowflake_function_delimiter)&&(dbVendor == EDbVendor.dbvsnowflake));
1907}
1908
1909/**
1910 * Returns the 1-based line number of the end of the last SQL statement that
1911 * was successfully recognized during raw-statement separation (e.g. via
1912 * vendor-specific {@code do*getrawsqlstatements} routines).
1913 * <p>
1914 * The value corresponds to the ending line of the last validated statement in
1915 * the most recent parse operation. Any trailing, incomplete statement at the
1916 * end of the input is intentionally excluded and will not affect this value.
1917 * <p>
1918 * Notes:
1919 * <ul>
1920 * <li>The line number is relative to the current input provided to the
1921 * parser (not an absolute position in an external, larger file).</li>
1922 * <li>This is useful when splitting huge SQL files by safe statement
1923 * boundaries — callers can cut the source at this line without risking a
1924 * partial statement.</li>
1925 * </ul>
1926 *
1927 * @return the 1-based line number of the last validated statement's ending
1928 *         line, or {@code -1} if no statement has been validated yet
1929 */
1930public int getLastLineNoOfLastStatementBeenValidated(){
1931        if (lastTokenOfStatementBeenValidated != null){
1932            return (int)lastTokenOfStatementBeenValidated.lineNo;
1933        }
1934        return -1;
1935}
1936
1937private TSourceToken lastTokenOfStatementBeenValidated;
1938
1939
1940void doongetrawsqlstatementevent(TCustomSqlStatement pcsqlstatement){
1941        doongetrawsqlstatementevent(pcsqlstatement,false);
1942}
1943
1944void doongetrawsqlstatementevent(TCustomSqlStatement pcsqlstatement, boolean isLastSQL){
1945    pcsqlstatement.setGsqlparser(this);
1946    pcsqlstatement.parser = this.fparser;
1947    pcsqlstatement.plsqlparser = this.fplsqlparser;
1948    pcsqlstatement.setStartToken(pcsqlstatement.sourcetokenlist.get(0));
1949    pcsqlstatement.setEndToken(pcsqlstatement.sourcetokenlist.get(pcsqlstatement.sourcetokenlist.size()-1));
1950    sqlstatements.add(pcsqlstatement);
1951
1952    if (!isLastSQL){ // 最后一个语句没有经过验证,只是在结束的时候强行加入的,很有可能是语法不正确的,因此我们这里只记录非最后的语句
1953        lastTokenOfStatementBeenValidated = pcsqlstatement.getEndToken();
1954    }
1955
1956    // if stored procedure body is not written in sql or plsql, then, set the token in body to
1957    if (    ((this.dbVendor == EDbVendor.dbvpostgresql)||(this.dbVendor == EDbVendor.dbvgreenplum) ||(this.dbVendor == EDbVendor.dbvredshift) ||(this.dbVendor == EDbVendor.dbvsnowflake) )
1958             && (pcsqlstatement instanceof TRoutine)
1959       ){
1960         if (!((TRoutine)pcsqlstatement).isBodyInSQL()){
1961             TSourceToken st;
1962             boolean inBody = false;
1963             StringBuilder routineBodyBuilder = new StringBuilder();
1964             for(int i=0;i<pcsqlstatement.sourcetokenlist.size();i++){
1965                 st = pcsqlstatement.sourcetokenlist.get(i);
1966                 if (isDollarFunctionDelimiter(st.tokencode,this.dbVendor)
1967//                         ((st.tokencode == TBaseType.rrw_postgresql_function_delimiter)&&(this.dbVendor == EDbVendor.dbvpostgresql))
1968//                        ||((st.tokencode == TBaseType.rrw_greenplum_function_delimiter)&&(this.dbVendor == EDbVendor.dbvgreenplum))
1969//                         ||((st.tokencode == TBaseType.rrw_redshift_function_delimiter)&&(this.dbVendor == EDbVendor.dbvredshift))
1970//                         ||((st.tokencode == TBaseType.rrw_snowflake_function_delimiter)&&(this.dbVendor == EDbVendor.dbvsnowflake))
1971                 ){
1972                     if (!inBody){
1973                         inBody = true;
1974                         routineBodyBuilder.setLength(0);
1975                         routineBodyBuilder.append(st.toString());
1976                     }else{
1977                         inBody = false;
1978                         routineBodyBuilder.append(st.toString());
1979                         break;
1980                     }
1981                     continue;
1982                 }
1983
1984                 if (inBody){
1985                     st.tokencode = TBaseType.sqlpluscmd;
1986                     routineBodyBuilder.append(st.toString());
1987                 }
1988             }
1989
1990             ((TRoutine)pcsqlstatement).setRoutineBody(routineBodyBuilder.toString());
1991         }
1992    }
1993}
1994
1995    boolean checkTokenPairWithEnd(int tokencode){
1996        return    ((tokencode == TBaseType.rrw_if)||(tokencode == TBaseType.rrw_case)
1997                ||(tokencode == TBaseType.rrw_loop)||(tokencode == TBaseType.rrw_repeat)
1998                ||(tokencode == TBaseType.rrw_while)||(tokencode == TBaseType.rrw_for)
1999                ||(tokencode == TBaseType.rrw_case)
2000        );
2001    }
2002
2003    private TCustomSqlStatement startDaxStmt(TSourceToken currToken,TCustomSqlStatement currStmt){
2004        TCustomSqlStatement newStmt = null;
2005        if (currToken == null) return null;
2006        if ((currToken.tokencode == '=')&&(currToken.isFirstTokenOfLine())){
2007            currToken.tokencode = TBaseType.equal_start_expr;
2008            newStmt = new TDaxExprStmt(EDbVendor.dbvdax);
2009        }else if ((currToken.tokencode == TBaseType.rrw_dax_define)&&(currToken.isFirstTokenOfLine())){
2010            newStmt = new TDaxEvaluateStmt(EDbVendor.dbvdax);
2011            ((TDaxEvaluateStmt)newStmt).setStartWithDefine(true);
2012        }else if ((currToken.tokencode == TBaseType.rrw_dax_evaluate)&&(currToken.isFirstTokenOfLine())){
2013            if ((currStmt != null)&&(currStmt instanceof TDaxEvaluateStmt)){
2014                TDaxEvaluateStmt tmp = (TDaxEvaluateStmt)currStmt;
2015                if (tmp.isStartWithDefine()) return  null;
2016            }
2017            newStmt = new TDaxEvaluateStmt(EDbVendor.dbvdax);
2018        }
2019
2020        if (newStmt == null){
2021            // let's check is this the first token of query
2022            boolean isFirst = currToken.isFirstTokenOfLine();
2023            TSourceToken prevToken = currToken.prevSolidToken();
2024            if ((isFirst)&&(prevToken == null)){
2025                newStmt = new TDaxExprStmt(EDbVendor.dbvdax);
2026            }
2027        }
2028        return newStmt;
2029    }
2030
2031int dogetrawsqlstatements(){
2032    // This method should not be called - all vendors are now delegated to vendor-specific parsers.
2033    // If this is reached, it means a new vendor was added but not included in getOrCreateVendorParser().
2034    throw new IllegalStateException(
2035        "dogetrawsqlstatements() called for vendor " + dbVendor +
2036        ". All vendors should be delegated via getOrCreateVendorParser(). " +
2037        "Please add this vendor to the switch statement in getOrCreateVendorParser().");
2038}
2039
2040    /**
2041     *  separates the SQL statements in the input SQL script without doing syntax check.
2042     *  <p></p>
2043     *  Use the {@link #getSqlstatements()} method to get the list of SQL statements.
2044     *  The SQL statement object is the instance of the sub-class of {@link TCustomSqlStatement}, get SQL statement type
2045     *  via the {@link TCustomSqlStatement#sqlstatementtype} field, get string representation of
2046     *  each SQL statement via the {@link TCustomSqlStatement#toString} method.
2047     *  <p></p>
2048     *  All source tokens in this SQL statement
2049     *  is available by using {@link TCustomSqlStatement#sourcetokenlist} filed.
2050     *  Since no parse tree is built by calling this method, no further detailed information about the SQL statement is available.
2051     *
2052      * @return 0 if get SQL statements successfully
2053     */
2054public int getrawsqlstatements(){
2055    // Clear errors from any previous call on this instance
2056    syntaxErrors.clear();
2057
2058    // Check for vendor parser delegation (MSSQL, etc.)
2059    // This ensures raw-split logic is maintained in only one place (vendor parser)
2060    SqlParser vp = getOrCreateVendorParser();
2061    if (vp != null) {
2062        return doDelegatedRawParse(vp);
2063    }
2064
2065    // Legacy path for non-delegated vendors
2066    int ret = readsql();
2067    if (ret != 0) return ret;
2068    dosqltexttotokenlist();
2069
2070    return dogetrawsqlstatements();
2071}
2072
2073    /**
2074     *  turns the input SQL into a sequence of token which is the
2075     *  basic lexis element of SQL syntax. Token is categorized as keyword, identifier,
2076     *  number, operator, whitespace and other types. All source tokens can be fetched
2077     *  via the {@link #getSourcetokenlist()} method.
2078     *
2079     */
2080public void tokenizeSqltext(){
2081    // Check for vendor parser delegation (MSSQL, etc.)
2082    // This ensures tokenization logic is maintained in only one place (vendor parser)
2083    SqlParser vp = getOrCreateVendorParser();
2084    if (vp != null) {
2085        doDelegatedTokenize(vp);
2086        return;
2087    }
2088
2089    // Legacy path for non-delegated vendors
2090    getFlexer();
2091    readsql();
2092    dosqltexttotokenlist();
2093}
2094
2095/**
2096 * Delegate tokenization to vendor parser and backfill results.
2097 * Similar to doDelegatedRawParse but only performs tokenization.
2098 */
2099private void doDelegatedTokenize(SqlParser vendorParser) {
2100    // Build context for vendor parser
2101    ParserContext context = buildContext();
2102
2103    // Delegate tokenization to vendor parser
2104    SqlParseResult tokenResult = vendorParser.tokenize(context);
2105
2106    // Copy results back to TGSqlParser fields for backward compatibility
2107    if (tokenResult.getSourceTokenList() != null) {
2108        this.sourcetokenlist = tokenResult.getSourceTokenList();
2109    }
2110    if (tokenResult.getLexer() != null) {
2111        this.flexer = tokenResult.getLexer();
2112    }
2113}
2114
2115
2116
2117void findAllSyntaxErrorsInPlsql(TCustomSqlStatement psql){
2118    if (psql.getErrorCount() > 0){
2119        copyerrormsg(psql);
2120    }
2121
2122    for (int k=0;k<psql.getStatements().size();k++){
2123        findAllSyntaxErrorsInPlsql(psql.getStatements().get(k));
2124    }
2125
2126}
2127
2128private TSQLEnv sqlEnv = null;
2129
2130    /**
2131     * SQL environment includes the database metadata such as procedure, function, trigger, table and etc.
2132     *
2133     * In order to link column to table correctly without connecting to database,
2134     * we need to provide a class which implements {@link TSQLEnv} to TGSqlParser.
2135     * this class tells TGSqlParser the relationship between column and table.
2136     *
2137     * <p>Take this SQL for example:
2138     * <pre>
2139     * SELECT Quantity,b.Time,c.Description
2140     * FROM
2141     * (SELECT ID2,Time FROM bTab) b
2142     * INNER JOIN aTab a on a.ID=b.ID
2143     * INNER JOIN cTab c on a.ID=c.ID
2144     * </pre>
2145     *
2146     * <p>General SQL Parser can build relationship between column: ID2 and table: bTable
2147     * correctly without metadata information from database because there is only one table
2148     * in from clause. But it can't judge column: Quantity belong to table: aTab or cTab,
2149     * since no table alias was prefixed to column: Quantity. If no metadata provided,
2150     * General SQL Parser will link column: Quantity to the first valid table (here it is aTab)
2151     *
2152     * <p>If we create a class  TRealDatabaseSQLEnv implements {@link TSQLEnv},then
2153     *  {@link #setSqlEnv(TSQLEnv)}, General SQL Parser can take this advantage to create
2154     *  a correct relationship between column and tables.
2155     *
2156     *<pre>
2157     * class TSQLServerEnv extends TSQLEnv{
2158     *
2159     *  public TSQLServerEnv(){
2160     *          super(EDbVendor.dbvmssql);
2161     *          initSQLEnv();
2162     *        }
2163     *
2164     *    &#64;Override
2165     *    public void initSQLEnv() {
2166     *
2167     *          // add a new database: master
2168     *          TSQLCatalog sqlCatalog = createSQLCatalog("master");
2169     *          // add a new schema: dbo
2170     *          TSQLSchema sqlSchema = sqlCatalog.createSchema("dbo");
2171     *          //add a new table: aTab
2172     *          TSQLTable aTab = sqlSchema.createTable("aTab");
2173     *          aTab.addColumn("Quantity1");
2174     *
2175     *          //add a new table: bTab
2176     *          TSQLTable bTab = sqlSchema.createTable("bTab");
2177     *          bTab.addColumn("Quantity2");
2178     *
2179     *          //add a new table: cTab
2180     *          TSQLTable cTab = sqlSchema.createTable("cTab");
2181     *          cTab.addColumn("Quantity");
2182     *
2183     *    }
2184     * }
2185     * </pre>
2186     *
2187     * @return SQL environment
2188     */
2189    public TSQLEnv getSqlEnv() {
2190        return sqlEnv;
2191    }
2192
2193    private boolean onlyNeedRawParseTree = false;
2194
2195    public void setOnlyNeedRawParseTree(boolean onlyNeedRawParseTree) {
2196        this.onlyNeedRawParseTree = onlyNeedRawParseTree;
2197    }
2198
2199    public void setSqlEnv(TSQLEnv sqlEnv) {
2200        this.sqlEnv = sqlEnv;
2201    }
2202
2203
2204     // Time tracking variables
2205     private boolean enableTimeLogging = false;
2206     private long rawSqlStatementsTime = 0;
2207     private long parsingTime = 0;
2208     private long semanticAnalysisTime = 0;
2209     private long interpreterTime = 0;
2210     
2211     /**
2212      * Enable or disable time logging for parser steps
2213      * @param enable true to enable time logging, false to disable
2214      */
2215     public void setEnableTimeLogging(boolean enable) {
2216         this.enableTimeLogging = enable;
2217     }
2218     
2219     /**
2220      * Check if time logging is enabled
2221      * @return true if time logging is enabled, false otherwise
2222      */
2223     public boolean isTimeLoggingEnabled() {
2224         return this.enableTimeLogging;
2225     }
2226     
2227     /**
2228      * Reset all accumulated time counters to zero
2229      */
2230     public void resetTimeCounters() {
2231         rawSqlStatementsTime = 0;
2232         parsingTime = 0;
2233         semanticAnalysisTime = 0;
2234         interpreterTime = 0;
2235     }
2236     
2237     /**
2238      * Get accumulated time spent getting raw SQL statements in milliseconds
2239      * @return time in milliseconds
2240      */
2241     public long getRawSqlStatementsTime() {
2242         return rawSqlStatementsTime;
2243     }
2244     
2245     /**
2246      * Get accumulated time spent parsing in milliseconds
2247      * @return time in milliseconds
2248      */
2249     public long getParsingTime() {
2250         return parsingTime;
2251     }
2252     
2253     /**
2254      * Get accumulated time spent on semantic analysis in milliseconds
2255      * @return time in milliseconds
2256      */
2257     public long getSemanticAnalysisTime() {
2258         return semanticAnalysisTime;
2259     }
2260     
2261     /**
2262      * Get accumulated time spent in interpreter in milliseconds
2263      * @return time in milliseconds
2264      */
2265     public long getInterpreterTime() {
2266         return interpreterTime;
2267     }
2268     
2269     /**
2270      * Get total accumulated time spent in all steps
2271      * @return total time in milliseconds
2272      */
2273     public long getTotalTime() {
2274         return rawSqlStatementsTime + parsingTime + semanticAnalysisTime + interpreterTime;
2275     }
2276
2277    /**
2278     * Get or create the vendor-specific parser for delegation.
2279     * The parser is cached in vendorParser field and reused for subsequent calls.
2280     * This allows getFlexer() to lazily create the parser to access its lexer.
2281     *
2282     * @return vendor-specific SqlParser or null if not a delegated vendor
2283     * @since 3.2.0.0
2284     */
2285    private SqlParser getOrCreateVendorParser() {
2286        // Return cached parser if available
2287        if (vendorParser != null) {
2288            return vendorParser;
2289        }
2290
2291        // Create new vendor parser based on database vendor
2292        switch (dbVendor) {
2293            case dbvmssql:
2294            case dbvazuresql:
2295            case dbvaccess:    // Access uses MSSQL lexer/parser
2296            case dbvgeneric:   // Generic uses MSSQL lexer/parser
2297            case dbvfirebird:  // Firebird uses MSSQL lexer/parser
2298            case dbvexasol:    // Exasol uses MSSQL lexer/parser
2299                vendorParser = new gudusoft.gsqlparser.parser.MssqlSqlParser();
2300                break;
2301            case dbvmysql:
2302                vendorParser = new gudusoft.gsqlparser.parser.MySqlSqlParser();
2303                break;
2304            case dbvpostgresql:
2305                vendorParser = new gudusoft.gsqlparser.parser.PostgreSqlParser();
2306                break;
2307            case dbvduckdb:
2308                vendorParser = new gudusoft.gsqlparser.parser.DuckdbSqlParser();
2309                break;
2310            case dbvoracle:
2311                vendorParser = new gudusoft.gsqlparser.parser.OracleSqlParser();
2312                break;
2313            case dbvbigquery:
2314                vendorParser = new gudusoft.gsqlparser.parser.BigQuerySqlParser();
2315                break;
2316            case dbvathena:
2317                vendorParser = new gudusoft.gsqlparser.parser.AthenaSqlParser();
2318                break;
2319            case dbvcouchbase:
2320                vendorParser = new gudusoft.gsqlparser.parser.CouchbaseSqlParser();
2321                break;
2322            case dbvdatabricks:
2323                vendorParser = new gudusoft.gsqlparser.parser.DatabricksSqlParser();
2324                break;
2325            case dbvdax:
2326                vendorParser = new gudusoft.gsqlparser.parser.DaxSqlParser();
2327                break;
2328            case dbvpowerquery:
2329                vendorParser = new gudusoft.gsqlparser.parser.PowerQuerySqlParser();
2330                break;
2331            case dbvdb2:
2332                vendorParser = new gudusoft.gsqlparser.parser.Db2SqlParser();
2333                break;
2334            case dbvdoris:
2335                vendorParser = new gudusoft.gsqlparser.parser.DorisSqlParser();
2336                break;
2337            case dbvstarrocks:
2338                vendorParser = new gudusoft.gsqlparser.parser.StarrocksSqlParser();
2339                break;
2340            case dbvflink:
2341                vendorParser = new gudusoft.gsqlparser.parser.FlinkSqlParser();
2342                break;
2343            case dbvgaussdb:
2344                vendorParser = new gudusoft.gsqlparser.parser.GaussDbSqlParser();
2345                break;
2346            case dbvedb:
2347                vendorParser = new gudusoft.gsqlparser.parser.EdbSqlParser();
2348                break;
2349            case dbvdameng:
2350                vendorParser = new gudusoft.gsqlparser.parser.DamengSqlParser();
2351                break;
2352            case dbvoceanbase:
2353                vendorParser = new gudusoft.gsqlparser.parser.OceanBaseSqlParser();
2354                break;
2355            case dbvgreenplum:
2356                vendorParser = new gudusoft.gsqlparser.parser.GreenplumSqlParser();
2357                break;
2358            case dbvhive:
2359                vendorParser = new gudusoft.gsqlparser.parser.HiveSqlParser();
2360                break;
2361            case dbvhana:
2362                vendorParser = new gudusoft.gsqlparser.parser.HanaSqlParser();
2363                break;
2364            case dbvimpala:
2365                vendorParser = new gudusoft.gsqlparser.parser.ImpalaSqlParser();
2366                break;
2367            case dbvinformix:
2368                vendorParser = new gudusoft.gsqlparser.parser.InformixSqlParser();
2369                break;
2370            case dbvmdx:
2371                vendorParser = new gudusoft.gsqlparser.parser.MdxSqlParser();
2372                break;
2373            case dbvnetezza:
2374                vendorParser = new gudusoft.gsqlparser.parser.NetezzaSqlParser();
2375                break;
2376            case dbvodbc:
2377                vendorParser = new gudusoft.gsqlparser.parser.OdbcSqlParser();
2378                break;
2379            case dbvopenedge:
2380                vendorParser = new gudusoft.gsqlparser.parser.OpenEdgeSqlParser();
2381                break;
2382            case dbvpresto:
2383                vendorParser = new gudusoft.gsqlparser.parser.PrestoSqlParser();
2384                break;
2385            case dbvredshift:
2386                vendorParser = new gudusoft.gsqlparser.parser.RedshiftSqlParser();
2387                break;
2388            case dbvsnowflake:
2389                vendorParser = new gudusoft.gsqlparser.parser.SnowflakeSqlParser();
2390                break;
2391            case dbvsqlite:
2392                vendorParser = new gudusoft.gsqlparser.parser.SqliteSqlParser();
2393                break;
2394            case dbvclickhouse:
2395                vendorParser = new gudusoft.gsqlparser.parser.ClickhouseSqlParser();
2396                break;
2397            case dbvsoql:
2398                vendorParser = new gudusoft.gsqlparser.parser.SoqlSqlParser();
2399                break;
2400            case dbvsparksql:
2401                vendorParser = new gudusoft.gsqlparser.parser.SparksqlSqlParser();
2402                break;
2403            case dbvsybase:
2404                vendorParser = new gudusoft.gsqlparser.parser.SybaseSqlParser();
2405                break;
2406            case dbvteradata:
2407                vendorParser = new gudusoft.gsqlparser.parser.TeradataSqlParser();
2408                break;
2409            case dbvtrino:
2410                vendorParser = new gudusoft.gsqlparser.parser.TrinoSqlParser();
2411                break;
2412            case dbvvertica:
2413                vendorParser = new gudusoft.gsqlparser.parser.VerticaSqlParser();
2414                break;
2415            case dbvansi:
2416                vendorParser = new gudusoft.gsqlparser.parser.AnsiSqlParser();
2417                break;
2418            default:
2419                return null;
2420        }
2421        return vendorParser;
2422    }
2423
2424    /**
2425     * Prepare this parser for reuse by clearing cached state.
2426     * This is useful when reusing a TGSqlParser instance (e.g., in TExecImmeStmt)
2427     * to avoid state pollution between parsing operations.
2428     *
2429     * Clears: vendorParser, sqlEnv, sqlfilename, and resets parsing options.
2430     *
2431     * @since 3.2.0.0
2432     */
2433    public void prepareForReuse() {
2434        // Clear cached vendor parser to force recreation
2435        this.vendorParser = null;
2436        // Clear SQL environment to avoid old schema information affecting new parse
2437        this.sqlEnv = null;
2438        // Clear filename in case it was set previously
2439        this.sqlfilename = null;
2440        // Reset parsing options to defaults
2441        this.isSinglePLBlock = false;
2442        // Clear statement list
2443        if (this.sqlstatements != null) {
2444            this.sqlstatements.clear();
2445        }
2446        // Clear error state
2447        this.syntaxErrors.clear();
2448    }
2449
2450    /**
2451     * Build a ParserContext from current TGSqlParser state.
2452     * This creates an immutable context for delegation to vendor parsers.
2453     *
2454     * @return immutable ParserContext
2455     * @since 3.2.0.0
2456     */
2457    private ParserContext buildContext() {
2458        ParserContext.Builder builder = new ParserContext.Builder(this.dbVendor);
2459
2460        // Set SQL text source (only one will be non-null or non-empty)
2461        // CRITICAL: Check for non-null AND non-empty because TGSqlParser initializes sqltext=""
2462        // which would cause vendor parser to use empty string instead of reading from file
2463        if (this.sqltext != null && !this.sqltext.isEmpty()) {
2464            builder.sqlText(this.sqltext);
2465        }
2466        if (this.sqlfilename != null && !this.sqlfilename.isEmpty()) {
2467            builder.sqlFilename(this.sqlfilename);
2468        }
2469
2470        // Set charset
2471        if (this.sqlCharset != null) {
2472            builder.sqlCharset(this.sqlCharset);
2473        }
2474
2475        // Set parsing options
2476        builder.enablePartialParsing(this.isEnablePartialParsing());
2477        builder.singlePLBlock(this.isSinglePLBlock);
2478
2479        // Mirror OceanBase tenant mode into the context so OceanBaseSqlParser
2480        // (and any other downstream component) can read it without a
2481        // back-reference to TGSqlParser.
2482        builder.oceanBaseTenantMode(this.oceanBaseTenantMode);
2483
2484        // Set gsqlparser reference for backward compatibility
2485        builder.gsqlparser(this);
2486
2487        // Set SQL environment if available
2488        if (this.sqlEnv != null) {
2489            builder.sqlEnv(this.sqlEnv);
2490        }
2491
2492        // Set token handle for callback during tokenization
2493        if (this.tokenHandle != null) {
2494            builder.tokenHandle(this.tokenHandle);
2495        }
2496
2497        return builder.build();
2498    }
2499
2500     /**
2501      * Returns time statistics as a formatted string
2502      * @return formatted string with time statistics
2503      */
2504     public String getTimeStatistics() {
2505         if (!enableTimeLogging) {
2506             return "Time logging is disabled";
2507         }
2508         
2509         StringBuilder sb = new StringBuilder();
2510         sb.append(String.format("Time statistics for TGSqlParser version: %s, released at: %s, dbvendor: %s:\n", TBaseType.versionid,TBaseType.releaseDate, this.dbVendor));
2511         sb.append(String.format("1. Raw SQL statements: %d ms (%.2f%%)\n", 
2512                 rawSqlStatementsTime,
2513                 getTotalTime() > 0 ? 100.0 * rawSqlStatementsTime / getTotalTime() : 0));
2514         sb.append(String.format("2. Parsing: %d ms (%.2f%%)\n", 
2515                 parsingTime,
2516                 getTotalTime() > 0 ? 100.0 * parsingTime / getTotalTime() : 0));
2517         sb.append(String.format("3. Semantic analysis: %d ms (%.2f%%)\n", 
2518                 semanticAnalysisTime,
2519                 getTotalTime() > 0 ? 100.0 * semanticAnalysisTime / getTotalTime() : 0));
2520         sb.append(String.format("4. Interpreter: %d ms (%.2f%%)\n", 
2521                 interpreterTime,
2522                 getTotalTime() > 0 ? 100.0 * interpreterTime / getTotalTime() : 0));
2523         sb.append(String.format("Total: %d ms", getTotalTime()));
2524         return sb.toString();
2525     }
2526
2527    /**
2528     * Delegate only tokenization and raw statement extraction to vendor parser.
2529     * The actual AST parsing is done by the common parsing loop in doparse().
2530     * This architecture ensures that:
2531     * 1. Vendor-specific tokenization/lexing is handled by vendor parser
2532     * 2. Raw statement extraction (splitting SQL text into statements) is vendor-specific
2533     * 3. AST parsing (building parse tree for each statement) uses common code path
2534     *
2535     * @param vendorParser the vendor-specific parser to use
2536     * @return 0 if successful, non-zero on error
2537     */
2538    private int doDelegatedRawParse(SqlParser vendorParser) {
2539        long phaseStart, phaseEnd;
2540
2541        // Build context for vendor parser
2542        ParserContext context = buildContext();
2543
2544        // Delegate only tokenization + raw statement extraction to vendor parser
2545        phaseStart = enableTimeLogging ? System.currentTimeMillis() : 0;
2546        SqlParseResult rawResult = vendorParser.getrawsqlstatements(context);
2547        if (enableTimeLogging) {
2548            phaseEnd = System.currentTimeMillis();
2549            rawSqlStatementsTime += (phaseEnd - phaseStart);
2550        }
2551
2552        // Copy results back to TGSqlParser fields for backward compatibility
2553        if (rawResult.getSourceTokenList() != null) {
2554            this.sourcetokenlist = rawResult.getSourceTokenList();
2555            this.sourcetokenlist.setGsqlparser(this);
2556        }
2557        if (rawResult.getLexer() != null) {
2558            this.flexer = rawResult.getLexer();
2559        }
2560        // Copy parser from vendor parser result - needed for parsestatement()
2561        if (rawResult.getParser() != null) {
2562            this.fparser = rawResult.getParser();
2563            // CRITICAL: Set gsqlparser on NodeFactory for correct AST construction
2564            this.fparser.getNf().setGsqlParser(this);
2565        }
2566        // Copy secondary parser (for Oracle PL/SQL) and set its NodeFactory
2567        if (rawResult.getSecondaryParser() != null) {
2568            this.fplsqlparser = rawResult.getSecondaryParser();
2569            // CRITICAL: Also set gsqlparser on secondary parser's NodeFactory
2570            // This is needed for Oracle PL/SQL statements which use fplsqlparser for parsing
2571            this.fplsqlparser.getNf().setGsqlParser(this);
2572        }
2573        if (rawResult.getSqlStatements() != null) {
2574            this.sqlstatements = rawResult.getSqlStatements();
2575        }
2576
2577        // Copy lastTokenOfStatementBeenValidated for getLastLineNoOfLastStatementBeenValidated() API
2578        this.lastTokenOfStatementBeenValidated = rawResult.getLastTokenOfStatementBeenValidated();
2579
2580        // Copy any tokenization errors
2581        if (rawResult.getSyntaxErrors() != null) {
2582            for (TSyntaxError error : rawResult.getSyntaxErrors()) {
2583                this.syntaxErrors.add(error);
2584            }
2585        }
2586
2587        return rawResult.getErrorCode();
2588    }
2589
2590     int doparse() {
2591         int j;
2592         long startTime, endTime;
2593         boolean useDelegatedRawParse = false;
2594
2595         // Clear errors from any previous parse() call on this instance
2596         syntaxErrors.clear();
2597
2598         // 1. Get raw SQL statements
2599         // For delegated vendors, use vendor parser for tokenization + raw extraction
2600         // The parsing loop below is common for all vendors
2601         SqlParser vp = getOrCreateVendorParser();
2602         if (vp != null) {
2603             int ret = doDelegatedRawParse(vp);
2604             if (ret != 0) {
2605                 // Tokenization/extraction failed, return error
2606                 return ret;
2607             }
2608             useDelegatedRawParse = true;
2609             // Continue to common parsing loop below
2610         }
2611
2612         // Legacy path: get raw statements for non-delegated vendors
2613         if (!useDelegatedRawParse) {
2614             startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2615
2616             int ret = getrawsqlstatements();
2617
2618             if (enableTimeLogging) {
2619                 endTime = System.currentTimeMillis();
2620                 rawSqlStatementsTime += (endTime - startTime);
2621             }
2622         }
2623
2624         boolean isPushGloablStack = false;
2625         //if (ret != 0) return ret;
2626         TFrame firstFrame = null;
2627 
2628         globalContext = new TContext();
2629 
2630         if (this.sqlEnv == null) {
2631             this.sqlEnv = new TSQLEnv(this.dbVendor) {
2632                 @Override
2633                 public void initSQLEnv() {
2634                 }
2635             };
2636             // this.sqlEnv.setEnableGetMetadataFromDDL(false);
2637         }
2638         globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
2639 
2640         //TStackFrame stackFrame = new TStackFrame(new TGlobalScope());
2641         if (getFrameStack().size() == 0) { // stack passed from outside gsqlparser will contain frames
2642             //getFrameStack().push(new TStackFrame(new TGlobalScope()));
2643             TGlobalScope globalScope = new TGlobalScope();
2644             globalScope.resetCurrentStmtIndex();
2645 
2646             globalScope.setSqlEnv(this.sqlEnv);
2647             firstFrame = new TFrame(globalScope);
2648             firstFrame.pushMeToStack(getFrameStack());
2649             isPushGloablStack = true;
2650         }
2651 
2652         // 2. start parsing
2653         startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2654         
2655         for (int i = 0; i < sqlstatements.size(); i++) {
2656             sqlstatements.getRawSql(i).setFrameStack(frameStack);
2657             j = sqlstatements.getRawSql(i).parsestatement(null, false, onlyNeedRawParseTree);
2658
2659             // NOTE: No need for setGsqlparserRecursively() here for delegated vendors.
2660             // The gsqlparser reference is already set on all AST nodes through two mechanisms:
2661             // 1. doDelegatedRawParse() sets fparser.getNf().setGsqlParser(this) and
2662             //    fplsqlparser.getNf().setGsqlParser(this) BEFORE parsestatement() is called
2663             // 2. TNodeFactory.createNode() propagates gsqlparser to every node it creates
2664             // The recursive walk was causing 4-5x performance degradation for Oracle parsing
2665             // by redundantly traversing the entire AST after parsing.
2666
2667             TCustomSqlStatement sql0 = null;
2668             if (sqlstatements.get(i).isoracleplsql()) {
2669                 // check syntax error in select/insert statement inside plsql
2670                 sql0 = sqlstatements.get(i);
2671                 findAllSyntaxErrorsInPlsql(sql0);
2672             }
2673 
2674             boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
2675 
2676             if (doRecover && ((j != 0) || (sqlstatements.get(i).getErrorCount() > 0))) {
2677                 if (((sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreatetable)
2678                         || ((sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreateindex) && (dbVendor != EDbVendor.dbvcouchbase))
2679                         ) && (!TBaseType.c_createTableStrictParsing)
2680                 ) {
2681                     // only parse main body of create table,
2682                     TCustomSqlStatement errorSqlStatement = (TCustomSqlStatement) sqlstatements.get(i);
2683 
2684                     int nested = 0;
2685                     boolean isIgnore = false, isFoundIgnoreToken = false;
2686                     TSourceToken firstIgnoreToken = null;
2687                     for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) {
2688                         TSourceToken st = errorSqlStatement.sourcetokenlist.get(k);
2689                         if (isIgnore) {
2690                             if (st.issolidtoken() && (st.tokencode != ';')) {
2691                                 isFoundIgnoreToken = true;
2692                                 if (firstIgnoreToken == null) {
2693                                     firstIgnoreToken = st;
2694                                 }
2695                             }
2696                             if (st.tokencode != ';') {
2697                                 st.tokencode = TBaseType.sqlpluscmd;
2698                             }
2699                             continue;
2700                         }
2701                         if (st.tokencode == (int) ')') {
2702                             nested--;
2703                             if (nested == 0) {
2704                                 //let's check is next token is
2705                                 // as ( select
2706                                 boolean isSelect = false;
2707                                 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
2708                                 if (st1 != null) {
2709                                     TSourceToken st2 = st.searchToken((int) '(', 2);
2710                                     if (st2 != null) {
2711                                         TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
2712                                         isSelect = (st3 != null);
2713                                     }
2714                                 }
2715                                 if (!isSelect) isIgnore = true;
2716                             }
2717                         }
2718                         if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
2719                             nested++;
2720                         }
2721                     }
2722 
2723                     if ((dbVendor == EDbVendor.dbvoracle) && ((firstIgnoreToken != null) && (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString())))) {
2724                         // if it is not the valid Oracle table properties option, let raise the error.
2725                         isFoundIgnoreToken = false;
2726                     }
2727                     if (isFoundIgnoreToken) {
2728                         errorSqlStatement.clearError();
2729                         j = sqlstatements.get(i).parsestatement(null, false);
2730                     }
2731                 }
2732 
2733                 if (((sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreatetrigger)
2734                         || (sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreatefunction)
2735                         || (sqlstatements.get(i).sqlstatementtype == sstcreateprocedure)
2736                         ) && (dbVendor == EDbVendor.dbvdb2)) { 
2737                     // db2 can handle Oracle pl/sql code, so we give it a try here
2738                     TCustomSqlStatement stmt = sqlstatements.get(i);
2739                     StringBuffer stmtStr = new StringBuffer(1024);
2740                     for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
2741                         stmtStr.append(stmt.sourcetokenlist.get(k).getAstext());
2742                     }
2743 
2744                     TGSqlParser lc_sqlparser = new TGSqlParser(EDbVendor.dbvoracle);
2745                     lc_sqlparser.sqltext = stmtStr.toString();
2746                     int iRet = lc_sqlparser.parse();
2747                     if (iRet == 0) {
2748                         sqlstatements.remove(i);
2749                         sqlstatements.add(i, lc_sqlparser.sqlstatements.get(0));
2750                         continue;
2751                     }
2752                 }
2753             }
2754 
2755             if ((j != 0) || (sqlstatements.get(i).getErrorCount() > 0)) {
2756                 copyerrormsg(sqlstatements.get(i));
2757 
2758                 if ((isEnablePartialParsing()) && (dbVendor == EDbVendor.dbvsybase) && (sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstmssqlcreateprocedure)) {
2759                     TMssqlCreateProcedure createProcedure = (TMssqlCreateProcedure) sqlstatements.get(i);
2760 
2761                     StringBuffer storedProcedure = new StringBuffer(1024);
2762                     boolean ASKeyword = false;
2763                     for (int k = 0; k < createProcedure.sourcetokenlist.size(); k++) {
2764                         if ((!ASKeyword) && (createProcedure.sourcetokenlist.get(k).tokencode == TBaseType.rrw_as)) {
2765                             ASKeyword = true;
2766                             continue;
2767                         }
2768                         if (ASKeyword) {
2769                             storedProcedure.append(createProcedure.sourcetokenlist.get(k).getAstext());
2770                         }
2771                     }
2772 
2773                     TGSqlParser lc_sqlparser = new TGSqlParser(dbVendor);
2774                     lc_sqlparser.sqltext = storedProcedure.toString();
2775                     lc_sqlparser.parse();
2776                     for (int k = 0; k < lc_sqlparser.sqlstatements.size(); k++) {
2777                         createProcedure.getBodyStatements().add(lc_sqlparser.sqlstatements.get(k));
2778                     }
2779                 }
2780             }
2781 
2782             // fire SQL Statement handle event if set, if return true, stop parsing
2783             if (sqlStatementHandle != null) {
2784                 if (sqlStatementHandle.processSQLStatement(sqlstatements.get(i), this)) break;
2785             }
2786         }
2787         
2788         if (enableTimeLogging) {
2789             endTime = System.currentTimeMillis();
2790             parsingTime += (endTime - startTime);
2791         }
2792 
2793         if (isPushGloablStack) {
2794             firstFrame.popMeFromStack(getFrameStack());
2795         }
2796 
2797         // 3. start semantic analysis
2798         startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2799
2800         // Reset resolver2 for each parse
2801         this.resolver2 = null;
2802
2803         // Determine effective resolver type (instance setting takes precedence over TBaseType)
2804         EResolverType effectiveResolverType = this.resolverType;
2805         if (effectiveResolverType == EResolverType.DEFAULT) {
2806             // Fall back to TBaseType settings for backward compatibility
2807             if (TBaseType.isEnableResolver2()) {
2808                 effectiveResolverType = EResolverType.RESOLVER2;
2809             } else if (TBaseType.isEnableResolver()) {
2810                 effectiveResolverType = EResolverType.RESOLVER;
2811             } else {
2812                 effectiveResolverType = EResolverType.NONE;
2813             }
2814         }
2815
2816         // Run the appropriate resolver based on effective type
2817         if (getErrorCount() == 0) {
2818             switch (effectiveResolverType) {
2819                 case RESOLVER:
2820                     TSQLResolver resolver = new TSQLResolver(globalContext, sqlstatements);
2821                     if (!resolver.resolve()) {
2822                         // Handle resolution errors
2823                         // addErrors(resolver.getLog().getErrors());
2824                     }
2825                     break;
2826
2827                 case RESOLVER2:
2828                     // Use provided config or create default
2829                     TSQLResolverConfig config = this.resolver2Config;
2830                     if (config == null) {
2831                         config = new TSQLResolverConfig();
2832                     }
2833                     // Always set vendor on config (needed for vendor-specific resolution like struct-field fallback)
2834                     config.setVendor(dbVendor);
2835                     // Pass null as globalContext to match original TSQLResolver2 behavior
2836                     this.resolver2 = new TSQLResolver2(null, sqlstatements, config);
2837                     // Pass sqlEnv to resolver2 for metadata lookup
2838                     if (this.sqlEnv != null) {
2839                         this.resolver2.setSqlEnv(this.sqlEnv);
2840                     }
2841                     if (!this.resolver2.resolve()) {
2842                         // Handle resolution errors if needed
2843                     }
2844                     break;
2845
2846                 case NONE:
2847                 default:
2848                     // No resolution
2849                     break;
2850             }
2851         }
2852
2853         if (enableTimeLogging) {
2854             endTime = System.currentTimeMillis();
2855             semanticAnalysisTime += (endTime - startTime);
2856         }
2857 
2858         // 4. start interpreter
2859         startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2860         
2861         if ((TBaseType.ENABLE_INTERPRETER) && (getErrorCount() == 0)) {
2862             TLog.clearLogs();
2863             TGlobalScope globalScope = new TGlobalScope(sqlEnv);
2864             TLog.enableInterpreterLogOnly();
2865 
2866             TASTEvaluator astEvaluator = new TASTEvaluator(this.sqlstatements, globalScope);
2867             astEvaluator.eval();
2868         }
2869         
2870         if (enableTimeLogging) {
2871             endTime = System.currentTimeMillis();
2872             interpreterTime += (endTime - startTime);
2873         }
2874 
2875         return getErrorCount();
2876     }
2877
2878     
2879void copyerrormsg(TCustomSqlStatement sql){
2880    for (int i = 0; i<sql.getSyntaxErrors().size(); i++){
2881            this.syntaxErrors.add(new TSyntaxError( (TSyntaxError)sql.getSyntaxErrors().get(i) ));
2882    }
2883//    for (int i = 0; i<sql.getSyntaxHints().size(); i++){
2884//            this.syntaxHints.add(new TSyntaxError( (TSyntaxError)sql.getSyntaxHints().get(i) ));
2885//    }
2886}
2887
2888private static String calculateLicenseKey(boolean ignoreMachineId){
2889
2890    if (userName == null) return null;
2891    if (machineId == null) return null;
2892
2893    byte[] bytesOfMessage=null;
2894    String licenseStr = "I love sql pretty printer, yeah!"+userName.toLowerCase();
2895    if (!ignoreMachineId){
2896        licenseStr += machineId.toLowerCase();
2897    }
2898    try {
2899        bytesOfMessage = licenseStr.getBytes("UTF-8");
2900    } catch (UnsupportedEncodingException e) {
2901        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
2902    }
2903
2904    MessageDigest md = null;
2905    try {
2906        md = MessageDigest.getInstance("MD5");
2907    } catch (NoSuchAlgorithmException e) {
2908        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
2909    }
2910    byte[] digest = md.digest(bytesOfMessage);
2911
2912    return null;//HardwareBinder.getHex(digest);
2913}
2914
2915private static boolean validateLicense(){
2916
2917    int ret = 0;
2918    return ret == 0;
2919
2920}
2921
2922private static boolean check_license_time(){
2923
2924    boolean  ret = false;
2925
2926    String toDate = TBaseType.license_expired_date;
2927
2928    DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
2929    Calendar currDtCal = Calendar.getInstance();
2930
2931    // Zero out the hour, minute, second, and millisecond
2932    currDtCal.set(Calendar.HOUR_OF_DAY, 0);
2933    currDtCal.set(Calendar.MINUTE, 0);
2934    currDtCal.set(Calendar.SECOND, 0);
2935    currDtCal.set(Calendar.MILLISECOND, 0);
2936
2937    Date currDt = currDtCal.getTime();
2938
2939    Date toDt;
2940    try {
2941        toDt = df.parse(toDate);
2942    } catch (ParseException e) {
2943        toDt = null;
2944        // Print some error message back to the user
2945    }
2946
2947    if (toDt != null){
2948        int results = toDt.compareTo(currDt);
2949        ret = results > 0;
2950    }
2951
2952    return ret;
2953}
2954
2955    public void setTeradataUtilityType(TeradataUtilityType teradataUtilityType) {
2956        if ((this.getFlexer() != null) && (this.getFlexer() instanceof TLexerTeradata)){
2957            ((TLexerTeradata)this.getFlexer()).setTeradataUtilityType(teradataUtilityType);
2958        }
2959    }
2960    
2961    /**
2962     * Dispose of parser resources and clean up references.
2963     * 
2964     * <p>This method releases internal resources used by the parser.
2965     * After calling this method, the parser should not be used further.</p>
2966     * 
2967     * <p>If a ManagedSourceBuffer was set, the source text remains in the buffer
2968     * so that tokens can still access it. The user is responsible for calling 
2969     * {@link ManagedSourceBuffer#release()} when all tokens are no longer needed.</p>
2970     * 
2971     * <p>Note: Tokens created by this parser will remain usable after dispose()
2972     * if a ManagedSourceBuffer was used, as they reference the buffer by ID
2973     * rather than holding direct parser references.</p>
2974     * 
2975     * @since 3.1.0.9
2976     */
2977    public void dispose() {
2978        // Note: We intentionally do NOT remove the source from managed buffer
2979        // This allows tokens to remain usable after parser disposal
2980        // The user must call buffer.release() when done with all tokens
2981        
2982        // Clear references to help GC
2983        flexer = null;
2984        fparser = null;
2985        fplsqlparser = null;
2986        finputstream = null;
2987        sqlInputStream = null;
2988        gcurrentsqlstatement = null;
2989        nextStmt = null;
2990        sqlstatements = null;
2991        sourcetokenlist = null;
2992        syntaxErrors = null;
2993    }
2994
2995}