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    private ISQLStatementHandle sqlStatementHandle = null;
920
921    public void setSqlStatementHandle(ISQLStatementHandle sqlStatementHandle) {
922        this.sqlStatementHandle = sqlStatementHandle;
923    }
924
925    private ITokenHandle tokenHandle = null;
926
927    /**
928     * Set an event handler which will be fired when a new source token is created by the lexer during tokenize the
929     * input SQL.
930     *
931     * @param tokenHandle the event handler to process the new created source token
932     */
933    public void setTokenHandle(ITokenHandle tokenHandle) {
934        this.tokenHandle = tokenHandle;
935    }
936
937    private ITokenListHandle tokenListHandle = null;
938
939    public void setTokenListHandle(ITokenListHandle tokenListHandle) {
940        this.tokenListHandle = tokenListHandle;
941    }
942
943    private IMetaDatabase metaDatabase = null;
944
945    /**
946     * @deprecated As of v2.0.3.1, please use {@link #getSqlEnv()} instead
947     *
948     *  set an instance of a class which implement the interface: {@link IMetaDatabase}.
949     *  The parser will call {@link IMetaDatabase#checkColumn} method when it needs to know
950     *  whether a column is belonged to a table.
951     *  <p></p>
952     *  The class that implements the interface: {@link IMetaDatabase} usually fetch the metadata from the database
953     *  by connecting to a database instance.
954     *  <p></p>
955     *  If the class is not provided, the parser has to guess the relationship between a un-qualified column and table
956     *  in the input SQL which may lead to a un-determined result between the column and table.
957     *
958     * @param metaDatabase a new instance of the class which implements the {@link IMetaDatabase} interface
959     * @see IMetaDatabase
960     */
961    public void setMetaDatabase(IMetaDatabase metaDatabase) {
962        this.metaDatabase = metaDatabase;
963    }
964
965    /**
966     * @deprecated As of v2.0.3.1, please use {@link #getSqlEnv()} instead
967     *
968     * a new instance of the class which implements the {@link IMetaDatabase} interface
969     *
970     * @return a new instance of the class which implements the {@link IMetaDatabase} interface
971     * @see #setMetaDatabase
972     */
973    public IMetaDatabase getMetaDatabase() {
974
975        return metaDatabase;
976    }
977
978    /**
979     * Not used.
980     *
981     */
982    public void freeParseTable() {
983        flexer.yystack = null;
984        flexer.yytextbuf = null;
985        flexer.buf = null;
986
987//        TLexerOracle.yyk = null;
988//        TLexerOracle.yykl = null;
989//        TLexerOracle.yykh = null;
990//        TLexerOracle.yym = null;
991//        TLexerOracle.yyml = null;
992//        TLexerOracle.yymh = null;
993//        TLexerOracle.yyt = null;
994//        TLexerOracle.yytl = null;
995//        TLexerOracle.yyth = null;
996//        TParserOracleSql.yyah = null;
997//        TParserOracleSql.yyal = null;
998//        TParserOracleSql.yygh = null;
999//        TParserOracleSql.yygl = null;
1000//        TParserOracleSql.yyd = null;
1001//        TParserOracleSql.yya_sym= null;
1002//        TParserOracleSql.yya_act= null;
1003//        TParserOracleSql.yyr_len= null;
1004//        TParserOracleSql.yyr_sym= null;
1005//        TParserOracleSql.yyg_sym= null;
1006//        TParserOracleSql.yyg_act= null;
1007//
1008//        TParserOraclePLSql.yyah = null;
1009//        TParserOraclePLSql.yyal = null;
1010//        TParserOraclePLSql.yygh = null;
1011//        TParserOraclePLSql.yygl = null;
1012//        TParserOraclePLSql.yyd = null;
1013//        TParserOraclePLSql.yya_sym= null;
1014//        TParserOraclePLSql.yya_act= null;
1015//        TParserOraclePLSql.yyr_len= null;
1016//        TParserOraclePLSql.yyr_sym= null;
1017//        TParserOraclePLSql.yyg_sym= null;
1018//        TParserOraclePLSql.yyg_act= null;
1019//
1020//        TLexerMssql.yyk = null;
1021//        TLexerMssql.yykl = null;
1022//        TLexerMssql.yykh = null;
1023//        TLexerMssql.yym = null;
1024//        TLexerMssql.yyml = null;
1025//        TLexerMssql.yymh = null;
1026//        TLexerMssql.yyt = null;
1027//        TLexerMssql.yytl = null;
1028//        TLexerMssql.yyth = null;
1029//        TParserMssqlSql.yyah = null;
1030//        TParserMssqlSql.yyal = null;
1031//        TParserMssqlSql.yygh = null;
1032//        TParserMssqlSql.yygl = null;
1033//        TParserMssqlSql.yyd = null;
1034//        TParserMssqlSql.yya_sym= null;
1035//        TParserMssqlSql.yya_act= null;
1036//        TParserMssqlSql.yyr_len= null;
1037//        TParserMssqlSql.yyr_sym= null;
1038//        TParserMssqlSql.yyg_sym= null;
1039//        TParserMssqlSql.yyg_act= null;
1040//
1041//        TLexerMysql.yyk = null;
1042//        TLexerMysql.yykl = null;
1043//        TLexerMysql.yykh = null;
1044//        TLexerMysql.yym = null;
1045//        TLexerMysql.yyml = null;
1046//        TLexerMysql.yymh = null;
1047//        TLexerMysql.yyt = null;
1048//        TLexerMysql.yytl = null;
1049//        TLexerMysql.yyth = null;
1050//        TParserMysqlSql.yyah = null;
1051//        TParserMysqlSql.yyal = null;
1052//        TParserMysqlSql.yygh = null;
1053//        TParserMysqlSql.yygl = null;
1054//        TParserMysqlSql.yyd = null;
1055//        TParserMysqlSql.yya_sym= null;
1056//        TParserMysqlSql.yya_act= null;
1057//        TParserMysqlSql.yyr_len= null;
1058//        TParserMysqlSql.yyr_sym= null;
1059//        TParserMysqlSql.yyg_sym= null;
1060//        TParserMysqlSql.yyg_act= null;
1061    }
1062
1063    /**
1064     *  Class constructor, create a new instance of the parser by setting the database vendor
1065     *
1066     * @param pdbvendor the database vendor whose grammar rule will be used to validate the syntax of the input SQL
1067     */
1068    public TGSqlParser(EDbVendor pdbvendor) {
1069        dbVendor = pdbvendor;
1070        sqltext = "";
1071        sqlfilename = "";
1072
1073        delimiterchar = ';';
1074        defaultDelimiterStr = ";";
1075
1076        // Most vendors are now delegated to vendor-specific parsers via getOrCreateVendorParser().
1077        // This constructor only handles delimiter settings for vendors with non-default delimiters.
1078        // Lexer/parser initialization is handled by getOrCreateVendorParser() for all vendors.
1079        switch(pdbvendor){
1080            case dbvazuresql:
1081                dbVendor = EDbVendor.dbvmssql;
1082                break;
1083            case dbvoracle:
1084            case dbvteradata:
1085            case dbvpostgresql:
1086            case dbvduckdb:
1087            case dbvredshift:
1088            case dbvgreenplum:
1089                delimiterchar = '/';
1090                break;
1091            case dbvdameng:
1092                if (TBaseType.enterprise_edition || (!TBaseType.full_edition) || TBaseType.dameng_edition) {
1093                    delimiterchar = '/';
1094                }
1095                break;
1096            case dbvdb2:
1097                delimiterchar = '@';
1098                break;
1099            case dbvmysql:
1100                delimiterchar = '$';
1101                defaultDelimiterStr = "$";
1102                break;
1103            case dbvoceanbase:
1104                // Effective delimiter is mode-dependent and resolved inside
1105                // OceanBaseSqlParser based on EOBTenantMode: ';' for MYSQL/SYSTEM,
1106                // '/' for ORACLE PL/SQL blocks. The '$' default mirrors MySQL's
1107                // DELIMITER override behavior so multi-statement scripts split
1108                // correctly under MYSQL mode (the default).
1109                delimiterchar = '$';
1110                defaultDelimiterStr = "$";
1111                break;
1112            case dbvdoris:
1113                if (TBaseType.enterprise_edition || (!TBaseType.full_edition) || TBaseType.doris_edition) {
1114                    delimiterchar = ';';
1115                }
1116                break;
1117            case dbvstarrocks:
1118                if (TBaseType.enterprise_edition || (!TBaseType.full_edition) || TBaseType.starrocks_edition) {
1119                    delimiterchar = ';';
1120                }
1121                break;
1122            default:
1123                // All other vendors use default delimiter (;)
1124                break;
1125        }
1126
1127        // fparser is null here - it will be set later in doDelegatedRawParse() from vendor parser result
1128        // All vendors are now delegated to vendor-specific parsers via getOrCreateVendorParser()
1129
1130        sourcetokenlist = new TSourceTokenList();
1131        sourcetokenlist.setGsqlparser(this);
1132        sqlstatements = new TStatementList();
1133        // Inject vendor-specific command resolver
1134        sqlcmds = SqlCmdsFactory.get(dbVendor);
1135        sqlpluskeywordList = new HashMap();
1136        syntaxErrors = new ArrayList();
1137
1138        errormessage = "";
1139
1140        if (TBaseType.license_expired_check){
1141            if (!check_license_time()) {
1142                flexer = null;
1143                fparser = null;
1144            }
1145        }
1146
1147        TGSqlParser.setCurrentDBVendor(dbVendor);
1148    }
1149
1150    TContext globalContext;
1151
1152
1153    /**
1154     * Create a select statement object from the parameter: subquery
1155     *
1156     * @param subquery a plain text select statement which need to be converted to a {@link gudusoft.gsqlparser.stmt.TSelectSqlStatement} object
1157     * @return a select statement object, return null if the input select string is syntax invalid.
1158     */
1159    public TSelectSqlStatement parseSubquery(String subquery){
1160        return parseSubquery(this.dbVendor,subquery);
1161    }
1162
1163    /**
1164     * this method is thread safe.
1165     * 
1166     * C:\prg\gsp_java\gsp_java_core\src\test\java\gudusoft\gsqlparser\ParseSubqueryThreadSafetyTest.java
1167     * 
1168     */
1169    public static TSelectSqlStatement parseSubquery(EDbVendor dbVendor, String subquery){
1170//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1171//        localParser.sqltext = subquery;
1172//        int iRet = localParser.doparse();
1173//        if (iRet != 0) {
1174//            return null;
1175//        }
1176
1177        TSingletonParser singletonParser = TSingletonParser.getInstance();
1178        TStatementList statements = singletonParser.getStmts(dbVendor,subquery);
1179        if (statements.size() == 0) return null;
1180
1181        return (TSelectSqlStatement)statements.get(0);
1182    }
1183
1184    /**
1185     * Create an expression object from the parameter: expr
1186     *
1187     * @param expr a plain text expression which will be converted to {@link gudusoft.gsqlparser.nodes.TExpression} object
1188     * @return an expression object, return null if the input expression string is not syntax valid.
1189     */
1190    public TExpression parseExpression(String expr){
1191        return parseExpression(this.dbVendor,expr);
1192    }
1193
1194    /*
1195     * this method is thread safe.
1196     * 
1197     * getStmts() is synchronized - only one thread can execute it at a time
1198     * getParser() is also synchronized
1199     * Each thread gets a new TStatementList instance
1200
1201     * this is the test case for this method.
1202     * C:\prg\gsp_java\gsp_java_core\src\test\java\gudusoft\gsqlparser\ParseExpressionThreadSafetyTest.java
1203     */
1204    public static TExpression parseExpression(EDbVendor dbVendor, String expr){
1205        TSingletonParser singletonParser = TSingletonParser.getInstance();
1206
1207        boolean e = TBaseType.isEnableResolver();
1208        TBaseType.setEnableResolver(false);
1209        TStatementList statements;
1210        try{
1211            statements  = singletonParser.getStmts(dbVendor,"select 1 from t where "+TBaseType.newline+expr);
1212        }finally {
1213            TBaseType.setEnableResolver(e);
1214        }
1215
1216        if (statements.size() == 0) return null;
1217
1218//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1219//        localParser.sqltext = "select 1 from t where "+TBaseType.newline+expr;
1220//        int iRet = localParser.doparse();
1221//        if (iRet != 0) {
1222//            return null;
1223//        }
1224
1225        return ((TSelectSqlStatement)statements.get(0)).getWhereClause().getCondition();
1226    }
1227
1228    /**
1229     *  Create a function object from the parameter: newFunction
1230     *
1231     * @param newFunction a plain text function which will be converted to {@link gudusoft.gsqlparser.nodes.TFunctionCall} object
1232     * @return a function object, or return null if the input string is not a valid function call.
1233     */
1234    public TFunctionCall parseFunctionCall(String newFunction){
1235        return parseFunctionCall(this.dbVendor,newFunction);
1236    }
1237
1238    public static TFunctionCall parseFunctionCall(EDbVendor dbVendor, String newFunction){
1239        TSingletonParser singletonParser = TSingletonParser.getInstance();
1240        TStatementList statements = singletonParser.getStmts(dbVendor,"select"+TBaseType.newline+newFunction+TBaseType.newline+"from t");
1241        if (statements.size() == 0) return null;
1242
1243
1244//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1245//        localParser.sqltext = "select"+TBaseType.newline+newFunction+TBaseType.newline+"from t";
1246//        int iRet = localParser.doparse();
1247//        if (iRet!= 0) {
1248//            return null;
1249//        }
1250
1251        return statements.get(0).getResultColumnList().getResultColumn(0).getExpr().getFunctionCall();
1252    }
1253
1254    /**
1255     * Create a database objectName from the parameter: newObjectName
1256     *
1257     * @param newObjectName a plain text objectName which will be converted to {@link gudusoft.gsqlparser.nodes.TObjectName} object
1258     * @return a database objectName object, return null is the input string is not a valid objectName.
1259     */
1260    public TObjectName parseObjectName(String newObjectName){
1261        //return parseObjectName(this.dbVendor,newObjectName);
1262        return TObjectName.createObjectName(this.dbVendor,EDbObjectType.column,newObjectName);
1263    }
1264
1265
1266
1267    /**
1268     *
1269     * @param dbVendor
1270     * @param newObjectName
1271     * @return
1272     */
1273    public static TObjectName parseObjectName(EDbVendor dbVendor, String newObjectName){
1274
1275        TSingletonParser singletonParser = TSingletonParser.getInstance();
1276        TStatementList statements = singletonParser.getStmts(dbVendor,"select"+TBaseType.newline+newObjectName+TBaseType.newline+"from t");
1277        if (statements.size() == 0) return null;
1278        TExpression e = statements.get(0).getResultColumnList().getResultColumn(0).getExpr();
1279
1280//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1281//        localParser.sqltext = "select"+TBaseType.newline+newObjectName+TBaseType.newline+"from t";
1282//        int iRet = localParser.doparse();
1283//        if (iRet!= 0) {
1284//            return null;
1285//        }
1286//        TExpression e = ((TSelectSqlStatement)localParser.sqlstatements.get(0)).getResultColumnList().getResultColumn(0).getExpr();
1287
1288        TObjectName lcResult = null;
1289        switch (e.getExpressionType()){
1290            case simple_object_name_t:
1291                lcResult = e.getObjectOperand();
1292                break;
1293            case simple_constant_t:
1294                lcResult = new TObjectName();
1295                lcResult.init(e.getConstantOperand().getValueToken());
1296                break;
1297            default:
1298                break;
1299        }
1300
1301        return lcResult;
1302    }
1303
1304    /**
1305     * Create an constant object from the parameter: newConstant
1306     *
1307     * @param newConstant a plian text constant which will be converted to {@link gudusoft.gsqlparser.nodes.TConstant} object
1308     * @return new constant object, returns null if the input is not a valid constant string.
1309     */
1310    public TConstant parseConstant(String newConstant){
1311        return parseConstant(this.dbVendor,newConstant);
1312    }
1313
1314    public static TConstant parseConstant(EDbVendor dbVendor, String newConstant){
1315//        TGSqlParser localParser = new TGSqlParser(dbVendor);
1316//        localParser.sqltext = "select"+TBaseType.newline+newConstant+TBaseType.newline+"from t";
1317//        int iRet = localParser.doparse();
1318//        if (iRet!= 0) {
1319//            return null;
1320//        }
1321
1322        TSingletonParser singletonParser = TSingletonParser.getInstance();
1323        TStatementList statements = singletonParser.getStmts(dbVendor,"select"+TBaseType.newline+newConstant+TBaseType.newline+"from t");
1324        if (statements.size() == 0) return null;
1325
1326        return statements.get(0).getResultColumnList().getResultColumn(0).getExpr().getConstantOperand();
1327    }
1328
1329
1330    /**
1331     * The total number of syntax errors founded in the input SQL script.
1332     * <br>Only the first syntax error in a SQL statement is counted if there are more than one syntax errors in
1333     * a single SQL statement.
1334     * <br> In a SQL script, only the first syntax error in each SQL statement is counted.
1335     *
1336     * @return The total number of syntax errors founded in the input SQL script. Returns 0 if no syntax error is founded.
1337     */
1338    public int getErrorCount(){
1339        return   syntaxErrors.size();
1340    }
1341
1342    /**
1343     * The text of error message generated by iterating all items in {@link #getSyntaxErrors}.
1344     * User may generate error message in their own format by iterating all items in {@link #getSyntaxErrors}.
1345     *
1346     * @return error message
1347     */
1348    public String getErrormessage(){
1349
1350        String s="",hint="Syntax error";
1351        TSyntaxError t;
1352        for (int i= 0; i< syntaxErrors.size(); i++)
1353        {
1354            t = (TSyntaxError) syntaxErrors.get(i);
1355            if (t.hint.length() > 0) hint = t.hint;
1356            s= s+hint+"("+t.errorno+") near: "+t.tokentext;
1357            s=s+"("+t.lineNo;
1358            s=s+","+t.columnNo +", token code:"+t.tokencode+")";
1359                //s=s+" expected tokentext:"+t.hint;
1360
1361           // break;//get only one message, remove this one and uncomment next line to get all error messages
1362            if (i !=  syntaxErrors.size() - 1)
1363              s = s +TBaseType.linebreak;
1364        }
1365
1366        if (errormessage.length() > 0){
1367            s = errormessage+TBaseType.linebreak+s; 
1368        }
1369        return s;
1370
1371    }
1372
1373    /**
1374     * check syntax of the input SQL. This method works exactly the same as {@link #parse} method.
1375     *
1376     * @return   0 means parse SQL script successfully, otherwise, use {@link #getErrorCount}, {@link #getErrormessage}
1377     * to get detailed error information.
1378     * @see #parse
1379     */
1380    public int checkSyntax(){
1381        return doparse();
1382    }
1383
1384    /**
1385     *   Check syntax of the input SQL, doing some kind of semantic analysis without connecting to a real database.
1386     *   <p></p>
1387     *  This method will do a in-depth analysis of the input SQL such as building the link between table and columns.
1388     *  The parse tree of the input SQL is available after calling this method.
1389     *
1390     *  The parser checks the syntax of those SQL statements one by one. If syntax error is found in a SQL statement,
1391     *  an error will be logged, no parse tree will be built for this SQL statement,
1392     *  the error message can be fetched using the {@link #getErrormessage()} method.
1393     *  <p></p>
1394     *  The syntax error in one SQL statement doesn't prevent the parser continue to check the syntax of the next SQL statement.
1395     *  After checking syntax of all SQL statements, use the {@link #getErrorCount()} method to get the total number of errors.
1396     *  <p></p>
1397     *  A syntax error in a SQL stored procedure will cease this parser to check syntax of the rest SQL statements
1398     *  in this stored procedure.
1399     *
1400     * @return   0 means parse SQL script successfully, otherwise, use {@link #getErrorCount}, {@link #getErrormessage}
1401     * to get detailed error information.
1402     * @see #getSyntaxErrors
1403     */
1404
1405    public int parse(){
1406        return doparse();
1407    }
1408
1409    public int validate(){
1410        if (sqlstatements.size() == 0) return 0;
1411
1412        TRelationValidator relationValidate = new TRelationValidator(globalContext);
1413        for(TCustomSqlStatement sqlStatement:sqlstatements){
1414            sqlStatement.acceptChildren(relationValidate);
1415        }
1416        return 0;
1417    }
1418
1419    void setdelimiterchar(char ch){
1420        delimiterchar = ch;
1421        flexer.delimiterchar = ch;
1422    }
1423    char curdelimiterchar;
1424    String userDelimiterStr ="";
1425
1426    boolean includesqlstatementtype(ESqlStatementType search, ESqlStatementType[] src){
1427        boolean ret = false;
1428        for(int i=0;i<src.length;i++){
1429            if (src[i] == search){
1430                ret = true;
1431                break;
1432            }
1433        }
1434        return ret;
1435    }
1436
1437    // private int getfileEncodingType(FileInputStream inputStream){
1438    //     BufferedInputStream fr = new BufferedInputStream(inputStream,8);
1439    //     return getfileEncodingType(fr);
1440    // }
1441
1442    private int getfileEncodingType(BufferedInputStream fr){
1443        int ret = 0; // default, 1: utf-16, 2: utf-32
1444       // BufferedInputStream fr = new BufferedInputStream(inputStream,8);
1445        try {
1446            byte[] bom = new byte[4];
1447            fr.mark(bom.length+1);
1448
1449            fr.read(bom,0,bom.length);
1450            if ( ((bom[0] == (byte)0xFF) && (bom[1] == (byte)0xFE))
1451                    ||((bom[0] == (byte)0xFE) && (bom[1] == (byte)0xFF))
1452                    )
1453            {
1454                ret = 1;
1455                if ( ((bom[2] == (byte)0xFF) && (bom[3] == (byte)0xFE))
1456                        ||((bom[2] == (byte)0xFE) && (bom[3] == (byte)0xFF))
1457                        ){
1458                    ret = 2;
1459                }
1460            }else{
1461                if ((bom[0] == (byte)0xEF) && (bom[1] == (byte)0xBB)&& (bom[2] == (byte)0xBF)){
1462                    ret = 3; //UTF-8,EF BB BF
1463                }
1464            }
1465            fr.reset();
1466        } catch (FileNotFoundException e) {
1467            // e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1468        } catch (IOException e) {
1469            //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1470        }
1471        return ret;
1472    }
1473
1474    // private int getfileEncodingType(String fn){
1475    //     int ret = 0; // default, 1: utf-16, 2: utf-32
1476    //     try {
1477    //         FileInputStream fr =   new FileInputStream(fn);
1478    //         ret = getfileEncodingType(fr);
1479    //         fr.close();
1480
1481    //     } catch (FileNotFoundException e) {
1482    //        // e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1483    //     } catch (IOException e) {
1484    //         //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1485    //     }
1486    //     return ret;
1487    // }
1488
1489    int readsql(){
1490        int ret  = 0;
1491
1492        syntaxErrors.clear();
1493        //syntaxHints.clear();
1494
1495        if (((!TBaseType.full_edition))||(!TBaseType.need_license_file)){
1496            if (!TBaseType.need_license_file){
1497                licenseType = TBaseType.license_type_dist;
1498                userName = "dist";
1499            }else {
1500                licenseType = TBaseType.license_type_trial;
1501                userName = TBaseType.license_trail_username;
1502            }
1503           // machineId = new HardwareBinder().getMachineIdString();
1504        }else {
1505            if (!licenseOK){
1506                errormessage = licenseMessage;
1507                return -1;
1508            }
1509        }
1510
1511        try{
1512            if (finputstream != null) finputstream.close();
1513            if (flexer == null){
1514                ret = -1;
1515                errormessage = "requested database not supported:"+this.dbVendor.toString();
1516                return ret;
1517            }
1518            if (flexer.yyinput != null)    flexer.yyinput.close();
1519
1520        }catch(IOException e){
1521            ret = -1;
1522            errormessage = "requested database not supported";
1523        }
1524        
1525        if (sqltext.length() > 0){
1526            finputstream = new BufferedReader(new StringReader(sqltext), TBaseType.LEXER_INPUT_BUFFER_SIZE);
1527            if ((!TBaseType.full_edition) && (sqltext.length() > TBaseType.query_size_limitation)){
1528                errormessage = TBaseType.trail_version_query_message;
1529                ret = -1;
1530            }
1531        }else if (sqlfilename.length() > 0){
1532            try{
1533
1534             streamFromSqlFile =   new FileInputStream(sqlfilename);
1535            // Buffer it for mark/reset support
1536            BufferedInputStream bufferedStream = new BufferedInputStream(streamFromSqlFile,8);             
1537            int encodingtype = getfileEncodingType(bufferedStream);
1538            streamFromSqlFile.getChannel().position(0);
1539
1540           if(encodingtype == 1){
1541                sqlStreamReader = new InputStreamReader(streamFromSqlFile,"UTF-16");
1542           }else if(encodingtype == 2){
1543                sqlStreamReader = new InputStreamReader(streamFromSqlFile,"UTF-32");
1544           }else if(encodingtype == 3){
1545              // System.out.println("utf-8");
1546                sqlStreamReader = new InputStreamReader(streamFromSqlFile,"UTF-8");
1547           }
1548           else{
1549               if (sqlCharset == null){
1550                   sqlCharset = Charset.defaultCharset().name();
1551                   //System.out.println("Charset used: "+Charset.defaultCharset().name());
1552               }
1553                sqlStreamReader = new InputStreamReader(streamFromSqlFile, sqlCharset);
1554              // isr = new InputStreamReader(fr,"Cp737");
1555           }
1556
1557            finputstream =  new BufferedReader(sqlStreamReader, TBaseType.LEXER_INPUT_BUFFER_SIZE);
1558            if (encodingtype == 3){
1559                //EF BB BF was not stripped by the InputStreamReader, so we do it
1560                finputstream.skip(1);
1561            }
1562
1563            if ((!TBaseType.full_edition)){
1564                File file = new File(sqlfilename);
1565                if (!file.exists() || !file.isFile()) {
1566                    ret = -1;
1567                    errormessage = "not a valid sql file.";
1568                }else{
1569                    if (file.length() > TBaseType.query_size_limitation){
1570                        errormessage = TBaseType.trail_version_file_message;
1571                        ret = -1;
1572                    }
1573                }
1574            }
1575
1576            }catch(FileNotFoundException e){
1577                ret = -1;
1578                errormessage = e.toString();
1579            } catch (UnsupportedEncodingException e) {
1580                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1581            } catch (IOException e) {
1582                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1583            }
1584
1585        }else if(this.sqlInputStream != null){
1586            int encodingtype = getfileEncodingType(sqlInputStream);
1587            InputStream fr =   sqlInputStream;
1588
1589            InputStreamReader isr = null;
1590            try{
1591
1592                if(encodingtype == 1){
1593                    isr = new InputStreamReader(fr,"UTF-16");
1594                }else if(encodingtype == 2){
1595                    isr = new InputStreamReader(fr,"UTF-32");
1596                }else if(encodingtype == 3){
1597                    // System.out.println("utf-8");
1598                    isr = new InputStreamReader(fr,"UTF-8");
1599                }
1600                else{
1601                    if (sqlCharset == null){
1602                        sqlCharset = Charset.defaultCharset().name();
1603                    }
1604
1605                    isr = new InputStreamReader(fr, sqlCharset);
1606                }
1607
1608                finputstream =  new BufferedReader(isr, TBaseType.LEXER_INPUT_BUFFER_SIZE);
1609                if (encodingtype == 3){
1610                    //EF BB BF was not stripped by the InputStreamReader, so we do it
1611                    finputstream.skip(1);
1612                }
1613
1614            }catch(FileNotFoundException e){
1615                ret = -1;
1616                errormessage = e.toString();
1617            } catch (UnsupportedEncodingException e) {
1618                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1619            } catch (IOException e) {
1620                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1621            }
1622
1623            try {
1624                if ((!TBaseType.full_edition) && (sqlInputStream.available() > TBaseType.query_size_limitation)){
1625                    errormessage = TBaseType.trail_version_query_message;
1626                    ret = -1;
1627                }
1628            } catch (IOException e) {
1629                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
1630            }
1631        }
1632
1633       if (finputstream == null) ret = -1;
1634
1635       if (ret == 0)
1636       {
1637           flexer.yyinput =  finputstream;
1638           flexer.setSqlCharset(this.sqlCharset);
1639           flexer.reset();
1640       }
1641
1642        sourcetokenlist.clear();
1643        sourcetokenlist.curpos = -1;
1644
1645       flexer.resetTokenTable();
1646
1647
1648        return ret;
1649    }
1650
1651    TSourceToken getanewsourcetoken(){
1652        TSourceToken pst = null,prevst;
1653        
1654        while (true) {
1655            pst = new TSourceToken("");
1656            if (flexer.yylexwrap(pst) == 0) { pst = null; break;}
1657            
1658            pst.setDbvendor(dbVendor);
1659            pst.tokenstatus = ETokenStatus.tsoriginal;
1660            if (pst.tokentype == ETokenType.ttreturn){
1661               pst.setAstext(towinlinebreak(pst.getAstext()));
1662            }
1663            //combine space && linebreak after a linebreak into one
1664            if ( (pst.tokentype == ETokenType.ttwhitespace)
1665             && (sourcetokenlist.curpos >= 0) )
1666              {
1667                prevst =  sourcetokenlist.get(sourcetokenlist.curpos);
1668                if ( prevst.tokentype == ETokenType.ttreturn )
1669                  {
1670                    //can't discard  whitespace after linebreak, it will be used
1671                    // to judge whether / at the { of the line is a sqlplus cmd or not
1672                    // check isvalidplacefordivtosqlpluscmd for more
1673                    prevst.setAstext(prevst.getAstext() + pst.getAstext());
1674                    //newst.free;
1675                    //newst = nil;
1676                    continue;
1677                  }
1678              }
1679
1680            if ( (pst.tokentype == ETokenType.ttreturn)
1681             && (sourcetokenlist.curpos >= 0) )
1682              {
1683                prevst =  sourcetokenlist.get(sourcetokenlist.curpos);
1684
1685                if ( prevst.tokentype == ETokenType.ttreturn )
1686                  {
1687                    prevst.setAstext(prevst.getAstext() + pst.getAstext());
1688                    //newst.free;
1689                    //newst = nil;
1690                    continue;
1691                  }
1692
1693                if ( prevst.tokentype == ETokenType.ttwhitespace )
1694                  {
1695                    //merge previous whitespace with this linebreak
1696                    //doesn't work for sqlplus cmd
1697
1698                   // prevst.sourcecode = newst.sourcecode;
1699                   // prevst.tokentype = newst.tokentype;
1700                   // prevst.tokencode = newst.tokencode;
1701                   // newst.free;
1702                   // newst = nil;
1703                   // continue;
1704
1705                   //prevst.prevsourcecode = prevst.astext;
1706                   //prevst.astext =  "";
1707                  }
1708              }
1709
1710            break;
1711
1712        }
1713
1714        if (pst != null){
1715            pst.container = sourcetokenlist;
1716            sourcetokenlist.curpos = sourcetokenlist.curpos+1;
1717            pst.posinlist = sourcetokenlist.curpos;
1718            if (tokenHandle != null){
1719                tokenHandle.processToken(pst);
1720            }
1721        }
1722
1723       // System.out.println(pst);
1724      //  flexer.setTokenTableValue(pst);
1725        return pst;
1726        
1727    }
1728
1729  String towinlinebreak(String s){
1730      return s;
1731// todo not implemented yet     
1732  }
1733
1734void checkconstarinttoken(TSourceToken lcprevtoken){
1735    TSourceTokenList lcStList = lcprevtoken.container;
1736    if (TBaseType.assigned(lcStList))
1737    {
1738         TSourceToken lcPPToken = lcStList.nextsolidtoken(lcprevtoken.posinlist,-2,false);
1739         if (TBaseType.assigned(lcPPToken))
1740        {
1741
1742             if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint"))
1743             {
1744                 //System.out.println(lcPPToken);
1745                 lcPPToken.tokencode = TBaseType.rw_constraint2;
1746             }
1747        }
1748    }
1749
1750}
1751
1752TSourceToken getprevtoken(TSourceToken ptoken){
1753   // ETokenType[] lcnonsolidtokenset = {ETokenType.ttwhitespace,ETokenType.ttreturn,ETokenType.ttsimplecomment,ETokenType.ttbracketedcomment} ;
1754    TSourceTokenList lcstlist = ptoken.container;
1755    if (TBaseType.assigned(lcstlist)){
1756        if ((ptoken.posinlist > 0)  && (lcstlist.size() > ptoken.posinlist-1))
1757        {
1758            if (!(
1759                    (lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttwhitespace)
1760                    ||(lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttreturn)
1761                    ||(lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttsimplecomment)
1762                    ||(lcstlist.get(ptoken.posinlist-1).tokentype ==  ETokenType.ttbracketedcomment)
1763                    )
1764                )
1765            {return lcstlist.get(ptoken.posinlist-1);}
1766            else{
1767            { return lcstlist.nextsolidtoken(ptoken.posinlist-1,-1,false);}
1768        }
1769        }
1770    }
1771    
1772    return null;
1773}
1774
1775void  docommonsqltexttotokenlist(){
1776
1777    TSourceToken asourcetoken,lcprevst;
1778    int yychar;
1779
1780    asourcetoken = getanewsourcetoken();
1781    if ( asourcetoken == null ) return;
1782    yychar = asourcetoken.tokencode;
1783
1784    while (yychar > 0)
1785    {
1786        sourcetokenlist.add(asourcetoken);
1787        asourcetoken = getanewsourcetoken();
1788        if ( asourcetoken == null ) break;
1789        yychar = asourcetoken.tokencode;
1790    }
1791
1792}
1793void dosqltexttotokenlist(){
1794    // Legacy path: Only called for non-delegated vendors (e.g., dbvfirebird, dbvexasol)
1795    // All delegated vendors (mssql, access, generic, mysql, oracle, etc.) use getOrCreateVendorParser()
1796    docommonsqltexttotokenlist();
1797
1798    doAfterTokenize();
1799
1800    TBaseType.resetTokenChain(sourcetokenlist,0);
1801
1802    processTokensInTokenTable(dbVendor);
1803    processTokensBeforeParse(dbVendor);
1804
1805    closeFileStream();
1806
1807    if (tokenListHandle != null){
1808        tokenListHandle.processTokenList(sourcetokenlist);
1809    }
1810}
1811
1812void doAfterTokenize(){
1813
1814    int leftParenCount = 0;
1815    int rightParenCount = 0;
1816    int leftIndex = 0;
1817    int rightIndex = sourcetokenlist.size() - 1;
1818
1819    // Count opening parentheses at the beginning
1820    while (leftIndex < sourcetokenlist.size() && sourcetokenlist.get(leftIndex).tokencode == '(') {
1821        leftParenCount++;
1822        leftIndex++;
1823    }
1824
1825    // Count closing parentheses at the end
1826    while (rightIndex >= 0 && sourcetokenlist.get(rightIndex).tokencode == ')') {
1827        rightParenCount++;
1828        rightIndex--;
1829    }
1830
1831    // Set matching parentheses to be ignored
1832    int parensToIgnore = Math.min(leftParenCount, rightParenCount);
1833    // if there is a semicolon before the right parenthesis, set the semicolon to be ignored
1834    // mantisbt/view.php?id=3690
1835
1836    if ((parensToIgnore > 0) && (sourcetokenlist.get(sourcetokenlist.size() - 1 - (parensToIgnore - 1) - 1).tokencode == ';')){
1837        // set to whitespace that this semicolon will be ignored during getting raw sql
1838        sourcetokenlist.get(sourcetokenlist.size() - 1 - (parensToIgnore - 1) - 1).tokentype = ETokenType.ttwhitespace;
1839        // set to ignore by yacc that this semicolon will be ignored during parsing
1840        sourcetokenlist.get(sourcetokenlist.size() - 1 - (parensToIgnore - 1) - 1).tokenstatus = ETokenStatus.tsignorebyyacc;
1841    }
1842//    for (int i = 0; i < parensToIgnore; i++) {
1843//        //sourcetokenlist.get(i).tokenstatus = ETokenStatus.tsignorebyyacc;
1844//        sourcetokenlist.get(i).tokencode = TBaseType.lexspace;
1845//
1846//       // sourcetokenlist.get(sourcetokenlist.size() - 1 - i).tokenstatus = ETokenStatus.tsignorebyyacc;
1847//        sourcetokenlist.get(sourcetokenlist.size() - 1 - i).tokencode = TBaseType.lexspace;
1848//    }
1849
1850}
1851
1852
1853
1854void processTokensBeforeParse(EDbVendor dbVendor){
1855
1856        // 为确保性能,只在snowflake数据库中处理,因为目前只有snowflake数据库中有连续的分号有用户提出这个需求,其他数据库中暂时不处理
1857   if (dbVendor != EDbVendor.dbvsnowflake) return;
1858
1859        // mantisbt/view.php?id=3579
1860        // if there are consecutive semicolon tokens, mark the second semi colon token as deleted token
1861    for(int i=0;i<sourcetokenlist.size();i++){
1862        TSourceToken st = sourcetokenlist.get(i);
1863        if (st.tokencode == ';'){
1864            TSourceToken nextToken = st.nextSolidToken();
1865            if (nextToken != null){
1866                if (nextToken.tokencode == ';'){
1867                    nextToken.tokenstatus = ETokenStatus.tsdeleted;
1868                }
1869            }
1870        }
1871    }
1872
1873}
1874
1875void processTokensInTokenTable(EDbVendor dbVendor){
1876    // 获得所有token后,根据需要对token code进行预处理, token table是 TBaseType.TOKEN_TABLE
1877     long[][] TOKEN_TABLE1 = flexer.TOKEN_TABLE;
1878
1879    switch (dbVendor){
1880        case dbvbigquery:
1881        case dbvsnowflake:
1882            // case 1, DO 关键字如果没有发现对于的 FOR, WHILE 等关键字,把 DO 关键字的token code设置为 TBaseType.ident
1883
1884            if (TOKEN_TABLE1[TBaseType.rrw_do][0] > 0){
1885                if ((TOKEN_TABLE1[TBaseType.rrw_while][0] == 0)&&(TOKEN_TABLE1[TBaseType.rrw_for][0] == 0)){
1886                    for(int i=0;i<sourcetokenlist.size();i++){
1887                        TSourceToken st = sourcetokenlist.get(i);
1888                        if (st.tokencode == TBaseType.rrw_do){
1889                            st.tokencode = TBaseType.ident;
1890                        }
1891                    }
1892                }
1893            }
1894
1895            break;
1896    }
1897
1898}
1899
1900boolean isDollarFunctionDelimiter(int tokencode, EDbVendor dbVendor){
1901        return ((tokencode == TBaseType.rrw_postgresql_function_delimiter)&&(dbVendor == EDbVendor.dbvpostgresql))
1902                ||((tokencode == TBaseType.rrw_greenplum_function_delimiter)&&(dbVendor == EDbVendor.dbvgreenplum))
1903                ||((tokencode == TBaseType.rrw_redshift_function_delimiter)&&(dbVendor == EDbVendor.dbvredshift))
1904                ||((tokencode == TBaseType.rrw_snowflake_function_delimiter)&&(dbVendor == EDbVendor.dbvsnowflake));
1905}
1906
1907/**
1908 * Returns the 1-based line number of the end of the last SQL statement that
1909 * was successfully recognized during raw-statement separation (e.g. via
1910 * vendor-specific {@code do*getrawsqlstatements} routines).
1911 * <p>
1912 * The value corresponds to the ending line of the last validated statement in
1913 * the most recent parse operation. Any trailing, incomplete statement at the
1914 * end of the input is intentionally excluded and will not affect this value.
1915 * <p>
1916 * Notes:
1917 * <ul>
1918 * <li>The line number is relative to the current input provided to the
1919 * parser (not an absolute position in an external, larger file).</li>
1920 * <li>This is useful when splitting huge SQL files by safe statement
1921 * boundaries — callers can cut the source at this line without risking a
1922 * partial statement.</li>
1923 * </ul>
1924 *
1925 * @return the 1-based line number of the last validated statement's ending
1926 *         line, or {@code -1} if no statement has been validated yet
1927 */
1928public int getLastLineNoOfLastStatementBeenValidated(){
1929        if (lastTokenOfStatementBeenValidated != null){
1930            return (int)lastTokenOfStatementBeenValidated.lineNo;
1931        }
1932        return -1;
1933}
1934
1935private TSourceToken lastTokenOfStatementBeenValidated;
1936
1937
1938void doongetrawsqlstatementevent(TCustomSqlStatement pcsqlstatement){
1939        doongetrawsqlstatementevent(pcsqlstatement,false);
1940}
1941
1942void doongetrawsqlstatementevent(TCustomSqlStatement pcsqlstatement, boolean isLastSQL){
1943    pcsqlstatement.setGsqlparser(this);
1944    pcsqlstatement.parser = this.fparser;
1945    pcsqlstatement.plsqlparser = this.fplsqlparser;
1946    pcsqlstatement.setStartToken(pcsqlstatement.sourcetokenlist.get(0));
1947    pcsqlstatement.setEndToken(pcsqlstatement.sourcetokenlist.get(pcsqlstatement.sourcetokenlist.size()-1));
1948    sqlstatements.add(pcsqlstatement);
1949
1950    if (!isLastSQL){ // 最后一个语句没有经过验证,只是在结束的时候强行加入的,很有可能是语法不正确的,因此我们这里只记录非最后的语句
1951        lastTokenOfStatementBeenValidated = pcsqlstatement.getEndToken();
1952    }
1953
1954    // if stored procedure body is not written in sql or plsql, then, set the token in body to
1955    if (    ((this.dbVendor == EDbVendor.dbvpostgresql)||(this.dbVendor == EDbVendor.dbvgreenplum) ||(this.dbVendor == EDbVendor.dbvredshift) ||(this.dbVendor == EDbVendor.dbvsnowflake) )
1956             && (pcsqlstatement instanceof TRoutine)
1957       ){
1958         if (!((TRoutine)pcsqlstatement).isBodyInSQL()){
1959             TSourceToken st;
1960             boolean inBody = false;
1961             StringBuilder routineBodyBuilder = new StringBuilder();
1962             for(int i=0;i<pcsqlstatement.sourcetokenlist.size();i++){
1963                 st = pcsqlstatement.sourcetokenlist.get(i);
1964                 if (isDollarFunctionDelimiter(st.tokencode,this.dbVendor)
1965//                         ((st.tokencode == TBaseType.rrw_postgresql_function_delimiter)&&(this.dbVendor == EDbVendor.dbvpostgresql))
1966//                        ||((st.tokencode == TBaseType.rrw_greenplum_function_delimiter)&&(this.dbVendor == EDbVendor.dbvgreenplum))
1967//                         ||((st.tokencode == TBaseType.rrw_redshift_function_delimiter)&&(this.dbVendor == EDbVendor.dbvredshift))
1968//                         ||((st.tokencode == TBaseType.rrw_snowflake_function_delimiter)&&(this.dbVendor == EDbVendor.dbvsnowflake))
1969                 ){
1970                     if (!inBody){
1971                         inBody = true;
1972                         routineBodyBuilder.setLength(0);
1973                         routineBodyBuilder.append(st.toString());
1974                     }else{
1975                         inBody = false;
1976                         routineBodyBuilder.append(st.toString());
1977                         break;
1978                     }
1979                     continue;
1980                 }
1981
1982                 if (inBody){
1983                     st.tokencode = TBaseType.sqlpluscmd;
1984                     routineBodyBuilder.append(st.toString());
1985                 }
1986             }
1987
1988             ((TRoutine)pcsqlstatement).setRoutineBody(routineBodyBuilder.toString());
1989         }
1990    }
1991}
1992
1993    boolean checkTokenPairWithEnd(int tokencode){
1994        return    ((tokencode == TBaseType.rrw_if)||(tokencode == TBaseType.rrw_case)
1995                ||(tokencode == TBaseType.rrw_loop)||(tokencode == TBaseType.rrw_repeat)
1996                ||(tokencode == TBaseType.rrw_while)||(tokencode == TBaseType.rrw_for)
1997                ||(tokencode == TBaseType.rrw_case)
1998        );
1999    }
2000
2001    private TCustomSqlStatement startDaxStmt(TSourceToken currToken,TCustomSqlStatement currStmt){
2002        TCustomSqlStatement newStmt = null;
2003        if (currToken == null) return null;
2004        if ((currToken.tokencode == '=')&&(currToken.isFirstTokenOfLine())){
2005            currToken.tokencode = TBaseType.equal_start_expr;
2006            newStmt = new TDaxExprStmt(EDbVendor.dbvdax);
2007        }else if ((currToken.tokencode == TBaseType.rrw_dax_define)&&(currToken.isFirstTokenOfLine())){
2008            newStmt = new TDaxEvaluateStmt(EDbVendor.dbvdax);
2009            ((TDaxEvaluateStmt)newStmt).setStartWithDefine(true);
2010        }else if ((currToken.tokencode == TBaseType.rrw_dax_evaluate)&&(currToken.isFirstTokenOfLine())){
2011            if ((currStmt != null)&&(currStmt instanceof TDaxEvaluateStmt)){
2012                TDaxEvaluateStmt tmp = (TDaxEvaluateStmt)currStmt;
2013                if (tmp.isStartWithDefine()) return  null;
2014            }
2015            newStmt = new TDaxEvaluateStmt(EDbVendor.dbvdax);
2016        }
2017
2018        if (newStmt == null){
2019            // let's check is this the first token of query
2020            boolean isFirst = currToken.isFirstTokenOfLine();
2021            TSourceToken prevToken = currToken.prevSolidToken();
2022            if ((isFirst)&&(prevToken == null)){
2023                newStmt = new TDaxExprStmt(EDbVendor.dbvdax);
2024            }
2025        }
2026        return newStmt;
2027    }
2028
2029int dogetrawsqlstatements(){
2030    // This method should not be called - all vendors are now delegated to vendor-specific parsers.
2031    // If this is reached, it means a new vendor was added but not included in getOrCreateVendorParser().
2032    throw new IllegalStateException(
2033        "dogetrawsqlstatements() called for vendor " + dbVendor +
2034        ". All vendors should be delegated via getOrCreateVendorParser(). " +
2035        "Please add this vendor to the switch statement in getOrCreateVendorParser().");
2036}
2037
2038    /**
2039     *  separates the SQL statements in the input SQL script without doing syntax check.
2040     *  <p></p>
2041     *  Use the {@link #getSqlstatements()} method to get the list of SQL statements.
2042     *  The SQL statement object is the instance of the sub-class of {@link TCustomSqlStatement}, get SQL statement type
2043     *  via the {@link TCustomSqlStatement#sqlstatementtype} field, get string representation of
2044     *  each SQL statement via the {@link TCustomSqlStatement#toString} method.
2045     *  <p></p>
2046     *  All source tokens in this SQL statement
2047     *  is available by using {@link TCustomSqlStatement#sourcetokenlist} filed.
2048     *  Since no parse tree is built by calling this method, no further detailed information about the SQL statement is available.
2049     *
2050      * @return 0 if get SQL statements successfully
2051     */
2052public int getrawsqlstatements(){
2053    // Clear errors from any previous call on this instance
2054    syntaxErrors.clear();
2055
2056    // Check for vendor parser delegation (MSSQL, etc.)
2057    // This ensures raw-split logic is maintained in only one place (vendor parser)
2058    SqlParser vp = getOrCreateVendorParser();
2059    if (vp != null) {
2060        return doDelegatedRawParse(vp);
2061    }
2062
2063    // Legacy path for non-delegated vendors
2064    int ret = readsql();
2065    if (ret != 0) return ret;
2066    dosqltexttotokenlist();
2067
2068    return dogetrawsqlstatements();
2069}
2070
2071    /**
2072     *  turns the input SQL into a sequence of token which is the
2073     *  basic lexis element of SQL syntax. Token is categorized as keyword, identifier,
2074     *  number, operator, whitespace and other types. All source tokens can be fetched
2075     *  via the {@link #getSourcetokenlist()} method.
2076     *
2077     */
2078public void tokenizeSqltext(){
2079    // Check for vendor parser delegation (MSSQL, etc.)
2080    // This ensures tokenization logic is maintained in only one place (vendor parser)
2081    SqlParser vp = getOrCreateVendorParser();
2082    if (vp != null) {
2083        doDelegatedTokenize(vp);
2084        return;
2085    }
2086
2087    // Legacy path for non-delegated vendors
2088    getFlexer();
2089    readsql();
2090    dosqltexttotokenlist();
2091}
2092
2093/**
2094 * Delegate tokenization to vendor parser and backfill results.
2095 * Similar to doDelegatedRawParse but only performs tokenization.
2096 */
2097private void doDelegatedTokenize(SqlParser vendorParser) {
2098    // Build context for vendor parser
2099    ParserContext context = buildContext();
2100
2101    // Delegate tokenization to vendor parser
2102    SqlParseResult tokenResult = vendorParser.tokenize(context);
2103
2104    // Copy results back to TGSqlParser fields for backward compatibility
2105    if (tokenResult.getSourceTokenList() != null) {
2106        this.sourcetokenlist = tokenResult.getSourceTokenList();
2107    }
2108    if (tokenResult.getLexer() != null) {
2109        this.flexer = tokenResult.getLexer();
2110    }
2111}
2112
2113
2114
2115void findAllSyntaxErrorsInPlsql(TCustomSqlStatement psql){
2116    if (psql.getErrorCount() > 0){
2117        copyerrormsg(psql);
2118    }
2119
2120    for (int k=0;k<psql.getStatements().size();k++){
2121        findAllSyntaxErrorsInPlsql(psql.getStatements().get(k));
2122    }
2123
2124}
2125
2126private TSQLEnv sqlEnv = null;
2127
2128    /**
2129     * SQL environment includes the database metadata such as procedure, function, trigger, table and etc.
2130     *
2131     * In order to link column to table correctly without connecting to database,
2132     * we need to provide a class which implements {@link TSQLEnv} to TGSqlParser.
2133     * this class tells TGSqlParser the relationship between column and table.
2134     *
2135     * <p>Take this SQL for example:
2136     * <pre>
2137     * SELECT Quantity,b.Time,c.Description
2138     * FROM
2139     * (SELECT ID2,Time FROM bTab) b
2140     * INNER JOIN aTab a on a.ID=b.ID
2141     * INNER JOIN cTab c on a.ID=c.ID
2142     * </pre>
2143     *
2144     * <p>General SQL Parser can build relationship between column: ID2 and table: bTable
2145     * correctly without metadata information from database because there is only one table
2146     * in from clause. But it can't judge column: Quantity belong to table: aTab or cTab,
2147     * since no table alias was prefixed to column: Quantity. If no metadata provided,
2148     * General SQL Parser will link column: Quantity to the first valid table (here it is aTab)
2149     *
2150     * <p>If we create a class  TRealDatabaseSQLEnv implements {@link TSQLEnv},then
2151     *  {@link #setSqlEnv(TSQLEnv)}, General SQL Parser can take this advantage to create
2152     *  a correct relationship between column and tables.
2153     *
2154     *<pre>
2155     * class TSQLServerEnv extends TSQLEnv{
2156     *
2157     *  public TSQLServerEnv(){
2158     *          super(EDbVendor.dbvmssql);
2159     *          initSQLEnv();
2160     *        }
2161     *
2162     *    &#64;Override
2163     *    public void initSQLEnv() {
2164     *
2165     *          // add a new database: master
2166     *          TSQLCatalog sqlCatalog = createSQLCatalog("master");
2167     *          // add a new schema: dbo
2168     *          TSQLSchema sqlSchema = sqlCatalog.createSchema("dbo");
2169     *          //add a new table: aTab
2170     *          TSQLTable aTab = sqlSchema.createTable("aTab");
2171     *          aTab.addColumn("Quantity1");
2172     *
2173     *          //add a new table: bTab
2174     *          TSQLTable bTab = sqlSchema.createTable("bTab");
2175     *          bTab.addColumn("Quantity2");
2176     *
2177     *          //add a new table: cTab
2178     *          TSQLTable cTab = sqlSchema.createTable("cTab");
2179     *          cTab.addColumn("Quantity");
2180     *
2181     *    }
2182     * }
2183     * </pre>
2184     *
2185     * @return SQL environment
2186     */
2187    public TSQLEnv getSqlEnv() {
2188        return sqlEnv;
2189    }
2190
2191    private boolean onlyNeedRawParseTree = false;
2192
2193    public void setOnlyNeedRawParseTree(boolean onlyNeedRawParseTree) {
2194        this.onlyNeedRawParseTree = onlyNeedRawParseTree;
2195    }
2196
2197    public void setSqlEnv(TSQLEnv sqlEnv) {
2198        this.sqlEnv = sqlEnv;
2199    }
2200
2201
2202     // Time tracking variables
2203     private boolean enableTimeLogging = false;
2204     private long rawSqlStatementsTime = 0;
2205     private long parsingTime = 0;
2206     private long semanticAnalysisTime = 0;
2207     private long interpreterTime = 0;
2208     
2209     /**
2210      * Enable or disable time logging for parser steps
2211      * @param enable true to enable time logging, false to disable
2212      */
2213     public void setEnableTimeLogging(boolean enable) {
2214         this.enableTimeLogging = enable;
2215     }
2216     
2217     /**
2218      * Check if time logging is enabled
2219      * @return true if time logging is enabled, false otherwise
2220      */
2221     public boolean isTimeLoggingEnabled() {
2222         return this.enableTimeLogging;
2223     }
2224     
2225     /**
2226      * Reset all accumulated time counters to zero
2227      */
2228     public void resetTimeCounters() {
2229         rawSqlStatementsTime = 0;
2230         parsingTime = 0;
2231         semanticAnalysisTime = 0;
2232         interpreterTime = 0;
2233     }
2234     
2235     /**
2236      * Get accumulated time spent getting raw SQL statements in milliseconds
2237      * @return time in milliseconds
2238      */
2239     public long getRawSqlStatementsTime() {
2240         return rawSqlStatementsTime;
2241     }
2242     
2243     /**
2244      * Get accumulated time spent parsing in milliseconds
2245      * @return time in milliseconds
2246      */
2247     public long getParsingTime() {
2248         return parsingTime;
2249     }
2250     
2251     /**
2252      * Get accumulated time spent on semantic analysis in milliseconds
2253      * @return time in milliseconds
2254      */
2255     public long getSemanticAnalysisTime() {
2256         return semanticAnalysisTime;
2257     }
2258     
2259     /**
2260      * Get accumulated time spent in interpreter in milliseconds
2261      * @return time in milliseconds
2262      */
2263     public long getInterpreterTime() {
2264         return interpreterTime;
2265     }
2266     
2267     /**
2268      * Get total accumulated time spent in all steps
2269      * @return total time in milliseconds
2270      */
2271     public long getTotalTime() {
2272         return rawSqlStatementsTime + parsingTime + semanticAnalysisTime + interpreterTime;
2273     }
2274
2275    /**
2276     * Get or create the vendor-specific parser for delegation.
2277     * The parser is cached in vendorParser field and reused for subsequent calls.
2278     * This allows getFlexer() to lazily create the parser to access its lexer.
2279     *
2280     * @return vendor-specific SqlParser or null if not a delegated vendor
2281     * @since 3.2.0.0
2282     */
2283    private SqlParser getOrCreateVendorParser() {
2284        // Return cached parser if available
2285        if (vendorParser != null) {
2286            return vendorParser;
2287        }
2288
2289        // Create new vendor parser based on database vendor
2290        switch (dbVendor) {
2291            case dbvmssql:
2292            case dbvazuresql:
2293            case dbvaccess:    // Access uses MSSQL lexer/parser
2294            case dbvgeneric:   // Generic uses MSSQL lexer/parser
2295            case dbvfirebird:  // Firebird uses MSSQL lexer/parser
2296            case dbvexasol:    // Exasol uses MSSQL lexer/parser
2297                vendorParser = new gudusoft.gsqlparser.parser.MssqlSqlParser();
2298                break;
2299            case dbvmysql:
2300                vendorParser = new gudusoft.gsqlparser.parser.MySqlSqlParser();
2301                break;
2302            case dbvpostgresql:
2303                vendorParser = new gudusoft.gsqlparser.parser.PostgreSqlParser();
2304                break;
2305            case dbvduckdb:
2306                vendorParser = new gudusoft.gsqlparser.parser.DuckdbSqlParser();
2307                break;
2308            case dbvoracle:
2309                vendorParser = new gudusoft.gsqlparser.parser.OracleSqlParser();
2310                break;
2311            case dbvbigquery:
2312                vendorParser = new gudusoft.gsqlparser.parser.BigQuerySqlParser();
2313                break;
2314            case dbvathena:
2315                vendorParser = new gudusoft.gsqlparser.parser.AthenaSqlParser();
2316                break;
2317            case dbvcouchbase:
2318                vendorParser = new gudusoft.gsqlparser.parser.CouchbaseSqlParser();
2319                break;
2320            case dbvdatabricks:
2321                vendorParser = new gudusoft.gsqlparser.parser.DatabricksSqlParser();
2322                break;
2323            case dbvdax:
2324                vendorParser = new gudusoft.gsqlparser.parser.DaxSqlParser();
2325                break;
2326            case dbvpowerquery:
2327                vendorParser = new gudusoft.gsqlparser.parser.PowerQuerySqlParser();
2328                break;
2329            case dbvdb2:
2330                vendorParser = new gudusoft.gsqlparser.parser.Db2SqlParser();
2331                break;
2332            case dbvdoris:
2333                vendorParser = new gudusoft.gsqlparser.parser.DorisSqlParser();
2334                break;
2335            case dbvstarrocks:
2336                vendorParser = new gudusoft.gsqlparser.parser.StarrocksSqlParser();
2337                break;
2338            case dbvflink:
2339                vendorParser = new gudusoft.gsqlparser.parser.FlinkSqlParser();
2340                break;
2341            case dbvgaussdb:
2342                vendorParser = new gudusoft.gsqlparser.parser.GaussDbSqlParser();
2343                break;
2344            case dbvedb:
2345                vendorParser = new gudusoft.gsqlparser.parser.EdbSqlParser();
2346                break;
2347            case dbvdameng:
2348                vendorParser = new gudusoft.gsqlparser.parser.DamengSqlParser();
2349                break;
2350            case dbvoceanbase:
2351                vendorParser = new gudusoft.gsqlparser.parser.OceanBaseSqlParser();
2352                break;
2353            case dbvgreenplum:
2354                vendorParser = new gudusoft.gsqlparser.parser.GreenplumSqlParser();
2355                break;
2356            case dbvhive:
2357                vendorParser = new gudusoft.gsqlparser.parser.HiveSqlParser();
2358                break;
2359            case dbvhana:
2360                vendorParser = new gudusoft.gsqlparser.parser.HanaSqlParser();
2361                break;
2362            case dbvimpala:
2363                vendorParser = new gudusoft.gsqlparser.parser.ImpalaSqlParser();
2364                break;
2365            case dbvinformix:
2366                vendorParser = new gudusoft.gsqlparser.parser.InformixSqlParser();
2367                break;
2368            case dbvmdx:
2369                vendorParser = new gudusoft.gsqlparser.parser.MdxSqlParser();
2370                break;
2371            case dbvnetezza:
2372                vendorParser = new gudusoft.gsqlparser.parser.NetezzaSqlParser();
2373                break;
2374            case dbvodbc:
2375                vendorParser = new gudusoft.gsqlparser.parser.OdbcSqlParser();
2376                break;
2377            case dbvopenedge:
2378                vendorParser = new gudusoft.gsqlparser.parser.OpenEdgeSqlParser();
2379                break;
2380            case dbvpresto:
2381                vendorParser = new gudusoft.gsqlparser.parser.PrestoSqlParser();
2382                break;
2383            case dbvredshift:
2384                vendorParser = new gudusoft.gsqlparser.parser.RedshiftSqlParser();
2385                break;
2386            case dbvsnowflake:
2387                vendorParser = new gudusoft.gsqlparser.parser.SnowflakeSqlParser();
2388                break;
2389            case dbvsqlite:
2390                vendorParser = new gudusoft.gsqlparser.parser.SqliteSqlParser();
2391                break;
2392            case dbvclickhouse:
2393                vendorParser = new gudusoft.gsqlparser.parser.ClickhouseSqlParser();
2394                break;
2395            case dbvsoql:
2396                vendorParser = new gudusoft.gsqlparser.parser.SoqlSqlParser();
2397                break;
2398            case dbvsparksql:
2399                vendorParser = new gudusoft.gsqlparser.parser.SparksqlSqlParser();
2400                break;
2401            case dbvsybase:
2402                vendorParser = new gudusoft.gsqlparser.parser.SybaseSqlParser();
2403                break;
2404            case dbvteradata:
2405                vendorParser = new gudusoft.gsqlparser.parser.TeradataSqlParser();
2406                break;
2407            case dbvtrino:
2408                vendorParser = new gudusoft.gsqlparser.parser.TrinoSqlParser();
2409                break;
2410            case dbvvertica:
2411                vendorParser = new gudusoft.gsqlparser.parser.VerticaSqlParser();
2412                break;
2413            case dbvansi:
2414                vendorParser = new gudusoft.gsqlparser.parser.AnsiSqlParser();
2415                break;
2416            default:
2417                return null;
2418        }
2419        return vendorParser;
2420    }
2421
2422    /**
2423     * Prepare this parser for reuse by clearing cached state.
2424     * This is useful when reusing a TGSqlParser instance (e.g., in TExecImmeStmt)
2425     * to avoid state pollution between parsing operations.
2426     *
2427     * Clears: vendorParser, sqlEnv, sqlfilename, and resets parsing options.
2428     *
2429     * @since 3.2.0.0
2430     */
2431    public void prepareForReuse() {
2432        // Clear cached vendor parser to force recreation
2433        this.vendorParser = null;
2434        // Clear SQL environment to avoid old schema information affecting new parse
2435        this.sqlEnv = null;
2436        // Clear filename in case it was set previously
2437        this.sqlfilename = null;
2438        // Reset parsing options to defaults
2439        this.isSinglePLBlock = false;
2440        // Clear statement list
2441        if (this.sqlstatements != null) {
2442            this.sqlstatements.clear();
2443        }
2444        // Clear error state
2445        this.syntaxErrors.clear();
2446    }
2447
2448    /**
2449     * Build a ParserContext from current TGSqlParser state.
2450     * This creates an immutable context for delegation to vendor parsers.
2451     *
2452     * @return immutable ParserContext
2453     * @since 3.2.0.0
2454     */
2455    private ParserContext buildContext() {
2456        ParserContext.Builder builder = new ParserContext.Builder(this.dbVendor);
2457
2458        // Set SQL text source (only one will be non-null or non-empty)
2459        // CRITICAL: Check for non-null AND non-empty because TGSqlParser initializes sqltext=""
2460        // which would cause vendor parser to use empty string instead of reading from file
2461        if (this.sqltext != null && !this.sqltext.isEmpty()) {
2462            builder.sqlText(this.sqltext);
2463        }
2464        if (this.sqlfilename != null && !this.sqlfilename.isEmpty()) {
2465            builder.sqlFilename(this.sqlfilename);
2466        }
2467
2468        // Set charset
2469        if (this.sqlCharset != null) {
2470            builder.sqlCharset(this.sqlCharset);
2471        }
2472
2473        // Set parsing options
2474        builder.enablePartialParsing(this.isEnablePartialParsing());
2475        builder.singlePLBlock(this.isSinglePLBlock);
2476
2477        // Mirror OceanBase tenant mode into the context so OceanBaseSqlParser
2478        // (and any other downstream component) can read it without a
2479        // back-reference to TGSqlParser.
2480        builder.oceanBaseTenantMode(this.oceanBaseTenantMode);
2481
2482        // Set gsqlparser reference for backward compatibility
2483        builder.gsqlparser(this);
2484
2485        // Set SQL environment if available
2486        if (this.sqlEnv != null) {
2487            builder.sqlEnv(this.sqlEnv);
2488        }
2489
2490        // Set token handle for callback during tokenization
2491        if (this.tokenHandle != null) {
2492            builder.tokenHandle(this.tokenHandle);
2493        }
2494
2495        return builder.build();
2496    }
2497
2498     /**
2499      * Returns time statistics as a formatted string
2500      * @return formatted string with time statistics
2501      */
2502     public String getTimeStatistics() {
2503         if (!enableTimeLogging) {
2504             return "Time logging is disabled";
2505         }
2506         
2507         StringBuilder sb = new StringBuilder();
2508         sb.append(String.format("Time statistics for TGSqlParser version: %s, released at: %s, dbvendor: %s:\n", TBaseType.versionid,TBaseType.releaseDate, this.dbVendor));
2509         sb.append(String.format("1. Raw SQL statements: %d ms (%.2f%%)\n", 
2510                 rawSqlStatementsTime,
2511                 getTotalTime() > 0 ? 100.0 * rawSqlStatementsTime / getTotalTime() : 0));
2512         sb.append(String.format("2. Parsing: %d ms (%.2f%%)\n", 
2513                 parsingTime,
2514                 getTotalTime() > 0 ? 100.0 * parsingTime / getTotalTime() : 0));
2515         sb.append(String.format("3. Semantic analysis: %d ms (%.2f%%)\n", 
2516                 semanticAnalysisTime,
2517                 getTotalTime() > 0 ? 100.0 * semanticAnalysisTime / getTotalTime() : 0));
2518         sb.append(String.format("4. Interpreter: %d ms (%.2f%%)\n", 
2519                 interpreterTime,
2520                 getTotalTime() > 0 ? 100.0 * interpreterTime / getTotalTime() : 0));
2521         sb.append(String.format("Total: %d ms", getTotalTime()));
2522         return sb.toString();
2523     }
2524
2525    /**
2526     * Delegate only tokenization and raw statement extraction to vendor parser.
2527     * The actual AST parsing is done by the common parsing loop in doparse().
2528     * This architecture ensures that:
2529     * 1. Vendor-specific tokenization/lexing is handled by vendor parser
2530     * 2. Raw statement extraction (splitting SQL text into statements) is vendor-specific
2531     * 3. AST parsing (building parse tree for each statement) uses common code path
2532     *
2533     * @param vendorParser the vendor-specific parser to use
2534     * @return 0 if successful, non-zero on error
2535     */
2536    private int doDelegatedRawParse(SqlParser vendorParser) {
2537        long phaseStart, phaseEnd;
2538
2539        // Build context for vendor parser
2540        ParserContext context = buildContext();
2541
2542        // Delegate only tokenization + raw statement extraction to vendor parser
2543        phaseStart = enableTimeLogging ? System.currentTimeMillis() : 0;
2544        SqlParseResult rawResult = vendorParser.getrawsqlstatements(context);
2545        if (enableTimeLogging) {
2546            phaseEnd = System.currentTimeMillis();
2547            rawSqlStatementsTime += (phaseEnd - phaseStart);
2548        }
2549
2550        // Copy results back to TGSqlParser fields for backward compatibility
2551        if (rawResult.getSourceTokenList() != null) {
2552            this.sourcetokenlist = rawResult.getSourceTokenList();
2553            this.sourcetokenlist.setGsqlparser(this);
2554        }
2555        if (rawResult.getLexer() != null) {
2556            this.flexer = rawResult.getLexer();
2557        }
2558        // Copy parser from vendor parser result - needed for parsestatement()
2559        if (rawResult.getParser() != null) {
2560            this.fparser = rawResult.getParser();
2561            // CRITICAL: Set gsqlparser on NodeFactory for correct AST construction
2562            this.fparser.getNf().setGsqlParser(this);
2563        }
2564        // Copy secondary parser (for Oracle PL/SQL) and set its NodeFactory
2565        if (rawResult.getSecondaryParser() != null) {
2566            this.fplsqlparser = rawResult.getSecondaryParser();
2567            // CRITICAL: Also set gsqlparser on secondary parser's NodeFactory
2568            // This is needed for Oracle PL/SQL statements which use fplsqlparser for parsing
2569            this.fplsqlparser.getNf().setGsqlParser(this);
2570        }
2571        if (rawResult.getSqlStatements() != null) {
2572            this.sqlstatements = rawResult.getSqlStatements();
2573        }
2574
2575        // Copy lastTokenOfStatementBeenValidated for getLastLineNoOfLastStatementBeenValidated() API
2576        this.lastTokenOfStatementBeenValidated = rawResult.getLastTokenOfStatementBeenValidated();
2577
2578        // Copy any tokenization errors
2579        if (rawResult.getSyntaxErrors() != null) {
2580            for (TSyntaxError error : rawResult.getSyntaxErrors()) {
2581                this.syntaxErrors.add(error);
2582            }
2583        }
2584
2585        return rawResult.getErrorCode();
2586    }
2587
2588     int doparse() {
2589         int j;
2590         long startTime, endTime;
2591         boolean useDelegatedRawParse = false;
2592
2593         // Clear errors from any previous parse() call on this instance
2594         syntaxErrors.clear();
2595
2596         // 1. Get raw SQL statements
2597         // For delegated vendors, use vendor parser for tokenization + raw extraction
2598         // The parsing loop below is common for all vendors
2599         SqlParser vp = getOrCreateVendorParser();
2600         if (vp != null) {
2601             int ret = doDelegatedRawParse(vp);
2602             if (ret != 0) {
2603                 // Tokenization/extraction failed, return error
2604                 return ret;
2605             }
2606             useDelegatedRawParse = true;
2607             // Continue to common parsing loop below
2608         }
2609
2610         // Legacy path: get raw statements for non-delegated vendors
2611         if (!useDelegatedRawParse) {
2612             startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2613
2614             int ret = getrawsqlstatements();
2615
2616             if (enableTimeLogging) {
2617                 endTime = System.currentTimeMillis();
2618                 rawSqlStatementsTime += (endTime - startTime);
2619             }
2620         }
2621
2622         boolean isPushGloablStack = false;
2623         //if (ret != 0) return ret;
2624         TFrame firstFrame = null;
2625 
2626         globalContext = new TContext();
2627 
2628         if (this.sqlEnv == null) {
2629             this.sqlEnv = new TSQLEnv(this.dbVendor) {
2630                 @Override
2631                 public void initSQLEnv() {
2632                 }
2633             };
2634             // this.sqlEnv.setEnableGetMetadataFromDDL(false);
2635         }
2636         globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
2637 
2638         //TStackFrame stackFrame = new TStackFrame(new TGlobalScope());
2639         if (getFrameStack().size() == 0) { // stack passed from outside gsqlparser will contain frames
2640             //getFrameStack().push(new TStackFrame(new TGlobalScope()));
2641             TGlobalScope globalScope = new TGlobalScope();
2642             globalScope.resetCurrentStmtIndex();
2643 
2644             globalScope.setSqlEnv(this.sqlEnv);
2645             firstFrame = new TFrame(globalScope);
2646             firstFrame.pushMeToStack(getFrameStack());
2647             isPushGloablStack = true;
2648         }
2649 
2650         // 2. start parsing
2651         startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2652         
2653         for (int i = 0; i < sqlstatements.size(); i++) {
2654             sqlstatements.getRawSql(i).setFrameStack(frameStack);
2655             j = sqlstatements.getRawSql(i).parsestatement(null, false, onlyNeedRawParseTree);
2656
2657             // NOTE: No need for setGsqlparserRecursively() here for delegated vendors.
2658             // The gsqlparser reference is already set on all AST nodes through two mechanisms:
2659             // 1. doDelegatedRawParse() sets fparser.getNf().setGsqlParser(this) and
2660             //    fplsqlparser.getNf().setGsqlParser(this) BEFORE parsestatement() is called
2661             // 2. TNodeFactory.createNode() propagates gsqlparser to every node it creates
2662             // The recursive walk was causing 4-5x performance degradation for Oracle parsing
2663             // by redundantly traversing the entire AST after parsing.
2664
2665             TCustomSqlStatement sql0 = null;
2666             if (sqlstatements.get(i).isoracleplsql()) {
2667                 // check syntax error in select/insert statement inside plsql
2668                 sql0 = sqlstatements.get(i);
2669                 findAllSyntaxErrorsInPlsql(sql0);
2670             }
2671 
2672             boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
2673 
2674             if (doRecover && ((j != 0) || (sqlstatements.get(i).getErrorCount() > 0))) {
2675                 if (((sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreatetable)
2676                         || ((sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreateindex) && (dbVendor != EDbVendor.dbvcouchbase))
2677                         ) && (!TBaseType.c_createTableStrictParsing)
2678                 ) {
2679                     // only parse main body of create table,
2680                     TCustomSqlStatement errorSqlStatement = (TCustomSqlStatement) sqlstatements.get(i);
2681 
2682                     int nested = 0;
2683                     boolean isIgnore = false, isFoundIgnoreToken = false;
2684                     TSourceToken firstIgnoreToken = null;
2685                     for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) {
2686                         TSourceToken st = errorSqlStatement.sourcetokenlist.get(k);
2687                         if (isIgnore) {
2688                             if (st.issolidtoken() && (st.tokencode != ';')) {
2689                                 isFoundIgnoreToken = true;
2690                                 if (firstIgnoreToken == null) {
2691                                     firstIgnoreToken = st;
2692                                 }
2693                             }
2694                             if (st.tokencode != ';') {
2695                                 st.tokencode = TBaseType.sqlpluscmd;
2696                             }
2697                             continue;
2698                         }
2699                         if (st.tokencode == (int) ')') {
2700                             nested--;
2701                             if (nested == 0) {
2702                                 //let's check is next token is
2703                                 // as ( select
2704                                 boolean isSelect = false;
2705                                 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
2706                                 if (st1 != null) {
2707                                     TSourceToken st2 = st.searchToken((int) '(', 2);
2708                                     if (st2 != null) {
2709                                         TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
2710                                         isSelect = (st3 != null);
2711                                     }
2712                                 }
2713                                 if (!isSelect) isIgnore = true;
2714                             }
2715                         }
2716                         if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
2717                             nested++;
2718                         }
2719                     }
2720 
2721                     if ((dbVendor == EDbVendor.dbvoracle) && ((firstIgnoreToken != null) && (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString())))) {
2722                         // if it is not the valid Oracle table properties option, let raise the error.
2723                         isFoundIgnoreToken = false;
2724                     }
2725                     if (isFoundIgnoreToken) {
2726                         errorSqlStatement.clearError();
2727                         j = sqlstatements.get(i).parsestatement(null, false);
2728                     }
2729                 }
2730 
2731                 if (((sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreatetrigger)
2732                         || (sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstcreatefunction)
2733                         || (sqlstatements.get(i).sqlstatementtype == sstcreateprocedure)
2734                         ) && (dbVendor == EDbVendor.dbvdb2)) { 
2735                     // db2 can handle Oracle pl/sql code, so we give it a try here
2736                     TCustomSqlStatement stmt = sqlstatements.get(i);
2737                     StringBuffer stmtStr = new StringBuffer(1024);
2738                     for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
2739                         stmtStr.append(stmt.sourcetokenlist.get(k).getAstext());
2740                     }
2741 
2742                     TGSqlParser lc_sqlparser = new TGSqlParser(EDbVendor.dbvoracle);
2743                     lc_sqlparser.sqltext = stmtStr.toString();
2744                     int iRet = lc_sqlparser.parse();
2745                     if (iRet == 0) {
2746                         sqlstatements.remove(i);
2747                         sqlstatements.add(i, lc_sqlparser.sqlstatements.get(0));
2748                         continue;
2749                     }
2750                 }
2751             }
2752 
2753             if ((j != 0) || (sqlstatements.get(i).getErrorCount() > 0)) {
2754                 copyerrormsg(sqlstatements.get(i));
2755 
2756                 if ((isEnablePartialParsing()) && (dbVendor == EDbVendor.dbvsybase) && (sqlstatements.get(i).sqlstatementtype == ESqlStatementType.sstmssqlcreateprocedure)) {
2757                     TMssqlCreateProcedure createProcedure = (TMssqlCreateProcedure) sqlstatements.get(i);
2758 
2759                     StringBuffer storedProcedure = new StringBuffer(1024);
2760                     boolean ASKeyword = false;
2761                     for (int k = 0; k < createProcedure.sourcetokenlist.size(); k++) {
2762                         if ((!ASKeyword) && (createProcedure.sourcetokenlist.get(k).tokencode == TBaseType.rrw_as)) {
2763                             ASKeyword = true;
2764                             continue;
2765                         }
2766                         if (ASKeyword) {
2767                             storedProcedure.append(createProcedure.sourcetokenlist.get(k).getAstext());
2768                         }
2769                     }
2770 
2771                     TGSqlParser lc_sqlparser = new TGSqlParser(dbVendor);
2772                     lc_sqlparser.sqltext = storedProcedure.toString();
2773                     lc_sqlparser.parse();
2774                     for (int k = 0; k < lc_sqlparser.sqlstatements.size(); k++) {
2775                         createProcedure.getBodyStatements().add(lc_sqlparser.sqlstatements.get(k));
2776                     }
2777                 }
2778             }
2779 
2780             // fire SQL Statement handle event if set, if return true, stop parsing
2781             if (sqlStatementHandle != null) {
2782                 if (sqlStatementHandle.processSQLStatement(sqlstatements.get(i), this)) break;
2783             }
2784         }
2785         
2786         if (enableTimeLogging) {
2787             endTime = System.currentTimeMillis();
2788             parsingTime += (endTime - startTime);
2789         }
2790 
2791         if (isPushGloablStack) {
2792             firstFrame.popMeFromStack(getFrameStack());
2793         }
2794 
2795         // 3. start semantic analysis
2796         startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2797
2798         // Reset resolver2 for each parse
2799         this.resolver2 = null;
2800
2801         // Determine effective resolver type (instance setting takes precedence over TBaseType)
2802         EResolverType effectiveResolverType = this.resolverType;
2803         if (effectiveResolverType == EResolverType.DEFAULT) {
2804             // Fall back to TBaseType settings for backward compatibility
2805             if (TBaseType.isEnableResolver2()) {
2806                 effectiveResolverType = EResolverType.RESOLVER2;
2807             } else if (TBaseType.isEnableResolver()) {
2808                 effectiveResolverType = EResolverType.RESOLVER;
2809             } else {
2810                 effectiveResolverType = EResolverType.NONE;
2811             }
2812         }
2813
2814         // Run the appropriate resolver based on effective type
2815         if (getErrorCount() == 0) {
2816             switch (effectiveResolverType) {
2817                 case RESOLVER:
2818                     TSQLResolver resolver = new TSQLResolver(globalContext, sqlstatements);
2819                     if (!resolver.resolve()) {
2820                         // Handle resolution errors
2821                         // addErrors(resolver.getLog().getErrors());
2822                     }
2823                     break;
2824
2825                 case RESOLVER2:
2826                     // Use provided config or create default
2827                     TSQLResolverConfig config = this.resolver2Config;
2828                     if (config == null) {
2829                         config = new TSQLResolverConfig();
2830                     }
2831                     // Always set vendor on config (needed for vendor-specific resolution like struct-field fallback)
2832                     config.setVendor(dbVendor);
2833                     // Pass null as globalContext to match original TSQLResolver2 behavior
2834                     this.resolver2 = new TSQLResolver2(null, sqlstatements, config);
2835                     // Pass sqlEnv to resolver2 for metadata lookup
2836                     if (this.sqlEnv != null) {
2837                         this.resolver2.setSqlEnv(this.sqlEnv);
2838                     }
2839                     if (!this.resolver2.resolve()) {
2840                         // Handle resolution errors if needed
2841                     }
2842                     break;
2843
2844                 case NONE:
2845                 default:
2846                     // No resolution
2847                     break;
2848             }
2849         }
2850
2851         if (enableTimeLogging) {
2852             endTime = System.currentTimeMillis();
2853             semanticAnalysisTime += (endTime - startTime);
2854         }
2855 
2856         // 4. start interpreter
2857         startTime = enableTimeLogging ? System.currentTimeMillis() : 0;
2858         
2859         if ((TBaseType.ENABLE_INTERPRETER) && (getErrorCount() == 0)) {
2860             TLog.clearLogs();
2861             TGlobalScope globalScope = new TGlobalScope(sqlEnv);
2862             TLog.enableInterpreterLogOnly();
2863 
2864             TASTEvaluator astEvaluator = new TASTEvaluator(this.sqlstatements, globalScope);
2865             astEvaluator.eval();
2866         }
2867         
2868         if (enableTimeLogging) {
2869             endTime = System.currentTimeMillis();
2870             interpreterTime += (endTime - startTime);
2871         }
2872 
2873         return getErrorCount();
2874     }
2875
2876     
2877void copyerrormsg(TCustomSqlStatement sql){
2878    for (int i = 0; i<sql.getSyntaxErrors().size(); i++){
2879            this.syntaxErrors.add(new TSyntaxError( (TSyntaxError)sql.getSyntaxErrors().get(i) ));
2880    }
2881//    for (int i = 0; i<sql.getSyntaxHints().size(); i++){
2882//            this.syntaxHints.add(new TSyntaxError( (TSyntaxError)sql.getSyntaxHints().get(i) ));
2883//    }
2884}
2885
2886private static String calculateLicenseKey(boolean ignoreMachineId){
2887
2888    if (userName == null) return null;
2889    if (machineId == null) return null;
2890
2891    byte[] bytesOfMessage=null;
2892    String licenseStr = "I love sql pretty printer, yeah!"+userName.toLowerCase();
2893    if (!ignoreMachineId){
2894        licenseStr += machineId.toLowerCase();
2895    }
2896    try {
2897        bytesOfMessage = licenseStr.getBytes("UTF-8");
2898    } catch (UnsupportedEncodingException e) {
2899        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
2900    }
2901
2902    MessageDigest md = null;
2903    try {
2904        md = MessageDigest.getInstance("MD5");
2905    } catch (NoSuchAlgorithmException e) {
2906        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
2907    }
2908    byte[] digest = md.digest(bytesOfMessage);
2909
2910    return null;//HardwareBinder.getHex(digest);
2911}
2912
2913private static boolean validateLicense(){
2914
2915    int ret = 0;
2916    return ret == 0;
2917
2918}
2919
2920private static boolean check_license_time(){
2921
2922    boolean  ret = false;
2923
2924    String toDate = TBaseType.license_expired_date;
2925
2926    DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
2927    Calendar currDtCal = Calendar.getInstance();
2928
2929    // Zero out the hour, minute, second, and millisecond
2930    currDtCal.set(Calendar.HOUR_OF_DAY, 0);
2931    currDtCal.set(Calendar.MINUTE, 0);
2932    currDtCal.set(Calendar.SECOND, 0);
2933    currDtCal.set(Calendar.MILLISECOND, 0);
2934
2935    Date currDt = currDtCal.getTime();
2936
2937    Date toDt;
2938    try {
2939        toDt = df.parse(toDate);
2940    } catch (ParseException e) {
2941        toDt = null;
2942        // Print some error message back to the user
2943    }
2944
2945    if (toDt != null){
2946        int results = toDt.compareTo(currDt);
2947        ret = results > 0;
2948    }
2949
2950    return ret;
2951}
2952
2953    public void setTeradataUtilityType(TeradataUtilityType teradataUtilityType) {
2954        if ((this.getFlexer() != null) && (this.getFlexer() instanceof TLexerTeradata)){
2955            ((TLexerTeradata)this.getFlexer()).setTeradataUtilityType(teradataUtilityType);
2956        }
2957    }
2958    
2959    /**
2960     * Dispose of parser resources and clean up references.
2961     * 
2962     * <p>This method releases internal resources used by the parser.
2963     * After calling this method, the parser should not be used further.</p>
2964     * 
2965     * <p>If a ManagedSourceBuffer was set, the source text remains in the buffer
2966     * so that tokens can still access it. The user is responsible for calling 
2967     * {@link ManagedSourceBuffer#release()} when all tokens are no longer needed.</p>
2968     * 
2969     * <p>Note: Tokens created by this parser will remain usable after dispose()
2970     * if a ManagedSourceBuffer was used, as they reference the buffer by ID
2971     * rather than holding direct parser references.</p>
2972     * 
2973     * @since 3.1.0.9
2974     */
2975    public void dispose() {
2976        // Note: We intentionally do NOT remove the source from managed buffer
2977        // This allows tokens to remain usable after parser disposal
2978        // The user must call buffer.release() when done with all tokens
2979        
2980        // Clear references to help GC
2981        flexer = null;
2982        fparser = null;
2983        fplsqlparser = null;
2984        finputstream = null;
2985        sqlInputStream = null;
2986        gcurrentsqlstatement = null;
2987        nextStmt = null;
2988        sqlstatements = null;
2989        sourcetokenlist = null;
2990        syntaxErrors = null;
2991    }
2992
2993}