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