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