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