001package gudusoft.gsqlparser.resolver2; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.resolver2.binding.BindingDiagnosticCode; 006import gudusoft.gsqlparser.resolver2.binding.BindingDiagnosticSeverity; 007import gudusoft.gsqlparser.resolver2.format.DisplayNameMode; 008import gudusoft.gsqlparser.resolver2.format.DisplayNamePolicy; 009import gudusoft.gsqlparser.resolver2.matcher.DefaultNameMatcher; 010import gudusoft.gsqlparser.resolver2.matcher.INameMatcher; 011import gudusoft.gsqlparser.resolver2.matcher.VendorNameMatcher; 012 013import java.util.EnumMap; 014import java.util.Map; 015 016/** 017 * Configuration for TSQLResolver2. 018 * Controls various aspects of name resolution behavior. 019 */ 020public class TSQLResolverConfig { 021 022 /** Name matcher for case sensitivity and matching rules */ 023 private INameMatcher nameMatcher = new DefaultNameMatcher(); 024 025 /** Database vendor for vendor-specific name matching */ 026 private EDbVendor vendor = null; 027 028 /** Whether to enable legacy compatibility mode (sync results to TTable.linkedColumns) */ 029 private boolean legacyCompatibilityEnabled = true; 030 031 /** 032 * Minimum confidence threshold for syncing to legacy structures. 033 * 034 * Recommended values: 035 * - 1.0 (default): Only sync definite results (safest, for SQL validation) 036 * - 0.7: Include high-confidence inferences (for data lineage analysis) 037 * - 0.5: Include all inferences (may mislead legacy code, not recommended) 038 * 039 * Use cases: 040 * - Data lineage tools may want 0.7 inference results 041 * - SQL formatters only need 1.0 definite results 042 */ 043 private double legacySyncMinConfidence = 1.0; 044 045 /** Maximum iterations for iterative resolution */ 046 private int maxIterations = 10; 047 048 /** Minimum progress rate to continue iteration (0.0 - 1.0) */ 049 private double minProgressRate = 0.01; // 1% 050 051 /** Number of stable passes required to declare convergence */ 052 private int stablePassesForConvergence = 2; 053 054 /** Whether to collect full candidates for ambiguous columns */ 055 private boolean collectFullCandidates = true; 056 057 /** Whether to enable legacy evidence collection (deprecated, use NamespaceEnhancer) */ 058 private boolean evidenceCollectionEnabled = false; 059 060 /** 061 * Whether to show datatype information for columns from CREATE TABLE statements. 062 * When enabled, columns from CREATE TABLE will include datatype info in format: 063 * columnName:datatypeName:length or columnName:datatypeName:precision:scale 064 */ 065 private boolean showDatatype = false; 066 067 /** 068 * Whether to show CTE (Common Table Expression) tables and their columns in output. 069 * When enabled, CTE tables are included in the tables list and CTE columns 070 * are included in the fields list with "(CTE)" suffix. 071 * Default is false for backward compatibility. 072 */ 073 private boolean showCTE = false; 074 075 // ========== DISPLAY NAME Configuration ========== 076 // Controls how identifier names are rendered for output 077 078 /** 079 * Display name mode - controls how identifiers are rendered. 080 * <ul> 081 * <li>DISPLAY: Strip delimiters, preserve original case (default, recommended for debugging)</li> 082 * <li>SQL_RENDER: Preserve delimiters for valid SQL regeneration</li> 083 * <li>CANONICAL: Apply vendor-specific case folding</li> 084 * </ul> 085 */ 086 private DisplayNameMode displayNameMode = DisplayNameMode.DISPLAY; 087 088 /** 089 * Display name policy - controls which occurrence to use when same object 090 * appears multiple times with different spellings. 091 * <ul> 092 * <li>PREFER_DEFINITION_SITE: Use spelling from definition (CTE def, CREATE TABLE, etc.)</li> 093 * <li>PREFER_FIRST_OCCURRENCE: Use first occurrence in SQL text</li> 094 * <li>PREFER_METADATA: Use spelling from database metadata</li> 095 * </ul> 096 */ 097 private DisplayNamePolicy displayNamePolicy = DisplayNamePolicy.PREFER_DEFINITION_SITE; 098 099 /** 100 * Whether to strip delimiters (quotes, backticks, brackets) from identifier display. 101 * Only applies when displayNameMode is DISPLAY. 102 * Default is true. 103 */ 104 private boolean stripDelimitersForDisplay = true; 105 106 // ========== GUESS_COLUMN_STRATEGY Configuration ========== 107 // Strategy for handling ambiguous columns (when a column could belong to multiple tables) 108 109 /** Pick the first candidate table (nearest in FROM clause order) */ 110 public static final int GUESS_COLUMN_STRATEGY_NEAREST = TBaseType.GUESS_COLUMN_STRATEGY_NEAREST; 111 112 /** Pick the last candidate table (farthest in FROM clause order) */ 113 public static final int GUESS_COLUMN_STRATEGY_FARTHEST = TBaseType.GUESS_COLUMN_STRATEGY_FARTHEST; 114 115 /** Do not pick any candidate, leave as unresolved/ambiguous */ 116 public static final int GUESS_COLUMN_STRATEGY_NOT_PICKUP = TBaseType.GUESS_COLUMN_STRATEGY_NOT_PICKUP; 117 118 /** Human-readable names for strategy values */ 119 public static final String[] GUESS_COLUMN_STRATEGY_NAMES = TBaseType.GUESS_COLUMN_STRATEGY_MSG; 120 121 /** 122 * Strategy for handling ambiguous columns. 123 * Default: reads from TBaseType.GUESS_COLUMN_STRATEGY for backward compatibility. 124 * Can be overridden per-config instance. 125 */ 126 private Integer guessColumnStrategy = null; // null means use TBaseType default 127 128 // ========== CONFIDENCE THRESHOLD Configuration ========== 129 // Controls when resolutions are considered "definite" vs "inferred" and when guessing is allowed 130 131 /** 132 * Minimum confidence threshold for a resolution to be considered "definite". 133 * 134 * <p>Resolutions with confidence >= this threshold are treated as having 135 * strong evidence (e.g., DDL metadata, qualified references). Resolutions 136 * below this threshold are considered "inferred" or "uncertain".</p> 137 * 138 * <p>Recommended values:</p> 139 * <ul> 140 * <li>0.9 (default): High bar - only high-confidence resolutions are definite</li> 141 * <li>0.7: Include more inferred resolutions as definite</li> 142 * <li>1.0: Only metadata-backed resolutions are definite</li> 143 * </ul> 144 */ 145 private double minDefiniteConfidence = 0.9; 146 147 /** 148 * Minimum confidence threshold to allow guessing from ambiguous candidates. 149 * 150 * <p>When multiple candidates exist and GUESS_COLUMN_STRATEGY is not NOT_PICKUP, 151 * at least one candidate must have confidence >= this threshold for guessing 152 * to be allowed. If all candidates are below this threshold, the column 153 * remains AMBIGUOUS regardless of strategy.</p> 154 * 155 * <p>Recommended values:</p> 156 * <ul> 157 * <li>0.95 (default): Very high bar - only guess with strong evidence</li> 158 * <li>0.9: Allow guessing with high-confidence candidates</li> 159 * <li>0.7: Allow guessing with inferred candidates (not recommended)</li> 160 * </ul> 161 */ 162 private double minConfidenceToGuess = 0.95; 163 164 /** 165 * Whether to allow guessing when all candidates are inferred (no DDL metadata). 166 * 167 * <p>When false (default), if all candidates have confidence below 168 * {@link #minDefiniteConfidence}, the column remains AMBIGUOUS even if 169 * GUESS_COLUMN_STRATEGY would normally pick one. This prevents guessing 170 * based on uncertain evidence.</p> 171 * 172 * <p>When true, guessing is allowed even when all candidates are inferred, 173 * as long as the GUESS_COLUMN_STRATEGY is not NOT_PICKUP. Use with caution 174 * as this may produce misleading lineage results.</p> 175 */ 176 private boolean allowGuessWhenAllInferred = false; 177 178 // ========== Binding Diagnostic Configuration (plan §5.3, S2) ========== 179 180 /** 181 * When {@code true}, the resolver2 binding-diagnostic post-pass runs once 182 * after iterative convergence and populates {@link 183 * gudusoft.gsqlparser.resolver2.binding.BindingResult}. Default {@code 184 * false} — accessors return the {@link 185 * gudusoft.gsqlparser.resolver2.binding.BindingResult#empty()} singleton 186 * with zero parse-time overhead. 187 */ 188 private boolean emitBindingDiagnostics = false; 189 190 /** 191 * When {@code true} (and {@link #emitBindingDiagnostics} is on), strict 192 * catalog mode emits {@code UNKNOWN_TABLE} / {@code UNKNOWN_ALIAS} / 193 * {@code CATALOG_METADATA_UNAVAILABLE}. Default {@code false} so that 194 * tables without authoritative metadata are silent (plan §5.5). 195 */ 196 private boolean bindingStrictCatalogValidation = false; 197 198 /** 199 * When {@code true}, successful {@link 200 * gudusoft.gsqlparser.resolver2.binding.BindingReference} entries are 201 * included in the result. Default {@code false} — IDE/lint consumers opt 202 * in; non-IDE consumers avoid the payload bloat. 203 */ 204 private boolean bindingIncludeSuccessfulReferences = false; 205 206 /** 207 * When {@code true}, the resolver records a 208 * {@link gudusoft.gsqlparser.resolver2.binding.BindingTrace} for every 209 * reference it binds (the dynamic-SQL publication proof's P3 input). 210 * Default {@code false}: no registry is created and the capture hooks are 211 * a single null check — zero overhead and zero behavior change on the 212 * normal path. Observation-only when enabled. 213 */ 214 private boolean captureBindingTrace = false; 215 216 /** 217 * Lazy map of per-code severity overrides. Allocated only when {@link 218 * #setBindingSeverityFor(BindingDiagnosticCode, BindingDiagnosticSeverity)} 219 * is first called so that the default-off path stays allocation-free. 220 */ 221 private EnumMap<BindingDiagnosticCode, BindingDiagnosticSeverity> bindingSeverityOverrides; 222 223 public TSQLResolverConfig() { 224 // Default configuration 225 } 226 227 public INameMatcher getNameMatcher() { 228 return nameMatcher; 229 } 230 231 public void setNameMatcher(INameMatcher nameMatcher) { 232 if (nameMatcher == null) { 233 throw new IllegalArgumentException("Name matcher cannot be null"); 234 } 235 this.nameMatcher = nameMatcher; 236 } 237 238 public boolean isLegacyCompatibilityEnabled() { 239 return legacyCompatibilityEnabled; 240 } 241 242 public void setLegacyCompatibilityEnabled(boolean enabled) { 243 this.legacyCompatibilityEnabled = enabled; 244 } 245 246 public double getLegacySyncMinConfidence() { 247 return legacySyncMinConfidence; 248 } 249 250 public void setLegacySyncMinConfidence(double threshold) { 251 if (threshold < 0.0 || threshold > 1.0) { 252 throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]"); 253 } 254 this.legacySyncMinConfidence = threshold; 255 } 256 257 public int getMaxIterations() { 258 return maxIterations; 259 } 260 261 public void setMaxIterations(int maxIterations) { 262 if (maxIterations < 1) { 263 throw new IllegalArgumentException("Max iterations must be at least 1"); 264 } 265 this.maxIterations = maxIterations; 266 } 267 268 public double getMinProgressRate() { 269 return minProgressRate; 270 } 271 272 public void setMinProgressRate(double minProgressRate) { 273 if (minProgressRate < 0.0 || minProgressRate > 1.0) { 274 throw new IllegalArgumentException("Progress rate must be in [0.0, 1.0]"); 275 } 276 this.minProgressRate = minProgressRate; 277 } 278 279 public int getStablePassesForConvergence() { 280 return stablePassesForConvergence; 281 } 282 283 public void setStablePassesForConvergence(int stablePasses) { 284 if (stablePasses < 1) { 285 throw new IllegalArgumentException("Stable passes must be at least 1"); 286 } 287 this.stablePassesForConvergence = stablePasses; 288 } 289 290 public boolean isCollectFullCandidates() { 291 return collectFullCandidates; 292 } 293 294 public void setCollectFullCandidates(boolean collectFullCandidates) { 295 this.collectFullCandidates = collectFullCandidates; 296 } 297 298 /** 299 * @deprecated Use NamespaceEnhancer instead 300 */ 301 public boolean isEvidenceCollectionEnabled() { 302 return evidenceCollectionEnabled; 303 } 304 305 /** 306 * @deprecated Use NamespaceEnhancer instead 307 */ 308 public void setEvidenceCollectionEnabled(boolean enabled) { 309 this.evidenceCollectionEnabled = enabled; 310 } 311 312 /** 313 * Check if datatype information should be shown for columns from CREATE TABLE statements. 314 * 315 * @return true if datatype information should be included in column names 316 */ 317 public boolean isShowDatatype() { 318 return showDatatype; 319 } 320 321 /** 322 * Set whether to show datatype information for columns from CREATE TABLE statements. 323 * When enabled, columns from CREATE TABLE will include datatype info in format: 324 * columnName:datatypeName:length or columnName:datatypeName:precision:scale 325 * 326 * @param showDatatype true to include datatype information 327 */ 328 public void setShowDatatype(boolean showDatatype) { 329 this.showDatatype = showDatatype; 330 } 331 332 /** 333 * Check if CTE (Common Table Expression) tables and columns should be shown in output. 334 * 335 * @return true if CTE tables and columns should be included 336 */ 337 public boolean isShowCTE() { 338 return showCTE; 339 } 340 341 /** 342 * Set whether to show CTE (Common Table Expression) tables and columns in output. 343 * When enabled, CTE tables are included in the tables list and CTE columns 344 * are included in the fields list with "(CTE)" suffix. 345 * 346 * @param showCTE true to include CTE tables and columns 347 */ 348 public void setShowCTE(boolean showCTE) { 349 this.showCTE = showCTE; 350 } 351 352 // ========== Display Name Getters and Setters ========== 353 354 /** 355 * Get the display name mode. 356 * 357 * @return the current display name mode 358 */ 359 public DisplayNameMode getDisplayNameMode() { 360 return displayNameMode; 361 } 362 363 /** 364 * Set the display name mode. 365 * 366 * @param mode the display name mode 367 */ 368 public void setDisplayNameMode(DisplayNameMode mode) { 369 this.displayNameMode = mode != null ? mode : DisplayNameMode.DISPLAY; 370 } 371 372 /** 373 * Get the display name policy. 374 * 375 * @return the current display name policy 376 */ 377 public DisplayNamePolicy getDisplayNamePolicy() { 378 return displayNamePolicy; 379 } 380 381 /** 382 * Set the display name policy. 383 * 384 * @param policy the display name policy 385 */ 386 public void setDisplayNamePolicy(DisplayNamePolicy policy) { 387 this.displayNamePolicy = policy != null ? policy : DisplayNamePolicy.PREFER_DEFINITION_SITE; 388 } 389 390 /** 391 * Check if delimiters should be stripped for display. 392 * 393 * @return true if delimiters should be stripped 394 */ 395 public boolean isStripDelimitersForDisplay() { 396 return stripDelimitersForDisplay; 397 } 398 399 /** 400 * Set whether to strip delimiters for display. 401 * 402 * @param stripDelimiters true to strip delimiters 403 */ 404 public void setStripDelimitersForDisplay(boolean stripDelimiters) { 405 this.stripDelimitersForDisplay = stripDelimiters; 406 } 407 408 /** 409 * Get the strategy for handling ambiguous columns. 410 * Returns the configured value, or TBaseType.GUESS_COLUMN_STRATEGY if not set. 411 * 412 * @return One of GUESS_COLUMN_STRATEGY_NEAREST, GUESS_COLUMN_STRATEGY_FARTHEST, 413 * or GUESS_COLUMN_STRATEGY_NOT_PICKUP 414 */ 415 public int getGuessColumnStrategy() { 416 return guessColumnStrategy != null ? guessColumnStrategy : TBaseType.GUESS_COLUMN_STRATEGY; 417 } 418 419 /** 420 * Set the strategy for handling ambiguous columns. 421 * 422 * @param strategy One of GUESS_COLUMN_STRATEGY_NEAREST, GUESS_COLUMN_STRATEGY_FARTHEST, 423 * or GUESS_COLUMN_STRATEGY_NOT_PICKUP 424 */ 425 public void setGuessColumnStrategy(int strategy) { 426 if (strategy < GUESS_COLUMN_STRATEGY_NEAREST || strategy > GUESS_COLUMN_STRATEGY_NOT_PICKUP) { 427 throw new IllegalArgumentException("Invalid strategy: " + strategy + 428 ". Must be GUESS_COLUMN_STRATEGY_NEAREST (0), GUESS_COLUMN_STRATEGY_FARTHEST (1), " + 429 "or GUESS_COLUMN_STRATEGY_NOT_PICKUP (2)"); 430 } 431 this.guessColumnStrategy = strategy; 432 } 433 434 /** 435 * Check if a custom guess column strategy has been set on this config. 436 * If false, the strategy from TBaseType.GUESS_COLUMN_STRATEGY will be used. 437 * 438 * @return true if a custom strategy is set 439 */ 440 public boolean hasCustomGuessColumnStrategy() { 441 return guessColumnStrategy != null; 442 } 443 444 /** 445 * Clear any custom guess column strategy, reverting to TBaseType.GUESS_COLUMN_STRATEGY. 446 */ 447 public void clearGuessColumnStrategy() { 448 this.guessColumnStrategy = null; 449 } 450 451 /** 452 * Get the human-readable name for the current strategy. 453 * 454 * @return Strategy name (e.g., "GUESS_COLUMN_STRATEGY_NEAREST") 455 */ 456 public String getGuessColumnStrategyName() { 457 int strategy = getGuessColumnStrategy(); 458 if (strategy >= 0 && strategy < GUESS_COLUMN_STRATEGY_NAMES.length) { 459 return GUESS_COLUMN_STRATEGY_NAMES[strategy]; 460 } 461 return "UNKNOWN(" + strategy + ")"; 462 } 463 464 // ========== Confidence Threshold Getters and Setters ========== 465 466 /** 467 * Get the minimum confidence threshold for definite resolutions. 468 * 469 * @return Threshold value [0.0, 1.0] 470 */ 471 public double getMinDefiniteConfidence() { 472 return minDefiniteConfidence; 473 } 474 475 /** 476 * Set the minimum confidence threshold for definite resolutions. 477 * 478 * @param threshold Threshold value [0.0, 1.0] 479 */ 480 public void setMinDefiniteConfidence(double threshold) { 481 if (threshold < 0.0 || threshold > 1.0) { 482 throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]"); 483 } 484 this.minDefiniteConfidence = threshold; 485 } 486 487 /** 488 * Get the minimum confidence threshold to allow guessing. 489 * 490 * @return Threshold value [0.0, 1.0] 491 */ 492 public double getMinConfidenceToGuess() { 493 return minConfidenceToGuess; 494 } 495 496 /** 497 * Set the minimum confidence threshold to allow guessing. 498 * 499 * @param threshold Threshold value [0.0, 1.0] 500 */ 501 public void setMinConfidenceToGuess(double threshold) { 502 if (threshold < 0.0 || threshold > 1.0) { 503 throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]"); 504 } 505 this.minConfidenceToGuess = threshold; 506 } 507 508 /** 509 * Check if guessing is allowed when all candidates are inferred. 510 * 511 * @return true if guessing is allowed with inferred candidates 512 */ 513 public boolean isAllowGuessWhenAllInferred() { 514 return allowGuessWhenAllInferred; 515 } 516 517 /** 518 * Set whether to allow guessing when all candidates are inferred. 519 * 520 * @param allow true to allow guessing with inferred candidates 521 */ 522 public void setAllowGuessWhenAllInferred(boolean allow) { 523 this.allowGuessWhenAllInferred = allow; 524 } 525 526 /** 527 * Check if a confidence value represents a definite resolution. 528 * 529 * @param confidence The confidence value to check 530 * @return true if the confidence is >= minDefiniteConfidence 531 */ 532 public boolean isDefiniteConfidence(double confidence) { 533 return confidence >= minDefiniteConfidence; 534 } 535 536 /** 537 * Check if a confidence value is sufficient to allow guessing. 538 * 539 * @param confidence The confidence value to check 540 * @return true if the confidence is >= minConfidenceToGuess 541 */ 542 public boolean canGuessWithConfidence(double confidence) { 543 return confidence >= minConfidenceToGuess; 544 } 545 546 // ========== Binding Diagnostic Getters and Setters (plan §5.3, S2) ========== 547 548 /** 549 * @return whether the binding-diagnostic post-pass is enabled 550 */ 551 public boolean isEmitBindingDiagnostics() { 552 return emitBindingDiagnostics; 553 } 554 555 /** 556 * Enable or disable the binding-diagnostic post-pass. 557 * 558 * <p>Default {@code false}. When off, the post-pass is not invoked and 559 * accessors return {@link 560 * gudusoft.gsqlparser.resolver2.binding.BindingResult#empty()}.</p> 561 * 562 * @param on whether to emit binding diagnostics 563 * @return this config (fluent) 564 */ 565 public TSQLResolverConfig setEmitBindingDiagnostics(boolean on) { 566 this.emitBindingDiagnostics = on; 567 return this; 568 } 569 570 /** 571 * @return whether strict catalog validation is enabled 572 */ 573 public boolean isBindingStrictCatalogValidation() { 574 return bindingStrictCatalogValidation; 575 } 576 577 /** 578 * Enable or disable strict catalog validation. Default {@code false}. 579 * 580 * @param on whether to enable strict mode 581 * @return this config (fluent) 582 */ 583 public TSQLResolverConfig setBindingStrictCatalogValidation(boolean on) { 584 this.bindingStrictCatalogValidation = on; 585 return this; 586 } 587 588 /** 589 * @return whether successful binding references are included in the 590 * result payload 591 */ 592 public boolean isBindingIncludeSuccessfulReferences() { 593 return bindingIncludeSuccessfulReferences; 594 } 595 596 /** 597 * Enable or disable successful-reference emission. Default {@code false} 598 * (payload bloat for non-IDE consumers). 599 * 600 * @param on whether to include successful references 601 * @return this config (fluent) 602 */ 603 public TSQLResolverConfig setBindingIncludeSuccessfulReferences(boolean on) { 604 this.bindingIncludeSuccessfulReferences = on; 605 return this; 606 } 607 608 /** @see #captureBindingTrace */ 609 public boolean isCaptureBindingTrace() { 610 return captureBindingTrace; 611 } 612 613 /** @see #captureBindingTrace */ 614 public TSQLResolverConfig setCaptureBindingTrace(boolean on) { 615 this.captureBindingTrace = on; 616 return this; 617 } 618 619 /** 620 * Override the severity emitted for a given binding diagnostic code. 621 * 622 * <p>The override map is allocated lazily so callers that never customize 623 * severities pay no allocation cost.</p> 624 * 625 * @param code the diagnostic code (must not be null) 626 * @param severity the severity to emit for this code (must not be null) 627 * @return this config (fluent) 628 * @throws IllegalArgumentException if either argument is null 629 */ 630 public TSQLResolverConfig setBindingSeverityFor(BindingDiagnosticCode code, 631 BindingDiagnosticSeverity severity) { 632 if (code == null) { 633 throw new IllegalArgumentException("BindingDiagnosticCode is required"); 634 } 635 if (severity == null) { 636 throw new IllegalArgumentException("BindingDiagnosticSeverity is required"); 637 } 638 if (bindingSeverityOverrides == null) { 639 bindingSeverityOverrides = new EnumMap<BindingDiagnosticCode, BindingDiagnosticSeverity>( 640 BindingDiagnosticCode.class); 641 } 642 bindingSeverityOverrides.put(code, severity); 643 return this; 644 } 645 646 /** 647 * Resolve the configured severity for a diagnostic code. Returns the 648 * override when one is set, otherwise the code's frozen default 649 * severity (plan §5.4). 650 * 651 * @param code the diagnostic code (must not be null) 652 * @return the configured or default severity for this code 653 */ 654 public BindingDiagnosticSeverity getBindingSeverityFor(BindingDiagnosticCode code) { 655 if (code == null) { 656 throw new IllegalArgumentException("BindingDiagnosticCode is required"); 657 } 658 if (bindingSeverityOverrides != null) { 659 BindingDiagnosticSeverity override = bindingSeverityOverrides.get(code); 660 if (override != null) { 661 return override; 662 } 663 } 664 return code.defaultSeverity(); 665 } 666 667 /** 668 * @return whether any per-code severity override has been configured. 669 * Used to keep the default-off path allocation-free in tests. 670 */ 671 public boolean hasBindingSeverityOverrides() { 672 return bindingSeverityOverrides != null && !bindingSeverityOverrides.isEmpty(); 673 } 674 675 /** 676 * @return an immutable view of the configured severity overrides, or an 677 * empty map when none are configured. Never null. 678 */ 679 public Map<BindingDiagnosticCode, BindingDiagnosticSeverity> getBindingSeverityOverrides() { 680 if (bindingSeverityOverrides == null || bindingSeverityOverrides.isEmpty()) { 681 return java.util.Collections.<BindingDiagnosticCode, BindingDiagnosticSeverity>emptyMap(); 682 } 683 return java.util.Collections.unmodifiableMap(bindingSeverityOverrides); 684 } 685 686 /** 687 * Create a default configuration 688 */ 689 public static TSQLResolverConfig createDefault() { 690 return new TSQLResolverConfig(); 691 } 692 693 /** 694 * Create configuration for case-sensitive matching 695 */ 696 public static TSQLResolverConfig createCaseSensitive() { 697 TSQLResolverConfig config = new TSQLResolverConfig(); 698 config.setNameMatcher(new DefaultNameMatcher(true)); 699 return config; 700 } 701 702 /** 703 * Create configuration for standalone mode (no legacy sync) 704 */ 705 public static TSQLResolverConfig createStandalone() { 706 TSQLResolverConfig config = new TSQLResolverConfig(); 707 config.setLegacyCompatibilityEnabled(false); 708 return config; 709 } 710 711 /** 712 * Create configuration with showDatatype enabled. 713 * This configuration includes datatype information for columns from CREATE TABLE statements. 714 */ 715 public static TSQLResolverConfig createWithDatatype() { 716 TSQLResolverConfig config = new TSQLResolverConfig(); 717 config.setShowDatatype(true); 718 return config; 719 } 720 721 /** 722 * Create configuration with showCTE enabled. 723 * This configuration includes CTE tables and columns in the output. 724 */ 725 public static TSQLResolverConfig createWithCTE() { 726 TSQLResolverConfig config = new TSQLResolverConfig(); 727 config.setShowCTE(true); 728 return config; 729 } 730 731 /** 732 * Create configuration for a specific database vendor. 733 * 734 * <p>This factory method creates a configuration with vendor-specific 735 * name matching rules. The VendorNameMatcher uses IdentifierService 736 * to properly handle case sensitivity and quote handling for each vendor.</p> 737 * 738 * <p>Example vendor behaviors:</p> 739 * <ul> 740 * <li>Oracle: Unquoted identifiers fold to UPPER, quoted are case-sensitive</li> 741 * <li>PostgreSQL: Unquoted identifiers fold to LOWER, quoted are case-sensitive</li> 742 * <li>MySQL: Depends on lower_case_table_names setting</li> 743 * <li>BigQuery: Table names are case-sensitive, column names are case-insensitive</li> 744 * </ul> 745 * 746 * @param vendor the database vendor 747 * @return configuration with vendor-specific name matcher 748 */ 749 public static TSQLResolverConfig createForVendor(EDbVendor vendor) { 750 TSQLResolverConfig config = new TSQLResolverConfig(); 751 config.vendor = vendor; 752 config.nameMatcher = new VendorNameMatcher(vendor); 753 return config; 754 } 755 756 /** 757 * Create configuration for a specific database vendor with datatype display enabled. 758 * 759 * @param vendor the database vendor 760 * @return configuration with vendor-specific name matcher and datatype display 761 */ 762 public static TSQLResolverConfig createForVendorWithDatatype(EDbVendor vendor) { 763 TSQLResolverConfig config = createForVendor(vendor); 764 config.setShowDatatype(true); 765 return config; 766 } 767 768 /** 769 * Get the database vendor, if set. 770 * 771 * @return the database vendor, or null if not set 772 */ 773 public EDbVendor getVendor() { 774 return vendor; 775 } 776 777 /** 778 * Set the database vendor and update name matcher accordingly. 779 * 780 * @param vendor the database vendor 781 */ 782 public void setVendor(EDbVendor vendor) { 783 this.vendor = vendor; 784 if (vendor != null) { 785 this.nameMatcher = new VendorNameMatcher(vendor); 786 } 787 } 788 789 /** 790 * Check if vendor-specific name matching is enabled. 791 * 792 * @return true if a vendor is configured 793 */ 794 public boolean hasVendor() { 795 return vendor != null; 796 } 797 798 @Override 799 public String toString() { 800 return String.format( 801 "TSQLResolverConfig{vendor=%s, nameMatcher=%s, legacyCompat=%s, minConfidence=%.2f, maxIter=%d, guessStrategy=%s, " + 802 "minDefiniteConf=%.2f, minGuessConf=%.2f, allowGuessInferred=%s, showDatatype=%s, showCTE=%s, displayMode=%s, displayPolicy=%s}", 803 vendor, 804 nameMatcher, 805 legacyCompatibilityEnabled, 806 legacySyncMinConfidence, 807 maxIterations, 808 getGuessColumnStrategyName(), 809 minDefiniteConfidence, 810 minConfidenceToGuess, 811 allowGuessWhenAllInferred, 812 showDatatype, 813 showCTE, 814 displayNameMode, 815 displayNamePolicy 816 ); 817 } 818}