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