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