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