001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.*; 004 005import java.util.ArrayList; 006 007/** 008 * Immutable result of a parsing operation. 009 * 010 * <p>This value object encapsulates all outputs produced by SQL parsing, 011 * providing clean separation between parser implementation and result data. 012 * Once built, a SqlParseResult instance cannot be modified (immutable). 013 * 014 * <p><b>Design Pattern:</b> Value Object + Builder 015 * <ul> 016 * <li>Immutable: Thread-safe, can be safely shared</li> 017 * <li>Builder: Flexible construction by parser implementations</li> 018 * <li>Clean boundaries: Input/Output separation</li> 019 * </ul> 020 * 021 * <p><b>Contents:</b> 022 * <ul> 023 * <li><b>Parse Tree:</b> TStatementList with all parsed statements</li> 024 * <li><b>Tokens:</b> TSourceTokenList with all source tokens</li> 025 * <li><b>Errors:</b> Error code, message, and detailed syntax errors</li> 026 * <li><b>Performance:</b> Timing metrics for each phase</li> 027 * <li><b>State:</b> Lexer and parser instances (for getFlexer() compatibility)</li> 028 * </ul> 029 * 030 * <p><b>Usage Example:</b> 031 * <pre> 032 * SqlParseResult result = parser.parse(context); 033 * 034 * // Check for errors 035 * if (result.hasErrors()) { 036 * System.err.println(result.getErrorMessage()); 037 * return; 038 * } 039 * 040 * // Access parsed statements 041 * TStatementList statements = result.getSqlStatements(); 042 * for (int i = 0; i < statements.size(); i++) { 043 * TCustomSqlStatement stmt = statements.get(i); 044 * // Process statement... 045 * } 046 * </pre> 047 * 048 * @see SqlParser 049 * @see ParserContext 050 * @since 3.2.0.0 051 */ 052public final class SqlParseResult { 053 054 // ========== Parse Results ========== 055 private final TSourceTokenList sourceTokenList; 056 private final TStatementList sqlStatements; 057 058 public TSourceToken getLastTokenOfStatementBeenValidated() { 059 return lastTokenOfStatementBeenValidated; 060 } 061 062 private TSourceToken lastTokenOfStatementBeenValidated; 063 064 065 // ========== Error Information ========== 066 private final String errorMessage; 067 private final int errorCode; 068 private final ArrayList<TSyntaxError> syntaxErrors; 069 070 // ========== Performance Metrics ========== 071 private final long tokenizationTimeMs; 072 private final long parsingTimeMs; 073 private final long semanticAnalysisTimeMs; 074 private final long interpreterTimeMs; 075 076 // ========== Vendor-Specific State ========== 077 // These are preserved for backward compatibility with getFlexer() 078 private final TCustomLexer lexer; 079 private final TCustomParser parser; 080 private final TCustomParser secondaryParser; // For Oracle: fplsqlparser 081 082 /** 083 * Private constructor - use Builder to create instances. 084 */ 085 private SqlParseResult(Builder builder) { 086 // Parse results 087 this.sourceTokenList = builder.sourceTokenList; 088 this.sqlStatements = builder.sqlStatements; 089 090 // Make defensive copy of statement list to preserve immutability 091 // This ensures that if the parser reuses its internal statement list, 092 // previous results remain independent and unchanged 093// if (builder.sqlStatements != null) { 094// this.sqlStatements = new TStatementList(); 095// for (int i = 0; i < builder.sqlStatements.size(); i++) { 096// this.sqlStatements.add(builder.sqlStatements.get(i)); 097// } 098// } else { 099// this.sqlStatements = null; 100// } 101 102 this.lastTokenOfStatementBeenValidated = builder.lastTokenOfStatementBeenValidated; 103 104 // Error information 105 this.errorMessage = builder.errorMessage; 106 this.errorCode = builder.errorCode; 107 this.syntaxErrors = builder.syntaxErrors != null ? 108 builder.syntaxErrors : new ArrayList<TSyntaxError>(); 109 110 // Performance metrics 111 this.tokenizationTimeMs = builder.tokenizationTimeMs; 112 this.parsingTimeMs = builder.parsingTimeMs; 113 this.semanticAnalysisTimeMs = builder.semanticAnalysisTimeMs; 114 this.interpreterTimeMs = builder.interpreterTimeMs; 115 116 // Vendor state 117 this.lexer = builder.lexer; 118 this.parser = builder.parser; 119 this.secondaryParser = builder.secondaryParser; 120 } 121 122 // ========== Getters (Read-only access) ========== 123 124 /** 125 * Get the list of source tokens. 126 * 127 * @return source token list, or null if tokenization failed 128 */ 129 public TSourceTokenList getSourceTokenList() { 130 return sourceTokenList; 131 } 132 133 /** 134 * Get the list of parsed SQL statements. 135 * 136 * @return statement list, or null if parsing failed 137 */ 138 public TStatementList getSqlStatements() { 139 return sqlStatements; 140 } 141 142 /** 143 * Get the error message if parsing failed. 144 * 145 * @return error message, or empty string if no error 146 */ 147 public String getErrorMessage() { 148 return errorMessage; 149 } 150 151 /** 152 * Get the error code. 153 * 154 * @return 0 if successful, non-zero if error occurred 155 */ 156 public int getErrorCode() { 157 return errorCode; 158 } 159 160 /** 161 * Get detailed syntax errors. 162 * 163 * @return list of syntax errors, empty if no errors 164 */ 165 public ArrayList<TSyntaxError> getSyntaxErrors() { 166 return syntaxErrors; 167 } 168 169 /** 170 * Get tokenization time in milliseconds. 171 * 172 * @return tokenization time in ms 173 */ 174 public long getTokenizationTimeMs() { 175 return tokenizationTimeMs; 176 } 177 178 /** 179 * Get parsing time in milliseconds. 180 * 181 * @return parsing time in ms 182 */ 183 public long getParsingTimeMs() { 184 return parsingTimeMs; 185 } 186 187 /** 188 * Get semantic analysis time in milliseconds. 189 * 190 * @return semantic analysis time in ms 191 */ 192 public long getSemanticAnalysisTimeMs() { 193 return semanticAnalysisTimeMs; 194 } 195 196 /** 197 * Get interpreter time in milliseconds. 198 * 199 * @return interpreter time in ms 200 */ 201 public long getInterpreterTimeMs() { 202 return interpreterTimeMs; 203 } 204 205 /** 206 * Get total time (all phases combined) in milliseconds. 207 * 208 * @return total time in ms 209 */ 210 public long getTotalTimeMs() { 211 return tokenizationTimeMs + parsingTimeMs + semanticAnalysisTimeMs + interpreterTimeMs; 212 } 213 214 /** 215 * Get the lexer instance (for backward compatibility). 216 * 217 * @return the lexer instance 218 */ 219 public TCustomLexer getLexer() { 220 return lexer; 221 } 222 223 /** 224 * Get the parser instance (for backward compatibility). 225 * 226 * @return the parser instance 227 */ 228 public TCustomParser getParser() { 229 return parser; 230 } 231 232 /** 233 * Get the secondary parser instance (for Oracle PL/SQL parser). 234 * 235 * @return the secondary parser instance, or null if not applicable 236 */ 237 public TCustomParser getSecondaryParser() { 238 return secondaryParser; 239 } 240 241 /** 242 * Check if parsing resulted in errors. 243 * 244 * @return true if there were errors, false otherwise 245 */ 246 public boolean hasErrors() { 247 return errorCode != 0 || (syntaxErrors != null && !syntaxErrors.isEmpty()); 248 } 249 250 /** 251 * Check if parsing was successful. 252 * 253 * @return true if no errors, false otherwise 254 */ 255 public boolean isSuccessful() { 256 return !hasErrors(); 257 } 258 259 /** 260 * Builder for constructing SqlParseResult instances. 261 * 262 * <p>Used by SqlParser implementations to build the result object 263 * as parsing progresses. 264 * 265 * <p><b>Example:</b> 266 * <pre> 267 * SqlParseResult.Builder builder = new SqlParseResult.Builder(); 268 * builder.sourceTokenList(tokens); 269 * builder.sqlStatements(statements); 270 * builder.errorCode(0); 271 * builder.tokenizationTimeMs(50); 272 * builder.parsingTimeMs(120); 273 * return builder.build(); 274 * </pre> 275 */ 276 public static class Builder { 277 // Optional fields with defaults 278 private TSourceTokenList sourceTokenList = null; 279 private TStatementList sqlStatements = null; 280 private TSourceToken lastTokenOfStatementBeenValidated; 281 282 private String errorMessage = ""; 283 private int errorCode = 0; 284 private ArrayList<TSyntaxError> syntaxErrors = new ArrayList<>(); 285 286 private long tokenizationTimeMs = 0; 287 private long parsingTimeMs = 0; 288 private long semanticAnalysisTimeMs = 0; 289 private long interpreterTimeMs = 0; 290 291 private TCustomLexer lexer = null; 292 private TCustomParser parser = null; 293 private TCustomParser secondaryParser = null; 294 295 /** 296 * Create a builder with default values. 297 */ 298 public Builder() { 299 // Default constructor 300 } 301 302 /** 303 * Set source token list. 304 * 305 * @param sourceTokenList the source token list 306 * @return this builder for method chaining 307 */ 308 public Builder sourceTokenList(TSourceTokenList sourceTokenList) { 309 this.sourceTokenList = sourceTokenList; 310 return this; 311 } 312 313 /** 314 * Set SQL statements list. 315 * 316 * @param sqlStatements the SQL statements list 317 * @return this builder for method chaining 318 */ 319 public Builder sqlStatements(TStatementList sqlStatements) { 320 this.sqlStatements = sqlStatements; 321 return this; 322 } 323 324 325 public Builder lastTokenOfStatementBeenValidated(TSourceToken lastTokenOfStatementBeenValidated) { 326 this.lastTokenOfStatementBeenValidated = lastTokenOfStatementBeenValidated; 327 return this; 328 } 329 330 /** 331 * Set error message. 332 * 333 * @param errorMessage the error message 334 * @return this builder for method chaining 335 */ 336 public Builder errorMessage(String errorMessage) { 337 this.errorMessage = errorMessage != null ? errorMessage : ""; 338 return this; 339 } 340 341 /** 342 * Set error code. 343 * 344 * @param errorCode the error code (0 = success) 345 * @return this builder for method chaining 346 */ 347 public Builder errorCode(int errorCode) { 348 this.errorCode = errorCode; 349 return this; 350 } 351 352 /** 353 * Set syntax errors list. 354 * 355 * @param syntaxErrors the syntax errors list 356 * @return this builder for method chaining 357 */ 358 public Builder syntaxErrors(ArrayList<TSyntaxError> syntaxErrors) { 359 this.syntaxErrors = syntaxErrors; 360 return this; 361 } 362 363 /** 364 * Add a single syntax error. 365 * 366 * @param syntaxError the syntax error to add 367 * @return this builder for method chaining 368 */ 369 public Builder addSyntaxError(TSyntaxError syntaxError) { 370 if (this.syntaxErrors == null) { 371 this.syntaxErrors = new ArrayList<>(); 372 } 373 this.syntaxErrors.add(syntaxError); 374 return this; 375 } 376 377 /** 378 * Set tokenization time in milliseconds. 379 * 380 * @param tokenizationTimeMs tokenization time in ms 381 * @return this builder for method chaining 382 */ 383 public Builder tokenizationTimeMs(long tokenizationTimeMs) { 384 this.tokenizationTimeMs = tokenizationTimeMs; 385 return this; 386 } 387 388 /** 389 * Set parsing time in milliseconds. 390 * 391 * @param parsingTimeMs parsing time in ms 392 * @return this builder for method chaining 393 */ 394 public Builder parsingTimeMs(long parsingTimeMs) { 395 this.parsingTimeMs = parsingTimeMs; 396 return this; 397 } 398 399 /** 400 * Set semantic analysis time in milliseconds. 401 * 402 * @param semanticAnalysisTimeMs semantic analysis time in ms 403 * @return this builder for method chaining 404 */ 405 public Builder semanticAnalysisTimeMs(long semanticAnalysisTimeMs) { 406 this.semanticAnalysisTimeMs = semanticAnalysisTimeMs; 407 return this; 408 } 409 410 /** 411 * Set interpreter time in milliseconds. 412 * 413 * @param interpreterTimeMs interpreter time in ms 414 * @return this builder for method chaining 415 */ 416 public Builder interpreterTimeMs(long interpreterTimeMs) { 417 this.interpreterTimeMs = interpreterTimeMs; 418 return this; 419 } 420 421 /** 422 * Set lexer instance (for backward compatibility). 423 * 424 * @param lexer the lexer instance 425 * @return this builder for method chaining 426 */ 427 public Builder lexer(TCustomLexer lexer) { 428 this.lexer = lexer; 429 return this; 430 } 431 432 /** 433 * Set parser instance (for backward compatibility). 434 * 435 * @param parser the parser instance 436 * @return this builder for method chaining 437 */ 438 public Builder parser(TCustomParser parser) { 439 this.parser = parser; 440 return this; 441 } 442 443 /** 444 * Set secondary parser instance (for Oracle PL/SQL parser). 445 * 446 * @param secondaryParser the secondary parser instance 447 * @return this builder for method chaining 448 */ 449 public Builder secondaryParser(TCustomParser secondaryParser) { 450 this.secondaryParser = secondaryParser; 451 return this; 452 } 453 454 /** 455 * Build the immutable SqlParseResult. 456 * 457 * @return a new immutable SqlParseResult instance 458 */ 459 public SqlParseResult build() { 460 return new SqlParseResult(this); 461 } 462 } 463 464 @Override 465 public String toString() { 466 return "SqlParseResult{" + 467 "errorCode=" + errorCode + 468 ", statements=" + (sqlStatements != null ? sqlStatements.size() : 0) + 469 ", tokens=" + (sourceTokenList != null ? sourceTokenList.size() : 0) + 470 ", errors=" + (syntaxErrors != null ? syntaxErrors.size() : 0) + 471 ", totalTimeMs=" + getTotalTimeMs() + 472 '}'; 473 } 474}