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